/**
* Queries de listings (anúncios)
* CRUD completo para a tabela listings
*/
import { query } from "../index";
export interface Listing {
listing_id: number;
seller_id: number;
item_id: number;
item_name: string;
quantity: number;
server_name: string;
price_pix: number | null;
price_tibia_coins: number | null;
price_gold_coins: number | null;
is_auction: boolean;
current_bid_amount: number | null;
current_bidder_id: number | null;
auction_ends_at: Date | null;
description: string | null;
listing_status: "active" | "sold" | "cancelled" | "in_escrow" | "expired";
created_at: Date;
updated_at: Date;
sold_at: Date | null;
views_count: number;
}
export interface CreateListingData {
seller_id: number;
item_id: number;
item_name: string;
quantity: number;
server_name: string;
price_pix?: number;
price_tibia_coins?: number;
price_gold_coins?: number;
is_auction?: boolean;
auction_ends_at?: Date;
description?: string;
}
export interface UpdateListingData {
quantity?: number;
price_pix?: number;
price_tibia_coins?: number;
price_gold_coins?: number;
description?: string;
listing_status?: Listing["listing_status"];
current_bid_amount?: number;
current_bidder_id?: number;
}
export interface ListingFilters {
status?: Listing["listing_status"];
server_name?: string;
item_id?: number;
seller_id?: number;
is_auction?: boolean;
search?: string;
page?: number;
pageSize?: number;
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
{
const result = await query(
`
INSERT INTO listings (
seller_id, item_id, item_name, quantity, server_name,
price_pix, price_tibia_coins, price_gold_coins,
is_auction, auction_ends_at, description
)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)
RETURNING *
`,
[
data.seller_id,
data.item_id,
data.item_name,
data.quantity,
data.server_name,
data.price_pix || null,
data.price_tibia_coins || null,
data.price_gold_coins || null,
data.is_auction || false,
data.auction_ends_at || null,
data.description || null,
]
);
return result.rows[0];
}
/**
* Busca anúncio por ID
*/
export async function getListingById(listingId: number): Promise {
const result = await query(
`
SELECT * FROM listings WHERE listing_id = $1
`,
[listingId]
);
return result.rows[0] || null;
}
/**
* Lista anúncios com filtros e paginação
*/
export async function getListings(
filters: ListingFilters = {}
): Promise<{ listings: Listing[]; total: number }> {
const {
status,
server_name,
item_id,
seller_id,
is_auction,
search,
page = 1,
pageSize = 20,
sortBy = "created_at",
sortOrder = "DESC",
} = filters;
const conditions: string[] = [];
const params: any[] = [];
let paramIndex = 1;
if (status) {
conditions.push(`listing_status = $${paramIndex}`);
params.push(status);
paramIndex++;
}
if (server_name) {
conditions.push(`server_name = $${paramIndex}`);
params.push(server_name);
paramIndex++;
}
if (item_id) {
conditions.push(`item_id = $${paramIndex}`);
params.push(item_id);
paramIndex++;
}
if (seller_id) {
conditions.push(`seller_id = $${paramIndex}`);
params.push(seller_id);
paramIndex++;
}
if (is_auction !== undefined) {
conditions.push(`is_auction = $${paramIndex}`);
params.push(is_auction);
paramIndex++;
}
if (search) {
conditions.push(`(item_name ILIKE $${paramIndex} OR description ILIKE $${paramIndex})`);
params.push(`%${search}%`);
paramIndex++;
}
const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
// Contar total
const countResult = await query<{ count: string }>(
`SELECT COUNT(*) as count FROM listings ${whereClause}`,
params
);
const total = parseInt(countResult.rows[0].count, 10);
// Validar sortBy
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";
// Buscar listings
const offset = (page - 1) * pageSize;
const result = await query(
`
SELECT * FROM listings
${whereClause}
ORDER BY ${sortColumn} ${sortDirection}
LIMIT $${paramIndex} OFFSET $${paramIndex + 1}
`,
[...params, pageSize, offset]
);
return {
listings: result.rows,
total,
};
}
/**
* Atualiza um anúncio
*/
export async function updateListing(
listingId: number,
data: UpdateListingData
): Promise {
const updates: string[] = [];
const params: any[] = [];
let paramIndex = 1;
if (data.quantity !== undefined) {
updates.push(`quantity = $${paramIndex}`);
params.push(data.quantity);
paramIndex++;
}
if (data.price_pix !== undefined) {
updates.push(`price_pix = $${paramIndex}`);
params.push(data.price_pix);
paramIndex++;
}
if (data.price_tibia_coins !== undefined) {
updates.push(`price_tibia_coins = $${paramIndex}`);
params.push(data.price_tibia_coins);
paramIndex++;
}
if (data.price_gold_coins !== undefined) {
updates.push(`price_gold_coins = $${paramIndex}`);
params.push(data.price_gold_coins);
paramIndex++;
}
if (data.description !== undefined) {
updates.push(`description = $${paramIndex}`);
params.push(data.description);
paramIndex++;
}
if (data.listing_status !== undefined) {
updates.push(`listing_status = $${paramIndex}`);
params.push(data.listing_status);
paramIndex++;
}
if (data.current_bid_amount !== undefined) {
updates.push(`current_bid_amount = $${paramIndex}`);
params.push(data.current_bid_amount);
paramIndex++;
}
if (data.current_bidder_id !== undefined) {
updates.push(`current_bidder_id = $${paramIndex}`);
params.push(data.current_bidder_id);
paramIndex++;
}
if (updates.length === 0) {
return getListingById(listingId);
}
params.push(listingId);
const result = await query(
`
UPDATE listings
SET ${updates.join(", ")}
WHERE listing_id = $${paramIndex}
RETURNING *
`,
params
);
return result.rows[0] || null;
}
/**
* Cancela um anúncio (soft delete - muda status)
*/
export async function deleteListing(listingId: number): Promise {
const result = await query(
`
UPDATE listings
SET listing_status = 'cancelled'
WHERE listing_id = $1
`,
[listingId]
);
return result.rowCount > 0;
}
/**
* Incrementa contador de views
*/
export async function incrementViews(listingId: number): Promise {
await query(
`
UPDATE listings
SET views_count = views_count + 1
WHERE listing_id = $1
`,
[listingId]
);
}
/**
* Busca anúncios de um vendedor
*/
export async function getListingsBySeller(
sellerId: number,
filters: { status?: Listing["listing_status"]; page?: number; pageSize?: number } = {}
): Promise<{ listings: Listing[]; total: number }> {
return getListings({
seller_id: sellerId,
...filters,
});
}
/**
* Busca apenas anúncios ativos
*/
export async function getActiveListings(
filters: Omit = {}
): Promise<{ listings: Listing[]; total: number }> {
return getListings({
...filters,
status: "active",
});
}