Gerenciamento de Itens (Admin + Publico)
Visao Geral
O sistema de gerenciamento de itens do ProCases opera em duas frentes:
- Admin (WaxpeerController): 7 endpoints protegidos por
AdminGuardpara importacao, sincronizacao e gestao de itens. Rota base:/api/admin/items/* - Publico (ItemsPublicController): 7 endpoints publicos para consulta de itens com margem de preco configuravel (
PriceMarginService.getCurrent()). Rota base:/api/items/*
O fluxo de importacao de itens segue duas estrategias complementares:
- Steam Database: importa o catalogo completo com raridades corretas a partir dos arquivos de jogo
- Waxpeer API: atualiza precos em tempo real e fornece imagens
Arquitetura de Arquivos
| Camada | Arquivo | Responsabilidade |
|---|---|---|
| Presentation | src/presentation/controllers/waxpeer.controller.ts | Controller admin (7 endpoints) |
| Presentation | src/presentation/controllers/items-public.controller.ts | Controller publico (7 endpoints) |
| Presentation | src/presentation/modules/admin.module.ts | Modulo admin |
| Presentation | src/presentation/modules/items-public.module.ts | Modulo publico |
| Application | src/application/use-cases/admin/sync-steam-database.use-case.ts | Sync Steam Database |
| Application | src/application/use-cases/admin/update-item-prices.use-case.ts | Atualizar precos Waxpeer |
| Application | src/application/use-cases/admin/sync-items-from-waxpeer.use-case.ts | Sync completo Waxpeer |
| Infrastructure | src/infrastructure/cs2/steam-database.service.ts | Parser Steam Database |
| Infrastructure | src/infrastructure/external-apis/waxpeer-snapshot.service.ts | Snapshot de precos Waxpeer |
| Infrastructure | src/infrastructure/external-apis/exchange-rate.service.ts | Cotacao USD/BRL |
| Infrastructure | src/infrastructure/external-apis/item-name-parser.service.ts | Parser de nomes de itens |
WaxpeerController (Admin - 7 Endpoints)
Todos os endpoints requerem AdminGuard.
POST /api/admin/items/sync-steam-database
Importa o catalogo completo de itens CS2 a partir dos arquivos de jogo hospedados no GitHub (SteamDatabase/GameTracking-CS2).
Body - SyncSteamDatabaseDto:
{
weaponCategories?: WeaponCategory[]; // Filtrar por categoria
includeDefaultWeapons?: boolean; // Incluir armas sem skin (default: false)
batchSize?: number; // Tamanho do batch (default: 100)
updateExisting?: boolean; // Atualizar existentes (default: false)
forceRefresh?: boolean; // Ignorar cache (default: false)
}Response (200):
{
success: true,
source: "Steam Database",
totalFetched: number, // Total de itens buscados
created: number, // Novos itens criados
updated: number, // Itens atualizados
skipped: number, // Itens pulados (ja existentes)
failed: number, // Itens que falharam
errors: string[] // Lista de erros
}POST /api/admin/items/update-prices
Atualiza precos dos itens existentes usando a API Waxpeer.
Body - UpdatePricesDto:
{
game?: 'csgo' | 'dota2' | 'tf2'; // Jogo (default: 'csgo')
batchSize?: number; // Tamanho do batch (default: 100)
updateAll?: boolean; // Atualizar todos (default: false)
specificItems?: string[]; // Market hash names especificos
minPriceCents?: number; // Preco minimo em cents
maxPriceCents?: number; // Preco maximo em cents
}Response (200):
{
success: true,
source: "Waxpeer",
totalChecked: number,
updated: number,
notFound: number,
failed: number,
totalValueCents: number,
errors: string[]
}Logica de selecao:
- Se
specificItemsfornecido: atualiza apenas esses itens - Se
updateAll = true: atualiza todos os itens do banco - Se nenhum filtro: atualiza apenas itens com
valueCents <= 100(sem preco)
GET /api/admin/items/items
Retorna itens brutos da API Waxpeer sem salvar no banco.
Query Parameters:
| Param | Tipo | Default | Descricao |
|---|---|---|---|
game | enum | csgo | csgo, dota2, tf2 |
skip | number | 0 | Paginacao |
limit | number | - | Itens por pagina (max 500) |
brand | string | - | Categoria (rifle, pistol, knife...) |
sort | enum | - | asc, desc, profit, best_deals |
search | string | - | Busca por nome |
max_price | number | - | Preco maximo (em coins) |
min_price | number | - | Preco minimo (em coins) |
GET /api/admin/items/steam-database/items
Retorna itens brutos do Steam Database sem salvar no banco.
Query Parameters:
| Param | Tipo | Descricao |
|---|---|---|
search | string | Busca por nome (opcional) |
Sem search: retorna os primeiros 100 itens do cache. Com search: busca por nome ou nome da skin.
GET /api/admin/items/steam-database/collections
Retorna todas as colecoes de armas CS2.
Response (200):
{
collections: string[] // Lista de nomes de colecoes ordenados
}POST /api/admin/items/steam-database/clear-cache
Limpa o cache local do Steam Database para forcar nova busca.
Response (200):
{
success: true,
message: "Steam Database cache cleared"
}GET /api/admin/items/weapon-categories
Retorna o mapeamento entre categorias Waxpeer e o enum WeaponCategory.
Response (200):
{
mappings: [
{ waxpeerBrand: "pistol", weaponCategory: "PISTOL", description: "Pistols (Glock, USP, Deagle, etc.)" },
{ waxpeerBrand: "rifle", weaponCategory: "RIFLE", description: "Rifles (AK-47, M4A4, M4A1-S, etc.)" },
{ waxpeerBrand: "smg", weaponCategory: "SMG", description: "SMGs (MP9, MAC-10, UMP-45, etc.)" },
{ waxpeerBrand: "shotgun", weaponCategory: "SHOTGUN", description: "Shotguns (Nova, XM1014, Mag-7, etc.)" },
{ waxpeerBrand: "sniper_rifle", weaponCategory: "SNIPER", description: "Sniper rifles (AWP, Scout, etc.)" },
{ waxpeerBrand: "machinegun", weaponCategory: "HEAVY", description: "Machine guns (Negev, M249)" },
{ waxpeerBrand: "knife", weaponCategory: "KNIFE", description: "Knives" },
{ waxpeerBrand: "gloves", weaponCategory: "GLOVE", description: "Gloves" },
{ waxpeerBrand: "key", weaponCategory: "OTHER", description: "Keys" },
{ waxpeerBrand: "pass", weaponCategory: "OTHER", description: "Operation passes" },
{ waxpeerBrand: "sticker", weaponCategory: "OTHER", description: "Stickers" },
]
}ItemsPublicController (Publico - 7 Endpoints)
Todos os endpoints sao marcados com @Public() (sem autenticacao), exceto POST /api/items/sync que requer AdminGuard.
Margem de Preco (PriceMarginService)
REGRA CRITICA: Todos os precos publicos incluem uma margem configuravel. A margem efetiva vem de PriceMarginService.getCurrent() (ajustavel pelo admin) — o valor no codigo (ex.: 1.2) e apenas o default. NUNCA assuma um percentual fixo.
private async applyMargin(priceCents: number): Promise<number> {
const margin = await this.priceMarginService.getCurrent(); // ex.: 1.2 (default)
return Math.round(priceCents * margin);
}Exemplo (com margem corrente = 1.2):
- Preco Waxpeer: $1.00 (100 cents)
- Preco com margem: $1.20 (120 cents)
- Preco em BRL (cotacao 5.25): R$ 6,30
GET /api/items
Retorna todos os itens com a margem corrente (PriceMarginService.getCurrent()) aplicada e conversao BRL.
Query Parameters:
| Param | Tipo | Descricao |
|---|---|---|
search | string | Busca por nome |
category | WeaponCategory | Filtrar por categoria |
rarity | Rarity | Filtrar por raridade |
minPrice | number | Preco minimo em cents (USD) |
maxPrice | number | Preco maximo em cents (USD) |
limit | number | Limitar resultados |
Response (200):
{
success: true,
count: number,
exchangeRate: number, // Cotacao USD->BRL atual
marginPercent: 20,
items: [
{
name: "AK-47 | Redline (Field-Tested)",
price: 12000, // Waxpeer coins com margem (1000 = $1)
originalPriceCents: 100, // Preco original em cents (sem margem)
priceCents: 120, // Preco em cents com margem
priceBrl: 6.30, // Preco em Reais
steamPrice: 10000, // Preco Steam original
imageUrl: "https://...",
rarity: "CLASSIFIED",
weaponCategory: "RIFLE",
marginPercent: 20
}
]
}GET /api/items/categories
Retorna categorias disponiveis com contagem de itens.
Response (200):
{
success: true,
categories: [
{ name: "RIFLE", count: 450 },
{ name: "PISTOL", count: 320 },
{ name: "KNIFE", count: 180 },
// ...
]
}GET /api/items/rarities
Retorna raridades disponiveis com contagem de itens.
Response (200):
{
success: true,
rarities: [
{ name: "CONSUMER", count: 200 },
{ name: "MIL_SPEC", count: 800 },
{ name: "COVERT", count: 50 },
// ...
]
}GET /api/items/cheap
Retorna itens baratos ($0-$0.50) com a margem corrente aplicada.
Query Parameters:
| Param | Tipo | Default | Descricao |
|---|---|---|---|
limit | number | 100 | Limitar resultados |
Itens sao filtrados por priceCents >= 0 && priceCents <= 50 (antes da margem) e ordenados por preco ascendente.
GET /api/items/stats
Retorna estatisticas do snapshot atual com a margem corrente aplicada.
Response (200):
{
success: true,
exchangeRate: {
usdToBrl: 5.25,
lastUpdated: "2025-06-15T14:00:00.000Z",
source: "AwesomeAPI (economia.awesomeapi.com.br)"
},
marginPercent: 20,
stats: {
totalItems: 5432,
totalValueUsd: "$1,234,567.89",
totalValueBrl: "R$ 6,481,481.42",
averagePriceUsd: "$227.28",
averagePriceBrl: "R$ 1,193.22",
cheapestItem: {
name: "P250 | Sand Dune (Battle-Scarred)",
priceUsd: "$0.04",
priceBrl: "R$ 0.20"
},
mostExpensiveItem: {
name: "AK-47 | Case Hardened (Factory New)",
priceUsd: "$12,000.00",
priceBrl: "R$ 63,000.00"
}
}
}GET /api/items/exchange-rate
Retorna a cotacao atual USD->BRL.
Response (200):
{
success: true,
rate: 5.25,
lastUpdated: "2025-06-15T14:00:00.000Z",
source: "AwesomeAPI (economia.awesomeapi.com.br)"
}POST /api/items/sync
Requer AdminGuard. Sincroniza itens do Waxpeer para o banco de dados.
Body - SyncItemsOptions:
{
weaponCategories?: WeaponCategory[]; // Filtrar por categoria
skipExisting?: boolean; // Pular existentes (default: false)
batchSize?: number; // Tamanho do batch (default: 100)
maxItems?: number; // Limitar quantidade
}Response (200):
{
success: true,
totalFetched: number,
created: number,
updated: number,
skipped: number,
failed: number,
errors: string[]
}SteamDatabaseService
Servico que busca e parseia os arquivos de jogo CS2 para extrair o catalogo completo de itens com raridades corretas.
Fontes de Dados
| Arquivo | URL | Conteudo |
|---|---|---|
items_game.txt | https://raw.githubusercontent.com/SteamDatabase/GameTracking-CS2/master/game/csgo/pak01_dir/scripts/items/items_game.txt | Definicoes de itens, paint kits, colecoes |
csgo_english.txt | https://raw.githubusercontent.com/SteamDatabase/GameTracking-CS2/master/game/csgo/pak01_dir/resource/csgo_english.txt | Traducoes de nomes |
Cache
private cache: Map<string, SteamDatabaseItem> | null = null;
private lastFetchTime: Date | null = null;
private readonly cacheTTL = 24 * 60 * 60 * 1000; // 24 horasO cache dura 24 horas. Pode ser limpo manualmente via clearCache() ou via endpoint POST /api/admin/items/steam-database/clear-cache.
Parser VDF (Valve Data Format)
O arquivo items_game.txt usa o formato proprietario VDF da Valve. O parser extrai:
- Paint Kits: Skins disponiveis (ID, nome, raridade)
- Weapons: Armas base (defindex, nome, prefab)
- Collections: Colecoes que mapeiam armas + paint kits + raridades
- Rarities: Definicoes de raridade
Interface do Item
export interface SteamDatabaseItem {
marketHashName: string; // Nome completo (ex: "AK-47 | Redline")
name: string; // Mesmo que marketHashName
weaponType: string; // Tipo da arma
skinName?: string; // Nome da skin (ex: "Redline")
rarity: Rarity; // Raridade Prisma enum
weaponCategory: WeaponCategory; // Categoria Prisma enum
collection?: string; // Colecao (ex: "Phoenix Collection")
isStatTrak: boolean; // Se e StatTrak
isSouvenir: boolean; // Se e Souvenir
imageUrl?: string; // URL da imagem (opcional)
}Mapeamento de Categorias
private mapWeaponTypeToCategory(weaponType: string): WeaponCategory {
const lower = weaponType.toLowerCase();
if (lower.includes('pistol') || lower.includes('glock')...) return WeaponCategory.PISTOL;
if (lower.includes('smg') || lower.includes('mac10')...) return WeaponCategory.SMG;
if (lower.includes('rifle') || lower.includes('ak47')...) return WeaponCategory.RIFLE;
if (lower.includes('shotgun') || lower.includes('nova')...) return WeaponCategory.SHOTGUN;
if (lower.includes('sniper') || lower.includes('awp')...) return WeaponCategory.SNIPER;
if (lower.includes('machinegun') || lower.includes('m249')...) return WeaponCategory.HEAVY;
if (lower.includes('knife')) return WeaponCategory.KNIFE;
if (lower.includes('glove')) return WeaponCategory.GLOVE;
return WeaponCategory.OTHER;
}Metodos Publicos
| Metodo | Retorno | Descricao |
|---|---|---|
fetchAllItems() | Map<string, SteamDatabaseItem> | Catalogo completo (com cache 24h) |
getItem(name) | SteamDatabaseItem | null | Busca por market hash name |
searchItems(term) | SteamDatabaseItem[] | Busca por nome ou skin name |
getItemsByCollection(name) | SteamDatabaseItem[] | Itens de uma colecao |
clearCache() | void | Limpa cache para forcar nova busca |
WaxpeerSnapshotService
Servico que busca precos em tempo real da API Waxpeer.
Endpoint
GET https://api.waxpeer.com/v1/prices?api={API_KEY}&game=csgoCache
private cache: FilteredItem[] | null = null;
private cacheTime: Date | null = null;
private readonly cacheTTL = 5 * 60 * 1000; // 5 minutosCache de 5 minutos. Se a API falhar e houver cache expirado, o cache expirado e retornado como fallback.
Conversao de Precos
A API Waxpeer usa "coins" onde 1000 coins = $1.00 USD:
private mapToFilteredItem(item: WaxpeerPriceItem): FilteredItem {
return {
name: item.name,
price: item.min, // coins (ex: 1000)
priceCents: Math.round(item.min / 10), // cents USD (ex: 100)
steamPrice: item.min,
imageUrl: item.img || WaxpeerSnapshotService.buildWaxpeerImageUrl(item.name),
rarity: this.inferRarity(item.rarity_color, item.name),
weaponCategory: this.inferWeaponCategory(item.name),
};
}Inferencia de Raridade por Cor Hex
private inferRarity(rarityColor?: string, name?: string): Rarity {
const colorMap: Record<string, Rarity> = {
'#b0c3d9': Rarity.CONSUMER, // Cinza
'#5e98d9': Rarity.INDUSTRIAL, // Azul claro
'#4b69ff': Rarity.MIL_SPEC, // Azul
'#8847ff': Rarity.RESTRICTED, // Roxo
'#d32ce6': Rarity.CLASSIFIED, // Rosa
'#eb4b4b': Rarity.COVERT, // Vermelho
'#ffd700': Rarity.EXTRAORDINARY, // Dourado
'#f8ae2a': Rarity.EXTRAORDINARY, // Dourado variante
'#e4ae39': Rarity.EXTRAORDINARY, // Dourado variante
};
const mapped = colorMap[rarityColor?.toLowerCase()];
if (mapped) return mapped;
// Fallback por nome
if (name?.toLowerCase().includes('knife') ||
name?.toLowerCase().includes('karambit') ||
name?.toLowerCase().includes('bayonet')) {
return Rarity.EXTRAORDINARY;
}
if (name?.toLowerCase().includes('gloves')) return Rarity.EXTRAORDINARY;
return Rarity.CONSUMER;
}Metodos Publicos
| Metodo | Retorno | Descricao |
|---|---|---|
getFilteredItems() | FilteredItem[] | Todos os itens (cache 5min) |
getItemsByCategory(cat) | FilteredItem[] | Filtro por categoria |
getItemsByRarity(rar) | FilteredItem[] | Filtro por raridade |
getItemsByPriceRange(min, max) | FilteredItem[] | Filtro por faixa de preco |
searchItems(query) | FilteredItem[] | Busca por nome |
clearCache() | void | Limpa cache |
ExchangeRateService
Servico que busca a cotacao USD->BRL em tempo real.
Fonte de Dados
GET https://economia.awesomeapi.com.br/json/last/USD-BRLCache
private cachedRate: number | null = null;
private cacheTime: Date | null = null;
private readonly cacheTTL = 30 * 60 * 1000; // 30 minutosFallback
Se a API falhar:
- Retorna cache expirado se disponivel
- Se nao houver cache, usa fallback fixo de
5.00
async getUsdToBrlRate(): Promise<number> {
try {
const response = await firstValueFrom(
this.httpService.get<ExchangeRateResponse>(this.apiUrl, { timeout: 10000 })
);
const rate = parseFloat(response.data.USDBRL.bid);
if (isNaN(rate) || rate <= 0) throw new Error('Cotacao invalida');
this.cachedRate = rate;
this.cacheTime = new Date();
return rate;
} catch (error) {
if (this.cachedRate) return this.cachedRate; // cache expirado
return 5.00; // fallback
}
}Metodos Publicos
| Metodo | Retorno | Descricao |
|---|---|---|
getUsdToBrlRate() | number | Cotacao atual (cache 30min) |
convertCentsToBrl(cents) | number | Converte cents USD para BRL |
convertUsdToBrl(usd) | number | Converte dolares para BRL |
getExchangeRateInfo() | {rate, lastUpdated, source} | Info completa |
clearCache() | void | Limpa cache |
ItemNameParserService
Servico que parseia nomes de itens CS2 no formato market hash name.
Formatos Suportados
"StatTrak(TM) M4A4 | Poly Mag (Factory New)"
"Souvenir M4A4 | Poly Mag (Field-Tested)"
"M4A4 | Poly Mag (Minimal Wear)"
"AK-47 | Redline"
"Glock-18"Interface de Retorno
export interface ParsedItemName {
fullName: string; // Nome completo original
weaponName: string; // Nome da arma (ex: "M4A4")
skinName: string; // Nome da skin (ex: "Poly Mag")
exterior?: ItemExterior; // Exterior enum (FACTORY_NEW, etc.)
isStatTrak: boolean; // Se e StatTrak
isSouvenir: boolean; // Se e Souvenir
}Logica de Parsing
parse(marketHashName: string): ParsedItemName {
let workingName = marketHashName;
let isStatTrak = false;
let isSouvenir = false;
// 1. Verificar e remover prefixos
if (workingName.includes('StatTrak(TM)')) {
isStatTrak = true;
workingName = workingName.replace('StatTrak(TM) ', '');
}
if (workingName.includes('Souvenir')) {
isSouvenir = true;
workingName = workingName.replace('Souvenir ', '');
}
// 2. Extrair exterior dos parenteses
const exteriorMatch = workingName.match(/\s*\(([^)]+)\)\s*$/);
// "Factory New" -> FACTORY_NEW
// "Minimal Wear" -> MINIMAL_WEAR
// "Field-Tested" -> FIELD_TESTED
// "Well-Worn" -> WELL_WORN
// "Battle-Scarred" -> BATTLE_SCARRED
// 3. Separar arma e skin pelo "|"
const parts = workingName.split('|').map(p => p.trim());
// parts[0] = "M4A4", parts[1] = "Poly Mag"
}Metodos Publicos
| Metodo | Retorno | Descricao |
|---|---|---|
parse(name) | ParsedItemName | Parse completo |
extractSkinName(name) | string | Apenas o nome da skin |
isDefaultItem(name) | boolean | Se e item sem skin |
SyncSteamDatabaseUseCase
Use case que coordena a importacao de itens do Steam Database para o banco.
Fluxo
async execute(options: SyncSteamDatabaseOptions): Promise<SyncSteamDatabaseResult> {
// 1. Limpar cache se forceRefresh
if (options.forceRefresh) this.steamDatabaseService.clearCache();
// 2. Buscar todos os itens do Steam Database
const steamItems = await this.steamDatabaseService.fetchAllItems();
// 3. Filtrar por categoria e default weapons
let itemsToProcess = Array.from(steamItems.values());
if (options.weaponCategories) {
itemsToProcess = itemsToProcess.filter(i => options.weaponCategories.includes(i.weaponCategory));
}
if (!options.includeDefaultWeapons) {
itemsToProcess = itemsToProcess.filter(i => i.skinName);
}
// 4. Buscar itens existentes no banco
const existingItems = await this.itemRepository.findAll();
const existingByMarketHash = new Map(existingItems.map(i => [i.marketHashName, i]));
// 5. Processar em batches
for (const batch of batches) {
await this.processBatch(batch, existingByMarketHash, options.updateExisting, result);
await this.sleep(50); // evitar sobrecarga
}
}Itens criados sem preco: valueCents = BigInt(0). O preco e preenchido depois pelo UpdateItemPricesUseCase ou SyncItemsFromWaxpeerUseCase.
UpdateItemPricesUseCase
Use case que atualiza precos dos itens existentes a partir da API Waxpeer.
Conversao de Preco
// Waxpeer usa coins: 1000 coins = $1.00
// Convertemos para cents: 1000 / 10 = 100 cents
const priceCents = Math.round(waxpeerItem.price / 10);Fluxo
async execute(options: UpdateItemPricesOptions): Promise<UpdateItemPricesResult> {
// 1. Selecionar itens para atualizar
// 2. Buscar precos da Waxpeer
const waxpeerItems = await this.waxpeerService.getAllItems(game);
const waxpeerByName = new Map(waxpeerItems.map(i => [i.name, i]));
// 3. Para cada item, buscar preco e atualizar
for (const item of itemsToUpdate) {
const waxpeerItem = waxpeerByName.get(item.marketHashName);
if (!waxpeerItem) { result.notFound++; continue; }
const priceCents = Math.round(waxpeerItem.price / 10);
await this.itemRepository.update(item.id, {
valueCents: BigInt(priceCents),
imageUrl: waxpeerItem.image,
});
}
}SyncItemsFromWaxpeerUseCase
Use case completo que busca itens do Waxpeer, parseia nomes, converte precos com a margem corrente e salva no banco com precos em USD e BRL.
Margem de preco (dinamica)
A margem vem de PriceMarginService.getCurrent() (ajustavel pelo admin), nao de uma constante fixa. O exemplo abaixo assume a margem corrente = 1.2:
const priceMargin = await this.priceMarginService.getCurrent(); // ex.: 1.2
// Preco original: 100 cents USD
const priceCentsWithMargin = Math.round(100 * priceMargin); // 120 cents USD
// Conversao para BRL (cotacao 5.25):
const priceUsd = 120 / 100; // $1.20
const priceBrl = 1.20 * 5.25; // R$ 6.30
const priceCentsBrl = Math.round(6.30 * 100); // 630 centavos BRLFluxo
async execute(options: SyncItemsOptions): Promise<SyncItemsResult> {
// 1. Buscar itens do Waxpeer (ja filtrados)
let items = await this.waxpeerSnapshotService.getFilteredItems();
items = items.filter(i => i.weaponCategory !== WeaponCategory.OTHER);
// 2. Buscar cotacao USD->BRL e margem corrente
const exchangeRate = await this.exchangeRateService.getUsdToBrlRate();
const priceMargin = await this.priceMarginService.getCurrent();
// 3. Buscar itens existentes
const existingByMarketHash = new Map(...);
// 4. Para cada item:
for (const waxpeerItem of items) {
const parsed = this.itemNameParser.parse(waxpeerItem.name);
if (!parsed.skinName) { result.skipped++; continue; } // pula default
const priceCentsWithMargin = Math.round(waxpeerItem.priceCents * priceMargin);
const priceUsd = priceCentsWithMargin / 100;
const priceBrl = priceUsd * exchangeRate;
const priceCentsBrl = Math.round(priceBrl * 100);
if (existingItem) {
await this.itemRepository.update(existingItem.id, {
valueCents: BigInt(priceCentsWithMargin),
valueCentsBrl: BigInt(priceCentsBrl),
imageUrl: waxpeerItem.imageUrl,
});
} else {
await this.itemRepository.create({
name: parsed.skinName,
marketHashName: waxpeerItem.name,
imageUrl: waxpeerItem.imageUrl,
rarity: waxpeerItem.rarity,
valueCents: BigInt(priceCentsWithMargin),
valueCentsBrl: BigInt(priceCentsBrl),
weaponCategory: waxpeerItem.weaponCategory,
exterior: parsed.exterior,
isStatTrak: parsed.isStatTrak,
});
}
}
}Fluxo de Importacao Recomendado
Importacao Inicial
1. POST /api/admin/items/sync-steam-database
- Importa catalogo completo com raridades corretas
- Itens criados com valueCents = 0 (sem preco)
2. POST /api/admin/items/update-prices { updateAll: true }
- Atualiza valueCents (USD) de todos os itens via Waxpeer
3. POST /api/items/sync
- Sincroniza com a margem corrente (PriceMarginService) e calcula valueCentsBrlAtualizacao de Precos (Periodica)
1. POST /api/admin/items/update-prices { updateAll: true }
- Atualiza precos USD
2. POST /api/items/sync { skipExisting: false }
- Recalcula BRL com cotacao atual e margemDebugging
Verificar precos de um item
SELECT name, market_hash_name, value_cents, value_cents_brl, rarity
FROM items
WHERE market_hash_name = 'AK-47 | Redline (Field-Tested)';Verificar cotacao atual
curl -s https://economia.awesomeapi.com.br/json/last/USD-BRL | jq '.USDBRL.bid'Problemas Comuns
| Problema | Causa | Solucao |
|---|---|---|
WAXPEER_API_KEY nao configurada | Variavel de ambiente ausente | Setar WAXPEER_API_KEY no .env |
| Precos zerados | Steam Database nao traz precos | Executar update-prices apos sync |
| Raridade CONSUMER em tudo | Cor hex nao mapeada | Verificar colorMap no WaxpeerSnapshotService |
| Cotacao fixa em 5.00 | API AwesomeAPI indisponivel | Verificar conectividade, cache expirado |
| Itens duplicados | marketHashName diferente | Verificar encoding de caracteres especiais |
| Cache nao limpa | TTL nao expirado | Usar clear-cache endpoint ou clearCache() |
Regras Importantes
- A margem corrente e aplicada em TODOS os precos publicos - vem de
PriceMarginService.getCurrent()(configuravel pelo admin); a constante no codigo e so o default valueCentse USD,valueCentsBrle BRL - nunca confundir- Steam Database e a fonte de raridades - Waxpeer usa cores aproximadas
- Cache de precos Waxpeer dura 5 minutos - nao bombardear a API
- Cache de cotacao dura 30 minutos - com fallback para 5.00
- Cache do Steam Database dura 24 horas - pode ser limpado manualmente
- Itens OTHER sao excluidos do sync (stickers, keys, passes)
- Itens default (sem skin) sao excluidos do sync por padrao
- Endpoints admin requerem AdminGuard - endpoints publicos sao
@Public() - Sleep de 50ms entre batches para evitar sobrecarga no banco
