import MarketPrice from "../models/MarketPrice.js"; /** * Market Price Service * Helper functions to look up prices from the market reference database * Used when loading inventory or updating item prices */ class MarketPriceService { /** * Get price for an item by market hash name (exact match) * @param {string} marketHashName - Steam market hash name * @param {string} game - Game identifier ('cs2' or 'rust') * @returns {Promise} - Price in USD or null if not found */ async getPrice(marketHashName, game = null) { try { const query = { marketHashName }; if (game) query.game = game; const marketItem = await MarketPrice.findOne(query); return marketItem ? marketItem.price : null; } catch (error) { console.error("Error getting price:", error.message); return null; } } /** * Get full item data by market hash name * @param {string} marketHashName - Steam market hash name * @param {string} game - Game identifier ('cs2' or 'rust') * @returns {Promise} - Full market item data or null */ async getItem(marketHashName, game = null) { try { const query = { marketHashName }; if (game) query.game = game; return await MarketPrice.findOne(query); } catch (error) { console.error("Error getting item:", error.message); return null; } } /** * Get prices for multiple items (batch lookup) * @param {Array} marketHashNames - Array of market hash names * @param {string} game - Game identifier ('cs2' or 'rust') * @returns {Promise} - Map of marketHashName to price */ async getPrices(marketHashNames, game = null) { try { const query = { marketHashName: { $in: marketHashNames } }; if (game) query.game = game; const items = await MarketPrice.find(query); // Create a map for quick lookups const priceMap = {}; items.forEach((item) => { priceMap[item.marketHashName] = item.price; }); return priceMap; } catch (error) { console.error("Error getting batch prices:", error.message); return {}; } } /** * Get full items data for multiple items (batch lookup) * @param {Array} marketHashNames - Array of market hash names * @param {string} game - Game identifier ('cs2' or 'rust') * @returns {Promise} - Array of market items */ async getItems(marketHashNames, game = null) { try { const query = { marketHashName: { $in: marketHashNames } }; if (game) query.game = game; return await MarketPrice.find(query); } catch (error) { console.error("Error getting batch items:", error.message); return []; } } /** * Search for items by name (partial match) * @param {string} searchTerm - Search term * @param {string} game - Game identifier ('cs2' or 'rust') * @param {number} limit - Maximum results to return * @returns {Promise} - Array of matching items */ async search(searchTerm, game = null, limit = 20) { try { const query = { $or: [ { name: { $regex: searchTerm, $options: "i" } }, { marketHashName: { $regex: searchTerm, $options: "i" } }, ], }; if (game) query.game = game; return await MarketPrice.find(query) .limit(limit) .sort({ price: -1 }); } catch (error) { console.error("Error searching items:", error.message); return []; } } /** * Get price statistics for a game * @param {string} game - Game identifier ('cs2' or 'rust') * @returns {Promise} - Statistics object */ async getStats(game = null) { try { return await MarketPrice.getStats(game); } catch (error) { console.error("Error getting stats:", error.message); return { count: 0, avgPrice: 0, minPrice: 0, maxPrice: 0, totalValue: 0, }; } } /** * Get top priced items * @param {string} game - Game identifier ('cs2' or 'rust') * @param {number} limit - Number of items to return * @returns {Promise} - Array of top priced items */ async getTopPriced(game = null, limit = 50) { try { const query = game ? { game } : {}; return await MarketPrice.find(query) .sort({ price: -1 }) .limit(limit); } catch (error) { console.error("Error getting top priced items:", error.message); return []; } } /** * Get items by price range * @param {number} minPrice - Minimum price * @param {number} maxPrice - Maximum price * @param {string} game - Game identifier ('cs2' or 'rust') * @param {number} limit - Maximum results to return * @returns {Promise} - Array of items in price range */ async getByPriceRange(minPrice, maxPrice, game = null, limit = 100) { try { const query = { price: { $gte: minPrice, $lte: maxPrice }, }; if (game) query.game = game; return await MarketPrice.find(query) .sort({ price: -1 }) .limit(limit); } catch (error) { console.error("Error getting items by price range:", error.message); return []; } } /** * Check if price data exists for a game * @param {string} game - Game identifier ('cs2' or 'rust') * @returns {Promise} - True if data exists */ async hasData(game) { try { const count = await MarketPrice.countDocuments({ game }); return count > 0; } catch (error) { console.error("Error checking data:", error.message); return false; } } /** * Get count of items in database * @param {string} game - Game identifier ('cs2' or 'rust'), or null for all * @returns {Promise} - Count of items */ async getCount(game = null) { try { const query = game ? { game } : {}; return await MarketPrice.countDocuments(query); } catch (error) { console.error("Error getting count:", error.message); return 0; } } /** * Get last update timestamp for a game * @param {string} game - Game identifier ('cs2' or 'rust') * @returns {Promise} - Last update date or null */ async getLastUpdate(game) { try { const item = await MarketPrice.findOne({ game }) .sort({ lastUpdated: -1 }) .select("lastUpdated"); return item ? item.lastUpdated : null; } catch (error) { console.error("Error getting last update:", error.message); return null; } } /** * Check if price data is outdated * @param {string} game - Game identifier ('cs2' or 'rust') * @param {number} hoursThreshold - Hours before considering outdated * @returns {Promise} - True if outdated */ async isOutdated(game, hoursThreshold = 24) { try { const lastUpdate = await this.getLastUpdate(game); if (!lastUpdate) return true; const now = new Date(); const diff = now - lastUpdate; const hoursDiff = diff / (1000 * 60 * 60); return hoursDiff > hoursThreshold; } catch (error) { console.error("Error checking if outdated:", error.message); return true; } } /** * Enrich inventory items with market prices * Used when loading Steam inventory to add price data * @param {Array} inventoryItems - Array of inventory items with market_hash_name * @param {string} game - Game identifier ('cs2' or 'rust') * @returns {Promise} - Inventory items enriched with price data */ async enrichInventory(inventoryItems, game) { try { // Extract all market hash names const marketHashNames = inventoryItems.map( (item) => item.market_hash_name ); // Get prices for all items const priceMap = await this.getPrices(marketHashNames, game); // Enrich each item with price return inventoryItems.map((item) => ({ ...item, marketPrice: priceMap[item.market_hash_name] || null, hasPriceData: !!priceMap[item.market_hash_name], })); } catch (error) { console.error("Error enriching inventory:", error.message); return inventoryItems; } } /** * Get suggested price for an item (with optional markup) * @param {string} marketHashName - Steam market hash name * @param {string} game - Game identifier ('cs2' or 'rust') * @param {number} markup - Markup percentage (e.g., 1.1 for 10% markup) * @returns {Promise} - Suggested price or null */ async getSuggestedPrice(marketHashName, game, markup = 1.0) { try { const price = await this.getPrice(marketHashName, game); if (!price) return null; // Apply markup return parseFloat((price * markup).toFixed(2)); } catch (error) { console.error("Error getting suggested price:", error.message); return null; } } /** * Format price for display * @param {number} price - Price in USD * @returns {string} - Formatted price string */ formatPrice(price) { if (price === null || price === undefined) return "N/A"; return new Intl.NumberFormat("en-US", { style: "currency", currency: "USD", }).format(price); } } // Export singleton instance const marketPriceService = new MarketPriceService(); export default marketPriceService;