@playcademy/sandbox 0.1.0-beta.7 → 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.
- package/dist/cli.js +630 -285
- package/dist/server.js +631 -286
- package/package.json +1 -1
package/dist/server.js
CHANGED
|
@@ -50943,7 +50943,8 @@ var PLAYCADEMY_CREDITS_ID = crypto.randomUUID();
|
|
|
50943
50943
|
var SAMPLE_ITEMS = [
|
|
50944
50944
|
{
|
|
50945
50945
|
id: PLAYCADEMY_CREDITS_ID,
|
|
50946
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
75836
|
+
version: "0.1.0-beta.7",
|
|
75816
75837
|
description: "Local development server for Playcademy game development",
|
|
75817
75838
|
type: "module",
|
|
75818
75839
|
exports: {
|
|
@@ -75917,7 +75938,7 @@ async function getUserMe(ctx) {
|
|
|
75917
75938
|
}
|
|
75918
75939
|
|
|
75919
75940
|
// ../data/src/constants.ts
|
|
75920
|
-
var
|
|
75941
|
+
var ITEM_SLUGS = {
|
|
75921
75942
|
PLAYCADEMY_CREDITS: "PLAYCADEMY_CREDITS",
|
|
75922
75943
|
PLAYCADEMY_XP: "PLAYCADEMY_XP",
|
|
75923
75944
|
FOUNDING_MEMBER_BADGE: "FOUNDING_MEMBER_BADGE",
|
|
@@ -75928,14 +75949,15 @@ var ITEM_INTERNAL_NAMES = {
|
|
|
75928
75949
|
SMALL_BACKPACK: "SMALL_BACKPACK"
|
|
75929
75950
|
};
|
|
75930
75951
|
var CURRENCIES = {
|
|
75931
|
-
PRIMARY:
|
|
75932
|
-
XP:
|
|
75952
|
+
PRIMARY: ITEM_SLUGS.PLAYCADEMY_CREDITS,
|
|
75953
|
+
XP: ITEM_SLUGS.PLAYCADEMY_XP
|
|
75933
75954
|
};
|
|
75934
75955
|
var BADGES = {
|
|
75935
|
-
FOUNDING_MEMBER:
|
|
75936
|
-
EARLY_ADOPTER:
|
|
75937
|
-
FIRST_GAME:
|
|
75956
|
+
FOUNDING_MEMBER: ITEM_SLUGS.FOUNDING_MEMBER_BADGE,
|
|
75957
|
+
EARLY_ADOPTER: ITEM_SLUGS.EARLY_ADOPTER_BADGE,
|
|
75958
|
+
FIRST_GAME: ITEM_SLUGS.FIRST_GAME_BADGE
|
|
75938
75959
|
};
|
|
75960
|
+
var INTERACTION_TYPE = Object.fromEntries(interactionTypeEnum.enumValues.map((value) => [value, value]));
|
|
75939
75961
|
|
|
75940
75962
|
// ../api-core/src/utils/levels.ts
|
|
75941
75963
|
var levelConfigCache = null;
|
|
@@ -76092,7 +76114,7 @@ async function addXP(ctx, amount) {
|
|
|
76092
76114
|
creditsAwarded: creditsToAward
|
|
76093
76115
|
});
|
|
76094
76116
|
if (creditsToAward > 0) {
|
|
76095
|
-
const [creditsItem] = await tx.select({ id: items.id }).from(items).where(eq(items.
|
|
76117
|
+
const [creditsItem] = await tx.select({ id: items.id }).from(items).where(eq(items.slug, CURRENCIES.PRIMARY)).limit(1);
|
|
76096
76118
|
if (!creditsItem) {
|
|
76097
76119
|
throw ApiError.internal(`${CURRENCIES.PRIMARY} item not found`);
|
|
76098
76120
|
}
|
|
@@ -76292,15 +76314,18 @@ async function getUserInventory(ctx) {
|
|
|
76292
76314
|
const db = getDatabase();
|
|
76293
76315
|
const inventory2 = await db.select({
|
|
76294
76316
|
id: inventoryItems.id,
|
|
76317
|
+
userId: inventoryItems.userId,
|
|
76295
76318
|
quantity: inventoryItems.quantity,
|
|
76296
76319
|
item: {
|
|
76297
76320
|
id: items.id,
|
|
76298
|
-
|
|
76321
|
+
slug: items.slug,
|
|
76322
|
+
gameId: items.gameId,
|
|
76299
76323
|
displayName: items.displayName,
|
|
76300
76324
|
description: items.description,
|
|
76301
76325
|
type: items.type,
|
|
76302
76326
|
imageUrl: items.imageUrl,
|
|
76303
|
-
metadata: items.metadata
|
|
76327
|
+
metadata: items.metadata,
|
|
76328
|
+
createdAt: items.createdAt
|
|
76304
76329
|
},
|
|
76305
76330
|
updatedAt: inventoryItems.updatedAt
|
|
76306
76331
|
}).from(inventoryItems).where(eq(inventoryItems.userId, user.id)).innerJoin(items, eq(inventoryItems.itemId, items.id));
|
|
@@ -77929,14 +77954,13 @@ async function listGames(ctx) {
|
|
|
77929
77954
|
if (!user) {
|
|
77930
77955
|
throw ApiError.unauthorized("Must be logged in to list games");
|
|
77931
77956
|
}
|
|
77932
|
-
if (user.role !== "admin" && user.role !== "developer") {
|
|
77933
|
-
throw ApiError.forbidden("Requires admin or developer privileges to list games");
|
|
77934
|
-
}
|
|
77935
77957
|
try {
|
|
77936
77958
|
const db = getDatabase();
|
|
77937
77959
|
let filter2 = undefined;
|
|
77938
77960
|
if (user.role === "developer") {
|
|
77939
77961
|
filter2 = eq(games.developerId, user.id);
|
|
77962
|
+
} else if (user.role === "player") {
|
|
77963
|
+
return [];
|
|
77940
77964
|
}
|
|
77941
77965
|
const games2 = await db.query.games.findMany({
|
|
77942
77966
|
where: filter2,
|
|
@@ -78177,147 +78201,516 @@ async function upsertGameBySlug(ctx) {
|
|
|
78177
78201
|
throw ApiError.internal("Internal server error", error2);
|
|
78178
78202
|
}
|
|
78179
78203
|
}
|
|
78180
|
-
|
|
78181
|
-
|
|
78182
|
-
|
|
78183
|
-
|
|
78184
|
-
|
|
78185
|
-
}
|
|
78186
|
-
return data2[offset];
|
|
78187
|
-
}
|
|
78188
|
-
uint16(data2, offset) {
|
|
78189
|
-
if (data2.byteLength < offset + 2) {
|
|
78190
|
-
throw new TypeError("Insufficient bytes");
|
|
78191
|
-
}
|
|
78192
|
-
return data2[offset] << 8 | data2[offset + 1];
|
|
78204
|
+
|
|
78205
|
+
// ../api-core/src/items/index.ts
|
|
78206
|
+
async function validateGameOwnership(user, gameId) {
|
|
78207
|
+
if (user.role === "admin") {
|
|
78208
|
+
return;
|
|
78193
78209
|
}
|
|
78194
|
-
|
|
78195
|
-
|
|
78196
|
-
|
|
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");
|
|
78197
78225
|
}
|
|
78198
|
-
|
|
78199
|
-
|
|
78200
|
-
|
|
78226
|
+
} catch (error2) {
|
|
78227
|
+
if (error2 instanceof ApiError) {
|
|
78228
|
+
throw error2;
|
|
78201
78229
|
}
|
|
78202
|
-
|
|
78230
|
+
logger2.error(`Error checking game ownership for ${gameId}:`, error2);
|
|
78231
|
+
throw ApiError.internal("Internal server error", error2);
|
|
78203
78232
|
}
|
|
78204
|
-
|
|
78205
|
-
|
|
78206
|
-
|
|
78207
|
-
|
|
78208
|
-
|
|
78209
|
-
for (let i3 = 0;i3 < 8; i3++) {
|
|
78210
|
-
result |= BigInt(data2[offset + i3]) << BigInt(56 - i3 * 8);
|
|
78211
|
-
}
|
|
78212
|
-
return result;
|
|
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");
|
|
78213
78238
|
}
|
|
78214
|
-
|
|
78215
|
-
|
|
78216
|
-
|
|
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();
|
|
78217
78250
|
}
|
|
78218
|
-
|
|
78219
|
-
|
|
78251
|
+
return allItems;
|
|
78252
|
+
} catch (error2) {
|
|
78253
|
+
if (error2 instanceof ApiError) {
|
|
78254
|
+
throw error2;
|
|
78220
78255
|
}
|
|
78221
|
-
|
|
78256
|
+
logger2.error("Error fetching items:", error2);
|
|
78257
|
+
throw ApiError.internal("Internal server error", error2);
|
|
78222
78258
|
}
|
|
78223
|
-
|
|
78224
|
-
|
|
78225
|
-
|
|
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");
|
|
78226
78276
|
}
|
|
78227
|
-
|
|
78228
|
-
|
|
78277
|
+
return item;
|
|
78278
|
+
} catch (error2) {
|
|
78279
|
+
if (error2 instanceof ApiError) {
|
|
78280
|
+
throw error2;
|
|
78229
78281
|
}
|
|
78230
|
-
|
|
78231
|
-
|
|
78282
|
+
logger2.error(`Error fetching item ${itemId}:`, error2);
|
|
78283
|
+
throw ApiError.internal("Internal server error", error2);
|
|
78232
78284
|
}
|
|
78233
|
-
|
|
78234
|
-
|
|
78235
|
-
|
|
78236
|
-
|
|
78237
|
-
|
|
78238
|
-
|
|
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)}`);
|
|
78239
78297
|
}
|
|
78240
|
-
|
|
78241
|
-
|
|
78298
|
+
inputData = validationResult.data;
|
|
78299
|
+
} catch (error2) {
|
|
78300
|
+
if (error2 instanceof ApiError) {
|
|
78301
|
+
throw error2;
|
|
78242
78302
|
}
|
|
78303
|
+
logger2.error("Failed to parse request body or invalid JSON:", error2);
|
|
78304
|
+
throw ApiError.badRequest("Invalid JSON body");
|
|
78243
78305
|
}
|
|
78244
|
-
|
|
78245
|
-
|
|
78246
|
-
|
|
78247
|
-
|
|
78248
|
-
|
|
78249
|
-
throw new TypeError("Invalid uint64 value");
|
|
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");
|
|
78250
78311
|
}
|
|
78251
|
-
|
|
78252
|
-
|
|
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
|
+
}
|
|
78253
78319
|
}
|
|
78320
|
+
logger2.error("Error creating item:", error2);
|
|
78321
|
+
throw ApiError.internal("Internal server error", error2);
|
|
78254
78322
|
}
|
|
78255
78323
|
}
|
|
78256
|
-
|
|
78257
|
-
|
|
78258
|
-
|
|
78259
|
-
|
|
78260
|
-
|
|
78261
|
-
}
|
|
78262
|
-
return data2[offset];
|
|
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");
|
|
78263
78329
|
}
|
|
78264
|
-
|
|
78265
|
-
|
|
78266
|
-
throw new TypeError("Insufficient bytes");
|
|
78267
|
-
}
|
|
78268
|
-
return data2[offset] | data2[offset + 1] << 8;
|
|
78330
|
+
if (!itemId) {
|
|
78331
|
+
throw ApiError.badRequest("Missing item ID");
|
|
78269
78332
|
}
|
|
78270
|
-
|
|
78271
|
-
|
|
78272
|
-
|
|
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)}`);
|
|
78273
78339
|
}
|
|
78274
|
-
|
|
78275
|
-
|
|
78276
|
-
|
|
78340
|
+
inputData = validationResult.data;
|
|
78341
|
+
} catch (error2) {
|
|
78342
|
+
if (error2 instanceof ApiError) {
|
|
78343
|
+
throw error2;
|
|
78277
78344
|
}
|
|
78278
|
-
|
|
78345
|
+
logger2.error("Failed to parse request body or invalid JSON:", error2);
|
|
78346
|
+
throw ApiError.badRequest("Invalid JSON body");
|
|
78279
78347
|
}
|
|
78280
|
-
|
|
78281
|
-
|
|
78282
|
-
throw new TypeError("Insufficient bytes");
|
|
78283
|
-
}
|
|
78284
|
-
let result = 0n;
|
|
78285
|
-
for (let i3 = 0;i3 < 8; i3++) {
|
|
78286
|
-
result |= BigInt(data2[offset + i3]) << BigInt(i3 * 8);
|
|
78287
|
-
}
|
|
78288
|
-
return result;
|
|
78348
|
+
if (Object.keys(inputData).length === 0) {
|
|
78349
|
+
throw ApiError.badRequest("No update data provided");
|
|
78289
78350
|
}
|
|
78290
|
-
|
|
78291
|
-
|
|
78292
|
-
|
|
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");
|
|
78293
78356
|
}
|
|
78294
|
-
|
|
78295
|
-
|
|
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
|
+
}
|
|
78296
78363
|
}
|
|
78297
|
-
|
|
78364
|
+
logger2.error(`Error updating item ${itemId}:`, error2);
|
|
78365
|
+
throw ApiError.internal("Internal server error", error2);
|
|
78298
78366
|
}
|
|
78299
|
-
|
|
78300
|
-
|
|
78301
|
-
|
|
78302
|
-
|
|
78303
|
-
|
|
78304
|
-
|
|
78305
|
-
}
|
|
78306
|
-
target[offset + 1] = value >> 8;
|
|
78307
|
-
target[offset] = value & 255;
|
|
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");
|
|
78308
78373
|
}
|
|
78309
|
-
|
|
78310
|
-
|
|
78311
|
-
|
|
78312
|
-
|
|
78313
|
-
|
|
78314
|
-
|
|
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");
|
|
78315
78382
|
}
|
|
78316
|
-
|
|
78317
|
-
|
|
78383
|
+
} catch (error2) {
|
|
78384
|
+
if (error2 instanceof ApiError) {
|
|
78385
|
+
throw error2;
|
|
78318
78386
|
}
|
|
78387
|
+
logger2.error(`Error deleting item ${itemId}:`, error2);
|
|
78388
|
+
throw ApiError.internal("Internal server error", error2);
|
|
78319
78389
|
}
|
|
78320
|
-
|
|
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
|
+
}
|
|
78573
|
+
// ../../node_modules/@oslojs/binary/dist/uint.js
|
|
78574
|
+
class BigEndian {
|
|
78575
|
+
uint8(data2, offset) {
|
|
78576
|
+
if (data2.byteLength < offset + 1) {
|
|
78577
|
+
throw new TypeError("Insufficient bytes");
|
|
78578
|
+
}
|
|
78579
|
+
return data2[offset];
|
|
78580
|
+
}
|
|
78581
|
+
uint16(data2, offset) {
|
|
78582
|
+
if (data2.byteLength < offset + 2) {
|
|
78583
|
+
throw new TypeError("Insufficient bytes");
|
|
78584
|
+
}
|
|
78585
|
+
return data2[offset] << 8 | data2[offset + 1];
|
|
78586
|
+
}
|
|
78587
|
+
uint32(data2, offset) {
|
|
78588
|
+
if (data2.byteLength < offset + 4) {
|
|
78589
|
+
throw new TypeError("Insufficient bytes");
|
|
78590
|
+
}
|
|
78591
|
+
let result = 0;
|
|
78592
|
+
for (let i3 = 0;i3 < 4; i3++) {
|
|
78593
|
+
result |= data2[offset + i3] << 24 - i3 * 8;
|
|
78594
|
+
}
|
|
78595
|
+
return result;
|
|
78596
|
+
}
|
|
78597
|
+
uint64(data2, offset) {
|
|
78598
|
+
if (data2.byteLength < offset + 8) {
|
|
78599
|
+
throw new TypeError("Insufficient bytes");
|
|
78600
|
+
}
|
|
78601
|
+
let result = 0n;
|
|
78602
|
+
for (let i3 = 0;i3 < 8; i3++) {
|
|
78603
|
+
result |= BigInt(data2[offset + i3]) << BigInt(56 - i3 * 8);
|
|
78604
|
+
}
|
|
78605
|
+
return result;
|
|
78606
|
+
}
|
|
78607
|
+
putUint8(target, value, offset) {
|
|
78608
|
+
if (target.length < offset + 1) {
|
|
78609
|
+
throw new TypeError("Not enough space");
|
|
78610
|
+
}
|
|
78611
|
+
if (value < 0 || value > 255) {
|
|
78612
|
+
throw new TypeError("Invalid uint8 value");
|
|
78613
|
+
}
|
|
78614
|
+
target[offset] = value;
|
|
78615
|
+
}
|
|
78616
|
+
putUint16(target, value, offset) {
|
|
78617
|
+
if (target.length < offset + 2) {
|
|
78618
|
+
throw new TypeError("Not enough space");
|
|
78619
|
+
}
|
|
78620
|
+
if (value < 0 || value > 65535) {
|
|
78621
|
+
throw new TypeError("Invalid uint16 value");
|
|
78622
|
+
}
|
|
78623
|
+
target[offset] = value >> 8;
|
|
78624
|
+
target[offset + 1] = value & 255;
|
|
78625
|
+
}
|
|
78626
|
+
putUint32(target, value, offset) {
|
|
78627
|
+
if (target.length < offset + 4) {
|
|
78628
|
+
throw new TypeError("Not enough space");
|
|
78629
|
+
}
|
|
78630
|
+
if (value < 0 || value > 4294967295) {
|
|
78631
|
+
throw new TypeError("Invalid uint32 value");
|
|
78632
|
+
}
|
|
78633
|
+
for (let i3 = 0;i3 < 4; i3++) {
|
|
78634
|
+
target[offset + i3] = value >> (3 - i3) * 8 & 255;
|
|
78635
|
+
}
|
|
78636
|
+
}
|
|
78637
|
+
putUint64(target, value, offset) {
|
|
78638
|
+
if (target.length < offset + 8) {
|
|
78639
|
+
throw new TypeError("Not enough space");
|
|
78640
|
+
}
|
|
78641
|
+
if (value < 0 || value > 18446744073709551615n) {
|
|
78642
|
+
throw new TypeError("Invalid uint64 value");
|
|
78643
|
+
}
|
|
78644
|
+
for (let i3 = 0;i3 < 8; i3++) {
|
|
78645
|
+
target[offset + i3] = Number(value >> BigInt((7 - i3) * 8) & 0xffn);
|
|
78646
|
+
}
|
|
78647
|
+
}
|
|
78648
|
+
}
|
|
78649
|
+
|
|
78650
|
+
class LittleEndian {
|
|
78651
|
+
uint8(data2, offset) {
|
|
78652
|
+
if (data2.byteLength < offset + 1) {
|
|
78653
|
+
throw new TypeError("Insufficient bytes");
|
|
78654
|
+
}
|
|
78655
|
+
return data2[offset];
|
|
78656
|
+
}
|
|
78657
|
+
uint16(data2, offset) {
|
|
78658
|
+
if (data2.byteLength < offset + 2) {
|
|
78659
|
+
throw new TypeError("Insufficient bytes");
|
|
78660
|
+
}
|
|
78661
|
+
return data2[offset] | data2[offset + 1] << 8;
|
|
78662
|
+
}
|
|
78663
|
+
uint32(data2, offset) {
|
|
78664
|
+
if (data2.byteLength < offset + 4) {
|
|
78665
|
+
throw new TypeError("Insufficient bytes");
|
|
78666
|
+
}
|
|
78667
|
+
let result = 0;
|
|
78668
|
+
for (let i3 = 0;i3 < 4; i3++) {
|
|
78669
|
+
result |= data2[offset + i3] << i3 * 8;
|
|
78670
|
+
}
|
|
78671
|
+
return result;
|
|
78672
|
+
}
|
|
78673
|
+
uint64(data2, offset) {
|
|
78674
|
+
if (data2.byteLength < offset + 8) {
|
|
78675
|
+
throw new TypeError("Insufficient bytes");
|
|
78676
|
+
}
|
|
78677
|
+
let result = 0n;
|
|
78678
|
+
for (let i3 = 0;i3 < 8; i3++) {
|
|
78679
|
+
result |= BigInt(data2[offset + i3]) << BigInt(i3 * 8);
|
|
78680
|
+
}
|
|
78681
|
+
return result;
|
|
78682
|
+
}
|
|
78683
|
+
putUint8(target, value, offset) {
|
|
78684
|
+
if (target.length < 1 + offset) {
|
|
78685
|
+
throw new TypeError("Insufficient space");
|
|
78686
|
+
}
|
|
78687
|
+
if (value < 0 || value > 255) {
|
|
78688
|
+
throw new TypeError("Invalid uint8 value");
|
|
78689
|
+
}
|
|
78690
|
+
target[offset] = value;
|
|
78691
|
+
}
|
|
78692
|
+
putUint16(target, value, offset) {
|
|
78693
|
+
if (target.length < 2 + offset) {
|
|
78694
|
+
throw new TypeError("Insufficient space");
|
|
78695
|
+
}
|
|
78696
|
+
if (value < 0 || value > 65535) {
|
|
78697
|
+
throw new TypeError("Invalid uint16 value");
|
|
78698
|
+
}
|
|
78699
|
+
target[offset + 1] = value >> 8;
|
|
78700
|
+
target[offset] = value & 255;
|
|
78701
|
+
}
|
|
78702
|
+
putUint32(target, value, offset) {
|
|
78703
|
+
if (target.length < 4 + offset) {
|
|
78704
|
+
throw new TypeError("Insufficient space");
|
|
78705
|
+
}
|
|
78706
|
+
if (value < 0 || value > 4294967295) {
|
|
78707
|
+
throw new TypeError("Invalid uint32 value");
|
|
78708
|
+
}
|
|
78709
|
+
for (let i3 = 0;i3 < 4; i3++) {
|
|
78710
|
+
target[offset + i3] = value >> i3 * 8 & 255;
|
|
78711
|
+
}
|
|
78712
|
+
}
|
|
78713
|
+
putUint64(target, value, offset) {
|
|
78321
78714
|
if (target.length < 8 + offset) {
|
|
78322
78715
|
throw new TypeError("Insufficient space");
|
|
78323
78716
|
}
|
|
@@ -79256,6 +79649,88 @@ gamesRouter.post("/uploads/finalize", async (c2) => {
|
|
|
79256
79649
|
return c2.json({ error: message2 }, 500);
|
|
79257
79650
|
}
|
|
79258
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
|
+
});
|
|
79259
79734
|
// src/routes/manifest.ts
|
|
79260
79735
|
var manifestRouter = new Hono2;
|
|
79261
79736
|
manifestRouter.get("/", async (c2) => {
|
|
@@ -79387,157 +79862,27 @@ shopRouter.get("/view", async (c2) => {
|
|
|
79387
79862
|
return c2.json({ error: message2 }, 500);
|
|
79388
79863
|
}
|
|
79389
79864
|
});
|
|
79390
|
-
//
|
|
79391
|
-
|
|
79392
|
-
|
|
79393
|
-
|
|
79394
|
-
|
|
79395
|
-
|
|
79396
|
-
|
|
79397
|
-
|
|
79398
|
-
|
|
79399
|
-
return allItems;
|
|
79400
|
-
} catch (error2) {
|
|
79401
|
-
if (error2 instanceof ApiError) {
|
|
79402
|
-
throw error2;
|
|
79403
|
-
}
|
|
79404
|
-
logger2.error("Error fetching items:", error2);
|
|
79405
|
-
throw ApiError.internal("Internal server error", error2);
|
|
79406
|
-
}
|
|
79407
|
-
}
|
|
79408
|
-
async function getItemById(ctx) {
|
|
79409
|
-
const user = ctx.user;
|
|
79410
|
-
const itemId = ctx.params.itemId;
|
|
79411
|
-
if (!user) {
|
|
79412
|
-
throw ApiError.unauthorized("Must be logged in to view item details");
|
|
79413
|
-
}
|
|
79414
|
-
if (!itemId) {
|
|
79415
|
-
throw ApiError.badRequest("Missing item ID");
|
|
79416
|
-
}
|
|
79417
|
-
try {
|
|
79418
|
-
const db = getDatabase();
|
|
79419
|
-
const item = await db.query.items.findFirst({
|
|
79420
|
-
where: eq(items.id, itemId)
|
|
79421
|
-
});
|
|
79422
|
-
if (!item) {
|
|
79423
|
-
throw ApiError.notFound("Item not found");
|
|
79424
|
-
}
|
|
79425
|
-
return item;
|
|
79426
|
-
} catch (error2) {
|
|
79427
|
-
if (error2 instanceof ApiError) {
|
|
79428
|
-
throw error2;
|
|
79429
|
-
}
|
|
79430
|
-
logger2.error(`Error fetching item ${itemId}:`, error2);
|
|
79431
|
-
throw ApiError.internal("Internal server error", error2);
|
|
79432
|
-
}
|
|
79433
|
-
}
|
|
79434
|
-
async function createItem(ctx) {
|
|
79435
|
-
const user = ctx.user;
|
|
79436
|
-
if (!user || user.role !== "admin") {
|
|
79437
|
-
throw ApiError.forbidden("Admin access required");
|
|
79438
|
-
}
|
|
79439
|
-
let inputData;
|
|
79440
|
-
try {
|
|
79441
|
-
const requestBody = await ctx.request.json();
|
|
79442
|
-
const validationResult = InsertItemSchema.safeParse(requestBody);
|
|
79443
|
-
if (!validationResult.success) {
|
|
79444
|
-
throw ApiError.badRequest(`Validation failed: ${formatValidationErrors(validationResult.error)}`);
|
|
79445
|
-
}
|
|
79446
|
-
inputData = validationResult.data;
|
|
79447
|
-
} catch (error2) {
|
|
79448
|
-
if (error2 instanceof ApiError) {
|
|
79449
|
-
throw error2;
|
|
79450
|
-
}
|
|
79451
|
-
logger2.error("Failed to parse request body or invalid JSON:", error2);
|
|
79452
|
-
throw ApiError.badRequest("Invalid JSON body");
|
|
79453
|
-
}
|
|
79454
|
-
try {
|
|
79455
|
-
const db = getDatabase();
|
|
79456
|
-
const [newItem] = await db.insert(items).values(inputData).returning();
|
|
79457
|
-
if (!newItem) {
|
|
79458
|
-
throw ApiError.internal("Failed to create item in database");
|
|
79459
|
-
}
|
|
79460
|
-
return newItem;
|
|
79461
|
-
} catch (error2) {
|
|
79462
|
-
if (error2 instanceof Error) {
|
|
79463
|
-
if (error2.message.includes("duplicate key value violates unique constraint")) {
|
|
79464
|
-
throw ApiError.conflict("An item with this internal name already exists");
|
|
79465
|
-
}
|
|
79466
|
-
}
|
|
79467
|
-
logger2.error("Error creating item:", error2);
|
|
79468
|
-
throw ApiError.internal("Internal server error", error2);
|
|
79469
|
-
}
|
|
79470
|
-
}
|
|
79471
|
-
async function updateItem(ctx) {
|
|
79472
|
-
const user = ctx.user;
|
|
79473
|
-
const itemId = ctx.params.itemId;
|
|
79474
|
-
if (!user || user.role !== "admin") {
|
|
79475
|
-
throw ApiError.forbidden("Admin access required");
|
|
79476
|
-
}
|
|
79477
|
-
if (!itemId) {
|
|
79478
|
-
throw ApiError.badRequest("Missing item ID");
|
|
79479
|
-
}
|
|
79480
|
-
let inputData;
|
|
79481
|
-
try {
|
|
79482
|
-
const requestBody = await ctx.request.json();
|
|
79483
|
-
const validationResult = UpdateItemSchema.safeParse(requestBody);
|
|
79484
|
-
if (!validationResult.success) {
|
|
79485
|
-
throw ApiError.badRequest(`Validation failed: ${formatValidationErrors(validationResult.error)}`);
|
|
79486
|
-
}
|
|
79487
|
-
inputData = validationResult.data;
|
|
79488
|
-
} catch (error2) {
|
|
79489
|
-
if (error2 instanceof ApiError) {
|
|
79490
|
-
throw error2;
|
|
79491
|
-
}
|
|
79492
|
-
logger2.error("Failed to parse request body or invalid JSON:", error2);
|
|
79493
|
-
throw ApiError.badRequest("Invalid JSON body");
|
|
79494
|
-
}
|
|
79495
|
-
if (Object.keys(inputData).length === 0) {
|
|
79496
|
-
throw ApiError.badRequest("No update data provided");
|
|
79497
|
-
}
|
|
79498
|
-
try {
|
|
79499
|
-
const db = getDatabase();
|
|
79500
|
-
const [updatedItem] = await db.update(items).set(inputData).where(eq(items.id, itemId)).returning();
|
|
79501
|
-
if (!updatedItem) {
|
|
79502
|
-
throw ApiError.notFound("Item not found for update");
|
|
79503
|
-
}
|
|
79504
|
-
return updatedItem;
|
|
79505
|
-
} catch (error2) {
|
|
79506
|
-
if (error2 instanceof Error) {
|
|
79507
|
-
if (error2.message.includes("duplicate key value violates unique constraint")) {
|
|
79508
|
-
throw ApiError.conflict("An item with this internal name already exists");
|
|
79509
|
-
}
|
|
79510
|
-
}
|
|
79511
|
-
logger2.error(`Error updating item ${itemId}:`, error2);
|
|
79512
|
-
throw ApiError.internal("Internal server error", error2);
|
|
79513
|
-
}
|
|
79514
|
-
}
|
|
79515
|
-
async function deleteItem(ctx) {
|
|
79516
|
-
const user = ctx.user;
|
|
79517
|
-
const itemId = ctx.params.itemId;
|
|
79518
|
-
if (!user || user.role !== "admin") {
|
|
79519
|
-
throw ApiError.forbidden("Admin access required");
|
|
79520
|
-
}
|
|
79521
|
-
if (!itemId) {
|
|
79522
|
-
throw ApiError.badRequest("Missing item ID");
|
|
79523
|
-
}
|
|
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
|
+
};
|
|
79524
79874
|
try {
|
|
79525
|
-
const
|
|
79526
|
-
|
|
79527
|
-
if (result.length === 0) {
|
|
79528
|
-
throw ApiError.notFound("Item not found for deletion");
|
|
79529
|
-
}
|
|
79875
|
+
const result = await resolveItem(ctx);
|
|
79876
|
+
return c2.json(result);
|
|
79530
79877
|
} catch (error2) {
|
|
79531
79878
|
if (error2 instanceof ApiError) {
|
|
79532
|
-
|
|
79879
|
+
return c2.json({ error: error2.message }, error2.statusCode);
|
|
79533
79880
|
}
|
|
79534
|
-
|
|
79535
|
-
|
|
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);
|
|
79536
79884
|
}
|
|
79537
|
-
}
|
|
79538
|
-
|
|
79539
|
-
// src/routes/items.ts
|
|
79540
|
-
var itemsRouter = new Hono2;
|
|
79885
|
+
});
|
|
79541
79886
|
itemsRouter.post("/", async (c2) => {
|
|
79542
79887
|
const ctx = {
|
|
79543
79888
|
user: c2.get("user"),
|
|
@@ -79710,8 +80055,8 @@ async function createCurrency(ctx) {
|
|
|
79710
80055
|
} catch (error2) {
|
|
79711
80056
|
if (error2 instanceof Error) {
|
|
79712
80057
|
if (error2.message.includes("duplicate key value violates unique constraint") || error2.message.includes("UNIQUE constraint failed")) {
|
|
79713
|
-
if (error2.message.includes("
|
|
79714
|
-
throw ApiError.conflict("A currency with this
|
|
80058
|
+
if (error2.message.includes("currencies_slug_unique")) {
|
|
80059
|
+
throw ApiError.conflict("A currency with this slug already exists");
|
|
79715
80060
|
}
|
|
79716
80061
|
throw ApiError.conflict("A similar currency already exists");
|
|
79717
80062
|
}
|
|
@@ -79757,8 +80102,8 @@ async function updateCurrency(ctx) {
|
|
|
79757
80102
|
} catch (error2) {
|
|
79758
80103
|
if (error2 instanceof Error) {
|
|
79759
80104
|
if (error2.message.includes("duplicate key value violates unique constraint") || error2.message.includes("UNIQUE constraint failed")) {
|
|
79760
|
-
if (error2.message.includes("
|
|
79761
|
-
throw ApiError.conflict("A currency with this
|
|
80105
|
+
if (error2.message.includes("currencies_slug_unique")) {
|
|
80106
|
+
throw ApiError.conflict("A currency with this slug already exists");
|
|
79762
80107
|
}
|
|
79763
80108
|
throw ApiError.conflict("A similar currency already exists");
|
|
79764
80109
|
}
|