- 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.
115 lines
2.7 KiB
TypeScript
115 lines
2.7 KiB
TypeScript
"use server";
|
|
|
|
import { revalidatePath } from "next/cache";
|
|
import { z } from "zod";
|
|
import { PERMS } from "@/lib/permissions";
|
|
import { prisma } from "@/lib/prisma";
|
|
import { adminAction } from "@/lib/safe-action";
|
|
import { ActionError, actionOk } from "@/lib/safe-action-shared";
|
|
import { rcon } from "@/lib/services/rcon";
|
|
import { logStaffActivity } from "@/lib/services/staff-activity";
|
|
|
|
const ITEMS_BASE_FIELDS = [
|
|
"publicName",
|
|
"itemName",
|
|
"type",
|
|
"spriteId",
|
|
"width",
|
|
"length",
|
|
"stackHeight",
|
|
"allowStack",
|
|
"allowSit",
|
|
"allowLay",
|
|
"allowWalk",
|
|
"allowGift",
|
|
"allowTrade",
|
|
"allowRecycle",
|
|
"allowMarketplaceSell",
|
|
"allowInventoryStack",
|
|
"interactionType",
|
|
"interactionModesCount",
|
|
"vendingIds",
|
|
"multiheight",
|
|
"customparams",
|
|
"effectIdMale",
|
|
"effectIdFemale",
|
|
"clothingOnWalk",
|
|
] as const;
|
|
|
|
const updateSchema = z.object({
|
|
id: z.coerce.number().int().positive(),
|
|
fields: z.record(z.string(), z.unknown()),
|
|
});
|
|
|
|
function pickAllowed(
|
|
fields: Record<string, unknown>,
|
|
allowed: readonly string[],
|
|
): Record<string, unknown> {
|
|
const out: Record<string, unknown> = {};
|
|
for (const key of allowed) {
|
|
if (Object.hasOwn(fields, key) && fields[key] !== undefined) {
|
|
out[key] = fields[key];
|
|
}
|
|
}
|
|
return out;
|
|
}
|
|
|
|
export const updateItemsBase = adminAction(
|
|
{ permission: PERMS.CATALOG_EDIT, schema: updateSchema },
|
|
async (ctx) => {
|
|
const { id, fields } = ctx.data;
|
|
const safe = pickAllowed(fields, ITEMS_BASE_FIELDS);
|
|
if (Object.keys(safe).length === 0) {
|
|
throw new ActionError("No valid fields to update");
|
|
}
|
|
|
|
const existing = await prisma.itemsBase.findUnique({
|
|
where: { id },
|
|
select: { id: true },
|
|
});
|
|
if (!existing) throw new ActionError("Item not found");
|
|
|
|
// Coerce common numeric / decimal fields from form strings.
|
|
const data: Record<string, unknown> = { ...safe };
|
|
for (const key of [
|
|
"spriteId",
|
|
"width",
|
|
"length",
|
|
"allowStack",
|
|
"allowSit",
|
|
"allowLay",
|
|
"allowWalk",
|
|
"allowGift",
|
|
"allowTrade",
|
|
"allowRecycle",
|
|
"allowMarketplaceSell",
|
|
"allowInventoryStack",
|
|
"interactionModesCount",
|
|
"effectIdMale",
|
|
"effectIdFemale",
|
|
]) {
|
|
if (data[key] !== undefined) data[key] = Number(data[key]);
|
|
}
|
|
if (data.stackHeight !== undefined) {
|
|
data.stackHeight = Number(data.stackHeight);
|
|
}
|
|
|
|
await prisma.itemsBase.update({
|
|
where: { id },
|
|
data: data as any,
|
|
});
|
|
await rcon.updateCatalog().catch(() => false);
|
|
await logStaffActivity({
|
|
staffId: Number(ctx.session.user.id),
|
|
action: "items_base_update",
|
|
description: `Updated items_base #${id}`,
|
|
targetType: "items_base",
|
|
targetId: id,
|
|
});
|
|
revalidatePath("/admin/items");
|
|
revalidatePath(`/admin/items/${id}`);
|
|
revalidatePath("/admin/catalog");
|
|
return actionOk({ id });
|
|
},
|
|
);
|