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
+19
-15
@@ -3,7 +3,7 @@
|
||||
* Gerencia pool de conexões e fornece helpers para queries
|
||||
*/
|
||||
|
||||
import { Pool, QueryResult, QueryResultRow } from "pg";
|
||||
import { Pool, type QueryResult, type QueryResultRow } from "pg";
|
||||
|
||||
let pool: Pool | null = null;
|
||||
|
||||
@@ -13,7 +13,7 @@ let pool: Pool | null = null;
|
||||
function validateEnvVars() {
|
||||
if (!process.env.PG_DB) {
|
||||
throw new Error(
|
||||
"PG_DB não está definido. Configure a variável de ambiente PG_DB com a connection string do PostgreSQL."
|
||||
"PG_DB não está definido. Configure a variável de ambiente PG_DB com a connection string do PostgreSQL.",
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -56,7 +56,7 @@ export function getPool(): Pool {
|
||||
*/
|
||||
export async function query<T extends QueryResultRow = any>(
|
||||
text: string,
|
||||
params?: any[]
|
||||
params?: any[],
|
||||
): Promise<QueryResult<T>> {
|
||||
const poolInstance = getPool();
|
||||
const start = Date.now();
|
||||
@@ -78,15 +78,15 @@ export async function query<T extends QueryResultRow = any>(
|
||||
const errorMessage = error instanceof Error ? error.message : String(error);
|
||||
const errorCode = error?.code;
|
||||
const errorStack = error instanceof Error ? error.stack : undefined;
|
||||
|
||||
|
||||
// Ignorar silenciosamente erros relacionados à tabela "items" não existir
|
||||
// Isso é esperado durante a transição e já está sendo tratado nas funções
|
||||
const isItemsTableMissing =
|
||||
errorCode === "42P01" &&
|
||||
(errorMessage.includes('relation "items" does not exist') ||
|
||||
text.includes('FROM items') ||
|
||||
text.includes('FROM "items"'));
|
||||
|
||||
const isItemsTableMissing =
|
||||
errorCode === "42P01" &&
|
||||
(errorMessage.includes('relation "items" does not exist') ||
|
||||
text.includes("FROM items") ||
|
||||
text.includes('FROM "items"'));
|
||||
|
||||
// Não logar erros esperados sobre tabela items não existir
|
||||
if (!isItemsTableMissing) {
|
||||
console.error("❌ Erro ao executar query:", {
|
||||
@@ -94,14 +94,19 @@ export async function query<T extends QueryResultRow = any>(
|
||||
error: errorMessage,
|
||||
stack: errorStack,
|
||||
});
|
||||
|
||||
|
||||
// Se o erro for sobre coluna não existir, sugerir executar migração
|
||||
if (errorMessage.includes("column") && errorMessage.includes("does not exist")) {
|
||||
console.error("\n💡 DICA: Parece que a migração do banco de dados não foi executada.");
|
||||
if (
|
||||
errorMessage.includes("column") &&
|
||||
errorMessage.includes("does not exist")
|
||||
) {
|
||||
console.error(
|
||||
"\n💡 DICA: Parece que a migração do banco de dados não foi executada.",
|
||||
);
|
||||
console.error(" Execute: pnpm run db:migrate\n");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
@@ -145,4 +150,3 @@ if (typeof process !== "undefined") {
|
||||
process.exit(0);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -3,17 +3,17 @@
|
||||
* Executa todas as migrations SQL em ordem
|
||||
*/
|
||||
|
||||
import { resolve } from "node:path";
|
||||
// Carregar variáveis de ambiente
|
||||
import { config } from "dotenv";
|
||||
import { resolve } from "path";
|
||||
|
||||
// Tentar carregar .env.local primeiro, depois .env
|
||||
config({ path: resolve(process.cwd(), ".env.local") });
|
||||
config({ path: resolve(process.cwd(), ".env") });
|
||||
|
||||
import { readFileSync } from "fs";
|
||||
import { join } from "path";
|
||||
import { getPool, query, healthCheck } from "../index";
|
||||
import { readFileSync } from "node:fs";
|
||||
import { join } from "node:path";
|
||||
import { getPool, healthCheck, query } from "../index";
|
||||
|
||||
const MIGRATIONS_DIR = join(__dirname, ".");
|
||||
|
||||
@@ -50,7 +50,9 @@ async function runMigrations() {
|
||||
const isHealthy = await healthCheck();
|
||||
|
||||
if (!isHealthy) {
|
||||
console.error("⌠Falha na conexão com PostgreSQL. Verifique a variável PG_DB.");
|
||||
console.error(
|
||||
"⌠Falha na conexão com PostgreSQL. Verifique a variável PG_DB.",
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
@@ -78,7 +80,7 @@ async function runMigrations() {
|
||||
// Verificar se já foi executada
|
||||
const checkResult = await query<{ version: string }>(
|
||||
"SELECT version FROM schema_migrations WHERE version = $1",
|
||||
[version]
|
||||
[version],
|
||||
);
|
||||
|
||||
if (checkResult.rows.length > 0) {
|
||||
@@ -95,10 +97,9 @@ async function runMigrations() {
|
||||
await query(sql);
|
||||
|
||||
// Registrar como executada
|
||||
await query(
|
||||
"INSERT INTO schema_migrations (version) VALUES ($1)",
|
||||
[version]
|
||||
);
|
||||
await query("INSERT INTO schema_migrations (version) VALUES ($1)", [
|
||||
version,
|
||||
]);
|
||||
|
||||
console.log(`✅ Migration ${version} executada com sucesso\n`);
|
||||
} catch (error) {
|
||||
@@ -108,7 +109,7 @@ async function runMigrations() {
|
||||
}
|
||||
|
||||
console.log("🎉 Todas as migrations foram executadas com sucesso!");
|
||||
|
||||
|
||||
// Fechar pool
|
||||
const pool = getPool();
|
||||
await pool.end();
|
||||
@@ -120,5 +121,3 @@ runMigrations().catch((error) => {
|
||||
console.error("⌠Erro fatal:", error);
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ export async function createBid(data: CreateBidData): Promise<Bid> {
|
||||
VALUES ($1, $2, $3, $4)
|
||||
RETURNING *
|
||||
`,
|
||||
[data.listing_id, data.bidder_id, data.bid_amount, data.bid_type || null]
|
||||
[data.listing_id, data.bidder_id, data.bid_amount, data.bid_type || null],
|
||||
);
|
||||
|
||||
return result.rows[0];
|
||||
@@ -43,7 +43,7 @@ export async function createBid(data: CreateBidData): Promise<Bid> {
|
||||
*/
|
||||
export async function getBidsByListing(
|
||||
listingId: number,
|
||||
activeOnly: boolean = true
|
||||
activeOnly: boolean = true,
|
||||
): Promise<Bid[]> {
|
||||
let sql = `
|
||||
SELECT * FROM bids
|
||||
@@ -73,7 +73,7 @@ export async function getHighestBid(listingId: number): Promise<Bid | null> {
|
||||
ORDER BY bid_amount DESC, created_at DESC
|
||||
LIMIT 1
|
||||
`,
|
||||
[listingId]
|
||||
[listingId],
|
||||
);
|
||||
|
||||
return result.rows[0] || null;
|
||||
@@ -89,7 +89,7 @@ export async function cancelBid(bidId: number): Promise<boolean> {
|
||||
SET is_active = FALSE
|
||||
WHERE bid_id = $1
|
||||
`,
|
||||
[bidId]
|
||||
[bidId],
|
||||
);
|
||||
|
||||
return (result.rowCount ?? 0) > 0;
|
||||
@@ -103,9 +103,8 @@ export async function getBidById(bidId: number): Promise<Bid | null> {
|
||||
`
|
||||
SELECT * FROM bids WHERE bid_id = $1
|
||||
`,
|
||||
[bidId]
|
||||
[bidId],
|
||||
);
|
||||
|
||||
return result.rows[0] || null;
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ export interface UpdateCharacterData {
|
||||
*/
|
||||
export async function createCharacter(
|
||||
userId: number,
|
||||
data: CreateCharacterData
|
||||
data: CreateCharacterData,
|
||||
): Promise<Character> {
|
||||
// Usar transação para garantir atomicidade
|
||||
const { getPool } = await import("../index");
|
||||
@@ -51,7 +51,7 @@ export async function createCharacter(
|
||||
SET is_main = FALSE
|
||||
WHERE user_id = $1
|
||||
`,
|
||||
[userId]
|
||||
[userId],
|
||||
);
|
||||
}
|
||||
|
||||
@@ -62,7 +62,7 @@ export async function createCharacter(
|
||||
VALUES ($1, $2, $3, $4)
|
||||
RETURNING *
|
||||
`,
|
||||
[userId, data.character_name, data.server_name, data.is_main || false]
|
||||
[userId, data.character_name, data.server_name, data.is_main || false],
|
||||
);
|
||||
|
||||
await client.query("COMMIT");
|
||||
@@ -86,7 +86,7 @@ export async function getUserCharacters(userId: number): Promise<Character[]> {
|
||||
WHERE user_id = $1
|
||||
ORDER BY is_main DESC, created_at ASC
|
||||
`,
|
||||
[userId]
|
||||
[userId],
|
||||
);
|
||||
|
||||
return result.rows;
|
||||
@@ -98,7 +98,7 @@ export async function getUserCharacters(userId: number): Promise<Character[]> {
|
||||
*/
|
||||
export async function getMainCharacterByUserId(
|
||||
userId: number,
|
||||
serverName: string
|
||||
serverName: string,
|
||||
): Promise<Character | null> {
|
||||
const result = await query<Character>(
|
||||
`
|
||||
@@ -107,7 +107,7 @@ export async function getMainCharacterByUserId(
|
||||
ORDER BY is_main DESC, created_at ASC
|
||||
LIMIT 1
|
||||
`,
|
||||
[userId, serverName]
|
||||
[userId, serverName],
|
||||
);
|
||||
|
||||
return result.rows[0] || null;
|
||||
@@ -117,13 +117,13 @@ export async function getMainCharacterByUserId(
|
||||
* Busca personagem por ID
|
||||
*/
|
||||
export async function getCharacterById(
|
||||
characterId: number
|
||||
characterId: number,
|
||||
): Promise<Character | null> {
|
||||
const result = await query<Character>(
|
||||
`
|
||||
SELECT * FROM user_characters WHERE character_id = $1
|
||||
`,
|
||||
[characterId]
|
||||
[characterId],
|
||||
);
|
||||
|
||||
return result.rows[0] || null;
|
||||
@@ -136,7 +136,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);
|
||||
@@ -164,7 +164,7 @@ export async function updateCharacter(
|
||||
SET is_main = FALSE
|
||||
WHERE user_id = $1 AND character_id != $2
|
||||
`,
|
||||
[userId, characterId]
|
||||
[userId, characterId],
|
||||
);
|
||||
}
|
||||
|
||||
@@ -203,7 +203,7 @@ export async function updateCharacter(
|
||||
WHERE character_id = $${paramIndex}
|
||||
RETURNING *
|
||||
`,
|
||||
values
|
||||
values,
|
||||
);
|
||||
|
||||
await client.query("COMMIT");
|
||||
@@ -223,7 +223,7 @@ export async function updateCharacter(
|
||||
*/
|
||||
export async function deleteCharacter(
|
||||
characterId: number,
|
||||
userId: number
|
||||
userId: number,
|
||||
): Promise<void> {
|
||||
// Verificar ownership
|
||||
const existing = await getCharacterById(characterId);
|
||||
@@ -239,7 +239,6 @@ export async function deleteCharacter(
|
||||
`
|
||||
DELETE FROM user_characters WHERE character_id = $1
|
||||
`,
|
||||
[characterId]
|
||||
[characterId],
|
||||
);
|
||||
}
|
||||
|
||||
@@ -26,7 +26,9 @@ export interface UnifiedItem {
|
||||
/**
|
||||
* Busca item por ID unificado
|
||||
*/
|
||||
export async function getItemByIdUnified(itemId: number): Promise<UnifiedItem | null> {
|
||||
export async function getItemByIdUnified(
|
||||
itemId: number,
|
||||
): Promise<UnifiedItem | null> {
|
||||
try {
|
||||
const result = await query<UnifiedItem>(
|
||||
`
|
||||
@@ -49,7 +51,7 @@ export async function getItemByIdUnified(itemId: number): Promise<UnifiedItem |
|
||||
FROM items
|
||||
WHERE item_id = $1
|
||||
`,
|
||||
[itemId]
|
||||
[itemId],
|
||||
);
|
||||
|
||||
if (result.rows.length === 0) {
|
||||
@@ -71,7 +73,9 @@ export async function getItemByIdUnified(itemId: number): Promise<UnifiedItem |
|
||||
/**
|
||||
* Busca item por nome (case-insensitive)
|
||||
*/
|
||||
export async function getItemByNameUnified(itemName: string): Promise<UnifiedItem | null> {
|
||||
export async function getItemByNameUnified(
|
||||
itemName: string,
|
||||
): Promise<UnifiedItem | null> {
|
||||
try {
|
||||
const result = await query<UnifiedItem>(
|
||||
`
|
||||
@@ -95,7 +99,7 @@ export async function getItemByNameUnified(itemName: string): Promise<UnifiedIte
|
||||
WHERE LOWER(item_name) = LOWER($1)
|
||||
LIMIT 1
|
||||
`,
|
||||
[itemName]
|
||||
[itemName],
|
||||
);
|
||||
|
||||
if (result.rows.length === 0) {
|
||||
@@ -123,7 +127,7 @@ export async function itemExists(itemId: number): Promise<boolean> {
|
||||
SELECT 1 FROM items WHERE item_id = $1
|
||||
) as exists
|
||||
`,
|
||||
[itemId]
|
||||
[itemId],
|
||||
);
|
||||
|
||||
return result.rows[0]?.exists || false;
|
||||
@@ -135,4 +139,3 @@ export async function itemExists(itemId: number): Promise<boolean> {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
+115
-56
@@ -89,21 +89,26 @@ 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> {
|
||||
// Validar item_id_unified se fornecido
|
||||
if (data.item_id_unified) {
|
||||
const exists = await itemExists(data.item_id_unified);
|
||||
if (!exists) {
|
||||
throw new Error(`Item com ID unificado ${data.item_id_unified} não encontrado na tabela items`);
|
||||
throw new Error(
|
||||
`Item com ID unificado ${data.item_id_unified} não encontrado na tabela items`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -162,11 +167,11 @@ export async function createListing(
|
||||
data.trade_item_id || null,
|
||||
data.trade_item_name || null,
|
||||
data.trade_item_quantity || null,
|
||||
data.is_auction ? (data.price_pix || null) : null, // initial_price_pix
|
||||
data.is_auction ? (data.price_tibia_coins || null) : null, // initial_price_tibia_coins
|
||||
data.is_auction ? (data.price_gold_coins || null) : null, // initial_price_gold_coins
|
||||
data.is_auction ? data.price_pix || null : null, // initial_price_pix
|
||||
data.is_auction ? data.price_tibia_coins || null : null, // initial_price_tibia_coins
|
||||
data.is_auction ? data.price_gold_coins || null : null, // initial_price_gold_coins
|
||||
minBidIncrement || null,
|
||||
]
|
||||
],
|
||||
);
|
||||
} catch (error) {
|
||||
// Se as colunas da migração 014 ou item_id_unified não existirem, usar INSERT sem elas
|
||||
@@ -210,11 +215,14 @@ export async function createListing(
|
||||
data.trade_item_id || null,
|
||||
data.trade_item_name || null,
|
||||
data.trade_item_quantity || null,
|
||||
]
|
||||
],
|
||||
);
|
||||
} catch (error2) {
|
||||
// Se item_id_unified também não existir, usar apenas item_id legado
|
||||
if (error2 instanceof Error && error2.message.includes("does not exist")) {
|
||||
if (
|
||||
error2 instanceof Error &&
|
||||
error2.message.includes("does not exist")
|
||||
) {
|
||||
result = await query<Listing>(
|
||||
`
|
||||
INSERT INTO listings (
|
||||
@@ -251,7 +259,7 @@ export async function createListing(
|
||||
data.trade_item_id || null,
|
||||
data.trade_item_name || null,
|
||||
data.trade_item_quantity || null,
|
||||
]
|
||||
],
|
||||
);
|
||||
} else {
|
||||
throw error2;
|
||||
@@ -268,7 +276,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> {
|
||||
let result;
|
||||
try {
|
||||
// Tentar query com todas as colunas (se migração 014 foi executada)
|
||||
@@ -298,7 +308,7 @@ export async function getListingById(listingId: number): Promise<Listing | null>
|
||||
AND uc.is_main = TRUE
|
||||
WHERE l.listing_id = $1
|
||||
`,
|
||||
[listingId]
|
||||
[listingId],
|
||||
);
|
||||
} catch (error) {
|
||||
// Se as colunas da migração 014 não existirem, usar query sem elas
|
||||
@@ -332,7 +342,7 @@ export async function getListingById(listingId: number): Promise<Listing | null>
|
||||
AND uc.is_main = TRUE
|
||||
WHERE l.listing_id = $1
|
||||
`,
|
||||
[listingId]
|
||||
[listingId],
|
||||
);
|
||||
} else {
|
||||
throw error;
|
||||
@@ -354,7 +364,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,
|
||||
@@ -404,36 +414,45 @@ export async function getListings(
|
||||
}
|
||||
|
||||
if (search) {
|
||||
conditions.push(`(l.item_name ILIKE $${paramIndex} OR l.description ILIKE $${paramIndex})`);
|
||||
conditions.push(
|
||||
`(l.item_name ILIKE $${paramIndex} OR l.description ILIKE $${paramIndex})`,
|
||||
);
|
||||
params.push(`%${search}%`);
|
||||
paramIndex++;
|
||||
}
|
||||
|
||||
const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "WHERE 1=1";
|
||||
const whereClause =
|
||||
conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "WHERE 1=1";
|
||||
|
||||
// Contar total
|
||||
const countResult = await query<{ count: string }>(
|
||||
`SELECT COUNT(*) as count FROM listings l ${whereClause}`,
|
||||
params
|
||||
params,
|
||||
);
|
||||
const total = parseInt(countResult.rows[0].count, 10);
|
||||
|
||||
// Validar sortBy - garantir que seja uma coluna válida
|
||||
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 sortDirection = sortOrder === "ASC" ? "ASC" : "DESC";
|
||||
|
||||
|
||||
// Mapear colunas para usar alias 'l.' quando necessário
|
||||
const sortColumnWithAlias = `l.${sortColumn}`;
|
||||
|
||||
// Buscar listings
|
||||
const offset = (page - 1) * pageSize;
|
||||
|
||||
|
||||
// Incrementar paramIndex para os parâmetros de LIMIT e OFFSET
|
||||
const limitParamIndex = paramIndex;
|
||||
const offsetParamIndex = paramIndex + 1;
|
||||
const finalParams = [...params, pageSize, offset];
|
||||
|
||||
|
||||
// Tentar query com todas as colunas (se migração 014 foi executada)
|
||||
// Se falhar, usar query sem as novas colunas
|
||||
let result;
|
||||
@@ -465,10 +484,10 @@ export async function getListings(
|
||||
ORDER BY ${sortColumnWithAlias} ${sortDirection}
|
||||
LIMIT $${limitParamIndex} OFFSET $${offsetParamIndex}
|
||||
`;
|
||||
|
||||
|
||||
result = await query<Listing & { seller_character_name: string }>(
|
||||
querySql,
|
||||
finalParams
|
||||
finalParams,
|
||||
);
|
||||
} catch (error) {
|
||||
// Se as colunas da migração 014 não existirem, usar query sem elas
|
||||
@@ -503,10 +522,10 @@ export async function getListings(
|
||||
ORDER BY ${sortColumnWithAlias} ${sortDirection}
|
||||
LIMIT $${limitParamIndex} OFFSET $${offsetParamIndex}
|
||||
`;
|
||||
|
||||
|
||||
result = await query<Listing & { seller_character_name: string }>(
|
||||
fallbackQuerySql,
|
||||
finalParams
|
||||
finalParams,
|
||||
);
|
||||
} else {
|
||||
throw error;
|
||||
@@ -514,7 +533,7 @@ export async function getListings(
|
||||
}
|
||||
|
||||
// Mapear resultados para garantir que seller_character_name seja null se não existir
|
||||
const listings = result.rows.map(row => ({
|
||||
const listings = result.rows.map((row) => ({
|
||||
...row,
|
||||
seller_character_name: row.seller_character_name || null,
|
||||
}));
|
||||
@@ -530,7 +549,7 @@ export async function getListings(
|
||||
*/
|
||||
export async function updateListing(
|
||||
listingId: number,
|
||||
data: UpdateListingData
|
||||
data: UpdateListingData,
|
||||
): Promise<Listing | null> {
|
||||
const updates: string[] = [];
|
||||
const params: any[] = [];
|
||||
@@ -589,7 +608,7 @@ export async function updateListing(
|
||||
}
|
||||
|
||||
params.push(listingId);
|
||||
|
||||
|
||||
let result;
|
||||
try {
|
||||
// Tentar query com todas as colunas (se migração 014 foi executada)
|
||||
@@ -611,13 +630,15 @@ export async function updateListing(
|
||||
l.trade_item_id, l.trade_item_name, l.trade_item_quantity,
|
||||
l.min_bid_increment, l.initial_price_pix, l.initial_price_tibia_coins, l.initial_price_gold_coins
|
||||
`,
|
||||
params
|
||||
params,
|
||||
);
|
||||
|
||||
|
||||
// Buscar dados do JOIN após o UPDATE
|
||||
if (result.rows.length > 0) {
|
||||
const listingId = result.rows[0].listing_id;
|
||||
const joinResult = await query<Listing & { seller_character_name: string }>(
|
||||
const joinResult = await query<
|
||||
Listing & { seller_character_name: string }
|
||||
>(
|
||||
`
|
||||
SELECT
|
||||
l.listing_id, l.seller_id, l.item_id, l.item_name, l.quantity, l.server_name,
|
||||
@@ -643,9 +664,9 @@ export async function updateListing(
|
||||
AND uc.is_main = TRUE
|
||||
WHERE l.listing_id = $1
|
||||
`,
|
||||
[listingId]
|
||||
[listingId],
|
||||
);
|
||||
|
||||
|
||||
if (joinResult.rows.length > 0) {
|
||||
result.rows[0] = joinResult.rows[0];
|
||||
}
|
||||
@@ -674,13 +695,15 @@ export async function updateListing(
|
||||
NULL::integer as initial_price_tibia_coins,
|
||||
NULL::bigint as initial_price_gold_coins
|
||||
`,
|
||||
params
|
||||
params,
|
||||
);
|
||||
|
||||
|
||||
// Buscar dados do JOIN após o UPDATE
|
||||
if (result.rows.length > 0) {
|
||||
const listingId = result.rows[0].listing_id;
|
||||
const joinResult = await query<Listing & { seller_character_name: string }>(
|
||||
const joinResult = await query<
|
||||
Listing & { seller_character_name: string }
|
||||
>(
|
||||
`
|
||||
SELECT
|
||||
l.listing_id, l.seller_id, l.item_id, l.item_name, l.quantity, l.server_name,
|
||||
@@ -709,9 +732,9 @@ export async function updateListing(
|
||||
AND uc.is_main = TRUE
|
||||
WHERE l.listing_id = $1
|
||||
`,
|
||||
[listingId]
|
||||
[listingId],
|
||||
);
|
||||
|
||||
|
||||
if (joinResult.rows.length > 0) {
|
||||
result.rows[0] = joinResult.rows[0];
|
||||
}
|
||||
@@ -742,7 +765,7 @@ export async function deleteListing(listingId: number): Promise<boolean> {
|
||||
SET listing_status = 'cancelled'
|
||||
WHERE listing_id = $1
|
||||
`,
|
||||
[listingId]
|
||||
[listingId],
|
||||
);
|
||||
|
||||
return (result.rowCount ?? 0) > 0;
|
||||
@@ -758,7 +781,7 @@ export async function incrementViews(listingId: number): Promise<void> {
|
||||
SET views_count = views_count + 1
|
||||
WHERE listing_id = $1
|
||||
`,
|
||||
[listingId]
|
||||
[listingId],
|
||||
);
|
||||
}
|
||||
|
||||
@@ -767,7 +790,11 @@ export async function incrementViews(listingId: number): Promise<void> {
|
||||
*/
|
||||
export async function getListingsBySeller(
|
||||
sellerId: number,
|
||||
filters: { status?: Listing["listing_status"]; page?: number; pageSize?: number } = {}
|
||||
filters: {
|
||||
status?: Listing["listing_status"];
|
||||
page?: number;
|
||||
pageSize?: number;
|
||||
} = {},
|
||||
): Promise<{ listings: Listing[]; total: number }> {
|
||||
return getListings({
|
||||
seller_id: sellerId,
|
||||
@@ -779,7 +806,7 @@ export async function getListingsBySeller(
|
||||
* Busca apenas anúncios ativos
|
||||
*/
|
||||
export async function getActiveListings(
|
||||
filters: Omit<ListingFilters, "status"> = {}
|
||||
filters: Omit<ListingFilters, "status"> = {},
|
||||
): Promise<{ listings: Listing[]; total: number }> {
|
||||
return getListings({
|
||||
...filters,
|
||||
@@ -795,8 +822,13 @@ export async function placeBid(
|
||||
listingId: number,
|
||||
bidderId: number,
|
||||
bidAmount: number,
|
||||
paymentMethod: "pix" | "tc" | "gp"
|
||||
): Promise<{ success: boolean; listing: Listing | null; previousBidderId: number | null; error?: string }> {
|
||||
_paymentMethod: "pix" | "tc" | "gp",
|
||||
): Promise<{
|
||||
success: boolean;
|
||||
listing: Listing | null;
|
||||
previousBidderId: number | null;
|
||||
error?: string;
|
||||
}> {
|
||||
const { getPool } = await import("../index");
|
||||
const pool = getPool();
|
||||
const client = await pool.connect();
|
||||
@@ -827,12 +859,17 @@ export async function placeBid(
|
||||
LEFT JOIN items i ON i.item_id = l.item_id_unified
|
||||
WHERE l.listing_id = $1
|
||||
`,
|
||||
[listingId]
|
||||
[listingId],
|
||||
);
|
||||
|
||||
if (listingResult.rows.length === 0) {
|
||||
await client.query("ROLLBACK");
|
||||
return { success: false, listing: null, previousBidderId: null, error: "Anúncio não encontrado" };
|
||||
return {
|
||||
success: false,
|
||||
listing: null,
|
||||
previousBidderId: null,
|
||||
error: "Anúncio não encontrado",
|
||||
};
|
||||
}
|
||||
|
||||
const listing = listingResult.rows[0];
|
||||
@@ -840,22 +877,45 @@ export async function placeBid(
|
||||
// Validações
|
||||
if (listing.listing_status !== "active") {
|
||||
await client.query("ROLLBACK");
|
||||
return { success: false, listing: null, previousBidderId: null, error: "Anúncio não está ativo" };
|
||||
return {
|
||||
success: false,
|
||||
listing: null,
|
||||
previousBidderId: null,
|
||||
error: "Anúncio não está ativo",
|
||||
};
|
||||
}
|
||||
|
||||
if (!listing.is_auction) {
|
||||
await client.query("ROLLBACK");
|
||||
return { success: false, listing: null, previousBidderId: null, error: "Este anúncio não é um leilão" };
|
||||
return {
|
||||
success: false,
|
||||
listing: null,
|
||||
previousBidderId: null,
|
||||
error: "Este anúncio não é um leilão",
|
||||
};
|
||||
}
|
||||
|
||||
if (listing.seller_id === bidderId) {
|
||||
await client.query("ROLLBACK");
|
||||
return { success: false, listing: null, previousBidderId: null, error: "Você não pode dar lance no seu próprio anúncio" };
|
||||
return {
|
||||
success: false,
|
||||
listing: null,
|
||||
previousBidderId: null,
|
||||
error: "Você não pode dar lance no seu próprio anúncio",
|
||||
};
|
||||
}
|
||||
|
||||
if (listing.auction_ends_at && new Date(listing.auction_ends_at) < new Date()) {
|
||||
if (
|
||||
listing.auction_ends_at &&
|
||||
new Date(listing.auction_ends_at) < new Date()
|
||||
) {
|
||||
await client.query("ROLLBACK");
|
||||
return { success: false, listing: null, previousBidderId: null, error: "Leilão expirou" };
|
||||
return {
|
||||
success: false,
|
||||
listing: null,
|
||||
previousBidderId: null,
|
||||
error: "Leilão expirou",
|
||||
};
|
||||
}
|
||||
|
||||
// Calcular lance mínimo
|
||||
@@ -886,7 +946,7 @@ export async function placeBid(
|
||||
updated_at = CURRENT_TIMESTAMP
|
||||
WHERE listing_id = $3
|
||||
`,
|
||||
[bidAmount, bidderId, listingId]
|
||||
[bidAmount, bidderId, listingId],
|
||||
);
|
||||
|
||||
// Buscar listing atualizado com JOIN
|
||||
@@ -912,7 +972,7 @@ export async function placeBid(
|
||||
LEFT JOIN items i ON i.item_id = l.item_id_unified
|
||||
WHERE l.listing_id = $1
|
||||
`,
|
||||
[listingId]
|
||||
[listingId],
|
||||
);
|
||||
|
||||
await client.query("COMMIT");
|
||||
@@ -929,4 +989,3 @@ export async function placeBid(
|
||||
client.release();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,13 @@ import { query } from "../index";
|
||||
export interface Notification {
|
||||
notification_id: number;
|
||||
user_id: number;
|
||||
notification_type: "bid" | "sale" | "purchase" | "escrow" | "dispute" | "system";
|
||||
notification_type:
|
||||
| "bid"
|
||||
| "sale"
|
||||
| "purchase"
|
||||
| "escrow"
|
||||
| "dispute"
|
||||
| "system";
|
||||
title: string;
|
||||
message: string;
|
||||
related_id: number | null;
|
||||
@@ -31,7 +37,7 @@ export interface CreateNotificationData {
|
||||
* Cria uma nova notificação
|
||||
*/
|
||||
export async function createNotification(
|
||||
data: CreateNotificationData
|
||||
data: CreateNotificationData,
|
||||
): Promise<Notification> {
|
||||
const result = await query<Notification>(
|
||||
`
|
||||
@@ -46,7 +52,7 @@ export async function createNotification(
|
||||
data.message,
|
||||
data.related_id || null,
|
||||
data.send_whatsapp || false,
|
||||
]
|
||||
],
|
||||
);
|
||||
|
||||
return result.rows[0];
|
||||
@@ -57,7 +63,7 @@ export async function createNotification(
|
||||
*/
|
||||
export async function getUserNotifications(
|
||||
userId: number,
|
||||
filters: { is_read?: boolean; limit?: number } = {}
|
||||
filters: { is_read?: boolean; limit?: number } = {},
|
||||
): Promise<Notification[]> {
|
||||
const { is_read, limit = 50 } = filters;
|
||||
|
||||
@@ -71,7 +77,8 @@ export async function getUserNotifications(
|
||||
paramIndex++;
|
||||
}
|
||||
|
||||
const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
||||
const whereClause =
|
||||
conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
||||
|
||||
const result = await query<Notification>(
|
||||
`
|
||||
@@ -80,7 +87,7 @@ export async function getUserNotifications(
|
||||
ORDER BY created_at DESC
|
||||
LIMIT $${paramIndex}
|
||||
`,
|
||||
[...params, limit]
|
||||
[...params, limit],
|
||||
);
|
||||
|
||||
return result.rows;
|
||||
@@ -95,7 +102,7 @@ export async function getUnreadCount(userId: number): Promise<number> {
|
||||
SELECT COUNT(*) as count FROM notifications
|
||||
WHERE user_id = $1 AND is_read = FALSE
|
||||
`,
|
||||
[userId]
|
||||
[userId],
|
||||
);
|
||||
|
||||
return parseInt(result.rows[0].count, 10);
|
||||
@@ -104,14 +111,17 @@ 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 query(
|
||||
`
|
||||
UPDATE notifications
|
||||
SET is_read = TRUE
|
||||
WHERE notification_id = $1 AND user_id = $2
|
||||
`,
|
||||
[notificationId, userId]
|
||||
[notificationId, userId],
|
||||
);
|
||||
|
||||
return (result.rowCount ?? 0) > 0;
|
||||
@@ -127,7 +137,7 @@ export async function markAllAsRead(userId: number): Promise<number> {
|
||||
SET is_read = TRUE
|
||||
WHERE user_id = $1 AND is_read = FALSE
|
||||
`,
|
||||
[userId]
|
||||
[userId],
|
||||
);
|
||||
|
||||
return result.rowCount || 0;
|
||||
@@ -136,15 +146,17 @@ 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> {
|
||||
const result = await query(
|
||||
`
|
||||
DELETE FROM notifications
|
||||
WHERE notification_id = $1 AND user_id = $2
|
||||
`,
|
||||
[notificationId, userId]
|
||||
[notificationId, userId],
|
||||
);
|
||||
|
||||
return (result.rowCount ?? 0) > 0;
|
||||
}
|
||||
|
||||
+17
-14
@@ -37,7 +37,7 @@ export interface ServerStats {
|
||||
*/
|
||||
export async function getAllServers(): Promise<Server[]> {
|
||||
const result = await query<Server>(
|
||||
`SELECT * FROM servers ORDER BY server_name ASC`
|
||||
`SELECT * FROM servers ORDER BY server_name ASC`,
|
||||
);
|
||||
return result.rows;
|
||||
}
|
||||
@@ -47,7 +47,7 @@ export async function getAllServers(): Promise<Server[]> {
|
||||
*/
|
||||
export async function getActiveServers(): Promise<Server[]> {
|
||||
const result = await query<Server>(
|
||||
`SELECT * FROM servers WHERE is_active = TRUE ORDER BY server_name ASC`
|
||||
`SELECT * FROM servers WHERE is_active = TRUE ORDER BY server_name ASC`,
|
||||
);
|
||||
return result.rows;
|
||||
}
|
||||
@@ -55,10 +55,12 @@ export async function getActiveServers(): Promise<Server[]> {
|
||||
/**
|
||||
* Busca servidor por nome
|
||||
*/
|
||||
export async function getServerByName(serverName: string): Promise<Server | null> {
|
||||
export async function getServerByName(
|
||||
serverName: string,
|
||||
): Promise<Server | null> {
|
||||
const result = await query<Server>(
|
||||
`SELECT * FROM servers WHERE server_name = $1`,
|
||||
[serverName]
|
||||
[serverName],
|
||||
);
|
||||
return result.rows[0] || null;
|
||||
}
|
||||
@@ -69,7 +71,7 @@ export async function getServerByName(serverName: string): Promise<Server | null
|
||||
export async function getServerById(serverId: number): Promise<Server | null> {
|
||||
const result = await query<Server>(
|
||||
`SELECT * FROM servers WHERE server_id = $1`,
|
||||
[serverId]
|
||||
[serverId],
|
||||
);
|
||||
return result.rows[0] || null;
|
||||
}
|
||||
@@ -80,9 +82,9 @@ export async function getServerById(serverId: number): Promise<Server | null> {
|
||||
export async function isServerActive(serverName: string): Promise<boolean> {
|
||||
const result = await query<{ count: string }>(
|
||||
`SELECT COUNT(*) as count FROM servers WHERE server_name = $1 AND is_active = TRUE`,
|
||||
[serverName]
|
||||
[serverName],
|
||||
);
|
||||
return parseInt(result.rows[0].count) > 0;
|
||||
return parseInt(result.rows[0].count, 10) > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -96,7 +98,7 @@ export async function incrementServerStats(
|
||||
total_subscribers: number;
|
||||
total_transactions: number;
|
||||
total_volume: number;
|
||||
}>
|
||||
}>,
|
||||
): Promise<void> {
|
||||
const updates: string[] = [];
|
||||
const values: any[] = [];
|
||||
@@ -133,7 +135,7 @@ export async function incrementServerStats(
|
||||
|
||||
await query(
|
||||
`UPDATE servers SET ${updates.join(", ")} WHERE server_name = $${paramIndex}`,
|
||||
values
|
||||
values,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -142,7 +144,7 @@ export async function incrementServerStats(
|
||||
*/
|
||||
export async function updateServerStats(
|
||||
serverName: string,
|
||||
stats: ServerStats
|
||||
stats: ServerStats,
|
||||
): Promise<void> {
|
||||
const updates: string[] = [];
|
||||
const values: any[] = [];
|
||||
@@ -185,14 +187,16 @@ export async function updateServerStats(
|
||||
|
||||
await query(
|
||||
`UPDATE servers SET ${updates.join(", ")} WHERE server_name = $${paramIndex}`,
|
||||
values
|
||||
values,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
@@ -202,7 +206,6 @@ export async function incrementUsersForServers(serverNames: string[]): Promise<v
|
||||
SET total_users = total_users + 1,
|
||||
last_activity_at = CURRENT_TIMESTAMP
|
||||
WHERE server_name = ANY($1::text[])`,
|
||||
[serverNames]
|
||||
[serverNames],
|
||||
);
|
||||
}
|
||||
|
||||
+45
-45
@@ -96,7 +96,7 @@ export interface MarketplaceStats {
|
||||
* Registra um evento de telemetria
|
||||
*/
|
||||
export async function trackEvent(
|
||||
data: TrackEventData
|
||||
data: TrackEventData,
|
||||
): Promise<TelemetryEvent> {
|
||||
const result = await query<TelemetryEvent>(
|
||||
`
|
||||
@@ -112,7 +112,7 @@ export async function trackEvent(
|
||||
data.user_id || null,
|
||||
data.session_id || null,
|
||||
JSON.stringify(data.metadata || {}),
|
||||
]
|
||||
],
|
||||
);
|
||||
|
||||
return result.rows[0];
|
||||
@@ -122,7 +122,7 @@ 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 [];
|
||||
|
||||
@@ -132,14 +132,14 @@ export async function trackEventsBatch(
|
||||
|
||||
for (const event of events) {
|
||||
placeholders.push(
|
||||
`($${paramIndex}, $${paramIndex + 1}, $${paramIndex + 2}, $${paramIndex + 3}, $${paramIndex + 4})`
|
||||
`($${paramIndex}, $${paramIndex + 1}, $${paramIndex + 2}, $${paramIndex + 3}, $${paramIndex + 4})`,
|
||||
);
|
||||
values.push(
|
||||
event.event_type,
|
||||
event.ad_id || null,
|
||||
event.user_id || null,
|
||||
event.session_id || null,
|
||||
JSON.stringify(event.metadata || {})
|
||||
JSON.stringify(event.metadata || {}),
|
||||
);
|
||||
paramIndex += 5;
|
||||
}
|
||||
@@ -152,7 +152,7 @@ export async function trackEventsBatch(
|
||||
VALUES ${placeholders.join(", ")}
|
||||
RETURNING *
|
||||
`,
|
||||
values
|
||||
values,
|
||||
);
|
||||
|
||||
return result.rows;
|
||||
@@ -161,13 +161,15 @@ 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 result = await query<AdAnalytics>(
|
||||
`
|
||||
SELECT * FROM ad_analytics
|
||||
WHERE ad_id = $1
|
||||
`,
|
||||
[adId]
|
||||
[adId],
|
||||
);
|
||||
|
||||
return result.rows[0] || null;
|
||||
@@ -179,21 +181,20 @@ 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 userCheck = await query<{ user_id: number }>(
|
||||
"SELECT user_id FROM users WHERE user_id = $1",
|
||||
[userId]
|
||||
[userId],
|
||||
);
|
||||
|
||||
if (userCheck.rows.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const dateFilter = startDate && endDate
|
||||
? "AND timestamp BETWEEN $2 AND $3"
|
||||
: "";
|
||||
const dateFilter =
|
||||
startDate && endDate ? "AND timestamp BETWEEN $2 AND $3" : "";
|
||||
|
||||
const params: any[] = [userId];
|
||||
if (startDate && endDate) {
|
||||
@@ -207,7 +208,7 @@ export async function getUserBehavior(
|
||||
FROM telemetry_events
|
||||
WHERE user_id = $1 AND event_type = 'search' ${dateFilter}
|
||||
`,
|
||||
params
|
||||
params,
|
||||
);
|
||||
|
||||
// Buscar filtros aplicados
|
||||
@@ -217,7 +218,7 @@ export async function getUserBehavior(
|
||||
FROM telemetry_events
|
||||
WHERE user_id = $1 AND event_type = 'filter' ${dateFilter}
|
||||
`,
|
||||
params
|
||||
params,
|
||||
);
|
||||
|
||||
// Buscar itens visualizados
|
||||
@@ -232,7 +233,7 @@ export async function getUserBehavior(
|
||||
WHERE user_id = $1 AND event_type = 'ad_view' AND ad_id IS NOT NULL ${dateFilter}
|
||||
ORDER BY timestamp DESC
|
||||
`,
|
||||
params
|
||||
params,
|
||||
);
|
||||
|
||||
// Buscar favoritos
|
||||
@@ -242,7 +243,7 @@ export async function getUserBehavior(
|
||||
FROM telemetry_events
|
||||
WHERE user_id = $1 AND event_type = 'favorite' AND ad_id IS NOT NULL ${dateFilter}
|
||||
`,
|
||||
params
|
||||
params,
|
||||
);
|
||||
|
||||
// Buscar compras
|
||||
@@ -257,7 +258,7 @@ export async function getUserBehavior(
|
||||
WHERE user_id = $1 AND event_type = 'purchase' AND ad_id IS NOT NULL ${dateFilter}
|
||||
ORDER BY timestamp DESC
|
||||
`,
|
||||
params
|
||||
params,
|
||||
);
|
||||
|
||||
// Buscar horas ativas
|
||||
@@ -272,18 +273,18 @@ export async function getUserBehavior(
|
||||
GROUP BY hour
|
||||
ORDER BY hour
|
||||
`,
|
||||
params
|
||||
params,
|
||||
);
|
||||
|
||||
// Buscar item_id dos anúncios visualizados
|
||||
const itemIds = await query<{ item_id: number }>(
|
||||
const _itemIds = await query<{ item_id: number }>(
|
||||
`
|
||||
SELECT DISTINCT l.item_id
|
||||
FROM telemetry_events te
|
||||
JOIN listings l ON l.listing_id = te.ad_id
|
||||
WHERE te.user_id = $1 AND te.event_type = 'ad_view' AND te.ad_id IS NOT NULL ${dateFilter}
|
||||
`,
|
||||
params
|
||||
params,
|
||||
);
|
||||
|
||||
return {
|
||||
@@ -317,11 +318,10 @@ export async function getUserBehavior(
|
||||
*/
|
||||
export async function getMarketplaceStats(
|
||||
startDate?: Date,
|
||||
endDate?: Date
|
||||
endDate?: Date,
|
||||
): Promise<MarketplaceStats> {
|
||||
const dateFilter = startDate && endDate
|
||||
? "WHERE timestamp BETWEEN $1 AND $2"
|
||||
: "";
|
||||
const dateFilter =
|
||||
startDate && endDate ? "WHERE timestamp BETWEEN $1 AND $2" : "";
|
||||
const params = startDate && endDate ? [startDate, endDate] : [];
|
||||
|
||||
// Total de transações
|
||||
@@ -331,7 +331,7 @@ export async function getMarketplaceStats(
|
||||
FROM telemetry_events
|
||||
WHERE event_type = 'purchase' ${dateFilter.replace("timestamp", "timestamp")}
|
||||
`,
|
||||
params
|
||||
params,
|
||||
);
|
||||
|
||||
// Preço médio por categoria (requer join com listings)
|
||||
@@ -349,7 +349,7 @@ export async function getMarketplaceStats(
|
||||
GROUP BY l.item_name
|
||||
LIMIT 10
|
||||
`,
|
||||
params
|
||||
params,
|
||||
);
|
||||
|
||||
// Taxa de conversão
|
||||
@@ -359,7 +359,7 @@ export async function getMarketplaceStats(
|
||||
FROM telemetry_events
|
||||
WHERE event_type = 'ad_view' ${dateFilter}
|
||||
`,
|
||||
params
|
||||
params,
|
||||
);
|
||||
|
||||
const purchases = await query<{ count: string }>(
|
||||
@@ -368,12 +368,13 @@ export async function getMarketplaceStats(
|
||||
FROM telemetry_events
|
||||
WHERE event_type = 'purchase' ${dateFilter}
|
||||
`,
|
||||
params
|
||||
params,
|
||||
);
|
||||
|
||||
const viewsCount = parseInt(views.rows[0]?.count || "0", 10);
|
||||
const purchasesCount = parseInt(purchases.rows[0]?.count || "0", 10);
|
||||
const conversionRate = viewsCount > 0 ? (purchasesCount / viewsCount) * 100 : 0;
|
||||
const conversionRate =
|
||||
viewsCount > 0 ? (purchasesCount / viewsCount) * 100 : 0;
|
||||
|
||||
// Itens mais procurados
|
||||
const mostSearched = await query<{
|
||||
@@ -390,7 +391,7 @@ export async function getMarketplaceStats(
|
||||
ORDER BY COUNT(*) DESC
|
||||
LIMIT 10
|
||||
`,
|
||||
params
|
||||
params,
|
||||
);
|
||||
|
||||
// Itens mais vendidos
|
||||
@@ -411,7 +412,7 @@ export async function getMarketplaceStats(
|
||||
ORDER BY COUNT(*) DESC
|
||||
LIMIT 10
|
||||
`,
|
||||
params
|
||||
params,
|
||||
);
|
||||
|
||||
// Horários de pico
|
||||
@@ -429,7 +430,7 @@ export async function getMarketplaceStats(
|
||||
ORDER BY COUNT(*) DESC
|
||||
LIMIT 10
|
||||
`,
|
||||
params
|
||||
params,
|
||||
);
|
||||
|
||||
// Tempo médio para venda
|
||||
@@ -441,7 +442,7 @@ export async function getMarketplaceStats(
|
||||
FROM ad_analytics aa
|
||||
WHERE aa.time_to_sell IS NOT NULL
|
||||
`,
|
||||
[]
|
||||
[],
|
||||
);
|
||||
|
||||
return {
|
||||
@@ -451,7 +452,7 @@ export async function getMarketplaceStats(
|
||||
acc[r.category] = parseFloat(r.avg_price);
|
||||
return acc;
|
||||
},
|
||||
{} as Record<string, number>
|
||||
{} as Record<string, number>,
|
||||
),
|
||||
conversion_rate: conversionRate,
|
||||
most_searched_items: mostSearched.rows.map((r) => ({
|
||||
@@ -493,7 +494,7 @@ export async function aggregateHourlyMetrics(): Promise<void> {
|
||||
ON CONFLICT (ad_id) DO UPDATE SET
|
||||
impressions = ad_analytics.impressions + EXCLUDED.impressions,
|
||||
last_updated = CURRENT_TIMESTAMP
|
||||
`
|
||||
`,
|
||||
);
|
||||
|
||||
// Agregar cliques
|
||||
@@ -511,7 +512,7 @@ export async function aggregateHourlyMetrics(): Promise<void> {
|
||||
GROUP BY ad_id
|
||||
) sub
|
||||
WHERE aa.ad_id = sub.ad_id
|
||||
`
|
||||
`,
|
||||
);
|
||||
|
||||
// Atualizar CTR (Click-Through Rate)
|
||||
@@ -524,7 +525,7 @@ export async function aggregateHourlyMetrics(): Promise<void> {
|
||||
END,
|
||||
last_updated = CURRENT_TIMESTAMP
|
||||
WHERE impressions > 0
|
||||
`
|
||||
`,
|
||||
);
|
||||
|
||||
// Agregar duração média de visualização
|
||||
@@ -550,7 +551,7 @@ export async function aggregateHourlyMetrics(): Promise<void> {
|
||||
AND ad_id = aa.ad_id
|
||||
AND timestamp >= NOW() - INTERVAL '1 hour'
|
||||
)
|
||||
`
|
||||
`,
|
||||
);
|
||||
|
||||
// Calcular bounce rate (views com duração < 3 segundos)
|
||||
@@ -572,7 +573,7 @@ export async function aggregateHourlyMetrics(): Promise<void> {
|
||||
END,
|
||||
last_updated = CURRENT_TIMESTAMP
|
||||
WHERE aa.impressions > 0
|
||||
`
|
||||
`,
|
||||
);
|
||||
|
||||
// Agregar inquiries
|
||||
@@ -590,7 +591,7 @@ export async function aggregateHourlyMetrics(): Promise<void> {
|
||||
GROUP BY ad_id
|
||||
) sub
|
||||
WHERE aa.ad_id = sub.ad_id
|
||||
`
|
||||
`,
|
||||
);
|
||||
|
||||
// Agregar favoritos
|
||||
@@ -608,7 +609,7 @@ export async function aggregateHourlyMetrics(): Promise<void> {
|
||||
GROUP BY ad_id
|
||||
) sub
|
||||
WHERE aa.ad_id = sub.ad_id
|
||||
`
|
||||
`,
|
||||
);
|
||||
|
||||
// Calcular taxa de conversão
|
||||
@@ -634,7 +635,7 @@ export async function aggregateHourlyMetrics(): Promise<void> {
|
||||
AND ad_id = aa.ad_id
|
||||
AND timestamp >= NOW() - INTERVAL '1 hour'
|
||||
)
|
||||
`
|
||||
`,
|
||||
);
|
||||
|
||||
// Calcular time_to_sell para anúncios vendidos
|
||||
@@ -650,7 +651,6 @@ export async function aggregateHourlyMetrics(): Promise<void> {
|
||||
AND l.sold_at IS NOT NULL
|
||||
AND aa.time_to_sell IS NULL
|
||||
AND l.sold_at >= NOW() - INTERVAL '1 hour'
|
||||
`
|
||||
`,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -61,7 +61,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));
|
||||
@@ -85,7 +85,7 @@ export async function createTransaction(
|
||||
data.amount,
|
||||
data.payment_method,
|
||||
escrowFee,
|
||||
]
|
||||
],
|
||||
);
|
||||
|
||||
return result.rows[0];
|
||||
@@ -95,13 +95,13 @@ export async function createTransaction(
|
||||
* Busca transação por ID
|
||||
*/
|
||||
export async function getTransactionById(
|
||||
transactionId: number
|
||||
transactionId: number,
|
||||
): Promise<Transaction | null> {
|
||||
const result = await query<Transaction>(
|
||||
`
|
||||
SELECT * FROM transactions WHERE transaction_id = $1
|
||||
`,
|
||||
[transactionId]
|
||||
[transactionId],
|
||||
);
|
||||
|
||||
return result.rows[0] || null;
|
||||
@@ -112,7 +112,7 @@ export async function getTransactionById(
|
||||
*/
|
||||
export async function updateTransactionStatus(
|
||||
transactionId: number,
|
||||
data: UpdateTransactionStatusData
|
||||
data: UpdateTransactionStatusData,
|
||||
): Promise<Transaction | null> {
|
||||
const updates: string[] = [];
|
||||
const params: any[] = [];
|
||||
@@ -172,7 +172,7 @@ export async function updateTransactionStatus(
|
||||
WHERE transaction_id = $${paramIndex}
|
||||
RETURNING *
|
||||
`,
|
||||
params
|
||||
params,
|
||||
);
|
||||
|
||||
return result.rows[0] || null;
|
||||
@@ -188,7 +188,7 @@ export async function getTransactionsByUser(
|
||||
status?: Transaction["transaction_status"];
|
||||
page?: number;
|
||||
pageSize?: number;
|
||||
} = {}
|
||||
} = {},
|
||||
): Promise<{ transactions: Transaction[]; total: number }> {
|
||||
const { role = "both", status, page = 1, pageSize = 20 } = filters;
|
||||
|
||||
@@ -201,7 +201,9 @@ export async function getTransactionsByUser(
|
||||
} else if (role === "seller") {
|
||||
conditions.push(`seller_id = $${paramIndex}`);
|
||||
} else {
|
||||
conditions.push(`(buyer_id = $${paramIndex} OR seller_id = $${paramIndex})`);
|
||||
conditions.push(
|
||||
`(buyer_id = $${paramIndex} OR seller_id = $${paramIndex})`,
|
||||
);
|
||||
}
|
||||
params.push(userId);
|
||||
paramIndex++;
|
||||
@@ -212,12 +214,13 @@ export async function getTransactionsByUser(
|
||||
paramIndex++;
|
||||
}
|
||||
|
||||
const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
||||
const whereClause =
|
||||
conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
||||
|
||||
// Contar total
|
||||
const countResult = await query<{ count: string }>(
|
||||
`SELECT COUNT(*) as count FROM transactions ${whereClause}`,
|
||||
params
|
||||
params,
|
||||
);
|
||||
const total = parseInt(countResult.rows[0].count, 10);
|
||||
|
||||
@@ -230,7 +233,7 @@ export async function getTransactionsByUser(
|
||||
ORDER BY created_at DESC
|
||||
LIMIT $${paramIndex} OFFSET $${paramIndex + 1}
|
||||
`,
|
||||
[...params, pageSize, offset]
|
||||
[...params, pageSize, offset],
|
||||
);
|
||||
|
||||
return {
|
||||
@@ -238,4 +241,3 @@ export async function getTransactionsByUser(
|
||||
total,
|
||||
};
|
||||
}
|
||||
|
||||
+15
-12
@@ -45,9 +45,13 @@ export interface UpdateUserData {
|
||||
* Cria um novo usuário
|
||||
*/
|
||||
export async function createUser(
|
||||
data: CreateUserData
|
||||
data: CreateUserData,
|
||||
): Promise<{ userId: number; username: string; email: string }> {
|
||||
const result = await query<{ user_id: number; username: string; email: string }>(
|
||||
const result = await query<{
|
||||
user_id: number;
|
||||
username: string;
|
||||
email: string;
|
||||
}>(
|
||||
`
|
||||
INSERT INTO users (username, email, password_hash, first_name, last_name, phone, cpf)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7)
|
||||
@@ -61,7 +65,7 @@ export async function createUser(
|
||||
data.last_name,
|
||||
data.phone || null,
|
||||
data.cpf || null,
|
||||
]
|
||||
],
|
||||
);
|
||||
|
||||
return {
|
||||
@@ -79,7 +83,7 @@ export async function getUserByEmail(email: string): Promise<User | null> {
|
||||
`
|
||||
SELECT * FROM users WHERE email = $1
|
||||
`,
|
||||
[email]
|
||||
[email],
|
||||
);
|
||||
|
||||
return result.rows[0] || null;
|
||||
@@ -89,13 +93,13 @@ export async function getUserByEmail(email: string): Promise<User | null> {
|
||||
* Busca usuário por username
|
||||
*/
|
||||
export async function getUserByUsername(
|
||||
username: string
|
||||
username: string,
|
||||
): Promise<User | null> {
|
||||
const result = await query<User>(
|
||||
`
|
||||
SELECT * FROM users WHERE username = $1
|
||||
`,
|
||||
[username]
|
||||
[username],
|
||||
);
|
||||
|
||||
return result.rows[0] || null;
|
||||
@@ -114,7 +118,7 @@ export async function getUserById(userId: number): Promise<User | null> {
|
||||
FROM users
|
||||
WHERE user_id = $1
|
||||
`,
|
||||
[userId]
|
||||
[userId],
|
||||
);
|
||||
|
||||
return result.rows[0] || null;
|
||||
@@ -128,7 +132,7 @@ export async function getUserByCpf(cpf: string): Promise<User | null> {
|
||||
`
|
||||
SELECT user_id, username, email FROM users WHERE cpf = $1
|
||||
`,
|
||||
[cpf]
|
||||
[cpf],
|
||||
);
|
||||
|
||||
return result.rows[0] || null;
|
||||
@@ -139,7 +143,7 @@ export async function getUserByCpf(cpf: string): Promise<User | null> {
|
||||
*/
|
||||
export async function updateUser(
|
||||
userId: number,
|
||||
data: UpdateUserData
|
||||
data: UpdateUserData,
|
||||
): Promise<User> {
|
||||
const updates: string[] = [];
|
||||
const values: any[] = [];
|
||||
@@ -187,7 +191,7 @@ export async function updateUser(
|
||||
account_level, has_donated, whatsapp_notifications,
|
||||
created_at, updated_at, last_login, is_active, is_verified
|
||||
`,
|
||||
values
|
||||
values,
|
||||
);
|
||||
|
||||
if (result.rows.length === 0) {
|
||||
@@ -207,7 +211,6 @@ export async function updateLastLogin(userId: number): Promise<void> {
|
||||
SET last_login = CURRENT_TIMESTAMP
|
||||
WHERE user_id = $1
|
||||
`,
|
||||
[userId]
|
||||
[userId],
|
||||
);
|
||||
}
|
||||
|
||||
@@ -3,9 +3,9 @@
|
||||
* Lista completa de servidores oficiais
|
||||
*/
|
||||
|
||||
import { resolve } from "node:path";
|
||||
// Carregar variáveis de ambiente
|
||||
import { config } from "dotenv";
|
||||
import { resolve } from "path";
|
||||
|
||||
// Tentar carregar .env.local primeiro, depois .env
|
||||
config({ path: resolve(process.cwd(), ".env.local") });
|
||||
@@ -118,7 +118,7 @@ export async function seedServers() {
|
||||
// Verificar se já existe
|
||||
const existing = await query<{ server_id: number }>(
|
||||
"SELECT server_id FROM servers WHERE server_name = $1",
|
||||
[server.server_name]
|
||||
[server.server_name],
|
||||
);
|
||||
|
||||
if (existing.rows.length > 0) {
|
||||
@@ -130,16 +130,21 @@ export async function seedServers() {
|
||||
await query(
|
||||
`INSERT INTO servers (server_name, server_type, location, is_active)
|
||||
VALUES ($1, $2, $3, $4)`,
|
||||
[server.server_name, server.server_type, server.location, true]
|
||||
[server.server_name, server.server_type, server.location, true],
|
||||
);
|
||||
|
||||
inserted++;
|
||||
} catch (error) {
|
||||
console.error(`❌ Erro ao inserir servidor ${server.server_name}:`, error);
|
||||
console.error(
|
||||
`❌ Erro ao inserir servidor ${server.server_name}:`,
|
||||
error,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`✅ Seed concluído: ${inserted} servidores inseridos, ${skipped} já existiam\n`);
|
||||
console.log(
|
||||
`✅ Seed concluído: ${inserted} servidores inseridos, ${skipped} já existiam\n`,
|
||||
);
|
||||
}
|
||||
|
||||
// Se executado diretamente
|
||||
@@ -154,4 +159,3 @@ if (require.main === module) {
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -3,15 +3,15 @@
|
||||
* Executa um health check e tenta uma query simples
|
||||
*/
|
||||
|
||||
import { resolve } from "node:path";
|
||||
// Carregar variáveis de ambiente
|
||||
import { config } from "dotenv";
|
||||
import { resolve } from "path";
|
||||
|
||||
// Tentar carregar .env.local primeiro, depois .env
|
||||
config({ path: resolve(process.cwd(), ".env.local") });
|
||||
config({ path: resolve(process.cwd(), ".env") });
|
||||
|
||||
import { healthCheck, query, closePool } from "./index";
|
||||
import { closePool, healthCheck, query } from "./index";
|
||||
|
||||
async function testConnection() {
|
||||
console.log("🔍 Testando conexão com PostgreSQL...\n");
|
||||
@@ -27,7 +27,9 @@ async function testConnection() {
|
||||
}
|
||||
|
||||
console.log("✅ Variável PG_DB encontrada");
|
||||
console.log(` Host: ${process.env.PG_DB.split("@")[1]?.split("/")[0] || "não detectado"}\n`);
|
||||
console.log(
|
||||
` Host: ${process.env.PG_DB.split("@")[1]?.split("/")[0] || "não detectado"}\n`,
|
||||
);
|
||||
|
||||
try {
|
||||
// Health check
|
||||
@@ -44,9 +46,11 @@ async function testConnection() {
|
||||
// Query de teste
|
||||
console.log("📊 Executando query de teste...");
|
||||
const result = await query("SELECT version(), NOW() as current_time");
|
||||
|
||||
|
||||
console.log("✅ Query executada com sucesso");
|
||||
console.log(` PostgreSQL Version: ${result.rows[0]?.version?.split(" ")[0] || "N/A"}`);
|
||||
console.log(
|
||||
` PostgreSQL Version: ${result.rows[0]?.version?.split(" ")[0] || "N/A"}`,
|
||||
);
|
||||
console.log(` Current Time: ${result.rows[0]?.current_time || "N/A"}\n`);
|
||||
|
||||
// Verificar se as tabelas existem
|
||||
@@ -76,14 +80,14 @@ async function testConnection() {
|
||||
} catch (error) {
|
||||
console.error("❌ Erro ao conectar com PostgreSQL:");
|
||||
console.error(error instanceof Error ? error.message : String(error));
|
||||
|
||||
|
||||
if (error instanceof Error && error.message.includes("timeout")) {
|
||||
console.log("\n💡 Verifique:");
|
||||
console.log(" - O servidor PostgreSQL está acessível?");
|
||||
console.log(" - A URL de conexão está correta?");
|
||||
console.log(" - O firewall permite conexões?");
|
||||
}
|
||||
|
||||
|
||||
if (error instanceof Error && error.message.includes("authentication")) {
|
||||
console.log("\n💡 Verifique:");
|
||||
console.log(" - Usuário e senha estão corretos?");
|
||||
@@ -96,4 +100,3 @@ async function testConnection() {
|
||||
}
|
||||
|
||||
testConnection();
|
||||
|
||||
Reference in new issue
Block a user