feat: implement user inventory management and class update endpoints
This commit is contained in:
@@ -149,17 +149,24 @@ export async function createWebServer(config: WebServerConfig = {}): Promise<Web
|
||||
if (url.pathname === "/api/quests" && req.method === "POST") {
|
||||
try {
|
||||
const { questService } = await import("@shared/modules/quest/quest.service");
|
||||
const data = await req.json();
|
||||
const { CreateQuestSchema } = await import("@shared/modules/quest/quest.types");
|
||||
|
||||
// Basic validation could be added here or rely on service/DB
|
||||
const rawBody = await req.json();
|
||||
const parsed = CreateQuestSchema.safeParse(rawBody);
|
||||
|
||||
if (!parsed.success) {
|
||||
return Response.json({ error: "Invalid payload", issues: parsed.error.issues }, { status: 400 });
|
||||
}
|
||||
|
||||
const data = parsed.data;
|
||||
const result = await questService.createQuest({
|
||||
name: data.name,
|
||||
description: data.description || "",
|
||||
triggerEvent: data.triggerEvent,
|
||||
requirements: { target: Number(data.target) || 1 },
|
||||
requirements: { target: data.target },
|
||||
rewards: {
|
||||
xp: Number(data.xpReward) || 0,
|
||||
balance: Number(data.balanceReward) || 0
|
||||
xp: data.xpReward,
|
||||
balance: data.balanceReward
|
||||
}
|
||||
});
|
||||
|
||||
@@ -209,11 +216,11 @@ export async function createWebServer(config: WebServerConfig = {}): Promise<Web
|
||||
const { questService } = await import("@shared/modules/quest/quest.service");
|
||||
const result = await questService.deleteQuest(id);
|
||||
|
||||
if (result.length === 0) {
|
||||
if (!result || result.length === 0) {
|
||||
return Response.json({ error: "Quest not found" }, { status: 404 });
|
||||
}
|
||||
|
||||
return Response.json({ success: true, deleted: result[0].id });
|
||||
return Response.json({ success: true, deleted: (result[0] as { id: number }).id });
|
||||
} catch (error) {
|
||||
logger.error("web", "Error deleting quest", error);
|
||||
return Response.json(
|
||||
@@ -232,20 +239,30 @@ export async function createWebServer(config: WebServerConfig = {}): Promise<Web
|
||||
|
||||
try {
|
||||
const { questService } = await import("@shared/modules/quest/quest.service");
|
||||
const data = await req.json();
|
||||
const { UpdateQuestSchema } = await import("@shared/modules/quest/quest.types");
|
||||
|
||||
const rawBody = await req.json();
|
||||
const parsed = UpdateQuestSchema.safeParse(rawBody);
|
||||
|
||||
if (!parsed.success) {
|
||||
return Response.json({ error: "Invalid payload", issues: parsed.error.issues }, { status: 400 });
|
||||
}
|
||||
|
||||
const data = parsed.data;
|
||||
const result = await questService.updateQuest(id, {
|
||||
name: data.name,
|
||||
description: data.description,
|
||||
triggerEvent: data.triggerEvent,
|
||||
requirements: { target: Number(data.target) || 1 },
|
||||
rewards: {
|
||||
xp: Number(data.xpReward) || 0,
|
||||
balance: Number(data.balanceReward) || 0
|
||||
}
|
||||
...(data.name !== undefined && { name: data.name }),
|
||||
...(data.description !== undefined && { description: data.description }),
|
||||
...(data.triggerEvent !== undefined && { triggerEvent: data.triggerEvent }),
|
||||
...(data.target !== undefined && { requirements: { target: data.target } }),
|
||||
...((data.xpReward !== undefined || data.balanceReward !== undefined) && {
|
||||
rewards: {
|
||||
xp: data.xpReward ?? 0,
|
||||
balance: data.balanceReward ?? 0
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
if (result.length === 0) {
|
||||
if (!result || result.length === 0) {
|
||||
return Response.json({ error: "Quest not found" }, { status: 404 });
|
||||
}
|
||||
|
||||
@@ -713,6 +730,482 @@ export async function createWebServer(config: WebServerConfig = {}): Promise<Web
|
||||
return new Response("Not found", { status: 404 });
|
||||
}
|
||||
|
||||
// =====================================
|
||||
// Users Management API
|
||||
// =====================================
|
||||
|
||||
// GET /api/users - List all users with filtering
|
||||
if (url.pathname === "/api/users" && req.method === "GET") {
|
||||
try {
|
||||
const { users } = await import("../../shared/db/schema");
|
||||
const { DrizzleClient } = await import("@shared/db/DrizzleClient");
|
||||
const { ilike, desc, asc, sql } = await import("drizzle-orm");
|
||||
|
||||
const search = url.searchParams.get("search") || undefined;
|
||||
const sortBy = url.searchParams.get("sortBy") || "balance";
|
||||
const sortOrder = url.searchParams.get("sortOrder") || "desc";
|
||||
const limit = url.searchParams.get("limit") ? parseInt(url.searchParams.get("limit")!) : 50;
|
||||
const offset = url.searchParams.get("offset") ? parseInt(url.searchParams.get("offset")!) : 0;
|
||||
|
||||
let query = DrizzleClient.select().from(users);
|
||||
|
||||
if (search) {
|
||||
query = query.where(ilike(users.username, `%${search}%`)) as typeof query;
|
||||
}
|
||||
|
||||
// Build order clause
|
||||
const sortColumn = sortBy === "level" ? users.level :
|
||||
sortBy === "xp" ? users.xp :
|
||||
sortBy === "username" ? users.username : users.balance;
|
||||
const orderFn = sortOrder === "asc" ? asc : desc;
|
||||
|
||||
const result = await query
|
||||
.orderBy(orderFn(sortColumn))
|
||||
.limit(limit)
|
||||
.offset(offset);
|
||||
|
||||
// Get total count
|
||||
const countResult = await DrizzleClient.select({ count: sql<number>`count(*)` }).from(users);
|
||||
const total = Number(countResult[0]?.count || 0);
|
||||
|
||||
const { jsonReplacer } = await import("@shared/lib/utils");
|
||||
return new Response(JSON.stringify({ users: result, total }, jsonReplacer), {
|
||||
headers: { "Content-Type": "application/json" }
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error("web", "Error fetching users", error);
|
||||
return Response.json(
|
||||
{ error: "Failed to fetch users", details: error instanceof Error ? error.message : String(error) },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// GET /api/users/:id - Get single user
|
||||
if (url.pathname.match(/^\/api\/users\/\d+$/) && req.method === "GET") {
|
||||
const id = url.pathname.split("/").pop()!;
|
||||
|
||||
try {
|
||||
const { userService } = await import("@shared/modules/user/user.service");
|
||||
const user = await userService.getUserById(id);
|
||||
|
||||
if (!user) {
|
||||
return Response.json({ error: "User not found" }, { status: 404 });
|
||||
}
|
||||
|
||||
const { jsonReplacer } = await import("@shared/lib/utils");
|
||||
return new Response(JSON.stringify(user, jsonReplacer), {
|
||||
headers: { "Content-Type": "application/json" }
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error("web", "Error fetching user", error);
|
||||
return Response.json(
|
||||
{ error: "Failed to fetch user", details: error instanceof Error ? error.message : String(error) },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// PUT /api/users/:id - Update user
|
||||
if (url.pathname.match(/^\/api\/users\/\d+$/) && req.method === "PUT") {
|
||||
const id = url.pathname.split("/").pop()!;
|
||||
|
||||
try {
|
||||
const { userService } = await import("@shared/modules/user/user.service");
|
||||
const data = await req.json() as Record<string, any>;
|
||||
|
||||
// Check if user exists
|
||||
const existing = await userService.getUserById(id);
|
||||
if (!existing) {
|
||||
return Response.json({ error: "User not found" }, { status: 404 });
|
||||
}
|
||||
|
||||
// Build update data (only allow safe fields)
|
||||
const updateData: any = {};
|
||||
if (data.username !== undefined) updateData.username = data.username;
|
||||
if (data.balance !== undefined) updateData.balance = BigInt(data.balance);
|
||||
if (data.xp !== undefined) updateData.xp = BigInt(data.xp);
|
||||
if (data.level !== undefined) updateData.level = parseInt(data.level);
|
||||
if (data.dailyStreak !== undefined) updateData.dailyStreak = parseInt(data.dailyStreak);
|
||||
if (data.isActive !== undefined) updateData.isActive = Boolean(data.isActive);
|
||||
if (data.settings !== undefined) updateData.settings = data.settings;
|
||||
if (data.classId !== undefined) updateData.classId = BigInt(data.classId);
|
||||
|
||||
const updatedUser = await userService.updateUser(id, updateData);
|
||||
|
||||
const { jsonReplacer } = await import("@shared/lib/utils");
|
||||
return new Response(JSON.stringify({ success: true, user: updatedUser }, jsonReplacer), {
|
||||
headers: { "Content-Type": "application/json" }
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error("web", "Error updating user", error);
|
||||
return Response.json(
|
||||
{ error: "Failed to update user", details: error instanceof Error ? error.message : String(error) },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// GET /api/users/:id/inventory - Get user inventory
|
||||
if (url.pathname.match(/^\/api\/users\/\d+\/inventory$/) && req.method === "GET") {
|
||||
const id = url.pathname.split("/")[3] || "0";
|
||||
|
||||
try {
|
||||
const { inventoryService } = await import("@shared/modules/inventory/inventory.service");
|
||||
const inventory = await inventoryService.getInventory(id);
|
||||
|
||||
const { jsonReplacer } = await import("@shared/lib/utils");
|
||||
return new Response(JSON.stringify({ inventory }, jsonReplacer), {
|
||||
headers: { "Content-Type": "application/json" }
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error("web", "Error fetching inventory", error);
|
||||
return Response.json(
|
||||
{ error: "Failed to fetch inventory", details: error instanceof Error ? error.message : String(error) },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// POST /api/users/:id/inventory - Add item to inventory
|
||||
if (url.pathname.match(/^\/api\/users\/\d+\/inventory$/) && req.method === "POST") {
|
||||
const id = url.pathname.split("/")[3] || "0";
|
||||
|
||||
try {
|
||||
const { inventoryService } = await import("@shared/modules/inventory/inventory.service");
|
||||
const data = await req.json() as Record<string, any>;
|
||||
|
||||
if (!data.itemId || !data.quantity) {
|
||||
return Response.json(
|
||||
{ error: "Missing required fields: itemId, quantity" },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
const entry = await inventoryService.addItem(id, data.itemId, BigInt(data.quantity));
|
||||
|
||||
const { jsonReplacer } = await import("@shared/lib/utils");
|
||||
return new Response(JSON.stringify({ success: true, entry }, jsonReplacer), {
|
||||
status: 201,
|
||||
headers: { "Content-Type": "application/json" }
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error("web", "Error adding item to inventory", error);
|
||||
return Response.json(
|
||||
{ error: "Failed to add item", details: error instanceof Error ? error.message : String(error) },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// DELETE /api/users/:id/inventory/:itemId - Remove item from inventory
|
||||
if (url.pathname.match(/^\/api\/users\/\d+\/inventory\/\d+$/) && req.method === "DELETE") {
|
||||
const parts = url.pathname.split("/");
|
||||
const userId = parts[3];
|
||||
const itemId = parseInt(parts[5]);
|
||||
|
||||
try {
|
||||
const { inventoryService } = await import("@shared/modules/inventory/inventory.service");
|
||||
|
||||
const amount = url.searchParams.get("amount");
|
||||
const quantity = amount ? BigInt(amount) : 1n;
|
||||
|
||||
await inventoryService.removeItem(userId, itemId, quantity);
|
||||
|
||||
return new Response(null, { status: 204 });
|
||||
} catch (error) {
|
||||
logger.error("web", "Error removing item from inventory", error);
|
||||
return Response.json(
|
||||
{ error: "Failed to remove item", details: error instanceof Error ? error.message : String(error) },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// =====================================
|
||||
// Classes Management API
|
||||
// =====================================
|
||||
|
||||
// GET /api/classes - List all classes
|
||||
if (url.pathname === "/api/classes" && req.method === "GET") {
|
||||
try {
|
||||
const { classService } = await import("@shared/modules/class/class.service");
|
||||
const classes = await classService.getAllClasses();
|
||||
|
||||
const { jsonReplacer } = await import("@shared/lib/utils");
|
||||
return new Response(JSON.stringify({ classes }, jsonReplacer), {
|
||||
headers: { "Content-Type": "application/json" }
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error("web", "Error fetching classes", error);
|
||||
return Response.json(
|
||||
{ error: "Failed to fetch classes", details: error instanceof Error ? error.message : String(error) },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// POST /api/classes - Create new class
|
||||
if (url.pathname === "/api/classes" && req.method === "POST") {
|
||||
try {
|
||||
const { classService } = await import("@shared/modules/class/class.service");
|
||||
const data = await req.json() as Record<string, any>;
|
||||
|
||||
if (!data.id || !data.name) {
|
||||
return Response.json(
|
||||
{ error: "Missing required fields: id and name are required" },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
const newClass = await classService.createClass({
|
||||
id: BigInt(data.id),
|
||||
name: data.name,
|
||||
balance: data.balance ? BigInt(data.balance) : 0n,
|
||||
roleId: data.roleId || null,
|
||||
});
|
||||
|
||||
const { jsonReplacer } = await import("@shared/lib/utils");
|
||||
return new Response(JSON.stringify({ success: true, class: newClass }, jsonReplacer), {
|
||||
status: 201,
|
||||
headers: { "Content-Type": "application/json" }
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error("web", "Error creating class", error);
|
||||
return Response.json(
|
||||
{ error: "Failed to create class", details: error instanceof Error ? error.message : String(error) },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// PUT /api/classes/:id - Update class
|
||||
if (url.pathname.match(/^\/api\/classes\/\d+$/) && req.method === "PUT") {
|
||||
const id = url.pathname.split("/").pop()!;
|
||||
|
||||
try {
|
||||
const { classService } = await import("@shared/modules/class/class.service");
|
||||
const data = await req.json() as Record<string, any>;
|
||||
|
||||
const updateData: any = {};
|
||||
if (data.name !== undefined) updateData.name = data.name;
|
||||
if (data.balance !== undefined) updateData.balance = BigInt(data.balance);
|
||||
if (data.roleId !== undefined) updateData.roleId = data.roleId;
|
||||
|
||||
const updatedClass = await classService.updateClass(BigInt(id), updateData);
|
||||
|
||||
if (!updatedClass) {
|
||||
return Response.json({ error: "Class not found" }, { status: 404 });
|
||||
}
|
||||
|
||||
const { jsonReplacer } = await import("@shared/lib/utils");
|
||||
return new Response(JSON.stringify({ success: true, class: updatedClass }, jsonReplacer), {
|
||||
headers: { "Content-Type": "application/json" }
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error("web", "Error updating class", error);
|
||||
return Response.json(
|
||||
{ error: "Failed to update class", details: error instanceof Error ? error.message : String(error) },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// DELETE /api/classes/:id - Delete class
|
||||
if (url.pathname.match(/^\/api\/classes\/\d+$/) && req.method === "DELETE") {
|
||||
const id = url.pathname.split("/").pop()!;
|
||||
|
||||
try {
|
||||
const { classService } = await import("@shared/modules/class/class.service");
|
||||
await classService.deleteClass(BigInt(id));
|
||||
|
||||
return new Response(null, { status: 204 });
|
||||
} catch (error) {
|
||||
logger.error("web", "Error deleting class", error);
|
||||
return Response.json(
|
||||
{ error: "Failed to delete class", details: error instanceof Error ? error.message : String(error) },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// =====================================
|
||||
// Moderation API
|
||||
// =====================================
|
||||
|
||||
// GET /api/moderation - List moderation cases
|
||||
if (url.pathname === "/api/moderation" && req.method === "GET") {
|
||||
try {
|
||||
const { ModerationService } = await import("@shared/modules/moderation/moderation.service");
|
||||
|
||||
const filter: any = {};
|
||||
if (url.searchParams.get("userId")) filter.userId = url.searchParams.get("userId");
|
||||
if (url.searchParams.get("moderatorId")) filter.moderatorId = url.searchParams.get("moderatorId");
|
||||
if (url.searchParams.get("type")) filter.type = url.searchParams.get("type");
|
||||
const activeParam = url.searchParams.get("active");
|
||||
if (activeParam !== null) filter.active = activeParam === "true";
|
||||
filter.limit = url.searchParams.get("limit") ? parseInt(url.searchParams.get("limit")!) : 50;
|
||||
filter.offset = url.searchParams.get("offset") ? parseInt(url.searchParams.get("offset")!) : 0;
|
||||
|
||||
const cases = await ModerationService.searchCases(filter);
|
||||
|
||||
const { jsonReplacer } = await import("@shared/lib/utils");
|
||||
return new Response(JSON.stringify({ cases }, jsonReplacer), {
|
||||
headers: { "Content-Type": "application/json" }
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error("web", "Error fetching moderation cases", error);
|
||||
return Response.json(
|
||||
{ error: "Failed to fetch moderation cases", details: error instanceof Error ? error.message : String(error) },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// GET /api/moderation/:caseId - Get single case
|
||||
if (url.pathname.match(/^\/api\/moderation\/CASE-\d+$/i) && req.method === "GET") {
|
||||
const caseId = url.pathname.split("/").pop()!.toUpperCase();
|
||||
|
||||
try {
|
||||
const { ModerationService } = await import("@shared/modules/moderation/moderation.service");
|
||||
const moderationCase = await ModerationService.getCaseById(caseId);
|
||||
|
||||
if (!moderationCase) {
|
||||
return Response.json({ error: "Case not found" }, { status: 404 });
|
||||
}
|
||||
|
||||
const { jsonReplacer } = await import("@shared/lib/utils");
|
||||
return new Response(JSON.stringify(moderationCase, jsonReplacer), {
|
||||
headers: { "Content-Type": "application/json" }
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error("web", "Error fetching moderation case", error);
|
||||
return Response.json(
|
||||
{ error: "Failed to fetch case", details: error instanceof Error ? error.message : String(error) },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// POST /api/moderation - Create new case
|
||||
if (url.pathname === "/api/moderation" && req.method === "POST") {
|
||||
try {
|
||||
const { ModerationService } = await import("@shared/modules/moderation/moderation.service");
|
||||
const data = await req.json() as Record<string, any>;
|
||||
|
||||
if (!data.type || !data.userId || !data.username || !data.moderatorId || !data.moderatorName || !data.reason) {
|
||||
return Response.json(
|
||||
{ error: "Missing required fields: type, userId, username, moderatorId, moderatorName, reason" },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
const newCase = await ModerationService.createCase({
|
||||
type: data.type,
|
||||
userId: data.userId,
|
||||
username: data.username,
|
||||
moderatorId: data.moderatorId,
|
||||
moderatorName: data.moderatorName,
|
||||
reason: data.reason,
|
||||
metadata: data.metadata || {},
|
||||
});
|
||||
|
||||
const { jsonReplacer } = await import("@shared/lib/utils");
|
||||
return new Response(JSON.stringify({ success: true, case: newCase }, jsonReplacer), {
|
||||
status: 201,
|
||||
headers: { "Content-Type": "application/json" }
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error("web", "Error creating moderation case", error);
|
||||
return Response.json(
|
||||
{ error: "Failed to create case", details: error instanceof Error ? error.message : String(error) },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// PUT /api/moderation/:caseId/clear - Clear/resolve a case
|
||||
if (url.pathname.match(/^\/api\/moderation\/CASE-\d+\/clear$/i) && req.method === "PUT") {
|
||||
const caseId = (url.pathname.split("/")[3] || "").toUpperCase();
|
||||
|
||||
try {
|
||||
const { ModerationService } = await import("@shared/modules/moderation/moderation.service");
|
||||
const data = await req.json() as Record<string, any>;
|
||||
|
||||
if (!data.clearedBy || !data.clearedByName) {
|
||||
return Response.json(
|
||||
{ error: "Missing required fields: clearedBy, clearedByName" },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
const updatedCase = await ModerationService.clearCase({
|
||||
caseId,
|
||||
clearedBy: data.clearedBy,
|
||||
clearedByName: data.clearedByName,
|
||||
reason: data.reason || "Cleared via API",
|
||||
});
|
||||
|
||||
if (!updatedCase) {
|
||||
return Response.json({ error: "Case not found" }, { status: 404 });
|
||||
}
|
||||
|
||||
const { jsonReplacer } = await import("@shared/lib/utils");
|
||||
return new Response(JSON.stringify({ success: true, case: updatedCase }, jsonReplacer), {
|
||||
headers: { "Content-Type": "application/json" }
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error("web", "Error clearing moderation case", error);
|
||||
return Response.json(
|
||||
{ error: "Failed to clear case", details: error instanceof Error ? error.message : String(error) },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// =====================================
|
||||
// Transactions API
|
||||
// =====================================
|
||||
|
||||
// GET /api/transactions - List transactions
|
||||
if (url.pathname === "/api/transactions" && req.method === "GET") {
|
||||
try {
|
||||
const { transactions } = await import("../../shared/db/schema");
|
||||
const { DrizzleClient } = await import("@shared/db/DrizzleClient");
|
||||
const { eq, desc } = await import("drizzle-orm");
|
||||
|
||||
const userId = url.searchParams.get("userId");
|
||||
const type = url.searchParams.get("type");
|
||||
const limit = url.searchParams.get("limit") ? parseInt(url.searchParams.get("limit")!) : 50;
|
||||
const offset = url.searchParams.get("offset") ? parseInt(url.searchParams.get("offset")!) : 0;
|
||||
|
||||
let query = DrizzleClient.select().from(transactions);
|
||||
|
||||
if (userId) {
|
||||
query = query.where(eq(transactions.userId, BigInt(userId))) as typeof query;
|
||||
}
|
||||
if (type) {
|
||||
query = query.where(eq(transactions.type, type)) as typeof query;
|
||||
}
|
||||
|
||||
const result = await query
|
||||
.orderBy(desc(transactions.createdAt))
|
||||
.limit(limit)
|
||||
.offset(offset);
|
||||
|
||||
const { jsonReplacer } = await import("@shared/lib/utils");
|
||||
return new Response(JSON.stringify({ transactions: result }, jsonReplacer), {
|
||||
headers: { "Content-Type": "application/json" }
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error("web", "Error fetching transactions", error);
|
||||
return Response.json(
|
||||
{ error: "Failed to fetch transactions", details: error instanceof Error ? error.message : String(error) },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// No frontend - return 404 for unknown routes
|
||||
return new Response("Not Found", { status: 404 });
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user