@playcademy/sandbox 0.1.0-beta.6 → 0.1.0-beta.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 (3) hide show
  1. package/dist/cli.js +539 -193
  2. package/dist/server.js +539 -193
  3. package/package.json +9 -8
package/dist/server.js CHANGED
@@ -1706,8 +1706,8 @@ is not a problem with esbuild. You need to fix your environment instead.
1706
1706
  if (isFirstPacket) {
1707
1707
  isFirstPacket = false;
1708
1708
  let binaryVersion = String.fromCharCode(...bytes);
1709
- if (binaryVersion !== "0.25.4") {
1710
- throw new Error(`Cannot start service: Host version "${"0.25.4"}" does not match binary version ${quote(binaryVersion)}`);
1709
+ if (binaryVersion !== "0.25.5") {
1710
+ throw new Error(`Cannot start service: Host version "${"0.25.5"}" does not match binary version ${quote(binaryVersion)}`);
1711
1711
  }
1712
1712
  return;
1713
1713
  }
@@ -2895,7 +2895,7 @@ for your current platform.`);
2895
2895
  } catch (e) {}
2896
2896
  if (pnpapi) {
2897
2897
  const root = pnpapi.getPackageInformation(pnpapi.topLevel).packageLocation;
2898
- const binTargetPath = path.join(root, "node_modules", ".cache", "esbuild", `pnpapi-${pkg.replace("/", "-")}-${"0.25.4"}-${path.basename(subpath)}`);
2898
+ const binTargetPath = path.join(root, "node_modules", ".cache", "esbuild", `pnpapi-${pkg.replace("/", "-")}-${"0.25.5"}-${path.basename(subpath)}`);
2899
2899
  if (!fs3.existsSync(binTargetPath)) {
2900
2900
  fs3.mkdirSync(path.dirname(binTargetPath), { recursive: true });
2901
2901
  fs3.copyFileSync(binPath, binTargetPath);
@@ -2923,7 +2923,7 @@ for your current platform.`);
2923
2923
  }
2924
2924
  }
2925
2925
  var _a;
