@playcademy/vite-plugin 0.2.6 → 0.2.8

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.
Files changed (2) hide show
  1. package/dist/index.js +809 -1716
  2. package/package.json +3 -3
package/dist/index.js CHANGED
@@ -41142,10 +41142,6 @@ import { homedir } from "node:os";
41142
41142
  import { join } from "node:path";
41143
41143
  import { stdout } from "process";
41144
41144
  import crypto3 from "node:crypto";
41145
- import { request } from "https";
41146
- import { pipeline } from "stream";
41147
- import { createPublicKey, createVerify, verify } from "crypto";
41148
- import * as crypto4 from "node:crypto";
41149
41145
  import * as s3 from "fs";
41150
41146
  import * as o3 from "path";
41151
41147
  import fs22 from "node:fs";
@@ -41153,9 +41149,10 @@ import { dirname, isAbsolute, join as join3 } from "node:path";
41153
41149
  import process2 from "process";
41154
41150
  import os from "os";
41155
41151
  import tty from "tty";
41156
- import { randomUUID as randomUUID2 } from "crypto";
41152
+ import { randomUUID } from "crypto";
41157
41153
  import fs3 from "node:fs";
41158
41154
  import fs4 from "node:fs";
41155
+ import * as crypto4 from "node:crypto";
41159
41156
  import { randomUUID as randomUUID3 } from "node:crypto";
41160
41157
  var __create2 = Object.create;
41161
41158
  var __getProtoOf2 = Object.getPrototypeOf;
