refactor: replace Prisma ORM runtime with Drizzle ORM facade
- Replace Prisma client runtime with Drizzle ORM (zero Prisma engine/query engine in production) - Add Prisma-compatible facade (@/lib/prisma-facade.ts) backed by Drizzle for backwards compatibility - Runtime queries route through Drizzle ORM; @prisma/client is now devDependency (types only) - Remove @prisma/adapter-mariadb dependency; delete prisma-pool.ts and types/prisma.ts - New Drizzle schema layer: src/db/schema.ts (176 tables) and src/lib/db.ts (connection) - Update README documenting the dual-layer ORM architecture - Restore src/generated/ gitignore (build artifact for local type generation) - 0 TypeScript errors, 583 tests passing The facade intentionally uses `any` types to match the Prisma Client API surface, allowing existing code to run unmodified while routing queries through Drizzle at runtime.
This commit is contained in:
1 parent
9d1c71d926
commit
56061e41d4
52 files changed
+5825
-748
No files matched your search
@@ -1,14 +1,12 @@
|
||||
"use server";
|
||||
|
||||
import { revalidatePath } from "next/cache";
|
||||
import type { $Enums } from "@/generated/prisma/client";
|
||||
import { requirePermissionRateLimited } from "@/lib/admin/guard";
|
||||
import { PERMS } from "@/lib/permissions";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
import { rcon } from "@/lib/services/rcon";
|
||||
import { logStaffActivity } from "@/lib/services/staff-activity";
|
||||
|
||||
type BanType = $Enums.bans_type;
|
||||
const BAN_TYPES: ReadonlySet<string> = new Set([
|
||||
"account",
|
||||
"ip",
|
||||
@@ -45,7 +43,7 @@ export async function createBan(formData: FormData): Promise<void> {
|
||||
timestamp: now,
|
||||
banExpire,
|
||||
banReason: reason,
|
||||
type: type as BanType,
|
||||
type: type as any,
|
||||
cfhTopic: -1,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
"use server";
|
||||
|
||||
import { revalidatePath } from "next/cache";
|
||||
import type { Prisma } from "@/generated/prisma/client";
|
||||
import { requirePermission } from "@/lib/admin/guard";
|
||||
import { PERMS } from "@/lib/permissions";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
@@ -58,7 +57,7 @@ export async function updateBcPage({
|
||||
}
|
||||
await prisma.catalogPagesBc.update({
|
||||
where: { id },
|
||||
data: data as Prisma.CatalogPagesBcUpdateInput,
|
||||
data: data as any,
|
||||
});
|
||||
await rcon.updateCatalog();
|
||||
await logStaffActivity({
|
||||
@@ -104,7 +103,7 @@ export async function updateBcItem({
|
||||
}
|
||||
await prisma.catalogItemsBc.update({
|
||||
where: { id },
|
||||
data: safe as Prisma.CatalogItemsBcUpdateInput,
|
||||
data: safe as any,
|
||||
});
|
||||
await rcon.updateCatalog();
|
||||
await logStaffActivity({
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"use server";
|
||||
|
||||
import { sql } from "drizzle-orm";
|
||||
import { revalidatePath } from "next/cache";
|
||||
import { Prisma } from "@/generated/prisma/client";
|
||||
import { requirePermission } from "@/lib/admin/guard";
|
||||
import { PERMS } from "@/lib/permissions";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
@@ -252,7 +252,7 @@ export async function moveCatalogItems({
|
||||
await prisma.$executeRaw`
|
||||
UPDATE catalog_items
|
||||
SET page_id = ${pageIdStr}
|
||||
WHERE id IN (${Prisma.join(ids)})
|
||||
WHERE id IN (${sql.join(ids, sql`, `)})
|
||||
`;
|
||||
await rcon.updateCatalog();
|
||||
revalidatePath("/admin/catalog");
|
||||
@@ -301,7 +301,7 @@ export async function updateCatalogItem({
|
||||
if (Object.keys(safeCatalog).length > 0) {
|
||||
await prisma.catalogItems.update({
|
||||
where: { id },
|
||||
data: safeCatalog as Prisma.CatalogItemsUpdateInput,
|
||||
data: safeCatalog as any,
|
||||
});
|
||||
}
|
||||
if (baseItem) {
|
||||
@@ -309,7 +309,7 @@ export async function updateCatalogItem({
|
||||
if (Object.keys(safeBase).length > 0) {
|
||||
await prisma.itemsBase.update({
|
||||
where: { id: baseItem.id },
|
||||
data: safeBase as Prisma.ItemsBaseUpdateInput,
|
||||
data: safeBase as any,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
"use server";
|
||||
|
||||
import { revalidatePath } from "next/cache";
|
||||
import type { Prisma } from "@/generated/prisma/client";
|
||||
import { requirePermission } from "@/lib/admin/guard";
|
||||
import { PERMS } from "@/lib/permissions";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
@@ -57,7 +56,7 @@ export async function updateCatalogPage({
|
||||
}
|
||||
await prisma.catalogPages.update({
|
||||
where: { id },
|
||||
data: data as Prisma.CatalogPagesUpdateInput,
|
||||
data: data as any,
|
||||
});
|
||||
await rcon.updateCatalog();
|
||||
await logStaffActivity({
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
import { revalidatePath } from "next/cache";
|
||||
import { redirect } from "next/navigation";
|
||||
import type { Prisma } from "@/generated/prisma/client";
|
||||
import { auth } from "@/lib/auth";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
import { clientIp, rateLimit } from "@/lib/rate-limit";
|
||||
@@ -90,7 +89,7 @@ export async function buyBadge(formData: FormData): Promise<void> {
|
||||
// between the two operations cannot orphan the user.
|
||||
if (price > 0) {
|
||||
await prisma.$transaction(
|
||||
async (tx: Prisma.TransactionClient) => {
|
||||
async (tx) => {
|
||||
await tx.user.update({
|
||||
where: { id: userId },
|
||||
data: { credits: { decrement: price } },
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
import { revalidatePath } from "next/cache";
|
||||
import { z } from "zod";
|
||||
import { Prisma } from "@/generated/prisma/client";
|
||||
import { PERMS } from "@/lib/permissions";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
import { adminAction } from "@/lib/safe-action";
|
||||
@@ -92,12 +91,12 @@ export const updateItemsBase = adminAction(
|
||||
if (data[key] !== undefined) data[key] = Number(data[key]);
|
||||
}
|
||||
if (data.stackHeight !== undefined) {
|
||||
data.stackHeight = new Prisma.Decimal(String(data.stackHeight));
|
||||
data.stackHeight = Number(data.stackHeight);
|
||||
}
|
||||
|
||||
await prisma.itemsBase.update({
|
||||
where: { id },
|
||||
data: data as Prisma.ItemsBaseUpdateInput,
|
||||
data: data as any,
|
||||
});
|
||||
await rcon.updateCatalog().catch(() => false);
|
||||
await logStaffActivity({
|
||||
|
||||
+74
-47
@@ -1,9 +1,18 @@
|
||||
"use server";
|
||||
|
||||
import { and, count, eq, inArray, sql } from "drizzle-orm";
|
||||
import type { ResultSetHeader } from "mysql2";
|
||||
import { revalidateTag } from "next/cache";
|
||||
import { z } from "zod";
|
||||
import {
|
||||
AclModelPermission,
|
||||
AclModelRole,
|
||||
AclPermission,
|
||||
AclRole,
|
||||
db,
|
||||
User,
|
||||
} from "@/lib/db";
|
||||
import { PERMS } from "@/lib/permission-slugs";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
import { adminAction } from "@/lib/safe-action";
|
||||
import { ActionError, actionOk } from "@/lib/safe-action-shared";
|
||||
import {
|
||||
@@ -22,16 +31,15 @@ const createRankSchema = z.object({
|
||||
export const createRank = adminAction(
|
||||
{ schema: createRankSchema, permission: PERMS.PERMISSIONS_MANAGE },
|
||||
async (ctx) => {
|
||||
const id = await createEmulatorRank(prisma, ctx.data);
|
||||
await prisma.aclRole.upsert({
|
||||
where: { slug: `rank_${id}` },
|
||||
create: {
|
||||
const id = await createEmulatorRank(db, ctx.data);
|
||||
await db
|
||||
.insert(AclRole)
|
||||
.values({
|
||||
slug: `rank_${id}`,
|
||||
title: ctx.data.rank_name,
|
||||
description: "CMS role synchronized from permission_ranks",
|
||||
},
|
||||
update: { title: ctx.data.rank_name },
|
||||
});
|
||||
})
|
||||
.onDuplicateKeyUpdate({ set: { title: ctx.data.rank_name } });
|
||||
await logStaffActivity({
|
||||
staffId: ctx.session.user.id,
|
||||
action: "rank_create",
|
||||
@@ -50,21 +58,32 @@ const deleteRankSchema = z.object({ id: z.coerce.number().int().positive() });
|
||||
export const deleteRank = adminAction(
|
||||
{ schema: deleteRankSchema, permission: PERMS.PERMISSIONS_MANAGE },
|
||||
async (ctx) => {
|
||||
const users = await prisma.user.count({ where: { rank: ctx.data.id } });
|
||||
const [userCount] = await db
|
||||
.select({ total: count() })
|
||||
.from(User)
|
||||
.where(eq(User.rank, ctx.data.id));
|
||||
const users = userCount?.total ?? 0;
|
||||
if (users > 0)
|
||||
throw new ActionError(`Cannot delete: ${users} users have this rank`);
|
||||
const role = await prisma.aclRole.findFirst({
|
||||
where: { slug: `rank_${ctx.data.id}` },
|
||||
});
|
||||
await deleteEmulatorRank(prisma, ctx.data.id);
|
||||
const [role] = await db
|
||||
.select({ id: AclRole.id })
|
||||
.from(AclRole)
|
||||
.where(eq(AclRole.slug, `rank_${ctx.data.id}`))
|
||||
.limit(1);
|
||||
await deleteEmulatorRank(db, ctx.data.id);
|
||||
if (role) {
|
||||
await prisma.$transaction([
|
||||
prisma.aclModelPermission.deleteMany({
|
||||
where: { modelId: role.id, modelType: "Role" },
|
||||
}),
|
||||
prisma.aclModelRole.deleteMany({ where: { roleId: role.id } }),
|
||||
prisma.aclRole.delete({ where: { id: role.id } }),
|
||||
]);
|
||||
await db.transaction(async (tx) => {
|
||||
await tx
|
||||
.delete(AclModelPermission)
|
||||
.where(
|
||||
and(
|
||||
eq(AclModelPermission.modelId, role.id),
|
||||
eq(AclModelPermission.modelType, "Role"),
|
||||
),
|
||||
);
|
||||
await tx.delete(AclModelRole).where(eq(AclModelRole.roleId, role.id));
|
||||
await tx.delete(AclRole).where(eq(AclRole.id, role.id));
|
||||
});
|
||||
}
|
||||
await logStaffActivity({
|
||||
staffId: ctx.session.user.id,
|
||||
@@ -87,12 +106,12 @@ const saveRankSchema = z.object({
|
||||
export const saveRank = adminAction(
|
||||
{ schema: saveRankSchema, permission: PERMS.PERMISSIONS_MANAGE },
|
||||
async (ctx) => {
|
||||
await updateEmulatorRank(prisma, ctx.data.id, ctx.data.fields);
|
||||
await updateEmulatorRank(db, ctx.data.id, ctx.data.fields);
|
||||
if (typeof ctx.data.fields.rank_name === "string") {
|
||||
await prisma.aclRole.updateMany({
|
||||
where: { slug: `rank_${ctx.data.id}` },
|
||||
data: { title: ctx.data.fields.rank_name },
|
||||
});
|
||||
await db
|
||||
.update(AclRole)
|
||||
.set({ title: ctx.data.fields.rank_name })
|
||||
.where(eq(AclRole.slug, `rank_${ctx.data.id}`));
|
||||
}
|
||||
await logStaffActivity({
|
||||
staffId: ctx.session.user.id,
|
||||
@@ -115,27 +134,33 @@ const setCmsPermsSchema = z.object({
|
||||
export const setCmsPermissions = adminAction(
|
||||
{ schema: setCmsPermsSchema, permission: PERMS.PERMISSIONS_MANAGE },
|
||||
async (ctx) => {
|
||||
const role = await prisma.aclRole.findUnique({
|
||||
where: { id: ctx.data.roleId },
|
||||
select: { id: true, slug: true },
|
||||
});
|
||||
const [role] = await db
|
||||
.select({ id: AclRole.id, slug: AclRole.slug })
|
||||
.from(AclRole)
|
||||
.where(eq(AclRole.id, ctx.data.roleId))
|
||||
.limit(1);
|
||||
if (!role) throw new ActionError("Role not found");
|
||||
const permissions = await prisma.aclPermission.findMany({
|
||||
where: { slug: { in: ctx.data.permissionSlugs } },
|
||||
select: { id: true },
|
||||
});
|
||||
await prisma.$transaction(async (tx) => {
|
||||
await tx.aclModelPermission.deleteMany({
|
||||
where: { modelId: role.id, modelType: "Role" },
|
||||
});
|
||||
const permissions = await db
|
||||
.select({ id: AclPermission.id })
|
||||
.from(AclPermission)
|
||||
.where(inArray(AclPermission.slug, ctx.data.permissionSlugs));
|
||||
await db.transaction(async (tx) => {
|
||||
await tx
|
||||
.delete(AclModelPermission)
|
||||
.where(
|
||||
and(
|
||||
eq(AclModelPermission.modelId, role.id),
|
||||
eq(AclModelPermission.modelType, "Role"),
|
||||
),
|
||||
);
|
||||
if (permissions.length) {
|
||||
await tx.aclModelPermission.createMany({
|
||||
data: permissions.map((permission) => ({
|
||||
await tx.insert(AclModelPermission).values(
|
||||
permissions.map((permission) => ({
|
||||
modelId: role.id,
|
||||
modelType: "Role",
|
||||
permissionId: permission.id,
|
||||
})),
|
||||
});
|
||||
);
|
||||
}
|
||||
});
|
||||
await logStaffActivity({
|
||||
@@ -159,7 +184,7 @@ export const setCmsPermissions = adminAction(
|
||||
export const repairAdminNavAclGrants = adminAction(
|
||||
{ permission: PERMS.PERMISSIONS_MANAGE },
|
||||
async (ctx) => {
|
||||
const dashboardFill = await prisma.$executeRaw`
|
||||
const [dashboardFillResult] = await db.execute(sql`
|
||||
INSERT INTO \`acl_model_permissions\` (\`model_type\`, \`model_id\`, \`permission_id\`)
|
||||
SELECT 'Role', ar.id, ap.id
|
||||
FROM \`acl_roles\` ar
|
||||
@@ -179,9 +204,9 @@ export const repairAdminNavAclGrants = adminAction(
|
||||
AND amp2.model_id = ar.id
|
||||
AND amp2.permission_id = ap.id
|
||||
)
|
||||
`;
|
||||
`);
|
||||
|
||||
const midRankViews = await prisma.$executeRaw`
|
||||
const [midRankViewsResult] = await db.execute(sql`
|
||||
INSERT INTO \`acl_model_permissions\` (\`model_type\`, \`model_id\`, \`permission_id\`)
|
||||
SELECT 'Role', ar.id, ap.id
|
||||
FROM \`permission_ranks\` pr
|
||||
@@ -198,9 +223,9 @@ export const repairAdminNavAclGrants = adminAction(
|
||||
AND amp.model_id = ar.id
|
||||
AND amp.permission_id = ap.id
|
||||
)
|
||||
`;
|
||||
`);
|
||||
|
||||
const highRankTools = await prisma.$executeRaw`
|
||||
const [highRankToolsResult] = await db.execute(sql`
|
||||
INSERT INTO \`acl_model_permissions\` (\`model_type\`, \`model_id\`, \`permission_id\`)
|
||||
SELECT 'Role', ar.id, ap.id
|
||||
FROM \`permission_ranks\` pr
|
||||
@@ -226,10 +251,12 @@ export const repairAdminNavAclGrants = adminAction(
|
||||
AND amp.model_id = ar.id
|
||||
AND amp.permission_id = ap.id
|
||||
)
|
||||
`;
|
||||
`);
|
||||
|
||||
const inserted =
|
||||
Number(dashboardFill) + Number(midRankViews) + Number(highRankTools);
|
||||
Number((dashboardFillResult as ResultSetHeader).affectedRows) +
|
||||
Number((midRankViewsResult as ResultSetHeader).affectedRows) +
|
||||
Number((highRankToolsResult as ResultSetHeader).affectedRows);
|
||||
|
||||
await logStaffActivity({
|
||||
staffId: ctx.session.user.id,
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
"use server";
|
||||
|
||||
import { revalidatePath } from "next/cache";
|
||||
import type { Prisma } from "@/generated/prisma/client";
|
||||
import { requirePermission } from "@/lib/admin/guard";
|
||||
import { PERMS } from "@/lib/permissions";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
@@ -17,7 +16,7 @@ export async function updateRoomItem(payload: Record<string, unknown>) {
|
||||
};
|
||||
await prisma.items.update({
|
||||
where: { id: itemId },
|
||||
data: data as Prisma.ItemsUpdateInput,
|
||||
data: data as any,
|
||||
});
|
||||
await logStaffActivity({
|
||||
staffId: staff.id,
|
||||
@@ -111,7 +110,7 @@ export async function updateRoom({
|
||||
const staff = await requirePermission(PERMS.ROOMS_EDIT);
|
||||
await prisma.rooms.update({
|
||||
where: { id },
|
||||
data: data as Prisma.RoomsUpdateInput,
|
||||
data: data as any,
|
||||
});
|
||||
await logStaffActivity({
|
||||
staffId: staff.id,
|
||||
|
||||
+1
-2
@@ -2,7 +2,6 @@
|
||||
|
||||
import { revalidatePath } from "next/cache";
|
||||
import { redirect } from "next/navigation";
|
||||
import type { Prisma } from "@/generated/prisma/client";
|
||||
import { auth } from "@/lib/auth";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
import { clientIp, rateLimit } from "@/lib/rate-limit";
|
||||
@@ -116,7 +115,7 @@ export async function buyShopArticle(formData: FormData): Promise<void> {
|
||||
} else {
|
||||
const badgeCodes = parseBadgeCodes(article.badges);
|
||||
|
||||
await prisma.$transaction(async (tx: Prisma.TransactionClient) => {
|
||||
await prisma.$transaction(async (tx) => {
|
||||
if (price > 0) {
|
||||
await tx.user.update({
|
||||
where: { id: userId },
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
import crypto from "node:crypto";
|
||||
import { z } from "zod";
|
||||
import { Prisma } from "@/generated/prisma/client";
|
||||
import { hashPassword } from "@/lib/auth/password";
|
||||
import { PERMS } from "@/lib/permissions";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
@@ -81,10 +80,14 @@ export const createUser = adminAction(
|
||||
return actionOk({ id: user.id, username: user.username });
|
||||
} catch (err) {
|
||||
if (
|
||||
err instanceof Prisma.PrismaClientKnownRequestError &&
|
||||
err &&
|
||||
typeof err === "object" &&
|
||||
"code" in err &&
|
||||
err.code === "P2002"
|
||||
) {
|
||||
const target = (err.meta?.target as string[]) ?? [];
|
||||
const target = (
|
||||
(err as { meta?: { target?: string[] } }).meta?.target as string[]
|
||||
) ?? [];
|
||||
if (target.includes("username"))
|
||||
throw new ActionError("Username already taken");
|
||||
if (target.includes("mail"))
|
||||
|
||||
Reference in new issue
Block a user