@playcademy/vite-plugin 0.2.23 → 0.2.24-beta.2
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/index.js +485 -397
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -24447,6 +24447,7 @@ var init_timeback2 = __esm(() => {
|
|
|
24447
24447
|
});
|
|
24448
24448
|
var WORKER_NAMING;
|
|
24449
24449
|
var SECRETS_PREFIX = "secrets_";
|
|
24450
|
+
var CLOUDFLARE_COMPATIBILITY_DATE = "2025-10-11";
|
|
24450
24451
|
var init_workers = __esm(() => {
|
|
24451
24452
|
WORKER_NAMING = {
|
|
24452
24453
|
STAGING_PREFIX: "staging-",
|
|
@@ -25334,7 +25335,7 @@ var package_default;
|
|
|
25334
25335
|
var init_package = __esm(() => {
|
|
25335
25336
|
package_default = {
|
|
25336
25337
|
name: "@playcademy/sandbox",
|
|
25337
|
-
version: "0.3.
|
|
25338
|
+
version: "0.3.17-beta.5",
|
|
25338
25339
|
description: "Local development server for Playcademy game development",
|
|
25339
25340
|
type: "module",
|
|
25340
25341
|
exports: {
|
|
@@ -47627,13 +47628,11 @@ var dedent;
|
|
|
47627
47628
|
var init_dedent = __esm(() => {
|
|
47628
47629
|
dedent = createDedent({});
|
|
47629
47630
|
});
|
|
47630
|
-
var DEFAULT_COMPATIBILITY_DATE;
|
|
47631
47631
|
var init_workers2 = __esm(() => {
|
|
47632
47632
|
init_dedent();
|
|
47633
47633
|
init_src2();
|
|
47634
47634
|
init_assets();
|
|
47635
47635
|
init_multipart();
|
|
47636
|
-
DEFAULT_COMPATIBILITY_DATE = new Date().toISOString().slice(0, 10);
|
|
47637
47636
|
});
|
|
47638
47637
|
var init_namespaces = __esm(() => {
|
|
47639
47638
|
init_d1();
|
|
@@ -47650,12 +47649,10 @@ var init_core = __esm(() => {
|
|
|
47650
47649
|
});
|
|
47651
47650
|
var CUSTOM_DOMAINS_KV_NAME = "cademy-custom-domains";
|
|
47652
47651
|
var QUEUE_NAME_PREFIX = "playcademy";
|
|
47653
|
-
var DEFAULT_COMPATIBILITY_DATE2;
|
|
47654
47652
|
var GAME_WORKER_DOMAIN_PRODUCTION;
|
|
47655
47653
|
var GAME_WORKER_DOMAIN_STAGING;
|
|
47656
47654
|
var init_constants2 = __esm(() => {
|
|
47657
47655
|
init_src();
|
|
47658
|
-
DEFAULT_COMPATIBILITY_DATE2 = new Date().toISOString().slice(0, 10);
|
|
47659
47656
|
GAME_WORKER_DOMAIN_PRODUCTION = GAME_WORKER_DOMAINS.production;
|
|
47660
47657
|
GAME_WORKER_DOMAIN_STAGING = GAME_WORKER_DOMAINS.staging;
|
|
47661
47658
|
});
|
|
@@ -50332,6 +50329,7 @@ class DeployService {
|
|
|
50332
50329
|
try {
|
|
50333
50330
|
result = await this.timeStep("Cloudflare deploy", () => cf.deploy(deploymentId, request.code, env, {
|
|
50334
50331
|
...deploymentOptions,
|
|
50332
|
+
compatibilityDate: request.compatibilityDate ?? CLOUDFLARE_COMPATIBILITY_DATE,
|
|
50335
50333
|
compatibilityFlags: request.compatibilityFlags,
|
|
50336
50334
|
existingResources: activeDeployment?.resources ?? undefined,
|
|
50337
50335
|
assetsPath: frontendAssetsPath,
|
|
@@ -50433,6 +50431,7 @@ var logger3;
|
|
|
50433
50431
|
var init_deploy_service = __esm(() => {
|
|
50434
50432
|
init_drizzle_orm();
|
|
50435
50433
|
init_playcademy();
|
|
50434
|
+
init_src();
|
|
50436
50435
|
init_tables_index();
|
|
50437
50436
|
init_src2();
|
|
50438
50437
|
init_config2();
|
|
@@ -50499,453 +50498,539 @@ var init_developer_service = __esm(() => {
|
|
|
50499
50498
|
init_errors();
|
|
50500
50499
|
logger4 = log.scope("DeveloperService");
|
|
50501
50500
|
});
|
|
50502
|
-
|
|
50503
|
-
|
|
50504
|
-
|
|
50505
|
-
static MANIFEST_FETCH_TIMEOUT_MS = 5000;
|
|
50506
|
-
static MAX_FETCH_ERROR_MESSAGE_LENGTH = 512;
|
|
50507
|
-
constructor(deps) {
|
|
50508
|
-
this.deps = deps;
|
|
50509
|
-
}
|
|
50510
|
-
static getManifestHost(manifestUrl) {
|
|
50511
|
-
try {
|
|
50512
|
-
return new URL(manifestUrl).host;
|
|
50513
|
-
} catch {
|
|
50514
|
-
return manifestUrl;
|
|
50515
|
-
}
|
|
50501
|
+
function sleep(ms) {
|
|
50502
|
+
if (ms <= 0) {
|
|
50503
|
+
return Promise.resolve();
|
|
50516
50504
|
}
|
|
50517
|
-
|
|
50518
|
-
|
|
50519
|
-
|
|
50520
|
-
|
|
50521
|
-
|
|
50522
|
-
|
|
50523
|
-
|
|
50524
|
-
|
|
50525
|
-
|
|
50505
|
+
return new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
50506
|
+
}
|
|
50507
|
+
var logger5;
|
|
50508
|
+
var inFlightManifestFetches;
|
|
50509
|
+
var GameService;
|
|
50510
|
+
var init_game_service = __esm(() => {
|
|
50511
|
+
init_drizzle_orm();
|
|
50512
|
+
init_tables_index();
|
|
50513
|
+
init_src2();
|
|
50514
|
+
init_errors();
|
|
50515
|
+
init_deployment_util();
|
|
50516
|
+
logger5 = log.scope("GameService");
|
|
50517
|
+
inFlightManifestFetches = new Map;
|
|
50518
|
+
GameService = class GameService2 {
|
|
50519
|
+
deps;
|
|
50520
|
+
static MANIFEST_FETCH_ATTEMPT_TIMEOUT_MS = 1e4;
|
|
50521
|
+
static MANIFEST_FETCH_MAX_RETRIES = 2;
|
|
50522
|
+
static MANIFEST_FETCH_RETRY_BACKOFF_MS = [250, 750];
|
|
50523
|
+
static MANIFEST_CACHE_TTL_SECONDS = 60;
|
|
50524
|
+
static MANIFEST_CACHE_KEY_PREFIX = "game:manifest";
|
|
50525
|
+
static MAX_FETCH_ERROR_MESSAGE_LENGTH = 512;
|
|
50526
|
+
constructor(deps) {
|
|
50527
|
+
this.deps = deps;
|
|
50528
|
+
}
|
|
50529
|
+
static getManifestHost(manifestUrl) {
|
|
50530
|
+
try {
|
|
50531
|
+
return new URL(manifestUrl).host;
|
|
50532
|
+
} catch {
|
|
50533
|
+
return manifestUrl;
|
|
50534
|
+
}
|
|
50526
50535
|
}
|
|
50527
|
-
|
|
50528
|
-
|
|
50529
|
-
|
|
50536
|
+
static getFetchErrorMessage(error) {
|
|
50537
|
+
let raw;
|
|
50538
|
+
if (error instanceof Error) {
|
|
50539
|
+
raw = error.message;
|
|
50540
|
+
} else if (typeof error === "string") {
|
|
50541
|
+
raw = error;
|
|
50542
|
+
}
|
|
50543
|
+
if (!raw) {
|
|
50544
|
+
return;
|
|
50545
|
+
}
|
|
50546
|
+
const normalized = raw.replace(/\s+/g, " ").trim();
|
|
50547
|
+
if (!normalized) {
|
|
50548
|
+
return;
|
|
50549
|
+
}
|
|
50550
|
+
return normalized.slice(0, GameService2.MAX_FETCH_ERROR_MESSAGE_LENGTH);
|
|
50530
50551
|
}
|
|
50531
|
-
|
|
50532
|
-
|
|
50533
|
-
static isRetryableStatus(status) {
|
|
50534
|
-
return status === 429 || status >= 500;
|
|
50535
|
-
}
|
|
50536
|
-
async list(caller) {
|
|
50537
|
-
const db2 = this.deps.db;
|
|
50538
|
-
const isAdmin = caller?.role === "admin";
|
|
50539
|
-
const isDeveloper = caller?.role === "developer";
|
|
50540
|
-
let whereClause;
|
|
50541
|
-
if (isAdmin) {
|
|
50542
|
-
whereClause = undefined;
|
|
50543
|
-
} else if (isDeveloper && caller?.id) {
|
|
50544
|
-
whereClause = or(ne(games.visibility, "internal"), eq(games.developerId, caller.id));
|
|
50545
|
-
} else {
|
|
50546
|
-
whereClause = ne(games.visibility, "internal");
|
|
50552
|
+
static isRetryableStatus(status) {
|
|
50553
|
+
return status === 429 || status >= 500;
|
|
50547
50554
|
}
|
|
50548
|
-
|
|
50549
|
-
|
|
50550
|
-
|
|
50551
|
-
|
|
50552
|
-
}
|
|
50553
|
-
async listManageable(user) {
|
|
50554
|
-
this.validateDeveloperStatus(user);
|
|
50555
|
-
const db2 = this.deps.db;
|
|
50556
|
-
return db2.query.games.findMany({
|
|
50557
|
-
where: user.role === "admin" ? undefined : eq(games.developerId, user.id),
|
|
50558
|
-
orderBy: [desc(games.createdAt)]
|
|
50559
|
-
});
|
|
50560
|
-
}
|
|
50561
|
-
async getSubjects() {
|
|
50562
|
-
const db2 = this.deps.db;
|
|
50563
|
-
const integrations = await db2.query.gameTimebackIntegrations.findMany({
|
|
50564
|
-
columns: { gameId: true, subject: true },
|
|
50565
|
-
orderBy: [asc(gameTimebackIntegrations.createdAt)]
|
|
50566
|
-
});
|
|
50567
|
-
const subjectMap = {};
|
|
50568
|
-
for (const integration of integrations) {
|
|
50569
|
-
if (!(integration.gameId in subjectMap)) {
|
|
50570
|
-
subjectMap[integration.gameId] = integration.subject;
|
|
50555
|
+
static getRetryBackoffMs(attemptIndex) {
|
|
50556
|
+
const backoff = GameService2.MANIFEST_FETCH_RETRY_BACKOFF_MS;
|
|
50557
|
+
if (backoff.length === 0) {
|
|
50558
|
+
return 0;
|
|
50571
50559
|
}
|
|
50560
|
+
return backoff[Math.min(attemptIndex, backoff.length - 1)] ?? 0;
|
|
50572
50561
|
}
|
|
50573
|
-
|
|
50574
|
-
|
|
50575
|
-
async getById(gameId, caller) {
|
|
50576
|
-
const db2 = this.deps.db;
|
|
50577
|
-
const game = await db2.query.games.findFirst({
|
|
50578
|
-
where: eq(games.id, gameId)
|
|
50579
|
-
});
|
|
50580
|
-
if (!game) {
|
|
50581
|
-
throw new NotFoundError("Game", gameId);
|
|
50562
|
+
static normalizeDeploymentUrl(deploymentUrl) {
|
|
50563
|
+
return deploymentUrl.replace(/\/$/, "");
|
|
50582
50564
|
}
|
|
50583
|
-
|
|
50584
|
-
|
|
50585
|
-
}
|
|
50586
|
-
async getBySlug(slug, caller) {
|
|
50587
|
-
const db2 = this.deps.db;
|
|
50588
|
-
const game = await db2.query.games.findFirst({
|
|
50589
|
-
where: eq(games.slug, slug)
|
|
50590
|
-
});
|
|
50591
|
-
if (!game) {
|
|
50592
|
-
throw new NotFoundError("Game", slug);
|
|
50565
|
+
static getManifestCacheKey(deploymentUrl) {
|
|
50566
|
+
return `${GameService2.MANIFEST_CACHE_KEY_PREFIX}:${deploymentUrl}`;
|
|
50593
50567
|
}
|
|
50594
|
-
|
|
50595
|
-
|
|
50596
|
-
|
|
50597
|
-
|
|
50598
|
-
|
|
50599
|
-
|
|
50600
|
-
|
|
50601
|
-
|
|
50602
|
-
|
|
50603
|
-
|
|
50604
|
-
|
|
50605
|
-
|
|
50606
|
-
|
|
50607
|
-
|
|
50608
|
-
|
|
50609
|
-
|
|
50610
|
-
manifestUrl,
|
|
50611
|
-
manifestHost,
|
|
50612
|
-
deploymentUrl,
|
|
50613
|
-
fetchOutcome,
|
|
50614
|
-
retryCount: 0,
|
|
50615
|
-
durationMs: Date.now() - startedAt,
|
|
50616
|
-
manifestErrorKind,
|
|
50617
|
-
...extra
|
|
50618
|
-
};
|
|
50568
|
+
async list(caller) {
|
|
50569
|
+
const db2 = this.deps.db;
|
|
50570
|
+
const isAdmin = caller?.role === "admin";
|
|
50571
|
+
const isDeveloper = caller?.role === "developer";
|
|
50572
|
+
let whereClause;
|
|
50573
|
+
if (isAdmin) {
|
|
50574
|
+
whereClause = undefined;
|
|
50575
|
+
} else if (isDeveloper && caller?.id) {
|
|
50576
|
+
whereClause = or(ne(games.visibility, "internal"), eq(games.developerId, caller.id));
|
|
50577
|
+
} else {
|
|
50578
|
+
whereClause = ne(games.visibility, "internal");
|
|
50579
|
+
}
|
|
50580
|
+
return db2.query.games.findMany({
|
|
50581
|
+
where: whereClause,
|
|
50582
|
+
orderBy: [desc(games.createdAt)]
|
|
50583
|
+
});
|
|
50619
50584
|
}
|
|
50620
|
-
|
|
50621
|
-
|
|
50622
|
-
|
|
50623
|
-
|
|
50624
|
-
|
|
50625
|
-
|
|
50626
|
-
},
|
|
50627
|
-
signal: controller.signal
|
|
50585
|
+
async listManageable(user) {
|
|
50586
|
+
this.validateDeveloperStatus(user);
|
|
50587
|
+
const db2 = this.deps.db;
|
|
50588
|
+
return db2.query.games.findMany({
|
|
50589
|
+
where: user.role === "admin" ? undefined : eq(games.developerId, user.id),
|
|
50590
|
+
orderBy: [desc(games.createdAt)]
|
|
50628
50591
|
});
|
|
50629
|
-
}
|
|
50630
|
-
|
|
50631
|
-
const
|
|
50632
|
-
const
|
|
50633
|
-
|
|
50634
|
-
|
|
50635
|
-
manifestUrl,
|
|
50636
|
-
error,
|
|
50637
|
-
details
|
|
50592
|
+
}
|
|
50593
|
+
async getSubjects() {
|
|
50594
|
+
const db2 = this.deps.db;
|
|
50595
|
+
const integrations = await db2.query.gameTimebackIntegrations.findMany({
|
|
50596
|
+
columns: { gameId: true, subject: true },
|
|
50597
|
+
orderBy: [asc(gameTimebackIntegrations.createdAt)]
|
|
50638
50598
|
});
|
|
50639
|
-
|
|
50640
|
-
|
|
50599
|
+
const subjectMap = {};
|
|
50600
|
+
for (const integration of integrations) {
|
|
50601
|
+
if (!(integration.gameId in subjectMap)) {
|
|
50602
|
+
subjectMap[integration.gameId] = integration.subject;
|
|
50603
|
+
}
|
|
50641
50604
|
}
|
|
50642
|
-
|
|
50643
|
-
} finally {
|
|
50644
|
-
clearTimeout(timeout);
|
|
50605
|
+
return subjectMap;
|
|
50645
50606
|
}
|
|
50646
|
-
|
|
50647
|
-
const
|
|
50648
|
-
const
|
|
50649
|
-
|
|
50650
|
-
const details = buildDetails("bad_status", manifestErrorKind, {
|
|
50651
|
-
manifestUrl: resolvedManifestUrl,
|
|
50652
|
-
manifestHost: resolvedManifestHost,
|
|
50653
|
-
status: response.status,
|
|
50654
|
-
contentType: response.headers.get("content-type") ?? undefined,
|
|
50655
|
-
cfRay: response.headers.get("cf-ray") ?? undefined,
|
|
50656
|
-
redirected: response.redirected,
|
|
50657
|
-
...response.redirected ? {
|
|
50658
|
-
originalManifestUrl: manifestUrl,
|
|
50659
|
-
originalManifestHost: manifestHost
|
|
50660
|
-
} : {}
|
|
50661
|
-
});
|
|
50662
|
-
const message = `Failed to fetch manifest: ${response.status} ${response.statusText}`;
|
|
50663
|
-
logger5.error("Game manifest returned non-ok response", {
|
|
50664
|
-
gameId,
|
|
50665
|
-
manifestUrl,
|
|
50666
|
-
status: response.status,
|
|
50667
|
-
details
|
|
50607
|
+
async getById(gameId, caller) {
|
|
50608
|
+
const db2 = this.deps.db;
|
|
50609
|
+
const game = await db2.query.games.findFirst({
|
|
50610
|
+
where: eq(games.id, gameId)
|
|
50668
50611
|
});
|
|
50669
|
-
if (
|
|
50670
|
-
throw new
|
|
50612
|
+
if (!game) {
|
|
50613
|
+
throw new NotFoundError("Game", gameId);
|
|
50671
50614
|
}
|
|
50672
|
-
|
|
50615
|
+
this.enforceVisibility(game, caller, gameId);
|
|
50616
|
+
return game;
|
|
50673
50617
|
}
|
|
50674
|
-
|
|
50675
|
-
|
|
50676
|
-
|
|
50677
|
-
|
|
50678
|
-
const resolvedManifestHost = GameService.getManifestHost(resolvedManifestUrl);
|
|
50679
|
-
const details = buildDetails("invalid_body", "permanent", {
|
|
50680
|
-
manifestUrl: resolvedManifestUrl,
|
|
50681
|
-
manifestHost: resolvedManifestHost,
|
|
50682
|
-
status: response.status,
|
|
50683
|
-
contentType: response.headers.get("content-type") ?? undefined,
|
|
50684
|
-
cfRay: response.headers.get("cf-ray") ?? undefined,
|
|
50685
|
-
redirected: response.redirected,
|
|
50686
|
-
...response.redirected ? {
|
|
50687
|
-
originalManifestUrl: manifestUrl,
|
|
50688
|
-
originalManifestHost: manifestHost
|
|
50689
|
-
} : {}
|
|
50690
|
-
});
|
|
50691
|
-
logger5.error("Failed to parse game manifest", {
|
|
50692
|
-
gameId,
|
|
50693
|
-
manifestUrl,
|
|
50694
|
-
error,
|
|
50695
|
-
details
|
|
50618
|
+
async getBySlug(slug, caller) {
|
|
50619
|
+
const db2 = this.deps.db;
|
|
50620
|
+
const game = await db2.query.games.findFirst({
|
|
50621
|
+
where: eq(games.slug, slug)
|
|
50696
50622
|
});
|
|
50697
|
-
|
|
50698
|
-
|
|
50699
|
-
}
|
|
50700
|
-
enforceVisibility(game, caller, lookupIdentifier) {
|
|
50701
|
-
if (game.visibility !== "internal") {
|
|
50702
|
-
return;
|
|
50703
|
-
}
|
|
50704
|
-
const isAdmin = caller?.role === "admin";
|
|
50705
|
-
const isOwner = caller?.id != null && caller.id === game.developerId;
|
|
50706
|
-
if (!isAdmin && !isOwner) {
|
|
50707
|
-
throw new NotFoundError("Game", lookupIdentifier);
|
|
50708
|
-
}
|
|
50709
|
-
}
|
|
50710
|
-
async upsertBySlug(slug, data, user) {
|
|
50711
|
-
const db2 = this.deps.db;
|
|
50712
|
-
const existingGame = await db2.query.games.findFirst({
|
|
50713
|
-
where: eq(games.slug, slug)
|
|
50714
|
-
});
|
|
50715
|
-
const isUpdate = Boolean(existingGame);
|
|
50716
|
-
const gameId = existingGame?.id ?? crypto.randomUUID();
|
|
50717
|
-
if (isUpdate) {
|
|
50718
|
-
await this.validateDeveloperAccess(user, gameId);
|
|
50719
|
-
} else {
|
|
50720
|
-
this.validateDeveloperStatus(user);
|
|
50721
|
-
}
|
|
50722
|
-
const gameDataForDb = {
|
|
50723
|
-
displayName: data.displayName,
|
|
50724
|
-
platform: data.platform,
|
|
50725
|
-
metadata: data.metadata,
|
|
50726
|
-
mapElementId: data.mapElementId,
|
|
50727
|
-
gameType: data.gameType,
|
|
50728
|
-
...data.visibility && { visibility: data.visibility },
|
|
50729
|
-
externalUrl: data.externalUrl || null,
|
|
50730
|
-
updatedAt: new Date
|
|
50731
|
-
};
|
|
50732
|
-
let gameResponse;
|
|
50733
|
-
if (isUpdate) {
|
|
50734
|
-
const [updatedGame] = await db2.update(games).set(gameDataForDb).where(eq(games.id, gameId)).returning();
|
|
50735
|
-
if (!updatedGame) {
|
|
50736
|
-
logger5.error("Game update returned no rows", { gameId, slug });
|
|
50737
|
-
throw new InternalError("DB update failed to return result for existing game");
|
|
50738
|
-
}
|
|
50739
|
-
gameResponse = updatedGame;
|
|
50740
|
-
} else {
|
|
50741
|
-
const insertData = {
|
|
50742
|
-
...gameDataForDb,
|
|
50743
|
-
id: gameId,
|
|
50744
|
-
slug,
|
|
50745
|
-
developerId: user.id,
|
|
50746
|
-
metadata: data.metadata || {},
|
|
50747
|
-
version: data.gameType === "external" ? "external" : "",
|
|
50748
|
-
deploymentUrl: null,
|
|
50749
|
-
createdAt: new Date
|
|
50750
|
-
};
|
|
50751
|
-
const [createdGame] = await db2.insert(games).values(insertData).returning();
|
|
50752
|
-
if (!createdGame) {
|
|
50753
|
-
logger5.error("Game insert returned no rows", { slug, developerId: user.id });
|
|
50754
|
-
throw new InternalError("DB insert failed to return result for new game");
|
|
50623
|
+
if (!game) {
|
|
50624
|
+
throw new NotFoundError("Game", slug);
|
|
50755
50625
|
}
|
|
50756
|
-
|
|
50626
|
+
this.enforceVisibility(game, caller, slug);
|
|
50627
|
+
return game;
|
|
50757
50628
|
}
|
|
50758
|
-
|
|
50759
|
-
|
|
50760
|
-
|
|
50761
|
-
|
|
50762
|
-
|
|
50763
|
-
|
|
50764
|
-
|
|
50765
|
-
|
|
50766
|
-
|
|
50767
|
-
|
|
50629
|
+
async getManifest(gameId, caller) {
|
|
50630
|
+
const game = await this.getById(gameId, caller);
|
|
50631
|
+
if (game.gameType !== "hosted" || !game.deploymentUrl) {
|
|
50632
|
+
throw new BadRequestError("Game does not have a deployment manifest");
|
|
50633
|
+
}
|
|
50634
|
+
const deploymentUrl = GameService2.normalizeDeploymentUrl(game.deploymentUrl);
|
|
50635
|
+
const cacheKey2 = GameService2.getManifestCacheKey(deploymentUrl);
|
|
50636
|
+
const cached = await this.deps.cache.get(cacheKey2);
|
|
50637
|
+
if (cached) {
|
|
50638
|
+
return cached;
|
|
50639
|
+
}
|
|
50640
|
+
const inFlight = inFlightManifestFetches.get(deploymentUrl);
|
|
50641
|
+
if (inFlight) {
|
|
50642
|
+
return inFlight;
|
|
50643
|
+
}
|
|
50644
|
+
const promise = this.fetchManifestFromOrigin({ gameId, deploymentUrl }).then(async (manifest) => {
|
|
50645
|
+
try {
|
|
50646
|
+
await this.deps.cache.set(cacheKey2, manifest, GameService2.MANIFEST_CACHE_TTL_SECONDS);
|
|
50647
|
+
} catch (cacheError) {
|
|
50648
|
+
logger5.warn("Failed to cache game manifest", {
|
|
50649
|
+
gameId,
|
|
50650
|
+
deploymentUrl,
|
|
50651
|
+
cacheKey: cacheKey2,
|
|
50652
|
+
error: cacheError
|
|
50653
|
+
});
|
|
50654
|
+
}
|
|
50655
|
+
return manifest;
|
|
50656
|
+
}).finally(() => {
|
|
50657
|
+
inFlightManifestFetches.delete(deploymentUrl);
|
|
50658
|
+
});
|
|
50659
|
+
inFlightManifestFetches.set(deploymentUrl, promise);
|
|
50660
|
+
return promise;
|
|
50661
|
+
}
|
|
50662
|
+
async fetchManifestFromOrigin(args2) {
|
|
50663
|
+
const { gameId, deploymentUrl } = args2;
|
|
50664
|
+
const manifestUrl = `${deploymentUrl}/playcademy.manifest.json`;
|
|
50665
|
+
const manifestHost = GameService2.getManifestHost(manifestUrl);
|
|
50666
|
+
const startedAt = Date.now();
|
|
50667
|
+
const maxAttempts = GameService2.MANIFEST_FETCH_MAX_RETRIES + 1;
|
|
50668
|
+
for (let attempt = 0;attempt < maxAttempts; attempt++) {
|
|
50669
|
+
const isLastAttempt = attempt === maxAttempts - 1;
|
|
50670
|
+
const outcome = await this.attemptManifestFetch({
|
|
50671
|
+
manifestUrl,
|
|
50672
|
+
manifestHost,
|
|
50673
|
+
deploymentUrl,
|
|
50674
|
+
startedAt,
|
|
50675
|
+
retryCount: attempt
|
|
50676
|
+
});
|
|
50677
|
+
if (outcome.kind === "success") {
|
|
50678
|
+
return outcome.manifest;
|
|
50679
|
+
}
|
|
50680
|
+
if (!outcome.retryable || isLastAttempt) {
|
|
50681
|
+
logger5.error("Failed to fetch game manifest", {
|
|
50682
|
+
gameId,
|
|
50683
|
+
manifestUrl,
|
|
50684
|
+
attempt: attempt + 1,
|
|
50685
|
+
maxAttempts,
|
|
50686
|
+
retryable: outcome.retryable,
|
|
50687
|
+
details: outcome.details,
|
|
50688
|
+
throwable: outcome.throwable,
|
|
50689
|
+
cause: outcome.cause
|
|
50690
|
+
});
|
|
50691
|
+
throw outcome.throwable;
|
|
50692
|
+
}
|
|
50693
|
+
const backoffMs = GameService2.getRetryBackoffMs(attempt);
|
|
50694
|
+
logger5.warn("Retrying game manifest fetch after transient failure", {
|
|
50695
|
+
gameId,
|
|
50696
|
+
manifestUrl,
|
|
50697
|
+
attempt: attempt + 1,
|
|
50698
|
+
maxAttempts,
|
|
50699
|
+
backoffMs,
|
|
50700
|
+
details: outcome.details,
|
|
50701
|
+
cause: outcome.cause
|
|
50768
50702
|
});
|
|
50703
|
+
await sleep(backoffMs);
|
|
50769
50704
|
}
|
|
50705
|
+
throw new InternalError("Exhausted manifest fetch retries without result");
|
|
50770
50706
|
}
|
|
50771
|
-
|
|
50772
|
-
|
|
50773
|
-
|
|
50774
|
-
|
|
50775
|
-
|
|
50776
|
-
|
|
50777
|
-
|
|
50778
|
-
|
|
50779
|
-
|
|
50780
|
-
|
|
50781
|
-
|
|
50782
|
-
|
|
50783
|
-
|
|
50784
|
-
|
|
50785
|
-
|
|
50786
|
-
|
|
50787
|
-
|
|
50788
|
-
}
|
|
50789
|
-
const activeDeployment = await db2.query.gameDeployments.findFirst({
|
|
50790
|
-
where: and(eq(gameDeployments.gameId, gameId), eq(gameDeployments.isActive, true)),
|
|
50791
|
-
columns: { deploymentId: true, provider: true, resources: true }
|
|
50792
|
-
});
|
|
50793
|
-
const customHostnames = await db2.select({
|
|
50794
|
-
hostname: gameCustomHostnames.hostname,
|
|
50795
|
-
cloudflareId: gameCustomHostnames.cloudflareId,
|
|
50796
|
-
environment: gameCustomHostnames.environment
|
|
50797
|
-
}).from(gameCustomHostnames).where(eq(gameCustomHostnames.gameId, gameId));
|
|
50798
|
-
const result = await db2.delete(games).where(eq(games.id, gameId)).returning({ id: games.id });
|
|
50799
|
-
if (result.length === 0) {
|
|
50800
|
-
throw new NotFoundError("Game", gameId);
|
|
50801
|
-
}
|
|
50802
|
-
logger5.info("Deleted game", {
|
|
50803
|
-
gameId: result[0].id,
|
|
50804
|
-
slug: gameToDelete.slug,
|
|
50805
|
-
hadActiveDeployment: Boolean(activeDeployment),
|
|
50806
|
-
customDomainsCount: customHostnames.length
|
|
50807
|
-
});
|
|
50808
|
-
this.deps.alerts.notifyGameDeletion({
|
|
50809
|
-
slug: gameToDelete.slug,
|
|
50810
|
-
displayName: gameToDelete.displayName,
|
|
50811
|
-
developer: { id: user.id, email: user.email }
|
|
50812
|
-
}).catch((error) => {
|
|
50813
|
-
logger5.warn("Failed to send deletion alert", { error });
|
|
50814
|
-
});
|
|
50815
|
-
if (activeDeployment?.provider === "cloudflare" && this.deps.cloudflare) {
|
|
50707
|
+
async attemptManifestFetch(args2) {
|
|
50708
|
+
const { manifestUrl, manifestHost, deploymentUrl, startedAt, retryCount } = args2;
|
|
50709
|
+
function buildDetails(fetchOutcome, manifestErrorKind, extra = {}) {
|
|
50710
|
+
return {
|
|
50711
|
+
manifestUrl,
|
|
50712
|
+
manifestHost,
|
|
50713
|
+
deploymentUrl,
|
|
50714
|
+
fetchOutcome,
|
|
50715
|
+
retryCount,
|
|
50716
|
+
durationMs: Date.now() - startedAt,
|
|
50717
|
+
manifestErrorKind,
|
|
50718
|
+
...extra
|
|
50719
|
+
};
|
|
50720
|
+
}
|
|
50721
|
+
const controller = new AbortController;
|
|
50722
|
+
const timeout = setTimeout(() => controller.abort(), GameService2.MANIFEST_FETCH_ATTEMPT_TIMEOUT_MS);
|
|
50723
|
+
let response;
|
|
50816
50724
|
try {
|
|
50817
|
-
await
|
|
50818
|
-
|
|
50819
|
-
|
|
50820
|
-
|
|
50821
|
-
|
|
50822
|
-
|
|
50823
|
-
logger5.info("Cleaned up Cloudflare resources", {
|
|
50824
|
-
gameId,
|
|
50825
|
-
deploymentId: activeDeployment.deploymentId,
|
|
50826
|
-
customDomainsDeleted: customHostnames.length
|
|
50725
|
+
response = await fetch(manifestUrl, {
|
|
50726
|
+
method: "GET",
|
|
50727
|
+
headers: {
|
|
50728
|
+
Accept: "application/json"
|
|
50729
|
+
},
|
|
50730
|
+
signal: controller.signal
|
|
50827
50731
|
});
|
|
50828
|
-
} catch (
|
|
50829
|
-
|
|
50830
|
-
|
|
50831
|
-
|
|
50832
|
-
|
|
50732
|
+
} catch (error) {
|
|
50733
|
+
const fetchErrorMessage = GameService2.getFetchErrorMessage(error);
|
|
50734
|
+
const details = buildDetails("network_error", "temporary", fetchErrorMessage ? { fetchErrorMessage } : {});
|
|
50735
|
+
const throwable = error instanceof Error && error.name === "AbortError" ? new TimeoutError("Timed out loading game manifest", details) : new ServiceUnavailableError("Failed to load game manifest", details);
|
|
50736
|
+
return { kind: "failure", retryable: true, throwable, details, cause: error };
|
|
50737
|
+
} finally {
|
|
50738
|
+
clearTimeout(timeout);
|
|
50739
|
+
}
|
|
50740
|
+
if (!response.ok) {
|
|
50741
|
+
const resolvedManifestUrl = response.url || manifestUrl;
|
|
50742
|
+
const resolvedManifestHost = GameService2.getManifestHost(resolvedManifestUrl);
|
|
50743
|
+
const manifestErrorKind = GameService2.isRetryableStatus(response.status) ? "temporary" : "permanent";
|
|
50744
|
+
const details = buildDetails("bad_status", manifestErrorKind, {
|
|
50745
|
+
manifestUrl: resolvedManifestUrl,
|
|
50746
|
+
manifestHost: resolvedManifestHost,
|
|
50747
|
+
status: response.status,
|
|
50748
|
+
contentType: response.headers.get("content-type") ?? undefined,
|
|
50749
|
+
cfRay: response.headers.get("cf-ray") ?? undefined,
|
|
50750
|
+
redirected: response.redirected,
|
|
50751
|
+
...response.redirected ? {
|
|
50752
|
+
originalManifestUrl: manifestUrl,
|
|
50753
|
+
originalManifestHost: manifestHost
|
|
50754
|
+
} : {}
|
|
50833
50755
|
});
|
|
50756
|
+
const message = `Failed to fetch manifest: ${response.status} ${response.statusText}`;
|
|
50757
|
+
const throwable = manifestErrorKind === "temporary" ? new ServiceUnavailableError(message, details) : new BadRequestError(message, details);
|
|
50758
|
+
return {
|
|
50759
|
+
kind: "failure",
|
|
50760
|
+
retryable: manifestErrorKind === "temporary",
|
|
50761
|
+
throwable,
|
|
50762
|
+
details
|
|
50763
|
+
};
|
|
50834
50764
|
}
|
|
50835
50765
|
try {
|
|
50836
|
-
const
|
|
50837
|
-
|
|
50838
|
-
|
|
50839
|
-
|
|
50840
|
-
|
|
50841
|
-
|
|
50766
|
+
const manifest = await response.json();
|
|
50767
|
+
return { kind: "success", manifest };
|
|
50768
|
+
} catch (error) {
|
|
50769
|
+
const resolvedManifestUrl = response.url || manifestUrl;
|
|
50770
|
+
const resolvedManifestHost = GameService2.getManifestHost(resolvedManifestUrl);
|
|
50771
|
+
const details = buildDetails("invalid_body", "permanent", {
|
|
50772
|
+
manifestUrl: resolvedManifestUrl,
|
|
50773
|
+
manifestHost: resolvedManifestHost,
|
|
50774
|
+
status: response.status,
|
|
50775
|
+
contentType: response.headers.get("content-type") ?? undefined,
|
|
50776
|
+
cfRay: response.headers.get("cf-ray") ?? undefined,
|
|
50777
|
+
redirected: response.redirected,
|
|
50778
|
+
...response.redirected ? {
|
|
50779
|
+
originalManifestUrl: manifestUrl,
|
|
50780
|
+
originalManifestHost: manifestHost
|
|
50781
|
+
} : {}
|
|
50782
|
+
});
|
|
50783
|
+
return {
|
|
50784
|
+
kind: "failure",
|
|
50785
|
+
retryable: false,
|
|
50786
|
+
throwable: new BadRequestError("Failed to parse game manifest", details),
|
|
50787
|
+
details,
|
|
50788
|
+
cause: error
|
|
50789
|
+
};
|
|
50790
|
+
}
|
|
50791
|
+
}
|
|
50792
|
+
enforceVisibility(game, caller, lookupIdentifier) {
|
|
50793
|
+
if (game.visibility !== "internal") {
|
|
50794
|
+
return;
|
|
50795
|
+
}
|
|
50796
|
+
const isAdmin = caller?.role === "admin";
|
|
50797
|
+
const isOwner = caller?.id != null && caller.id === game.developerId;
|
|
50798
|
+
if (!isAdmin && !isOwner) {
|
|
50799
|
+
throw new NotFoundError("Game", lookupIdentifier);
|
|
50800
|
+
}
|
|
50801
|
+
}
|
|
50802
|
+
async upsertBySlug(slug, data, user) {
|
|
50803
|
+
const db2 = this.deps.db;
|
|
50804
|
+
const existingGame = await db2.query.games.findFirst({
|
|
50805
|
+
where: eq(games.slug, slug)
|
|
50806
|
+
});
|
|
50807
|
+
const isUpdate = Boolean(existingGame);
|
|
50808
|
+
const gameId = existingGame?.id ?? crypto.randomUUID();
|
|
50809
|
+
if (isUpdate) {
|
|
50810
|
+
await this.validateDeveloperAccess(user, gameId);
|
|
50811
|
+
} else {
|
|
50812
|
+
this.validateDeveloperStatus(user);
|
|
50813
|
+
}
|
|
50814
|
+
const gameDataForDb = {
|
|
50815
|
+
displayName: data.displayName,
|
|
50816
|
+
platform: data.platform,
|
|
50817
|
+
metadata: data.metadata,
|
|
50818
|
+
mapElementId: data.mapElementId,
|
|
50819
|
+
gameType: data.gameType,
|
|
50820
|
+
...data.visibility && { visibility: data.visibility },
|
|
50821
|
+
externalUrl: data.externalUrl || null,
|
|
50822
|
+
updatedAt: new Date
|
|
50823
|
+
};
|
|
50824
|
+
let gameResponse;
|
|
50825
|
+
if (isUpdate) {
|
|
50826
|
+
const [updatedGame] = await db2.update(games).set(gameDataForDb).where(eq(games.id, gameId)).returning();
|
|
50827
|
+
if (!updatedGame) {
|
|
50828
|
+
logger5.error("Game update returned no rows", { gameId, slug });
|
|
50829
|
+
throw new InternalError("DB update failed to return result for existing game");
|
|
50830
|
+
}
|
|
50831
|
+
gameResponse = updatedGame;
|
|
50832
|
+
} else {
|
|
50833
|
+
const insertData = {
|
|
50834
|
+
...gameDataForDb,
|
|
50835
|
+
id: gameId,
|
|
50836
|
+
slug,
|
|
50837
|
+
developerId: user.id,
|
|
50838
|
+
metadata: data.metadata || {},
|
|
50839
|
+
version: data.gameType === "external" ? "external" : "",
|
|
50840
|
+
deploymentUrl: null,
|
|
50841
|
+
createdAt: new Date
|
|
50842
|
+
};
|
|
50843
|
+
const [createdGame] = await db2.insert(games).values(insertData).returning();
|
|
50844
|
+
if (!createdGame) {
|
|
50845
|
+
logger5.error("Game insert returned no rows", { slug, developerId: user.id });
|
|
50846
|
+
throw new InternalError("DB insert failed to return result for new game");
|
|
50847
|
+
}
|
|
50848
|
+
gameResponse = createdGame;
|
|
50849
|
+
}
|
|
50850
|
+
if (data.mapElementId) {
|
|
50851
|
+
try {
|
|
50852
|
+
await db2.update(mapElements).set({
|
|
50853
|
+
interactionType: "game_entry",
|
|
50854
|
+
gameId: gameResponse.id
|
|
50855
|
+
}).where(eq(mapElements.id, data.mapElementId));
|
|
50856
|
+
} catch (mapError) {
|
|
50857
|
+
logger5.warn("Failed to update map element", {
|
|
50858
|
+
mapElementId: data.mapElementId,
|
|
50859
|
+
error: mapError
|
|
50842
50860
|
});
|
|
50843
50861
|
}
|
|
50844
|
-
} catch (keyError) {
|
|
50845
|
-
logger5.warn("Failed to cleanup API key", { gameId, error: keyError });
|
|
50846
50862
|
}
|
|
50863
|
+
logger5.info("Upserted game", {
|
|
50864
|
+
gameId: gameResponse.id,
|
|
50865
|
+
slug: gameResponse.slug,
|
|
50866
|
+
operation: isUpdate ? "update" : "create",
|
|
50867
|
+
displayName: gameResponse.displayName
|
|
50868
|
+
});
|
|
50869
|
+
return gameResponse;
|
|
50847
50870
|
}
|
|
50848
|
-
|
|
50849
|
-
|
|
50850
|
-
|
|
50851
|
-
|
|
50852
|
-
}
|
|
50853
|
-
async validateOwnership(user, gameId) {
|
|
50854
|
-
if (user.role === "admin") {
|
|
50855
|
-
const gameExists = await this.deps.db.query.games.findFirst({
|
|
50871
|
+
async delete(gameId, user) {
|
|
50872
|
+
await this.validateDeveloperAccess(user, gameId);
|
|
50873
|
+
const db2 = this.deps.db;
|
|
50874
|
+
const gameToDelete = await db2.query.games.findFirst({
|
|
50856
50875
|
where: eq(games.id, gameId),
|
|
50857
|
-
columns: { id: true }
|
|
50876
|
+
columns: { id: true, slug: true, displayName: true }
|
|
50858
50877
|
});
|
|
50859
|
-
if (!
|
|
50878
|
+
if (!gameToDelete?.slug) {
|
|
50860
50879
|
throw new NotFoundError("Game", gameId);
|
|
50861
50880
|
}
|
|
50862
|
-
|
|
50881
|
+
const activeDeployment = await db2.query.gameDeployments.findFirst({
|
|
50882
|
+
where: and(eq(gameDeployments.gameId, gameId), eq(gameDeployments.isActive, true)),
|
|
50883
|
+
columns: { deploymentId: true, provider: true, resources: true }
|
|
50884
|
+
});
|
|
50885
|
+
const customHostnames = await db2.select({
|
|
50886
|
+
hostname: gameCustomHostnames.hostname,
|
|
50887
|
+
cloudflareId: gameCustomHostnames.cloudflareId,
|
|
50888
|
+
environment: gameCustomHostnames.environment
|
|
50889
|
+
}).from(gameCustomHostnames).where(eq(gameCustomHostnames.gameId, gameId));
|
|
50890
|
+
const result = await db2.delete(games).where(eq(games.id, gameId)).returning({ id: games.id });
|
|
50891
|
+
if (result.length === 0) {
|
|
50892
|
+
throw new NotFoundError("Game", gameId);
|
|
50893
|
+
}
|
|
50894
|
+
logger5.info("Deleted game", {
|
|
50895
|
+
gameId: result[0].id,
|
|
50896
|
+
slug: gameToDelete.slug,
|
|
50897
|
+
hadActiveDeployment: Boolean(activeDeployment),
|
|
50898
|
+
customDomainsCount: customHostnames.length
|
|
50899
|
+
});
|
|
50900
|
+
this.deps.alerts.notifyGameDeletion({
|
|
50901
|
+
slug: gameToDelete.slug,
|
|
50902
|
+
displayName: gameToDelete.displayName,
|
|
50903
|
+
developer: { id: user.id, email: user.email }
|
|
50904
|
+
}).catch((error) => {
|
|
50905
|
+
logger5.warn("Failed to send deletion alert", { error });
|
|
50906
|
+
});
|
|
50907
|
+
if (activeDeployment?.provider === "cloudflare" && this.deps.cloudflare) {
|
|
50908
|
+
try {
|
|
50909
|
+
await this.deps.cloudflare.delete(activeDeployment.deploymentId, {
|
|
50910
|
+
deleteBindings: true,
|
|
50911
|
+
resources: activeDeployment.resources ?? undefined,
|
|
50912
|
+
customDomains: customHostnames.length > 0 ? customHostnames : undefined,
|
|
50913
|
+
gameSlug: gameToDelete.slug
|
|
50914
|
+
});
|
|
50915
|
+
logger5.info("Cleaned up Cloudflare resources", {
|
|
50916
|
+
gameId,
|
|
50917
|
+
deploymentId: activeDeployment.deploymentId,
|
|
50918
|
+
customDomainsDeleted: customHostnames.length
|
|
50919
|
+
});
|
|
50920
|
+
} catch (cfError) {
|
|
50921
|
+
logger5.warn("Failed to cleanup Cloudflare resources", {
|
|
50922
|
+
gameId,
|
|
50923
|
+
deploymentId: activeDeployment.deploymentId,
|
|
50924
|
+
error: cfError
|
|
50925
|
+
});
|
|
50926
|
+
}
|
|
50927
|
+
try {
|
|
50928
|
+
const deletedKeyId = await this.deps.deleteApiKeyByName(getGameWorkerApiKeyName(gameToDelete.slug), user.id);
|
|
50929
|
+
if (deletedKeyId) {
|
|
50930
|
+
logger5.info("Cleaned up API key for deleted game", {
|
|
50931
|
+
gameId,
|
|
50932
|
+
slug: gameToDelete.slug,
|
|
50933
|
+
keyId: deletedKeyId
|
|
50934
|
+
});
|
|
50935
|
+
}
|
|
50936
|
+
} catch (keyError) {
|
|
50937
|
+
logger5.warn("Failed to cleanup API key", { gameId, error: keyError });
|
|
50938
|
+
}
|
|
50939
|
+
}
|
|
50940
|
+
return {
|
|
50941
|
+
slug: gameToDelete.slug,
|
|
50942
|
+
displayName: gameToDelete.displayName
|
|
50943
|
+
};
|
|
50863
50944
|
}
|
|
50864
|
-
|
|
50865
|
-
|
|
50866
|
-
|
|
50867
|
-
|
|
50868
|
-
|
|
50869
|
-
|
|
50870
|
-
|
|
50871
|
-
|
|
50945
|
+
async validateOwnership(user, gameId) {
|
|
50946
|
+
if (user.role === "admin") {
|
|
50947
|
+
const gameExists = await this.deps.db.query.games.findFirst({
|
|
50948
|
+
where: eq(games.id, gameId),
|
|
50949
|
+
columns: { id: true }
|
|
50950
|
+
});
|
|
50951
|
+
if (!gameExists) {
|
|
50952
|
+
throw new NotFoundError("Game", gameId);
|
|
50953
|
+
}
|
|
50954
|
+
return;
|
|
50955
|
+
}
|
|
50956
|
+
const db2 = this.deps.db;
|
|
50957
|
+
const gameOwnership = await db2.query.games.findFirst({
|
|
50958
|
+
where: and(eq(games.id, gameId), eq(games.developerId, user.id)),
|
|
50872
50959
|
columns: { id: true }
|
|
50873
50960
|
});
|
|
50874
|
-
if (!
|
|
50875
|
-
|
|
50961
|
+
if (!gameOwnership) {
|
|
50962
|
+
const gameExists = await db2.query.games.findFirst({
|
|
50963
|
+
where: eq(games.id, gameId),
|
|
50964
|
+
columns: { id: true }
|
|
50965
|
+
});
|
|
50966
|
+
if (!gameExists) {
|
|
50967
|
+
throw new NotFoundError("Game", gameId);
|
|
50968
|
+
}
|
|
50969
|
+
throw new AccessDeniedError("You do not own this game");
|
|
50876
50970
|
}
|
|
50877
|
-
throw new AccessDeniedError("You do not own this game");
|
|
50878
50971
|
}
|
|
50879
|
-
|
|
50880
|
-
|
|
50881
|
-
|
|
50882
|
-
|
|
50883
|
-
|
|
50884
|
-
|
|
50972
|
+
async validateDeveloperAccess(user, gameId) {
|
|
50973
|
+
this.validateDeveloperStatus(user);
|
|
50974
|
+
if (user.role === "admin") {
|
|
50975
|
+
const gameExists = await this.deps.db.query.games.findFirst({
|
|
50976
|
+
where: eq(games.id, gameId),
|
|
50977
|
+
columns: { id: true }
|
|
50978
|
+
});
|
|
50979
|
+
if (!gameExists) {
|
|
50980
|
+
throw new NotFoundError("Game", gameId);
|
|
50981
|
+
}
|
|
50982
|
+
return;
|
|
50983
|
+
}
|
|
50984
|
+
const db2 = this.deps.db;
|
|
50985
|
+
const existingGame = await db2.query.games.findFirst({
|
|
50986
|
+
where: and(eq(games.id, gameId), eq(games.developerId, user.id)),
|
|
50885
50987
|
columns: { id: true }
|
|
50886
50988
|
});
|
|
50887
|
-
if (!
|
|
50989
|
+
if (!existingGame) {
|
|
50888
50990
|
throw new NotFoundError("Game", gameId);
|
|
50889
50991
|
}
|
|
50890
|
-
return;
|
|
50891
|
-
}
|
|
50892
|
-
const db2 = this.deps.db;
|
|
50893
|
-
const existingGame = await db2.query.games.findFirst({
|
|
50894
|
-
where: and(eq(games.id, gameId), eq(games.developerId, user.id)),
|
|
50895
|
-
columns: { id: true }
|
|
50896
|
-
});
|
|
50897
|
-
if (!existingGame) {
|
|
50898
|
-
throw new NotFoundError("Game", gameId);
|
|
50899
50992
|
}
|
|
50900
|
-
|
|
50901
|
-
|
|
50902
|
-
|
|
50903
|
-
|
|
50904
|
-
|
|
50905
|
-
|
|
50906
|
-
|
|
50993
|
+
async validateDeveloperAccessBySlug(user, slug) {
|
|
50994
|
+
this.validateDeveloperStatus(user);
|
|
50995
|
+
const db2 = this.deps.db;
|
|
50996
|
+
if (user.role === "admin") {
|
|
50997
|
+
const game2 = await db2.query.games.findFirst({
|
|
50998
|
+
where: eq(games.slug, slug)
|
|
50999
|
+
});
|
|
51000
|
+
if (!game2) {
|
|
51001
|
+
throw new NotFoundError("Game", slug);
|
|
51002
|
+
}
|
|
51003
|
+
return game2;
|
|
51004
|
+
}
|
|
51005
|
+
const game = await db2.query.games.findFirst({
|
|
51006
|
+
where: and(eq(games.slug, slug), eq(games.developerId, user.id))
|
|
50907
51007
|
});
|
|
50908
|
-
if (!
|
|
51008
|
+
if (!game) {
|
|
50909
51009
|
throw new NotFoundError("Game", slug);
|
|
50910
51010
|
}
|
|
50911
|
-
return
|
|
51011
|
+
return game;
|
|
50912
51012
|
}
|
|
50913
|
-
|
|
50914
|
-
|
|
50915
|
-
|
|
50916
|
-
|
|
50917
|
-
|
|
50918
|
-
|
|
50919
|
-
|
|
50920
|
-
|
|
50921
|
-
|
|
50922
|
-
|
|
50923
|
-
|
|
50924
|
-
}
|
|
50925
|
-
if (user.developerStatus !== "approved") {
|
|
50926
|
-
const status = user.developerStatus || "none";
|
|
50927
|
-
if (status === "pending") {
|
|
50928
|
-
throw new AccessDeniedError("Developer application is pending approval. You will be notified when approved.");
|
|
50929
|
-
} else {
|
|
50930
|
-
throw new AccessDeniedError("Developer status required. Apply for developer access to create and manage games.");
|
|
51013
|
+
validateDeveloperStatus(user) {
|
|
51014
|
+
if (user.role === "admin") {
|
|
51015
|
+
return;
|
|
51016
|
+
}
|
|
51017
|
+
if (user.developerStatus !== "approved") {
|
|
51018
|
+
const status = user.developerStatus || "none";
|
|
51019
|
+
if (status === "pending") {
|
|
51020
|
+
throw new AccessDeniedError("Developer application is pending approval. You will be notified when approved.");
|
|
51021
|
+
} else {
|
|
51022
|
+
throw new AccessDeniedError("Developer status required. Apply for developer access to create and manage games.");
|
|
51023
|
+
}
|
|
50931
51024
|
}
|
|
50932
51025
|
}
|
|
50933
|
-
}
|
|
50934
|
-
}
|
|
50935
|
-
var logger5;
|
|
50936
|
-
var init_game_service = __esm(() => {
|
|
50937
|
-
init_drizzle_orm();
|
|
50938
|
-
init_tables_index();
|
|
50939
|
-
init_src2();
|
|
50940
|
-
init_errors();
|
|
50941
|
-
init_deployment_util();
|
|
50942
|
-
logger5 = log.scope("GameService");
|
|
51026
|
+
};
|
|
50943
51027
|
});
|
|
50944
51028
|
function createGameServices(deps) {
|
|
50945
51029
|
const { db: db2, config: config2, cloudflare, auth: auth2, storage, cache, alerts } = deps;
|
|
50946
51030
|
const game = new GameService({
|
|
50947
51031
|
db: db2,
|
|
50948
51032
|
alerts,
|
|
51033
|
+
cache,
|
|
50949
51034
|
cloudflare,
|
|
50950
51035
|
deleteApiKeyByName: auth2.deleteApiKeyByName.bind(auth2)
|
|
50951
51036
|
});
|
|
@@ -52736,7 +52821,8 @@ class SeedService {
|
|
|
52736
52821
|
PLAYCADEMY_BASE_URL: ""
|
|
52737
52822
|
}, {
|
|
52738
52823
|
bindings: { d1: [deploymentId], r2: [], kv: [] },
|
|
52739
|
-
keepAssets: false
|
|
52824
|
+
keepAssets: false,
|
|
52825
|
+
compatibilityDate: CLOUDFLARE_COMPATIBILITY_DATE
|
|
52740
52826
|
});
|
|
52741
52827
|
logger14.info("Worker deployed", { seedDeploymentId, url: result.url });
|
|
52742
52828
|
if (secrets && Object.keys(secrets).length > 0) {
|
|
@@ -52866,6 +52952,7 @@ class SeedService {
|
|
|
52866
52952
|
}
|
|
52867
52953
|
var logger14;
|
|
52868
52954
|
var init_seed_service = __esm(() => {
|
|
52955
|
+
init_src();
|
|
52869
52956
|
init_setup2();
|
|
52870
52957
|
init_src2();
|
|
52871
52958
|
init_config2();
|
|
@@ -119670,6 +119757,7 @@ var init_schemas2 = __esm(() => {
|
|
|
119670
119757
|
code: exports_external.string().optional(),
|
|
119671
119758
|
codeUploadToken: exports_external.string().optional(),
|
|
119672
119759
|
config: exports_external.unknown().optional(),
|
|
119760
|
+
compatibilityDate: exports_external.string().optional(),
|
|
119673
119761
|
compatibilityFlags: exports_external.array(exports_external.string()).optional(),
|
|
119674
119762
|
bindings: exports_external.object({
|
|
119675
119763
|
database: exports_external.array(exports_external.string()).optional(),
|
|
@@ -125198,7 +125286,7 @@ var import_picocolors12 = __toESM(require_picocolors(), 1);
|
|
|
125198
125286
|
// package.json
|
|
125199
125287
|
var package_default2 = {
|
|
125200
125288
|
name: "@playcademy/vite-plugin",
|
|
125201
|
-
version: "0.2.
|
|
125289
|
+
version: "0.2.24-beta.2",
|
|
125202
125290
|
type: "module",
|
|
125203
125291
|
exports: {
|
|
125204
125292
|
".": {
|