style: clean up code formatting and remove trailing whitespace
refactor: reorganize imports and fix type imports
This commit is contained in:
1 parent
d3704602fd
commit
47c04de6fe
232 files changed
+7140
-4798
No files matched your search
@@ -1,10 +1,14 @@
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { type NextRequest, NextResponse } from "next/server";
|
||||
import { requireAdminUser } from "@/lib/auth/middleware";
|
||||
import { addColumnToCategory, type SQLiteColumnDefinition } from "@/lib/items/sqlite-manager";
|
||||
import {
|
||||
addColumnToCategory,
|
||||
type SQLiteColumnDefinition,
|
||||
} from "@/lib/items/sqlite-manager";
|
||||
import { secureLogger } from "@/lib/secure-logger";
|
||||
|
||||
function sanitizeColumn(column: any): SQLiteColumnDefinition {
|
||||
const name = typeof column?.name === "string" ? column.name.trim().toLowerCase() : "";
|
||||
const name =
|
||||
typeof column?.name === "string" ? column.name.trim().toLowerCase() : "";
|
||||
const type = typeof column?.type === "string" ? column.type : "TEXT";
|
||||
const notNull = column?.notNull === true;
|
||||
const defaultValue = column?.defaultValue ?? undefined;
|
||||
@@ -26,7 +30,10 @@ function sanitizeColumn(column: any): SQLiteColumnDefinition {
|
||||
} satisfies SQLiteColumnDefinition;
|
||||
}
|
||||
|
||||
export async function POST(request: NextRequest, { params }: { params: Promise<{ name: string }> }) {
|
||||
export async function POST(
|
||||
request: NextRequest,
|
||||
{ params }: { params: Promise<{ name: string }> },
|
||||
) {
|
||||
const auth = await requireAdminUser(request);
|
||||
if (auth instanceof NextResponse) {
|
||||
return auth;
|
||||
@@ -38,14 +45,19 @@ export async function POST(request: NextRequest, { params }: { params: Promise<{
|
||||
const body = await request.json();
|
||||
const column = sanitizeColumn(body?.column ?? body);
|
||||
const created = addColumnToCategory({ category: name, column });
|
||||
return NextResponse.json({ success: true, column: created }, { status: 201 });
|
||||
return NextResponse.json(
|
||||
{ success: true, column: created },
|
||||
{ status: 201 },
|
||||
);
|
||||
} catch (error) {
|
||||
secureLogger.error("Falha ao adicionar coluna à categoria", error);
|
||||
return NextResponse.json(
|
||||
{ success: false, error: error instanceof Error ? error.message : "Erro ao adicionar coluna" },
|
||||
{
|
||||
success: false,
|
||||
error:
|
||||
error instanceof Error ? error.message : "Erro ao adicionar coluna",
|
||||
},
|
||||
{ status: 400 },
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { type NextRequest, NextResponse } from "next/server";
|
||||
import { requireAdminUser } from "@/lib/auth/middleware";
|
||||
import {
|
||||
listCategoryTemplates,
|
||||
createCategoryTemplate,
|
||||
type SQLiteColumnDefinition,
|
||||
type CreateCategoryInput,
|
||||
createCategoryTemplate,
|
||||
listCategoryTemplates,
|
||||
type SQLiteColumnDefinition,
|
||||
} from "@/lib/items/sqlite-manager";
|
||||
import { secureLogger } from "@/lib/secure-logger";
|
||||
|
||||
@@ -16,7 +16,10 @@ export async function GET(request: NextRequest) {
|
||||
|
||||
try {
|
||||
const templates = listCategoryTemplates();
|
||||
return NextResponse.json({ success: true, categories: templates }, { status: 200 });
|
||||
return NextResponse.json(
|
||||
{ success: true, categories: templates },
|
||||
{ status: 200 },
|
||||
);
|
||||
} catch (error) {
|
||||
secureLogger.error("Falha ao listar categorias dinâmicas", error);
|
||||
return NextResponse.json(
|
||||
@@ -27,7 +30,8 @@ export async function GET(request: NextRequest) {
|
||||
}
|
||||
|
||||
function sanitizeColumn(column: any): SQLiteColumnDefinition {
|
||||
const name = typeof column?.name === "string" ? column.name.trim().toLowerCase() : "";
|
||||
const name =
|
||||
typeof column?.name === "string" ? column.name.trim().toLowerCase() : "";
|
||||
const type = typeof column?.type === "string" ? column.type : "TEXT";
|
||||
const notNull = column?.notNull === true;
|
||||
const defaultValue = column?.defaultValue ?? undefined;
|
||||
@@ -53,7 +57,8 @@ export async function POST(request: NextRequest) {
|
||||
|
||||
try {
|
||||
const body = await request.json();
|
||||
const rawName = typeof body?.name === "string" ? body.name.trim().toLowerCase() : "";
|
||||
const rawName =
|
||||
typeof body?.name === "string" ? body.name.trim().toLowerCase() : "";
|
||||
if (!rawName) {
|
||||
return NextResponse.json(
|
||||
{ success: false, error: "Nome da categoria é obrigatório" },
|
||||
@@ -61,10 +66,17 @@ export async function POST(request: NextRequest) {
|
||||
);
|
||||
}
|
||||
|
||||
const columnsArray: unknown[] = Array.isArray(body?.columns) ? body.columns : [];
|
||||
const columns = columnsArray.map((column) => sanitizeColumn(column)).filter((column) => {
|
||||
return !(column && ["id", "nome", "image_url", "image_local_path"].includes(column.name));
|
||||
});
|
||||
const columnsArray: unknown[] = Array.isArray(body?.columns)
|
||||
? body.columns
|
||||
: [];
|
||||
const columns = columnsArray
|
||||
.map((column) => sanitizeColumn(column))
|
||||
.filter((column) => {
|
||||
return !(
|
||||
column &&
|
||||
["id", "nome", "image_url", "image_local_path"].includes(column.name)
|
||||
);
|
||||
});
|
||||
|
||||
const input: CreateCategoryInput = {
|
||||
name: rawName,
|
||||
@@ -76,7 +88,11 @@ export async function POST(request: NextRequest) {
|
||||
} catch (error) {
|
||||
secureLogger.error("Falha ao criar categoria dinâmica", error);
|
||||
return NextResponse.json(
|
||||
{ success: false, error: error instanceof Error ? error.message : "Erro ao criar categoria" },
|
||||
{
|
||||
success: false,
|
||||
error:
|
||||
error instanceof Error ? error.message : "Erro ao criar categoria",
|
||||
},
|
||||
{ status: 400 },
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { requireAdminUser } from "@/lib/auth/middleware";
|
||||
import { type NextRequest, NextResponse } from "next/server";
|
||||
import { generateItemStructure } from "@/lib/ai/generate-item-structure";
|
||||
import { calculateTokenCost } from "@/lib/ai/token-pricing";
|
||||
import { recordTokenUsage } from "@/lib/ai/token-usage";
|
||||
import { requireAdminUser } from "@/lib/auth/middleware";
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
const auth = await requireAdminUser(request);
|
||||
@@ -39,7 +39,7 @@ export async function POST(request: NextRequest) {
|
||||
success: false,
|
||||
error: "Forneça descrição ou ao menos uma imagem para análise.",
|
||||
},
|
||||
{ status: 400 }
|
||||
{ status: 400 },
|
||||
);
|
||||
}
|
||||
|
||||
@@ -64,7 +64,8 @@ export async function POST(request: NextRequest) {
|
||||
});
|
||||
|
||||
// Cálculo de custo baseado em usageMetadata
|
||||
const model = (result.model ?? "gemini-2.5-flash") as keyof typeof import("@/lib/ai/token-pricing").TOKEN_PRICING;
|
||||
const _model = (result.model ??
|
||||
"gemini-2.5-flash") as keyof typeof import("@/lib/ai/token-pricing").TOKEN_PRICING;
|
||||
const breakdown = calculateTokenCost("gemini-2.5-flash", result.usage);
|
||||
|
||||
// Persistir histórico para relatórios (não bloqueia resposta; erros são logados no Redis util)
|
||||
@@ -104,7 +105,6 @@ export async function POST(request: NextRequest) {
|
||||
model: result.model ?? "gemini-2.5-flash",
|
||||
requestId,
|
||||
},
|
||||
{ status: 200 }
|
||||
{ status: 200 },
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,18 +1,21 @@
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
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,
|
||||
getItemById,
|
||||
} from "@/lib/prisma/queries/items";
|
||||
import {
|
||||
insertCategoryItem,
|
||||
updateCategoryItem,
|
||||
listCategoryTemplates,
|
||||
} from "@/lib/items/sqlite-manager";
|
||||
|
||||
function parseNumber(value: string | null | undefined, fallback: number): number {
|
||||
function parseNumber(
|
||||
value: string | null | undefined,
|
||||
fallback: number,
|
||||
): number {
|
||||
const parsed = Number(value);
|
||||
return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
|
||||
}
|
||||
@@ -32,18 +35,25 @@ function parseList(value: unknown): string[] {
|
||||
return [];
|
||||
}
|
||||
|
||||
function normalizeDynamicFields(category: string, rawFields: Record<string, unknown> | undefined) {
|
||||
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());
|
||||
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 columnMap = new Map(
|
||||
template.columns.map((column) => [column.name, column]),
|
||||
);
|
||||
const normalized: Record<string, unknown> = {};
|
||||
|
||||
for (const [key, value] of Object.entries(rawFields)) {
|
||||
@@ -114,7 +124,7 @@ export async function GET(request: NextRequest) {
|
||||
error: "Falha ao consultar itens",
|
||||
message: error?.message || "Erro desconhecido",
|
||||
},
|
||||
{ status: 500 }
|
||||
{ status: 500 },
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -128,21 +138,28 @@ export async function POST(request: NextRequest) {
|
||||
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;
|
||||
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 }
|
||||
{ status: 400 },
|
||||
);
|
||||
}
|
||||
|
||||
const dynamicFields = normalizeDynamicFields(category, body?.fields);
|
||||
|
||||
const imageUrl = typeof body?.imageUrl === "string" ? body.imageUrl.trim() || null : null;
|
||||
const imageUrl =
|
||||
typeof body?.imageUrl === "string" ? body.imageUrl.trim() || null : null;
|
||||
const imageLocalPath =
|
||||
typeof body?.imageLocalPath === "string" ? body.imageLocalPath.trim() || null : null;
|
||||
typeof body?.imageLocalPath === "string"
|
||||
? body.imageLocalPath.trim() || null
|
||||
: null;
|
||||
|
||||
const sqliteData = {
|
||||
nome: name,
|
||||
@@ -158,16 +175,25 @@ export async function POST(request: NextRequest) {
|
||||
|
||||
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 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 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,
|
||||
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,
|
||||
@@ -177,7 +203,9 @@ export async function POST(request: NextRequest) {
|
||||
servers: finalServers,
|
||||
tags: parseList(body?.tags),
|
||||
description:
|
||||
typeof body?.description === "string" ? body.description.trim() || null : null,
|
||||
typeof body?.description === "string"
|
||||
? body.description.trim() || null
|
||||
: null,
|
||||
metadata,
|
||||
source_table_name: category,
|
||||
source_table_id: sqliteId,
|
||||
@@ -188,7 +216,7 @@ export async function POST(request: NextRequest) {
|
||||
success: true,
|
||||
item,
|
||||
},
|
||||
{ status: 201 }
|
||||
{ status: 201 },
|
||||
);
|
||||
} catch (error: any) {
|
||||
console.error("[admin/items][POST] erro", error?.message);
|
||||
@@ -198,7 +226,7 @@ export async function POST(request: NextRequest) {
|
||||
error: "Falha ao criar item",
|
||||
message: error?.message || "Erro desconhecido",
|
||||
},
|
||||
{ status: 500 }
|
||||
{ status: 500 },
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -216,7 +244,7 @@ export async function PUT(request: NextRequest) {
|
||||
if (!Number.isFinite(id) || id <= 0) {
|
||||
return NextResponse.json(
|
||||
{ success: false, error: "ID inválido." },
|
||||
{ status: 400 }
|
||||
{ status: 400 },
|
||||
);
|
||||
}
|
||||
|
||||
@@ -224,44 +252,67 @@ export async function PUT(request: NextRequest) {
|
||||
if (!existing) {
|
||||
return NextResponse.json(
|
||||
{ success: false, error: "Item não encontrado." },
|
||||
{ status: 404 }
|
||||
{ status: 404 },
|
||||
);
|
||||
}
|
||||
|
||||
const category = (typeof body?.category === "string" ? body.category.trim().toLowerCase() : existing.category) ?? existing.category;
|
||||
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 }
|
||||
{
|
||||
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 imageUrl =
|
||||
typeof body?.imageUrl === "string" ? body.imageUrl.trim() : undefined;
|
||||
const imageLocalPath =
|
||||
typeof body?.imageLocalPath === "string" ? body.imageLocalPath.trim() : undefined;
|
||||
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,
|
||||
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;
|
||||
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);
|
||||
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 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,
|
||||
@@ -279,8 +330,8 @@ export async function PUT(request: NextRequest) {
|
||||
typeof body?.description === "string"
|
||||
? body.description.trim()
|
||||
: body?.description === null
|
||||
? null
|
||||
: undefined,
|
||||
? null
|
||||
: undefined,
|
||||
metadata,
|
||||
});
|
||||
|
||||
@@ -289,7 +340,7 @@ export async function PUT(request: NextRequest) {
|
||||
success: true,
|
||||
item,
|
||||
},
|
||||
{ status: 200 }
|
||||
{ status: 200 },
|
||||
);
|
||||
} catch (error: any) {
|
||||
console.error("[admin/items][PUT] erro", error?.message);
|
||||
@@ -299,8 +350,7 @@ export async function PUT(request: NextRequest) {
|
||||
error: "Falha ao atualizar item",
|
||||
message: error?.message || "Erro desconhecido",
|
||||
},
|
||||
{ status: 500 }
|
||||
{ status: 500 },
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
+20
-24
@@ -1,27 +1,24 @@
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
import {
|
||||
checkRateLimit,
|
||||
getClientIP,
|
||||
createRateLimitResponse,
|
||||
} from "@/lib/rate-limiter";
|
||||
import { secureLogger } from "@/lib/secure-logger";
|
||||
import { loginSchema } from "@/lib/validation-schemas";
|
||||
import { randomUUID } from "node:crypto";
|
||||
import { type NextRequest, NextResponse } from "next/server";
|
||||
import { generateAccessToken, generateRefreshToken } from "@/lib/auth/jwt";
|
||||
import { comparePassword } from "@/lib/auth/password";
|
||||
import {
|
||||
generateAccessToken,
|
||||
generateRefreshToken,
|
||||
} from "@/lib/auth/jwt";
|
||||
getDefaultSubscriptionTier,
|
||||
getSubscriptionTierById,
|
||||
} from "@/lib/prisma/queries/subscription-tiers";
|
||||
import {
|
||||
getUserByEmail,
|
||||
getUserByUsername,
|
||||
updateLastLogin,
|
||||
} from "@/lib/prisma/queries/users";
|
||||
import { setSession } from "@/lib/redis/session";
|
||||
import { randomUUID } from "crypto";
|
||||
import {
|
||||
getDefaultSubscriptionTier,
|
||||
getSubscriptionTierById,
|
||||
} from "@/lib/prisma/queries/subscription-tiers";
|
||||
checkRateLimit,
|
||||
createRateLimitResponse,
|
||||
getClientIP,
|
||||
} from "@/lib/rate-limiter";
|
||||
import { setSession } from "@/lib/redis/session";
|
||||
import { secureLogger } from "@/lib/secure-logger";
|
||||
import { loginSchema } from "@/lib/validation-schemas";
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
try {
|
||||
@@ -55,7 +52,7 @@ export async function POST(request: NextRequest) {
|
||||
error: "Dados de entrada inválidos",
|
||||
details: errors,
|
||||
},
|
||||
{ status: 400 }
|
||||
{ status: 400 },
|
||||
);
|
||||
}
|
||||
|
||||
@@ -77,7 +74,7 @@ export async function POST(request: NextRequest) {
|
||||
success: false,
|
||||
error: "Email/username ou senha incorretos",
|
||||
},
|
||||
{ status: 401 }
|
||||
{ status: 401 },
|
||||
);
|
||||
}
|
||||
|
||||
@@ -88,7 +85,7 @@ export async function POST(request: NextRequest) {
|
||||
success: false,
|
||||
error: "Conta desativada. Entre em contato com o suporte.",
|
||||
},
|
||||
{ status: 403 }
|
||||
{ status: 403 },
|
||||
);
|
||||
}
|
||||
|
||||
@@ -104,7 +101,7 @@ export async function POST(request: NextRequest) {
|
||||
success: false,
|
||||
error: "Erro interno. Tente novamente mais tarde.",
|
||||
},
|
||||
{ status: 500 }
|
||||
{ status: 500 },
|
||||
);
|
||||
}
|
||||
|
||||
@@ -121,7 +118,7 @@ export async function POST(request: NextRequest) {
|
||||
success: false,
|
||||
error: "Email/username ou senha incorretos",
|
||||
},
|
||||
{ status: 401 }
|
||||
{ status: 401 },
|
||||
);
|
||||
}
|
||||
|
||||
@@ -181,7 +178,7 @@ export async function POST(request: NextRequest) {
|
||||
},
|
||||
accessToken,
|
||||
},
|
||||
{ status: 200 }
|
||||
{ status: 200 },
|
||||
);
|
||||
|
||||
// Adicionar refresh token e sessionId como cookies httpOnly
|
||||
@@ -220,8 +217,7 @@ export async function POST(request: NextRequest) {
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : "Erro interno",
|
||||
},
|
||||
{ status: 500 }
|
||||
{ status: 500 },
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { secureLogger } from "@/lib/secure-logger";
|
||||
import { type NextRequest, NextResponse } from "next/server";
|
||||
import { requireAuth } from "@/lib/auth/middleware";
|
||||
import { getUserById } from "@/lib/prisma/queries/users";
|
||||
import { getUserCharacters } from "@/lib/prisma/queries/characters";
|
||||
import { getUserById } from "@/lib/prisma/queries/users";
|
||||
import { secureLogger } from "@/lib/secure-logger";
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
try {
|
||||
// Middleware de autenticação
|
||||
const authResult = await requireAuth(request);
|
||||
|
||||
|
||||
// Se retornou NextResponse, é um erro de autenticação
|
||||
if (authResult instanceof NextResponse) {
|
||||
return authResult;
|
||||
}
|
||||
|
||||
|
||||
const auth = authResult;
|
||||
|
||||
// Buscar dados do usuário
|
||||
@@ -25,7 +25,7 @@ export async function GET(request: NextRequest) {
|
||||
success: false,
|
||||
error: "Usuário não encontrado",
|
||||
},
|
||||
{ status: 404 }
|
||||
{ status: 404 },
|
||||
);
|
||||
}
|
||||
|
||||
@@ -66,7 +66,7 @@ export async function GET(request: NextRequest) {
|
||||
createdAt: char.created_at,
|
||||
})),
|
||||
},
|
||||
{ status: 200 }
|
||||
{ status: 200 },
|
||||
);
|
||||
} catch (error) {
|
||||
// Se for NextResponse (erro de autenticação), re-lançar
|
||||
@@ -85,8 +85,7 @@ export async function GET(request: NextRequest) {
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : "Erro interno",
|
||||
},
|
||||
{ status: 500 }
|
||||
{ status: 500 },
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { type NextRequest, NextResponse } from "next/server";
|
||||
import { generateAccessToken, verifyRefreshToken } from "@/lib/auth/jwt";
|
||||
import {
|
||||
checkRateLimit,
|
||||
getClientIP,
|
||||
createRateLimitResponse,
|
||||
getClientIP,
|
||||
} from "@/lib/rate-limiter";
|
||||
import { secureLogger } from "@/lib/secure-logger";
|
||||
import { verifyRefreshToken, generateAccessToken } from "@/lib/auth/jwt";
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
try {
|
||||
@@ -26,7 +26,7 @@ export async function POST(request: NextRequest) {
|
||||
success: false,
|
||||
error: "Refresh token não fornecido",
|
||||
},
|
||||
{ status: 401 }
|
||||
{ status: 401 },
|
||||
);
|
||||
}
|
||||
|
||||
@@ -40,7 +40,7 @@ export async function POST(request: NextRequest) {
|
||||
success: false,
|
||||
error: "Refresh token inválido ou expirado",
|
||||
},
|
||||
{ status: 401 }
|
||||
{ status: 401 },
|
||||
);
|
||||
|
||||
response.cookies.delete("refreshToken");
|
||||
@@ -60,7 +60,7 @@ export async function POST(request: NextRequest) {
|
||||
success: true,
|
||||
accessToken,
|
||||
},
|
||||
{ status: 200 }
|
||||
{ status: 200 },
|
||||
);
|
||||
} catch (error) {
|
||||
secureLogger.error("❌ Erro ao renovar token", {
|
||||
@@ -74,8 +74,7 @@ export async function POST(request: NextRequest) {
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : "Erro interno",
|
||||
},
|
||||
{ status: 500 }
|
||||
{ status: 500 },
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,29 +1,18 @@
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { randomUUID } from "node:crypto";
|
||||
import { type NextRequest, NextResponse } from "next/server";
|
||||
import { generateAccessToken, generateRefreshToken } from "@/lib/auth/jwt";
|
||||
import { hashPassword } from "@/lib/auth/password";
|
||||
import { isServerActive } from "@/lib/prisma/queries/servers";
|
||||
import { getDefaultSubscriptionTier } from "@/lib/prisma/queries/subscription-tiers";
|
||||
import { getUserByEmail, getUserByUsername } from "@/lib/prisma/queries/users";
|
||||
import {
|
||||
checkRateLimit,
|
||||
getClientIP,
|
||||
createRateLimitResponse,
|
||||
getClientIP,
|
||||
} from "@/lib/rate-limiter";
|
||||
import { setSession } from "@/lib/redis/session";
|
||||
import { secureLogger } from "@/lib/secure-logger";
|
||||
import { registerSchema } from "@/lib/validation-schemas";
|
||||
import { hashPassword } from "@/lib/auth/password";
|
||||
import {
|
||||
generateAccessToken,
|
||||
generateRefreshToken,
|
||||
} from "@/lib/auth/jwt";
|
||||
import {
|
||||
getUserByEmail,
|
||||
getUserByUsername,
|
||||
} from "@/lib/prisma/queries/users";
|
||||
import {
|
||||
isServerActive,
|
||||
incrementUsersForServers,
|
||||
} from "@/lib/prisma/queries/servers";
|
||||
import { setSession } from "@/lib/redis/session";
|
||||
import { randomUUID } from "crypto";
|
||||
import { createUser } from "@/lib/prisma/queries/users";
|
||||
import { createCharacter } from "@/lib/prisma/queries/characters";
|
||||
import { getDefaultSubscriptionTier } from "@/lib/prisma/queries/subscription-tiers";
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
try {
|
||||
@@ -62,7 +51,7 @@ export async function POST(request: NextRequest) {
|
||||
error: "Dados de entrada inválidos",
|
||||
details: errors,
|
||||
},
|
||||
{ status: 400 }
|
||||
{ status: 400 },
|
||||
);
|
||||
}
|
||||
|
||||
@@ -76,7 +65,7 @@ export async function POST(request: NextRequest) {
|
||||
success: false,
|
||||
error: "Email já está em uso",
|
||||
},
|
||||
{ status: 409 }
|
||||
{ status: 409 },
|
||||
);
|
||||
}
|
||||
|
||||
@@ -88,7 +77,7 @@ export async function POST(request: NextRequest) {
|
||||
success: false,
|
||||
error: "Username já está em uso",
|
||||
},
|
||||
{ status: 409 }
|
||||
{ status: 409 },
|
||||
);
|
||||
}
|
||||
|
||||
@@ -104,7 +93,7 @@ export async function POST(request: NextRequest) {
|
||||
success: false,
|
||||
error: `Servidor "${serverName}" não existe ou não está ativo`,
|
||||
},
|
||||
{ status: 400 }
|
||||
{ status: 400 },
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -114,8 +103,11 @@ export async function POST(request: NextRequest) {
|
||||
|
||||
// Criar usuário e personagens em transação atômica usando Prisma
|
||||
const { prisma } = await import("@/lib/prisma/app");
|
||||
|
||||
const createdCharacters: Array<{ characterName: string; serverName: string }> = [];
|
||||
|
||||
const createdCharacters: Array<{
|
||||
characterName: string;
|
||||
serverName: string;
|
||||
}> = [];
|
||||
|
||||
// Usar transação Prisma
|
||||
const newUser = await prisma.$transaction(async (tx) => {
|
||||
@@ -199,7 +191,7 @@ export async function POST(request: NextRequest) {
|
||||
{
|
||||
tierSlug: defaultTier?.slug ?? undefined,
|
||||
tierLevel: defaultTier?.level ?? undefined,
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
// Criar sessão no Redis
|
||||
@@ -235,7 +227,7 @@ export async function POST(request: NextRequest) {
|
||||
},
|
||||
accessToken,
|
||||
},
|
||||
{ status: 201 }
|
||||
{ status: 201 },
|
||||
);
|
||||
|
||||
// Adicionar refresh token e sessionId como cookies httpOnly
|
||||
@@ -274,8 +266,7 @@ export async function POST(request: NextRequest) {
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : "Erro interno",
|
||||
},
|
||||
{ status: 500 }
|
||||
{ status: 500 },
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,17 +1,25 @@
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { cookies } from "next/headers";
|
||||
import { secureLogger } from "@/lib/secure-logger";
|
||||
import { getSession, refreshSession, deleteSession, setSession } from "@/lib/redis/session";
|
||||
import { type NextRequest, NextResponse } from "next/server";
|
||||
import { verifyAccessToken } from "@/lib/auth/jwt";
|
||||
import { getMainCharacter } from "@/lib/prisma/queries/characters";
|
||||
import {
|
||||
getDefaultSubscriptionTier,
|
||||
getSubscriptionTierById,
|
||||
} from "@/lib/prisma/queries/subscription-tiers";
|
||||
import { getUserById } from "@/lib/prisma/queries/users";
|
||||
import { getSubscriptionTierById, getDefaultSubscriptionTier } from "@/lib/prisma/queries/subscription-tiers";
|
||||
import {
|
||||
deleteSession,
|
||||
getSession,
|
||||
refreshSession,
|
||||
setSession,
|
||||
} from "@/lib/redis/session";
|
||||
import { secureLogger } from "@/lib/secure-logger";
|
||||
|
||||
/**
|
||||
* GET /api/auth/session
|
||||
* Verifica e retorna dados da sessão atual
|
||||
*/
|
||||
export async function GET(request: NextRequest) {
|
||||
export async function GET(_request: NextRequest) {
|
||||
try {
|
||||
// Buscar sessionId do cookie
|
||||
const cookieStore = await cookies();
|
||||
@@ -24,7 +32,7 @@ export async function GET(request: NextRequest) {
|
||||
authenticated: false,
|
||||
error: "Nenhuma sessão encontrada",
|
||||
},
|
||||
{ status: 401 }
|
||||
{ status: 401 },
|
||||
);
|
||||
}
|
||||
|
||||
@@ -39,7 +47,7 @@ export async function GET(request: NextRequest) {
|
||||
authenticated: false,
|
||||
error: "Sessão expirada ou inválida",
|
||||
},
|
||||
{ status: 401 }
|
||||
{ status: 401 },
|
||||
);
|
||||
|
||||
response.cookies.delete("sessionId");
|
||||
@@ -58,21 +66,19 @@ export async function GET(request: NextRequest) {
|
||||
authenticated: false,
|
||||
error: "Token expirado",
|
||||
},
|
||||
{ status: 401 }
|
||||
{ status: 401 },
|
||||
);
|
||||
|
||||
response.cookies.delete("sessionId");
|
||||
return response;
|
||||
}
|
||||
|
||||
let tierInfo:
|
||||
| {
|
||||
subscription_tier_id: number | null;
|
||||
name: string | null;
|
||||
slug: string | null;
|
||||
level: number | null;
|
||||
}
|
||||
| null = {
|
||||
let tierInfo: {
|
||||
subscription_tier_id: number | null;
|
||||
name: string | null;
|
||||
slug: string | null;
|
||||
level: number | null;
|
||||
} | null = {
|
||||
subscription_tier_id: session.subscriptionTierId,
|
||||
name: session.subscriptionTierName,
|
||||
slug: session.subscriptionTierSlug,
|
||||
@@ -86,7 +92,9 @@ export async function GET(request: NextRequest) {
|
||||
) {
|
||||
const dbUser = await getUserById(session.userId);
|
||||
const fetchedTier = dbUser?.subscription_tier
|
||||
? await getSubscriptionTierById(dbUser.subscription_tier.subscription_tier_id)
|
||||
? await getSubscriptionTierById(
|
||||
dbUser.subscription_tier.subscription_tier_id,
|
||||
)
|
||||
: await getDefaultSubscriptionTier();
|
||||
|
||||
tierInfo = fetchedTier
|
||||
@@ -139,7 +147,7 @@ export async function GET(request: NextRequest) {
|
||||
: null,
|
||||
},
|
||||
},
|
||||
{ status: 200 }
|
||||
{ status: 200 },
|
||||
);
|
||||
} catch (error) {
|
||||
secureLogger.error("❌ Erro ao verificar sessão", {
|
||||
@@ -154,7 +162,7 @@ export async function GET(request: NextRequest) {
|
||||
authenticated: false,
|
||||
error: error instanceof Error ? error.message : "Erro interno",
|
||||
},
|
||||
{ status: 500 }
|
||||
{ status: 500 },
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -163,7 +171,7 @@ export async function GET(request: NextRequest) {
|
||||
* DELETE /api/auth/session
|
||||
* Encerra a sessão atual
|
||||
*/
|
||||
export async function DELETE(request: NextRequest) {
|
||||
export async function DELETE(_request: NextRequest) {
|
||||
try {
|
||||
const cookieStore = await cookies();
|
||||
const sessionId = cookieStore.get("sessionId")?.value;
|
||||
@@ -177,7 +185,7 @@ export async function DELETE(request: NextRequest) {
|
||||
success: true,
|
||||
message: "Sessão encerrada",
|
||||
},
|
||||
{ status: 200 }
|
||||
{ status: 200 },
|
||||
);
|
||||
|
||||
response.cookies.delete("sessionId");
|
||||
@@ -193,8 +201,7 @@ export async function DELETE(request: NextRequest) {
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : "Erro interno",
|
||||
},
|
||||
{ status: 500 }
|
||||
{ status: 500 },
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,13 +3,16 @@
|
||||
* POST /api/chats/[chatId]/messages - Envia mensagem (fallback se Socket.io não estiver disponível)
|
||||
*/
|
||||
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { type NextRequest, NextResponse } from "next/server";
|
||||
import { requireAuth } from "@/lib/auth/middleware";
|
||||
import { connectMongoDB } from "@/lib/mongodb/connection";
|
||||
import { Chat } from "@/lib/mongodb/models/Chat";
|
||||
import { Message } from "@/lib/mongodb/models/Message";
|
||||
import { sendMessageSchema, getMessagesSchema } from "@/lib/validation/chat-schemas";
|
||||
import { secureLogger } from "@/lib/secure-logger";
|
||||
import {
|
||||
getMessagesSchema,
|
||||
sendMessageSchema,
|
||||
} from "@/lib/validation/chat-schemas";
|
||||
|
||||
/**
|
||||
* GET /api/chats/[chatId]/messages
|
||||
@@ -17,7 +20,7 @@ import { secureLogger } from "@/lib/secure-logger";
|
||||
*/
|
||||
export async function GET(
|
||||
request: NextRequest,
|
||||
{ params }: { params: Promise<{ chatId: string }> }
|
||||
{ params }: { params: Promise<{ chatId: string }> },
|
||||
) {
|
||||
try {
|
||||
const authResult = await requireAuth(request);
|
||||
@@ -26,7 +29,7 @@ export async function GET(
|
||||
|
||||
const { chatId } = await params;
|
||||
const { searchParams } = new URL(request.url);
|
||||
|
||||
|
||||
const queryValidation = getMessagesSchema.safeParse({
|
||||
page: searchParams.get("page"),
|
||||
limit: searchParams.get("limit"),
|
||||
@@ -39,7 +42,7 @@ export async function GET(
|
||||
error: "Parâmetros inválidos",
|
||||
details: queryValidation.error.issues,
|
||||
},
|
||||
{ status: 400 }
|
||||
{ status: 400 },
|
||||
);
|
||||
}
|
||||
|
||||
@@ -53,14 +56,17 @@ export async function GET(
|
||||
if (!chat) {
|
||||
return NextResponse.json(
|
||||
{ success: false, error: "Chat não encontrado" },
|
||||
{ status: 404 }
|
||||
{ status: 404 },
|
||||
);
|
||||
}
|
||||
|
||||
if (chat.buyer_id !== auth.userId && chat.seller_id !== auth.userId) {
|
||||
return NextResponse.json(
|
||||
{ success: false, error: "Você não tem permissão para acessar este chat" },
|
||||
{ status: 403 }
|
||||
{
|
||||
success: false,
|
||||
error: "Você não tem permissão para acessar este chat",
|
||||
},
|
||||
{ status: 403 },
|
||||
);
|
||||
}
|
||||
|
||||
@@ -88,7 +94,7 @@ export async function GET(
|
||||
},
|
||||
{
|
||||
$set: { read_at: new Date() },
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
// Atualizar contador de não lidas
|
||||
@@ -100,19 +106,17 @@ export async function GET(
|
||||
await chat.save();
|
||||
|
||||
// Serializar mensagens (ordem reversa para mostrar mais antigas primeiro)
|
||||
const serializedMessages = messages
|
||||
.reverse()
|
||||
.map((msg) => ({
|
||||
_id: msg._id.toString(),
|
||||
chat_id: msg.chat_id,
|
||||
sender_id: msg.sender_id,
|
||||
content: msg.content,
|
||||
created_at: msg.created_at.toISOString(),
|
||||
read_at: msg.read_at?.toISOString() || null,
|
||||
edited_at: msg.edited_at?.toISOString() || null,
|
||||
message_type: msg.message_type,
|
||||
metadata: msg.metadata || null,
|
||||
}));
|
||||
const serializedMessages = messages.reverse().map((msg) => ({
|
||||
_id: msg._id.toString(),
|
||||
chat_id: msg.chat_id,
|
||||
sender_id: msg.sender_id,
|
||||
content: msg.content,
|
||||
created_at: msg.created_at.toISOString(),
|
||||
read_at: msg.read_at?.toISOString() || null,
|
||||
edited_at: msg.edited_at?.toISOString() || null,
|
||||
message_type: msg.message_type,
|
||||
metadata: msg.metadata || null,
|
||||
}));
|
||||
|
||||
return NextResponse.json(
|
||||
{
|
||||
@@ -125,7 +129,7 @@ export async function GET(
|
||||
totalPages: Math.ceil(total / limit),
|
||||
},
|
||||
},
|
||||
{ status: 200 }
|
||||
{ status: 200 },
|
||||
);
|
||||
} catch (error) {
|
||||
secureLogger.error("❌ Erro ao buscar mensagens", {
|
||||
@@ -135,7 +139,7 @@ export async function GET(
|
||||
|
||||
return NextResponse.json(
|
||||
{ success: false, error: "Erro ao buscar mensagens" },
|
||||
{ status: 500 }
|
||||
{ status: 500 },
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -146,7 +150,7 @@ export async function GET(
|
||||
*/
|
||||
export async function POST(
|
||||
request: NextRequest,
|
||||
{ params }: { params: Promise<{ chatId: string }> }
|
||||
{ params }: { params: Promise<{ chatId: string }> },
|
||||
) {
|
||||
try {
|
||||
const authResult = await requireAuth(request);
|
||||
@@ -164,7 +168,7 @@ export async function POST(
|
||||
error: "Dados inválidos",
|
||||
details: validationResult.error.issues,
|
||||
},
|
||||
{ status: 400 }
|
||||
{ status: 400 },
|
||||
);
|
||||
}
|
||||
|
||||
@@ -178,14 +182,17 @@ export async function POST(
|
||||
if (!chat) {
|
||||
return NextResponse.json(
|
||||
{ success: false, error: "Chat não encontrado" },
|
||||
{ status: 404 }
|
||||
{ status: 404 },
|
||||
);
|
||||
}
|
||||
|
||||
if (chat.buyer_id !== auth.userId && chat.seller_id !== auth.userId) {
|
||||
return NextResponse.json(
|
||||
{ success: false, error: "Você não tem permissão para enviar mensagens neste chat" },
|
||||
{ status: 403 }
|
||||
{
|
||||
success: false,
|
||||
error: "Você não tem permissão para enviar mensagens neste chat",
|
||||
},
|
||||
{ status: 403 },
|
||||
);
|
||||
}
|
||||
|
||||
@@ -202,7 +209,7 @@ export async function POST(
|
||||
// Atualizar chat
|
||||
chat.last_message_at = new Date();
|
||||
chat.last_message_preview = content.substring(0, 100);
|
||||
|
||||
|
||||
if (chat.buyer_id === auth.userId) {
|
||||
chat.seller_unread_count += 1;
|
||||
} else {
|
||||
@@ -228,7 +235,7 @@ export async function POST(
|
||||
},
|
||||
});
|
||||
}
|
||||
} catch (socketError) {
|
||||
} catch (_socketError) {
|
||||
// Socket.io não disponível, mensagem já foi salva
|
||||
secureLogger.warn("Socket.io não disponível, mensagem salva via REST", {
|
||||
chatId,
|
||||
@@ -248,7 +255,7 @@ export async function POST(
|
||||
message_type: message.message_type,
|
||||
},
|
||||
},
|
||||
{ status: 201 }
|
||||
{ status: 201 },
|
||||
);
|
||||
} catch (error) {
|
||||
secureLogger.error("❌ Erro ao enviar mensagem", {
|
||||
@@ -258,8 +265,7 @@ export async function POST(
|
||||
|
||||
return NextResponse.json(
|
||||
{ success: false, error: "Erro ao enviar mensagem" },
|
||||
{ status: 500 }
|
||||
{ status: 500 },
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* GET /api/chats/[chatId] - Busca detalhes de um chat específico
|
||||
*/
|
||||
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { type NextRequest, NextResponse } from "next/server";
|
||||
import { requireAuth } from "@/lib/auth/middleware";
|
||||
import { connectMongoDB } from "@/lib/mongodb/connection";
|
||||
import { Chat } from "@/lib/mongodb/models/Chat";
|
||||
@@ -10,7 +10,7 @@ import { secureLogger } from "@/lib/secure-logger";
|
||||
|
||||
export async function GET(
|
||||
request: NextRequest,
|
||||
{ params }: { params: Promise<{ chatId: string }> }
|
||||
{ params }: { params: Promise<{ chatId: string }> },
|
||||
) {
|
||||
try {
|
||||
const authResult = await requireAuth(request);
|
||||
@@ -27,15 +27,18 @@ export async function GET(
|
||||
if (!chat) {
|
||||
return NextResponse.json(
|
||||
{ success: false, error: "Chat não encontrado" },
|
||||
{ status: 404 }
|
||||
{ status: 404 },
|
||||
);
|
||||
}
|
||||
|
||||
// Verificar se o usuário pertence ao chat
|
||||
if (chat.buyer_id !== auth.userId && chat.seller_id !== auth.userId) {
|
||||
return NextResponse.json(
|
||||
{ success: false, error: "Você não tem permissão para acessar este chat" },
|
||||
{ status: 403 }
|
||||
{
|
||||
success: false,
|
||||
error: "Você não tem permissão para acessar este chat",
|
||||
},
|
||||
{ status: 403 },
|
||||
);
|
||||
}
|
||||
|
||||
@@ -59,7 +62,7 @@ export async function GET(
|
||||
seller_cache: chat.seller_cache,
|
||||
},
|
||||
},
|
||||
{ status: 200 }
|
||||
{ status: 200 },
|
||||
);
|
||||
} catch (error) {
|
||||
secureLogger.error("❌ Erro ao buscar chat", {
|
||||
@@ -69,8 +72,7 @@ export async function GET(
|
||||
|
||||
return NextResponse.json(
|
||||
{ success: false, error: "Erro ao buscar chat" },
|
||||
{ status: 500 }
|
||||
{ status: 500 },
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* POST /api/chats/mark-all-read
|
||||
* Marca todas as mensagens de todos os chats do usuário como lidas
|
||||
*/
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { type NextRequest, NextResponse } from "next/server";
|
||||
import { requireAuth } from "@/lib/auth/middleware";
|
||||
import { connectMongoDB } from "@/lib/mongodb/connection";
|
||||
import { Chat } from "@/lib/mongodb/models/Chat";
|
||||
@@ -26,7 +26,7 @@ export async function POST(request: NextRequest) {
|
||||
// Para cada chat, marcar mensagens não lidas como lidas
|
||||
const updatePromises = chats.map(async (chat) => {
|
||||
const chatId = chat.chat_id;
|
||||
|
||||
|
||||
// Marcar mensagens como lidas
|
||||
await Message.updateMany(
|
||||
{
|
||||
@@ -36,7 +36,7 @@ export async function POST(request: NextRequest) {
|
||||
},
|
||||
{
|
||||
$set: { read_at: new Date() },
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
// Zerar contador de não lidas
|
||||
@@ -45,7 +45,7 @@ export async function POST(request: NextRequest) {
|
||||
} else {
|
||||
chat.seller_unread_count = 0;
|
||||
}
|
||||
|
||||
|
||||
return chat.save();
|
||||
});
|
||||
|
||||
@@ -56,7 +56,7 @@ export async function POST(request: NextRequest) {
|
||||
success: true,
|
||||
message: "Todos os chats marcados como lidos",
|
||||
},
|
||||
{ status: 200 }
|
||||
{ status: 200 },
|
||||
);
|
||||
} catch (error) {
|
||||
secureLogger.error("❌ Erro ao marcar chats como lidos", {
|
||||
@@ -66,7 +66,7 @@ export async function POST(request: NextRequest) {
|
||||
|
||||
return NextResponse.json(
|
||||
{ success: false, error: "Erro ao marcar como lido" },
|
||||
{ status: 500 }
|
||||
{ status: 500 },
|
||||
);
|
||||
}
|
||||
}
|
||||
+17
-16
@@ -3,15 +3,15 @@
|
||||
* POST /api/chats - Cria novo chat
|
||||
*/
|
||||
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { type NextRequest, NextResponse } from "next/server";
|
||||
import { requireAuth } from "@/lib/auth/middleware";
|
||||
import { connectMongoDB } from "@/lib/mongodb/connection";
|
||||
import { Chat } from "@/lib/mongodb/models/Chat";
|
||||
import { createChatSchema } from "@/lib/validation/chat-schemas";
|
||||
import { getListingById } from "@/lib/prisma/queries/listings";
|
||||
import { getMainCharacter } from "@/lib/prisma/queries/characters";
|
||||
import { getListingById } from "@/lib/prisma/queries/listings";
|
||||
import { getUserById } from "@/lib/prisma/queries/users";
|
||||
import { secureLogger } from "@/lib/secure-logger";
|
||||
import { createChatSchema } from "@/lib/validation/chat-schemas";
|
||||
|
||||
/**
|
||||
* GET /api/chats
|
||||
@@ -57,7 +57,7 @@ export async function GET(request: NextRequest) {
|
||||
chats: serializedChats,
|
||||
count: serializedChats.length,
|
||||
},
|
||||
{ status: 200 }
|
||||
{ status: 200 },
|
||||
);
|
||||
} catch (error) {
|
||||
secureLogger.error("❌ Erro ao buscar chats", {
|
||||
@@ -67,7 +67,7 @@ export async function GET(request: NextRequest) {
|
||||
|
||||
return NextResponse.json(
|
||||
{ success: false, error: "Erro ao buscar chats" },
|
||||
{ status: 500 }
|
||||
{ status: 500 },
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -92,7 +92,7 @@ export async function POST(request: NextRequest) {
|
||||
error: "Dados inválidos",
|
||||
details: validationResult.error.issues,
|
||||
},
|
||||
{ status: 400 }
|
||||
{ status: 400 },
|
||||
);
|
||||
}
|
||||
|
||||
@@ -105,7 +105,7 @@ export async function POST(request: NextRequest) {
|
||||
if (!listing) {
|
||||
return NextResponse.json(
|
||||
{ success: false, error: "Anúncio não encontrado" },
|
||||
{ status: 404 }
|
||||
{ status: 404 },
|
||||
);
|
||||
}
|
||||
|
||||
@@ -113,7 +113,7 @@ export async function POST(request: NextRequest) {
|
||||
if (listing.seller_id === auth.userId) {
|
||||
return NextResponse.json(
|
||||
{ success: false, error: "Você não pode criar um chat com você mesmo" },
|
||||
{ status: 400 }
|
||||
{ status: 400 },
|
||||
);
|
||||
}
|
||||
|
||||
@@ -121,7 +121,7 @@ export async function POST(request: NextRequest) {
|
||||
if (listing.listing_status !== "active") {
|
||||
return NextResponse.json(
|
||||
{ success: false, error: "Anúncio não está disponível para chat" },
|
||||
{ status: 400 }
|
||||
{ status: 400 },
|
||||
);
|
||||
}
|
||||
|
||||
@@ -154,7 +154,7 @@ export async function POST(request: NextRequest) {
|
||||
},
|
||||
already_exists: true,
|
||||
},
|
||||
{ status: 200 }
|
||||
{ status: 200 },
|
||||
);
|
||||
}
|
||||
|
||||
@@ -163,7 +163,7 @@ export async function POST(request: NextRequest) {
|
||||
if (!buyer) {
|
||||
return NextResponse.json(
|
||||
{ success: false, error: "Usuário não encontrado" },
|
||||
{ status: 404 }
|
||||
{ status: 404 },
|
||||
);
|
||||
}
|
||||
|
||||
@@ -174,7 +174,7 @@ export async function POST(request: NextRequest) {
|
||||
if (!seller) {
|
||||
return NextResponse.json(
|
||||
{ success: false, error: "Vendedor não encontrado" },
|
||||
{ status: 404 }
|
||||
{ status: 404 },
|
||||
);
|
||||
}
|
||||
|
||||
@@ -191,7 +191,9 @@ export async function POST(request: NextRequest) {
|
||||
item_image_url: null, // Imagem será obtida via item_id se necessário
|
||||
price_pix: listing.price_pix ? Number(listing.price_pix) : null,
|
||||
price_tibia_coins: listing.price_tibia_coins || null,
|
||||
price_gold_coins: listing.price_gold_coins ? Number(listing.price_gold_coins) : null,
|
||||
price_gold_coins: listing.price_gold_coins
|
||||
? Number(listing.price_gold_coins)
|
||||
: null,
|
||||
server_name: listing.server_name,
|
||||
listing_status: listing.listing_status,
|
||||
},
|
||||
@@ -248,7 +250,7 @@ export async function POST(request: NextRequest) {
|
||||
},
|
||||
already_exists: false,
|
||||
},
|
||||
{ status: 201 }
|
||||
{ status: 201 },
|
||||
);
|
||||
} catch (error) {
|
||||
secureLogger.error("❌ Erro ao criar chat", {
|
||||
@@ -258,8 +260,7 @@ export async function POST(request: NextRequest) {
|
||||
|
||||
return NextResponse.json(
|
||||
{ success: false, error: "Erro ao criar chat" },
|
||||
{ status: 500 }
|
||||
{ status: 500 },
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* GET /api/chats/unread-count
|
||||
* Retorna contagem de usuários únicos com mensagens não vistas
|
||||
*/
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { type NextRequest, NextResponse } from "next/server";
|
||||
import { requireAuth } from "@/lib/auth/middleware";
|
||||
import { connectMongoDB } from "@/lib/mongodb/connection";
|
||||
import { Chat } from "@/lib/mongodb/models/Chat";
|
||||
@@ -54,7 +54,7 @@ export async function GET(request: NextRequest) {
|
||||
return total + chat.seller_unread_count;
|
||||
}, 0),
|
||||
},
|
||||
{ status: 200 }
|
||||
{ status: 200 },
|
||||
);
|
||||
} catch (error) {
|
||||
secureLogger.error("❌ Erro ao buscar contagem de chats não lidos", {
|
||||
@@ -64,7 +64,7 @@ export async function GET(request: NextRequest) {
|
||||
|
||||
return NextResponse.json(
|
||||
{ success: false, error: "Erro ao buscar contagem" },
|
||||
{ status: 500 }
|
||||
{ status: 500 },
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,13 @@
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { type NextRequest, NextResponse } from "next/server";
|
||||
import { abacatePay } from "@/lib/abacatepay";
|
||||
import { DONATION_PRODUCTS, DonorData } from "@/types/donation";
|
||||
import { createDonationSchema } from "@/lib/validation-schemas";
|
||||
import {
|
||||
checkRateLimit,
|
||||
getClientIP,
|
||||
createRateLimitResponse,
|
||||
getClientIP,
|
||||
} from "@/lib/rate-limiter";
|
||||
import { secureLogger } from "@/lib/secure-logger";
|
||||
import { createDonationSchema } from "@/lib/validation-schemas";
|
||||
import { DONATION_PRODUCTS } from "@/types/donation";
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
try {
|
||||
@@ -49,7 +49,7 @@ export async function POST(request: NextRequest) {
|
||||
error: "Dados de entrada inválidos",
|
||||
details: errors,
|
||||
},
|
||||
{ status: 400 }
|
||||
{ status: 400 },
|
||||
);
|
||||
}
|
||||
|
||||
@@ -72,8 +72,8 @@ export async function POST(request: NextRequest) {
|
||||
if (validatedData.tier === "custom") {
|
||||
// Converter para centavos
|
||||
finalAmount = Math.round(validatedData.customAmount! * 100);
|
||||
finalDescription = `Doação Personalizada - R$ ${validatedData.customAmount!.toFixed(
|
||||
2
|
||||
finalDescription = `Doação Personalizada - R$ ${validatedData.customAmount?.toFixed(
|
||||
2,
|
||||
)}`;
|
||||
|
||||
secureLogger.info("💰 Valor customizado calculado", {
|
||||
@@ -161,7 +161,7 @@ export async function POST(request: NextRequest) {
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : "Erro interno",
|
||||
},
|
||||
{ status: 500 }
|
||||
{ status: 500 },
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { type NextRequest, NextResponse } from "next/server";
|
||||
import { getPaymentStatus } from "@/lib/payment-cache";
|
||||
import {
|
||||
checkRateLimit,
|
||||
getClientIP,
|
||||
createRateLimitResponse,
|
||||
getClientIP,
|
||||
} from "@/lib/rate-limiter";
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
@@ -21,7 +21,7 @@ export async function GET(request: NextRequest) {
|
||||
if (!pixId) {
|
||||
return NextResponse.json(
|
||||
{ error: "pixId é obrigatório" },
|
||||
{ status: 400 }
|
||||
{ status: 400 },
|
||||
);
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ export async function GET(request: NextRequest) {
|
||||
console.error("Erro ao verificar status do pagamento:", error);
|
||||
return NextResponse.json(
|
||||
{ error: "Erro ao verificar status" },
|
||||
{ status: 500 }
|
||||
{ status: 500 },
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { type NextRequest, NextResponse } from "next/server";
|
||||
import { abacatePay } from "@/lib/abacatepay";
|
||||
import {
|
||||
checkRateLimit,
|
||||
getClientIP,
|
||||
createRateLimitResponse,
|
||||
getClientIP,
|
||||
} from "@/lib/rate-limiter";
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
@@ -21,7 +21,7 @@ export async function GET(request: NextRequest) {
|
||||
if (!billingId) {
|
||||
return NextResponse.json(
|
||||
{ success: false, error: "ID da cobrança é obrigatório" },
|
||||
{ status: 400 }
|
||||
{ status: 400 },
|
||||
);
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@ export async function GET(request: NextRequest) {
|
||||
error:
|
||||
error instanceof Error ? error.message : "Erro ao verificar cobrança",
|
||||
},
|
||||
{ status: 500 }
|
||||
{ status: 500 },
|
||||
);
|
||||
}
|
||||
}
|
||||
+31
-23
@@ -1,9 +1,9 @@
|
||||
import { NextRequest } from "next/server";
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import { getItemById } from "@/lib/items-db";
|
||||
import type { NextRequest } from "next/server";
|
||||
import { getItemByIdUnified } from "@/lib/db/queries/items";
|
||||
import { buildItemImagePath } from "@/lib/item-images";
|
||||
import { getItemById } from "@/lib/items-db";
|
||||
|
||||
export async function GET(req: NextRequest) {
|
||||
const { searchParams } = new URL(req.url);
|
||||
@@ -17,12 +17,12 @@ export async function GET(req: NextRequest) {
|
||||
if (itemId) {
|
||||
try {
|
||||
const itemIdNum = parseInt(itemId, 10);
|
||||
if (!isNaN(itemIdNum)) {
|
||||
if (!Number.isNaN(itemIdNum)) {
|
||||
// Tentar primeiro buscar na tabela unificada PostgreSQL
|
||||
// Se a tabela não existir, retornará null silenciosamente
|
||||
let item: any = null;
|
||||
item = await getItemByIdUnified(itemIdNum);
|
||||
|
||||
|
||||
// Se encontrou na tabela unificada, validar o nome se fornecido
|
||||
if (item && itemName && item.item_name) {
|
||||
if (item.item_name.toLowerCase() !== itemName.toLowerCase()) {
|
||||
@@ -30,24 +30,28 @@ export async function GET(req: NextRequest) {
|
||||
item = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Se não encontrou na tabela unificada, tentar SQLite legado
|
||||
// Passar itemName para buscar por ID E NOME (evita ambiguidade)
|
||||
if (!item) {
|
||||
item = getItemById(itemIdNum, itemName || undefined);
|
||||
}
|
||||
|
||||
|
||||
if (item) {
|
||||
// PRIORIDADE: 1. image_local_path (local), 2. image_url (externa), 3. image (fallback)
|
||||
const imageLocalPath = item.image_local_path || "";
|
||||
const imageUrl = item.image_url || "";
|
||||
const image = item.image || "";
|
||||
|
||||
|
||||
// Tentar primeiro a imagem local
|
||||
if (imageLocalPath) {
|
||||
const normalizedPath = imageLocalPath.replace(/\\/g, "/");
|
||||
const category = (item.categoria || item.category || "other").toLowerCase();
|
||||
|
||||
const category = (
|
||||
item.categoria ||
|
||||
item.category ||
|
||||
"other"
|
||||
).toLowerCase();
|
||||
|
||||
// Se já for um caminho completo (começa com data/), usar diretamente
|
||||
if (normalizedPath.startsWith("data/")) {
|
||||
imagePath = normalizedPath;
|
||||
@@ -55,12 +59,12 @@ export async function GET(req: NextRequest) {
|
||||
// Construir o caminho usando a função helper
|
||||
imagePath = buildItemImagePath(category, normalizedPath);
|
||||
}
|
||||
|
||||
|
||||
// Verificar se o arquivo existe antes de usar
|
||||
if (imagePath) {
|
||||
const baseDir = path.join(process.cwd(), "data", "images");
|
||||
const resolved = path.join(process.cwd(), imagePath);
|
||||
|
||||
|
||||
if (resolved.startsWith(baseDir)) {
|
||||
try {
|
||||
await fs.promises.access(resolved);
|
||||
@@ -72,11 +76,14 @@ export async function GET(req: NextRequest) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Se não encontrou imagem local ou não existe, usar URL externa como fallback
|
||||
if (!imagePath && (imageUrl || image)) {
|
||||
const externalUrl = imageUrl || image;
|
||||
if (externalUrl.startsWith("http://") || externalUrl.startsWith("https://")) {
|
||||
if (
|
||||
externalUrl.startsWith("http://") ||
|
||||
externalUrl.startsWith("https://")
|
||||
) {
|
||||
return Response.redirect(externalUrl, 302);
|
||||
}
|
||||
}
|
||||
@@ -99,7 +106,7 @@ export async function GET(req: NextRequest) {
|
||||
// Permitir apenas caminhos sob data/images para segurança
|
||||
const baseDir = path.join(process.cwd(), "data", "images");
|
||||
const resolved = path.join(process.cwd(), imagePath);
|
||||
|
||||
|
||||
if (!resolved.startsWith(baseDir)) {
|
||||
return new Response("Forbidden", { status: 403 });
|
||||
}
|
||||
@@ -111,18 +118,19 @@ export async function GET(req: NextRequest) {
|
||||
ext === ".gif"
|
||||
? "image/gif"
|
||||
: ext === ".png"
|
||||
? "image/png"
|
||||
: ext === ".webp"
|
||||
? "image/webp"
|
||||
: ext === ".jpg" || ext === ".jpeg"
|
||||
? "image/jpeg"
|
||||
: "application/octet-stream";
|
||||
return new Response(file, { status: 200, headers: { "Content-Type": contentType } });
|
||||
? "image/png"
|
||||
: ext === ".webp"
|
||||
? "image/webp"
|
||||
: ext === ".jpg" || ext === ".jpeg"
|
||||
? "image/jpeg"
|
||||
: "application/octet-stream";
|
||||
return new Response(file, {
|
||||
status: 200,
|
||||
headers: { "Content-Type": contentType },
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("[api/images] Erro ao ler arquivo:", error);
|
||||
// Retornar 404 silenciosamente para não gerar erros no console
|
||||
return new Response("Not Found", { status: 404 });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { NextRequest } from "next/server";
|
||||
import type { NextRequest } from "next/server";
|
||||
import { prisma } from "@/lib/prisma/app";
|
||||
|
||||
export async function GET(_req: NextRequest) {
|
||||
@@ -15,11 +15,6 @@ export async function GET(_req: NextRequest) {
|
||||
.filter((category): category is string => Boolean(category)),
|
||||
});
|
||||
} catch (e: any) {
|
||||
return Response.json(
|
||||
{ error: e?.message || "Erro" },
|
||||
{ status: 500 }
|
||||
);
|
||||
return Response.json({ error: e?.message || "Erro" }, { status: 500 });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+19
-9
@@ -1,6 +1,6 @@
|
||||
import { NextRequest } from "next/server";
|
||||
import { searchItems, type ItemSort } from "@/lib/prisma/queries/items";
|
||||
import type { NextRequest } from "next/server";
|
||||
import { buildPublicItemImageUrl } from "@/lib/item-images";
|
||||
import { type ItemSort, searchItems } from "@/lib/prisma/queries/items";
|
||||
|
||||
function parseArray(param: string | null): string[] {
|
||||
if (!param) return [];
|
||||
@@ -28,7 +28,8 @@ export async function GET(req: NextRequest) {
|
||||
const rarities = parseArray(searchParams.get("rarity"));
|
||||
const server = searchParams.get("server") || undefined;
|
||||
const sortParamRaw = (searchParams.get("sort") || "name").toLowerCase();
|
||||
const sortParam = (allowedSorts.find((sort) => sort === sortParamRaw) || "name") as ItemSort;
|
||||
const sortParam = (allowedSorts.find((sort) => sort === sortParamRaw) ||
|
||||
"name") as ItemSort;
|
||||
const page = Number(searchParams.get("page") || "1");
|
||||
const pageSize = Number(searchParams.get("pageSize") || "24");
|
||||
|
||||
@@ -48,7 +49,11 @@ export async function GET(req: NextRequest) {
|
||||
return { ...item, image_url: img, image: img };
|
||||
}
|
||||
if (img.startsWith("data/")) {
|
||||
return { ...item, image_url: img, image: `/api/images?path=${encodeURIComponent(img)}` };
|
||||
return {
|
||||
...item,
|
||||
image_url: img,
|
||||
image: `/api/images?path=${encodeURIComponent(img)}`,
|
||||
};
|
||||
}
|
||||
const category = item.category || "other";
|
||||
return {
|
||||
@@ -60,8 +65,15 @@ export async function GET(req: NextRequest) {
|
||||
|
||||
const totalPages = Math.max(1, Math.ceil(total / Math.max(1, pageSize)));
|
||||
return Response.json(
|
||||
{ items: itemsWithImage, page, pageSize, total, totalPages, sort: sortParam },
|
||||
{ status: 200 }
|
||||
{
|
||||
items: itemsWithImage,
|
||||
page,
|
||||
pageSize,
|
||||
total,
|
||||
totalPages,
|
||||
sort: sortParam,
|
||||
},
|
||||
{ status: 200 },
|
||||
);
|
||||
} catch (error: any) {
|
||||
console.error("[api/items] Erro:", error?.message, error?.stack);
|
||||
@@ -70,9 +82,7 @@ export async function GET(req: NextRequest) {
|
||||
error: "Falha ao consultar itens",
|
||||
message: error?.message || "Erro desconhecido",
|
||||
},
|
||||
{ status: 500 }
|
||||
{ status: 500 },
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { secureLogger } from "@/lib/secure-logger";
|
||||
import { type NextRequest, NextResponse } from "next/server";
|
||||
import { requireAuth } from "@/lib/auth/middleware";
|
||||
import { getListingById, updateListing } from "@/lib/prisma/queries/listings";
|
||||
import { secureLogger } from "@/lib/secure-logger";
|
||||
|
||||
/**
|
||||
* POST /api/listings/:id/activate
|
||||
@@ -9,7 +9,7 @@ import { getListingById, updateListing } from "@/lib/prisma/queries/listings";
|
||||
*/
|
||||
export async function POST(
|
||||
request: NextRequest,
|
||||
{ params }: { params: Promise<{ id: string }> }
|
||||
{ params }: { params: Promise<{ id: string }> },
|
||||
) {
|
||||
try {
|
||||
// Autenticação obrigatória
|
||||
@@ -24,13 +24,13 @@ export async function POST(
|
||||
const { id } = await params;
|
||||
const listingId = parseInt(id, 10);
|
||||
|
||||
if (isNaN(listingId)) {
|
||||
if (Number.isNaN(listingId)) {
|
||||
return NextResponse.json(
|
||||
{
|
||||
success: false,
|
||||
error: "ID de anúncio inválido",
|
||||
},
|
||||
{ status: 400 }
|
||||
{ status: 400 },
|
||||
);
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@ export async function POST(
|
||||
success: false,
|
||||
error: "Anúncio não encontrado",
|
||||
},
|
||||
{ status: 404 }
|
||||
{ status: 404 },
|
||||
);
|
||||
}
|
||||
|
||||
@@ -53,7 +53,7 @@ export async function POST(
|
||||
success: false,
|
||||
error: "Você não tem permissão para reativar este anúncio",
|
||||
},
|
||||
{ status: 403 }
|
||||
{ status: 403 },
|
||||
);
|
||||
}
|
||||
|
||||
@@ -63,7 +63,7 @@ export async function POST(
|
||||
success: false,
|
||||
error: "Apenas anúncios pausados podem ser reativados",
|
||||
},
|
||||
{ status: 400 }
|
||||
{ status: 400 },
|
||||
);
|
||||
}
|
||||
|
||||
@@ -78,7 +78,7 @@ export async function POST(
|
||||
success: false,
|
||||
error: "Erro ao reativar anúncio",
|
||||
},
|
||||
{ status: 500 }
|
||||
{ status: 500 },
|
||||
);
|
||||
}
|
||||
|
||||
@@ -93,7 +93,7 @@ export async function POST(
|
||||
listing: updated,
|
||||
message: "Anúncio reativado com sucesso",
|
||||
},
|
||||
{ status: 200 }
|
||||
{ status: 200 },
|
||||
);
|
||||
} catch (error) {
|
||||
secureLogger.error("❌ Erro ao reativar anúncio", {
|
||||
@@ -105,8 +105,7 @@ export async function POST(
|
||||
success: false,
|
||||
error: "Erro ao reativar anúncio",
|
||||
},
|
||||
{ status: 500 }
|
||||
{ status: 500 },
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { secureLogger } from "@/lib/secure-logger";
|
||||
import { type NextRequest, NextResponse } from "next/server";
|
||||
import { requireAuth } from "@/lib/auth/middleware";
|
||||
import { placeBid } from "@/lib/prisma/queries/listings";
|
||||
import { createNotification } from "@/lib/prisma/queries/notifications";
|
||||
import { secureLogger } from "@/lib/secure-logger";
|
||||
|
||||
/**
|
||||
* POST /api/listings/[id]/bid
|
||||
@@ -10,7 +10,7 @@ import { createNotification } from "@/lib/prisma/queries/notifications";
|
||||
*/
|
||||
export async function POST(
|
||||
request: NextRequest,
|
||||
{ params }: { params: Promise<{ id: string }> }
|
||||
{ params }: { params: Promise<{ id: string }> },
|
||||
) {
|
||||
try {
|
||||
const authResult = await requireAuth(request);
|
||||
@@ -19,10 +19,10 @@ export async function POST(
|
||||
|
||||
const { id } = await params;
|
||||
const listingId = parseInt(id, 10);
|
||||
if (isNaN(listingId)) {
|
||||
if (Number.isNaN(listingId)) {
|
||||
return NextResponse.json(
|
||||
{ success: false, error: "ID de anúncio inválido" },
|
||||
{ status: 400 }
|
||||
{ status: 400 },
|
||||
);
|
||||
}
|
||||
|
||||
@@ -31,34 +31,42 @@ export async function POST(
|
||||
|
||||
if (!amount || typeof amount !== "number" || amount <= 0) {
|
||||
return NextResponse.json(
|
||||
{ success: false, error: "Valor do lance é obrigatório e deve ser maior que zero" },
|
||||
{ status: 400 }
|
||||
{
|
||||
success: false,
|
||||
error: "Valor do lance é obrigatório e deve ser maior que zero",
|
||||
},
|
||||
{ status: 400 },
|
||||
);
|
||||
}
|
||||
|
||||
if (!payment_method || !["pix", "tc", "gp"].includes(payment_method)) {
|
||||
return NextResponse.json(
|
||||
{ success: false, error: "Método de pagamento inválido" },
|
||||
{ status: 400 }
|
||||
{ status: 400 },
|
||||
);
|
||||
}
|
||||
|
||||
// Colocar lance
|
||||
const result = await placeBid(listingId, auth.userId, amount, payment_method);
|
||||
const result = await placeBid(
|
||||
listingId,
|
||||
auth.userId,
|
||||
amount,
|
||||
payment_method,
|
||||
);
|
||||
|
||||
if (!result.success) {
|
||||
return NextResponse.json(
|
||||
{ success: false, error: result.error },
|
||||
{ status: 400 }
|
||||
{ status: 400 },
|
||||
);
|
||||
}
|
||||
|
||||
// Criar notificação para o vendedor
|
||||
await createNotification({
|
||||
user_id: result.listing!.seller_id,
|
||||
user_id: result.listing?.seller_id,
|
||||
notification_type: "bid",
|
||||
title: "Novo lance!",
|
||||
message: `Novo lance de R$ ${amount.toFixed(2)} no item "${result.listing!.item_name}"`,
|
||||
message: `Novo lance de R$ ${amount.toFixed(2)} no item "${result.listing?.item_name}"`,
|
||||
related_id: listingId,
|
||||
send_whatsapp: false,
|
||||
});
|
||||
@@ -69,7 +77,7 @@ export async function POST(
|
||||
user_id: result.previousBidderId,
|
||||
notification_type: "bid",
|
||||
title: "Lance superado",
|
||||
message: `Seu lance foi superado no item "${result.listing!.item_name}"`,
|
||||
message: `Seu lance foi superado no item "${result.listing?.item_name}"`,
|
||||
related_id: listingId,
|
||||
send_whatsapp: false,
|
||||
});
|
||||
@@ -84,8 +92,12 @@ export async function POST(
|
||||
metadata: {
|
||||
bid_amount: amount,
|
||||
payment_method: payment_method,
|
||||
previous_bid: result.listing!.current_bid_amount ? Number(result.listing!.current_bid_amount) : null,
|
||||
min_increment: result.listing!.min_bid_increment ? Number(result.listing!.min_bid_increment) : null,
|
||||
previous_bid: result.listing?.current_bid_amount
|
||||
? Number(result.listing?.current_bid_amount)
|
||||
: null,
|
||||
min_increment: result.listing?.min_bid_increment
|
||||
? Number(result.listing?.min_bid_increment)
|
||||
: null,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -102,7 +114,7 @@ export async function POST(
|
||||
listing: result.listing,
|
||||
message: "Lance realizado com sucesso",
|
||||
},
|
||||
{ status: 200 }
|
||||
{ status: 200 },
|
||||
);
|
||||
} catch (error) {
|
||||
secureLogger.error("❌ Erro ao processar lance", {
|
||||
@@ -112,8 +124,7 @@ export async function POST(
|
||||
|
||||
return NextResponse.json(
|
||||
{ success: false, error: "Erro ao processar lance" },
|
||||
{ status: 500 }
|
||||
{ status: 500 },
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { secureLogger } from "@/lib/secure-logger";
|
||||
import { type NextRequest, NextResponse } from "next/server";
|
||||
import { requireAuth } from "@/lib/auth/middleware";
|
||||
import { getListingById } from "@/lib/prisma/queries/listings";
|
||||
import { prisma } from "@/lib/prisma/app";
|
||||
import { getListingById } from "@/lib/prisma/queries/listings";
|
||||
import { trackEvent } from "@/lib/prisma/queries/telemetry";
|
||||
import { secureLogger } from "@/lib/secure-logger";
|
||||
|
||||
/**
|
||||
* GET /api/listings/[id]/bids
|
||||
@@ -11,7 +11,7 @@ import { trackEvent } from "@/lib/prisma/queries/telemetry";
|
||||
*/
|
||||
export async function GET(
|
||||
request: NextRequest,
|
||||
{ params }: { params: Promise<{ id: string }> }
|
||||
{ params }: { params: Promise<{ id: string }> },
|
||||
) {
|
||||
try {
|
||||
const authResult = await requireAuth(request);
|
||||
@@ -19,10 +19,10 @@ export async function GET(
|
||||
|
||||
const { id } = await params;
|
||||
const listingId = parseInt(id, 10);
|
||||
if (isNaN(listingId)) {
|
||||
if (Number.isNaN(listingId)) {
|
||||
return NextResponse.json(
|
||||
{ success: false, error: "ID de anúncio inválido" },
|
||||
{ status: 400 }
|
||||
{ status: 400 },
|
||||
);
|
||||
}
|
||||
|
||||
@@ -31,14 +31,14 @@ export async function GET(
|
||||
if (!listing) {
|
||||
return NextResponse.json(
|
||||
{ success: false, error: "Anúncio não encontrado" },
|
||||
{ status: 404 }
|
||||
{ status: 404 },
|
||||
);
|
||||
}
|
||||
|
||||
if (!listing.is_auction) {
|
||||
return NextResponse.json(
|
||||
{ success: false, error: "Este anúncio não é um leilão" },
|
||||
{ status: 400 }
|
||||
{ status: 400 },
|
||||
);
|
||||
}
|
||||
|
||||
@@ -92,7 +92,7 @@ export async function GET(
|
||||
bids: formattedBids,
|
||||
total: formattedBids.length,
|
||||
},
|
||||
{ status: 200 }
|
||||
{ status: 200 },
|
||||
);
|
||||
} catch (error) {
|
||||
secureLogger.error("❌ Erro ao buscar histórico de lances", {
|
||||
@@ -102,8 +102,7 @@ export async function GET(
|
||||
|
||||
return NextResponse.json(
|
||||
{ success: false, error: "Erro ao buscar histórico de lances" },
|
||||
{ status: 500 }
|
||||
{ status: 500 },
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { secureLogger } from "@/lib/secure-logger";
|
||||
import { type NextRequest, NextResponse } from "next/server";
|
||||
import { requireAuth } from "@/lib/auth/middleware";
|
||||
import { getListingById } from "@/lib/prisma/queries/listings";
|
||||
import { isFavorite, toggleFavorite } from "@/lib/prisma/queries/favorites";
|
||||
import { getListingById } from "@/lib/prisma/queries/listings";
|
||||
import { secureLogger } from "@/lib/secure-logger";
|
||||
|
||||
/**
|
||||
* POST /api/listings/:id/favorite
|
||||
@@ -10,7 +10,7 @@ import { isFavorite, toggleFavorite } from "@/lib/prisma/queries/favorites";
|
||||
*/
|
||||
export async function POST(
|
||||
request: NextRequest,
|
||||
{ params }: { params: Promise<{ id: string }> }
|
||||
{ params }: { params: Promise<{ id: string }> },
|
||||
) {
|
||||
try {
|
||||
// Autenticação obrigatória
|
||||
@@ -25,13 +25,13 @@ export async function POST(
|
||||
const { id } = await params;
|
||||
const listingId = parseInt(id, 10);
|
||||
|
||||
if (isNaN(listingId)) {
|
||||
if (Number.isNaN(listingId)) {
|
||||
return NextResponse.json(
|
||||
{
|
||||
success: false,
|
||||
error: "ID de anúncio inválido",
|
||||
},
|
||||
{ status: 400 }
|
||||
{ status: 400 },
|
||||
);
|
||||
}
|
||||
|
||||
@@ -44,17 +44,20 @@ export async function POST(
|
||||
success: false,
|
||||
error: "Anúncio não encontrado",
|
||||
},
|
||||
{ status: 404 }
|
||||
{ status: 404 },
|
||||
);
|
||||
}
|
||||
|
||||
// Toggle favorite
|
||||
const favorited = await toggleFavorite(auth.userId, listingId);
|
||||
|
||||
secureLogger.info(`✅ Anúncio ${favorited ? "favoritado" : "desfavoritado"}`, {
|
||||
listingId,
|
||||
userId: auth.userId,
|
||||
});
|
||||
secureLogger.info(
|
||||
`✅ Anúncio ${favorited ? "favoritado" : "desfavoritado"}`,
|
||||
{
|
||||
listingId,
|
||||
userId: auth.userId,
|
||||
},
|
||||
);
|
||||
|
||||
return NextResponse.json(
|
||||
{
|
||||
@@ -62,7 +65,7 @@ export async function POST(
|
||||
favorited,
|
||||
message: favorited ? "Anúncio favoritado" : "Anúncio desfavoritado",
|
||||
},
|
||||
{ status: 200 }
|
||||
{ status: 200 },
|
||||
);
|
||||
} catch (error) {
|
||||
secureLogger.error("❌ Erro ao favoritar/desfavoritar anúncio", {
|
||||
@@ -75,7 +78,7 @@ export async function POST(
|
||||
success: false,
|
||||
error: "Erro ao favoritar/desfavoritar anúncio",
|
||||
},
|
||||
{ status: 500 }
|
||||
{ status: 500 },
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -86,7 +89,7 @@ export async function POST(
|
||||
*/
|
||||
export async function GET(
|
||||
request: NextRequest,
|
||||
{ params }: { params: Promise<{ id: string }> }
|
||||
{ params }: { params: Promise<{ id: string }> },
|
||||
) {
|
||||
try {
|
||||
// Autenticação obrigatória
|
||||
@@ -101,13 +104,13 @@ export async function GET(
|
||||
const { id } = await params;
|
||||
const listingId = parseInt(id, 10);
|
||||
|
||||
if (isNaN(listingId)) {
|
||||
if (Number.isNaN(listingId)) {
|
||||
return NextResponse.json(
|
||||
{
|
||||
success: false,
|
||||
error: "ID de anúncio inválido",
|
||||
},
|
||||
{ status: 400 }
|
||||
{ status: 400 },
|
||||
);
|
||||
}
|
||||
|
||||
@@ -118,7 +121,7 @@ export async function GET(
|
||||
success: true,
|
||||
favorited,
|
||||
},
|
||||
{ status: 200 }
|
||||
{ status: 200 },
|
||||
);
|
||||
} catch (error) {
|
||||
secureLogger.error("❌ Erro ao verificar favorito", {
|
||||
@@ -130,8 +133,7 @@ export async function GET(
|
||||
success: false,
|
||||
error: "Erro ao verificar favorito",
|
||||
},
|
||||
{ status: 500 }
|
||||
{ status: 500 },
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { secureLogger } from "@/lib/secure-logger";
|
||||
import { type NextRequest, NextResponse } from "next/server";
|
||||
import { requireAuth } from "@/lib/auth/middleware";
|
||||
import { connectMongoDB } from "@/lib/mongodb/connection";
|
||||
import { Chat } from "@/lib/mongodb/models/Chat";
|
||||
import { Message } from "@/lib/mongodb/models/Message";
|
||||
import { getMainCharacter } from "@/lib/prisma/queries/characters";
|
||||
import { getListingById } from "@/lib/prisma/queries/listings";
|
||||
import { createNotification } from "@/lib/prisma/queries/notifications";
|
||||
import {
|
||||
getPurchaseIntentionById,
|
||||
updatePurchaseIntentionStatus,
|
||||
} from "@/lib/prisma/queries/purchase-intentions";
|
||||
import { createNotification } from "@/lib/prisma/queries/notifications";
|
||||
import { connectMongoDB } from "@/lib/mongodb/connection";
|
||||
import { Chat } from "@/lib/mongodb/models/Chat";
|
||||
import { Message } from "@/lib/mongodb/models/Message";
|
||||
import { getUserById } from "@/lib/prisma/queries/users";
|
||||
import { getMainCharacter } from "@/lib/prisma/queries/characters";
|
||||
import { secureLogger } from "@/lib/secure-logger";
|
||||
|
||||
/**
|
||||
* GET /api/listings/[id]/intentions/[intentionId]
|
||||
@@ -19,7 +19,7 @@ import { getMainCharacter } from "@/lib/prisma/queries/characters";
|
||||
*/
|
||||
export async function GET(
|
||||
request: NextRequest,
|
||||
{ params }: { params: Promise<{ id: string; intentionId: string }> }
|
||||
{ params }: { params: Promise<{ id: string; intentionId: string }> },
|
||||
) {
|
||||
try {
|
||||
const authResult = await requireAuth(request);
|
||||
@@ -27,10 +27,10 @@ export async function GET(
|
||||
|
||||
const { intentionId: intentionIdParam } = await params;
|
||||
const intentionId = parseInt(intentionIdParam, 10);
|
||||
if (isNaN(intentionId)) {
|
||||
if (Number.isNaN(intentionId)) {
|
||||
return NextResponse.json(
|
||||
{ success: false, error: "ID de intenção inválido" },
|
||||
{ status: 400 }
|
||||
{ status: 400 },
|
||||
);
|
||||
}
|
||||
|
||||
@@ -38,15 +38,21 @@ export async function GET(
|
||||
if (!intention) {
|
||||
return NextResponse.json(
|
||||
{ success: false, error: "Intenção não encontrada" },
|
||||
{ status: 404 }
|
||||
{ status: 404 },
|
||||
);
|
||||
}
|
||||
|
||||
// Verificar permissões: vendedor ou comprador
|
||||
if (intention.listing?.seller_id !== authResult.userId && intention.buyer_id !== authResult.userId) {
|
||||
if (
|
||||
intention.listing?.seller_id !== authResult.userId &&
|
||||
intention.buyer_id !== authResult.userId
|
||||
) {
|
||||
return NextResponse.json(
|
||||
{ success: false, error: "Você não tem permissão para ver esta intenção" },
|
||||
{ status: 403 }
|
||||
{
|
||||
success: false,
|
||||
error: "Você não tem permissão para ver esta intenção",
|
||||
},
|
||||
{ status: 403 },
|
||||
);
|
||||
}
|
||||
|
||||
@@ -55,7 +61,7 @@ export async function GET(
|
||||
success: true,
|
||||
intention,
|
||||
},
|
||||
{ status: 200 }
|
||||
{ status: 200 },
|
||||
);
|
||||
} catch (error) {
|
||||
secureLogger.error("❌ Erro ao buscar intenção", {
|
||||
@@ -65,7 +71,7 @@ export async function GET(
|
||||
|
||||
return NextResponse.json(
|
||||
{ success: false, error: "Erro ao buscar intenção" },
|
||||
{ status: 500 }
|
||||
{ status: 500 },
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -76,7 +82,7 @@ export async function GET(
|
||||
*/
|
||||
export async function PATCH(
|
||||
request: NextRequest,
|
||||
{ params }: { params: Promise<{ id: string; intentionId: string }> }
|
||||
{ params }: { params: Promise<{ id: string; intentionId: string }> },
|
||||
) {
|
||||
try {
|
||||
const authResult = await requireAuth(request);
|
||||
@@ -84,10 +90,10 @@ export async function PATCH(
|
||||
|
||||
const { intentionId: intentionIdParam } = await params;
|
||||
const intentionId = parseInt(intentionIdParam, 10);
|
||||
if (isNaN(intentionId)) {
|
||||
if (Number.isNaN(intentionId)) {
|
||||
return NextResponse.json(
|
||||
{ success: false, error: "ID de intenção inválido" },
|
||||
{ status: 400 }
|
||||
{ status: 400 },
|
||||
);
|
||||
}
|
||||
|
||||
@@ -100,8 +106,11 @@ export async function PATCH(
|
||||
|
||||
if (!status || !["accepted", "rejected"].includes(status)) {
|
||||
return NextResponse.json(
|
||||
{ success: false, error: "Status inválido. Use 'accepted' ou 'rejected'" },
|
||||
{ status: 400 }
|
||||
{
|
||||
success: false,
|
||||
error: "Status inválido. Use 'accepted' ou 'rejected'",
|
||||
},
|
||||
{ status: 400 },
|
||||
);
|
||||
}
|
||||
|
||||
@@ -110,15 +119,18 @@ export async function PATCH(
|
||||
if (!intention) {
|
||||
return NextResponse.json(
|
||||
{ success: false, error: "Intenção não encontrada" },
|
||||
{ status: 404 }
|
||||
{ status: 404 },
|
||||
);
|
||||
}
|
||||
|
||||
// Verificar se o usuário é o vendedor
|
||||
if (intention.listing?.seller_id !== authResult.userId) {
|
||||
return NextResponse.json(
|
||||
{ success: false, error: "Apenas o vendedor pode aceitar ou rejeitar intenções" },
|
||||
{ status: 403 }
|
||||
{
|
||||
success: false,
|
||||
error: "Apenas o vendedor pode aceitar ou rejeitar intenções",
|
||||
},
|
||||
{ status: 403 },
|
||||
);
|
||||
}
|
||||
|
||||
@@ -126,16 +138,20 @@ export async function PATCH(
|
||||
if (intention.status !== "pending") {
|
||||
return NextResponse.json(
|
||||
{ success: false, error: `Esta intenção já foi ${intention.status}` },
|
||||
{ status: 400 }
|
||||
{ status: 400 },
|
||||
);
|
||||
}
|
||||
|
||||
// Verificar se o anúncio ainda está disponível (ativo ou em escrow)
|
||||
const listing = await getListingById(intention.listing_id);
|
||||
if (!listing || (listing.listing_status !== "active" && listing.listing_status !== "in_escrow")) {
|
||||
if (
|
||||
!listing ||
|
||||
(listing.listing_status !== "active" &&
|
||||
listing.listing_status !== "in_escrow")
|
||||
) {
|
||||
return NextResponse.json(
|
||||
{ success: false, error: "Anúncio não está mais disponível" },
|
||||
{ status: 400 }
|
||||
{ status: 400 },
|
||||
);
|
||||
}
|
||||
|
||||
@@ -143,7 +159,7 @@ export async function PATCH(
|
||||
const updatedIntention = await updatePurchaseIntentionStatus(
|
||||
intentionId,
|
||||
status,
|
||||
sellerResponse
|
||||
sellerResponse,
|
||||
);
|
||||
|
||||
await sendSellerResponseToChat({
|
||||
@@ -159,19 +175,23 @@ export async function PATCH(
|
||||
user_id: intention.buyer_id,
|
||||
notification_type: status === "accepted" ? "purchase" : "system",
|
||||
title: status === "accepted" ? "Intenção aceita!" : "Intenção rejeitada",
|
||||
message: status === "accepted"
|
||||
? `O vendedor aceitou sua intenção de compra do item "${intention.listing?.item_name}"`
|
||||
: `O vendedor rejeitou sua intenção de compra do item "${intention.listing?.item_name}"`,
|
||||
message:
|
||||
status === "accepted"
|
||||
? `O vendedor aceitou sua intenção de compra do item "${intention.listing?.item_name}"`
|
||||
: `O vendedor rejeitou sua intenção de compra do item "${intention.listing?.item_name}"`,
|
||||
related_id: intention.listing_id,
|
||||
send_whatsapp: false,
|
||||
});
|
||||
|
||||
secureLogger.info(`✅ Intenção ${status === "accepted" ? "aceita" : "rejeitada"}`, {
|
||||
userId: authResult.userId,
|
||||
intentionId,
|
||||
listingId: intention.listing_id,
|
||||
buyerId: intention.buyer_id,
|
||||
});
|
||||
secureLogger.info(
|
||||
`✅ Intenção ${status === "accepted" ? "aceita" : "rejeitada"}`,
|
||||
{
|
||||
userId: authResult.userId,
|
||||
intentionId,
|
||||
listingId: intention.listing_id,
|
||||
buyerId: intention.buyer_id,
|
||||
},
|
||||
);
|
||||
|
||||
return NextResponse.json(
|
||||
{
|
||||
@@ -179,7 +199,7 @@ export async function PATCH(
|
||||
intention: updatedIntention,
|
||||
message: `Intenção ${status === "accepted" ? "aceita" : "rejeitada"} com sucesso`,
|
||||
},
|
||||
{ status: 200 }
|
||||
{ status: 200 },
|
||||
);
|
||||
} catch (error) {
|
||||
secureLogger.error("❌ Erro ao atualizar intenção", {
|
||||
@@ -189,7 +209,7 @@ export async function PATCH(
|
||||
|
||||
return NextResponse.json(
|
||||
{ success: false, error: "Erro ao atualizar intenção" },
|
||||
{ status: 500 }
|
||||
{ status: 500 },
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -202,7 +222,10 @@ async function sendSellerResponseToChat({
|
||||
sellerId,
|
||||
}: {
|
||||
listing: Awaited<ReturnType<typeof getListingById>>;
|
||||
intention: Exclude<Awaited<ReturnType<typeof getPurchaseIntentionById>>, null>;
|
||||
intention: Exclude<
|
||||
Awaited<ReturnType<typeof getPurchaseIntentionById>>,
|
||||
null
|
||||
>;
|
||||
status: "accepted" | "rejected";
|
||||
sellerResponse?: string;
|
||||
sellerId: number;
|
||||
@@ -225,12 +248,13 @@ async function sendSellerResponseToChat({
|
||||
let chat = await Chat.findOne({ chat_id: chatId });
|
||||
|
||||
if (!chat) {
|
||||
const [buyer, seller, buyerCharacter, sellerCharacter] = await Promise.all([
|
||||
getUserById(intention.buyer_id),
|
||||
getUserById(listing.seller_id),
|
||||
getMainCharacter(intention.buyer_id),
|
||||
getMainCharacter(listing.seller_id),
|
||||
]);
|
||||
const [buyer, seller, buyerCharacter, sellerCharacter] =
|
||||
await Promise.all([
|
||||
getUserById(intention.buyer_id),
|
||||
getUserById(listing.seller_id),
|
||||
getMainCharacter(intention.buyer_id),
|
||||
getMainCharacter(listing.seller_id),
|
||||
]);
|
||||
|
||||
chat = new Chat({
|
||||
chat_id: chatId,
|
||||
@@ -284,4 +308,3 @@ async function sendSellerResponseToChat({
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +1,14 @@
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { secureLogger } from "@/lib/secure-logger";
|
||||
import { type NextRequest, NextResponse } from "next/server";
|
||||
import { requireAuth } from "@/lib/auth/middleware";
|
||||
import { getListingById } from "@/lib/prisma/queries/listings";
|
||||
import { createNotification } from "@/lib/prisma/queries/notifications";
|
||||
import {
|
||||
createPurchaseIntention,
|
||||
getPurchaseIntentionsByListing,
|
||||
hasPendingIntention,
|
||||
getPendingIntentionByBuyerAndListing,
|
||||
getPurchaseIntentionsByListing,
|
||||
} from "@/lib/prisma/queries/purchase-intentions";
|
||||
import { createNotification } from "@/lib/prisma/queries/notifications";
|
||||
import { trackEvent } from "@/lib/prisma/queries/telemetry";
|
||||
import { secureLogger } from "@/lib/secure-logger";
|
||||
|
||||
/**
|
||||
* POST /api/listings/[id]/intentions
|
||||
@@ -17,7 +16,7 @@ import { trackEvent } from "@/lib/prisma/queries/telemetry";
|
||||
*/
|
||||
export async function POST(
|
||||
request: NextRequest,
|
||||
{ params }: { params: Promise<{ id: string }> }
|
||||
{ params }: { params: Promise<{ id: string }> },
|
||||
) {
|
||||
try {
|
||||
const authResult = await requireAuth(request);
|
||||
@@ -26,10 +25,10 @@ export async function POST(
|
||||
|
||||
const { id } = await params;
|
||||
const listingId = parseInt(id, 10);
|
||||
if (isNaN(listingId)) {
|
||||
if (Number.isNaN(listingId)) {
|
||||
return NextResponse.json(
|
||||
{ success: false, error: "ID de anúncio inválido" },
|
||||
{ status: 400 }
|
||||
{ status: 400 },
|
||||
);
|
||||
}
|
||||
|
||||
@@ -41,10 +40,13 @@ export async function POST(
|
||||
custom_offer_currency,
|
||||
} = body;
|
||||
|
||||
if (!payment_method || !["pix", "tibia_coins", "gold_coins", "trade"].includes(payment_method)) {
|
||||
if (
|
||||
!payment_method ||
|
||||
!["pix", "tibia_coins", "gold_coins", "trade"].includes(payment_method)
|
||||
) {
|
||||
return NextResponse.json(
|
||||
{ success: false, error: "Método de pagamento inválido" },
|
||||
{ status: 400 }
|
||||
{ status: 400 },
|
||||
);
|
||||
}
|
||||
|
||||
@@ -53,15 +55,19 @@ export async function POST(
|
||||
if (!listing) {
|
||||
return NextResponse.json(
|
||||
{ success: false, error: "Anúncio não encontrado" },
|
||||
{ status: 404 }
|
||||
{ status: 404 },
|
||||
);
|
||||
}
|
||||
|
||||
// Validar se não é o vendedor
|
||||
if (listing.seller_id === auth.userId) {
|
||||
return NextResponse.json(
|
||||
{ success: false, error: "Você não pode criar uma intenção de compra para seu próprio anúncio" },
|
||||
{ status: 403 }
|
||||
{
|
||||
success: false,
|
||||
error:
|
||||
"Você não pode criar uma intenção de compra para seu próprio anúncio",
|
||||
},
|
||||
{ status: 403 },
|
||||
);
|
||||
}
|
||||
|
||||
@@ -69,15 +75,18 @@ export async function POST(
|
||||
if (listing.listing_status !== "active") {
|
||||
return NextResponse.json(
|
||||
{ success: false, error: "Anúncio não está disponível para compra" },
|
||||
{ status: 400 }
|
||||
{ status: 400 },
|
||||
);
|
||||
}
|
||||
|
||||
// Validar se é leilão (leilões não podem ter intenções, apenas lances)
|
||||
if (listing.is_auction) {
|
||||
return NextResponse.json(
|
||||
{ success: false, error: "Este item está em leilão. Use a funcionalidade de lance." },
|
||||
{ status: 400 }
|
||||
{
|
||||
success: false,
|
||||
error: "Este item está em leilão. Use a funcionalidade de lance.",
|
||||
},
|
||||
{ status: 400 },
|
||||
);
|
||||
}
|
||||
|
||||
@@ -95,7 +104,7 @@ export async function POST(
|
||||
error: `Método de pagamento "${payment_method}" não está disponível para este anúncio`,
|
||||
available_methods: availableMethods,
|
||||
},
|
||||
{ status: 400 }
|
||||
{ status: 400 },
|
||||
);
|
||||
}
|
||||
|
||||
@@ -107,9 +116,10 @@ export async function POST(
|
||||
return NextResponse.json(
|
||||
{
|
||||
success: false,
|
||||
error: "Ofertas personalizadas só são permitidas para métodos monetários (PIX, Tibia Coins ou Gold Coins).",
|
||||
error:
|
||||
"Ofertas personalizadas só são permitidas para métodos monetários (PIX, Tibia Coins ou Gold Coins).",
|
||||
},
|
||||
{ status: 400 }
|
||||
{ status: 400 },
|
||||
);
|
||||
}
|
||||
|
||||
@@ -118,14 +128,16 @@ export async function POST(
|
||||
return NextResponse.json(
|
||||
{
|
||||
success: false,
|
||||
error: "Valor da oferta personalizada deve ser numérico e maior que zero.",
|
||||
error:
|
||||
"Valor da oferta personalizada deve ser numérico e maior que zero.",
|
||||
},
|
||||
{ status: 400 }
|
||||
{ status: 400 },
|
||||
);
|
||||
}
|
||||
|
||||
normalizedOfferCurrency =
|
||||
custom_offer_currency && ["pix", "tibia_coins", "gold_coins"].includes(custom_offer_currency)
|
||||
custom_offer_currency &&
|
||||
["pix", "tibia_coins", "gold_coins"].includes(custom_offer_currency)
|
||||
? custom_offer_currency
|
||||
: payment_method;
|
||||
|
||||
@@ -136,7 +148,10 @@ export async function POST(
|
||||
}
|
||||
|
||||
// Verificar se já existe uma intenção pendente
|
||||
const existingIntention = await getPendingIntentionByBuyerAndListing(listingId, auth.userId);
|
||||
const existingIntention = await getPendingIntentionByBuyerAndListing(
|
||||
listingId,
|
||||
auth.userId,
|
||||
);
|
||||
if (existingIntention) {
|
||||
// Se já existe e ainda pode trocar o método
|
||||
if (existingIntention.payment_method_change_count < 1) {
|
||||
@@ -144,23 +159,27 @@ export async function POST(
|
||||
return NextResponse.json(
|
||||
{
|
||||
success: false,
|
||||
error: "Você já possui uma intenção de compra pendente para este anúncio",
|
||||
error:
|
||||
"Você já possui uma intenção de compra pendente para este anúncio",
|
||||
existing_intention_id: existingIntention.intention_id,
|
||||
can_change_payment_method: true,
|
||||
payment_method_change_count: existingIntention.payment_method_change_count,
|
||||
message: "Use o endpoint PATCH /api/purchase-intentions/[intentionId]/payment-method para trocar o método de pagamento",
|
||||
payment_method_change_count:
|
||||
existingIntention.payment_method_change_count,
|
||||
message:
|
||||
"Use o endpoint PATCH /api/purchase-intentions/[intentionId]/payment-method para trocar o método de pagamento",
|
||||
},
|
||||
{ status: 400 }
|
||||
{ status: 400 },
|
||||
);
|
||||
} else {
|
||||
// Já atingiu o limite de trocas
|
||||
return NextResponse.json(
|
||||
{
|
||||
success: false,
|
||||
error: "Você já possui uma intenção de compra pendente para este anúncio e atingiu o limite de trocas de método de pagamento",
|
||||
error:
|
||||
"Você já possui uma intenção de compra pendente para este anúncio e atingiu o limite de trocas de método de pagamento",
|
||||
existing_intention_id: existingIntention.intention_id,
|
||||
},
|
||||
{ status: 400 }
|
||||
{ status: 400 },
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -197,7 +216,9 @@ export async function POST(
|
||||
payment_method,
|
||||
listing_price_pix: listing.price_pix ? Number(listing.price_pix) : null,
|
||||
listing_price_tc: listing.price_tibia_coins || null,
|
||||
listing_price_gp: listing.price_gold_coins ? Number(listing.price_gold_coins) : null,
|
||||
listing_price_gp: listing.price_gold_coins
|
||||
? Number(listing.price_gold_coins)
|
||||
: null,
|
||||
custom_offer_amount: normalizedOfferValue,
|
||||
custom_offer_currency: normalizedOfferCurrency,
|
||||
},
|
||||
@@ -214,9 +235,10 @@ export async function POST(
|
||||
{
|
||||
success: true,
|
||||
intention,
|
||||
message: "Intenção de compra criada com sucesso. O vendedor será notificado.",
|
||||
message:
|
||||
"Intenção de compra criada com sucesso. O vendedor será notificado.",
|
||||
},
|
||||
{ status: 201 }
|
||||
{ status: 201 },
|
||||
);
|
||||
} catch (error) {
|
||||
secureLogger.error("❌ Erro ao criar intenção de compra", {
|
||||
@@ -226,7 +248,7 @@ export async function POST(
|
||||
|
||||
return NextResponse.json(
|
||||
{ success: false, error: "Erro ao criar intenção de compra" },
|
||||
{ status: 500 }
|
||||
{ status: 500 },
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -237,19 +259,19 @@ export async function POST(
|
||||
*/
|
||||
export async function GET(
|
||||
request: NextRequest,
|
||||
{ params }: { params: Promise<{ id: string }> }
|
||||
{ params }: { params: Promise<{ id: string }> },
|
||||
) {
|
||||
try {
|
||||
const authResult = await requireAuth(request);
|
||||
if (authResult instanceof NextResponse) return authResult;
|
||||
const auth = authResult;
|
||||
|
||||
|
||||
const { id } = await params;
|
||||
const listingId = parseInt(id, 10);
|
||||
if (isNaN(listingId)) {
|
||||
if (Number.isNaN(listingId)) {
|
||||
return NextResponse.json(
|
||||
{ success: false, error: "ID de anúncio inválido" },
|
||||
{ status: 400 }
|
||||
{ status: 400 },
|
||||
);
|
||||
}
|
||||
|
||||
@@ -258,15 +280,18 @@ export async function GET(
|
||||
if (!listing) {
|
||||
return NextResponse.json(
|
||||
{ success: false, error: "Anúncio não encontrado" },
|
||||
{ status: 404 }
|
||||
{ status: 404 },
|
||||
);
|
||||
}
|
||||
|
||||
// Apenas o vendedor pode ver as intenções
|
||||
if (listing.seller_id !== auth.userId) {
|
||||
return NextResponse.json(
|
||||
{ success: false, error: "Você não tem permissão para ver as intenções deste anúncio" },
|
||||
{ status: 403 }
|
||||
{
|
||||
success: false,
|
||||
error: "Você não tem permissão para ver as intenções deste anúncio",
|
||||
},
|
||||
{ status: 403 },
|
||||
);
|
||||
}
|
||||
|
||||
@@ -297,7 +322,7 @@ export async function GET(
|
||||
intentions,
|
||||
count: intentions.length,
|
||||
},
|
||||
{ status: 200 }
|
||||
{ status: 200 },
|
||||
);
|
||||
} catch (error) {
|
||||
secureLogger.error("❌ Erro ao buscar intenções de compra", {
|
||||
@@ -307,8 +332,7 @@ export async function GET(
|
||||
|
||||
return NextResponse.json(
|
||||
{ success: false, error: "Erro ao buscar intenções de compra" },
|
||||
{ status: 500 }
|
||||
{ status: 500 },
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { secureLogger } from "@/lib/secure-logger";
|
||||
import { type NextRequest, NextResponse } from "next/server";
|
||||
import { requireAuth } from "@/lib/auth/middleware";
|
||||
import { getListingById, updateListing } from "@/lib/prisma/queries/listings";
|
||||
import { getListingById } from "@/lib/prisma/queries/listings";
|
||||
import { secureLogger } from "@/lib/secure-logger";
|
||||
|
||||
/**
|
||||
* POST /api/listings/:id/mark-sold
|
||||
@@ -9,7 +9,7 @@ import { getListingById, updateListing } from "@/lib/prisma/queries/listings";
|
||||
*/
|
||||
export async function POST(
|
||||
request: NextRequest,
|
||||
{ params }: { params: Promise<{ id: string }> }
|
||||
{ params }: { params: Promise<{ id: string }> },
|
||||
) {
|
||||
try {
|
||||
// Autenticação obrigatória
|
||||
@@ -24,13 +24,13 @@ export async function POST(
|
||||
const { id } = await params;
|
||||
const listingId = parseInt(id, 10);
|
||||
|
||||
if (isNaN(listingId)) {
|
||||
if (Number.isNaN(listingId)) {
|
||||
return NextResponse.json(
|
||||
{
|
||||
success: false,
|
||||
error: "ID de anúncio inválido",
|
||||
},
|
||||
{ status: 400 }
|
||||
{ status: 400 },
|
||||
);
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@ export async function POST(
|
||||
success: false,
|
||||
error: "Anúncio não encontrado",
|
||||
},
|
||||
{ status: 404 }
|
||||
{ status: 404 },
|
||||
);
|
||||
}
|
||||
|
||||
@@ -53,18 +53,22 @@ export async function POST(
|
||||
success: false,
|
||||
error: "Você não tem permissão para marcar este anúncio como vendido",
|
||||
},
|
||||
{ status: 403 }
|
||||
{ status: 403 },
|
||||
);
|
||||
}
|
||||
|
||||
// Verificar se o anúncio pode ser marcado como vendido
|
||||
if (listing.listing_status !== "active" && listing.listing_status !== "in_escrow") {
|
||||
if (
|
||||
listing.listing_status !== "active" &&
|
||||
listing.listing_status !== "in_escrow"
|
||||
) {
|
||||
return NextResponse.json(
|
||||
{
|
||||
success: false,
|
||||
error: "Apenas anúncios ativos ou em escrow podem ser marcados como vendidos",
|
||||
error:
|
||||
"Apenas anúncios ativos ou em escrow podem ser marcados como vendidos",
|
||||
},
|
||||
{ status: 400 }
|
||||
{ status: 400 },
|
||||
);
|
||||
}
|
||||
|
||||
@@ -90,12 +94,24 @@ export async function POST(
|
||||
// Converter campos para formato esperado
|
||||
const updated = {
|
||||
...updatedListing,
|
||||
price_pix: updatedListing.price_pix ? Number(updatedListing.price_pix) : null,
|
||||
current_bid_amount: updatedListing.current_bid_amount ? Number(updatedListing.current_bid_amount) : null,
|
||||
min_bid_increment: updatedListing.min_bid_increment ? Number(updatedListing.min_bid_increment) : null,
|
||||
initial_price_pix: updatedListing.initial_price_pix ? Number(updatedListing.initial_price_pix) : null,
|
||||
price_gold_coins: updatedListing.price_gold_coins ? Number(updatedListing.price_gold_coins) : null,
|
||||
initial_price_gold_coins: updatedListing.initial_price_gold_coins ? Number(updatedListing.initial_price_gold_coins) : null,
|
||||
price_pix: updatedListing.price_pix
|
||||
? Number(updatedListing.price_pix)
|
||||
: null,
|
||||
current_bid_amount: updatedListing.current_bid_amount
|
||||
? Number(updatedListing.current_bid_amount)
|
||||
: null,
|
||||
min_bid_increment: updatedListing.min_bid_increment
|
||||
? Number(updatedListing.min_bid_increment)
|
||||
: null,
|
||||
initial_price_pix: updatedListing.initial_price_pix
|
||||
? Number(updatedListing.initial_price_pix)
|
||||
: null,
|
||||
price_gold_coins: updatedListing.price_gold_coins
|
||||
? Number(updatedListing.price_gold_coins)
|
||||
: null,
|
||||
initial_price_gold_coins: updatedListing.initial_price_gold_coins
|
||||
? Number(updatedListing.initial_price_gold_coins)
|
||||
: null,
|
||||
seller_character_name: character?.character_name || null,
|
||||
};
|
||||
|
||||
@@ -105,7 +121,7 @@ export async function POST(
|
||||
success: false,
|
||||
error: "Erro ao marcar anúncio como vendido",
|
||||
},
|
||||
{ status: 500 }
|
||||
{ status: 500 },
|
||||
);
|
||||
}
|
||||
|
||||
@@ -120,7 +136,7 @@ export async function POST(
|
||||
listing: updated,
|
||||
message: "Anúncio marcado como vendido com sucesso",
|
||||
},
|
||||
{ status: 200 }
|
||||
{ status: 200 },
|
||||
);
|
||||
} catch (error) {
|
||||
secureLogger.error("❌ Erro ao marcar anúncio como vendido", {
|
||||
@@ -132,8 +148,7 @@ export async function POST(
|
||||
success: false,
|
||||
error: "Erro ao marcar anúncio como vendido",
|
||||
},
|
||||
{ status: 500 }
|
||||
{ status: 500 },
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { secureLogger } from "@/lib/secure-logger";
|
||||
import { type NextRequest, NextResponse } from "next/server";
|
||||
import { requireAuth } from "@/lib/auth/middleware";
|
||||
import { getListingById, updateListing } from "@/lib/prisma/queries/listings";
|
||||
import { secureLogger } from "@/lib/secure-logger";
|
||||
|
||||
/**
|
||||
* POST /api/listings/:id/pause
|
||||
@@ -9,7 +9,7 @@ import { getListingById, updateListing } from "@/lib/prisma/queries/listings";
|
||||
*/
|
||||
export async function POST(
|
||||
request: NextRequest,
|
||||
{ params }: { params: Promise<{ id: string }> }
|
||||
{ params }: { params: Promise<{ id: string }> },
|
||||
) {
|
||||
try {
|
||||
// Autenticação obrigatória
|
||||
@@ -24,13 +24,13 @@ export async function POST(
|
||||
const { id } = await params;
|
||||
const listingId = parseInt(id, 10);
|
||||
|
||||
if (isNaN(listingId)) {
|
||||
if (Number.isNaN(listingId)) {
|
||||
return NextResponse.json(
|
||||
{
|
||||
success: false,
|
||||
error: "ID de anúncio inválido",
|
||||
},
|
||||
{ status: 400 }
|
||||
{ status: 400 },
|
||||
);
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@ export async function POST(
|
||||
success: false,
|
||||
error: "Anúncio não encontrado",
|
||||
},
|
||||
{ status: 404 }
|
||||
{ status: 404 },
|
||||
);
|
||||
}
|
||||
|
||||
@@ -53,7 +53,7 @@ export async function POST(
|
||||
success: false,
|
||||
error: "Você não tem permissão para pausar este anúncio",
|
||||
},
|
||||
{ status: 403 }
|
||||
{ status: 403 },
|
||||
);
|
||||
}
|
||||
|
||||
@@ -63,7 +63,7 @@ export async function POST(
|
||||
success: false,
|
||||
error: "Apenas anúncios ativos podem ser pausados",
|
||||
},
|
||||
{ status: 400 }
|
||||
{ status: 400 },
|
||||
);
|
||||
}
|
||||
|
||||
@@ -79,7 +79,7 @@ export async function POST(
|
||||
success: false,
|
||||
error: "Erro ao pausar anúncio",
|
||||
},
|
||||
{ status: 500 }
|
||||
{ status: 500 },
|
||||
);
|
||||
}
|
||||
|
||||
@@ -94,7 +94,7 @@ export async function POST(
|
||||
listing: updated,
|
||||
message: "Anúncio pausado com sucesso",
|
||||
},
|
||||
{ status: 200 }
|
||||
{ status: 200 },
|
||||
);
|
||||
} catch (error) {
|
||||
secureLogger.error("❌ Erro ao pausar anúncio", {
|
||||
@@ -106,8 +106,7 @@ export async function POST(
|
||||
success: false,
|
||||
error: "Erro ao pausar anúncio",
|
||||
},
|
||||
{ status: 500 }
|
||||
{ status: 500 },
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { secureLogger } from "@/lib/secure-logger";
|
||||
import { type NextRequest, NextResponse } from "next/server";
|
||||
import { requireAuth } from "@/lib/auth/middleware";
|
||||
import { getListingById } from "@/lib/prisma/queries/listings";
|
||||
import { createNotification } from "@/lib/prisma/queries/notifications";
|
||||
import {
|
||||
createPurchaseIntention,
|
||||
hasPendingIntention,
|
||||
getPendingIntentionByBuyerAndListing,
|
||||
} from "@/lib/prisma/queries/purchase-intentions";
|
||||
import { trackEvent } from "@/lib/prisma/queries/telemetry";
|
||||
import { secureLogger } from "@/lib/secure-logger";
|
||||
|
||||
/**
|
||||
* POST /api/listings/[id]/purchase
|
||||
@@ -16,7 +15,7 @@ import { trackEvent } from "@/lib/prisma/queries/telemetry";
|
||||
*/
|
||||
export async function POST(
|
||||
request: NextRequest,
|
||||
{ params }: { params: Promise<{ id: string }> }
|
||||
{ params }: { params: Promise<{ id: string }> },
|
||||
) {
|
||||
try {
|
||||
const authResult = await requireAuth(request);
|
||||
@@ -25,10 +24,10 @@ export async function POST(
|
||||
|
||||
const { id } = await params;
|
||||
const listingId = parseInt(id, 10);
|
||||
if (isNaN(listingId)) {
|
||||
if (Number.isNaN(listingId)) {
|
||||
return NextResponse.json(
|
||||
{ success: false, error: "ID de anúncio inválido" },
|
||||
{ status: 400 }
|
||||
{ status: 400 },
|
||||
);
|
||||
}
|
||||
|
||||
@@ -43,7 +42,7 @@ export async function POST(
|
||||
if (!payment_method) {
|
||||
return NextResponse.json(
|
||||
{ success: false, error: "Método de pagamento é obrigatório" },
|
||||
{ status: 400 }
|
||||
{ status: 400 },
|
||||
);
|
||||
}
|
||||
|
||||
@@ -52,7 +51,7 @@ export async function POST(
|
||||
if (!listing) {
|
||||
return NextResponse.json(
|
||||
{ success: false, error: "Anúncio não encontrado" },
|
||||
{ status: 404 }
|
||||
{ status: 404 },
|
||||
);
|
||||
}
|
||||
|
||||
@@ -60,7 +59,7 @@ export async function POST(
|
||||
if (listing.seller_id === auth.userId) {
|
||||
return NextResponse.json(
|
||||
{ success: false, error: "Você não pode comprar seu próprio anúncio" },
|
||||
{ status: 403 }
|
||||
{ status: 403 },
|
||||
);
|
||||
}
|
||||
|
||||
@@ -68,15 +67,18 @@ export async function POST(
|
||||
if (listing.listing_status !== "active") {
|
||||
return NextResponse.json(
|
||||
{ success: false, error: "Anúncio não está disponível para compra" },
|
||||
{ status: 400 }
|
||||
{ status: 400 },
|
||||
);
|
||||
}
|
||||
|
||||
// Validar se é leilão (leilões não podem ser comprados diretamente, apenas com lances)
|
||||
if (listing.is_auction) {
|
||||
return NextResponse.json(
|
||||
{ success: false, error: "Este item está em leilão. Use a funcionalidade de lance." },
|
||||
{ status: 400 }
|
||||
{
|
||||
success: false,
|
||||
error: "Este item está em leilão. Use a funcionalidade de lance.",
|
||||
},
|
||||
{ status: 400 },
|
||||
);
|
||||
}
|
||||
|
||||
@@ -99,7 +101,7 @@ export async function POST(
|
||||
error: `Método de pagamento "${payment_method}" não está disponível para este anúncio`,
|
||||
available_methods: availableMethods,
|
||||
},
|
||||
{ status: 400 }
|
||||
{ status: 400 },
|
||||
);
|
||||
}
|
||||
|
||||
@@ -111,9 +113,10 @@ export async function POST(
|
||||
return NextResponse.json(
|
||||
{
|
||||
success: false,
|
||||
error: "Ofertas personalizadas só são permitidas para métodos monetários (PIX, Tibia Coins ou Gold Coins).",
|
||||
error:
|
||||
"Ofertas personalizadas só são permitidas para métodos monetários (PIX, Tibia Coins ou Gold Coins).",
|
||||
},
|
||||
{ status: 400 }
|
||||
{ status: 400 },
|
||||
);
|
||||
}
|
||||
|
||||
@@ -122,14 +125,16 @@ export async function POST(
|
||||
return NextResponse.json(
|
||||
{
|
||||
success: false,
|
||||
error: "Valor da oferta personalizada deve ser numérico e maior que zero.",
|
||||
error:
|
||||
"Valor da oferta personalizada deve ser numérico e maior que zero.",
|
||||
},
|
||||
{ status: 400 }
|
||||
{ status: 400 },
|
||||
);
|
||||
}
|
||||
|
||||
normalizedOfferCurrency =
|
||||
custom_offer_currency && ["pix", "tibia_coins", "gold_coins"].includes(custom_offer_currency)
|
||||
custom_offer_currency &&
|
||||
["pix", "tibia_coins", "gold_coins"].includes(custom_offer_currency)
|
||||
? custom_offer_currency
|
||||
: normalizedMethod;
|
||||
|
||||
@@ -140,7 +145,10 @@ export async function POST(
|
||||
}
|
||||
|
||||
// Verificar se já existe uma intenção pendente
|
||||
const existingIntention = await getPendingIntentionByBuyerAndListing(listingId, auth.userId);
|
||||
const existingIntention = await getPendingIntentionByBuyerAndListing(
|
||||
listingId,
|
||||
auth.userId,
|
||||
);
|
||||
if (existingIntention) {
|
||||
// Se já existe e ainda pode trocar o método
|
||||
if (existingIntention.payment_method_change_count < 1) {
|
||||
@@ -148,23 +156,27 @@ export async function POST(
|
||||
return NextResponse.json(
|
||||
{
|
||||
success: false,
|
||||
error: "Você já possui uma intenção de compra pendente para este anúncio",
|
||||
error:
|
||||
"Você já possui uma intenção de compra pendente para este anúncio",
|
||||
existing_intention_id: existingIntention.intention_id,
|
||||
can_change_payment_method: true,
|
||||
payment_method_change_count: existingIntention.payment_method_change_count,
|
||||
message: "Use o endpoint PATCH /api/purchase-intentions/[intentionId]/payment-method para trocar o método de pagamento",
|
||||
payment_method_change_count:
|
||||
existingIntention.payment_method_change_count,
|
||||
message:
|
||||
"Use o endpoint PATCH /api/purchase-intentions/[intentionId]/payment-method para trocar o método de pagamento",
|
||||
},
|
||||
{ status: 400 }
|
||||
{ status: 400 },
|
||||
);
|
||||
} else {
|
||||
// Já atingiu o limite de trocas
|
||||
return NextResponse.json(
|
||||
{
|
||||
success: false,
|
||||
error: "Você já possui uma intenção de compra pendente para este anúncio e atingiu o limite de trocas de método de pagamento",
|
||||
error:
|
||||
"Você já possui uma intenção de compra pendente para este anúncio e atingiu o limite de trocas de método de pagamento",
|
||||
existing_intention_id: existingIntention.intention_id,
|
||||
},
|
||||
{ status: 400 }
|
||||
{ status: 400 },
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -201,7 +213,9 @@ export async function POST(
|
||||
payment_method: normalizedMethod,
|
||||
listing_price_pix: listing.price_pix ? Number(listing.price_pix) : null,
|
||||
listing_price_tc: listing.price_tibia_coins || null,
|
||||
listing_price_gp: listing.price_gold_coins ? Number(listing.price_gold_coins) : null,
|
||||
listing_price_gp: listing.price_gold_coins
|
||||
? Number(listing.price_gold_coins)
|
||||
: null,
|
||||
custom_offer_amount: normalizedOfferValue,
|
||||
custom_offer_currency: normalizedOfferCurrency,
|
||||
},
|
||||
@@ -218,9 +232,10 @@ export async function POST(
|
||||
{
|
||||
success: true,
|
||||
intention,
|
||||
message: "Intenção de compra criada com sucesso. O vendedor será notificado.",
|
||||
message:
|
||||
"Intenção de compra criada com sucesso. O vendedor será notificado.",
|
||||
},
|
||||
{ status: 201 }
|
||||
{ status: 201 },
|
||||
);
|
||||
} catch (error) {
|
||||
secureLogger.error("❌ Erro ao criar intenção de compra", {
|
||||
@@ -230,8 +245,7 @@ export async function POST(
|
||||
|
||||
return NextResponse.json(
|
||||
{ success: false, error: "Erro ao criar intenção de compra" },
|
||||
{ status: 500 }
|
||||
{ status: 500 },
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { secureLogger } from "@/lib/secure-logger";
|
||||
import { requireAuth, optionalAuth } from "@/lib/auth/middleware";
|
||||
import { type NextRequest, NextResponse } from "next/server";
|
||||
import { optionalAuth, requireAuth } from "@/lib/auth/middleware";
|
||||
import {
|
||||
deleteListing,
|
||||
getListingById,
|
||||
updateListing,
|
||||
deleteListing,
|
||||
} from "@/lib/prisma/queries/listings";
|
||||
import { hasPendingIntention } from "@/lib/prisma/queries/purchase-intentions";
|
||||
import { secureLogger } from "@/lib/secure-logger";
|
||||
import { updateListingSchema } from "@/lib/validation-schemas";
|
||||
|
||||
/**
|
||||
@@ -15,20 +15,20 @@ import { updateListingSchema } from "@/lib/validation-schemas";
|
||||
*/
|
||||
export async function GET(
|
||||
request: NextRequest,
|
||||
{ params }: { params: Promise<{ id: string }> }
|
||||
{ params }: { params: Promise<{ id: string }> },
|
||||
) {
|
||||
try {
|
||||
const auth = await optionalAuth(request);
|
||||
const { id } = await params;
|
||||
const listingId = parseInt(id, 10);
|
||||
|
||||
if (isNaN(listingId)) {
|
||||
if (Number.isNaN(listingId)) {
|
||||
return NextResponse.json(
|
||||
{
|
||||
success: false,
|
||||
error: "ID de anúncio inválido",
|
||||
},
|
||||
{ status: 400 }
|
||||
{ status: 400 },
|
||||
);
|
||||
}
|
||||
|
||||
@@ -40,14 +40,13 @@ export async function GET(
|
||||
success: false,
|
||||
error: "Anúncio não encontrado",
|
||||
},
|
||||
{ status: 404 }
|
||||
{ status: 404 },
|
||||
);
|
||||
}
|
||||
|
||||
const hasPendingForUser =
|
||||
auth && auth.userId
|
||||
? await hasPendingIntention(listingId, auth.userId)
|
||||
: false;
|
||||
const hasPendingForUser = auth?.userId
|
||||
? await hasPendingIntention(listingId, auth.userId)
|
||||
: false;
|
||||
|
||||
return NextResponse.json(
|
||||
{
|
||||
@@ -57,7 +56,7 @@ export async function GET(
|
||||
has_pending_intention_for_current_user: hasPendingForUser,
|
||||
},
|
||||
},
|
||||
{ status: 200 }
|
||||
{ status: 200 },
|
||||
);
|
||||
} catch (error) {
|
||||
secureLogger.error("❌ Erro ao buscar anúncio", {
|
||||
@@ -70,7 +69,7 @@ export async function GET(
|
||||
success: false,
|
||||
error: "Erro ao buscar anúncio",
|
||||
},
|
||||
{ status: 500 }
|
||||
{ status: 500 },
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -81,7 +80,7 @@ export async function GET(
|
||||
*/
|
||||
export async function PATCH(
|
||||
request: NextRequest,
|
||||
{ params }: { params: Promise<{ id: string }> }
|
||||
{ params }: { params: Promise<{ id: string }> },
|
||||
) {
|
||||
try {
|
||||
// Autenticação obrigatória
|
||||
@@ -96,13 +95,13 @@ export async function PATCH(
|
||||
const { id } = await params;
|
||||
const listingId = parseInt(id, 10);
|
||||
|
||||
if (isNaN(listingId)) {
|
||||
if (Number.isNaN(listingId)) {
|
||||
return NextResponse.json(
|
||||
{
|
||||
success: false,
|
||||
error: "ID de anúncio inválido",
|
||||
},
|
||||
{ status: 400 }
|
||||
{ status: 400 },
|
||||
);
|
||||
}
|
||||
|
||||
@@ -115,7 +114,7 @@ export async function PATCH(
|
||||
success: false,
|
||||
error: "Anúncio não encontrado",
|
||||
},
|
||||
{ status: 404 }
|
||||
{ status: 404 },
|
||||
);
|
||||
}
|
||||
|
||||
@@ -125,7 +124,7 @@ export async function PATCH(
|
||||
success: false,
|
||||
error: "Você não tem permissão para editar este anúncio",
|
||||
},
|
||||
{ status: 403 }
|
||||
{ status: 403 },
|
||||
);
|
||||
}
|
||||
|
||||
@@ -146,7 +145,7 @@ export async function PATCH(
|
||||
error: "Dados de entrada inválidos",
|
||||
details: errors,
|
||||
},
|
||||
{ status: 400 }
|
||||
{ status: 400 },
|
||||
);
|
||||
}
|
||||
|
||||
@@ -159,16 +158,37 @@ export async function PATCH(
|
||||
price_tibia_coins?: number;
|
||||
price_gold_coins?: number;
|
||||
description?: string;
|
||||
listing_status?: "active" | "sold" | "cancelled" | "in_escrow" | "expired";
|
||||
listing_status?:
|
||||
| "active"
|
||||
| "sold"
|
||||
| "cancelled"
|
||||
| "in_escrow"
|
||||
| "expired";
|
||||
current_bid_amount?: number;
|
||||
current_bidder_id?: number;
|
||||
} = {
|
||||
...(validatedData.quantity !== undefined && { quantity: validatedData.quantity }),
|
||||
...(validatedData.price_pix !== null && validatedData.price_pix !== undefined && { price_pix: validatedData.price_pix }),
|
||||
...(validatedData.price_tibia_coins !== null && validatedData.price_tibia_coins !== undefined && { price_tibia_coins: validatedData.price_tibia_coins }),
|
||||
...(validatedData.price_gold_coins !== null && validatedData.price_gold_coins !== undefined && { price_gold_coins: validatedData.price_gold_coins }),
|
||||
...(validatedData.description !== null && validatedData.description !== undefined && { description: validatedData.description }),
|
||||
...(validatedData.listing_status !== undefined && { listing_status: validatedData.listing_status }),
|
||||
...(validatedData.quantity !== undefined && {
|
||||
quantity: validatedData.quantity,
|
||||
}),
|
||||
...(validatedData.price_pix !== null &&
|
||||
validatedData.price_pix !== undefined && {
|
||||
price_pix: validatedData.price_pix,
|
||||
}),
|
||||
...(validatedData.price_tibia_coins !== null &&
|
||||
validatedData.price_tibia_coins !== undefined && {
|
||||
price_tibia_coins: validatedData.price_tibia_coins,
|
||||
}),
|
||||
...(validatedData.price_gold_coins !== null &&
|
||||
validatedData.price_gold_coins !== undefined && {
|
||||
price_gold_coins: validatedData.price_gold_coins,
|
||||
}),
|
||||
...(validatedData.description !== null &&
|
||||
validatedData.description !== undefined && {
|
||||
description: validatedData.description,
|
||||
}),
|
||||
...(validatedData.listing_status !== undefined && {
|
||||
listing_status: validatedData.listing_status,
|
||||
}),
|
||||
};
|
||||
|
||||
// Atualizar anúncio
|
||||
@@ -180,7 +200,7 @@ export async function PATCH(
|
||||
success: false,
|
||||
error: "Erro ao atualizar anúncio",
|
||||
},
|
||||
{ status: 500 }
|
||||
{ status: 500 },
|
||||
);
|
||||
}
|
||||
|
||||
@@ -195,7 +215,7 @@ export async function PATCH(
|
||||
success: true,
|
||||
listing: updatedListing,
|
||||
},
|
||||
{ status: 200 }
|
||||
{ status: 200 },
|
||||
);
|
||||
} catch (error) {
|
||||
secureLogger.error("❌ Erro ao atualizar anúncio", {
|
||||
@@ -208,7 +228,7 @@ export async function PATCH(
|
||||
success: false,
|
||||
error: "Erro ao atualizar anúncio",
|
||||
},
|
||||
{ status: 500 }
|
||||
{ status: 500 },
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -219,7 +239,7 @@ export async function PATCH(
|
||||
*/
|
||||
export async function DELETE(
|
||||
request: NextRequest,
|
||||
{ params }: { params: Promise<{ id: string }> }
|
||||
{ params }: { params: Promise<{ id: string }> },
|
||||
) {
|
||||
try {
|
||||
// Autenticação obrigatória
|
||||
@@ -234,13 +254,13 @@ export async function DELETE(
|
||||
const { id } = await params;
|
||||
const listingId = parseInt(id, 10);
|
||||
|
||||
if (isNaN(listingId)) {
|
||||
if (Number.isNaN(listingId)) {
|
||||
return NextResponse.json(
|
||||
{
|
||||
success: false,
|
||||
error: "ID de anúncio inválido",
|
||||
},
|
||||
{ status: 400 }
|
||||
{ status: 400 },
|
||||
);
|
||||
}
|
||||
|
||||
@@ -253,7 +273,7 @@ export async function DELETE(
|
||||
success: false,
|
||||
error: "Anúncio não encontrado",
|
||||
},
|
||||
{ status: 404 }
|
||||
{ status: 404 },
|
||||
);
|
||||
}
|
||||
|
||||
@@ -263,7 +283,7 @@ export async function DELETE(
|
||||
success: false,
|
||||
error: "Você não tem permissão para cancelar este anúncio",
|
||||
},
|
||||
{ status: 403 }
|
||||
{ status: 403 },
|
||||
);
|
||||
}
|
||||
|
||||
@@ -276,7 +296,7 @@ export async function DELETE(
|
||||
success: false,
|
||||
error: "Erro ao cancelar anúncio",
|
||||
},
|
||||
{ status: 500 }
|
||||
{ status: 500 },
|
||||
);
|
||||
}
|
||||
|
||||
@@ -291,7 +311,7 @@ export async function DELETE(
|
||||
success: true,
|
||||
message: "Anúncio cancelado com sucesso",
|
||||
},
|
||||
{ status: 200 }
|
||||
{ status: 200 },
|
||||
);
|
||||
} catch (error) {
|
||||
secureLogger.error("❌ Erro ao cancelar anúncio", {
|
||||
@@ -304,8 +324,7 @@ export async function DELETE(
|
||||
success: false,
|
||||
error: "Erro ao cancelar anúncio",
|
||||
},
|
||||
{ status: 500 }
|
||||
{ status: 500 },
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,23 +1,23 @@
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { secureLogger } from "@/lib/secure-logger";
|
||||
import { getListingById } from "@/lib/prisma/queries/listings";
|
||||
import { type NextRequest, NextResponse } from "next/server";
|
||||
import { getMainCharacterByUserId } from "@/lib/prisma/queries/characters";
|
||||
import { getListingById } from "@/lib/prisma/queries/listings";
|
||||
import { secureLogger } from "@/lib/secure-logger";
|
||||
|
||||
/**
|
||||
* GET /api/listings/[id]/seller
|
||||
* Retorna informações do vendedor (nome do personagem)
|
||||
*/
|
||||
export async function GET(
|
||||
request: NextRequest,
|
||||
{ params }: { params: Promise<{ id: string }> }
|
||||
_request: NextRequest,
|
||||
{ params }: { params: Promise<{ id: string }> },
|
||||
) {
|
||||
try {
|
||||
const { id } = await params;
|
||||
const listingId = parseInt(id, 10);
|
||||
if (isNaN(listingId)) {
|
||||
if (Number.isNaN(listingId)) {
|
||||
return NextResponse.json(
|
||||
{ success: false, error: "ID de anúncio inválido" },
|
||||
{ status: 400 }
|
||||
{ status: 400 },
|
||||
);
|
||||
}
|
||||
|
||||
@@ -25,12 +25,15 @@ export async function GET(
|
||||
if (!listing) {
|
||||
return NextResponse.json(
|
||||
{ success: false, error: "Anúncio não encontrado" },
|
||||
{ status: 404 }
|
||||
{ status: 404 },
|
||||
);
|
||||
}
|
||||
|
||||
// Buscar personagem principal do vendedor no servidor do listing
|
||||
const character = await getMainCharacterByUserId(listing.seller_id, listing.server_name);
|
||||
const character = await getMainCharacterByUserId(
|
||||
listing.seller_id,
|
||||
listing.server_name,
|
||||
);
|
||||
|
||||
return NextResponse.json(
|
||||
{
|
||||
@@ -41,7 +44,7 @@ export async function GET(
|
||||
server_name: listing.server_name,
|
||||
},
|
||||
},
|
||||
{ status: 200 }
|
||||
{ status: 200 },
|
||||
);
|
||||
} catch (error) {
|
||||
secureLogger.error("❌ Erro ao buscar informações do vendedor", {
|
||||
@@ -51,8 +54,7 @@ export async function GET(
|
||||
|
||||
return NextResponse.json(
|
||||
{ success: false, error: "Erro ao buscar informações do vendedor" },
|
||||
{ status: 500 }
|
||||
{ status: 500 },
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { secureLogger } from "@/lib/secure-logger";
|
||||
import { type NextRequest, NextResponse } from "next/server";
|
||||
import { incrementViews } from "@/lib/prisma/queries/listings";
|
||||
import { secureLogger } from "@/lib/secure-logger";
|
||||
|
||||
/**
|
||||
* POST /api/listings/[id]/views
|
||||
@@ -8,20 +8,20 @@ import { incrementViews } from "@/lib/prisma/queries/listings";
|
||||
* Não requer autenticação (público)
|
||||
*/
|
||||
export async function POST(
|
||||
request: NextRequest,
|
||||
{ params }: { params: Promise<{ id: string }> }
|
||||
_request: NextRequest,
|
||||
{ params }: { params: Promise<{ id: string }> },
|
||||
) {
|
||||
try {
|
||||
const { id } = await params;
|
||||
const listingId = parseInt(id, 10);
|
||||
|
||||
if (isNaN(listingId)) {
|
||||
if (Number.isNaN(listingId)) {
|
||||
return NextResponse.json(
|
||||
{
|
||||
success: false,
|
||||
error: "ID de anúncio inválido",
|
||||
},
|
||||
{ status: 400 }
|
||||
{ status: 400 },
|
||||
);
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ export async function POST(
|
||||
success: true,
|
||||
message: "View incrementada",
|
||||
},
|
||||
{ status: 200 }
|
||||
{ status: 200 },
|
||||
);
|
||||
} catch (error) {
|
||||
secureLogger.error("❌ Erro ao incrementar views", {
|
||||
@@ -46,8 +46,7 @@ export async function POST(
|
||||
success: false,
|
||||
error: "Erro ao incrementar views",
|
||||
},
|
||||
{ status: 500 }
|
||||
{ status: 500 },
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { secureLogger } from "@/lib/secure-logger";
|
||||
import { type NextRequest, NextResponse } from "next/server";
|
||||
import { requireAuth } from "@/lib/auth/middleware";
|
||||
import { getListings } from "@/lib/prisma/queries/listings";
|
||||
import { secureLogger } from "@/lib/secure-logger";
|
||||
|
||||
/**
|
||||
* GET /api/listings/my
|
||||
@@ -35,8 +35,7 @@ export async function GET(request: NextRequest) {
|
||||
pageSize: searchParams.get("pageSize")
|
||||
? parseInt(searchParams.get("pageSize")!, 10)
|
||||
: 20,
|
||||
sortBy: (searchParams.get("sortBy") ||
|
||||
"created_at") as
|
||||
sortBy: (searchParams.get("sortBy") || "created_at") as
|
||||
| "created_at"
|
||||
| "price_pix"
|
||||
| "price_tibia_coins"
|
||||
@@ -58,7 +57,7 @@ export async function GET(request: NextRequest) {
|
||||
totalPages: Math.ceil(listingsData.total / filters.pageSize),
|
||||
},
|
||||
},
|
||||
{ status: 200 }
|
||||
{ status: 200 },
|
||||
);
|
||||
} catch (error) {
|
||||
secureLogger.error("❌ Erro ao buscar anúncios do usuário", {
|
||||
@@ -71,8 +70,7 @@ export async function GET(request: NextRequest) {
|
||||
success: false,
|
||||
error: "Erro ao buscar anúncios",
|
||||
},
|
||||
{ status: 500 }
|
||||
{ status: 500 },
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
+22
-22
@@ -1,19 +1,19 @@
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { type NextRequest, NextResponse } from "next/server";
|
||||
import { optionalAuth, requireAuth } from "@/lib/auth/middleware";
|
||||
import {
|
||||
createListing,
|
||||
getActiveListings,
|
||||
getListings,
|
||||
} from "@/lib/prisma/queries/listings";
|
||||
import { getPendingIntentionListingIdsForBuyer } from "@/lib/prisma/queries/purchase-intentions";
|
||||
import { isServerActive } from "@/lib/prisma/queries/servers";
|
||||
import {
|
||||
checkRateLimit,
|
||||
getClientIP,
|
||||
createRateLimitResponse,
|
||||
getClientIP,
|
||||
} from "@/lib/rate-limiter";
|
||||
import { secureLogger } from "@/lib/secure-logger";
|
||||
import { createListingSchema } from "@/lib/validation-schemas";
|
||||
import { requireAuth, optionalAuth } from "@/lib/auth/middleware";
|
||||
import {
|
||||
getListings,
|
||||
createListing,
|
||||
getActiveListings,
|
||||
} from "@/lib/prisma/queries/listings";
|
||||
import { isServerActive } from "@/lib/prisma/queries/servers";
|
||||
import { getPendingIntentionListingIdsForBuyer } from "@/lib/prisma/queries/purchase-intentions";
|
||||
|
||||
/**
|
||||
* GET /api/listings
|
||||
@@ -52,8 +52,7 @@ export async function GET(request: NextRequest) {
|
||||
pageSize: searchParams.get("pageSize")
|
||||
? parseInt(searchParams.get("pageSize")!, 10)
|
||||
: 20,
|
||||
sortBy: (searchParams.get("sortBy") ||
|
||||
"created_at") as
|
||||
sortBy: (searchParams.get("sortBy") || "created_at") as
|
||||
| "created_at"
|
||||
| "price_pix"
|
||||
| "price_tibia_coins"
|
||||
@@ -73,13 +72,15 @@ export async function GET(request: NextRequest) {
|
||||
const listingIds = listingsResponse.map((listing) => listing.listing_id);
|
||||
const pendingListingIds = await getPendingIntentionListingIdsForBuyer(
|
||||
auth.userId,
|
||||
listingIds
|
||||
listingIds,
|
||||
);
|
||||
const pendingSet = new Set(pendingListingIds);
|
||||
|
||||
listingsResponse = listingsResponse.map((listing) => ({
|
||||
...listing,
|
||||
has_pending_intention_for_current_user: pendingSet.has(listing.listing_id),
|
||||
has_pending_intention_for_current_user: pendingSet.has(
|
||||
listing.listing_id,
|
||||
),
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -94,7 +95,7 @@ export async function GET(request: NextRequest) {
|
||||
totalPages: Math.ceil(listingsData.total / filters.pageSize),
|
||||
},
|
||||
},
|
||||
{ status: 200 }
|
||||
{ status: 200 },
|
||||
);
|
||||
} catch (error) {
|
||||
secureLogger.error("❌ Erro ao buscar anúncios", {
|
||||
@@ -107,7 +108,7 @@ export async function GET(request: NextRequest) {
|
||||
success: false,
|
||||
error: "Erro ao buscar anúncios",
|
||||
},
|
||||
{ status: 500 }
|
||||
{ status: 500 },
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -158,7 +159,7 @@ export async function POST(request: NextRequest) {
|
||||
error: "Dados de entrada inválidos",
|
||||
details: errors,
|
||||
},
|
||||
{ status: 400 }
|
||||
{ status: 400 },
|
||||
);
|
||||
}
|
||||
|
||||
@@ -172,7 +173,7 @@ export async function POST(request: NextRequest) {
|
||||
success: false,
|
||||
error: `Servidor "${validatedData.server_name}" não existe ou não está ativo`,
|
||||
},
|
||||
{ status: 400 }
|
||||
{ status: 400 },
|
||||
);
|
||||
}
|
||||
|
||||
@@ -181,7 +182,7 @@ export async function POST(request: NextRequest) {
|
||||
// Mas a interface CreateListingData exige item_id, então usamos item_id_unified como fallback
|
||||
// O refine do schema garante que pelo menos um está presente, então o fallback nunca será usado
|
||||
const itemId = validatedData.item_id ?? validatedData.item_id_unified!;
|
||||
|
||||
|
||||
const listing = await createListing({
|
||||
seller_id: auth.userId,
|
||||
item_id: itemId,
|
||||
@@ -210,7 +211,7 @@ export async function POST(request: NextRequest) {
|
||||
success: true,
|
||||
listing,
|
||||
},
|
||||
{ status: 201 }
|
||||
{ status: 201 },
|
||||
);
|
||||
} catch (error) {
|
||||
secureLogger.error("❌ Erro ao criar anúncio", {
|
||||
@@ -223,8 +224,7 @@ export async function POST(request: NextRequest) {
|
||||
success: false,
|
||||
error: "Erro ao criar anúncio",
|
||||
},
|
||||
{ status: 500 }
|
||||
{ status: 500 },
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { secureLogger } from "@/lib/secure-logger";
|
||||
import { type NextRequest, NextResponse } from "next/server";
|
||||
import { requireAuth } from "@/lib/auth/middleware";
|
||||
import { markAsRead } from "@/lib/prisma/queries/notifications";
|
||||
import { secureLogger } from "@/lib/secure-logger";
|
||||
|
||||
/**
|
||||
* POST /api/notifications/[id]/read
|
||||
@@ -9,7 +9,7 @@ import { markAsRead } from "@/lib/prisma/queries/notifications";
|
||||
*/
|
||||
export async function POST(
|
||||
request: NextRequest,
|
||||
{ params }: { params: Promise<{ id: string }> }
|
||||
{ params }: { params: Promise<{ id: string }> },
|
||||
) {
|
||||
try {
|
||||
const authResult = await requireAuth(request);
|
||||
@@ -18,10 +18,10 @@ export async function POST(
|
||||
|
||||
const { id } = await params;
|
||||
const notificationId = parseInt(id, 10);
|
||||
if (isNaN(notificationId)) {
|
||||
if (Number.isNaN(notificationId)) {
|
||||
return NextResponse.json(
|
||||
{ success: false, error: "ID de notificação inválido" },
|
||||
{ status: 400 }
|
||||
{ status: 400 },
|
||||
);
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ export async function POST(
|
||||
if (!success) {
|
||||
return NextResponse.json(
|
||||
{ success: false, error: "Notificação não encontrada ou já está lida" },
|
||||
{ status: 404 }
|
||||
{ status: 404 },
|
||||
);
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ export async function POST(
|
||||
success: true,
|
||||
message: "Notificação marcada como lida",
|
||||
},
|
||||
{ status: 200 }
|
||||
{ status: 200 },
|
||||
);
|
||||
} catch (error) {
|
||||
secureLogger.error("❌ Erro ao marcar notificação como lida", {
|
||||
@@ -48,8 +48,7 @@ export async function POST(
|
||||
|
||||
return NextResponse.json(
|
||||
{ success: false, error: "Erro ao marcar notificação como lida" },
|
||||
{ status: 500 }
|
||||
{ status: 500 },
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { secureLogger } from "@/lib/secure-logger";
|
||||
import { type NextRequest, NextResponse } from "next/server";
|
||||
import { requireAuth } from "@/lib/auth/middleware";
|
||||
import {
|
||||
getUserNotifications,
|
||||
getUnreadCount,
|
||||
markAsRead,
|
||||
getUserNotifications,
|
||||
markAllAsRead,
|
||||
} from "@/lib/prisma/queries/notifications";
|
||||
import { secureLogger } from "@/lib/secure-logger";
|
||||
|
||||
/**
|
||||
* GET /api/notifications
|
||||
@@ -25,7 +24,8 @@ export async function GET(request: NextRequest) {
|
||||
: 50;
|
||||
|
||||
const notifications = await getUserNotifications(auth.userId, {
|
||||
is_read: isRead === "true" ? true : isRead === "false" ? false : undefined,
|
||||
is_read:
|
||||
isRead === "true" ? true : isRead === "false" ? false : undefined,
|
||||
limit,
|
||||
});
|
||||
|
||||
@@ -37,7 +37,7 @@ export async function GET(request: NextRequest) {
|
||||
notifications,
|
||||
unread_count: unreadCount,
|
||||
},
|
||||
{ status: 200 }
|
||||
{ status: 200 },
|
||||
);
|
||||
} catch (error) {
|
||||
secureLogger.error("❌ Erro ao buscar notificações", {
|
||||
@@ -47,7 +47,7 @@ export async function GET(request: NextRequest) {
|
||||
|
||||
return NextResponse.json(
|
||||
{ success: false, error: "Erro ao buscar notificações" },
|
||||
{ status: 500 }
|
||||
{ status: 500 },
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -73,13 +73,13 @@ export async function POST(request: NextRequest) {
|
||||
message: `${count} notificações marcadas como lidas`,
|
||||
count,
|
||||
},
|
||||
{ status: 200 }
|
||||
{ status: 200 },
|
||||
);
|
||||
}
|
||||
|
||||
return NextResponse.json(
|
||||
{ success: false, error: "Ação inválida" },
|
||||
{ status: 400 }
|
||||
{ status: 400 },
|
||||
);
|
||||
} catch (error) {
|
||||
secureLogger.error("❌ Erro ao marcar notificações como lidas", {
|
||||
@@ -88,8 +88,7 @@ export async function POST(request: NextRequest) {
|
||||
|
||||
return NextResponse.json(
|
||||
{ success: false, error: "Erro ao marcar notificações como lidas" },
|
||||
{ status: 500 }
|
||||
{ status: 500 },
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { type NextRequest, NextResponse } from "next/server";
|
||||
import { requireAuth } from "@/lib/auth/middleware";
|
||||
import { getUnreadCount } from "@/lib/prisma/queries/notifications";
|
||||
|
||||
@@ -18,13 +18,12 @@ export async function GET(request: NextRequest) {
|
||||
success: true,
|
||||
count,
|
||||
},
|
||||
{ status: 200 }
|
||||
{ status: 200 },
|
||||
);
|
||||
} catch (error) {
|
||||
} catch (_error) {
|
||||
return NextResponse.json(
|
||||
{ success: false, error: "Erro ao buscar contagem de notificações" },
|
||||
{ status: 500 }
|
||||
{ status: 500 },
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,14 +2,14 @@
|
||||
* PATCH /api/purchase-intentions/[intentionId]/payment-method
|
||||
* Permite ao comprador trocar o método de pagamento (máximo 1 vez)
|
||||
*/
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { type NextRequest, NextResponse } from "next/server";
|
||||
import { requireAuth } from "@/lib/auth/middleware";
|
||||
import { createSystemMessageInChat } from "@/lib/chat/system-messages";
|
||||
import { getListingById } from "@/lib/prisma/queries/listings";
|
||||
import {
|
||||
getPurchaseIntentionById,
|
||||
updatePurchaseIntentionPaymentMethod,
|
||||
} from "@/lib/prisma/queries/purchase-intentions";
|
||||
import { getListingById } from "@/lib/prisma/queries/listings";
|
||||
import { createSystemMessageInChat } from "@/lib/chat/system-messages";
|
||||
import { secureLogger } from "@/lib/secure-logger";
|
||||
|
||||
function getPaymentMethodLabel(method: string): string {
|
||||
@@ -29,7 +29,7 @@ function getPaymentMethodLabel(method: string): string {
|
||||
|
||||
export async function PATCH(
|
||||
request: NextRequest,
|
||||
{ params }: { params: Promise<{ intentionId: string }> }
|
||||
{ params }: { params: Promise<{ intentionId: string }> },
|
||||
) {
|
||||
try {
|
||||
const authResult = await requireAuth(request);
|
||||
@@ -38,21 +38,24 @@ export async function PATCH(
|
||||
|
||||
const { intentionId: intentionIdParam } = await params;
|
||||
const intentionId = parseInt(intentionIdParam, 10);
|
||||
|
||||
if (isNaN(intentionId)) {
|
||||
|
||||
if (Number.isNaN(intentionId)) {
|
||||
return NextResponse.json(
|
||||
{ success: false, error: "ID de intenção inválido" },
|
||||
{ status: 400 }
|
||||
{ status: 400 },
|
||||
);
|
||||
}
|
||||
|
||||
const body = await request.json();
|
||||
const { payment_method } = body;
|
||||
|
||||
if (!payment_method || !["pix", "tibia_coins", "gold_coins", "trade"].includes(payment_method)) {
|
||||
if (
|
||||
!payment_method ||
|
||||
!["pix", "tibia_coins", "gold_coins", "trade"].includes(payment_method)
|
||||
) {
|
||||
return NextResponse.json(
|
||||
{ success: false, error: "Método de pagamento inválido" },
|
||||
{ status: 400 }
|
||||
{ status: 400 },
|
||||
);
|
||||
}
|
||||
|
||||
@@ -61,39 +64,53 @@ export async function PATCH(
|
||||
if (!intention) {
|
||||
return NextResponse.json(
|
||||
{ success: false, error: "Intenção não encontrada" },
|
||||
{ status: 404 }
|
||||
{ status: 404 },
|
||||
);
|
||||
}
|
||||
|
||||
// Verificar se o usuário é o comprador
|
||||
if (intention.buyer_id !== auth.userId) {
|
||||
return NextResponse.json(
|
||||
{ success: false, error: "Apenas o comprador pode trocar o método de pagamento" },
|
||||
{ status: 403 }
|
||||
{
|
||||
success: false,
|
||||
error: "Apenas o comprador pode trocar o método de pagamento",
|
||||
},
|
||||
{ status: 403 },
|
||||
);
|
||||
}
|
||||
|
||||
// Verificar se a intenção está pendente
|
||||
if (intention.status !== "pending") {
|
||||
return NextResponse.json(
|
||||
{ success: false, error: "Apenas intenções pendentes podem ter o método de pagamento alterado" },
|
||||
{ status: 400 }
|
||||
{
|
||||
success: false,
|
||||
error:
|
||||
"Apenas intenções pendentes podem ter o método de pagamento alterado",
|
||||
},
|
||||
{ status: 400 },
|
||||
);
|
||||
}
|
||||
|
||||
// Verificar se já atingiu o limite de trocas
|
||||
if (intention.payment_method_change_count >= 1) {
|
||||
return NextResponse.json(
|
||||
{ success: false, error: "Você já atingiu o limite de trocas de método de pagamento (1 vez)" },
|
||||
{ status: 400 }
|
||||
{
|
||||
success: false,
|
||||
error:
|
||||
"Você já atingiu o limite de trocas de método de pagamento (1 vez)",
|
||||
},
|
||||
{ status: 400 },
|
||||
);
|
||||
}
|
||||
|
||||
// Verificar se o novo método é diferente
|
||||
if (intention.payment_method === payment_method) {
|
||||
return NextResponse.json(
|
||||
{ success: false, error: "O novo método de pagamento deve ser diferente do atual" },
|
||||
{ status: 400 }
|
||||
{
|
||||
success: false,
|
||||
error: "O novo método de pagamento deve ser diferente do atual",
|
||||
},
|
||||
{ status: 400 },
|
||||
);
|
||||
}
|
||||
|
||||
@@ -102,7 +119,7 @@ export async function PATCH(
|
||||
if (!listing) {
|
||||
return NextResponse.json(
|
||||
{ success: false, error: "Anúncio não encontrado" },
|
||||
{ status: 404 }
|
||||
{ status: 404 },
|
||||
);
|
||||
}
|
||||
|
||||
@@ -120,7 +137,7 @@ export async function PATCH(
|
||||
error: `Método de pagamento "${payment_method}" não está disponível para este anúncio`,
|
||||
available_methods: availableMethods,
|
||||
},
|
||||
{ status: 400 }
|
||||
{ status: 400 },
|
||||
);
|
||||
}
|
||||
|
||||
@@ -128,7 +145,7 @@ export async function PATCH(
|
||||
const oldPaymentMethod = intention.payment_method;
|
||||
const updatedIntention = await updatePurchaseIntentionPaymentMethod(
|
||||
intentionId,
|
||||
payment_method as any
|
||||
payment_method as any,
|
||||
);
|
||||
|
||||
// Criar mensagem do sistema no chat
|
||||
@@ -156,9 +173,10 @@ export async function PATCH(
|
||||
{
|
||||
success: true,
|
||||
intention: updatedIntention,
|
||||
message: "Método de pagamento atualizado com sucesso. O vendedor foi notificado.",
|
||||
message:
|
||||
"Método de pagamento atualizado com sucesso. O vendedor foi notificado.",
|
||||
},
|
||||
{ status: 200 }
|
||||
{ status: 200 },
|
||||
);
|
||||
} catch (error) {
|
||||
secureLogger.error("❌ Erro ao atualizar método de pagamento", {
|
||||
@@ -167,9 +185,14 @@ export async function PATCH(
|
||||
});
|
||||
|
||||
return NextResponse.json(
|
||||
{ success: false, error: error instanceof Error ? error.message : "Erro ao atualizar método de pagamento" },
|
||||
{ status: 500 }
|
||||
{
|
||||
success: false,
|
||||
error:
|
||||
error instanceof Error
|
||||
? error.message
|
||||
: "Erro ao atualizar método de pagamento",
|
||||
},
|
||||
{ status: 500 },
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { secureLogger } from "@/lib/secure-logger";
|
||||
import { type NextRequest, NextResponse } from "next/server";
|
||||
import { requireAuth } from "@/lib/auth/middleware";
|
||||
import { getPurchaseIntentionsByBuyer } from "@/lib/prisma/queries/purchase-intentions";
|
||||
import { prisma } from "@/lib/prisma/app";
|
||||
import { getPurchaseIntentionsByBuyer } from "@/lib/prisma/queries/purchase-intentions";
|
||||
import { trackEvent } from "@/lib/prisma/queries/telemetry";
|
||||
import { secureLogger } from "@/lib/secure-logger";
|
||||
|
||||
/**
|
||||
* GET /api/purchase-intentions/my
|
||||
@@ -78,9 +78,7 @@ export async function GET(request: NextRequest) {
|
||||
|
||||
// Buscar character names dos vendedores em batch
|
||||
const sellerServerPairs = [
|
||||
...new Set(
|
||||
listings.map((l) => `${l.seller_id}:${l.server_name}`)
|
||||
),
|
||||
...new Set(listings.map((l) => `${l.seller_id}:${l.server_name}`)),
|
||||
];
|
||||
|
||||
const characterMap = new Map<string, string | null>();
|
||||
@@ -98,7 +96,7 @@ export async function GET(request: NextRequest) {
|
||||
},
|
||||
});
|
||||
characterMap.set(pair, character?.character_name || null);
|
||||
})
|
||||
}),
|
||||
);
|
||||
|
||||
// Serializar datas para string
|
||||
@@ -123,8 +121,10 @@ export async function GET(request: NextRequest) {
|
||||
|
||||
return {
|
||||
...intention,
|
||||
created_at: serializeDate(intention.created_at as Date) || intention.created_at,
|
||||
updated_at: serializeDate(intention.updated_at as Date) || intention.updated_at,
|
||||
created_at:
|
||||
serializeDate(intention.created_at as Date) || intention.created_at,
|
||||
updated_at:
|
||||
serializeDate(intention.updated_at as Date) || intention.updated_at,
|
||||
expires_at: serializeDate(intention.expires_at as Date | null),
|
||||
accepted_at: serializeDate(intention.accepted_at as Date | null),
|
||||
rejected_at: serializeDate(intention.rejected_at as Date | null),
|
||||
@@ -140,7 +140,8 @@ export async function GET(request: NextRequest) {
|
||||
seller_id: listing.seller_id,
|
||||
price_pix: listing.price_pix ? Number(listing.price_pix) : null,
|
||||
price_tibia_coins:
|
||||
listing.price_tibia_coins !== null && listing.price_tibia_coins !== undefined
|
||||
listing.price_tibia_coins !== null &&
|
||||
listing.price_tibia_coins !== undefined
|
||||
? Number(listing.price_tibia_coins)
|
||||
: null,
|
||||
price_gold_coins: listing.price_gold_coins
|
||||
@@ -185,7 +186,7 @@ export async function GET(request: NextRequest) {
|
||||
intentions: intentionsWithListingDetails,
|
||||
count: intentionsWithListingDetails.length,
|
||||
},
|
||||
{ status: 200 }
|
||||
{ status: 200 },
|
||||
);
|
||||
} catch (error) {
|
||||
secureLogger.error("❌ Erro ao buscar intenções de compra do usuário", {
|
||||
@@ -195,8 +196,7 @@ export async function GET(request: NextRequest) {
|
||||
|
||||
return NextResponse.json(
|
||||
{ success: false, error: "Erro ao buscar intenções de compra" },
|
||||
{ status: 500 }
|
||||
{ status: 500 },
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { type NextRequest, NextResponse } from "next/server";
|
||||
import { getActiveServers } from "@/lib/prisma/queries/servers";
|
||||
import { secureLogger } from "@/lib/secure-logger";
|
||||
|
||||
@@ -6,7 +6,7 @@ import { secureLogger } from "@/lib/secure-logger";
|
||||
* GET /api/servers
|
||||
* Lista todos os servidores ativos disponíveis
|
||||
*/
|
||||
export async function GET(request: NextRequest) {
|
||||
export async function GET(_request: NextRequest) {
|
||||
try {
|
||||
const servers = await getActiveServers();
|
||||
|
||||
@@ -30,7 +30,7 @@ export async function GET(request: NextRequest) {
|
||||
},
|
||||
})),
|
||||
},
|
||||
{ status: 200 }
|
||||
{ status: 200 },
|
||||
);
|
||||
} catch (error) {
|
||||
secureLogger.error("❌ Erro ao buscar servidores", {
|
||||
@@ -43,8 +43,7 @@ export async function GET(request: NextRequest) {
|
||||
success: false,
|
||||
error: "Erro ao buscar servidores",
|
||||
},
|
||||
{ status: 500 }
|
||||
{ status: 500 },
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,4 +13,3 @@ export async function GET() {
|
||||
message: "Socket.io está ativo",
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { type NextRequest, NextResponse } from "next/server";
|
||||
import { requireAdminUser } from "@/lib/auth/middleware";
|
||||
import {
|
||||
countUsersInSubscriptionTier,
|
||||
deleteSubscriptionTier,
|
||||
getSubscriptionTierById,
|
||||
updateSubscriptionTier,
|
||||
deleteSubscriptionTier,
|
||||
countUsersInSubscriptionTier,
|
||||
} from "@/lib/prisma/queries/subscription-tiers";
|
||||
import { updateSubscriptionTierSchema } from "@/lib/validation-schemas";
|
||||
import { secureLogger } from "@/lib/secure-logger";
|
||||
import { updateSubscriptionTierSchema } from "@/lib/validation-schemas";
|
||||
|
||||
export async function GET(
|
||||
request: NextRequest,
|
||||
{ params }: { params: Promise<{ id: string }> }
|
||||
{ params }: { params: Promise<{ id: string }> },
|
||||
) {
|
||||
const auth = await requireAdminUser(request);
|
||||
if (auth instanceof NextResponse) {
|
||||
@@ -25,7 +25,7 @@ export async function GET(
|
||||
if (Number.isNaN(tierId) || tierId <= 0) {
|
||||
return NextResponse.json(
|
||||
{ success: false, error: "ID de tier inválido" },
|
||||
{ status: 400 }
|
||||
{ status: 400 },
|
||||
);
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ export async function GET(
|
||||
if (!tier) {
|
||||
return NextResponse.json(
|
||||
{ success: false, error: "Plano não encontrado" },
|
||||
{ status: 404 }
|
||||
{ status: 404 },
|
||||
);
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@ export async function GET(
|
||||
success: true,
|
||||
tier,
|
||||
},
|
||||
{ status: 200 }
|
||||
{ status: 200 },
|
||||
);
|
||||
} catch (error) {
|
||||
secureLogger.error("❌ Erro ao consultar subscription tier", {
|
||||
@@ -55,14 +55,14 @@ export async function GET(
|
||||
success: false,
|
||||
error: "Erro ao consultar plano de assinatura",
|
||||
},
|
||||
{ status: 500 }
|
||||
{ status: 500 },
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export async function PATCH(
|
||||
request: NextRequest,
|
||||
{ params }: { params: Promise<{ id: string }> }
|
||||
{ params }: { params: Promise<{ id: string }> },
|
||||
) {
|
||||
const auth = await requireAdminUser(request);
|
||||
if (auth instanceof NextResponse) {
|
||||
@@ -76,7 +76,7 @@ export async function PATCH(
|
||||
if (Number.isNaN(tierId) || tierId <= 0) {
|
||||
return NextResponse.json(
|
||||
{ success: false, error: "ID de tier inválido" },
|
||||
{ status: 400 }
|
||||
{ status: 400 },
|
||||
);
|
||||
}
|
||||
|
||||
@@ -105,7 +105,7 @@ export async function PATCH(
|
||||
success: true,
|
||||
tier: updatedTier,
|
||||
},
|
||||
{ status: 200 }
|
||||
{ status: 200 },
|
||||
);
|
||||
} catch (error) {
|
||||
if (error instanceof Error && "issues" in error) {
|
||||
@@ -115,7 +115,7 @@ export async function PATCH(
|
||||
error: "Dados inválidos",
|
||||
details: (error as any).issues,
|
||||
},
|
||||
{ status: 400 }
|
||||
{ status: 400 },
|
||||
);
|
||||
}
|
||||
|
||||
@@ -128,14 +128,14 @@ export async function PATCH(
|
||||
success: false,
|
||||
error: "Erro ao atualizar plano de assinatura",
|
||||
},
|
||||
{ status: 500 }
|
||||
{ status: 500 },
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export async function DELETE(
|
||||
request: NextRequest,
|
||||
{ params }: { params: Promise<{ id: string }> }
|
||||
{ params }: { params: Promise<{ id: string }> },
|
||||
) {
|
||||
const auth = await requireAdminUser(request);
|
||||
if (auth instanceof NextResponse) {
|
||||
@@ -149,7 +149,7 @@ export async function DELETE(
|
||||
if (Number.isNaN(tierId) || tierId <= 0) {
|
||||
return NextResponse.json(
|
||||
{ success: false, error: "ID de tier inválido" },
|
||||
{ status: 400 }
|
||||
{ status: 400 },
|
||||
);
|
||||
}
|
||||
|
||||
@@ -158,7 +158,7 @@ export async function DELETE(
|
||||
if (!tier) {
|
||||
return NextResponse.json(
|
||||
{ success: false, error: "Plano não encontrado" },
|
||||
{ status: 404 }
|
||||
{ status: 404 },
|
||||
);
|
||||
}
|
||||
|
||||
@@ -168,7 +168,7 @@ export async function DELETE(
|
||||
success: false,
|
||||
error: "O plano padrão 'free' não pode ser removido.",
|
||||
},
|
||||
{ status: 400 }
|
||||
{ status: 400 },
|
||||
);
|
||||
}
|
||||
|
||||
@@ -178,10 +178,11 @@ export async function DELETE(
|
||||
return NextResponse.json(
|
||||
{
|
||||
success: false,
|
||||
error: "Plano não pode ser removido enquanto houver usuários associados.",
|
||||
error:
|
||||
"Plano não pode ser removido enquanto houver usuários associados.",
|
||||
users: userCount,
|
||||
},
|
||||
{ status: 409 }
|
||||
{ status: 409 },
|
||||
);
|
||||
}
|
||||
|
||||
@@ -191,7 +192,7 @@ export async function DELETE(
|
||||
{
|
||||
success: true,
|
||||
},
|
||||
{ status: 200 }
|
||||
{ status: 200 },
|
||||
);
|
||||
} catch (error) {
|
||||
secureLogger.error("❌ Erro ao remover subscription tier", {
|
||||
@@ -203,8 +204,7 @@ export async function DELETE(
|
||||
success: false,
|
||||
error: "Erro ao remover plano de assinatura",
|
||||
},
|
||||
{ status: 500 }
|
||||
{ status: 500 },
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { type NextRequest, NextResponse } from "next/server";
|
||||
import { requireAdminUser } from "@/lib/auth/middleware";
|
||||
import {
|
||||
assignUserToSubscriptionTier,
|
||||
getDefaultSubscriptionTier,
|
||||
getSubscriptionTierById,
|
||||
getSubscriptionTierBySlug,
|
||||
getDefaultSubscriptionTier,
|
||||
} from "@/lib/prisma/queries/subscription-tiers";
|
||||
import { assignSubscriptionTierSchema } from "@/lib/validation-schemas";
|
||||
import { secureLogger } from "@/lib/secure-logger";
|
||||
import { getUserById } from "@/lib/prisma/queries/users";
|
||||
import { secureLogger } from "@/lib/secure-logger";
|
||||
import { assignSubscriptionTierSchema } from "@/lib/validation-schemas";
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
const auth = await requireAdminUser(request);
|
||||
@@ -27,19 +27,17 @@ export async function POST(request: NextRequest) {
|
||||
success: false,
|
||||
error: "Usuário não encontrado.",
|
||||
},
|
||||
{ status: 404 }
|
||||
{ status: 404 },
|
||||
);
|
||||
}
|
||||
|
||||
let targetTierId: number | null = null;
|
||||
let targetTier:
|
||||
| {
|
||||
subscription_tier_id: number;
|
||||
name: string;
|
||||
slug: string;
|
||||
level: number;
|
||||
}
|
||||
| null = null;
|
||||
let targetTier: {
|
||||
subscription_tier_id: number;
|
||||
name: string;
|
||||
slug: string;
|
||||
level: number;
|
||||
} | null = null;
|
||||
|
||||
if (parsed.tierId !== undefined) {
|
||||
if (parsed.tierId === null) {
|
||||
@@ -50,7 +48,7 @@ export async function POST(request: NextRequest) {
|
||||
success: false,
|
||||
error: "Plano padrão não encontrado.",
|
||||
},
|
||||
{ status: 500 }
|
||||
{ status: 500 },
|
||||
);
|
||||
}
|
||||
targetTier = defaultTier;
|
||||
@@ -62,20 +60,23 @@ export async function POST(request: NextRequest) {
|
||||
success: false,
|
||||
error: "Plano informado não existe.",
|
||||
},
|
||||
{ status: 404 }
|
||||
{ status: 404 },
|
||||
);
|
||||
}
|
||||
targetTier = tierById;
|
||||
}
|
||||
} else if (parsed.tierSlug) {
|
||||
const tierBySlug = await getSubscriptionTierBySlug(parsed.tierSlug, false);
|
||||
const tierBySlug = await getSubscriptionTierBySlug(
|
||||
parsed.tierSlug,
|
||||
false,
|
||||
);
|
||||
if (!tierBySlug) {
|
||||
return NextResponse.json(
|
||||
{
|
||||
success: false,
|
||||
error: "Plano informado não existe.",
|
||||
},
|
||||
{ status: 404 }
|
||||
{ status: 404 },
|
||||
);
|
||||
}
|
||||
targetTier = tierBySlug;
|
||||
@@ -87,7 +88,7 @@ export async function POST(request: NextRequest) {
|
||||
success: false,
|
||||
error: "Plano de assinatura não identificado.",
|
||||
},
|
||||
{ status: 400 }
|
||||
{ status: 400 },
|
||||
);
|
||||
}
|
||||
|
||||
@@ -106,7 +107,7 @@ export async function POST(request: NextRequest) {
|
||||
level: targetTier.level,
|
||||
},
|
||||
},
|
||||
{ status: 200 }
|
||||
{ status: 200 },
|
||||
);
|
||||
} catch (error) {
|
||||
if (error instanceof Error && "issues" in error) {
|
||||
@@ -116,7 +117,7 @@ export async function POST(request: NextRequest) {
|
||||
error: "Dados inválidos",
|
||||
details: (error as any).issues,
|
||||
},
|
||||
{ status: 400 }
|
||||
{ status: 400 },
|
||||
);
|
||||
}
|
||||
|
||||
@@ -129,8 +130,7 @@ export async function POST(request: NextRequest) {
|
||||
success: false,
|
||||
error: "Erro ao atribuir plano de assinatura ao usuário",
|
||||
},
|
||||
{ status: 500 }
|
||||
{ status: 500 },
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { type NextRequest, NextResponse } from "next/server";
|
||||
import { requireAdminUser } from "@/lib/auth/middleware";
|
||||
import {
|
||||
getAllSubscriptionTiers,
|
||||
createSubscriptionTier,
|
||||
getAllSubscriptionTiers,
|
||||
} from "@/lib/prisma/queries/subscription-tiers";
|
||||
import { createSubscriptionTierSchema } from "@/lib/validation-schemas";
|
||||
import { secureLogger } from "@/lib/secure-logger";
|
||||
import { createSubscriptionTierSchema } from "@/lib/validation-schemas";
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
const auth = await requireAdminUser(request);
|
||||
@@ -20,7 +20,7 @@ export async function GET(request: NextRequest) {
|
||||
success: true,
|
||||
tiers,
|
||||
},
|
||||
{ status: 200 }
|
||||
{ status: 200 },
|
||||
);
|
||||
} catch (error) {
|
||||
secureLogger.error("❌ Erro ao listar subscription tiers", {
|
||||
@@ -32,7 +32,7 @@ export async function GET(request: NextRequest) {
|
||||
success: false,
|
||||
error: "Erro ao listar planos de assinatura",
|
||||
},
|
||||
{ status: 500 }
|
||||
{ status: 500 },
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -68,7 +68,7 @@ export async function POST(request: NextRequest) {
|
||||
success: true,
|
||||
tier,
|
||||
},
|
||||
{ status: 201 }
|
||||
{ status: 201 },
|
||||
);
|
||||
} catch (error) {
|
||||
if (error instanceof NextResponse) {
|
||||
@@ -82,7 +82,7 @@ export async function POST(request: NextRequest) {
|
||||
error: "Dados inválidos",
|
||||
details: (error as any).issues,
|
||||
},
|
||||
{ status: 400 }
|
||||
{ status: 400 },
|
||||
);
|
||||
}
|
||||
|
||||
@@ -95,8 +95,7 @@ export async function POST(request: NextRequest) {
|
||||
success: false,
|
||||
error: "Erro ao criar plano de assinatura",
|
||||
},
|
||||
{ status: 500 }
|
||||
{ status: 500 },
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
+28
-27
@@ -1,21 +1,21 @@
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { type NextRequest, NextResponse } from "next/server";
|
||||
import { optionalAuth } from "@/lib/auth/middleware";
|
||||
import {
|
||||
getAdAnalytics,
|
||||
getMarketplaceStats,
|
||||
trackEvent,
|
||||
trackEventsBatch,
|
||||
} from "@/lib/prisma/queries/telemetry";
|
||||
import {
|
||||
checkRateLimit,
|
||||
getClientIP,
|
||||
createRateLimitResponse,
|
||||
getClientIP,
|
||||
} from "@/lib/rate-limiter";
|
||||
import { secureLogger } from "@/lib/secure-logger";
|
||||
import {
|
||||
telemetryEventSchema,
|
||||
telemetryBatchSchema,
|
||||
telemetryEventSchema,
|
||||
} from "@/lib/validation-schemas";
|
||||
import { optionalAuth } from "@/lib/auth/middleware";
|
||||
import {
|
||||
trackEvent,
|
||||
trackEventsBatch,
|
||||
getAdAnalytics,
|
||||
getMarketplaceStats,
|
||||
} from "@/lib/prisma/queries/telemetry";
|
||||
|
||||
/**
|
||||
* POST /api/telemetry/event
|
||||
@@ -33,12 +33,14 @@ export async function POST(request: NextRequest) {
|
||||
|
||||
// Autenticação opcional (usuários anônimos podem gerar eventos)
|
||||
const authResult = await optionalAuth(request);
|
||||
const userId = authResult instanceof NextResponse ? undefined : authResult?.userId;
|
||||
const userId =
|
||||
authResult instanceof NextResponse ? undefined : authResult?.userId;
|
||||
|
||||
const body = await request.json();
|
||||
|
||||
// Verificar se é batch ou evento único
|
||||
const isBatch = body.events && Array.isArray(body.events) && body.events.length > 0;
|
||||
const isBatch =
|
||||
body.events && Array.isArray(body.events) && body.events.length > 0;
|
||||
|
||||
if (isBatch) {
|
||||
// Batch de eventos
|
||||
@@ -50,7 +52,7 @@ export async function POST(request: NextRequest) {
|
||||
metadata: event.metadata || {},
|
||||
})),
|
||||
};
|
||||
|
||||
|
||||
const validationResult = telemetryBatchSchema.safeParse(normalizedBody);
|
||||
|
||||
if (!validationResult.success) {
|
||||
@@ -60,7 +62,7 @@ export async function POST(request: NextRequest) {
|
||||
error: "Dados de entrada inválidos",
|
||||
details: validationResult.error.issues,
|
||||
},
|
||||
{ status: 400 }
|
||||
{ status: 400 },
|
||||
);
|
||||
}
|
||||
|
||||
@@ -88,7 +90,7 @@ export async function POST(request: NextRequest) {
|
||||
success: true,
|
||||
events: trackedEvents,
|
||||
},
|
||||
{ status: 201 }
|
||||
{ status: 201 },
|
||||
);
|
||||
} else {
|
||||
// Evento único
|
||||
@@ -97,7 +99,7 @@ export async function POST(request: NextRequest) {
|
||||
...body,
|
||||
metadata: body.metadata || {},
|
||||
};
|
||||
|
||||
|
||||
const validationResult = telemetryEventSchema.safeParse(normalizedBody);
|
||||
|
||||
if (!validationResult.success) {
|
||||
@@ -107,7 +109,7 @@ export async function POST(request: NextRequest) {
|
||||
error: "Dados de entrada inválidos",
|
||||
details: validationResult.error.issues,
|
||||
},
|
||||
{ status: 400 }
|
||||
{ status: 400 },
|
||||
);
|
||||
}
|
||||
|
||||
@@ -134,7 +136,7 @@ export async function POST(request: NextRequest) {
|
||||
success: true,
|
||||
event: trackedEvent,
|
||||
},
|
||||
{ status: 201 }
|
||||
{ status: 201 },
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
@@ -148,7 +150,7 @@ export async function POST(request: NextRequest) {
|
||||
success: false,
|
||||
error: "Erro ao registrar evento",
|
||||
},
|
||||
{ status: 500 }
|
||||
{ status: 500 },
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -178,7 +180,7 @@ export async function GET(request: NextRequest) {
|
||||
success: true,
|
||||
stats,
|
||||
},
|
||||
{ status: 200 }
|
||||
{ status: 200 },
|
||||
);
|
||||
}
|
||||
|
||||
@@ -186,13 +188,13 @@ export async function GET(request: NextRequest) {
|
||||
if (adIdParam) {
|
||||
const adId = parseInt(adIdParam, 10);
|
||||
|
||||
if (isNaN(adId)) {
|
||||
if (Number.isNaN(adId)) {
|
||||
return NextResponse.json(
|
||||
{
|
||||
success: false,
|
||||
error: "ID do anúncio inválido",
|
||||
},
|
||||
{ status: 400 }
|
||||
{ status: 400 },
|
||||
);
|
||||
}
|
||||
|
||||
@@ -204,7 +206,7 @@ export async function GET(request: NextRequest) {
|
||||
success: false,
|
||||
error: "Analytics não encontrado para este anúncio",
|
||||
},
|
||||
{ status: 404 }
|
||||
{ status: 404 },
|
||||
);
|
||||
}
|
||||
|
||||
@@ -213,7 +215,7 @@ export async function GET(request: NextRequest) {
|
||||
success: true,
|
||||
analytics,
|
||||
},
|
||||
{ status: 200 }
|
||||
{ status: 200 },
|
||||
);
|
||||
}
|
||||
|
||||
@@ -222,7 +224,7 @@ export async function GET(request: NextRequest) {
|
||||
success: false,
|
||||
error: "Parâmetro 'adId' ou 'dashboard' é obrigatório",
|
||||
},
|
||||
{ status: 400 }
|
||||
{ status: 400 },
|
||||
);
|
||||
} catch (error) {
|
||||
secureLogger.error("❌ Erro ao buscar analytics", {
|
||||
@@ -235,8 +237,7 @@ export async function GET(request: NextRequest) {
|
||||
success: false,
|
||||
error: "Erro ao buscar analytics",
|
||||
},
|
||||
{ status: 500 }
|
||||
{ status: 500 },
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,22 +1,22 @@
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { secureLogger } from "@/lib/secure-logger";
|
||||
import { type NextRequest, NextResponse } from "next/server";
|
||||
import { getUserById } from "@/lib/prisma/queries/users";
|
||||
import { secureLogger } from "@/lib/secure-logger";
|
||||
|
||||
export async function GET(
|
||||
request: NextRequest,
|
||||
{ params }: { params: Promise<{ id: string }> }
|
||||
_request: NextRequest,
|
||||
{ params }: { params: Promise<{ id: string }> },
|
||||
) {
|
||||
try {
|
||||
const { id } = await params;
|
||||
const userId = parseInt(id, 10);
|
||||
|
||||
if (isNaN(userId)) {
|
||||
if (Number.isNaN(userId)) {
|
||||
return NextResponse.json(
|
||||
{
|
||||
success: false,
|
||||
error: "ID de usuário inválido",
|
||||
},
|
||||
{ status: 400 }
|
||||
{ status: 400 },
|
||||
);
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ export async function GET(
|
||||
success: false,
|
||||
error: "Usuário não encontrado",
|
||||
},
|
||||
{ status: 404 }
|
||||
{ status: 404 },
|
||||
);
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@ export async function GET(
|
||||
createdAt: user.created_at,
|
||||
},
|
||||
},
|
||||
{ status: 200 }
|
||||
{ status: 200 },
|
||||
);
|
||||
} catch (error) {
|
||||
secureLogger.error("❌ Erro ao buscar perfil público", {
|
||||
@@ -60,8 +60,7 @@ export async function GET(
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : "Erro interno",
|
||||
},
|
||||
{ status: 500 }
|
||||
{ status: 500 },
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,37 +1,36 @@
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { secureLogger } from "@/lib/secure-logger";
|
||||
import { type NextRequest, NextResponse } from "next/server";
|
||||
import { requireAuth } from "@/lib/auth/middleware";
|
||||
import {
|
||||
updateCharacter,
|
||||
deleteCharacter,
|
||||
getCharacterById,
|
||||
updateCharacter,
|
||||
} from "@/lib/prisma/queries/characters";
|
||||
import { secureLogger } from "@/lib/secure-logger";
|
||||
import { characterSchema } from "@/lib/validation-schemas";
|
||||
|
||||
export async function PUT(
|
||||
request: NextRequest,
|
||||
{ params }: { params: Promise<{ id: string }> }
|
||||
{ params }: { params: Promise<{ id: string }> },
|
||||
) {
|
||||
try {
|
||||
// Middleware de autenticação
|
||||
const authResult = await requireAuth(request);
|
||||
|
||||
|
||||
if (authResult instanceof NextResponse) {
|
||||
return authResult;
|
||||
}
|
||||
|
||||
|
||||
const auth = authResult;
|
||||
|
||||
const { id } = await params;
|
||||
const characterId = parseInt(id, 10);
|
||||
|
||||
if (isNaN(characterId)) {
|
||||
if (Number.isNaN(characterId)) {
|
||||
return NextResponse.json(
|
||||
{
|
||||
success: false,
|
||||
error: "ID de personagem inválido",
|
||||
},
|
||||
{ status: 400 }
|
||||
{ status: 400 },
|
||||
);
|
||||
}
|
||||
|
||||
@@ -52,22 +51,18 @@ export async function PUT(
|
||||
error: "Dados de entrada inválidos",
|
||||
details: errors,
|
||||
},
|
||||
{ status: 400 }
|
||||
{ status: 400 },
|
||||
);
|
||||
}
|
||||
|
||||
const validatedData = validationResult.data;
|
||||
|
||||
// Atualizar personagem (verifica ownership internamente)
|
||||
const updatedCharacter = await updateCharacter(
|
||||
characterId,
|
||||
auth.userId,
|
||||
{
|
||||
character_name: validatedData.characterName,
|
||||
server_name: validatedData.serverName,
|
||||
is_main: validatedData.isMain,
|
||||
}
|
||||
);
|
||||
const updatedCharacter = await updateCharacter(characterId, auth.userId, {
|
||||
character_name: validatedData.characterName,
|
||||
server_name: validatedData.serverName,
|
||||
is_main: validatedData.isMain,
|
||||
});
|
||||
|
||||
secureLogger.info("✅ Personagem atualizado com sucesso", {
|
||||
userId: auth.userId,
|
||||
@@ -87,7 +82,7 @@ export async function PUT(
|
||||
createdAt: updatedCharacter.created_at,
|
||||
},
|
||||
},
|
||||
{ status: 200 }
|
||||
{ status: 200 },
|
||||
);
|
||||
} catch (error) {
|
||||
// Se for NextResponse (erro de autenticação), re-lançar
|
||||
@@ -96,16 +91,13 @@ export async function PUT(
|
||||
}
|
||||
|
||||
// Verificar se é erro de permissão
|
||||
if (
|
||||
error instanceof Error &&
|
||||
error.message.includes("não tem permissão")
|
||||
) {
|
||||
if (error instanceof Error && error.message.includes("não tem permissão")) {
|
||||
return NextResponse.json(
|
||||
{
|
||||
success: false,
|
||||
error: error.message,
|
||||
},
|
||||
{ status: 403 }
|
||||
{ status: 403 },
|
||||
);
|
||||
}
|
||||
|
||||
@@ -120,35 +112,35 @@ export async function PUT(
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : "Erro interno",
|
||||
},
|
||||
{ status: 500 }
|
||||
{ status: 500 },
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export async function DELETE(
|
||||
request: NextRequest,
|
||||
{ params }: { params: Promise<{ id: string }> }
|
||||
{ params }: { params: Promise<{ id: string }> },
|
||||
) {
|
||||
try {
|
||||
// Middleware de autenticação
|
||||
const authResult = await requireAuth(request);
|
||||
|
||||
|
||||
if (authResult instanceof NextResponse) {
|
||||
return authResult;
|
||||
}
|
||||
|
||||
|
||||
const auth = authResult;
|
||||
|
||||
const { id } = await params;
|
||||
const characterId = parseInt(id, 10);
|
||||
|
||||
if (isNaN(characterId)) {
|
||||
if (Number.isNaN(characterId)) {
|
||||
return NextResponse.json(
|
||||
{
|
||||
success: false,
|
||||
error: "ID de personagem inválido",
|
||||
},
|
||||
{ status: 400 }
|
||||
{ status: 400 },
|
||||
);
|
||||
}
|
||||
|
||||
@@ -165,7 +157,7 @@ export async function DELETE(
|
||||
{
|
||||
success: true,
|
||||
},
|
||||
{ status: 200 }
|
||||
{ status: 200 },
|
||||
);
|
||||
} catch (error) {
|
||||
// Se for NextResponse (erro de autenticação), re-lançar
|
||||
@@ -174,16 +166,13 @@ export async function DELETE(
|
||||
}
|
||||
|
||||
// Verificar se é erro de permissão
|
||||
if (
|
||||
error instanceof Error &&
|
||||
error.message.includes("não tem permissão")
|
||||
) {
|
||||
if (error instanceof Error && error.message.includes("não tem permissão")) {
|
||||
return NextResponse.json(
|
||||
{
|
||||
success: false,
|
||||
error: error.message,
|
||||
},
|
||||
{ status: 403 }
|
||||
{ status: 403 },
|
||||
);
|
||||
}
|
||||
|
||||
@@ -198,8 +187,7 @@ export async function DELETE(
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : "Erro interno",
|
||||
},
|
||||
{ status: 500 }
|
||||
{ status: 500 },
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,18 +1,21 @@
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { secureLogger } from "@/lib/secure-logger";
|
||||
import { type NextRequest, NextResponse } from "next/server";
|
||||
import { requireAuth } from "@/lib/auth/middleware";
|
||||
import { getUserCharacters, createCharacter } from "@/lib/prisma/queries/characters";
|
||||
import {
|
||||
createCharacter,
|
||||
getUserCharacters,
|
||||
} from "@/lib/prisma/queries/characters";
|
||||
import { secureLogger } from "@/lib/secure-logger";
|
||||
import { characterSchema } from "@/lib/validation-schemas";
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
try {
|
||||
// Middleware de autenticação
|
||||
const authResult = await requireAuth(request);
|
||||
|
||||
|
||||
if (authResult instanceof NextResponse) {
|
||||
return authResult;
|
||||
}
|
||||
|
||||
|
||||
const auth = authResult;
|
||||
|
||||
// Listar personagens do usuário
|
||||
@@ -30,7 +33,7 @@ export async function GET(request: NextRequest) {
|
||||
createdAt: char.created_at,
|
||||
})),
|
||||
},
|
||||
{ status: 200 }
|
||||
{ status: 200 },
|
||||
);
|
||||
} catch (error) {
|
||||
// Se for NextResponse (erro de autenticação), re-lançar
|
||||
@@ -49,7 +52,7 @@ export async function GET(request: NextRequest) {
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : "Erro interno",
|
||||
},
|
||||
{ status: 500 }
|
||||
{ status: 500 },
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -58,11 +61,11 @@ export async function POST(request: NextRequest) {
|
||||
try {
|
||||
// Middleware de autenticação
|
||||
const authResult = await requireAuth(request);
|
||||
|
||||
|
||||
if (authResult instanceof NextResponse) {
|
||||
return authResult;
|
||||
}
|
||||
|
||||
|
||||
const auth = authResult;
|
||||
|
||||
const body = await request.json();
|
||||
@@ -82,7 +85,7 @@ export async function POST(request: NextRequest) {
|
||||
error: "Dados de entrada inválidos",
|
||||
details: errors,
|
||||
},
|
||||
{ status: 400 }
|
||||
{ status: 400 },
|
||||
);
|
||||
}
|
||||
|
||||
@@ -113,7 +116,7 @@ export async function POST(request: NextRequest) {
|
||||
createdAt: newCharacter.created_at,
|
||||
},
|
||||
},
|
||||
{ status: 201 }
|
||||
{ status: 201 },
|
||||
);
|
||||
} catch (error) {
|
||||
// Se for NextResponse (erro de autenticação), re-lançar
|
||||
@@ -128,7 +131,7 @@ export async function POST(request: NextRequest) {
|
||||
success: false,
|
||||
error: "Este personagem já está cadastrado neste servidor",
|
||||
},
|
||||
{ status: 409 }
|
||||
{ status: 409 },
|
||||
);
|
||||
}
|
||||
|
||||
@@ -143,8 +146,7 @@ export async function POST(request: NextRequest) {
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : "Erro interno",
|
||||
},
|
||||
{ status: 500 }
|
||||
{ status: 500 },
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
+13
-14
@@ -1,19 +1,19 @@
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { secureLogger } from "@/lib/secure-logger";
|
||||
import { type NextRequest, NextResponse } from "next/server";
|
||||
import { requireAuth } from "@/lib/auth/middleware";
|
||||
import { getUserById, updateUser } from "@/lib/prisma/queries/users";
|
||||
import { getUserCharacters } from "@/lib/prisma/queries/characters";
|
||||
import { getUserById, updateUser } from "@/lib/prisma/queries/users";
|
||||
import { secureLogger } from "@/lib/secure-logger";
|
||||
import { updateUserSchema } from "@/lib/validation-schemas";
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
try {
|
||||
// Middleware de autenticação
|
||||
const authResult = await requireAuth(request);
|
||||
|
||||
|
||||
if (authResult instanceof NextResponse) {
|
||||
return authResult;
|
||||
}
|
||||
|
||||
|
||||
const auth = authResult;
|
||||
|
||||
// Buscar dados do usuário
|
||||
@@ -25,7 +25,7 @@ export async function GET(request: NextRequest) {
|
||||
success: false,
|
||||
error: "Usuário não encontrado",
|
||||
},
|
||||
{ status: 404 }
|
||||
{ status: 404 },
|
||||
);
|
||||
}
|
||||
|
||||
@@ -61,7 +61,7 @@ export async function GET(request: NextRequest) {
|
||||
createdAt: char.created_at,
|
||||
})),
|
||||
},
|
||||
{ status: 200 }
|
||||
{ status: 200 },
|
||||
);
|
||||
} catch (error) {
|
||||
// Se for NextResponse (erro de autenticação), re-lançar
|
||||
@@ -80,7 +80,7 @@ export async function GET(request: NextRequest) {
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : "Erro interno",
|
||||
},
|
||||
{ status: 500 }
|
||||
{ status: 500 },
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -89,11 +89,11 @@ export async function PUT(request: NextRequest) {
|
||||
try {
|
||||
// Middleware de autenticação
|
||||
const authResult = await requireAuth(request);
|
||||
|
||||
|
||||
if (authResult instanceof NextResponse) {
|
||||
return authResult;
|
||||
}
|
||||
|
||||
|
||||
const auth = authResult;
|
||||
|
||||
const body = await request.json();
|
||||
@@ -113,7 +113,7 @@ export async function PUT(request: NextRequest) {
|
||||
error: "Dados de entrada inválidos",
|
||||
details: errors,
|
||||
},
|
||||
{ status: 400 }
|
||||
{ status: 400 },
|
||||
);
|
||||
}
|
||||
|
||||
@@ -153,7 +153,7 @@ export async function PUT(request: NextRequest) {
|
||||
isVerified: updatedUser.is_verified,
|
||||
},
|
||||
},
|
||||
{ status: 200 }
|
||||
{ status: 200 },
|
||||
);
|
||||
} catch (error) {
|
||||
// Se for NextResponse (erro de autenticação), re-lançar
|
||||
@@ -172,8 +172,7 @@ export async function PUT(request: NextRequest) {
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : "Erro interno",
|
||||
},
|
||||
{ status: 500 }
|
||||
{ status: 500 },
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { abacatePay } from "@/lib/abacatepay";
|
||||
import { type NextRequest, NextResponse } from "next/server";
|
||||
import { markAsPaid } from "@/lib/payment-cache";
|
||||
import {
|
||||
checkRateLimit,
|
||||
getClientIP,
|
||||
createRateLimitResponse,
|
||||
getClientIP,
|
||||
} from "@/lib/rate-limiter";
|
||||
import { secureLogger } from "@/lib/secure-logger";
|
||||
|
||||
@@ -56,11 +55,11 @@ export async function POST(request: NextRequest) {
|
||||
// Rejeitar eventos com mais de 5 minutos de diferença
|
||||
if (timeDiff > 5 * 60 * 1000) {
|
||||
secureLogger.warn("Webhook rejeitado: timestamp inválido", {
|
||||
timeDiff: timeDiff / 1000 + "s",
|
||||
timeDiff: `${timeDiff / 1000}s`,
|
||||
});
|
||||
return NextResponse.json(
|
||||
{ error: "Timestamp inválido" },
|
||||
{ status: 400 }
|
||||
{ status: 400 },
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -119,7 +118,7 @@ export async function POST(request: NextRequest) {
|
||||
|
||||
secureLogger.info(
|
||||
"Registro de pagamento criado",
|
||||
secureLogger.mask(donationRecord)
|
||||
secureLogger.mask(donationRecord),
|
||||
);
|
||||
|
||||
// Salvar no cache para frontend detectar via polling
|
||||
@@ -150,7 +149,7 @@ export async function POST(request: NextRequest) {
|
||||
secureLogger.error("Webhook error", error);
|
||||
return NextResponse.json(
|
||||
{ error: "Erro ao processar webhook" },
|
||||
{ status: 500 }
|
||||
{ status: 500 },
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user