@playcademy/sandbox 0.2.3 → 0.3.1
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/README.md +20 -16
- package/dist/cli.js +817 -584
- package/dist/config.d.ts +38 -2
- package/dist/config.js +31 -5
- package/dist/constants.d.ts +193 -0
- package/dist/constants.js +231 -0
- package/dist/mocks/timeback.d.ts +30 -0
- package/dist/server.d.ts +21 -2
- package/dist/server.js +796 -461
- package/package.json +5 -1
package/dist/server.js
CHANGED
|
@@ -6388,7 +6388,7 @@ If you have no idea what this means or what Pirates is, let me explain: Pirates
|
|
|
6388
6388
|
resolve2(data);
|
|
6389
6389
|
});
|
|
6390
6390
|
});
|
|
6391
|
-
var
|
|
6391
|
+
var readFileSync2 = (fp) => {
|
|
6392
6392
|
return _fs.default.readFileSync(fp, "utf8");
|
|
6393
6393
|
};
|
|
6394
6394
|
var pathExists = (fp) => new Promise((resolve2) => {
|
|
@@ -6619,7 +6619,7 @@ If you have no idea what this means or what Pirates is, let me explain: Pirates
|
|
|
6619
6619
|
data: this.packageJsonCache.get(filepath)[options.packageKey]
|
|
6620
6620
|
};
|
|
6621
6621
|
}
|
|
6622
|
-
const data = this.options.parseJSON(
|
|
6622
|
+
const data = this.options.parseJSON(readFileSync2(filepath));
|
|
6623
6623
|
return {
|
|
6624
6624
|
path: filepath,
|
|
6625
6625
|
data
|
|
@@ -6627,7 +6627,7 @@ If you have no idea what this means or what Pirates is, let me explain: Pirates
|
|
|
6627
6627
|
}
|
|
6628
6628
|
return {
|
|
6629
6629
|
path: filepath,
|
|
6630
|
-
data:
|
|
6630
|
+
data: readFileSync2(filepath)
|
|
6631
6631
|
};
|
|
6632
6632
|
}
|
|
6633
6633
|
return {};
|
|
@@ -8226,19 +8226,19 @@ If you have no idea what this means or what Pirates is, let me explain: Pirates
|
|
|
8226
8226
|
return walkForTsConfig(parentDirectory, readdirSync);
|
|
8227
8227
|
}
|
|
8228
8228
|
exports2.walkForTsConfig = walkForTsConfig;
|
|
8229
|
-
function loadTsconfig(configFilePath,
|
|
8230
|
-
if (
|
|
8231
|
-
|
|
8229
|
+
function loadTsconfig(configFilePath, existsSync3, readFileSync2) {
|
|
8230
|
+
if (existsSync3 === undefined) {
|
|
8231
|
+
existsSync3 = fs3.existsSync;
|
|
8232
8232
|
}
|
|
8233
|
-
if (
|
|
8234
|
-
|
|
8233
|
+
if (readFileSync2 === undefined) {
|
|
8234
|
+
readFileSync2 = function(filename) {
|
|
8235
8235
|
return fs3.readFileSync(filename, "utf8");
|
|
8236
8236
|
};
|
|
8237
8237
|
}
|
|
8238
|
-
if (!
|
|
8238
|
+
if (!existsSync3(configFilePath)) {
|
|
8239
8239
|
return;
|
|
8240
8240
|
}
|
|
8241
|
-
var configString =
|
|
8241
|
+
var configString = readFileSync2(configFilePath);
|
|
8242
8242
|
var cleanedJson = StripBom(configString);
|
|
8243
8243
|
var config2;
|
|
8244
8244
|
try {
|
|
@@ -8251,27 +8251,27 @@ If you have no idea what this means or what Pirates is, let me explain: Pirates
|
|
|
8251
8251
|
var base = undefined;
|
|
8252
8252
|
if (Array.isArray(extendedConfig)) {
|
|
8253
8253
|
base = extendedConfig.reduce(function(currBase, extendedConfigElement) {
|
|
8254
|
-
return mergeTsconfigs(currBase, loadTsconfigFromExtends(configFilePath, extendedConfigElement,
|
|
8254
|
+
return mergeTsconfigs(currBase, loadTsconfigFromExtends(configFilePath, extendedConfigElement, existsSync3, readFileSync2));
|
|
8255
8255
|
}, {});
|
|
8256
8256
|
} else {
|
|
8257
|
-
base = loadTsconfigFromExtends(configFilePath, extendedConfig,
|
|
8257
|
+
base = loadTsconfigFromExtends(configFilePath, extendedConfig, existsSync3, readFileSync2);
|
|
8258
8258
|
}
|
|
8259
8259
|
return mergeTsconfigs(base, config2);
|
|
8260
8260
|
}
|
|
8261
8261
|
return config2;
|
|
8262
8262
|
}
|
|
8263
8263
|
exports2.loadTsconfig = loadTsconfig;
|
|
8264
|
-
function loadTsconfigFromExtends(configFilePath, extendedConfigValue,
|
|
8264
|
+
function loadTsconfigFromExtends(configFilePath, extendedConfigValue, existsSync3, readFileSync2) {
|
|
8265
8265
|
var _a;
|
|
8266
8266
|
if (typeof extendedConfigValue === "string" && extendedConfigValue.indexOf(".json") === -1) {
|
|
8267
8267
|
extendedConfigValue += ".json";
|
|
8268
8268
|
}
|
|
8269
8269
|
var currentDir = path.dirname(configFilePath);
|
|
8270
8270
|
var extendedConfigPath = path.join(currentDir, extendedConfigValue);
|
|
8271
|
-
if (extendedConfigValue.indexOf("/") !== -1 && extendedConfigValue.indexOf(".") !== -1 && !
|
|
8271
|
+
if (extendedConfigValue.indexOf("/") !== -1 && extendedConfigValue.indexOf(".") !== -1 && !existsSync3(extendedConfigPath)) {
|
|
8272
8272
|
extendedConfigPath = path.join(currentDir, "node_modules", extendedConfigValue);
|
|
8273
8273
|
}
|
|
8274
|
-
var config2 = loadTsconfig(extendedConfigPath,
|
|
8274
|
+
var config2 = loadTsconfig(extendedConfigPath, existsSync3, readFileSync2) || {};
|
|
8275
8275
|
if ((_a = config2.compilerOptions) === null || _a === undefined ? undefined : _a.baseUrl) {
|
|
8276
8276
|
var extendsDir = path.dirname(extendedConfigValue);
|
|
8277
8277
|
config2.compilerOptions.baseUrl = path.join(extendsDir, config2.compilerOptions.baseUrl);
|
|
@@ -32321,7 +32321,7 @@ globstar while`, file, fr, pattern, pr2, swallowee);
|
|
|
32321
32321
|
return new SQL2([new StringChunk2(str)]);
|
|
32322
32322
|
}
|
|
32323
32323
|
sql22.raw = raw2;
|
|
32324
|
-
function
|
|
32324
|
+
function join4(chunks, separator) {
|
|
32325
32325
|
const result = [];
|
|
32326
32326
|
for (const [i3, chunk] of chunks.entries()) {
|
|
32327
32327
|
if (i3 > 0 && separator !== undefined) {
|
|
@@ -32331,7 +32331,7 @@ globstar while`, file, fr, pattern, pr2, swallowee);
|
|
|
32331
32331
|
}
|
|
32332
32332
|
return new SQL2(result);
|
|
32333
32333
|
}
|
|
32334
|
-
sql22.join =
|
|
32334
|
+
sql22.join = join4;
|
|
32335
32335
|
function identifier(value) {
|
|
32336
32336
|
return new Name2(value);
|
|
32337
32337
|
}
|
|
@@ -35161,7 +35161,7 @@ params: ${params}`);
|
|
|
35161
35161
|
const tableName = getTableLikeName2(table62);
|
|
35162
35162
|
for (const item of extractUsedTable(table62))
|
|
35163
35163
|
this.usedTables.add(item);
|
|
35164
|
-
if (typeof tableName === "string" && this.config.joins?.some((
|
|
35164
|
+
if (typeof tableName === "string" && this.config.joins?.some((join4) => join4.alias === tableName)) {
|
|
35165
35165
|
throw new Error(`Alias "${tableName}" is already used in this query`);
|
|
35166
35166
|
}
|
|
35167
35167
|
if (!this.isPartialSelect) {
|
|
@@ -36019,7 +36019,7 @@ params: ${params}`);
|
|
|
36019
36019
|
createJoin(joinType) {
|
|
36020
36020
|
return (table62, on2) => {
|
|
36021
36021
|
const tableName = getTableLikeName2(table62);
|
|
36022
|
-
if (typeof tableName === "string" && this.config.joins.some((
|
|
36022
|
+
if (typeof tableName === "string" && this.config.joins.some((join4) => join4.alias === tableName)) {
|
|
36023
36023
|
throw new Error(`Alias "${tableName}" is already used in this query`);
|
|
36024
36024
|
}
|
|
36025
36025
|
if (typeof on2 === "function") {
|
|
@@ -36065,10 +36065,10 @@ params: ${params}`);
|
|
|
36065
36065
|
const fromFields = this.getTableLikeFields(this.config.from);
|
|
36066
36066
|
fields[tableName] = fromFields;
|
|
36067
36067
|
}
|
|
36068
|
-
for (const
|
|
36069
|
-
const tableName2 = getTableLikeName2(
|
|
36070
|
-
if (typeof tableName2 === "string" && !is2(
|
|
36071
|
-
const fromFields = this.getTableLikeFields(
|
|
36068
|
+
for (const join4 of this.config.joins) {
|
|
36069
|
+
const tableName2 = getTableLikeName2(join4.table);
|
|
36070
|
+
if (typeof tableName2 === "string" && !is2(join4.table, SQL2)) {
|
|
36071
|
+
const fromFields = this.getTableLikeFields(join4.table);
|
|
36072
36072
|
fields[tableName2] = fromFields;
|
|
36073
36073
|
}
|
|
36074
36074
|
}
|
|
@@ -39643,7 +39643,7 @@ ORDER BY
|
|
|
39643
39643
|
const tableName = getTableLikeName2(table62);
|
|
39644
39644
|
for (const item of extractUsedTable2(table62))
|
|
39645
39645
|
this.usedTables.add(item);
|
|
39646
|
-
if (typeof tableName === "string" && this.config.joins?.some((
|
|
39646
|
+
if (typeof tableName === "string" && this.config.joins?.some((join4) => join4.alias === tableName)) {
|
|
39647
39647
|
throw new Error(`Alias "${tableName}" is already used in this query`);
|
|
39648
39648
|
}
|
|
39649
39649
|
if (!this.isPartialSelect) {
|
|
@@ -40084,7 +40084,7 @@ ORDER BY
|
|
|
40084
40084
|
createJoin(joinType) {
|
|
40085
40085
|
return (table62, on2) => {
|
|
40086
40086
|
const tableName = getTableLikeName2(table62);
|
|
40087
|
-
if (typeof tableName === "string" && this.config.joins.some((
|
|
40087
|
+
if (typeof tableName === "string" && this.config.joins.some((join4) => join4.alias === tableName)) {
|
|
40088
40088
|
throw new Error(`Alias "${tableName}" is already used in this query`);
|
|
40089
40089
|
}
|
|
40090
40090
|
if (typeof on2 === "function") {
|
|
@@ -43691,7 +43691,7 @@ ${withStyle.errorWarning(`We've found duplicated view name across ${source_defau
|
|
|
43691
43691
|
const tableName = getTableLikeName2(table62);
|
|
43692
43692
|
for (const item of extractUsedTable3(table62))
|
|
43693
43693
|
this.usedTables.add(item);
|
|
43694
|
-
if (typeof tableName === "string" && this.config.joins?.some((
|
|
43694
|
+
if (typeof tableName === "string" && this.config.joins?.some((join4) => join4.alias === tableName)) {
|
|
43695
43695
|
throw new Error(`Alias "${tableName}" is already used in this query`);
|
|
43696
43696
|
}
|
|
43697
43697
|
if (!this.isPartialSelect) {
|
|
@@ -47733,7 +47733,7 @@ AND
|
|
|
47733
47733
|
const tableName = getTableLikeName2(table62);
|
|
47734
47734
|
for (const item of extractUsedTable4(table62))
|
|
47735
47735
|
this.usedTables.add(item);
|
|
47736
|
-
if (typeof tableName === "string" && this.config.joins?.some((
|
|
47736
|
+
if (typeof tableName === "string" && this.config.joins?.some((join4) => join4.alias === tableName)) {
|
|
47737
47737
|
throw new Error(`Alias "${tableName}" is already used in this query`);
|
|
47738
47738
|
}
|
|
47739
47739
|
if (!this.isPartialSelect) {
|
|
@@ -133161,7 +133161,7 @@ function hasTimebackCredentials() {
|
|
|
133161
133161
|
return false;
|
|
133162
133162
|
}
|
|
133163
133163
|
function hasTimebackFullConfig() {
|
|
133164
|
-
return hasTimebackCredentials() && !!(config.timeback.courseId && config.timeback.
|
|
133164
|
+
return hasTimebackCredentials() && !!(config.timeback.courseId && config.timeback.timebackId);
|
|
133165
133165
|
}
|
|
133166
133166
|
function requireTimebackCredentials() {
|
|
133167
133167
|
if (hasTimebackCredentials())
|
|
@@ -133215,10 +133215,35 @@ function configureTimeback(options) {
|
|
|
133215
133215
|
config.timeback.courseId = options.courseId;
|
|
133216
133216
|
process.env.SANDBOX_TIMEBACK_COURSE_ID = options.courseId;
|
|
133217
133217
|
}
|
|
133218
|
-
if (options.
|
|
133219
|
-
config.timeback.
|
|
133220
|
-
process.env.SANDBOX_TIMEBACK_STUDENT_ID = options.
|
|
133218
|
+
if (options.timebackId) {
|
|
133219
|
+
config.timeback.timebackId = options.timebackId;
|
|
133220
|
+
process.env.SANDBOX_TIMEBACK_STUDENT_ID = options.timebackId;
|
|
133221
|
+
const isMockMode = options.timebackId === "mock";
|
|
133222
|
+
process.env.SANDBOX_TIMEBACK_MOCK_MODE = isMockMode ? "true" : "false";
|
|
133221
133223
|
}
|
|
133224
|
+
if (options.organization) {
|
|
133225
|
+
config.timeback.organization = options.organization;
|
|
133226
|
+
process.env.SANDBOX_TIMEBACK_ORG_ID = options.organization.id;
|
|
133227
|
+
if (options.organization.name) {
|
|
133228
|
+
process.env.SANDBOX_TIMEBACK_ORG_NAME = options.organization.name;
|
|
133229
|
+
}
|
|
133230
|
+
if (options.organization.type) {
|
|
133231
|
+
process.env.SANDBOX_TIMEBACK_ORG_TYPE = options.organization.type;
|
|
133232
|
+
}
|
|
133233
|
+
}
|
|
133234
|
+
if (options.role) {
|
|
133235
|
+
config.timeback.role = options.role;
|
|
133236
|
+
process.env.SANDBOX_TIMEBACK_ROLE = options.role;
|
|
133237
|
+
}
|
|
133238
|
+
}
|
|
133239
|
+
function getTimebackDisplayMode() {
|
|
133240
|
+
const { timebackId, mode } = config.timeback;
|
|
133241
|
+
if (!timebackId)
|
|
133242
|
+
return null;
|
|
133243
|
+
if (timebackId === "mock" || !hasTimebackCredentials()) {
|
|
133244
|
+
return "mock";
|
|
133245
|
+
}
|
|
133246
|
+
return mode;
|
|
133222
133247
|
}
|
|
133223
133248
|
|
|
133224
133249
|
// src/config/mutators.ts
|
|
@@ -133242,13 +133267,203 @@ var config = {
|
|
|
133242
133267
|
clientSecret: process.env.TIMEBACK_API_CLIENT_SECRET,
|
|
133243
133268
|
authUrl: process.env.TIMEBACK_API_AUTH_URL,
|
|
133244
133269
|
courseId: process.env.SANDBOX_TIMEBACK_COURSE_ID,
|
|
133245
|
-
|
|
133270
|
+
timebackId: process.env.SANDBOX_TIMEBACK_STUDENT_ID
|
|
133246
133271
|
}
|
|
133247
133272
|
};
|
|
133248
133273
|
process.env.BETTER_AUTH_SECRET = config.auth.betterAuthSecret;
|
|
133249
133274
|
process.env.GAME_JWT_SECRET = config.auth.gameJwtSecret;
|
|
133250
133275
|
process.env.PUBLIC_IS_LOCAL = "true";
|
|
133251
133276
|
|
|
133277
|
+
// src/constants/demo-users.ts
|
|
133278
|
+
var now = new Date;
|
|
133279
|
+
var DEMO_USER_IDS = {
|
|
133280
|
+
player: "00000000-0000-0000-0000-000000000001",
|
|
133281
|
+
developer: "00000000-0000-0000-0000-000000000002",
|
|
133282
|
+
admin: "00000000-0000-0000-0000-000000000003",
|
|
133283
|
+
pendingDeveloper: "00000000-0000-0000-0000-000000000004",
|
|
133284
|
+
unverifiedPlayer: "00000000-0000-0000-0000-000000000005"
|
|
133285
|
+
};
|
|
133286
|
+
var DEMO_USERS = {
|
|
133287
|
+
admin: {
|
|
133288
|
+
id: DEMO_USER_IDS.admin,
|
|
133289
|
+
name: "Admin User",
|
|
133290
|
+
username: "admin_user",
|
|
133291
|
+
email: "admin@playcademy.com",
|
|
133292
|
+
emailVerified: true,
|
|
133293
|
+
image: null,
|
|
133294
|
+
role: "admin",
|
|
133295
|
+
developerStatus: "approved",
|
|
133296
|
+
createdAt: now,
|
|
133297
|
+
updatedAt: now
|
|
133298
|
+
},
|
|
133299
|
+
player: {
|
|
133300
|
+
id: DEMO_USER_IDS.player,
|
|
133301
|
+
name: "Player User",
|
|
133302
|
+
username: "player_user",
|
|
133303
|
+
email: "player@playcademy.com",
|
|
133304
|
+
emailVerified: true,
|
|
133305
|
+
image: null,
|
|
133306
|
+
role: "player",
|
|
133307
|
+
developerStatus: "none",
|
|
133308
|
+
createdAt: now,
|
|
133309
|
+
updatedAt: now
|
|
133310
|
+
},
|
|
133311
|
+
developer: {
|
|
133312
|
+
id: DEMO_USER_IDS.developer,
|
|
133313
|
+
name: "Developer User",
|
|
133314
|
+
username: "developer_user",
|
|
133315
|
+
email: "developer@playcademy.com",
|
|
133316
|
+
emailVerified: true,
|
|
133317
|
+
image: null,
|
|
133318
|
+
role: "developer",
|
|
133319
|
+
developerStatus: "approved",
|
|
133320
|
+
createdAt: now,
|
|
133321
|
+
updatedAt: now
|
|
133322
|
+
},
|
|
133323
|
+
pendingDeveloper: {
|
|
133324
|
+
id: DEMO_USER_IDS.pendingDeveloper,
|
|
133325
|
+
name: "Pending Developer",
|
|
133326
|
+
username: "pending_dev",
|
|
133327
|
+
email: "pending@playcademy.com",
|
|
133328
|
+
emailVerified: true,
|
|
133329
|
+
image: null,
|
|
133330
|
+
role: "developer",
|
|
133331
|
+
developerStatus: "pending",
|
|
133332
|
+
createdAt: now,
|
|
133333
|
+
updatedAt: now
|
|
133334
|
+
},
|
|
133335
|
+
unverifiedPlayer: {
|
|
133336
|
+
id: DEMO_USER_IDS.unverifiedPlayer,
|
|
133337
|
+
name: "Unverified Player",
|
|
133338
|
+
username: "unverified_player",
|
|
133339
|
+
email: "unverified@playcademy.com",
|
|
133340
|
+
emailVerified: false,
|
|
133341
|
+
image: null,
|
|
133342
|
+
role: "player",
|
|
133343
|
+
developerStatus: "none",
|
|
133344
|
+
createdAt: now,
|
|
133345
|
+
updatedAt: now
|
|
133346
|
+
}
|
|
133347
|
+
};
|
|
133348
|
+
var DEMO_USER = DEMO_USERS.player;
|
|
133349
|
+
// src/constants/demo-tokens.ts
|
|
133350
|
+
var DEMO_TOKENS = {
|
|
133351
|
+
"sandbox-demo-token": DEMO_USERS.player,
|
|
133352
|
+
"sandbox-admin-token": DEMO_USERS.admin,
|
|
133353
|
+
"sandbox-player-token": DEMO_USERS.player,
|
|
133354
|
+
"sandbox-developer-token": DEMO_USERS.developer,
|
|
133355
|
+
"sandbox-pending-dev-token": DEMO_USERS.pendingDeveloper,
|
|
133356
|
+
"sandbox-unverified-token": DEMO_USERS.unverifiedPlayer,
|
|
133357
|
+
"mock-game-token-for-local-dev": DEMO_USERS.player
|
|
133358
|
+
};
|
|
133359
|
+
var DEMO_TOKEN = "sandbox-demo-token";
|
|
133360
|
+
var MOCK_GAME_ID = "mock-game-id-from-template";
|
|
133361
|
+
// src/constants/demo-items.ts
|
|
133362
|
+
var DEMO_ITEM_IDS = {
|
|
133363
|
+
playcademyCredits: "10000000-0000-0000-0000-000000000001",
|
|
133364
|
+
foundingMemberBadge: "10000000-0000-0000-0000-000000000002",
|
|
133365
|
+
earlyAdopterBadge: "10000000-0000-0000-0000-000000000003",
|
|
133366
|
+
firstGameBadge: "10000000-0000-0000-0000-000000000004",
|
|
133367
|
+
commonSword: "10000000-0000-0000-0000-000000000005",
|
|
133368
|
+
smallHealthPotion: "10000000-0000-0000-0000-000000000006",
|
|
133369
|
+
smallBackpack: "10000000-0000-0000-0000-000000000007"
|
|
133370
|
+
};
|
|
133371
|
+
var PLAYCADEMY_CREDITS_ID = DEMO_ITEM_IDS.playcademyCredits;
|
|
133372
|
+
var SAMPLE_ITEMS = [
|
|
133373
|
+
{
|
|
133374
|
+
id: PLAYCADEMY_CREDITS_ID,
|
|
133375
|
+
slug: "PLAYCADEMY_CREDITS",
|
|
133376
|
+
gameId: null,
|
|
133377
|
+
displayName: "PLAYCADEMY credits",
|
|
133378
|
+
description: "The main currency used across PLAYCADEMY.",
|
|
133379
|
+
type: "currency",
|
|
133380
|
+
isPlaceable: false,
|
|
133381
|
+
imageUrl: "http://playcademy-sandbox.local/playcademy-credit.png",
|
|
133382
|
+
metadata: {
|
|
133383
|
+
rarity: "common"
|
|
133384
|
+
}
|
|
133385
|
+
},
|
|
133386
|
+
{
|
|
133387
|
+
id: DEMO_ITEM_IDS.foundingMemberBadge,
|
|
133388
|
+
slug: "FOUNDING_MEMBER_BADGE",
|
|
133389
|
+
gameId: null,
|
|
133390
|
+
displayName: "Founding Member Badge",
|
|
133391
|
+
description: "Reserved for founding core team of the PLAYCADEMY platform.",
|
|
133392
|
+
type: "badge",
|
|
133393
|
+
isPlaceable: false,
|
|
133394
|
+
imageUrl: null,
|
|
133395
|
+
metadata: {
|
|
133396
|
+
rarity: "legendary"
|
|
133397
|
+
}
|
|
133398
|
+
},
|
|
133399
|
+
{
|
|
133400
|
+
id: DEMO_ITEM_IDS.earlyAdopterBadge,
|
|
133401
|
+
slug: "EARLY_ADOPTER_BADGE",
|
|
133402
|
+
gameId: null,
|
|
133403
|
+
displayName: "Early Adopter Badge",
|
|
133404
|
+
description: "Awarded to users who joined during the beta phase.",
|
|
133405
|
+
type: "badge",
|
|
133406
|
+
isPlaceable: false,
|
|
133407
|
+
imageUrl: null,
|
|
133408
|
+
metadata: {
|
|
133409
|
+
rarity: "epic"
|
|
133410
|
+
}
|
|
133411
|
+
},
|
|
133412
|
+
{
|
|
133413
|
+
id: DEMO_ITEM_IDS.firstGameBadge,
|
|
133414
|
+
slug: "FIRST_GAME_BADGE",
|
|
133415
|
+
gameId: null,
|
|
133416
|
+
displayName: "First Game Played",
|
|
133417
|
+
description: "Awarded for playing your first game in the Playcademy platform.",
|
|
133418
|
+
type: "badge",
|
|
133419
|
+
isPlaceable: false,
|
|
133420
|
+
imageUrl: "http://playcademy-sandbox.local/first-game-badge.png",
|
|
133421
|
+
metadata: {
|
|
133422
|
+
rarity: "uncommon"
|
|
133423
|
+
}
|
|
133424
|
+
},
|
|
133425
|
+
{
|
|
133426
|
+
id: DEMO_ITEM_IDS.commonSword,
|
|
133427
|
+
slug: "COMMON_SWORD",
|
|
133428
|
+
gameId: null,
|
|
133429
|
+
displayName: "Common Sword",
|
|
133430
|
+
description: "A basic sword, good for beginners.",
|
|
133431
|
+
type: "unlock",
|
|
133432
|
+
isPlaceable: false,
|
|
133433
|
+
imageUrl: "http://playcademy-sandbox.local/common-sword.png",
|
|
133434
|
+
metadata: undefined
|
|
133435
|
+
},
|
|
133436
|
+
{
|
|
133437
|
+
id: DEMO_ITEM_IDS.smallHealthPotion,
|
|
133438
|
+
slug: "SMALL_HEALTH_POTION",
|
|
133439
|
+
gameId: null,
|
|
133440
|
+
displayName: "Small Health Potion",
|
|
133441
|
+
description: "Restores a small amount of health.",
|
|
133442
|
+
type: "other",
|
|
133443
|
+
isPlaceable: false,
|
|
133444
|
+
imageUrl: "http://playcademy-sandbox.local/small-health-potion.png",
|
|
133445
|
+
metadata: undefined
|
|
133446
|
+
},
|
|
133447
|
+
{
|
|
133448
|
+
id: DEMO_ITEM_IDS.smallBackpack,
|
|
133449
|
+
slug: "SMALL_BACKPACK",
|
|
133450
|
+
gameId: null,
|
|
133451
|
+
displayName: "Small Backpack",
|
|
133452
|
+
description: "Increases your inventory capacity by 5 slots.",
|
|
133453
|
+
type: "upgrade",
|
|
133454
|
+
isPlaceable: false,
|
|
133455
|
+
imageUrl: "http://playcademy-sandbox.local/small-backpack.png",
|
|
133456
|
+
metadata: undefined
|
|
133457
|
+
}
|
|
133458
|
+
];
|
|
133459
|
+
var SAMPLE_INVENTORY = [
|
|
133460
|
+
{
|
|
133461
|
+
id: "20000000-0000-0000-0000-000000000001",
|
|
133462
|
+
userId: DEMO_USER.id,
|
|
133463
|
+
itemId: PLAYCADEMY_CREDITS_ID,
|
|
133464
|
+
quantity: 1000
|
|
133465
|
+
}
|
|
133466
|
+
];
|
|
133252
133467
|
// ../../node_modules/@hono/node-server/dist/index.mjs
|
|
133253
133468
|
import { createServer as createServerHTTP } from "http";
|
|
133254
133469
|
import { Http2ServerRequest as Http2ServerRequest2 } from "http2";
|
|
@@ -133790,10 +134005,106 @@ var serve = (options, listeningListener) => {
|
|
|
133790
134005
|
});
|
|
133791
134006
|
return server;
|
|
133792
134007
|
};
|
|
134008
|
+
|
|
134009
|
+
// ../utils/src/port.ts
|
|
134010
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
134011
|
+
import { createServer } from "node:net";
|
|
134012
|
+
import { homedir } from "node:os";
|
|
134013
|
+
import { join } from "node:path";
|
|
134014
|
+
function getRegistryPath() {
|
|
134015
|
+
const home = homedir();
|
|
134016
|
+
const dir = join(home, ".playcademy");
|
|
134017
|
+
if (!existsSync(dir)) {
|
|
134018
|
+
mkdirSync(dir, { recursive: true });
|
|
134019
|
+
}
|
|
134020
|
+
return join(dir, ".proc");
|
|
134021
|
+
}
|
|
134022
|
+
function readRegistry() {
|
|
134023
|
+
const registryPath = getRegistryPath();
|
|
134024
|
+
if (!existsSync(registryPath)) {
|
|
134025
|
+
return {};
|
|
134026
|
+
}
|
|
134027
|
+
try {
|
|
134028
|
+
const content = readFileSync(registryPath, "utf-8");
|
|
134029
|
+
return JSON.parse(content);
|
|
134030
|
+
} catch {
|
|
134031
|
+
return {};
|
|
134032
|
+
}
|
|
134033
|
+
}
|
|
134034
|
+
function writeRegistry(registry) {
|
|
134035
|
+
const registryPath = getRegistryPath();
|
|
134036
|
+
writeFileSync(registryPath, JSON.stringify(registry, null, 2), "utf-8");
|
|
134037
|
+
}
|
|
134038
|
+
function getServerKey(type, port) {
|
|
134039
|
+
return `${type}-${port}`;
|
|
134040
|
+
}
|
|
134041
|
+
function writeServerInfo(type, info2) {
|
|
134042
|
+
const registry = readRegistry();
|
|
134043
|
+
const key = getServerKey(type, info2.port);
|
|
134044
|
+
registry[key] = info2;
|
|
134045
|
+
writeRegistry(registry);
|
|
134046
|
+
}
|
|
134047
|
+
function cleanupServerInfo(type, projectRoot, pid) {
|
|
134048
|
+
const registry = readRegistry();
|
|
134049
|
+
const keysToRemove = [];
|
|
134050
|
+
for (const [key, info2] of Object.entries(registry)) {
|
|
134051
|
+
if (key.startsWith(`${type}-`)) {
|
|
134052
|
+
let matches = true;
|
|
134053
|
+
if (projectRoot && info2.projectRoot !== projectRoot) {
|
|
134054
|
+
matches = false;
|
|
134055
|
+
}
|
|
134056
|
+
if (pid !== undefined && info2.pid !== pid) {
|
|
134057
|
+
matches = false;
|
|
134058
|
+
}
|
|
134059
|
+
if (matches) {
|
|
134060
|
+
keysToRemove.push(key);
|
|
134061
|
+
}
|
|
134062
|
+
}
|
|
134063
|
+
}
|
|
134064
|
+
for (const key of keysToRemove) {
|
|
134065
|
+
delete registry[key];
|
|
134066
|
+
}
|
|
134067
|
+
if (keysToRemove.length > 0) {
|
|
134068
|
+
writeRegistry(registry);
|
|
134069
|
+
}
|
|
134070
|
+
}
|
|
134071
|
+
async function isPortInUse(port) {
|
|
134072
|
+
return new Promise((resolve) => {
|
|
134073
|
+
const server = createServer();
|
|
134074
|
+
server.once("error", () => {
|
|
134075
|
+
resolve(true);
|
|
134076
|
+
});
|
|
134077
|
+
server.once("listening", () => {
|
|
134078
|
+
server.close();
|
|
134079
|
+
resolve(false);
|
|
134080
|
+
});
|
|
134081
|
+
server.listen(port);
|
|
134082
|
+
});
|
|
134083
|
+
}
|
|
134084
|
+
async function waitForPort(port, timeoutMs = 5000) {
|
|
134085
|
+
const start2 = Date.now();
|
|
134086
|
+
while (await isPortInUse(port)) {
|
|
134087
|
+
if (Date.now() - start2 > timeoutMs) {
|
|
134088
|
+
throw new Error(`Port ${port} is already in use.
|
|
134089
|
+
` + `Stop the other server or specify a different port with --port <number>.`);
|
|
134090
|
+
}
|
|
134091
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
134092
|
+
}
|
|
134093
|
+
}
|
|
134094
|
+
async function requirePortAvailable(port, timeoutMs = 100) {
|
|
134095
|
+
const start2 = Date.now();
|
|
134096
|
+
while (await isPortInUse(port)) {
|
|
134097
|
+
if (Date.now() - start2 > timeoutMs) {
|
|
134098
|
+
throw new Error(`Port ${port} is already in use.
|
|
134099
|
+
` + `Stop the other server or specify a different port with --port <number>.`);
|
|
134100
|
+
}
|
|
134101
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
134102
|
+
}
|
|
134103
|
+
}
|
|
133793
134104
|
// package.json
|
|
133794
134105
|
var package_default = {
|
|
133795
134106
|
name: "@playcademy/sandbox",
|
|
133796
|
-
version: "0.
|
|
134107
|
+
version: "0.3.1",
|
|
133797
134108
|
description: "Local development server for Playcademy game development",
|
|
133798
134109
|
type: "module",
|
|
133799
134110
|
exports: {
|
|
@@ -133808,6 +134119,10 @@ var package_default = {
|
|
|
133808
134119
|
"./config": {
|
|
133809
134120
|
import: "./dist/config.js",
|
|
133810
134121
|
types: "./dist/config.d.ts"
|
|
134122
|
+
},
|
|
134123
|
+
"./constants": {
|
|
134124
|
+
import: "./dist/constants.js",
|
|
134125
|
+
types: "./dist/constants.d.ts"
|
|
133811
134126
|
}
|
|
133812
134127
|
},
|
|
133813
134128
|
bin: {
|
|
@@ -136303,7 +136618,7 @@ function sql(strings, ...params) {
|
|
|
136303
136618
|
return new SQL([new StringChunk(str)]);
|
|
136304
136619
|
}
|
|
136305
136620
|
sql2.raw = raw2;
|
|
136306
|
-
function
|
|
136621
|
+
function join2(chunks, separator) {
|
|
136307
136622
|
const result = [];
|
|
136308
136623
|
for (const [i2, chunk] of chunks.entries()) {
|
|
136309
136624
|
if (i2 > 0 && separator !== undefined) {
|
|
@@ -136313,7 +136628,7 @@ function sql(strings, ...params) {
|
|
|
136313
136628
|
}
|
|
136314
136629
|
return new SQL(result);
|
|
136315
136630
|
}
|
|
136316
|
-
sql2.join =
|
|
136631
|
+
sql2.join = join2;
|
|
136317
136632
|
function identifier(value) {
|
|
136318
136633
|
return new Name(value);
|
|
136319
136634
|
}
|
|
@@ -139290,7 +139605,7 @@ class PgSelectQueryBuilderBase extends TypedQueryBuilder {
|
|
|
139290
139605
|
return (table, on) => {
|
|
139291
139606
|
const baseTableName = this.tableName;
|
|
139292
139607
|
const tableName = getTableLikeName(table);
|
|
139293
|
-
if (typeof tableName === "string" && this.config.joins?.some((
|
|
139608
|
+
if (typeof tableName === "string" && this.config.joins?.some((join2) => join2.alias === tableName)) {
|
|
139294
139609
|
throw new Error(`Alias "${tableName}" is already used in this query`);
|
|
139295
139610
|
}
|
|
139296
139611
|
if (!this.isPartialSelect) {
|
|
@@ -139799,7 +140114,7 @@ class PgUpdateBase extends QueryPromise {
|
|
|
139799
140114
|
createJoin(joinType) {
|
|
139800
140115
|
return (table, on) => {
|
|
139801
140116
|
const tableName = getTableLikeName(table);
|
|
139802
|
-
if (typeof tableName === "string" && this.config.joins.some((
|
|
140117
|
+
if (typeof tableName === "string" && this.config.joins.some((join2) => join2.alias === tableName)) {
|
|
139803
140118
|
throw new Error(`Alias "${tableName}" is already used in this query`);
|
|
139804
140119
|
}
|
|
139805
140120
|
if (typeof on === "function") {
|
|
@@ -139849,10 +140164,10 @@ class PgUpdateBase extends QueryPromise {
|
|
|
139849
140164
|
const fromFields = this.getTableLikeFields(this.config.from);
|
|
139850
140165
|
fields[tableName] = fromFields;
|
|
139851
140166
|
}
|
|
139852
|
-
for (const
|
|
139853
|
-
const tableName2 = getTableLikeName(
|
|
139854
|
-
if (typeof tableName2 === "string" && !is(
|
|
139855
|
-
const fromFields = this.getTableLikeFields(
|
|
140167
|
+
for (const join2 of this.config.joins) {
|
|
140168
|
+
const tableName2 = getTableLikeName(join2.table);
|
|
140169
|
+
if (typeof tableName2 === "string" && !is(join2.table, SQL)) {
|
|
140170
|
+
const fromFields = this.getTableLikeFields(join2.table);
|
|
139856
140171
|
fields[tableName2] = fromFields;
|
|
139857
140172
|
}
|
|
139858
140173
|
}
|
|
@@ -140995,195 +141310,6 @@ var notificationsRelations = relations(notifications, ({ one }) => ({
|
|
|
140995
141310
|
references: [users.id]
|
|
140996
141311
|
})
|
|
140997
141312
|
}));
|
|
140998
|
-
// src/constants/demo-users.ts
|
|
140999
|
-
var now = new Date;
|
|
141000
|
-
var DEMO_USER_IDS = {
|
|
141001
|
-
admin: "00000000-0000-0000-0000-000000000001",
|
|
141002
|
-
player: "00000000-0000-0000-0000-000000000002",
|
|
141003
|
-
developer: "00000000-0000-0000-0000-000000000003",
|
|
141004
|
-
pendingDeveloper: "00000000-0000-0000-0000-000000000004",
|
|
141005
|
-
unverifiedPlayer: "00000000-0000-0000-0000-000000000005"
|
|
141006
|
-
};
|
|
141007
|
-
var DEMO_USERS = {
|
|
141008
|
-
admin: {
|
|
141009
|
-
id: DEMO_USER_IDS.admin,
|
|
141010
|
-
name: "Admin User",
|
|
141011
|
-
username: "admin_user",
|
|
141012
|
-
email: "admin@playcademy.com",
|
|
141013
|
-
emailVerified: true,
|
|
141014
|
-
image: null,
|
|
141015
|
-
role: "admin",
|
|
141016
|
-
developerStatus: "approved",
|
|
141017
|
-
createdAt: now,
|
|
141018
|
-
updatedAt: now
|
|
141019
|
-
},
|
|
141020
|
-
player: {
|
|
141021
|
-
id: DEMO_USER_IDS.player,
|
|
141022
|
-
name: "Player User",
|
|
141023
|
-
username: "player_user",
|
|
141024
|
-
email: "player@playcademy.com",
|
|
141025
|
-
emailVerified: true,
|
|
141026
|
-
image: null,
|
|
141027
|
-
role: "player",
|
|
141028
|
-
developerStatus: "none",
|
|
141029
|
-
createdAt: now,
|
|
141030
|
-
updatedAt: now
|
|
141031
|
-
},
|
|
141032
|
-
developer: {
|
|
141033
|
-
id: DEMO_USER_IDS.developer,
|
|
141034
|
-
name: "Developer User",
|
|
141035
|
-
username: "developer_user",
|
|
141036
|
-
email: "developer@playcademy.com",
|
|
141037
|
-
emailVerified: true,
|
|
141038
|
-
image: null,
|
|
141039
|
-
role: "developer",
|
|
141040
|
-
developerStatus: "approved",
|
|
141041
|
-
createdAt: now,
|
|
141042
|
-
updatedAt: now
|
|
141043
|
-
},
|
|
141044
|
-
pendingDeveloper: {
|
|
141045
|
-
id: DEMO_USER_IDS.pendingDeveloper,
|
|
141046
|
-
name: "Pending Developer",
|
|
141047
|
-
username: "pending_dev",
|
|
141048
|
-
email: "pending@playcademy.com",
|
|
141049
|
-
emailVerified: true,
|
|
141050
|
-
image: null,
|
|
141051
|
-
role: "developer",
|
|
141052
|
-
developerStatus: "pending",
|
|
141053
|
-
createdAt: now,
|
|
141054
|
-
updatedAt: now
|
|
141055
|
-
},
|
|
141056
|
-
unverifiedPlayer: {
|
|
141057
|
-
id: DEMO_USER_IDS.unverifiedPlayer,
|
|
141058
|
-
name: "Unverified Player",
|
|
141059
|
-
username: "unverified_player",
|
|
141060
|
-
email: "unverified@playcademy.com",
|
|
141061
|
-
emailVerified: false,
|
|
141062
|
-
image: null,
|
|
141063
|
-
role: "player",
|
|
141064
|
-
developerStatus: "none",
|
|
141065
|
-
createdAt: now,
|
|
141066
|
-
updatedAt: now
|
|
141067
|
-
}
|
|
141068
|
-
};
|
|
141069
|
-
var DEMO_USER = DEMO_USERS.admin;
|
|
141070
|
-
// src/constants/demo-tokens.ts
|
|
141071
|
-
var DEMO_TOKENS = {
|
|
141072
|
-
"sandbox-demo-token": DEMO_USERS.admin,
|
|
141073
|
-
"sandbox-admin-token": DEMO_USERS.admin,
|
|
141074
|
-
"sandbox-player-token": DEMO_USERS.player,
|
|
141075
|
-
"sandbox-developer-token": DEMO_USERS.developer,
|
|
141076
|
-
"sandbox-pending-dev-token": DEMO_USERS.pendingDeveloper,
|
|
141077
|
-
"sandbox-unverified-token": DEMO_USERS.unverifiedPlayer,
|
|
141078
|
-
"mock-game-token-for-local-dev": DEMO_USERS.admin
|
|
141079
|
-
};
|
|
141080
|
-
var MOCK_GAME_ID = "mock-game-id-from-template";
|
|
141081
|
-
// src/constants/demo-items.ts
|
|
141082
|
-
var DEMO_ITEM_IDS = {
|
|
141083
|
-
playcademyCredits: "10000000-0000-0000-0000-000000000001",
|
|
141084
|
-
foundingMemberBadge: "10000000-0000-0000-0000-000000000002",
|
|
141085
|
-
earlyAdopterBadge: "10000000-0000-0000-0000-000000000003",
|
|
141086
|
-
firstGameBadge: "10000000-0000-0000-0000-000000000004",
|
|
141087
|
-
commonSword: "10000000-0000-0000-0000-000000000005",
|
|
141088
|
-
smallHealthPotion: "10000000-0000-0000-0000-000000000006",
|
|
141089
|
-
smallBackpack: "10000000-0000-0000-0000-000000000007"
|
|
141090
|
-
};
|
|
141091
|
-
var PLAYCADEMY_CREDITS_ID = DEMO_ITEM_IDS.playcademyCredits;
|
|
141092
|
-
var SAMPLE_ITEMS = [
|
|
141093
|
-
{
|
|
141094
|
-
id: PLAYCADEMY_CREDITS_ID,
|
|
141095
|
-
slug: "PLAYCADEMY_CREDITS",
|
|
141096
|
-
gameId: null,
|
|
141097
|
-
displayName: "PLAYCADEMY credits",
|
|
141098
|
-
description: "The main currency used across PLAYCADEMY.",
|
|
141099
|
-
type: "currency",
|
|
141100
|
-
isPlaceable: false,
|
|
141101
|
-
imageUrl: "http://playcademy-sandbox.local/playcademy-credit.png",
|
|
141102
|
-
metadata: {
|
|
141103
|
-
rarity: "common"
|
|
141104
|
-
}
|
|
141105
|
-
},
|
|
141106
|
-
{
|
|
141107
|
-
id: DEMO_ITEM_IDS.foundingMemberBadge,
|
|
141108
|
-
slug: "FOUNDING_MEMBER_BADGE",
|
|
141109
|
-
gameId: null,
|
|
141110
|
-
displayName: "Founding Member Badge",
|
|
141111
|
-
description: "Reserved for founding core team of the PLAYCADEMY platform.",
|
|
141112
|
-
type: "badge",
|
|
141113
|
-
isPlaceable: false,
|
|
141114
|
-
imageUrl: null,
|
|
141115
|
-
metadata: {
|
|
141116
|
-
rarity: "legendary"
|
|
141117
|
-
}
|
|
141118
|
-
},
|
|
141119
|
-
{
|
|
141120
|
-
id: DEMO_ITEM_IDS.earlyAdopterBadge,
|
|
141121
|
-
slug: "EARLY_ADOPTER_BADGE",
|
|
141122
|
-
gameId: null,
|
|
141123
|
-
displayName: "Early Adopter Badge",
|
|
141124
|
-
description: "Awarded to users who joined during the beta phase.",
|
|
141125
|
-
type: "badge",
|
|
141126
|
-
isPlaceable: false,
|
|
141127
|
-
imageUrl: null,
|
|
141128
|
-
metadata: {
|
|
141129
|
-
rarity: "epic"
|
|
141130
|
-
}
|
|
141131
|
-
},
|
|
141132
|
-
{
|
|
141133
|
-
id: DEMO_ITEM_IDS.firstGameBadge,
|
|
141134
|
-
slug: "FIRST_GAME_BADGE",
|
|
141135
|
-
gameId: null,
|
|
141136
|
-
displayName: "First Game Played",
|
|
141137
|
-
description: "Awarded for playing your first game in the Playcademy platform.",
|
|
141138
|
-
type: "badge",
|
|
141139
|
-
isPlaceable: false,
|
|
141140
|
-
imageUrl: "http://playcademy-sandbox.local/first-game-badge.png",
|
|
141141
|
-
metadata: {
|
|
141142
|
-
rarity: "uncommon"
|
|
141143
|
-
}
|
|
141144
|
-
},
|
|
141145
|
-
{
|
|
141146
|
-
id: DEMO_ITEM_IDS.commonSword,
|
|
141147
|
-
slug: "COMMON_SWORD",
|
|
141148
|
-
gameId: null,
|
|
141149
|
-
displayName: "Common Sword",
|
|
141150
|
-
description: "A basic sword, good for beginners.",
|
|
141151
|
-
type: "unlock",
|
|
141152
|
-
isPlaceable: false,
|
|
141153
|
-
imageUrl: "http://playcademy-sandbox.local/common-sword.png",
|
|
141154
|
-
metadata: undefined
|
|
141155
|
-
},
|
|
141156
|
-
{
|
|
141157
|
-
id: DEMO_ITEM_IDS.smallHealthPotion,
|
|
141158
|
-
slug: "SMALL_HEALTH_POTION",
|
|
141159
|
-
gameId: null,
|
|
141160
|
-
displayName: "Small Health Potion",
|
|
141161
|
-
description: "Restores a small amount of health.",
|
|
141162
|
-
type: "other",
|
|
141163
|
-
isPlaceable: false,
|
|
141164
|
-
imageUrl: "http://playcademy-sandbox.local/small-health-potion.png",
|
|
141165
|
-
metadata: undefined
|
|
141166
|
-
},
|
|
141167
|
-
{
|
|
141168
|
-
id: DEMO_ITEM_IDS.smallBackpack,
|
|
141169
|
-
slug: "SMALL_BACKPACK",
|
|
141170
|
-
gameId: null,
|
|
141171
|
-
displayName: "Small Backpack",
|
|
141172
|
-
description: "Increases your inventory capacity by 5 slots.",
|
|
141173
|
-
type: "upgrade",
|
|
141174
|
-
isPlaceable: false,
|
|
141175
|
-
imageUrl: "http://playcademy-sandbox.local/small-backpack.png",
|
|
141176
|
-
metadata: undefined
|
|
141177
|
-
}
|
|
141178
|
-
];
|
|
141179
|
-
var SAMPLE_INVENTORY = [
|
|
141180
|
-
{
|
|
141181
|
-
id: "20000000-0000-0000-0000-000000000001",
|
|
141182
|
-
userId: DEMO_USER.id,
|
|
141183
|
-
itemId: PLAYCADEMY_CREDITS_ID,
|
|
141184
|
-
quantity: 1000
|
|
141185
|
-
}
|
|
141186
|
-
];
|
|
141187
141313
|
// src/server/auth.ts
|
|
141188
141314
|
function extractBearerToken(authHeader) {
|
|
141189
141315
|
if (!authHeader?.startsWith("Bearer ")) {
|
|
@@ -141191,25 +141317,53 @@ function extractBearerToken(authHeader) {
|
|
|
141191
141317
|
}
|
|
141192
141318
|
return authHeader.substring(7);
|
|
141193
141319
|
}
|
|
141194
|
-
function
|
|
141320
|
+
function parseSandboxToken(token) {
|
|
141321
|
+
try {
|
|
141322
|
+
const parts2 = token.split(".");
|
|
141323
|
+
if (parts2.length !== 3 || parts2[2] !== "sandbox") {
|
|
141324
|
+
return null;
|
|
141325
|
+
}
|
|
141326
|
+
const header = JSON.parse(atob(parts2[0]));
|
|
141327
|
+
if (header.typ !== "sandbox") {
|
|
141328
|
+
return null;
|
|
141329
|
+
}
|
|
141330
|
+
const payload = JSON.parse(atob(parts2[1]));
|
|
141331
|
+
if (!payload.uid || !payload.sub) {
|
|
141332
|
+
return null;
|
|
141333
|
+
}
|
|
141334
|
+
return {
|
|
141335
|
+
userId: payload.uid,
|
|
141336
|
+
gameSlug: payload.sub
|
|
141337
|
+
};
|
|
141338
|
+
} catch {
|
|
141339
|
+
return null;
|
|
141340
|
+
}
|
|
141341
|
+
}
|
|
141342
|
+
function parseJwtClaims(token) {
|
|
141195
141343
|
try {
|
|
141196
141344
|
const parts2 = token.split(".");
|
|
141197
141345
|
if (parts2.length === 3 && parts2[1]) {
|
|
141198
141346
|
const payload = JSON.parse(atob(parts2[1]));
|
|
141199
|
-
|
|
141347
|
+
if (payload.uid) {
|
|
141348
|
+
return {
|
|
141349
|
+
userId: payload.uid,
|
|
141350
|
+
gameId: payload.sub
|
|
141351
|
+
};
|
|
141352
|
+
}
|
|
141200
141353
|
}
|
|
141201
|
-
} catch
|
|
141202
|
-
console.warn("[Auth] Failed to decode JWT token:", error);
|
|
141203
|
-
}
|
|
141354
|
+
} catch {}
|
|
141204
141355
|
return null;
|
|
141205
141356
|
}
|
|
141206
|
-
function
|
|
141357
|
+
function resolveAuth(token) {
|
|
141207
141358
|
const demoUser = DEMO_TOKENS[token];
|
|
141208
|
-
if (demoUser)
|
|
141209
|
-
return demoUser.id;
|
|
141210
|
-
}
|
|
141359
|
+
if (demoUser)
|
|
141360
|
+
return { userId: demoUser.id };
|
|
141211
141361
|
if (token.includes(".")) {
|
|
141212
|
-
|
|
141362
|
+
const sandboxClaims = parseSandboxToken(token);
|
|
141363
|
+
if (sandboxClaims) {
|
|
141364
|
+
return sandboxClaims;
|
|
141365
|
+
}
|
|
141366
|
+
return parseJwtClaims(token);
|
|
141213
141367
|
}
|
|
141214
141368
|
return null;
|
|
141215
141369
|
}
|
|
@@ -141224,6 +141378,18 @@ async function fetchUserFromDatabase(db, userId) {
|
|
|
141224
141378
|
throw error;
|
|
141225
141379
|
}
|
|
141226
141380
|
}
|
|
141381
|
+
async function resolveGameIdFromSlug(db, slug) {
|
|
141382
|
+
try {
|
|
141383
|
+
const game = await db.query.games.findFirst({
|
|
141384
|
+
where: eq(games.slug, slug),
|
|
141385
|
+
columns: { id: true }
|
|
141386
|
+
});
|
|
141387
|
+
return game?.id || null;
|
|
141388
|
+
} catch (error) {
|
|
141389
|
+
console.error("[Auth] Error looking up game by slug:", error);
|
|
141390
|
+
return null;
|
|
141391
|
+
}
|
|
141392
|
+
}
|
|
141227
141393
|
function isPublicRoute(path, exceptions) {
|
|
141228
141394
|
return exceptions.some((exception) => {
|
|
141229
141395
|
if (path === exception)
|
|
@@ -141245,11 +141411,11 @@ async function authenticateRequest(c) {
|
|
|
141245
141411
|
shouldReturn404: true
|
|
141246
141412
|
};
|
|
141247
141413
|
}
|
|
141248
|
-
let
|
|
141414
|
+
let claims;
|
|
141249
141415
|
if (apiKey && !bearerToken) {
|
|
141250
|
-
|
|
141416
|
+
claims = { userId: DEMO_USERS.admin.id };
|
|
141251
141417
|
} else {
|
|
141252
|
-
const resolved =
|
|
141418
|
+
const resolved = resolveAuth(token);
|
|
141253
141419
|
if (!resolved) {
|
|
141254
141420
|
return {
|
|
141255
141421
|
success: false,
|
|
@@ -141257,26 +141423,22 @@ async function authenticateRequest(c) {
|
|
|
141257
141423
|
shouldReturn404: true
|
|
141258
141424
|
};
|
|
141259
141425
|
}
|
|
141260
|
-
|
|
141426
|
+
claims = resolved;
|
|
141261
141427
|
}
|
|
141262
141428
|
const db = c.get("db");
|
|
141263
141429
|
if (!db) {
|
|
141264
141430
|
console.error("[Auth] Database not available in context");
|
|
141265
|
-
return {
|
|
141266
|
-
success: false,
|
|
141267
|
-
error: "Internal server error",
|
|
141268
|
-
shouldReturn404: false
|
|
141269
|
-
};
|
|
141431
|
+
return { success: false, error: "Internal server error", shouldReturn404: false };
|
|
141270
141432
|
}
|
|
141271
|
-
const user = await fetchUserFromDatabase(db,
|
|
141433
|
+
const user = await fetchUserFromDatabase(db, claims.userId);
|
|
141272
141434
|
if (!user) {
|
|
141273
|
-
return {
|
|
141274
|
-
|
|
141275
|
-
|
|
141276
|
-
|
|
141277
|
-
|
|
141435
|
+
return { success: false, error: "User not found or token invalid", shouldReturn404: true };
|
|
141436
|
+
}
|
|
141437
|
+
let gameId = claims.gameId;
|
|
141438
|
+
if (!gameId && claims.gameSlug) {
|
|
141439
|
+
gameId = await resolveGameIdFromSlug(db, claims.gameSlug) ?? undefined;
|
|
141278
141440
|
}
|
|
141279
|
-
return { success: true, user };
|
|
141441
|
+
return { success: true, user, gameId };
|
|
141280
141442
|
}
|
|
141281
141443
|
function setupAuth(options = {}) {
|
|
141282
141444
|
const { exceptions = [] } = options;
|
|
@@ -141288,6 +141450,8 @@ function setupAuth(options = {}) {
|
|
|
141288
141450
|
const result = await authenticateRequest(c);
|
|
141289
141451
|
if (result.success) {
|
|
141290
141452
|
c.set("user", result.user);
|
|
141453
|
+
if (result.gameId)
|
|
141454
|
+
c.set("gameId", result.gameId);
|
|
141291
141455
|
await next();
|
|
141292
141456
|
return;
|
|
141293
141457
|
}
|
|
@@ -147149,34 +147313,34 @@ function getDatabase() {
|
|
|
147149
147313
|
|
|
147150
147314
|
// src/database/path-manager.ts
|
|
147151
147315
|
import fs2 from "node:fs";
|
|
147152
|
-
import { dirname, isAbsolute, join as
|
|
147316
|
+
import { dirname, isAbsolute, join as join3 } from "node:path";
|
|
147153
147317
|
|
|
147154
147318
|
class DatabasePathManager {
|
|
147155
|
-
static DEFAULT_DB_SUBPATH =
|
|
147319
|
+
static DEFAULT_DB_SUBPATH = join3("@playcademy", "vite-plugin", "node_modules", ".playcademy", "sandbox.db");
|
|
147156
147320
|
static findNodeModulesPath() {
|
|
147157
147321
|
let currentDir = process.cwd();
|
|
147158
147322
|
while (currentDir !== dirname(currentDir)) {
|
|
147159
|
-
const nodeModulesPath =
|
|
147323
|
+
const nodeModulesPath = join3(currentDir, "node_modules");
|
|
147160
147324
|
if (fs2.existsSync(nodeModulesPath)) {
|
|
147161
147325
|
return nodeModulesPath;
|
|
147162
147326
|
}
|
|
147163
147327
|
currentDir = dirname(currentDir);
|
|
147164
147328
|
}
|
|
147165
|
-
return
|
|
147329
|
+
return join3(process.cwd(), "node_modules");
|
|
147166
147330
|
}
|
|
147167
147331
|
static resolveDatabasePath(customPath) {
|
|
147168
147332
|
if (customPath) {
|
|
147169
147333
|
if (customPath === ":memory:")
|
|
147170
147334
|
return ":memory:";
|
|
147171
|
-
return isAbsolute(customPath) ? customPath :
|
|
147335
|
+
return isAbsolute(customPath) ? customPath : join3(process.cwd(), customPath);
|
|
147172
147336
|
}
|
|
147173
|
-
return
|
|
147337
|
+
return join3(this.findNodeModulesPath(), this.DEFAULT_DB_SUBPATH);
|
|
147174
147338
|
}
|
|
147175
147339
|
static ensureDatabaseDirectory(dbPath) {
|
|
147176
147340
|
if (dbPath === ":memory:")
|
|
147177
147341
|
return;
|
|
147178
147342
|
const dirPath = dirname(dbPath);
|
|
147179
|
-
const absolutePath = isAbsolute(dirPath) ? dirPath :
|
|
147343
|
+
const absolutePath = isAbsolute(dirPath) ? dirPath : join3(process.cwd(), dirPath);
|
|
147180
147344
|
try {
|
|
147181
147345
|
if (!fs2.existsSync(absolutePath)) {
|
|
147182
147346
|
fs2.mkdirSync(absolutePath, { recursive: true });
|
|
@@ -147503,7 +147667,51 @@ var init_overworld = __esm3(() => {
|
|
|
147503
147667
|
FIRST_GAME: ITEM_SLUGS2.FIRST_GAME_BADGE
|
|
147504
147668
|
};
|
|
147505
147669
|
});
|
|
147506
|
-
var
|
|
147670
|
+
var TIMEBACK_ORG_SOURCED_ID = "PLAYCADEMY";
|
|
147671
|
+
var TIMEBACK_ORG_NAME = "Playcademy Studios";
|
|
147672
|
+
var TIMEBACK_ORG_TYPE = "department";
|
|
147673
|
+
var TIMEBACK_COURSE_DEFAULTS;
|
|
147674
|
+
var TIMEBACK_RESOURCE_DEFAULTS;
|
|
147675
|
+
var TIMEBACK_COMPONENT_DEFAULTS;
|
|
147676
|
+
var TIMEBACK_COMPONENT_RESOURCE_DEFAULTS;
|
|
147677
|
+
var init_timeback = __esm3(() => {
|
|
147678
|
+
TIMEBACK_COURSE_DEFAULTS = {
|
|
147679
|
+
gradingScheme: "STANDARD",
|
|
147680
|
+
level: {
|
|
147681
|
+
elementary: "Elementary",
|
|
147682
|
+
middle: "Middle",
|
|
147683
|
+
high: "High",
|
|
147684
|
+
ap: "AP"
|
|
147685
|
+
},
|
|
147686
|
+
goals: {
|
|
147687
|
+
dailyXp: 50,
|
|
147688
|
+
dailyLessons: 3
|
|
147689
|
+
},
|
|
147690
|
+
metrics: {
|
|
147691
|
+
totalXp: 1000,
|
|
147692
|
+
totalLessons: 50
|
|
147693
|
+
}
|
|
147694
|
+
};
|
|
147695
|
+
TIMEBACK_RESOURCE_DEFAULTS = {
|
|
147696
|
+
vendorId: "playcademy",
|
|
147697
|
+
roles: ["primary"],
|
|
147698
|
+
importance: "primary",
|
|
147699
|
+
metadata: {
|
|
147700
|
+
type: "interactive",
|
|
147701
|
+
toolProvider: "Playcademy",
|
|
147702
|
+
instructionalMethod: "exploratory",
|
|
147703
|
+
language: "en-US"
|
|
147704
|
+
}
|
|
147705
|
+
};
|
|
147706
|
+
TIMEBACK_COMPONENT_DEFAULTS = {
|
|
147707
|
+
sortOrder: 1,
|
|
147708
|
+
prerequisiteCriteria: "ALL"
|
|
147709
|
+
};
|
|
147710
|
+
TIMEBACK_COMPONENT_RESOURCE_DEFAULTS = {
|
|
147711
|
+
sortOrder: 1,
|
|
147712
|
+
lessonType: "quiz"
|
|
147713
|
+
};
|
|
147714
|
+
});
|
|
147507
147715
|
var init_workers = () => {};
|
|
147508
147716
|
var init_src2 = __esm3(() => {
|
|
147509
147717
|
init_auth();
|
|
@@ -147544,7 +147752,6 @@ var HTTP_DEFAULTS;
|
|
|
147544
147752
|
var AUTH_DEFAULTS;
|
|
147545
147753
|
var CACHE_DEFAULTS;
|
|
147546
147754
|
var CONFIG_DEFAULTS;
|
|
147547
|
-
var DEFAULT_PLAYCADEMY_ORGANIZATION_ID;
|
|
147548
147755
|
var PLAYCADEMY_DEFAULTS;
|
|
147549
147756
|
var RESOURCE_DEFAULTS;
|
|
147550
147757
|
var HTTP_STATUS;
|
|
@@ -147691,54 +147898,26 @@ var init_constants = __esm3(() => {
|
|
|
147691
147898
|
CONFIG_DEFAULTS = {
|
|
147692
147899
|
fileNames: ["timeback.config.js", "timeback.config.json"]
|
|
147693
147900
|
};
|
|
147694
|
-
DEFAULT_PLAYCADEMY_ORGANIZATION_ID = process.env.TIMEBACK_ORG_SOURCE_ID || "PLAYCADEMY";
|
|
147695
147901
|
PLAYCADEMY_DEFAULTS = {
|
|
147696
|
-
organization:
|
|
147902
|
+
organization: TIMEBACK_ORG_SOURCED_ID,
|
|
147697
147903
|
launchBaseUrls: PLAYCADEMY_BASE_URLS
|
|
147698
147904
|
};
|
|
147699
147905
|
RESOURCE_DEFAULTS = {
|
|
147700
147906
|
organization: {
|
|
147701
|
-
name:
|
|
147702
|
-
type:
|
|
147907
|
+
name: TIMEBACK_ORG_NAME,
|
|
147908
|
+
type: TIMEBACK_ORG_TYPE
|
|
147703
147909
|
},
|
|
147704
147910
|
course: {
|
|
147705
|
-
gradingScheme:
|
|
147706
|
-
level:
|
|
147707
|
-
elementary: "Elementary",
|
|
147708
|
-
middle: "Middle",
|
|
147709
|
-
high: "High",
|
|
147710
|
-
ap: "AP"
|
|
147711
|
-
},
|
|
147911
|
+
gradingScheme: TIMEBACK_COURSE_DEFAULTS.gradingScheme,
|
|
147912
|
+
level: TIMEBACK_COURSE_DEFAULTS.level,
|
|
147712
147913
|
metadata: {
|
|
147713
|
-
goals:
|
|
147714
|
-
|
|
147715
|
-
dailyLessons: 3
|
|
147716
|
-
},
|
|
147717
|
-
metrics: {
|
|
147718
|
-
totalXp: 1000,
|
|
147719
|
-
totalLessons: 50
|
|
147720
|
-
}
|
|
147914
|
+
goals: TIMEBACK_COURSE_DEFAULTS.goals,
|
|
147915
|
+
metrics: TIMEBACK_COURSE_DEFAULTS.metrics
|
|
147721
147916
|
}
|
|
147722
147917
|
},
|
|
147723
|
-
component:
|
|
147724
|
-
|
|
147725
|
-
|
|
147726
|
-
},
|
|
147727
|
-
resource: {
|
|
147728
|
-
vendorId: "playcademy",
|
|
147729
|
-
roles: ["primary"],
|
|
147730
|
-
importance: "primary",
|
|
147731
|
-
metadata: {
|
|
147732
|
-
type: "interactive",
|
|
147733
|
-
toolProvider: "Playcademy",
|
|
147734
|
-
instructionalMethod: "exploratory",
|
|
147735
|
-
language: "en-US"
|
|
147736
|
-
}
|
|
147737
|
-
},
|
|
147738
|
-
componentResource: {
|
|
147739
|
-
sortOrder: 1,
|
|
147740
|
-
lessonType: "quiz"
|
|
147741
|
-
}
|
|
147918
|
+
component: TIMEBACK_COMPONENT_DEFAULTS,
|
|
147919
|
+
resource: TIMEBACK_RESOURCE_DEFAULTS,
|
|
147920
|
+
componentResource: TIMEBACK_COMPONENT_RESOURCE_DEFAULTS
|
|
147742
147921
|
};
|
|
147743
147922
|
HTTP_STATUS = {
|
|
147744
147923
|
CLIENT_ERROR_MIN: 400,
|
|
@@ -153746,7 +153925,8 @@ class TimebackClient {
|
|
|
153746
153925
|
courseId: enrollment.course.id,
|
|
153747
153926
|
status: "active",
|
|
153748
153927
|
grades,
|
|
153749
|
-
subjects
|
|
153928
|
+
subjects,
|
|
153929
|
+
school: enrollment.school
|
|
153750
153930
|
};
|
|
153751
153931
|
});
|
|
153752
153932
|
this.cacheManager.setEnrollments(studentId, enrollments);
|
|
@@ -153819,7 +153999,51 @@ var init_overworld2 = __esm4(() => {
|
|
|
153819
153999
|
FIRST_GAME: ITEM_SLUGS3.FIRST_GAME_BADGE
|
|
153820
154000
|
};
|
|
153821
154001
|
});
|
|
153822
|
-
var
|
|
154002
|
+
var TIMEBACK_ORG_SOURCED_ID2 = "PLAYCADEMY";
|
|
154003
|
+
var TIMEBACK_ORG_NAME2 = "Playcademy Studios";
|
|
154004
|
+
var TIMEBACK_ORG_TYPE2 = "department";
|
|
154005
|
+
var TIMEBACK_COURSE_DEFAULTS2;
|
|
154006
|
+
var TIMEBACK_RESOURCE_DEFAULTS2;
|
|
154007
|
+
var TIMEBACK_COMPONENT_DEFAULTS2;
|
|
154008
|
+
var TIMEBACK_COMPONENT_RESOURCE_DEFAULTS2;
|
|
154009
|
+
var init_timeback2 = __esm4(() => {
|
|
154010
|
+
TIMEBACK_COURSE_DEFAULTS2 = {
|
|
154011
|
+
gradingScheme: "STANDARD",
|
|
154012
|
+
level: {
|
|
154013
|
+
elementary: "Elementary",
|
|
154014
|
+
middle: "Middle",
|
|
154015
|
+
high: "High",
|
|
154016
|
+
ap: "AP"
|
|
154017
|
+
},
|
|
154018
|
+
goals: {
|
|
154019
|
+
dailyXp: 50,
|
|
154020
|
+
dailyLessons: 3
|
|
154021
|
+
},
|
|
154022
|
+
metrics: {
|
|
154023
|
+
totalXp: 1000,
|
|
154024
|
+
totalLessons: 50
|
|
154025
|
+
}
|
|
154026
|
+
};
|
|
154027
|
+
TIMEBACK_RESOURCE_DEFAULTS2 = {
|
|
154028
|
+
vendorId: "playcademy",
|
|
154029
|
+
roles: ["primary"],
|
|
154030
|
+
importance: "primary",
|
|
154031
|
+
metadata: {
|
|
154032
|
+
type: "interactive",
|
|
154033
|
+
toolProvider: "Playcademy",
|
|
154034
|
+
instructionalMethod: "exploratory",
|
|
154035
|
+
language: "en-US"
|
|
154036
|
+
}
|
|
154037
|
+
};
|
|
154038
|
+
TIMEBACK_COMPONENT_DEFAULTS2 = {
|
|
154039
|
+
sortOrder: 1,
|
|
154040
|
+
prerequisiteCriteria: "ALL"
|
|
154041
|
+
};
|
|
154042
|
+
TIMEBACK_COMPONENT_RESOURCE_DEFAULTS2 = {
|
|
154043
|
+
sortOrder: 1,
|
|
154044
|
+
lessonType: "quiz"
|
|
154045
|
+
};
|
|
154046
|
+
});
|
|
153823
154047
|
var init_workers2 = () => {};
|
|
153824
154048
|
var init_src3 = __esm4(() => {
|
|
153825
154049
|
init_auth2();
|
|
@@ -153851,7 +154075,6 @@ var HTTP_DEFAULTS2;
|
|
|
153851
154075
|
var AUTH_DEFAULTS2;
|
|
153852
154076
|
var CACHE_DEFAULTS2;
|
|
153853
154077
|
var CONFIG_DEFAULTS2;
|
|
153854
|
-
var DEFAULT_PLAYCADEMY_ORGANIZATION_ID2;
|
|
153855
154078
|
var PLAYCADEMY_DEFAULTS2;
|
|
153856
154079
|
var RESOURCE_DEFAULTS2;
|
|
153857
154080
|
var HTTP_STATUS2;
|
|
@@ -153998,54 +154221,26 @@ var init_constants2 = __esm4(() => {
|
|
|
153998
154221
|
CONFIG_DEFAULTS2 = {
|
|
153999
154222
|
fileNames: ["timeback.config.js", "timeback.config.json"]
|
|
154000
154223
|
};
|
|
154001
|
-
DEFAULT_PLAYCADEMY_ORGANIZATION_ID2 = process.env.TIMEBACK_ORG_SOURCE_ID || "PLAYCADEMY";
|
|
154002
154224
|
PLAYCADEMY_DEFAULTS2 = {
|
|
154003
|
-
organization:
|
|
154225
|
+
organization: TIMEBACK_ORG_SOURCED_ID2,
|
|
154004
154226
|
launchBaseUrls: PLAYCADEMY_BASE_URLS2
|
|
154005
154227
|
};
|
|
154006
154228
|
RESOURCE_DEFAULTS2 = {
|
|
154007
154229
|
organization: {
|
|
154008
|
-
name:
|
|
154009
|
-
type:
|
|
154230
|
+
name: TIMEBACK_ORG_NAME2,
|
|
154231
|
+
type: TIMEBACK_ORG_TYPE2
|
|
154010
154232
|
},
|
|
154011
154233
|
course: {
|
|
154012
|
-
gradingScheme:
|
|
154013
|
-
level:
|
|
154014
|
-
elementary: "Elementary",
|
|
154015
|
-
middle: "Middle",
|
|
154016
|
-
high: "High",
|
|
154017
|
-
ap: "AP"
|
|
154018
|
-
},
|
|
154019
|
-
metadata: {
|
|
154020
|
-
goals: {
|
|
154021
|
-
dailyXp: 50,
|
|
154022
|
-
dailyLessons: 3
|
|
154023
|
-
},
|
|
154024
|
-
metrics: {
|
|
154025
|
-
totalXp: 1000,
|
|
154026
|
-
totalLessons: 50
|
|
154027
|
-
}
|
|
154028
|
-
}
|
|
154029
|
-
},
|
|
154030
|
-
component: {
|
|
154031
|
-
sortOrder: 1,
|
|
154032
|
-
prerequisiteCriteria: "ALL"
|
|
154033
|
-
},
|
|
154034
|
-
resource: {
|
|
154035
|
-
vendorId: "playcademy",
|
|
154036
|
-
roles: ["primary"],
|
|
154037
|
-
importance: "primary",
|
|
154234
|
+
gradingScheme: TIMEBACK_COURSE_DEFAULTS2.gradingScheme,
|
|
154235
|
+
level: TIMEBACK_COURSE_DEFAULTS2.level,
|
|
154038
154236
|
metadata: {
|
|
154039
|
-
|
|
154040
|
-
|
|
154041
|
-
instructionalMethod: "exploratory",
|
|
154042
|
-
language: "en-US"
|
|
154237
|
+
goals: TIMEBACK_COURSE_DEFAULTS2.goals,
|
|
154238
|
+
metrics: TIMEBACK_COURSE_DEFAULTS2.metrics
|
|
154043
154239
|
}
|
|
154044
154240
|
},
|
|
154045
|
-
|
|
154046
|
-
|
|
154047
|
-
|
|
154048
|
-
}
|
|
154241
|
+
component: TIMEBACK_COMPONENT_DEFAULTS2,
|
|
154242
|
+
resource: TIMEBACK_RESOURCE_DEFAULTS2,
|
|
154243
|
+
componentResource: TIMEBACK_COMPONENT_RESOURCE_DEFAULTS2
|
|
154049
154244
|
};
|
|
154050
154245
|
HTTP_STATUS2 = {
|
|
154051
154246
|
CLIENT_ERROR_MIN: 400,
|
|
@@ -154396,68 +154591,89 @@ function buildResourceMetadata({
|
|
|
154396
154591
|
}
|
|
154397
154592
|
return metadata2;
|
|
154398
154593
|
}
|
|
154399
|
-
// ../api-core/src/utils/timeback-
|
|
154594
|
+
// ../api-core/src/utils/timeback-profile.ts
|
|
154400
154595
|
init_src();
|
|
154401
|
-
async function
|
|
154402
|
-
|
|
154403
|
-
|
|
154404
|
-
|
|
154405
|
-
const
|
|
154406
|
-
|
|
154407
|
-
|
|
154408
|
-
|
|
154409
|
-
|
|
154410
|
-
|
|
154411
|
-
|
|
154596
|
+
async function fetchStudentFromOneRoster(timebackId) {
|
|
154597
|
+
log2.debug("[OneRoster] Fetching student profile", { timebackId });
|
|
154598
|
+
try {
|
|
154599
|
+
const client = await getTimebackClient();
|
|
154600
|
+
const user = await client.oneroster.users.get(timebackId);
|
|
154601
|
+
const primaryRoleEntry = user.roles.find((r2) => r2.roleType === "primary");
|
|
154602
|
+
const role = primaryRoleEntry?.role ?? user.roles[0]?.role ?? "student";
|
|
154603
|
+
const orgMap = new Map;
|
|
154604
|
+
if (user.primaryOrg) {
|
|
154605
|
+
orgMap.set(user.primaryOrg.sourcedId, {
|
|
154606
|
+
id: user.primaryOrg.sourcedId,
|
|
154607
|
+
name: user.primaryOrg.name ?? null,
|
|
154608
|
+
type: user.primaryOrg.type || "school",
|
|
154609
|
+
isPrimary: true
|
|
154610
|
+
});
|
|
154611
|
+
}
|
|
154612
|
+
for (const r2 of user.roles) {
|
|
154613
|
+
if (r2.org && !orgMap.has(r2.org.sourcedId)) {
|
|
154614
|
+
orgMap.set(r2.org.sourcedId, {
|
|
154615
|
+
id: r2.org.sourcedId,
|
|
154616
|
+
name: null,
|
|
154617
|
+
type: "school",
|
|
154618
|
+
isPrimary: false
|
|
154619
|
+
});
|
|
154620
|
+
}
|
|
154621
|
+
}
|
|
154622
|
+
const organizations = Array.from(orgMap.values());
|
|
154623
|
+
return { role, organizations };
|
|
154624
|
+
} catch (error2) {
|
|
154625
|
+
log2.warn("[OneRoster] Failed to fetch student, using defaults", { error: error2, timebackId });
|
|
154626
|
+
return { role: "student", organizations: [] };
|
|
154412
154627
|
}
|
|
154413
|
-
|
|
154628
|
+
}
|
|
154629
|
+
async function fetchEnrollmentsFromEduBridge(timebackId) {
|
|
154630
|
+
const db = getDatabase();
|
|
154631
|
+
log2.debug("[EduBridge] Fetching student enrollments", { timebackId });
|
|
154414
154632
|
try {
|
|
154415
154633
|
const client = await getTimebackClient();
|
|
154416
|
-
const
|
|
154417
|
-
const courseIds =
|
|
154418
|
-
if (courseIds.length === 0)
|
|
154634
|
+
const enrollments = await client.getEnrollments(timebackId);
|
|
154635
|
+
const courseIds = enrollments.map((e) => e.courseId).filter((id) => Boolean(id));
|
|
154636
|
+
if (courseIds.length === 0)
|
|
154419
154637
|
return [];
|
|
154420
|
-
|
|
154638
|
+
const courseToSchool = new Map(enrollments.filter((e) => e.school?.id).map((e) => [e.courseId, e.school.id]));
|
|
154421
154639
|
const integrations = await db.query.gameTimebackIntegrations.findMany({
|
|
154422
154640
|
where: inArray(gameTimebackIntegrations.courseId, courseIds)
|
|
154423
154641
|
});
|
|
154424
|
-
return integrations.map((
|
|
154425
|
-
gameId:
|
|
154426
|
-
grade:
|
|
154427
|
-
subject:
|
|
154428
|
-
courseId:
|
|
154642
|
+
return integrations.map((i3) => ({
|
|
154643
|
+
gameId: i3.gameId,
|
|
154644
|
+
grade: i3.grade,
|
|
154645
|
+
subject: i3.subject,
|
|
154646
|
+
courseId: i3.courseId,
|
|
154647
|
+
orgId: courseToSchool.get(i3.courseId)
|
|
154429
154648
|
}));
|
|
154430
154649
|
} catch (error2) {
|
|
154431
|
-
log2.warn("[
|
|
154432
|
-
error: error2,
|
|
154433
|
-
timebackId
|
|
154434
|
-
});
|
|
154650
|
+
log2.warn("[EduBridge] Failed to fetch enrollments", { error: error2, timebackId });
|
|
154435
154651
|
return [];
|
|
154436
154652
|
}
|
|
154437
154653
|
}
|
|
154438
|
-
|
|
154439
|
-
|
|
154440
|
-
|
|
154441
|
-
|
|
154442
|
-
|
|
154443
|
-
|
|
154444
|
-
|
|
154445
|
-
|
|
154446
|
-
return role;
|
|
154447
|
-
} catch (error2) {
|
|
154448
|
-
log2.warn("[timeback] Failed to fetch user role, defaulting to student:", {
|
|
154449
|
-
error: error2,
|
|
154450
|
-
timebackId
|
|
154451
|
-
});
|
|
154452
|
-
return "student";
|
|
154453
|
-
}
|
|
154654
|
+
function filterEnrollmentsByGame(enrollments, gameId) {
|
|
154655
|
+
return enrollments.filter((e) => e.gameId === gameId).map(({ gameId: _4, ...rest }) => rest);
|
|
154656
|
+
}
|
|
154657
|
+
function filterOrganizationsByEnrollments(organizations, enrollments) {
|
|
154658
|
+
const enrollmentOrgIds = new Set(enrollments.map((e) => e.orgId).filter(Boolean));
|
|
154659
|
+
if (enrollmentOrgIds.size === 0)
|
|
154660
|
+
return [];
|
|
154661
|
+
return organizations.filter((o4) => enrollmentOrgIds.has(o4.id));
|
|
154454
154662
|
}
|
|
154455
|
-
async function fetchUserTimebackData(timebackId) {
|
|
154456
|
-
const [role,
|
|
154457
|
-
|
|
154458
|
-
|
|
154663
|
+
async function fetchUserTimebackData(timebackId, gameId) {
|
|
154664
|
+
const [{ role, organizations: allOrganizations }, allEnrollments] = await Promise.all([
|
|
154665
|
+
fetchStudentFromOneRoster(timebackId),
|
|
154666
|
+
fetchEnrollmentsFromEduBridge(timebackId)
|
|
154459
154667
|
]);
|
|
154460
|
-
|
|
154668
|
+
const enrollments = gameId ? filterEnrollmentsByGame(allEnrollments, gameId) : allEnrollments;
|
|
154669
|
+
const organizations = gameId ? filterOrganizationsByEnrollments(allOrganizations, enrollments) : allOrganizations;
|
|
154670
|
+
log2.debug("[Timeback] Fetched student data", {
|
|
154671
|
+
timebackId,
|
|
154672
|
+
role,
|
|
154673
|
+
enrollments: enrollments.map((e) => `${e.subject}:${e.grade}`),
|
|
154674
|
+
organizations: organizations.map((o4) => `${o4.name ?? o4.id} (${o4.type})`)
|
|
154675
|
+
});
|
|
154676
|
+
return { id: timebackId, role, enrollments, organizations };
|
|
154461
154677
|
}
|
|
154462
154678
|
// ../data/src/domains/achievement/types.ts
|
|
154463
154679
|
var AchievementCompletionType;
|
|
@@ -156375,7 +156591,7 @@ async function publishPersonalBestNotification(userId, gameId, rank, newScore, p
|
|
|
156375
156591
|
}
|
|
156376
156592
|
// ../cloudflare/src/playcademy/provider.ts
|
|
156377
156593
|
import { readdir as readdir2, readFile as readFile2, stat } from "node:fs/promises";
|
|
156378
|
-
import { join as
|
|
156594
|
+
import { join as join5, relative } from "node:path";
|
|
156379
156595
|
init_src();
|
|
156380
156596
|
|
|
156381
156597
|
// ../cloudflare/src/core/client.ts
|
|
@@ -156902,7 +157118,7 @@ init_src();
|
|
|
156902
157118
|
// ../cloudflare/src/utils/assets.ts
|
|
156903
157119
|
import { createHash } from "node:crypto";
|
|
156904
157120
|
import { readdir, readFile } from "node:fs/promises";
|
|
156905
|
-
import { join as
|
|
157121
|
+
import { join as join4 } from "node:path";
|
|
156906
157122
|
function hashFile(content) {
|
|
156907
157123
|
return createHash("md5").update(content).digest("hex");
|
|
156908
157124
|
}
|
|
@@ -156922,7 +157138,7 @@ async function scanAssetDirectory(distPath) {
|
|
|
156922
157138
|
async function scanFiles(dir, baseDir = dir) {
|
|
156923
157139
|
const entries = await readdir(dir, { withFileTypes: true });
|
|
156924
157140
|
for (const entry of entries) {
|
|
156925
|
-
const fullPath =
|
|
157141
|
+
const fullPath = join4(dir, entry.name);
|
|
156926
157142
|
if (entry.isDirectory()) {
|
|
156927
157143
|
await scanFiles(fullPath, baseDir);
|
|
156928
157144
|
} else {
|
|
@@ -157294,7 +157510,7 @@ class CloudflareProvider {
|
|
|
157294
157510
|
async function scanDirectory(dir) {
|
|
157295
157511
|
const entries = await readdir2(dir, { withFileTypes: true });
|
|
157296
157512
|
for (const entry of entries) {
|
|
157297
|
-
const fullPath =
|
|
157513
|
+
const fullPath = join5(dir, entry.name);
|
|
157298
157514
|
if (entry.isDirectory()) {
|
|
157299
157515
|
if (await scanDirectory(fullPath))
|
|
157300
157516
|
return true;
|
|
@@ -157316,7 +157532,7 @@ class CloudflareProvider {
|
|
|
157316
157532
|
async resolveAssetBasePath(dirPath) {
|
|
157317
157533
|
const entries = await readdir2(dirPath, { withFileTypes: true });
|
|
157318
157534
|
if (entries.length === 1 && entries[0]?.isDirectory()) {
|
|
157319
|
-
const unwrappedPath =
|
|
157535
|
+
const unwrappedPath = join5(dirPath, entries[0].name);
|
|
157320
157536
|
log2.debug("[CloudflareProvider] Unwrapping wrapper directory", {
|
|
157321
157537
|
wrapper: entries[0].name
|
|
157322
157538
|
});
|
|
@@ -157327,7 +157543,7 @@ class CloudflareProvider {
|
|
|
157327
157543
|
async uploadFilesToR2(dir, baseDir, bucketName) {
|
|
157328
157544
|
const entries = await readdir2(dir, { withFileTypes: true });
|
|
157329
157545
|
for (const entry of entries) {
|
|
157330
|
-
const fullPath =
|
|
157546
|
+
const fullPath = join5(dir, entry.name);
|
|
157331
157547
|
if (entry.isDirectory()) {
|
|
157332
157548
|
await this.uploadFilesToR2(fullPath, baseDir, bucketName);
|
|
157333
157549
|
} else {
|
|
@@ -157815,40 +158031,19 @@ async function seedCurrencies(db) {
|
|
|
157815
158031
|
}
|
|
157816
158032
|
}
|
|
157817
158033
|
|
|
157818
|
-
// src/lib/logging/adapter.ts
|
|
157819
|
-
var customLogger;
|
|
157820
|
-
function setLogger(logger3) {
|
|
157821
|
-
customLogger = logger3;
|
|
157822
|
-
}
|
|
157823
|
-
function getLogger() {
|
|
157824
|
-
if (customLogger) {
|
|
157825
|
-
return customLogger;
|
|
157826
|
-
}
|
|
157827
|
-
return {
|
|
157828
|
-
info: (msg) => console.log(msg),
|
|
157829
|
-
warn: (msg) => console.warn(msg),
|
|
157830
|
-
error: (msg) => console.error(msg)
|
|
157831
|
-
};
|
|
157832
|
-
}
|
|
157833
|
-
var logger3 = {
|
|
157834
|
-
info: (msg) => {
|
|
157835
|
-
if (customLogger || !config.embedded) {
|
|
157836
|
-
getLogger().info(msg);
|
|
157837
|
-
}
|
|
157838
|
-
},
|
|
157839
|
-
warn: (msg) => getLogger().warn(msg),
|
|
157840
|
-
error: (msg) => getLogger().error(msg)
|
|
157841
|
-
};
|
|
157842
158034
|
// src/database/seed/timeback.ts
|
|
157843
|
-
function
|
|
157844
|
-
|
|
157845
|
-
return null;
|
|
157846
|
-
if (studentId === "mock")
|
|
157847
|
-
return `mock-student-${crypto.randomUUID().slice(0, 8)}`;
|
|
157848
|
-
return studentId;
|
|
158035
|
+
function generateMockStudentId(userId) {
|
|
158036
|
+
return `mock-student-${userId.slice(-8)}`;
|
|
157849
158037
|
}
|
|
157850
|
-
function
|
|
157851
|
-
|
|
158038
|
+
function generateTimebackId(userId, isPrimaryUser = false) {
|
|
158039
|
+
const timebackId = config.timeback.timebackId;
|
|
158040
|
+
if (!timebackId) {
|
|
158041
|
+
return null;
|
|
158042
|
+
}
|
|
158043
|
+
if (timebackId === "mock") {
|
|
158044
|
+
return generateMockStudentId(userId);
|
|
158045
|
+
}
|
|
158046
|
+
return isPrimaryUser ? timebackId : generateMockStudentId(userId);
|
|
157852
158047
|
}
|
|
157853
158048
|
async function seedTimebackIntegrations(db, gameId, courses) {
|
|
157854
158049
|
const now2 = new Date;
|
|
@@ -157872,12 +158067,10 @@ async function seedTimebackIntegrations(db, gameId, courses) {
|
|
|
157872
158067
|
});
|
|
157873
158068
|
seededCount++;
|
|
157874
158069
|
} catch (error2) {
|
|
157875
|
-
console.error(`❌ Error seeding
|
|
158070
|
+
console.error(`❌ Error seeding Timeback integration for ${course.subject}:${course.grade}:`, error2);
|
|
157876
158071
|
}
|
|
157877
158072
|
}
|
|
157878
|
-
|
|
157879
|
-
logger3.info(`\uD83D\uDCDA Seeded ${seededCount} TimeBack integration(s)`);
|
|
157880
|
-
}
|
|
158073
|
+
return seededCount;
|
|
157881
158074
|
}
|
|
157882
158075
|
|
|
157883
158076
|
// src/database/seed/games.ts
|
|
@@ -157913,7 +158106,6 @@ async function seedCurrentProjectGame(db, project) {
|
|
|
157913
158106
|
where: (games3, { eq: eq3 }) => eq3(games3.slug, project.slug)
|
|
157914
158107
|
});
|
|
157915
158108
|
if (existingGame) {
|
|
157916
|
-
logger3.info(`\uD83C\uDFAE Game "${project.displayName}" (${project.slug}) already exists`);
|
|
157917
158109
|
if (project.timebackCourses && project.timebackCourses.length > 0) {
|
|
157918
158110
|
await seedTimebackIntegrations(db, existingGame.id, project.timebackCourses);
|
|
157919
158111
|
}
|
|
@@ -158006,11 +158198,12 @@ async function seedSpriteTemplates(db) {
|
|
|
158006
158198
|
// src/database/seed/index.ts
|
|
158007
158199
|
async function seedDemoData(db) {
|
|
158008
158200
|
try {
|
|
158009
|
-
const
|
|
158010
|
-
for (const
|
|
158201
|
+
const primaryUserId = DEMO_USERS.player.id;
|
|
158202
|
+
for (const user of Object.values(DEMO_USERS)) {
|
|
158203
|
+
const isPrimaryUser = user.id === primaryUserId;
|
|
158011
158204
|
const userValues = {
|
|
158012
158205
|
...user,
|
|
158013
|
-
timebackId:
|
|
158206
|
+
timebackId: generateTimebackId(user.id, isPrimaryUser)
|
|
158014
158207
|
};
|
|
158015
158208
|
await db.insert(users).values(userValues).onConflictDoNothing();
|
|
158016
158209
|
}
|
|
@@ -158031,7 +158224,7 @@ async function seedDemoData(db) {
|
|
|
158031
158224
|
console.error("❌ Error seeding demo data:", error2);
|
|
158032
158225
|
throw error2;
|
|
158033
158226
|
}
|
|
158034
|
-
return DEMO_USERS.
|
|
158227
|
+
return DEMO_USERS.player;
|
|
158035
158228
|
}
|
|
158036
158229
|
|
|
158037
158230
|
// src/server/database.ts
|
|
@@ -158071,6 +158264,13 @@ async function setupServerDatabase(processedOptions, project) {
|
|
|
158071
158264
|
// src/server/options.ts
|
|
158072
158265
|
init_src();
|
|
158073
158266
|
var import_json_colorizer = __toESM(require_dist2(), 1);
|
|
158267
|
+
|
|
158268
|
+
// src/lib/logging/adapter.ts
|
|
158269
|
+
var customLogger;
|
|
158270
|
+
function setLogger(logger3) {
|
|
158271
|
+
customLogger = logger3;
|
|
158272
|
+
}
|
|
158273
|
+
// src/server/options.ts
|
|
158074
158274
|
function processServerOptions(port, options) {
|
|
158075
158275
|
const {
|
|
158076
158276
|
verbose = false,
|
|
@@ -158121,6 +158321,10 @@ async function startRealtimeServer(realtimeOptions, betterAuthSecret) {
|
|
|
158121
158321
|
if (!realtimeOptions.enabled) {
|
|
158122
158322
|
return null;
|
|
158123
158323
|
}
|
|
158324
|
+
if (!realtimeOptions.port) {
|
|
158325
|
+
return null;
|
|
158326
|
+
}
|
|
158327
|
+
await waitForPort(realtimeOptions.port);
|
|
158124
158328
|
if (typeof Bun === "undefined") {
|
|
158125
158329
|
try {
|
|
158126
158330
|
return Promise.resolve().then(() => (init_sandbox(), exports_sandbox)).then(({ createSandboxRealtimeServer: createSandboxRealtimeServer2 }) => createSandboxRealtimeServer2({
|
|
@@ -163414,12 +163618,32 @@ async function getUserMe(ctx) {
|
|
|
163414
163618
|
log2.error(`[API /users/me] User not found in DB for valid token ID: ${user.id}`);
|
|
163415
163619
|
throw ApiError.notFound("User not found");
|
|
163416
163620
|
}
|
|
163621
|
+
const timeback3 = userData.timebackId ? await fetchUserTimebackData(userData.timebackId, ctx.gameId) : undefined;
|
|
163622
|
+
if (ctx.gameId) {
|
|
163623
|
+
return {
|
|
163624
|
+
id: userData.id,
|
|
163625
|
+
name: userData.name,
|
|
163626
|
+
role: userData.role,
|
|
163627
|
+
username: userData.username,
|
|
163628
|
+
email: userData.email,
|
|
163629
|
+
timeback: timeback3
|
|
163630
|
+
};
|
|
163631
|
+
}
|
|
163417
163632
|
const timebackAccount = await db.query.accounts.findFirst({
|
|
163418
163633
|
where: and(eq(accounts.userId, user.id), eq(accounts.providerId, "timeback"))
|
|
163419
163634
|
});
|
|
163420
|
-
const timeback3 = userData.timebackId ? await fetchUserTimebackData(userData.timebackId) : undefined;
|
|
163421
163635
|
return {
|
|
163422
|
-
|
|
163636
|
+
id: userData.id,
|
|
163637
|
+
name: userData.name,
|
|
163638
|
+
username: userData.username,
|
|
163639
|
+
email: userData.email,
|
|
163640
|
+
emailVerified: userData.emailVerified,
|
|
163641
|
+
image: userData.image,
|
|
163642
|
+
role: userData.role,
|
|
163643
|
+
developerStatus: userData.developerStatus,
|
|
163644
|
+
characterCreated: userData.characterCreated,
|
|
163645
|
+
createdAt: userData.createdAt,
|
|
163646
|
+
updatedAt: userData.updatedAt,
|
|
163423
163647
|
hasTimebackAccount: !!timebackAccount,
|
|
163424
163648
|
timeback: timeback3
|
|
163425
163649
|
};
|
|
@@ -163431,16 +163655,107 @@ async function getUserMe(ctx) {
|
|
|
163431
163655
|
}
|
|
163432
163656
|
}
|
|
163433
163657
|
|
|
163658
|
+
// src/mocks/timeback.ts
|
|
163659
|
+
init_src();
|
|
163660
|
+
function shouldMockTimeback() {
|
|
163661
|
+
return config.timeback.timebackId === "mock";
|
|
163662
|
+
}
|
|
163663
|
+
function getMockStudentProfile() {
|
|
163664
|
+
const { organization: org, role } = config.timeback;
|
|
163665
|
+
return {
|
|
163666
|
+
role: role ?? "student",
|
|
163667
|
+
organizations: [
|
|
163668
|
+
{
|
|
163669
|
+
id: org?.id ?? "PLAYCADEMY",
|
|
163670
|
+
name: org?.name ?? "Playcademy Studios",
|
|
163671
|
+
type: org?.type ?? "department",
|
|
163672
|
+
isPrimary: true
|
|
163673
|
+
}
|
|
163674
|
+
]
|
|
163675
|
+
};
|
|
163676
|
+
}
|
|
163677
|
+
async function getMockEnrollments(db) {
|
|
163678
|
+
const allIntegrations = await db.query.gameTimebackIntegrations.findMany();
|
|
163679
|
+
return allIntegrations.map((i4) => ({
|
|
163680
|
+
gameId: i4.gameId,
|
|
163681
|
+
grade: i4.grade,
|
|
163682
|
+
subject: i4.subject,
|
|
163683
|
+
courseId: i4.courseId
|
|
163684
|
+
}));
|
|
163685
|
+
}
|
|
163686
|
+
async function getMockTimebackData(db, timebackId, gameId) {
|
|
163687
|
+
const { role, organizations } = getMockStudentProfile();
|
|
163688
|
+
const allEnrollments = await getMockEnrollments(db);
|
|
163689
|
+
const enrollments = gameId ? allEnrollments.filter((e2) => e2.gameId === gameId).map(({ gameId: _5, ...rest }) => rest) : allEnrollments;
|
|
163690
|
+
log2.debug("[Timeback] Sandbox is using mock data", {
|
|
163691
|
+
timebackId,
|
|
163692
|
+
role,
|
|
163693
|
+
enrollments: enrollments.map((e2) => `${e2.subject}:${e2.grade}`),
|
|
163694
|
+
organizations: organizations.map((o5) => `${o5.name ?? o5.id}`)
|
|
163695
|
+
});
|
|
163696
|
+
return { id: timebackId, role, enrollments, organizations };
|
|
163697
|
+
}
|
|
163698
|
+
async function buildMockUserResponse(db, user, gameId) {
|
|
163699
|
+
const timeback3 = user.timebackId ? await getMockTimebackData(db, user.timebackId, gameId) : undefined;
|
|
163700
|
+
if (gameId) {
|
|
163701
|
+
return {
|
|
163702
|
+
id: user.id,
|
|
163703
|
+
name: user.name,
|
|
163704
|
+
role: user.role,
|
|
163705
|
+
username: user.username,
|
|
163706
|
+
email: user.email,
|
|
163707
|
+
timeback: timeback3
|
|
163708
|
+
};
|
|
163709
|
+
}
|
|
163710
|
+
const timebackAccount = await db.query.accounts.findFirst({
|
|
163711
|
+
where: and(eq(accounts.userId, user.id), eq(accounts.providerId, "timeback"))
|
|
163712
|
+
});
|
|
163713
|
+
return {
|
|
163714
|
+
id: user.id,
|
|
163715
|
+
name: user.name,
|
|
163716
|
+
username: user.username,
|
|
163717
|
+
email: user.email,
|
|
163718
|
+
emailVerified: user.emailVerified,
|
|
163719
|
+
image: user.image,
|
|
163720
|
+
role: user.role,
|
|
163721
|
+
developerStatus: user.developerStatus,
|
|
163722
|
+
characterCreated: user.characterCreated,
|
|
163723
|
+
createdAt: user.createdAt,
|
|
163724
|
+
updatedAt: user.updatedAt,
|
|
163725
|
+
hasTimebackAccount: !!timebackAccount,
|
|
163726
|
+
timeback: timeback3
|
|
163727
|
+
};
|
|
163728
|
+
}
|
|
163729
|
+
|
|
163434
163730
|
// src/routes/platform/users.ts
|
|
163435
163731
|
var usersRouter = new Hono2;
|
|
163436
163732
|
usersRouter.get("/me", async (c3) => {
|
|
163437
|
-
const
|
|
163438
|
-
|
|
163439
|
-
|
|
163440
|
-
|
|
163441
|
-
|
|
163442
|
-
}
|
|
163733
|
+
const user = c3.get("user");
|
|
163734
|
+
const gameId = c3.get("gameId");
|
|
163735
|
+
if (!user) {
|
|
163736
|
+
const error2 = ApiError.unauthorized("Valid session or bearer token required");
|
|
163737
|
+
return c3.json(createErrorResponse(error2), error2.statusCode);
|
|
163738
|
+
}
|
|
163443
163739
|
try {
|
|
163740
|
+
if (shouldMockTimeback()) {
|
|
163741
|
+
const db = c3.get("db");
|
|
163742
|
+
const userData2 = await db.query.users.findFirst({
|
|
163743
|
+
where: eq(users.id, user.id)
|
|
163744
|
+
});
|
|
163745
|
+
if (!userData2) {
|
|
163746
|
+
const error2 = ApiError.notFound("User not found");
|
|
163747
|
+
return c3.json(createErrorResponse(error2), error2.statusCode);
|
|
163748
|
+
}
|
|
163749
|
+
const response = await buildMockUserResponse(db, userData2, gameId);
|
|
163750
|
+
return c3.json(response);
|
|
163751
|
+
}
|
|
163752
|
+
const ctx = {
|
|
163753
|
+
user,
|
|
163754
|
+
params: {},
|
|
163755
|
+
url: new URL(c3.req.url),
|
|
163756
|
+
request: c3.req.raw,
|
|
163757
|
+
gameId
|
|
163758
|
+
};
|
|
163444
163759
|
const userData = await getUserMe(ctx);
|
|
163445
163760
|
return c3.json(userData);
|
|
163446
163761
|
} catch (error2) {
|
|
@@ -168780,7 +169095,7 @@ async function getTodayTimeBackXp(ctx) {
|
|
|
168780
169095
|
throw error2;
|
|
168781
169096
|
if (error2 instanceof InvalidTimezoneError)
|
|
168782
169097
|
throw ApiError.badRequest(error2.message);
|
|
168783
|
-
log2.error("[
|
|
169098
|
+
log2.error("[Timeback] getTodayTimeBackXp failed", { error: error2 });
|
|
168784
169099
|
throw ApiError.internal("Failed to get today's TimeBack XP", error2);
|
|
168785
169100
|
}
|
|
168786
169101
|
}
|
|
@@ -168796,7 +169111,7 @@ async function getTotalTimeBackXp(ctx) {
|
|
|
168796
169111
|
totalXp: Number(result[0]?.totalXp) || 0
|
|
168797
169112
|
};
|
|
168798
169113
|
} catch (error2) {
|
|
168799
|
-
log2.error("[
|
|
169114
|
+
log2.error("[Timeback] getTotalTimeBackXp failed", { error: error2 });
|
|
168800
169115
|
throw ApiError.internal("Failed to get total TimeBack XP", error2);
|
|
168801
169116
|
}
|
|
168802
169117
|
}
|
|
@@ -168846,7 +169161,7 @@ async function updateTodayTimeBackXp(ctx) {
|
|
|
168846
169161
|
} catch (error2) {
|
|
168847
169162
|
if (error2 instanceof ApiError)
|
|
168848
169163
|
throw error2;
|
|
168849
|
-
log2.error("[
|
|
169164
|
+
log2.error("[Timeback] updateTodayTimeBackXp failed", { error: error2 });
|
|
168850
169165
|
throw ApiError.internal("Failed to update today's TimeBack XP", error2);
|
|
168851
169166
|
}
|
|
168852
169167
|
}
|
|
@@ -168881,7 +169196,7 @@ async function getTimeBackXpHistory(ctx) {
|
|
|
168881
169196
|
}))
|
|
168882
169197
|
};
|
|
168883
169198
|
} catch (error2) {
|
|
168884
|
-
log2.error("[
|
|
169199
|
+
log2.error("[Timeback] getTimeBackXpHistory failed", { error: error2 });
|
|
168885
169200
|
throw ApiError.internal("Failed to get TimeBack XP history", error2);
|
|
168886
169201
|
}
|
|
168887
169202
|
}
|
|
@@ -168897,7 +169212,7 @@ async function getStudentEnrollments(ctx) {
|
|
|
168897
169212
|
throw ApiError.badRequest("Missing timebackId parameter");
|
|
168898
169213
|
}
|
|
168899
169214
|
log2.debug("[API] Getting student enrollments", { userId: user.id, timebackId });
|
|
168900
|
-
const enrollments = await
|
|
169215
|
+
const enrollments = await fetchEnrollmentsFromEduBridge(timebackId);
|
|
168901
169216
|
log2.info("[API] Retrieved student enrollments", {
|
|
168902
169217
|
userId: user.id,
|
|
168903
169218
|
timebackId,
|
|
@@ -169098,13 +169413,23 @@ timebackRouter.post("/end-activity", async (c3) => {
|
|
|
169098
169413
|
});
|
|
169099
169414
|
timebackRouter.get("/enrollments/:timebackId", async (c3) => {
|
|
169100
169415
|
const timebackId = c3.req.param("timebackId");
|
|
169101
|
-
const
|
|
169102
|
-
|
|
169103
|
-
|
|
169104
|
-
|
|
169105
|
-
|
|
169106
|
-
};
|
|
169416
|
+
const user = c3.get("user");
|
|
169417
|
+
if (!user) {
|
|
169418
|
+
const error2 = ApiError.unauthorized("Must be logged in to get enrollments");
|
|
169419
|
+
return c3.json(createErrorResponse(error2), error2.statusCode);
|
|
169420
|
+
}
|
|
169107
169421
|
try {
|
|
169422
|
+
if (shouldMockTimeback()) {
|
|
169423
|
+
const db = c3.get("db");
|
|
169424
|
+
const enrollments2 = await getMockEnrollments(db);
|
|
169425
|
+
return c3.json({ enrollments: enrollments2 });
|
|
169426
|
+
}
|
|
169427
|
+
const ctx = {
|
|
169428
|
+
user,
|
|
169429
|
+
params: { timebackId },
|
|
169430
|
+
url: new URL(c3.req.url),
|
|
169431
|
+
request: c3.req.raw
|
|
169432
|
+
};
|
|
169108
169433
|
const result = await getStudentEnrollments(ctx);
|
|
169109
169434
|
return c3.json(result);
|
|
169110
169435
|
} catch (error2) {
|
|
@@ -169420,6 +169745,7 @@ function registerRoutes(app) {
|
|
|
169420
169745
|
// src/server/index.ts
|
|
169421
169746
|
var version3 = package_default.version;
|
|
169422
169747
|
async function startServer(port, project, options = {}) {
|
|
169748
|
+
await waitForPort(port);
|
|
169423
169749
|
const processedOptions = processServerOptions(port, options);
|
|
169424
169750
|
const db = await setupServerDatabase(processedOptions, project);
|
|
169425
169751
|
const app = createApp(db, {
|
|
@@ -169432,11 +169758,20 @@ async function startServer(port, project, options = {}) {
|
|
|
169432
169758
|
return {
|
|
169433
169759
|
main: mainServer,
|
|
169434
169760
|
realtime: realtimeServer,
|
|
169761
|
+
timebackMode: getTimebackDisplayMode(),
|
|
169762
|
+
setRole: (role) => {
|
|
169763
|
+
config.timeback.role = role;
|
|
169764
|
+
},
|
|
169435
169765
|
stop: () => {
|
|
169436
|
-
|
|
169437
|
-
|
|
169438
|
-
|
|
169439
|
-
|
|
169766
|
+
return new Promise((resolve2) => {
|
|
169767
|
+
if (realtimeServer?.stop)
|
|
169768
|
+
realtimeServer.stop();
|
|
169769
|
+
if (mainServer.close) {
|
|
169770
|
+
mainServer.close(() => resolve2());
|
|
169771
|
+
} else {
|
|
169772
|
+
resolve2();
|
|
169773
|
+
}
|
|
169774
|
+
});
|
|
169440
169775
|
}
|
|
169441
169776
|
};
|
|
169442
169777
|
}
|