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

Co-authored-by: Cursor <[email protected]>
This commit is contained in:
SimoandCursor committed 2026-08-01 14:15:32 +02:00
1 parent 2cd0863cb8
commit 580972c0a0
40 files changed
+1089 -808

No files matched your search

+98 -73
View File
@@ -1,12 +1,28 @@
import {
and,
asc,
count,
desc,
eq,
inArray,
like,
or,
type SQL,
} from "drizzle-orm";
import { redirect } from "next/navigation";
import { getTranslations } from "next-intl/server";
import { StatusCard } from "@/components/admin/dashboard";
import { TicketQueueBanner } from "@/components/admin/ticket-queue-banner";
import { fetchTicketQueueOpenCounts } from "@/lib/admin/ticket-queue-counts";
import { calcPagination, parseListParams } from "@/lib/admin-helpers";
import {
db,
User,
WebsiteHelpCenterTicketReplies,
WebsiteHelpCenterTickets,
} from "@/lib/db";
import { formatDate } from "@/lib/format-date";
import { canAccess, getAdminContext, PERMS } from "@/lib/permissions";
import { prisma } from "@/lib/prisma";
import { type HelpTicketRow, HelpTicketsTable } from "./help-tickets-table";
export default async function AdminHelpTicketsPage({
@@ -24,87 +40,95 @@ export default async function AdminHelpTicketsPage({
const parsed = parseListParams(new URLSearchParams(raw));
const statusFilter = raw.filter_status || "open";
const conditions: any[] = [];
const conditions: SQL[] = [];
if (statusFilter === "open") {
conditions.push({ open: true });
conditions.push(eq(WebsiteHelpCenterTickets.open, true));
} else if (statusFilter === "closed") {
conditions.push({ open: false });
conditions.push(eq(WebsiteHelpCenterTickets.open, false));
}
if (parsed.search.trim()) {
const q = parsed.search.trim();
const asId = /^\d+$/.test(q) ? BigInt(q) : null;
const matchingUsers = await prisma.user
.findMany({
where: { username: { contains: q } },
select: { id: true },
take: 50,
})
const matchingUsers = await db
.select({ id: User.id })
.from(User)
.where(like(User.username, `%${q}%`))
.limit(50)
.catch(() => []);
conditions.push({
OR: [
{ title: { contains: q } },
{ content: { contains: q } },
...(matchingUsers.length > 0
? [{ userId: { in: matchingUsers.map((u) => u.id) } }]
: []),
...(asId !== null ? [{ id: asId }] : []),
],
});
const searchOr = [
like(WebsiteHelpCenterTickets.title, `%${q}%`),
like(WebsiteHelpCenterTickets.content, `%${q}%`),
...(matchingUsers.length > 0
? [
inArray(
WebsiteHelpCenterTickets.userId,
matchingUsers.map((u) => u.id),
),
]
: []),
...(asId !== null ? [eq(WebsiteHelpCenterTickets.id, asId)] : []),
];
conditions.push(or(...searchOr)!);
}
const where: any =
conditions.length === 0
? {}
: conditions.length === 1
? conditions[0]
: { AND: conditions };
const where = conditions.length > 0 ? and(...conditions) : undefined;
const SORT_MAP: Record<string, any> = {
title: { title: "asc" },
open: { open: "desc" },
user: { userId: "asc" },
date: { createdAt: "desc" },
updated: { updatedAt: "desc" },
id: { id: "desc" },
};
const orderCol =
parsed.sort === "title"
? WebsiteHelpCenterTickets.title
: parsed.sort === "open"
? WebsiteHelpCenterTickets.open
: parsed.sort === "user"
? WebsiteHelpCenterTickets.userId
: parsed.sort === "date"
? WebsiteHelpCenterTickets.createdAt
: parsed.sort === "id"
? WebsiteHelpCenterTickets.id
: WebsiteHelpCenterTickets.updatedAt;
const orderBy = parsed.order === "asc" ? asc(orderCol) : desc(orderCol);
const baseOrder = SORT_MAP[parsed.sort ?? "updated"] ?? { updatedAt: "desc" };
const orderField = Object.keys(baseOrder)[0] as keyof typeof baseOrder;
const orderBy = {
[orderField]: parsed.order,
} as any;
const [total, openCount, closedCount, queues] = await Promise.all([
prisma.websiteHelpCenterTickets.count({ where }).catch(() => 0),
prisma.websiteHelpCenterTickets
.count({ where: { open: true } })
.catch(() => 0),
prisma.websiteHelpCenterTickets
.count({ where: { open: false } })
.catch(() => 0),
const [totalRows, openRows, closedRows, queues] = await Promise.all([
db
.select({ total: count() })
.from(WebsiteHelpCenterTickets)
.where(where)
.catch(() => [{ total: 0 }]),
db
.select({ total: count() })
.from(WebsiteHelpCenterTickets)
.where(eq(WebsiteHelpCenterTickets.open, true))
.catch(() => [{ total: 0 }]),
db
.select({ total: count() })
.from(WebsiteHelpCenterTickets)
.where(eq(WebsiteHelpCenterTickets.open, false))
.catch(() => [{ total: 0 }]),
fetchTicketQueueOpenCounts(),
]);
const total = Number(totalRows[0]?.total ?? 0);
const openCount = Number(openRows[0]?.total ?? 0);
const closedCount = Number(closedRows[0]?.total ?? 0);
const pagination = calcPagination(total, parsed.page, parsed.perPage);
const tickets = await prisma.websiteHelpCenterTickets
.findMany({
where,
orderBy,
skip: pagination.offset,
take: pagination.perPage,
select: {
id: true,
userId: true,
title: true,
open: true,
createdAt: true,
updatedAt: true,
},
const tickets = await db
.select({
id: WebsiteHelpCenterTickets.id,
userId: WebsiteHelpCenterTickets.userId,
title: WebsiteHelpCenterTickets.title,
open: WebsiteHelpCenterTickets.open,
createdAt: WebsiteHelpCenterTickets.createdAt,
updatedAt: WebsiteHelpCenterTickets.updatedAt,
})
.from(WebsiteHelpCenterTickets)
.where(where)
.orderBy(orderBy)
.limit(pagination.perPage)
.offset(pagination.offset)
.catch(() => []);
const ticketIds = tickets.map((ticket) => ticket.id);
@@ -118,26 +142,27 @@ export default async function AdminHelpTicketsPage({
const [replyGroups, users] = await Promise.all([
ticketIds.length > 0
? prisma.websiteHelpCenterTicketReplies
.groupBy({
by: ["ticketId"],
where: { ticketId: { in: ticketIds } },
_count: true,
? db
.select({
ticketId: WebsiteHelpCenterTicketReplies.ticketId,
total: count(),
})
.from(WebsiteHelpCenterTicketReplies)
.where(inArray(WebsiteHelpCenterTicketReplies.ticketId, ticketIds))
.groupBy(WebsiteHelpCenterTicketReplies.ticketId)
.catch(() => [])
: Promise.resolve([]),
userIds.length > 0
? prisma.user
.findMany({
where: { id: { in: userIds } },
select: { id: true, username: true },
})
? db
.select({ id: User.id, username: User.username })
.from(User)
.where(inArray(User.id, userIds))
.catch(() => [])
: Promise.resolve([]),
]);
const replyCountByTicket = new Map(
replyGroups.map((g) => [String(g.ticketId), g._count]),
replyGroups.map((g) => [String(g.ticketId), Number(g.total)]),
);
const usernameById = new Map(users.map((u) => [u.id, u.username]));