@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/cli.js CHANGED
@@ -1707,8 +1707,8 @@ is not a problem with esbuild. You need to fix your environment instead.
1707
1707
  if (isFirstPacket) {
1708
1708
  isFirstPacket = false;
1709
1709
  let binaryVersion = String.fromCharCode(...bytes);
1710
- if (binaryVersion !== "0.25.4") {
1711
- throw new Error(`Cannot start service: Host version "${"0.25.4"}" does not match binary version ${quote(binaryVersion)}`);
1710
+ if (binaryVersion !== "0.25.5") {
1711
+ throw new Error(`Cannot start service: Host version "${"0.25.5"}" does not match binary version ${quote(binaryVersion)}`);
1712
1712
  }
1713
1713
  return;
1714
1714
  }
@@ -2896,7 +2896,7 @@ for your current platform.`);
2896
2896
  } catch (e) {}
2897
2897
  if (pnpapi) {
2898
2898
  const root = pnpapi.getPackageInformation(pnpapi.topLevel).packageLocation;
2899
- const binTargetPath = path.join(root, "node_modules", ".cache", "esbuild", `pnpapi-${pkg.replace("/", "-")}-${"0.25.4"}-${path.basename(subpath)}`);
2899
+ const binTargetPath = path.join(root, "node_modules", ".cache", "esbuild", `pnpapi-${pkg.replace("/", "-")}-${"0.25.5"}-${path.basename(subpath)}`);
2900
2900
  if (!fs3.existsSync(binTargetPath)) {
2901
2901
  fs3.mkdirSync(path.dirname(binTargetPath), { recursive: true });
2902
2902
  fs3.copyFileSync(binPath, binTargetPath);
@@ -2924,7 +2924,7 @@ for your current platform.`);
2924
2924
  }
2925
2925
  }
2926
2926
  var _a;
2927
- var isInternalWorkerThread = ((_a = worker_threads == null ? undefined : worker_threads.workerData) == null ? undefined : _a.esbuildVersion) === "0.25.4";
2927
+ var isInternalWorkerThread = ((_a = worker_threads == null ? undefined : worker_threads.workerData) == null ? undefined : _a.esbuildVersion) === "0.25.5";
2928
2928
  var esbuildCommandAndArgs = () => {
2929
2929
  if ((!ESBUILD_BINARY_PATH || false) && (path2.basename(__filename) !== "main.js" || path2.basename(__dirname) !== "lib")) {
2930
2930
  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.
@@ -2986,7 +2986,7 @@ More information: The file containing the code for esbuild's JavaScript API (${_
2986
2986
  }
2987
2987
  }
2988
2988
  };
2989
- var version2 = "0.25.4";
2989
+ var version2 = "0.25.5";
2990
2990
  var build = (options) => ensureServiceIsRunning().build(options);
2991
2991
  var context = (buildOptions) => ensureServiceIsRunning().context(buildOptions);
2992
2992
  var transform = (input, options) => ensureServiceIsRunning().transform(input, options);
@@ -3104,7 +3104,7 @@ More information: The file containing the code for esbuild's JavaScript API (${_
3104
3104
  if (longLivedService)
3105
3105
  return longLivedService;
3106
3106
  let [command, args2] = esbuildCommandAndArgs();
