357 lines
9.7 KiB
TypeScript
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 },
|
|
);
|
|
}
|
|
}
|