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
@@ -18,4 +18,3 @@ export const prisma =
|
||||
if (process.env.NODE_ENV !== "production") {
|
||||
globalForPrisma.prisma = prisma;
|
||||
}
|
||||
|
||||
+42
-32
@@ -4,11 +4,11 @@
|
||||
* usamos $queryRaw para queries dinâmicas
|
||||
*/
|
||||
|
||||
import { Prisma } from "@prisma/client";
|
||||
// Nota: itemsPrisma não está disponível ainda - usando better-sqlite3 diretamente
|
||||
// TODO: Implementar quando migrar SQLite para Prisma
|
||||
// import { itemsPrisma } from "./items";
|
||||
import { enToPtCategoryMap } from "../item-images";
|
||||
import { Prisma } from "@prisma/client";
|
||||
|
||||
// Stub temporário - não está sendo usado no momento
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
@@ -50,7 +50,6 @@ function getOrderBy(sort: SearchParams["sort"]): string {
|
||||
return "weight DESC, name ASC";
|
||||
case "category":
|
||||
return "category ASC, name ASC";
|
||||
case "name":
|
||||
default:
|
||||
return "name ASC";
|
||||
}
|
||||
@@ -60,7 +59,7 @@ function getOrderBy(sort: SearchParams["sort"]): string {
|
||||
* Busca itens no banco SQLite usando Prisma $queryRaw
|
||||
*/
|
||||
export async function searchItems<T = any>(
|
||||
params: SearchParams
|
||||
params: SearchParams,
|
||||
): Promise<SearchResult<T>> {
|
||||
const {
|
||||
q = "",
|
||||
@@ -80,27 +79,37 @@ export async function searchItems<T = any>(
|
||||
// Log básico do schema na primeira chamada
|
||||
if (!schemaLogged) {
|
||||
try {
|
||||
const tables = await (itemsPrisma.$queryRaw as any)(
|
||||
Prisma.raw(`SELECT name FROM sqlite_master WHERE type='table' ORDER BY name`)
|
||||
) as Array<{ name: string }>;
|
||||
console.info("[items-db] Tabelas:", tables.map((t: { name: string }) => t.name));
|
||||
const tables = (await (itemsPrisma.$queryRaw as any)(
|
||||
Prisma.raw(
|
||||
`SELECT name FROM sqlite_master WHERE type='table' ORDER BY name`,
|
||||
),
|
||||
)) as Array<{ name: string }>;
|
||||
console.info(
|
||||
"[items-db] Tabelas:",
|
||||
tables.map((t: { name: string }) => t.name),
|
||||
);
|
||||
schemaLogged = true;
|
||||
} catch (intErr) {
|
||||
console.warn("[items-db] Falha ao ler schema:", (intErr as any)?.message);
|
||||
console.warn(
|
||||
"[items-db] Falha ao ler schema:",
|
||||
(intErr as any)?.message,
|
||||
);
|
||||
schemaLogged = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Buscar todas as tabelas disponíveis
|
||||
const allTables = await (itemsPrisma.$queryRaw as any)(
|
||||
Prisma.raw(`SELECT name FROM sqlite_master WHERE type='table' AND name <> 'sqlite_sequence'`)
|
||||
) as Array<{ name: string }>;
|
||||
const allTables = (await (itemsPrisma.$queryRaw as any)(
|
||||
Prisma.raw(
|
||||
`SELECT name FROM sqlite_master WHERE type='table' AND name <> 'sqlite_sequence'`,
|
||||
),
|
||||
)) as Array<{ name: string }>;
|
||||
|
||||
// Filtra por categorias solicitadas (EN → PT)
|
||||
const allowedPt = new Set(
|
||||
categories.length > 0
|
||||
? categories.map((en) => enToPtCategoryMap[en] || en)
|
||||
: allTables.map((t: { name: string }) => t.name)
|
||||
: allTables.map((t: { name: string }) => t.name),
|
||||
);
|
||||
const candidateTables = allTables
|
||||
.map((t: { name: string }) => t.name)
|
||||
@@ -112,11 +121,13 @@ export async function searchItems<T = any>(
|
||||
for (const tableName of candidateTables) {
|
||||
try {
|
||||
// Verificar colunas da tabela
|
||||
const cols = await (itemsPrisma.$queryRaw as any)(
|
||||
Prisma.raw(`PRAGMA table_info(${tableName})`)
|
||||
) as Array<{ name: string; type: string }>;
|
||||
const cols = (await (itemsPrisma.$queryRaw as any)(
|
||||
Prisma.raw(`PRAGMA table_info(${tableName})`),
|
||||
)) as Array<{ name: string; type: string }>;
|
||||
|
||||
const colNames = new Set(cols.map((c: { name?: string }) => (c?.name || "").toLowerCase()));
|
||||
const colNames = new Set(
|
||||
cols.map((c: { name?: string }) => (c?.name || "").toLowerCase()),
|
||||
);
|
||||
|
||||
// Requer colunas mínimas
|
||||
if (
|
||||
@@ -184,13 +195,16 @@ export async function searchItems<T = any>(
|
||||
${levelCol} AS level,
|
||||
${weightCol} AS weight,
|
||||
${slotsCol} AS slots,
|
||||
${skillCol} AS skill${extraSelect ? ", " + extraSelect : ""}
|
||||
${skillCol} AS skill${extraSelect ? `, ${extraSelect}` : ""}
|
||||
FROM ${tableName} ${whereSqlTbl}`;
|
||||
|
||||
selects.push(selectSql);
|
||||
bindings.push(...tableBindings);
|
||||
} catch (e) {
|
||||
console.warn(`[items-db] Ignorando tabela ${tableName}:`, (e as any)?.message);
|
||||
console.warn(
|
||||
`[items-db] Ignorando tabela ${tableName}:`,
|
||||
(e as any)?.message,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -204,20 +218,20 @@ export async function searchItems<T = any>(
|
||||
|
||||
// Contagem total
|
||||
const countSql = `SELECT COUNT(1) as cnt FROM (${unionSql}) AS u`;
|
||||
const countResult = await (itemsPrisma.$queryRaw as any)(
|
||||
const countResult = (await (itemsPrisma.$queryRaw as any)(
|
||||
Prisma.raw(countSql),
|
||||
...bindings
|
||||
) as Array<{ cnt: number }>;
|
||||
...bindings,
|
||||
)) as Array<{ cnt: number }>;
|
||||
const total = countResult[0]?.cnt ?? 0;
|
||||
|
||||
// Buscar itens com paginação
|
||||
const selectSql = `SELECT * FROM (${unionSql}) AS u ORDER BY ${orderBy} LIMIT ? OFFSET ?`;
|
||||
const items = await (itemsPrisma.$queryRaw as any)(
|
||||
const items = (await (itemsPrisma.$queryRaw as any)(
|
||||
Prisma.raw(selectSql),
|
||||
...bindings,
|
||||
safePageSize,
|
||||
offset
|
||||
) as T[];
|
||||
offset,
|
||||
)) as T[];
|
||||
|
||||
return { items, total };
|
||||
} catch (error) {
|
||||
@@ -239,18 +253,15 @@ export async function getItemById(itemId: number): Promise<any | null> {
|
||||
|
||||
for (const table of tables) {
|
||||
try {
|
||||
const item = await (itemsPrisma.$queryRaw as any)(
|
||||
const item = (await (itemsPrisma.$queryRaw as any)(
|
||||
Prisma.raw(`SELECT * FROM ${table.name} WHERE id = ?`),
|
||||
itemId
|
||||
) as any[];
|
||||
itemId,
|
||||
)) as any[];
|
||||
|
||||
if (item && item.length > 0) {
|
||||
return item[0];
|
||||
}
|
||||
} catch (err) {
|
||||
// Ignorar erros de tabelas que não têm coluna 'id' ou outros problemas
|
||||
continue;
|
||||
}
|
||||
} catch (_err) {}
|
||||
}
|
||||
|
||||
return null;
|
||||
@@ -259,4 +270,3 @@ export async function getItemById(itemId: number): Promise<any | null> {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -1,9 +1,9 @@
|
||||
/**
|
||||
* Prisma Client para SQLite (Banco de Itens do TibiaWiki)
|
||||
*
|
||||
*
|
||||
* NOTA: Por enquanto, mantemos better-sqlite3 para queries SQLite
|
||||
* devido às limitações do Prisma com tabelas dinâmicas.
|
||||
*
|
||||
*
|
||||
* Este arquivo está preparado para migração futura quando necessário.
|
||||
*/
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@ export async function createBid(data: CreateBidData): Promise<Bid> {
|
||||
return {
|
||||
...bid,
|
||||
bid_amount: Number(bid.bid_amount),
|
||||
bid_type: (bid.bid_type as "pix" | "tibia_coins" | "gold_coins" | null),
|
||||
bid_type: bid.bid_type as "pix" | "tibia_coins" | "gold_coins" | null,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -46,22 +46,19 @@ export async function createBid(data: CreateBidData): Promise<Bid> {
|
||||
*/
|
||||
export async function getBidsByListing(
|
||||
listingId: number,
|
||||
activeOnly: boolean = true
|
||||
activeOnly: boolean = true,
|
||||
): Promise<Bid[]> {
|
||||
const bids = await prisma.bid.findMany({
|
||||
where: {
|
||||
listing_id: listingId,
|
||||
...(activeOnly ? { is_active: true } : {}),
|
||||
},
|
||||
orderBy: [
|
||||
{ bid_amount: "desc" },
|
||||
{ created_at: "desc" },
|
||||
],
|
||||
orderBy: [{ bid_amount: "desc" }, { created_at: "desc" }],
|
||||
});
|
||||
return bids.map(bid => ({
|
||||
return bids.map((bid) => ({
|
||||
...bid,
|
||||
bid_amount: Number(bid.bid_amount),
|
||||
bid_type: (bid.bid_type as "pix" | "tibia_coins" | "gold_coins" | null),
|
||||
bid_type: bid.bid_type as "pix" | "tibia_coins" | "gold_coins" | null,
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -74,16 +71,13 @@ export async function getHighestBid(listingId: number): Promise<Bid | null> {
|
||||
listing_id: listingId,
|
||||
is_active: true,
|
||||
},
|
||||
orderBy: [
|
||||
{ bid_amount: "desc" },
|
||||
{ created_at: "desc" },
|
||||
],
|
||||
orderBy: [{ bid_amount: "desc" }, { created_at: "desc" }],
|
||||
});
|
||||
if (!bid) return null;
|
||||
return {
|
||||
...bid,
|
||||
bid_amount: Number(bid.bid_amount),
|
||||
bid_type: (bid.bid_type as "pix" | "tibia_coins" | "gold_coins" | null),
|
||||
bid_type: bid.bid_type as "pix" | "tibia_coins" | "gold_coins" | null,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -110,7 +104,6 @@ export async function getBidById(bidId: number): Promise<Bid | null> {
|
||||
return {
|
||||
...bid,
|
||||
bid_amount: Number(bid.bid_amount),
|
||||
bid_type: (bid.bid_type as "pix" | "tibia_coins" | "gold_coins" | null),
|
||||
bid_type: bid.bid_type as "pix" | "tibia_coins" | "gold_coins" | null,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ export interface UpdateCharacterData {
|
||||
*/
|
||||
export async function createCharacter(
|
||||
userId: number,
|
||||
data: CreateCharacterData
|
||||
data: CreateCharacterData,
|
||||
): Promise<Character> {
|
||||
return await prisma.$transaction(async (tx) => {
|
||||
// Se este personagem será main, desmarcar outros
|
||||
@@ -62,25 +62,22 @@ export async function createCharacter(
|
||||
export async function getUserCharacters(userId: number): Promise<Character[]> {
|
||||
return await prisma.userCharacter.findMany({
|
||||
where: { user_id: userId },
|
||||
orderBy: [
|
||||
{ is_main: "desc" },
|
||||
{ created_at: "asc" },
|
||||
],
|
||||
orderBy: [{ is_main: "desc" }, { created_at: "asc" }],
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Busca personagem principal de um usuário
|
||||
*/
|
||||
export async function getMainCharacter(userId: number): Promise<Character | null> {
|
||||
export async function getMainCharacter(
|
||||
userId: number,
|
||||
): Promise<Character | null> {
|
||||
return await prisma.userCharacter.findFirst({
|
||||
where: {
|
||||
user_id: userId,
|
||||
is_main: true,
|
||||
},
|
||||
orderBy: [
|
||||
{ created_at: "asc" },
|
||||
],
|
||||
orderBy: [{ created_at: "asc" }],
|
||||
});
|
||||
}
|
||||
|
||||
@@ -89,17 +86,14 @@ export async function getMainCharacter(userId: number): Promise<Character | null
|
||||
*/
|
||||
export async function getMainCharacterByUserId(
|
||||
userId: number,
|
||||
serverName: string
|
||||
serverName: string,
|
||||
): Promise<Character | null> {
|
||||
return await prisma.userCharacter.findFirst({
|
||||
where: {
|
||||
user_id: userId,
|
||||
server_name: serverName,
|
||||
},
|
||||
orderBy: [
|
||||
{ is_main: "desc" },
|
||||
{ created_at: "asc" },
|
||||
],
|
||||
orderBy: [{ is_main: "desc" }, { created_at: "asc" }],
|
||||
});
|
||||
}
|
||||
|
||||
@@ -107,7 +101,7 @@ export async function getMainCharacterByUserId(
|
||||
* Busca personagem por ID
|
||||
*/
|
||||
export async function getCharacterById(
|
||||
characterId: number
|
||||
characterId: number,
|
||||
): Promise<Character | null> {
|
||||
return await prisma.userCharacter.findUnique({
|
||||
where: { character_id: characterId },
|
||||
@@ -121,7 +115,7 @@ export async function getCharacterById(
|
||||
export async function updateCharacter(
|
||||
characterId: number,
|
||||
userId: number,
|
||||
data: UpdateCharacterData
|
||||
data: UpdateCharacterData,
|
||||
): Promise<Character> {
|
||||
// Verificar ownership primeiro
|
||||
const existing = await getCharacterById(characterId);
|
||||
@@ -147,8 +141,10 @@ export async function updateCharacter(
|
||||
|
||||
// Atualizar personagem
|
||||
const updateData: any = {};
|
||||
if (data.character_name !== undefined) updateData.character_name = data.character_name;
|
||||
if (data.server_name !== undefined) updateData.server_name = data.server_name;
|
||||
if (data.character_name !== undefined)
|
||||
updateData.character_name = data.character_name;
|
||||
if (data.server_name !== undefined)
|
||||
updateData.server_name = data.server_name;
|
||||
if (data.is_main !== undefined) updateData.is_main = data.is_main;
|
||||
|
||||
if (Object.keys(updateData).length === 0) {
|
||||
@@ -168,7 +164,7 @@ export async function updateCharacter(
|
||||
*/
|
||||
export async function deleteCharacter(
|
||||
characterId: number,
|
||||
userId: number
|
||||
userId: number,
|
||||
): Promise<void> {
|
||||
// Verificar ownership
|
||||
const existing = await getCharacterById(characterId);
|
||||
@@ -184,4 +180,3 @@ export async function deleteCharacter(
|
||||
where: { character_id: characterId },
|
||||
});
|
||||
}
|
||||
|
||||
@@ -7,7 +7,10 @@ import { prisma } from "../app";
|
||||
/**
|
||||
* Verifica se um anúncio está favoritado por um usuário
|
||||
*/
|
||||
export async function isFavorite(userId: number, listingId: number): Promise<boolean> {
|
||||
export async function isFavorite(
|
||||
userId: number,
|
||||
listingId: number,
|
||||
): Promise<boolean> {
|
||||
const favorite = await prisma.userFavorite.findUnique({
|
||||
where: {
|
||||
user_id_ad_id: {
|
||||
@@ -23,7 +26,10 @@ export async function isFavorite(userId: number, listingId: number): Promise<boo
|
||||
/**
|
||||
* Favorita um anúncio
|
||||
*/
|
||||
export async function addFavorite(userId: number, listingId: number): Promise<void> {
|
||||
export async function addFavorite(
|
||||
userId: number,
|
||||
listingId: number,
|
||||
): Promise<void> {
|
||||
await prisma.userFavorite.upsert({
|
||||
where: {
|
||||
user_id_ad_id: {
|
||||
@@ -42,23 +48,31 @@ export async function addFavorite(userId: number, listingId: number): Promise<vo
|
||||
/**
|
||||
* Remove favorito de um anúncio
|
||||
*/
|
||||
export async function removeFavorite(userId: number, listingId: number): Promise<void> {
|
||||
await prisma.userFavorite.delete({
|
||||
where: {
|
||||
user_id_ad_id: {
|
||||
user_id: userId,
|
||||
ad_id: listingId,
|
||||
export async function removeFavorite(
|
||||
userId: number,
|
||||
listingId: number,
|
||||
): Promise<void> {
|
||||
await prisma.userFavorite
|
||||
.delete({
|
||||
where: {
|
||||
user_id_ad_id: {
|
||||
user_id: userId,
|
||||
ad_id: listingId,
|
||||
},
|
||||
},
|
||||
},
|
||||
}).catch(() => {
|
||||
// Ignorar erro se não existir (idempotente)
|
||||
});
|
||||
})
|
||||
.catch(() => {
|
||||
// Ignorar erro se não existir (idempotente)
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle favorite (favorita se não estiver, desfavorita se estiver)
|
||||
*/
|
||||
export async function toggleFavorite(userId: number, listingId: number): Promise<boolean> {
|
||||
export async function toggleFavorite(
|
||||
userId: number,
|
||||
listingId: number,
|
||||
): Promise<boolean> {
|
||||
const isFavorited = await isFavorite(userId, listingId);
|
||||
|
||||
if (isFavorited) {
|
||||
@@ -69,4 +83,3 @@ export async function toggleFavorite(userId: number, listingId: number): Promise
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
+75
-35
@@ -4,9 +4,9 @@
|
||||
*/
|
||||
|
||||
import { Prisma } from "@prisma/client";
|
||||
import { prisma } from "../app";
|
||||
import { ensureItemsSeeded } from "../../items/import-from-sqlite";
|
||||
import { secureLogger } from "../../secure-logger";
|
||||
import { prisma } from "../app";
|
||||
|
||||
export type ItemSort =
|
||||
| "name"
|
||||
@@ -96,13 +96,14 @@ function buildOrder(sort: ItemSort): Prisma.ItemOrderByWithRelationInput[] {
|
||||
return [{ category: "asc" }, { item_name: "asc" }];
|
||||
case "name":
|
||||
return [{ item_name: "asc" }];
|
||||
case "updated-desc":
|
||||
default:
|
||||
return [{ updated_at: "desc" }];
|
||||
}
|
||||
}
|
||||
|
||||
function parseMetadata(metadata: Prisma.JsonValue | null | undefined): Record<string, any> {
|
||||
function parseMetadata(
|
||||
metadata: Prisma.JsonValue | null | undefined,
|
||||
): Record<string, any> {
|
||||
if (!metadata || typeof metadata !== "object" || Array.isArray(metadata)) {
|
||||
return {};
|
||||
}
|
||||
@@ -142,8 +143,14 @@ function extractTags(meta: Record<string, any>): string[] {
|
||||
}
|
||||
|
||||
function buildItem(raw: any): Item {
|
||||
const createdAt = raw.created_at instanceof Date ? raw.created_at.toISOString() : raw.created_at;
|
||||
const updatedAt = raw.updated_at instanceof Date ? raw.updated_at.toISOString() : raw.updated_at;
|
||||
const createdAt =
|
||||
raw.created_at instanceof Date
|
||||
? raw.created_at.toISOString()
|
||||
: raw.created_at;
|
||||
const updatedAt =
|
||||
raw.updated_at instanceof Date
|
||||
? raw.updated_at.toISOString()
|
||||
: raw.updated_at;
|
||||
|
||||
return {
|
||||
...raw,
|
||||
@@ -158,7 +165,10 @@ function buildItemWithMeta(raw: any): ItemWithMeta {
|
||||
const base = buildItem(raw);
|
||||
const servers = extractServers(base.metadata);
|
||||
const tags = extractTags(base.metadata);
|
||||
const description = typeof base.metadata.description === "string" ? base.metadata.description : null;
|
||||
const description =
|
||||
typeof base.metadata.description === "string"
|
||||
? base.metadata.description
|
||||
: null;
|
||||
|
||||
return {
|
||||
...base,
|
||||
@@ -177,7 +187,12 @@ function toDecimal(value: number | null | undefined): Prisma.Decimal | null {
|
||||
|
||||
function buildMetadata(
|
||||
existing: Record<string, any>,
|
||||
overrides: { servers?: string[]; tags?: string[]; description?: string | null; metadata?: Record<string, any> }
|
||||
overrides: {
|
||||
servers?: string[];
|
||||
tags?: string[];
|
||||
description?: string | null;
|
||||
metadata?: Record<string, any>;
|
||||
},
|
||||
): Record<string, any> {
|
||||
const merged = { ...existing, ...(overrides.metadata || {}) };
|
||||
|
||||
@@ -220,10 +235,15 @@ const FORBIDDEN_VALUE_KEYS = new Set<string>([
|
||||
* Verifica recursivamente se há campos proibidos relacionados a valores/preços.
|
||||
* Lança erro se encontrar.
|
||||
*/
|
||||
export function assertNoValueFieldsInMetadata(meta: Record<string, any> | undefined | null, context?: string): void {
|
||||
export function assertNoValueFieldsInMetadata(
|
||||
meta: Record<string, any> | undefined | null,
|
||||
context?: string,
|
||||
): void {
|
||||
if (!meta || typeof meta !== "object") return;
|
||||
|
||||
const stack: Array<{ obj: Record<string, any>; path: string }> = [{ obj: meta as Record<string, any>, path: "metadata" }];
|
||||
const stack: Array<{ obj: Record<string, any>; path: string }> = [
|
||||
{ obj: meta as Record<string, any>, path: "metadata" },
|
||||
];
|
||||
while (stack.length) {
|
||||
const { obj, path } = stack.pop()!;
|
||||
for (const key of Object.keys(obj)) {
|
||||
@@ -264,7 +284,10 @@ export async function getItemByName(itemName: string): Promise<Item | null> {
|
||||
return buildItem(item);
|
||||
}
|
||||
|
||||
export async function getItemBySource(sourceTableName: string, sourceTableId: number): Promise<Item | null> {
|
||||
export async function getItemBySource(
|
||||
sourceTableName: string,
|
||||
sourceTableId: number,
|
||||
): Promise<Item | null> {
|
||||
const item = await prisma.item.findUnique({
|
||||
where: {
|
||||
source_table_name_source_table_id: {
|
||||
@@ -279,15 +302,10 @@ export async function getItemBySource(sourceTableName: string, sourceTableId: nu
|
||||
return buildItem(item);
|
||||
}
|
||||
|
||||
export async function searchAdminItems(params: AdminSearchParams = {}): Promise<AdminItemsResult> {
|
||||
const {
|
||||
q = "",
|
||||
category,
|
||||
rarity,
|
||||
server,
|
||||
page = 1,
|
||||
pageSize = 50,
|
||||
} = params;
|
||||
export async function searchAdminItems(
|
||||
params: AdminSearchParams = {},
|
||||
): Promise<AdminItemsResult> {
|
||||
const { q = "", category, rarity, server, page = 1, pageSize = 50 } = params;
|
||||
|
||||
await ensureItemsSeeded();
|
||||
|
||||
@@ -355,7 +373,9 @@ export async function searchAdminItems(params: AdminSearchParams = {}): Promise<
|
||||
total,
|
||||
page: safePage,
|
||||
pageSize: safePageSize,
|
||||
servers: Array.from(serversSet).sort((a, b) => a.localeCompare(b, "pt-BR", { sensitivity: "base" })),
|
||||
servers: Array.from(serversSet).sort((a, b) =>
|
||||
a.localeCompare(b, "pt-BR", { sensitivity: "base" }),
|
||||
),
|
||||
categories: categoryRows
|
||||
.map((row) => row.category)
|
||||
.filter((c): c is string => Boolean(c))
|
||||
@@ -363,12 +383,16 @@ export async function searchAdminItems(params: AdminSearchParams = {}): Promise<
|
||||
};
|
||||
}
|
||||
|
||||
export async function searchItems(params: SearchItemsParams = {}): Promise<{ items: ItemWithMeta[]; total: number }> {
|
||||
export async function searchItems(
|
||||
params: SearchItemsParams = {},
|
||||
): Promise<{ items: ItemWithMeta[]; total: number }> {
|
||||
const { items, total } = await searchAdminItems(params);
|
||||
return { items, total };
|
||||
}
|
||||
|
||||
export async function createAdminItem(input: ItemUpsertInput): Promise<ItemWithMeta> {
|
||||
export async function createAdminItem(
|
||||
input: ItemUpsertInput,
|
||||
): Promise<ItemWithMeta> {
|
||||
const now = Date.now();
|
||||
// Validação: impedir qualquer campo de valor em metadata (preço etc.)
|
||||
assertNoValueFieldsInMetadata(input.metadata, "createAdminItem");
|
||||
@@ -384,19 +408,25 @@ export async function createAdminItem(input: ItemUpsertInput): Promise<ItemWithM
|
||||
weight: toDecimal(input.weight),
|
||||
slots: input.slots ?? null,
|
||||
skill: input.skill ?? null,
|
||||
metadata: buildMetadata({}, {
|
||||
servers: input.servers ?? [],
|
||||
tags: input.tags ?? [],
|
||||
description: input.description ?? null,
|
||||
metadata: input.metadata ?? {},
|
||||
}),
|
||||
metadata: buildMetadata(
|
||||
{},
|
||||
{
|
||||
servers: input.servers ?? [],
|
||||
tags: input.tags ?? [],
|
||||
description: input.description ?? null,
|
||||
metadata: input.metadata ?? {},
|
||||
},
|
||||
),
|
||||
};
|
||||
|
||||
const created = await prisma.item.create({ data });
|
||||
return buildItemWithMeta(created);
|
||||
}
|
||||
|
||||
export async function updateAdminItem(itemId: number, input: ItemUpsertInput): Promise<ItemWithMeta> {
|
||||
export async function updateAdminItem(
|
||||
itemId: number,
|
||||
input: ItemUpsertInput,
|
||||
): Promise<ItemWithMeta> {
|
||||
const existing = await prisma.item.findUnique({ where: { item_id: itemId } });
|
||||
if (!existing) {
|
||||
throw new Error("Item não encontrado");
|
||||
@@ -408,24 +438,34 @@ export async function updateAdminItem(itemId: number, input: ItemUpsertInput): P
|
||||
const metadata = buildMetadata(existingMeta, {
|
||||
servers: input.servers ?? extractServers(existingMeta),
|
||||
tags: input.tags ?? extractTags(existingMeta),
|
||||
description: input.description !== undefined ? input.description : existingMeta.description,
|
||||
description:
|
||||
input.description !== undefined
|
||||
? input.description
|
||||
: existingMeta.description,
|
||||
metadata: input.metadata,
|
||||
});
|
||||
|
||||
const data: Prisma.ItemUpdateInput = {
|
||||
item_name: input.item_name ?? existing.item_name,
|
||||
category: input.category ?? existing.category,
|
||||
image_local_path: input.image_local_path !== undefined ? input.image_local_path : existing.image_local_path,
|
||||
image_url: input.image_url !== undefined ? input.image_url : existing.image_url,
|
||||
image_local_path:
|
||||
input.image_local_path !== undefined
|
||||
? input.image_local_path
|
||||
: existing.image_local_path,
|
||||
image_url:
|
||||
input.image_url !== undefined ? input.image_url : existing.image_url,
|
||||
rarity: input.rarity !== undefined ? input.rarity : existing.rarity,
|
||||
level: input.level !== undefined ? input.level : existing.level,
|
||||
weight: input.weight !== undefined ? toDecimal(input.weight) : existing.weight,
|
||||
weight:
|
||||
input.weight !== undefined ? toDecimal(input.weight) : existing.weight,
|
||||
slots: input.slots !== undefined ? input.slots : existing.slots,
|
||||
skill: input.skill !== undefined ? input.skill : existing.skill,
|
||||
metadata,
|
||||
};
|
||||
|
||||
const updated = await prisma.item.update({ where: { item_id: itemId }, data });
|
||||
const updated = await prisma.item.update({
|
||||
where: { item_id: itemId },
|
||||
data,
|
||||
});
|
||||
return buildItemWithMeta(updated);
|
||||
}
|
||||
|
||||
+188
-68
@@ -3,15 +3,20 @@
|
||||
* Migrado de lib/db/queries/listings.ts
|
||||
*/
|
||||
|
||||
import type { Prisma } from "@prisma/client";
|
||||
import { prisma } from "../app";
|
||||
import { Prisma } from "@prisma/client";
|
||||
import {
|
||||
getHighestCustomOfferForListing,
|
||||
getHighestCustomOffersForListings,
|
||||
} from "./purchase-intentions";
|
||||
|
||||
// Re-exportar tipos para compatibilidade
|
||||
export type ListingStatus = "active" | "sold" | "cancelled" | "in_escrow" | "expired";
|
||||
export type ListingStatus =
|
||||
| "active"
|
||||
| "sold"
|
||||
| "cancelled"
|
||||
| "in_escrow"
|
||||
| "expired";
|
||||
|
||||
export interface Listing {
|
||||
listing_id: number;
|
||||
@@ -88,16 +93,19 @@ export interface ListingFilters {
|
||||
search?: string;
|
||||
page?: number;
|
||||
pageSize?: number;
|
||||
sortBy?: "created_at" | "price_pix" | "price_tibia_coins" | "price_gold_coins" | "views_count";
|
||||
sortBy?:
|
||||
| "created_at"
|
||||
| "price_pix"
|
||||
| "price_tibia_coins"
|
||||
| "price_gold_coins"
|
||||
| "views_count";
|
||||
sortOrder?: "ASC" | "DESC";
|
||||
}
|
||||
|
||||
/**
|
||||
* Cria um novo anúncio
|
||||
*/
|
||||
export async function createListing(
|
||||
data: CreateListingData
|
||||
): Promise<Listing> {
|
||||
export async function createListing(data: CreateListingData): Promise<Listing> {
|
||||
// Calcular incremento mÃnimo se for leilão e não foi fornecido
|
||||
let minBidIncrement = data.min_bid_increment;
|
||||
if (data.is_auction && !minBidIncrement) {
|
||||
@@ -114,7 +122,9 @@ export async function createListing(
|
||||
server_name: data.server_name,
|
||||
price_pix: data.price_pix || null,
|
||||
price_tibia_coins: data.price_tibia_coins || null,
|
||||
price_gold_coins: data.price_gold_coins ? BigInt(data.price_gold_coins) : null,
|
||||
price_gold_coins: data.price_gold_coins
|
||||
? BigInt(data.price_gold_coins)
|
||||
: null,
|
||||
is_auction: data.is_auction || false,
|
||||
auction_ends_at: data.auction_ends_at || null,
|
||||
description: data.description || null,
|
||||
@@ -122,9 +132,14 @@ export async function createListing(
|
||||
trade_item_name: data.trade_item_name || null,
|
||||
trade_item_quantity: data.trade_item_quantity || null,
|
||||
min_bid_increment: minBidIncrement || null,
|
||||
initial_price_pix: data.is_auction ? (data.price_pix || null) : null,
|
||||
initial_price_tibia_coins: data.is_auction ? (data.price_tibia_coins || null) : null,
|
||||
initial_price_gold_coins: data.is_auction && data.price_gold_coins ? BigInt(data.price_gold_coins) : null,
|
||||
initial_price_pix: data.is_auction ? data.price_pix || null : null,
|
||||
initial_price_tibia_coins: data.is_auction
|
||||
? data.price_tibia_coins || null
|
||||
: null,
|
||||
initial_price_gold_coins:
|
||||
data.is_auction && data.price_gold_coins
|
||||
? BigInt(data.price_gold_coins)
|
||||
: null,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -141,11 +156,21 @@ export async function createListing(
|
||||
return {
|
||||
...listing,
|
||||
price_pix: listing.price_pix ? Number(listing.price_pix) : null,
|
||||
current_bid_amount: listing.current_bid_amount ? Number(listing.current_bid_amount) : null,
|
||||
min_bid_increment: listing.min_bid_increment ? Number(listing.min_bid_increment) : null,
|
||||
initial_price_pix: listing.initial_price_pix ? Number(listing.initial_price_pix) : null,
|
||||
price_gold_coins: listing.price_gold_coins ? Number(listing.price_gold_coins) : null,
|
||||
initial_price_gold_coins: listing.initial_price_gold_coins ? Number(listing.initial_price_gold_coins) : null,
|
||||
current_bid_amount: listing.current_bid_amount
|
||||
? Number(listing.current_bid_amount)
|
||||
: null,
|
||||
min_bid_increment: listing.min_bid_increment
|
||||
? Number(listing.min_bid_increment)
|
||||
: null,
|
||||
initial_price_pix: listing.initial_price_pix
|
||||
? Number(listing.initial_price_pix)
|
||||
: null,
|
||||
price_gold_coins: listing.price_gold_coins
|
||||
? Number(listing.price_gold_coins)
|
||||
: null,
|
||||
initial_price_gold_coins: listing.initial_price_gold_coins
|
||||
? Number(listing.initial_price_gold_coins)
|
||||
: null,
|
||||
seller_character_name: character?.character_name || null,
|
||||
highest_custom_offer_amount: null,
|
||||
highest_custom_offer_currency: null,
|
||||
@@ -155,7 +180,9 @@ export async function createListing(
|
||||
/**
|
||||
* Busca anúncio por ID
|
||||
*/
|
||||
export async function getListingById(listingId: number): Promise<Listing | null> {
|
||||
export async function getListingById(
|
||||
listingId: number,
|
||||
): Promise<Listing | null> {
|
||||
const listing = await prisma.listing.findUnique({
|
||||
where: { listing_id: listingId },
|
||||
});
|
||||
@@ -179,11 +206,21 @@ export async function getListingById(listingId: number): Promise<Listing | null>
|
||||
return {
|
||||
...listing,
|
||||
price_pix: listing.price_pix ? Number(listing.price_pix) : null,
|
||||
current_bid_amount: listing.current_bid_amount ? Number(listing.current_bid_amount) : null,
|
||||
min_bid_increment: listing.min_bid_increment ? Number(listing.min_bid_increment) : null,
|
||||
initial_price_pix: listing.initial_price_pix ? Number(listing.initial_price_pix) : null,
|
||||
price_gold_coins: listing.price_gold_coins ? Number(listing.price_gold_coins) : null,
|
||||
initial_price_gold_coins: listing.initial_price_gold_coins ? Number(listing.initial_price_gold_coins) : null,
|
||||
current_bid_amount: listing.current_bid_amount
|
||||
? Number(listing.current_bid_amount)
|
||||
: null,
|
||||
min_bid_increment: listing.min_bid_increment
|
||||
? Number(listing.min_bid_increment)
|
||||
: null,
|
||||
initial_price_pix: listing.initial_price_pix
|
||||
? Number(listing.initial_price_pix)
|
||||
: null,
|
||||
price_gold_coins: listing.price_gold_coins
|
||||
? Number(listing.price_gold_coins)
|
||||
: null,
|
||||
initial_price_gold_coins: listing.initial_price_gold_coins
|
||||
? Number(listing.initial_price_gold_coins)
|
||||
: null,
|
||||
seller_character_name: character?.character_name || null,
|
||||
highest_custom_offer_amount: highestOffer?.amount ?? null,
|
||||
highest_custom_offer_currency: highestOffer?.currency ?? null,
|
||||
@@ -194,7 +231,7 @@ export async function getListingById(listingId: number): Promise<Listing | null>
|
||||
* Lista anúncios com filtros e paginação
|
||||
*/
|
||||
export async function getListings(
|
||||
filters: ListingFilters = {}
|
||||
filters: ListingFilters = {},
|
||||
): Promise<{ listings: Listing[]; total: number }> {
|
||||
const {
|
||||
status,
|
||||
@@ -239,7 +276,13 @@ export async function getListings(
|
||||
}
|
||||
|
||||
// Validar sortBy
|
||||
const validSortColumns = ["created_at", "price_pix", "price_tibia_coins", "price_gold_coins", "views_count"];
|
||||
const validSortColumns = [
|
||||
"created_at",
|
||||
"price_pix",
|
||||
"price_tibia_coins",
|
||||
"price_gold_coins",
|
||||
"views_count",
|
||||
];
|
||||
const sortColumn = validSortColumns.includes(sortBy) ? sortBy : "created_at";
|
||||
const orderBy: Prisma.ListingOrderByWithRelationInput = {
|
||||
[sortColumn]: sortOrder.toLowerCase() as "asc" | "desc",
|
||||
@@ -258,7 +301,7 @@ export async function getListings(
|
||||
|
||||
// Buscar character names para cada listing (otimizado com uma query por seller_id + server_name)
|
||||
const sellerServerPairs = new Set(
|
||||
listings.map((l) => `${l.seller_id}:${l.server_name}`)
|
||||
listings.map((l) => `${l.seller_id}:${l.server_name}`),
|
||||
);
|
||||
|
||||
const charactersMap = new Map<string, string>();
|
||||
@@ -277,7 +320,7 @@ export async function getListings(
|
||||
}
|
||||
|
||||
const highestOffers = await getHighestCustomOffersForListings(
|
||||
listings.map((listing) => listing.listing_id)
|
||||
listings.map((listing) => listing.listing_id),
|
||||
);
|
||||
|
||||
// Converter todos os campos Decimal e BigInt para números JavaScript
|
||||
@@ -287,11 +330,21 @@ export async function getListings(
|
||||
return {
|
||||
...listing,
|
||||
price_pix: listing.price_pix ? Number(listing.price_pix) : null,
|
||||
current_bid_amount: listing.current_bid_amount ? Number(listing.current_bid_amount) : null,
|
||||
min_bid_increment: listing.min_bid_increment ? Number(listing.min_bid_increment) : null,
|
||||
initial_price_pix: listing.initial_price_pix ? Number(listing.initial_price_pix) : null,
|
||||
price_gold_coins: listing.price_gold_coins ? Number(listing.price_gold_coins) : null,
|
||||
initial_price_gold_coins: listing.initial_price_gold_coins ? Number(listing.initial_price_gold_coins) : null,
|
||||
current_bid_amount: listing.current_bid_amount
|
||||
? Number(listing.current_bid_amount)
|
||||
: null,
|
||||
min_bid_increment: listing.min_bid_increment
|
||||
? Number(listing.min_bid_increment)
|
||||
: null,
|
||||
initial_price_pix: listing.initial_price_pix
|
||||
? Number(listing.initial_price_pix)
|
||||
: null,
|
||||
price_gold_coins: listing.price_gold_coins
|
||||
? Number(listing.price_gold_coins)
|
||||
: null,
|
||||
initial_price_gold_coins: listing.initial_price_gold_coins
|
||||
? Number(listing.initial_price_gold_coins)
|
||||
: null,
|
||||
seller_character_name: charactersMap.get(key) || null,
|
||||
highest_custom_offer_amount: highestOffer?.amount ?? null,
|
||||
highest_custom_offer_currency: highestOffer?.currency ?? null,
|
||||
@@ -309,17 +362,21 @@ export async function getListings(
|
||||
*/
|
||||
export async function updateListing(
|
||||
listingId: number,
|
||||
data: UpdateListingData
|
||||
data: UpdateListingData,
|
||||
): Promise<Listing | null> {
|
||||
const updateData: Prisma.ListingUpdateInput = {};
|
||||
|
||||
if (data.quantity !== undefined) updateData.quantity = data.quantity;
|
||||
if (data.price_pix !== undefined) updateData.price_pix = data.price_pix;
|
||||
if (data.price_tibia_coins !== undefined) updateData.price_tibia_coins = data.price_tibia_coins;
|
||||
if (data.price_gold_coins !== undefined) updateData.price_gold_coins = BigInt(data.price_gold_coins);
|
||||
if (data.price_tibia_coins !== undefined)
|
||||
updateData.price_tibia_coins = data.price_tibia_coins;
|
||||
if (data.price_gold_coins !== undefined)
|
||||
updateData.price_gold_coins = BigInt(data.price_gold_coins);
|
||||
if (data.description !== undefined) updateData.description = data.description;
|
||||
if (data.listing_status !== undefined) updateData.listing_status = data.listing_status;
|
||||
if (data.current_bid_amount !== undefined) updateData.current_bid_amount = data.current_bid_amount;
|
||||
if (data.listing_status !== undefined)
|
||||
updateData.listing_status = data.listing_status;
|
||||
if (data.current_bid_amount !== undefined)
|
||||
updateData.current_bid_amount = data.current_bid_amount;
|
||||
if (data.current_bidder_id !== undefined) {
|
||||
updateData.current_bidder_id = data.current_bidder_id;
|
||||
}
|
||||
@@ -338,17 +395,29 @@ export async function updateListing(
|
||||
},
|
||||
});
|
||||
|
||||
const highestOffer = await getHighestCustomOfferForListing(listing.listing_id);
|
||||
const highestOffer = await getHighestCustomOfferForListing(
|
||||
listing.listing_id,
|
||||
);
|
||||
|
||||
// Converter todos os campos Decimal e BigInt para números JavaScript
|
||||
return {
|
||||
...listing,
|
||||
price_pix: listing.price_pix ? Number(listing.price_pix) : null,
|
||||
current_bid_amount: listing.current_bid_amount ? Number(listing.current_bid_amount) : null,
|
||||
min_bid_increment: listing.min_bid_increment ? Number(listing.min_bid_increment) : null,
|
||||
initial_price_pix: listing.initial_price_pix ? Number(listing.initial_price_pix) : null,
|
||||
price_gold_coins: listing.price_gold_coins ? Number(listing.price_gold_coins) : null,
|
||||
initial_price_gold_coins: listing.initial_price_gold_coins ? Number(listing.initial_price_gold_coins) : null,
|
||||
current_bid_amount: listing.current_bid_amount
|
||||
? Number(listing.current_bid_amount)
|
||||
: null,
|
||||
min_bid_increment: listing.min_bid_increment
|
||||
? Number(listing.min_bid_increment)
|
||||
: null,
|
||||
initial_price_pix: listing.initial_price_pix
|
||||
? Number(listing.initial_price_pix)
|
||||
: null,
|
||||
price_gold_coins: listing.price_gold_coins
|
||||
? Number(listing.price_gold_coins)
|
||||
: null,
|
||||
initial_price_gold_coins: listing.initial_price_gold_coins
|
||||
? Number(listing.initial_price_gold_coins)
|
||||
: null,
|
||||
seller_character_name: character?.character_name || null,
|
||||
highest_custom_offer_amount: highestOffer?.amount ?? null,
|
||||
highest_custom_offer_currency: highestOffer?.currency ?? null,
|
||||
@@ -364,7 +433,7 @@ export async function deleteListing(listingId: number): Promise<boolean> {
|
||||
where: { listing_id: listingId },
|
||||
});
|
||||
return true;
|
||||
} catch (error) {
|
||||
} catch (_error) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -388,7 +457,7 @@ export async function incrementViews(listingId: number): Promise<void> {
|
||||
*/
|
||||
export async function getListingsBySeller(
|
||||
sellerId: number,
|
||||
filters: { status?: ListingStatus; page?: number; pageSize?: number } = {}
|
||||
filters: { status?: ListingStatus; page?: number; pageSize?: number } = {},
|
||||
): Promise<{ listings: Listing[]; total: number }> {
|
||||
return getListings({
|
||||
seller_id: sellerId,
|
||||
@@ -402,7 +471,7 @@ export async function getListingsBySeller(
|
||||
* Lista apenas anúncios ativos (incluindo vendidos nas últimas 1 hora)
|
||||
*/
|
||||
export async function getActiveListings(
|
||||
filters: Omit<ListingFilters, "status"> = {}
|
||||
filters: Omit<ListingFilters, "status"> = {},
|
||||
): Promise<{ listings: Listing[]; total: number }> {
|
||||
const {
|
||||
server_name,
|
||||
@@ -449,8 +518,18 @@ export async function getActiveListings(
|
||||
if (search) {
|
||||
const searchCondition = {
|
||||
OR: [
|
||||
{ item_name: { contains: search, mode: "insensitive" as Prisma.QueryMode } },
|
||||
{ description: { contains: search, mode: "insensitive" as Prisma.QueryMode } },
|
||||
{
|
||||
item_name: {
|
||||
contains: search,
|
||||
mode: "insensitive" as Prisma.QueryMode,
|
||||
},
|
||||
},
|
||||
{
|
||||
description: {
|
||||
contains: search,
|
||||
mode: "insensitive" as Prisma.QueryMode,
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
// Combinar com a condição OR existente
|
||||
@@ -472,7 +551,13 @@ export async function getActiveListings(
|
||||
}
|
||||
|
||||
// Validar sortBy
|
||||
const validSortColumns = ["created_at", "price_pix", "price_tibia_coins", "price_gold_coins", "views_count"];
|
||||
const validSortColumns = [
|
||||
"created_at",
|
||||
"price_pix",
|
||||
"price_tibia_coins",
|
||||
"price_gold_coins",
|
||||
"views_count",
|
||||
];
|
||||
const sortColumn = validSortColumns.includes(sortBy) ? sortBy : "created_at";
|
||||
const orderBy: Prisma.ListingOrderByWithRelationInput = {
|
||||
[sortColumn]: sortOrder.toLowerCase() as "asc" | "desc",
|
||||
@@ -491,7 +576,7 @@ export async function getActiveListings(
|
||||
|
||||
// Buscar character names para cada listing
|
||||
const sellerServerPairs = new Set(
|
||||
listings.map((l) => `${l.seller_id}:${l.server_name}`)
|
||||
listings.map((l) => `${l.seller_id}:${l.server_name}`),
|
||||
);
|
||||
|
||||
const charactersMap = new Map<string, string>();
|
||||
@@ -510,7 +595,7 @@ export async function getActiveListings(
|
||||
}
|
||||
|
||||
const highestOffers = await getHighestCustomOffersForListings(
|
||||
listings.map((listing) => listing.listing_id)
|
||||
listings.map((listing) => listing.listing_id),
|
||||
);
|
||||
|
||||
// Converter todos os campos Decimal e BigInt para números JavaScript
|
||||
@@ -520,11 +605,21 @@ export async function getActiveListings(
|
||||
return {
|
||||
...listing,
|
||||
price_pix: listing.price_pix ? Number(listing.price_pix) : null,
|
||||
current_bid_amount: listing.current_bid_amount ? Number(listing.current_bid_amount) : null,
|
||||
min_bid_increment: listing.min_bid_increment ? Number(listing.min_bid_increment) : null,
|
||||
initial_price_pix: listing.initial_price_pix ? Number(listing.initial_price_pix) : null,
|
||||
price_gold_coins: listing.price_gold_coins ? Number(listing.price_gold_coins) : null,
|
||||
initial_price_gold_coins: listing.initial_price_gold_coins ? Number(listing.initial_price_gold_coins) : null,
|
||||
current_bid_amount: listing.current_bid_amount
|
||||
? Number(listing.current_bid_amount)
|
||||
: null,
|
||||
min_bid_increment: listing.min_bid_increment
|
||||
? Number(listing.min_bid_increment)
|
||||
: null,
|
||||
initial_price_pix: listing.initial_price_pix
|
||||
? Number(listing.initial_price_pix)
|
||||
: null,
|
||||
price_gold_coins: listing.price_gold_coins
|
||||
? Number(listing.price_gold_coins)
|
||||
: null,
|
||||
initial_price_gold_coins: listing.initial_price_gold_coins
|
||||
? Number(listing.initial_price_gold_coins)
|
||||
: null,
|
||||
seller_character_name: charactersMap.get(key) || null,
|
||||
highest_custom_offer_amount: highestOffer?.amount ?? null,
|
||||
highest_custom_offer_currency: highestOffer?.currency ?? null,
|
||||
@@ -544,8 +639,13 @@ export async function placeBid(
|
||||
listingId: number,
|
||||
bidderId: number,
|
||||
amount: number,
|
||||
paymentMethod: "pix" | "tc" | "gp"
|
||||
): Promise<{ success: boolean; listing?: Listing; error?: string; previousBidderId?: number }> {
|
||||
_paymentMethod: "pix" | "tc" | "gp",
|
||||
): Promise<{
|
||||
success: boolean;
|
||||
listing?: Listing;
|
||||
error?: string;
|
||||
previousBidderId?: number;
|
||||
}> {
|
||||
// Usar transação para garantir atomicidade
|
||||
return await prisma.$transaction(async (tx) => {
|
||||
// Buscar listing atual
|
||||
@@ -566,16 +666,26 @@ export async function placeBid(
|
||||
}
|
||||
|
||||
if (listing.seller_id === bidderId) {
|
||||
return { success: false, error: "Você não pode dar lance no seu próprio anúncio" };
|
||||
return {
|
||||
success: false,
|
||||
error: "Você não pode dar lance no seu próprio anúncio",
|
||||
};
|
||||
}
|
||||
|
||||
// Validar valor mÃnimo
|
||||
const minIncrement = listing.min_bid_increment ? Number(listing.min_bid_increment) : 10;
|
||||
const currentBid = listing.current_bid_amount ? Number(listing.current_bid_amount) : 0;
|
||||
const minIncrement = listing.min_bid_increment
|
||||
? Number(listing.min_bid_increment)
|
||||
: 10;
|
||||
const currentBid = listing.current_bid_amount
|
||||
? Number(listing.current_bid_amount)
|
||||
: 0;
|
||||
const minBid = currentBid + minIncrement;
|
||||
|
||||
if (amount < minBid) {
|
||||
return { success: false, error: `O lance deve ser de pelo menos ${minBid.toFixed(2)}` };
|
||||
return {
|
||||
success: false,
|
||||
error: `O lance deve ser de pelo menos ${minBid.toFixed(2)}`,
|
||||
};
|
||||
}
|
||||
|
||||
const previousBidderId = listing.current_bidder_id;
|
||||
@@ -603,12 +713,24 @@ export async function placeBid(
|
||||
success: true,
|
||||
listing: {
|
||||
...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,
|
||||
highest_custom_offer_amount: null,
|
||||
highest_custom_offer_currency: null,
|
||||
@@ -617,5 +739,3 @@ export async function placeBid(
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,13 @@
|
||||
|
||||
import { prisma } from "../app";
|
||||
|
||||
export type NotificationType = "bid" | "sale" | "purchase" | "escrow" | "dispute" | "system";
|
||||
export type NotificationType =
|
||||
| "bid"
|
||||
| "sale"
|
||||
| "purchase"
|
||||
| "escrow"
|
||||
| "dispute"
|
||||
| "system";
|
||||
|
||||
export interface Notification {
|
||||
notification_id: number;
|
||||
@@ -33,7 +39,7 @@ export interface CreateNotificationData {
|
||||
* Cria uma nova notificação
|
||||
*/
|
||||
export async function createNotification(
|
||||
data: CreateNotificationData
|
||||
data: CreateNotificationData,
|
||||
): Promise<Notification> {
|
||||
const notification = await prisma.notification.create({
|
||||
data: {
|
||||
@@ -56,7 +62,7 @@ export async function createNotification(
|
||||
*/
|
||||
export async function getUserNotifications(
|
||||
userId: number,
|
||||
filters: { is_read?: boolean; type?: NotificationType; limit?: number } = {}
|
||||
filters: { is_read?: boolean; type?: NotificationType; limit?: number } = {},
|
||||
): Promise<Notification[]> {
|
||||
const { is_read, type, limit = 50 } = filters;
|
||||
|
||||
@@ -75,7 +81,7 @@ export async function getUserNotifications(
|
||||
orderBy: { created_at: "desc" },
|
||||
take: limit,
|
||||
});
|
||||
return notifications.map(notification => ({
|
||||
return notifications.map((notification) => ({
|
||||
...notification,
|
||||
notification_type: notification.notification_type as NotificationType,
|
||||
}));
|
||||
@@ -96,7 +102,10 @@ export async function getUnreadCount(userId: number): Promise<number> {
|
||||
/**
|
||||
* Marca notificação como lida
|
||||
*/
|
||||
export async function markAsRead(notificationId: number, userId: number): Promise<boolean> {
|
||||
export async function markAsRead(
|
||||
notificationId: number,
|
||||
userId: number,
|
||||
): Promise<boolean> {
|
||||
const result = await prisma.notification.updateMany({
|
||||
where: {
|
||||
notification_id: notificationId,
|
||||
@@ -130,7 +139,10 @@ export async function markAllAsRead(userId: number): Promise<number> {
|
||||
/**
|
||||
* Remove notificação
|
||||
*/
|
||||
export async function deleteNotification(notificationId: number, userId: number): Promise<boolean> {
|
||||
export async function deleteNotification(
|
||||
notificationId: number,
|
||||
userId: number,
|
||||
): Promise<boolean> {
|
||||
try {
|
||||
await prisma.notification.delete({
|
||||
where: {
|
||||
@@ -139,8 +151,7 @@ export async function deleteNotification(notificationId: number, userId: number)
|
||||
},
|
||||
});
|
||||
return true;
|
||||
} catch (error) {
|
||||
} catch (_error) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,10 +2,15 @@
|
||||
* Queries de Purchase Intentions usando Prisma
|
||||
*/
|
||||
|
||||
import { prisma } from "../app";
|
||||
import { Prisma } from "@prisma/client";
|
||||
import { prisma } from "../app";
|
||||
|
||||
export type PurchaseIntentionStatus = "pending" | "accepted" | "rejected" | "expired" | "completed";
|
||||
export type PurchaseIntentionStatus =
|
||||
| "pending"
|
||||
| "accepted"
|
||||
| "rejected"
|
||||
| "expired"
|
||||
| "completed";
|
||||
export type PaymentMethod = "pix" | "tibia_coins" | "gold_coins" | "trade";
|
||||
|
||||
export interface CreatePurchaseIntentionData {
|
||||
@@ -54,7 +59,8 @@ function serializePurchaseIntention(intention: any): PurchaseIntention {
|
||||
return {
|
||||
...intention,
|
||||
custom_offer_amount:
|
||||
intention.custom_offer_amount !== null && intention.custom_offer_amount !== undefined
|
||||
intention.custom_offer_amount !== null &&
|
||||
intention.custom_offer_amount !== undefined
|
||||
? Number(intention.custom_offer_amount)
|
||||
: null,
|
||||
custom_offer_currency: intention.custom_offer_currency ?? null,
|
||||
@@ -66,7 +72,7 @@ function serializePurchaseIntention(intention: any): PurchaseIntention {
|
||||
* Cria uma nova intenção de compra
|
||||
*/
|
||||
export async function createPurchaseIntention(
|
||||
data: CreatePurchaseIntentionData
|
||||
data: CreatePurchaseIntentionData,
|
||||
): Promise<PurchaseIntention> {
|
||||
const result = await prisma.purchaseIntention.create({
|
||||
data: {
|
||||
@@ -77,12 +83,14 @@ export async function createPurchaseIntention(
|
||||
expires_at: data.expires_at || null,
|
||||
status: "pending",
|
||||
custom_offer_amount:
|
||||
data.custom_offer_amount !== undefined && data.custom_offer_amount !== null
|
||||
data.custom_offer_amount !== undefined &&
|
||||
data.custom_offer_amount !== null
|
||||
? new Prisma.Decimal(data.custom_offer_amount)
|
||||
: null,
|
||||
custom_offer_currency: data.custom_offer_currency || null,
|
||||
custom_offer_created_at:
|
||||
data.custom_offer_amount !== undefined && data.custom_offer_amount !== null
|
||||
data.custom_offer_amount !== undefined &&
|
||||
data.custom_offer_amount !== null
|
||||
? new Date()
|
||||
: null,
|
||||
},
|
||||
@@ -112,7 +120,7 @@ export async function createPurchaseIntention(
|
||||
*/
|
||||
export async function getPurchaseIntentionsByListing(
|
||||
listingId: number,
|
||||
filters: { status?: PurchaseIntentionStatus; limit?: number } = {}
|
||||
filters: { status?: PurchaseIntentionStatus; limit?: number } = {},
|
||||
): Promise<PurchaseIntention[]> {
|
||||
const { status, limit = 50 } = filters;
|
||||
|
||||
@@ -151,7 +159,7 @@ export async function getPurchaseIntentionsByListing(
|
||||
*/
|
||||
export async function getPurchaseIntentionsByBuyer(
|
||||
buyerId: number,
|
||||
filters: { status?: PurchaseIntentionStatus; limit?: number } = {}
|
||||
filters: { status?: PurchaseIntentionStatus; limit?: number } = {},
|
||||
): Promise<PurchaseIntention[]> {
|
||||
const { status, limit = 50 } = filters;
|
||||
|
||||
@@ -189,7 +197,7 @@ export async function getPurchaseIntentionsByBuyer(
|
||||
* Busca uma intenção específica por ID
|
||||
*/
|
||||
export async function getPurchaseIntentionById(
|
||||
intentionId: number
|
||||
intentionId: number,
|
||||
): Promise<PurchaseIntention | null> {
|
||||
const intention = await prisma.purchaseIntention.findUnique({
|
||||
where: { intention_id: intentionId },
|
||||
@@ -220,7 +228,7 @@ export async function getPurchaseIntentionById(
|
||||
export async function updatePurchaseIntentionStatus(
|
||||
intentionId: number,
|
||||
status: PurchaseIntentionStatus,
|
||||
sellerResponse?: string
|
||||
sellerResponse?: string,
|
||||
): Promise<PurchaseIntention> {
|
||||
const updateData: any = {
|
||||
status,
|
||||
@@ -264,7 +272,9 @@ export async function updatePurchaseIntentionStatus(
|
||||
/**
|
||||
* Conta intenções pendentes de um anúncio
|
||||
*/
|
||||
export async function getPendingIntentionsCount(listingId: number): Promise<number> {
|
||||
export async function getPendingIntentionsCount(
|
||||
listingId: number,
|
||||
): Promise<number> {
|
||||
return await prisma.purchaseIntention.count({
|
||||
where: {
|
||||
listing_id: listingId,
|
||||
@@ -278,7 +288,7 @@ export async function getPendingIntentionsCount(listingId: number): Promise<numb
|
||||
*/
|
||||
export async function hasPendingIntention(
|
||||
listingId: number,
|
||||
buyerId: number
|
||||
buyerId: number,
|
||||
): Promise<boolean> {
|
||||
const count = await prisma.purchaseIntention.count({
|
||||
where: {
|
||||
@@ -296,7 +306,7 @@ export async function hasPendingIntention(
|
||||
*/
|
||||
export async function getPendingIntentionByBuyerAndListing(
|
||||
listingId: number,
|
||||
buyerId: number
|
||||
buyerId: number,
|
||||
): Promise<PurchaseIntention | null> {
|
||||
const intention = await prisma.purchaseIntention.findFirst({
|
||||
where: {
|
||||
@@ -333,7 +343,7 @@ export async function getPendingIntentionByBuyerAndListing(
|
||||
*/
|
||||
export async function updatePurchaseIntentionPaymentMethod(
|
||||
intentionId: number,
|
||||
newPaymentMethod: PaymentMethod
|
||||
newPaymentMethod: PaymentMethod,
|
||||
): Promise<PurchaseIntention> {
|
||||
// Buscar intenção atual
|
||||
const intention = await prisma.purchaseIntention.findUnique({
|
||||
@@ -346,7 +356,9 @@ export async function updatePurchaseIntentionPaymentMethod(
|
||||
|
||||
// Verificar se pode trocar (máximo 1 troca)
|
||||
if (intention.payment_method_change_count >= 1) {
|
||||
throw new Error("Você já atingiu o limite de trocas de método de pagamento");
|
||||
throw new Error(
|
||||
"Você já atingiu o limite de trocas de método de pagamento",
|
||||
);
|
||||
}
|
||||
|
||||
// Verificar se o novo método é diferente
|
||||
@@ -388,7 +400,7 @@ export async function updatePurchaseIntentionPaymentMethod(
|
||||
*/
|
||||
export async function getPendingIntentionListingIdsForBuyer(
|
||||
buyerId: number,
|
||||
listingIds: number[]
|
||||
listingIds: number[],
|
||||
): Promise<number[]> {
|
||||
if (listingIds.length === 0) {
|
||||
return [];
|
||||
@@ -416,7 +428,7 @@ export async function getPendingIntentionListingIdsForBuyer(
|
||||
}
|
||||
|
||||
export async function getHighestCustomOfferForListing(
|
||||
listingId: number
|
||||
listingId: number,
|
||||
): Promise<{ amount: number; currency: string } | null> {
|
||||
const offer = await prisma.purchaseIntention.findFirst({
|
||||
where: {
|
||||
@@ -443,7 +455,7 @@ export async function getHighestCustomOfferForListing(
|
||||
}
|
||||
|
||||
export async function getHighestCustomOffersForListings(
|
||||
listingIds: number[]
|
||||
listingIds: number[],
|
||||
): Promise<Map<number, { amount: number; currency: string }>> {
|
||||
if (listingIds.length === 0) {
|
||||
return new Map();
|
||||
@@ -461,10 +473,7 @@ export async function getHighestCustomOffersForListings(
|
||||
custom_offer_amount: true,
|
||||
custom_offer_currency: true,
|
||||
},
|
||||
orderBy: [
|
||||
{ listing_id: "asc" },
|
||||
{ custom_offer_amount: "desc" },
|
||||
],
|
||||
orderBy: [{ listing_id: "asc" }, { custom_offer_amount: "desc" }],
|
||||
});
|
||||
|
||||
const result = new Map<number, { amount: number; currency: string }>();
|
||||
@@ -479,4 +488,3 @@ export async function getHighestCustomOffersForListings(
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -39,12 +39,19 @@ export async function getAllServers(): Promise<Server[]> {
|
||||
const servers = await prisma.server.findMany({
|
||||
orderBy: { server_name: "asc" },
|
||||
});
|
||||
return servers.map(server => ({
|
||||
return servers.map((server) => ({
|
||||
...server,
|
||||
server_type: server.server_type as "pvp" | "no-pvp" | "retro-pvp" | "hardcore-pvp" | null,
|
||||
server_type: server.server_type as
|
||||
| "pvp"
|
||||
| "no-pvp"
|
||||
| "retro-pvp"
|
||||
| "hardcore-pvp"
|
||||
| null,
|
||||
location: server.location as "NA" | "SA" | "EU" | "OC" | "BR" | null,
|
||||
total_volume: server.total_volume ? Number(server.total_volume) : 0,
|
||||
average_listing_price: server.average_listing_price ? Number(server.average_listing_price) : 0,
|
||||
average_listing_price: server.average_listing_price
|
||||
? Number(server.average_listing_price)
|
||||
: 0,
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -56,29 +63,45 @@ export async function getActiveServers(): Promise<Server[]> {
|
||||
where: { is_active: true },
|
||||
orderBy: { server_name: "asc" },
|
||||
});
|
||||
return servers.map(server => ({
|
||||
return servers.map((server) => ({
|
||||
...server,
|
||||
server_type: server.server_type as "pvp" | "no-pvp" | "retro-pvp" | "hardcore-pvp" | null,
|
||||
server_type: server.server_type as
|
||||
| "pvp"
|
||||
| "no-pvp"
|
||||
| "retro-pvp"
|
||||
| "hardcore-pvp"
|
||||
| null,
|
||||
location: server.location as "NA" | "SA" | "EU" | "OC" | "BR" | null,
|
||||
total_volume: server.total_volume ? Number(server.total_volume) : 0,
|
||||
average_listing_price: server.average_listing_price ? Number(server.average_listing_price) : 0,
|
||||
average_listing_price: server.average_listing_price
|
||||
? Number(server.average_listing_price)
|
||||
: 0,
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Busca servidor por nome
|
||||
*/
|
||||
export async function getServerByName(serverName: string): Promise<Server | null> {
|
||||
export async function getServerByName(
|
||||
serverName: string,
|
||||
): Promise<Server | null> {
|
||||
const server = await prisma.server.findUnique({
|
||||
where: { server_name: serverName },
|
||||
});
|
||||
if (!server) return null;
|
||||
return {
|
||||
...server,
|
||||
server_type: server.server_type as "pvp" | "no-pvp" | "retro-pvp" | "hardcore-pvp" | null,
|
||||
server_type: server.server_type as
|
||||
| "pvp"
|
||||
| "no-pvp"
|
||||
| "retro-pvp"
|
||||
| "hardcore-pvp"
|
||||
| null,
|
||||
location: server.location as "NA" | "SA" | "EU" | "OC" | "BR" | null,
|
||||
total_volume: server.total_volume ? Number(server.total_volume) : 0,
|
||||
average_listing_price: server.average_listing_price ? Number(server.average_listing_price) : 0,
|
||||
average_listing_price: server.average_listing_price
|
||||
? Number(server.average_listing_price)
|
||||
: 0,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -92,10 +115,17 @@ export async function getServerById(serverId: number): Promise<Server | null> {
|
||||
if (!server) return null;
|
||||
return {
|
||||
...server,
|
||||
server_type: server.server_type as "pvp" | "no-pvp" | "retro-pvp" | "hardcore-pvp" | null,
|
||||
server_type: server.server_type as
|
||||
| "pvp"
|
||||
| "no-pvp"
|
||||
| "retro-pvp"
|
||||
| "hardcore-pvp"
|
||||
| null,
|
||||
location: server.location as "NA" | "SA" | "EU" | "OC" | "BR" | null,
|
||||
total_volume: server.total_volume ? Number(server.total_volume) : 0,
|
||||
average_listing_price: server.average_listing_price ? Number(server.average_listing_price) : 0,
|
||||
average_listing_price: server.average_listing_price
|
||||
? Number(server.average_listing_price)
|
||||
: 0,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -124,7 +154,7 @@ export async function incrementServerStats(
|
||||
total_subscribers: number;
|
||||
total_transactions: number;
|
||||
total_volume: number;
|
||||
}>
|
||||
}>,
|
||||
): Promise<void> {
|
||||
const updateData: any = {
|
||||
last_activity_at: new Date(),
|
||||
@@ -157,17 +187,24 @@ export async function incrementServerStats(
|
||||
*/
|
||||
export async function updateServerStats(
|
||||
serverName: string,
|
||||
stats: ServerStats
|
||||
stats: ServerStats,
|
||||
): Promise<void> {
|
||||
const updateData: any = {};
|
||||
|
||||
if (stats.total_listings !== undefined) updateData.total_listings = stats.total_listings;
|
||||
if (stats.total_users !== undefined) updateData.total_users = stats.total_users;
|
||||
if (stats.total_subscribers !== undefined) updateData.total_subscribers = stats.total_subscribers;
|
||||
if (stats.total_transactions !== undefined) updateData.total_transactions = stats.total_transactions;
|
||||
if (stats.total_volume !== undefined) updateData.total_volume = stats.total_volume;
|
||||
if (stats.average_listing_price !== undefined) updateData.average_listing_price = stats.average_listing_price;
|
||||
if (stats.last_activity_at !== undefined) updateData.last_activity_at = stats.last_activity_at;
|
||||
if (stats.total_listings !== undefined)
|
||||
updateData.total_listings = stats.total_listings;
|
||||
if (stats.total_users !== undefined)
|
||||
updateData.total_users = stats.total_users;
|
||||
if (stats.total_subscribers !== undefined)
|
||||
updateData.total_subscribers = stats.total_subscribers;
|
||||
if (stats.total_transactions !== undefined)
|
||||
updateData.total_transactions = stats.total_transactions;
|
||||
if (stats.total_volume !== undefined)
|
||||
updateData.total_volume = stats.total_volume;
|
||||
if (stats.average_listing_price !== undefined)
|
||||
updateData.average_listing_price = stats.average_listing_price;
|
||||
if (stats.last_activity_at !== undefined)
|
||||
updateData.last_activity_at = stats.last_activity_at;
|
||||
|
||||
if (Object.keys(updateData).length === 0) {
|
||||
return;
|
||||
@@ -182,7 +219,9 @@ export async function updateServerStats(
|
||||
/**
|
||||
* Incrementa total_users de múltiplos servidores
|
||||
*/
|
||||
export async function incrementUsersForServers(serverNames: string[]): Promise<void> {
|
||||
export async function incrementUsersForServers(
|
||||
serverNames: string[],
|
||||
): Promise<void> {
|
||||
if (serverNames.length === 0) {
|
||||
return;
|
||||
}
|
||||
@@ -197,4 +236,3 @@ export async function incrementUsersForServers(serverNames: string[]): Promise<v
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -81,7 +81,7 @@ const featureSelect = {
|
||||
};
|
||||
|
||||
export async function getAllSubscriptionTiers(
|
||||
includeFeatures = false
|
||||
includeFeatures = false,
|
||||
): Promise<SubscriptionTierSummary[]> {
|
||||
const tiers = await prisma.subscriptionTier.findMany({
|
||||
select: {
|
||||
@@ -93,10 +93,7 @@ export async function getAllSubscriptionTiers(
|
||||
}
|
||||
: false,
|
||||
},
|
||||
orderBy: [
|
||||
{ level: "asc" },
|
||||
{ subscription_tier_id: "asc" },
|
||||
],
|
||||
orderBy: [{ level: "asc" }, { subscription_tier_id: "asc" }],
|
||||
});
|
||||
|
||||
return tiers as SubscriptionTierSummary[];
|
||||
@@ -104,7 +101,7 @@ export async function getAllSubscriptionTiers(
|
||||
|
||||
export async function getSubscriptionTierBySlug(
|
||||
slug: string,
|
||||
includeFeatures = false
|
||||
includeFeatures = false,
|
||||
): Promise<SubscriptionTierSummary | null> {
|
||||
const tier = await prisma.subscriptionTier.findUnique({
|
||||
where: { slug },
|
||||
@@ -124,7 +121,7 @@ export async function getSubscriptionTierBySlug(
|
||||
|
||||
export async function getSubscriptionTierById(
|
||||
tierId: number,
|
||||
includeFeatures = false
|
||||
includeFeatures = false,
|
||||
): Promise<SubscriptionTierSummary | null> {
|
||||
const tier = await prisma.subscriptionTier.findUnique({
|
||||
where: { subscription_tier_id: tierId },
|
||||
@@ -152,7 +149,7 @@ export async function getDefaultSubscriptionTier(): Promise<SubscriptionTierSumm
|
||||
}
|
||||
|
||||
export async function createSubscriptionTier(
|
||||
input: CreateSubscriptionTierInput
|
||||
input: CreateSubscriptionTierInput,
|
||||
): Promise<SubscriptionTierSummary> {
|
||||
const tier = await prisma.subscriptionTier.create({
|
||||
data: {
|
||||
@@ -194,9 +191,9 @@ export async function createSubscriptionTier(
|
||||
|
||||
export async function updateSubscriptionTier(
|
||||
tierId: number,
|
||||
input: UpdateSubscriptionTierInput
|
||||
input: UpdateSubscriptionTierInput,
|
||||
): Promise<SubscriptionTierSummary> {
|
||||
const tier = await prisma.subscriptionTier.update({
|
||||
const _tier = await prisma.subscriptionTier.update({
|
||||
where: { subscription_tier_id: tierId },
|
||||
data: {
|
||||
name: input.name ?? undefined,
|
||||
@@ -269,7 +266,7 @@ export async function deleteSubscriptionTier(tierId: number): Promise<void> {
|
||||
|
||||
export async function assignUserToSubscriptionTier(
|
||||
userId: number,
|
||||
tierId: number | null
|
||||
tierId: number | null,
|
||||
): Promise<void> {
|
||||
await prisma.user.update({
|
||||
where: { user_id: userId },
|
||||
@@ -290,9 +287,10 @@ export async function assignUserToSubscriptionTier(
|
||||
});
|
||||
}
|
||||
|
||||
export async function countUsersInSubscriptionTier(tierId: number): Promise<number> {
|
||||
export async function countUsersInSubscriptionTier(
|
||||
tierId: number,
|
||||
): Promise<number> {
|
||||
return prisma.user.count({
|
||||
where: { subscription_tier_id: tierId },
|
||||
});
|
||||
}
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
* Migrado de lib/db/queries/telemetry.ts
|
||||
*/
|
||||
|
||||
import { prisma } from "../app";
|
||||
import { Prisma } from "@prisma/client";
|
||||
import { prisma } from "../app";
|
||||
|
||||
export type TelemetryEventType =
|
||||
| "ad_view"
|
||||
@@ -102,7 +102,7 @@ export interface MarketplaceStats {
|
||||
* Registra um evento de telemetria
|
||||
*/
|
||||
export async function trackEvent(
|
||||
data: TrackEventData
|
||||
data: TrackEventData,
|
||||
): Promise<TelemetryEvent> {
|
||||
const event = await prisma.telemetryEvent.create({
|
||||
data: {
|
||||
@@ -123,28 +123,26 @@ export async function trackEvent(
|
||||
* Registra múltiplos eventos em batch
|
||||
*/
|
||||
export async function trackEventsBatch(
|
||||
events: TrackEventData[]
|
||||
events: TrackEventData[],
|
||||
): Promise<TelemetryEvent[]> {
|
||||
if (events.length === 0) return [];
|
||||
|
||||
const eventsResult = await prisma.$transaction(
|
||||
async (tx) => {
|
||||
return await Promise.all(
|
||||
events.map((event) =>
|
||||
tx.telemetryEvent.create({
|
||||
data: {
|
||||
event_type: event.event_type as TelemetryEventType,
|
||||
ad_id: event.ad_id || null,
|
||||
user_id: event.user_id || null,
|
||||
session_id: event.session_id || null,
|
||||
metadata: event.metadata || {},
|
||||
},
|
||||
})
|
||||
)
|
||||
);
|
||||
}
|
||||
);
|
||||
return eventsResult.map(event => ({
|
||||
const eventsResult = await prisma.$transaction(async (tx) => {
|
||||
return await Promise.all(
|
||||
events.map((event) =>
|
||||
tx.telemetryEvent.create({
|
||||
data: {
|
||||
event_type: event.event_type as TelemetryEventType,
|
||||
ad_id: event.ad_id || null,
|
||||
user_id: event.user_id || null,
|
||||
session_id: event.session_id || null,
|
||||
metadata: event.metadata || {},
|
||||
},
|
||||
}),
|
||||
),
|
||||
);
|
||||
});
|
||||
return eventsResult.map((event) => ({
|
||||
...event,
|
||||
event_type: event.event_type as TelemetryEventType,
|
||||
}));
|
||||
@@ -153,7 +151,9 @@ export async function trackEventsBatch(
|
||||
/**
|
||||
* Busca analytics de um anúncio específico
|
||||
*/
|
||||
export async function getAdAnalytics(adId: number): Promise<AdAnalytics | null> {
|
||||
export async function getAdAnalytics(
|
||||
adId: number,
|
||||
): Promise<AdAnalytics | null> {
|
||||
const analytics = await prisma.adAnalytics.findUnique({
|
||||
where: { ad_id: adId },
|
||||
});
|
||||
@@ -173,7 +173,7 @@ export async function getAdAnalytics(adId: number): Promise<AdAnalytics | null>
|
||||
export async function getUserBehavior(
|
||||
userId: number,
|
||||
startDate?: Date,
|
||||
endDate?: Date
|
||||
endDate?: Date,
|
||||
): Promise<UserBehavior | null> {
|
||||
// Verificar se usuário existe
|
||||
const user = await prisma.user.findUnique({
|
||||
@@ -278,7 +278,7 @@ export async function getUserBehavior(
|
||||
`;
|
||||
|
||||
// Buscar item_id dos anúncios visualizados
|
||||
const itemIds = await prisma.$queryRaw<Array<{ item_id: number }>>`
|
||||
const _itemIds = await prisma.$queryRaw<Array<{ item_id: number }>>`
|
||||
SELECT DISTINCT l.item_id
|
||||
FROM telemetry_events te
|
||||
JOIN listings l ON l.listing_id = te.ad_id
|
||||
@@ -295,7 +295,7 @@ export async function getUserBehavior(
|
||||
.filter((q): q is string => !!q),
|
||||
filters_applied: filterEvents.reduce(
|
||||
(acc, r) => ({ ...acc, ...(r.metadata as any) }),
|
||||
{} as Record<string, any>
|
||||
{} as Record<string, any>,
|
||||
),
|
||||
items_viewed: viewEvents.map((r) => ({
|
||||
item_id: r.ad_id!,
|
||||
@@ -320,11 +320,12 @@ export async function getUserBehavior(
|
||||
*/
|
||||
export async function getMarketplaceStats(
|
||||
startDate?: Date,
|
||||
endDate?: Date
|
||||
endDate?: Date,
|
||||
): Promise<MarketplaceStats> {
|
||||
const dateFilter = startDate && endDate
|
||||
? Prisma.sql`WHERE timestamp BETWEEN ${startDate} AND ${endDate}`
|
||||
: Prisma.empty;
|
||||
const dateFilter =
|
||||
startDate && endDate
|
||||
? Prisma.sql`WHERE timestamp BETWEEN ${startDate} AND ${endDate}`
|
||||
: Prisma.empty;
|
||||
|
||||
// Total de transações
|
||||
const transactionsResult = await prisma.$queryRaw<Array<{ count: bigint }>>`
|
||||
@@ -366,7 +367,8 @@ export async function getMarketplaceStats(
|
||||
|
||||
const viewsCount = Number(viewsResult[0]?.count || 0);
|
||||
const purchasesCount = Number(purchasesResult[0]?.count || 0);
|
||||
const conversion_rate = viewsCount > 0 ? (purchasesCount / viewsCount) * 100 : 0;
|
||||
const conversion_rate =
|
||||
viewsCount > 0 ? (purchasesCount / viewsCount) * 100 : 0;
|
||||
|
||||
// Itens mais procurados
|
||||
const mostSearchedResult = await prisma.$queryRaw<
|
||||
@@ -415,9 +417,7 @@ export async function getMarketplaceStats(
|
||||
`;
|
||||
|
||||
// Tempo médio para venda
|
||||
const avgTimeResult = await prisma.$queryRaw<
|
||||
Array<{ avg_hours: string }>
|
||||
>`
|
||||
const avgTimeResult = await prisma.$queryRaw<Array<{ avg_hours: string }>>`
|
||||
SELECT AVG(aa.time_to_sell)::TEXT as avg_hours
|
||||
FROM ad_analytics aa
|
||||
WHERE aa.time_to_sell IS NOT NULL
|
||||
@@ -430,7 +430,7 @@ export async function getMarketplaceStats(
|
||||
acc[r.item_name] = parseFloat(r.avg_price);
|
||||
return acc;
|
||||
},
|
||||
{} as Record<string, number>
|
||||
{} as Record<string, number>,
|
||||
),
|
||||
conversion_rate,
|
||||
most_searched_items: mostSearchedResult.map((r) => ({
|
||||
@@ -458,7 +458,7 @@ export async function getMarketplaceStats(
|
||||
export async function aggregateHourlyMetrics(): Promise<void> {
|
||||
// Usar $queryRaw para queries complexas de agregação
|
||||
// Por enquanto, manter a lógica original mas adaptada para Prisma
|
||||
|
||||
|
||||
// Agregar impressões
|
||||
await prisma.$executeRaw`
|
||||
INSERT INTO ad_analytics (ad_id, impressions, last_updated)
|
||||
@@ -617,4 +617,3 @@ export async function aggregateHourlyMetrics(): Promise<void> {
|
||||
AND l.sold_at >= NOW() - INTERVAL '1 hour'
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
* Migrado de lib/db/queries/transactions.ts
|
||||
*/
|
||||
|
||||
import type { Prisma } from "@prisma/client";
|
||||
import { prisma } from "../app";
|
||||
import { Prisma } from "@prisma/client";
|
||||
|
||||
export type TransactionStatus =
|
||||
| "pending"
|
||||
@@ -64,7 +64,7 @@ export interface UpdateTransactionStatusData {
|
||||
* Cria uma nova transação
|
||||
*/
|
||||
export async function createTransaction(
|
||||
data: CreateTransactionData
|
||||
data: CreateTransactionData,
|
||||
): Promise<Transaction> {
|
||||
// Calcular escrow_fee se não fornecido (2% do valor)
|
||||
const escrowFee = data.escrow_fee ?? Number((data.amount * 0.02).toFixed(2));
|
||||
@@ -87,7 +87,11 @@ export async function createTransaction(
|
||||
amount: Number(transaction.amount),
|
||||
escrow_fee: transaction.escrow_fee ? Number(transaction.escrow_fee) : null,
|
||||
transaction_status: transaction.transaction_status as TransactionStatus,
|
||||
payment_method: transaction.payment_method as "pix" | "tibia_coins" | "gold_coins" | null,
|
||||
payment_method: transaction.payment_method as
|
||||
| "pix"
|
||||
| "tibia_coins"
|
||||
| "gold_coins"
|
||||
| null,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -95,7 +99,7 @@ export async function createTransaction(
|
||||
* Busca transação por ID
|
||||
*/
|
||||
export async function getTransactionById(
|
||||
transactionId: number
|
||||
transactionId: number,
|
||||
): Promise<Transaction | null> {
|
||||
const transaction = await prisma.transaction.findUnique({
|
||||
where: { transaction_id: transactionId },
|
||||
@@ -106,7 +110,11 @@ export async function getTransactionById(
|
||||
amount: Number(transaction.amount),
|
||||
escrow_fee: transaction.escrow_fee ? Number(transaction.escrow_fee) : null,
|
||||
transaction_status: transaction.transaction_status as TransactionStatus,
|
||||
payment_method: transaction.payment_method as "pix" | "tibia_coins" | "gold_coins" | null,
|
||||
payment_method: transaction.payment_method as
|
||||
| "pix"
|
||||
| "tibia_coins"
|
||||
| "gold_coins"
|
||||
| null,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -115,19 +123,25 @@ export async function getTransactionById(
|
||||
*/
|
||||
export async function updateTransactionStatus(
|
||||
transactionId: number,
|
||||
data: UpdateTransactionStatusData
|
||||
data: UpdateTransactionStatusData,
|
||||
): Promise<Transaction | null> {
|
||||
const updateData: Prisma.TransactionUpdateInput = {
|
||||
transaction_status: data.transaction_status,
|
||||
};
|
||||
|
||||
if (data.deposited_at !== undefined) updateData.deposited_at = data.deposited_at;
|
||||
if (data.delivered_at !== undefined) updateData.delivered_at = data.delivered_at;
|
||||
if (data.completed_at !== undefined) updateData.completed_at = data.completed_at;
|
||||
if (data.buyer_confirmed !== undefined) updateData.buyer_confirmed = data.buyer_confirmed;
|
||||
if (data.seller_confirmed !== undefined) updateData.seller_confirmed = data.seller_confirmed;
|
||||
if (data.deposited_at !== undefined)
|
||||
updateData.deposited_at = data.deposited_at;
|
||||
if (data.delivered_at !== undefined)
|
||||
updateData.delivered_at = data.delivered_at;
|
||||
if (data.completed_at !== undefined)
|
||||
updateData.completed_at = data.completed_at;
|
||||
if (data.buyer_confirmed !== undefined)
|
||||
updateData.buyer_confirmed = data.buyer_confirmed;
|
||||
if (data.seller_confirmed !== undefined)
|
||||
updateData.seller_confirmed = data.seller_confirmed;
|
||||
if (data.buyer_notes !== undefined) updateData.buyer_notes = data.buyer_notes;
|
||||
if (data.seller_notes !== undefined) updateData.seller_notes = data.seller_notes;
|
||||
if (data.seller_notes !== undefined)
|
||||
updateData.seller_notes = data.seller_notes;
|
||||
|
||||
const transaction = await prisma.transaction.update({
|
||||
where: { transaction_id: transactionId },
|
||||
@@ -138,7 +152,11 @@ export async function updateTransactionStatus(
|
||||
amount: Number(transaction.amount),
|
||||
escrow_fee: transaction.escrow_fee ? Number(transaction.escrow_fee) : null,
|
||||
transaction_status: transaction.transaction_status as TransactionStatus,
|
||||
payment_method: transaction.payment_method as "pix" | "tibia_coins" | "gold_coins" | null,
|
||||
payment_method: transaction.payment_method as
|
||||
| "pix"
|
||||
| "tibia_coins"
|
||||
| "gold_coins"
|
||||
| null,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -152,7 +170,7 @@ export async function getTransactionsByUser(
|
||||
status?: TransactionStatus;
|
||||
page?: number;
|
||||
pageSize?: number;
|
||||
} = {}
|
||||
} = {},
|
||||
): Promise<{ transactions: Transaction[]; total: number }> {
|
||||
const { role = "both", status, page = 1, pageSize = 20 } = filters;
|
||||
|
||||
@@ -163,10 +181,7 @@ export async function getTransactionsByUser(
|
||||
} else if (role === "seller") {
|
||||
where.seller_id = userId;
|
||||
} else {
|
||||
where.OR = [
|
||||
{ buyer_id: userId },
|
||||
{ seller_id: userId },
|
||||
];
|
||||
where.OR = [{ buyer_id: userId }, { seller_id: userId }];
|
||||
}
|
||||
|
||||
if (status) {
|
||||
@@ -185,14 +200,19 @@ export async function getTransactionsByUser(
|
||||
});
|
||||
|
||||
return {
|
||||
transactions: transactions.map(transaction => ({
|
||||
transactions: transactions.map((transaction) => ({
|
||||
...transaction,
|
||||
amount: Number(transaction.amount),
|
||||
escrow_fee: transaction.escrow_fee ? Number(transaction.escrow_fee) : null,
|
||||
escrow_fee: transaction.escrow_fee
|
||||
? Number(transaction.escrow_fee)
|
||||
: null,
|
||||
transaction_status: transaction.transaction_status as TransactionStatus,
|
||||
payment_method: transaction.payment_method as "pix" | "tibia_coins" | "gold_coins" | null,
|
||||
payment_method: transaction.payment_method as
|
||||
| "pix"
|
||||
| "tibia_coins"
|
||||
| "gold_coins"
|
||||
| null,
|
||||
})),
|
||||
total,
|
||||
};
|
||||
}
|
||||
|
||||
+14
-11
@@ -3,9 +3,9 @@
|
||||
* Migrado de lib/db/queries/users.ts
|
||||
*/
|
||||
|
||||
import type { User } from "@prisma/client";
|
||||
import { prisma } from "../app";
|
||||
import { getDefaultSubscriptionTier } from "./subscription-tiers";
|
||||
import type { User } from "@prisma/client";
|
||||
|
||||
export interface CreateUserData {
|
||||
username: string;
|
||||
@@ -29,7 +29,7 @@ export interface UpdateUserData {
|
||||
* Cria um novo usuário
|
||||
*/
|
||||
export async function createUser(
|
||||
data: CreateUserData
|
||||
data: CreateUserData,
|
||||
): Promise<{ userId: number; username: string; email: string }> {
|
||||
const defaultTier = await getDefaultSubscriptionTier();
|
||||
const user = await prisma.user.create({
|
||||
@@ -69,7 +69,9 @@ type UserWithTier = User & {
|
||||
} | null;
|
||||
};
|
||||
|
||||
export async function getUserByEmail(email: string): Promise<UserWithTier | null> {
|
||||
export async function getUserByEmail(
|
||||
email: string,
|
||||
): Promise<UserWithTier | null> {
|
||||
return (await prisma.user.findUnique({
|
||||
where: { email },
|
||||
include: {
|
||||
@@ -89,7 +91,7 @@ export async function getUserByEmail(email: string): Promise<UserWithTier | null
|
||||
* Busca usuário por username
|
||||
*/
|
||||
export async function getUserByUsername(
|
||||
username: string
|
||||
username: string,
|
||||
): Promise<UserWithTier | null> {
|
||||
return (await prisma.user.findUnique({
|
||||
where: { username },
|
||||
@@ -134,7 +136,7 @@ type PublicUser = {
|
||||
};
|
||||
|
||||
export async function getUserById(userId: number): Promise<PublicUser | null> {
|
||||
return await prisma.user.findUnique({
|
||||
return (await prisma.user.findUnique({
|
||||
where: { user_id: userId },
|
||||
select: {
|
||||
user_id: true,
|
||||
@@ -161,21 +163,23 @@ export async function getUserById(userId: number): Promise<PublicUser | null> {
|
||||
},
|
||||
},
|
||||
},
|
||||
}) as PublicUser | null;
|
||||
})) as PublicUser | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Busca usuário por CPF (verifica duplicidade)
|
||||
*/
|
||||
export async function getUserByCpf(cpf: string): Promise<Pick<User, 'user_id' | 'username' | 'email'> | null> {
|
||||
return await prisma.user.findUnique({
|
||||
export async function getUserByCpf(
|
||||
cpf: string,
|
||||
): Promise<Pick<User, "user_id" | "username" | "email"> | null> {
|
||||
return (await prisma.user.findUnique({
|
||||
where: { cpf },
|
||||
select: {
|
||||
user_id: true,
|
||||
username: true,
|
||||
email: true,
|
||||
},
|
||||
}) as Pick<User, 'user_id' | 'username' | 'email'> | null;
|
||||
})) as Pick<User, "user_id" | "username" | "email"> | null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -183,7 +187,7 @@ export async function getUserByCpf(cpf: string): Promise<Pick<User, 'user_id' |
|
||||
*/
|
||||
export async function updateUser(
|
||||
userId: number,
|
||||
data: UpdateUserData
|
||||
data: UpdateUserData,
|
||||
): Promise<PublicUser> {
|
||||
const updateData: any = {};
|
||||
if (data.first_name !== undefined) updateData.first_name = data.first_name;
|
||||
@@ -243,4 +247,3 @@ export async function updateLastLogin(userId: number): Promise<void> {
|
||||
data: { last_login: new Date() },
|
||||
});
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user