refactor(db): migrate app pages and APIs from Prisma facade to Drizzle (5)

Co-authored-by: Cursor <[email protected]>
This commit is contained in:
SimoandCursor committed 2026-08-01 14:15:39 +02:00
1 parent 9aa4f331bf
commit 30b54e99e7
40 files changed
+1183 -784

No files matched your search

+18 -16
View File
@@ -1,32 +1,34 @@
import { count, desc } from "drizzle-orm";
import { apiJson, pagination } from "@/lib/api";
import { prisma } from "@/lib/prisma";
import { CameraWeb, db } from "@/lib/db";
export const dynamic = "force-dynamic";
/**
* GET /api/photos — recent community photos (camera_web), newest first.
* Mirrors the /photos page query (prisma.cameraWeb). Returns id, userId, url
* and timestamp plus pagination metadata.
* Mirrors the /photos page query. Returns id, userId, url and timestamp plus
* pagination metadata.
*/
export async function GET(req: Request) {
const sp = new URL(req.url).searchParams;
const { page, perPage, skip, take } = pagination(sp);
try {
const [total, photos] = await Promise.all([
prisma.cameraWeb.count(),
prisma.cameraWeb.findMany({
select: {
id: true,
userId: true,
url: true,
timestamp: true,
},
orderBy: { timestamp: "desc" },
skip,
take,
}),
const [totalRows, photos] = await Promise.all([
db.select({ total: count() }).from(CameraWeb),
db
.select({
id: CameraWeb.id,
userId: CameraWeb.userId,
url: CameraWeb.url,
timestamp: CameraWeb.timestamp,
})
.from(CameraWeb)
.orderBy(desc(CameraWeb.timestamp))
.limit(take)
.offset(skip),
]);
const total = totalRows[0]?.total ?? 0;
return apiJson({
data: photos,
+7 -5
View File
@@ -1,5 +1,6 @@
import { eq } from "drizzle-orm";
import { apiJson } from "@/lib/api";
import { prisma } from "@/lib/prisma";
import { db, User } from "@/lib/db";
import { siteSettings } from "@/lib/services/site-settings";
// Current on-air DJ. The DJ is set manually via the radio_current_dj_id setting
@@ -17,10 +18,11 @@ export async function GET(_req: Request) {
return apiJson({ dj: null });
}
const user = await prisma.user.findUnique({
where: { id },
select: { username: true, look: true },
});
const [user] = await db
.select({ username: User.username, look: User.look })
.from(User)
.where(eq(User.id, id))
.limit(1);
if (!user) {
return apiJson({ dj: null });
+17 -10
View File
@@ -1,5 +1,6 @@
import { inArray, sum } from "drizzle-orm";
import { apiJson } from "@/lib/api";
import { prisma } from "@/lib/prisma";
import { db, RadioListenerPoints, User } from "@/lib/db";
// Radio listener-points leaderboard: the top 20 users by total points, summed
// across radio_listener_points and joined to users for username/look. Public
@@ -10,13 +11,19 @@ export async function GET(_req: Request) {
try {
// Sum points per user. Sort/slice in JS so we stay adapter-agnostic about
// aggregate ordering, then resolve the top 20 to usernames/looks.
const grouped = await prisma.radioListenerPoints.groupBy({
by: ["userId"],
_sum: { points: true },
});
const grouped = await db
.select({
userId: RadioListenerPoints.userId,
points: sum(RadioListenerPoints.points),
})
.from(RadioListenerPoints)
.groupBy(RadioListenerPoints.userId);
const ranked = grouped
.map((g) => ({ userId: g.userId, points: g._sum.points ?? 0 }))
.map((g) => ({
userId: Number(g.userId),
points: Number(g.points ?? 0),
}))
.sort((a, b) => b.points - a.points)
.slice(0, 20);
@@ -25,10 +32,10 @@ export async function GET(_req: Request) {
}
const userIds = ranked.map((r) => r.userId);
const users = await prisma.user.findMany({
where: { id: { in: userIds } },
select: { id: true, username: true, look: true },
});
const users = await db
.select({ id: User.id, username: User.username, look: User.look })
.from(User)
.where(inArray(User.id, userIds));
const userById = new Map(users.map((u) => [u.id, u]));
const data = ranked.map((r) => {
+7 -6
View File
@@ -1,6 +1,7 @@
import { eq, sum } from "drizzle-orm";
import { apiError, apiJson } from "@/lib/api";
import { bearerUserId } from "@/lib/api-auth";
import { prisma } from "@/lib/prisma";
import { db, RadioListenerPoints } from "@/lib/db";
// The Bearer-authed user's total radio listener points: the sum of all
// radio_listener_points.points rows for that user_id.
@@ -11,12 +12,12 @@ export async function GET(req: Request) {
if (!uid) return apiError("Unauthorized", 401);
try {
const agg = await prisma.radioListenerPoints.aggregate({
where: { userId: uid },
_sum: { points: true },
});
const [agg] = await db
.select({ points: sum(RadioListenerPoints.points) })
.from(RadioListenerPoints)
.where(eq(RadioListenerPoints.userId, BigInt(uid)));
return apiJson({ points: agg._sum.points ?? 0 });
return apiJson({ points: Number(agg?.points ?? 0) });
} catch {
// DB unavailable — report zero rather than a 500.
return apiJson({ points: 0 }, { status: 200 });
+16 -17
View File
@@ -1,6 +1,7 @@
import { desc, inArray } from "drizzle-orm";
import { apiError, apiJson } from "@/lib/api";
import { bearerUserId } from "@/lib/api-auth";
import { prisma } from "@/lib/prisma";
import { db, RadioShouts, User } from "@/lib/db";
// Latest 50 radio shouts with their author's username/look resolved. Mirrors the
// query behind the public /radio/shouts page (radio_shouts ordered by created_at
@@ -12,19 +13,20 @@ const MAX_MESSAGE_LENGTH = 255;
export async function GET(_req: Request) {
try {
const shouts = await prisma.radioShouts.findMany({
orderBy: { createdAt: "desc" },
take: 50,
});
const shouts = await db
.select()
.from(RadioShouts)
.orderBy(desc(RadioShouts.createdAt))
.limit(50);
// Resolve author usernames/looks. radio_shouts.user_id is an UnsignedBigInt
// while users.id is an Int, so narrow to Number for the lookup.
const authorIds = Array.from(new Set(shouts.map((s) => Number(s.userId))));
const authors = authorIds.length
? await prisma.user.findMany({
where: { id: { in: authorIds } },
select: { id: true, username: true, look: true },
})
? await db
.select({ id: User.id, username: User.username, look: User.look })
.from(User)
.where(inArray(User.id, authorIds))
: [];
const authorById = new Map(authors.map((a) => [a.id, a]));
@@ -67,14 +69,11 @@ export async function POST(req: Request) {
try {
const now = new Date();
await prisma.radioShouts.create({
data: {
userId: BigInt(uid),
message,
createdAt: now,
updatedAt: now,
},
select: { id: true },
await db.insert(RadioShouts).values({
userId: BigInt(uid),
message,
createdAt: now,
updatedAt: now,
});
return apiJson({ ok: true });
+15 -11
View File
@@ -1,22 +1,26 @@
// Public REST: website store categories (website_shop_categories).
// AtomCMS JSON API parity — read-only list ordered by `order` then name.
import { asc } from "drizzle-orm";
import { apiJson } from "@/lib/api";
import { prisma } from "@/lib/prisma";
import { db, WebsiteShopCategories } from "@/lib/db";
export const dynamic = "force-dynamic";
export async function GET(_req: Request) {
try {
const data = await prisma.websiteShopCategories.findMany({
orderBy: [{ order: "asc" }, { name: "asc" }],
select: {
id: true,
name: true,
description: true,
icon: true,
order: true,
},
});
const data = await db
.select({
id: WebsiteShopCategories.id,
name: WebsiteShopCategories.name,
description: WebsiteShopCategories.description,
icon: WebsiteShopCategories.icon,
order: WebsiteShopCategories.order,
})
.from(WebsiteShopCategories)
.orderBy(
asc(WebsiteShopCategories.order),
asc(WebsiteShopCategories.name),
);
return apiJson({ data });
} catch {
+40 -30
View File
@@ -1,8 +1,9 @@
// Public REST: website store packages (website_shop_articles).
// AtomCMS JSON API parity — read-only list of buyable packages, paginated and
// ordered by `position` (then name), matching the admin /admin/shop query.
import { asc, count, eq } from "drizzle-orm";
import { apiError, apiJson, pagination, positiveBigInt } from "@/lib/api";
import { prisma } from "@/lib/prisma";
import { db, WebsiteShopArticles } from "@/lib/db";
export const dynamic = "force-dynamic";
@@ -12,40 +13,49 @@ export async function GET(req: Request) {
// Optional ?category=<id> filter (website_shop_category_id is a BigInt).
const categoryRaw = sp.get("category");
let where: { categoryId?: bigint } = {};
let categoryId: bigint | undefined;
if (categoryRaw !== null) {
const categoryId = positiveBigInt(categoryRaw);
if (!categoryId) return apiError("A valid category id is required", 422);
where = { categoryId };
const parsed = positiveBigInt(categoryRaw);
if (!parsed) return apiError("A valid category id is required", 422);
categoryId = parsed;
}
try {
const [total, data] = await Promise.all([
prisma.websiteShopArticles.count({ where }),
prisma.websiteShopArticles.findMany({
where,
orderBy: [{ position: "asc" }, { name: "asc" }],
skip,
take,
select: {
id: true,
categoryId: true,
name: true,
info: true,
iconUrl: true,
color: true,
costs: true,
giveRank: true,
isGiftable: true,
credits: true,
duckets: true,
diamonds: true,
badges: true,
furniture: true,
position: true,
},
}),
const where =
categoryId !== undefined
? eq(WebsiteShopArticles.categoryId, categoryId)
: undefined;
const [totalRows, data] = await Promise.all([
db.select({ total: count() }).from(WebsiteShopArticles).where(where),
db
.select({
id: WebsiteShopArticles.id,
categoryId: WebsiteShopArticles.categoryId,
name: WebsiteShopArticles.name,
info: WebsiteShopArticles.info,
iconUrl: WebsiteShopArticles.iconUrl,
color: WebsiteShopArticles.color,
costs: WebsiteShopArticles.costs,
giveRank: WebsiteShopArticles.giveRank,
isGiftable: WebsiteShopArticles.isGiftable,
credits: WebsiteShopArticles.credits,
duckets: WebsiteShopArticles.duckets,
diamonds: WebsiteShopArticles.diamonds,
badges: WebsiteShopArticles.badges,
furniture: WebsiteShopArticles.furniture,
position: WebsiteShopArticles.position,
})
.from(WebsiteShopArticles)
.where(where)
.orderBy(
asc(WebsiteShopArticles.position),
asc(WebsiteShopArticles.name),
)
.limit(take)
.offset(skip),
]);
const total = totalRows[0]?.total ?? 0;
return apiJson({
data,
+13 -7
View File
@@ -1,5 +1,6 @@
import { asc, desc, gte } from "drizzle-orm";
import { apiJson } from "@/lib/api";
import { prisma } from "@/lib/prisma";
import { db, User } from "@/lib/db";
import { siteSettings } from "@/lib/services/site-settings";
// Public REST API — staff list. Mirrors src/app/staff/page.tsx: users whose
@@ -11,12 +12,17 @@ export async function GET(_req: Request) {
const minStaffRank =
Number(await siteSettings.get("min_staff_rank", "7")) || 7;
const staff = await prisma.user.findMany({
where: { rank: { gte: minStaffRank } },
select: { username: true, look: true, rank: true, motto: true },
orderBy: [{ rank: "desc" }, { username: "asc" }],
take: 100,
});
const staff = await db
.select({
username: User.username,
look: User.look,
rank: User.rank,
motto: User.motto,
})
.from(User)
.where(gte(User.rank, minStaffRank))
.orderBy(desc(User.rank), asc(User.username))
.limit(100);
return apiJson({ data: staff }, { status: 200 });
} catch {
+13 -12
View File
@@ -1,5 +1,6 @@
import { asc, eq } from "drizzle-orm";
import { apiJson } from "@/lib/api";
import { prisma } from "@/lib/prisma";
import { db, WebsiteTeams } from "@/lib/db";
// Public REST API — website teams (staff ranks). Mirrors src/app/staff/page.tsx:
// visible ranks (hiddenRank=false) ordered by id.
@@ -7,17 +8,17 @@ export const dynamic = "force-dynamic";
export async function GET(_req: Request) {
try {
const rows = await prisma.websiteTeams.findMany({
where: { hiddenRank: false },
select: {
id: true,
rankName: true,
badge: true,
jobDescription: true,
staffColor: true,
},
orderBy: { id: "asc" },
});
const rows = await db
.select({
id: WebsiteTeams.id,
rankName: WebsiteTeams.rankName,
badge: WebsiteTeams.badge,
jobDescription: WebsiteTeams.jobDescription,
staffColor: WebsiteTeams.staffColor,
})
.from(WebsiteTeams)
.where(eq(WebsiteTeams.hiddenRank, false))
.orderBy(asc(WebsiteTeams.id));
// apiJson serialises BigInt ids → string automatically.
return apiJson({ data: rows }, { status: 200 });
+34 -23
View File
@@ -4,9 +4,14 @@
// into website_help_center_ticket_replies. The target ticket must exist and
// belong to the authed user. Fail-soft: never a 500.
import { eq } from "drizzle-orm";
import { apiError, apiJson, positiveBigInt } from "@/lib/api";
import { bearerUserId } from "@/lib/api-auth";
import { prisma } from "@/lib/prisma";
import {
db,
WebsiteHelpCenterTicketReplies,
WebsiteHelpCenterTickets,
} from "@/lib/db";
import { rateLimit } from "@/lib/rate-limit";
import { createOwnedTicketReply } from "@/lib/services/ticket-replies";
@@ -36,30 +41,36 @@ export async function POST(
try {
// Ownership check — only the ticket owner may reply.
const reply = await prisma.$transaction((tx) =>
const reply = await db.transaction(async (tx) =>
createOwnedTicketReply(
{
findTicket: (ticketId) =>
tx.websiteHelpCenterTickets.findUnique({
where: { id: ticketId },
select: { id: true, userId: true },
}),
createReply: (data) =>
tx.websiteHelpCenterTicketReplies.create({
data,
select: {
id: true,
userId: true,
content: true,
createdAt: true,
},
}),
touchTicket: (ticketId, updatedAt) =>
tx.websiteHelpCenterTickets.update({
where: { id: ticketId },
data: { updatedAt },
select: { id: true },
}),
findTicket: async (id) => {
const [row] = await tx
.select({
id: WebsiteHelpCenterTickets.id,
userId: WebsiteHelpCenterTickets.userId,
})
.from(WebsiteHelpCenterTickets)
.where(eq(WebsiteHelpCenterTickets.id, id))
.limit(1);
return row ?? null;
},
createReply: async (data) => {
const [result] = await tx
.insert(WebsiteHelpCenterTicketReplies)
.values(data);
return {
id: BigInt(result.insertId),
userId: data.userId,
content: data.content,
createdAt: data.createdAt,
};
},
touchTicket: (id, updatedAt) =>
tx
.update(WebsiteHelpCenterTickets)
.set({ updatedAt })
.where(eq(WebsiteHelpCenterTickets.id, id)),
},
{ ticketId, userId: uid, content },
),
+34 -22
View File
@@ -4,9 +4,15 @@
// together with its replies; reply author usernames are resolved in a single
// users lookup. Fail-soft: never a 500.
import { asc, eq, inArray } from "drizzle-orm";
import { apiError, apiJson, positiveBigInt } from "@/lib/api";
import { bearerUserId } from "@/lib/api-auth";
import { prisma } from "@/lib/prisma";
import {
db,
User,
WebsiteHelpCenterTicketReplies,
WebsiteHelpCenterTickets,
} from "@/lib/db";
export const dynamic = "force-dynamic";
@@ -23,37 +29,43 @@ export async function GET(
if (!ticketId) return apiError("Invalid ticket id", 422);
try {
const ticket = await prisma.websiteHelpCenterTickets.findUnique({
where: { id: ticketId },
select: {
id: true,
userId: true,
categoryId: true,
title: true,
content: true,
open: true,
createdAt: true,
},
});
const [ticket] = await db
.select({
id: WebsiteHelpCenterTickets.id,
userId: WebsiteHelpCenterTickets.userId,
categoryId: WebsiteHelpCenterTickets.categoryId,
title: WebsiteHelpCenterTickets.title,
content: WebsiteHelpCenterTickets.content,
open: WebsiteHelpCenterTickets.open,
createdAt: WebsiteHelpCenterTickets.createdAt,
})
.from(WebsiteHelpCenterTickets)
.where(eq(WebsiteHelpCenterTickets.id, ticketId))
.limit(1);
// Ownership check — return 404 (not 403) so a foreign id is indistinguishable
// from a missing one.
if (!ticket || ticket.userId !== uid)
return apiError("Ticket not found", 404);
const replies = await prisma.websiteHelpCenterTicketReplies.findMany({
where: { ticketId },
select: { id: true, userId: true, content: true, createdAt: true },
orderBy: { id: "asc" },
});
const replies = await db
.select({
id: WebsiteHelpCenterTicketReplies.id,
userId: WebsiteHelpCenterTicketReplies.userId,
content: WebsiteHelpCenterTicketReplies.content,
createdAt: WebsiteHelpCenterTicketReplies.createdAt,
})
.from(WebsiteHelpCenterTicketReplies)
.where(eq(WebsiteHelpCenterTicketReplies.ticketId, ticketId))
.orderBy(asc(WebsiteHelpCenterTicketReplies.id));
// Resolve author usernames in one query.
const authorIds = [...new Set(replies.map((r) => r.userId))];
const authors = authorIds.length
? await prisma.user.findMany({
where: { id: { in: authorIds } },
select: { id: true, username: true },
})
? await db
.select({ id: User.id, username: User.username })
.from(User)
.where(inArray(User.id, authorIds))
: [];
const nameById = new Map(authors.map((a) => [a.id, a.username]));
+26 -22
View File
@@ -4,9 +4,11 @@
// Backed by website_help_center_tickets (WebsiteHelpCenterTickets). Fail-soft:
// DB errors return an apiError envelope, never a 500.
import { desc, eq } from "drizzle-orm";
import type { ResultSetHeader } from "mysql2";
import { apiError, apiJson, positiveBigInt } from "@/lib/api";
import { bearerUserId } from "@/lib/api-auth";
import { prisma } from "@/lib/prisma";
import { db, WebsiteHelpCenterTickets } from "@/lib/db";
import { rateLimit } from "@/lib/rate-limit";
export const dynamic = "force-dynamic";
@@ -17,11 +19,16 @@ export async function GET(req: Request) {
if (!uid) return apiError("Unauthorized", 401);
try {
const tickets = await prisma.websiteHelpCenterTickets.findMany({
where: { userId: uid },
select: { id: true, title: true, open: true, createdAt: true },
orderBy: { id: "desc" },
});
const tickets = await db
.select({
id: WebsiteHelpCenterTickets.id,
title: WebsiteHelpCenterTickets.title,
open: WebsiteHelpCenterTickets.open,
createdAt: WebsiteHelpCenterTickets.createdAt,
})
.from(WebsiteHelpCenterTickets)
.where(eq(WebsiteHelpCenterTickets.userId, uid))
.orderBy(desc(WebsiteHelpCenterTickets.id));
return apiJson({
tickets: tickets.map((t) => ({
@@ -74,26 +81,23 @@ export async function POST(req: Request) {
try {
const now = new Date();
const ticket = await prisma.websiteHelpCenterTickets.create({
data: {
userId: uid,
categoryId,
title,
content,
open: true,
createdAt: now,
updatedAt: now,
},
select: { id: true, title: true, open: true, createdAt: true },
});
const [result] = (await db.insert(WebsiteHelpCenterTickets).values({
userId: uid,
categoryId,
title,
content,
open: true,
createdAt: now,
updatedAt: now,
})) as unknown as [ResultSetHeader];
return apiJson(
{
ticket: {
id: ticket.id,
title: ticket.title,
open: ticket.open,
createdAt: ticket.createdAt,
id: BigInt(result.insertId),
title,
open: true,
createdAt: now,
},
},
{ status: 201 },
+15 -13
View File
@@ -5,8 +5,9 @@
// page (src/app/u/[username]/page.tsx). Never exposes password / auth_ticket /
// 2FA secrets / pincode / mail.
import { eq } from "drizzle-orm";
import { apiJson } from "@/lib/api";
import { prisma } from "@/lib/prisma";
import { db, User } from "@/lib/db";
export const dynamic = "force-dynamic";
@@ -17,18 +18,19 @@ export async function GET(
const { username } = await params;
try {
const user = await prisma.user.findUnique({
where: { username },
select: {
username: true,
look: true,
motto: true,
rank: true,
credits: true,
online: true,
accountCreated: true,
},
});
const [user] = await db
.select({
username: User.username,
look: User.look,
motto: User.motto,
rank: User.rank,
credits: User.credits,
online: User.online,
accountCreated: User.accountCreated,
})
.from(User)
.where(eq(User.username, username))
.limit(1);
if (!user) {
return apiJson({ error: "User not found" }, { status: 404 });
+29 -20
View File
@@ -1,8 +1,9 @@
// Public REST: a single rare furni trade value by id (website_rare_values),
// together with its parent category. AtomCMS JSON API parity. Returns { error }
// (404) when the id is unknown.
import { eq } from "drizzle-orm";
import { apiJson } from "@/lib/api";
import { prisma } from "@/lib/prisma";
import { db, WebsiteRareValueCategories, WebsiteRareValues } from "@/lib/db";
export const dynamic = "force-dynamic";
@@ -25,21 +26,22 @@ export async function GET(
}
try {
const value = await prisma.websiteRareValues.findUnique({
where: { id: valueId },
select: {
id: true,
categoryId: true,
itemId: true,
name: true,
creditValue: true,
currencyValue: true,
currencyType: true,
furnitureIcon: true,
createdAt: true,
updatedAt: true,
},
});
const [value] = await db
.select({
id: WebsiteRareValues.id,
categoryId: WebsiteRareValues.categoryId,
itemId: WebsiteRareValues.itemId,
name: WebsiteRareValues.name,
creditValue: WebsiteRareValues.creditValue,
currencyValue: WebsiteRareValues.currencyValue,
currencyType: WebsiteRareValues.currencyType,
furnitureIcon: WebsiteRareValues.furnitureIcon,
createdAt: WebsiteRareValues.createdAt,
updatedAt: WebsiteRareValues.updatedAt,
})
.from(WebsiteRareValues)
.where(eq(WebsiteRareValues.id, valueId))
.limit(1);
if (!value) {
return apiJson({ error: "Rare value not found" }, { status: 404 });
@@ -54,10 +56,17 @@ export async function GET(
priority: number;
} | null = null;
try {
category = await prisma.websiteRareValueCategories.findUnique({
where: { id: value.categoryId },
select: { id: true, name: true, badge: true, priority: true },
});
const [row] = await db
.select({
id: WebsiteRareValueCategories.id,
name: WebsiteRareValueCategories.name,
badge: WebsiteRareValueCategories.badge,
priority: WebsiteRareValueCategories.priority,
})
.from(WebsiteRareValueCategories)
.where(eq(WebsiteRareValueCategories.id, value.categoryId))
.limit(1);
category = row ?? null;
} catch {
category = null;
}
+14 -10
View File
@@ -1,22 +1,26 @@
// Public REST: rare value categories (website_rare_value_categories).
// AtomCMS JSON API parity — read-only list ordered by priority then name,
// matching the admin /admin/rare-values query.
import { asc } from "drizzle-orm";
import { apiJson } from "@/lib/api";
import { prisma } from "@/lib/prisma";
import { db, WebsiteRareValueCategories } from "@/lib/db";
export const dynamic = "force-dynamic";
export async function GET(_req: Request) {
try {
const data = await prisma.websiteRareValueCategories.findMany({
orderBy: [{ priority: "asc" }, { name: "asc" }],
select: {
id: true,
name: true,
badge: true,
priority: true,
},
});
const data = await db
.select({
id: WebsiteRareValueCategories.id,
name: WebsiteRareValueCategories.name,
badge: WebsiteRareValueCategories.badge,
priority: WebsiteRareValueCategories.priority,
})
.from(WebsiteRareValueCategories)
.orderBy(
asc(WebsiteRareValueCategories.priority),
asc(WebsiteRareValueCategories.name),
);
return apiJson({ data });
} catch {
+30 -23
View File
@@ -1,8 +1,9 @@
// Public REST: rare furni trade values (website_rare_values).
// AtomCMS JSON API parity — read-only catalog of rares with their credit /
// currency values. Supports ?category=<id> filter; paginated, ordered by name.
import { asc, count, eq } from "drizzle-orm";
import { apiError, apiJson, pagination, positiveBigInt } from "@/lib/api";
import { prisma } from "@/lib/prisma";
import { db, WebsiteRareValues } from "@/lib/db";
export const dynamic = "force-dynamic";
@@ -12,33 +13,39 @@ export async function GET(req: Request) {
// Optional ?category=<id> filter (category_id is a BigInt).
const categoryRaw = sp.get("category");
let where: { categoryId?: bigint } = {};
let categoryId: bigint | undefined;
if (categoryRaw !== null) {
const categoryId = positiveBigInt(categoryRaw);
if (!categoryId) return apiError("A valid category id is required", 422);
where = { categoryId };
const parsed = positiveBigInt(categoryRaw);
if (!parsed) return apiError("A valid category id is required", 422);
categoryId = parsed;
}
try {
const [total, data] = await Promise.all([
prisma.websiteRareValues.count({ where }),
prisma.websiteRareValues.findMany({
where,
orderBy: { name: "asc" },
skip,
take,
select: {
id: true,
categoryId: true,
itemId: true,
name: true,
creditValue: true,
currencyValue: true,
currencyType: true,
furnitureIcon: true,
},
}),
const where =
categoryId !== undefined
? eq(WebsiteRareValues.categoryId, categoryId)
: undefined;
const [totalRows, data] = await Promise.all([
db.select({ total: count() }).from(WebsiteRareValues).where(where),
db
.select({
id: WebsiteRareValues.id,
categoryId: WebsiteRareValues.categoryId,
itemId: WebsiteRareValues.itemId,
name: WebsiteRareValues.name,
creditValue: WebsiteRareValues.creditValue,
currencyValue: WebsiteRareValues.currencyValue,
currencyType: WebsiteRareValues.currencyType,
furnitureIcon: WebsiteRareValues.furnitureIcon,
})
.from(WebsiteRareValues)
.where(where)
.orderBy(asc(WebsiteRareValues.name))
.limit(take)
.offset(skip),
]);
const total = totalRows[0]?.total ?? 0;
return apiJson({
data,