3107
- let child = child_process.spawn(command, args2.concat(`--service=${"0.25.4"}`, "--ping"), {
3107
+ let child = child_process.spawn(command, args2.concat(`--service=${"0.25.5"}`, "--ping"), {
3108
3108
  windowsHide: true,
3109
3109
  stdio: ["pipe", "pipe", "inherit"],
3110
3110
  cwd: defaultWD
@@ -3212,7 +3212,7 @@ More information: The file containing the code for esbuild's JavaScript API (${_
3212
3212
  esbuild: node_exports
3213
3213
  });
3214
3214
  callback(service);
3215
- let stdout = child_process.execFileSync(command, args2.concat(`--service=${"0.25.4"}`), {
3215
+ let stdout = child_process.execFileSync(command, args2.concat(`--service=${"0.25.5"}`), {
3216
3216
  cwd: defaultWD,
3217
3217
  windowsHide: true,
3218
3218
  input: stdin,
@@ -3228,7 +3228,7 @@ More information: The file containing the code for esbuild's JavaScript API (${_
3228
3228
  var startWorkerThreadService = (worker_threads2) => {
3229
3229
  let { port1: mainPort, port2: workerPort } = new worker_threads2.MessageChannel;
3230
3230
  let worker = new worker_threads2.Worker(__filename, {
3231
- workerData: { workerPort, defaultWD, esbuildVersion: "0.25.4" },
3231
+ workerData: { workerPort, defaultWD, esbuildVersion: "0.25.5" },
3232
3232
  transferList: [workerPort],
3233
3233
  execArgv: []
3234
3234
  });
@@ -52246,9 +52246,9 @@ import { Http2ServerRequest } from "http2";
52246
52246
  import { Readable } from "stream";
52247
52247
  import crypto2 from "crypto";
52248
52248
  var RequestError = class extends Error {
52249
- static name = "RequestError";
52250
52249
  constructor(message, options) {
52251
52250
  super(message, options);
52251
+ this.name = "RequestError";
52252
52252
  }
52253
52253
  };
52254
52254
  var toRequestError = (e) => {
@@ -52853,7 +52853,8 @@ var PLAYCADEMY_CREDITS_ID = crypto.randomUUID();
52853
52853
  var SAMPLE_ITEMS = [
52854
52854
  {
52855
52855
  id: PLAYCADEMY_CREDITS_ID,
52856
- internalName: "PLAYCADEMY_CREDITS",
52856
+ slug: "PLAYCADEMY_CREDITS",
52857
+ gameId: null,
52857
52858
  displayName: "PLAYCADEMY credits",
52858
52859
  description: "The main currency used across PLAYCADEMY.",
52859
52860
  type: "currency",
@@ -52864,7 +52865,8 @@ var SAMPLE_ITEMS = [
52864
52865
  },
52865
52866
  {
52866
52867
  id: crypto.randomUUID(),
52867
- internalName: "FOUNDING_MEMBER_BADGE",
52868
+ slug: "FOUNDING_MEMBER_BADGE",
52869
+ gameId: null,
52868
52870
  displayName: "Founding Member Badge",
52869
52871
  description: "Reserved for founding core team of the PLAYCADEMY platform.",
52870
52872
  type: "badge",
@@ -52875,7 +52877,8 @@ var SAMPLE_ITEMS = [
52875
52877
  },
52876
52878
  {
52877
52879
  id: crypto.randomUUID(),
52878
- internalName: "EARLY_ADOPTER_BADGE",
52880
+ slug: "EARLY_ADOPTER_BADGE",
52881
+ gameId: null,
52879
52882
  displayName: "Early Adopter Badge",
52880
52883
  description: "Awarded to users who joined during the beta phase.",
52881
52884
  type: "badge",
@@ -52886,7 +52889,8 @@ var SAMPLE_ITEMS = [
52886
52889
  },
52887
52890
  {
52888
52891
  id: crypto.randomUUID(),
52889
- internalName: "FIRST_GAME_BADGE",
52892
+ slug: "FIRST_GAME_BADGE",
52893
+ gameId: null,
52890
52894
  displayName: "First Game Played",
52891
52895
  description: "Awarded for playing your first game in the Playcademy platform.",
52892
52896
  type: "badge",
@@ -52897,7 +52901,8 @@ var SAMPLE_ITEMS = [
52897
52901
  },
52898
52902
  {
52899
52903
  id: crypto.randomUUID(),
52900
- internalName: "COMMON_SWORD",
52904
+ slug: "COMMON_SWORD",
52905
+ gameId: null,
52901
52906
  displayName: "Common Sword",
52902
52907
  description: "A basic sword, good for beginners.",
52903
52908
  type: "unlock",
@@ -52906,7 +52911,8 @@ var SAMPLE_ITEMS = [
52906
52911
  },
52907
52912
  {
52908
52913
  id: crypto.randomUUID(),
52909
- internalName: "SMALL_HEALTH_POTION",
52914
+ slug: "SMALL_HEALTH_POTION",
52915
+ gameId: null,
52910
52916
  displayName: "Small Health Potion",
52911
52917
  description: "Restores a small amount of health.",
52912
52918
  type: "other",
@@ -52915,7 +52921,8 @@ var SAMPLE_ITEMS = [
52915
52921
  },
52916
52922
  {
52917
52923
  id: crypto.randomUUID(),
52918
- internalName: "SMALL_BACKPACK",
52924
+ slug: "SMALL_BACKPACK",
52925
+ gameId: null,
52919
52926
  displayName: "Small Backpack",
52920
52927
  description: "Increases your inventory capacity by 5 slots.",
52921
52928
  type: "upgrade",
@@ -77388,14 +77395,20 @@ var itemTypeEnum = pgEnum("item_type", [
77388
77395
  ]);
77389
77396
  var items = pgTable("items", {
77390
77397
  id: uuid("id").primaryKey().defaultRandom(),
77391
- internalName: text("internal_name").notNull().unique(),
77398
+ slug: text("slug").notNull(),
77399
+ gameId: uuid("game_id").references(() => games.id, {
77400
+ onDelete: "cascade"
77401
+ }),
77392
77402
  displayName: text("display_name").notNull(),
77393
77403
  description: text("description"),
77394
77404
  type: itemTypeEnum("type").notNull().default("other"),
77395
77405
  imageUrl: text("image_url"),
77396
77406
  metadata: jsonb("metadata").default({}),
77397
77407
  createdAt: timestamp("created_at").defaultNow().notNull()
77398
- });
77408
+ }, (table) => [
77409
+ uniqueIndex("items_game_slug_idx").on(table.gameId, table.slug),
77410
+ uniqueIndex("items_global_slug_idx").on(table.slug).where(sql`game_id IS NULL`)
77411
+ ]);
77399
77412
  var inventoryItems = pgTable("inventory_items", {
77400
77413
  id: uuid("id").primaryKey().defaultRandom(),
77401
77414
  userId: text("user_id").notNull().references(() => users.id, { onDelete: "cascade" }),
@@ -77428,9 +77441,13 @@ var shopListings = pgTable("shop_listings", {
77428
77441
  }, (table) => [
77429
77442
  uniqueIndex("unique_item_currency_listing_idx").on(table.itemId, table.currencyId)
77430
77443
  ]);
77431
- var itemsRelations = relations(items, ({ many }) => ({
77444
+ var itemsRelations = relations(items, ({ many, one }) => ({
77432
77445
  shopListings: many(shopListings),
77433
- inventoryItems: many(inventoryItems)
77446
+ inventoryItems: many(inventoryItems),
77447
+ game: one(games, {
77448
+ fields: [items.gameId],
77449
+ references: [games.id]
77450
+ })
77434
77451
  }));
77435
77452
  var currenciesRelations = relations(currencies, ({ many }) => ({
77436
77453
  shopListings: many(shopListings)
@@ -77479,14 +77496,18 @@ var ItemMetadataSchema = exports_external.object({
77479
77496
  var InsertItemSchema = createInsertSchema(items, {
77480
77497
  imageUrl: exports_external.string().refine((val) => validateRelativePath2(val, "imageUrl")).optional().nullable(),
77481
77498
  type: exports_external.enum(itemTypeEnum.enumValues).default("other"),
77482
- metadata: ItemMetadataSchema.optional()
77499
+ metadata: ItemMetadataSchema.optional(),
77500
+ gameId: exports_external.string().uuid().optional().nullable()
77483
77501
  }).omit({ id: true });
77484
77502
  var SelectItemSchema = createSelectSchema(items);
77485
77503
  var UpdateItemSchema = InsertItemSchema.pick({
77504
+ slug: true,
77505
+ displayName: true,
77506
+ description: true,
77486
77507
  type: true,
77487
77508
  metadata: true,
77488
77509
  imageUrl: true
77489
- });
77510
+ }).partial();
77490
77511
  var InsertInventoryItemSchema = createInsertSchema(inventoryItems).omit({
77491
77512
  id: true,
77492
77513
  updatedAt: true
@@ -77722,7 +77743,7 @@ async function seedCurrentProjectGame(db, project) {
77722
77743
  // package.json
77723
77744
  var package_default = {
77724
77745
  name: "@playcademy/sandbox",
77725
- version: "0.1.0-beta.5",
77746
+ version: "0.1.0-beta.7",
77726
77747
  description: "Local development server for Playcademy game development",
77727
77748
  type: "module",
77728
77749
  exports: {
@@ -77735,18 +77756,18 @@ var package_default = {
77735
77756
  types: "./dist/cli.js"
77736
77757
  }
77737
77758
  },
77738
- files: [
77739
- "dist"
77740
- ],
77741
77759
  bin: {
77742
77760
  "playcademy-sandbox": "./dist/cli.js"
77743
77761
  },
77762
+ files: [
77763
+ "dist"
77764
+ ],
77744
77765
  scripts: {
77745
- dev: "bun --watch src/cli.ts",
77746
- start: "bun src/cli.ts",
77747
77766
  build: "bun run build.ts",
77767
+ bump: 'bunx bumpp --no-tag --no-push -c "chore(@playcademy/sandbox): release v%s"',
77768
+ dev: "bun --watch src/cli.ts",
77748
77769
  pub: "bun run build && bun run bump && bun publish --access public",
77749
- bump: 'bunx bumpp --no-tag --no-push -c "chore(@playcademy/sandbox): release v%s"'
77770
+ start: "bun src/cli.ts"
77750
77771
  },
77751
77772
  dependencies: {
77752
77773
  "@electric-sql/pglite": "^0.3.2",
@@ -77761,7 +77782,8 @@ var package_default = {
77761
77782
  devDependencies: {
77762
77783
  "@playcademy/api-core": "workspace:*",
77763
77784
  "@playcademy/data": "workspace:*",
77764
- "@types/bun": "latest"
77785
+ "@types/bun": "latest",
77786
+ "yocto-spinner": "catalog:"
77765
77787
  },
77766
77788
  peerDependencies: {
77767
77789
  typescript: "^5"
@@ -77826,7 +77848,7 @@ async function getUserMe(ctx) {
77826
77848
  }
77827
77849
 
77828
77850
  // ../data/src/constants.ts
77829
- var ITEM_INTERNAL_NAMES = {
77851
+ var ITEM_SLUGS = {
77830
77852
  PLAYCADEMY_CREDITS: "PLAYCADEMY_CREDITS",
77831
77853
  PLAYCADEMY_XP: "PLAYCADEMY_XP",
77832
77854
  FOUNDING_MEMBER_BADGE: "FOUNDING_MEMBER_BADGE",
@@ -77837,14 +77859,15 @@ var ITEM_INTERNAL_NAMES = {
77837
77859
  SMALL_BACKPACK: "SMALL_BACKPACK"
77838
77860
  };
77839
77861
  var CURRENCIES = {
77840
- PRIMARY: ITEM_INTERNAL_NAMES.PLAYCADEMY_CREDITS,
77841
- XP: ITEM_INTERNAL_NAMES.PLAYCADEMY_XP
77862
+ PRIMARY: ITEM_SLUGS.PLAYCADEMY_CREDITS,
77863
+ XP: ITEM_SLUGS.PLAYCADEMY_XP
77842
77864
  };
77843
77865
  var BADGES = {
77844
- FOUNDING_MEMBER: ITEM_INTERNAL_NAMES.FOUNDING_MEMBER_BADGE,
77845
- EARLY_ADOPTER: ITEM_INTERNAL_NAMES.EARLY_ADOPTER_BADGE,
77846
- FIRST_GAME: ITEM_INTERNAL_NAMES.FIRST_GAME_BADGE
77866
+ FOUNDING_MEMBER: ITEM_SLUGS.FOUNDING_MEMBER_BADGE,
77867
+ EARLY_ADOPTER: ITEM_SLUGS.EARLY_ADOPTER_BADGE,
77868
+ FIRST_GAME: ITEM_SLUGS.FIRST_GAME_BADGE
77847
77869
  };
77870
+ var INTERACTION_TYPE = Object.fromEntries(interactionTypeEnum.enumValues.map((value) => [value, value]));
77848
77871
 
77849
77872
  // ../api-core/src/utils/levels.ts
77850
77873
  var levelConfigCache = null;
@@ -78001,7 +78024,7 @@ async function addXP(ctx, amount) {
78001
78024
  creditsAwarded: creditsToAward
78002
78025
  });
78003
78026
  if (creditsToAward > 0) {
78004
- const [creditsItem] = await tx.select({ id: items.id }).from(items).where(eq(items.internalName, CURRENCIES.PRIMARY)).limit(1);
78027
+ const [creditsItem] = await tx.select({ id: items.id }).from(items).where(eq(items.slug, CURRENCIES.PRIMARY)).limit(1);
78005
78028
  if (!creditsItem) {
78006
78029
  throw ApiError.internal(`${CURRENCIES.PRIMARY} item not found`);
78007
78030
  }
@@ -78201,15 +78224,18 @@ async function getUserInventory(ctx) {
78201
78224
  const db = getDatabase();
78202
78225
  const inventory2 = await db.select({
78203
78226
  id: inventoryItems.id,
78227
+ userId: inventoryItems.userId,
78204
78228
  quantity: inventoryItems.quantity,
78205
78229
  item: {
78206
78230
  id: items.id,
78207
- internalName: items.internalName,
78231
+ slug: items.slug,
78232
+ gameId: items.gameId,
78208
78233
  displayName: items.displayName,
78209
78234
  description: items.description,
78210
78235
  type: items.type,
78211
78236
  imageUrl: items.imageUrl,
78212
- metadata: items.metadata
78237
+ metadata: items.metadata,
78238
+ createdAt: items.createdAt
78213
78239
  },
78214
78240
  updatedAt: inventoryItems.updatedAt
78215
78241
  }).from(inventoryItems).where(eq(inventoryItems.userId, user.id)).innerJoin(items, eq(inventoryItems.itemId, items.id));
@@ -79838,14 +79864,13 @@ async function listGames(ctx) {
79838
79864
  if (!user) {
79839
79865
  throw ApiError.unauthorized("Must be logged in to list games");
79840
79866
  }
79841
- if (user.role !== "admin" && user.role !== "developer") {
79842
- throw ApiError.forbidden("Requires admin or developer privileges to list games");
79843
- }
79844
79867
  try {
79845
79868
  const db = getDatabase();
79846
79869
  let filter2 = undefined;
79847
79870
  if (user.role === "developer") {
79848
79871
  filter2 = eq(games.developerId, user.id);
79872
+ } else if (user.role === "player") {
79873
+ return [];
79849
79874
  }
79850
79875
  const games2 = await db.query.games.findMany({
79851
79876
  where: filter2,
@@ -80086,6 +80111,375 @@ async function upsertGameBySlug(ctx) {
80086
80111
  throw ApiError.internal("Internal server error", error2);
80087
80112
  }
80088
80113
  }
80114
+
80115
+ // ../api-core/src/items/index.ts
80116
+ async function validateGameOwnership(user, gameId) {
80117
+ if (user.role === "admin") {
80118
+ return;
80119
+ }
80120
+ try {
80121
+ const db = getDatabase();
80122
+ const gameOwnership = await db.query.games.findFirst({
80123
+ where: and(eq(games.id, gameId), eq(games.developerId, user.id)),
80124
+ columns: { id: true }
80125
+ });
80126
+ if (!gameOwnership) {
80127
+ const gameExists = await db.query.games.findFirst({
80128
+ where: eq(games.id, gameId),
80129
+ columns: { id: true }
80130
+ });
80131
+ if (!gameExists) {
80132
+ throw ApiError.notFound("Game not found");
80133
+ }
80134
+ throw ApiError.forbidden("You do not own this game");
80135
+ }
80136
+ } catch (error2) {
80137
+ if (error2 instanceof ApiError) {
80138
+ throw error2;
80139
+ }
80140
+ logger2.error(`Error checking game ownership for ${gameId}:`, error2);
80141
+ throw ApiError.internal("Internal server error", error2);
80142
+ }
80143
+ }
80144
+ async function listItems(ctx) {
80145
+ const user = ctx.user;
80146
+ if (!user) {
80147
+ throw ApiError.unauthorized("Must be logged in to view items");
80148
+ }
80149
+ try {
80150
+ const db = getDatabase();
80151
+ const url2 = new URL(ctx.request.url);
80152
+ const gameId = url2.searchParams.get("gameId");
80153
+ let allItems;
80154
+ if (gameId) {
80155
+ allItems = await db.query.items.findMany({
80156
+ where: eq(items.gameId, gameId)
80157
+ });
80158
+ } else {
80159
+ allItems = await db.query.items.findMany();
80160
+ }
80161
+ return allItems;
80162
+ } catch (error2) {
80163
+ if (error2 instanceof ApiError) {
80164
+ throw error2;
80165
+ }
80166
+ logger2.error("Error fetching items:", error2);
80167
+ throw ApiError.internal("Internal server error", error2);
80168
+ }
80169
+ }
80170
+ async function getItemById(ctx) {
80171
+ const user = ctx.user;
80172
+ const itemId = ctx.params.itemId;
80173
+ if (!user) {
80174
+ throw ApiError.unauthorized("Must be logged in to view item details");
80175
+ }
80176
+ if (!itemId) {
80177
+ throw ApiError.badRequest("Missing item ID");
80178
+ }
80179
+ try {
80180
+ const db = getDatabase();
80181
+ const item = await db.query.items.findFirst({
80182
+ where: eq(items.id, itemId)
80183
+ });
80184
+ if (!item) {
80185
+ throw ApiError.notFound("Item not found");
80186
+ }
80187
+ return item;
80188
+ } catch (error2) {
80189
+ if (error2 instanceof ApiError) {
80190
+ throw error2;
80191
+ }
80192
+ logger2.error(`Error fetching item ${itemId}:`, error2);
80193
+ throw ApiError.internal("Internal server error", error2);
80194
+ }
80195
+ }
80196
+ async function createItem(ctx) {
80197
+ const user = ctx.user;
80198
+ if (!user || user.role !== "admin") {
80199
+ throw ApiError.forbidden("Admin access required");
80200
+ }
80201
+ let inputData;
80202
+ try {
80203
+ const requestBody = await ctx.request.json();
80204
+ const validationResult = InsertItemSchema.safeParse(requestBody);
80205
+ if (!validationResult.success) {
80206
+ throw ApiError.badRequest(`Validation failed: ${formatValidationErrors(validationResult.error)}`);
80207
+ }
80208
+ inputData = validationResult.data;
80209
+ } catch (error2) {
80210
+ if (error2 instanceof ApiError) {
80211
+ throw error2;
80212
+ }
80213
+ logger2.error("Failed to parse request body or invalid JSON:", error2);
80214
+ throw ApiError.badRequest("Invalid JSON body");
80215
+ }
80216
+ try {
80217
+ const db = getDatabase();
80218
+ const [newItem] = await db.insert(items).values(inputData).returning();
80219
+ if (!newItem) {
80220
+ throw ApiError.internal("Failed to create item in database");
80221
+ }
80222
+ return newItem;
80223
+ } catch (error2) {
80224
+ if (error2 instanceof Error) {
80225
+ if (error2.message.includes("duplicate key value violates unique constraint")) {
80226
+ const gameScope = inputData.gameId ? "for this game" : "as a platform item";
80227
+ throw ApiError.conflict(`An item with slug '${inputData.slug}' already exists ${gameScope}`);
80228
+ }
80229
+ }
80230
+ logger2.error("Error creating item:", error2);
80231
+ throw ApiError.internal("Internal server error", error2);
80232
+ }
80233
+ }
80234
+ async function updateItem(ctx) {
80235
+ const user = ctx.user;
80236
+ const itemId = ctx.params.itemId;
80237
+ if (!user || user.role !== "admin") {
80238
+ throw ApiError.forbidden("Admin access required");
80239
+ }
80240
+ if (!itemId) {
80241
+ throw ApiError.badRequest("Missing item ID");
80242
+ }
80243
+ let inputData;
80244
+ try {
80245
+ const requestBody = await ctx.request.json();
80246
+ const validationResult = UpdateItemSchema.safeParse(requestBody);
80247
+ if (!validationResult.success) {
80248
+ throw ApiError.badRequest(`Validation failed: ${formatValidationErrors(validationResult.error)}`);
80249
+ }
80250
+ inputData = validationResult.data;
80251
+ } catch (error2) {
80252
+ if (error2 instanceof ApiError) {
80253
+ throw error2;
80254
+ }
80255
+ logger2.error("Failed to parse request body or invalid JSON:", error2);
80256
+ throw ApiError.badRequest("Invalid JSON body");
80257
+ }
80258
+ if (Object.keys(inputData).length === 0) {
80259
+ throw ApiError.badRequest("No update data provided");
80260
+ }
80261
+ try {
80262
+ const db = getDatabase();
80263
+ const [updatedItem] = await db.update(items).set(inputData).where(eq(items.id, itemId)).returning();
80264
+ if (!updatedItem) {
80265
+ throw ApiError.notFound("Item not found for update");
80266
+ }
80267
+ return updatedItem;
80268
+ } catch (error2) {
80269
+ if (error2 instanceof Error) {
80270
+ if (error2.message.includes("duplicate key value violates unique constraint")) {
80271
+ throw ApiError.conflict("An item with this slug already exists within the same scope (platform or game)");
80272
+ }
80273
+ }
80274
+ logger2.error(`Error updating item ${itemId}:`, error2);
80275
+ throw ApiError.internal("Internal server error", error2);
80276
+ }
80277
+ }
80278
+ async function deleteItem(ctx) {
80279
+ const user = ctx.user;
80280
+ const itemId = ctx.params.itemId;
80281
+ if (!user || user.role !== "admin") {
80282
+ throw ApiError.forbidden("Admin access required");
80283
+ }
80284
+ if (!itemId) {
80285
+ throw ApiError.badRequest("Missing item ID");
80286
+ }
80287
+ try {
80288
+ const db = getDatabase();
80289
+ const result = await db.delete(items).where(eq(items.id, itemId)).returning({ id: items.id });
80290
+ if (result.length === 0) {
80291
+ throw ApiError.notFound("Item not found for deletion");
80292
+ }
80293
+ } catch (error2) {
80294
+ if (error2 instanceof ApiError) {
80295
+ throw error2;
80296
+ }
80297
+ logger2.error(`Error deleting item ${itemId}:`, error2);
80298
+ throw ApiError.internal("Internal server error", error2);
80299
+ }
80300
+ }
80301
+ async function resolveItem(ctx) {
80302
+ const user = ctx.user;
80303
+ const url2 = new URL(ctx.request.url);
80304
+ const slug = url2.searchParams.get("slug");
80305
+ const gameId = url2.searchParams.get("gameId");
80306
+ if (!user) {
80307
+ throw ApiError.unauthorized("Must be logged in to resolve items");
80308
+ }
80309
+ if (!slug) {
80310
+ throw ApiError.badRequest("Missing slug parameter");
80311
+ }
80312
+ try {
80313
+ const db = getDatabase();
80314
+ if (gameId) {
80315
+ const gameItem = await db.query.items.findFirst({
80316
+ where: and(eq(items.slug, slug), eq(items.gameId, gameId))
80317
+ });
80318
+ if (gameItem) {
80319
+ return gameItem;
80320
+ }
80321
+ }
80322
+ const platformItem = await db.query.items.findFirst({
80323
+ where: and(eq(items.slug, slug), isNull(items.gameId))
80324
+ });
80325
+ if (!platformItem) {
80326
+ throw ApiError.notFound(`Item with slug '${slug}' not found`);
80327
+ }
80328
+ return platformItem;
80329
+ } catch (error2) {
80330
+ if (error2 instanceof ApiError) {
80331
+ throw error2;
80332
+ }
80333
+ logger2.error(`Error resolving item slug ${slug}:`, error2);
80334
+ throw ApiError.internal("Internal server error", error2);
80335
+ }
80336
+ }
80337
+ async function listGameItems(ctx) {
80338
+ const user = ctx.user;
80339
+ const gameId = ctx.params.gameId;
80340
+ if (!user) {
80341
+ throw ApiError.unauthorized("Must be logged in to view items");
80342
+ }
80343
+ if (!gameId) {
80344
+ throw ApiError.badRequest("Missing game ID");
80345
+ }
80346
+ try {
80347
+ const db = getDatabase();
80348
+ const gameItems = await db.query.items.findMany({
80349
+ where: eq(items.gameId, gameId)
80350
+ });
80351
+ return gameItems;
80352
+ } catch (error2) {
80353
+ if (error2 instanceof ApiError) {
80354
+ throw error2;
80355
+ }
80356
+ logger2.error(`Error fetching items for game ${gameId}:`, error2);
80357
+ throw ApiError.internal("Internal server error", error2);
80358
+ }
80359
+ }
80360
+ async function createGameItem(ctx) {
80361
+ const user = ctx.user;
80362
+ const gameId = ctx.params.gameId;
80363
+ if (!user) {
80364
+ throw ApiError.unauthorized("Must be logged in to create items");
80365
+ }
80366
+ if (!gameId) {
80367
+ throw ApiError.badRequest("Missing game ID");
80368
+ }
80369
+ await validateGameOwnership(user, gameId);
80370
+ let inputData;
80371
+ try {
80372
+ const requestBody = await ctx.request.json();
80373
+ const bodyData = typeof requestBody === "object" && requestBody !== null ? requestBody : {};
80374
+ const validationResult = InsertItemSchema.safeParse({
80375
+ ...bodyData,
80376
+ gameId
80377
+ });
80378
+ if (!validationResult.success) {
80379
+ throw ApiError.badRequest(`Validation failed: ${formatValidationErrors(validationResult.error)}`);
80380
+ }
80381
+ inputData = validationResult.data;
80382
+ } catch (error2) {
80383
+ if (error2 instanceof ApiError) {
80384
+ throw error2;
80385
+ }
80386
+ logger2.error("Failed to parse request body or invalid JSON:", error2);
80387
+ throw ApiError.badRequest("Invalid JSON body");
80388
+ }
80389
+ try {
80390
+ const db = getDatabase();
80391
+ const [newItem] = await db.insert(items).values(inputData).returning();
80392
+ if (!newItem) {
80393
+ throw ApiError.internal("Failed to create item in database");
80394
+ }
80395
+ return newItem;
80396
+ } catch (error2) {
80397
+ if (error2 instanceof Error) {
80398
+ if (error2.message.includes("duplicate key value violates unique constraint")) {
80399
+ throw ApiError.conflict(`An item with slug '${inputData.slug}' already exists for this game`);
80400
+ }
80401
+ }
80402
+ logger2.error("Error creating game item:", error2);
80403
+ throw ApiError.internal("Internal server error", error2);
80404
+ }
80405
+ }
80406
+ async function updateGameItem(ctx) {
80407
+ const user = ctx.user;
80408
+ const gameId = ctx.params.gameId;
80409
+ const itemId = ctx.params.itemId;
80410
+ if (!user) {
80411
+ throw ApiError.unauthorized("Must be logged in to update items");
80412
+ }
80413
+ if (!gameId || !itemId) {
80414
+ throw ApiError.badRequest("Missing game ID or item ID");
80415
+ }
80416
+ await validateGameOwnership(user, gameId);
80417
+ let inputData;
80418
+ try {
80419
+ const requestBody = await ctx.request.json();
80420
+ const validationResult = UpdateItemSchema.safeParse(requestBody);
80421
+ if (!validationResult.success) {
80422
+ throw ApiError.badRequest(`Validation failed: ${formatValidationErrors(validationResult.error)}`);
80423
+ }
80424
+ inputData = validationResult.data;
80425
+ } catch (error2) {
80426
+ if (error2 instanceof ApiError) {
80427
+ throw error2;
80428
+ }
80429
+ logger2.error("Failed to parse request body or invalid JSON:", error2);
80430
+ throw ApiError.badRequest("Invalid JSON body");
80431
+ }
80432
+ if (Object.keys(inputData).length === 0) {
80433
+ throw ApiError.badRequest("No update data provided");
80434
+ }
80435
+ try {
80436
+ const db = getDatabase();
80437
+ const existingItem = await db.query.items.findFirst({
80438
+ where: and(eq(items.id, itemId), eq(items.gameId, gameId))
80439
+ });
80440
+ if (!existingItem) {
80441
+ throw ApiError.notFound("Item not found for this game");
80442
+ }
80443
+ const [updatedItem] = await db.update(items).set(inputData).where(eq(items.id, itemId)).returning();
80444
+ if (!updatedItem) {
80445
+ throw ApiError.notFound("Item not found for update");
80446
+ }
80447
+ return updatedItem;
80448
+ } catch (error2) {
80449
+ if (error2 instanceof Error) {
80450
+ if (error2.message.includes("duplicate key value violates unique constraint")) {
80451
+ throw ApiError.conflict("An item with this slug already exists for this game");
80452
+ }
80453
+ }
80454
+ logger2.error(`Error updating game item ${itemId}:`, error2);
80455
+ throw ApiError.internal("Internal server error", error2);
80456
+ }
80457
+ }
80458
+ async function deleteGameItem(ctx) {
80459
+ const user = ctx.user;
80460
+ const gameId = ctx.params.gameId;
80461
+ const itemId = ctx.params.itemId;
80462
+ if (!user) {
80463
+ throw ApiError.unauthorized("Must be logged in to delete items");
80464
+ }
80465
+ if (!gameId || !itemId) {
80466
+ throw ApiError.badRequest("Missing game ID or item ID");
80467
+ }
80468
+ await validateGameOwnership(user, gameId);
80469
+ try {
80470
+ const db = getDatabase();
80471
+ const result = await db.delete(items).where(and(eq(items.id, itemId), eq(items.gameId, gameId))).returning({ id: items.id });
80472
+ if (result.length === 0) {
80473
+ throw ApiError.notFound("Item not found for this game");
80474
+ }
80475
+ } catch (error2) {
80476
+ if (error2 instanceof ApiError) {
80477
+ throw error2;
80478
+ }
80479
+ logger2.error(`Error deleting game item ${itemId}:`, error2);
80480
+ throw ApiError.internal("Internal server error", error2);
80481
+ }
80482
+ }
80089
80483
  // ../../node_modules/@oslojs/binary/dist/uint.js
80090
80484
  class BigEndian {
80091
80485
  uint8(data2, offset) {
@@ -81165,6 +81559,88 @@ gamesRouter.post("/uploads/finalize", async (c2) => {
81165
81559
  return c2.json({ error: message2 }, 500);
81166
81560
  }
81167
81561
  });
81562
+ gamesRouter.get("/:gameId/items", async (c2) => {
81563
+ const gameId = c2.req.param("gameId");
81564
+ const ctx = {
81565
+ user: c2.get("user"),
81566
+ params: { gameId },
81567
+ url: new URL(c2.req.url),
81568
+ request: c2.req.raw
81569
+ };
81570
+ try {
81571
+ const result = await listGameItems(ctx);
81572
+ return c2.json(result);
81573
+ } catch (error2) {
81574
+ if (error2 instanceof ApiError) {
81575
+ return c2.json({ error: error2.message }, error2.statusCode);
81576
+ }
81577
+ console.error("Error in listGameItems:", error2);
81578
+ const message2 = error2 instanceof Error ? error2.message : "Internal server error";
81579
+ return c2.json({ error: message2 }, 500);
81580
+ }
81581
+ });
81582
+ gamesRouter.post("/:gameId/items", async (c2) => {
81583
+ const gameId = c2.req.param("gameId");
81584
+ const ctx = {
81585
+ user: c2.get("user"),
81586
+ params: { gameId },
81587
+ url: new URL(c2.req.url),
81588
+ request: c2.req.raw
81589
+ };
81590
+ try {
81591
+ const result = await createGameItem(ctx);
81592
+ return c2.json(result, 201);
81593
+ } catch (error2) {
81594
+ if (error2 instanceof ApiError) {
81595
+ return c2.json({ error: error2.message }, error2.statusCode);
81596
+ }
81597
+ console.error("Error in createGameItem:", error2);
81598
+ const message2 = error2 instanceof Error ? error2.message : "Internal server error";
81599
+ return c2.json({ error: message2 }, 500);
81600
+ }
81601
+ });
81602
+ gamesRouter.patch("/:gameId/items/:itemId", async (c2) => {
81603
+ const gameId = c2.req.param("gameId");
81604
+ const itemId = c2.req.param("itemId");
81605
+ const ctx = {
81606
+ user: c2.get("user"),
81607
+ params: { gameId, itemId },
81608
+ url: new URL(c2.req.url),
81609
+ request: c2.req.raw
81610
+ };
81611
+ try {
81612
+ const result = await updateGameItem(ctx);
81613
+ return c2.json(result);
81614
+ } catch (error2) {
81615
+ if (error2 instanceof ApiError) {
81616
+ return c2.json({ error: error2.message }, error2.statusCode);
81617
+ }
81618
+ console.error("Error in updateGameItem:", error2);
81619
+ const message2 = error2 instanceof Error ? error2.message : "Internal server error";
81620
+ return c2.json({ error: message2 }, 500);
81621
+ }
81622
+ });
81623
+ gamesRouter.delete("/:gameId/items/:itemId", async (c2) => {
81624
+ const gameId = c2.req.param("gameId");
81625
+ const itemId = c2.req.param("itemId");
81626
+ const ctx = {
81627
+ user: c2.get("user"),
81628
+ params: { gameId, itemId },
81629
+ url: new URL(c2.req.url),
81630
+ request: c2.req.raw
81631
+ };
81632
+ try {
81633
+ await deleteGameItem(ctx);
81634
+ return c2.body(null, 204);
81635
+ } catch (error2) {
81636
+ if (error2 instanceof ApiError) {
81637
+ return c2.json({ error: error2.message }, error2.statusCode);
81638
+ }
81639
+ console.error("Error in deleteGameItem:", error2);
81640
+ const message2 = error2 instanceof Error ? error2.message : "Internal server error";
81641
+ return c2.json({ error: message2 }, 500);
81642
+ }
81643
+ });
81168
81644
  // src/routes/manifest.ts
81169
81645
  var manifestRouter = new Hono2;
81170
81646
  manifestRouter.get("/", async (c2) => {
@@ -81179,7 +81655,7 @@ manifestRouter.get("/", async (c2) => {
81179
81655
  { method: "GET", url: `${baseUrl}/api/users/me` },
81180
81656
  { method: "GET", url: `${baseUrl}/api/inventory` },
81181
81657
  { method: "POST", url: `${baseUrl}/api/inventory/add` },
81182
- { method: "POST", url: `${baseUrl}/api/inventory/spend` },
81658
+ { method: "POST", url: `${baseUrl}/api/inventory/remove` },
81183
81659
  { method: "POST", url: `${baseUrl}/api/games/:gameId/sessions` },
81184
81660
  {
81185
81661
  method: "POST",
@@ -81296,157 +81772,27 @@ shopRouter.get("/view", async (c2) => {
81296
81772
  return c2.json({ error: message2 }, 500);
81297
81773
  }
81298
81774
  });
81299
- // ../api-core/src/items/index.ts
81300
- async function listItems(ctx) {
81301
- const user = ctx.user;
81302
- if (!user) {
81303
- throw ApiError.unauthorized("Must be logged in to view items");
81304
- }
81305
- try {
81306
- const db = getDatabase();
81307
- const allItems = await db.query.items.findMany();
81308
- return allItems;
81309
- } catch (error2) {
81310
- if (error2 instanceof ApiError) {
81311
- throw error2;
81312
- }
81313
- logger2.error("Error fetching items:", error2);
81314
- throw ApiError.internal("Internal server error", error2);
81315
- }
81316
- }
81317
- async function getItemById(ctx) {
81318
- const user = ctx.user;
81319
- const itemId = ctx.params.itemId;
81320
- if (!user) {
81321
- throw ApiError.unauthorized("Must be logged in to view item details");
81322
- }
81323
- if (!itemId) {
81324
- throw ApiError.badRequest("Missing item ID");
81325
- }
81326
- try {
81327
- const db = getDatabase();
81328
- const item = await db.query.items.findFirst({
81329
- where: eq(items.id, itemId)
81330
- });
81331
- if (!item) {
81332
- throw ApiError.notFound("Item not found");
81333
- }
81334
- return item;
81335
- } catch (error2) {
81336
- if (error2 instanceof ApiError) {
81337
- throw error2;
81338
- }
81339
- logger2.error(`Error fetching item ${itemId}:`, error2);
81340
- throw ApiError.internal("Internal server error", error2);
81341
- }
81342
- }
81343
- async function createItem(ctx) {
81344
- const user = ctx.user;
81345
- if (!user || user.role !== "admin") {
81346
- throw ApiError.forbidden("Admin access required");
81347
- }
81348
- let inputData;
81349
- try {
81350
- const requestBody = await ctx.request.json();
81351
- const validationResult = InsertItemSchema.safeParse(requestBody);
81352
- if (!validationResult.success) {
81353
- throw ApiError.badRequest(`Validation failed: ${formatValidationErrors(validationResult.error)}`);
81354
- }
81355
- inputData = validationResult.data;
81356
- } catch (error2) {
81357
- if (error2 instanceof ApiError) {
81358
- throw error2;
81359
- }
81360
- logger2.error("Failed to parse request body or invalid JSON:", error2);
81361
- throw ApiError.badRequest("Invalid JSON body");
81362
- }
81363
- try {
81364
- const db = getDatabase();
81365
- const [newItem] = await db.insert(items).values(inputData).returning();
81366
- if (!newItem) {
81367
- throw ApiError.internal("Failed to create item in database");
81368
- }
81369
- return newItem;
81370
- } catch (error2) {
81371
- if (error2 instanceof Error) {
81372
- if (error2.message.includes("duplicate key value violates unique constraint")) {
81373
- throw ApiError.conflict("An item with this internal name already exists");
81374
- }
81375
- }
81376
- logger2.error("Error creating item:", error2);
81377
- throw ApiError.internal("Internal server error", error2);
81378
- }
81379
- }
81380
- async function updateItem(ctx) {
81381
- const user = ctx.user;
81382
- const itemId = ctx.params.itemId;
81383
- if (!user || user.role !== "admin") {
81384
- throw ApiError.forbidden("Admin access required");
81385
- }
81386
- if (!itemId) {
81387
- throw ApiError.badRequest("Missing item ID");
81388
- }
81389
- let inputData;
81390
- try {
81391
- const requestBody = await ctx.request.json();
81392
- const validationResult = UpdateItemSchema.safeParse(requestBody);
81393
- if (!validationResult.success) {
81394
- throw ApiError.badRequest(`Validation failed: ${formatValidationErrors(validationResult.error)}`);
81395
- }
81396
- inputData = validationResult.data;
81397
- } catch (error2) {
81398
- if (error2 instanceof ApiError) {
81399
- throw error2;
81400
- }
81401
- logger2.error("Failed to parse request body or invalid JSON:", error2);
81402
- throw ApiError.badRequest("Invalid JSON body");
81403
- }
81404
- if (Object.keys(inputData).length === 0) {
81405
- throw ApiError.badRequest("No update data provided");
81406
- }
81407
- try {
81408
- const db = getDatabase();
81409
- const [updatedItem] = await db.update(items).set(inputData).where(eq(items.id, itemId)).returning();
81410
- if (!updatedItem) {
81411
- throw ApiError.notFound("Item not found for update");
81412
- }
81413
- return updatedItem;
81414
- } catch (error2) {
81415
- if (error2 instanceof Error) {
81416
- if (error2.message.includes("duplicate key value violates unique constraint")) {
81417
- throw ApiError.conflict("An item with this internal name already exists");
81418
- }
81419
- }
81420
- logger2.error(`Error updating item ${itemId}:`, error2);
81421
- throw ApiError.internal("Internal server error", error2);
81422
- }
81423
- }
81424
- async function deleteItem(ctx) {
81425
- const user = ctx.user;
81426
- const itemId = ctx.params.itemId;
81427
- if (!user || user.role !== "admin") {
81428
- throw ApiError.forbidden("Admin access required");
81429
- }
81430
- if (!itemId) {
81431
- throw ApiError.badRequest("Missing item ID");
81432
- }
81775
+ // src/routes/items.ts
81776
+ var itemsRouter = new Hono2;
81777
+ itemsRouter.get("/resolve", async (c2) => {
81778
+ const ctx = {
81779
+ user: c2.get("user"),
81780
+ params: {},
81781
+ url: new URL(c2.req.url),
81782
+ request: c2.req.raw
81783
+ };
81433
81784
  try {
81434
- const db = getDatabase();
81435
- const result = await db.delete(items).where(eq(items.id, itemId)).returning({ id: items.id });
81436
- if (result.length === 0) {
81437
- throw ApiError.notFound("Item not found for deletion");
81438
- }
81785
+ const result = await resolveItem(ctx);
81786
+ return c2.json(result);
81439
81787
  } catch (error2) {
81440
81788
  if (error2 instanceof ApiError) {
81441
- throw error2;
81789
+ return c2.json({ error: error2.message }, error2.statusCode);
81442
81790
  }
81443
- logger2.error(`Error deleting item ${itemId}:`, error2);
81444
- throw ApiError.internal("Internal server error", error2);
81791
+ console.error("Error in resolveItem:", error2);
81792
+ const message2 = error2 instanceof Error ? error2.message : "Internal server error";
81793
+ return c2.json({ error: message2 }, 500);
81445
81794
  }
81446
- }
81447
-
81448
- // src/routes/items.ts
81449
- var itemsRouter = new Hono2;
81795
+ });
81450
81796
  itemsRouter.post("/", async (c2) => {
81451
81797
  const ctx = {
81452
81798
  user: c2.get("user"),
@@ -81619,8 +81965,8 @@ async function createCurrency(ctx) {
81619
81965
  } catch (error2) {
81620
81966
  if (error2 instanceof Error) {
81621
81967
  if (error2.message.includes("duplicate key value violates unique constraint") || error2.message.includes("UNIQUE constraint failed")) {
81622
- if (error2.message.includes("currencies_internal_name_unique")) {
81623
- throw ApiError.conflict("A currency with this internal name already exists");
81968
+ if (error2.message.includes("currencies_slug_unique")) {
81969
+ throw ApiError.conflict("A currency with this slug already exists");
81624
81970
  }
81625
81971
  throw ApiError.conflict("A similar currency already exists");
81626
81972
  }
@@ -81666,8 +82012,8 @@ async function updateCurrency(ctx) {
81666
82012
  } catch (error2) {
81667
82013
  if (error2 instanceof Error) {
81668
82014
  if (error2.message.includes("duplicate key value violates unique constraint") || error2.message.includes("UNIQUE constraint failed")) {
81669
- if (error2.message.includes("currencies_internal_name_unique")) {
81670
- throw ApiError.conflict("A currency with this internal name already exists");
82015
+ if (error2.message.includes("currencies_slug_unique")) {
82016
+ throw ApiError.conflict("A currency with this slug already exists");
81671
82017
  }
81672
82018
  throw ApiError.conflict("A similar currency already exists");
81673
82019
  }