/** * 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", }); }