@playcademy/sandbox 0.2.2 → 0.3.0
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 +809 -570
- 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 +22 -2
- package/dist/server.js +797 -462
- package/package.json +5 -1
package/dist/cli.js
CHANGED
|
@@ -6389,7 +6389,7 @@ If you have no idea what this means or what Pirates is, let me explain: Pirates
|
|
|
6389
6389
|
resolve2(data);
|
|
6390
6390
|
});
|
|
6391
6391
|
});
|
|
6392
|
-
var
|
|
6392
|
+
var readFileSync2 = (fp) => {
|
|
6393
6393
|
return _fs.default.readFileSync(fp, "utf8");
|
|
6394
6394
|
};
|
|
6395
6395
|
var pathExists = (fp) => new Promise((resolve2) => {
|
|
@@ -6620,7 +6620,7 @@ If you have no idea what this means or what Pirates is, let me explain: Pirates
|
|
|
6620
6620
|
data: this.packageJsonCache.get(filepath)[options.packageKey]
|
|
6621
6621
|
};
|
|
6622
6622
|
}
|
|
6623
|
-
const data = this.options.parseJSON(
|
|
6623
|
+
const data = this.options.parseJSON(readFileSync2(filepath));
|
|
6624
6624
|
return {
|
|
6625
6625
|
path: filepath,
|
|
6626
6626
|
data
|
|
@@ -6628,7 +6628,7 @@ If you have no idea what this means or what Pirates is, let me explain: Pirates
|
|
|
6628
6628
|
}
|
|
6629
6629
|
return {
|
|
6630
6630
|
path: filepath,
|
|
6631
|
-
data:
|
|
6631
|
+
data: readFileSync2(filepath)
|
|
6632
6632
|
};
|
|
6633
6633
|
}
|
|
6634
6634
|
return {};
|
|
@@ -8227,19 +8227,19 @@ If you have no idea what this means or what Pirates is, let me explain: Pirates
|
|
|
8227
8227
|
return walkForTsConfig(parentDirectory, readdirSync);
|
|
8228
8228
|
}
|
|
8229
8229
|
exports2.walkForTsConfig = walkForTsConfig;
|
|
8230
|
-
function loadTsconfig(configFilePath,
|
|
8231
|
-
if (
|
|
8232
|
-
|
|
8230
|
+
function loadTsconfig(configFilePath, existsSync3, readFileSync2) {
|
|
8231
|
+
if (existsSync3 === undefined) {
|
|
8232
|
+
existsSync3 = fs3.existsSync;
|
|
8233
8233
|
}
|
|
8234
|
-
if (
|
|
8235
|
-
|
|
8234
|
+
if (readFileSync2 === undefined) {
|
|
8235
|
+
readFileSync2 = function(filename) {
|
|
8236
8236
|
return fs3.readFileSync(filename, "utf8");
|
|
8237
8237
|
};
|
|
8238
8238
|
}
|
|
8239
|
-
if (!
|
|
8239
|
+
if (!existsSync3(configFilePath)) {
|
|
8240
8240
|
return;
|
|
8241
8241
|
}
|
|
8242
|
-
var configString =
|
|
8242
|
+
var configString = readFileSync2(configFilePath);
|
|
8243
8243
|
var cleanedJson = StripBom(configString);
|
|
8244
8244
|
var config2;
|
|
8245
8245
|
try {
|
|
@@ -8252,27 +8252,27 @@ If you have no idea what this means or what Pirates is, let me explain: Pirates
|
|
|
8252
8252
|
var base = undefined;
|
|
8253
8253
|
if (Array.isArray(extendedConfig)) {
|
|
8254
8254
|
base = extendedConfig.reduce(function(currBase, extendedConfigElement) {
|
|
8255
|
-
return mergeTsconfigs(currBase, loadTsconfigFromExtends(configFilePath, extendedConfigElement,
|
|
8255
|
+
return mergeTsconfigs(currBase, loadTsconfigFromExtends(configFilePath, extendedConfigElement, existsSync3, readFileSync2));
|
|
8256
8256
|
}, {});
|
|
8257
8257
|
} else {
|
|
8258
|
-
base = loadTsconfigFromExtends(configFilePath, extendedConfig,
|
|
8258
|
+
base = loadTsconfigFromExtends(configFilePath, extendedConfig, existsSync3, readFileSync2);
|
|
8259
8259
|
}
|
|
8260
8260
|
return mergeTsconfigs(base, config2);
|
|
8261
8261
|
}
|
|
8262
8262
|
return config2;
|
|
8263
8263
|
}
|
|
8264
8264
|
exports2.loadTsconfig = loadTsconfig;
|
|
8265
|
-
function loadTsconfigFromExtends(configFilePath, extendedConfigValue,
|
|
8265
|
+
function loadTsconfigFromExtends(configFilePath, extendedConfigValue, existsSync3, readFileSync2) {
|
|
8266
8266
|
var _a;
|
|
8267
8267
|
if (typeof extendedConfigValue === "string" && extendedConfigValue.indexOf(".json") === -1) {
|
|
8268
8268
|
extendedConfigValue += ".json";
|
|
8269
8269
|
}
|
|
8270
8270
|
var currentDir = path.dirname(configFilePath);
|
|
8271
8271
|
var extendedConfigPath = path.join(currentDir, extendedConfigValue);
|
|
8272
|
-
if (extendedConfigValue.indexOf("/") !== -1 && extendedConfigValue.indexOf(".") !== -1 && !
|
|
8272
|
+
if (extendedConfigValue.indexOf("/") !== -1 && extendedConfigValue.indexOf(".") !== -1 && !existsSync3(extendedConfigPath)) {
|
|
8273
8273
|
extendedConfigPath = path.join(currentDir, "node_modules", extendedConfigValue);
|
|
8274
8274
|
}
|
|
8275
|
-
var config2 = loadTsconfig(extendedConfigPath,
|
|
8275
|
+
var config2 = loadTsconfig(extendedConfigPath, existsSync3, readFileSync2) || {};
|
|
8276
8276
|
if ((_a = config2.compilerOptions) === null || _a === undefined ? undefined : _a.baseUrl) {
|
|
8277
8277
|
var extendsDir = path.dirname(extendedConfigValue);
|
|
8278
8278
|
config2.compilerOptions.baseUrl = path.join(extendsDir, config2.compilerOptions.baseUrl);
|
|
@@ -32322,7 +32322,7 @@ globstar while`, file, fr, pattern, pr2, swallowee);
|
|
|
32322
32322
|
return new SQL2([new StringChunk2(str)]);
|
|
32323
32323
|
}
|
|
32324
32324
|
sql22.raw = raw2;
|
|
32325
|
-
function
|
|
32325
|
+
function join4(chunks, separator) {
|
|
32326
32326
|
const result = [];
|
|
32327
32327
|
for (const [i3, chunk] of chunks.entries()) {
|
|
32328
32328
|
if (i3 > 0 && separator !== undefined) {
|
|
@@ -32332,7 +32332,7 @@ globstar while`, file, fr, pattern, pr2, swallowee);
|
|
|
32332
32332
|
}
|
|
32333
32333
|
return new SQL2(result);
|
|
32334
32334
|
}
|
|
32335
|
-
sql22.join =
|
|
32335
|
+
sql22.join = join4;
|
|
32336
32336
|
function identifier(value) {
|
|
32337
32337
|
return new Name2(value);
|
|
32338
32338
|
}
|
|
@@ -35162,7 +35162,7 @@ params: ${params}`);
|
|
|
35162
35162
|
const tableName = getTableLikeName2(table62);
|
|
35163
35163
|
for (const item of extractUsedTable(table62))
|
|
35164
35164
|
this.usedTables.add(item);
|
|
35165
|
-
if (typeof tableName === "string" && this.config.joins?.some((
|
|
35165
|
+
if (typeof tableName === "string" && this.config.joins?.some((join4) => join4.alias === tableName)) {
|
|
35166
35166
|
throw new Error(`Alias "${tableName}" is already used in this query`);
|
|
35167
35167
|
}
|
|
35168
35168
|
if (!this.isPartialSelect) {
|
|
@@ -36020,7 +36020,7 @@ params: ${params}`);
|
|
|
36020
36020
|
createJoin(joinType) {
|
|
36021
36021
|
return (table62, on2) => {
|
|
36022
36022
|
const tableName = getTableLikeName2(table62);
|
|
36023
|
-
if (typeof tableName === "string" && this.config.joins.some((
|
|
36023
|
+
if (typeof tableName === "string" && this.config.joins.some((join4) => join4.alias === tableName)) {
|
|
36024
36024
|
throw new Error(`Alias "${tableName}" is already used in this query`);
|
|
36025
36025
|
}
|
|
36026
36026
|
if (typeof on2 === "function") {
|
|
@@ -36066,10 +36066,10 @@ params: ${params}`);
|
|
|
36066
36066
|
const fromFields = this.getTableLikeFields(this.config.from);
|
|
36067
36067
|
fields[tableName] = fromFields;
|
|
36068
36068
|
}
|
|
36069
|
-
for (const
|
|
36070
|
-
const tableName2 = getTableLikeName2(
|
|
36071
|
-
if (typeof tableName2 === "string" && !is2(
|
|
36072
|
-
const fromFields = this.getTableLikeFields(
|
|
36069
|
+
for (const join4 of this.config.joins) {
|
|
36070
|
+
const tableName2 = getTableLikeName2(join4.table);
|
|
36071
|
+
if (typeof tableName2 === "string" && !is2(join4.table, SQL2)) {
|
|
36072
|
+
const fromFields = this.getTableLikeFields(join4.table);
|
|
36073
36073
|
fields[tableName2] = fromFields;
|
|
36074
36074
|
}
|
|
36075
36075
|
}
|
|
@@ -39644,7 +39644,7 @@ ORDER BY
|
|
|
39644
39644
|
const tableName = getTableLikeName2(table62);
|
|
39645
39645
|
for (const item of extractUsedTable2(table62))
|
|
39646
39646
|
this.usedTables.add(item);
|
|
39647
|
-
if (typeof tableName === "string" && this.config.joins?.some((
|
|
39647
|
+
if (typeof tableName === "string" && this.config.joins?.some((join4) => join4.alias === tableName)) {
|
|
39648
39648
|
throw new Error(`Alias "${tableName}" is already used in this query`);
|
|
39649
39649
|
}
|
|
39650
39650
|
if (!this.isPartialSelect) {
|
|
@@ -40085,7 +40085,7 @@ ORDER BY
|
|
|
40085
40085
|
createJoin(joinType) {
|
|
40086
40086
|
return (table62, on2) => {
|
|
40087
40087
|
const tableName = getTableLikeName2(table62);
|
|
40088
|
-
if (typeof tableName === "string" && this.config.joins.some((
|
|
40088
|
+
if (typeof tableName === "string" && this.config.joins.some((join4) => join4.alias === tableName)) {
|
|
40089
40089
|
throw new Error(`Alias "${tableName}" is already used in this query`);
|
|
40090
40090
|
}
|
|
40091
40091
|
if (typeof on2 === "function") {
|
|
@@ -43692,7 +43692,7 @@ ${withStyle.errorWarning(`We've found duplicated view name across ${source_defau
|
|
|
43692
43692
|
const tableName = getTableLikeName2(table62);
|
|
43693
43693
|
for (const item of extractUsedTable3(table62))
|
|
43694
43694
|
this.usedTables.add(item);
|
|
43695
|
-
if (typeof tableName === "string" && this.config.joins?.some((
|
|
43695
|
+
if (typeof tableName === "string" && this.config.joins?.some((join4) => join4.alias === tableName)) {
|
|
43696
43696
|
throw new Error(`Alias "${tableName}" is already used in this query`);
|
|
43697
43697
|
}
|
|
43698
43698
|
if (!this.isPartialSelect) {
|
|
@@ -47734,7 +47734,7 @@ AND
|
|
|
47734
47734
|
const tableName = getTableLikeName2(table62);
|
|
47735
47735
|
for (const item of extractUsedTable4(table62))
|
|
47736
47736
|
this.usedTables.add(item);
|
|
47737
|
-
if (typeof tableName === "string" && this.config.joins?.some((
|
|
47737
|
+
if (typeof tableName === "string" && this.config.joins?.some((join4) => join4.alias === tableName)) {
|
|
47738
47738
|
throw new Error(`Alias "${tableName}" is already used in this query`);
|
|
47739
47739
|
}
|
|
47740
47740
|
if (!this.isPartialSelect) {
|
|
@@ -135071,7 +135071,7 @@ function hasTimebackCredentials() {
|
|
|
135071
135071
|
return false;
|
|
135072
135072
|
}
|
|
135073
135073
|
function hasTimebackFullConfig() {
|
|
135074
|
-
return hasTimebackCredentials() && !!(config.timeback.courseId && config.timeback.
|
|
135074
|
+
return hasTimebackCredentials() && !!(config.timeback.courseId && config.timeback.timebackId);
|
|
135075
135075
|
}
|
|
135076
135076
|
function requireTimebackCredentials() {
|
|
135077
135077
|
if (hasTimebackCredentials())
|
|
@@ -135125,11 +135125,36 @@ function configureTimeback(options) {
|
|
|
135125
135125
|
config.timeback.courseId = options.courseId;
|
|
135126
135126
|
process.env.SANDBOX_TIMEBACK_COURSE_ID = options.courseId;
|
|
135127
135127
|
}
|
|
135128
|
-
if (options.
|
|
135129
|
-
config.timeback.
|
|
135130
|
-
process.env.SANDBOX_TIMEBACK_STUDENT_ID = options.
|
|
135128
|
+
if (options.timebackId) {
|
|
135129
|
+
config.timeback.timebackId = options.timebackId;
|
|
135130
|
+
process.env.SANDBOX_TIMEBACK_STUDENT_ID = options.timebackId;
|
|
135131
|
+
const isMockMode = options.timebackId === "mock";
|
|
135132
|
+
process.env.SANDBOX_TIMEBACK_MOCK_MODE = isMockMode ? "true" : "false";
|
|
135133
|
+
}
|
|
135134
|
+
if (options.organization) {
|
|
135135
|
+
config.timeback.organization = options.organization;
|
|
135136
|
+
process.env.SANDBOX_TIMEBACK_ORG_ID = options.organization.id;
|
|
135137
|
+
if (options.organization.name) {
|
|
135138
|
+
process.env.SANDBOX_TIMEBACK_ORG_NAME = options.organization.name;
|
|
135139
|
+
}
|
|
135140
|
+
if (options.organization.type) {
|
|
135141
|
+
process.env.SANDBOX_TIMEBACK_ORG_TYPE = options.organization.type;
|
|
135142
|
+
}
|
|
135143
|
+
}
|
|
135144
|
+
if (options.role) {
|
|
135145
|
+
config.timeback.role = options.role;
|
|
135146
|
+
process.env.SANDBOX_TIMEBACK_ROLE = options.role;
|
|
135131
135147
|
}
|
|
135132
135148
|
}
|
|
135149
|
+
function getTimebackDisplayMode() {
|
|
135150
|
+
const { timebackId, mode } = config.timeback;
|
|
135151
|
+
if (!timebackId)
|
|
135152
|
+
return null;
|
|
135153
|
+
if (timebackId === "mock" || !hasTimebackCredentials()) {
|
|
135154
|
+
return "mock";
|
|
135155
|
+
}
|
|
135156
|
+
return mode;
|
|
135157
|
+
}
|
|
135133
135158
|
|
|
135134
135159
|
// src/config/mutators.ts
|
|
135135
135160
|
function setEmbeddedMode(embedded) {
|
|
@@ -135152,13 +135177,203 @@ var config = {
|
|
|
135152
135177
|
clientSecret: process.env.TIMEBACK_API_CLIENT_SECRET,
|
|
135153
135178
|
authUrl: process.env.TIMEBACK_API_AUTH_URL,
|
|
135154
135179
|
courseId: process.env.SANDBOX_TIMEBACK_COURSE_ID,
|
|
135155
|
-
|
|
135180
|
+
timebackId: process.env.SANDBOX_TIMEBACK_STUDENT_ID
|
|
135156
135181
|
}
|
|
135157
135182
|
};
|
|
135158
135183
|
process.env.BETTER_AUTH_SECRET = config.auth.betterAuthSecret;
|
|
135159
135184
|
process.env.GAME_JWT_SECRET = config.auth.gameJwtSecret;
|
|
135160
135185
|
process.env.PUBLIC_IS_LOCAL = "true";
|
|
135161
135186
|
|
|
135187
|
+
// src/constants/demo-users.ts
|
|
135188
|
+
var now = new Date;
|
|
135189
|
+
var DEMO_USER_IDS = {
|
|
135190
|
+
player: "00000000-0000-0000-0000-000000000001",
|
|
135191
|
+
developer: "00000000-0000-0000-0000-000000000002",
|
|
135192
|
+
admin: "00000000-0000-0000-0000-000000000003",
|
|
135193
|
+
pendingDeveloper: "00000000-0000-0000-0000-000000000004",
|
|
135194
|
+
unverifiedPlayer: "00000000-0000-0000-0000-000000000005"
|
|
135195
|
+
};
|
|
135196
|
+
var DEMO_USERS = {
|
|
135197
|
+
admin: {
|
|
135198
|
+
id: DEMO_USER_IDS.admin,
|
|
135199
|
+
name: "Admin User",
|
|
135200
|
+
username: "admin_user",
|
|
135201
|
+
email: "admin@playcademy.com",
|
|
135202
|
+
emailVerified: true,
|
|
135203
|
+
image: null,
|
|
135204
|
+
role: "admin",
|
|
135205
|
+
developerStatus: "approved",
|
|
135206
|
+
createdAt: now,
|
|
135207
|
+
updatedAt: now
|
|
135208
|
+
},
|
|
135209
|
+
player: {
|
|
135210
|
+
id: DEMO_USER_IDS.player,
|
|
135211
|
+
name: "Player User",
|
|
135212
|
+
username: "player_user",
|
|
135213
|
+
email: "player@playcademy.com",
|
|
135214
|
+
emailVerified: true,
|
|
135215
|
+
image: null,
|
|
135216
|
+
role: "player",
|
|
135217
|
+
developerStatus: "none",
|
|
135218
|
+
createdAt: now,
|
|
135219
|
+
updatedAt: now
|
|
135220
|
+
},
|
|
135221
|
+
developer: {
|
|
135222
|
+
id: DEMO_USER_IDS.developer,
|
|
135223
|
+
name: "Developer User",
|
|
135224
|
+
username: "developer_user",
|
|
135225
|
+
email: "developer@playcademy.com",
|
|
135226
|
+
emailVerified: true,
|
|
135227
|
+
image: null,
|
|
135228
|
+
role: "developer",
|
|
135229
|
+
developerStatus: "approved",
|
|
135230
|
+
createdAt: now,
|
|
135231
|
+
updatedAt: now
|
|
135232
|
+
},
|
|
135233
|
+
pendingDeveloper: {
|
|
135234
|
+
id: DEMO_USER_IDS.pendingDeveloper,
|
|
135235
|
+
name: "Pending Developer",
|
|
135236
|
+
username: "pending_dev",
|
|
135237
|
+
email: "pending@playcademy.com",
|
|
135238
|
+
emailVerified: true,
|
|
135239
|
+
image: null,
|
|
135240
|
+
role: "developer",
|
|
135241
|
+
developerStatus: "pending",
|
|
135242
|
+
createdAt: now,
|
|
135243
|
+
updatedAt: now
|
|
135244
|
+
},
|
|
135245
|
+
unverifiedPlayer: {
|
|
135246
|
+
id: DEMO_USER_IDS.unverifiedPlayer,
|
|
135247
|
+
name: "Unverified Player",
|
|
135248
|
+
username: "unverified_player",
|
|
135249
|
+
email: "unverified@playcademy.com",
|
|
135250
|
+
emailVerified: false,
|
|
135251
|
+
image: null,
|
|
135252
|
+
role: "player",
|
|
135253
|
+
developerStatus: "none",
|
|
135254
|
+
createdAt: now,
|
|
135255
|
+
updatedAt: now
|
|
135256
|
+
}
|
|
135257
|
+
};
|
|
135258
|
+
var DEMO_USER = DEMO_USERS.player;
|
|
135259
|
+
// src/constants/demo-tokens.ts
|
|
135260
|
+
var DEMO_TOKENS = {
|
|
135261
|
+
"sandbox-demo-token": DEMO_USERS.player,
|
|
135262
|
+
"sandbox-admin-token": DEMO_USERS.admin,
|
|
135263
|
+
"sandbox-player-token": DEMO_USERS.player,
|
|
135264
|
+
"sandbox-developer-token": DEMO_USERS.developer,
|
|
135265
|
+
"sandbox-pending-dev-token": DEMO_USERS.pendingDeveloper,
|
|
135266
|
+
"sandbox-unverified-token": DEMO_USERS.unverifiedPlayer,
|
|
135267
|
+
"mock-game-token-for-local-dev": DEMO_USERS.player
|
|
135268
|
+
};
|
|
135269
|
+
var DEMO_TOKEN = "sandbox-demo-token";
|
|
135270
|
+
var MOCK_GAME_ID = "mock-game-id-from-template";
|
|
135271
|
+
// src/constants/demo-items.ts
|
|
135272
|
+
var DEMO_ITEM_IDS = {
|
|
135273
|
+
playcademyCredits: "10000000-0000-0000-0000-000000000001",
|
|
135274
|
+
foundingMemberBadge: "10000000-0000-0000-0000-000000000002",
|
|
135275
|
+
earlyAdopterBadge: "10000000-0000-0000-0000-000000000003",
|
|
135276
|
+
firstGameBadge: "10000000-0000-0000-0000-000000000004",
|
|
135277
|
+
commonSword: "10000000-0000-0000-0000-000000000005",
|
|
135278
|
+
smallHealthPotion: "10000000-0000-0000-0000-000000000006",
|
|
135279
|
+
smallBackpack: "10000000-0000-0000-0000-000000000007"
|
|
135280
|
+
};
|
|
135281
|
+
var PLAYCADEMY_CREDITS_ID = DEMO_ITEM_IDS.playcademyCredits;
|
|
135282
|
+
var SAMPLE_ITEMS = [
|
|
135283
|
+
{
|
|
135284
|
+
id: PLAYCADEMY_CREDITS_ID,
|
|
135285
|
+
slug: "PLAYCADEMY_CREDITS",
|
|
135286
|
+
gameId: null,
|
|
135287
|
+
displayName: "PLAYCADEMY credits",
|
|
135288
|
+
description: "The main currency used across PLAYCADEMY.",
|
|
135289
|
+
type: "currency",
|
|
135290
|
+
isPlaceable: false,
|
|
135291
|
+
imageUrl: "http://playcademy-sandbox.local/playcademy-credit.png",
|
|
135292
|
+
metadata: {
|
|
135293
|
+
rarity: "common"
|
|
135294
|
+
}
|
|
135295
|
+
},
|
|
135296
|
+
{
|
|
135297
|
+
id: DEMO_ITEM_IDS.foundingMemberBadge,
|
|
135298
|
+
slug: "FOUNDING_MEMBER_BADGE",
|
|
135299
|
+
gameId: null,
|
|
135300
|
+
displayName: "Founding Member Badge",
|
|
135301
|
+
description: "Reserved for founding core team of the PLAYCADEMY platform.",
|
|
135302
|
+
type: "badge",
|
|
135303
|
+
isPlaceable: false,
|
|
135304
|
+
imageUrl: null,
|
|
135305
|
+
metadata: {
|
|
135306
|
+
rarity: "legendary"
|
|
135307
|
+
}
|
|
135308
|
+
},
|
|
135309
|
+
{
|
|
135310
|
+
id: DEMO_ITEM_IDS.earlyAdopterBadge,
|
|
135311
|
+
slug: "EARLY_ADOPTER_BADGE",
|
|
135312
|
+
gameId: null,
|
|
135313
|
+
displayName: "Early Adopter Badge",
|
|
135314
|
+
description: "Awarded to users who joined during the beta phase.",
|
|
135315
|
+
type: "badge",
|
|
135316
|
+
isPlaceable: false,
|
|
135317
|
+
imageUrl: null,
|
|
135318
|
+
metadata: {
|
|
135319
|
+
rarity: "epic"
|
|
135320
|
+
}
|
|
135321
|
+
},
|
|
135322
|
+
{
|
|
135323
|
+
id: DEMO_ITEM_IDS.firstGameBadge,
|
|
135324
|
+
slug: "FIRST_GAME_BADGE",
|
|
135325
|
+
gameId: null,
|
|
135326
|
+
displayName: "First Game Played",
|
|
135327
|
+
description: "Awarded for playing your first game in the Playcademy platform.",
|
|
135328
|
+
type: "badge",
|
|
135329
|
+
isPlaceable: false,
|
|
135330
|
+
imageUrl: "http://playcademy-sandbox.local/first-game-badge.png",
|
|
135331
|
+
metadata: {
|
|
135332
|
+
rarity: "uncommon"
|
|
135333
|
+
}
|
|
135334
|
+
},
|
|
135335
|
+
{
|
|
135336
|
+
id: DEMO_ITEM_IDS.commonSword,
|
|
135337
|
+
slug: "COMMON_SWORD",
|
|
135338
|
+
gameId: null,
|
|
135339
|
+
displayName: "Common Sword",
|
|
135340
|
+
description: "A basic sword, good for beginners.",
|
|
135341
|
+
type: "unlock",
|
|
135342
|
+
isPlaceable: false,
|
|
135343
|
+
imageUrl: "http://playcademy-sandbox.local/common-sword.png",
|
|
135344
|
+
metadata: undefined
|
|
135345
|
+
},
|
|
135346
|
+
{
|
|
135347
|
+
id: DEMO_ITEM_IDS.smallHealthPotion,
|
|
135348
|
+
slug: "SMALL_HEALTH_POTION",
|
|
135349
|
+
gameId: null,
|
|
135350
|
+
displayName: "Small Health Potion",
|
|
135351
|
+
description: "Restores a small amount of health.",
|
|
135352
|
+
type: "other",
|
|
135353
|
+
isPlaceable: false,
|
|
135354
|
+
imageUrl: "http://playcademy-sandbox.local/small-health-potion.png",
|
|
135355
|
+
metadata: undefined
|
|
135356
|
+
},
|
|
135357
|
+
{
|
|
135358
|
+
id: DEMO_ITEM_IDS.smallBackpack,
|
|
135359
|
+
slug: "SMALL_BACKPACK",
|
|
135360
|
+
gameId: null,
|
|
135361
|
+
displayName: "Small Backpack",
|
|
135362
|
+
description: "Increases your inventory capacity by 5 slots.",
|
|
135363
|
+
type: "upgrade",
|
|
135364
|
+
isPlaceable: false,
|
|
135365
|
+
imageUrl: "http://playcademy-sandbox.local/small-backpack.png",
|
|
135366
|
+
metadata: undefined
|
|
135367
|
+
}
|
|
135368
|
+
];
|
|
135369
|
+
var SAMPLE_INVENTORY = [
|
|
135370
|
+
{
|
|
135371
|
+
id: "20000000-0000-0000-0000-000000000001",
|
|
135372
|
+
userId: DEMO_USER.id,
|
|
135373
|
+
itemId: PLAYCADEMY_CREDITS_ID,
|
|
135374
|
+
quantity: 1000
|
|
135375
|
+
}
|
|
135376
|
+
];
|
|
135162
135377
|
// ../../node_modules/@hono/node-server/dist/index.mjs
|
|
135163
135378
|
import { createServer as createServerHTTP } from "http";
|
|
135164
135379
|
import { Http2ServerRequest as Http2ServerRequest2 } from "http2";
|
|
@@ -135700,10 +135915,106 @@ var serve = (options, listeningListener) => {
|
|
|
135700
135915
|
});
|
|
135701
135916
|
return server;
|
|
135702
135917
|
};
|
|
135918
|
+
|
|
135919
|
+
// ../utils/src/port.ts
|
|
135920
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
135921
|
+
import { createServer } from "node:net";
|
|
135922
|
+
import { homedir } from "node:os";
|
|
135923
|
+
import { join } from "node:path";
|
|
135924
|
+
function getRegistryPath() {
|
|
135925
|
+
const home = homedir();
|
|
135926
|
+
const dir = join(home, ".playcademy");
|
|
135927
|
+
if (!existsSync(dir)) {
|
|
135928
|
+
mkdirSync(dir, { recursive: true });
|
|
135929
|
+
}
|
|
135930
|
+
return join(dir, ".proc");
|
|
135931
|
+
}
|
|
135932
|
+
function readRegistry() {
|
|
135933
|
+
const registryPath = getRegistryPath();
|
|
135934
|
+
if (!existsSync(registryPath)) {
|
|
135935
|
+
return {};
|
|
135936
|
+
}
|
|
135937
|
+
try {
|
|
135938
|
+
const content = readFileSync(registryPath, "utf-8");
|
|
135939
|
+
return JSON.parse(content);
|
|
135940
|
+
} catch {
|
|
135941
|
+
return {};
|
|
135942
|
+
}
|
|
135943
|
+
}
|
|
135944
|
+
function writeRegistry(registry) {
|
|
135945
|
+
const registryPath = getRegistryPath();
|
|
135946
|
+
writeFileSync(registryPath, JSON.stringify(registry, null, 2), "utf-8");
|
|
135947
|
+
}
|
|
135948
|
+
function getServerKey(type, port) {
|
|
135949
|
+
return `${type}-${port}`;
|
|
135950
|
+
}
|
|
135951
|
+
function writeServerInfo(type, info2) {
|
|
135952
|
+
const registry = readRegistry();
|
|
135953
|
+
const key = getServerKey(type, info2.port);
|
|
135954
|
+
registry[key] = info2;
|
|
135955
|
+
writeRegistry(registry);
|
|
135956
|
+
}
|
|
135957
|
+
function cleanupServerInfo(type, projectRoot, pid) {
|
|
135958
|
+
const registry = readRegistry();
|
|
135959
|
+
const keysToRemove = [];
|
|
135960
|
+
for (const [key, info2] of Object.entries(registry)) {
|
|
135961
|
+
if (key.startsWith(`${type}-`)) {
|
|
135962
|
+
let matches = true;
|
|
135963
|
+
if (projectRoot && info2.projectRoot !== projectRoot) {
|
|
135964
|
+
matches = false;
|
|
135965
|
+
}
|
|
135966
|
+
if (pid !== undefined && info2.pid !== pid) {
|
|
135967
|
+
matches = false;
|
|
135968
|
+
}
|
|
135969
|
+
if (matches) {
|
|
135970
|
+
keysToRemove.push(key);
|
|
135971
|
+
}
|
|
135972
|
+
}
|
|
135973
|
+
}
|
|
135974
|
+
for (const key of keysToRemove) {
|
|
135975
|
+
delete registry[key];
|
|
135976
|
+
}
|
|
135977
|
+
if (keysToRemove.length > 0) {
|
|
135978
|
+
writeRegistry(registry);
|
|
135979
|
+
}
|
|
135980
|
+
}
|
|
135981
|
+
async function isPortInUse(port) {
|
|
135982
|
+
return new Promise((resolve) => {
|
|
135983
|
+
const server = createServer();
|
|
135984
|
+
server.once("error", () => {
|
|
135985
|
+
resolve(true);
|
|
135986
|
+
});
|
|
135987
|
+
server.once("listening", () => {
|
|
135988
|
+
server.close();
|
|
135989
|
+
resolve(false);
|
|
135990
|
+
});
|
|
135991
|
+
server.listen(port);
|
|
135992
|
+
});
|
|
135993
|
+
}
|
|
135994
|
+
async function waitForPort(port, timeoutMs = 5000) {
|
|
135995
|
+
const start2 = Date.now();
|
|
135996
|
+
while (await isPortInUse(port)) {
|
|
135997
|
+
if (Date.now() - start2 > timeoutMs) {
|
|
135998
|
+
throw new Error(`Port ${port} is already in use.
|
|
135999
|
+
` + `Stop the other server or specify a different port with --port <number>.`);
|
|
136000
|
+
}
|
|
136001
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
136002
|
+
}
|
|
136003
|
+
}
|
|
136004
|
+
async function requirePortAvailable(port, timeoutMs = 100) {
|
|
136005
|
+
const start2 = Date.now();
|
|
136006
|
+
while (await isPortInUse(port)) {
|
|
136007
|
+
if (Date.now() - start2 > timeoutMs) {
|
|
136008
|
+
throw new Error(`Port ${port} is already in use.
|
|
136009
|
+
` + `Stop the other server or specify a different port with --port <number>.`);
|
|
136010
|
+
}
|
|
136011
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
136012
|
+
}
|
|
136013
|
+
}
|
|
135703
136014
|
// package.json
|
|
135704
136015
|
var package_default = {
|
|
135705
136016
|
name: "@playcademy/sandbox",
|
|
135706
|
-
version: "0.
|
|
136017
|
+
version: "0.3.0",
|
|
135707
136018
|
description: "Local development server for Playcademy game development",
|
|
135708
136019
|
type: "module",
|
|
135709
136020
|
exports: {
|
|
@@ -135718,6 +136029,10 @@ var package_default = {
|
|
|
135718
136029
|
"./config": {
|
|
135719
136030
|
import: "./dist/config.js",
|
|
135720
136031
|
types: "./dist/config.d.ts"
|
|
136032
|
+
},
|
|
136033
|
+
"./constants": {
|
|
136034
|
+
import: "./dist/constants.js",
|
|
136035
|
+
types: "./dist/constants.d.ts"
|
|
135721
136036
|
}
|
|
135722
136037
|
},
|
|
135723
136038
|
bin: {
|
|
@@ -138213,7 +138528,7 @@ function sql(strings, ...params) {
|
|
|
138213
138528
|
return new SQL([new StringChunk(str)]);
|
|
138214
138529
|
}
|
|
138215
138530
|
sql2.raw = raw2;
|
|
138216
|
-
function
|
|
138531
|
+
function join2(chunks, separator) {
|
|
138217
138532
|
const result = [];
|
|
138218
138533
|
for (const [i2, chunk] of chunks.entries()) {
|
|
138219
138534
|
if (i2 > 0 && separator !== undefined) {
|
|
@@ -138223,7 +138538,7 @@ function sql(strings, ...params) {
|
|
|
138223
138538
|
}
|
|
138224
138539
|
return new SQL(result);
|
|
138225
138540
|
}
|
|
138226
|
-
sql2.join =
|
|
138541
|
+
sql2.join = join2;
|
|
138227
138542
|
function identifier(value) {
|
|
138228
138543
|
return new Name(value);
|
|
138229
138544
|
}
|
|
@@ -141200,7 +141515,7 @@ class PgSelectQueryBuilderBase extends TypedQueryBuilder {
|
|
|
141200
141515
|
return (table, on) => {
|
|
141201
141516
|
const baseTableName = this.tableName;
|
|
141202
141517
|
const tableName = getTableLikeName(table);
|
|
141203
|
-
if (typeof tableName === "string" && this.config.joins?.some((
|
|
141518
|
+
if (typeof tableName === "string" && this.config.joins?.some((join2) => join2.alias === tableName)) {
|
|
141204
141519
|
throw new Error(`Alias "${tableName}" is already used in this query`);
|
|
141205
141520
|
}
|
|
141206
141521
|
if (!this.isPartialSelect) {
|
|
@@ -141709,7 +142024,7 @@ class PgUpdateBase extends QueryPromise {
|
|
|
141709
142024
|
createJoin(joinType) {
|
|
141710
142025
|
return (table, on) => {
|
|
141711
142026
|
const tableName = getTableLikeName(table);
|
|
141712
|
-
if (typeof tableName === "string" && this.config.joins.some((
|
|
142027
|
+
if (typeof tableName === "string" && this.config.joins.some((join2) => join2.alias === tableName)) {
|
|
141713
142028
|
throw new Error(`Alias "${tableName}" is already used in this query`);
|
|
141714
142029
|
}
|
|
141715
142030
|
if (typeof on === "function") {
|
|
@@ -141759,10 +142074,10 @@ class PgUpdateBase extends QueryPromise {
|
|
|
141759
142074
|
const fromFields = this.getTableLikeFields(this.config.from);
|
|
141760
142075
|
fields[tableName] = fromFields;
|
|
141761
142076
|
}
|
|
141762
|
-
for (const
|
|
141763
|
-
const tableName2 = getTableLikeName(
|
|
141764
|
-
if (typeof tableName2 === "string" && !is(
|
|
141765
|
-
const fromFields = this.getTableLikeFields(
|
|
142077
|
+
for (const join2 of this.config.joins) {
|
|
142078
|
+
const tableName2 = getTableLikeName(join2.table);
|
|
142079
|
+
if (typeof tableName2 === "string" && !is(join2.table, SQL)) {
|
|
142080
|
+
const fromFields = this.getTableLikeFields(join2.table);
|
|
141766
142081
|
fields[tableName2] = fromFields;
|
|
141767
142082
|
}
|
|
141768
142083
|
}
|
|
@@ -142905,195 +143220,6 @@ var notificationsRelations = relations(notifications, ({ one }) => ({
|
|
|
142905
143220
|
references: [users.id]
|
|
142906
143221
|
})
|
|
142907
143222
|
}));
|
|
142908
|
-
// src/constants/demo-users.ts
|
|
142909
|
-
var now = new Date;
|
|
142910
|
-
var DEMO_USER_IDS = {
|
|
142911
|
-
admin: "00000000-0000-0000-0000-000000000001",
|
|
142912
|
-
player: "00000000-0000-0000-0000-000000000002",
|
|
142913
|
-
developer: "00000000-0000-0000-0000-000000000003",
|
|
142914
|
-
pendingDeveloper: "00000000-0000-0000-0000-000000000004",
|
|
142915
|
-
unverifiedPlayer: "00000000-0000-0000-0000-000000000005"
|
|
142916
|
-
};
|
|
142917
|
-
var DEMO_USERS = {
|
|
142918
|
-
admin: {
|
|
142919
|
-
id: DEMO_USER_IDS.admin,
|
|
142920
|
-
name: "Admin User",
|
|
142921
|
-
username: "admin_user",
|
|
142922
|
-
email: "admin@playcademy.com",
|
|
142923
|
-
emailVerified: true,
|
|
142924
|
-
image: null,
|
|
142925
|
-
role: "admin",
|
|
142926
|
-
developerStatus: "approved",
|
|
142927
|
-
createdAt: now,
|
|
142928
|
-
updatedAt: now
|
|
142929
|
-
},
|
|
142930
|
-
player: {
|
|
142931
|
-
id: DEMO_USER_IDS.player,
|
|
142932
|
-
name: "Player User",
|
|
142933
|
-
username: "player_user",
|
|
142934
|
-
email: "player@playcademy.com",
|
|
142935
|
-
emailVerified: true,
|
|
142936
|
-
image: null,
|
|
142937
|
-
role: "player",
|
|
142938
|
-
developerStatus: "none",
|
|
142939
|
-
createdAt: now,
|
|
142940
|
-
updatedAt: now
|
|
142941
|
-
},
|
|
142942
|
-
developer: {
|
|
142943
|
-
id: DEMO_USER_IDS.developer,
|
|
142944
|
-
name: "Developer User",
|
|
142945
|
-
username: "developer_user",
|
|
142946
|
-
email: "developer@playcademy.com",
|
|
142947
|
-
emailVerified: true,
|
|
142948
|
-
image: null,
|
|
142949
|
-
role: "developer",
|
|
142950
|
-
developerStatus: "approved",
|
|
142951
|
-
createdAt: now,
|
|
142952
|
-
updatedAt: now
|
|
142953
|
-
},
|
|
142954
|
-
pendingDeveloper: {
|
|
142955
|
-
id: DEMO_USER_IDS.pendingDeveloper,
|
|
142956
|
-
name: "Pending Developer",
|
|
142957
|
-
username: "pending_dev",
|
|
142958
|
-
email: "pending@playcademy.com",
|
|
142959
|
-
emailVerified: true,
|
|
142960
|
-
image: null,
|
|
142961
|
-
role: "developer",
|
|
142962
|
-
developerStatus: "pending",
|
|
142963
|
-
createdAt: now,
|
|
142964
|
-
updatedAt: now
|
|
142965
|
-
},
|
|
142966
|
-
unverifiedPlayer: {
|
|
142967
|
-
id: DEMO_USER_IDS.unverifiedPlayer,
|
|
142968
|
-
name: "Unverified Player",
|
|
142969
|
-
username: "unverified_player",
|
|
142970
|
-
email: "unverified@playcademy.com",
|
|
142971
|
-
emailVerified: false,
|
|
142972
|
-
image: null,
|
|
142973
|
-
role: "player",
|
|
142974
|
-
developerStatus: "none",
|
|
142975
|
-
createdAt: now,
|
|
142976
|
-
updatedAt: now
|
|
142977
|
-
}
|
|
142978
|
-
};
|
|
142979
|
-
var DEMO_USER = DEMO_USERS.admin;
|
|
142980
|
-
// src/constants/demo-tokens.ts
|
|
142981
|
-
var DEMO_TOKENS = {
|
|
142982
|
-
"sandbox-demo-token": DEMO_USERS.admin,
|
|
142983
|
-
"sandbox-admin-token": DEMO_USERS.admin,
|
|
142984
|
-
"sandbox-player-token": DEMO_USERS.player,
|
|
142985
|
-
"sandbox-developer-token": DEMO_USERS.developer,
|
|
142986
|
-
"sandbox-pending-dev-token": DEMO_USERS.pendingDeveloper,
|
|
142987
|
-
"sandbox-unverified-token": DEMO_USERS.unverifiedPlayer,
|
|
142988
|
-
"mock-game-token-for-local-dev": DEMO_USERS.admin
|
|
142989
|
-
};
|
|
142990
|
-
var MOCK_GAME_ID = "mock-game-id-from-template";
|
|
142991
|
-
// src/constants/demo-items.ts
|
|
142992
|
-
var DEMO_ITEM_IDS = {
|
|
142993
|
-
playcademyCredits: "10000000-0000-0000-0000-000000000001",
|
|
142994
|
-
foundingMemberBadge: "10000000-0000-0000-0000-000000000002",
|
|
142995
|
-
earlyAdopterBadge: "10000000-0000-0000-0000-000000000003",
|
|
142996
|
-
firstGameBadge: "10000000-0000-0000-0000-000000000004",
|
|
142997
|
-
commonSword: "10000000-0000-0000-0000-000000000005",
|
|
142998
|
-
smallHealthPotion: "10000000-0000-0000-0000-000000000006",
|
|
142999
|
-
smallBackpack: "10000000-0000-0000-0000-000000000007"
|
|
143000
|
-
};
|
|
143001
|
-
var PLAYCADEMY_CREDITS_ID = DEMO_ITEM_IDS.playcademyCredits;
|
|
143002
|
-
var SAMPLE_ITEMS = [
|
|
143003
|
-
{
|
|
143004
|
-
id: PLAYCADEMY_CREDITS_ID,
|
|
143005
|
-
slug: "PLAYCADEMY_CREDITS",
|
|
143006
|
-
gameId: null,
|
|
143007
|
-
displayName: "PLAYCADEMY credits",
|
|
143008
|
-
description: "The main currency used across PLAYCADEMY.",
|
|
143009
|
-
type: "currency",
|
|
143010
|
-
isPlaceable: false,
|
|
143011
|
-
imageUrl: "http://playcademy-sandbox.local/playcademy-credit.png",
|
|
143012
|
-
metadata: {
|
|
143013
|
-
rarity: "common"
|
|
143014
|
-
}
|
|
143015
|
-
},
|
|
143016
|
-
{
|
|
143017
|
-
id: DEMO_ITEM_IDS.foundingMemberBadge,
|
|
143018
|
-
slug: "FOUNDING_MEMBER_BADGE",
|
|
143019
|
-
gameId: null,
|
|
143020
|
-
displayName: "Founding Member Badge",
|
|
143021
|
-
description: "Reserved for founding core team of the PLAYCADEMY platform.",
|
|
143022
|
-
type: "badge",
|
|
143023
|
-
isPlaceable: false,
|
|
143024
|
-
imageUrl: null,
|
|
143025
|
-
metadata: {
|
|
143026
|
-
rarity: "legendary"
|
|
143027
|
-
}
|
|
143028
|
-
},
|
|
143029
|
-
{
|
|
143030
|
-
id: DEMO_ITEM_IDS.earlyAdopterBadge,
|
|
143031
|
-
slug: "EARLY_ADOPTER_BADGE",
|
|
143032
|
-
gameId: null,
|
|
143033
|
-
displayName: "Early Adopter Badge",
|
|
143034
|
-
description: "Awarded to users who joined during the beta phase.",
|
|
143035
|
-
type: "badge",
|
|
143036
|
-
isPlaceable: false,
|
|
143037
|
-
imageUrl: null,
|
|
143038
|
-
metadata: {
|
|
143039
|
-
rarity: "epic"
|
|
143040
|
-
}
|
|
143041
|
-
},
|
|
143042
|
-
{
|
|
143043
|
-
id: DEMO_ITEM_IDS.firstGameBadge,
|
|
143044
|
-
slug: "FIRST_GAME_BADGE",
|
|
143045
|
-
gameId: null,
|
|
143046
|
-
displayName: "First Game Played",
|
|
143047
|
-
description: "Awarded for playing your first game in the Playcademy platform.",
|
|
143048
|
-
type: "badge",
|
|
143049
|
-
isPlaceable: false,
|
|
143050
|
-
imageUrl: "http://playcademy-sandbox.local/first-game-badge.png",
|
|
143051
|
-
metadata: {
|
|
143052
|
-
rarity: "uncommon"
|
|
143053
|
-
}
|
|
143054
|
-
},
|
|
143055
|
-
{
|
|
143056
|
-
id: DEMO_ITEM_IDS.commonSword,
|
|
143057
|
-
slug: "COMMON_SWORD",
|
|
143058
|
-
gameId: null,
|
|
143059
|
-
displayName: "Common Sword",
|
|
143060
|
-
description: "A basic sword, good for beginners.",
|
|
143061
|
-
type: "unlock",
|
|
143062
|
-
isPlaceable: false,
|
|
143063
|
-
imageUrl: "http://playcademy-sandbox.local/common-sword.png",
|
|
143064
|
-
metadata: undefined
|
|
143065
|
-
},
|
|
143066
|
-
{
|
|
143067
|
-
id: DEMO_ITEM_IDS.smallHealthPotion,
|
|
143068
|
-
slug: "SMALL_HEALTH_POTION",
|
|
143069
|
-
gameId: null,
|
|
143070
|
-
displayName: "Small Health Potion",
|
|
143071
|
-
description: "Restores a small amount of health.",
|
|
143072
|
-
type: "other",
|
|
143073
|
-
isPlaceable: false,
|
|
143074
|
-
imageUrl: "http://playcademy-sandbox.local/small-health-potion.png",
|
|
143075
|
-
metadata: undefined
|
|
143076
|
-
},
|
|
143077
|
-
{
|
|
143078
|
-
id: DEMO_ITEM_IDS.smallBackpack,
|
|
143079
|
-
slug: "SMALL_BACKPACK",
|
|
143080
|
-
gameId: null,
|
|
143081
|
-
displayName: "Small Backpack",
|
|
143082
|
-
description: "Increases your inventory capacity by 5 slots.",
|
|
143083
|
-
type: "upgrade",
|
|
143084
|
-
isPlaceable: false,
|
|
143085
|
-
imageUrl: "http://playcademy-sandbox.local/small-backpack.png",
|
|
143086
|
-
metadata: undefined
|
|
143087
|
-
}
|
|
143088
|
-
];
|
|
143089
|
-
var SAMPLE_INVENTORY = [
|
|
143090
|
-
{
|
|
143091
|
-
id: "20000000-0000-0000-0000-000000000001",
|
|
143092
|
-
userId: DEMO_USER.id,
|
|
143093
|
-
itemId: PLAYCADEMY_CREDITS_ID,
|
|
143094
|
-
quantity: 1000
|
|
143095
|
-
}
|
|
143096
|
-
];
|
|
143097
143223
|
// src/server/auth.ts
|
|
143098
143224
|
function extractBearerToken(authHeader) {
|
|
143099
143225
|
if (!authHeader?.startsWith("Bearer ")) {
|
|
@@ -143101,25 +143227,53 @@ function extractBearerToken(authHeader) {
|
|
|
143101
143227
|
}
|
|
143102
143228
|
return authHeader.substring(7);
|
|
143103
143229
|
}
|
|
143104
|
-
function
|
|
143230
|
+
function parseSandboxToken(token) {
|
|
143231
|
+
try {
|
|
143232
|
+
const parts2 = token.split(".");
|
|
143233
|
+
if (parts2.length !== 3 || parts2[2] !== "sandbox") {
|
|
143234
|
+
return null;
|
|
143235
|
+
}
|
|
143236
|
+
const header = JSON.parse(atob(parts2[0]));
|
|
143237
|
+
if (header.typ !== "sandbox") {
|
|
143238
|
+
return null;
|
|
143239
|
+
}
|
|
143240
|
+
const payload = JSON.parse(atob(parts2[1]));
|
|
143241
|
+
if (!payload.uid || !payload.sub) {
|
|
143242
|
+
return null;
|
|
143243
|
+
}
|
|
143244
|
+
return {
|
|
143245
|
+
userId: payload.uid,
|
|
143246
|
+
gameSlug: payload.sub
|
|
143247
|
+
};
|
|
143248
|
+
} catch {
|
|
143249
|
+
return null;
|
|
143250
|
+
}
|
|
143251
|
+
}
|
|
143252
|
+
function parseJwtClaims(token) {
|
|
143105
143253
|
try {
|
|
143106
143254
|
const parts2 = token.split(".");
|
|
143107
143255
|
if (parts2.length === 3 && parts2[1]) {
|
|
143108
143256
|
const payload = JSON.parse(atob(parts2[1]));
|
|
143109
|
-
|
|
143257
|
+
if (payload.uid) {
|
|
143258
|
+
return {
|
|
143259
|
+
userId: payload.uid,
|
|
143260
|
+
gameId: payload.sub
|
|
143261
|
+
};
|
|
143262
|
+
}
|
|
143110
143263
|
}
|
|
143111
|
-
} catch
|
|
143112
|
-
console.warn("[Auth] Failed to decode JWT token:", error);
|
|
143113
|
-
}
|
|
143264
|
+
} catch {}
|
|
143114
143265
|
return null;
|
|
143115
143266
|
}
|
|
143116
|
-
function
|
|
143267
|
+
function resolveAuth(token) {
|
|
143117
143268
|
const demoUser = DEMO_TOKENS[token];
|
|
143118
|
-
if (demoUser)
|
|
143119
|
-
return demoUser.id;
|
|
143120
|
-
}
|
|
143269
|
+
if (demoUser)
|
|
143270
|
+
return { userId: demoUser.id };
|
|
143121
143271
|
if (token.includes(".")) {
|
|
143122
|
-
|
|
143272
|
+
const sandboxClaims = parseSandboxToken(token);
|
|
143273
|
+
if (sandboxClaims) {
|
|
143274
|
+
return sandboxClaims;
|
|
143275
|
+
}
|
|
143276
|
+
return parseJwtClaims(token);
|
|
143123
143277
|
}
|
|
143124
143278
|
return null;
|
|
143125
143279
|
}
|
|
@@ -143134,6 +143288,18 @@ async function fetchUserFromDatabase(db, userId) {
|
|
|
143134
143288
|
throw error;
|
|
143135
143289
|
}
|
|
143136
143290
|
}
|
|
143291
|
+
async function resolveGameIdFromSlug(db, slug) {
|
|
143292
|
+
try {
|
|
143293
|
+
const game = await db.query.games.findFirst({
|
|
143294
|
+
where: eq(games.slug, slug),
|
|
143295
|
+
columns: { id: true }
|
|
143296
|
+
});
|
|
143297
|
+
return game?.id || null;
|
|
143298
|
+
} catch (error) {
|
|
143299
|
+
console.error("[Auth] Error looking up game by slug:", error);
|
|
143300
|
+
return null;
|
|
143301
|
+
}
|
|
143302
|
+
}
|
|
143137
143303
|
function isPublicRoute(path, exceptions) {
|
|
143138
143304
|
return exceptions.some((exception) => {
|
|
143139
143305
|
if (path === exception)
|
|
@@ -143155,11 +143321,11 @@ async function authenticateRequest(c) {
|
|
|
143155
143321
|
shouldReturn404: true
|
|
143156
143322
|
};
|
|
143157
143323
|
}
|
|
143158
|
-
let
|
|
143324
|
+
let claims;
|
|
143159
143325
|
if (apiKey && !bearerToken) {
|
|
143160
|
-
|
|
143326
|
+
claims = { userId: DEMO_USERS.admin.id };
|
|
143161
143327
|
} else {
|
|
143162
|
-
const resolved =
|
|
143328
|
+
const resolved = resolveAuth(token);
|
|
143163
143329
|
if (!resolved) {
|
|
143164
143330
|
return {
|
|
143165
143331
|
success: false,
|
|
@@ -143167,26 +143333,22 @@ async function authenticateRequest(c) {
|
|
|
143167
143333
|
shouldReturn404: true
|
|
143168
143334
|
};
|
|
143169
143335
|
}
|
|
143170
|
-
|
|
143336
|
+
claims = resolved;
|
|
143171
143337
|
}
|
|
143172
143338
|
const db = c.get("db");
|
|
143173
143339
|
if (!db) {
|
|
143174
143340
|
console.error("[Auth] Database not available in context");
|
|
143175
|
-
return {
|
|
143176
|
-
success: false,
|
|
143177
|
-
error: "Internal server error",
|
|
143178
|
-
shouldReturn404: false
|
|
143179
|
-
};
|
|
143341
|
+
return { success: false, error: "Internal server error", shouldReturn404: false };
|
|
143180
143342
|
}
|
|
143181
|
-
const user = await fetchUserFromDatabase(db,
|
|
143343
|
+
const user = await fetchUserFromDatabase(db, claims.userId);
|
|
143182
143344
|
if (!user) {
|
|
143183
|
-
return {
|
|
143184
|
-
success: false,
|
|
143185
|
-
error: "User not found or token invalid",
|
|
143186
|
-
shouldReturn404: true
|
|
143187
|
-
};
|
|
143345
|
+
return { success: false, error: "User not found or token invalid", shouldReturn404: true };
|
|
143188
143346
|
}
|
|
143189
|
-
|
|
143347
|
+
let gameId = claims.gameId;
|
|
143348
|
+
if (!gameId && claims.gameSlug) {
|
|
143349
|
+
gameId = await resolveGameIdFromSlug(db, claims.gameSlug) ?? undefined;
|
|
143350
|
+
}
|
|
143351
|
+
return { success: true, user, gameId };
|
|
143190
143352
|
}
|
|
143191
143353
|
function setupAuth(options = {}) {
|
|
143192
143354
|
const { exceptions = [] } = options;
|
|
@@ -143198,6 +143360,8 @@ function setupAuth(options = {}) {
|
|
|
143198
143360
|
const result = await authenticateRequest(c);
|
|
143199
143361
|
if (result.success) {
|
|
143200
143362
|
c.set("user", result.user);
|
|
143363
|
+
if (result.gameId)
|
|
143364
|
+
c.set("gameId", result.gameId);
|
|
143201
143365
|
await next();
|
|
143202
143366
|
return;
|
|
143203
143367
|
}
|
|
@@ -149059,34 +149223,34 @@ function getDatabase() {
|
|
|
149059
149223
|
|
|
149060
149224
|
// src/database/path-manager.ts
|
|
149061
149225
|
import fs2 from "node:fs";
|
|
149062
|
-
import { dirname, isAbsolute, join as
|
|
149226
|
+
import { dirname, isAbsolute, join as join3 } from "node:path";
|
|
149063
149227
|
|
|
149064
149228
|
class DatabasePathManager {
|
|
149065
|
-
static DEFAULT_DB_SUBPATH =
|
|
149229
|
+
static DEFAULT_DB_SUBPATH = join3("@playcademy", "vite-plugin", "node_modules", ".playcademy", "sandbox.db");
|
|
149066
149230
|
static findNodeModulesPath() {
|
|
149067
149231
|
let currentDir = process.cwd();
|
|
149068
149232
|
while (currentDir !== dirname(currentDir)) {
|
|
149069
|
-
const nodeModulesPath =
|
|
149233
|
+
const nodeModulesPath = join3(currentDir, "node_modules");
|
|
149070
149234
|
if (fs2.existsSync(nodeModulesPath)) {
|
|
149071
149235
|
return nodeModulesPath;
|
|
149072
149236
|
}
|
|
149073
149237
|
currentDir = dirname(currentDir);
|
|
149074
149238
|
}
|
|
149075
|
-
return
|
|
149239
|
+
return join3(process.cwd(), "node_modules");
|
|
149076
149240
|
}
|
|
149077
149241
|
static resolveDatabasePath(customPath) {
|
|
149078
149242
|
if (customPath) {
|
|
149079
149243
|
if (customPath === ":memory:")
|
|
149080
149244
|
return ":memory:";
|
|
149081
|
-
return isAbsolute(customPath) ? customPath :
|
|
149245
|
+
return isAbsolute(customPath) ? customPath : join3(process.cwd(), customPath);
|
|
149082
149246
|
}
|
|
149083
|
-
return
|
|
149247
|
+
return join3(this.findNodeModulesPath(), this.DEFAULT_DB_SUBPATH);
|
|
149084
149248
|
}
|
|
149085
149249
|
static ensureDatabaseDirectory(dbPath) {
|
|
149086
149250
|
if (dbPath === ":memory:")
|
|
149087
149251
|
return;
|
|
149088
149252
|
const dirPath = dirname(dbPath);
|
|
149089
|
-
const absolutePath = isAbsolute(dirPath) ? dirPath :
|
|
149253
|
+
const absolutePath = isAbsolute(dirPath) ? dirPath : join3(process.cwd(), dirPath);
|
|
149090
149254
|
try {
|
|
149091
149255
|
if (!fs2.existsSync(absolutePath)) {
|
|
149092
149256
|
fs2.mkdirSync(absolutePath, { recursive: true });
|
|
@@ -149413,7 +149577,51 @@ var init_overworld = __esm3(() => {
|
|
|
149413
149577
|
FIRST_GAME: ITEM_SLUGS2.FIRST_GAME_BADGE
|
|
149414
149578
|
};
|
|
149415
149579
|
});
|
|
149416
|
-
var
|
|
149580
|
+
var TIMEBACK_ORG_SOURCED_ID = "PLAYCADEMY";
|
|
149581
|
+
var TIMEBACK_ORG_NAME = "Playcademy Studios";
|
|
149582
|
+
var TIMEBACK_ORG_TYPE = "department";
|
|
149583
|
+
var TIMEBACK_COURSE_DEFAULTS;
|
|
149584
|
+
var TIMEBACK_RESOURCE_DEFAULTS;
|
|
149585
|
+
var TIMEBACK_COMPONENT_DEFAULTS;
|
|
149586
|
+
var TIMEBACK_COMPONENT_RESOURCE_DEFAULTS;
|
|
149587
|
+
var init_timeback = __esm3(() => {
|
|
149588
|
+
TIMEBACK_COURSE_DEFAULTS = {
|
|
149589
|
+
gradingScheme: "STANDARD",
|
|
149590
|
+
level: {
|
|
149591
|
+
elementary: "Elementary",
|
|
149592
|
+
middle: "Middle",
|
|
149593
|
+
high: "High",
|
|
149594
|
+
ap: "AP"
|
|
149595
|
+
},
|
|
149596
|
+
goals: {
|
|
149597
|
+
dailyXp: 50,
|
|
149598
|
+
dailyLessons: 3
|
|
149599
|
+
},
|
|
149600
|
+
metrics: {
|
|
149601
|
+
totalXp: 1000,
|
|
149602
|
+
totalLessons: 50
|
|
149603
|
+
}
|
|
149604
|
+
};
|
|
149605
|
+
TIMEBACK_RESOURCE_DEFAULTS = {
|
|
149606
|
+
vendorId: "playcademy",
|
|
149607
|
+
roles: ["primary"],
|
|
149608
|
+
importance: "primary",
|
|
149609
|
+
metadata: {
|
|
149610
|
+
type: "interactive",
|
|
149611
|
+
toolProvider: "Playcademy",
|
|
149612
|
+
instructionalMethod: "exploratory",
|
|
149613
|
+
language: "en-US"
|
|
149614
|
+
}
|
|
149615
|
+
};
|
|
149616
|
+
TIMEBACK_COMPONENT_DEFAULTS = {
|
|
149617
|
+
sortOrder: 1,
|
|
149618
|
+
prerequisiteCriteria: "ALL"
|
|
149619
|
+
};
|
|
149620
|
+
TIMEBACK_COMPONENT_RESOURCE_DEFAULTS = {
|
|
149621
|
+
sortOrder: 1,
|
|
149622
|
+
lessonType: "quiz"
|
|
149623
|
+
};
|
|
149624
|
+
});
|
|
149417
149625
|
var init_workers = () => {};
|
|
149418
149626
|
var init_src2 = __esm3(() => {
|
|
149419
149627
|
init_auth();
|
|
@@ -149454,7 +149662,6 @@ var HTTP_DEFAULTS;
|
|
|
149454
149662
|
var AUTH_DEFAULTS;
|
|
149455
149663
|
var CACHE_DEFAULTS;
|
|
149456
149664
|
var CONFIG_DEFAULTS;
|
|
149457
|
-
var DEFAULT_PLAYCADEMY_ORGANIZATION_ID;
|
|
149458
149665
|
var PLAYCADEMY_DEFAULTS;
|
|
149459
149666
|
var RESOURCE_DEFAULTS;
|
|
149460
149667
|
var HTTP_STATUS;
|
|
@@ -149601,54 +149808,26 @@ var init_constants = __esm3(() => {
|
|
|
149601
149808
|
CONFIG_DEFAULTS = {
|
|
149602
149809
|
fileNames: ["timeback.config.js", "timeback.config.json"]
|
|
149603
149810
|
};
|
|
149604
|
-
DEFAULT_PLAYCADEMY_ORGANIZATION_ID = process.env.TIMEBACK_ORG_SOURCE_ID || "PLAYCADEMY";
|
|
149605
149811
|
PLAYCADEMY_DEFAULTS = {
|
|
149606
|
-
organization:
|
|
149812
|
+
organization: TIMEBACK_ORG_SOURCED_ID,
|
|
149607
149813
|
launchBaseUrls: PLAYCADEMY_BASE_URLS
|
|
149608
149814
|
};
|
|
149609
149815
|
RESOURCE_DEFAULTS = {
|
|
149610
149816
|
organization: {
|
|
149611
|
-
name:
|
|
149612
|
-
type:
|
|
149817
|
+
name: TIMEBACK_ORG_NAME,
|
|
149818
|
+
type: TIMEBACK_ORG_TYPE
|
|
149613
149819
|
},
|
|
149614
149820
|
course: {
|
|
149615
|
-
gradingScheme:
|
|
149616
|
-
level:
|
|
149617
|
-
elementary: "Elementary",
|
|
149618
|
-
middle: "Middle",
|
|
149619
|
-
high: "High",
|
|
149620
|
-
ap: "AP"
|
|
149621
|
-
},
|
|
149821
|
+
gradingScheme: TIMEBACK_COURSE_DEFAULTS.gradingScheme,
|
|
149822
|
+
level: TIMEBACK_COURSE_DEFAULTS.level,
|
|
149622
149823
|
metadata: {
|
|
149623
|
-
goals:
|
|
149624
|
-
|
|
149625
|
-
dailyLessons: 3
|
|
149626
|
-
},
|
|
149627
|
-
metrics: {
|
|
149628
|
-
totalXp: 1000,
|
|
149629
|
-
totalLessons: 50
|
|
149630
|
-
}
|
|
149824
|
+
goals: TIMEBACK_COURSE_DEFAULTS.goals,
|
|
149825
|
+
metrics: TIMEBACK_COURSE_DEFAULTS.metrics
|
|
149631
149826
|
}
|
|
149632
149827
|
},
|
|
149633
|
-
component:
|
|
149634
|
-
|
|
149635
|
-
|
|
149636
|
-
},
|
|
149637
|
-
resource: {
|
|
149638
|
-
vendorId: "playcademy",
|
|
149639
|
-
roles: ["primary"],
|
|
149640
|
-
importance: "primary",
|
|
149641
|
-
metadata: {
|
|
149642
|
-
type: "interactive",
|
|
149643
|
-
toolProvider: "Playcademy",
|
|
149644
|
-
instructionalMethod: "exploratory",
|
|
149645
|
-
language: "en-US"
|
|
149646
|
-
}
|
|
149647
|
-
},
|
|
149648
|
-
componentResource: {
|
|
149649
|
-
sortOrder: 1,
|
|
149650
|
-
lessonType: "quiz"
|
|
149651
|
-
}
|
|
149828
|
+
component: TIMEBACK_COMPONENT_DEFAULTS,
|
|
149829
|
+
resource: TIMEBACK_RESOURCE_DEFAULTS,
|
|
149830
|
+
componentResource: TIMEBACK_COMPONENT_RESOURCE_DEFAULTS
|
|
149652
149831
|
};
|
|
149653
149832
|
HTTP_STATUS = {
|
|
149654
149833
|
CLIENT_ERROR_MIN: 400,
|
|
@@ -155656,7 +155835,8 @@ class TimebackClient {
|
|
|
155656
155835
|
courseId: enrollment.course.id,
|
|
155657
155836
|
status: "active",
|
|
155658
155837
|
grades,
|
|
155659
|
-
subjects
|
|
155838
|
+
subjects,
|
|
155839
|
+
school: enrollment.school
|
|
155660
155840
|
};
|
|
155661
155841
|
});
|
|
155662
155842
|
this.cacheManager.setEnrollments(studentId, enrollments);
|
|
@@ -155729,7 +155909,51 @@ var init_overworld2 = __esm4(() => {
|
|
|
155729
155909
|
FIRST_GAME: ITEM_SLUGS3.FIRST_GAME_BADGE
|
|
155730
155910
|
};
|
|
155731
155911
|
});
|
|
155732
|
-
var
|
|
155912
|
+
var TIMEBACK_ORG_SOURCED_ID2 = "PLAYCADEMY";
|
|
155913
|
+
var TIMEBACK_ORG_NAME2 = "Playcademy Studios";
|
|
155914
|
+
var TIMEBACK_ORG_TYPE2 = "department";
|
|
155915
|
+
var TIMEBACK_COURSE_DEFAULTS2;
|
|
155916
|
+
var TIMEBACK_RESOURCE_DEFAULTS2;
|
|
155917
|
+
var TIMEBACK_COMPONENT_DEFAULTS2;
|
|
155918
|
+
var TIMEBACK_COMPONENT_RESOURCE_DEFAULTS2;
|
|
155919
|
+
var init_timeback2 = __esm4(() => {
|
|
155920
|
+
TIMEBACK_COURSE_DEFAULTS2 = {
|
|
155921
|
+
gradingScheme: "STANDARD",
|
|
155922
|
+
level: {
|
|
155923
|
+
elementary: "Elementary",
|
|
155924
|
+
middle: "Middle",
|
|
155925
|
+
high: "High",
|
|
155926
|
+
ap: "AP"
|
|
155927
|
+
},
|
|
155928
|
+
goals: {
|
|
155929
|
+
dailyXp: 50,
|
|
155930
|
+
dailyLessons: 3
|
|
155931
|
+
},
|
|
155932
|
+
metrics: {
|
|
155933
|
+
totalXp: 1000,
|
|
155934
|
+
totalLessons: 50
|
|
155935
|
+
}
|
|
155936
|
+
};
|
|
155937
|
+
TIMEBACK_RESOURCE_DEFAULTS2 = {
|
|
155938
|
+
vendorId: "playcademy",
|
|
155939
|
+
roles: ["primary"],
|
|
155940
|
+
importance: "primary",
|
|
155941
|
+
metadata: {
|
|
155942
|
+
type: "interactive",
|
|
155943
|
+
toolProvider: "Playcademy",
|
|
155944
|
+
instructionalMethod: "exploratory",
|
|
155945
|
+
language: "en-US"
|
|
155946
|
+
}
|
|
155947
|
+
};
|
|
155948
|
+
TIMEBACK_COMPONENT_DEFAULTS2 = {
|
|
155949
|
+
sortOrder: 1,
|
|
155950
|
+
prerequisiteCriteria: "ALL"
|
|
155951
|
+
};
|
|
155952
|
+
TIMEBACK_COMPONENT_RESOURCE_DEFAULTS2 = {
|
|
155953
|
+
sortOrder: 1,
|
|
155954
|
+
lessonType: "quiz"
|
|
155955
|
+
};
|
|
155956
|
+
});
|
|
155733
155957
|
var init_workers2 = () => {};
|
|
155734
155958
|
var init_src3 = __esm4(() => {
|
|
155735
155959
|
init_auth2();
|
|
@@ -155761,7 +155985,6 @@ var HTTP_DEFAULTS2;
|
|
|
155761
155985
|
var AUTH_DEFAULTS2;
|
|
155762
155986
|
var CACHE_DEFAULTS2;
|
|
155763
155987
|
var CONFIG_DEFAULTS2;
|
|
155764
|
-
var DEFAULT_PLAYCADEMY_ORGANIZATION_ID2;
|
|
155765
155988
|
var PLAYCADEMY_DEFAULTS2;
|
|
155766
155989
|
var RESOURCE_DEFAULTS2;
|
|
155767
155990
|
var HTTP_STATUS2;
|
|
@@ -155908,54 +156131,26 @@ var init_constants2 = __esm4(() => {
|
|
|
155908
156131
|
CONFIG_DEFAULTS2 = {
|
|
155909
156132
|
fileNames: ["timeback.config.js", "timeback.config.json"]
|
|
155910
156133
|
};
|
|
155911
|
-
DEFAULT_PLAYCADEMY_ORGANIZATION_ID2 = process.env.TIMEBACK_ORG_SOURCE_ID || "PLAYCADEMY";
|
|
155912
156134
|
PLAYCADEMY_DEFAULTS2 = {
|
|
155913
|
-
organization:
|
|
156135
|
+
organization: TIMEBACK_ORG_SOURCED_ID2,
|
|
155914
156136
|
launchBaseUrls: PLAYCADEMY_BASE_URLS2
|
|
155915
156137
|
};
|
|
155916
156138
|
RESOURCE_DEFAULTS2 = {
|
|
155917
156139
|
organization: {
|
|
155918
|
-
name:
|
|
155919
|
-
type:
|
|
156140
|
+
name: TIMEBACK_ORG_NAME2,
|
|
156141
|
+
type: TIMEBACK_ORG_TYPE2
|
|
155920
156142
|
},
|
|
155921
156143
|
course: {
|
|
155922
|
-
gradingScheme:
|
|
155923
|
-
level:
|
|
155924
|
-
elementary: "Elementary",
|
|
155925
|
-
middle: "Middle",
|
|
155926
|
-
high: "High",
|
|
155927
|
-
ap: "AP"
|
|
155928
|
-
},
|
|
156144
|
+
gradingScheme: TIMEBACK_COURSE_DEFAULTS2.gradingScheme,
|
|
156145
|
+
level: TIMEBACK_COURSE_DEFAULTS2.level,
|
|
155929
156146
|
metadata: {
|
|
155930
|
-
goals:
|
|
155931
|
-
|
|
155932
|
-
dailyLessons: 3
|
|
155933
|
-
},
|
|
155934
|
-
metrics: {
|
|
155935
|
-
totalXp: 1000,
|
|
155936
|
-
totalLessons: 50
|
|
155937
|
-
}
|
|
156147
|
+
goals: TIMEBACK_COURSE_DEFAULTS2.goals,
|
|
156148
|
+
metrics: TIMEBACK_COURSE_DEFAULTS2.metrics
|
|
155938
156149
|
}
|
|
155939
156150
|
},
|
|
155940
|
-
component:
|
|
155941
|
-
|
|
155942
|
-
|
|
155943
|
-
},
|
|
155944
|
-
resource: {
|
|
155945
|
-
vendorId: "playcademy",
|
|
155946
|
-
roles: ["primary"],
|
|
155947
|
-
importance: "primary",
|
|
155948
|
-
metadata: {
|
|
155949
|
-
type: "interactive",
|
|
155950
|
-
toolProvider: "Playcademy",
|
|
155951
|
-
instructionalMethod: "exploratory",
|
|
155952
|
-
language: "en-US"
|
|
155953
|
-
}
|
|
155954
|
-
},
|
|
155955
|
-
componentResource: {
|
|
155956
|
-
sortOrder: 1,
|
|
155957
|
-
lessonType: "quiz"
|
|
155958
|
-
}
|
|
156151
|
+
component: TIMEBACK_COMPONENT_DEFAULTS2,
|
|
156152
|
+
resource: TIMEBACK_RESOURCE_DEFAULTS2,
|
|
156153
|
+
componentResource: TIMEBACK_COMPONENT_RESOURCE_DEFAULTS2
|
|
155959
156154
|
};
|
|
155960
156155
|
HTTP_STATUS2 = {
|
|
155961
156156
|
CLIENT_ERROR_MIN: 400,
|
|
@@ -156306,68 +156501,89 @@ function buildResourceMetadata({
|
|
|
156306
156501
|
}
|
|
156307
156502
|
return metadata2;
|
|
156308
156503
|
}
|
|
156309
|
-
// ../api-core/src/utils/timeback-
|
|
156504
|
+
// ../api-core/src/utils/timeback-profile.ts
|
|
156310
156505
|
init_src();
|
|
156311
|
-
async function
|
|
156312
|
-
|
|
156313
|
-
|
|
156314
|
-
|
|
156315
|
-
const
|
|
156316
|
-
|
|
156317
|
-
|
|
156318
|
-
|
|
156319
|
-
|
|
156320
|
-
|
|
156321
|
-
|
|
156506
|
+
async function fetchStudentFromOneRoster(timebackId) {
|
|
156507
|
+
log2.debug("[OneRoster] Fetching student profile", { timebackId });
|
|
156508
|
+
try {
|
|
156509
|
+
const client = await getTimebackClient();
|
|
156510
|
+
const user = await client.oneroster.users.get(timebackId);
|
|
156511
|
+
const primaryRoleEntry = user.roles.find((r2) => r2.roleType === "primary");
|
|
156512
|
+
const role = primaryRoleEntry?.role ?? user.roles[0]?.role ?? "student";
|
|
156513
|
+
const orgMap = new Map;
|
|
156514
|
+
if (user.primaryOrg) {
|
|
156515
|
+
orgMap.set(user.primaryOrg.sourcedId, {
|
|
156516
|
+
id: user.primaryOrg.sourcedId,
|
|
156517
|
+
name: user.primaryOrg.name ?? null,
|
|
156518
|
+
type: user.primaryOrg.type || "school",
|
|
156519
|
+
isPrimary: true
|
|
156520
|
+
});
|
|
156521
|
+
}
|
|
156522
|
+
for (const r2 of user.roles) {
|
|
156523
|
+
if (r2.org && !orgMap.has(r2.org.sourcedId)) {
|
|
156524
|
+
orgMap.set(r2.org.sourcedId, {
|
|
156525
|
+
id: r2.org.sourcedId,
|
|
156526
|
+
name: null,
|
|
156527
|
+
type: "school",
|
|
156528
|
+
isPrimary: false
|
|
156529
|
+
});
|
|
156530
|
+
}
|
|
156531
|
+
}
|
|
156532
|
+
const organizations = Array.from(orgMap.values());
|
|
156533
|
+
return { role, organizations };
|
|
156534
|
+
} catch (error2) {
|
|
156535
|
+
log2.warn("[OneRoster] Failed to fetch student, using defaults", { error: error2, timebackId });
|
|
156536
|
+
return { role: "student", organizations: [] };
|
|
156322
156537
|
}
|
|
156323
|
-
|
|
156538
|
+
}
|
|
156539
|
+
async function fetchEnrollmentsFromEduBridge(timebackId) {
|
|
156540
|
+
const db = getDatabase();
|
|
156541
|
+
log2.debug("[EduBridge] Fetching student enrollments", { timebackId });
|
|
156324
156542
|
try {
|
|
156325
156543
|
const client = await getTimebackClient();
|
|
156326
|
-
const
|
|
156327
|
-
const courseIds =
|
|
156328
|
-
if (courseIds.length === 0)
|
|
156544
|
+
const enrollments = await client.getEnrollments(timebackId);
|
|
156545
|
+
const courseIds = enrollments.map((e) => e.courseId).filter((id) => Boolean(id));
|
|
156546
|
+
if (courseIds.length === 0)
|
|
156329
156547
|
return [];
|
|
156330
|
-
|
|
156548
|
+
const courseToSchool = new Map(enrollments.filter((e) => e.school?.id).map((e) => [e.courseId, e.school.id]));
|
|
156331
156549
|
const integrations = await db.query.gameTimebackIntegrations.findMany({
|
|
156332
156550
|
where: inArray(gameTimebackIntegrations.courseId, courseIds)
|
|
156333
156551
|
});
|
|
156334
|
-
return integrations.map((
|
|
156335
|
-
gameId:
|
|
156336
|
-
grade:
|
|
156337
|
-
subject:
|
|
156338
|
-
courseId:
|
|
156552
|
+
return integrations.map((i3) => ({
|
|
156553
|
+
gameId: i3.gameId,
|
|
156554
|
+
grade: i3.grade,
|
|
156555
|
+
subject: i3.subject,
|
|
156556
|
+
courseId: i3.courseId,
|
|
156557
|
+
orgId: courseToSchool.get(i3.courseId)
|
|
156339
156558
|
}));
|
|
156340
156559
|
} catch (error2) {
|
|
156341
|
-
log2.warn("[
|
|
156342
|
-
error: error2,
|
|
156343
|
-
timebackId
|
|
156344
|
-
});
|
|
156560
|
+
log2.warn("[EduBridge] Failed to fetch enrollments", { error: error2, timebackId });
|
|
156345
156561
|
return [];
|
|
156346
156562
|
}
|
|
156347
156563
|
}
|
|
156348
|
-
|
|
156349
|
-
|
|
156350
|
-
|
|
156351
|
-
|
|
156352
|
-
|
|
156353
|
-
|
|
156354
|
-
|
|
156355
|
-
|
|
156356
|
-
return role;
|
|
156357
|
-
} catch (error2) {
|
|
156358
|
-
log2.warn("[timeback] Failed to fetch user role, defaulting to student:", {
|
|
156359
|
-
error: error2,
|
|
156360
|
-
timebackId
|
|
156361
|
-
});
|
|
156362
|
-
return "student";
|
|
156363
|
-
}
|
|
156564
|
+
function filterEnrollmentsByGame(enrollments, gameId) {
|
|
156565
|
+
return enrollments.filter((e) => e.gameId === gameId).map(({ gameId: _4, ...rest }) => rest);
|
|
156566
|
+
}
|
|
156567
|
+
function filterOrganizationsByEnrollments(organizations, enrollments) {
|
|
156568
|
+
const enrollmentOrgIds = new Set(enrollments.map((e) => e.orgId).filter(Boolean));
|
|
156569
|
+
if (enrollmentOrgIds.size === 0)
|
|
156570
|
+
return [];
|
|
156571
|
+
return organizations.filter((o4) => enrollmentOrgIds.has(o4.id));
|
|
156364
156572
|
}
|
|
156365
|
-
async function fetchUserTimebackData(timebackId) {
|
|
156366
|
-
const [role,
|
|
156367
|
-
|
|
156368
|
-
|
|
156573
|
+
async function fetchUserTimebackData(timebackId, gameId) {
|
|
156574
|
+
const [{ role, organizations: allOrganizations }, allEnrollments] = await Promise.all([
|
|
156575
|
+
fetchStudentFromOneRoster(timebackId),
|
|
156576
|
+
fetchEnrollmentsFromEduBridge(timebackId)
|
|
156369
156577
|
]);
|
|
156370
|
-
|
|
156578
|
+
const enrollments = gameId ? filterEnrollmentsByGame(allEnrollments, gameId) : allEnrollments;
|
|
156579
|
+
const organizations = gameId ? filterOrganizationsByEnrollments(allOrganizations, enrollments) : allOrganizations;
|
|
156580
|
+
log2.debug("[Timeback] Fetched student data", {
|
|
156581
|
+
timebackId,
|
|
156582
|
+
role,
|
|
156583
|
+
enrollments: enrollments.map((e) => `${e.subject}:${e.grade}`),
|
|
156584
|
+
organizations: organizations.map((o4) => `${o4.name ?? o4.id} (${o4.type})`)
|
|
156585
|
+
});
|
|
156586
|
+
return { id: timebackId, role, enrollments, organizations };
|
|
156371
156587
|
}
|
|
156372
156588
|
// ../data/src/domains/achievement/types.ts
|
|
156373
156589
|
var AchievementCompletionType;
|
|
@@ -158285,7 +158501,7 @@ async function publishPersonalBestNotification(userId, gameId, rank, newScore, p
|
|
|
158285
158501
|
}
|
|
158286
158502
|
// ../cloudflare/src/playcademy/provider.ts
|
|
158287
158503
|
import { readdir as readdir2, readFile as readFile2, stat } from "node:fs/promises";
|
|
158288
|
-
import { join as
|
|
158504
|
+
import { join as join5, relative } from "node:path";
|
|
158289
158505
|
init_src();
|
|
158290
158506
|
|
|
158291
158507
|
// ../cloudflare/src/core/client.ts
|
|
@@ -158812,7 +159028,7 @@ init_src();
|
|
|
158812
159028
|
// ../cloudflare/src/utils/assets.ts
|
|
158813
159029
|
import { createHash } from "node:crypto";
|
|
158814
159030
|
import { readdir, readFile } from "node:fs/promises";
|
|
158815
|
-
import { join as
|
|
159031
|
+
import { join as join4 } from "node:path";
|
|
158816
159032
|
function hashFile(content) {
|
|
158817
159033
|
return createHash("md5").update(content).digest("hex");
|
|
158818
159034
|
}
|
|
@@ -158832,7 +159048,7 @@ async function scanAssetDirectory(distPath) {
|
|
|
158832
159048
|
async function scanFiles(dir, baseDir = dir) {
|
|
158833
159049
|
const entries = await readdir(dir, { withFileTypes: true });
|
|
158834
159050
|
for (const entry of entries) {
|
|
158835
|
-
const fullPath =
|
|
159051
|
+
const fullPath = join4(dir, entry.name);
|
|
158836
159052
|
if (entry.isDirectory()) {
|
|
158837
159053
|
await scanFiles(fullPath, baseDir);
|
|
158838
159054
|
} else {
|
|
@@ -159204,7 +159420,7 @@ class CloudflareProvider {
|
|
|
159204
159420
|
async function scanDirectory(dir) {
|
|
159205
159421
|
const entries = await readdir2(dir, { withFileTypes: true });
|
|
159206
159422
|
for (const entry of entries) {
|
|
159207
|
-
const fullPath =
|
|
159423
|
+
const fullPath = join5(dir, entry.name);
|
|
159208
159424
|
if (entry.isDirectory()) {
|
|
159209
159425
|
if (await scanDirectory(fullPath))
|
|
159210
159426
|
return true;
|
|
@@ -159226,7 +159442,7 @@ class CloudflareProvider {
|
|
|
159226
159442
|
async resolveAssetBasePath(dirPath) {
|
|
159227
159443
|
const entries = await readdir2(dirPath, { withFileTypes: true });
|
|
159228
159444
|
if (entries.length === 1 && entries[0]?.isDirectory()) {
|
|
159229
|
-
const unwrappedPath =
|
|
159445
|
+
const unwrappedPath = join5(dirPath, entries[0].name);
|
|
159230
159446
|
log2.debug("[CloudflareProvider] Unwrapping wrapper directory", {
|
|
159231
159447
|
wrapper: entries[0].name
|
|
159232
159448
|
});
|
|
@@ -159237,7 +159453,7 @@ class CloudflareProvider {
|
|
|
159237
159453
|
async uploadFilesToR2(dir, baseDir, bucketName) {
|
|
159238
159454
|
const entries = await readdir2(dir, { withFileTypes: true });
|
|
159239
159455
|
for (const entry of entries) {
|
|
159240
|
-
const fullPath =
|
|
159456
|
+
const fullPath = join5(dir, entry.name);
|
|
159241
159457
|
if (entry.isDirectory()) {
|
|
159242
159458
|
await this.uploadFilesToR2(fullPath, baseDir, bucketName);
|
|
159243
159459
|
} else {
|
|
@@ -159725,46 +159941,25 @@ async function seedCurrencies(db) {
|
|
|
159725
159941
|
}
|
|
159726
159942
|
}
|
|
159727
159943
|
|
|
159728
|
-
// src/lib/logging/adapter.ts
|
|
159729
|
-
var customLogger;
|
|
159730
|
-
function setLogger(logger3) {
|
|
159731
|
-
customLogger = logger3;
|
|
159732
|
-
}
|
|
159733
|
-
function getLogger() {
|
|
159734
|
-
if (customLogger) {
|
|
159735
|
-
return customLogger;
|
|
159736
|
-
}
|
|
159737
|
-
return {
|
|
159738
|
-
info: (msg) => console.log(msg),
|
|
159739
|
-
warn: (msg) => console.warn(msg),
|
|
159740
|
-
error: (msg) => console.error(msg)
|
|
159741
|
-
};
|
|
159742
|
-
}
|
|
159743
|
-
var logger3 = {
|
|
159744
|
-
info: (msg) => {
|
|
159745
|
-
if (customLogger || !config.embedded) {
|
|
159746
|
-
getLogger().info(msg);
|
|
159747
|
-
}
|
|
159748
|
-
},
|
|
159749
|
-
warn: (msg) => getLogger().warn(msg),
|
|
159750
|
-
error: (msg) => getLogger().error(msg)
|
|
159751
|
-
};
|
|
159752
159944
|
// src/database/seed/timeback.ts
|
|
159753
|
-
function
|
|
159754
|
-
|
|
159755
|
-
return null;
|
|
159756
|
-
if (studentId === "mock")
|
|
159757
|
-
return `mock-student-${crypto.randomUUID().slice(0, 8)}`;
|
|
159758
|
-
return studentId;
|
|
159945
|
+
function generateMockStudentId(userId) {
|
|
159946
|
+
return `mock-student-${userId.slice(-8)}`;
|
|
159759
159947
|
}
|
|
159760
|
-
function
|
|
159761
|
-
|
|
159948
|
+
function generateTimebackId(userId, isPrimaryUser = false) {
|
|
159949
|
+
const timebackId = config.timeback.timebackId;
|
|
159950
|
+
if (!timebackId) {
|
|
159951
|
+
return null;
|
|
159952
|
+
}
|
|
159953
|
+
if (timebackId === "mock") {
|
|
159954
|
+
return generateMockStudentId(userId);
|
|
159955
|
+
}
|
|
159956
|
+
return isPrimaryUser ? timebackId : generateMockStudentId(userId);
|
|
159762
159957
|
}
|
|
159763
159958
|
async function seedTimebackIntegrations(db, gameId, courses) {
|
|
159764
159959
|
const now2 = new Date;
|
|
159765
159960
|
let seededCount = 0;
|
|
159766
159961
|
for (const course of courses) {
|
|
159767
|
-
const courseId = `mock-${course.subject.toLowerCase()}-g${course.grade}`;
|
|
159962
|
+
const courseId = course.courseId || `mock-${course.subject.toLowerCase()}-g${course.grade}`;
|
|
159768
159963
|
try {
|
|
159769
159964
|
const existing = await db.query.gameTimebackIntegrations.findFirst({
|
|
159770
159965
|
where: (table14, { and: and3, eq: eq3 }) => and3(eq3(table14.gameId, gameId), eq3(table14.grade, course.grade), eq3(table14.subject, course.subject))
|
|
@@ -159782,12 +159977,10 @@ async function seedTimebackIntegrations(db, gameId, courses) {
|
|
|
159782
159977
|
});
|
|
159783
159978
|
seededCount++;
|
|
159784
159979
|
} catch (error2) {
|
|
159785
|
-
console.error(`❌ Error seeding
|
|
159980
|
+
console.error(`❌ Error seeding Timeback integration for ${course.subject}:${course.grade}:`, error2);
|
|
159786
159981
|
}
|
|
159787
159982
|
}
|
|
159788
|
-
|
|
159789
|
-
logger3.info(`\uD83D\uDCDA Seeded ${seededCount} TimeBack integration(s)`);
|
|
159790
|
-
}
|
|
159983
|
+
return seededCount;
|
|
159791
159984
|
}
|
|
159792
159985
|
|
|
159793
159986
|
// src/database/seed/games.ts
|
|
@@ -159823,7 +160016,6 @@ async function seedCurrentProjectGame(db, project) {
|
|
|
159823
160016
|
where: (games3, { eq: eq3 }) => eq3(games3.slug, project.slug)
|
|
159824
160017
|
});
|
|
159825
160018
|
if (existingGame) {
|
|
159826
|
-
logger3.info(`\uD83C\uDFAE Game "${project.displayName}" (${project.slug}) already exists`);
|
|
159827
160019
|
if (project.timebackCourses && project.timebackCourses.length > 0) {
|
|
159828
160020
|
await seedTimebackIntegrations(db, existingGame.id, project.timebackCourses);
|
|
159829
160021
|
}
|
|
@@ -159916,11 +160108,12 @@ async function seedSpriteTemplates(db) {
|
|
|
159916
160108
|
// src/database/seed/index.ts
|
|
159917
160109
|
async function seedDemoData(db) {
|
|
159918
160110
|
try {
|
|
159919
|
-
const
|
|
159920
|
-
for (const
|
|
160111
|
+
const primaryUserId = DEMO_USERS.player.id;
|
|
160112
|
+
for (const user of Object.values(DEMO_USERS)) {
|
|
160113
|
+
const isPrimaryUser = user.id === primaryUserId;
|
|
159921
160114
|
const userValues = {
|
|
159922
160115
|
...user,
|
|
159923
|
-
timebackId:
|
|
160116
|
+
timebackId: generateTimebackId(user.id, isPrimaryUser)
|
|
159924
160117
|
};
|
|
159925
160118
|
await db.insert(users).values(userValues).onConflictDoNothing();
|
|
159926
160119
|
}
|
|
@@ -159941,7 +160134,7 @@ async function seedDemoData(db) {
|
|
|
159941
160134
|
console.error("❌ Error seeding demo data:", error2);
|
|
159942
160135
|
throw error2;
|
|
159943
160136
|
}
|
|
159944
|
-
return DEMO_USERS.
|
|
160137
|
+
return DEMO_USERS.player;
|
|
159945
160138
|
}
|
|
159946
160139
|
|
|
159947
160140
|
// src/server/database.ts
|
|
@@ -159981,6 +160174,13 @@ async function setupServerDatabase(processedOptions, project) {
|
|
|
159981
160174
|
// src/server/options.ts
|
|
159982
160175
|
init_src();
|
|
159983
160176
|
var import_json_colorizer = __toESM(require_dist2(), 1);
|
|
160177
|
+
|
|
160178
|
+
// src/lib/logging/adapter.ts
|
|
160179
|
+
var customLogger;
|
|
160180
|
+
function setLogger(logger3) {
|
|
160181
|
+
customLogger = logger3;
|
|
160182
|
+
}
|
|
160183
|
+
// src/server/options.ts
|
|
159984
160184
|
function processServerOptions(port, options) {
|
|
159985
160185
|
const {
|
|
159986
160186
|
verbose = false,
|
|
@@ -160031,6 +160231,10 @@ async function startRealtimeServer(realtimeOptions, betterAuthSecret) {
|
|
|
160031
160231
|
if (!realtimeOptions.enabled) {
|
|
160032
160232
|
return null;
|
|
160033
160233
|
}
|
|
160234
|
+
if (!realtimeOptions.port) {
|
|
160235
|
+
return null;
|
|
160236
|
+
}
|
|
160237
|
+
await waitForPort(realtimeOptions.port);
|
|
160034
160238
|
if (typeof Bun === "undefined") {
|
|
160035
160239
|
try {
|
|
160036
160240
|
return Promise.resolve().then(() => (init_sandbox(), exports_sandbox)).then(({ createSandboxRealtimeServer: createSandboxRealtimeServer2 }) => createSandboxRealtimeServer2({
|
|
@@ -165324,12 +165528,32 @@ async function getUserMe(ctx) {
|
|
|
165324
165528
|
log2.error(`[API /users/me] User not found in DB for valid token ID: ${user.id}`);
|
|
165325
165529
|
throw ApiError.notFound("User not found");
|
|
165326
165530
|
}
|
|
165531
|
+
const timeback3 = userData.timebackId ? await fetchUserTimebackData(userData.timebackId, ctx.gameId) : undefined;
|
|
165532
|
+
if (ctx.gameId) {
|
|
165533
|
+
return {
|
|
165534
|
+
id: userData.id,
|
|
165535
|
+
name: userData.name,
|
|
165536
|
+
role: userData.role,
|
|
165537
|
+
username: userData.username,
|
|
165538
|
+
email: userData.email,
|
|
165539
|
+
timeback: timeback3
|
|
165540
|
+
};
|
|
165541
|
+
}
|
|
165327
165542
|
const timebackAccount = await db.query.accounts.findFirst({
|
|
165328
165543
|
where: and(eq(accounts.userId, user.id), eq(accounts.providerId, "timeback"))
|
|
165329
165544
|
});
|
|
165330
|
-
const timeback3 = userData.timebackId ? await fetchUserTimebackData(userData.timebackId) : undefined;
|
|
165331
165545
|
return {
|
|
165332
|
-
|
|
165546
|
+
id: userData.id,
|
|
165547
|
+
name: userData.name,
|
|
165548
|
+
username: userData.username,
|
|
165549
|
+
email: userData.email,
|
|
165550
|
+
emailVerified: userData.emailVerified,
|
|
165551
|
+
image: userData.image,
|
|
165552
|
+
role: userData.role,
|
|
165553
|
+
developerStatus: userData.developerStatus,
|
|
165554
|
+
characterCreated: userData.characterCreated,
|
|
165555
|
+
createdAt: userData.createdAt,
|
|
165556
|
+
updatedAt: userData.updatedAt,
|
|
165333
165557
|
hasTimebackAccount: !!timebackAccount,
|
|
165334
165558
|
timeback: timeback3
|
|
165335
165559
|
};
|
|
@@ -165341,16 +165565,107 @@ async function getUserMe(ctx) {
|
|
|
165341
165565
|
}
|
|
165342
165566
|
}
|
|
165343
165567
|
|
|
165568
|
+
// src/mocks/timeback.ts
|
|
165569
|
+
init_src();
|
|
165570
|
+
function shouldMockTimeback() {
|
|
165571
|
+
return config.timeback.timebackId === "mock";
|
|
165572
|
+
}
|
|
165573
|
+
function getMockStudentProfile() {
|
|
165574
|
+
const { organization: org, role } = config.timeback;
|
|
165575
|
+
return {
|
|
165576
|
+
role: role ?? "student",
|
|
165577
|
+
organizations: [
|
|
165578
|
+
{
|
|
165579
|
+
id: org?.id ?? "PLAYCADEMY",
|
|
165580
|
+
name: org?.name ?? "Playcademy Studios",
|
|
165581
|
+
type: org?.type ?? "department",
|
|
165582
|
+
isPrimary: true
|
|
165583
|
+
}
|
|
165584
|
+
]
|
|
165585
|
+
};
|
|
165586
|
+
}
|
|
165587
|
+
async function getMockEnrollments(db) {
|
|
165588
|
+
const allIntegrations = await db.query.gameTimebackIntegrations.findMany();
|
|
165589
|
+
return allIntegrations.map((i4) => ({
|
|
165590
|
+
gameId: i4.gameId,
|
|
165591
|
+
grade: i4.grade,
|
|
165592
|
+
subject: i4.subject,
|
|
165593
|
+
courseId: i4.courseId
|
|
165594
|
+
}));
|
|
165595
|
+
}
|
|
165596
|
+
async function getMockTimebackData(db, timebackId, gameId) {
|
|
165597
|
+
const { role, organizations } = getMockStudentProfile();
|
|
165598
|
+
const allEnrollments = await getMockEnrollments(db);
|
|
165599
|
+
const enrollments = gameId ? allEnrollments.filter((e2) => e2.gameId === gameId).map(({ gameId: _5, ...rest }) => rest) : allEnrollments;
|
|
165600
|
+
log2.debug("[Timeback] Sandbox is using mock data", {
|
|
165601
|
+
timebackId,
|
|
165602
|
+
role,
|
|
165603
|
+
enrollments: enrollments.map((e2) => `${e2.subject}:${e2.grade}`),
|
|
165604
|
+
organizations: organizations.map((o5) => `${o5.name ?? o5.id}`)
|
|
165605
|
+
});
|
|
165606
|
+
return { id: timebackId, role, enrollments, organizations };
|
|
165607
|
+
}
|
|
165608
|
+
async function buildMockUserResponse(db, user, gameId) {
|
|
165609
|
+
const timeback3 = user.timebackId ? await getMockTimebackData(db, user.timebackId, gameId) : undefined;
|
|
165610
|
+
if (gameId) {
|
|
165611
|
+
return {
|
|
165612
|
+
id: user.id,
|
|
165613
|
+
name: user.name,
|
|
165614
|
+
role: user.role,
|
|
165615
|
+
username: user.username,
|
|
165616
|
+
email: user.email,
|
|
165617
|
+
timeback: timeback3
|
|
165618
|
+
};
|
|
165619
|
+
}
|
|
165620
|
+
const timebackAccount = await db.query.accounts.findFirst({
|
|
165621
|
+
where: and(eq(accounts.userId, user.id), eq(accounts.providerId, "timeback"))
|
|
165622
|
+
});
|
|
165623
|
+
return {
|
|
165624
|
+
id: user.id,
|
|
165625
|
+
name: user.name,
|
|
165626
|
+
username: user.username,
|
|
165627
|
+
email: user.email,
|
|
165628
|
+
emailVerified: user.emailVerified,
|
|
165629
|
+
image: user.image,
|
|
165630
|
+
role: user.role,
|
|
165631
|
+
developerStatus: user.developerStatus,
|
|
165632
|
+
characterCreated: user.characterCreated,
|
|
165633
|
+
createdAt: user.createdAt,
|
|
165634
|
+
updatedAt: user.updatedAt,
|
|
165635
|
+
hasTimebackAccount: !!timebackAccount,
|
|
165636
|
+
timeback: timeback3
|
|
165637
|
+
};
|
|
165638
|
+
}
|
|
165639
|
+
|
|
165344
165640
|
// src/routes/platform/users.ts
|
|
165345
165641
|
var usersRouter = new Hono2;
|
|
165346
165642
|
usersRouter.get("/me", async (c3) => {
|
|
165347
|
-
const
|
|
165348
|
-
|
|
165349
|
-
|
|
165350
|
-
|
|
165351
|
-
|
|
165352
|
-
}
|
|
165643
|
+
const user = c3.get("user");
|
|
165644
|
+
const gameId = c3.get("gameId");
|
|
165645
|
+
if (!user) {
|
|
165646
|
+
const error2 = ApiError.unauthorized("Valid session or bearer token required");
|
|
165647
|
+
return c3.json(createErrorResponse(error2), error2.statusCode);
|
|
165648
|
+
}
|
|
165353
165649
|
try {
|
|
165650
|
+
if (shouldMockTimeback()) {
|
|
165651
|
+
const db = c3.get("db");
|
|
165652
|
+
const userData2 = await db.query.users.findFirst({
|
|
165653
|
+
where: eq(users.id, user.id)
|
|
165654
|
+
});
|
|
165655
|
+
if (!userData2) {
|
|
165656
|
+
const error2 = ApiError.notFound("User not found");
|
|
165657
|
+
return c3.json(createErrorResponse(error2), error2.statusCode);
|
|
165658
|
+
}
|
|
165659
|
+
const response = await buildMockUserResponse(db, userData2, gameId);
|
|
165660
|
+
return c3.json(response);
|
|
165661
|
+
}
|
|
165662
|
+
const ctx = {
|
|
165663
|
+
user,
|
|
165664
|
+
params: {},
|
|
165665
|
+
url: new URL(c3.req.url),
|
|
165666
|
+
request: c3.req.raw,
|
|
165667
|
+
gameId
|
|
165668
|
+
};
|
|
165354
165669
|
const userData = await getUserMe(ctx);
|
|
165355
165670
|
return c3.json(userData);
|
|
165356
165671
|
} catch (error2) {
|
|
@@ -170690,7 +171005,7 @@ async function getTodayTimeBackXp(ctx) {
|
|
|
170690
171005
|
throw error2;
|
|
170691
171006
|
if (error2 instanceof InvalidTimezoneError)
|
|
170692
171007
|
throw ApiError.badRequest(error2.message);
|
|
170693
|
-
log2.error("[
|
|
171008
|
+
log2.error("[Timeback] getTodayTimeBackXp failed", { error: error2 });
|
|
170694
171009
|
throw ApiError.internal("Failed to get today's TimeBack XP", error2);
|
|
170695
171010
|
}
|
|
170696
171011
|
}
|
|
@@ -170706,7 +171021,7 @@ async function getTotalTimeBackXp(ctx) {
|
|
|
170706
171021
|
totalXp: Number(result[0]?.totalXp) || 0
|
|
170707
171022
|
};
|
|
170708
171023
|
} catch (error2) {
|
|
170709
|
-
log2.error("[
|
|
171024
|
+
log2.error("[Timeback] getTotalTimeBackXp failed", { error: error2 });
|
|
170710
171025
|
throw ApiError.internal("Failed to get total TimeBack XP", error2);
|
|
170711
171026
|
}
|
|
170712
171027
|
}
|
|
@@ -170756,7 +171071,7 @@ async function updateTodayTimeBackXp(ctx) {
|
|
|
170756
171071
|
} catch (error2) {
|
|
170757
171072
|
if (error2 instanceof ApiError)
|
|
170758
171073
|
throw error2;
|
|
170759
|
-
log2.error("[
|
|
171074
|
+
log2.error("[Timeback] updateTodayTimeBackXp failed", { error: error2 });
|
|
170760
171075
|
throw ApiError.internal("Failed to update today's TimeBack XP", error2);
|
|
170761
171076
|
}
|
|
170762
171077
|
}
|
|
@@ -170791,7 +171106,7 @@ async function getTimeBackXpHistory(ctx) {
|
|
|
170791
171106
|
}))
|
|
170792
171107
|
};
|
|
170793
171108
|
} catch (error2) {
|
|
170794
|
-
log2.error("[
|
|
171109
|
+
log2.error("[Timeback] getTimeBackXpHistory failed", { error: error2 });
|
|
170795
171110
|
throw ApiError.internal("Failed to get TimeBack XP history", error2);
|
|
170796
171111
|
}
|
|
170797
171112
|
}
|
|
@@ -170807,7 +171122,7 @@ async function getStudentEnrollments(ctx) {
|
|
|
170807
171122
|
throw ApiError.badRequest("Missing timebackId parameter");
|
|
170808
171123
|
}
|
|
170809
171124
|
log2.debug("[API] Getting student enrollments", { userId: user.id, timebackId });
|
|
170810
|
-
const enrollments = await
|
|
171125
|
+
const enrollments = await fetchEnrollmentsFromEduBridge(timebackId);
|
|
170811
171126
|
log2.info("[API] Retrieved student enrollments", {
|
|
170812
171127
|
userId: user.id,
|
|
170813
171128
|
timebackId,
|
|
@@ -171008,13 +171323,23 @@ timebackRouter.post("/end-activity", async (c3) => {
|
|
|
171008
171323
|
});
|
|
171009
171324
|
timebackRouter.get("/enrollments/:timebackId", async (c3) => {
|
|
171010
171325
|
const timebackId = c3.req.param("timebackId");
|
|
171011
|
-
const
|
|
171012
|
-
|
|
171013
|
-
|
|
171014
|
-
|
|
171015
|
-
|
|
171016
|
-
};
|
|
171326
|
+
const user = c3.get("user");
|
|
171327
|
+
if (!user) {
|
|
171328
|
+
const error2 = ApiError.unauthorized("Must be logged in to get enrollments");
|
|
171329
|
+
return c3.json(createErrorResponse(error2), error2.statusCode);
|
|
171330
|
+
}
|
|
171017
171331
|
try {
|
|
171332
|
+
if (shouldMockTimeback()) {
|
|
171333
|
+
const db = c3.get("db");
|
|
171334
|
+
const enrollments2 = await getMockEnrollments(db);
|
|
171335
|
+
return c3.json({ enrollments: enrollments2 });
|
|
171336
|
+
}
|
|
171337
|
+
const ctx = {
|
|
171338
|
+
user,
|
|
171339
|
+
params: { timebackId },
|
|
171340
|
+
url: new URL(c3.req.url),
|
|
171341
|
+
request: c3.req.raw
|
|
171342
|
+
};
|
|
171018
171343
|
const result = await getStudentEnrollments(ctx);
|
|
171019
171344
|
return c3.json(result);
|
|
171020
171345
|
} catch (error2) {
|
|
@@ -171330,6 +171655,7 @@ function registerRoutes(app) {
|
|
|
171330
171655
|
// src/server/index.ts
|
|
171331
171656
|
var version3 = package_default.version;
|
|
171332
171657
|
async function startServer(port, project, options = {}) {
|
|
171658
|
+
await waitForPort(port);
|
|
171333
171659
|
const processedOptions = processServerOptions(port, options);
|
|
171334
171660
|
const db = await setupServerDatabase(processedOptions, project);
|
|
171335
171661
|
const app = createApp(db, {
|
|
@@ -171342,11 +171668,20 @@ async function startServer(port, project, options = {}) {
|
|
|
171342
171668
|
return {
|
|
171343
171669
|
main: mainServer,
|
|
171344
171670
|
realtime: realtimeServer,
|
|
171671
|
+
timebackMode: getTimebackDisplayMode(),
|
|
171672
|
+
setRole: (role) => {
|
|
171673
|
+
config.timeback.role = role;
|
|
171674
|
+
},
|
|
171345
171675
|
stop: () => {
|
|
171346
|
-
|
|
171347
|
-
|
|
171348
|
-
|
|
171349
|
-
|
|
171676
|
+
return new Promise((resolve2) => {
|
|
171677
|
+
if (realtimeServer?.stop)
|
|
171678
|
+
realtimeServer.stop();
|
|
171679
|
+
if (mainServer.close) {
|
|
171680
|
+
mainServer.close(() => resolve2());
|
|
171681
|
+
} else {
|
|
171682
|
+
resolve2();
|
|
171683
|
+
}
|
|
171684
|
+
});
|
|
171350
171685
|
}
|
|
171351
171686
|
};
|
|
171352
171687
|
}
|
|
@@ -171367,105 +171702,6 @@ var {
|
|
|
171367
171702
|
Help
|
|
171368
171703
|
} = import__4.default;
|
|
171369
171704
|
|
|
171370
|
-
// ../utils/src/port.ts
|
|
171371
|
-
import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync, writeFileSync } from "node:fs";
|
|
171372
|
-
import { createServer } from "node:net";
|
|
171373
|
-
import { homedir } from "node:os";
|
|
171374
|
-
import { join as join5 } from "node:path";
|
|
171375
|
-
async function isPortAvailableOnHost(port, host) {
|
|
171376
|
-
return new Promise((resolve2) => {
|
|
171377
|
-
const server2 = createServer();
|
|
171378
|
-
let resolved = false;
|
|
171379
|
-
const cleanup = (result) => {
|
|
171380
|
-
if (resolved)
|
|
171381
|
-
return;
|
|
171382
|
-
resolved = true;
|
|
171383
|
-
try {
|
|
171384
|
-
server2.close();
|
|
171385
|
-
} catch {}
|
|
171386
|
-
resolve2(result);
|
|
171387
|
-
};
|
|
171388
|
-
const timeout = setTimeout(() => cleanup(true), 100);
|
|
171389
|
-
server2.once("error", (err2) => {
|
|
171390
|
-
clearTimeout(timeout);
|
|
171391
|
-
if (err2.code === "EAFNOSUPPORT" || err2.code === "EADDRNOTAVAIL") {
|
|
171392
|
-
cleanup(true);
|
|
171393
|
-
} else {
|
|
171394
|
-
cleanup(false);
|
|
171395
|
-
}
|
|
171396
|
-
});
|
|
171397
|
-
server2.once("listening", () => {
|
|
171398
|
-
clearTimeout(timeout);
|
|
171399
|
-
cleanup(true);
|
|
171400
|
-
});
|
|
171401
|
-
server2.listen(port, host).unref();
|
|
171402
|
-
});
|
|
171403
|
-
}
|
|
171404
|
-
async function findAvailablePort(startPort = 4321) {
|
|
171405
|
-
if (await isPortAvailableOnHost(startPort, "0.0.0.0")) {
|
|
171406
|
-
return startPort;
|
|
171407
|
-
} else {
|
|
171408
|
-
return findAvailablePort(startPort + 1);
|
|
171409
|
-
}
|
|
171410
|
-
}
|
|
171411
|
-
function getRegistryPath() {
|
|
171412
|
-
const home = homedir();
|
|
171413
|
-
const dir = join5(home, ".playcademy");
|
|
171414
|
-
if (!existsSync2(dir)) {
|
|
171415
|
-
mkdirSync2(dir, { recursive: true });
|
|
171416
|
-
}
|
|
171417
|
-
return join5(dir, ".proc");
|
|
171418
|
-
}
|
|
171419
|
-
function readRegistry() {
|
|
171420
|
-
const registryPath = getRegistryPath();
|
|
171421
|
-
if (!existsSync2(registryPath)) {
|
|
171422
|
-
return {};
|
|
171423
|
-
}
|
|
171424
|
-
try {
|
|
171425
|
-
const content = readFileSync(registryPath, "utf-8");
|
|
171426
|
-
return JSON.parse(content);
|
|
171427
|
-
} catch {
|
|
171428
|
-
return {};
|
|
171429
|
-
}
|
|
171430
|
-
}
|
|
171431
|
-
function writeRegistry(registry2) {
|
|
171432
|
-
const registryPath = getRegistryPath();
|
|
171433
|
-
writeFileSync(registryPath, JSON.stringify(registry2, null, 2), "utf-8");
|
|
171434
|
-
}
|
|
171435
|
-
function getServerKey(type, port) {
|
|
171436
|
-
return `${type}-${port}`;
|
|
171437
|
-
}
|
|
171438
|
-
function writeServerInfo(type, info2) {
|
|
171439
|
-
const registry2 = readRegistry();
|
|
171440
|
-
const key = getServerKey(type, info2.port);
|
|
171441
|
-
registry2[key] = info2;
|
|
171442
|
-
writeRegistry(registry2);
|
|
171443
|
-
}
|
|
171444
|
-
function cleanupServerInfo(type, projectRoot, pid) {
|
|
171445
|
-
const registry2 = readRegistry();
|
|
171446
|
-
const keysToRemove = [];
|
|
171447
|
-
for (const [key, info2] of Object.entries(registry2)) {
|
|
171448
|
-
if (key.startsWith(`${type}-`)) {
|
|
171449
|
-
let matches = true;
|
|
171450
|
-
if (projectRoot && info2.projectRoot !== projectRoot) {
|
|
171451
|
-
matches = false;
|
|
171452
|
-
}
|
|
171453
|
-
if (pid !== undefined && info2.pid !== pid) {
|
|
171454
|
-
matches = false;
|
|
171455
|
-
}
|
|
171456
|
-
if (matches) {
|
|
171457
|
-
keysToRemove.push(key);
|
|
171458
|
-
}
|
|
171459
|
-
}
|
|
171460
|
-
}
|
|
171461
|
-
for (const key of keysToRemove) {
|
|
171462
|
-
delete registry2[key];
|
|
171463
|
-
}
|
|
171464
|
-
if (keysToRemove.length > 0) {
|
|
171465
|
-
writeRegistry(registry2);
|
|
171466
|
-
}
|
|
171467
|
-
}
|
|
171468
|
-
|
|
171469
171705
|
// src/cli/display.ts
|
|
171470
171706
|
var import_picocolors = __toESM(require_picocolors(), 1);
|
|
171471
171707
|
function printBanner(options) {
|
|
@@ -171687,7 +171923,7 @@ function parseTimebackOptions(options) {
|
|
|
171687
171923
|
onerosterApiUrl: options.timebackOnerosterUrl || "http://localhost:9000",
|
|
171688
171924
|
caliperApiUrl: options.timebackCaliperUrl || "http://localhost:9001",
|
|
171689
171925
|
courseId: options.timebackCourseId,
|
|
171690
|
-
|
|
171926
|
+
timebackId: options.timebackStudentId
|
|
171691
171927
|
};
|
|
171692
171928
|
}
|
|
171693
171929
|
if (options.timebackOnerosterUrl) {
|
|
@@ -171696,7 +171932,7 @@ function parseTimebackOptions(options) {
|
|
|
171696
171932
|
onerosterApiUrl: options.timebackOnerosterUrl,
|
|
171697
171933
|
caliperApiUrl: options.timebackCaliperUrl,
|
|
171698
171934
|
courseId: options.timebackCourseId,
|
|
171699
|
-
|
|
171935
|
+
timebackId: options.timebackStudentId
|
|
171700
171936
|
};
|
|
171701
171937
|
}
|
|
171702
171938
|
return;
|
|
@@ -171706,12 +171942,15 @@ function parseTimebackOptions(options) {
|
|
|
171706
171942
|
var program2 = new Command;
|
|
171707
171943
|
program2.name("playcademy-sandbox").description("Local development server for Playcademy game development").version(version3).option("-p, --port <number>", "Port to run the server on", "4321").option("-v, --verbose", "Enable verbose logging", false).option("--project-name <name>", "Name of the current project").option("--project-slug <slug>", "Slug of the current project").option("--realtime", "Enable the realtime server", false).option("--realtime-port <number>", "Port for the realtime server (defaults to main port + 1)").option("--no-seed", "Do not seed the database with demo data").option("--recreate-db", "Recreate the on-disk database on start", false).option("--memory", "Use in-memory database (no persistence)", false).option("--db-path <path>", "Custom path for the database file (relative to cwd or absolute)").option("--config-path <path>", "Path to playcademy.config.json (defaults to cwd)").option("--timeback-local", "Use local TimeBack instance").option("--timeback-oneroster-url <url>", "TimeBack OneRoster API URL").option("--timeback-caliper-url <url>", "TimeBack Caliper API URL").option("--timeback-course-id <id>", "TimeBack course ID for seeding").option("--timeback-student-id <id>", "TimeBack student ID for demo user").action(async (options) => {
|
|
171708
171944
|
try {
|
|
171709
|
-
const
|
|
171710
|
-
const
|
|
171711
|
-
|
|
171945
|
+
const port = parseInt(options.port);
|
|
171946
|
+
const realtimePort = options.realtimePort ? parseInt(options.realtimePort) : port + 1;
|
|
171947
|
+
await requirePortAvailable(port);
|
|
171948
|
+
if (options.realtime) {
|
|
171949
|
+
await requirePortAvailable(realtimePort);
|
|
171950
|
+
}
|
|
171712
171951
|
const project = await parseProjectInfo(options);
|
|
171713
171952
|
const timebackOptions = parseTimebackOptions(options);
|
|
171714
|
-
const servers = await startServer(
|
|
171953
|
+
const servers = await startServer(port, project, {
|
|
171715
171954
|
seed: options.seed,
|
|
171716
171955
|
verbose: options.verbose,
|
|
171717
171956
|
memoryOnly: options.memory,
|
|
@@ -171725,14 +171964,14 @@ program2.name("playcademy-sandbox").description("Local development server for Pl
|
|
|
171725
171964
|
});
|
|
171726
171965
|
writeServerInfo("sandbox", {
|
|
171727
171966
|
pid: process.pid,
|
|
171728
|
-
port
|
|
171729
|
-
url: `http://localhost:${
|
|
171967
|
+
port,
|
|
171968
|
+
url: `http://localhost:${port}/api`,
|
|
171730
171969
|
startedAt: Date.now(),
|
|
171731
171970
|
projectRoot: process.cwd()
|
|
171732
171971
|
});
|
|
171733
171972
|
printBanner({
|
|
171734
171973
|
version: version3,
|
|
171735
|
-
port
|
|
171974
|
+
port,
|
|
171736
171975
|
realtimePort,
|
|
171737
171976
|
hasRealtime: !!servers.realtime
|
|
171738
171977
|
});
|