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