refactor(db): migrate app pages and APIs from Prisma facade to Drizzle (4)
Co-authored-by: Cursor <[email protected]>
This commit is contained in:
1 parent
580972c0a0
commit
9aa4f331bf
40 files changed
+1259
-927
No files matched your search
@@ -1,7 +1,8 @@
|
||||
import { asc, count, eq, inArray, like, or } from "drizzle-orm";
|
||||
import { withAdmin } from "@/lib/api-handler";
|
||||
import { apiError, apiOk } from "@/lib/api-response";
|
||||
import { CatalogItemsBc, CatalogPages, CatalogPagesBc, db } from "@/lib/db";
|
||||
import { PERMS } from "@/lib/permissions";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
import {
|
||||
deletePage,
|
||||
getAncestors,
|
||||
@@ -26,10 +27,16 @@ type PageRow = {
|
||||
|
||||
async function getBcTreeFlat(): Promise<TreeNode[]> {
|
||||
const [allPages, itemCounts] = await Promise.all([
|
||||
prisma.catalogPagesBc.findMany({ orderBy: { orderNum: "asc" } }),
|
||||
prisma.catalogItemsBc.groupBy({ by: ["pageId"], _count: true }),
|
||||
db.select().from(CatalogPagesBc).orderBy(asc(CatalogPagesBc.orderNum)),
|
||||
db
|
||||
.select({
|
||||
pageId: CatalogItemsBc.pageId,
|
||||
total: count(),
|
||||
})
|
||||
.from(CatalogItemsBc)
|
||||
.groupBy(CatalogItemsBc.pageId),
|
||||
]);
|
||||
const itemCountMap = new Map(itemCounts.map((c) => [c.pageId, c._count]));
|
||||
const itemCountMap = new Map(itemCounts.map((c) => [c.pageId, c.total]));
|
||||
const childCountMap = new Map<number, number>();
|
||||
for (const p of allPages) {
|
||||
childCountMap.set(p.parentId, (childCountMap.get(p.parentId) ?? 0) + 1);
|
||||
@@ -91,37 +98,49 @@ async function getChildren(
|
||||
): Promise<TreeNode[]> {
|
||||
// Roots in Habbo DBs are usually parent_id = -1, sometimes 0.
|
||||
const parentFilter =
|
||||
parentId <= 0 ? { OR: [{ parentId: -1 }, { parentId: 0 }] } : { parentId };
|
||||
parentId <= 0
|
||||
? or(eq(CatalogPages.parentId, -1), eq(CatalogPages.parentId, 0))
|
||||
: eq(CatalogPages.parentId, parentId);
|
||||
const parentFilterBc =
|
||||
parentId <= 0
|
||||
? or(eq(CatalogPagesBc.parentId, -1), eq(CatalogPagesBc.parentId, 0))
|
||||
: eq(CatalogPagesBc.parentId, parentId);
|
||||
|
||||
if (isBc) {
|
||||
const pages = await prisma.catalogPagesBc.findMany({
|
||||
where: parentFilter,
|
||||
orderBy: { orderNum: "asc" },
|
||||
});
|
||||
const pages = await db
|
||||
.select()
|
||||
.from(CatalogPagesBc)
|
||||
.where(parentFilterBc)
|
||||
.orderBy(asc(CatalogPagesBc.orderNum));
|
||||
const ids = pages.map((p) => toInt(p.id));
|
||||
const [childCounts, itemCounts] = await Promise.all([
|
||||
ids.length
|
||||
? prisma.catalogPagesBc.groupBy({
|
||||
by: ["parentId"],
|
||||
where: { parentId: { in: ids } },
|
||||
_count: true,
|
||||
})
|
||||
? db
|
||||
.select({
|
||||
parentId: CatalogPagesBc.parentId,
|
||||
total: count(),
|
||||
})
|
||||
.from(CatalogPagesBc)
|
||||
.where(inArray(CatalogPagesBc.parentId, ids))
|
||||
.groupBy(CatalogPagesBc.parentId)
|
||||
: Promise.resolve([]),
|
||||
ids.length
|
||||
? prisma.catalogItemsBc
|
||||
.groupBy({
|
||||
by: ["pageId"],
|
||||
where: { pageId: { in: ids } },
|
||||
_count: true,
|
||||
? db
|
||||
.select({
|
||||
pageId: CatalogItemsBc.pageId,
|
||||
total: count(),
|
||||
})
|
||||
.from(CatalogItemsBc)
|
||||
.where(inArray(CatalogItemsBc.pageId, ids))
|
||||
.groupBy(CatalogItemsBc.pageId)
|
||||
.catch(() => [])
|
||||
: Promise.resolve([]),
|
||||
]);
|
||||
const childMap = Object.fromEntries(
|
||||
childCounts.map((c) => [toInt(c.parentId), c._count]),
|
||||
childCounts.map((c) => [toInt(c.parentId), c.total]),
|
||||
);
|
||||
const itemMap = Object.fromEntries(
|
||||
itemCounts.map((c) => [toInt(c.pageId), c._count]),
|
||||
itemCounts.map((c) => [toInt(c.pageId), c.total]),
|
||||
);
|
||||
return pages.map((p) => {
|
||||
const id = toInt(p.id);
|
||||
@@ -143,23 +162,27 @@ async function getChildren(
|
||||
});
|
||||
}
|
||||
|
||||
const pages = await prisma.catalogPages.findMany({
|
||||
where: parentFilter,
|
||||
orderBy: { orderNum: "asc" },
|
||||
});
|
||||
const pages = await db
|
||||
.select()
|
||||
.from(CatalogPages)
|
||||
.where(parentFilter)
|
||||
.orderBy(asc(CatalogPages.orderNum));
|
||||
const ids = pages.map((p) => toInt(p.id));
|
||||
const [childCounts, itemMap] = await Promise.all([
|
||||
ids.length
|
||||
? prisma.catalogPages.groupBy({
|
||||
by: ["parentId"],
|
||||
where: { parentId: { in: ids } },
|
||||
_count: true,
|
||||
})
|
||||
? db
|
||||
.select({
|
||||
parentId: CatalogPages.parentId,
|
||||
total: count(),
|
||||
})
|
||||
.from(CatalogPages)
|
||||
.where(inArray(CatalogPages.parentId, ids))
|
||||
.groupBy(CatalogPages.parentId)
|
||||
: Promise.resolve([]),
|
||||
getCatalogItemCounts(ids),
|
||||
]);
|
||||
const childMap = Object.fromEntries(
|
||||
childCounts.map((c) => [toInt(c.parentId), c._count]),
|
||||
childCounts.map((c) => [toInt(c.parentId), c.total]),
|
||||
);
|
||||
return pages.map((p) => {
|
||||
const id = toInt(p.id);
|
||||
@@ -184,19 +207,17 @@ async function getChildren(
|
||||
async function searchPages(q: string, isBc: boolean): Promise<TreeNode[]> {
|
||||
const needle = q.trim();
|
||||
const idExact = Number.parseInt(needle, 10);
|
||||
const idFilter =
|
||||
Number.isFinite(idExact) && String(idExact) === needle
|
||||
? [{ id: idExact }]
|
||||
: [];
|
||||
const idOk = Number.isFinite(idExact) && String(idExact) === needle;
|
||||
|
||||
if (isBc) {
|
||||
const pages = await prisma.catalogPagesBc.findMany({
|
||||
where: {
|
||||
OR: [{ caption: { contains: needle } }, ...idFilter],
|
||||
},
|
||||
orderBy: { orderNum: "asc" },
|
||||
take: 50,
|
||||
});
|
||||
const conditions = [like(CatalogPagesBc.caption, `%${needle}%`)];
|
||||
if (idOk) conditions.push(eq(CatalogPagesBc.id, idExact));
|
||||
const pages = await db
|
||||
.select()
|
||||
.from(CatalogPagesBc)
|
||||
.where(or(...conditions))
|
||||
.orderBy(asc(CatalogPagesBc.orderNum))
|
||||
.limit(50);
|
||||
return pages.map((p) =>
|
||||
toTreeNode(
|
||||
{
|
||||
@@ -216,26 +237,30 @@ async function searchPages(q: string, isBc: boolean): Promise<TreeNode[]> {
|
||||
);
|
||||
}
|
||||
|
||||
const pages = await prisma.catalogPages.findMany({
|
||||
where: {
|
||||
OR: [{ caption: { contains: needle } }, ...idFilter],
|
||||
},
|
||||
orderBy: { orderNum: "asc" },
|
||||
take: 50,
|
||||
});
|
||||
const conditions = [like(CatalogPages.caption, `%${needle}%`)];
|
||||
if (idOk) conditions.push(eq(CatalogPages.id, idExact));
|
||||
const pages = await db
|
||||
.select()
|
||||
.from(CatalogPages)
|
||||
.where(or(...conditions))
|
||||
.orderBy(asc(CatalogPages.orderNum))
|
||||
.limit(50);
|
||||
const ids = pages.map((p) => toInt(p.id));
|
||||
const [childCounts, itemMap] = await Promise.all([
|
||||
ids.length
|
||||
? prisma.catalogPages.groupBy({
|
||||
by: ["parentId"],
|
||||
where: { parentId: { in: ids } },
|
||||
_count: true,
|
||||
})
|
||||
? db
|
||||
.select({
|
||||
parentId: CatalogPages.parentId,
|
||||
total: count(),
|
||||
})
|
||||
.from(CatalogPages)
|
||||
.where(inArray(CatalogPages.parentId, ids))
|
||||
.groupBy(CatalogPages.parentId)
|
||||
: Promise.resolve([]),
|
||||
getCatalogItemCounts(ids),
|
||||
]);
|
||||
const childMap = Object.fromEntries(
|
||||
childCounts.map((c) => [toInt(c.parentId), c._count]),
|
||||
childCounts.map((c) => [toInt(c.parentId), c.total]),
|
||||
);
|
||||
return pages.map((p) => {
|
||||
const id = toInt(p.id);
|
||||
@@ -273,11 +298,19 @@ export const GET = withAdmin(
|
||||
const id = Number(pageIdRaw);
|
||||
if (!Number.isFinite(id) || id <= 0) return apiError("Invalid pageId");
|
||||
if (isBc) {
|
||||
const page = await prisma.catalogPagesBc.findUnique({ where: { id } });
|
||||
const [page] = await db
|
||||
.select()
|
||||
.from(CatalogPagesBc)
|
||||
.where(eq(CatalogPagesBc.id, id))
|
||||
.limit(1);
|
||||
if (!page) return apiError("Page not found", 404);
|
||||
return apiOk({ page });
|
||||
}
|
||||
const page = await prisma.catalogPages.findUnique({ where: { id } });
|
||||
const [page] = await db
|
||||
.select()
|
||||
.from(CatalogPages)
|
||||
.where(eq(CatalogPages.id, id))
|
||||
.limit(1);
|
||||
if (!page) return apiError("Page not found", 404);
|
||||
return apiOk({ page });
|
||||
}
|
||||
@@ -290,10 +323,15 @@ export const GET = withAdmin(
|
||||
const chain: Array<{ id: number; caption: string }> = [];
|
||||
let currentId = id;
|
||||
for (let i = 0; i < 20; i++) {
|
||||
const page = await prisma.catalogPagesBc.findUnique({
|
||||
where: { id: currentId },
|
||||
select: { id: true, caption: true, parentId: true },
|
||||
});
|
||||
const [page] = await db
|
||||
.select({
|
||||
id: CatalogPagesBc.id,
|
||||
caption: CatalogPagesBc.caption,
|
||||
parentId: CatalogPagesBc.parentId,
|
||||
})
|
||||
.from(CatalogPagesBc)
|
||||
.where(eq(CatalogPagesBc.id, currentId))
|
||||
.limit(1);
|
||||
if (!page) break;
|
||||
chain.unshift({ id: page.id, caption: page.caption });
|
||||
if (page.parentId <= 0) break;
|
||||
@@ -340,29 +378,37 @@ export const PATCH = withAdmin(
|
||||
}
|
||||
const isBc = body.catalog === "bc";
|
||||
if (isBc) {
|
||||
const page = await prisma.catalogPagesBc.findUnique({
|
||||
where: { id: pageId },
|
||||
select: { enabled: true, visible: true },
|
||||
});
|
||||
const [page] = await db
|
||||
.select({
|
||||
enabled: CatalogPagesBc.enabled,
|
||||
visible: CatalogPagesBc.visible,
|
||||
})
|
||||
.from(CatalogPagesBc)
|
||||
.where(eq(CatalogPagesBc.id, pageId))
|
||||
.limit(1);
|
||||
if (!page) return apiError("Page not found", 404);
|
||||
const current = field === "enabled" ? page.enabled : page.visible;
|
||||
await prisma.catalogPagesBc.update({
|
||||
where: { id: pageId },
|
||||
data: { [field]: current === "1" ? "0" : "1" },
|
||||
});
|
||||
await db
|
||||
.update(CatalogPagesBc)
|
||||
.set({ [field]: current === "1" ? "0" : "1" })
|
||||
.where(eq(CatalogPagesBc.id, pageId));
|
||||
await rcon.updateCatalog();
|
||||
return apiOk({});
|
||||
}
|
||||
const page = await prisma.catalogPages.findUnique({
|
||||
where: { id: pageId },
|
||||
select: { enabled: true, visible: true },
|
||||
});
|
||||
const [page] = await db
|
||||
.select({
|
||||
enabled: CatalogPages.enabled,
|
||||
visible: CatalogPages.visible,
|
||||
})
|
||||
.from(CatalogPages)
|
||||
.where(eq(CatalogPages.id, pageId))
|
||||
.limit(1);
|
||||
if (!page) return apiError("Page not found", 404);
|
||||
const current = field === "enabled" ? page.enabled : page.visible;
|
||||
await prisma.catalogPages.update({
|
||||
where: { id: pageId },
|
||||
data: { [field]: current === "1" ? "0" : "1" },
|
||||
});
|
||||
await db
|
||||
.update(CatalogPages)
|
||||
.set({ [field]: current === "1" ? "0" : "1" })
|
||||
.where(eq(CatalogPages.id, pageId));
|
||||
await rcon.updateCatalog();
|
||||
return apiOk({});
|
||||
},
|
||||
@@ -379,43 +425,46 @@ export const DELETE = withAdmin(
|
||||
return apiError("Invalid pageId");
|
||||
}
|
||||
if (isBc) {
|
||||
const page = await prisma.catalogPagesBc.findUnique({
|
||||
where: { id: pageId },
|
||||
select: { parentId: true },
|
||||
});
|
||||
const [page] = await db
|
||||
.select({ parentId: CatalogPagesBc.parentId })
|
||||
.from(CatalogPagesBc)
|
||||
.where(eq(CatalogPagesBc.id, pageId))
|
||||
.limit(1);
|
||||
if (!page) return apiError("Page not found", 404);
|
||||
if (mode === "reparent") {
|
||||
await prisma.$transaction([
|
||||
prisma.catalogPagesBc.updateMany({
|
||||
where: { parentId: pageId },
|
||||
data: { parentId: page.parentId },
|
||||
}),
|
||||
prisma.catalogItemsBc.deleteMany({ where: { pageId } }),
|
||||
prisma.catalogPagesBc.delete({ where: { id: pageId } }),
|
||||
]);
|
||||
await db.transaction(async (tx) => {
|
||||
await tx
|
||||
.update(CatalogPagesBc)
|
||||
.set({ parentId: page.parentId })
|
||||
.where(eq(CatalogPagesBc.parentId, pageId));
|
||||
await tx
|
||||
.delete(CatalogItemsBc)
|
||||
.where(eq(CatalogItemsBc.pageId, pageId));
|
||||
await tx.delete(CatalogPagesBc).where(eq(CatalogPagesBc.id, pageId));
|
||||
});
|
||||
} else {
|
||||
// Deep cascade: collect all descendants
|
||||
const toDelete: number[] = [pageId];
|
||||
const queue: number[] = [pageId];
|
||||
while (queue.length > 0) {
|
||||
const children = await prisma.catalogPagesBc.findMany({
|
||||
where: { parentId: { in: queue } },
|
||||
select: { id: true },
|
||||
});
|
||||
const children = await db
|
||||
.select({ id: CatalogPagesBc.id })
|
||||
.from(CatalogPagesBc)
|
||||
.where(inArray(CatalogPagesBc.parentId, queue));
|
||||
queue.length = 0;
|
||||
for (const child of children) {
|
||||
toDelete.push(child.id);
|
||||
queue.push(child.id);
|
||||
}
|
||||
}
|
||||
await prisma.$transaction([
|
||||
prisma.catalogItemsBc.deleteMany({
|
||||
where: { pageId: { in: toDelete } },
|
||||
}),
|
||||
prisma.catalogPagesBc.deleteMany({
|
||||
where: { id: { in: toDelete } },
|
||||
}),
|
||||
]);
|
||||
await db.transaction(async (tx) => {
|
||||
await tx
|
||||
.delete(CatalogItemsBc)
|
||||
.where(inArray(CatalogItemsBc.pageId, toDelete));
|
||||
await tx
|
||||
.delete(CatalogPagesBc)
|
||||
.where(inArray(CatalogPagesBc.id, toDelete));
|
||||
});
|
||||
}
|
||||
await rcon.updateCatalog();
|
||||
return apiOk({});
|
||||
|
||||
Reference in new issue
Block a user