first commit
This commit is contained in:
319
services/marketPrice.js
Normal file
319
services/marketPrice.js
Normal file
@@ -0,0 +1,319 @@
|
||||
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;
|
||||
Reference in New Issue
Block a user