@playcademy/sandbox 0.3.6 → 0.3.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 +361 -187
- package/dist/mocks/timeback.d.ts +5 -0
- package/dist/server.js +361 -187
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -997,7 +997,7 @@ ${t}`), i3 = h(this, y2).getSize();
|
|
|
997
997
|
|
|
998
998
|
// ../../node_modules/esbuild/lib/main.js
|
|
999
999
|
var require_main = __commonJS((exports, module2) => {
|
|
1000
|
-
var __dirname = "/Users/hbauer/work/
|
|
1000
|
+
var __dirname = "/Users/hbauer/work/clones/playcademy-test/node_modules/esbuild/lib", __filename = "/Users/hbauer/work/clones/playcademy-test/node_modules/esbuild/lib/main.js";
|
|
1001
1001
|
var __defProp2 = Object.defineProperty;
|
|
1002
1002
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
1003
1003
|
var __getOwnPropNames2 = Object.getOwnPropertyNames;
|
|
@@ -86665,9 +86665,9 @@ var require_serde = __commonJS((exports, module2) => {
|
|
|
86665
86665
|
strictParseShort: () => strictParseShort2
|
|
86666
86666
|
});
|
|
86667
86667
|
module2.exports = __toCommonJS(serde_exports);
|
|
86668
|
-
var
|
|
86668
|
+
var import_schema3 = require_schema();
|
|
86669
86669
|
var copyDocumentWithTransform2 = (source, schemaRef, transform = (_5) => _5) => {
|
|
86670
|
-
const ns =
|
|
86670
|
+
const ns = import_schema3.NormalizedSchema.of(schemaRef);
|
|
86671
86671
|
switch (typeof source) {
|
|
86672
86672
|
case "undefined":
|
|
86673
86673
|
case "boolean":
|
|
@@ -87280,7 +87280,7 @@ var require_protocols = __commonJS((exports, module2) => {
|
|
|
87280
87280
|
return "%" + c3.charCodeAt(0).toString(16).toUpperCase();
|
|
87281
87281
|
});
|
|
87282
87282
|
}
|
|
87283
|
-
var
|
|
87283
|
+
var import_schema22 = require_schema();
|
|
87284
87284
|
var import_protocol_http2 = require_dist_cjs2();
|
|
87285
87285
|
var import_schema3 = require_schema();
|
|
87286
87286
|
var import_serde = require_serde();
|
|
@@ -87446,7 +87446,7 @@ var require_protocols = __commonJS((exports, module2) => {
|
|
|
87446
87446
|
const query = {};
|
|
87447
87447
|
const headers = {};
|
|
87448
87448
|
const endpoint = await context.endpoint();
|
|
87449
|
-
const ns =
|
|
87449
|
+
const ns = import_schema22.NormalizedSchema.of(operationSchema?.input);
|
|
87450
87450
|
const schema4 = ns.getSchema();
|
|
87451
87451
|
let hasNonHttpBindingMember = false;
|
|
87452
87452
|
let payload;
|
|
@@ -87463,7 +87463,7 @@ var require_protocols = __commonJS((exports, module2) => {
|
|
|
87463
87463
|
if (endpoint) {
|
|
87464
87464
|
this.updateServiceEndpoint(request3, endpoint);
|
|
87465
87465
|
this.setHostPrefix(request3, operationSchema, input);
|
|
87466
|
-
const opTraits =
|
|
87466
|
+
const opTraits = import_schema22.NormalizedSchema.translateTraits(operationSchema.traits);
|
|
87467
87467
|
if (opTraits.http) {
|
|
87468
87468
|
request3.method = opTraits.http[0];
|
|
87469
87469
|
const [path3, search] = opTraits.http[1].split("?");
|
|
@@ -87541,7 +87541,7 @@ var require_protocols = __commonJS((exports, module2) => {
|
|
|
87541
87541
|
if (traits.httpQueryParams) {
|
|
87542
87542
|
for (const [key, val2] of Object.entries(data)) {
|
|
87543
87543
|
if (!(key in query)) {
|
|
87544
|
-
this.serializeQuery(
|
|
87544
|
+
this.serializeQuery(import_schema22.NormalizedSchema.of([
|
|
87545
87545
|
ns.getValueSchema(),
|
|
87546
87546
|
{
|
|
87547
87547
|
...traits,
|
|
@@ -87571,12 +87571,12 @@ var require_protocols = __commonJS((exports, module2) => {
|
|
|
87571
87571
|
}
|
|
87572
87572
|
async deserializeResponse(operationSchema, context, response) {
|
|
87573
87573
|
const deserializer = this.deserializer;
|
|
87574
|
-
const ns =
|
|
87574
|
+
const ns = import_schema22.NormalizedSchema.of(operationSchema.output);
|
|
87575
87575
|
const dataObject = {};
|
|
87576
87576
|
if (response.statusCode >= 300) {
|
|
87577
87577
|
const bytes = await collectBody2(response.body, context);
|
|
87578
87578
|
if (bytes.byteLength > 0) {
|
|
87579
|
-
Object.assign(dataObject, await deserializer.read(
|
|
87579
|
+
Object.assign(dataObject, await deserializer.read(import_schema22.SCHEMA.DOCUMENT, bytes));
|
|
87580
87580
|
}
|
|
87581
87581
|
await this.handleError(operationSchema, context, response, dataObject, this.deserializeMetadata(response));
|
|
87582
87582
|
throw new Error("@smithy/core/protocols - HTTP Protocol error handler failed to throw.");
|
|
@@ -92261,7 +92261,7 @@ var require_protocols2 = __commonJS((exports, module2) => {
|
|
|
92261
92261
|
this.serdeContext = serdeContext;
|
|
92262
92262
|
}
|
|
92263
92263
|
};
|
|
92264
|
-
var
|
|
92264
|
+
var import_schema4 = require_schema();
|
|
92265
92265
|
var import_serde2 = require_serde();
|
|
92266
92266
|
var import_util_base64 = require_dist_cjs9();
|
|
92267
92267
|
var import_serde = require_serde();
|
|
@@ -92352,7 +92352,7 @@ var require_protocols2 = __commonJS((exports, module2) => {
|
|
|
92352
92352
|
}
|
|
92353
92353
|
_read(schema4, value) {
|
|
92354
92354
|
const isObject4 = value !== null && typeof value === "object";
|
|
92355
|
-
const ns =
|
|
92355
|
+
const ns = import_schema4.NormalizedSchema.of(schema4);
|
|
92356
92356
|
if (ns.isListSchema() && Array.isArray(value)) {
|
|
92357
92357
|
const listMember = ns.getValueSchema();
|
|
92358
92358
|
const out2 = [];
|
|
@@ -92396,13 +92396,13 @@ var require_protocols2 = __commonJS((exports, module2) => {
|
|
|
92396
92396
|
}
|
|
92397
92397
|
if (ns.isTimestampSchema()) {
|
|
92398
92398
|
const options = this.settings.timestampFormat;
|
|
92399
|
-
const format = options.useTrait ? ns.getSchema() ===
|
|
92399
|
+
const format = options.useTrait ? ns.getSchema() === import_schema4.SCHEMA.TIMESTAMP_DEFAULT ? options.default : ns.getSchema() ?? options.default : options.default;
|
|
92400
92400
|
switch (format) {
|
|
92401
|
-
case
|
|
92401
|
+
case import_schema4.SCHEMA.TIMESTAMP_DATE_TIME:
|
|
92402
92402
|
return (0, import_serde2.parseRfc3339DateTimeWithOffset)(value);
|
|
92403
|
-
case
|
|
92403
|
+
case import_schema4.SCHEMA.TIMESTAMP_HTTP_DATE:
|
|
92404
92404
|
return (0, import_serde2.parseRfc7231DateTime)(value);
|
|
92405
|
-
case
|
|
92405
|
+
case import_schema4.SCHEMA.TIMESTAMP_EPOCH_SECONDS:
|
|
92406
92406
|
return (0, import_serde2.parseEpochTimestamp)(value);
|
|
92407
92407
|
default:
|
|
92408
92408
|
console.warn("Missing timestamp format, parsing value with Date constructor:", value);
|
|
@@ -92711,7 +92711,7 @@ var require_protocols2 = __commonJS((exports, module2) => {
|
|
|
92711
92711
|
}
|
|
92712
92712
|
};
|
|
92713
92713
|
var import_protocols2 = require_protocols();
|
|
92714
|
-
var
|
|
92714
|
+
var import_schema42 = require_schema();
|
|
92715
92715
|
var import_util_body_length_browser2 = require_dist_cjs21();
|
|
92716
92716
|
var AwsRestJsonProtocol = class extends import_protocols2.HttpBindingProtocol {
|
|
92717
92717
|
static {
|
|
@@ -92727,7 +92727,7 @@ var require_protocols2 = __commonJS((exports, module2) => {
|
|
|
92727
92727
|
const settings = {
|
|
92728
92728
|
timestampFormat: {
|
|
92729
92729
|
useTrait: true,
|
|
92730
|
-
default:
|
|
92730
|
+
default: import_schema42.SCHEMA.TIMESTAMP_EPOCH_SECONDS
|
|
92731
92731
|
},
|
|
92732
92732
|
httpBindings: true,
|
|
92733
92733
|
jsonName: true
|
|
@@ -92748,7 +92748,7 @@ var require_protocols2 = __commonJS((exports, module2) => {
|
|
|
92748
92748
|
}
|
|
92749
92749
|
async serializeRequest(operationSchema, input, context) {
|
|
92750
92750
|
const request3 = await super.serializeRequest(operationSchema, input, context);
|
|
92751
|
-
const inputSchema =
|
|
92751
|
+
const inputSchema = import_schema42.NormalizedSchema.of(operationSchema.input);
|
|
92752
92752
|
const members = inputSchema.getMemberSchemas();
|
|
92753
92753
|
if (!request3.headers["content-type"]) {
|
|
92754
92754
|
const httpPayloadMember = Object.values(members).find((m5) => {
|
|
@@ -92792,19 +92792,19 @@ var require_protocols2 = __commonJS((exports, module2) => {
|
|
|
92792
92792
|
if (errorIdentifier.includes("#")) {
|
|
92793
92793
|
[namespace, errorName] = errorIdentifier.split("#");
|
|
92794
92794
|
}
|
|
92795
|
-
const registry2 =
|
|
92795
|
+
const registry2 = import_schema42.TypeRegistry.for(namespace);
|
|
92796
92796
|
let errorSchema;
|
|
92797
92797
|
try {
|
|
92798
92798
|
errorSchema = registry2.getSchema(errorIdentifier);
|
|
92799
92799
|
} catch (e2) {
|
|
92800
|
-
const baseExceptionSchema =
|
|
92800
|
+
const baseExceptionSchema = import_schema42.TypeRegistry.for("smithy.ts.sdk.synthetic." + namespace).getBaseException();
|
|
92801
92801
|
if (baseExceptionSchema) {
|
|
92802
92802
|
const ErrorCtor = baseExceptionSchema.ctor;
|
|
92803
92803
|
throw Object.assign(new ErrorCtor(errorName), dataObject);
|
|
92804
92804
|
}
|
|
92805
92805
|
throw new Error(errorName);
|
|
92806
92806
|
}
|
|
92807
|
-
const ns =
|
|
92807
|
+
const ns = import_schema42.NormalizedSchema.of(errorSchema);
|
|
92808
92808
|
const message2 = dataObject.message ?? dataObject.Message ?? "Unknown";
|
|
92809
92809
|
const exception = new errorSchema.ctor(message2);
|
|
92810
92810
|
await this.deserializeHttpMessage(errorSchema, context, response, dataObject);
|
|
@@ -121481,7 +121481,7 @@ async function requirePortAvailable(port, timeoutMs = 100) {
|
|
|
121481
121481
|
// package.json
|
|
121482
121482
|
var package_default = {
|
|
121483
121483
|
name: "@playcademy/sandbox",
|
|
121484
|
-
version: "0.3.
|
|
121484
|
+
version: "0.3.7",
|
|
121485
121485
|
description: "Local development server for Playcademy game development",
|
|
121486
121486
|
type: "module",
|
|
121487
121487
|
exports: {
|
|
@@ -136260,6 +136260,21 @@ async function mintGameJwt(gameId, userId) {
|
|
|
136260
136260
|
const token = await signJwt({ uid: userId }, "game", expirationTimeSeconds, gameId);
|
|
136261
136261
|
return { token, exp: expirationTimestampMillis };
|
|
136262
136262
|
}
|
|
136263
|
+
async function mintRealtimeJwt(userId, gameId, username, role) {
|
|
136264
|
+
const payload = {
|
|
136265
|
+
sub: userId
|
|
136266
|
+
};
|
|
136267
|
+
if (gameId) {
|
|
136268
|
+
payload.gameId = gameId;
|
|
136269
|
+
}
|
|
136270
|
+
if (username) {
|
|
136271
|
+
payload.username = username;
|
|
136272
|
+
}
|
|
136273
|
+
if (role) {
|
|
136274
|
+
payload.role = role;
|
|
136275
|
+
}
|
|
136276
|
+
return await signJwt(payload, "realtime", "30m");
|
|
136277
|
+
}
|
|
136263
136278
|
// ../api-core/src/utils/validation.ts
|
|
136264
136279
|
function formatValidationErrors(error2) {
|
|
136265
136280
|
const flattened = error2.flatten();
|
|
@@ -145364,6 +145379,17 @@ function createCustomHostnamesNamespace(config2) {
|
|
|
145364
145379
|
}
|
|
145365
145380
|
};
|
|
145366
145381
|
}
|
|
145382
|
+
// ../cloudflare/src/utils/schema.ts
|
|
145383
|
+
function buildSchemaSql(sql3) {
|
|
145384
|
+
const trimmed = sql3.trim();
|
|
145385
|
+
if (!trimmed) {
|
|
145386
|
+
return "";
|
|
145387
|
+
}
|
|
145388
|
+
return `PRAGMA defer_foreign_keys = on;
|
|
145389
|
+
${trimmed}
|
|
145390
|
+
PRAGMA defer_foreign_keys = off;`;
|
|
145391
|
+
}
|
|
145392
|
+
|
|
145367
145393
|
// ../cloudflare/src/core/namespaces/d1.ts
|
|
145368
145394
|
function createD1Namespace(config2) {
|
|
145369
145395
|
const { client, accountId } = config2;
|
|
@@ -145398,26 +145424,22 @@ function createD1Namespace(config2) {
|
|
|
145398
145424
|
async executeSchema(databaseId, schema4) {
|
|
145399
145425
|
log2.debug("[Cloudflare D1] Executing schema", {
|
|
145400
145426
|
databaseId,
|
|
145401
|
-
schemaHash: schema4.hash
|
|
145427
|
+
schemaHash: schema4.hash,
|
|
145428
|
+
sqlLength: schema4.sql.length
|
|
145402
145429
|
});
|
|
145403
145430
|
try {
|
|
145404
|
-
const
|
|
145405
|
-
|
|
145406
|
-
|
|
145407
|
-
|
|
145408
|
-
|
|
145409
|
-
|
|
145410
|
-
|
|
145411
|
-
|
|
145412
|
-
|
|
145413
|
-
sql: statement
|
|
145414
|
-
});
|
|
145415
|
-
}
|
|
145416
|
-
}
|
|
145431
|
+
const sql3 = buildSchemaSql(schema4.sql);
|
|
145432
|
+
log2.debug("[Cloudflare D1] Executing schema", {
|
|
145433
|
+
databaseId,
|
|
145434
|
+
sql: sql3
|
|
145435
|
+
});
|
|
145436
|
+
await client.d1.database.query(databaseId, {
|
|
145437
|
+
account_id: accountId,
|
|
145438
|
+
sql: sql3
|
|
145439
|
+
});
|
|
145417
145440
|
log2.info("[Cloudflare D1] Schema executed", {
|
|
145418
145441
|
databaseId,
|
|
145419
|
-
schemaHash: schema4.hash
|
|
145420
|
-
statementsExecuted: statements.length
|
|
145442
|
+
schemaHash: schema4.hash
|
|
145421
145443
|
});
|
|
145422
145444
|
} catch (error2) {
|
|
145423
145445
|
log2.error("[Cloudflare D1] Failed to execute schema", {
|
|
@@ -145447,52 +145469,31 @@ function createD1Namespace(config2) {
|
|
|
145447
145469
|
},
|
|
145448
145470
|
async reset(name4) {
|
|
145449
145471
|
log2.debug("[Cloudflare D1] Resetting database", { name: name4 });
|
|
145450
|
-
|
|
145451
|
-
|
|
145452
|
-
|
|
145453
|
-
|
|
145454
|
-
|
|
145455
|
-
|
|
145472
|
+
try {
|
|
145473
|
+
const databases = await client.d1.database.list({ account_id: accountId });
|
|
145474
|
+
for await (const db of databases) {
|
|
145475
|
+
if (db.name === name4 && db.uuid) {
|
|
145476
|
+
log2.debug("[Cloudflare D1] Deleting existing database", {
|
|
145477
|
+
name: name4,
|
|
145478
|
+
uuid: db.uuid
|
|
145479
|
+
});
|
|
145480
|
+
await client.d1.database.delete(db.uuid, { account_id: accountId });
|
|
145481
|
+
log2.info("[Cloudflare D1] Deleted existing database", { name: name4 });
|
|
145482
|
+
break;
|
|
145483
|
+
}
|
|
145456
145484
|
}
|
|
145485
|
+
} catch (error2) {
|
|
145486
|
+
log2.warn("[Cloudflare D1] Failed to delete existing database", { name: name4, error: error2 });
|
|
145457
145487
|
}
|
|
145458
|
-
|
|
145459
|
-
throw new Error(`D1 database not found: ${name4}`);
|
|
145460
|
-
}
|
|
145461
|
-
const tablesResult = await client.d1.database.query(databaseId, {
|
|
145488
|
+
const result = await client.d1.database.create({
|
|
145462
145489
|
account_id: accountId,
|
|
145463
|
-
|
|
145490
|
+
name: name4
|
|
145464
145491
|
});
|
|
145465
|
-
|
|
145466
|
-
|
|
145467
|
-
log2.info("[Cloudflare D1] No tables to drop", { name: name4, databaseId });
|
|
145468
|
-
return;
|
|
145492
|
+
if (!result.uuid) {
|
|
145493
|
+
throw new Error("Database creation succeeded but no UUID returned");
|
|
145469
145494
|
}
|
|
145470
|
-
log2.
|
|
145471
|
-
|
|
145472
|
-
count: tables.length,
|
|
145473
|
-
tables: tables.map((t2) => t2.name)
|
|
145474
|
-
});
|
|
145475
|
-
await client.d1.database.query(databaseId, {
|
|
145476
|
-
account_id: accountId,
|
|
145477
|
-
sql: "PRAGMA defer_foreign_keys = on"
|
|
145478
|
-
});
|
|
145479
|
-
for (const table14 of tables) {
|
|
145480
|
-
if (!table14.name)
|
|
145481
|
-
continue;
|
|
145482
|
-
await client.d1.database.query(databaseId, {
|
|
145483
|
-
account_id: accountId,
|
|
145484
|
-
sql: `DROP TABLE IF EXISTS "${table14.name}"`
|
|
145485
|
-
});
|
|
145486
|
-
}
|
|
145487
|
-
await client.d1.database.query(databaseId, {
|
|
145488
|
-
account_id: accountId,
|
|
145489
|
-
sql: "PRAGMA defer_foreign_keys = off"
|
|
145490
|
-
});
|
|
145491
|
-
log2.info("[Cloudflare D1] Database reset complete", {
|
|
145492
|
-
name: name4,
|
|
145493
|
-
databaseId,
|
|
145494
|
-
tablesDropped: tables.length
|
|
145495
|
-
});
|
|
145495
|
+
log2.info("[Cloudflare D1] Database reset complete", { name: name4, uuid: result.uuid });
|
|
145496
|
+
return result.uuid;
|
|
145496
145497
|
}
|
|
145497
145498
|
};
|
|
145498
145499
|
}
|
|
@@ -146698,6 +146699,56 @@ async function verifyGameAccessBySlug(slug2, user) {
|
|
|
146698
146699
|
function getGameWorkerApiKeyName(slug2) {
|
|
146699
146700
|
return `game-worker-${slug2}`.substring(0, 32);
|
|
146700
146701
|
}
|
|
146702
|
+
// ../api-core/src/utils/secrets-storage.ts
|
|
146703
|
+
var import_client_s32 = __toESM(require_dist_cjs81(), 1);
|
|
146704
|
+
function getSecretsConfig() {
|
|
146705
|
+
const bucketName = process.env.GAME_SECRETS_BUCKET;
|
|
146706
|
+
const masterKey = process.env.GAME_SECRETS_MASTER_KEY;
|
|
146707
|
+
if (!bucketName || !masterKey) {
|
|
146708
|
+
throw new Error("Secrets storage not configured");
|
|
146709
|
+
}
|
|
146710
|
+
const accessKeyId = process.env.CLOUDFLARE_R2_ACCESS_KEY_ID;
|
|
146711
|
+
const secretAccessKey = process.env.CLOUDFLARE_R2_SECRET_ACCESS_KEY;
|
|
146712
|
+
const endpoint = process.env.CLOUDFLARE_R2_DEFAULT_ENDPOINT;
|
|
146713
|
+
if (!accessKeyId || !secretAccessKey || !endpoint) {
|
|
146714
|
+
throw new Error("R2 credentials not configured");
|
|
146715
|
+
}
|
|
146716
|
+
return {
|
|
146717
|
+
bucketName,
|
|
146718
|
+
credentials: {
|
|
146719
|
+
accessKeyId,
|
|
146720
|
+
secretAccessKey,
|
|
146721
|
+
endpoint
|
|
146722
|
+
},
|
|
146723
|
+
masterKey
|
|
146724
|
+
};
|
|
146725
|
+
}
|
|
146726
|
+
function getSecretsKey(gameId) {
|
|
146727
|
+
return `${gameId}.json.enc`;
|
|
146728
|
+
}
|
|
146729
|
+
function createR2Client(config2) {
|
|
146730
|
+
return new import_client_s32.S3Client({
|
|
146731
|
+
region: "auto",
|
|
146732
|
+
endpoint: config2.credentials.endpoint,
|
|
146733
|
+
credentials: {
|
|
146734
|
+
accessKeyId: config2.credentials.accessKeyId,
|
|
146735
|
+
secretAccessKey: config2.credentials.secretAccessKey
|
|
146736
|
+
}
|
|
146737
|
+
});
|
|
146738
|
+
}
|
|
146739
|
+
async function deleteSecrets(gameId, config2) {
|
|
146740
|
+
const client = createR2Client(config2);
|
|
146741
|
+
const key = getSecretsKey(gameId);
|
|
146742
|
+
try {
|
|
146743
|
+
await client.send(new import_client_s32.DeleteObjectCommand({
|
|
146744
|
+
Bucket: config2.bucketName,
|
|
146745
|
+
Key: key
|
|
146746
|
+
}));
|
|
146747
|
+
} catch (error2) {
|
|
146748
|
+
log2.error("[SecretsStorage] Failed to delete secrets", { gameId, error: error2 });
|
|
146749
|
+
throw new Error(`Failed to delete secrets for game ${gameId}`);
|
|
146750
|
+
}
|
|
146751
|
+
}
|
|
146701
146752
|
// src/database/seed/achievements.ts
|
|
146702
146753
|
async function seedAchievements(db) {
|
|
146703
146754
|
const now2 = new Date;
|
|
@@ -146741,6 +146792,30 @@ async function seedCurrencies(db) {
|
|
|
146741
146792
|
}
|
|
146742
146793
|
}
|
|
146743
146794
|
|
|
146795
|
+
// src/lib/logging/adapter.ts
|
|
146796
|
+
var customLogger;
|
|
146797
|
+
function setLogger(logger3) {
|
|
146798
|
+
customLogger = logger3;
|
|
146799
|
+
}
|
|
146800
|
+
function getLogger() {
|
|
146801
|
+
if (customLogger) {
|
|
146802
|
+
return customLogger;
|
|
146803
|
+
}
|
|
146804
|
+
return {
|
|
146805
|
+
info: (msg) => console.log(msg),
|
|
146806
|
+
warn: (msg) => console.warn(msg),
|
|
146807
|
+
error: (msg) => console.error(msg)
|
|
146808
|
+
};
|
|
146809
|
+
}
|
|
146810
|
+
var logger3 = {
|
|
146811
|
+
info: (msg) => {
|
|
146812
|
+
if (customLogger || !config.embedded) {
|
|
146813
|
+
getLogger().info(msg);
|
|
146814
|
+
}
|
|
146815
|
+
},
|
|
146816
|
+
warn: (msg) => getLogger().warn(msg),
|
|
146817
|
+
error: (msg) => getLogger().error(msg)
|
|
146818
|
+
};
|
|
146744
146819
|
// src/database/seed/timeback.ts
|
|
146745
146820
|
function generateMockStudentId(userId) {
|
|
146746
146821
|
return `mock-student-${userId.slice(-8)}`;
|
|
@@ -146805,7 +146880,7 @@ async function seedCoreGames(db) {
|
|
|
146805
146880
|
try {
|
|
146806
146881
|
await db.insert(games).values(gameData).onConflictDoNothing();
|
|
146807
146882
|
} catch (error2) {
|
|
146808
|
-
|
|
146883
|
+
logger3.error(`Error seeding core game '${gameData.slug}': ${error2}`);
|
|
146809
146884
|
}
|
|
146810
146885
|
}
|
|
146811
146886
|
}
|
|
@@ -146846,7 +146921,7 @@ async function seedCurrentProjectGame(db, project) {
|
|
|
146846
146921
|
}
|
|
146847
146922
|
return newGame;
|
|
146848
146923
|
} catch (error2) {
|
|
146849
|
-
|
|
146924
|
+
logger3.error(`❌ Error seeding project game: ${error2}`);
|
|
146850
146925
|
throw error2;
|
|
146851
146926
|
}
|
|
146852
146927
|
}
|
|
@@ -146973,13 +147048,6 @@ async function setupServerDatabase(processedOptions, project) {
|
|
|
146973
147048
|
|
|
146974
147049
|
// src/server/options.ts
|
|
146975
147050
|
var import_json_colorizer = __toESM(require_dist2(), 1);
|
|
146976
|
-
|
|
146977
|
-
// src/lib/logging/adapter.ts
|
|
146978
|
-
var customLogger;
|
|
146979
|
-
function setLogger(logger3) {
|
|
146980
|
-
customLogger = logger3;
|
|
146981
|
-
}
|
|
146982
|
-
// src/server/options.ts
|
|
146983
147051
|
function processServerOptions(_port, options) {
|
|
146984
147052
|
const {
|
|
146985
147053
|
verbose = false,
|
|
@@ -147107,6 +147175,8 @@ manifestRouter.get("/", async (c3) => {
|
|
|
147107
147175
|
{ method: "GET", url: `${baseUrl}/api/notifications/stats/:userId` },
|
|
147108
147176
|
{ method: "POST", url: `${baseUrl}/api/notifications/deliver` },
|
|
147109
147177
|
{ method: "POST", url: `${baseUrl}/api/timeback/populate-student` },
|
|
147178
|
+
{ method: "GET", url: `${baseUrl}/api/timeback/user` },
|
|
147179
|
+
{ method: "GET", url: `${baseUrl}/api/timeback/user/:timebackId` },
|
|
147110
147180
|
{ method: "GET", url: `${baseUrl}/api/timeback/xp/today` },
|
|
147111
147181
|
{ method: "PUT", url: `${baseUrl}/api/timeback/xp/today` },
|
|
147112
147182
|
{ method: "GET", url: `${baseUrl}/api/timeback/xp/total` },
|
|
@@ -152366,6 +152436,10 @@ async function getMockTimebackData(db, timebackId, gameId) {
|
|
|
152366
152436
|
});
|
|
152367
152437
|
return { id: timebackId, role, enrollments, organizations };
|
|
152368
152438
|
}
|
|
152439
|
+
async function getMockTimebackUser(db, gameId) {
|
|
152440
|
+
const timebackId = config.timeback.timebackId || "mock-student-00000001";
|
|
152441
|
+
return getMockTimebackData(db, timebackId, gameId);
|
|
152442
|
+
}
|
|
152369
152443
|
async function buildMockUserResponse(db, user, gameId) {
|
|
152370
152444
|
const timeback3 = user.timebackId ? await getMockTimebackData(db, user.timebackId, gameId) : undefined;
|
|
152371
152445
|
if (gameId) {
|
|
@@ -152496,57 +152570,6 @@ usersRouter.all("/", async (c3) => {
|
|
|
152496
152570
|
const error2 = ApiError.methodNotAllowed("Method not allowed");
|
|
152497
152571
|
return c3.json(createErrorResponse(error2), 405);
|
|
152498
152572
|
});
|
|
152499
|
-
// ../api-core/src/utils/secrets-storage.ts
|
|
152500
|
-
var import_client_s32 = __toESM(require_dist_cjs81(), 1);
|
|
152501
|
-
function getSecretsConfig() {
|
|
152502
|
-
const bucketName = process.env.GAME_SECRETS_BUCKET;
|
|
152503
|
-
const masterKey = process.env.GAME_SECRETS_MASTER_KEY;
|
|
152504
|
-
if (!bucketName || !masterKey) {
|
|
152505
|
-
throw new Error("Secrets storage not configured");
|
|
152506
|
-
}
|
|
152507
|
-
const accessKeyId = process.env.CLOUDFLARE_R2_ACCESS_KEY_ID;
|
|
152508
|
-
const secretAccessKey = process.env.CLOUDFLARE_R2_SECRET_ACCESS_KEY;
|
|
152509
|
-
const endpoint = process.env.CLOUDFLARE_R2_DEFAULT_ENDPOINT;
|
|
152510
|
-
if (!accessKeyId || !secretAccessKey || !endpoint) {
|
|
152511
|
-
throw new Error("R2 credentials not configured");
|
|
152512
|
-
}
|
|
152513
|
-
return {
|
|
152514
|
-
bucketName,
|
|
152515
|
-
credentials: {
|
|
152516
|
-
accessKeyId,
|
|
152517
|
-
secretAccessKey,
|
|
152518
|
-
endpoint
|
|
152519
|
-
},
|
|
152520
|
-
masterKey
|
|
152521
|
-
};
|
|
152522
|
-
}
|
|
152523
|
-
function getSecretsKey(gameId) {
|
|
152524
|
-
return `${gameId}.json.enc`;
|
|
152525
|
-
}
|
|
152526
|
-
function createR2Client(config2) {
|
|
152527
|
-
return new import_client_s32.S3Client({
|
|
152528
|
-
region: "auto",
|
|
152529
|
-
endpoint: config2.credentials.endpoint,
|
|
152530
|
-
credentials: {
|
|
152531
|
-
accessKeyId: config2.credentials.accessKeyId,
|
|
152532
|
-
secretAccessKey: config2.credentials.secretAccessKey
|
|
152533
|
-
}
|
|
152534
|
-
});
|
|
152535
|
-
}
|
|
152536
|
-
async function deleteSecrets(gameId, config2) {
|
|
152537
|
-
const client = createR2Client(config2);
|
|
152538
|
-
const key = getSecretsKey(gameId);
|
|
152539
|
-
try {
|
|
152540
|
-
await client.send(new import_client_s32.DeleteObjectCommand({
|
|
152541
|
-
Bucket: config2.bucketName,
|
|
152542
|
-
Key: key
|
|
152543
|
-
}));
|
|
152544
|
-
} catch (error2) {
|
|
152545
|
-
log2.error("[SecretsStorage] Failed to delete secrets", { gameId, error: error2 });
|
|
152546
|
-
throw new Error(`Failed to delete secrets for game ${gameId}`);
|
|
152547
|
-
}
|
|
152548
|
-
}
|
|
152549
|
-
|
|
152550
152573
|
// ../../node_modules/ulidx/dist/node/index.js
|
|
152551
152574
|
import crypto6 from "node:crypto";
|
|
152552
152575
|
|
|
@@ -154943,6 +154966,7 @@ gameUploadsRouter.post("/uploads/initiate", async (c3) => {
|
|
|
154943
154966
|
// src/routes/platform/games/verify.ts
|
|
154944
154967
|
var gameVerifyRouter = new Hono2;
|
|
154945
154968
|
gameVerifyRouter.post("/verify", async (c3) => {
|
|
154969
|
+
const clonedRequest = c3.req.raw.clone();
|
|
154946
154970
|
const body2 = await c3.req.json().catch(() => ({}));
|
|
154947
154971
|
const token2 = body2?.token;
|
|
154948
154972
|
if (!token2) {
|
|
@@ -154971,11 +154995,43 @@ gameVerifyRouter.post("/verify", async (c3) => {
|
|
|
154971
154995
|
}
|
|
154972
154996
|
});
|
|
154973
154997
|
}
|
|
154998
|
+
if (token2.endsWith(".sandbox")) {
|
|
154999
|
+
try {
|
|
155000
|
+
const parts2 = token2.split(".");
|
|
155001
|
+
if (parts2.length === 3) {
|
|
155002
|
+
const payload = JSON.parse(atob(parts2[1]));
|
|
155003
|
+
const userId = payload.uid;
|
|
155004
|
+
const gameIdOrSlug = payload.sub;
|
|
155005
|
+
const db = c3.get("db");
|
|
155006
|
+
const userData = await db.query.users.findFirst({
|
|
155007
|
+
where: (users2, { eq: eq3 }) => eq3(users2.id, userId)
|
|
155008
|
+
});
|
|
155009
|
+
if (!userData) {
|
|
155010
|
+
return c3.json({ error: "User not found in sandbox" }, 500);
|
|
155011
|
+
}
|
|
155012
|
+
return c3.json({
|
|
155013
|
+
claims: payload,
|
|
155014
|
+
gameId: gameIdOrSlug,
|
|
155015
|
+
user: {
|
|
155016
|
+
sub: userData.id,
|
|
155017
|
+
email: userData.email || "",
|
|
155018
|
+
name: userData.name || userData.username || "",
|
|
155019
|
+
email_verified: true,
|
|
155020
|
+
given_name: undefined,
|
|
155021
|
+
family_name: undefined,
|
|
155022
|
+
timeback_id: userData.timebackId || undefined
|
|
155023
|
+
}
|
|
155024
|
+
});
|
|
155025
|
+
}
|
|
155026
|
+
} catch {
|
|
155027
|
+
return c3.json({ error: "Invalid sandbox token" }, 400);
|
|
155028
|
+
}
|
|
155029
|
+
}
|
|
154974
155030
|
const ctx = {
|
|
154975
155031
|
user: undefined,
|
|
154976
155032
|
params: {},
|
|
154977
155033
|
url: new URL(c3.req.url),
|
|
154978
|
-
request:
|
|
155034
|
+
request: clonedRequest
|
|
154979
155035
|
};
|
|
154980
155036
|
try {
|
|
154981
155037
|
const result = await verifyGameToken(ctx);
|
|
@@ -157122,7 +157178,7 @@ async function deliverNotifications(ctx) {
|
|
|
157122
157178
|
if (!user) {
|
|
157123
157179
|
throw ApiError.unauthorized("Must be logged in to deliver notifications");
|
|
157124
157180
|
}
|
|
157125
|
-
log2.
|
|
157181
|
+
log2.debug("[API] Delivering pending notifications", {
|
|
157126
157182
|
userId: user.id
|
|
157127
157183
|
});
|
|
157128
157184
|
try {
|
|
@@ -157230,6 +157286,58 @@ notificationsRouter.post("/deliver", async (c3) => {
|
|
|
157230
157286
|
return c3.json(createUnknownErrorResponse(error2), 500);
|
|
157231
157287
|
}
|
|
157232
157288
|
});
|
|
157289
|
+
// ../api-core/src/handlers/realtime/token.ts
|
|
157290
|
+
async function generateRealtimeToken(ctx) {
|
|
157291
|
+
const user = ctx.user;
|
|
157292
|
+
const gameId = ctx.params?.gameId;
|
|
157293
|
+
log2.debug("[API] generating realtime token", {
|
|
157294
|
+
userId: user?.id || "anonymous",
|
|
157295
|
+
gameId: gameId || "none"
|
|
157296
|
+
});
|
|
157297
|
+
if (!user) {
|
|
157298
|
+
throw ApiError.unauthorized("Valid session or bearer token required");
|
|
157299
|
+
}
|
|
157300
|
+
try {
|
|
157301
|
+
if (gameId) {
|
|
157302
|
+
const db = getDatabase();
|
|
157303
|
+
const game = await db.query.games.findFirst({
|
|
157304
|
+
where: eq(games.id, gameId),
|
|
157305
|
+
columns: { id: true }
|
|
157306
|
+
});
|
|
157307
|
+
if (!game) {
|
|
157308
|
+
throw ApiError.notFound("Game not found");
|
|
157309
|
+
}
|
|
157310
|
+
}
|
|
157311
|
+
const displayName = user.username || (user.name ? user.name.split(" ")[0] : undefined) || undefined;
|
|
157312
|
+
const token2 = await mintRealtimeJwt(user.id, gameId, displayName, user.role || undefined);
|
|
157313
|
+
return { token: token2 };
|
|
157314
|
+
} catch (error2) {
|
|
157315
|
+
if (error2 instanceof ApiError)
|
|
157316
|
+
throw error2;
|
|
157317
|
+
log2.error("[API /realtime/token] Failed to generate realtime token:", { error: error2 });
|
|
157318
|
+
throw ApiError.internal("Internal server error", error2);
|
|
157319
|
+
}
|
|
157320
|
+
}
|
|
157321
|
+
// src/routes/platform/realtime.ts
|
|
157322
|
+
var realtimeRouter = new Hono2;
|
|
157323
|
+
realtimeRouter.post("/token", async (c3) => {
|
|
157324
|
+
const ctx = {
|
|
157325
|
+
user: c3.get("user"),
|
|
157326
|
+
params: {},
|
|
157327
|
+
url: new URL(c3.req.url),
|
|
157328
|
+
request: c3.req.raw
|
|
157329
|
+
};
|
|
157330
|
+
try {
|
|
157331
|
+
const result = await generateRealtimeToken(ctx);
|
|
157332
|
+
return c3.json(result);
|
|
157333
|
+
} catch (error2) {
|
|
157334
|
+
if (error2 instanceof ApiError) {
|
|
157335
|
+
return c3.json(createErrorResponse(error2), error2.statusCode);
|
|
157336
|
+
}
|
|
157337
|
+
console.error("Error in realtime/token:", error2);
|
|
157338
|
+
return c3.json(createUnknownErrorResponse(error2), 500);
|
|
157339
|
+
}
|
|
157340
|
+
});
|
|
157233
157341
|
// ../api-core/src/utils/timeback-errors.ts
|
|
157234
157342
|
function isTimebackApiError(error2) {
|
|
157235
157343
|
return typeof error2 === "object" && error2 !== null && "name" in error2 && error2.name === "TimebackApiError" && "status" in error2 && "details" in error2;
|
|
@@ -157764,24 +157872,59 @@ async function getTimeBackXpHistory(ctx) {
|
|
|
157764
157872
|
throw ApiError.internal("Failed to get TimeBack XP history", error2);
|
|
157765
157873
|
}
|
|
157766
157874
|
}
|
|
157767
|
-
// ../api-core/src/handlers/timeback/
|
|
157768
|
-
async function
|
|
157875
|
+
// ../api-core/src/handlers/timeback/user.ts
|
|
157876
|
+
async function getTimebackUser(ctx) {
|
|
157877
|
+
const user = ctx.user;
|
|
157878
|
+
if (!user) {
|
|
157879
|
+
throw ApiError.unauthorized("Must be logged in to get timeback data");
|
|
157880
|
+
}
|
|
157881
|
+
const db = getDatabase();
|
|
157882
|
+
const userData = await db.query.users.findFirst({
|
|
157883
|
+
where: eq(users.id, user.id)
|
|
157884
|
+
});
|
|
157885
|
+
if (!userData) {
|
|
157886
|
+
throw ApiError.notFound("User not found");
|
|
157887
|
+
}
|
|
157888
|
+
if (!userData.timebackId) {
|
|
157889
|
+
throw ApiError.notFound("User does not have a TimeBack account");
|
|
157890
|
+
}
|
|
157891
|
+
log2.debug("[API] Getting timeback user data", {
|
|
157892
|
+
userId: user.id,
|
|
157893
|
+
timebackId: userData.timebackId,
|
|
157894
|
+
gameId: ctx.gameId
|
|
157895
|
+
});
|
|
157896
|
+
const timeback3 = await fetchUserTimebackData(userData.timebackId, ctx.gameId);
|
|
157897
|
+
log2.info("[API] Retrieved timeback user data", {
|
|
157898
|
+
userId: user.id,
|
|
157899
|
+
timebackId: userData.timebackId,
|
|
157900
|
+
role: timeback3.role,
|
|
157901
|
+
enrollmentCount: timeback3.enrollments.length,
|
|
157902
|
+
organizationCount: timeback3.organizations.length
|
|
157903
|
+
});
|
|
157904
|
+
return timeback3;
|
|
157905
|
+
}
|
|
157906
|
+
async function getTimebackUserById(ctx) {
|
|
157769
157907
|
const user = ctx.user;
|
|
157770
157908
|
if (!user) {
|
|
157771
|
-
throw ApiError.unauthorized("Must be logged in to get
|
|
157909
|
+
throw ApiError.unauthorized("Must be logged in to get timeback data");
|
|
157772
157910
|
}
|
|
157773
157911
|
const timebackId = ctx.params.timebackId;
|
|
157774
157912
|
if (!timebackId) {
|
|
157775
157913
|
throw ApiError.badRequest("Missing timebackId parameter");
|
|
157776
157914
|
}
|
|
157777
|
-
log2.debug("[API] Getting
|
|
157778
|
-
|
|
157779
|
-
|
|
157780
|
-
|
|
157915
|
+
log2.debug("[API] Getting timeback user by ID", {
|
|
157916
|
+
requesterId: user.id,
|
|
157917
|
+
timebackId
|
|
157918
|
+
});
|
|
157919
|
+
const timeback3 = await fetchUserTimebackData(timebackId);
|
|
157920
|
+
log2.info("[API] Retrieved timeback user by ID", {
|
|
157921
|
+
requesterId: user.id,
|
|
157781
157922
|
timebackId,
|
|
157782
|
-
|
|
157923
|
+
role: timeback3.role,
|
|
157924
|
+
enrollmentCount: timeback3.enrollments.length,
|
|
157925
|
+
organizationCount: timeback3.organizations.length
|
|
157783
157926
|
});
|
|
157784
|
-
return
|
|
157927
|
+
return timeback3;
|
|
157785
157928
|
}
|
|
157786
157929
|
// src/routes/integrations/timeback.ts
|
|
157787
157930
|
var timebackRouter = new Hono2;
|
|
@@ -157974,32 +158117,62 @@ timebackRouter.post("/end-activity", async (c3) => {
|
|
|
157974
158117
|
return c3.json(createUnknownErrorResponse(error2), 500);
|
|
157975
158118
|
}
|
|
157976
158119
|
});
|
|
157977
|
-
timebackRouter.get("/
|
|
158120
|
+
timebackRouter.get("/user", async (c3) => {
|
|
158121
|
+
const user2 = c3.get("user");
|
|
158122
|
+
const gameId = c3.get("gameId");
|
|
158123
|
+
if (!user2) {
|
|
158124
|
+
const error2 = ApiError.unauthorized("Must be logged in to get timeback data");
|
|
158125
|
+
return c3.json(createErrorResponse(error2), error2.statusCode);
|
|
158126
|
+
}
|
|
158127
|
+
try {
|
|
158128
|
+
if (shouldMockTimeback()) {
|
|
158129
|
+
const db = c3.get("db");
|
|
158130
|
+
const timeback3 = await getMockTimebackUser(db, gameId);
|
|
158131
|
+
return c3.json(timeback3);
|
|
158132
|
+
}
|
|
158133
|
+
const ctx = {
|
|
158134
|
+
user: user2,
|
|
158135
|
+
params: {},
|
|
158136
|
+
url: new URL(c3.req.url),
|
|
158137
|
+
request: c3.req.raw,
|
|
158138
|
+
gameId
|
|
158139
|
+
};
|
|
158140
|
+
const result = await getTimebackUser(ctx);
|
|
158141
|
+
return c3.json(result);
|
|
158142
|
+
} catch (error2) {
|
|
158143
|
+
if (error2 instanceof ApiError) {
|
|
158144
|
+
return c3.json(createErrorResponse(error2), error2.statusCode);
|
|
158145
|
+
}
|
|
158146
|
+
console.error("Error in getTimebackUser:", error2);
|
|
158147
|
+
return c3.json(createUnknownErrorResponse(error2), 500);
|
|
158148
|
+
}
|
|
158149
|
+
});
|
|
158150
|
+
timebackRouter.get("/user/:timebackId", async (c3) => {
|
|
157978
158151
|
const timebackId = c3.req.param("timebackId");
|
|
157979
|
-
const
|
|
157980
|
-
if (!
|
|
157981
|
-
const error2 = ApiError.unauthorized("Must be logged in to get
|
|
158152
|
+
const user2 = c3.get("user");
|
|
158153
|
+
if (!user2) {
|
|
158154
|
+
const error2 = ApiError.unauthorized("Must be logged in to get timeback data");
|
|
157982
158155
|
return c3.json(createErrorResponse(error2), error2.statusCode);
|
|
157983
158156
|
}
|
|
157984
158157
|
try {
|
|
157985
158158
|
if (shouldMockTimeback()) {
|
|
157986
158159
|
const db = c3.get("db");
|
|
157987
|
-
const
|
|
157988
|
-
return c3.json(
|
|
158160
|
+
const timeback3 = await getMockTimebackUser(db);
|
|
158161
|
+
return c3.json(timeback3);
|
|
157989
158162
|
}
|
|
157990
158163
|
const ctx = {
|
|
157991
|
-
user,
|
|
158164
|
+
user: user2,
|
|
157992
158165
|
params: { timebackId },
|
|
157993
158166
|
url: new URL(c3.req.url),
|
|
157994
158167
|
request: c3.req.raw
|
|
157995
158168
|
};
|
|
157996
|
-
const result = await
|
|
158169
|
+
const result = await getTimebackUserById(ctx);
|
|
157997
158170
|
return c3.json(result);
|
|
157998
158171
|
} catch (error2) {
|
|
157999
158172
|
if (error2 instanceof ApiError) {
|
|
158000
158173
|
return c3.json(createErrorResponse(error2), error2.statusCode);
|
|
158001
158174
|
}
|
|
158002
|
-
console.error("Error in
|
|
158175
|
+
console.error("Error in getTimebackUserById:", error2);
|
|
158003
158176
|
return c3.json(createUnknownErrorResponse(error2), 500);
|
|
158004
158177
|
}
|
|
158005
158178
|
});
|
|
@@ -158036,29 +158209,29 @@ async function provisionUserFromLti(claims) {
|
|
|
158036
158209
|
where: and(eq(accounts.accountId, ltiTimebackId), eq(accounts.providerId, providerId))
|
|
158037
158210
|
});
|
|
158038
158211
|
if (existingAccount) {
|
|
158039
|
-
const
|
|
158212
|
+
const user3 = await db.query.users.findFirst({
|
|
158040
158213
|
where: eq(users.id, existingAccount.userId)
|
|
158041
158214
|
});
|
|
158042
|
-
if (
|
|
158215
|
+
if (user3) {
|
|
158043
158216
|
log2.info("[lti-timeback] Found existing user by LTI account linkage", {
|
|
158044
|
-
userId:
|
|
158217
|
+
userId: user3.id,
|
|
158045
158218
|
ltiTimebackId
|
|
158046
158219
|
});
|
|
158047
|
-
return
|
|
158220
|
+
return user3;
|
|
158048
158221
|
}
|
|
158049
158222
|
}
|
|
158050
|
-
const
|
|
158223
|
+
const user2 = await db.query.users.findFirst({
|
|
158051
158224
|
where: eq(users.email, email)
|
|
158052
158225
|
});
|
|
158053
|
-
if (
|
|
158226
|
+
if (user2) {
|
|
158054
158227
|
await db.transaction(async (tx) => {
|
|
158055
158228
|
const existingLtiAccount = await tx.query.accounts.findFirst({
|
|
158056
|
-
where: and(eq(accounts.userId,
|
|
158229
|
+
where: and(eq(accounts.userId, user2.id), eq(accounts.providerId, providerId))
|
|
158057
158230
|
});
|
|
158058
158231
|
if (!existingLtiAccount) {
|
|
158059
158232
|
await tx.insert(accounts).values({
|
|
158060
158233
|
id: crypto7.randomUUID(),
|
|
158061
|
-
userId:
|
|
158234
|
+
userId: user2.id,
|
|
158062
158235
|
accountId: ltiTimebackId,
|
|
158063
158236
|
providerId,
|
|
158064
158237
|
accessToken: null,
|
|
@@ -158069,14 +158242,14 @@ async function provisionUserFromLti(claims) {
|
|
|
158069
158242
|
updatedAt: new Date
|
|
158070
158243
|
});
|
|
158071
158244
|
log2.info("[lti-timeback] Linked existing user to LTI provider", {
|
|
158072
|
-
userId:
|
|
158073
|
-
email:
|
|
158245
|
+
userId: user2.id,
|
|
158246
|
+
email: user2.email,
|
|
158074
158247
|
ltiTimebackId,
|
|
158075
158248
|
note: "User may have different TimeBack ID from OAuth flow"
|
|
158076
158249
|
});
|
|
158077
158250
|
}
|
|
158078
158251
|
});
|
|
158079
|
-
return
|
|
158252
|
+
return user2;
|
|
158080
158253
|
}
|
|
158081
158254
|
const newUserId = crypto7.randomUUID();
|
|
158082
158255
|
const createdUser = await db.transaction(async (tx) => {
|
|
@@ -158119,10 +158292,10 @@ async function processLtiLaunch(idToken) {
|
|
|
158119
158292
|
try {
|
|
158120
158293
|
const claims = await verifyLtiToken(idToken);
|
|
158121
158294
|
validateLtiClaims(claims);
|
|
158122
|
-
const
|
|
158295
|
+
const user2 = await provisionUserFromLti(claims);
|
|
158123
158296
|
const targetUri = claims["https://purl.imsglobal.org/spec/lti/claim/target_link_uri"];
|
|
158124
158297
|
return {
|
|
158125
|
-
user,
|
|
158298
|
+
user: user2,
|
|
158126
158299
|
targetUri,
|
|
158127
158300
|
claims
|
|
158128
158301
|
};
|
|
@@ -158142,45 +158315,45 @@ async function processTimeBackLtiLaunch(ctx) {
|
|
|
158142
158315
|
}
|
|
158143
158316
|
const currentHost = ctx.url.hostname;
|
|
158144
158317
|
const launchResult = await processLtiLaunch(idToken);
|
|
158145
|
-
const { user, targetUri, claims } = launchResult;
|
|
158318
|
+
const { user: user2, targetUri, claims } = launchResult;
|
|
158146
158319
|
log2.info("[lti-timeback] User roles", {
|
|
158147
|
-
userId:
|
|
158320
|
+
userId: user2.id,
|
|
158148
158321
|
isLearner: LtiRoleChecks.isLearner(claims),
|
|
158149
158322
|
isInstructor: LtiRoleChecks.isInstructor(claims),
|
|
158150
158323
|
isAdministrator: LtiRoleChecks.isAdministrator(claims),
|
|
158151
158324
|
allRoles: claims["https://purl.imsglobal.org/spec/lti/claim/roles"]
|
|
158152
158325
|
});
|
|
158153
|
-
const sessionToken = await createLtiSession(
|
|
158326
|
+
const sessionToken = await createLtiSession(user2.id);
|
|
158154
158327
|
const redirectPath = extractRedirectPath(targetUri, currentHost);
|
|
158155
158328
|
log2.info("[lti-timeback] LTI launch successful", {
|
|
158156
|
-
userId:
|
|
158329
|
+
userId: user2.id,
|
|
158157
158330
|
redirectPath
|
|
158158
158331
|
});
|
|
158159
158332
|
return {
|
|
158160
|
-
user,
|
|
158333
|
+
user: user2,
|
|
158161
158334
|
redirectPath,
|
|
158162
158335
|
sessionToken
|
|
158163
158336
|
};
|
|
158164
158337
|
}
|
|
158165
158338
|
async function getTimeBackLtiStatus(ctx) {
|
|
158166
|
-
const
|
|
158167
|
-
if (!
|
|
158339
|
+
const user2 = ctx.user;
|
|
158340
|
+
if (!user2) {
|
|
158168
158341
|
throw ApiError.unauthorized("Must be logged in");
|
|
158169
158342
|
}
|
|
158170
158343
|
const db = getDatabase();
|
|
158171
158344
|
const ltiAccount = await db.query.accounts.findFirst({
|
|
158172
|
-
where: and(eq(accounts.userId,
|
|
158345
|
+
where: and(eq(accounts.userId, user2.id), eq(accounts.providerId, AUTH_PROVIDER_IDS.TIMEBACK_LTI))
|
|
158173
158346
|
});
|
|
158174
158347
|
const oauthAccount = await db.query.accounts.findFirst({
|
|
158175
|
-
where: and(eq(accounts.userId,
|
|
158348
|
+
where: and(eq(accounts.userId, user2.id), eq(accounts.providerId, AUTH_PROVIDER_IDS.TIMEBACK))
|
|
158176
158349
|
});
|
|
158177
158350
|
return {
|
|
158178
158351
|
hasLtiAccount: !!ltiAccount,
|
|
158179
158352
|
ltiTimebackId: ltiAccount?.accountId || undefined,
|
|
158180
158353
|
hasOAuthAccount: !!oauthAccount,
|
|
158181
|
-
oauthTimebackId: oauthAccount?.accountId ||
|
|
158182
|
-
email:
|
|
158183
|
-
userId:
|
|
158354
|
+
oauthTimebackId: oauthAccount?.accountId || user2.timebackId || undefined,
|
|
158355
|
+
email: user2.email,
|
|
158356
|
+
userId: user2.id
|
|
158184
158357
|
};
|
|
158185
158358
|
}
|
|
158186
158359
|
|
|
@@ -158219,20 +158392,20 @@ ltiRouter.all("/launch", async (c3) => {
|
|
|
158219
158392
|
}
|
|
158220
158393
|
});
|
|
158221
158394
|
ltiRouter.get("/status", async (c3) => {
|
|
158222
|
-
let
|
|
158395
|
+
let user2 = undefined;
|
|
158223
158396
|
const authHeader = c3.req.header("Authorization");
|
|
158224
158397
|
if (authHeader?.startsWith("Bearer ")) {
|
|
158225
158398
|
const token2 = authHeader.substring(7);
|
|
158226
158399
|
const demoUser = DEMO_TOKENS[token2];
|
|
158227
158400
|
if (demoUser) {
|
|
158228
158401
|
const db = c3.get("db");
|
|
158229
|
-
|
|
158402
|
+
user2 = await db.query.users.findFirst({
|
|
158230
158403
|
where: eq(users.id, demoUser.id)
|
|
158231
158404
|
});
|
|
158232
158405
|
}
|
|
158233
158406
|
}
|
|
158234
158407
|
const ctx = {
|
|
158235
|
-
user,
|
|
158408
|
+
user: user2,
|
|
158236
158409
|
params: {},
|
|
158237
158410
|
url: new URL(c3.req.url),
|
|
158238
158411
|
request: c3.req.raw
|
|
@@ -158267,6 +158440,7 @@ function registerRoutes(app) {
|
|
|
158267
158440
|
app.route("/api/sprites", spriteRouter);
|
|
158268
158441
|
app.route("/api/achievements", achievementsRouter);
|
|
158269
158442
|
app.route("/api/notifications", notificationsRouter);
|
|
158443
|
+
app.route("/api/realtime", realtimeRouter);
|
|
158270
158444
|
app.route("/api/dev", devRouter);
|
|
158271
158445
|
app.route("/api/timeback", timebackRouter);
|
|
158272
158446
|
app.route("/api/lti", ltiRouter);
|