Files
2025-11-11 03:08:36 -03:00

357 lines
9.7 KiB
TypeScript

import { type NextRequest, NextResponse } from "next/server";
import { requireAdminUser } from "@/lib/auth/middleware";
import {
insertCategoryItem,
listCategoryTemplates,
updateCategoryItem,
} from "@/lib/items/sqlite-manager";
import {
createAdminItem,
getItemById,
searchAdminItems,
updateAdminItem,
} from "@/lib/prisma/queries/items";
function parseNumber(
value: string | null | undefined,
fallback: number,
): number {
const parsed = Number(value);
return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
}
function parseList(value: unknown): string[] {
if (Array.isArray(value)) {
return value
.map((entry) => (typeof entry === "string" ? entry.trim() : ""))
.filter(Boolean);
}
if (typeof value === "string") {
return value
.split(",")
.map((entry) => entry.trim())
.filter(Boolean);
}
return [];
}
function normalizeDynamicFields(
category: string,
rawFields: Record<string, unknown> | undefined,
) {
if (!rawFields || typeof rawFields !== "object") {
return {} as Record<string, unknown>;
}
const templates = listCategoryTemplates();
const template = templates.find(
(entry) => entry.name === category.toLowerCase(),
);
if (!template) {
return rawFields;
}
const columnMap = new Map(
template.columns.map((column) => [column.name, column]),
);
const normalized: Record<string, unknown> = {};
for (const [key, value] of Object.entries(rawFields)) {
if (["id", "nome", "image_url", "image_local_path"].includes(key)) {
continue;
}
const column = columnMap.get(key);
if (!column) {
continue;
}
if (value === "" || value === null || value === undefined) {
normalized[key] = null;
continue;
}
switch (column.type) {
case "INTEGER":
case "BOOLEAN": {
const parsed = Number(value);
normalized[key] = Number.isFinite(parsed) ? parsed : null;
break;
}
case "REAL": {
const parsed = Number(value);
normalized[key] = Number.isFinite(parsed) ? parsed : null;
break;
}
default:
normalized[key] = String(value);
}
}
return normalized;
}
export async function GET(request: NextRequest) {
const auth = await requireAdminUser(request);
if (auth instanceof NextResponse) {
return auth;
}
const { searchParams } = new URL(request.url);
const page = parseNumber(searchParams.get("page"), 1);
const pageSize = Math.min(parseNumber(searchParams.get("pageSize"), 50), 200);
const q = searchParams.get("q") || undefined;
const category = searchParams.get("category") || undefined;
const rarity = searchParams.get("rarity") || undefined;
const server = searchParams.get("server") || undefined;
try {
const result = await searchAdminItems({
q,
category,
rarity,
server,
page,
pageSize,
});
return NextResponse.json(result, { status: 200 });
} catch (error: any) {
console.error("[admin/items][GET] erro", error?.message);
return NextResponse.json(
{
success: false,
error: "Falha ao consultar itens",
message: error?.message || "Erro desconhecido",
},
{ status: 500 },
);
}
}
export async function POST(request: NextRequest) {
const auth = await requireAdminUser(request);
if (auth instanceof NextResponse) {
return auth;
}
try {
const body = await request.json();
const name = typeof body?.name === "string" ? body.name.trim() : "";
const category =
typeof body?.category === "string"
? body.category.trim().toLowerCase()
: "";
const originServer =
typeof body?.server === "string" ? body.server.trim() || null : null;
if (!name || !category) {
return NextResponse.json(
{ success: false, error: "Nome e categoria são obrigatórios." },
{ status: 400 },
);
}
const dynamicFields = normalizeDynamicFields(category, body?.fields);
const imageUrl =
typeof body?.imageUrl === "string" ? body.imageUrl.trim() || null : null;
const imageLocalPath =
typeof body?.imageLocalPath === "string"
? body.imageLocalPath.trim() || null
: null;
const sqliteData = {
nome: name,
image_url: imageUrl,
image_local_path: imageLocalPath,
...dynamicFields,
} as Record<string, unknown>;
// Se enviado servidor de origem, inclui na linha do SQLite e garante presença na lista de servidores do item
if (originServer) {
sqliteData.server = originServer;
}
const sqliteId = insertCategoryItem({ category, data: sqliteData });
const metadataBase =
typeof body?.metadata === "object" && body.metadata !== null
? body.metadata
: {};
const metadata = { ...metadataBase, ...dynamicFields } as Record<
string,
unknown
>;
const itemServers = parseList(body?.servers);
const finalServers = originServer
? Array.from(new Set([originServer, ...itemServers]))
: itemServers;
const item = await createAdminItem({
item_name: name,
category,
rarity:
typeof body?.rarity === "string" ? body.rarity.trim() || null : null,
image_url: imageUrl,
image_local_path: imageLocalPath,
level: typeof body?.level === "number" ? body.level : null,
weight: typeof body?.weight === "number" ? body.weight : null,
slots: typeof body?.slots === "number" ? body.slots : null,
skill: typeof body?.skill === "string" ? body.skill.trim() || null : null,
servers: finalServers,
tags: parseList(body?.tags),
description:
typeof body?.description === "string"
? body.description.trim() || null
: null,
metadata,
source_table_name: category,
source_table_id: sqliteId,
});
return NextResponse.json(
{
success: true,
item,
},
{ status: 201 },
);
} catch (error: any) {
console.error("[admin/items][POST] erro", error?.message);
return NextResponse.json(
{
success: false,
error: "Falha ao criar item",
message: error?.message || "Erro desconhecido",
},
{ status: 500 },
);
}
}
export async function PUT(request: NextRequest) {
const auth = await requireAdminUser(request);
if (auth instanceof NextResponse) {
return auth;
}
try {
const body = await request.json();
const id = Number(body?.id);
if (!Number.isFinite(id) || id <= 0) {
return NextResponse.json(
{ success: false, error: "ID inválido." },
{ status: 400 },
);
}
const existing = await getItemById(id);
if (!existing) {
return NextResponse.json(
{ success: false, error: "Item não encontrado." },
{ status: 404 },
);
}
const category =
(typeof body?.category === "string"
? body.category.trim().toLowerCase()
: existing.category) ?? existing.category;
if (category !== existing.category) {
return NextResponse.json(
{
success: false,
error: "Alterar a categoria de um item não é suportado.",
},
{ status: 400 },
);
}
const dynamicFields = normalizeDynamicFields(category, body?.fields);
const imageUrl =
typeof body?.imageUrl === "string" ? body.imageUrl.trim() : undefined;
const imageLocalPath =
typeof body?.imageLocalPath === "string"
? body.imageLocalPath.trim()
: undefined;
if (existing.source_table_id && existing.source_table_name) {
const sqliteData: Record<string, unknown> = {
nome:
typeof body?.name === "string"
? body.name.trim()
: existing.item_name,
};
if (imageUrl !== undefined) sqliteData.image_url = imageUrl;
else sqliteData.image_url = existing.image_url;
if (imageLocalPath !== undefined)
sqliteData.image_local_path = imageLocalPath;
else sqliteData.image_local_path = existing.image_local_path;
for (const [key, value] of Object.entries(dynamicFields)) {
sqliteData[key] = value;
}
updateCategoryItem(
existing.source_table_name,
existing.source_table_id,
sqliteData,
);
}
const metadataBase =
typeof body?.metadata === "object" && body.metadata !== null
? body.metadata
: existing.metadata;
const metadata = { ...metadataBase, ...dynamicFields } as Record<
string,
unknown
>;
const item = await updateAdminItem(id, {
item_name: typeof body?.name === "string" ? body.name.trim() : undefined,
category,
rarity: typeof body?.rarity === "string" ? body.rarity.trim() : undefined,
image_url: imageUrl,
image_local_path: imageLocalPath,
level: typeof body?.level === "number" ? body.level : undefined,
weight: typeof body?.weight === "number" ? body.weight : undefined,
slots: typeof body?.slots === "number" ? body.slots : undefined,
skill: typeof body?.skill === "string" ? body.skill.trim() : undefined,
servers: parseList(body?.servers),
tags: parseList(body?.tags),
description:
typeof body?.description === "string"
? body.description.trim()
: body?.description === null
? null
: undefined,
metadata,
});
return NextResponse.json(
{
success: true,
item,
},
{ status: 200 },
);
} catch (error: any) {
console.error("[admin/items][PUT] erro", error?.message);
return NextResponse.json(
{
success: false,
error: "Falha ao atualizar item",
message: error?.message || "Erro desconhecido",
},
{ status: 500 },
);
}
}