2926
- var isInternalWorkerThread = ((_a = worker_threads == null ? undefined : worker_threads.workerData) == null ? undefined : _a.esbuildVersion) === "0.25.4";
2926
+ var isInternalWorkerThread = ((_a = worker_threads == null ? undefined : worker_threads.workerData) == null ? undefined : _a.esbuildVersion) === "0.25.5";
2927
2927
  var esbuildCommandAndArgs = () => {
2928
2928
  if ((!ESBUILD_BINARY_PATH || false) && (path2.basename(__filename) !== "main.js" || path2.basename(__dirname) !== "lib")) {
2929
2929
  throw new Error(`The esbuild JavaScript API cannot be bundled. Please mark the "esbuild" package as external so it's not included in the bundle.
@@ -2985,7 +2985,7 @@ More information: The file containing the code for esbuild's JavaScript API (${_
2985
2985
  }
2986
2986
  }
2987
2987
  };
2988
- var version2 = "0.25.4";
2988
+ var version2 = "0.25.5";
2989
2989
  var build = (options) => ensureServiceIsRunning().build(options);
2990
2990
  var context = (buildOptions) => ensureServiceIsRunning().context(buildOptions);
2991
2991
  var transform = (input, options) => ensureServiceIsRunning().transform(input, options);
@@ -3103,7 +3103,7 @@ More information: The file containing the code for esbuild's JavaScript API (${_
3103
3103
  if (longLivedService)
3104
3104
  return longLivedService;
3105
3105
  let [command, args2] = esbuildCommandAndArgs();
3106
- let child = child_process.spawn(command, args2.concat(`--service=${"0.25.4"}`, "--ping"), {
3106
+ let child = child_process.spawn(command, args2.concat(`--service=${"0.25.5"}`, "--ping"), {
3107
3107
  windowsHide: true,
3108
3108
  stdio: ["pipe", "pipe", "inherit"],
3109
3109
  cwd: defaultWD
@@ -3211,7 +3211,7 @@ More information: The file containing the code for esbuild's JavaScript API (${_
3211
3211
  esbuild: node_exports
3212
3212
  });
3213
3213
  callback(service);
3214
- let stdout = child_process.execFileSync(command, args2.concat(`--service=${"0.25.4"}`), {
3214
+ let stdout = child_process.execFileSync(command, args2.concat(`--service=${"0.25.5"}`), {
3215
3215
  cwd: defaultWD,
3216
3216
  windowsHide: true,
3217
3217
  input: stdin,
@@ -3227,7 +3227,7 @@ More information: The file containing the code for esbuild's JavaScript API (${_
3227
3227
  var startWorkerThreadService = (worker_threads2) => {
3228
3228
  let { port1: mainPort, port2: workerPort } = new worker_threads2.MessageChannel;
3229
3229
  let worker = new worker_threads2.Worker(__filename, {
3230
- workerData: { workerPort, defaultWD, esbuildVersion: "0.25.4" },
3230
+ workerData: { workerPort, defaultWD, esbuildVersion: "0.25.5" },
3231
3231
  transferList: [workerPort],
3232
3232
  execArgv: []
3233
3233
  });
@@ -50336,9 +50336,9 @@ import { Http2ServerRequest } from "http2";
50336
50336
  import { Readable } from "stream";
50337
50337
  import crypto2 from "crypto";
50338
50338
  var RequestError = class extends Error {
50339
- static name = "RequestError";
50340
50339
  constructor(message, options) {
50341
50340
  super(message, options);
50341
+ this.name = "RequestError";
50342
50342
  }
50343
50343
  };
50344
50344
  var toRequestError = (e) => {
@@ -50943,7 +50943,8 @@ var PLAYCADEMY_CREDITS_ID = crypto.randomUUID();
50943
50943
  var SAMPLE_ITEMS = [
50944
50944
  {
50945
50945
  id: PLAYCADEMY_CREDITS_ID,
50946
- internalName: "PLAYCADEMY_CREDITS",
50946
+ slug: "PLAYCADEMY_CREDITS",
50947
+ gameId: null,
50947
50948
  displayName: "PLAYCADEMY credits",
50948
50949
  description: "The main currency used across PLAYCADEMY.",
50949
50950
  type: "currency",
@@ -50954,7 +50955,8 @@ var SAMPLE_ITEMS = [
50954
50955
  },
50955
50956
  {
50956
50957
  id: crypto.randomUUID(),
50957
- internalName: "FOUNDING_MEMBER_BADGE",
50958
+ slug: "FOUNDING_MEMBER_BADGE",
50959
+ gameId: null,
50958
50960
  displayName: "Founding Member Badge",
50959
50961
  description: "Reserved for founding core team of the PLAYCADEMY platform.",
50960
50962
  type: "badge",
@@ -50965,7 +50967,8 @@ var SAMPLE_ITEMS = [
50965
50967
  },
50966
50968
  {
50967
50969
  id: crypto.randomUUID(),
50968
- internalName: "EARLY_ADOPTER_BADGE",
50970
+ slug: "EARLY_ADOPTER_BADGE",
50971
+ gameId: null,
50969
50972
  displayName: "Early Adopter Badge",
50970
50973
  description: "Awarded to users who joined during the beta phase.",
50971
50974
  type: "badge",
@@ -50976,7 +50979,8 @@ var SAMPLE_ITEMS = [
50976
50979
  },
50977
50980
  {
50978
50981
  id: crypto.randomUUID(),
50979
- internalName: "FIRST_GAME_BADGE",
50982
+ slug: "FIRST_GAME_BADGE",
50983
+ gameId: null,
50980
50984
  displayName: "First Game Played",
50981
50985
  description: "Awarded for playing your first game in the Playcademy platform.",
50982
50986
  type: "badge",
@@ -50987,7 +50991,8 @@ var SAMPLE_ITEMS = [
50987
50991
  },
50988
50992
  {
50989
50993
  id: crypto.randomUUID(),
50990
- internalName: "COMMON_SWORD",
50994
+ slug: "COMMON_SWORD",
50995
+ gameId: null,
50991
50996
  displayName: "Common Sword",
50992
50997
  description: "A basic sword, good for beginners.",
50993
50998
  type: "unlock",
@@ -50996,7 +51001,8 @@ var SAMPLE_ITEMS = [
50996
51001
  },
50997
51002
  {
50998
51003
  id: crypto.randomUUID(),
50999
- internalName: "SMALL_HEALTH_POTION",
51004
+ slug: "SMALL_HEALTH_POTION",
51005
+ gameId: null,
51000
51006
  displayName: "Small Health Potion",
51001
51007
  description: "Restores a small amount of health.",
51002
51008
  type: "other",
@@ -51005,7 +51011,8 @@ var SAMPLE_ITEMS = [
51005
51011
  },
51006
51012
  {
51007
51013
  id: crypto.randomUUID(),
51008
- internalName: "SMALL_BACKPACK",
51014
+ slug: "SMALL_BACKPACK",
51015
+ gameId: null,
51009
51016
  displayName: "Small Backpack",
51010
51017
  description: "Increases your inventory capacity by 5 slots.",
51011
51018
  type: "upgrade",
@@ -75478,14 +75485,20 @@ var itemTypeEnum = pgEnum("item_type", [
75478
75485
  ]);
75479
75486
  var items = pgTable("items", {
75480
75487
  id: uuid("id").primaryKey().defaultRandom(),
75481
- internalName: text("internal_name").notNull().unique(),
75488
+ slug: text("slug").notNull(),
75489
+ gameId: uuid("game_id").references(() => games.id, {
75490
+ onDelete: "cascade"
75491
+ }),
75482
75492
  displayName: text("display_name").notNull(),
75483
75493
  description: text("description"),
75484
75494
  type: itemTypeEnum("type").notNull().default("other"),
75485
75495
  imageUrl: text("image_url"),
75486
75496
  metadata: jsonb("metadata").default({}),
75487
75497
  createdAt: timestamp("created_at").defaultNow().notNull()
75488
- });
75498
+ }, (table) => [
75499
+ uniqueIndex("items_game_slug_idx").on(table.gameId, table.slug),
75500
+ uniqueIndex("items_global_slug_idx").on(table.slug).where(sql`game_id IS NULL`)
75501
+ ]);
75489
75502
  var inventoryItems = pgTable("inventory_items", {
75490
75503
  id: uuid("id").primaryKey().defaultRandom(),
75491
75504
  userId: text("user_id").notNull().references(() => users.id, { onDelete: "cascade" }),
@@ -75518,9 +75531,13 @@ var shopListings = pgTable("shop_listings", {
75518
75531
  }, (table) => [
75519
75532
  uniqueIndex("unique_item_currency_listing_idx").on(table.itemId, table.currencyId)
75520
75533
  ]);
75521
- var itemsRelations = relations(items, ({ many }) => ({
75534
+ var itemsRelations = relations(items, ({ many, one }) => ({
75522
75535
  shopListings: many(shopListings),
75523
- inventoryItems: many(inventoryItems)
75536
+ inventoryItems: many(inventoryItems),
75537
+ game: one(games, {
75538
+ fields: [items.gameId],
75539
+ references: [games.id]
75540
+ })
75524
75541
  }));
75525
75542
  var currenciesRelations = relations(currencies, ({ many }) => ({
75526
75543
  shopListings: many(shopListings)
@@ -75569,14 +75586,18 @@ var ItemMetadataSchema = exports_external.object({
75569
75586
  var InsertItemSchema = createInsertSchema(items, {
75570
75587
  imageUrl: exports_external.string().refine((val) => validateRelativePath2(val, "imageUrl")).optional().nullable(),
75571
75588
  type: exports_external.enum(itemTypeEnum.enumValues).default("other"),
75572
- metadata: ItemMetadataSchema.optional()
75589
+ metadata: ItemMetadataSchema.optional(),
75590
+ gameId: exports_external.string().uuid().optional().nullable()
75573
75591
  }).omit({ id: true });
75574
75592
  var SelectItemSchema = createSelectSchema(items);
75575
75593
  var UpdateItemSchema = InsertItemSchema.pick({
75594
+ slug: true,
75595
+ displayName: true,
75596
+ description: true,
75576
75597
  type: true,
75577
75598
  metadata: true,
75578
75599
  imageUrl: true
75579
- });
75600
+ }).partial();
75580
75601
  var InsertInventoryItemSchema = createInsertSchema(inventoryItems).omit({
75581
75602
  id: true,
75582
75603
  updatedAt: true
@@ -75812,7 +75833,7 @@ async function seedCurrentProjectGame(db, project) {
75812
75833
  // package.json
75813
75834
  var package_default = {
75814
75835
  name: "@playcademy/sandbox",
75815
- version: "0.1.0-beta.5",
75836
+ version: "0.1.0-beta.7",
75816
75837
  description: "Local development server for Playcademy game development",
75817
75838
  type: "module",
75818
75839
  exports: {
@@ -75825,18 +75846,18 @@ var package_default = {
75825
75846
  types: "./dist/cli.js"
75826
75847
  }
75827
75848
  },
75828
- files: [
75829
- "dist"
75830
- ],
75831
75849
  bin: {
75832
75850
  "playcademy-sandbox": "./dist/cli.js"
75833
75851
  },
75852
+ files: [
75853
+ "dist"
75854
+ ],
75834
75855
  scripts: {
75835
- dev: "bun --watch src/cli.ts",
75836
- start: "bun src/cli.ts",
75837
75856
  build: "bun run build.ts",
75857
+ bump: 'bunx bumpp --no-tag --no-push -c "chore(@playcademy/sandbox): release v%s"',
75858
+ dev: "bun --watch src/cli.ts",
75838
75859
  pub: "bun run build && bun run bump && bun publish --access public",
75839
- bump: 'bunx bumpp --no-tag --no-push -c "chore(@playcademy/sandbox): release v%s"'
75860
+ start: "bun src/cli.ts"
75840
75861
  },
75841
75862
  dependencies: {
75842
75863
  "@electric-sql/pglite": "^0.3.2",
@@ -75851,7 +75872,8 @@ var package_default = {
75851
75872
  devDependencies: {
75852
75873
  "@playcademy/api-core": "workspace:*",
75853
75874
  "@playcademy/data": "workspace:*",
75854
- "@types/bun": "latest"
75875
+ "@types/bun": "latest",
75876
+ "yocto-spinner": "catalog:"
75855
75877
  },
75856
75878
  peerDependencies: {
75857
75879
  typescript: "^5"
@@ -75916,7 +75938,7 @@ async function getUserMe(ctx) {
75916
75938
  }
75917
75939
 
75918
75940
  // ../data/src/constants.ts
75919
- var ITEM_INTERNAL_NAMES = {
75941
+ var ITEM_SLUGS = {
75920
75942
  PLAYCADEMY_CREDITS: "PLAYCADEMY_CREDITS",
75921
75943
  PLAYCADEMY_XP: "PLAYCADEMY_XP",
75922
75944
  FOUNDING_MEMBER_BADGE: "FOUNDING_MEMBER_BADGE",
@@ -75927,14 +75949,15 @@ var ITEM_INTERNAL_NAMES = {
75927
75949
  SMALL_BACKPACK: "SMALL_BACKPACK"
75928
75950
  };
75929
75951
  var CURRENCIES = {
75930
- PRIMARY: ITEM_INTERNAL_NAMES.PLAYCADEMY_CREDITS,
75931
- XP: ITEM_INTERNAL_NAMES.PLAYCADEMY_XP
75952
+ PRIMARY: ITEM_SLUGS.PLAYCADEMY_CREDITS,
75953
+ XP: ITEM_SLUGS.PLAYCADEMY_XP
75932
75954
  };
75933
75955
  var BADGES = {
75934
- FOUNDING_MEMBER: ITEM_INTERNAL_NAMES.FOUNDING_MEMBER_BADGE,
75935
- EARLY_ADOPTER: ITEM_INTERNAL_NAMES.EARLY_ADOPTER_BADGE,
75936
- FIRST_GAME: ITEM_INTERNAL_NAMES.FIRST_GAME_BADGE
75956
+ FOUNDING_MEMBER: ITEM_SLUGS.FOUNDING_MEMBER_BADGE,
75957
+ EARLY_ADOPTER: ITEM_SLUGS.EARLY_ADOPTER_BADGE,
75958
+ FIRST_GAME: ITEM_SLUGS.FIRST_GAME_BADGE
75937
75959
  };
75960
+ var INTERACTION_TYPE = Object.fromEntries(interactionTypeEnum.enumValues.map((value) => [value, value]));
75938
75961
 
75939
75962
  // ../api-core/src/utils/levels.ts
75940
75963
  var levelConfigCache = null;
@@ -76091,7 +76114,7 @@ async function addXP(ctx, amount) {
76091
76114
  creditsAwarded: creditsToAward
76092
76115
  });
76093
76116
  if (creditsToAward > 0) {
76094
- const [creditsItem] = await tx.select({ id: items.id }).from(items).where(eq(items.internalName, CURRENCIES.PRIMARY)).limit(1);
76117
+ const [creditsItem] = await tx.select({ id: items.id }).from(items).where(eq(items.slug, CURRENCIES.PRIMARY)).limit(1);
76095
76118
  if (!creditsItem) {
76096
76119
  throw ApiError.internal(`${CURRENCIES.PRIMARY} item not found`);
76097
76120
  }
@@ -76291,15 +76314,18 @@ async function getUserInventory(ctx) {
76291
76314
  const db = getDatabase();
76292
76315
  const inventory2 = await db.select({
76293
76316
  id: inventoryItems.id,
76317
+ userId: inventoryItems.userId,
76294
76318
  quantity: inventoryItems.quantity,
76295
76319
  item: {
76296
76320
  id: items.id,
76297
- internalName: items.internalName,
76321
+ slug: items.slug,
76322
+ gameId: items.gameId,
76298
76323
  displayName: items.displayName,
76299
76324
  description: items.description,
76300
76325
  type: items.type,
76301
76326
  imageUrl: items.imageUrl,
76302
- metadata: items.metadata
76327
+ metadata: items.metadata,
76328
+ createdAt: items.createdAt
76303
76329
  },
76304
76330
  updatedAt: inventoryItems.updatedAt
76305
76331
  }).from(inventoryItems).where(eq(inventoryItems.userId, user.id)).innerJoin(items, eq(inventoryItems.itemId, items.id));
@@ -77928,14 +77954,13 @@ async function listGames(ctx) {
77928
77954
  if (!user) {
77929
77955
  throw ApiError.unauthorized("Must be logged in to list games");
77930
77956
  }
77931
- if (user.role !== "admin" && user.role !== "developer") {
77932
- throw ApiError.forbidden("Requires admin or developer privileges to list games");
77933
- }
77934
77957
  try {
77935
77958
  const db = getDatabase();
77936
77959
  let filter2 = undefined;
77937
77960
  if (user.role === "developer") {
77938
77961
  filter2 = eq(games.developerId, user.id);
77962
+ } else if (user.role === "player") {
77963
+ return [];
77939
77964
  }
77940
77965
  const games2 = await db.query.games.findMany({
77941
77966
  where: filter2,
@@ -78176,6 +78201,375 @@ async function upsertGameBySlug(ctx) {
78176
78201
  throw ApiError.internal("Internal server error", error2);
78177
78202
  }
78178
78203
  }
78204
+
78205
+ // ../api-core/src/items/index.ts
78206
+ async function validateGameOwnership(user, gameId) {
78207
+ if (user.role === "admin") {
78208
+ return;
78209
+ }
78210
+ try {
78211
+ const db = getDatabase();
78212
+ const gameOwnership = await db.query.games.findFirst({
78213
+ where: and(eq(games.id, gameId), eq(games.developerId, user.id)),
78214
+ columns: { id: true }
78215
+ });
78216
+ if (!gameOwnership) {
78217
+ const gameExists = await db.query.games.findFirst({
78218
+ where: eq(games.id, gameId),
78219
+ columns: { id: true }
78220
+ });
78221
+ if (!gameExists) {
78222
+ throw ApiError.notFound("Game not found");
78223
+ }
78224
+ throw ApiError.forbidden("You do not own this game");
78225
+ }
78226
+ } catch (error2) {
78227
+ if (error2 instanceof ApiError) {
78228
+ throw error2;
78229
+ }
78230
+ logger2.error(`Error checking game ownership for ${gameId}:`, error2);
78231
+ throw ApiError.internal("Internal server error", error2);
78232
+ }
78233
+ }
78234
+ async function listItems(ctx) {
78235
+ const user = ctx.user;
78236
+ if (!user) {
78237
+ throw ApiError.unauthorized("Must be logged in to view items");
78238
+ }
78239
+ try {
78240
+ const db = getDatabase();
78241
+ const url2 = new URL(ctx.request.url);
78242
+ const gameId = url2.searchParams.get("gameId");
78243
+ let allItems;
78244
+ if (gameId) {
78245
+ allItems = await db.query.items.findMany({
78246
+ where: eq(items.gameId, gameId)
78247
+ });
78248
+ } else {
78249
+ allItems = await db.query.items.findMany();
78250
+ }
78251
+ return allItems;
78252
+ } catch (error2) {
78253
+ if (error2 instanceof ApiError) {
78254
+ throw error2;
78255
+ }
78256
+ logger2.error("Error fetching items:", error2);
78257
+ throw ApiError.internal("Internal server error", error2);
78258
+ }
78259
+ }
78260
+ async function getItemById(ctx) {
78261
+ const user = ctx.user;
78262
+ const itemId = ctx.params.itemId;
78263
+ if (!user) {
78264
+ throw ApiError.unauthorized("Must be logged in to view item details");
78265
+ }
78266
+ if (!itemId) {
78267
+ throw ApiError.badRequest("Missing item ID");
78268
+ }
78269
+ try {
78270
+ const db = getDatabase();
78271
+ const item = await db.query.items.findFirst({
78272
+ where: eq(items.id, itemId)
78273
+ });
78274
+ if (!item) {
78275
+ throw ApiError.notFound("Item not found");
78276
+ }
78277
+ return item;
78278
+ } catch (error2) {
78279
+ if (error2 instanceof ApiError) {
78280
+ throw error2;
78281
+ }
78282
+ logger2.error(`Error fetching item ${itemId}:`, error2);
78283
+ throw ApiError.internal("Internal server error", error2);
78284
+ }
78285
+ }
78286
+ async function createItem(ctx) {
78287
+ const user = ctx.user;
78288
+ if (!user || user.role !== "admin") {
78289
+ throw ApiError.forbidden("Admin access required");
78290
+ }
78291
+ let inputData;
78292
+ try {
78293
+ const requestBody = await ctx.request.json();
78294
+ const validationResult = InsertItemSchema.safeParse(requestBody);
78295
+ if (!validationResult.success) {
78296
+ throw ApiError.badRequest(`Validation failed: ${formatValidationErrors(validationResult.error)}`);
78297
+ }
78298
+ inputData = validationResult.data;
78299
+ } catch (error2) {
78300
+ if (error2 instanceof ApiError) {
78301
+ throw error2;
78302
+ }
78303
+ logger2.error("Failed to parse request body or invalid JSON:", error2);
78304
+ throw ApiError.badRequest("Invalid JSON body");
78305
+ }
78306
+ try {
78307
+ const db = getDatabase();
78308
+ const [newItem] = await db.insert(items).values(inputData).returning();
78309
+ if (!newItem) {
78310
+ throw ApiError.internal("Failed to create item in database");
78311
+ }
78312
+ return newItem;
78313
+ } catch (error2) {
78314
+ if (error2 instanceof Error) {
78315
+ if (error2.message.includes("duplicate key value violates unique constraint")) {
78316
+ const gameScope = inputData.gameId ? "for this game" : "as a platform item";
78317
+ throw ApiError.conflict(`An item with slug '${inputData.slug}' already exists ${gameScope}`);
78318
+ }
78319
+ }
78320
+ logger2.error("Error creating item:", error2);
78321
+ throw ApiError.internal("Internal server error", error2);
78322
+ }
78323
+ }
78324
+ async function updateItem(ctx) {
78325
+ const user = ctx.user;
78326
+ const itemId = ctx.params.itemId;
78327
+ if (!user || user.role !== "admin") {
78328
+ throw ApiError.forbidden("Admin access required");
78329
+ }
78330
+ if (!itemId) {
78331
+ throw ApiError.badRequest("Missing item ID");
78332
+ }
78333
+ let inputData;
78334
+ try {
78335
+ const requestBody = await ctx.request.json();
78336
+ const validationResult = UpdateItemSchema.safeParse(requestBody);
78337
+ if (!validationResult.success) {
78338
+ throw ApiError.badRequest(`Validation failed: ${formatValidationErrors(validationResult.error)}`);
78339
+ }
78340
+ inputData = validationResult.data;
78341
+ } catch (error2) {
78342
+ if (error2 instanceof ApiError) {
78343
+ throw error2;
78344
+ }
78345
+ logger2.error("Failed to parse request body or invalid JSON:", error2);
78346
+ throw ApiError.badRequest("Invalid JSON body");
78347
+ }
78348
+ if (Object.keys(inputData).length === 0) {
78349
+ throw ApiError.badRequest("No update data provided");
78350
+ }
78351
+ try {
78352
+ const db = getDatabase();
78353
+ const [updatedItem] = await db.update(items).set(inputData).where(eq(items.id, itemId)).returning();
78354
+ if (!updatedItem) {
78355
+ throw ApiError.notFound("Item not found for update");
78356
+ }
78357
+ return updatedItem;
78358
+ } catch (error2) {
78359
+ if (error2 instanceof Error) {
78360
+ if (error2.message.includes("duplicate key value violates unique constraint")) {
78361
+ throw ApiError.conflict("An item with this slug already exists within the same scope (platform or game)");
78362
+ }
78363
+ }
78364
+ logger2.error(`Error updating item ${itemId}:`, error2);
78365
+ throw ApiError.internal("Internal server error", error2);
78366
+ }
78367
+ }
78368
+ async function deleteItem(ctx) {
78369
+ const user = ctx.user;
78370
+ const itemId = ctx.params.itemId;
78371
+ if (!user || user.role !== "admin") {
78372
+ throw ApiError.forbidden("Admin access required");
78373
+ }
78374
+ if (!itemId) {
78375
+ throw ApiError.badRequest("Missing item ID");
78376
+ }
78377
+ try {
78378
+ const db = getDatabase();
78379
+ const result = await db.delete(items).where(eq(items.id, itemId)).returning({ id: items.id });
78380
+ if (result.length === 0) {
78381
+ throw ApiError.notFound("Item not found for deletion");
78382
+ }
78383
+ } catch (error2) {
78384
+ if (error2 instanceof ApiError) {
78385
+ throw error2;
78386
+ }
78387
+ logger2.error(`Error deleting item ${itemId}:`, error2);
78388
+ throw ApiError.internal("Internal server error", error2);
78389
+ }
78390
+ }
78391
+ async function resolveItem(ctx) {
78392
+ const user = ctx.user;
78393
+ const url2 = new URL(ctx.request.url);
78394
+ const slug = url2.searchParams.get("slug");
78395
+ const gameId = url2.searchParams.get("gameId");
78396
+ if (!user) {
78397
+ throw ApiError.unauthorized("Must be logged in to resolve items");
78398
+ }
78399
+ if (!slug) {
78400
+ throw ApiError.badRequest("Missing slug parameter");
78401
+ }
78402
+ try {
78403
+ const db = getDatabase();
78404
+ if (gameId) {
78405
+ const gameItem = await db.query.items.findFirst({
78406
+ where: and(eq(items.slug, slug), eq(items.gameId, gameId))
78407
+ });
78408
+ if (gameItem) {
78409
+ return gameItem;
78410
+ }
78411
+ }
78412
+ const platformItem = await db.query.items.findFirst({
78413
+ where: and(eq(items.slug, slug), isNull(items.gameId))
78414
+ });
78415
+ if (!platformItem) {
78416
+ throw ApiError.notFound(`Item with slug '${slug}' not found`);
78417
+ }
78418
+ return platformItem;
78419
+ } catch (error2) {
78420
+ if (error2 instanceof ApiError) {
78421
+ throw error2;
78422
+ }
78423
+ logger2.error(`Error resolving item slug ${slug}:`, error2);
78424
+ throw ApiError.internal("Internal server error", error2);
78425
+ }
78426
+ }
78427
+ async function listGameItems(ctx) {
78428
+ const user = ctx.user;
78429
+ const gameId = ctx.params.gameId;
78430
+ if (!user) {
78431
+ throw ApiError.unauthorized("Must be logged in to view items");
78432
+ }
78433
+ if (!gameId) {
78434
+ throw ApiError.badRequest("Missing game ID");
78435
+ }
78436
+ try {
78437
+ const db = getDatabase();
78438
+ const gameItems = await db.query.items.findMany({
78439
+ where: eq(items.gameId, gameId)
78440
+ });
78441
+ return gameItems;
78442
+ } catch (error2) {
78443
+ if (error2 instanceof ApiError) {
78444
+ throw error2;
78445
+ }
78446
+ logger2.error(`Error fetching items for game ${gameId}:`, error2);
78447
+ throw ApiError.internal("Internal server error", error2);
78448
+ }
78449
+ }
78450
+ async function createGameItem(ctx) {
78451
+ const user = ctx.user;
78452
+ const gameId = ctx.params.gameId;
78453
+ if (!user) {
78454
+ throw ApiError.unauthorized("Must be logged in to create items");
78455
+ }
78456
+ if (!gameId) {
78457
+ throw ApiError.badRequest("Missing game ID");
78458
+ }
78459
+ await validateGameOwnership(user, gameId);
78460
+ let inputData;
78461
+ try {
78462
+ const requestBody = await ctx.request.json();
78463
+ const bodyData = typeof requestBody === "object" && requestBody !== null ? requestBody : {};
78464
+ const validationResult = InsertItemSchema.safeParse({
78465
+ ...bodyData,
78466
+ gameId
78467
+ });
78468
+ if (!validationResult.success) {
78469
+ throw ApiError.badRequest(`Validation failed: ${formatValidationErrors(validationResult.error)}`);
78470
+ }
78471
+ inputData = validationResult.data;
78472
+ } catch (error2) {
78473
+ if (error2 instanceof ApiError) {
78474
+ throw error2;
78475
+ }
78476
+ logger2.error("Failed to parse request body or invalid JSON:", error2);
78477
+ throw ApiError.badRequest("Invalid JSON body");
78478
+ }
78479
+ try {
78480
+ const db = getDatabase();
78481
+ const [newItem] = await db.insert(items).values(inputData).returning();
78482
+ if (!newItem) {
78483
+ throw ApiError.internal("Failed to create item in database");
78484
+ }
78485
+ return newItem;
78486
+ } catch (error2) {
78487
+ if (error2 instanceof Error) {
78488
+ if (error2.message.includes("duplicate key value violates unique constraint")) {
78489
+ throw ApiError.conflict(`An item with slug '${inputData.slug}' already exists for this game`);
78490
+ }
78491
+ }
78492
+ logger2.error("Error creating game item:", error2);
78493
+ throw ApiError.internal("Internal server error", error2);
78494
+ }
78495
+ }
78496
+ async function updateGameItem(ctx) {
78497
+ const user = ctx.user;
78498
+ const gameId = ctx.params.gameId;
78499
+ const itemId = ctx.params.itemId;
78500
+ if (!user) {
78501
+ throw ApiError.unauthorized("Must be logged in to update items");
78502
+ }
78503
+ if (!gameId || !itemId) {
78504
+ throw ApiError.badRequest("Missing game ID or item ID");
78505
+ }
78506
+ await validateGameOwnership(user, gameId);
78507
+ let inputData;
78508
+ try {
78509
+ const requestBody = await ctx.request.json();
78510
+ const validationResult = UpdateItemSchema.safeParse(requestBody);
78511
+ if (!validationResult.success) {
78512
+ throw ApiError.badRequest(`Validation failed: ${formatValidationErrors(validationResult.error)}`);
78513
+ }
78514
+ inputData = validationResult.data;
78515
+ } catch (error2) {
78516
+ if (error2 instanceof ApiError) {
78517
+ throw error2;
78518
+ }
78519
+ logger2.error("Failed to parse request body or invalid JSON:", error2);
78520
+ throw ApiError.badRequest("Invalid JSON body");
78521
+ }
78522
+ if (Object.keys(inputData).length === 0) {
78523
+ throw ApiError.badRequest("No update data provided");
78524
+ }
78525
+ try {
78526
+ const db = getDatabase();
78527
+ const existingItem = await db.query.items.findFirst({
78528
+ where: and(eq(items.id, itemId), eq(items.gameId, gameId))
78529
+ });
78530
+ if (!existingItem) {
78531
+ throw ApiError.notFound("Item not found for this game");
78532
+ }
78533
+ const [updatedItem] = await db.update(items).set(inputData).where(eq(items.id, itemId)).returning();
78534
+ if (!updatedItem) {
78535
+ throw ApiError.notFound("Item not found for update");
78536
+ }
78537
+ return updatedItem;
78538
+ } catch (error2) {
78539
+ if (error2 instanceof Error) {
78540
+ if (error2.message.includes("duplicate key value violates unique constraint")) {
78541
+ throw ApiError.conflict("An item with this slug already exists for this game");
78542
+ }
78543
+ }
78544
+ logger2.error(`Error updating game item ${itemId}:`, error2);
78545
+ throw ApiError.internal("Internal server error", error2);
78546
+ }
78547
+ }
78548
+ async function deleteGameItem(ctx) {
78549
+ const user = ctx.user;
78550
+ const gameId = ctx.params.gameId;
78551
+ const itemId = ctx.params.itemId;
78552
+ if (!user) {
78553
+ throw ApiError.unauthorized("Must be logged in to delete items");
78554
+ }
78555
+ if (!gameId || !itemId) {
78556
+ throw ApiError.badRequest("Missing game ID or item ID");
78557
+ }
78558
+ await validateGameOwnership(user, gameId);
78559
+ try {
78560
+ const db = getDatabase();
78561
+ const result = await db.delete(items).where(and(eq(items.id, itemId), eq(items.gameId, gameId))).returning({ id: items.id });
78562
+ if (result.length === 0) {
78563
+ throw ApiError.notFound("Item not found for this game");
78564
+ }
78565
+ } catch (error2) {
78566
+ if (error2 instanceof ApiError) {
78567
+ throw error2;
78568
+ }
78569
+ logger2.error(`Error deleting game item ${itemId}:`, error2);
78570
+ throw ApiError.internal("Internal server error", error2);
78571
+ }
78572
+ }
78179
78573
  // ../../node_modules/@oslojs/binary/dist/uint.js
78180
78574
  class BigEndian {
78181
78575
  uint8(data2, offset) {
@@ -79255,6 +79649,88 @@ gamesRouter.post("/uploads/finalize", async (c2) => {
79255
79649
  return c2.json({ error: message2 }, 500);
79256
79650
  }
79257
79651
  });
79652
+ gamesRouter.get("/:gameId/items", async (c2) => {
79653
+ const gameId = c2.req.param("gameId");
79654
+ const ctx = {
79655
+ user: c2.get("user"),
79656
+ params: { gameId },
79657
+ url: new URL(c2.req.url),
79658
+ request: c2.req.raw
79659
+ };
79660
+ try {
79661
+ const result = await listGameItems(ctx);
79662
+ return c2.json(result);
79663
+ } catch (error2) {
79664
+ if (error2 instanceof ApiError) {
79665
+ return c2.json({ error: error2.message }, error2.statusCode);
79666
+ }
79667
+ console.error("Error in listGameItems:", error2);
79668
+ const message2 = error2 instanceof Error ? error2.message : "Internal server error";
79669
+ return c2.json({ error: message2 }, 500);
79670
+ }
79671
+ });
79672
+ gamesRouter.post("/:gameId/items", async (c2) => {
79673
+ const gameId = c2.req.param("gameId");
79674
+ const ctx = {
79675
+ user: c2.get("user"),
79676
+ params: { gameId },
79677
+ url: new URL(c2.req.url),
79678
+ request: c2.req.raw
79679
+ };
79680
+ try {
79681
+ const result = await createGameItem(ctx);
79682
+ return c2.json(result, 201);
79683
+ } catch (error2) {
79684
+ if (error2 instanceof ApiError) {
79685
+ return c2.json({ error: error2.message }, error2.statusCode);
79686
+ }
79687
+ console.error("Error in createGameItem:", error2);
79688
+ const message2 = error2 instanceof Error ? error2.message : "Internal server error";
79689
+ return c2.json({ error: message2 }, 500);
79690
+ }
79691
+ });
79692
+ gamesRouter.patch("/:gameId/items/:itemId", async (c2) => {
79693
+ const gameId = c2.req.param("gameId");
79694
+ const itemId = c2.req.param("itemId");
79695
+ const ctx = {
79696
+ user: c2.get("user"),
79697
+ params: { gameId, itemId },
79698
+ url: new URL(c2.req.url),
79699
+ request: c2.req.raw
79700
+ };
79701
+ try {
79702
+ const result = await updateGameItem(ctx);
79703
+ return c2.json(result);
79704
+ } catch (error2) {
79705
+ if (error2 instanceof ApiError) {
79706
+ return c2.json({ error: error2.message }, error2.statusCode);
79707
+ }
79708
+ console.error("Error in updateGameItem:", error2);
79709
+ const message2 = error2 instanceof Error ? error2.message : "Internal server error";
79710
+ return c2.json({ error: message2 }, 500);
79711
+ }
79712
+ });
79713
+ gamesRouter.delete("/:gameId/items/:itemId", async (c2) => {
79714
+ const gameId = c2.req.param("gameId");
79715
+ const itemId = c2.req.param("itemId");
79716
+ const ctx = {
79717
+ user: c2.get("user"),
79718
+ params: { gameId, itemId },
79719
+ url: new URL(c2.req.url),
79720
+ request: c2.req.raw
79721
+ };
79722
+ try {
79723
+ await deleteGameItem(ctx);
79724
+ return c2.body(null, 204);
79725
+ } catch (error2) {
79726
+ if (error2 instanceof ApiError) {
79727
+ return c2.json({ error: error2.message }, error2.statusCode);
79728
+ }
79729
+ console.error("Error in deleteGameItem:", error2);
79730
+ const message2 = error2 instanceof Error ? error2.message : "Internal server error";
79731
+ return c2.json({ error: message2 }, 500);
79732
+ }
79733
+ });
79258
79734
  // src/routes/manifest.ts
79259
79735
  var manifestRouter = new Hono2;
79260
79736
  manifestRouter.get("/", async (c2) => {
@@ -79269,7 +79745,7 @@ manifestRouter.get("/", async (c2) => {
79269
79745
  { method: "GET", url: `${baseUrl}/api/users/me` },
79270
79746
  { method: "GET", url: `${baseUrl}/api/inventory` },
79271
79747
  { method: "POST", url: `${baseUrl}/api/inventory/add` },
79272
- { method: "POST", url: `${baseUrl}/api/inventory/spend` },
79748
+ { method: "POST", url: `${baseUrl}/api/inventory/remove` },
79273
79749
  { method: "POST", url: `${baseUrl}/api/games/:gameId/sessions` },
79274
79750
  {
79275
79751
  method: "POST",
@@ -79386,157 +79862,27 @@ shopRouter.get("/view", async (c2) => {
79386
79862
  return c2.json({ error: message2 }, 500);
79387
79863
  }
79388
79864
  });
79389
- // ../api-core/src/items/index.ts
79390
- async function listItems(ctx) {
79391
- const user = ctx.user;
79392
- if (!user) {
79393
- throw ApiError.unauthorized("Must be logged in to view items");
79394
- }
79395
- try {
79396
- const db = getDatabase();
79397
- const allItems = await db.query.items.findMany();
79398
- return allItems;
79399
- } catch (error2) {
79400
- if (error2 instanceof ApiError) {
79401
- throw error2;
79402
- }
79403
- logger2.error("Error fetching items:", error2);
79404
- throw ApiError.internal("Internal server error", error2);
79405
- }
79406
- }
79407
- async function getItemById(ctx) {
79408
- const user = ctx.user;
79409
- const itemId = ctx.params.itemId;
79410
- if (!user) {
79411
- throw ApiError.unauthorized("Must be logged in to view item details");
79412
- }
79413
- if (!itemId) {
79414
- throw ApiError.badRequest("Missing item ID");
79415
- }
79416
- try {
79417
- const db = getDatabase();
79418
- const item = await db.query.items.findFirst({
79419
- where: eq(items.id, itemId)
79420
- });
79421
- if (!item) {
79422
- throw ApiError.notFound("Item not found");
79423
- }
79424
- return item;
79425
- } catch (error2) {
79426
- if (error2 instanceof ApiError) {
79427
- throw error2;
79428
- }
79429
- logger2.error(`Error fetching item ${itemId}:`, error2);
79430
- throw ApiError.internal("Internal server error", error2);
79431
- }
79432
- }
79433
- async function createItem(ctx) {
79434
- const user = ctx.user;
79435
- if (!user || user.role !== "admin") {
79436
- throw ApiError.forbidden("Admin access required");
79437
- }
79438
- let inputData;
79439
- try {
79440
- const requestBody = await ctx.request.json();
79441
- const validationResult = InsertItemSchema.safeParse(requestBody);
79442
- if (!validationResult.success) {
79443
- throw ApiError.badRequest(`Validation failed: ${formatValidationErrors(validationResult.error)}`);
79444
- }
79445
- inputData = validationResult.data;
79446
- } catch (error2) {
79447
- if (error2 instanceof ApiError) {
79448
- throw error2;
79449
- }
79450
- logger2.error("Failed to parse request body or invalid JSON:", error2);
79451
- throw ApiError.badRequest("Invalid JSON body");
79452
- }
79453
- try {
79454
- const db = getDatabase();
79455
- const [newItem] = await db.insert(items).values(inputData).returning();
79456
- if (!newItem) {
79457
- throw ApiError.internal("Failed to create item in database");
79458
- }
79459
- return newItem;
79460
- } catch (error2) {
79461
- if (error2 instanceof Error) {
79462
- if (error2.message.includes("duplicate key value violates unique constraint")) {
79463
- throw ApiError.conflict("An item with this internal name already exists");
79464
- }
79465
- }
79466
- logger2.error("Error creating item:", error2);
79467
- throw ApiError.internal("Internal server error", error2);
79468
- }
79469
- }
79470
- async function updateItem(ctx) {
79471
- const user = ctx.user;
79472
- const itemId = ctx.params.itemId;
79473
- if (!user || user.role !== "admin") {
79474
- throw ApiError.forbidden("Admin access required");
79475
- }
79476
- if (!itemId) {
79477
- throw ApiError.badRequest("Missing item ID");
79478
- }
79479
- let inputData;
79480
- try {
79481
- const requestBody = await ctx.request.json();
79482
- const validationResult = UpdateItemSchema.safeParse(requestBody);
79483
- if (!validationResult.success) {
79484
- throw ApiError.badRequest(`Validation failed: ${formatValidationErrors(validationResult.error)}`);
79485
- }
79486
- inputData = validationResult.data;
79487
- } catch (error2) {
79488
- if (error2 instanceof ApiError) {
79489
- throw error2;
79490
- }
79491
- logger2.error("Failed to parse request body or invalid JSON:", error2);
79492
- throw ApiError.badRequest("Invalid JSON body");
79493
- }
79494
- if (Object.keys(inputData).length === 0) {
79495
- throw ApiError.badRequest("No update data provided");
79496
- }
79497
- try {
79498
- const db = getDatabase();
79499
- const [updatedItem] = await db.update(items).set(inputData).where(eq(items.id, itemId)).returning();
79500
- if (!updatedItem) {
79501
- throw ApiError.notFound("Item not found for update");
79502
- }
79503
- return updatedItem;
79504
- } catch (error2) {
79505
- if (error2 instanceof Error) {
79506
- if (error2.message.includes("duplicate key value violates unique constraint")) {
79507
- throw ApiError.conflict("An item with this internal name already exists");
79508
- }
79509
- }
79510
- logger2.error(`Error updating item ${itemId}:`, error2);
79511
- throw ApiError.internal("Internal server error", error2);
79512
- }
79513
- }
79514
- async function deleteItem(ctx) {
79515
- const user = ctx.user;
79516
- const itemId = ctx.params.itemId;
79517
- if (!user || user.role !== "admin") {
79518
- throw ApiError.forbidden("Admin access required");
79519
- }
79520
- if (!itemId) {
79521
- throw ApiError.badRequest("Missing item ID");
79522
- }
79865
+ // src/routes/items.ts
79866
+ var itemsRouter = new Hono2;
79867
+ itemsRouter.get("/resolve", async (c2) => {
79868
+ const ctx = {
79869
+ user: c2.get("user"),
79870
+ params: {},
79871
+ url: new URL(c2.req.url),
79872
+ request: c2.req.raw
79873
+ };
79523
79874
  try {
79524
- const db = getDatabase();
79525
- const result = await db.delete(items).where(eq(items.id, itemId)).returning({ id: items.id });
79526
- if (result.length === 0) {
79527
- throw ApiError.notFound("Item not found for deletion");
79528
- }
79875
+ const result = await resolveItem(ctx);
79876
+ return c2.json(result);
79529
79877
  } catch (error2) {
79530
79878
  if (error2 instanceof ApiError) {
79531
- throw error2;
79879
+ return c2.json({ error: error2.message }, error2.statusCode);
79532
79880
  }
79533
- logger2.error(`Error deleting item ${itemId}:`, error2);
79534
- throw ApiError.internal("Internal server error", error2);
79881
+ console.error("Error in resolveItem:", error2);
79882
+ const message2 = error2 instanceof Error ? error2.message : "Internal server error";
79883
+ return c2.json({ error: message2 }, 500);
79535
79884
  }
79536
- }
79537
-
79538
- // src/routes/items.ts
79539
- var itemsRouter = new Hono2;
79885
+ });
79540
79886
  itemsRouter.post("/", async (c2) => {
79541
79887
  const ctx = {
79542
79888
  user: c2.get("user"),
@@ -79709,8 +80055,8 @@ async function createCurrency(ctx) {
79709
80055
  } catch (error2) {
79710
80056
  if (error2 instanceof Error) {
79711
80057
  if (error2.message.includes("duplicate key value violates unique constraint") || error2.message.includes("UNIQUE constraint failed")) {
79712
- if (error2.message.includes("currencies_internal_name_unique")) {
79713
- throw ApiError.conflict("A currency with this internal name already exists");
80058
+ if (error2.message.includes("currencies_slug_unique")) {
80059
+ throw ApiError.conflict("A currency with this slug already exists");
79714
80060
  }
79715
80061
  throw ApiError.conflict("A similar currency already exists");
79716
80062
  }
@@ -79756,8 +80102,8 @@ async function updateCurrency(ctx) {
79756
80102
  } catch (error2) {
79757
80103
  if (error2 instanceof Error) {
79758
80104
  if (error2.message.includes("duplicate key value violates unique constraint") || error2.message.includes("UNIQUE constraint failed")) {
79759
- if (error2.message.includes("currencies_internal_name_unique")) {
79760
- throw ApiError.conflict("A currency with this internal name already exists");
80105
+ if (error2.message.includes("currencies_slug_unique")) {
80106
+ throw ApiError.conflict("A currency with this slug already exists");
79761
80107
  }
79762
80108
  throw ApiError.conflict("A similar currency already exists");
79763
80109
  }