@playcademy/sandbox 0.3.5 → 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 +370 -190
- package/dist/mocks/timeback.d.ts +5 -0
- package/dist/server.js +361 -187
- package/package.json +1 -1
package/dist/server.js
CHANGED
|
@@ -996,7 +996,7 @@ ${t}`), i3 = h(this, y2).getSize();
|
|
|
996
996
|
|
|
997
997
|
// ../../node_modules/esbuild/lib/main.js
|
|
998
998
|
var require_main = __commonJS((exports, module2) => {
|
|
999
|
-
var __dirname = "/Users/hbauer/work/
|
|
999
|
+
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";
|
|
1000
1000
|
var __defProp2 = Object.defineProperty;
|
|
1001
1001
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
1002
1002
|
var __getOwnPropNames2 = Object.getOwnPropertyNames;
|
|
@@ -86664,9 +86664,9 @@ var require_serde = __commonJS((exports, module2) => {
|
|
|
86664
86664
|
strictParseShort: () => strictParseShort2
|
|
86665
86665
|
});
|
|
86666
86666
|
module2.exports = __toCommonJS(serde_exports);
|
|
86667
|
-
var
|
|
86667
|
+
var import_schema3 = require_schema();
|
|
86668
86668
|
var copyDocumentWithTransform2 = (source, schemaRef, transform = (_5) => _5) => {
|
|
86669
|
-
const ns =
|
|
86669
|
+
const ns = import_schema3.NormalizedSchema.of(schemaRef);
|
|
86670
86670
|
switch (typeof source) {
|
|
86671
86671
|
case "undefined":
|
|
86672
86672
|
case "boolean":
|
|
@@ -87279,7 +87279,7 @@ var require_protocols = __commonJS((exports, module2) => {
|
|
|
87279
87279
|
return "%" + c3.charCodeAt(0).toString(16).toUpperCase();
|
|
87280
87280
|
});
|
|
87281
87281
|
}
|
|
87282
|
-
var
|
|
87282
|
+
var import_schema22 = require_schema();
|
|
87283
87283
|
var import_protocol_http2 = require_dist_cjs2();
|
|
87284
87284
|
var import_schema3 = require_schema();
|
|
87285
87285
|
var import_serde = require_serde();
|
|
@@ -87445,7 +87445,7 @@ var require_protocols = __commonJS((exports, module2) => {
|
|
|
87445
87445
|
const query = {};
|
|
87446
87446
|
const headers = {};
|
|
87447
87447
|
const endpoint = await context.endpoint();
|
|
87448
|
-
const ns =
|
|
87448
|
+
const ns = import_schema22.NormalizedSchema.of(operationSchema?.input);
|
|
87449
87449
|
const schema4 = ns.getSchema();
|
|
87450
87450
|
let hasNonHttpBindingMember = false;
|
|
87451
87451
|
let payload;
|
|
@@ -87462,7 +87462,7 @@ var require_protocols = __commonJS((exports, module2) => {
|
|
|
87462
87462
|
if (endpoint) {
|
|
87463
87463
|
this.updateServiceEndpoint(request3, endpoint);
|
|
87464
87464
|
this.setHostPrefix(request3, operationSchema, input);
|
|
87465
|
-
const opTraits =
|
|
87465
|
+
const opTraits = import_schema22.NormalizedSchema.translateTraits(operationSchema.traits);
|
|
87466
87466
|
if (opTraits.http) {
|
|
87467
87467
|
request3.method = opTraits.http[0];
|
|
87468
87468
|
const [path3, search] = opTraits.http[1].split("?");
|
|
@@ -87540,7 +87540,7 @@ var require_protocols = __commonJS((exports, module2) => {
|
|
|
87540
87540
|
if (traits.httpQueryParams) {
|
|
87541
87541
|
for (const [key, val2] of Object.entries(data)) {
|
|
87542
87542
|
if (!(key in query)) {
|
|
87543
|
-
this.serializeQuery(
|
|
87543
|
+
this.serializeQuery(import_schema22.NormalizedSchema.of([
|
|
87544
87544
|
ns.getValueSchema(),
|
|
87545
87545
|
{
|
|
87546
87546
|
...traits,
|
|
@@ -87570,12 +87570,12 @@ var require_protocols = __commonJS((exports, module2) => {
|
|
|
87570
87570
|
}
|
|
87571
87571
|
async deserializeResponse(operationSchema, context, response) {
|
|
87572
87572
|
const deserializer = this.deserializer;
|
|
87573
|
-
const ns =
|
|
87573
|
+
const ns = import_schema22.NormalizedSchema.of(operationSchema.output);
|
|
87574
87574
|
const dataObject = {};
|
|
87575
87575
|
if (response.statusCode >= 300) {
|
|
87576
87576
|
const bytes = await collectBody2(response.body, context);
|
|
87577
87577
|
if (bytes.byteLength > 0) {
|
|
87578
|
-
Object.assign(dataObject, await deserializer.read(
|
|
87578
|
+
Object.assign(dataObject, await deserializer.read(import_schema22.SCHEMA.DOCUMENT, bytes));
|
|
87579
87579
|
}
|
|
87580
87580
|
await this.handleError(operationSchema, context, response, dataObject, this.deserializeMetadata(response));
|
|
87581
87581
|
throw new Error("@smithy/core/protocols - HTTP Protocol error handler failed to throw.");
|
|
@@ -92260,7 +92260,7 @@ var require_protocols2 = __commonJS((exports, module2) => {
|
|
|
92260
92260
|
this.serdeContext = serdeContext;
|
|
92261
92261
|
}
|
|
92262
92262
|
};
|
|
92263
|
-
var
|
|
92263
|
+
var import_schema4 = require_schema();
|
|
92264
92264
|
var import_serde2 = require_serde();
|
|
92265
92265
|
var import_util_base64 = require_dist_cjs9();
|
|
92266
92266
|
var import_serde = require_serde();
|
|
@@ -92351,7 +92351,7 @@ var require_protocols2 = __commonJS((exports, module2) => {
|
|
|
92351
92351
|
}
|
|
92352
92352
|
_read(schema4, value) {
|
|
92353
92353
|
const isObject4 = value !== null && typeof value === "object";
|
|
92354
|
-
const ns =
|
|
92354
|
+
const ns = import_schema4.NormalizedSchema.of(schema4);
|
|
92355
92355
|
if (ns.isListSchema() && Array.isArray(value)) {
|
|
92356
92356
|
const listMember = ns.getValueSchema();
|
|
92357
92357
|
const out2 = [];
|
|
@@ -92395,13 +92395,13 @@ var require_protocols2 = __commonJS((exports, module2) => {
|
|
|
92395
92395
|
}
|
|
92396
92396
|
if (ns.isTimestampSchema()) {
|
|
92397
92397
|
const options = this.settings.timestampFormat;
|
|
92398
|
-
const format = options.useTrait ? ns.getSchema() ===
|
|
92398
|
+
const format = options.useTrait ? ns.getSchema() === import_schema4.SCHEMA.TIMESTAMP_DEFAULT ? options.default : ns.getSchema() ?? options.default : options.default;
|
|
92399
92399
|
switch (format) {
|
|
92400
|
-
case
|
|
92400
|
+
case import_schema4.SCHEMA.TIMESTAMP_DATE_TIME:
|
|
92401
92401
|
return (0, import_serde2.parseRfc3339DateTimeWithOffset)(value);
|
|
92402
|
-
case
|
|
92402
|
+
case import_schema4.SCHEMA.TIMESTAMP_HTTP_DATE:
|
|
92403
92403
|
return (0, import_serde2.parseRfc7231DateTime)(value);
|
|
92404
|
-
case
|
|
92404
|
+
case import_schema4.SCHEMA.TIMESTAMP_EPOCH_SECONDS:
|
|
92405
92405
|
return (0, import_serde2.parseEpochTimestamp)(value);
|
|
92406
92406
|
default:
|
|
92407
92407
|
console.warn("Missing timestamp format, parsing value with Date constructor:", value);
|
|
@@ -92710,7 +92710,7 @@ var require_protocols2 = __commonJS((exports, module2) => {
|
|
|
92710
92710
|
}
|
|
92711
92711
|
};
|
|
92712
92712
|
var import_protocols2 = require_protocols();
|
|
92713
|
-
var
|
|
92713
|
+
var import_schema42 = require_schema();
|
|
92714
92714
|
var import_util_body_length_browser2 = require_dist_cjs21();
|
|
92715
92715
|
var AwsRestJsonProtocol = class extends import_protocols2.HttpBindingProtocol {
|
|
92716
92716
|
static {
|
|
@@ -92726,7 +92726,7 @@ var require_protocols2 = __commonJS((exports, module2) => {
|
|
|
92726
92726
|
const settings = {
|
|
92727
92727
|
timestampFormat: {
|
|
92728
92728
|
useTrait: true,
|
|
92729
|
-
default:
|
|
92729
|
+
default: import_schema42.SCHEMA.TIMESTAMP_EPOCH_SECONDS
|
|
92730
92730
|
},
|
|
92731
92731
|
httpBindings: true,
|
|
92732
92732
|
jsonName: true
|
|
@@ -92747,7 +92747,7 @@ var require_protocols2 = __commonJS((exports, module2) => {
|
|
|
92747
92747
|
}
|
|
92748
92748
|
async serializeRequest(operationSchema, input, context) {
|
|
92749
92749
|
const request3 = await super.serializeRequest(operationSchema, input, context);
|
|
92750
|
-
const inputSchema =
|
|
92750
|
+
const inputSchema = import_schema42.NormalizedSchema.of(operationSchema.input);
|
|
92751
92751
|
const members = inputSchema.getMemberSchemas();
|
|
92752
92752
|
if (!request3.headers["content-type"]) {
|
|
92753
92753
|
const httpPayloadMember = Object.values(members).find((m5) => {
|
|
@@ -92791,19 +92791,19 @@ var require_protocols2 = __commonJS((exports, module2) => {
|
|
|
92791
92791
|
if (errorIdentifier.includes("#")) {
|
|
92792
92792
|
[namespace, errorName] = errorIdentifier.split("#");
|
|
92793
92793
|
}
|
|
92794
|
-
const registry2 =
|
|
92794
|
+
const registry2 = import_schema42.TypeRegistry.for(namespace);
|
|
92795
92795
|
let errorSchema;
|
|
92796
92796
|
try {
|
|
92797
92797
|
errorSchema = registry2.getSchema(errorIdentifier);
|
|
92798
92798
|
} catch (e2) {
|
|
92799
|
-
const baseExceptionSchema =
|
|
92799
|
+
const baseExceptionSchema = import_schema42.TypeRegistry.for("smithy.ts.sdk.synthetic." + namespace).getBaseException();
|
|
92800
92800
|
if (baseExceptionSchema) {
|
|
92801
92801
|
const ErrorCtor = baseExceptionSchema.ctor;
|
|
92802
92802
|
throw Object.assign(new ErrorCtor(errorName), dataObject);
|
|
92803
92803
|
}
|
|
92804
92804
|
throw new Error(errorName);
|
|
92805
92805
|
}
|
|
92806
|
-
const ns =
|
|
92806
|
+
const ns = import_schema42.NormalizedSchema.of(errorSchema);
|
|
92807
92807
|
const message2 = dataObject.message ?? dataObject.Message ?? "Unknown";
|
|
92808
92808
|
const exception = new errorSchema.ctor(message2);
|
|
92809
92809
|
await this.deserializeHttpMessage(errorSchema, context, response, dataObject);
|
|
@@ -119571,7 +119571,7 @@ async function requirePortAvailable(port, timeoutMs = 100) {
|
|
|
119571
119571
|
// package.json
|
|
119572
119572
|
var package_default = {
|
|
119573
119573
|
name: "@playcademy/sandbox",
|
|
119574
|
-
version: "0.3.
|
|
119574
|
+
version: "0.3.7",
|
|
119575
119575
|
description: "Local development server for Playcademy game development",
|
|
119576
119576
|
type: "module",
|
|
119577
119577
|
exports: {
|
|
@@ -134350,6 +134350,21 @@ async function mintGameJwt(gameId, userId) {
|
|
|
134350
134350
|
const token = await signJwt({ uid: userId }, "game", expirationTimeSeconds, gameId);
|
|
134351
134351
|
return { token, exp: expirationTimestampMillis };
|
|
134352
134352
|
}
|
|
134353
|
+
async function mintRealtimeJwt(userId, gameId, username, role) {
|
|
134354
|
+
const payload = {
|
|
134355
|
+
sub: userId
|
|
134356
|
+
};
|
|
134357
|
+
if (gameId) {
|
|
134358
|
+
payload.gameId = gameId;
|
|
134359
|
+
}
|
|
134360
|
+
if (username) {
|
|
134361
|
+
payload.username = username;
|
|
134362
|
+
}
|
|
134363
|
+
if (role) {
|
|
134364
|
+
payload.role = role;
|
|
134365
|
+
}
|
|
134366
|
+
return await signJwt(payload, "realtime", "30m");
|
|
134367
|
+
}
|
|
134353
134368
|
// ../api-core/src/utils/validation.ts
|
|
134354
134369
|
function formatValidationErrors(error2) {
|
|
134355
134370
|
const flattened = error2.flatten();
|
|
@@ -143454,6 +143469,17 @@ function createCustomHostnamesNamespace(config2) {
|
|
|
143454
143469
|
}
|
|
143455
143470
|
};
|
|
143456
143471
|
}
|
|
143472
|
+
// ../cloudflare/src/utils/schema.ts
|
|
143473
|
+
function buildSchemaSql(sql3) {
|
|
143474
|
+
const trimmed = sql3.trim();
|
|
143475
|
+
if (!trimmed) {
|
|
143476
|
+
return "";
|
|
143477
|
+
}
|
|
143478
|
+
return `PRAGMA defer_foreign_keys = on;
|
|
143479
|
+
${trimmed}
|
|
143480
|
+
PRAGMA defer_foreign_keys = off;`;
|
|
143481
|
+
}
|
|
143482
|
+
|
|
143457
143483
|
// ../cloudflare/src/core/namespaces/d1.ts
|
|
143458
143484
|
function createD1Namespace(config2) {
|
|
143459
143485
|
const { client, accountId } = config2;
|
|
@@ -143488,26 +143514,22 @@ function createD1Namespace(config2) {
|
|
|
143488
143514
|
async executeSchema(databaseId, schema4) {
|
|
143489
143515
|
log2.debug("[Cloudflare D1] Executing schema", {
|
|
143490
143516
|
databaseId,
|
|
143491
|
-
schemaHash: schema4.hash
|
|
143517
|
+
schemaHash: schema4.hash,
|
|
143518
|
+
sqlLength: schema4.sql.length
|
|
143492
143519
|
});
|
|
143493
143520
|
try {
|
|
143494
|
-
const
|
|
143495
|
-
|
|
143496
|
-
|
|
143497
|
-
|
|
143498
|
-
|
|
143499
|
-
|
|
143500
|
-
|
|
143501
|
-
|
|
143502
|
-
|
|
143503
|
-
sql: statement
|
|
143504
|
-
});
|
|
143505
|
-
}
|
|
143506
|
-
}
|
|
143521
|
+
const sql3 = buildSchemaSql(schema4.sql);
|
|
143522
|
+
log2.debug("[Cloudflare D1] Executing schema", {
|
|
143523
|
+
databaseId,
|
|
143524
|
+
sql: sql3
|
|
143525
|
+
});
|
|
143526
|
+
await client.d1.database.query(databaseId, {
|
|
143527
|
+
account_id: accountId,
|
|
143528
|
+
sql: sql3
|
|
143529
|
+
});
|
|
143507
143530
|
log2.info("[Cloudflare D1] Schema executed", {
|
|
143508
143531
|
databaseId,
|
|
143509
|
-
schemaHash: schema4.hash
|
|
143510
|
-
statementsExecuted: statements.length
|
|
143532
|
+
schemaHash: schema4.hash
|
|
143511
143533
|
});
|
|
143512
143534
|
} catch (error2) {
|
|
143513
143535
|
log2.error("[Cloudflare D1] Failed to execute schema", {
|
|
@@ -143537,52 +143559,31 @@ function createD1Namespace(config2) {
|
|
|
143537
143559
|
},
|
|
143538
143560
|
async reset(name4) {
|
|
143539
143561
|
log2.debug("[Cloudflare D1] Resetting database", { name: name4 });
|
|
143540
|
-
|
|
143541
|
-
|
|
143542
|
-
|
|
143543
|
-
|
|
143544
|
-
|
|
143545
|
-
|
|
143562
|
+
try {
|
|
143563
|
+
const databases = await client.d1.database.list({ account_id: accountId });
|
|
143564
|
+
for await (const db of databases) {
|
|
143565
|
+
if (db.name === name4 && db.uuid) {
|
|
143566
|
+
log2.debug("[Cloudflare D1] Deleting existing database", {
|
|
143567
|
+
name: name4,
|
|
143568
|
+
uuid: db.uuid
|
|
143569
|
+
});
|
|
143570
|
+
await client.d1.database.delete(db.uuid, { account_id: accountId });
|
|
143571
|
+
log2.info("[Cloudflare D1] Deleted existing database", { name: name4 });
|
|
143572
|
+
break;
|
|
143573
|
+
}
|
|
143546
143574
|
}
|
|
143575
|
+
} catch (error2) {
|
|
143576
|
+
log2.warn("[Cloudflare D1] Failed to delete existing database", { name: name4, error: error2 });
|
|
143547
143577
|
}
|
|
143548
|
-
|
|
143549
|
-
throw new Error(`D1 database not found: ${name4}`);
|
|
143550
|
-
}
|
|
143551
|
-
const tablesResult = await client.d1.database.query(databaseId, {
|
|
143578
|
+
const result = await client.d1.database.create({
|
|
143552
143579
|
account_id: accountId,
|
|
143553
|
-
|
|
143580
|
+
name: name4
|
|
143554
143581
|
});
|
|
143555
|
-
|
|
143556
|
-
|
|
143557
|
-
log2.info("[Cloudflare D1] No tables to drop", { name: name4, databaseId });
|
|
143558
|
-
return;
|
|
143582
|
+
if (!result.uuid) {
|
|
143583
|
+
throw new Error("Database creation succeeded but no UUID returned");
|
|
143559
143584
|
}
|
|
143560
|
-
log2.
|
|
143561
|
-
|
|
143562
|
-
count: tables.length,
|
|
143563
|
-
tables: tables.map((t2) => t2.name)
|
|
143564
|
-
});
|
|
143565
|
-
await client.d1.database.query(databaseId, {
|
|
143566
|
-
account_id: accountId,
|
|
143567
|
-
sql: "PRAGMA defer_foreign_keys = on"
|
|
143568
|
-
});
|
|
143569
|
-
for (const table14 of tables) {
|
|
143570
|
-
if (!table14.name)
|
|
143571
|
-
continue;
|
|
143572
|
-
await client.d1.database.query(databaseId, {
|
|
143573
|
-
account_id: accountId,
|
|
143574
|
-
sql: `DROP TABLE IF EXISTS "${table14.name}"`
|
|
143575
|
-
});
|
|
143576
|
-
}
|
|
143577
|
-
await client.d1.database.query(databaseId, {
|
|
143578
|
-
account_id: accountId,
|
|
143579
|
-
sql: "PRAGMA defer_foreign_keys = off"
|
|
143580
|
-
});
|
|
143581
|
-
log2.info("[Cloudflare D1] Database reset complete", {
|
|
143582
|
-
name: name4,
|
|
143583
|
-
databaseId,
|
|
143584
|
-
tablesDropped: tables.length
|
|
143585
|
-
});
|
|
143585
|
+
log2.info("[Cloudflare D1] Database reset complete", { name: name4, uuid: result.uuid });
|
|
143586
|
+
return result.uuid;
|
|
143586
143587
|
}
|
|
143587
143588
|
};
|
|
143588
143589
|
}
|
|
@@ -144788,6 +144789,56 @@ async function verifyGameAccessBySlug(slug2, user) {
|
|
|
144788
144789
|
function getGameWorkerApiKeyName(slug2) {
|
|
144789
144790
|
return `game-worker-${slug2}`.substring(0, 32);
|
|
144790
144791
|
}
|
|
144792
|
+
// ../api-core/src/utils/secrets-storage.ts
|
|
144793
|
+
var import_client_s32 = __toESM(require_dist_cjs81(), 1);
|
|
144794
|
+
function getSecretsConfig() {
|
|
144795
|
+
const bucketName = process.env.GAME_SECRETS_BUCKET;
|
|
144796
|
+
const masterKey = process.env.GAME_SECRETS_MASTER_KEY;
|
|
144797
|
+
if (!bucketName || !masterKey) {
|
|
144798
|
+
throw new Error("Secrets storage not configured");
|
|
144799
|
+
}
|
|
144800
|
+
const accessKeyId = process.env.CLOUDFLARE_R2_ACCESS_KEY_ID;
|
|
144801
|
+
const secretAccessKey = process.env.CLOUDFLARE_R2_SECRET_ACCESS_KEY;
|
|
144802
|
+
const endpoint = process.env.CLOUDFLARE_R2_DEFAULT_ENDPOINT;
|
|
144803
|
+
if (!accessKeyId || !secretAccessKey || !endpoint) {
|
|
144804
|
+
throw new Error("R2 credentials not configured");
|
|
144805
|
+
}
|
|
144806
|
+
return {
|
|
144807
|
+
bucketName,
|
|
144808
|
+
credentials: {
|
|
144809
|
+
accessKeyId,
|
|
144810
|
+
secretAccessKey,
|
|
144811
|
+
endpoint
|
|
144812
|
+
},
|
|
144813
|
+
masterKey
|
|
144814
|
+
};
|
|
144815
|
+
}
|
|
144816
|
+
function getSecretsKey(gameId) {
|
|
144817
|
+
return `${gameId}.json.enc`;
|
|
144818
|
+
}
|
|
144819
|
+
function createR2Client(config2) {
|
|
144820
|
+
return new import_client_s32.S3Client({
|
|
144821
|
+
region: "auto",
|
|
144822
|
+
endpoint: config2.credentials.endpoint,
|
|
144823
|
+
credentials: {
|
|
144824
|
+
accessKeyId: config2.credentials.accessKeyId,
|
|
144825
|
+
secretAccessKey: config2.credentials.secretAccessKey
|
|
144826
|
+
}
|
|
144827
|
+
});
|
|
144828
|
+
}
|
|
144829
|
+
async function deleteSecrets(gameId, config2) {
|
|
144830
|
+
const client = createR2Client(config2);
|
|
144831
|
+
const key = getSecretsKey(gameId);
|
|
144832
|
+
try {
|
|
144833
|
+
await client.send(new import_client_s32.DeleteObjectCommand({
|
|
144834
|
+
Bucket: config2.bucketName,
|
|
144835
|
+
Key: key
|
|
144836
|
+
}));
|
|
144837
|
+
} catch (error2) {
|
|
144838
|
+
log2.error("[SecretsStorage] Failed to delete secrets", { gameId, error: error2 });
|
|
144839
|
+
throw new Error(`Failed to delete secrets for game ${gameId}`);
|
|
144840
|
+
}
|
|
144841
|
+
}
|
|
144791
144842
|
// src/database/seed/achievements.ts
|
|
144792
144843
|
async function seedAchievements(db) {
|
|
144793
144844
|
const now2 = new Date;
|
|
@@ -144831,6 +144882,30 @@ async function seedCurrencies(db) {
|
|
|
144831
144882
|
}
|
|
144832
144883
|
}
|
|
144833
144884
|
|
|
144885
|
+
// src/lib/logging/adapter.ts
|
|
144886
|
+
var customLogger;
|
|
144887
|
+
function setLogger(logger3) {
|
|
144888
|
+
customLogger = logger3;
|
|
144889
|
+
}
|
|
144890
|
+
function getLogger() {
|
|
144891
|
+
if (customLogger) {
|
|
144892
|
+
return customLogger;
|
|
144893
|
+
}
|
|
144894
|
+
return {
|
|
144895
|
+
info: (msg) => console.log(msg),
|
|
144896
|
+
warn: (msg) => console.warn(msg),
|
|
144897
|
+
error: (msg) => console.error(msg)
|
|
144898
|
+
};
|
|
144899
|
+
}
|
|
144900
|
+
var logger3 = {
|
|
144901
|
+
info: (msg) => {
|
|
144902
|
+
if (customLogger || !config.embedded) {
|
|
144903
|
+
getLogger().info(msg);
|
|
144904
|
+
}
|
|
144905
|
+
},
|
|
144906
|
+
warn: (msg) => getLogger().warn(msg),
|
|
144907
|
+
error: (msg) => getLogger().error(msg)
|
|
144908
|
+
};
|
|
144834
144909
|
// src/database/seed/timeback.ts
|
|
144835
144910
|
function generateMockStudentId(userId) {
|
|
144836
144911
|
return `mock-student-${userId.slice(-8)}`;
|
|
@@ -144895,7 +144970,7 @@ async function seedCoreGames(db) {
|
|
|
144895
144970
|
try {
|
|
144896
144971
|
await db.insert(games).values(gameData).onConflictDoNothing();
|
|
144897
144972
|
} catch (error2) {
|
|
144898
|
-
|
|
144973
|
+
logger3.error(`Error seeding core game '${gameData.slug}': ${error2}`);
|
|
144899
144974
|
}
|
|
144900
144975
|
}
|
|
144901
144976
|
}
|
|
@@ -144936,7 +145011,7 @@ async function seedCurrentProjectGame(db, project) {
|
|
|
144936
145011
|
}
|
|
144937
145012
|
return newGame;
|
|
144938
145013
|
} catch (error2) {
|
|
144939
|
-
|
|
145014
|
+
logger3.error(`❌ Error seeding project game: ${error2}`);
|
|
144940
145015
|
throw error2;
|
|
144941
145016
|
}
|
|
144942
145017
|
}
|
|
@@ -145063,13 +145138,6 @@ async function setupServerDatabase(processedOptions, project) {
|
|
|
145063
145138
|
|
|
145064
145139
|
// src/server/options.ts
|
|
145065
145140
|
var import_json_colorizer = __toESM(require_dist2(), 1);
|
|
145066
|
-
|
|
145067
|
-
// src/lib/logging/adapter.ts
|
|
145068
|
-
var customLogger;
|
|
145069
|
-
function setLogger(logger3) {
|
|
145070
|
-
customLogger = logger3;
|
|
145071
|
-
}
|
|
145072
|
-
// src/server/options.ts
|
|
145073
145141
|
function processServerOptions(_port, options) {
|
|
145074
145142
|
const {
|
|
145075
145143
|
verbose = false,
|
|
@@ -145197,6 +145265,8 @@ manifestRouter.get("/", async (c3) => {
|
|
|
145197
145265
|
{ method: "GET", url: `${baseUrl}/api/notifications/stats/:userId` },
|
|
145198
145266
|
{ method: "POST", url: `${baseUrl}/api/notifications/deliver` },
|
|
145199
145267
|
{ method: "POST", url: `${baseUrl}/api/timeback/populate-student` },
|
|
145268
|
+
{ method: "GET", url: `${baseUrl}/api/timeback/user` },
|
|
145269
|
+
{ method: "GET", url: `${baseUrl}/api/timeback/user/:timebackId` },
|
|
145200
145270
|
{ method: "GET", url: `${baseUrl}/api/timeback/xp/today` },
|
|
145201
145271
|
{ method: "PUT", url: `${baseUrl}/api/timeback/xp/today` },
|
|
145202
145272
|
{ method: "GET", url: `${baseUrl}/api/timeback/xp/total` },
|
|
@@ -150456,6 +150526,10 @@ async function getMockTimebackData(db, timebackId, gameId) {
|
|
|
150456
150526
|
});
|
|
150457
150527
|
return { id: timebackId, role, enrollments, organizations };
|
|
150458
150528
|
}
|
|
150529
|
+
async function getMockTimebackUser(db, gameId) {
|
|
150530
|
+
const timebackId = config.timeback.timebackId || "mock-student-00000001";
|
|
150531
|
+
return getMockTimebackData(db, timebackId, gameId);
|
|
150532
|
+
}
|
|
150459
150533
|
async function buildMockUserResponse(db, user, gameId) {
|
|
150460
150534
|
const timeback3 = user.timebackId ? await getMockTimebackData(db, user.timebackId, gameId) : undefined;
|
|
150461
150535
|
if (gameId) {
|
|
@@ -150586,57 +150660,6 @@ usersRouter.all("/", async (c3) => {
|
|
|
150586
150660
|
const error2 = ApiError.methodNotAllowed("Method not allowed");
|
|
150587
150661
|
return c3.json(createErrorResponse(error2), 405);
|
|
150588
150662
|
});
|
|
150589
|
-
// ../api-core/src/utils/secrets-storage.ts
|
|
150590
|
-
var import_client_s32 = __toESM(require_dist_cjs81(), 1);
|
|
150591
|
-
function getSecretsConfig() {
|
|
150592
|
-
const bucketName = process.env.GAME_SECRETS_BUCKET;
|
|
150593
|
-
const masterKey = process.env.GAME_SECRETS_MASTER_KEY;
|
|
150594
|
-
if (!bucketName || !masterKey) {
|
|
150595
|
-
throw new Error("Secrets storage not configured");
|
|
150596
|
-
}
|
|
150597
|
-
const accessKeyId = process.env.CLOUDFLARE_R2_ACCESS_KEY_ID;
|
|
150598
|
-
const secretAccessKey = process.env.CLOUDFLARE_R2_SECRET_ACCESS_KEY;
|
|
150599
|
-
const endpoint = process.env.CLOUDFLARE_R2_DEFAULT_ENDPOINT;
|
|
150600
|
-
if (!accessKeyId || !secretAccessKey || !endpoint) {
|
|
150601
|
-
throw new Error("R2 credentials not configured");
|
|
150602
|
-
}
|
|
150603
|
-
return {
|
|
150604
|
-
bucketName,
|
|
150605
|
-
credentials: {
|
|
150606
|
-
accessKeyId,
|
|
150607
|
-
secretAccessKey,
|
|
150608
|
-
endpoint
|
|
150609
|
-
},
|
|
150610
|
-
masterKey
|
|
150611
|
-
};
|
|
150612
|
-
}
|
|
150613
|
-
function getSecretsKey(gameId) {
|
|
150614
|
-
return `${gameId}.json.enc`;
|
|
150615
|
-
}
|
|
150616
|
-
function createR2Client(config2) {
|
|
150617
|
-
return new import_client_s32.S3Client({
|
|
150618
|
-
region: "auto",
|
|
150619
|
-
endpoint: config2.credentials.endpoint,
|
|
150620
|
-
credentials: {
|
|
150621
|
-
accessKeyId: config2.credentials.accessKeyId,
|
|
150622
|
-
secretAccessKey: config2.credentials.secretAccessKey
|
|
150623
|
-
}
|
|
150624
|
-
});
|
|
150625
|
-
}
|
|
150626
|
-
async function deleteSecrets(gameId, config2) {
|
|
150627
|
-
const client = createR2Client(config2);
|
|
150628
|
-
const key = getSecretsKey(gameId);
|
|
150629
|
-
try {
|
|
150630
|
-
await client.send(new import_client_s32.DeleteObjectCommand({
|
|
150631
|
-
Bucket: config2.bucketName,
|
|
150632
|
-
Key: key
|
|
150633
|
-
}));
|
|
150634
|
-
} catch (error2) {
|
|
150635
|
-
log2.error("[SecretsStorage] Failed to delete secrets", { gameId, error: error2 });
|
|
150636
|
-
throw new Error(`Failed to delete secrets for game ${gameId}`);
|
|
150637
|
-
}
|
|
150638
|
-
}
|
|
150639
|
-
|
|
150640
150663
|
// ../../node_modules/ulidx/dist/node/index.js
|
|
150641
150664
|
import crypto6 from "node:crypto";
|
|
150642
150665
|
|
|
@@ -153033,6 +153056,7 @@ gameUploadsRouter.post("/uploads/initiate", async (c3) => {
|
|
|
153033
153056
|
// src/routes/platform/games/verify.ts
|
|
153034
153057
|
var gameVerifyRouter = new Hono2;
|
|
153035
153058
|
gameVerifyRouter.post("/verify", async (c3) => {
|
|
153059
|
+
const clonedRequest = c3.req.raw.clone();
|
|
153036
153060
|
const body2 = await c3.req.json().catch(() => ({}));
|
|
153037
153061
|
const token2 = body2?.token;
|
|
153038
153062
|
if (!token2) {
|
|
@@ -153061,11 +153085,43 @@ gameVerifyRouter.post("/verify", async (c3) => {
|
|
|
153061
153085
|
}
|
|
153062
153086
|
});
|
|
153063
153087
|
}
|
|
153088
|
+
if (token2.endsWith(".sandbox")) {
|
|
153089
|
+
try {
|
|
153090
|
+
const parts2 = token2.split(".");
|
|
153091
|
+
if (parts2.length === 3) {
|
|
153092
|
+
const payload = JSON.parse(atob(parts2[1]));
|
|
153093
|
+
const userId = payload.uid;
|
|
153094
|
+
const gameIdOrSlug = payload.sub;
|
|
153095
|
+
const db = c3.get("db");
|
|
153096
|
+
const userData = await db.query.users.findFirst({
|
|
153097
|
+
where: (users2, { eq: eq3 }) => eq3(users2.id, userId)
|
|
153098
|
+
});
|
|
153099
|
+
if (!userData) {
|
|
153100
|
+
return c3.json({ error: "User not found in sandbox" }, 500);
|
|
153101
|
+
}
|
|
153102
|
+
return c3.json({
|
|
153103
|
+
claims: payload,
|
|
153104
|
+
gameId: gameIdOrSlug,
|
|
153105
|
+
user: {
|
|
153106
|
+
sub: userData.id,
|
|
153107
|
+
email: userData.email || "",
|
|
153108
|
+
name: userData.name || userData.username || "",
|
|
153109
|
+
email_verified: true,
|
|
153110
|
+
given_name: undefined,
|
|
153111
|
+
family_name: undefined,
|
|
153112
|
+
timeback_id: userData.timebackId || undefined
|
|
153113
|
+
}
|
|
153114
|
+
});
|
|
153115
|
+
}
|
|
153116
|
+
} catch {
|
|
153117
|
+
return c3.json({ error: "Invalid sandbox token" }, 400);
|
|
153118
|
+
}
|
|
153119
|
+
}
|
|
153064
153120
|
const ctx = {
|
|
153065
153121
|
user: undefined,
|
|
153066
153122
|
params: {},
|
|
153067
153123
|
url: new URL(c3.req.url),
|
|
153068
|
-
request:
|
|
153124
|
+
request: clonedRequest
|
|
153069
153125
|
};
|
|
153070
153126
|
try {
|
|
153071
153127
|
const result = await verifyGameToken(ctx);
|
|
@@ -155212,7 +155268,7 @@ async function deliverNotifications(ctx) {
|
|
|
155212
155268
|
if (!user) {
|
|
155213
155269
|
throw ApiError.unauthorized("Must be logged in to deliver notifications");
|
|
155214
155270
|
}
|
|
155215
|
-
log2.
|
|
155271
|
+
log2.debug("[API] Delivering pending notifications", {
|
|
155216
155272
|
userId: user.id
|
|
155217
155273
|
});
|
|
155218
155274
|
try {
|
|
@@ -155320,6 +155376,58 @@ notificationsRouter.post("/deliver", async (c3) => {
|
|
|
155320
155376
|
return c3.json(createUnknownErrorResponse(error2), 500);
|
|
155321
155377
|
}
|
|
155322
155378
|
});
|
|
155379
|
+
// ../api-core/src/handlers/realtime/token.ts
|
|
155380
|
+
async function generateRealtimeToken(ctx) {
|
|
155381
|
+
const user = ctx.user;
|
|
155382
|
+
const gameId = ctx.params?.gameId;
|
|
155383
|
+
log2.debug("[API] generating realtime token", {
|
|
155384
|
+
userId: user?.id || "anonymous",
|
|
155385
|
+
gameId: gameId || "none"
|
|
155386
|
+
});
|
|
155387
|
+
if (!user) {
|
|
155388
|
+
throw ApiError.unauthorized("Valid session or bearer token required");
|
|
155389
|
+
}
|
|
155390
|
+
try {
|
|
155391
|
+
if (gameId) {
|
|
155392
|
+
const db = getDatabase();
|
|
155393
|
+
const game = await db.query.games.findFirst({
|
|
155394
|
+
where: eq(games.id, gameId),
|
|
155395
|
+
columns: { id: true }
|
|
155396
|
+
});
|
|
155397
|
+
if (!game) {
|
|
155398
|
+
throw ApiError.notFound("Game not found");
|
|
155399
|
+
}
|
|
155400
|
+
}
|
|
155401
|
+
const displayName = user.username || (user.name ? user.name.split(" ")[0] : undefined) || undefined;
|
|
155402
|
+
const token2 = await mintRealtimeJwt(user.id, gameId, displayName, user.role || undefined);
|
|
155403
|
+
return { token: token2 };
|
|
155404
|
+
} catch (error2) {
|
|
155405
|
+
if (error2 instanceof ApiError)
|
|
155406
|
+
throw error2;
|
|
155407
|
+
log2.error("[API /realtime/token] Failed to generate realtime token:", { error: error2 });
|
|
155408
|
+
throw ApiError.internal("Internal server error", error2);
|
|
155409
|
+
}
|
|
155410
|
+
}
|
|
155411
|
+
// src/routes/platform/realtime.ts
|
|
155412
|
+
var realtimeRouter = new Hono2;
|
|
155413
|
+
realtimeRouter.post("/token", async (c3) => {
|
|
155414
|
+
const ctx = {
|
|
155415
|
+
user: c3.get("user"),
|
|
155416
|
+
params: {},
|
|
155417
|
+
url: new URL(c3.req.url),
|
|
155418
|
+
request: c3.req.raw
|
|
155419
|
+
};
|
|
155420
|
+
try {
|
|
155421
|
+
const result = await generateRealtimeToken(ctx);
|
|
155422
|
+
return c3.json(result);
|
|
155423
|
+
} catch (error2) {
|
|
155424
|
+
if (error2 instanceof ApiError) {
|
|
155425
|
+
return c3.json(createErrorResponse(error2), error2.statusCode);
|
|
155426
|
+
}
|
|
155427
|
+
console.error("Error in realtime/token:", error2);
|
|
155428
|
+
return c3.json(createUnknownErrorResponse(error2), 500);
|
|
155429
|
+
}
|
|
155430
|
+
});
|
|
155323
155431
|
// ../api-core/src/utils/timeback-errors.ts
|
|
155324
155432
|
function isTimebackApiError(error2) {
|
|
155325
155433
|
return typeof error2 === "object" && error2 !== null && "name" in error2 && error2.name === "TimebackApiError" && "status" in error2 && "details" in error2;
|
|
@@ -155854,24 +155962,59 @@ async function getTimeBackXpHistory(ctx) {
|
|
|
155854
155962
|
throw ApiError.internal("Failed to get TimeBack XP history", error2);
|
|
155855
155963
|
}
|
|
155856
155964
|
}
|
|
155857
|
-
// ../api-core/src/handlers/timeback/
|
|
155858
|
-
async function
|
|
155965
|
+
// ../api-core/src/handlers/timeback/user.ts
|
|
155966
|
+
async function getTimebackUser(ctx) {
|
|
155967
|
+
const user = ctx.user;
|
|
155968
|
+
if (!user) {
|
|
155969
|
+
throw ApiError.unauthorized("Must be logged in to get timeback data");
|
|
155970
|
+
}
|
|
155971
|
+
const db = getDatabase();
|
|
155972
|
+
const userData = await db.query.users.findFirst({
|
|
155973
|
+
where: eq(users.id, user.id)
|
|
155974
|
+
});
|
|
155975
|
+
if (!userData) {
|
|
155976
|
+
throw ApiError.notFound("User not found");
|
|
155977
|
+
}
|
|
155978
|
+
if (!userData.timebackId) {
|
|
155979
|
+
throw ApiError.notFound("User does not have a TimeBack account");
|
|
155980
|
+
}
|
|
155981
|
+
log2.debug("[API] Getting timeback user data", {
|
|
155982
|
+
userId: user.id,
|
|
155983
|
+
timebackId: userData.timebackId,
|
|
155984
|
+
gameId: ctx.gameId
|
|
155985
|
+
});
|
|
155986
|
+
const timeback3 = await fetchUserTimebackData(userData.timebackId, ctx.gameId);
|
|
155987
|
+
log2.info("[API] Retrieved timeback user data", {
|
|
155988
|
+
userId: user.id,
|
|
155989
|
+
timebackId: userData.timebackId,
|
|
155990
|
+
role: timeback3.role,
|
|
155991
|
+
enrollmentCount: timeback3.enrollments.length,
|
|
155992
|
+
organizationCount: timeback3.organizations.length
|
|
155993
|
+
});
|
|
155994
|
+
return timeback3;
|
|
155995
|
+
}
|
|
155996
|
+
async function getTimebackUserById(ctx) {
|
|
155859
155997
|
const user = ctx.user;
|
|
155860
155998
|
if (!user) {
|
|
155861
|
-
throw ApiError.unauthorized("Must be logged in to get
|
|
155999
|
+
throw ApiError.unauthorized("Must be logged in to get timeback data");
|
|
155862
156000
|
}
|
|
155863
156001
|
const timebackId = ctx.params.timebackId;
|
|
155864
156002
|
if (!timebackId) {
|
|
155865
156003
|
throw ApiError.badRequest("Missing timebackId parameter");
|
|
155866
156004
|
}
|
|
155867
|
-
log2.debug("[API] Getting
|
|
155868
|
-
|
|
155869
|
-
|
|
155870
|
-
|
|
156005
|
+
log2.debug("[API] Getting timeback user by ID", {
|
|
156006
|
+
requesterId: user.id,
|
|
156007
|
+
timebackId
|
|
156008
|
+
});
|
|
156009
|
+
const timeback3 = await fetchUserTimebackData(timebackId);
|
|
156010
|
+
log2.info("[API] Retrieved timeback user by ID", {
|
|
156011
|
+
requesterId: user.id,
|
|
155871
156012
|
timebackId,
|
|
155872
|
-
|
|
156013
|
+
role: timeback3.role,
|
|
156014
|
+
enrollmentCount: timeback3.enrollments.length,
|
|
156015
|
+
organizationCount: timeback3.organizations.length
|
|
155873
156016
|
});
|
|
155874
|
-
return
|
|
156017
|
+
return timeback3;
|
|
155875
156018
|
}
|
|
155876
156019
|
// src/routes/integrations/timeback.ts
|
|
155877
156020
|
var timebackRouter = new Hono2;
|
|
@@ -156064,32 +156207,62 @@ timebackRouter.post("/end-activity", async (c3) => {
|
|
|
156064
156207
|
return c3.json(createUnknownErrorResponse(error2), 500);
|
|
156065
156208
|
}
|
|
156066
156209
|
});
|
|
156067
|
-
timebackRouter.get("/
|
|
156210
|
+
timebackRouter.get("/user", async (c3) => {
|
|
156211
|
+
const user2 = c3.get("user");
|
|
156212
|
+
const gameId = c3.get("gameId");
|
|
156213
|
+
if (!user2) {
|
|
156214
|
+
const error2 = ApiError.unauthorized("Must be logged in to get timeback data");
|
|
156215
|
+
return c3.json(createErrorResponse(error2), error2.statusCode);
|
|
156216
|
+
}
|
|
156217
|
+
try {
|
|
156218
|
+
if (shouldMockTimeback()) {
|
|
156219
|
+
const db = c3.get("db");
|
|
156220
|
+
const timeback3 = await getMockTimebackUser(db, gameId);
|
|
156221
|
+
return c3.json(timeback3);
|
|
156222
|
+
}
|
|
156223
|
+
const ctx = {
|
|
156224
|
+
user: user2,
|
|
156225
|
+
params: {},
|
|
156226
|
+
url: new URL(c3.req.url),
|
|
156227
|
+
request: c3.req.raw,
|
|
156228
|
+
gameId
|
|
156229
|
+
};
|
|
156230
|
+
const result = await getTimebackUser(ctx);
|
|
156231
|
+
return c3.json(result);
|
|
156232
|
+
} catch (error2) {
|
|
156233
|
+
if (error2 instanceof ApiError) {
|
|
156234
|
+
return c3.json(createErrorResponse(error2), error2.statusCode);
|
|
156235
|
+
}
|
|
156236
|
+
console.error("Error in getTimebackUser:", error2);
|
|
156237
|
+
return c3.json(createUnknownErrorResponse(error2), 500);
|
|
156238
|
+
}
|
|
156239
|
+
});
|
|
156240
|
+
timebackRouter.get("/user/:timebackId", async (c3) => {
|
|
156068
156241
|
const timebackId = c3.req.param("timebackId");
|
|
156069
|
-
const
|
|
156070
|
-
if (!
|
|
156071
|
-
const error2 = ApiError.unauthorized("Must be logged in to get
|
|
156242
|
+
const user2 = c3.get("user");
|
|
156243
|
+
if (!user2) {
|
|
156244
|
+
const error2 = ApiError.unauthorized("Must be logged in to get timeback data");
|
|
156072
156245
|
return c3.json(createErrorResponse(error2), error2.statusCode);
|
|
156073
156246
|
}
|
|
156074
156247
|
try {
|
|
156075
156248
|
if (shouldMockTimeback()) {
|
|
156076
156249
|
const db = c3.get("db");
|
|
156077
|
-
const
|
|
156078
|
-
return c3.json(
|
|
156250
|
+
const timeback3 = await getMockTimebackUser(db);
|
|
156251
|
+
return c3.json(timeback3);
|
|
156079
156252
|
}
|
|
156080
156253
|
const ctx = {
|
|
156081
|
-
user,
|
|
156254
|
+
user: user2,
|
|
156082
156255
|
params: { timebackId },
|
|
156083
156256
|
url: new URL(c3.req.url),
|
|
156084
156257
|
request: c3.req.raw
|
|
156085
156258
|
};
|
|
156086
|
-
const result = await
|
|
156259
|
+
const result = await getTimebackUserById(ctx);
|
|
156087
156260
|
return c3.json(result);
|
|
156088
156261
|
} catch (error2) {
|
|
156089
156262
|
if (error2 instanceof ApiError) {
|
|
156090
156263
|
return c3.json(createErrorResponse(error2), error2.statusCode);
|
|
156091
156264
|
}
|
|
156092
|
-
console.error("Error in
|
|
156265
|
+
console.error("Error in getTimebackUserById:", error2);
|
|
156093
156266
|
return c3.json(createUnknownErrorResponse(error2), 500);
|
|
156094
156267
|
}
|
|
156095
156268
|
});
|
|
@@ -156126,29 +156299,29 @@ async function provisionUserFromLti(claims) {
|
|
|
156126
156299
|
where: and(eq(accounts.accountId, ltiTimebackId), eq(accounts.providerId, providerId))
|
|
156127
156300
|
});
|
|
156128
156301
|
if (existingAccount) {
|
|
156129
|
-
const
|
|
156302
|
+
const user3 = await db.query.users.findFirst({
|
|
156130
156303
|
where: eq(users.id, existingAccount.userId)
|
|
156131
156304
|
});
|
|
156132
|
-
if (
|
|
156305
|
+
if (user3) {
|
|
156133
156306
|
log2.info("[lti-timeback] Found existing user by LTI account linkage", {
|
|
156134
|
-
userId:
|
|
156307
|
+
userId: user3.id,
|
|
156135
156308
|
ltiTimebackId
|
|
156136
156309
|
});
|
|
156137
|
-
return
|
|
156310
|
+
return user3;
|
|
156138
156311
|
}
|
|
156139
156312
|
}
|
|
156140
|
-
const
|
|
156313
|
+
const user2 = await db.query.users.findFirst({
|
|
156141
156314
|
where: eq(users.email, email)
|
|
156142
156315
|
});
|
|
156143
|
-
if (
|
|
156316
|
+
if (user2) {
|
|
156144
156317
|
await db.transaction(async (tx) => {
|
|
156145
156318
|
const existingLtiAccount = await tx.query.accounts.findFirst({
|
|
156146
|
-
where: and(eq(accounts.userId,
|
|
156319
|
+
where: and(eq(accounts.userId, user2.id), eq(accounts.providerId, providerId))
|
|
156147
156320
|
});
|
|
156148
156321
|
if (!existingLtiAccount) {
|
|
156149
156322
|
await tx.insert(accounts).values({
|
|
156150
156323
|
id: crypto7.randomUUID(),
|
|
156151
|
-
userId:
|
|
156324
|
+
userId: user2.id,
|
|
156152
156325
|
accountId: ltiTimebackId,
|
|
156153
156326
|
providerId,
|
|
156154
156327
|
accessToken: null,
|
|
@@ -156159,14 +156332,14 @@ async function provisionUserFromLti(claims) {
|
|
|
156159
156332
|
updatedAt: new Date
|
|
156160
156333
|
});
|
|
156161
156334
|
log2.info("[lti-timeback] Linked existing user to LTI provider", {
|
|
156162
|
-
userId:
|
|
156163
|
-
email:
|
|
156335
|
+
userId: user2.id,
|
|
156336
|
+
email: user2.email,
|
|
156164
156337
|
ltiTimebackId,
|
|
156165
156338
|
note: "User may have different TimeBack ID from OAuth flow"
|
|
156166
156339
|
});
|
|
156167
156340
|
}
|
|
156168
156341
|
});
|
|
156169
|
-
return
|
|
156342
|
+
return user2;
|
|
156170
156343
|
}
|
|
156171
156344
|
const newUserId = crypto7.randomUUID();
|
|
156172
156345
|
const createdUser = await db.transaction(async (tx) => {
|
|
@@ -156209,10 +156382,10 @@ async function processLtiLaunch(idToken) {
|
|
|
156209
156382
|
try {
|
|
156210
156383
|
const claims = await verifyLtiToken(idToken);
|
|
156211
156384
|
validateLtiClaims(claims);
|
|
156212
|
-
const
|
|
156385
|
+
const user2 = await provisionUserFromLti(claims);
|
|
156213
156386
|
const targetUri = claims["https://purl.imsglobal.org/spec/lti/claim/target_link_uri"];
|
|
156214
156387
|
return {
|
|
156215
|
-
user,
|
|
156388
|
+
user: user2,
|
|
156216
156389
|
targetUri,
|
|
156217
156390
|
claims
|
|
156218
156391
|
};
|
|
@@ -156232,45 +156405,45 @@ async function processTimeBackLtiLaunch(ctx) {
|
|
|
156232
156405
|
}
|
|
156233
156406
|
const currentHost = ctx.url.hostname;
|
|
156234
156407
|
const launchResult = await processLtiLaunch(idToken);
|
|
156235
|
-
const { user, targetUri, claims } = launchResult;
|
|
156408
|
+
const { user: user2, targetUri, claims } = launchResult;
|
|
156236
156409
|
log2.info("[lti-timeback] User roles", {
|
|
156237
|
-
userId:
|
|
156410
|
+
userId: user2.id,
|
|
156238
156411
|
isLearner: LtiRoleChecks.isLearner(claims),
|
|
156239
156412
|
isInstructor: LtiRoleChecks.isInstructor(claims),
|
|
156240
156413
|
isAdministrator: LtiRoleChecks.isAdministrator(claims),
|
|
156241
156414
|
allRoles: claims["https://purl.imsglobal.org/spec/lti/claim/roles"]
|
|
156242
156415
|
});
|
|
156243
|
-
const sessionToken = await createLtiSession(
|
|
156416
|
+
const sessionToken = await createLtiSession(user2.id);
|
|
156244
156417
|
const redirectPath = extractRedirectPath(targetUri, currentHost);
|
|
156245
156418
|
log2.info("[lti-timeback] LTI launch successful", {
|
|
156246
|
-
userId:
|
|
156419
|
+
userId: user2.id,
|
|
156247
156420
|
redirectPath
|
|
156248
156421
|
});
|
|
156249
156422
|
return {
|
|
156250
|
-
user,
|
|
156423
|
+
user: user2,
|
|
156251
156424
|
redirectPath,
|
|
156252
156425
|
sessionToken
|
|
156253
156426
|
};
|
|
156254
156427
|
}
|
|
156255
156428
|
async function getTimeBackLtiStatus(ctx) {
|
|
156256
|
-
const
|
|
156257
|
-
if (!
|
|
156429
|
+
const user2 = ctx.user;
|
|
156430
|
+
if (!user2) {
|
|
156258
156431
|
throw ApiError.unauthorized("Must be logged in");
|
|
156259
156432
|
}
|
|
156260
156433
|
const db = getDatabase();
|
|
156261
156434
|
const ltiAccount = await db.query.accounts.findFirst({
|
|
156262
|
-
where: and(eq(accounts.userId,
|
|
156435
|
+
where: and(eq(accounts.userId, user2.id), eq(accounts.providerId, AUTH_PROVIDER_IDS.TIMEBACK_LTI))
|
|
156263
156436
|
});
|
|
156264
156437
|
const oauthAccount = await db.query.accounts.findFirst({
|
|
156265
|
-
where: and(eq(accounts.userId,
|
|
156438
|
+
where: and(eq(accounts.userId, user2.id), eq(accounts.providerId, AUTH_PROVIDER_IDS.TIMEBACK))
|
|
156266
156439
|
});
|
|
156267
156440
|
return {
|
|
156268
156441
|
hasLtiAccount: !!ltiAccount,
|
|
156269
156442
|
ltiTimebackId: ltiAccount?.accountId || undefined,
|
|
156270
156443
|
hasOAuthAccount: !!oauthAccount,
|
|
156271
|
-
oauthTimebackId: oauthAccount?.accountId ||
|
|
156272
|
-
email:
|
|
156273
|
-
userId:
|
|
156444
|
+
oauthTimebackId: oauthAccount?.accountId || user2.timebackId || undefined,
|
|
156445
|
+
email: user2.email,
|
|
156446
|
+
userId: user2.id
|
|
156274
156447
|
};
|
|
156275
156448
|
}
|
|
156276
156449
|
|
|
@@ -156309,20 +156482,20 @@ ltiRouter.all("/launch", async (c3) => {
|
|
|
156309
156482
|
}
|
|
156310
156483
|
});
|
|
156311
156484
|
ltiRouter.get("/status", async (c3) => {
|
|
156312
|
-
let
|
|
156485
|
+
let user2 = undefined;
|
|
156313
156486
|
const authHeader = c3.req.header("Authorization");
|
|
156314
156487
|
if (authHeader?.startsWith("Bearer ")) {
|
|
156315
156488
|
const token2 = authHeader.substring(7);
|
|
156316
156489
|
const demoUser = DEMO_TOKENS[token2];
|
|
156317
156490
|
if (demoUser) {
|
|
156318
156491
|
const db = c3.get("db");
|
|
156319
|
-
|
|
156492
|
+
user2 = await db.query.users.findFirst({
|
|
156320
156493
|
where: eq(users.id, demoUser.id)
|
|
156321
156494
|
});
|
|
156322
156495
|
}
|
|
156323
156496
|
}
|
|
156324
156497
|
const ctx = {
|
|
156325
|
-
user,
|
|
156498
|
+
user: user2,
|
|
156326
156499
|
params: {},
|
|
156327
156500
|
url: new URL(c3.req.url),
|
|
156328
156501
|
request: c3.req.raw
|
|
@@ -156357,6 +156530,7 @@ function registerRoutes(app) {
|
|
|
156357
156530
|
app.route("/api/sprites", spriteRouter);
|
|
156358
156531
|
app.route("/api/achievements", achievementsRouter);
|
|
156359
156532
|
app.route("/api/notifications", notificationsRouter);
|
|
156533
|
+
app.route("/api/realtime", realtimeRouter);
|
|
156360
156534
|
app.route("/api/dev", devRouter);
|
|
156361
156535
|
app.route("/api/timeback", timebackRouter);
|
|
156362
156536
|
app.route("/api/lti", ltiRouter);
|