@playcademy/sandbox 0.3.17-beta.36 → 0.3.17-beta.38
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +232 -138
- package/dist/constants.js +4 -2
- package/dist/server.js +232 -138
- package/package.json +1 -1
package/dist/server.js
CHANGED
|
@@ -338,7 +338,8 @@ var PLAYCADEMY_BASE_URLS, GAME_WORKER_DOMAINS;
|
|
|
338
338
|
var init_domains = __esm(() => {
|
|
339
339
|
PLAYCADEMY_BASE_URLS = {
|
|
340
340
|
production: "https://hub.playcademy.net",
|
|
341
|
-
staging: "https://hub.dev.playcademy.net"
|
|
341
|
+
staging: "https://hub.dev.playcademy.net",
|
|
342
|
+
local: "http://localhost:5174"
|
|
342
343
|
};
|
|
343
344
|
GAME_WORKER_DOMAINS = {
|
|
344
345
|
production: "playcademy.gg",
|
|
@@ -446,7 +447,8 @@ var WORKER_NAMING, SECRETS_PREFIX = "secrets_", CLOUDFLARE_COMPATIBILITY_DATE =
|
|
|
446
447
|
var init_workers = __esm(() => {
|
|
447
448
|
WORKER_NAMING = {
|
|
448
449
|
STAGING_PREFIX: "staging-",
|
|
449
|
-
STAGING_SUFFIX: "-staging"
|
|
450
|
+
STAGING_SUFFIX: "-staging",
|
|
451
|
+
LOCAL_PREFIX: "local-"
|
|
450
452
|
};
|
|
451
453
|
});
|
|
452
454
|
|
|
@@ -1329,7 +1331,7 @@ var package_default;
|
|
|
1329
1331
|
var init_package = __esm(() => {
|
|
1330
1332
|
package_default = {
|
|
1331
1333
|
name: "@playcademy/sandbox",
|
|
1332
|
-
version: "0.3.17-beta.
|
|
1334
|
+
version: "0.3.17-beta.38",
|
|
1333
1335
|
description: "Local development server for Playcademy game development",
|
|
1334
1336
|
type: "module",
|
|
1335
1337
|
exports: {
|
|
@@ -5874,21 +5876,16 @@ var init_esm = __esm(() => {
|
|
|
5874
5876
|
// ../api-core/src/config/schema.ts
|
|
5875
5877
|
function createMinimalConfig(overrides) {
|
|
5876
5878
|
return apiConfigSchema.parse({
|
|
5877
|
-
|
|
5878
|
-
isLocal: false,
|
|
5879
|
+
sstStage: "test",
|
|
5879
5880
|
...overrides
|
|
5880
5881
|
});
|
|
5881
5882
|
}
|
|
5882
5883
|
function getPlatformEnvironment(config2) {
|
|
5883
|
-
return config2.
|
|
5884
|
+
return config2.sstStage === "production" ? "production" : "staging";
|
|
5884
5885
|
}
|
|
5885
|
-
|
|
5886
|
-
return config2.stage === "production";
|
|
5887
|
-
}
|
|
5888
|
-
var stageSchema, ltiConfigSchema, realtimeConfigSchema, apiConfigSchema;
|
|
5886
|
+
var ltiConfigSchema, realtimeConfigSchema, apiConfigSchema;
|
|
5889
5887
|
var init_schema = __esm(() => {
|
|
5890
5888
|
init_esm();
|
|
5891
|
-
stageSchema = exports_external.enum(["production", "dev", "local"]);
|
|
5892
5889
|
ltiConfigSchema = exports_external.object({
|
|
5893
5890
|
audience: exports_external.string(),
|
|
5894
5891
|
jwksUrl: exports_external.string().url(),
|
|
@@ -5899,7 +5896,7 @@ var init_schema = __esm(() => {
|
|
|
5899
5896
|
publishSecret: exports_external.string()
|
|
5900
5897
|
});
|
|
5901
5898
|
apiConfigSchema = exports_external.object({
|
|
5902
|
-
|
|
5899
|
+
sstStage: exports_external.string(),
|
|
5903
5900
|
isLocal: exports_external.boolean().default(false),
|
|
5904
5901
|
baseUrl: exports_external.string().url().optional(),
|
|
5905
5902
|
gameDomain: exports_external.string().optional(),
|
|
@@ -11296,8 +11293,9 @@ var init_pg_core = __esm(() => {
|
|
|
11296
11293
|
});
|
|
11297
11294
|
|
|
11298
11295
|
// ../data/src/domains/game/table.ts
|
|
11299
|
-
var gamePlatformEnum, gameTypeEnum, gameVisibilityEnum, games, gameSessions, gameStates, deploymentProviderEnum, deployJobStatusEnum, gameDeployments, gameDeployJobs, customHostnameStatusEnum, customHostnameSslStatusEnum, customHostnameEnvironmentEnum, gameCustomHostnames;
|
|
11296
|
+
var gamePlatformEnum, gameTypeEnum, gameVisibilityEnum, games, gameMemberRoleEnum, gameMembers, gameMembersRelations, gameSessions, gameStates, deploymentProviderEnum, deployJobStatusEnum, gameDeployments, gameDeployJobs, customHostnameStatusEnum, customHostnameSslStatusEnum, customHostnameEnvironmentEnum, gameCustomHostnames;
|
|
11300
11297
|
var init_table3 = __esm(() => {
|
|
11298
|
+
init_drizzle_orm();
|
|
11301
11299
|
init_pg_core();
|
|
11302
11300
|
init_table5();
|
|
11303
11301
|
init_table6();
|
|
@@ -11306,9 +11304,6 @@ var init_table3 = __esm(() => {
|
|
|
11306
11304
|
gameVisibilityEnum = pgEnum("game_visibility", ["visible", "unlisted", "internal"]);
|
|
11307
11305
|
games = pgTable("games", {
|
|
11308
11306
|
id: uuid("id").primaryKey().defaultRandom(),
|
|
11309
|
-
developerId: text("developer_id").references(() => users.id, {
|
|
11310
|
-
onDelete: "set null"
|
|
11311
|
-
}),
|
|
11312
11307
|
slug: varchar("slug", { length: 255 }).notNull().unique(),
|
|
11313
11308
|
displayName: varchar("display_name", { length: 255 }).notNull(),
|
|
11314
11309
|
version: varchar("version", { length: 50 }).notNull(),
|
|
@@ -11324,6 +11319,24 @@ var init_table3 = __esm(() => {
|
|
|
11324
11319
|
createdAt: timestamp("created_at", { withTimezone: true }).defaultNow(),
|
|
11325
11320
|
updatedAt: timestamp("updated_at", { withTimezone: true }).defaultNow()
|
|
11326
11321
|
});
|
|
11322
|
+
gameMemberRoleEnum = pgEnum("game_member_role", ["owner", "collaborator"]);
|
|
11323
|
+
gameMembers = pgTable("game_members", {
|
|
11324
|
+
id: uuid("id").primaryKey().defaultRandom(),
|
|
11325
|
+
gameId: uuid("game_id").notNull().references(() => games.id, { onDelete: "cascade" }),
|
|
11326
|
+
userId: text("user_id").notNull().references(() => users.id, { onDelete: "cascade" }),
|
|
11327
|
+
role: gameMemberRoleEnum("role").notNull().default("collaborator"),
|
|
11328
|
+
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow()
|
|
11329
|
+
}, (table3) => [uniqueIndex("game_members_game_user_idx").on(table3.gameId, table3.userId)]);
|
|
11330
|
+
gameMembersRelations = relations(gameMembers, ({ one }) => ({
|
|
11331
|
+
game: one(games, {
|
|
11332
|
+
fields: [gameMembers.gameId],
|
|
11333
|
+
references: [games.id]
|
|
11334
|
+
}),
|
|
11335
|
+
user: one(users, {
|
|
11336
|
+
fields: [gameMembers.userId],
|
|
11337
|
+
references: [users.id]
|
|
11338
|
+
})
|
|
11339
|
+
}));
|
|
11327
11340
|
gameSessions = pgTable("game_sessions", {
|
|
11328
11341
|
id: uuid("id").primaryKey().defaultRandom(),
|
|
11329
11342
|
userId: text("user_id").notNull().references(() => users.id, { onDelete: "cascade" }),
|
|
@@ -12141,6 +12154,9 @@ __export(exports_tables_index, {
|
|
|
12141
12154
|
gameScoresRelations: () => gameScoresRelations,
|
|
12142
12155
|
gameScores: () => gameScores,
|
|
12143
12156
|
gamePlatformEnum: () => gamePlatformEnum,
|
|
12157
|
+
gameMembersRelations: () => gameMembersRelations,
|
|
12158
|
+
gameMembers: () => gameMembers,
|
|
12159
|
+
gameMemberRoleEnum: () => gameMemberRoleEnum,
|
|
12144
12160
|
gameDeployments: () => gameDeployments,
|
|
12145
12161
|
gameDeployJobs: () => gameDeployJobs,
|
|
12146
12162
|
gameCustomHostnames: () => gameCustomHostnames,
|
|
@@ -26272,9 +26288,38 @@ var init_playcademy = __esm(() => {
|
|
|
26272
26288
|
init_infra();
|
|
26273
26289
|
});
|
|
26274
26290
|
|
|
26291
|
+
// ../utils/src/tunnel.ts
|
|
26292
|
+
async function getTunnelUrl() {
|
|
26293
|
+
let response;
|
|
26294
|
+
try {
|
|
26295
|
+
response = await fetch(`${METRICS_BASE}/config`);
|
|
26296
|
+
} catch {
|
|
26297
|
+
throw new Error("Local tunnel is not running. Start it with `bun dev` or `bun scripts/infra/tunnel.ts`.");
|
|
26298
|
+
}
|
|
26299
|
+
if (!response.ok) {
|
|
26300
|
+
throw new Error(`Tunnel metrics endpoint returned ${response.status}`);
|
|
26301
|
+
}
|
|
26302
|
+
const data = await response.json();
|
|
26303
|
+
const hostname = data.config.ingress.find((r) => r.hostname)?.hostname;
|
|
26304
|
+
if (!hostname) {
|
|
26305
|
+
throw new Error("Tunnel is running but no hostname found in ingress config");
|
|
26306
|
+
}
|
|
26307
|
+
return `https://${hostname}`;
|
|
26308
|
+
}
|
|
26309
|
+
var TUNNEL_METRICS_PORT = 20241, METRICS_BASE;
|
|
26310
|
+
var init_tunnel = __esm(() => {
|
|
26311
|
+
METRICS_BASE = `http://127.0.0.1:${TUNNEL_METRICS_PORT}`;
|
|
26312
|
+
});
|
|
26313
|
+
|
|
26275
26314
|
// ../api-core/src/utils/deployment.util.ts
|
|
26276
|
-
function getDeploymentId(gameSlug,
|
|
26277
|
-
|
|
26315
|
+
function getDeploymentId(gameSlug, sstStage) {
|
|
26316
|
+
if (sstStage === "production") {
|
|
26317
|
+
return gameSlug;
|
|
26318
|
+
}
|
|
26319
|
+
if (sstStage === "dev") {
|
|
26320
|
+
return `${WORKER_NAMING.STAGING_PREFIX}${gameSlug}`;
|
|
26321
|
+
}
|
|
26322
|
+
return `${WORKER_NAMING.LOCAL_PREFIX}${sstStage}-${gameSlug}`;
|
|
26278
26323
|
}
|
|
26279
26324
|
function getGameWorkerApiKeyName(slug) {
|
|
26280
26325
|
return `game-worker-${slug}`.substring(0, 32);
|
|
@@ -26499,8 +26544,7 @@ class DeployService {
|
|
|
26499
26544
|
const game = await this.deps.validateDeveloperAccessBySlug(user, slug);
|
|
26500
26545
|
const flags2 = this.validateDeployRequest(request, slug);
|
|
26501
26546
|
const { hasBackend, hasFrontend } = flags2;
|
|
26502
|
-
const
|
|
26503
|
-
const deploymentId = getDeploymentId(slug, isProd);
|
|
26547
|
+
const deploymentId = getDeploymentId(slug, this.deps.config.sstStage);
|
|
26504
26548
|
let frontendAssetsPath;
|
|
26505
26549
|
let tempDir;
|
|
26506
26550
|
if (hasFrontend) {
|
|
@@ -26514,7 +26558,15 @@ class DeployService {
|
|
|
26514
26558
|
frontendAssetsPath = extracted.assetsPath;
|
|
26515
26559
|
yield { type: "status", data: { message: "Extracting assets" } };
|
|
26516
26560
|
}
|
|
26517
|
-
|
|
26561
|
+
let platformBaseUrl = this.deps.config.baseUrl;
|
|
26562
|
+
if (this.deps.config.isLocal) {
|
|
26563
|
+
try {
|
|
26564
|
+
platformBaseUrl = await getTunnelUrl();
|
|
26565
|
+
} catch {
|
|
26566
|
+
throw new ValidationError("Local tunnel is not running. Ensure cloudflared is installed (`brew install cloudflared`) and the tunnel DevCommand started successfully.");
|
|
26567
|
+
}
|
|
26568
|
+
}
|
|
26569
|
+
const env = { GAME_ID: game.id, PLAYCADEMY_BASE_URL: platformBaseUrl };
|
|
26518
26570
|
yield {
|
|
26519
26571
|
type: "status",
|
|
26520
26572
|
data: { message: hasBackend ? "Deploying backend code" : "Deploying to platform" }
|
|
@@ -26563,14 +26615,14 @@ class DeployService {
|
|
|
26563
26615
|
return;
|
|
26564
26616
|
}
|
|
26565
26617
|
const workerBindings = {};
|
|
26566
|
-
if (bindings?.database
|
|
26567
|
-
workerBindings.d1 =
|
|
26618
|
+
if (bindings?.database) {
|
|
26619
|
+
workerBindings.d1 = [deploymentId];
|
|
26568
26620
|
}
|
|
26569
|
-
if (bindings?.keyValue
|
|
26570
|
-
workerBindings.kv =
|
|
26621
|
+
if (bindings?.keyValue) {
|
|
26622
|
+
workerBindings.kv = [deploymentId];
|
|
26571
26623
|
}
|
|
26572
|
-
if (bindings?.bucket
|
|
26573
|
-
workerBindings.r2 =
|
|
26624
|
+
if (bindings?.bucket) {
|
|
26625
|
+
workerBindings.r2 = [deploymentId];
|
|
26574
26626
|
}
|
|
26575
26627
|
if (bindings?.queues) {
|
|
26576
26628
|
let toQueueName = function(queueKey) {
|
|
@@ -26637,7 +26689,7 @@ var init_deploy_service = __esm(() => {
|
|
|
26637
26689
|
init_src();
|
|
26638
26690
|
init_tables_index();
|
|
26639
26691
|
init_src2();
|
|
26640
|
-
|
|
26692
|
+
init_tunnel();
|
|
26641
26693
|
init_errors();
|
|
26642
26694
|
init_deployment_util();
|
|
26643
26695
|
logger3 = log.scope("DeployService");
|
|
@@ -26775,29 +26827,33 @@ var init_game_service = __esm(() => {
|
|
|
26775
26827
|
const db2 = this.deps.db;
|
|
26776
26828
|
const isAdmin = caller?.role === "admin";
|
|
26777
26829
|
const isDeveloper = caller?.role === "developer";
|
|
26778
|
-
let whereClause;
|
|
26779
26830
|
if (isAdmin) {
|
|
26780
|
-
|
|
26781
|
-
|
|
26782
|
-
|
|
26783
|
-
}
|
|
26784
|
-
|
|
26831
|
+
return db2.query.games.findMany({
|
|
26832
|
+
orderBy: [desc(games.createdAt)]
|
|
26833
|
+
});
|
|
26834
|
+
}
|
|
26835
|
+
if (isDeveloper && caller?.id) {
|
|
26836
|
+
const rows = await db2.select().from(games).where(or(ne(games.visibility, "internal"), exists(db2.select({ one: sql`1` }).from(gameMembers).where(and(eq(gameMembers.gameId, games.id), eq(gameMembers.userId, caller.id)))))).orderBy(desc(games.createdAt));
|
|
26837
|
+
return rows;
|
|
26785
26838
|
}
|
|
26786
26839
|
return db2.query.games.findMany({
|
|
26787
|
-
where:
|
|
26840
|
+
where: ne(games.visibility, "internal"),
|
|
26788
26841
|
orderBy: [desc(games.createdAt)]
|
|
26789
26842
|
});
|
|
26790
26843
|
}
|
|
26791
|
-
async
|
|
26844
|
+
async listAccessible(user) {
|
|
26792
26845
|
const seesAllGames = user.role === "admin" || user.role === "teacher";
|
|
26793
26846
|
if (!seesAllGames) {
|
|
26794
26847
|
this.validateDeveloperStatus(user);
|
|
26795
26848
|
}
|
|
26796
26849
|
const db2 = this.deps.db;
|
|
26797
|
-
|
|
26798
|
-
|
|
26799
|
-
|
|
26800
|
-
|
|
26850
|
+
if (seesAllGames) {
|
|
26851
|
+
return db2.query.games.findMany({
|
|
26852
|
+
orderBy: [desc(games.createdAt)]
|
|
26853
|
+
});
|
|
26854
|
+
}
|
|
26855
|
+
const rows = await db2.select({ games }).from(games).innerJoin(gameMembers, eq(gameMembers.gameId, games.id)).where(eq(gameMembers.userId, user.id)).orderBy(desc(games.createdAt));
|
|
26856
|
+
return rows.map((r) => r.games);
|
|
26801
26857
|
}
|
|
26802
26858
|
async getSubjects() {
|
|
26803
26859
|
const db2 = this.deps.db;
|
|
@@ -26821,7 +26877,7 @@ var init_game_service = __esm(() => {
|
|
|
26821
26877
|
if (!game) {
|
|
26822
26878
|
throw new NotFoundError("Game", gameId);
|
|
26823
26879
|
}
|
|
26824
|
-
this.enforceVisibility(game, caller, gameId);
|
|
26880
|
+
await this.enforceVisibility(game, caller, gameId);
|
|
26825
26881
|
return game;
|
|
26826
26882
|
}
|
|
26827
26883
|
async getBySlug(slug, caller) {
|
|
@@ -26832,7 +26888,7 @@ var init_game_service = __esm(() => {
|
|
|
26832
26888
|
if (!game) {
|
|
26833
26889
|
throw new NotFoundError("Game", slug);
|
|
26834
26890
|
}
|
|
26835
|
-
this.enforceVisibility(game, caller, slug);
|
|
26891
|
+
await this.enforceVisibility(game, caller, slug);
|
|
26836
26892
|
return game;
|
|
26837
26893
|
}
|
|
26838
26894
|
async getManifest(gameId, caller) {
|
|
@@ -26998,15 +27054,20 @@ var init_game_service = __esm(() => {
|
|
|
26998
27054
|
};
|
|
26999
27055
|
}
|
|
27000
27056
|
}
|
|
27001
|
-
enforceVisibility(game, caller, lookupIdentifier) {
|
|
27057
|
+
async enforceVisibility(game, caller, lookupIdentifier) {
|
|
27002
27058
|
if (game.visibility !== "internal") {
|
|
27003
27059
|
return;
|
|
27004
27060
|
}
|
|
27005
|
-
|
|
27006
|
-
const isOwner = caller?.id != null && caller.id === game.developerId;
|
|
27007
|
-
if (!isAdmin && !isOwner) {
|
|
27061
|
+
if (!caller) {
|
|
27008
27062
|
throw new NotFoundError("Game", lookupIdentifier);
|
|
27009
27063
|
}
|
|
27064
|
+
if (caller.role === "admin") {
|
|
27065
|
+
return;
|
|
27066
|
+
}
|
|
27067
|
+
if (await this.hasGameMembership(game.id, caller.id)) {
|
|
27068
|
+
return;
|
|
27069
|
+
}
|
|
27070
|
+
throw new NotFoundError("Game", lookupIdentifier);
|
|
27010
27071
|
}
|
|
27011
27072
|
async upsertBySlug(slug, data, user) {
|
|
27012
27073
|
const db2 = this.deps.db;
|
|
@@ -27043,17 +27104,24 @@ var init_game_service = __esm(() => {
|
|
|
27043
27104
|
...gameDataForDb,
|
|
27044
27105
|
id: gameId,
|
|
27045
27106
|
slug,
|
|
27046
|
-
developerId: user.id,
|
|
27047
27107
|
metadata: data.metadata || {},
|
|
27048
27108
|
version: data.gameType === "external" ? "external" : "",
|
|
27049
27109
|
deploymentUrl: null,
|
|
27050
27110
|
createdAt: new Date
|
|
27051
27111
|
};
|
|
27052
|
-
const
|
|
27053
|
-
|
|
27054
|
-
|
|
27055
|
-
|
|
27056
|
-
|
|
27112
|
+
const createdGame = await db2.transaction(async (tx) => {
|
|
27113
|
+
const [game] = await tx.insert(games).values(insertData).returning();
|
|
27114
|
+
if (!game) {
|
|
27115
|
+
logger5.error("Game insert returned no rows", { slug, userId: user.id });
|
|
27116
|
+
throw new InternalError("DB insert failed to return result for new game");
|
|
27117
|
+
}
|
|
27118
|
+
await tx.insert(gameMembers).values({
|
|
27119
|
+
gameId: game.id,
|
|
27120
|
+
userId: user.id,
|
|
27121
|
+
role: "owner"
|
|
27122
|
+
});
|
|
27123
|
+
return game;
|
|
27124
|
+
});
|
|
27057
27125
|
gameResponse = createdGame;
|
|
27058
27126
|
}
|
|
27059
27127
|
if (data.mapElementId) {
|
|
@@ -27151,51 +27219,43 @@ var init_game_service = __esm(() => {
|
|
|
27151
27219
|
displayName: gameToDelete.displayName
|
|
27152
27220
|
};
|
|
27153
27221
|
}
|
|
27222
|
+
async hasGameMembership(gameId, userId) {
|
|
27223
|
+
const membership = await this.deps.db.query.gameMembers.findFirst({
|
|
27224
|
+
where: and(eq(gameMembers.gameId, gameId), eq(gameMembers.userId, userId)),
|
|
27225
|
+
columns: { id: true }
|
|
27226
|
+
});
|
|
27227
|
+
return Boolean(membership);
|
|
27228
|
+
}
|
|
27154
27229
|
async validateOwnership(user, gameId) {
|
|
27155
|
-
if (user.role === "admin") {
|
|
27156
|
-
const gameExists = await this.deps.db.query.games.findFirst({
|
|
27157
|
-
where: eq(games.id, gameId),
|
|
27158
|
-
columns: { id: true }
|
|
27159
|
-
});
|
|
27160
|
-
if (!gameExists) {
|
|
27161
|
-
throw new NotFoundError("Game", gameId);
|
|
27162
|
-
}
|
|
27163
|
-
return;
|
|
27164
|
-
}
|
|
27165
27230
|
const db2 = this.deps.db;
|
|
27166
|
-
const
|
|
27167
|
-
where:
|
|
27231
|
+
const gameExists = await db2.query.games.findFirst({
|
|
27232
|
+
where: eq(games.id, gameId),
|
|
27168
27233
|
columns: { id: true }
|
|
27169
27234
|
});
|
|
27170
|
-
if (!
|
|
27171
|
-
|
|
27172
|
-
|
|
27173
|
-
|
|
27174
|
-
|
|
27175
|
-
|
|
27176
|
-
|
|
27177
|
-
}
|
|
27235
|
+
if (!gameExists) {
|
|
27236
|
+
throw new NotFoundError("Game", gameId);
|
|
27237
|
+
}
|
|
27238
|
+
if (user.role === "admin") {
|
|
27239
|
+
return;
|
|
27240
|
+
}
|
|
27241
|
+
if (!await this.hasGameMembership(gameId, user.id)) {
|
|
27178
27242
|
throw new AccessDeniedError("You do not own this game");
|
|
27179
27243
|
}
|
|
27180
27244
|
}
|
|
27181
27245
|
async validateDeveloperAccess(user, gameId) {
|
|
27182
27246
|
this.validateDeveloperStatus(user);
|
|
27183
|
-
if (user.role === "admin") {
|
|
27184
|
-
const gameExists = await this.deps.db.query.games.findFirst({
|
|
27185
|
-
where: eq(games.id, gameId),
|
|
27186
|
-
columns: { id: true }
|
|
27187
|
-
});
|
|
27188
|
-
if (!gameExists) {
|
|
27189
|
-
throw new NotFoundError("Game", gameId);
|
|
27190
|
-
}
|
|
27191
|
-
return;
|
|
27192
|
-
}
|
|
27193
27247
|
const db2 = this.deps.db;
|
|
27194
|
-
const
|
|
27195
|
-
where:
|
|
27248
|
+
const gameExists = await db2.query.games.findFirst({
|
|
27249
|
+
where: eq(games.id, gameId),
|
|
27196
27250
|
columns: { id: true }
|
|
27197
27251
|
});
|
|
27198
|
-
if (!
|
|
27252
|
+
if (!gameExists) {
|
|
27253
|
+
throw new NotFoundError("Game", gameId);
|
|
27254
|
+
}
|
|
27255
|
+
if (user.role === "admin") {
|
|
27256
|
+
return;
|
|
27257
|
+
}
|
|
27258
|
+
if (!await this.hasGameMembership(gameId, user.id)) {
|
|
27199
27259
|
throw new NotFoundError("Game", gameId);
|
|
27200
27260
|
}
|
|
27201
27261
|
}
|
|
@@ -27215,21 +27275,18 @@ var init_game_service = __esm(() => {
|
|
|
27215
27275
|
async validateDeveloperAccessBySlug(user, slug) {
|
|
27216
27276
|
this.validateDeveloperStatus(user);
|
|
27217
27277
|
const db2 = this.deps.db;
|
|
27218
|
-
if (user.role === "admin") {
|
|
27219
|
-
const game2 = await db2.query.games.findFirst({
|
|
27220
|
-
where: eq(games.slug, slug)
|
|
27221
|
-
});
|
|
27222
|
-
if (!game2) {
|
|
27223
|
-
throw new NotFoundError("Game", slug);
|
|
27224
|
-
}
|
|
27225
|
-
return game2;
|
|
27226
|
-
}
|
|
27227
27278
|
const game = await db2.query.games.findFirst({
|
|
27228
|
-
where:
|
|
27279
|
+
where: eq(games.slug, slug)
|
|
27229
27280
|
});
|
|
27230
27281
|
if (!game) {
|
|
27231
27282
|
throw new NotFoundError("Game", slug);
|
|
27232
27283
|
}
|
|
27284
|
+
if (user.role === "admin") {
|
|
27285
|
+
return game;
|
|
27286
|
+
}
|
|
27287
|
+
if (!await this.hasGameMembership(game.id, user.id)) {
|
|
27288
|
+
throw new NotFoundError("Game", slug);
|
|
27289
|
+
}
|
|
27233
27290
|
return game;
|
|
27234
27291
|
}
|
|
27235
27292
|
validateDeveloperStatus(user) {
|
|
@@ -28192,8 +28249,7 @@ class DatabaseService {
|
|
|
28192
28249
|
async reset(slug, user, schema2) {
|
|
28193
28250
|
const d1 = this.getD1();
|
|
28194
28251
|
const game = await this.deps.validateDeveloperAccessBySlug(user, slug);
|
|
28195
|
-
const
|
|
28196
|
-
const deploymentId = getDeploymentId(slug, isProd);
|
|
28252
|
+
const deploymentId = getDeploymentId(slug, this.deps.config.sstStage);
|
|
28197
28253
|
logger9.debug("Resetting database", {
|
|
28198
28254
|
userId: user.id,
|
|
28199
28255
|
gameId: game.id,
|
|
@@ -28269,7 +28325,6 @@ var init_database_service = __esm(() => {
|
|
|
28269
28325
|
init_drizzle_orm();
|
|
28270
28326
|
init_tables_index();
|
|
28271
28327
|
init_src2();
|
|
28272
|
-
init_config2();
|
|
28273
28328
|
init_errors();
|
|
28274
28329
|
init_deployment_util();
|
|
28275
28330
|
logger9 = log.scope("DatabaseService");
|
|
@@ -28788,8 +28843,7 @@ class SecretsService {
|
|
|
28788
28843
|
return this.deps.cloudflare;
|
|
28789
28844
|
}
|
|
28790
28845
|
getDeploymentId(slug) {
|
|
28791
|
-
|
|
28792
|
-
return getDeploymentId(slug, isProd);
|
|
28846
|
+
return getDeploymentId(slug, this.deps.config.sstStage);
|
|
28793
28847
|
}
|
|
28794
28848
|
async listKeys(slug, user) {
|
|
28795
28849
|
const game = await this.deps.validateDeveloperAccessBySlug(user, slug);
|
|
@@ -28917,7 +28971,6 @@ var logger13, INTERNAL_SECRET_KEYS;
|
|
|
28917
28971
|
var init_secrets_service = __esm(() => {
|
|
28918
28972
|
init_src();
|
|
28919
28973
|
init_src2();
|
|
28920
|
-
init_config2();
|
|
28921
28974
|
init_errors();
|
|
28922
28975
|
init_deployment_util();
|
|
28923
28976
|
logger13 = log.scope("SecretsService");
|
|
@@ -28969,8 +29022,7 @@ class SeedService {
|
|
|
28969
29022
|
async seed(slug, code, user, secrets) {
|
|
28970
29023
|
const cf = this.getCloudflare();
|
|
28971
29024
|
const game = await this.deps.validateDeveloperAccessBySlug(user, slug);
|
|
28972
|
-
const
|
|
28973
|
-
const deploymentId = getDeploymentId(slug, isProd);
|
|
29025
|
+
const deploymentId = getDeploymentId(slug, this.deps.config.sstStage);
|
|
28974
29026
|
const uniqueSuffix = Date.now().toString(36);
|
|
28975
29027
|
const seedDeploymentId = `seed-${deploymentId}-${uniqueSuffix}`;
|
|
28976
29028
|
logger14.debug("Seeding database", {
|
|
@@ -29209,7 +29261,6 @@ var init_seed_service = __esm(() => {
|
|
|
29209
29261
|
init_src();
|
|
29210
29262
|
init_setup2();
|
|
29211
29263
|
init_src2();
|
|
29212
|
-
init_config2();
|
|
29213
29264
|
init_errors();
|
|
29214
29265
|
init_deployment_util();
|
|
29215
29266
|
logger14 = log.scope("SeedService");
|
|
@@ -34860,7 +34911,7 @@ class LogsService {
|
|
|
34860
34911
|
constructor(deps) {
|
|
34861
34912
|
this.deps = deps;
|
|
34862
34913
|
}
|
|
34863
|
-
async generateToken(user, slug2,
|
|
34914
|
+
async generateToken(user, slug2, sstStage) {
|
|
34864
34915
|
const db2 = this.deps.db;
|
|
34865
34916
|
if (user.role === "admin") {
|
|
34866
34917
|
const game = await db2.query.games.findFirst({
|
|
@@ -34870,7 +34921,7 @@ class LogsService {
|
|
|
34870
34921
|
if (!game) {
|
|
34871
34922
|
throw new NotFoundError("Game", slug2);
|
|
34872
34923
|
}
|
|
34873
|
-
logger28.info("Admin accessing game logs", { adminId: user.id, slug: slug2,
|
|
34924
|
+
logger28.info("Admin accessing game logs", { adminId: user.id, slug: slug2, sstStage });
|
|
34874
34925
|
} else {
|
|
34875
34926
|
const isApprovedDev = user.developerStatus === "approved";
|
|
34876
34927
|
if (!isApprovedDev) {
|
|
@@ -34878,10 +34929,17 @@ class LogsService {
|
|
|
34878
34929
|
throw new AccessDeniedError("Must be an approved developer");
|
|
34879
34930
|
}
|
|
34880
34931
|
const game = await db2.query.games.findFirst({
|
|
34881
|
-
where:
|
|
34932
|
+
where: eq(games.slug, slug2),
|
|
34882
34933
|
columns: { id: true }
|
|
34883
34934
|
});
|
|
34884
34935
|
if (!game) {
|
|
34936
|
+
throw new NotFoundError("Game", slug2);
|
|
34937
|
+
}
|
|
34938
|
+
const membership = await db2.query.gameMembers.findFirst({
|
|
34939
|
+
where: and(eq(gameMembers.gameId, game.id), eq(gameMembers.userId, user.id)),
|
|
34940
|
+
columns: { id: true }
|
|
34941
|
+
});
|
|
34942
|
+
if (!membership) {
|
|
34885
34943
|
logger28.warn("Developer attempted access to unowned game logs", {
|
|
34886
34944
|
userId: user.id,
|
|
34887
34945
|
slug: slug2
|
|
@@ -34889,8 +34947,7 @@ class LogsService {
|
|
|
34889
34947
|
throw new NotFoundError("Game", slug2);
|
|
34890
34948
|
}
|
|
34891
34949
|
}
|
|
34892
|
-
const
|
|
34893
|
-
const workerId = getDeploymentId(slug2, isProduction3);
|
|
34950
|
+
const workerId = getDeploymentId(slug2, sstStage);
|
|
34894
34951
|
const token = await this.deps.mintLogStreamToken(user.id, workerId);
|
|
34895
34952
|
logger28.debug("Generated log stream token", {
|
|
34896
34953
|
userId: user.id,
|
|
@@ -35636,7 +35693,7 @@ function createServices(ctx) {
|
|
|
35636
35693
|
discord,
|
|
35637
35694
|
cloudflare,
|
|
35638
35695
|
storage,
|
|
35639
|
-
stage: config2.
|
|
35696
|
+
stage: config2.sstStage
|
|
35640
35697
|
});
|
|
35641
35698
|
const player = createPlayerServices({
|
|
35642
35699
|
db: db2,
|
|
@@ -38870,7 +38927,7 @@ var init_providers = __esm(() => {
|
|
|
38870
38927
|
function buildConfig(options) {
|
|
38871
38928
|
const baseUrl = `http://localhost:${options.port ?? 3000}`;
|
|
38872
38929
|
return createMinimalConfig({
|
|
38873
|
-
|
|
38930
|
+
sstStage: "sandbox",
|
|
38874
38931
|
baseUrl,
|
|
38875
38932
|
gameDomain: "localhost",
|
|
38876
38933
|
uploadBucket: "sandbox-uploads",
|
|
@@ -38907,7 +38964,7 @@ function createSandboxContext(options) {
|
|
|
38907
38964
|
Object.assign(services, createServices(ctx));
|
|
38908
38965
|
cachedServiceContext = ctx;
|
|
38909
38966
|
log.debug("[Sandbox] ServiceContext initialized", {
|
|
38910
|
-
|
|
38967
|
+
sstStage: config2.sstStage,
|
|
38911
38968
|
baseUrl: config2.baseUrl,
|
|
38912
38969
|
hasTimeback: Boolean(timeback2)
|
|
38913
38970
|
});
|
|
@@ -93762,7 +93819,6 @@ async function seedCoreGames(db2) {
|
|
|
93762
93819
|
const coreGames = [
|
|
93763
93820
|
{
|
|
93764
93821
|
id: CORE_GAME_UUIDS.PLAYGROUND,
|
|
93765
|
-
developerId: DEMO_USERS.developer.id,
|
|
93766
93822
|
slug: "playground",
|
|
93767
93823
|
displayName: "Playground",
|
|
93768
93824
|
version: "local",
|
|
@@ -93779,6 +93835,11 @@ async function seedCoreGames(db2) {
|
|
|
93779
93835
|
for (const gameData of coreGames) {
|
|
93780
93836
|
try {
|
|
93781
93837
|
await db2.insert(games).values(gameData).onConflictDoNothing();
|
|
93838
|
+
await db2.insert(gameMembers).values({
|
|
93839
|
+
gameId: gameData.id,
|
|
93840
|
+
userId: DEMO_USERS.developer.id,
|
|
93841
|
+
role: "owner"
|
|
93842
|
+
}).onConflictDoNothing();
|
|
93782
93843
|
} catch (error2) {
|
|
93783
93844
|
logger37.error(`Error seeding core game '${gameData.slug}': ${error2}`);
|
|
93784
93845
|
}
|
|
@@ -93803,7 +93864,6 @@ async function seedCurrentProjectGame(db2, project) {
|
|
|
93803
93864
|
}
|
|
93804
93865
|
const gameRecord = {
|
|
93805
93866
|
id: desiredGameId ?? crypto.randomUUID(),
|
|
93806
|
-
developerId: DEMO_USERS.developer.id,
|
|
93807
93867
|
slug: project.slug,
|
|
93808
93868
|
displayName: project.displayName,
|
|
93809
93869
|
version: project.version,
|
|
@@ -93821,6 +93881,11 @@ async function seedCurrentProjectGame(db2, project) {
|
|
|
93821
93881
|
if (!newGame) {
|
|
93822
93882
|
throw new Error("Failed to create game record");
|
|
93823
93883
|
}
|
|
93884
|
+
await db2.insert(gameMembers).values({
|
|
93885
|
+
gameId: newGame.id,
|
|
93886
|
+
userId: DEMO_USERS.developer.id,
|
|
93887
|
+
role: "owner"
|
|
93888
|
+
}).onConflictDoNothing();
|
|
93824
93889
|
if (project.timebackCourses && project.timebackCourses.length > 0) {
|
|
93825
93890
|
await seedTimebackIntegrations(db2, newGame.id, project.timebackCourses);
|
|
93826
93891
|
}
|
|
@@ -94809,7 +94874,6 @@ var init_schemas2 = __esm(() => {
|
|
|
94809
94874
|
id: true,
|
|
94810
94875
|
slug: true,
|
|
94811
94876
|
createdAt: true,
|
|
94812
|
-
developerId: true,
|
|
94813
94877
|
version: true
|
|
94814
94878
|
}).refine((data) => {
|
|
94815
94879
|
if (data.gameType === "hosted" && data.deploymentUrl === null) {
|
|
@@ -94902,9 +94966,9 @@ var init_schemas2 = __esm(() => {
|
|
|
94902
94966
|
compatibilityDate: exports_external.string().optional(),
|
|
94903
94967
|
compatibilityFlags: exports_external.array(exports_external.string()).optional(),
|
|
94904
94968
|
bindings: exports_external.object({
|
|
94905
|
-
database: exports_external.array(exports_external.string()).optional(),
|
|
94906
|
-
keyValue: exports_external.array(exports_external.string()).optional(),
|
|
94907
|
-
bucket: exports_external.array(exports_external.string()).optional(),
|
|
94969
|
+
database: exports_external.union([exports_external.literal(true), exports_external.array(exports_external.string())]).optional(),
|
|
94970
|
+
keyValue: exports_external.union([exports_external.literal(true), exports_external.array(exports_external.string())]).optional(),
|
|
94971
|
+
bucket: exports_external.union([exports_external.literal(true), exports_external.array(exports_external.string())]).optional(),
|
|
94908
94972
|
queues: exports_external.record(exports_external.string(), exports_external.union([
|
|
94909
94973
|
exports_external.literal(true),
|
|
94910
94974
|
exports_external.object({
|
|
@@ -96211,7 +96275,7 @@ var init_domain_controller = __esm(() => {
|
|
|
96211
96275
|
});
|
|
96212
96276
|
|
|
96213
96277
|
// ../api-core/src/controllers/game.controller.ts
|
|
96214
|
-
var logger48, list3,
|
|
96278
|
+
var logger48, list3, listAccessible, getSubjects, getById2, getBySlug, getManifest, upsertBySlug, remove3, games2;
|
|
96215
96279
|
var init_game_controller = __esm(() => {
|
|
96216
96280
|
init_esm();
|
|
96217
96281
|
init_schemas_index();
|
|
@@ -96224,9 +96288,9 @@ var init_game_controller = __esm(() => {
|
|
|
96224
96288
|
logger48.debug("Listing games", { userId: ctx.user.id });
|
|
96225
96289
|
return ctx.services.game.list(ctx.user);
|
|
96226
96290
|
});
|
|
96227
|
-
|
|
96228
|
-
logger48.debug("Listing
|
|
96229
|
-
return ctx.services.game.
|
|
96291
|
+
listAccessible = requireNonAnonymous(async (ctx) => {
|
|
96292
|
+
logger48.debug("Listing accessible games", { userId: ctx.user.id });
|
|
96293
|
+
return ctx.services.game.listAccessible(ctx.user);
|
|
96230
96294
|
});
|
|
96231
96295
|
getSubjects = requireNonAnonymous(async (ctx) => {
|
|
96232
96296
|
logger48.debug("Getting game subjects", { userId: ctx.user.id });
|
|
@@ -96299,7 +96363,7 @@ var init_game_controller = __esm(() => {
|
|
|
96299
96363
|
});
|
|
96300
96364
|
games2 = {
|
|
96301
96365
|
list: list3,
|
|
96302
|
-
|
|
96366
|
+
listAccessible,
|
|
96303
96367
|
getSubjects,
|
|
96304
96368
|
getById: getById2,
|
|
96305
96369
|
getManifest,
|
|
@@ -96875,8 +96939,8 @@ var init_logs_controller = __esm(() => {
|
|
|
96875
96939
|
let body2;
|
|
96876
96940
|
try {
|
|
96877
96941
|
const json4 = await ctx.request.json();
|
|
96878
|
-
if (json4.environment !== "staging" && json4.environment !== "production") {
|
|
96879
|
-
throw ApiError.badRequest('Invalid environment. Must be "staging" or "production".');
|
|
96942
|
+
if (json4.environment !== "local" && json4.environment !== "staging" && json4.environment !== "production") {
|
|
96943
|
+
throw ApiError.badRequest('Invalid environment. Must be "local", "staging", or "production".');
|
|
96880
96944
|
}
|
|
96881
96945
|
body2 = json4;
|
|
96882
96946
|
} catch (error2) {
|
|
@@ -96890,7 +96954,13 @@ var init_logs_controller = __esm(() => {
|
|
|
96890
96954
|
slug: slug2,
|
|
96891
96955
|
environment: body2.environment
|
|
96892
96956
|
});
|
|
96893
|
-
|
|
96957
|
+
const envToSstStage = {
|
|
96958
|
+
local: ctx.config.sstStage,
|
|
96959
|
+
staging: "dev",
|
|
96960
|
+
production: "production"
|
|
96961
|
+
};
|
|
96962
|
+
const sstStage = envToSstStage[body2.environment] ?? "dev";
|
|
96963
|
+
return ctx.services.logs.generateToken(ctx.user, slug2, sstStage);
|
|
96894
96964
|
});
|
|
96895
96965
|
logs = {
|
|
96896
96966
|
generateToken
|
|
@@ -97242,6 +97312,7 @@ var init_seed_controller = __esm(() => {
|
|
|
97242
97312
|
var logger61, start2, end, mintToken, sessions2;
|
|
97243
97313
|
var init_session_controller = __esm(() => {
|
|
97244
97314
|
init_src2();
|
|
97315
|
+
init_tunnel();
|
|
97245
97316
|
init_errors();
|
|
97246
97317
|
init_utils11();
|
|
97247
97318
|
logger61 = log.scope("SessionController");
|
|
@@ -97276,7 +97347,14 @@ var init_session_controller = __esm(() => {
|
|
|
97276
97347
|
throw ApiError.badRequest("Missing game ID or slug");
|
|
97277
97348
|
}
|
|
97278
97349
|
logger61.debug("Minting token", { userId: ctx.user.id, gameIdOrSlug, launchId: ctx.launchId });
|
|
97279
|
-
|
|
97350
|
+
const { token, exp } = await ctx.services.session.mintToken(gameIdOrSlug, ctx.user.id);
|
|
97351
|
+
let baseUrl;
|
|
97352
|
+
if (ctx.config.isLocal) {
|
|
97353
|
+
try {
|
|
97354
|
+
baseUrl = await getTunnelUrl();
|
|
97355
|
+
} catch {}
|
|
97356
|
+
}
|
|
97357
|
+
return { token, exp, baseUrl };
|
|
97280
97358
|
});
|
|
97281
97359
|
sessions2 = {
|
|
97282
97360
|
start: start2,
|
|
@@ -98420,6 +98498,7 @@ var init_crud = __esm(() => {
|
|
|
98420
98498
|
init_api();
|
|
98421
98499
|
gameCrudRouter = new Hono2;
|
|
98422
98500
|
gameCrudRouter.get("/", handle2(games2.list));
|
|
98501
|
+
gameCrudRouter.get("/accessible", handle2(games2.listAccessible));
|
|
98423
98502
|
gameCrudRouter.get("/:gameId{[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}}/manifest", handle2(games2.getManifest));
|
|
98424
98503
|
gameCrudRouter.get("/:gameId{[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}}", handle2(games2.getById));
|
|
98425
98504
|
gameCrudRouter.get("/:slug", handle2(games2.getBySlug));
|
|
@@ -98562,10 +98641,12 @@ var init_deploy = __esm(() => {
|
|
|
98562
98641
|
const db2 = ctx.db;
|
|
98563
98642
|
const existingGames = await db2.select().from(games).where(eq(games.slug, slug2));
|
|
98564
98643
|
const existingGame = existingGames[0];
|
|
98565
|
-
if (existingGame) {
|
|
98566
|
-
const
|
|
98567
|
-
|
|
98568
|
-
|
|
98644
|
+
if (existingGame && user.role !== "admin") {
|
|
98645
|
+
const membership = await db2.query.gameMembers.findFirst({
|
|
98646
|
+
where: and(eq(gameMembers.gameId, existingGame.id), eq(gameMembers.userId, user.id)),
|
|
98647
|
+
columns: { id: true }
|
|
98648
|
+
});
|
|
98649
|
+
if (!membership) {
|
|
98569
98650
|
return c2.json({
|
|
98570
98651
|
error: {
|
|
98571
98652
|
code: "FORBIDDEN",
|
|
@@ -98589,17 +98670,24 @@ var init_deploy = __esm(() => {
|
|
|
98589
98670
|
if (!body2.metadata?.displayName) {
|
|
98590
98671
|
return c2.json({ error: { code: "BAD_REQUEST", message: "Display name required for new game" } }, 400);
|
|
98591
98672
|
}
|
|
98592
|
-
const
|
|
98673
|
+
const gameValues = {
|
|
98593
98674
|
slug: slug2,
|
|
98594
98675
|
displayName: body2.metadata.displayName,
|
|
98595
98676
|
version: "1.0.0",
|
|
98596
98677
|
platform: body2.metadata.platform ?? "web",
|
|
98597
98678
|
gameType: "hosted",
|
|
98598
|
-
developerId: user.id,
|
|
98599
98679
|
deploymentUrl: `http://localhost:4321`,
|
|
98600
98680
|
metadata: body2.metadata.metadata ?? {}
|
|
98601
|
-
}
|
|
98602
|
-
game =
|
|
98681
|
+
};
|
|
98682
|
+
game = await db2.transaction(async (tx) => {
|
|
98683
|
+
const [inserted] = await tx.insert(games).values(gameValues).returning();
|
|
98684
|
+
await tx.insert(gameMembers).values({
|
|
98685
|
+
gameId: inserted.id,
|
|
98686
|
+
userId: user.id,
|
|
98687
|
+
role: "owner"
|
|
98688
|
+
});
|
|
98689
|
+
return inserted;
|
|
98690
|
+
});
|
|
98603
98691
|
}
|
|
98604
98692
|
let backendCode = body2.code;
|
|
98605
98693
|
if (!backendCode && body2.codeUploadToken) {
|
|
@@ -98678,13 +98766,19 @@ var init_deploy = __esm(() => {
|
|
|
98678
98766
|
const ctx = getSandboxContext();
|
|
98679
98767
|
const game = await ctx.db.query.games.findFirst({
|
|
98680
98768
|
where: eq(games.slug, slug2 ?? ""),
|
|
98681
|
-
columns: { id: true
|
|
98769
|
+
columns: { id: true }
|
|
98682
98770
|
});
|
|
98683
98771
|
if (!game) {
|
|
98684
98772
|
return c2.json({ error: { code: "NOT_FOUND", message: "Game not found" } }, 404);
|
|
98685
98773
|
}
|
|
98686
|
-
if (
|
|
98687
|
-
|
|
98774
|
+
if (user.role !== "admin") {
|
|
98775
|
+
const membership = await ctx.db.query.gameMembers.findFirst({
|
|
98776
|
+
where: and(eq(gameMembers.gameId, game.id), eq(gameMembers.userId, user.id)),
|
|
98777
|
+
columns: { id: true }
|
|
98778
|
+
});
|
|
98779
|
+
if (!membership) {
|
|
98780
|
+
return c2.json({ error: { code: "NOT_FOUND", message: "Game not found" } }, 404);
|
|
98781
|
+
}
|
|
98688
98782
|
}
|
|
98689
98783
|
const job = await ctx.db.query.gameDeployJobs.findFirst({
|
|
98690
98784
|
where: and(eq(gameDeployJobs.id, jobId), eq(gameDeployJobs.gameId, game.id))
|