@@ -42382,7 +42379,7 @@ var package_default;
42382
42379
  var init_package = __esm(() => {
42383
42380
  package_default = {
42384
42381
  name: "@playcademy/sandbox",
42385
- version: "0.3.8",
42382
+ version: "0.3.10",
42386
42383
  description: "Local development server for Playcademy game development",
42387
42384
  type: "module",
42388
42385
  exports: {
@@ -46989,7 +46986,7 @@ var realtimeConfigSchema;
46989
46986
  var apiConfigSchema;
46990
46987
  var init_schema = __esm(() => {
46991
46988
  init_esm();
46992
- stageSchema = exports_external.enum(["production", "staging", "local"]);
46989
+ stageSchema = exports_external.enum(["production", "dev", "local"]);
46993
46990
  ltiConfigSchema = exports_external.object({
46994
46991
  audience: exports_external.string(),
46995
46992
  jwksUrl: exports_external.string().url(),
@@ -54625,7 +54622,7 @@ class DeployService {
54625
54622
  }
54626
54623
  return cf;
54627
54624
  }
54628
- async createAndPersistApiKey(user, slug2, game, keyName) {
54625
+ async createApiKey(user, slug2, keyName) {
54629
54626
  const { id, key: apiKey } = await this.ctx.providers.auth.createApiKey({
54630
54627
  userId: user.id,
54631
54628
  name: keyName,
@@ -54634,16 +54631,6 @@ class DeployService {
54634
54631
  games: [`read:${slug2}`, `write:${slug2}`]
54635
54632
  }
54636
54633
  });
54637
- try {
54638
- const existingSecrets = await this.ctx.providers.secrets.readSecrets(game.id) || {};
54639
- await this.ctx.providers.secrets.writeSecrets(game.id, {
54640
- ...existingSecrets,
54641
- PLAYCADEMY_API_KEY: apiKey
54642
- });
54643
- logger8.debug("Persisted API key to secrets", { gameId: game.id });
54644
- } catch (error) {
54645
- logger8.warn("Failed to persist API key to secrets", { error });
54646
- }
54647
54634
  logger8.info("Created new game-scoped API key", {
54648
54635
  userId: user.id,
54649
54636
  slug: slug2,
@@ -54651,56 +54638,55 @@ class DeployService {
54651
54638
  });
54652
54639
  return apiKey;
54653
54640
  }
54654
- async retrieveApiKeyFromSecrets(gameId) {
54655
- try {
54656
- const secrets = await this.ctx.providers.secrets.readSecrets(gameId);
54657
- if (secrets?.PLAYCADEMY_API_KEY) {
54658
- logger8.debug("Retrieved API key from secrets");
54659
- return secrets.PLAYCADEMY_API_KEY;
54660
- }
54661
- return null;
54662
- } catch (error) {
54663
- logger8.warn("Failed to retrieve secrets", { error });
54664
- return null;
54665
- }
54666
- }
54667
- async regenerateAndPersistApiKey(user, slug2, game, existingKeyId, keyName) {
54668
- logger8.info("Regenerating API key (migration)", {
54669
- userId: user.id,
54670
- slug: slug2,
54671
- oldKeyId: existingKeyId
54672
- });
54673
- try {
54674
- await this.ctx.providers.auth.deleteApiKey(existingKeyId);
54675
- logger8.debug("Revoked old API key", { keyId: existingKeyId });
54676
- } catch (error) {
54677
- logger8.warn("Failed to revoke old API key", {
54678
- keyId: existingKeyId,
54679
- error
54641
+ async regenerateApiKey(user, slug2, existingKeyId, keyName) {
54642
+ if (existingKeyId) {
54643
+ logger8.info("Regenerating API key", {
54644
+ userId: user.id,
54645
+ slug: slug2,
54646
+ oldKeyId: existingKeyId
54680
54647
  });
54648
+ try {
54649
+ await this.ctx.providers.auth.deleteApiKey(existingKeyId);
54650
+ logger8.debug("Revoked old API key", { keyId: existingKeyId });
54651
+ } catch (error) {
54652
+ logger8.warn("Failed to revoke old API key", {
54653
+ keyId: existingKeyId,
54654
+ error
54655
+ });
54656
+ }
54681
54657
  }
54682
- return this.createAndPersistApiKey(user, slug2, game, keyName);
54658
+ return this.createApiKey(user, slug2, keyName);
54683
54659
  }
54684
- async resolveApiKeyForDeployment(user, slug2, game, headers) {
54660
+ async ensureApiKeyOnWorker(user, slug2, deploymentId, headers) {
54661
+ const cf = this.getCloudflare();
54685
54662
  const keyName = getGameWorkerApiKeyName(slug2);
54686
54663
  const existingKeys = await this.ctx.providers.auth.listApiKeys(headers);
54687
54664
  const existingKey = existingKeys.find((k) => k.name === keyName);
54665
+ let apiKey;
54688
54666
  if (!existingKey) {
54689
- return this.createAndPersistApiKey(user, slug2, game, keyName);
54690
- }
54691
- const apiKey = await this.retrieveApiKeyFromSecrets(game.id);
54692
- if (apiKey) {
54693
- return apiKey;
54667
+ apiKey = await this.createApiKey(user, slug2, keyName);
54668
+ } else {
54669
+ try {
54670
+ const workerSecrets = await cf.listSecrets(deploymentId);
54671
+ if (workerSecrets.includes("PLAYCADEMY_API_KEY")) {
54672
+ logger8.debug("API key already on worker", { slug: slug2, deploymentId });
54673
+ return;
54674
+ }
54675
+ } catch (error) {
54676
+ logger8.warn("Could not check worker secrets, will regenerate key", { error });
54677
+ }
54678
+ apiKey = await this.regenerateApiKey(user, slug2, existingKey.id, keyName);
54694
54679
  }
54695
- return this.regenerateAndPersistApiKey(user, slug2, game, existingKey.id, keyName);
54680
+ await cf.setSecrets(deploymentId, { PLAYCADEMY_API_KEY: apiKey });
54681
+ logger8.info("Set API key on worker", { slug: slug2, deploymentId });
54696
54682
  }
54697
- async* deploy(slug2, request2, user, uploadDeps, extractZip) {
54683
+ async* deploy(slug2, request, user, uploadDeps, extractZip) {
54698
54684
  const cf = this.getCloudflare();
54699
54685
  const db2 = this.ctx.db;
54700
54686
  const game = await this.ctx.services.game.validateDeveloperAccessBySlug(user, slug2);
54701
- const hasBackend = !!request2.code;
54702
- const hasFrontend = !!request2.uploadToken;
54703
- const hasMetadata = !!request2.metadata;
54687
+ const hasBackend = !!request.code;
54688
+ const hasFrontend = !!request.uploadToken;
54689
+ const hasMetadata = !!request.metadata;
54704
54690
  logger8.debug("Deploying game", { slug: slug2, hasBackend, hasFrontend, hasMetadata });
54705
54691
  if (!hasBackend && !hasFrontend && !hasMetadata) {
54706
54692
  throw new ValidationError("Must provide at least one of: uploadToken (frontend), code (backend), or metadata");
@@ -54720,11 +54706,11 @@ class DeployService {
54720
54706
  throw new ValidationError("Upload dependencies not configured for frontend deployment");
54721
54707
  }
54722
54708
  yield { type: "status", data: { message: "Fetching temporary files" } };
54723
- const frontendZip = await uploadDeps.getObjectAsByteArray(request2.uploadToken);
54709
+ const frontendZip = await uploadDeps.getObjectAsByteArray(request.uploadToken);
54724
54710
  if (!frontendZip || frontendZip.length === 0) {
54725
54711
  logger8.error("Frontend upload empty or not found", {
54726
54712
  slug: slug2,
54727
- uploadToken: request2.uploadToken
54713
+ uploadToken: request.uploadToken
54728
54714
  });
54729
54715
  throw new ValidationError("Uploaded file is empty or not found");
54730
54716
  }
@@ -54734,23 +54720,21 @@ class DeployService {
54734
54720
  tempDir = path2.join(os2.tmpdir(), `playcademy-deploy-${game.id}-${Date.now()}`);
54735
54721
  frontendAssetsPath = path2.join(tempDir, "dist");
54736
54722
  await extractZip(frontendZip, frontendAssetsPath);
54737
- uploadDeps.deleteObject(request2.uploadToken).catch(() => {
54738
- logger8.warn("Failed to delete temp object", { key: request2.uploadToken });
54723
+ uploadDeps.deleteObject(request.uploadToken).catch(() => {
54724
+ logger8.warn("Failed to delete temp object", { key: request.uploadToken });
54739
54725
  });
54740
54726
  }
54741
54727
  const env = {
54742
54728
  GAME_ID: game.id,
54743
- PLAYCADEMY_BASE_URL: playcademyBaseUrl,
54744
- ...request2._gameApiKey && { PLAYCADEMY_API_KEY: request2._gameApiKey },
54745
- ...request2.secrets && { secrets: request2.secrets }
54729
+ PLAYCADEMY_BASE_URL: playcademyBaseUrl
54746
54730
  };
54747
54731
  const deployMsg = hasBackend ? "Deploying backend code" : "Deploying to platform";
54748
54732
  yield { type: "status", data: { message: deployMsg } };
54749
54733
  const keepAssets = hasBackend && !hasFrontend;
54750
- const deploymentOptions = this.mapBindingsToOptions(request2.bindings, request2.schema);
54734
+ const deploymentOptions = this.mapBindingsToOptions(request.bindings, request.schema);
54751
54735
  let result;
54752
54736
  try {
54753
- result = await cf.deploy(deploymentId, request2.code, env, {
54737
+ result = await cf.deploy(deploymentId, request.code, env, {
54754
54738
  ...deploymentOptions,
54755
54739
  assetsPath: frontendAssetsPath,
54756
54740
  keepAssets
@@ -54763,8 +54747,12 @@ class DeployService {
54763
54747
  });
54764
54748
  }
54765
54749
  }
54766
- const codeHash = hasBackend ? await generateDeploymentHash(request2.code) : null;
54750
+ const codeHash = hasBackend ? await generateDeploymentHash(request.code) : null;
54767
54751
  await this.saveDeployment(game.id, result.deploymentId, result.url, codeHash, result.resources);
54752
+ if (hasBackend && request._headers) {
54753
+ yield { type: "status", data: { message: "Configuring worker secrets" } };
54754
+ await this.ensureApiKeyOnWorker(user, slug2, result.deploymentId, request._headers);
54755
+ }
54768
54756
  yield { type: "status", data: { message: "Finalizing deployment" } };
54769
54757
  if (hasMetadata || hasFrontend) {
54770
54758
  const updates = { updatedAt: new Date };
@@ -54772,12 +54760,12 @@ class DeployService {
54772
54760
  updates.deploymentUrl = result.url;
54773
54761
  }
54774
54762
  if (hasMetadata) {
54775
- if (request2.metadata.displayName)
54776
- updates.displayName = request2.metadata.displayName;
54777
- if (request2.metadata.platform)
54778
- updates.platform = request2.metadata.platform;
54779
- if (request2.metadata.metadata)
54780
- updates.metadata = request2.metadata.metadata;
54763
+ if (request.metadata.displayName)
54764
+ updates.displayName = request.metadata.displayName;
54765
+ if (request.metadata.platform)
54766
+ updates.platform = request.metadata.platform;
54767
+ if (request.metadata.metadata)
54768
+ updates.metadata = request.metadata.metadata;
54781
54769
  }
54782
54770
  await db2.update(games).set(updates).where(eq(games.id, game.id));
54783
54771
  }
@@ -55245,12 +55233,6 @@ class GameService {
55245
55233
  } catch (keyError) {
55246
55234
  logger11.warn("Failed to cleanup API key", { gameId, error: keyError });
55247
55235
  }
55248
- try {
55249
- await this.ctx.providers.secrets.deleteSecrets(gameId);
55250
- logger11.info("Cleaned up secrets for deleted game", { gameId });
55251
- } catch (secretsError) {
55252
- logger11.warn("Failed to cleanup secrets", { gameId, error: secretsError });
55253
- }
55254
55236
  }
55255
55237
  return {
55256
55238
  slug: gameToDelete.slug,
@@ -56138,1087 +56120,67 @@ var init_level_service = __esm(() => {
56138
56120
  init_errors();
56139
56121
  logger15 = log.scope("LevelService");
56140
56122
  });
56141
- var JwtBaseError;
56142
- var FailedAssertionError;
56143
- var JwtParseError;
56144
- var ParameterValidationError;
56145
- var JwtInvalidSignatureError;
56146
- var JwtInvalidSignatureAlgorithmError;
56147
- var JwtInvalidClaimError;
56148
- var JwtInvalidIssuerError;
56149
- var JwtInvalidAudienceError;
56150
- var JwtInvalidScopeError;
56151
- var JwtExpiredError;
56152
- var JwtNotBeforeError;
56153
- var CognitoJwtInvalidGroupError;
56154
- var CognitoJwtInvalidTokenUseError;
56155
- var CognitoJwtInvalidClientIdError;
56156
- var JwksValidationError;
56157
- var JwkValidationError;
56158
- var JwtWithoutValidKidError;
56159
- var KidNotFoundInJwksError;
56160
- var WaitPeriodNotYetEndedJwkError;
56161
- var JwksNotAvailableInCacheError;
56162
- var JwkInvalidUseError;
56163
- var JwkInvalidKtyError;
56164
- var FetchError;
56165
- var NonRetryableFetchError;
56166
- var init_error = __esm(() => {
56167
- JwtBaseError = class JwtBaseError2 extends Error {
56168
- };
56169
- FailedAssertionError = class FailedAssertionError2 extends JwtBaseError {
56170
- constructor(msg, actual, expected) {
56171
- super(msg);
56172
- this.failedAssertion = {
56173
- actual,
56174
- expected
56175
- };
56176
- }
56177
- };
56178
- JwtParseError = class JwtParseError2 extends JwtBaseError {
56179
- constructor(msg, error) {
56180
- const message = error != null ? `${msg}: ${error}` : msg;
56181
- super(message);
56182
- }
56183
- };
56184
- ParameterValidationError = class ParameterValidationError2 extends JwtBaseError {
56185
- };
56186
- JwtInvalidSignatureError = class JwtInvalidSignatureError2 extends JwtBaseError {
56187
- };
56188
- JwtInvalidSignatureAlgorithmError = class JwtInvalidSignatureAlgorithmError2 extends FailedAssertionError {
56189
- };
56190
- JwtInvalidClaimError = class JwtInvalidClaimError2 extends FailedAssertionError {
56191
- withRawJwt({ header, payload }) {
56192
- this.rawJwt = {
56193
- header,
56194
- payload
56195
- };
56196
- return this;
56197
- }
56198
- };
56199
- JwtInvalidIssuerError = class JwtInvalidIssuerError2 extends JwtInvalidClaimError {
56200
- };
56201
- JwtInvalidAudienceError = class JwtInvalidAudienceError2 extends JwtInvalidClaimError {
56202
- };
56203
- JwtInvalidScopeError = class JwtInvalidScopeError2 extends JwtInvalidClaimError {
56204
- };
56205
- JwtExpiredError = class JwtExpiredError2 extends JwtInvalidClaimError {
56206
- };
56207
- JwtNotBeforeError = class JwtNotBeforeError2 extends JwtInvalidClaimError {
56208
- };
56209
- CognitoJwtInvalidGroupError = class CognitoJwtInvalidGroupError2 extends JwtInvalidClaimError {
56210
- };
56211
- CognitoJwtInvalidTokenUseError = class CognitoJwtInvalidTokenUseError2 extends JwtInvalidClaimError {
56212
- };
56213
- CognitoJwtInvalidClientIdError = class CognitoJwtInvalidClientIdError2 extends JwtInvalidClaimError {
56214
- };
56215
- JwksValidationError = class JwksValidationError2 extends JwtBaseError {
56216
- };
56217
- JwkValidationError = class JwkValidationError2 extends JwtBaseError {
56218
- };
56219
- JwtWithoutValidKidError = class JwtWithoutValidKidError2 extends JwtBaseError {
56220
- };
56221
- KidNotFoundInJwksError = class KidNotFoundInJwksError2 extends JwtBaseError {
56222
- };
56223
- WaitPeriodNotYetEndedJwkError = class WaitPeriodNotYetEndedJwkError2 extends JwtBaseError {
56224
- };
56225
- JwksNotAvailableInCacheError = class JwksNotAvailableInCacheError2 extends JwtBaseError {
56226
- };
56227
- JwkInvalidUseError = class JwkInvalidUseError2 extends FailedAssertionError {
56228
- };
56229
- JwkInvalidKtyError = class JwkInvalidKtyError2 extends FailedAssertionError {
56230
- };
56231
- FetchError = class FetchError2 extends JwtBaseError {
56232
- constructor(uri, msg) {
56233
- super(`Failed to fetch ${uri}: ${msg}`);
56234
- }
56235
- };
56236
- NonRetryableFetchError = class NonRetryableFetchError2 extends FetchError {
56237
- };
56238
- });
56239
- async function fetch2(uri, requestOptions, data) {
56240
- let responseTimeout;
56241
- return new Promise((resolve2, reject) => {
56242
- const req = request(uri, {
56243
- method: "GET",
56244
- ...requestOptions
56245
- }, (response) => {
56246
- if (response.statusCode !== 200) {
56247
- done(new NonRetryableFetchError(uri, `Status code is ${response.statusCode}, expected 200`));
56248
- return;
56249
- }
56250
- pipeline(response, async (responseBody) => {
56251
- const chunks = [];
56252
- for await (const chunk of responseBody) {
56253
- chunks.push(chunk);
56254
- }
56255
- return Buffer.concat(chunks);
56256
- }, done);
56257
- });
56258
- if (requestOptions?.responseTimeout) {
56259
- responseTimeout = setTimeout(() => done(new FetchError(uri, `Response time-out (after ${requestOptions.responseTimeout} ms.)`)), requestOptions.responseTimeout);
56260
- responseTimeout.unref();
56261
- }
56262
- function done(err2, data2) {
56263
- if (responseTimeout)
56264
- clearTimeout(responseTimeout);
56265
- if (err2 == null) {
56266
- resolve2(data2);
56267
- return;
56268
- }
56269
- req.socket?.emit("agentRemove");
56270
- if (!(err2 instanceof FetchError)) {
56271
- err2 = new FetchError(uri, err2.message);
56272
- }
56273
- req.destroy();
56274
- reject(err2);
56275
- }
56276
- req.on("error", done);
56277
- req.end(data);
56278
- });
56279
- }
56280
- var init_https_node = __esm(() => {
56281
- init_error();
56282
- });
56283
- var JwtSignatureAlgorithmHashNames;
56284
- var nodeWebCompat;
56285
- var init_node_web_compat_node = __esm(() => {
56286
- init_https_node();
56287
- (function(JwtSignatureAlgorithmHashNames2) {
56288
- JwtSignatureAlgorithmHashNames2["RS256"] = "RSA-SHA256";
56289
- JwtSignatureAlgorithmHashNames2["RS384"] = "RSA-SHA384";
56290
- JwtSignatureAlgorithmHashNames2["RS512"] = "RSA-SHA512";
56291
- JwtSignatureAlgorithmHashNames2["ES256"] = "RSA-SHA256";
56292
- JwtSignatureAlgorithmHashNames2["ES384"] = "RSA-SHA384";
56293
- JwtSignatureAlgorithmHashNames2["ES512"] = "RSA-SHA512";
56294
- })(JwtSignatureAlgorithmHashNames || (JwtSignatureAlgorithmHashNames = {}));
56295
- nodeWebCompat = {
56296
- fetch: fetch2,
56297
- transformJwkToKeyObjectSync: (jwk) => createPublicKey({
56298
- key: jwk,
56299
- format: "jwk"
56300
- }),
56301
- transformJwkToKeyObjectAsync: async (jwk) => createPublicKey({
56302
- key: jwk,
56303
- format: "jwk"
56304
- }),
56305
- parseB64UrlString: (b64) => Buffer.from(b64, "base64").toString("utf8"),
56306
- verifySignatureSync: ({ alg, keyObject, jwsSigningInput, signature }) => alg !== "EdDSA" ? createVerify(JwtSignatureAlgorithmHashNames[alg]).update(jwsSigningInput).verify({
56307
- key: keyObject,
56308
- dsaEncoding: "ieee-p1363"
56309
- }, signature, "base64") : verify(null, Buffer.from(jwsSigningInput), keyObject, Buffer.from(signature, "base64")),
56310
- verifySignatureAsync: async (args2) => nodeWebCompat.verifySignatureSync(args2),
56311
- defaultFetchTimeouts: {
56312
- socketIdle: 1500,
56313
- response: 3000
56314
- },
56315
- setTimeoutUnref: (...args2) => setTimeout(...args2).unref(),
56316
- transformPemToJwk: async (pem) => {
56317
- return createPublicKey({
56318
- key: Buffer.from(pem),
56319
- format: "pem"
56320
- }).export({
56321
- format: "jwk"
56322
- });
56323
- }
56324
- };
56325
- });
56326
-
56327
- class SimpleFetcher {
56328
- constructor(props) {
56329
- this.defaultRequestOptions = {
56330
- timeout: nodeWebCompat.defaultFetchTimeouts.socketIdle,
56331
- responseTimeout: nodeWebCompat.defaultFetchTimeouts.response,
56332
- ...props?.defaultRequestOptions
56333
- };
56334
- }
56335
- async fetch(uri, requestOptions, data) {
56336
- requestOptions = { ...this.defaultRequestOptions, ...requestOptions };
56337
- try {
56338
- return await fetch3(uri, requestOptions, data);
56339
- } catch (err2) {
56340
- if (err2 instanceof NonRetryableFetchError) {
56341
- throw err2;
56342
- }
56343
- return fetch3(uri, requestOptions, data);
56344
- }
56345
- }
56346
- }
56347
- var fetch3;
56348
- var init_https = __esm(() => {
56349
- init_error();
56350
- init_node_web_compat_node();
56351
- fetch3 = nodeWebCompat.fetch.bind(undefined);
56352
- });
56353
- function isJsonObject(j) {
56354
- return typeof j === "object" && !Array.isArray(j) && j !== null;
56355
- }
56356
- function safeJsonParse(s) {
56357
- return JSON.parse(s, (_2, value) => {
56358
- if (typeof value === "object" && !Array.isArray(value) && value !== null) {
56359
- delete value.__proto__;
56360
- delete value.constructor;
56361
- }
56362
- return value;
56363
- });
56364
- }
56365
- function assertStringEquals(name3, actual, expected, errorConstructor = FailedAssertionError) {
56366
- if (!actual) {
56367
- throw new errorConstructor(`Missing ${name3}. Expected: ${expected}`, actual, expected);
56368
- }
56369
- if (typeof actual !== "string") {
56370
- throw new errorConstructor(`${name3} is not of type string`, actual, expected);
56371
- }
56372
- if (expected !== actual) {
56373
- throw new errorConstructor(`${name3} not allowed: ${actual}. Expected: ${expected}`, actual, expected);
56374
- }
56375
- }
56376
- function assertStringArrayContainsString(name3, actual, expected, errorConstructor = FailedAssertionError) {
56377
- if (!actual) {
56378
- throw new errorConstructor(`Missing ${name3}. ${expectationMessage(expected)}`, actual, expected);
56379
- }
56380
- if (typeof actual !== "string") {
56381
- throw new errorConstructor(`${name3} is not of type string`, actual, expected);
56382
- }
56383
- return assertStringArraysOverlap(name3, actual, expected, errorConstructor);
56384
- }
56385
- function assertStringArraysOverlap(name3, actual, expected, errorConstructor = FailedAssertionError) {
56386
- if (!actual) {
56387
- throw new errorConstructor(`Missing ${name3}. ${expectationMessage(expected)}`, actual, expected);
56388
- }
56389
- const expectedAsSet = new Set(Array.isArray(expected) ? expected : [expected]);
56390
- if (typeof actual === "string") {
56391
- actual = [actual];
56392
- }
56393
- if (!Array.isArray(actual)) {
56394
- throw new errorConstructor(`${name3} is not an array`, actual, expected);
56395
- }
56396
- const overlaps = actual.some((actualItem) => {
56397
- if (typeof actualItem !== "string") {
56398
- throw new errorConstructor(`${name3} includes elements that are not of type string`, actual, expected);
56399
- }
56400
- return expectedAsSet.has(actualItem);
56401
- });
56402
- if (!overlaps) {
56403
- throw new errorConstructor(`${name3} not allowed: ${actual.join(", ")}. ${expectationMessage(expected)}`, actual, expected);
56404
- }
56405
- }
56406
- function expectationMessage(expected) {
56407
- if (Array.isArray(expected)) {
56408
- if (expected.length > 1) {
56409
- return `Expected one of: ${expected.join(", ")}`;
56410
- }
56411
- return `Expected: ${expected[0]}`;
56412
- }
56413
- return `Expected: ${expected}`;
56414
- }
56415
- function assertIsNotPromise(actual, errorFactory) {
56416
- if (actual && typeof actual.then === "function") {
56417
- throw errorFactory();
56418
- }
56419
- }
56420
- var init_assert = __esm(() => {
56421
- init_error();
56422
- });
56423
- function findJwkInJwks(jwks, kid) {
56424
- return jwks.keys.find((jwk) => jwk.kid != null && jwk.kid === kid);
56425
- }
56426
- function assertIsJwks(jwks) {
56427
- if (!jwks) {
56428
- throw new JwksValidationError("JWKS empty");
56429
- }
56430
- if (!isJsonObject(jwks)) {
56431
- throw new JwksValidationError("JWKS should be an object");
56432
- }
56433
- if (!Object.keys(jwks).includes("keys")) {
56434
- throw new JwksValidationError("JWKS does not include keys");
56435
- }
56436
- if (!Array.isArray(jwks.keys)) {
56437
- throw new JwksValidationError("JWKS keys should be an array");
56438
- }
56439
- for (const jwk of jwks.keys) {
56440
- assertIsJwk(jwk);
56441
- }
56442
- }
56443
- function assertIsSignatureJwk(jwk) {
56444
- assertStringArrayContainsString("JWK kty", jwk.kty, ["EC", "RSA", "OKP"], JwkInvalidKtyError);
56445
- if (jwk.kty === "EC") {
56446
- assertIsEsSignatureJwk(jwk);
56447
- } else if (jwk.kty === "RSA") {
56448
- assertIsRsaSignatureJwk(jwk);
56449
- } else if (jwk.kty === "OKP") {
56450
- assertIsEdDSASignatureJwk(jwk);
56451
- }
56452
- }
56453
- function assertIsEdDSASignatureJwk(jwk) {
56454
- if (jwk.use) {
56455
- assertStringEquals("JWK use", jwk.use, "sig", JwkInvalidUseError);
56456
- }
56457
- assertStringEquals("JWK kty", jwk.kty, "OKP", JwkInvalidKtyError);
56458
- if (!jwk.crv)
56459
- throw new JwkValidationError("Missing Curve (crv)");
56460
- if (!jwk.x)
56461
- throw new JwkValidationError("Missing X Coordinate (x)");
56462
- }
56463
- function assertIsEsSignatureJwk(jwk) {
56464
- if (jwk.use) {
56465
- assertStringEquals("JWK use", jwk.use, "sig", JwkInvalidUseError);
56466
- }
56467
- assertStringEquals("JWK kty", jwk.kty, "EC", JwkInvalidKtyError);
56468
- if (!jwk.crv)
56469
- throw new JwkValidationError("Missing Curve (crv)");
56470
- if (!jwk.x)
56471
- throw new JwkValidationError("Missing X Coordinate (x)");
56472
- if (!jwk.y)
56473
- throw new JwkValidationError("Missing Y Coordinate (y)");
56474
- }
56475
- function assertIsRsaSignatureJwk(jwk) {
56476
- if (jwk.use) {
56477
- assertStringEquals("JWK use", jwk.use, "sig", JwkInvalidUseError);
56478
- }
56479
- assertStringEquals("JWK kty", jwk.kty, "RSA", JwkInvalidKtyError);
56480
- if (!jwk.n)
56481
- throw new JwkValidationError("Missing modulus (n)");
56482
- if (!jwk.e)
56483
- throw new JwkValidationError("Missing exponent (e)");
56484
- }
56485
- function assertIsJwk(jwk) {
56486
- if (!jwk) {
56487
- throw new JwkValidationError("JWK empty");
56488
- }
56489
- if (!isJsonObject(jwk)) {
56490
- throw new JwkValidationError("JWK should be an object");
56491
- }
56492
- for (const field of mandatoryJwkFieldNames) {
56493
- if (typeof jwk[field] !== "string") {
56494
- throw new JwkValidationError(`JWK ${field} should be a string`);
56495
- }
56496
- }
56497
- for (const field of optionalJwkFieldNames) {
56498
- if (field in jwk && typeof jwk[field] !== "string") {
56499
- throw new JwkValidationError(`JWK ${field} should be a string`);
56500
- }
56501
- }
56502
- }
56503
- function isJwks(jwks) {
56504
- try {
56505
- assertIsJwks(jwks);
56506
- return true;
56507
- } catch {
56508
- return false;
56509
- }
56510
- }
56511
- function isJwk(jwk) {
56512
- try {
56513
- assertIsJwk(jwk);
56514
- return true;
56515
- } catch {
56516
- return false;
56517
- }
56518
- }
56519
-
56520
- class SimplePenaltyBox {
56521
- constructor(props) {
56522
- this.waitingUris = new Map;
56523
- this.waitSeconds = props?.waitSeconds ?? 10;
56524
- }
56525
- async wait(jwksUri) {
56526
- if (this.waitingUris.has(jwksUri)) {
56527
- throw new WaitPeriodNotYetEndedJwkError("Not allowed to fetch JWKS yet, still waiting for back off period to end");
56528
- }
56529
- }
56530
- release(jwksUri) {
56531
- const i2 = this.waitingUris.get(jwksUri);
56532
- if (i2) {
56533
- clearTimeout(i2);
56534
- this.waitingUris.delete(jwksUri);
56535
- }
56536
- }
56537
- registerFailedAttempt(jwksUri) {
56538
- const i2 = nodeWebCompat.setTimeoutUnref(() => {
56539
- this.waitingUris.delete(jwksUri);
56540
- }, this.waitSeconds * 1000);
56541
- this.waitingUris.set(jwksUri, i2);
56542
- }
56543
- registerSuccessfulAttempt(jwksUri) {
56544
- this.release(jwksUri);
56545
- }
56546
- }
56547
56123
 
56548
- class SimpleJwksCache {
56549
- constructor(props) {
56550
- this.jwksCache = new Map;
56551
- this.fetchingJwks = new Map;
56552
- this.penaltyBox = props?.penaltyBox ?? new SimplePenaltyBox;
56553
- this.fetcher = props?.fetcher ?? new SimpleFetcher;
56554
- this.jwksParser = props?.jwksParser ?? parseJwks;
56555
- }
56556
- addJwks(jwksUri, jwks) {
56557
- this.jwksCache.set(jwksUri, jwks);
56558
- }
56559
- async getJwks(jwksUri) {
56560
- const existingFetch = this.fetchingJwks.get(jwksUri);
56561
- if (existingFetch) {
56562
- return existingFetch;
56563
- }
56564
- const jwksPromise = this.fetcher.fetch(jwksUri).then(this.jwksParser);
56565
- this.fetchingJwks.set(jwksUri, jwksPromise);
56566
- let jwks;
56567
- try {
56568
- jwks = await jwksPromise;
56569
- } finally {
56570
- this.fetchingJwks.delete(jwksUri);
56571
- }
56572
- this.jwksCache.set(jwksUri, jwks);
56573
- return jwks;
56574
- }
56575
- getCachedJwk(jwksUri, decomposedJwt) {
56576
- if (typeof decomposedJwt.header.kid !== "string") {
56577
- throw new JwtWithoutValidKidError("JWT header does not have valid kid claim");
56578
- }
56579
- if (!this.jwksCache.has(jwksUri)) {
56580
- throw new JwksNotAvailableInCacheError(`JWKS for uri ${jwksUri} not yet available in cache`);
56581
- }
56582
- const jwk = findJwkInJwks(this.jwksCache.get(jwksUri), decomposedJwt.header.kid);
56583
- if (!jwk) {
56584
- throw new KidNotFoundInJwksError(`JWK for kid ${decomposedJwt.header.kid} not found in the JWKS`);
56585
- }
56586
- return jwk;
56587
- }
56588
- async getJwk(jwksUri, decomposedJwt) {
56589
- if (typeof decomposedJwt.header.kid !== "string") {
56590
- throw new JwtWithoutValidKidError("JWT header does not have valid kid claim");
56591
- }
56592
- const cachedJwks = this.jwksCache.get(jwksUri);
56593
- if (cachedJwks) {
56594
- const cachedJwk = findJwkInJwks(cachedJwks, decomposedJwt.header.kid);
56595
- if (cachedJwk) {
56596
- return cachedJwk;
56597
- }
56598
- }
56599
- await this.penaltyBox.wait(jwksUri, decomposedJwt.header.kid);
56600
- const jwks = await this.getJwks(jwksUri);
56601
- const jwk = findJwkInJwks(jwks, decomposedJwt.header.kid);
56602
- if (!jwk) {
56603
- this.penaltyBox.registerFailedAttempt(jwksUri, decomposedJwt.header.kid);
56604
- throw new KidNotFoundInJwksError(`JWK for kid "${decomposedJwt.header.kid}" not found in the JWKS`);
56605
- } else {
56606
- this.penaltyBox.registerSuccessfulAttempt(jwksUri, decomposedJwt.header.kid);
56607
- }
56608
- return jwk;
56609
- }
56610
- }
56611
- var optionalJwkFieldNames;
56612
- var mandatoryJwkFieldNames;
56613
- var parseJwks = function(jwksBin) {
56614
- let jwks;
56615
- try {
56616
- const jwksText = new TextDecoder("utf8", {
56617
- fatal: true,
56618
- ignoreBOM: true
56619
- }).decode(jwksBin);
56620
- jwks = safeJsonParse(jwksText);
56621
- } catch (err2) {
56622
- throw new JwksValidationError(`JWKS could not be parsed as JSON: ${err2}`);
56623
- }
56624
- assertIsJwks(jwks);
56625
- return jwks;
56626
- };
56627
- var init_jwk = __esm(() => {
56628
- init_https();
56629
- init_error();
56630
- init_node_web_compat_node();
56631
- init_assert();
56632
- optionalJwkFieldNames = [
56633
- "use",
56634
- "alg",
56635
- "kid",
56636
- "n",
56637
- "e",
56638
- "x",
56639
- "y",
56640
- "crv"
56641
- ];
56642
- mandatoryJwkFieldNames = [
56643
- "kty"
56644
- ];
56645
- });
56646
- function assertJwtHeader(header) {
56647
- if (!isJsonObject(header)) {
56648
- throw new JwtParseError("JWT header is not an object");
56649
- }
56650
- if (header.alg !== undefined && typeof header.alg !== "string") {
56651
- throw new JwtParseError("JWT header alg claim is not a string");
56652
- }
56653
- if (header.kid !== undefined && typeof header.kid !== "string") {
56654
- throw new JwtParseError("JWT header kid claim is not a string");
56655
- }
56656
- }
56657
- function assertJwtPayload(payload) {
56658
- if (!isJsonObject(payload)) {
56659
- throw new JwtParseError("JWT payload is not an object");
56660
- }
56661
- if (payload.exp !== undefined && !Number.isFinite(payload.exp)) {
56662
- throw new JwtParseError("JWT payload exp claim is not a number");
56663
- }
56664
- if (payload.iss !== undefined && typeof payload.iss !== "string") {
56665
- throw new JwtParseError("JWT payload iss claim is not a string");
56666
- }
56667
- if (payload.sub !== undefined && typeof payload.sub !== "string") {
56668
- throw new JwtParseError("JWT payload sub claim is not a string");
56669
- }
56670
- if (payload.aud !== undefined && typeof payload.aud !== "string" && (!Array.isArray(payload.aud) || payload.aud.some((aud) => typeof aud !== "string"))) {
56671
- throw new JwtParseError("JWT payload aud claim is not a string or array of strings");
56672
- }
56673
- if (payload.nbf !== undefined && !Number.isFinite(payload.nbf)) {
56674
- throw new JwtParseError("JWT payload nbf claim is not a number");
56675
- }
56676
- if (payload.iat !== undefined && !Number.isFinite(payload.iat)) {
56677
- throw new JwtParseError("JWT payload iat claim is not a number");
56678
- }
56679
- if (payload.scope !== undefined && typeof payload.scope !== "string") {
56680
- throw new JwtParseError("JWT payload scope claim is not a string");
56681
- }
56682
- if (payload.jti !== undefined && typeof payload.jti !== "string") {
56683
- throw new JwtParseError("JWT payload jti claim is not a string");
56684
- }
56685
- }
56686
- function decomposeUnverifiedJwt(jwt) {
56687
- if (!jwt) {
56688
- throw new JwtParseError("Empty JWT");
56689
- }
56690
- if (typeof jwt !== "string") {
56691
- throw new JwtParseError("JWT is not a string");
56692
- }
56693
- if (!JWT_REGEX.test(jwt)) {
56694
- throw new JwtParseError("JWT string does not consist of exactly 3 parts (header, payload, signature)");
56695
- }
56696
- const [headerB64, payloadB64, signatureB64] = jwt.split(".");
56697
- const [headerString, payloadString] = [headerB64, payloadB64].map(nodeWebCompat.parseB64UrlString);
56698
- let header;
56699
- try {
56700
- header = safeJsonParse(headerString);
56701
- } catch (err2) {
56702
- throw new JwtParseError("Invalid JWT. Header is not a valid JSON object", err2);
56703
- }
56704
- assertJwtHeader(header);
56705
- let payload;
56706
- try {
56707
- payload = safeJsonParse(payloadString);
56708
- } catch (err2) {
56709
- throw new JwtParseError("Invalid JWT. Payload is not a valid JSON object", err2);
56710
- }
56711
- assertJwtPayload(payload);
56712
- return {
56713
- header,
56714
- headerB64,
56715
- payload,
56716
- payloadB64,
56717
- signatureB64
56718
- };
56719
- }
56720
- function validateJwtFields(payload, options) {
56721
- if (payload.exp !== undefined) {
56722
- if (payload.exp + (options.graceSeconds ?? 0) < Date.now() / 1000) {
56723
- throw new JwtExpiredError(`Token expired at ${new Date(payload.exp * 1000).toISOString()}`, payload.exp);
56724
- }
56725
- }
56726
- if (payload.nbf !== undefined) {
56727
- if (payload.nbf - (options.graceSeconds ?? 0) > Date.now() / 1000) {
56728
- throw new JwtNotBeforeError(`Token can't be used before ${new Date(payload.nbf * 1000).toISOString()}`, payload.nbf);
56729
- }
56730
- }
56731
- if (options.issuer !== null) {
56732
- if (options.issuer === undefined) {
56733
- throw new ParameterValidationError("issuer must be provided or set to null explicitly");
56734
- }
56735
- assertStringArrayContainsString("Issuer", payload.iss, options.issuer, JwtInvalidIssuerError);
56736
- }
56737
- if (options.audience !== null) {
56738
- if (options.audience === undefined) {
56739
- throw new ParameterValidationError("audience must be provided or set to null explicitly");
56740
- }
56741
- assertStringArraysOverlap("Audience", payload.aud, options.audience, JwtInvalidAudienceError);
56742
- }
56743
- if (options.scope != null) {
56744
- assertStringArraysOverlap("Scope", payload.scope?.split(" "), options.scope, JwtInvalidScopeError);
56745
- }
56746
- }
56747
- var JWT_REGEX;
56748
- var init_jwt = __esm(() => {
56749
- init_assert();
56750
- init_error();
56751
- init_node_web_compat_node();
56752
- JWT_REGEX = /^[A-Za-z0-9_-]+={0,2}\.[A-Za-z0-9_-]+={0,2}\.[A-Za-z0-9_-]+={0,2}$/;
56753
- });
56754
- function validateJwtHeaderAndJwk(header, jwk) {
56755
- assertIsSignatureJwk(jwk);
56756
- if (jwk.alg) {
56757
- assertStringEquals("JWT signature algorithm", header.alg, jwk.alg, JwtInvalidSignatureAlgorithmError);
56758
- }
56759
- assertStringArrayContainsString("JWT signature algorithm", header.alg, supportedSignatureAlgorithms, JwtInvalidSignatureAlgorithmError);
56760
- }
56761
- async function verifyDecomposedJwt(decomposedJwt, jwksUri, options, jwkFetcher, transformJwkToKeyObjectFn) {
56762
- const { header, headerB64, payload, payloadB64, signatureB64 } = decomposedJwt;
56763
- const jwk = await jwkFetcher(jwksUri, decomposedJwt);
56764
- validateJwtHeaderAndJwk(decomposedJwt.header, jwk);
56765
- const keyObject = await transformJwkToKeyObjectFn(jwk, header.alg, payload.iss);
56766
- const valid = await nodeWebCompat.verifySignatureAsync({
56767
- jwsSigningInput: `${headerB64}.${payloadB64}`,
56768
- signature: signatureB64,
56769
- alg: header.alg,
56770
- keyObject
56771
- });
56772
- if (!valid) {
56773
- throw new JwtInvalidSignatureError("Invalid signature");
56774
- }
56775
- try {
56776
- validateJwtFields(payload, options);
56777
- if (options.customJwtCheck) {
56778
- await options.customJwtCheck({ header, payload, jwk });
56779
- }
56780
- } catch (err2) {
56781
- if (options.includeRawJwtInErrors && err2 instanceof JwtInvalidClaimError) {
56782
- throw err2.withRawJwt(decomposedJwt);
56783
- }
56784
- throw err2;
56785
- }
56786
- return payload;
56787
- }
56788
- function verifyDecomposedJwtSync(decomposedJwt, jwkOrJwks, options, transformJwkToKeyObjectFn) {
56789
- const { header, headerB64, payload, payloadB64, signatureB64 } = decomposedJwt;
56790
- let jwk;
56791
- if (isJwk(jwkOrJwks)) {
56792
- jwk = jwkOrJwks;
56793
- } else if (isJwks(jwkOrJwks)) {
56794
- const locatedJwk = header.kid ? findJwkInJwks(jwkOrJwks, header.kid) : undefined;
56795
- if (!locatedJwk) {
56796
- throw new KidNotFoundInJwksError(`JWK for kid ${header.kid} not found in the JWKS`);
56797
- }
56798
- jwk = locatedJwk;
56799
- } else {
56800
- throw new ParameterValidationError([
56801
- `Expected a valid JWK or JWKS (parsed as JavaScript object), but received: ${jwkOrJwks}.`,
56802
- "If you're passing a JWKS URI, use the async verify() method instead, it will download and parse the JWKS for you"
56803
- ].join());
56804
- }
56805
- validateJwtHeaderAndJwk(decomposedJwt.header, jwk);
56806
- const keyObject = transformJwkToKeyObjectFn(jwk, header.alg, payload.iss);
56807
- const valid = nodeWebCompat.verifySignatureSync({
56808
- jwsSigningInput: `${headerB64}.${payloadB64}`,
56809
- signature: signatureB64,
56810
- alg: header.alg,
56811
- keyObject
56812
- });
56813
- if (!valid) {
56814
- throw new JwtInvalidSignatureError("Invalid signature");
56815
- }
56816
- try {
56817
- validateJwtFields(payload, options);
56818
- if (options.customJwtCheck) {
56819
- const res = options.customJwtCheck({ header, payload, jwk });
56820
- assertIsNotPromise(res, () => new ParameterValidationError("Custom JWT checks must be synchronous but a promise was returned"));
56821
- }
56822
- } catch (err2) {
56823
- if (options.includeRawJwtInErrors && err2 instanceof JwtInvalidClaimError) {
56824
- throw err2.withRawJwt(decomposedJwt);
56825
- }
56826
- throw err2;
56124
+ class LogsService {
56125
+ ctx;
56126
+ constructor(ctx) {
56127
+ this.ctx = ctx;
56827
56128
  }
56828
- return payload;
56829
- }
56830
-
56831
- class JwtVerifierBase {
56832
- constructor(verifyProperties, jwksCache = new SimpleJwksCache) {
56833
- this.jwksCache = jwksCache;
56834
- this.issuersConfig = new Map;
56835
- this.publicKeyCache = new KeyObjectCache;
56836
- if (Array.isArray(verifyProperties)) {
56837
- if (!verifyProperties.length) {
56838
- throw new ParameterValidationError("Provide at least one issuer configuration");
56839
- }
56840
- verifyProperties.forEach((prop, index2) => {
56841
- if (this.issuersConfig.has(prop.issuer)) {
56842
- throw new ParameterValidationError(`issuer ${prop.issuer} supplied multiple times`);
56843
- } else if (prop.issuer === null && verifyProperties.length >= 2) {
56844
- throw new ParameterValidationError(`issuer cannot be null when multiple issuers are supplied (at issuer: ${index2})`);
56845
- }
56846
- this.issuersConfig.set(prop.issuer, this.withJwksUri(prop));
56129
+ async generateToken(user, slug2, environment) {
56130
+ const db2 = this.ctx.db;
56131
+ if (user.role === "admin") {
56132
+ const game = await db2.query.games.findFirst({
56133
+ where: eq(games.slug, slug2),
56134
+ columns: { id: true }
56847
56135
  });
56848
- } else {
56849
- this.issuersConfig.set(verifyProperties.issuer, this.withJwksUri(verifyProperties));
56850
- }
56851
- }
56852
- getIssuerConfig(issuer) {
56853
- if (this.issuersConfig.size === 1) {
56854
- issuer = this.issuersConfig.keys().next().value;
56855
- }
56856
- if (issuer === undefined) {
56857
- throw new ParameterValidationError("issuer must be provided");
56858
- }
56859
- const config2 = this.issuersConfig.get(issuer);
56860
- if (!config2) {
56861
- throw new ParameterValidationError(`issuer not configured: ${issuer}`);
56862
- }
56863
- return config2;
56864
- }
56865
- cacheJwks(...[jwks, issuer]) {
56866
- const issuerConfig = this.getIssuerConfig(issuer);
56867
- this.jwksCache.addJwks(issuerConfig.jwksUri, jwks);
56868
- this.publicKeyCache.clearCache(issuerConfig.issuer);
56869
- }
56870
- async hydrate() {
56871
- const jwksFetches = Array.from(this.issuersConfig.values()).map(({ jwksUri }) => this.jwksCache.getJwks(jwksUri));
56872
- await Promise.all(jwksFetches);
56873
- }
56874
- verifySync(...[jwt, properties]) {
56875
- const { decomposedJwt, jwksUri, verifyProperties } = this.getVerifyParameters(jwt, properties);
56876
- return this.verifyDecomposedJwtSync(decomposedJwt, jwksUri, verifyProperties);
56877
- }
56878
- verifyDecomposedJwtSync(decomposedJwt, jwksUri, verifyProperties) {
56879
- const jwk = this.jwksCache.getCachedJwk(jwksUri, decomposedJwt);
56880
- return verifyDecomposedJwtSync(decomposedJwt, jwk, verifyProperties, this.publicKeyCache.transformJwkToKeyObjectSync.bind(this.publicKeyCache));
56881
- }
56882
- async verify(...[jwt, properties]) {
56883
- const { decomposedJwt, jwksUri, verifyProperties } = this.getVerifyParameters(jwt, properties);
56884
- return this.verifyDecomposedJwt(decomposedJwt, jwksUri, verifyProperties);
56885
- }
56886
- verifyDecomposedJwt(decomposedJwt, jwksUri, verifyProperties) {
56887
- return verifyDecomposedJwt(decomposedJwt, jwksUri, verifyProperties, this.jwksCache.getJwk.bind(this.jwksCache), this.publicKeyCache.transformJwkToKeyObjectAsync.bind(this.publicKeyCache));
56888
- }
56889
- getVerifyParameters(jwt, verifyProperties) {
56890
- const decomposedJwt = decomposeUnverifiedJwt(jwt);
56891
- const issuerConfig = this.getIssuerConfig(decomposedJwt.payload.iss);
56892
- return {
56893
- decomposedJwt,
56894
- jwksUri: issuerConfig.jwksUri,
56895
- verifyProperties: {
56896
- ...issuerConfig,
56897
- ...verifyProperties
56136
+ if (!game) {
56137
+ throw new NotFoundError("Game", slug2);
56898
56138
  }
56899
- };
56900
- }
56901
- withJwksUri(config2) {
56902
- if (config2.jwksUri) {
56903
- return config2;
56904
- }
56905
- const issuer = config2.issuer;
56906
- if (!issuer) {
56907
- throw new ParameterValidationError("jwksUri must be provided for issuer null");
56908
- }
56909
- const issuerUri = new URL(issuer).pathname.replace(/\/$/, "");
56910
- return {
56911
- jwksUri: new URL(`${issuerUri}/.well-known/jwks.json`, issuer).href,
56912
- ...config2
56913
- };
56914
- }
56915
- }
56916
-
56917
- class KeyObjectCache {
56918
- constructor(transformJwkToKeyObjectSyncFn = nodeWebCompat.transformJwkToKeyObjectSync, transformJwkToKeyObjectAsyncFn = nodeWebCompat.transformJwkToKeyObjectAsync) {
56919
- this.transformJwkToKeyObjectSyncFn = transformJwkToKeyObjectSyncFn;
56920
- this.transformJwkToKeyObjectAsyncFn = transformJwkToKeyObjectAsyncFn;
56921
- this.publicKeys = new Map;
56922
- }
56923
- transformJwkToKeyObjectSync(jwk, jwtHeaderAlg, issuer) {
56924
- const alg = jwk.alg ?? jwtHeaderAlg;
56925
- if (!issuer || !jwk.kid || !alg) {
56926
- return this.transformJwkToKeyObjectSyncFn(jwk, alg, issuer);
56927
- }
56928
- const fromCache = this.publicKeys.get(issuer)?.get(jwk.kid)?.get(alg);
56929
- if (fromCache)
56930
- return fromCache;
56931
- const publicKey = this.transformJwkToKeyObjectSyncFn(jwk, alg, issuer);
56932
- this.putKeyObjectInCache(issuer, jwk.kid, alg, publicKey);
56933
- return publicKey;
56934
- }
56935
- async transformJwkToKeyObjectAsync(jwk, jwtHeaderAlg, issuer) {
56936
- const alg = jwk.alg ?? jwtHeaderAlg;
56937
- if (!issuer || !jwk.kid || !alg) {
56938
- return this.transformJwkToKeyObjectAsyncFn(jwk, alg, issuer);
56939
- }
56940
- const fromCache = this.publicKeys.get(issuer)?.get(jwk.kid)?.get(alg);
56941
- if (fromCache)
56942
- return fromCache;
56943
- const publicKey = await this.transformJwkToKeyObjectAsyncFn(jwk, alg, issuer);
56944
- this.putKeyObjectInCache(issuer, jwk.kid, alg, publicKey);
56945
- return publicKey;
56946
- }
56947
- putKeyObjectInCache(issuer, kid, alg, publicKey) {
56948
- const cachedIssuer = this.publicKeys.get(issuer);
56949
- const cachedIssuerKid = cachedIssuer?.get(kid);
56950
- if (cachedIssuerKid) {
56951
- cachedIssuerKid.set(alg, publicKey);
56952
- } else if (cachedIssuer) {
56953
- cachedIssuer.set(kid, new Map([[alg, publicKey]]));
56139
+ logger16.info("Admin accessing game logs", { adminId: user.id, slug: slug2, environment });
56954
56140
  } else {
56955
- this.publicKeys.set(issuer, new Map([[kid, new Map([[alg, publicKey]])]]));
56956
- }
56957
- }
56958
- clearCache(issuer) {
56959
- this.publicKeys.delete(issuer);
56960
- }
56961
- }
56962
- var supportedSignatureAlgorithms;
56963
- var JwtVerifier;
56964
- var init_jwt_verifier = __esm(() => {
56965
- init_jwk();
56966
- init_assert();
56967
- init_jwt();
56968
- init_error();
56969
- init_node_web_compat_node();
56970
- supportedSignatureAlgorithms = [
56971
- "RS256",
56972
- "RS384",
56973
- "RS512",
56974
- "ES256",
56975
- "ES384",
56976
- "ES512",
56977
- "EdDSA"
56978
- ];
56979
- JwtVerifier = class JwtVerifier2 extends JwtVerifierBase {
56980
- static create(verifyProperties, additionalProperties) {
56981
- return new this(verifyProperties, additionalProperties?.jwksCache);
56982
- }
56983
- };
56984
- });
56985
- function validateCognitoJwtFields(payload, options) {
56986
- if (options.groups != null) {
56987
- assertStringArraysOverlap("Cognito group", payload["cognito:groups"], options.groups, CognitoJwtInvalidGroupError);
56988
- }
56989
- assertStringArrayContainsString("Token use", payload.token_use, ["id", "access"], CognitoJwtInvalidTokenUseError);
56990
- if (options.tokenUse !== null) {
56991
- if (options.tokenUse === undefined) {
56992
- throw new ParameterValidationError("tokenUse must be provided or set to null explicitly");
56993
- }
56994
- assertStringEquals("Token use", payload.token_use, options.tokenUse, CognitoJwtInvalidTokenUseError);
56995
- }
56996
- if (options.clientId !== null) {
56997
- if (options.clientId === undefined) {
56998
- throw new ParameterValidationError("clientId must be provided or set to null explicitly");
56999
- }
57000
- if (payload.token_use === "id") {
57001
- assertStringArrayContainsString('Client ID ("audience")', payload.aud, options.clientId, CognitoJwtInvalidClientIdError);
57002
- } else {
57003
- assertStringArrayContainsString("Client ID", payload.client_id, options.clientId, CognitoJwtInvalidClientIdError);
57004
- }
57005
- }
57006
- }
57007
- var CognitoJwtVerifier;
57008
- var init_cognito_verifier = __esm(() => {
57009
- init_error();
57010
- init_jwt_verifier();
57011
- init_assert();
57012
- CognitoJwtVerifier = class CognitoJwtVerifier2 extends JwtVerifierBase {
57013
- constructor(props, jwksCache) {
57014
- const issuerConfig = Array.isArray(props) ? props.map((p) => ({
57015
- ...p,
57016
- ...CognitoJwtVerifier2.parseUserPoolId(p.userPoolId),
57017
- audience: null
57018
- })) : {
57019
- ...props,
57020
- ...CognitoJwtVerifier2.parseUserPoolId(props.userPoolId),
57021
- audience: null
57022
- };
57023
- super(issuerConfig, jwksCache);
57024
- }
57025
- static parseUserPoolId(userPoolId) {
57026
- const match = userPoolId.match(this.USER_POOL_ID_REGEX);
57027
- if (!match) {
57028
- throw new ParameterValidationError(`Invalid Cognito User Pool ID: ${userPoolId}`);
57029
- }
57030
- const region = match.groups.region;
57031
- const issuer = `https://cognito-idp.${region}.amazonaws.com/${userPoolId}`;
57032
- return {
57033
- issuer,
57034
- jwksUri: `${issuer}/.well-known/jwks.json`
57035
- };
57036
- }
57037
- static create(verifyProperties, additionalProperties) {
57038
- return new this(verifyProperties, additionalProperties?.jwksCache);
57039
- }
57040
- verifySync(...[jwt, properties]) {
57041
- const { decomposedJwt, jwksUri, verifyProperties } = this.getVerifyParameters(jwt, properties);
57042
- this.verifyDecomposedJwtSync(decomposedJwt, jwksUri, verifyProperties);
57043
- try {
57044
- validateCognitoJwtFields(decomposedJwt.payload, verifyProperties);
57045
- } catch (err2) {
57046
- if (verifyProperties.includeRawJwtInErrors && err2 instanceof JwtInvalidClaimError) {
57047
- throw err2.withRawJwt(decomposedJwt);
57048
- }
57049
- throw err2;
57050
- }
57051
- return decomposedJwt.payload;
57052
- }
57053
- async verify(...[jwt, properties]) {
57054
- const { decomposedJwt, jwksUri, verifyProperties } = this.getVerifyParameters(jwt, properties);
57055
- await this.verifyDecomposedJwt(decomposedJwt, jwksUri, verifyProperties);
57056
- try {
57057
- validateCognitoJwtFields(decomposedJwt.payload, verifyProperties);
57058
- } catch (err2) {
57059
- if (verifyProperties.includeRawJwtInErrors && err2 instanceof JwtInvalidClaimError) {
57060
- throw err2.withRawJwt(decomposedJwt);
57061
- }
57062
- throw err2;
56141
+ const isApprovedDev = user.developerStatus === "approved";
56142
+ if (!isApprovedDev) {
56143
+ logger16.warn("Unapproved developer attempted log access", { userId: user.id, slug: slug2 });
56144
+ throw new AccessDeniedError("Must be an approved developer");
57063
56145
  }
57064
- return decomposedJwt.payload;
57065
- }
57066
- cacheJwks(...[jwks, userPoolId]) {
57067
- let issuer;
57068
- if (userPoolId !== undefined) {
57069
- issuer = CognitoJwtVerifier2.parseUserPoolId(userPoolId).issuer;
57070
- } else if (Array.from(this.issuersConfig).length > 1) {
57071
- throw new ParameterValidationError("userPoolId must be provided");
56146
+ const game = await db2.query.games.findFirst({
56147
+ where: and(eq(games.slug, slug2), eq(games.developerId, user.id)),
56148
+ columns: { id: true }
56149
+ });
56150
+ if (!game) {
56151
+ logger16.warn("Developer attempted access to unowned game logs", {
56152
+ userId: user.id,
56153
+ slug: slug2
56154
+ });
56155
+ throw new NotFoundError("Game", slug2);
57072
56156
  }
57073
- const issuerConfig = this.getIssuerConfig(issuer);
57074
- super.cacheJwks(jwks, issuerConfig.issuer);
57075
- }
57076
- };
57077
- CognitoJwtVerifier.USER_POOL_ID_REGEX = /^(?<region>[a-z]{2}-(gov-)?[a-z]+-\d)_[a-zA-Z0-9]+$/;
57078
- });
57079
- var init_alb_cache = __esm(() => {
57080
- init_error();
57081
- init_https();
57082
- init_node_web_compat_node();
57083
- });
57084
- var init_alb_verifier = __esm(() => {
57085
- init_alb_cache();
57086
- init_assert();
57087
- init_error();
57088
- init_jwt_verifier();
57089
- });
57090
- var init_esm2 = __esm(() => {
57091
- init_jwt_verifier();
57092
- init_cognito_verifier();
57093
- init_alb_verifier();
57094
- init_jwt_verifier();
57095
- });
57096
- function generateUsername(email) {
57097
- const baseUsername = (email.split("@")[0] || "user").toLowerCase();
57098
- const cleanUsername = baseUsername.replace(/[^a-z0-9]/g, "");
57099
- const randomSuffix = Math.random().toString(36).substring(2, 7);
57100
- return `${cleanUsername}_${randomSuffix}`;
57101
- }
57102
- function extractRedirectPath(targetUri, currentHost) {
57103
- try {
57104
- const targetUrl = new URL(targetUri);
57105
- if (targetUrl.hostname === currentHost) {
57106
- return targetUrl.pathname + targetUrl.search;
57107
56157
  }
57108
- } catch {}
57109
- return "/";
57110
- }
57111
- function getLtiRoles(claims) {
57112
- return claims["https://purl.imsglobal.org/spec/lti/claim/roles"] || [];
57113
- }
57114
- function hasLtiRole(claims, role) {
57115
- return getLtiRoles(claims).some((r) => r.includes(role));
57116
- }
57117
- function validateLtiClaims(claims) {
57118
- const messageType = claims["https://purl.imsglobal.org/spec/lti/claim/message_type"];
57119
- const version2 = claims["https://purl.imsglobal.org/spec/lti/claim/version"];
57120
- if (messageType !== "LtiResourceLinkRequest") {
57121
- return `Invalid LTI message type: ${messageType}`;
57122
- }
57123
- if (version2 !== "1.3.0") {
57124
- return `Unsupported LTI version: ${version2}`;
56158
+ const isProduction3 = environment === "production";
56159
+ const workerId = getDeploymentId(slug2, isProduction3);
56160
+ const token = await this.ctx.providers.auth.mintLogStreamToken(user.id, workerId);
56161
+ logger16.debug("Generated log stream token", {
56162
+ userId: user.id,
56163
+ slug: slug2,
56164
+ workerId
56165
+ });
56166
+ return { token, workerId };
57125
56167
  }
57126
- return null;
57127
56168
  }
57128
- var LtiRoleChecks;
57129
- var init_lti_util = __esm(() => {
57130
- LtiRoleChecks = {
57131
- isLearner: (claims) => hasLtiRole(claims, "Learner"),
57132
- isInstructor: (claims) => hasLtiRole(claims, "Instructor"),
57133
- isAdministrator: (claims) => hasLtiRole(claims, "Administrator"),
57134
- isContentDeveloper: (claims) => hasLtiRole(claims, "ContentDeveloper"),
57135
- isMentor: (claims) => hasLtiRole(claims, "Mentor")
57136
- };
56169
+ var logger16;
56170
+ var init_logs_service = __esm(() => {
56171
+ init_drizzle_orm();
56172
+ init_tables_index();
56173
+ init_src2();
56174
+ init_errors();
56175
+ init_deployment_util();
56176
+ logger16 = log.scope("LogsService");
57137
56177
  });
57138
56178
 
57139
56179
  class LtiService {
57140
56180
  ctx;
57141
- verifier = null;
57142
56181
  constructor(ctx) {
57143
56182
  this.ctx = ctx;
57144
56183
  }
57145
- getConfig() {
57146
- if (!this.ctx.config.lti) {
57147
- logger16.error("LTI configuration not available");
57148
- throw new ValidationError("LTI is not configured");
57149
- }
57150
- return this.ctx.config.lti;
57151
- }
57152
- getVerifier() {
57153
- if (!this.verifier) {
57154
- const lti = this.getConfig();
57155
- this.verifier = JwtVerifier.create({
57156
- issuer: lti.issuer,
57157
- audience: lti.audience,
57158
- jwksUri: lti.jwksUrl
57159
- });
57160
- }
57161
- return this.verifier;
57162
- }
57163
- async verifyToken(idToken) {
57164
- if (this.ctx.config.ltiTestMode) {
57165
- if (!idToken.startsWith("mock:")) {
57166
- throw new ValidationError("Invalid LTI token");
57167
- }
57168
- try {
57169
- const jsonStr = Buffer.from(idToken.slice(5), "base64").toString();
57170
- return JSON.parse(jsonStr);
57171
- } catch {
57172
- throw new ValidationError("Invalid LTI token format");
57173
- }
57174
- }
57175
- try {
57176
- const verifier = this.getVerifier();
57177
- const claims = await verifier.verify(idToken);
57178
- logger16.info("Verified token", {
57179
- sub: claims.sub,
57180
- email: claims.email,
57181
- roles: claims["https://purl.imsglobal.org/spec/lti/claim/roles"]
57182
- });
57183
- return claims;
57184
- } catch (error) {
57185
- logger16.error("Token verification failed", {
57186
- error: error instanceof Error ? error.message : String(error)
57187
- });
57188
- throw new ValidationError("Invalid LTI token");
57189
- }
57190
- }
57191
- async processLaunch(idToken, currentHost) {
57192
- const claims = await this.verifyToken(idToken);
57193
- const validationError = validateLtiClaims(claims);
57194
- if (validationError) {
57195
- logger16.warn("LTI claims validation failed", {
57196
- error: validationError,
57197
- sub: claims.sub
57198
- });
57199
- throw new ValidationError(validationError);
57200
- }
57201
- const user = await this.provisionUser(claims);
57202
- logger16.info("Processed launch roles", {
57203
- userId: user.id,
57204
- isLearner: LtiRoleChecks.isLearner(claims),
57205
- isInstructor: LtiRoleChecks.isInstructor(claims),
57206
- isAdministrator: LtiRoleChecks.isAdministrator(claims),
57207
- allRoles: claims["https://purl.imsglobal.org/spec/lti/claim/roles"]
57208
- });
57209
- const sessionToken = await this.createSession(user.id);
57210
- const targetUri = claims["https://purl.imsglobal.org/spec/lti/claim/target_link_uri"];
57211
- const redirectPath = extractRedirectPath(targetUri, currentHost);
57212
- logger16.info("Launch processed", { userId: user.id, redirectPath });
57213
- const userInfo = {
57214
- sub: user.id,
57215
- email: user.email,
57216
- name: user.name,
57217
- email_verified: user.emailVerified,
57218
- timeback_id: user.timebackId ?? undefined
57219
- };
57220
- return { user: userInfo, redirectPath, sessionToken };
57221
- }
57222
56184
  async getStatus(user) {
57223
56185
  const db2 = this.ctx.db;
57224
56186
  const [ltiAccount, oauthAccount, userRecord] = await Promise.all([
@@ -57242,134 +56204,11 @@ class LtiService {
57242
56204
  userId: user.id
57243
56205
  };
57244
56206
  }
57245
- async createSession(userId) {
57246
- const db2 = this.ctx.db;
57247
- const sessionToken = crypto4.randomUUID();
57248
- const expiresAt = new Date(Date.now() + 2592000000);
57249
- const [session2] = await db2.insert(sessions).values({
57250
- id: crypto4.randomUUID(),
57251
- userId,
57252
- token: sessionToken,
57253
- expiresAt,
57254
- createdAt: new Date,
57255
- updatedAt: new Date
57256
- }).returning({ id: sessions.id });
57257
- if (!session2) {
57258
- logger16.error("Session insert returned no rows", { userId });
57259
- throw new InternalError("Failed to create session");
57260
- }
57261
- logger16.info("Created session", {
57262
- userId,
57263
- providerId: AUTH_PROVIDER_IDS.TIMEBACK_LTI
57264
- });
57265
- return sessionToken;
57266
- }
57267
- async provisionUser(claims) {
57268
- const db2 = this.ctx.db;
57269
- const email = claims.email;
57270
- const ltiTimebackId = claims.sub;
57271
- const providerId = AUTH_PROVIDER_IDS.TIMEBACK_LTI;
57272
- if (!email) {
57273
- throw new ValidationError("Email is required in LTI claims");
57274
- }
57275
- const existingAccount = await db2.query.accounts.findFirst({
57276
- where: and(eq(accounts.accountId, ltiTimebackId), eq(accounts.providerId, providerId))
57277
- });
57278
- if (existingAccount) {
57279
- const user = await db2.query.users.findFirst({
57280
- where: eq(users.id, existingAccount.userId)
57281
- });
57282
- if (user) {
57283
- logger16.info("Found user by account", {
57284
- userId: user.id,
57285
- ltiTimebackId
57286
- });
57287
- return user;
57288
- }
57289
- }
57290
- const existingUser = await db2.query.users.findFirst({
57291
- where: eq(users.email, email)
57292
- });
57293
- if (existingUser) {
57294
- await db2.transaction(async (tx) => {
57295
- const existingLtiAccount = await tx.query.accounts.findFirst({
57296
- where: and(eq(accounts.userId, existingUser.id), eq(accounts.providerId, providerId))
57297
- });
57298
- if (!existingLtiAccount) {
57299
- const [account] = await tx.insert(accounts).values({
57300
- id: crypto4.randomUUID(),
57301
- userId: existingUser.id,
57302
- accountId: ltiTimebackId,
57303
- providerId,
57304
- accessToken: null,
57305
- refreshToken: null,
57306
- accessTokenExpiresAt: null,
57307
- refreshTokenExpiresAt: null,
57308
- createdAt: new Date,
57309
- updatedAt: new Date
57310
- }).returning({ id: accounts.id });
57311
- if (!account) {
57312
- logger16.error("LTI account link insert returned no rows", {
57313
- userId: existingUser.id,
57314
- ltiTimebackId
57315
- });
57316
- throw new InternalError("Failed to link LTI account");
57317
- }
57318
- logger16.info("Linked existing user", {
57319
- userId: existingUser.id,
57320
- ltiTimebackId
57321
- });
57322
- }
57323
- });
57324
- return existingUser;
57325
- }
57326
- const newUserId = crypto4.randomUUID();
57327
- const createdUser = await db2.transaction(async (tx) => {
57328
- const [insertedUser] = await tx.insert(users).values({
57329
- id: newUserId,
57330
- email,
57331
- emailVerified: true,
57332
- timebackId: ltiTimebackId,
57333
- username: generateUsername(email),
57334
- name: claims.name || claims.given_name || email.split("@")[0] || "Timeback User",
57335
- createdAt: new Date,
57336
- updatedAt: new Date
57337
- }).returning();
57338
- if (!insertedUser) {
57339
- logger16.error("LTI user insert returned no rows", { email, ltiTimebackId });
57340
- throw new InternalError("Failed to create user");
57341
- }
57342
- await tx.insert(accounts).values({
57343
- id: crypto4.randomUUID(),
57344
- userId: newUserId,
57345
- accountId: ltiTimebackId,
57346
- providerId,
57347
- accessToken: null,
57348
- refreshToken: null,
57349
- accessTokenExpiresAt: null,
57350
- refreshTokenExpiresAt: null,
57351
- createdAt: new Date,
57352
- updatedAt: new Date
57353
- });
57354
- logger16.info("Provisioned user", {
57355
- userId: insertedUser.id,
57356
- ltiTimebackId
57357
- });
57358
- return insertedUser;
57359
- });
57360
- return createdUser;
57361
- }
57362
56207
  }
57363
- var logger16;
57364
56208
  var init_lti_service = __esm(() => {
57365
- init_esm2();
57366
56209
  init_drizzle_orm();
57367
56210
  init_src();
57368
56211
  init_tables_index();
57369
- init_src2();
57370
- init_errors();
57371
- init_lti_util();
57372
- logger16 = log.scope("LtiService");
57373
56212
  });
57374
56213
 
57375
56214
  class MapService {
@@ -57860,56 +56699,88 @@ class SecretsService {
57860
56699
  constructor(ctx) {
57861
56700
  this.ctx = ctx;
57862
56701
  }
57863
- async listKeys(slug2, user) {
57864
- const game = await this.ctx.services.game.validateDeveloperAccessBySlug(user, slug2);
57865
- const secrets = await this.ctx.providers.secrets.readSecrets(game.id);
57866
- const keys = secrets ? Object.keys(secrets).filter((k) => !INTERNAL_SECRET_KEYS.includes(k)) : [];
57867
- logger20.debug("Listed secret keys", { gameId: game.id, slug: slug2, keyCount: keys.length });
57868
- return keys;
56702
+ getCloudflare() {
56703
+ if (!this.ctx.cloudflare) {
56704
+ throw new ValidationError("Secrets management requires Cloudflare provider");
56705
+ }
56706
+ return this.ctx.cloudflare;
57869
56707
  }
57870
- async getValues(slug2, user) {
56708
+ getDeploymentId(slug2) {
56709
+ const isProd = isProduction2(this.ctx.config);
56710
+ return getDeploymentId(slug2, isProd);
56711
+ }
56712
+ async listKeys(slug2, user) {
57871
56713
  const game = await this.ctx.services.game.validateDeveloperAccessBySlug(user, slug2);
57872
- const secrets = await this.ctx.providers.secrets.readSecrets(game.id);
57873
- if (!secrets) {
57874
- return {};
57875
- }
57876
- const filtered = {};
57877
- for (const [key, value] of Object.entries(secrets)) {
57878
- if (!INTERNAL_SECRET_KEYS.includes(key)) {
57879
- filtered[key] = value;
56714
+ const cf = this.getCloudflare();
56715
+ const deploymentId = this.getDeploymentId(slug2);
56716
+ try {
56717
+ const allKeys = await cf.listSecrets(deploymentId);
56718
+ const keys = allKeys.filter((k) => k.startsWith(SECRETS_PREFIX)).map((k) => k.slice(SECRETS_PREFIX.length));
56719
+ logger20.debug("Listed secret keys", { gameId: game.id, slug: slug2, keyCount: keys.length });
56720
+ return keys;
56721
+ } catch (error) {
56722
+ const message = error instanceof Error ? error.message : String(error);
56723
+ if (message.includes("not found") || message.includes("10007")) {
56724
+ logger20.debug("Worker not found, returning empty secrets list", {
56725
+ gameId: game.id,
56726
+ slug: slug2
56727
+ });
56728
+ return [];
57880
56729
  }
56730
+ throw error;
57881
56731
  }
57882
- logger20.debug("Retrieved secret values", { gameId: game.id, slug: slug2 });
57883
- return filtered;
57884
56732
  }
57885
56733
  async setSecrets(slug2, newSecrets, user) {
57886
56734
  const game = await this.ctx.services.game.validateDeveloperAccessBySlug(user, slug2);
56735
+ const cf = this.getCloudflare();
56736
+ const deploymentId = this.getDeploymentId(slug2);
57887
56737
  const secretKeys = Object.keys(newSecrets);
57888
56738
  if (secretKeys.length === 0) {
56739
+ logger20.warn("No secrets provided", { userId: user.id, slug: slug2 });
57889
56740
  throw new ValidationError("At least one secret must be provided");
57890
56741
  }
57891
56742
  for (const [key, value] of Object.entries(newSecrets)) {
57892
56743
  if (typeof value !== "string") {
56744
+ logger20.warn("Secret value must be a string", { userId: user.id, slug: slug2, key });
57893
56745
  throw new ValidationError(`Secret value for "${key}" must be a string`);
57894
56746
  }
57895
56747
  if (INTERNAL_SECRET_KEYS.includes(key)) {
57896
- logger20.warn("Attempted to set reserved secret", {
57897
- userId: user.id,
56748
+ logger20.warn("Attempted to set reserved secret", { userId: user.id, slug: slug2, key });
56749
+ throw new ValidationError(`Cannot set reserved secret "${key}"`);
56750
+ }
56751
+ }
56752
+ try {
56753
+ const prefixedSecrets = {};
56754
+ for (const [key, value] of Object.entries(newSecrets)) {
56755
+ prefixedSecrets[`${SECRETS_PREFIX}${key}`] = value;
56756
+ }
56757
+ await cf.setSecrets(deploymentId, prefixedSecrets);
56758
+ logger20.info("Set secrets", {
56759
+ gameId: game.id,
56760
+ slug: slug2,
56761
+ deploymentId,
56762
+ keys: secretKeys
56763
+ });
56764
+ const allKeys = await cf.listSecrets(deploymentId);
56765
+ return allKeys.filter((k) => k.startsWith(SECRETS_PREFIX)).map((k) => k.slice(SECRETS_PREFIX.length));
56766
+ } catch (error) {
56767
+ const message = error instanceof Error ? error.message : String(error);
56768
+ if (message.includes("not found") || message.includes("10007")) {
56769
+ logger20.warn("Cannot set secrets - game not deployed", {
57898
56770
  gameId: game.id,
57899
- key
56771
+ slug: slug2,
56772
+ deploymentId
57900
56773
  });
57901
- throw new ValidationError(`Cannot set reserved secret "${key}"`);
56774
+ throw new ValidationError("Game must be deployed before setting secrets. Run `playcademy deploy` first.");
57902
56775
  }
56776
+ logger20.error("Failed to set secrets", {
56777
+ gameId: game.id,
56778
+ slug: slug2,
56779
+ deploymentId,
56780
+ error: message
56781
+ });
56782
+ throw error;
57903
56783
  }
57904
- const existingSecrets = await this.ctx.providers.secrets.readSecrets(game.id) || {};
57905
- const updatedSecrets = { ...existingSecrets, ...newSecrets };
57906
- await this.ctx.providers.secrets.writeSecrets(game.id, updatedSecrets);
57907
- logger20.info("Set secrets", {
57908
- gameId: game.id,
57909
- slug: slug2,
57910
- addedKeys: secretKeys
57911
- });
57912
- return Object.keys(updatedSecrets).filter((k) => !INTERNAL_SECRET_KEYS.includes(k));
57913
56784
  }
57914
56785
  async deleteSecret(slug2, key, user) {
57915
56786
  if (INTERNAL_SECRET_KEYS.includes(key)) {
@@ -57921,26 +56792,55 @@ class SecretsService {
57921
56792
  throw new ValidationError(`Cannot delete reserved secret "${key}"`);
57922
56793
  }
57923
56794
  const game = await this.ctx.services.game.validateDeveloperAccessBySlug(user, slug2);
57924
- const secrets = await this.ctx.providers.secrets.readSecrets(game.id);
57925
- if (!secrets || !(key in secrets)) {
57926
- throw new NotFoundError("Secret", key);
57927
- }
57928
- delete secrets[key];
57929
- if (Object.keys(secrets).length > 0) {
57930
- await this.ctx.providers.secrets.writeSecrets(game.id, secrets);
57931
- } else {
57932
- await this.ctx.providers.secrets.deleteSecrets(game.id);
56795
+ const cf = this.getCloudflare();
56796
+ const deploymentId = this.getDeploymentId(slug2);
56797
+ try {
56798
+ const prefixedKey = `${SECRETS_PREFIX}${key}`;
56799
+ const existingKeys = await cf.listSecrets(deploymentId);
56800
+ if (!existingKeys.includes(prefixedKey)) {
56801
+ throw new NotFoundError("Secret", key);
56802
+ }
56803
+ await cf.deleteSecret(deploymentId, prefixedKey);
56804
+ logger20.info("Deleted secret", {
56805
+ gameId: game.id,
56806
+ slug: slug2,
56807
+ deploymentId,
56808
+ key
56809
+ });
56810
+ } catch (error) {
56811
+ if (error instanceof NotFoundError) {
56812
+ throw error;
56813
+ }
56814
+ const message = error instanceof Error ? error.message : String(error);
56815
+ if (message.includes("not found") || message.includes("10007")) {
56816
+ logger20.warn("Cannot delete secret - game not deployed", {
56817
+ gameId: game.id,
56818
+ slug: slug2,
56819
+ deploymentId
56820
+ });
56821
+ throw new ValidationError("Game must be deployed before managing secrets. Run `playcademy deploy` first.");
56822
+ }
56823
+ logger20.error("Failed to delete secret", {
56824
+ gameId: game.id,
56825
+ slug: slug2,
56826
+ deploymentId,
56827
+ key,
56828
+ error: message
56829
+ });
56830
+ throw error;
57933
56831
  }
57934
- logger20.info("Deleted secret", { gameId: game.id, slug: slug2, key });
57935
56832
  }
57936
56833
  }
57937
56834
  var logger20;
56835
+ var SECRETS_PREFIX = "secrets_";
57938
56836
  var INTERNAL_SECRET_KEYS;
57939
56837
  var init_secrets_service = __esm(() => {
57940
56838
  init_src2();
56839
+ init_config2();
57941
56840
  init_errors();
56841
+ init_deployment_util();
57942
56842
  logger20 = log.scope("SecretsService");
57943
- INTERNAL_SECRET_KEYS = ["PLAYCADEMY_API_KEY"];
56843
+ INTERNAL_SECRET_KEYS = ["PLAYCADEMY_API_KEY", "GAME_ID", "PLAYCADEMY_BASE_URL"];
57944
56844
  });
57945
56845
 
57946
56846
  class SeedService {
@@ -59370,11 +58270,11 @@ class TimebackService {
59370
58270
  return [];
59371
58271
  }
59372
58272
  }
59373
- async setupIntegration(gameId, request2, user) {
58273
+ async setupIntegration(gameId, request, user) {
59374
58274
  const client = this.requireClient();
59375
58275
  const db2 = this.ctx.db;
59376
58276
  await this.ctx.services.game.validateDeveloperAccess(user, gameId);
59377
- const { courses, baseConfig, verbose } = request2;
58277
+ const { courses, baseConfig, verbose } = request;
59378
58278
  const existing = await db2.query.gameTimebackIntegrations.findMany({
59379
58279
  where: eq(gameTimebackIntegrations.gameId, gameId)
59380
58280
  });
@@ -59618,8 +58518,8 @@ class UploadService {
59618
58518
  constructor(ctx) {
59619
58519
  this.ctx = ctx;
59620
58520
  }
59621
- async initiate(request2, user) {
59622
- const { fileName, gameId } = request2;
58521
+ async initiate(request, user) {
58522
+ const { fileName, gameId } = request;
59623
58523
  const bucketName = this.ctx.config.uploadBucket;
59624
58524
  if (!bucketName) {
59625
58525
  logger27.error("Upload bucket not configured in environment");
@@ -59865,6 +58765,7 @@ function createServices(ctx) {
59865
58765
  item: new ItemService(ctx),
59866
58766
  leaderboard: new LeaderboardService(ctx),
59867
58767
  level: new LevelService(ctx),
58768
+ logs: new LogsService(ctx),
59868
58769
  lti: new LtiService(ctx),
59869
58770
  map: new MapService(ctx),
59870
58771
  notification: new NotificationService(ctx),
@@ -59896,6 +58797,7 @@ var init_services = __esm(() => {
59896
58797
  init_item_service();
59897
58798
  init_leaderboard_service();
59898
58799
  init_level_service();
58800
+ init_logs_service();
59899
58801
  init_lti_service();
59900
58802
  init_map_service();
59901
58803
  init_notification_service();
@@ -59990,6 +58892,38 @@ function createSandboxAuthProvider() {
59990
58892
  const header = btoa(JSON.stringify({ alg: "none", typ: "sandbox" }));
59991
58893
  const payloadStr = btoa(JSON.stringify(payload));
59992
58894
  return `${header}.${payloadStr}.sandbox`;
58895
+ },
58896
+ async mintLogStreamToken(userId, workerId) {
58897
+ const jti = crypto.randomUUID();
58898
+ const payload = {
58899
+ sub: userId,
58900
+ game: workerId,
58901
+ jti,
58902
+ exp: Date.now() + 60000
58903
+ };
58904
+ const header = btoa(JSON.stringify({ alg: "none", typ: "sandbox" }));
58905
+ const payloadStr = btoa(JSON.stringify(payload));
58906
+ return `${header}.${payloadStr}.sandbox`;
58907
+ },
58908
+ async validateLogStreamToken(token) {
58909
+ try {
58910
+ const parts2 = token.split(".");
58911
+ if (parts2.length !== 3)
58912
+ return null;
58913
+ if (parts2[2] === "sandbox") {
58914
+ const payload = JSON.parse(atob(parts2[1]));
58915
+ if (payload.jti && payload.sub && payload.game) {
58916
+ if (payload.exp && payload.exp < Date.now()) {
58917
+ log.debug("[SandboxAuthProvider] Log stream token expired");
58918
+ return null;
58919
+ }
58920
+ return { jti: payload.jti, sub: payload.sub, game: payload.game };
58921
+ }
58922
+ }
58923
+ return null;
58924
+ } catch {
58925
+ return null;
58926
+ }
59993
58927
  }
59994
58928
  };
59995
58929
  }
@@ -60044,33 +58978,6 @@ var init_cache_provider = __esm(() => {
60044
58978
  cache = new Map;
60045
58979
  gameOrigins = [];
60046
58980
  });
60047
- function createSandboxSecretsProvider() {
60048
- return {
60049
- async readSecrets(gameId) {
60050
- const secrets = secretsStorage.get(gameId);
60051
- return secrets ?? null;
60052
- },
60053
- async writeSecrets(gameId, secrets) {
60054
- secretsStorage.set(gameId, { ...secrets });
60055
- log.debug("[SandboxSecretsProvider] Stored secrets", {
60056
- gameId,
60057
- keyCount: Object.keys(secrets).length
60058
- });
60059
- },
60060
- async deleteSecrets(gameId) {
60061
- secretsStorage.delete(gameId);
60062
- log.debug("[SandboxSecretsProvider] Deleted secrets", { gameId });
60063
- }
60064
- };
60065
- }
60066
- function clearSandboxSecrets() {
60067
- secretsStorage.clear();
60068
- }
60069
- var secretsStorage;
60070
- var init_secrets_provider = __esm(() => {
60071
- init_src2();
60072
- secretsStorage = new Map;
60073
- });
60074
58981
  function getBucket(bucketName) {
60075
58982
  let bucket = storage.get(bucketName);
60076
58983
  if (!bucket) {
@@ -60142,7 +59049,6 @@ var init_storage_provider = __esm(() => {
60142
59049
  var init_providers = __esm(() => {
60143
59050
  init_auth_provider();
60144
59051
  init_cache_provider();
60145
- init_secrets_provider();
60146
59052
  init_storage_provider();
60147
59053
  });
60148
59054
  function buildConfig(options) {
@@ -60159,7 +59065,6 @@ function buildProviders() {
60159
59065
  return {
60160
59066
  auth: createSandboxAuthProvider(),
60161
59067
  storage: createSandboxStorageProvider(),
60162
- secrets: createSandboxSecretsProvider(),
60163
59068
  cache: createSandboxCacheProvider()
60164
59069
  };
60165
59070
  }
@@ -60292,8 +59197,8 @@ var GET_MATCH_RESULT;
60292
59197
  var init_constants3 = __esm(() => {
60293
59198
  GET_MATCH_RESULT = Symbol();
60294
59199
  });
60295
- async function parseFormData(request2, options) {
60296
- const formData = await request2.formData();
59200
+ async function parseFormData(request, options) {
59201
+ const formData = await request.formData();
60297
59202
  if (formData) {
60298
59203
  return convertFormDataToBodyData(formData, options);
60299
59204
  }
@@ -60320,12 +59225,12 @@ function convertFormDataToBodyData(formData, options) {
60320
59225
  }
60321
59226
  return form;
60322
59227
  }
60323
- var parseBody = async (request2, options = /* @__PURE__ */ Object.create(null)) => {
59228
+ var parseBody = async (request, options = /* @__PURE__ */ Object.create(null)) => {
60324
59229
  const { all = false, dot = false } = options;
60325
- const headers = request2 instanceof HonoRequest ? request2.raw.headers : request2.headers;
59230
+ const headers = request instanceof HonoRequest ? request.raw.headers : request.headers;
60326
59231
  const contentType = headers.get("Content-Type");
60327
59232
  if (contentType?.startsWith("multipart/form-data") || contentType?.startsWith("application/x-www-form-urlencoded")) {
60328
- return parseFormData(request2, { all, dot });
59233
+ return parseFormData(request, { all, dot });
60329
59234
  }
60330
59235
  return {};
60331
59236
  };
@@ -60427,8 +59332,8 @@ var tryDecode = (str, decoder) => {
60427
59332
  }
60428
59333
  };
60429
59334
  var tryDecodeURI = (str) => tryDecode(str, decodeURI);
60430
- var getPath = (request2) => {
60431
- const url = request2.url;
59335
+ var getPath = (request) => {
59336
+ const url = request.url;
60432
59337
  const start2 = url.indexOf("/", url.indexOf(":") + 4);
60433
59338
  let i2 = start2;
60434
59339
  for (;i2 < url.length; i2++) {
@@ -60443,8 +59348,8 @@ var getPath = (request2) => {
60443
59348
  }
60444
59349
  return url.slice(start2, i2);
60445
59350
  };
60446
- var getPathNoStrict = (request2) => {
60447
- const result = getPath(request2);
59351
+ var getPathNoStrict = (request) => {
59352
+ const result = getPath(request);
60448
59353
  return result.length > 1 && result.at(-1) === "/" ? result.slice(0, -1) : result;
60449
59354
  };
60450
59355
  var mergePath = (base, sub, ...rest) => {
@@ -60576,8 +59481,8 @@ var init_request = __esm(() => {
60576
59481
  routeIndex = 0;
60577
59482
  path;
60578
59483
  bodyCache = {};
60579
- constructor(request2, path2 = "/", matchResult = [[]]) {
60580
- this.raw = request2;
59484
+ constructor(request, path2 = "/", matchResult = [[]]) {
59485
+ this.raw = request;
60581
59486
  this.path = path2;
60582
59487
  this.#matchResult = matchResult;
60583
59488
  this.#validatedData = {};
@@ -61013,7 +59918,7 @@ var Hono = class {
61013
59918
  } else {
61014
59919
  optionHandler = options.optionHandler;
61015
59920
  if (options.replaceRequest === false) {
61016
- replaceRequest = (request2) => request2;
59921
+ replaceRequest = (request) => request;
61017
59922
  } else {
61018
59923
  replaceRequest = options.replaceRequest;
61019
59924
  }
@@ -61032,10 +59937,10 @@ var Hono = class {
61032
59937
  replaceRequest ||= (() => {
61033
59938
  const mergedPath = mergePath(this._basePath, path2);
61034
59939
  const pathPrefixLength = mergedPath === "/" ? 0 : mergedPath.length;
61035
- return (request2) => {
61036
- const url = new URL(request2.url);
59940
+ return (request) => {
59941
+ const url = new URL(request.url);
61037
59942
  url.pathname = url.pathname.slice(pathPrefixLength) || "/";
61038
- return new Request(url, request2);
59943
+ return new Request(url, request);
61039
59944
  };
61040
59945
  })();
61041
59946
  const handler = async (c, next) => {
@@ -61061,13 +59966,13 @@ var Hono = class {
61061
59966
  }
61062
59967
  throw err2;
61063
59968
  }
61064
- #dispatch(request2, executionCtx, env, method) {
59969
+ #dispatch(request, executionCtx, env, method) {
61065
59970
  if (method === "HEAD") {
61066
- return (async () => new Response(null, await this.#dispatch(request2, executionCtx, env, "GET")))();
59971
+ return (async () => new Response(null, await this.#dispatch(request, executionCtx, env, "GET")))();
61067
59972
  }
61068
- const path2 = this.getPath(request2, { env });
59973
+ const path2 = this.getPath(request, { env });
61069
59974
  const matchResult = this.router.match(method, path2);
61070
- const c = new Context(request2, {
59975
+ const c = new Context(request, {
61071
59976
  path: path2,
61072
59977
  matchResult,
61073
59978
  env,
@@ -61098,8 +60003,8 @@ var Hono = class {
61098
60003
  }
61099
60004
  })();
61100
60005
  }
61101
- fetch = (request2, ...rest) => {
61102
- return this.#dispatch(request2, rest[1], rest[0], request2.method);
60006
+ fetch = (request, ...rest) => {
60007
+ return this.#dispatch(request, rest[1], rest[0], request.method);
61103
60008
  };
61104
60009
  request = (input, requestInit, Env, executionCtx) => {
61105
60010
  if (input instanceof Request) {
@@ -69766,24 +68671,24 @@ is not a problem with esbuild. You need to fix your environment instead.
69766
68671
  throw new Error("The service is no longer running" + closeData.reason);
69767
68672
  streamIn.writeToStdin(encodePacket({ id, isRequest: false, value }));
69768
68673
  };
69769
- let handleRequest = async (id, request2) => {
68674
+ let handleRequest = async (id, request) => {
69770
68675
  try {
69771
- if (request2.command === "ping") {
68676
+ if (request.command === "ping") {
69772
68677
  sendResponse(id, {});
69773
68678
  return;
69774
68679
  }
69775
- if (typeof request2.key === "number") {
69776
- const requestCallbacks = requestCallbacksByKey[request2.key];
68680
+ if (typeof request.key === "number") {
68681
+ const requestCallbacks = requestCallbacksByKey[request.key];
69777
68682
  if (!requestCallbacks) {
69778
68683
  return;
69779
68684
  }
69780
- const callback = requestCallbacks[request2.command];
68685
+ const callback = requestCallbacks[request.command];
69781
68686
  if (callback) {
69782
- await callback(id, request2);
68687
+ await callback(id, request);
69783
68688
  return;
69784
68689
  }
69785
68690
  }
69786
- throw new Error(`Invalid command: ` + request2.command);
68691
+ throw new Error(`Invalid command: ` + request.command);
69787
68692
  } catch (e) {
69788
68693
  const errors3 = [extractErrorMessageV8(e, streamIn, null, undefined, "")];
69789
68694
  try {
@@ -69852,15 +68757,15 @@ is not a problem with esbuild. You need to fix your environment instead.
69852
68757
  flags: flags2,
69853
68758
  mangleCache
69854
68759
  } = flagsForTransformOptions(callName, options, isTTY2, transformLogLevelDefault);
69855
- let request2 = {
68760
+ let request = {
69856
68761
  command: "transform",
69857
68762
  flags: flags2,
69858
68763
  inputFS: inputPath !== null,
69859
68764
  input: inputPath !== null ? encodeUTF8(inputPath) : typeof input === "string" ? encodeUTF8(input) : input
69860
68765
  };
69861
68766
  if (mangleCache)
69862
- request2.mangleCache = mangleCache;
69863
- sendRequest(refs, request2, (error, response) => {
68767
+ request.mangleCache = mangleCache;
68768
+ sendRequest(refs, request, (error, response) => {
69864
68769
  if (error)
69865
68770
  return callback(new Error(error), null);
69866
68771
  let errors3 = replaceDetailsInMessages(response.errors, details);
@@ -69938,16 +68843,16 @@ is not a problem with esbuild. You need to fix your environment instead.
69938
68843
  throw new Error(`Missing "kind" in ${callName}() call`);
69939
68844
  if (kind !== "error" && kind !== "warning")
69940
68845
  throw new Error(`Expected "kind" to be "error" or "warning" in ${callName}() call`);
69941
- let request2 = {
68846
+ let request = {
69942
68847
  command: "format-msgs",
69943
68848
  messages: sanitizeMessages(messages, "messages", null, "", terminalWidth),
69944
68849
  isWarning: kind === "warning"
69945
68850
  };
69946
68851
  if (color !== undefined)
69947
- request2.color = color;
68852
+ request.color = color;
69948
68853
  if (terminalWidth !== undefined)
69949
- request2.terminalWidth = terminalWidth;
69950
- sendRequest(refs, request2, (error, response) => {
68854
+ request.terminalWidth = terminalWidth;
68855
+ sendRequest(refs, request, (error, response) => {
69951
68856
  if (error)
69952
68857
  return callback(new Error(error), null);
69953
68858
  callback(null, response.messages);
@@ -69960,15 +68865,15 @@ is not a problem with esbuild. You need to fix your environment instead.
69960
68865
  let color = getFlag(options, keys, "color", mustBeBoolean);
69961
68866
  let verbose = getFlag(options, keys, "verbose", mustBeBoolean);
69962
68867
  checkForInvalidFlags(options, keys, `in ${callName}() call`);
69963
- let request2 = {
68868
+ let request = {
69964
68869
  command: "analyze-metafile",
69965
68870
  metafile
69966
68871
  };
69967
68872
  if (color !== undefined)
69968
- request2.color = color;
68873
+ request.color = color;
69969
68874
  if (verbose !== undefined)
69970
- request2.verbose = verbose;
69971
- sendRequest(refs, request2, (error, response) => {
68875
+ request.verbose = verbose;
68876
+ sendRequest(refs, request, (error, response) => {
69972
68877
  if (error)
69973
68878
  return callback(new Error(error), null);
69974
68879
  callback(null, response.result);
@@ -70041,7 +68946,7 @@ is not a problem with esbuild. You need to fix your environment instead.
70041
68946
  } = flagsForBuildOptions(callName, options, isTTY2, buildLogLevelDefault, writeDefault);
70042
68947
  if (write && !streamIn.hasFS)
70043
68948
  throw new Error(`The "write" option is unavailable in this environment`);
70044
- const request2 = {
68949
+ const request = {
70045
68950
  command: "build",
70046
68951
  key: buildKey,
70047
68952
  entries,
@@ -70054,9 +68959,9 @@ is not a problem with esbuild. You need to fix your environment instead.
70054
68959
  context: isContext
70055
68960
  };
70056
68961
  if (requestPlugins)
70057
- request2.plugins = requestPlugins;
68962
+ request.plugins = requestPlugins;
70058
68963
  if (mangleCache)
70059
- request2.mangleCache = mangleCache;
68964
+ request.mangleCache = mangleCache;
70060
68965
  const buildResponseToResult = (response, callback2) => {
70061
68966
  const result = {
70062
68967
  errors: replaceDetailsInMessages(response.errors, details),
@@ -70086,8 +68991,8 @@ is not a problem with esbuild. You need to fix your environment instead.
70086
68991
  let latestResultPromise;
70087
68992
  let provideLatestResult;
70088
68993
  if (isContext)
70089
- requestCallbacks["on-end"] = (id, request22) => new Promise((resolve2) => {
70090
- buildResponseToResult(request22, (err2, result, onEndErrors, onEndWarnings) => {
68994
+ requestCallbacks["on-end"] = (id, request2) => new Promise((resolve2) => {
68995
+ buildResponseToResult(request2, (err2, result, onEndErrors, onEndWarnings) => {
70091
68996
  const response = {
70092
68997
  errors: onEndErrors,
70093
68998
  warnings: onEndWarnings
@@ -70100,7 +69005,7 @@ is not a problem with esbuild. You need to fix your environment instead.
70100
69005
  resolve2();
70101
69006
  });
70102
69007
  });
70103
- sendRequest(refs, request2, (error, response) => {
69008
+ sendRequest(refs, request, (error, response) => {
70104
69009
  if (error)
70105
69010
  return callback(new Error(error), null);
70106
69011
  if (!isContext) {
@@ -70123,11 +69028,11 @@ is not a problem with esbuild. You need to fix your environment instead.
70123
69028
  settlePromise = () => err2 ? reject(err2) : resolve2(result2);
70124
69029
  };
70125
69030
  const triggerAnotherBuild = () => {
70126
- const request22 = {
69031
+ const request2 = {
70127
69032
  command: "rebuild",
70128
69033
  key: buildKey
70129
69034
  };
70130
- sendRequest(refs, request22, (error2, response2) => {
69035
+ sendRequest(refs, request2, (error2, response2) => {
70131
69036
  if (error2) {
70132
69037
  reject(new Error(error2));
70133
69038
  } else if (settlePromise) {
@@ -70147,13 +69052,13 @@ is not a problem with esbuild. You need to fix your environment instead.
70147
69052
  const keys = {};
70148
69053
  const delay = getFlag(options2, keys, "delay", mustBeInteger);
70149
69054
  checkForInvalidFlags(options2, keys, `in watch() call`);
70150
- const request22 = {
69055
+ const request2 = {
70151
69056
  command: "watch",
70152
69057
  key: buildKey
70153
69058
  };
70154
69059
  if (delay)
70155
- request22.delay = delay;
70156
- sendRequest(refs, request22, (error2) => {
69060
+ request2.delay = delay;
69061
+ sendRequest(refs, request2, (error2) => {
70157
69062
  if (error2)
70158
69063
  reject(new Error(error2));
70159
69064
  else
@@ -70173,33 +69078,33 @@ is not a problem with esbuild. You need to fix your environment instead.
70173
69078
  const cors2 = getFlag(options2, keys, "cors", mustBeObject);
70174
69079
  const onRequest = getFlag(options2, keys, "onRequest", mustBeFunction);
70175
69080
  checkForInvalidFlags(options2, keys, `in serve() call`);
70176
- const request22 = {
69081
+ const request2 = {
70177
69082
  command: "serve",
70178
69083
  key: buildKey,
70179
69084
  onRequest: !!onRequest
70180
69085
  };
70181
69086
  if (port !== undefined)
70182
- request22.port = port;
69087
+ request2.port = port;
70183
69088
  if (host !== undefined)
70184
- request22.host = host;
69089
+ request2.host = host;
70185
69090
  if (servedir !== undefined)
70186
- request22.servedir = servedir;
69091
+ request2.servedir = servedir;
70187
69092
  if (keyfile !== undefined)
70188
- request22.keyfile = keyfile;
69093
+ request2.keyfile = keyfile;
70189
69094
  if (certfile !== undefined)
70190
- request22.certfile = certfile;
69095
+ request2.certfile = certfile;
70191
69096
  if (fallback !== undefined)
70192
- request22.fallback = fallback;
69097
+ request2.fallback = fallback;
70193
69098
  if (cors2) {
70194
69099
  const corsKeys = {};
70195
69100
  const origin = getFlag(cors2, corsKeys, "origin", mustBeStringOrArrayOfStrings);
70196
69101
  checkForInvalidFlags(cors2, corsKeys, `on "cors" object`);
70197
69102
  if (Array.isArray(origin))
70198
- request22.corsOrigin = origin;
69103
+ request2.corsOrigin = origin;
70199
69104
  else if (origin !== undefined)
70200
- request22.corsOrigin = [origin];
69105
+ request2.corsOrigin = [origin];
70201
69106
  }
70202
- sendRequest(refs, request22, (error2, response2) => {
69107
+ sendRequest(refs, request2, (error2, response2) => {
70203
69108
  if (error2)
70204
69109
  return reject(new Error(error2));
70205
69110
  if (onRequest) {
@@ -70214,11 +69119,11 @@ is not a problem with esbuild. You need to fix your environment instead.
70214
69119
  cancel: () => new Promise((resolve2) => {
70215
69120
  if (didDispose)
70216
69121
  return resolve2();
70217
- const request22 = {
69122
+ const request2 = {
70218
69123
  command: "cancel",
70219
69124
  key: buildKey
70220
69125
  };
70221
- sendRequest(refs, request22, () => {
69126
+ sendRequest(refs, request2, () => {
70222
69127
  resolve2();
70223
69128
  });
70224
69129
  }),
@@ -70226,11 +69131,11 @@ is not a problem with esbuild. You need to fix your environment instead.
70226
69131
  if (didDispose)
70227
69132
  return resolve2();
70228
69133
  didDispose = true;
70229
- const request22 = {
69134
+ const request2 = {
70230
69135
  command: "dispose",
70231
69136
  key: buildKey
70232
69137
  };
70233
- sendRequest(refs, request22, () => {
69138
+ sendRequest(refs, request2, () => {
70234
69139
  resolve2();
70235
69140
  scheduleOnDisposeCallbacks();
70236
69141
  refs.unref();
@@ -70288,29 +69193,29 @@ is not a problem with esbuild. You need to fix your environment instead.
70288
69193
  let importAttributes = getFlag(options, keys2, "with", mustBeObject);
70289
69194
  checkForInvalidFlags(options, keys2, "in resolve() call");
70290
69195
  return new Promise((resolve22, reject) => {
70291
- const request2 = {
69196
+ const request = {
70292
69197
  command: "resolve",
70293
69198
  path: path3,
70294
69199
  key: buildKey,
70295
69200
  pluginName: name3
70296
69201
  };
70297
69202
  if (pluginName != null)
70298
- request2.pluginName = pluginName;
69203
+ request.pluginName = pluginName;
70299
69204
  if (importer != null)
70300
- request2.importer = importer;
69205
+ request.importer = importer;
70301
69206
  if (namespace != null)
70302
- request2.namespace = namespace;
69207
+ request.namespace = namespace;
70303
69208
  if (resolveDir != null)
70304
- request2.resolveDir = resolveDir;
69209
+ request.resolveDir = resolveDir;
70305
69210
  if (kind != null)
70306
- request2.kind = kind;
69211
+ request.kind = kind;
70307
69212
  else
70308
69213
  throw new Error(`Must specify "kind" when calling "resolve"`);
70309
69214
  if (pluginData != null)
70310
- request2.pluginData = details.store(pluginData);
69215
+ request.pluginData = details.store(pluginData);
70311
69216
  if (importAttributes != null)
70312
- request2.with = sanitizeStringMap(importAttributes, "with");
70313
- sendRequest(refs, request2, (error, response) => {
69217
+ request.with = sanitizeStringMap(importAttributes, "with");
69218
+ sendRequest(refs, request, (error, response) => {
70314
69219
  if (error !== null)
70315
69220
  reject(new Error(error));
70316
69221
  else
@@ -70380,7 +69285,7 @@ is not a problem with esbuild. You need to fix your environment instead.
70380
69285
  return { ok: false, error: e, pluginName: name3 };
70381
69286
  }
70382
69287
  }
70383
- requestCallbacks["on-start"] = async (id, request2) => {
69288
+ requestCallbacks["on-start"] = async (id, request) => {
70384
69289
  details.clear();
70385
69290
  let response = { errors: [], warnings: [] };
70386
69291
  await Promise.all(onStartCallbacks.map(async ({ name: name3, callback, note }) => {
@@ -70404,19 +69309,19 @@ is not a problem with esbuild. You need to fix your environment instead.
70404
69309
  }));
70405
69310
  sendResponse(id, response);
70406
69311
  };
70407
- requestCallbacks["on-resolve"] = async (id, request2) => {
69312
+ requestCallbacks["on-resolve"] = async (id, request) => {
70408
69313
  let response = {}, name3 = "", callback, note;
70409
- for (let id2 of request2.ids) {
69314
+ for (let id2 of request.ids) {
70410
69315
  try {
70411
69316
  ({ name: name3, callback, note } = onResolveCallbacks[id2]);
70412
69317
  let result = await callback({
70413
- path: request2.path,
70414
- importer: request2.importer,
70415
- namespace: request2.namespace,
70416
- resolveDir: request2.resolveDir,
70417
- kind: request2.kind,
70418
- pluginData: details.load(request2.pluginData),
70419
- with: request2.with
69318
+ path: request.path,
69319
+ importer: request.importer,
69320
+ namespace: request.namespace,
69321
+ resolveDir: request.resolveDir,
69322
+ kind: request.kind,
69323
+ pluginData: details.load(request.pluginData),
69324
+ with: request.with
70420
69325
  });
70421
69326
  if (result != null) {
70422
69327
  if (typeof result !== "object")
@@ -70466,17 +69371,17 @@ is not a problem with esbuild. You need to fix your environment instead.
70466
69371
  }
70467
69372
  sendResponse(id, response);
70468
69373
  };
70469
- requestCallbacks["on-load"] = async (id, request2) => {
69374
+ requestCallbacks["on-load"] = async (id, request) => {
70470
69375
  let response = {}, name3 = "", callback, note;
70471
- for (let id2 of request2.ids) {
69376
+ for (let id2 of request.ids) {
70472
69377
  try {
70473
69378
  ({ name: name3, callback, note } = onLoadCallbacks[id2]);
70474
69379
  let result = await callback({
70475
- path: request2.path,
70476
- namespace: request2.namespace,
70477
- suffix: request2.suffix,
70478
- pluginData: details.load(request2.pluginData),
70479
- with: request2.with
69380
+ path: request.path,
69381
+ namespace: request.namespace,
69382
+ suffix: request.suffix,
69383
+ pluginData: details.load(request.pluginData),
69384
+ with: request.with
70480
69385
  });
70481
69386
  if (result != null) {
70482
69387
  if (typeof result !== "object")
@@ -71003,7 +69908,7 @@ for your current platform.`);
71003
69908
  return { binPath, isWASM };
71004
69909
  }
71005
69910
  var child_process = __require2("child_process");
71006
- var crypto5 = __require2("crypto");
69911
+ var crypto42 = __require2("crypto");
71007
69912
  var path22 = __require2("path");
71008
69913
  var fs222 = __require2("fs");
71009
69914
  var os22 = __require2("os");
@@ -71317,7 +70222,7 @@ More information: The file containing the code for esbuild's JavaScript API (${_
71317
70222
  afterClose(null);
71318
70223
  };
71319
70224
  var randomFileName = () => {
71320
- return path22.join(os22.tmpdir(), `esbuild-${crypto5.randomBytes(32).toString("hex")}`);
70225
+ return path22.join(os22.tmpdir(), `esbuild-${crypto42.randomBytes(32).toString("hex")}`);
71321
70226
  };
71322
70227
  var workerThreadService = null;
71323
70228
  var startWorkerThreadService = (worker_threads2) => {
@@ -73904,8 +72809,8 @@ var require_node22 = __commonJS2((exports) => {
73904
72809
  }
73905
72810
  } catch (err2) {}
73906
72811
  var bufferFrom = require_buffer_from();
73907
- function dynamicRequire(mod, request2) {
73908
- return mod.require(request2);
72812
+ function dynamicRequire(mod, request) {
72813
+ return mod.require(request);
73909
72814
  }
73910
72815
  var errorFormatterInstalled = false;
73911
72816
  var uncaughtShimInstalled = false;
@@ -76709,10 +75614,10 @@ If you have no idea what this means or what Pirates is, let me explain: Pirates
76709
75614
  var Module2 = __require2("module");
76710
75615
  var originalResolveFilename = Module2._resolveFilename;
76711
75616
  var coreModules = getCoreModules(Module2.builtinModules);
76712
- Module2._resolveFilename = function(request2, _parent) {
76713
- var isCoreModule = coreModules.hasOwnProperty(request2);
75617
+ Module2._resolveFilename = function(request, _parent) {
75618
+ var isCoreModule = coreModules.hasOwnProperty(request);
76714
75619
  if (!isCoreModule) {
76715
- var found = matchPath(request2);
75620
+ var found = matchPath(request);
76716
75621
  if (found) {
76717
75622
  var modifiedArguments = __spreadArray([found], [].slice.call(arguments, 1), true);
76718
75623
  return originalResolveFilename.apply(this, modifiedArguments);
@@ -76879,10 +75784,10 @@ If you have no idea what this means or what Pirates is, let me explain: Pirates
76879
75784
  const matchPath = (0, import_tsconfig_paths.createMatchPath)(configLoaderResult.absoluteBaseUrl, configLoaderResult.paths, configLoaderResult.mainFields, configLoaderResult.addMatchAll);
76880
75785
  const Module2 = __require2("module");
76881
75786
  const originalResolveFilename = Module2._resolveFilename;
76882
- Module2._resolveFilename = function(request2, _parent) {
76883
- const isCoreModule = _module2.builtinModules.includes(request2);
75787
+ Module2._resolveFilename = function(request, _parent) {
75788
+ const isCoreModule = _module2.builtinModules.includes(request);
76884
75789
  if (!isCoreModule) {
76885
- const found = matchPath(request2);
75790
+ const found = matchPath(request);
76886
75791
  if (found) {
76887
75792
  const modifiedArguments = [found, ...[].slice.call(arguments, 1)];
76888
75793
  return originalResolveFilename.apply(this, modifiedArguments);
@@ -79901,7 +78806,7 @@ var coerce2;
79901
78806
  var init_types8;
79902
78807
  var init_external2;
79903
78808
  var init_v32;
79904
- var init_esm3;
78809
+ var init_esm2;
79905
78810
  var enumSchema;
79906
78811
  var enumSchemaV1;
79907
78812
  var indexColumn;
@@ -82371,7 +81276,7 @@ var sqlitePushIntrospect = async (db2, filters) => {
82371
81276
  };
82372
81277
  var generateDrizzleJson = (imports, prevId, schemaFilters, casing2) => {
82373
81278
  const prepared = prepareFromExports(imports);
82374
- const id = randomUUID2();
81279
+ const id = randomUUID();
82375
81280
  const snapshot = generatePgSnapshot(prepared.tables, prepared.enums, prepared.schemas, prepared.sequences, prepared.roles, prepared.policies, prepared.views, prepared.matViews, casing2, schemaFilters);
82376
81281
  return fillPgSnapshot({
82377
81282
  serialized: snapshot,
@@ -82420,7 +81325,7 @@ var pushSchema = async (imports, drizzleInstance, schemaFilters, tablesFilter, e
82420
81325
  var generateSQLiteDrizzleJson = async (imports, prevId, casing2) => {
82421
81326
  const { prepareFromExports: prepareFromExports5 } = await Promise.resolve().then(() => (init_sqliteImports(), sqliteImports_exports));
82422
81327
  const prepared = prepareFromExports5(imports);
82423
- const id = randomUUID2();
81328
+ const id = randomUUID();
82424
81329
  const snapshot = generateSqliteSnapshot(prepared.tables, prepared.views, casing2);
82425
81330
  return {
82426
81331
  ...snapshot,
@@ -82471,7 +81376,7 @@ var pushSQLiteSchema = async (imports, drizzleInstance) => {
82471
81376
  var generateMySQLDrizzleJson = async (imports, prevId, casing2) => {
82472
81377
  const { prepareFromExports: prepareFromExports5 } = await Promise.resolve().then(() => (init_mysqlImports(), mysqlImports_exports));
82473
81378
  const prepared = prepareFromExports5(imports);
82474
- const id = randomUUID2();
81379
+ const id = randomUUID();
82475
81380
  const snapshot = generateMySqlSnapshot(prepared.tables, prepared.views, casing2);
82476
81381
  return {
82477
81382
  ...snapshot,
@@ -82521,7 +81426,7 @@ var pushMySQLSchema = async (imports, drizzleInstance, databaseName) => {
82521
81426
  var generateSingleStoreDrizzleJson = async (imports, prevId, casing2) => {
82522
81427
  const { prepareFromExports: prepareFromExports5 } = await Promise.resolve().then(() => (init_singlestoreImports(), singlestoreImports_exports));
82523
81428
  const prepared = prepareFromExports5(imports);
82524
- const id = randomUUID2();
81429
+ const id = randomUUID();
82525
81430
  const snapshot = generateSingleStoreSnapshot(prepared.tables, casing2);
82526
81431
  return {
82527
81432
  ...snapshot,
@@ -89311,7 +88216,7 @@ See: https://github.com/isaacs/node-glob/issues/167`);
89311
88216
  init_external2();
89312
88217
  }
89313
88218
  });
89314
- init_esm3 = __esm3({
88219
+ init_esm2 = __esm3({
89315
88220
  "../node_modules/.pnpm/zod@3.25.42/node_modules/zod/dist/esm/index.js"() {
89316
88221
  init_v32();
89317
88222
  init_v32();
@@ -89320,7 +88225,7 @@ See: https://github.com/isaacs/node-glob/issues/167`);
89320
88225
  init_gelSchema = __esm3({
89321
88226
  "src/serializer/gelSchema.ts"() {
89322
88227
  init_global2();
89323
- init_esm3();
88228
+ init_esm2();
89324
88229
  enumSchema = objectType2({
89325
88230
  name: stringType2(),
89326
88231
  schema: stringType2(),
@@ -89574,7 +88479,7 @@ See: https://github.com/isaacs/node-glob/issues/167`);
89574
88479
  });
89575
88480
  init_mysqlSchema = __esm3({
89576
88481
  "src/serializer/mysqlSchema.ts"() {
89577
- init_esm3();
88482
+ init_esm2();
89578
88483
  init_global2();
89579
88484
  index22 = objectType2({
89580
88485
  name: stringType2(),
@@ -89880,7 +88785,7 @@ See: https://github.com/isaacs/node-glob/issues/167`);
89880
88785
  init_pgSchema = __esm3({
89881
88786
  "src/serializer/pgSchema.ts"() {
89882
88787
  init_global2();
89883
- init_esm3();
88788
+ init_esm2();
89884
88789
  indexV2 = objectType2({
89885
88790
  name: stringType2(),
89886
88791
  columns: recordType2(stringType2(), objectType2({
@@ -90601,7 +89506,7 @@ See: https://github.com/isaacs/node-glob/issues/167`);
90601
89506
  });
90602
89507
  init_singlestoreSchema = __esm3({
90603
89508
  "src/serializer/singlestoreSchema.ts"() {
90604
- init_esm3();
89509
+ init_esm2();
90605
89510
  init_global2();
90606
89511
  index4 = objectType2({
90607
89512
  name: stringType2(),
@@ -90760,7 +89665,7 @@ See: https://github.com/isaacs/node-glob/issues/167`);
90760
89665
  });
90761
89666
  init_sqliteSchema = __esm3({
90762
89667
  "src/serializer/sqliteSchema.ts"() {
90763
- init_esm3();
89668
+ init_esm2();
90764
89669
  init_global2();
90765
89670
  index5 = objectType2({
90766
89671
  name: stringType2(),
@@ -98438,7 +97343,7 @@ ${BREAKPOINT}ALTER TABLE ${tableNameWithSchema} ADD CONSTRAINT "${statement.newC
98438
97343
  });
98439
97344
  init_snapshotsDiffer = __esm3({
98440
97345
  "src/snapshotsDiffer.ts"() {
98441
- init_esm3();
97346
+ init_esm2();
98442
97347
  init_jsonDiffer();
98443
97348
  init_sqlgenerator();
98444
97349
  init_jsonStatements();
@@ -100549,7 +99454,7 @@ ${BREAKPOINT}ALTER TABLE ${tableNameWithSchema} ADD CONSTRAINT "${statement.newC
100549
99454
  });
100550
99455
  init_schemaValidator = __esm3({
100551
99456
  "src/schemaValidator.ts"() {
100552
- init_esm3();
99457
+ init_esm2();
100553
99458
  init_mysqlSchema();
100554
99459
  init_pgSchema();
100555
99460
  init_singlestoreSchema();
@@ -100567,7 +99472,7 @@ ${BREAKPOINT}ALTER TABLE ${tableNameWithSchema} ADD CONSTRAINT "${statement.newC
100567
99472
  });
100568
99473
  init_common2 = __esm3({
100569
99474
  "src/cli/validations/common.ts"() {
100570
- init_esm3();
99475
+ init_esm2();
100571
99476
  init_schemaValidator();
100572
99477
  init_outputs();
100573
99478
  sqliteDriversLiterals = [
@@ -115769,7 +114674,7 @@ AND
115769
114674
  });
115770
114675
  init_cli = __esm3({
115771
114676
  "src/cli/validations/cli.ts"() {
115772
- init_esm3();
114677
+ init_esm2();
115773
114678
  init_schemaValidator();
115774
114679
  init_common2();
115775
114680
  cliConfigGenerate = objectType2({
@@ -115830,7 +114735,7 @@ AND
115830
114735
  });
115831
114736
  init_gel = __esm3({
115832
114737
  "src/cli/validations/gel.ts"() {
115833
- init_esm3();
114738
+ init_esm2();
115834
114739
  init_views();
115835
114740
  init_common2();
115836
114741
  gelCredentials = unionType2([
@@ -115874,7 +114779,7 @@ AND
115874
114779
  });
115875
114780
  init_libsql = __esm3({
115876
114781
  "src/cli/validations/libsql.ts"() {
115877
- init_esm3();
114782
+ init_esm2();
115878
114783
  init_views();
115879
114784
  init_common2();
115880
114785
  libSQLCredentials = objectType2({
@@ -115885,7 +114790,7 @@ AND
115885
114790
  });
115886
114791
  init_mysql = __esm3({
115887
114792
  "src/cli/validations/mysql.ts"() {
115888
- init_esm3();
114793
+ init_esm2();
115889
114794
  init_views();
115890
114795
  init_common2();
115891
114796
  init_outputs();
@@ -115918,7 +114823,7 @@ AND
115918
114823
  });
115919
114824
  init_postgres = __esm3({
115920
114825
  "src/cli/validations/postgres.ts"() {
115921
- init_esm3();
114826
+ init_esm2();
115922
114827
  init_views();
115923
114828
  init_common2();
115924
114829
  postgresCredentials = unionType2([
@@ -115963,7 +114868,7 @@ AND
115963
114868
  });
115964
114869
  init_singlestore = __esm3({
115965
114870
  "src/cli/validations/singlestore.ts"() {
115966
- init_esm3();
114871
+ init_esm2();
115967
114872
  init_views();
115968
114873
  init_common2();
115969
114874
  init_outputs();
@@ -115997,7 +114902,7 @@ AND
115997
114902
  init_sqlite = __esm3({
115998
114903
  "src/cli/validations/sqlite.ts"() {
115999
114904
  init_global2();
116000
- init_esm3();
114905
+ init_esm2();
116001
114906
  init_views();
116002
114907
  init_common2();
116003
114908
  sqliteCredentials = unionType2([
@@ -116024,7 +114929,7 @@ AND
116024
114929
  });
116025
114930
  init_studio = __esm3({
116026
114931
  "src/cli/validations/studio.ts"() {
116027
- init_esm3();
114932
+ init_esm2();
116028
114933
  init_schemaValidator();
116029
114934
  init_common2();
116030
114935
  init_mysql();
@@ -116056,7 +114961,7 @@ AND
116056
114961
  init_utils9 = __esm3({
116057
114962
  "src/cli/commands/utils.ts"() {
116058
114963
  import_hanji7 = __toESM22(require_hanji());
116059
- init_esm3();
114964
+ init_esm2();
116060
114965
  init_getTablesFilterByExtensions();
116061
114966
  init_global2();
116062
114967
  init_schemaValidator();
@@ -121502,6 +120407,137 @@ var init_auth_util = __esm(() => {
121502
120407
  init_errors();
121503
120408
  init_types9();
121504
120409
  });
120410
+ function generateUsername(email) {
120411
+ const baseUsername = (email.split("@")[0] || "user").toLowerCase();
120412
+ const cleanUsername = baseUsername.replace(/[^a-z0-9]/g, "");
120413
+ const randomSuffix = Math.random().toString(36).substring(2, 7);
120414
+ return `${cleanUsername}_${randomSuffix}`;
120415
+ }
120416
+ function extractRedirectPath(targetUri, currentHost) {
120417
+ try {
120418
+ const targetUrl = new URL(targetUri);
120419
+ if (targetUrl.hostname === currentHost) {
120420
+ return targetUrl.pathname + targetUrl.search;
120421
+ }
120422
+ } catch {}
120423
+ return "/";
120424
+ }
120425
+ function validateLtiClaims(claims) {
120426
+ const messageType = claims["https://purl.imsglobal.org/spec/lti/claim/message_type"];
120427
+ const version4 = claims["https://purl.imsglobal.org/spec/lti/claim/version"];
120428
+ if (messageType !== "LtiResourceLinkRequest") {
120429
+ return `Invalid LTI message type: ${messageType}`;
120430
+ }
120431
+ if (version4 !== "1.3.0") {
120432
+ return `Unsupported LTI version: ${version4}`;
120433
+ }
120434
+ return null;
120435
+ }
120436
+ var init_lti_util = () => {};
120437
+ async function provisionLtiUser(db2, claims) {
120438
+ const database2 = db2;
120439
+ const email = claims.email;
120440
+ const ltiTimebackId = claims.sub;
120441
+ const providerId = AUTH_PROVIDER_IDS.TIMEBACK_LTI;
120442
+ if (!email) {
120443
+ throw new ValidationError("Email is required in LTI claims");
120444
+ }
120445
+ const existingAccount = await database2.query.accounts.findFirst({
120446
+ where: and(eq(accounts.accountId, ltiTimebackId), eq(accounts.providerId, providerId))
120447
+ });
120448
+ if (existingAccount) {
120449
+ const user = await database2.query.users.findFirst({
120450
+ where: eq(users.id, existingAccount.userId)
120451
+ });
120452
+ if (user) {
120453
+ logger32.info("Found user by LTI account", {
120454
+ userId: user.id,
120455
+ ltiTimebackId
120456
+ });
120457
+ return user;
120458
+ }
120459
+ }
120460
+ const existingUser = await database2.query.users.findFirst({
120461
+ where: eq(users.email, email)
120462
+ });
120463
+ if (existingUser) {
120464
+ await database2.transaction(async (tx) => {
120465
+ const existingLtiAccount = await tx.query.accounts.findFirst({
120466
+ where: and(eq(accounts.userId, existingUser.id), eq(accounts.providerId, providerId))
120467
+ });
120468
+ if (!existingLtiAccount) {
120469
+ const [account] = await tx.insert(accounts).values({
120470
+ id: crypto4.randomUUID(),
120471
+ userId: existingUser.id,
120472
+ accountId: ltiTimebackId,
120473
+ providerId,
120474
+ accessToken: null,
120475
+ refreshToken: null,
120476
+ accessTokenExpiresAt: null,
120477
+ refreshTokenExpiresAt: null,
120478
+ createdAt: new Date,
120479
+ updatedAt: new Date
120480
+ }).returning({ id: accounts.id });
120481
+ if (!account) {
120482
+ logger32.error("LTI account link insert returned no rows", {
120483
+ userId: existingUser.id,
120484
+ ltiTimebackId
120485
+ });
120486
+ throw new InternalError("Failed to link LTI account");
120487
+ }
120488
+ logger32.info("Linked LTI account to existing user", {
120489
+ userId: existingUser.id,
120490
+ ltiTimebackId
120491
+ });
120492
+ }
120493
+ });
120494
+ return existingUser;
120495
+ }
120496
+ const newUserId = crypto4.randomUUID();
120497
+ const createdUser = await database2.transaction(async (tx) => {
120498
+ const [insertedUser] = await tx.insert(users).values({
120499
+ id: newUserId,
120500
+ email,
120501
+ emailVerified: true,
120502
+ username: generateUsername(email),
120503
+ name: claims.name || claims.given_name || email.split("@")[0] || "Timeback User",
120504
+ createdAt: new Date,
120505
+ updatedAt: new Date
120506
+ }).returning();
120507
+ if (!insertedUser) {
120508
+ logger32.error("LTI user insert returned no rows", { email, ltiTimebackId });
120509
+ throw new InternalError("Failed to create user");
120510
+ }
120511
+ await tx.insert(accounts).values({
120512
+ id: crypto4.randomUUID(),
120513
+ userId: newUserId,
120514
+ accountId: ltiTimebackId,
120515
+ providerId,
120516
+ accessToken: null,
120517
+ refreshToken: null,
120518
+ accessTokenExpiresAt: null,
120519
+ refreshTokenExpiresAt: null,
120520
+ createdAt: new Date,
120521
+ updatedAt: new Date
120522
+ });
120523
+ logger32.info("Provisioned new user from LTI", {
120524
+ userId: insertedUser.id,
120525
+ ltiTimebackId
120526
+ });
120527
+ return insertedUser;
120528
+ });
120529
+ return createdUser;
120530
+ }
120531
+ var logger32;
120532
+ var init_lti_provisioning = __esm(() => {
120533
+ init_drizzle_orm();
120534
+ init_src();
120535
+ init_tables_index();
120536
+ init_src2();
120537
+ init_errors();
120538
+ init_lti_util();
120539
+ logger32 = log.scope("LtiProvisioning");
120540
+ });
121505
120541
  function formatZodError(error2) {
121506
120542
  const flat = error2.flatten();
121507
120543
  const result = {};
@@ -121524,10 +120560,11 @@ var init_utils11 = __esm(() => {
121524
120560
  init_deployment_util();
121525
120561
  init_leaderboard_util();
121526
120562
  init_lti_util();
120563
+ init_lti_provisioning();
121527
120564
  init_scope_util();
121528
120565
  init_timeback_util();
121529
120566
  });
121530
- var logger32;
120567
+ var logger33;
121531
120568
  var listCurrent;
121532
120569
  var listHistory;
121533
120570
  var postProgress;
@@ -121538,14 +120575,14 @@ var init_achievement_controller = __esm(() => {
121538
120575
  init_src2();
121539
120576
  init_errors();
121540
120577
  init_utils11();
121541
- logger32 = log.scope("AchievementController");
120578
+ logger33 = log.scope("AchievementController");
121542
120579
  listCurrent = requireAuth(async (ctx) => {
121543
- logger32.debug("Listing current achievements", { userId: ctx.user.id, gameId: ctx.gameId });
120580
+ logger33.debug("Listing current achievements", { userId: ctx.user.id, gameId: ctx.gameId });
121544
120581
  return ctx.services.achievement.listCurrent(ctx.user, ctx.gameId);
121545
120582
  });
121546
120583
  listHistory = requireAuth(async (ctx) => {
121547
120584
  const limit = Math.max(1, Math.min(100, Number(ctx.url.searchParams.get("limit")) || 20));
121548
- logger32.debug("Listing achievement history", { userId: ctx.user.id, limit });
120585
+ logger33.debug("Listing achievement history", { userId: ctx.user.id, limit });
121549
120586
  return ctx.services.achievement.listHistory(ctx.user, limit);
121550
120587
  });
121551
120588
  postProgress = requireAuth(async (ctx) => {
@@ -121556,12 +120593,12 @@ var init_achievement_controller = __esm(() => {
121556
120593
  } catch (error2) {
121557
120594
  if (error2 instanceof exports_external.ZodError) {
121558
120595
  const details = formatZodError(error2);
121559
- logger32.warn("Submit achievement progress validation failed", { details });
120596
+ logger33.warn("Submit achievement progress validation failed", { details });
121560
120597
  throw ApiError.unprocessableEntity("Invalid request body", details);
121561
120598
  }
121562
120599
  throw ApiError.badRequest("Invalid JSON body");
121563
120600
  }
121564
- logger32.debug("Submitting progress", {
120601
+ logger33.debug("Submitting progress", {
121565
120602
  userId: ctx.user.id,
121566
120603
  achievementId: body2.achievementId
121567
120604
  });
@@ -121573,15 +120610,15 @@ var init_achievement_controller = __esm(() => {
121573
120610
  postProgress
121574
120611
  };
121575
120612
  });
121576
- var logger33;
120613
+ var logger34;
121577
120614
  var getAllowedOrigins;
121578
120615
  var init_admin_controller = __esm(() => {
121579
120616
  init_src2();
121580
120617
  init_utils11();
121581
- logger33 = log.scope("AdminController");
120618
+ logger34 = log.scope("AdminController");
121582
120619
  getAllowedOrigins = requireAdmin(async (ctx) => {
121583
120620
  const shouldRefresh = ctx.url.searchParams.get("refresh") === "true";
121584
- logger33.debug("Getting allowed origins", { userId: ctx.user.id, refresh: shouldRefresh });
120621
+ logger34.debug("Getting allowed origins", { userId: ctx.user.id, refresh: shouldRefresh });
121585
120622
  if (shouldRefresh) {
121586
120623
  await ctx.providers.cache.refreshGameOrigins();
121587
120624
  }
@@ -121594,7 +120631,7 @@ var init_admin_controller = __esm(() => {
121594
120631
  };
121595
120632
  });
121596
120633
  });
121597
- var logger34;
120634
+ var logger35;
121598
120635
  var listFiles;
121599
120636
  var getFile;
121600
120637
  var putFile;
@@ -121606,7 +120643,7 @@ var init_bucket_controller = __esm(() => {
121606
120643
  init_src2();
121607
120644
  init_errors();
121608
120645
  init_utils11();
121609
- logger34 = log.scope("BucketController");
120646
+ logger35 = log.scope("BucketController");
121610
120647
  listFiles = requireDeveloper(async (ctx) => {
121611
120648
  const slug2 = ctx.params.slug;
121612
120649
  if (!slug2) {
@@ -121614,7 +120651,7 @@ var init_bucket_controller = __esm(() => {
121614
120651
  }
121615
120652
  const url = ctx.url;
121616
120653
  const prefix2 = url.searchParams.get("prefix") || undefined;
121617
- logger34.debug("Listing files", { userId: ctx.user.id, slug: slug2, prefix: prefix2 });
120654
+ logger35.debug("Listing files", { userId: ctx.user.id, slug: slug2, prefix: prefix2 });
121618
120655
  const files = await ctx.services.bucket.listFiles(slug2, ctx.user, prefix2);
121619
120656
  return { files };
121620
120657
  });
@@ -121624,7 +120661,7 @@ var init_bucket_controller = __esm(() => {
121624
120661
  if (!slug2 || !key) {
121625
120662
  throw ApiError.badRequest("Missing game slug or file key");
121626
120663
  }
121627
- logger34.debug("Getting file", { userId: ctx.user.id, slug: slug2, key });
120664
+ logger35.debug("Getting file", { userId: ctx.user.id, slug: slug2, key });
121628
120665
  const object = await ctx.services.bucket.getFile(slug2, key, ctx.user);
121629
120666
  return new Response(Buffer.from(object.body), {
121630
120667
  status: 200,
@@ -121643,7 +120680,7 @@ var init_bucket_controller = __esm(() => {
121643
120680
  const arrayBuffer = await ctx.request.arrayBuffer();
121644
120681
  const body2 = new Uint8Array(arrayBuffer);
121645
120682
  const contentType = ctx.request.headers.get("content-type") || undefined;
121646
- logger34.debug("Uploading file", {
120683
+ logger35.debug("Uploading file", {
121647
120684
  userId: ctx.user.id,
121648
120685
  slug: slug2,
121649
120686
  key,
@@ -121659,7 +120696,7 @@ var init_bucket_controller = __esm(() => {
121659
120696
  if (!slug2 || !key) {
121660
120697
  throw ApiError.badRequest("Missing game slug or file key");
121661
120698
  }
121662
- logger34.debug("Deleting file", { userId: ctx.user.id, slug: slug2, key });
120699
+ logger35.debug("Deleting file", { userId: ctx.user.id, slug: slug2, key });
121663
120700
  await ctx.services.bucket.deleteFile(slug2, key, ctx.user);
121664
120701
  return { success: true, key };
121665
120702
  });
@@ -121671,12 +120708,12 @@ var init_bucket_controller = __esm(() => {
121671
120708
  } catch (error2) {
121672
120709
  if (error2 instanceof exports_external.ZodError) {
121673
120710
  const details = formatZodError(error2);
121674
- logger34.warn("Initiate upload validation failed", { details });
120711
+ logger35.warn("Initiate upload validation failed", { details });
121675
120712
  throw ApiError.unprocessableEntity("Validation failed", details);
121676
120713
  }
121677
120714
  throw ApiError.badRequest("Invalid JSON body");
121678
120715
  }
121679
- logger34.debug("Initiating multipart upload", {
120716
+ logger35.debug("Initiating multipart upload", {
121680
120717
  userId: ctx.user.id,
121681
120718
  gameId: body2.gameId,
121682
120719
  fileName: body2.fileName
@@ -121691,10 +120728,10 @@ async function listComponents(ctx) {
121691
120728
  if (!isNaN(parsed) && isFinite(parsed)) {
121692
120729
  level = Math.floor(Math.max(0, parsed));
121693
120730
  }
121694
- logger35.debug("Listing components", { level });
120731
+ logger36.debug("Listing components", { level });
121695
120732
  return ctx.services.character.listAvailableComponents(level);
121696
120733
  }
121697
- var logger35;
120734
+ var logger36;
121698
120735
  var get;
121699
120736
  var getByUserId;
121700
120737
  var create;
@@ -121708,9 +120745,9 @@ var init_character_controller = __esm(() => {
121708
120745
  init_src2();
121709
120746
  init_errors();
121710
120747
  init_utils11();
121711
- logger35 = log.scope("CharacterController");
120748
+ logger36 = log.scope("CharacterController");
121712
120749
  get = requireAuth(async (ctx) => {
121713
- logger35.debug("Getting character", { userId: ctx.user.id });
120750
+ logger36.debug("Getting character", { userId: ctx.user.id });
121714
120751
  return ctx.services.character.getByUser(ctx.user);
121715
120752
  });
121716
120753
  getByUserId = requireAuth(async (ctx) => {
@@ -121718,7 +120755,7 @@ var init_character_controller = __esm(() => {
121718
120755
  if (!userId) {
121719
120756
  throw ApiError.badRequest("User ID is required in the URL path");
121720
120757
  }
121721
- logger35.debug("Getting character by user ID", { requestedUserId: userId });
120758
+ logger36.debug("Getting character by user ID", { requestedUserId: userId });
121722
120759
  return ctx.services.character.getByUserId(userId);
121723
120760
  });
121724
120761
  create = requireAuth(async (ctx) => {
@@ -121729,12 +120766,12 @@ var init_character_controller = __esm(() => {
121729
120766
  } catch (error2) {
121730
120767
  if (error2 instanceof exports_external.ZodError) {
121731
120768
  const details = formatZodError(error2);
121732
- logger35.warn("Create character validation failed", { details });
120769
+ logger36.warn("Create character validation failed", { details });
121733
120770
  throw ApiError.unprocessableEntity("Invalid request body", details);
121734
120771
  }
121735
120772
  throw ApiError.badRequest("Invalid JSON body");
121736
120773
  }
121737
- logger35.debug("Creating character", {
120774
+ logger36.debug("Creating character", {
121738
120775
  userId: ctx.user.id,
121739
120776
  bodyComponentId: body2.bodyComponentId,
121740
120777
  hairstyleComponentId: body2.hairstyleComponentId
@@ -121749,12 +120786,12 @@ var init_character_controller = __esm(() => {
121749
120786
  } catch (error2) {
121750
120787
  if (error2 instanceof exports_external.ZodError) {
121751
120788
  const details = formatZodError(error2);
121752
- logger35.warn("Update character validation failed", { details });
120789
+ logger36.warn("Update character validation failed", { details });
121753
120790
  throw ApiError.unprocessableEntity("Invalid request body", details);
121754
120791
  }
121755
120792
  throw ApiError.badRequest("Invalid JSON body");
121756
120793
  }
121757
- logger35.debug("Updating character", {
120794
+ logger36.debug("Updating character", {
121758
120795
  userId: ctx.user.id,
121759
120796
  bodyComponentId: body2.bodyComponentId,
121760
120797
  hairstyleComponentId: body2.hairstyleComponentId,
@@ -121770,12 +120807,12 @@ var init_character_controller = __esm(() => {
121770
120807
  } catch (error2) {
121771
120808
  if (error2 instanceof exports_external.ZodError) {
121772
120809
  const details = formatZodError(error2);
121773
- logger35.warn("Equip accessory validation failed", { details });
120810
+ logger36.warn("Equip accessory validation failed", { details });
121774
120811
  throw ApiError.unprocessableEntity("Invalid request body", details);
121775
120812
  }
121776
120813
  throw ApiError.badRequest("Invalid JSON body");
121777
120814
  }
121778
- logger35.debug("Equipping accessory", {
120815
+ logger36.debug("Equipping accessory", {
121779
120816
  userId: ctx.user.id,
121780
120817
  slot: body2.slot,
121781
120818
  accessoryComponentId: body2.accessoryComponentId
@@ -121787,7 +120824,7 @@ var init_character_controller = __esm(() => {
121787
120824
  if (!slot) {
121788
120825
  throw ApiError.badRequest("Slot is required in the URL path");
121789
120826
  }
121790
- logger35.debug("Removing accessory", { userId: ctx.user.id, slot });
120827
+ logger36.debug("Removing accessory", { userId: ctx.user.id, slot });
121791
120828
  await ctx.services.character.removeAccessory(slot, ctx.user);
121792
120829
  return { success: true };
121793
120830
  });
@@ -121801,7 +120838,7 @@ var init_character_controller = __esm(() => {
121801
120838
  removeAccessory
121802
120839
  };
121803
120840
  });
121804
- var logger36;
120841
+ var logger37;
121805
120842
  var list;
121806
120843
  var getById;
121807
120844
  var create2;
@@ -121815,9 +120852,9 @@ var init_currency_controller = __esm(() => {
121815
120852
  init_src4();
121816
120853
  init_errors();
121817
120854
  init_utils11();
121818
- logger36 = log.scope("CurrencyController");
120855
+ logger37 = log.scope("CurrencyController");
121819
120856
  list = requireAuth(async (ctx) => {
121820
- logger36.debug("Listing currencies", { userId: ctx.user.id });
120857
+ logger37.debug("Listing currencies", { userId: ctx.user.id });
121821
120858
  return ctx.services.currency.list();
121822
120859
  });
121823
120860
  getById = requireAuth(async (ctx) => {
@@ -121828,7 +120865,7 @@ var init_currency_controller = __esm(() => {
121828
120865
  if (!isValidUUID(currencyId)) {
121829
120866
  throw ApiError.unprocessableEntity("currencyId must be a valid UUID format");
121830
120867
  }
121831
- logger36.debug("Getting currency", { userId: ctx.user.id, currencyId });
120868
+ logger37.debug("Getting currency", { userId: ctx.user.id, currencyId });
121832
120869
  return ctx.services.currency.getById(currencyId);
121833
120870
  });
121834
120871
  create2 = requireAdmin(async (ctx) => {
@@ -121839,12 +120876,12 @@ var init_currency_controller = __esm(() => {
121839
120876
  } catch (error2) {
121840
120877
  if (error2 instanceof exports_external.ZodError) {
121841
120878
  const details = formatZodError(error2);
121842
- logger36.warn("Create currency validation failed", { details });
120879
+ logger37.warn("Create currency validation failed", { details });
121843
120880
  throw ApiError.unprocessableEntity("Validation failed", details);
121844
120881
  }
121845
120882
  throw ApiError.badRequest("Invalid JSON body");
121846
120883
  }
121847
- logger36.debug("Creating currency", {
120884
+ logger37.debug("Creating currency", {
121848
120885
  userId: ctx.user.id,
121849
120886
  symbol: body2.symbol,
121850
120887
  itemId: body2.itemId,
@@ -121867,12 +120904,12 @@ var init_currency_controller = __esm(() => {
121867
120904
  } catch (error2) {
121868
120905
  if (error2 instanceof exports_external.ZodError) {
121869
120906
  const details = formatZodError(error2);
121870
- logger36.warn("Update currency validation failed", { details });
120907
+ logger37.warn("Update currency validation failed", { details });
121871
120908
  throw ApiError.unprocessableEntity("Validation failed", details);
121872
120909
  }
121873
120910
  throw ApiError.badRequest("Invalid JSON body");
121874
120911
  }
121875
- logger36.debug("Updating currency", {
120912
+ logger37.debug("Updating currency", {
121876
120913
  userId: ctx.user.id,
121877
120914
  currencyId,
121878
120915
  symbol: body2.symbol,
@@ -121889,7 +120926,7 @@ var init_currency_controller = __esm(() => {
121889
120926
  if (!isValidUUID(currencyId)) {
121890
120927
  throw ApiError.unprocessableEntity("currencyId must be a valid UUID format");
121891
120928
  }
121892
- logger36.debug("Deleting currency", { userId: ctx.user.id, currencyId });
120929
+ logger37.debug("Deleting currency", { userId: ctx.user.id, currencyId });
121893
120930
  await ctx.services.currency.delete(currencyId);
121894
120931
  });
121895
120932
  currencyController = {
@@ -121900,7 +120937,7 @@ var init_currency_controller = __esm(() => {
121900
120937
  remove
121901
120938
  };
121902
120939
  });
121903
- var logger37;
120940
+ var logger38;
121904
120941
  var reset;
121905
120942
  var init_database_controller = __esm(() => {
121906
120943
  init_esm();
@@ -121908,7 +120945,7 @@ var init_database_controller = __esm(() => {
121908
120945
  init_src2();
121909
120946
  init_errors();
121910
120947
  init_utils11();
121911
- logger37 = log.scope("DatabaseController");
120948
+ logger38 = log.scope("DatabaseController");
121912
120949
  reset = requireDeveloper(async (ctx) => {
121913
120950
  const slug2 = ctx.params.slug;
121914
120951
  if (!slug2) {
@@ -121921,11 +120958,11 @@ var init_database_controller = __esm(() => {
121921
120958
  } catch (error2) {
121922
120959
  if (error2 instanceof exports_external.ZodError) {
121923
120960
  const details = formatZodError(error2);
121924
- logger37.warn("Database reset validation failed", { details });
120961
+ logger38.warn("Database reset validation failed", { details });
121925
120962
  throw ApiError.unprocessableEntity("Validation failed", details);
121926
120963
  }
121927
120964
  }
121928
- logger37.debug("Resetting database", {
120965
+ logger38.debug("Resetting database", {
121929
120966
  userId: ctx.user.id,
121930
120967
  slug: slug2,
121931
120968
  hasSchema: !!body2.schema
@@ -130879,29 +129916,29 @@ var init_zip = __esm(() => {
130879
129916
  init_src2();
130880
129917
  import_jszip = __toESM2(require_lib4(), 1);
130881
129918
  });
130882
- var logger38;
129919
+ var logger39;
130883
129920
  var init_deploy_controller = __esm(() => {
130884
129921
  init_schemas_index();
130885
129922
  init_src2();
130886
129923
  init_zip();
130887
129924
  init_errors();
130888
129925
  init_utils11();
130889
- logger38 = log.scope("DeployController");
129926
+ logger39 = log.scope("DeployController");
130890
129927
  });
130891
- var logger39;
129928
+ var logger40;
130892
129929
  var apply;
130893
129930
  var getStatus;
130894
129931
  var developer;
130895
129932
  var init_developer_controller = __esm(() => {
130896
129933
  init_src2();
130897
129934
  init_utils11();
130898
- logger39 = log.scope("DeveloperController");
129935
+ logger40 = log.scope("DeveloperController");
130899
129936
  apply = requireAuth(async (ctx) => {
130900
- logger39.debug("Applying for developer status", { userId: ctx.user.id });
129937
+ logger40.debug("Applying for developer status", { userId: ctx.user.id });
130901
129938
  await ctx.services.developer.apply(ctx.user);
130902
129939
  });
130903
129940
  getStatus = requireAuth(async (ctx) => {
130904
- logger39.debug("Getting developer status", { userId: ctx.user.id });
129941
+ logger40.debug("Getting developer status", { userId: ctx.user.id });
130905
129942
  const status = await ctx.services.developer.getStatus(ctx.user.id);
130906
129943
  return { status };
130907
129944
  });
@@ -130910,7 +129947,7 @@ var init_developer_controller = __esm(() => {
130910
129947
  getStatus
130911
129948
  };
130912
129949
  });
130913
- var logger40;
129950
+ var logger41;
130914
129951
  var add;
130915
129952
  var list2;
130916
129953
  var getStatus2;
@@ -130923,7 +129960,7 @@ var init_domain_controller = __esm(() => {
130923
129960
  init_config2();
130924
129961
  init_errors();
130925
129962
  init_utils11();
130926
- logger40 = log.scope("DomainController");
129963
+ logger41 = log.scope("DomainController");
130927
129964
  add = requireDeveloper(async (ctx) => {
130928
129965
  const slug2 = ctx.params.slug;
130929
129966
  if (!slug2) {
@@ -130936,12 +129973,12 @@ var init_domain_controller = __esm(() => {
130936
129973
  } catch (error2) {
130937
129974
  if (error2 instanceof exports_external.ZodError) {
130938
129975
  const details = formatZodError(error2);
130939
- logger40.warn("Add domain validation failed", { details });
129976
+ logger41.warn("Add domain validation failed", { details });
130940
129977
  throw ApiError.unprocessableEntity("Validation failed", details);
130941
129978
  }
130942
129979
  throw ApiError.badRequest("Invalid JSON body");
130943
129980
  }
130944
- logger40.debug("Adding domain", { userId: ctx.user.id, slug: slug2, hostname: body2.hostname });
129981
+ logger41.debug("Adding domain", { userId: ctx.user.id, slug: slug2, hostname: body2.hostname });
130945
129982
  return ctx.services.domain.add(slug2, body2.hostname, body2.environment, ctx.user);
130946
129983
  });
130947
129984
  list2 = requireDeveloper(async (ctx) => {
@@ -130950,7 +129987,7 @@ var init_domain_controller = __esm(() => {
130950
129987
  throw ApiError.badRequest("Missing game slug");
130951
129988
  }
130952
129989
  const environment = getPlatformEnvironment(ctx.config);
130953
- logger40.debug("Listing domains", { userId: ctx.user.id, slug: slug2, environment });
129990
+ logger41.debug("Listing domains", { userId: ctx.user.id, slug: slug2, environment });
130954
129991
  const domains22 = await ctx.services.domain.list(slug2, environment, ctx.user);
130955
129992
  return { domains: domains22 };
130956
129993
  });
@@ -130965,7 +130002,7 @@ var init_domain_controller = __esm(() => {
130965
130002
  }
130966
130003
  const refresh = ctx.url.searchParams.get("refresh") === "true";
130967
130004
  const environment = getPlatformEnvironment(ctx.config);
130968
- logger40.debug("Getting domain status", { userId: ctx.user.id, slug: slug2, hostname, refresh });
130005
+ logger41.debug("Getting domain status", { userId: ctx.user.id, slug: slug2, hostname, refresh });
130969
130006
  return ctx.services.domain.getStatus(slug2, hostname, environment, ctx.user, refresh);
130970
130007
  });
130971
130008
  remove2 = requireDeveloper(async (ctx) => {
@@ -130978,7 +130015,7 @@ var init_domain_controller = __esm(() => {
130978
130015
  throw ApiError.badRequest("Missing hostname");
130979
130016
  }
130980
130017
  const environment = ctx.config.stage === "production" ? "production" : "staging";
130981
- logger40.debug("Removing domain", { userId: ctx.user.id, slug: slug2, hostname, environment });
130018
+ logger41.debug("Removing domain", { userId: ctx.user.id, slug: slug2, hostname, environment });
130982
130019
  await ctx.services.domain.delete(slug2, hostname, environment, ctx.user);
130983
130020
  });
130984
130021
  domains2 = {
@@ -130988,7 +130025,7 @@ var init_domain_controller = __esm(() => {
130988
130025
  remove: remove2
130989
130026
  };
130990
130027
  });
130991
- var logger41;
130028
+ var logger42;
130992
130029
  var list3;
130993
130030
  var getById2;
130994
130031
  var getBySlug;
@@ -131002,9 +130039,9 @@ var init_game_controller = __esm(() => {
131002
130039
  init_src4();
131003
130040
  init_errors();
131004
130041
  init_utils11();
131005
- logger41 = log.scope("GameController");
130042
+ logger42 = log.scope("GameController");
131006
130043
  list3 = requireAuth(async (ctx) => {
131007
- logger41.debug("Listing games", { userId: ctx.user.id });
130044
+ logger42.debug("Listing games", { userId: ctx.user.id });
131008
130045
  return ctx.services.game.list();
131009
130046
  });
131010
130047
  getById2 = requireAuth(async (ctx) => {
@@ -131015,7 +130052,7 @@ var init_game_controller = __esm(() => {
131015
130052
  if (!isValidUUID(gameId)) {
131016
130053
  throw ApiError.unprocessableEntity("gameId must be a valid UUID format");
131017
130054
  }
131018
- logger41.debug("Getting game by ID", { userId: ctx.user.id, gameId });
130055
+ logger42.debug("Getting game by ID", { userId: ctx.user.id, gameId });
131019
130056
  return ctx.services.game.getById(gameId);
131020
130057
  });
131021
130058
  getBySlug = requireAuth(async (ctx) => {
@@ -131023,7 +130060,7 @@ var init_game_controller = __esm(() => {
131023
130060
  if (!slug2) {
131024
130061
  throw ApiError.badRequest("Missing game slug");
131025
130062
  }
131026
- logger41.debug("Getting game by slug", { userId: ctx.user.id, slug: slug2 });
130063
+ logger42.debug("Getting game by slug", { userId: ctx.user.id, slug: slug2 });
131027
130064
  return ctx.services.game.getBySlug(slug2);
131028
130065
  });
131029
130066
  upsertBySlug = requireAuth(async (ctx) => {
@@ -131038,12 +130075,12 @@ var init_game_controller = __esm(() => {
131038
130075
  } catch (error2) {
131039
130076
  if (error2 instanceof exports_external.ZodError) {
131040
130077
  const details = formatZodError(error2);
131041
- logger41.warn("Upsert game validation failed", { details });
130078
+ logger42.warn("Upsert game validation failed", { details });
131042
130079
  throw ApiError.unprocessableEntity("Validation failed", details);
131043
130080
  }
131044
130081
  throw ApiError.badRequest("Invalid JSON body");
131045
130082
  }
131046
- logger41.debug("Upserting game", { userId: ctx.user.id, slug: slug2, displayName: body2.displayName });
130083
+ logger42.debug("Upserting game", { userId: ctx.user.id, slug: slug2, displayName: body2.displayName });
131047
130084
  return ctx.services.game.upsertBySlug(slug2, body2, ctx.user);
131048
130085
  });
131049
130086
  remove3 = requireAuth(async (ctx) => {
@@ -131054,7 +130091,7 @@ var init_game_controller = __esm(() => {
131054
130091
  if (!isValidUUID(gameId)) {
131055
130092
  throw ApiError.unprocessableEntity("gameId must be a valid UUID format");
131056
130093
  }
131057
- logger41.debug("Deleting game", { userId: ctx.user.id, gameId });
130094
+ logger42.debug("Deleting game", { userId: ctx.user.id, gameId });
131058
130095
  await ctx.services.game.delete(gameId, ctx.user);
131059
130096
  });
131060
130097
  games2 = {
@@ -131065,7 +130102,7 @@ var init_game_controller = __esm(() => {
131065
130102
  remove: remove3
131066
130103
  };
131067
130104
  });
131068
- var logger42;
130105
+ var logger43;
131069
130106
  var list4;
131070
130107
  var addItem;
131071
130108
  var removeItem;
@@ -131076,9 +130113,9 @@ var init_inventory_controller = __esm(() => {
131076
130113
  init_src2();
131077
130114
  init_errors();
131078
130115
  init_utils11();
131079
- logger42 = log.scope("InventoryController");
130116
+ logger43 = log.scope("InventoryController");
131080
130117
  list4 = requireAuth(async (ctx) => {
131081
- logger42.debug("Listing inventory", { userId: ctx.user.id });
130118
+ logger43.debug("Listing inventory", { userId: ctx.user.id });
131082
130119
  return ctx.services.inventory.list(ctx.user);
131083
130120
  });
131084
130121
  addItem = requireAuth(async (ctx) => {
@@ -131089,12 +130126,12 @@ var init_inventory_controller = __esm(() => {
131089
130126
  } catch (error2) {
131090
130127
  if (error2 instanceof exports_external.ZodError) {
131091
130128
  const details = formatZodError(error2);
131092
- logger42.warn("Add inventory item validation failed", { details });
130129
+ logger43.warn("Add inventory item validation failed", { details });
131093
130130
  throw ApiError.unprocessableEntity("Invalid request body", details);
131094
130131
  }
131095
130132
  throw ApiError.badRequest("Invalid JSON body");
131096
130133
  }
131097
- logger42.debug("Adding item", {
130134
+ logger43.debug("Adding item", {
131098
130135
  userId: ctx.user.id,
131099
130136
  itemId: body2.itemId,
131100
130137
  qty: body2.qty
@@ -131109,12 +130146,12 @@ var init_inventory_controller = __esm(() => {
131109
130146
  } catch (error2) {
131110
130147
  if (error2 instanceof exports_external.ZodError) {
131111
130148
  const details = formatZodError(error2);
131112
- logger42.warn("Remove inventory item validation failed", { details });
130149
+ logger43.warn("Remove inventory item validation failed", { details });
131113
130150
  throw ApiError.unprocessableEntity("Invalid request body", details);
131114
130151
  }
131115
130152
  throw ApiError.badRequest("Invalid JSON body");
131116
130153
  }
131117
- logger42.debug("Removing item", {
130154
+ logger43.debug("Removing item", {
131118
130155
  userId: ctx.user.id,
131119
130156
  itemId: body2.itemId,
131120
130157
  qty: body2.qty
@@ -131127,7 +130164,7 @@ var init_inventory_controller = __esm(() => {
131127
130164
  removeItem
131128
130165
  };
131129
130166
  });
131130
- var logger43;
130167
+ var logger44;
131131
130168
  var list5;
131132
130169
  var getById3;
131133
130170
  var resolve2;
@@ -131146,10 +130183,10 @@ var init_item_controller = __esm(() => {
131146
130183
  init_src4();
131147
130184
  init_errors();
131148
130185
  init_utils11();
131149
- logger43 = log.scope("ItemController");
130186
+ logger44 = log.scope("ItemController");
131150
130187
  list5 = requireAuth(async (ctx) => {
131151
130188
  const gameId = ctx.url.searchParams.get("gameId") || undefined;
131152
- logger43.debug("Listing items", { userId: ctx.user.id, gameId });
130189
+ logger44.debug("Listing items", { userId: ctx.user.id, gameId });
131153
130190
  return ctx.services.item.list(gameId);
131154
130191
  });
131155
130192
  getById3 = requireAuth(async (ctx) => {
@@ -131160,7 +130197,7 @@ var init_item_controller = __esm(() => {
131160
130197
  if (!isValidUUID(itemId)) {
131161
130198
  throw ApiError.unprocessableEntity("itemId must be a valid UUID format");
131162
130199
  }
131163
- logger43.debug("Getting item", { userId: ctx.user.id, itemId });
130200
+ logger44.debug("Getting item", { userId: ctx.user.id, itemId });
131164
130201
  return ctx.services.item.getById(itemId);
131165
130202
  });
131166
130203
  resolve2 = requireAuth(async (ctx) => {
@@ -131172,7 +130209,7 @@ var init_item_controller = __esm(() => {
131172
130209
  if (gameId && !isValidUUID(gameId)) {
131173
130210
  throw ApiError.unprocessableEntity("gameId must be a valid UUID format");
131174
130211
  }
131175
- logger43.debug("Resolving item", { userId: ctx.user.id, slug: slug2, gameId });
130212
+ logger44.debug("Resolving item", { userId: ctx.user.id, slug: slug2, gameId });
131176
130213
  return ctx.services.item.resolveBySlug(slug2, gameId);
131177
130214
  });
131178
130215
  create3 = requireRole(["admin"], async (ctx) => {
@@ -131183,12 +130220,12 @@ var init_item_controller = __esm(() => {
131183
130220
  } catch (error2) {
131184
130221
  if (error2 instanceof exports_external.ZodError) {
131185
130222
  const details = formatZodError(error2);
131186
- logger43.warn("Create item validation failed", { details });
130223
+ logger44.warn("Create item validation failed", { details });
131187
130224
  throw ApiError.unprocessableEntity("Validation failed", details);
131188
130225
  }
131189
130226
  throw ApiError.badRequest("Invalid JSON body");
131190
130227
  }
131191
- logger43.debug("Creating item", {
130228
+ logger44.debug("Creating item", {
131192
130229
  userId: ctx.user.id,
131193
130230
  slug: body2.slug,
131194
130231
  displayName: body2.displayName
@@ -131210,7 +130247,7 @@ var init_item_controller = __esm(() => {
131210
130247
  } catch (error2) {
131211
130248
  if (error2 instanceof exports_external.ZodError) {
131212
130249
  const details = formatZodError(error2);
131213
- logger43.warn("Update item validation failed", { details });
130250
+ logger44.warn("Update item validation failed", { details });
131214
130251
  throw ApiError.unprocessableEntity("Validation failed", details);
131215
130252
  }
131216
130253
  throw ApiError.badRequest("Invalid JSON body");
@@ -131218,7 +130255,7 @@ var init_item_controller = __esm(() => {
131218
130255
  if (Object.keys(body2).length === 0) {
131219
130256
  throw ApiError.badRequest("No update data provided");
131220
130257
  }
131221
- logger43.debug("Updating item", {
130258
+ logger44.debug("Updating item", {
131222
130259
  userId: ctx.user.id,
131223
130260
  itemId,
131224
130261
  slug: body2.slug,
@@ -131235,7 +130272,7 @@ var init_item_controller = __esm(() => {
131235
130272
  if (!isValidUUID(itemId)) {
131236
130273
  throw ApiError.unprocessableEntity("itemId must be a valid UUID format");
131237
130274
  }
131238
- logger43.debug("Deleting item", { userId: ctx.user.id, itemId });
130275
+ logger44.debug("Deleting item", { userId: ctx.user.id, itemId });
131239
130276
  await ctx.services.item.delete(itemId);
131240
130277
  });
131241
130278
  listByGame = requireAuth(async (ctx) => {
@@ -131246,7 +130283,7 @@ var init_item_controller = __esm(() => {
131246
130283
  if (!isValidUUID(gameId)) {
131247
130284
  throw ApiError.unprocessableEntity("gameId must be a valid UUID format");
131248
130285
  }
131249
- logger43.debug("Listing game items", { userId: ctx.user.id, gameId });
130286
+ logger44.debug("Listing game items", { userId: ctx.user.id, gameId });
131250
130287
  return ctx.services.item.listByGame(gameId);
131251
130288
  });
131252
130289
  createForGame = requireAuth(async (ctx) => {
@@ -131264,12 +130301,12 @@ var init_item_controller = __esm(() => {
131264
130301
  } catch (error2) {
131265
130302
  if (error2 instanceof exports_external.ZodError) {
131266
130303
  const details = formatZodError(error2);
131267
- logger43.warn("Create game item validation failed", { details });
130304
+ logger44.warn("Create game item validation failed", { details });
131268
130305
  throw ApiError.unprocessableEntity("Validation failed", details);
131269
130306
  }
131270
130307
  throw ApiError.badRequest("Invalid JSON body");
131271
130308
  }
131272
- logger43.debug("Creating game item", {
130309
+ logger44.debug("Creating game item", {
131273
130310
  userId: ctx.user.id,
131274
130311
  gameId,
131275
130312
  slug: body2.slug,
@@ -131296,7 +130333,7 @@ var init_item_controller = __esm(() => {
131296
130333
  } catch (error2) {
131297
130334
  if (error2 instanceof exports_external.ZodError) {
131298
130335
  const details = formatZodError(error2);
131299
- logger43.warn("Update game item validation failed", { details });
130336
+ logger44.warn("Update game item validation failed", { details });
131300
130337
  throw ApiError.unprocessableEntity("Validation failed", details);
131301
130338
  }
131302
130339
  throw ApiError.badRequest("Invalid JSON body");
@@ -131304,7 +130341,7 @@ var init_item_controller = __esm(() => {
131304
130341
  if (Object.keys(body2).length === 0) {
131305
130342
  throw ApiError.badRequest("No update data provided");
131306
130343
  }
131307
- logger43.debug("Updating game item", {
130344
+ logger44.debug("Updating game item", {
131308
130345
  userId: ctx.user.id,
131309
130346
  gameId,
131310
130347
  itemId,
@@ -131326,7 +130363,7 @@ var init_item_controller = __esm(() => {
131326
130363
  if (!isValidUUID(itemId)) {
131327
130364
  throw ApiError.unprocessableEntity("itemId must be a valid UUID format");
131328
130365
  }
131329
- logger43.debug("Deleting game item", { userId: ctx.user.id, gameId, itemId });
130366
+ logger44.debug("Deleting game item", { userId: ctx.user.id, gameId, itemId });
131330
130367
  await ctx.services.item.deleteForGame(gameId, itemId, ctx.user);
131331
130368
  });
131332
130369
  items2 = {
@@ -131342,7 +130379,7 @@ var init_item_controller = __esm(() => {
131342
130379
  deleteForGame
131343
130380
  };
131344
130381
  });
131345
- var logger44;
130382
+ var logger45;
131346
130383
  var submitScore;
131347
130384
  var getGlobalLeaderboard;
131348
130385
  var getLeaderboard;
@@ -131357,7 +130394,7 @@ var init_leaderboard_controller = __esm(() => {
131357
130394
  init_src4();
131358
130395
  init_errors();
131359
130396
  init_utils11();
131360
- logger44 = log.scope("LeaderboardController");
130397
+ logger45 = log.scope("LeaderboardController");
131361
130398
  submitScore = requireAuth(async (ctx) => {
131362
130399
  const gameId = ctx.params.gameId;
131363
130400
  if (!gameId) {
@@ -131370,12 +130407,12 @@ var init_leaderboard_controller = __esm(() => {
131370
130407
  } catch (error2) {
131371
130408
  if (error2 instanceof exports_external.ZodError) {
131372
130409
  const details = formatZodError(error2);
131373
- logger44.warn("Submit score validation failed", { details });
130410
+ logger45.warn("Submit score validation failed", { details });
131374
130411
  throw ApiError.unprocessableEntity("Validation failed", details);
131375
130412
  }
131376
130413
  throw ApiError.badRequest("Invalid JSON body");
131377
130414
  }
131378
- logger44.debug("Submitting score", {
130415
+ logger45.debug("Submitting score", {
131379
130416
  userId: ctx.user.id,
131380
130417
  gameId,
131381
130418
  score: body2.score
@@ -131398,12 +130435,12 @@ var init_leaderboard_controller = __esm(() => {
131398
130435
  } catch (error2) {
131399
130436
  if (error2 instanceof exports_external.ZodError) {
131400
130437
  const details = formatZodError(error2);
131401
- logger44.warn("Get global leaderboard query validation failed", { details });
130438
+ logger45.warn("Get global leaderboard query validation failed", { details });
131402
130439
  throw ApiError.badRequest("Invalid query parameters", details);
131403
130440
  }
131404
130441
  throw ApiError.badRequest("Invalid query parameters");
131405
130442
  }
131406
- logger44.debug("Getting global leaderboard", {
130443
+ logger45.debug("Getting global leaderboard", {
131407
130444
  userId: ctx.user.id,
131408
130445
  gameId,
131409
130446
  ...query
@@ -131426,12 +130463,12 @@ var init_leaderboard_controller = __esm(() => {
131426
130463
  } catch (error2) {
131427
130464
  if (error2 instanceof exports_external.ZodError) {
131428
130465
  const details = formatZodError(error2);
131429
- logger44.warn("Get leaderboard query validation failed", { details });
130466
+ logger45.warn("Get leaderboard query validation failed", { details });
131430
130467
  throw ApiError.badRequest("Invalid query parameters", details);
131431
130468
  }
131432
130469
  throw ApiError.badRequest("Invalid query parameters");
131433
130470
  }
131434
- logger44.debug("Getting leaderboard", {
130471
+ logger45.debug("Getting leaderboard", {
131435
130472
  userId: ctx.user.id,
131436
130473
  gameId,
131437
130474
  ...query
@@ -131446,7 +130483,7 @@ var init_leaderboard_controller = __esm(() => {
131446
130483
  if (!isValidUUID(userId)) {
131447
130484
  throw ApiError.unprocessableEntity("userId must be a valid UUID format");
131448
130485
  }
131449
- logger44.debug("Getting user rank", {
130486
+ logger45.debug("Getting user rank", {
131450
130487
  requesterId: ctx.user.id,
131451
130488
  gameId,
131452
130489
  targetUserId: userId
@@ -131464,7 +130501,7 @@ var init_leaderboard_controller = __esm(() => {
131464
130501
  const url = ctx.url;
131465
130502
  const limit = Math.min(Number(url.searchParams.get("limit") || "50"), 100);
131466
130503
  const gameId = url.searchParams.get("gameId") || undefined;
131467
- logger44.debug("Getting user all scores", {
130504
+ logger45.debug("Getting user all scores", {
131468
130505
  requesterId: ctx.user.id,
131469
130506
  targetUserId: userId,
131470
130507
  gameId,
@@ -131482,7 +130519,7 @@ var init_leaderboard_controller = __esm(() => {
131482
130519
  }
131483
130520
  const url = ctx.url;
131484
130521
  const limit = Math.min(Number(url.searchParams.get("limit") || "10"), 100);
131485
- logger44.debug("Getting user scores", {
130522
+ logger45.debug("Getting user scores", {
131486
130523
  requesterId: ctx.user.id,
131487
130524
  gameId,
131488
130525
  targetUserId: userId,
@@ -131500,7 +130537,7 @@ var init_leaderboard_controller = __esm(() => {
131500
130537
  };
131501
130538
  });
131502
130539
  async function listConfigs(ctx) {
131503
- logger45.debug("Listing level configs");
130540
+ logger46.debug("Listing level configs");
131504
130541
  return ctx.services.level.listConfigs();
131505
130542
  }
131506
130543
  async function getConfig(ctx) {
@@ -131512,10 +130549,10 @@ async function getConfig(ctx) {
131512
130549
  if (isNaN(level) || level < 1) {
131513
130550
  throw ApiError.badRequest("Level must be a positive integer");
131514
130551
  }
131515
- logger45.debug("Getting level config", { level });
130552
+ logger46.debug("Getting level config", { level });
131516
130553
  return ctx.services.level.getConfig(level);
131517
130554
  }
131518
- var logger45;
130555
+ var logger46;
131519
130556
  var getByUser;
131520
130557
  var getProgress;
131521
130558
  var levels;
@@ -131523,13 +130560,13 @@ var init_level_controller = __esm(() => {
131523
130560
  init_src2();
131524
130561
  init_errors();
131525
130562
  init_utils11();
131526
- logger45 = log.scope("LevelController");
130563
+ logger46 = log.scope("LevelController");
131527
130564
  getByUser = requireAuth(async (ctx) => {
131528
- logger45.debug("Getting user level", { userId: ctx.user.id });
130565
+ logger46.debug("Getting user level", { userId: ctx.user.id });
131529
130566
  return ctx.services.level.getByUser(ctx.user);
131530
130567
  });
131531
130568
  getProgress = requireAuth(async (ctx) => {
131532
- logger45.debug("Getting level progress", { userId: ctx.user.id });
130569
+ logger46.debug("Getting level progress", { userId: ctx.user.id });
131533
130570
  return ctx.services.level.getProgress(ctx.user);
131534
130571
  });
131535
130572
  levels = {
@@ -131539,34 +130576,58 @@ var init_level_controller = __esm(() => {
131539
130576
  getProgress
131540
130577
  };
131541
130578
  });
131542
- async function launch(ctx) {
131543
- const formData = await ctx.request.formData();
131544
- const idToken = formData.get("id_token");
131545
- if (!idToken || typeof idToken !== "string") {
131546
- throw ApiError.badRequest("Missing or invalid id_token");
131547
- }
131548
- const currentHost = ctx.url.hostname;
131549
- logger46.debug("Processing launch", { host: currentHost });
131550
- return ctx.services.lti.processLaunch(idToken, currentHost);
131551
- }
131552
- var logger46;
130579
+ var logger47;
130580
+ var generateToken;
130581
+ var logs;
130582
+ var init_logs_controller = __esm(() => {
130583
+ init_src2();
130584
+ init_errors();
130585
+ init_utils11();
130586
+ logger47 = log.scope("LogsController");
130587
+ generateToken = requireDeveloper(async (ctx) => {
130588
+ const slug2 = ctx.params.slug;
130589
+ if (!slug2) {
130590
+ throw ApiError.badRequest("Missing game slug");
130591
+ }
130592
+ let body2;
130593
+ try {
130594
+ const json4 = await ctx.request.json();
130595
+ if (json4.environment !== "staging" && json4.environment !== "production") {
130596
+ throw ApiError.badRequest('Invalid environment. Must be "staging" or "production".');
130597
+ }
130598
+ body2 = json4;
130599
+ } catch (error2) {
130600
+ if (error2 instanceof ApiError)
130601
+ throw error2;
130602
+ throw ApiError.badRequest("Invalid JSON body");
130603
+ }
130604
+ logger47.debug("Generating log stream token", {
130605
+ userId: ctx.user.id,
130606
+ slug: slug2,
130607
+ environment: body2.environment
130608
+ });
130609
+ return ctx.services.logs.generateToken(ctx.user, slug2, body2.environment);
130610
+ });
130611
+ logs = {
130612
+ generateToken
130613
+ };
130614
+ });
130615
+ var logger48;
131553
130616
  var getStatus3;
131554
130617
  var lti;
131555
130618
  var init_lti_controller = __esm(() => {
131556
130619
  init_src2();
131557
- init_errors();
131558
130620
  init_utils11();
131559
- logger46 = log.scope("LtiController");
130621
+ logger48 = log.scope("LtiController");
131560
130622
  getStatus3 = requireAuth(async (ctx) => {
131561
- logger46.debug("Getting status", { userId: ctx.user.id });
130623
+ logger48.debug("Getting status", { userId: ctx.user.id });
131562
130624
  return ctx.services.lti.getStatus(ctx.user);
131563
130625
  });
131564
130626
  lti = {
131565
- launch,
131566
130627
  getStatus: getStatus3
131567
130628
  };
131568
130629
  });
131569
- var logger47;
130630
+ var logger49;
131570
130631
  var getByIdentifier;
131571
130632
  var getElements;
131572
130633
  var getObjects;
@@ -131580,13 +130641,13 @@ var init_map_controller = __esm(() => {
131580
130641
  init_src4();
131581
130642
  init_errors();
131582
130643
  init_utils11();
131583
- logger47 = log.scope("MapController");
130644
+ logger49 = log.scope("MapController");
131584
130645
  getByIdentifier = requireAuth(async (ctx) => {
131585
130646
  const identifier = ctx.params.identifier;
131586
130647
  if (!identifier) {
131587
130648
  throw ApiError.badRequest("Missing map identifier");
131588
130649
  }
131589
- logger47.debug("Getting map", { userId: ctx.user.id, identifier });
130650
+ logger49.debug("Getting map", { userId: ctx.user.id, identifier });
131590
130651
  return ctx.services.map.getByIdentifier(identifier);
131591
130652
  });
131592
130653
  getElements = requireAuth(async (ctx) => {
@@ -131597,7 +130658,7 @@ var init_map_controller = __esm(() => {
131597
130658
  if (!isValidUUID(mapId)) {
131598
130659
  throw ApiError.unprocessableEntity("mapId must be a valid UUID format");
131599
130660
  }
131600
- logger47.debug("Getting map elements", { userId: ctx.user.id, mapId });
130661
+ logger49.debug("Getting map elements", { userId: ctx.user.id, mapId });
131601
130662
  return ctx.services.map.getElements(mapId);
131602
130663
  });
131603
130664
  getObjects = requireAuth(async (ctx) => {
@@ -131608,7 +130669,7 @@ var init_map_controller = __esm(() => {
131608
130669
  if (!isValidUUID(mapId)) {
131609
130670
  throw ApiError.unprocessableEntity("mapId must be a valid UUID format");
131610
130671
  }
131611
- logger47.debug("Getting map objects", { userId: ctx.user.id, mapId });
130672
+ logger49.debug("Getting map objects", { userId: ctx.user.id, mapId });
131612
130673
  return ctx.services.map.getObjects(mapId, ctx.user.id);
131613
130674
  });
131614
130675
  createObject = requireAuth(async (ctx) => {
@@ -131630,12 +130691,12 @@ var init_map_controller = __esm(() => {
131630
130691
  } catch (error2) {
131631
130692
  if (error2 instanceof exports_external.ZodError) {
131632
130693
  const details = formatZodError(error2);
131633
- logger47.warn("Create map object validation failed", { details });
130694
+ logger49.warn("Create map object validation failed", { details });
131634
130695
  throw ApiError.unprocessableEntity("Validation failed", details);
131635
130696
  }
131636
130697
  throw ApiError.badRequest("Invalid JSON body");
131637
130698
  }
131638
- logger47.debug("Creating map object", {
130699
+ logger49.debug("Creating map object", {
131639
130700
  userId: ctx.user.id,
131640
130701
  mapId,
131641
130702
  itemId: body2.itemId,
@@ -131659,7 +130720,7 @@ var init_map_controller = __esm(() => {
131659
130720
  if (!isValidUUID(objectId)) {
131660
130721
  throw ApiError.unprocessableEntity("objectId must be a valid UUID format");
131661
130722
  }
131662
- logger47.debug("Deleting map object", { userId: ctx.user.id, mapId, objectId });
130723
+ logger49.debug("Deleting map object", { userId: ctx.user.id, mapId, objectId });
131663
130724
  await ctx.services.map.deleteObject(mapId, objectId, ctx.user);
131664
130725
  });
131665
130726
  maps2 = {
@@ -131670,7 +130731,7 @@ var init_map_controller = __esm(() => {
131670
130731
  deleteObject
131671
130732
  };
131672
130733
  });
131673
- var logger48;
130734
+ var logger50;
131674
130735
  var list6;
131675
130736
  var updateStatus;
131676
130737
  var getStats;
@@ -131683,7 +130744,7 @@ var init_notification_controller = __esm(() => {
131683
130744
  init_src2();
131684
130745
  init_errors();
131685
130746
  init_utils11();
131686
- logger48 = log.scope("NotificationController");
130747
+ logger50 = log.scope("NotificationController");
131687
130748
  list6 = requireAuth(async (ctx) => {
131688
130749
  const query = {
131689
130750
  status: ctx.url.searchParams.get("status") || undefined,
@@ -131694,10 +130755,10 @@ var init_notification_controller = __esm(() => {
131694
130755
  const result = NotificationListQuerySchema.omit({ userId: true }).safeParse(query);
131695
130756
  if (!result.success) {
131696
130757
  const details = formatZodError(result.error);
131697
- logger48.warn("List notifications query validation failed", { details });
130758
+ logger50.warn("List notifications query validation failed", { details });
131698
130759
  throw ApiError.badRequest("Invalid query parameters", details);
131699
130760
  }
131700
- logger48.debug("Listing notifications", { userId: ctx.user.id, ...result.data });
130761
+ logger50.debug("Listing notifications", { userId: ctx.user.id, ...result.data });
131701
130762
  return ctx.services.notification.list(ctx.user, result.data);
131702
130763
  });
131703
130764
  updateStatus = requireAuth(async (ctx) => {
@@ -131712,12 +130773,12 @@ var init_notification_controller = __esm(() => {
131712
130773
  } catch (error2) {
131713
130774
  if (error2 instanceof exports_external.ZodError) {
131714
130775
  const details = formatZodError(error2);
131715
- logger48.warn("Update notification status validation failed", { details });
130776
+ logger50.warn("Update notification status validation failed", { details });
131716
130777
  throw ApiError.unprocessableEntity("Invalid request body", details);
131717
130778
  }
131718
130779
  throw ApiError.badRequest("Invalid JSON body");
131719
130780
  }
131720
- logger48.debug("Updating status", {
130781
+ logger50.debug("Updating status", {
131721
130782
  userId: ctx.user.id,
131722
130783
  notificationId,
131723
130784
  status: body2.status
@@ -131727,7 +130788,7 @@ var init_notification_controller = __esm(() => {
131727
130788
  getStats = requireAuth(async (ctx) => {
131728
130789
  const startDate = ctx.url.searchParams.get("startDate");
131729
130790
  const endDate = ctx.url.searchParams.get("endDate");
131730
- logger48.debug("Getting stats", { userId: ctx.user.id, startDate, endDate });
130791
+ logger50.debug("Getting stats", { userId: ctx.user.id, startDate, endDate });
131731
130792
  return ctx.services.notification.getStats(ctx.user, {
131732
130793
  startDate: startDate ? new Date(startDate) : undefined,
131733
130794
  endDate: endDate ? new Date(endDate) : undefined
@@ -131741,12 +130802,12 @@ var init_notification_controller = __esm(() => {
131741
130802
  } catch (error2) {
131742
130803
  if (error2 instanceof exports_external.ZodError) {
131743
130804
  const details = formatZodError(error2);
131744
- logger48.warn("Create notification validation failed", { details });
130805
+ logger50.warn("Create notification validation failed", { details });
131745
130806
  throw ApiError.unprocessableEntity("Invalid request body", details);
131746
130807
  }
131747
130808
  throw ApiError.badRequest("Invalid JSON body");
131748
130809
  }
131749
- logger48.debug("Creating notification", {
130810
+ logger50.debug("Creating notification", {
131750
130811
  userId: ctx.user.id,
131751
130812
  targetUserId: body2.userId,
131752
130813
  type: body2.type
@@ -131764,12 +130825,12 @@ var init_notification_controller = __esm(() => {
131764
130825
  });
131765
130826
  });
131766
130827
  deliver = requireAuth(async (ctx) => {
131767
- logger48.debug("Delivering notifications", { userId: ctx.user.id });
130828
+ logger50.debug("Delivering notifications", { userId: ctx.user.id });
131768
130829
  try {
131769
130830
  await ctx.services.notification.deliverPending(ctx.user.id);
131770
130831
  return { success: true };
131771
130832
  } catch (error2) {
131772
- logger48.error("Failed to deliver notifications", { error: error2 });
130833
+ logger50.error("Failed to deliver notifications", { error: error2 });
131773
130834
  throw ApiError.internal("Failed to deliver notifications");
131774
130835
  }
131775
130836
  });
@@ -131781,28 +130842,27 @@ var init_notification_controller = __esm(() => {
131781
130842
  deliver
131782
130843
  };
131783
130844
  });
131784
- var logger49;
131785
- var generateToken;
130845
+ var logger51;
130846
+ var generateToken2;
131786
130847
  var realtime;
131787
130848
  var init_realtime_controller = __esm(() => {
131788
130849
  init_src2();
131789
130850
  init_utils11();
131790
- logger49 = log.scope("RealtimeController");
131791
- generateToken = requireAuth(async (ctx) => {
130851
+ logger51 = log.scope("RealtimeController");
130852
+ generateToken2 = requireAuth(async (ctx) => {
131792
130853
  const gameIdOrSlug = ctx.params.gameId;
131793
- logger49.debug("Generating token", {
130854
+ logger51.debug("Generating token", {
131794
130855
  userId: ctx.user.id,
131795
130856
  gameId: gameIdOrSlug || "global"
131796
130857
  });
131797
130858
  return ctx.services.realtime.generateToken(ctx.user, gameIdOrSlug);
131798
130859
  });
131799
130860
  realtime = {
131800
- generateToken
130861
+ generateToken: generateToken2
131801
130862
  };
131802
130863
  });
131803
- var logger50;
130864
+ var logger52;
131804
130865
  var listKeys;
131805
- var getValues;
131806
130866
  var setSecrets;
131807
130867
  var deleteSecret;
131808
130868
  var secrets;
@@ -131812,25 +130872,16 @@ var init_secrets_controller = __esm(() => {
131812
130872
  init_src2();
131813
130873
  init_errors();
131814
130874
  init_utils11();
131815
- logger50 = log.scope("SecretsController");
130875
+ logger52 = log.scope("SecretsController");
131816
130876
  listKeys = requireDeveloper(async (ctx) => {
131817
130877
  const slug2 = ctx.params.slug;
131818
130878
  if (!slug2) {
131819
130879
  throw ApiError.badRequest("Missing game slug");
131820
130880
  }
131821
- logger50.debug("Listing secret keys", { userId: ctx.user.id, slug: slug2 });
130881
+ logger52.debug("Listing secret keys", { userId: ctx.user.id, slug: slug2 });
131822
130882
  const keys = await ctx.services.secrets.listKeys(slug2, ctx.user);
131823
130883
  return { keys };
131824
130884
  });
131825
- getValues = requireDeveloper(async (ctx) => {
131826
- const slug2 = ctx.params.slug;
131827
- if (!slug2) {
131828
- throw ApiError.badRequest("Missing game slug");
131829
- }
131830
- logger50.debug("Getting secret values", { userId: ctx.user.id, slug: slug2 });
131831
- const secrets2 = await ctx.services.secrets.getValues(slug2, ctx.user);
131832
- return { secrets: secrets2 };
131833
- });
131834
130885
  setSecrets = requireDeveloper(async (ctx) => {
131835
130886
  const slug2 = ctx.params.slug;
131836
130887
  if (!slug2) {
@@ -131843,12 +130894,12 @@ var init_secrets_controller = __esm(() => {
131843
130894
  } catch (error2) {
131844
130895
  if (error2 instanceof exports_external.ZodError) {
131845
130896
  const details = formatZodError(error2);
131846
- logger50.warn("Set secrets validation failed", { details });
130897
+ logger52.warn("Set secrets validation failed", { details });
131847
130898
  throw ApiError.unprocessableEntity("Validation failed", details);
131848
130899
  }
131849
130900
  throw ApiError.badRequest("Invalid JSON body");
131850
130901
  }
131851
- logger50.debug("Setting secrets", {
130902
+ logger52.debug("Setting secrets", {
131852
130903
  userId: ctx.user.id,
131853
130904
  slug: slug2,
131854
130905
  keyCount: Object.keys(body2).length
@@ -131865,18 +130916,17 @@ var init_secrets_controller = __esm(() => {
131865
130916
  if (!key) {
131866
130917
  throw ApiError.badRequest("Missing secret key");
131867
130918
  }
131868
- logger50.debug("Deleting secret", { userId: ctx.user.id, slug: slug2, key });
130919
+ logger52.debug("Deleting secret", { userId: ctx.user.id, slug: slug2, key });
131869
130920
  await ctx.services.secrets.deleteSecret(slug2, key, ctx.user);
131870
130921
  return { success: true };
131871
130922
  });
131872
130923
  secrets = {
131873
130924
  listKeys,
131874
- getValues,
131875
130925
  setSecrets,
131876
130926
  deleteSecret
131877
130927
  };
131878
130928
  });
131879
- var logger51;
130929
+ var logger53;
131880
130930
  var seed;
131881
130931
  var init_seed_controller = __esm(() => {
131882
130932
  init_esm();
@@ -131884,7 +130934,7 @@ var init_seed_controller = __esm(() => {
131884
130934
  init_src2();
131885
130935
  init_errors();
131886
130936
  init_utils11();
131887
- logger51 = log.scope("SeedController");
130937
+ logger53 = log.scope("SeedController");
131888
130938
  seed = requireDeveloper(async (ctx) => {
131889
130939
  const slug2 = ctx.params.slug;
131890
130940
  if (!slug2) {
@@ -131897,16 +130947,16 @@ var init_seed_controller = __esm(() => {
131897
130947
  } catch (error2) {
131898
130948
  if (error2 instanceof exports_external.ZodError) {
131899
130949
  const details = formatZodError(error2);
131900
- logger51.warn("Seed database validation failed", { details });
130950
+ logger53.warn("Seed database validation failed", { details });
131901
130951
  throw ApiError.unprocessableEntity("Validation failed", details);
131902
130952
  }
131903
130953
  throw ApiError.badRequest("Invalid JSON body");
131904
130954
  }
131905
- logger51.debug("Seeding database", { userId: ctx.user.id, slug: slug2, codeLength: body2.code.length });
130955
+ logger53.debug("Seeding database", { userId: ctx.user.id, slug: slug2, codeLength: body2.code.length });
131906
130956
  return ctx.services.seed.seed(slug2, body2.code, ctx.user);
131907
130957
  });
131908
130958
  });
131909
- var logger52;
130959
+ var logger54;
131910
130960
  var start2;
131911
130961
  var end;
131912
130962
  var mintToken;
@@ -131915,13 +130965,13 @@ var init_session_controller = __esm(() => {
131915
130965
  init_src2();
131916
130966
  init_errors();
131917
130967
  init_utils11();
131918
- logger52 = log.scope("SessionController");
130968
+ logger54 = log.scope("SessionController");
131919
130969
  start2 = requireAuth(async (ctx) => {
131920
130970
  const gameIdOrSlug = ctx.params.gameId;
131921
130971
  if (!gameIdOrSlug) {
131922
130972
  throw ApiError.badRequest("Missing game ID or slug");
131923
130973
  }
131924
- logger52.debug("Starting session", { userId: ctx.user.id, gameIdOrSlug });
130974
+ logger54.debug("Starting session", { userId: ctx.user.id, gameIdOrSlug });
131925
130975
  return ctx.services.session.start(gameIdOrSlug, ctx.user.id);
131926
130976
  });
131927
130977
  end = requireAuth(async (ctx) => {
@@ -131933,7 +130983,7 @@ var init_session_controller = __esm(() => {
131933
130983
  if (!sessionId) {
131934
130984
  throw ApiError.badRequest("Missing session ID");
131935
130985
  }
131936
- logger52.debug("Ending session", { userId: ctx.user.id, gameIdOrSlug, sessionId });
130986
+ logger54.debug("Ending session", { userId: ctx.user.id, gameIdOrSlug, sessionId });
131937
130987
  return ctx.services.session.end(gameIdOrSlug, sessionId, ctx.user.id);
131938
130988
  });
131939
130989
  mintToken = requireAuth(async (ctx) => {
@@ -131941,7 +130991,7 @@ var init_session_controller = __esm(() => {
131941
130991
  if (!gameIdOrSlug) {
131942
130992
  throw ApiError.badRequest("Missing game ID or slug");
131943
130993
  }
131944
- logger52.debug("Minting token", { userId: ctx.user.id, gameIdOrSlug });
130994
+ logger54.debug("Minting token", { userId: ctx.user.id, gameIdOrSlug });
131945
130995
  return ctx.services.session.mintToken(gameIdOrSlug, ctx.user.id);
131946
130996
  });
131947
130997
  sessions2 = {
@@ -131950,22 +131000,22 @@ var init_session_controller = __esm(() => {
131950
131000
  mintToken
131951
131001
  };
131952
131002
  });
131953
- var logger53;
131003
+ var logger55;
131954
131004
  var getShopView;
131955
131005
  var shop;
131956
131006
  var init_shop_controller = __esm(() => {
131957
131007
  init_src2();
131958
131008
  init_utils11();
131959
- logger53 = log.scope("ShopController");
131009
+ logger55 = log.scope("ShopController");
131960
131010
  getShopView = requireAuth(async (ctx) => {
131961
- logger53.debug("Getting shop view", { userId: ctx.user.id });
131011
+ logger55.debug("Getting shop view", { userId: ctx.user.id });
131962
131012
  return ctx.services.shop.getShopView(ctx.user);
131963
131013
  });
131964
131014
  shop = {
131965
131015
  getShopView
131966
131016
  };
131967
131017
  });
131968
- var logger54;
131018
+ var logger56;
131969
131019
  var list7;
131970
131020
  var getById4;
131971
131021
  var create5;
@@ -131984,9 +131034,9 @@ var init_shop_listing_controller = __esm(() => {
131984
131034
  init_src4();
131985
131035
  init_errors();
131986
131036
  init_utils11();
131987
- logger54 = log.scope("ShopListingController");
131037
+ logger56 = log.scope("ShopListingController");
131988
131038
  list7 = requireAdmin(async (ctx) => {
131989
- logger54.debug("Listing shop listings", { userId: ctx.user.id });
131039
+ logger56.debug("Listing shop listings", { userId: ctx.user.id });
131990
131040
  return ctx.services.shopListing.list();
131991
131041
  });
131992
131042
  getById4 = requireAdmin(async (ctx) => {
@@ -131997,7 +131047,7 @@ var init_shop_listing_controller = __esm(() => {
131997
131047
  if (!isValidUUID(listingId)) {
131998
131048
  throw ApiError.unprocessableEntity("listingId must be a valid UUID format");
131999
131049
  }
132000
- logger54.debug("Getting listing", { userId: ctx.user.id, listingId });
131050
+ logger56.debug("Getting listing", { userId: ctx.user.id, listingId });
132001
131051
  return ctx.services.shopListing.getById(listingId);
132002
131052
  });
132003
131053
  create5 = requireAdmin(async (ctx) => {
@@ -132008,12 +131058,12 @@ var init_shop_listing_controller = __esm(() => {
132008
131058
  } catch (error2) {
132009
131059
  if (error2 instanceof exports_external.ZodError) {
132010
131060
  const details = formatZodError(error2);
132011
- logger54.warn("Create shop listing validation failed", { details });
131061
+ logger56.warn("Create shop listing validation failed", { details });
132012
131062
  throw ApiError.unprocessableEntity("Validation failed", details);
132013
131063
  }
132014
131064
  throw ApiError.badRequest("Invalid JSON body");
132015
131065
  }
132016
- logger54.debug("Creating listing", {
131066
+ logger56.debug("Creating listing", {
132017
131067
  userId: ctx.user.id,
132018
131068
  itemId: body2.itemId,
132019
131069
  currencyId: body2.currencyId,
@@ -132036,12 +131086,12 @@ var init_shop_listing_controller = __esm(() => {
132036
131086
  } catch (error2) {
132037
131087
  if (error2 instanceof exports_external.ZodError) {
132038
131088
  const details = formatZodError(error2);
132039
- logger54.warn("Update shop listing validation failed", { details });
131089
+ logger56.warn("Update shop listing validation failed", { details });
132040
131090
  throw ApiError.unprocessableEntity("Validation failed", details);
132041
131091
  }
132042
131092
  throw ApiError.badRequest("Invalid JSON body");
132043
131093
  }
132044
- logger54.debug("Updating listing", {
131094
+ logger56.debug("Updating listing", {
132045
131095
  userId: ctx.user.id,
132046
131096
  listingId,
132047
131097
  price: body2.price,
@@ -132058,7 +131108,7 @@ var init_shop_listing_controller = __esm(() => {
132058
131108
  if (!isValidUUID(listingId)) {
132059
131109
  throw ApiError.unprocessableEntity("listingId must be a valid UUID format");
132060
131110
  }
132061
- logger54.debug("Deleting listing", { userId: ctx.user.id, listingId });
131111
+ logger56.debug("Deleting listing", { userId: ctx.user.id, listingId });
132062
131112
  await ctx.services.shopListing.delete(listingId);
132063
131113
  });
132064
131114
  listByGame2 = requireAuth(async (ctx) => {
@@ -132069,7 +131119,7 @@ var init_shop_listing_controller = __esm(() => {
132069
131119
  if (!isValidUUID(gameId)) {
132070
131120
  throw ApiError.unprocessableEntity("gameId must be a valid UUID format");
132071
131121
  }
132072
- logger54.debug("Listing game listings", { userId: ctx.user.id, gameId });
131122
+ logger56.debug("Listing game listings", { userId: ctx.user.id, gameId });
132073
131123
  return ctx.services.shopListing.listByGame(gameId, ctx.user);
132074
131124
  });
132075
131125
  getByGameItem = requireAuth(async (ctx) => {
@@ -132084,7 +131134,7 @@ var init_shop_listing_controller = __esm(() => {
132084
131134
  if (!isValidUUID(itemId)) {
132085
131135
  throw ApiError.unprocessableEntity("itemId must be a valid UUID format");
132086
131136
  }
132087
- logger54.debug("Getting game item listing", { userId: ctx.user.id, gameId, itemId });
131137
+ logger56.debug("Getting game item listing", { userId: ctx.user.id, gameId, itemId });
132088
131138
  return ctx.services.shopListing.getByGameItem(gameId, itemId, ctx.user);
132089
131139
  });
132090
131140
  createForGameItem = requireAuth(async (ctx) => {
@@ -132106,12 +131156,12 @@ var init_shop_listing_controller = __esm(() => {
132106
131156
  } catch (error2) {
132107
131157
  if (error2 instanceof exports_external.ZodError) {
132108
131158
  const details = formatZodError(error2);
132109
- logger54.warn("Create game item listing validation failed", { details });
131159
+ logger56.warn("Create game item listing validation failed", { details });
132110
131160
  throw ApiError.unprocessableEntity("Validation failed", details);
132111
131161
  }
132112
131162
  throw ApiError.badRequest("Invalid JSON body");
132113
131163
  }
132114
- logger54.debug("Creating game item listing", {
131164
+ logger56.debug("Creating game item listing", {
132115
131165
  userId: ctx.user.id,
132116
131166
  gameId,
132117
131167
  itemId,
@@ -132139,12 +131189,12 @@ var init_shop_listing_controller = __esm(() => {
132139
131189
  } catch (error2) {
132140
131190
  if (error2 instanceof exports_external.ZodError) {
132141
131191
  const details = formatZodError(error2);
132142
- logger54.warn("Update game item listing validation failed", { details });
131192
+ logger56.warn("Update game item listing validation failed", { details });
132143
131193
  throw ApiError.unprocessableEntity("Validation failed", details);
132144
131194
  }
132145
131195
  throw ApiError.badRequest("Invalid JSON body");
132146
131196
  }
132147
- logger54.debug("Updating game item listing", {
131197
+ logger56.debug("Updating game item listing", {
132148
131198
  userId: ctx.user.id,
132149
131199
  gameId,
132150
131200
  itemId,
@@ -132166,7 +131216,7 @@ var init_shop_listing_controller = __esm(() => {
132166
131216
  if (!isValidUUID(itemId)) {
132167
131217
  throw ApiError.unprocessableEntity("itemId must be a valid UUID format");
132168
131218
  }
132169
- logger54.debug("Deleting game item listing", {
131219
+ logger56.debug("Deleting game item listing", {
132170
131220
  userId: ctx.user.id,
132171
131221
  gameId,
132172
131222
  itemId
@@ -132191,20 +131241,20 @@ async function getBySlug2(ctx) {
132191
131241
  if (!slug2) {
132192
131242
  throw ApiError.badRequest("Template slug is required");
132193
131243
  }
132194
- logger55.debug("Getting sprite by slug", { slug: slug2 });
131244
+ logger57.debug("Getting sprite by slug", { slug: slug2 });
132195
131245
  return ctx.services.sprite.getBySlug(slug2);
132196
131246
  }
132197
- var logger55;
131247
+ var logger57;
132198
131248
  var sprites;
132199
131249
  var init_sprite_controller = __esm(() => {
132200
131250
  init_src2();
132201
131251
  init_errors();
132202
- logger55 = log.scope("SpriteController");
131252
+ logger57 = log.scope("SpriteController");
132203
131253
  sprites = {
132204
131254
  getBySlug: getBySlug2
132205
131255
  };
132206
131256
  });
132207
- var logger56;
131257
+ var logger58;
132208
131258
  var getTodayXp;
132209
131259
  var getTotalXp;
132210
131260
  var updateTodayXp;
@@ -132226,15 +131276,15 @@ var init_timeback_controller = __esm(() => {
132226
131276
  init_src4();
132227
131277
  init_errors();
132228
131278
  init_utils11();
132229
- logger56 = log.scope("TimebackController");
131279
+ logger58 = log.scope("TimebackController");
132230
131280
  getTodayXp = requireAuth(async (ctx) => {
132231
131281
  const date4 = ctx.url.searchParams.get("date") || undefined;
132232
131282
  const tz = ctx.url.searchParams.get("tz") || undefined;
132233
- logger56.debug("Getting today XP", { userId: ctx.user.id, date: date4, tz });
131283
+ logger58.debug("Getting today XP", { userId: ctx.user.id, date: date4, tz });
132234
131284
  return ctx.services.timeback.getTodayXp(ctx.user.id, date4, tz);
132235
131285
  });
132236
131286
  getTotalXp = requireAuth(async (ctx) => {
132237
- logger56.debug("Getting total XP", { userId: ctx.user.id });
131287
+ logger58.debug("Getting total XP", { userId: ctx.user.id });
132238
131288
  return ctx.services.timeback.getTotalXp(ctx.user.id);
132239
131289
  });
132240
131290
  updateTodayXp = requireAuth(async (ctx) => {
@@ -132245,18 +131295,18 @@ var init_timeback_controller = __esm(() => {
132245
131295
  } catch (error2) {
132246
131296
  if (error2 instanceof exports_external.ZodError) {
132247
131297
  const details = formatZodError(error2);
132248
- logger56.warn("Update today XP validation failed", { details });
131298
+ logger58.warn("Update today XP validation failed", { details });
132249
131299
  throw ApiError.unprocessableEntity("Validation failed", details);
132250
131300
  }
132251
131301
  throw ApiError.badRequest("Invalid JSON body");
132252
131302
  }
132253
- logger56.debug("Updating today XP", { userId: ctx.user.id, xp: body2.xp });
131303
+ logger58.debug("Updating today XP", { userId: ctx.user.id, xp: body2.xp });
132254
131304
  return ctx.services.timeback.updateTodayXp(ctx.user.id, body2);
132255
131305
  });
132256
131306
  getXpHistory = requireAuth(async (ctx) => {
132257
131307
  const startDate = ctx.url.searchParams.get("startDate") || undefined;
132258
131308
  const endDate = ctx.url.searchParams.get("endDate") || undefined;
132259
- logger56.debug("Getting XP history", { userId: ctx.user.id, startDate, endDate });
131309
+ logger58.debug("Getting XP history", { userId: ctx.user.id, startDate, endDate });
132260
131310
  return ctx.services.timeback.getXpHistory(ctx.user.id, startDate, endDate);
132261
131311
  });
132262
131312
  populateStudent = requireAuth(async (ctx) => {
@@ -132267,18 +131317,18 @@ var init_timeback_controller = __esm(() => {
132267
131317
  } catch (error2) {
132268
131318
  if (error2 instanceof exports_external.ZodError) {
132269
131319
  const details = formatZodError(error2);
132270
- logger56.warn("Populate student validation failed", { details });
131320
+ logger58.warn("Populate student validation failed", { details });
132271
131321
  throw ApiError.unprocessableEntity("Validation failed", details);
132272
131322
  }
132273
131323
  }
132274
- logger56.debug("Populating student", {
131324
+ logger58.debug("Populating student", {
132275
131325
  userId: ctx.user.id,
132276
131326
  hasProvidedNames: !!providedNames
132277
131327
  });
132278
131328
  return ctx.services.timeback.populateStudent(ctx.user, providedNames);
132279
131329
  });
132280
131330
  getUser = requireAuth(async (ctx) => {
132281
- logger56.debug("Getting user", { userId: ctx.user.id, gameId: ctx.gameId });
131331
+ logger58.debug("Getting user", { userId: ctx.user.id, gameId: ctx.gameId });
132282
131332
  return ctx.services.timeback.getUserData(ctx.user.id, ctx.gameId);
132283
131333
  });
132284
131334
  getUserById = requireAuth(async (ctx) => {
@@ -132286,7 +131336,7 @@ var init_timeback_controller = __esm(() => {
132286
131336
  if (!timebackId) {
132287
131337
  throw ApiError.badRequest("Missing timebackId parameter");
132288
131338
  }
132289
- logger56.debug("Getting user by ID", { requesterId: ctx.user.id, timebackId });
131339
+ logger58.debug("Getting user by ID", { requesterId: ctx.user.id, timebackId });
132290
131340
  return ctx.services.timeback.getUserDataByTimebackId(timebackId);
132291
131341
  });
132292
131342
  setupIntegration = requireDeveloper(async (ctx) => {
@@ -132297,12 +131347,12 @@ var init_timeback_controller = __esm(() => {
132297
131347
  } catch (error2) {
132298
131348
  if (error2 instanceof exports_external.ZodError) {
132299
131349
  const details = formatZodError(error2);
132300
- logger56.warn("Setup integration validation failed", { details });
131350
+ logger58.warn("Setup integration validation failed", { details });
132301
131351
  throw ApiError.unprocessableEntity("Validation failed", details);
132302
131352
  }
132303
131353
  throw ApiError.badRequest("Invalid JSON body");
132304
131354
  }
132305
- logger56.debug("Setting up integration", {
131355
+ logger58.debug("Setting up integration", {
132306
131356
  userId: ctx.user.id,
132307
131357
  gameId: body2.gameId
132308
131358
  });
@@ -132314,7 +131364,7 @@ var init_timeback_controller = __esm(() => {
132314
131364
  throw ApiError.badRequest("Missing gameId");
132315
131365
  if (!isValidUUID(gameId))
132316
131366
  throw ApiError.unprocessableEntity("Invalid gameId format");
132317
- logger56.debug("Getting integrations", { userId: ctx.user.id, gameId });
131367
+ logger58.debug("Getting integrations", { userId: ctx.user.id, gameId });
132318
131368
  return ctx.services.timeback.getIntegrations(gameId, ctx.user);
132319
131369
  });
132320
131370
  verifyIntegration = requireDeveloper(async (ctx) => {
@@ -132323,7 +131373,7 @@ var init_timeback_controller = __esm(() => {
132323
131373
  throw ApiError.badRequest("Missing gameId");
132324
131374
  if (!isValidUUID(gameId))
132325
131375
  throw ApiError.unprocessableEntity("Invalid gameId format");
132326
- logger56.debug("Verifying integration", { userId: ctx.user.id, gameId });
131376
+ logger58.debug("Verifying integration", { userId: ctx.user.id, gameId });
132327
131377
  return ctx.services.timeback.verifyIntegration(gameId, ctx.user);
132328
131378
  });
132329
131379
  getConfig2 = requireDeveloper(async (ctx) => {
@@ -132332,7 +131382,7 @@ var init_timeback_controller = __esm(() => {
132332
131382
  throw ApiError.badRequest("Missing gameId");
132333
131383
  if (!isValidUUID(gameId))
132334
131384
  throw ApiError.unprocessableEntity("Invalid gameId format");
132335
- logger56.debug("Getting config", { userId: ctx.user.id, gameId });
131385
+ logger58.debug("Getting config", { userId: ctx.user.id, gameId });
132336
131386
  return ctx.services.timeback.getConfig(gameId, ctx.user);
132337
131387
  });
132338
131388
  deleteIntegrations = requireDeveloper(async (ctx) => {
@@ -132341,7 +131391,7 @@ var init_timeback_controller = __esm(() => {
132341
131391
  throw ApiError.badRequest("Missing gameId");
132342
131392
  if (!isValidUUID(gameId))
132343
131393
  throw ApiError.unprocessableEntity("Invalid gameId format");
132344
- logger56.debug("Deleting integrations", { userId: ctx.user.id, gameId });
131394
+ logger58.debug("Deleting integrations", { userId: ctx.user.id, gameId });
132345
131395
  await ctx.services.timeback.deleteIntegrations(gameId, ctx.user);
132346
131396
  });
132347
131397
  endActivity = requireDeveloper(async (ctx) => {
@@ -132352,13 +131402,13 @@ var init_timeback_controller = __esm(() => {
132352
131402
  } catch (error2) {
132353
131403
  if (error2 instanceof exports_external.ZodError) {
132354
131404
  const details = formatZodError(error2);
132355
- logger56.warn("End activity validation failed", { details });
131405
+ logger58.warn("End activity validation failed", { details });
132356
131406
  throw ApiError.unprocessableEntity("Validation failed", details);
132357
131407
  }
132358
131408
  throw ApiError.badRequest("Invalid JSON body");
132359
131409
  }
132360
131410
  const { gameId, studentId, activityData, scoreData, timingData, xpEarned, masteredUnits } = body2;
132361
- logger56.debug("Ending activity", { userId: ctx.user.id, gameId });
131411
+ logger58.debug("Ending activity", { userId: ctx.user.id, gameId });
132362
131412
  return ctx.services.timeback.endActivity(gameId, studentId, activityData, scoreData, timingData, xpEarned, masteredUnits, ctx.user);
132363
131413
  });
132364
131414
  timeback2 = {
@@ -132377,7 +131427,7 @@ var init_timeback_controller = __esm(() => {
132377
131427
  endActivity
132378
131428
  };
132379
131429
  });
132380
- var logger57;
131430
+ var logger59;
132381
131431
  var initiate;
132382
131432
  var init_upload_controller = __esm(() => {
132383
131433
  init_esm();
@@ -132385,7 +131435,7 @@ var init_upload_controller = __esm(() => {
132385
131435
  init_src2();
132386
131436
  init_errors();
132387
131437
  init_utils11();
132388
- logger57 = log.scope("UploadController");
131438
+ logger59 = log.scope("UploadController");
132389
131439
  initiate = requireDeveloper(async (ctx) => {
132390
131440
  let body2;
132391
131441
  try {
@@ -132394,37 +131444,37 @@ var init_upload_controller = __esm(() => {
132394
131444
  } catch (error2) {
132395
131445
  if (error2 instanceof exports_external.ZodError) {
132396
131446
  const details = formatZodError(error2);
132397
- logger57.warn("Initiate upload validation failed", { details });
131447
+ logger59.warn("Initiate upload validation failed", { details });
132398
131448
  throw ApiError.unprocessableEntity("Validation failed", details);
132399
131449
  }
132400
131450
  throw ApiError.badRequest("Invalid JSON body");
132401
131451
  }
132402
- logger57.debug("Initiating upload", { userId: ctx.user.id, gameId: body2.gameId });
131452
+ logger59.debug("Initiating upload", { userId: ctx.user.id, gameId: body2.gameId });
132403
131453
  return ctx.services.upload.initiate(body2, ctx.user);
132404
131454
  });
132405
131455
  });
132406
- var logger58;
131456
+ var logger60;
132407
131457
  var getMe;
132408
131458
  var users2;
132409
131459
  var init_user_controller = __esm(() => {
132410
131460
  init_src2();
132411
131461
  init_utils11();
132412
- logger58 = log.scope("UserController");
131462
+ logger60 = log.scope("UserController");
132413
131463
  getMe = requireAuth(async (ctx) => {
132414
- logger58.debug("Getting current user", { userId: ctx.user.id, gameId: ctx.gameId });
131464
+ logger60.debug("Getting current user", { userId: ctx.user.id, gameId: ctx.gameId });
132415
131465
  return ctx.services.user.getMe(ctx.user, ctx.gameId);
132416
131466
  });
132417
131467
  users2 = {
132418
131468
  getMe
132419
131469
  };
132420
131470
  });
132421
- var logger59;
131471
+ var logger61;
132422
131472
  var init_verify_controller = __esm(() => {
132423
131473
  init_schemas_index();
132424
131474
  init_src2();
132425
131475
  init_errors();
132426
131476
  init_utils11();
132427
- logger59 = log.scope("VerifyController");
131477
+ logger61 = log.scope("VerifyController");
132428
131478
  });
132429
131479
  var init_controllers = __esm(() => {
132430
131480
  init_achievement_controller();
@@ -132441,6 +131491,7 @@ var init_controllers = __esm(() => {
132441
131491
  init_item_controller();
132442
131492
  init_leaderboard_controller();
132443
131493
  init_level_controller();
131494
+ init_logs_controller();
132444
131495
  init_lti_controller();
132445
131496
  init_map_controller();
132446
131497
  init_notification_controller();
@@ -132931,6 +131982,14 @@ var init_items = __esm(() => {
132931
131982
  gameItemsRouter.patch("/:gameId/items/:itemId", handle2(items2.updateForGame));
132932
131983
  gameItemsRouter.delete("/:gameId/items/:itemId", handle2(items2.deleteForGame, { status: 204 }));
132933
131984
  });
131985
+ var gameLogsRouter;
131986
+ var init_logs = __esm(() => {
131987
+ init_dist3();
131988
+ init_controllers();
131989
+ init_api();
131990
+ gameLogsRouter = new Hono2;
131991
+ gameLogsRouter.post("/:slug/logs/token", handle2(logs.generateToken));
131992
+ });
132934
131993
  var gameScoresRouter;
132935
131994
  var init_scores = __esm(() => {
132936
131995
  init_dist3();
@@ -132947,7 +132006,6 @@ var init_secrets = __esm(() => {
132947
132006
  init_api();
132948
132007
  gameSecretsRouter = new Hono2;
132949
132008
  gameSecretsRouter.get("/:slug/secrets", handle2(secrets.listKeys));
132950
- gameSecretsRouter.get("/:slug/secrets/values", handle2(secrets.getValues));
132951
132009
  gameSecretsRouter.post("/:slug/secrets", handle2(secrets.setSecrets));
132952
132010
  gameSecretsRouter.delete("/:slug/secrets/:key", handle2(secrets.deleteSecret));
132953
132011
  });
@@ -133231,6 +132289,7 @@ var init_games2 = __esm(() => {
133231
132289
  init_deploy();
133232
132290
  init_domains3();
133233
132291
  init_items();
132292
+ init_logs();
133234
132293
  init_scores();
133235
132294
  init_secrets();
133236
132295
  init_seed2();
@@ -133246,6 +132305,7 @@ var init_games2 = __esm(() => {
133246
132305
  gamesRouter.route("/", gameDeployRouter);
133247
132306
  gamesRouter.route("/", gameDomainsRouter);
133248
132307
  gamesRouter.route("/", gameItemsRouter);
132308
+ gamesRouter.route("/", gameLogsRouter);
133249
132309
  gamesRouter.route("/", gameShopRouter);
133250
132310
  gamesRouter.route("/", gameScoresRouter);
133251
132311
  gamesRouter.route("/", gameSecretsRouter);
@@ -133441,51 +132501,86 @@ var init_timeback6 = __esm(() => {
133441
132501
  return handle2(timeback2.getUserById)(c2);
133442
132502
  });
133443
132503
  });
132504
+ function verifyMockToken(idToken) {
132505
+ if (!idToken.startsWith("mock:")) {
132506
+ throw new Error("Invalid LTI token - must be mock token in sandbox");
132507
+ }
132508
+ try {
132509
+ const jsonStr = Buffer.from(idToken.slice(5), "base64").toString();
132510
+ return JSON.parse(jsonStr);
132511
+ } catch {
132512
+ throw new Error("Invalid LTI token format");
132513
+ }
132514
+ }
132515
+ var logger62;
133444
132516
  var ltiRouter;
133445
132517
  var init_lti = __esm(() => {
133446
132518
  init_drizzle_orm();
133447
132519
  init_dist3();
133448
132520
  init_controllers();
133449
- init_errors();
132521
+ init_utils11();
133450
132522
  init_tables_index();
132523
+ init_src2();
133451
132524
  init_constants();
133452
132525
  init_api();
133453
- init_context();
132526
+ logger62 = log.scope("SandboxLti");
133454
132527
  ltiRouter = new Hono2;
133455
- ltiRouter.all("/launch", async (c2) => {
133456
- if (c2.req.method !== "POST") {
133457
- return c2.json({
133458
- error: "method_not_allowed",
133459
- message: "LTI launches must use POST method"
133460
- }, 405);
133461
- }
133462
- const sandboxCtx = getSandboxContext();
133463
- const ctx = {
133464
- db: sandboxCtx.db,
133465
- config: sandboxCtx.config,
133466
- providers: sandboxCtx.providers,
133467
- services: sandboxCtx.services,
133468
- user: undefined,
133469
- params: {},
133470
- url: new URL(c2.req.url),
133471
- request: c2.req.raw
133472
- };
132528
+ ltiRouter.post("/launch", async (c2) => {
132529
+ const db2 = c2.get("db");
133473
132530
  try {
133474
- const result = await lti.launch(ctx);
133475
- c2.header("Set-Cookie", `better-auth.session=${result.sessionToken}; Path=/; HttpOnly; SameSite=Lax; Max-Age=2592000`);
133476
- return c2.redirect(result.redirectPath);
133477
- } catch (error2) {
133478
- if (error2 instanceof ApiError) {
132531
+ const formData = await c2.req.formData();
132532
+ const idToken = formData.get("id_token");
132533
+ if (!idToken || typeof idToken !== "string") {
133479
132534
  return c2.json({
133480
- error: "lti_launch_failed",
133481
- message: error2.message,
133482
- code: error2.code,
133483
- details: error2.details
133484
- }, error2.status);
132535
+ error: "missing_token",
132536
+ message: "Missing or invalid id_token in request"
132537
+ }, 400);
133485
132538
  }
132539
+ let claims;
132540
+ try {
132541
+ claims = verifyMockToken(idToken);
132542
+ } catch (error2) {
132543
+ const errorMessage = error2 instanceof Error ? error2.message : String(error2);
132544
+ logger62.error("LTI token verification failed", { error: errorMessage });
132545
+ return c2.json({
132546
+ error: "invalid_token",
132547
+ message: errorMessage
132548
+ }, 401);
132549
+ }
132550
+ const validationError = validateLtiClaims(claims);
132551
+ if (validationError) {
132552
+ logger62.warn("LTI claims validation failed", {
132553
+ error: validationError,
132554
+ sub: claims.sub
132555
+ });
132556
+ return c2.json({
132557
+ error: "invalid_claims",
132558
+ message: validationError
132559
+ }, 400);
132560
+ }
132561
+ const user = await provisionLtiUser(db2, claims);
132562
+ const sessionToken = crypto.randomUUID();
132563
+ const expiresAt = new Date(Date.now() + 2592000000);
132564
+ await db2.insert(sessions).values({
132565
+ id: crypto.randomUUID(),
132566
+ userId: user.id,
132567
+ token: sessionToken,
132568
+ expiresAt,
132569
+ createdAt: new Date,
132570
+ updatedAt: new Date
132571
+ });
132572
+ logger62.info("LTI launch successful", { userId: user.id });
132573
+ const targetUri = claims["https://purl.imsglobal.org/spec/lti/claim/target_link_uri"];
132574
+ const currentHost = new URL(c2.req.url).hostname;
132575
+ const redirectPath = extractRedirectPath(targetUri, currentHost);
132576
+ c2.header("Set-Cookie", `sandbox-session=${sessionToken}; Path=/; HttpOnly; SameSite=Lax; Max-Age=2592000`);
132577
+ return c2.redirect(redirectPath);
132578
+ } catch (error2) {
132579
+ const errorMessage = error2 instanceof Error ? error2.message : String(error2);
132580
+ logger62.error("Unexpected error during LTI launch", { error: errorMessage });
133486
132581
  return c2.json({
133487
132582
  error: "unexpected_error",
133488
- message: "An unexpected error occurred during LTI launch. Please try again or contact support."
132583
+ message: "An unexpected error occurred during LTI launch"
133489
132584
  }, 500);
133490
132585
  }
133491
132586
  });
@@ -133609,7 +132704,6 @@ async function startServer(port, project, options = {}) {
133609
132704
  resetSandboxContext();
133610
132705
  resetHandlers();
133611
132706
  clearSandboxCache();
133612
- clearSandboxSecrets();
133613
132707
  clearSandboxStorage();
133614
132708
  const db2 = await setupServerDatabase(processedOptions, project);
133615
132709
  createSandboxContext({ db: db2, port });
@@ -133636,7 +132730,6 @@ async function startServer(port, project, options = {}) {
133636
132730
  resetSandboxContext();
133637
132731
  resetHandlers();
133638
132732
  clearSandboxCache();
133639
- clearSandboxSecrets();
133640
132733
  clearSandboxStorage();
133641
132734
  }
133642
132735
  };
@@ -134819,7 +133912,7 @@ var import_picocolors12 = __toESM(require_picocolors(), 1);
134819
133912
  // package.json
134820
133913
  var package_default2 = {
134821
133914
  name: "@playcademy/vite-plugin",
134822
- version: "0.2.6",
133915
+ version: "0.2.8",
134823
133916
  type: "module",
134824
133917
  exports: {
134825
133918
  ".": {