320 lines
9.2 KiB
JavaScript
320 lines
9.2 KiB
JavaScript
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<number|null>} - 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<Object|null>} - 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<string>} marketHashNames - Array of market hash names
|
|
* @param {string} game - Game identifier ('cs2' or 'rust')
|
|
* @returns {Promise<Object>} - 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<string>} marketHashNames - Array of market hash names
|
|
* @param {string} game - Game identifier ('cs2' or 'rust')
|
|
* @returns {Promise<Array>} - 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>} - 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<Object>} - 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>} - 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>} - 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<boolean>} - 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<number>} - 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<Date|null>} - 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<boolean>} - 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<Array>} - 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<number|null>} - 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;
|