Sistema Admin / Backoffice - Documentacao Completa
Indice
- Visao Geral
- Autenticacao e Autorizacao
- Dashboard
- Gerenciamento de Usuarios
- Gerenciamento de Caixas
- Gerenciamento de Itens
- Gerenciamento de Batalhas
- Gerenciamento Financeiro
- Analytics
- DTOs e Interfaces de Resposta
Visao Geral
O sistema Admin/Backoffice expoe 44 endpoints sob o prefixo /admin, organizados em 8 grupos funcionais. Todos os endpoints passam por dois guards obrigatorios a nivel de classe: AuthGuard (verifica sessao ativa) e AdminGuard (verifica role do usuario).
Arquivo do Controller: src/presentation/controllers/admin.controller.tsArquivo de DTOs: src/application/dto/admin.dto.tsUse Cases: src/application/use-cases/admin/
Arquitetura
AdminController (Presentation)
-> Use Case (Application)
-> PrismaClient / Repository (Infrastructure)
-> TransactionService, MoneyService, LockService (Application Services)Toda formatacao monetaria e feita pelo MoneyService.format(), que converte bigint em centavos para formato brasileiro R$ X.XXX,XX. O controller NUNCA contem logica de negocio; apenas delega para use cases e formata a resposta HTTP.
Autenticacao e Autorizacao
Guards Aplicados
Os dois guards sao aplicados a nivel de classe, portanto todos os 44 endpoints os herdam automaticamente.
@Controller('admin')
@UseGuards(AuthGuard, AdminGuard)
export class AdminController { ... }AuthGuard
Verifica se a requisicao possui um sessionId valido. A sessao e armazenada em Redis e deve estar ativa. Se a sessao nao existir ou estiver expirada, retorna 401 Unauthorized.
AdminGuard
Verifica se o usuario autenticado possui role ADMIN ou SUPER_ADMIN. Caso contrario, retorna 403 Forbidden.
@Injectable()
export class AdminGuard implements CanActivate {
canActivate(context: ExecutionContext): boolean {
const request = context.switchToHttp().getRequest();
const user = request.user;
if (!user) {
throw new ForbiddenException('User not authenticated');
}
if (user.role !== 'ADMIN' && user.role !== 'SUPER_ADMIN') {
throw new ForbiddenException('Admin access required');
}
return true;
}
}Roles Aceitas
| Role | Acesso |
|---|---|
USER | Nenhum endpoint admin |
ADMIN | Todos os endpoints admin |
SUPER_ADMIN | Todos os endpoints admin + protecao contra ban |
Respostas de Erro Comuns
// 401 - Sessao invalida ou expirada
{
"statusCode": 401,
"message": "Unauthorized"
}
// 403 - Usuario sem permissao admin
{
"statusCode": 403,
"message": "Admin access required"
}Dashboard
O grupo de Dashboard fornece 4 endpoints para visualizacao geral da plataforma em tempo real.
GET /admin/dashboard/stats
Retorna estatisticas gerais do dashboard incluindo totais de usuarios, receita, depositos, saques e lucro.
Query Parameters: Nenhum
Response (200):
interface DashboardStatsResponseDto {
totalUsers: number;
activeUsersToday: number;
totalRevenueCents: string;
revenueTodayCents: string;
totalCaseOpenings: number;
caseOpeningsToday: number;
totalBattles: number;
battlesToday: number;
pendingDeposits: number;
pendingWithdrawals: number;
houseEdgeCents: string;
totalDepositedCents: string;
totalDepositedFormatted: string;
totalWithdrawnCents: string;
totalWithdrawnFormatted: string;
totalPlatformBalanceCents: string;
totalPlatformBalanceFormatted: string;
profitCents: string;
profitFormatted: string;
}Exemplo de Response:
{
"totalUsers": 1523,
"activeUsersToday": 87,
"totalRevenueCents": "4523100",
"revenueTodayCents": "156000",
"totalCaseOpenings": 9812,
"caseOpeningsToday": 342,
"totalBattles": 1203,
"battlesToday": 45,
"pendingDeposits": 3,
"pendingWithdrawals": 7,
"houseEdgeCents": "4523100",
"totalDepositedCents": "12500000",
"totalDepositedFormatted": "R$ 125.000,00",
"totalWithdrawnCents": "7976900",
"totalWithdrawnFormatted": "R$ 79.769,00",
"totalPlatformBalanceCents": "3200000",
"totalPlatformBalanceFormatted": "R$ 32.000,00",
"profitCents": "4523100",
"profitFormatted": "R$ 45.231,00"
}Calculo de Metricas:
totalRevenueCents= totalDepositedCents - totalWithdrawnCentsrevenueTodayCents= depositosHoje - saquesHojehouseEdgeCents= mesmo que profitCents (depositos confirmados - saques completados)totalPlatformBalanceCents= soma de balanceCents de todos os usuarios
GET /admin/dashboard/revenue-chart
Retorna dados para o grafico de receita segmentados por periodo.
Query Parameters:
| Parametro | Tipo | Obrigatorio | Default | Valores |
|---|---|---|---|---|
period | string | Nao | week | day, week, month, year |
Intervalos por Periodo:
day: 24 intervalos de 1 hora (ultimas 24h)week: 7 intervalos de 1 dia (ultimos 7 dias)month: 30 intervalos de 1 dia (ultimos 30 dias)year: 12 intervalos de 1 mes (ultimos 12 meses)
Response (200):
interface RevenueChartDataPoint {
date: string; // ISO 8601
revenue: number; // centavos (depositos - saques)
deposits: number; // centavos total depositado
withdrawals: number; // centavos total sacado
caseOpenings: number; // quantidade de aberturas
battles: number; // quantidade de batalhas
}Exemplo de Response:
[
{
"date": "2026-02-04T00:00:00.000Z",
"revenue": 230000,
"deposits": 450000,
"withdrawals": 220000,
"caseOpenings": 156,
"battles": 12
},
{
"date": "2026-02-05T00:00:00.000Z",
"revenue": 180000,
"deposits": 380000,
"withdrawals": 200000,
"caseOpenings": 134,
"battles": 9
}
]GET /admin/dashboard/overview
Retorna estatisticas completas de overview com GGR (Gross Gaming Revenue), ativos dos jogadores e breakdown por tipo de jogo (caixas, upgrades, batalhas, swaps).
Query Parameters:
| Parametro | Tipo | Obrigatorio | Default | Valores |
|---|---|---|---|---|
period | string | Nao | month | day, week, month, year |
Response (200):
interface OverviewStatsResponse {
totalGgrCents: string;
totalGgrFormatted: string;
totalWageredCents: string;
totalWageredFormatted: string;
totalPaidCents: string;
totalPaidFormatted: string;
totalPlayerBalanceCents: string;
totalPlayerBalanceFormatted: string;
totalPlayerInventoryCents: string;
totalPlayerInventoryFormatted: string;
totalPlayerAssetsCents: string;
totalPlayerAssetsFormatted: string;
uniqueActivePlayers: number;
totalUsers: number;
totalDepositedCents: string;
totalDepositedFormatted: string;
caseOpenings: GameTypeBreakdown;
upgrades: GameTypeBreakdown;
battles: GameTypeBreakdown;
swaps: GameTypeBreakdown;
period: string;
}
interface GameTypeBreakdown {
count: number;
wageredCents: string;
wageredFormatted: string;
paidCents: string;
paidFormatted: string;
ggrCents: string;
ggrFormatted: string;
rtpPercent: number;
}Exemplo de Response:
{
"totalGgrCents": "1250000",
"totalGgrFormatted": "R$ 12.500,00",
"totalWageredCents": "5000000",
"totalWageredFormatted": "R$ 50.000,00",
"totalPaidCents": "3750000",
"totalPaidFormatted": "R$ 37.500,00",
"totalPlayerBalanceCents": "890000",
"totalPlayerBalanceFormatted": "R$ 8.900,00",
"totalPlayerInventoryCents": "2300000",
"totalPlayerInventoryFormatted": "R$ 23.000,00",
"totalPlayerAssetsCents": "3190000",
"totalPlayerAssetsFormatted": "R$ 31.900,00",
"uniqueActivePlayers": 245,
"totalUsers": 1523,
"totalDepositedCents": "15000000",
"totalDepositedFormatted": "R$ 150.000,00",
"caseOpenings": {
"count": 3421,
"wageredCents": "3000000",
"wageredFormatted": "R$ 30.000,00",
"paidCents": "2100000",
"paidFormatted": "R$ 21.000,00",
"ggrCents": "900000",
"ggrFormatted": "R$ 9.000,00",
"rtpPercent": 70.0
},
"upgrades": {
"count": 890,
"wageredCents": "1200000",
"wageredFormatted": "R$ 12.000,00",
"paidCents": "960000",
"paidFormatted": "R$ 9.600,00",
"ggrCents": "240000",
"ggrFormatted": "R$ 2.400,00",
"rtpPercent": 80.0
},
"battles": {
"count": 156,
"wageredCents": "500000",
"wageredFormatted": "R$ 5.000,00",
"paidCents": "420000",
"paidFormatted": "R$ 4.200,00",
"ggrCents": "80000",
"ggrFormatted": "R$ 800,00",
"rtpPercent": 84.0
},
"swaps": {
"count": 312,
"wageredCents": "300000",
"wageredFormatted": "R$ 3.000,00",
"paidCents": "270000",
"paidFormatted": "R$ 2.700,00",
"ggrCents": "30000",
"ggrFormatted": "R$ 300,00",
"rtpPercent": 90.0
},
"period": "month"
}Detalhes de Calculo:
totalPlayerAssets= totalPlayerBalance + totalPlayerInventorytotalDeposited= depositos confirmados + creditos administrativos- GGR de batalhas usa
BattleSettlement(REMOVED_TO_POOL - ADDED_FROM_POOL) - Jogadores unicos sao contados via UNION de case_openings, upgrades, battles, battle_players e site_swaps
- Bots (steamId com prefixo
BOT_) sao excluidos da contagem de jogadores unicos
GET /admin/dashboard/recent-activity
Retorna as atividades mais recentes da plataforma, mesclando aberturas de caixas, batalhas, depositos, saques e upgrades em uma timeline unificada.
Query Parameters:
| Parametro | Tipo | Obrigatorio | Default | Min | Max |
|---|---|---|---|---|---|
limit | number | Nao | 10 | 1 | 50 |
Response (200):
interface RecentActivityItem {
id: string;
type: 'CASE_OPENING' | 'BATTLE' | 'DEPOSIT' | 'WITHDRAWAL' | 'UPGRADE';
userId: string;
username: string;
avatarUrl?: string;
description: string;
valueCents: string;
valueFormatted: string;
createdAt: string;
metadata?: Record<string, unknown>;
}Exemplo de Response:
[
{
"id": "98712345678",
"type": "CASE_OPENING",
"userId": "1",
"username": "PlayerOne",
"avatarUrl": "https://avatars.steamstatic.com/abc123.jpg",
"description": "Abriu Star Case e ganhou AK-47 | Redline",
"valueCents": "15900",
"valueFormatted": "R$ 159,00",
"createdAt": "2026-02-11T14:30:00.000Z",
"metadata": {
"caseId": "123",
"itemId": "456"
}
},
{
"id": "98712345679",
"type": "DEPOSIT",
"userId": "2",
"username": "PlayerTwo",
"description": "Depositou via PIX",
"valueCents": "50000",
"valueFormatted": "R$ 500,00",
"createdAt": "2026-02-11T14:28:00.000Z",
"metadata": {
"method": "PIX"
}
},
{
"id": "98712345680",
"type": "UPGRADE",
"userId": "3",
"username": "PlayerThree",
"description": "Ganhou upgrade para AWP | Dragon Lore",
"valueCents": "125000",
"valueFormatted": "R$ 1.250,00",
"createdAt": "2026-02-11T14:25:00.000Z",
"metadata": {
"success": true,
"chancePercent": 15.5
}
}
]Descricoes por Tipo:
CASE_OPENING: "Abriu {caseName} e ganhou {itemName}"BATTLE: "Criou uma batalha {type}" (ex: "ONE VS ONE")DEPOSIT: "Depositou via {method}"WITHDRAWAL: "Sacou via {method}"UPGRADE: "Ganhou upgrade para {itemName}" ou "Perdeu upgrade para {itemName}"
Gerenciamento de Usuarios
O grupo de gerenciamento de usuarios expoe 9 endpoints para listagem, detalhes, moderacao e historico de atividades dos usuarios.
GET /admin/users
Lista todos os usuarios com filtros, paginacao e ordenacao.
Query Parameters:
| Parametro | Tipo | Obrigatorio | Default | Valores |
|---|---|---|---|---|
page | number | Nao | 1 | Min: 1 |
limit | number | Nao | 20 | Min: 1, Max: 100 |
status | string | Nao | - | ACTIVE, SUSPENDED, BANNED |
role | string | Nao | - | USER, ADMIN, SUPER_ADMIN |
search | string | Nao | - | Busca por username/steamId |
sortBy | string | Nao | - | Campo de ordenacao |
sortOrder | string | Nao | - | asc, desc |
Response (200):
interface PaginatedResponse<UserAdminResponseDto> {
data: UserAdminResponseDto[];
total: number;
page: number;
limit: number;
totalPages: number;
}
interface UserAdminResponseDto {
id: string;
steamId: string;
username: string;
avatarUrl?: string;
balanceCents: string;
balanceFormatted: string;
tickets: number;
role: string;
status: string;
totalDepositedCents: string;
totalDepositedFormatted: string;
totalWithdrawnCents: string;
totalWithdrawnFormatted: string;
totalWageredCents: string;
totalWageredFormatted: string;
lastLoginAt?: string;
lastLoginIp?: string;
createdAt: string;
}Exemplo de Response:
{
"data": [
{
"id": "1",
"steamId": "76561198012345678",
"username": "PlayerOne",
"avatarUrl": "https://avatars.steamstatic.com/abc.jpg",
"balanceCents": "186321",
"balanceFormatted": "R$ 1.863,21",
"tickets": 5,
"role": "USER",
"status": "ACTIVE",
"totalDepositedCents": "500000",
"totalDepositedFormatted": "R$ 5.000,00",
"totalWithdrawnCents": "250000",
"totalWithdrawnFormatted": "R$ 2.500,00",
"totalWageredCents": "800000",
"totalWageredFormatted": "R$ 8.000,00",
"lastLoginAt": "2026-02-11T10:00:00.000Z",
"lastLoginIp": "203.0.113.10",
"createdAt": "2025-11-01T08:00:00.000Z"
}
],
"total": 1523,
"page": 1,
"limit": 20,
"totalPages": 77
}GET /admin/users/:id
Retorna detalhes completos de um usuario especifico, incluindo informacoes de inventario.
Path Parameters:
| Parametro | Tipo | Descricao |
|---|---|---|
id | string | ID do usuario (bigint) |
Response (200):
interface UserDetailResponseDto {
id: string;
steamId: string;
username: string;
avatarUrl?: string;
profileUrl?: string;
balanceCents: string;
balanceFormatted: string;
tickets: number;
role: string;
status: string;
twoFactorEnabled: boolean;
referralCode?: string;
referredById?: string;
totalDepositedCents: string;
totalDepositedFormatted: string;
totalWithdrawnCents: string;
totalWithdrawnFormatted: string;
totalWageredCents: string;
totalWageredFormatted: string;
inventoryItemCount: number;
inventoryValueCents: string;
inventoryValueFormatted: string;
lastLoginAt?: string;
lastLoginIp?: string;
createdAt: string;
updatedAt: string;
}Exemplo de Response:
{
"id": "1",
"steamId": "76561198012345678",
"username": "PlayerOne",
"avatarUrl": "https://avatars.steamstatic.com/abc.jpg",
"profileUrl": "https://steamcommunity.com/profiles/76561198012345678",
"balanceCents": "186321",
"balanceFormatted": "R$ 1.863,21",
"tickets": 5,
"role": "USER",
"status": "ACTIVE",
"twoFactorEnabled": false,
"referralCode": "PLAYER1ABC",
"totalDepositedCents": "500000",
"totalDepositedFormatted": "R$ 5.000,00",
"totalWithdrawnCents": "250000",
"totalWithdrawnFormatted": "R$ 2.500,00",
"totalWageredCents": "800000",
"totalWageredFormatted": "R$ 8.000,00",
"inventoryItemCount": 12,
"inventoryValueCents": "345000",
"inventoryValueFormatted": "R$ 3.450,00",
"lastLoginAt": "2026-02-11T10:00:00.000Z",
"lastLoginIp": "203.0.113.10",
"createdAt": "2025-11-01T08:00:00.000Z",
"updatedAt": "2026-02-11T10:00:00.000Z"
}PUT /admin/users/:id/status
Atualiza o status de um usuario (ativar, suspender ou banir).
Path Parameters:
| Parametro | Tipo | Descricao |
|---|---|---|
id | string | ID do usuario (bigint) |
Request Body:
class UpdateUserStatusDto {
status: 'ACTIVE' | 'SUSPENDED' | 'BANNED';
}Exemplo de Request:
{
"status": "SUSPENDED"
}Response (200):
{
"success": true
}POST /admin/users/:id/balance
Ajusta o saldo de um usuario (credito ou debito administrativo). Utiliza LockService.withUserBalanceLock() para prevenir race conditions. Cria transacao auditavel via TransactionService.
Path Parameters:
| Parametro | Tipo | Descricao |
|---|---|---|
id | string | ID do usuario (bigint) |
Request Body:
class UpdateUserBalanceDto {
amountCents: number; // positivo = credito, negativo = debito
reason: string;
}Exemplo de Request (Credito):
{
"amountCents": 50000,
"reason": "Compensacao por erro no sistema"
}Exemplo de Request (Debito):
{
"amountCents": -20000,
"reason": "Correcao de saldo indevido"
}Response (200):
{
"success": true,
"newBalanceCents": "236321",
"newBalanceFormatted": "R$ 2.363,21"
}Erros Possiveis:
// 404 - Usuario nao encontrado
{ "statusCode": 404, "message": "User not found" }
// 400 - Saldo insuficiente para debito
{ "statusCode": 400, "message": "Insufficient balance" }Comportamento Interno:
- Valores positivos:
TransactionService.credit()com reason"Admin adjustment: {reason} (by admin {adminId})" - Valores negativos:
TransactionService.debit()com mesma formatacao de reason - A operacao e executada dentro de
LockService.withUserBalanceLock()(distributed lock via Redlock) - Toda a operacao roda em
prisma.$transaction()
POST /admin/users/:id/ban
Bane um usuario, invalidando todas as suas sessoes ativas.
Path Parameters:
| Parametro | Tipo | Descricao |
|---|---|---|
id | string | ID do usuario (bigint) |
Request Body:
class BanUserDto {
reason: string;
}Exemplo de Request:
{
"reason": "Uso de software nao autorizado"
}Response (200):
{
"success": true,
"reason": "Uso de software nao autorizado"
}Erros Possiveis:
// 404 - Usuario nao encontrado
{ "statusCode": 404, "message": "Usuario nao encontrado" }
// 400 - Ja banido
{ "statusCode": 400, "message": "Usuario ja esta banido" }
// 400 - Tentativa de banir SUPER_ADMIN
{ "statusCode": 400, "message": "Nao e possivel banir um SUPER_ADMIN" }Comportamento Interno:
- Atualiza
user.statusparaBANNED - Deleta todas as sessoes do usuario (
session.deleteMany) - Executa em
prisma.$transaction()para atomicidade - SUPER_ADMIN nao pode ser banido (protecao)
POST /admin/users/:id/unban
Remove o ban de um usuario, reativando sua conta.
Path Parameters:
| Parametro | Tipo | Descricao |
|---|---|---|
id | string | ID do usuario (bigint) |
Request Body: Nenhum
Response (200):
{
"success": true
}GET /admin/users/:userId/case-openings
Retorna o historico de aberturas de caixas de um usuario especifico.
Path Parameters:
| Parametro | Tipo | Descricao |
|---|---|---|
userId | string | ID do usuario (bigint) |
Query Parameters:
| Parametro | Tipo | Obrigatorio | Default | Valores |
|---|---|---|---|---|
page | number | Nao | 1 | Min: 1 |
limit | number | Nao | 20 | Min: 1, Max: 100 |
caseId | string | Nao | - | Filtrar por caixa especifica |
Response (200):
interface PaginatedResponse<CaseOpeningAdminResponseDto> {
data: CaseOpeningAdminResponseDto[];
total: number;
page: number;
limit: number;
totalPages: number;
}Exemplo de Response:
{
"data": [
{
"id": "98712345678",
"caseId": "100",
"caseName": "Star Case",
"caseImageUrl": "https://cdn.example.com/cases/star.png",
"itemId": "500",
"itemName": "AK-47 | Redline",
"itemImageUrl": "https://cdn.example.com/items/ak47-redline.png",
"itemRarity": "CLASSIFIED",
"pricePaidCents": "1990",
"pricePaidFormatted": "R$ 19,90",
"itemValueCents": "15900",
"itemValueFormatted": "R$ 159,00",
"profitCents": "13910",
"profitFormatted": "R$ 139,10",
"isFlip": false,
"roll": 8734521,
"createdAt": "2026-02-10T15:30:00.000Z"
}
],
"total": 45,
"page": 1,
"limit": 20,
"totalPages": 3
}GET /admin/users/:userId/battles
Retorna o historico de batalhas de um usuario especifico.
Path Parameters:
| Parametro | Tipo | Descricao |
|---|---|---|
userId | string | ID do usuario (bigint) |
Query Parameters:
| Parametro | Tipo | Obrigatorio | Default | Valores |
|---|---|---|---|---|
page | number | Nao | 1 | Min: 1 |
limit | number | Nao | 20 | Min: 1, Max: 100 |
status | string | Nao | - | WAITING, IN_PROGRESS, FINISHED, CANCELLED |
Response (200):
{
"data": [
{
"id": "200",
"mode": "NORMAL",
"type": "ONE_VS_ONE",
"status": "FINISHED",
"creatorId": "1",
"creatorUsername": "PlayerOne",
"creatorAvatarUrl": "https://avatars.steamstatic.com/abc.jpg",
"winnerId": "1",
"winnerUsername": "PlayerOne",
"winnerTeam": 1,
"totalValueCents": "5970",
"totalValueFormatted": "R$ 59,70",
"isPrivate": false,
"playerCount": 2,
"caseCount": 3,
"createdAt": "2026-02-09T12:00:00.000Z",
"startedAt": "2026-02-09T12:01:00.000Z",
"finishedAt": "2026-02-09T12:05:00.000Z"
}
],
"total": 12,
"page": 1,
"limit": 20,
"totalPages": 1
}GET /admin/users/:userId/transactions
Retorna o historico de transacoes financeiras de um usuario.
Path Parameters:
| Parametro | Tipo | Descricao |
|---|---|---|
userId | string | ID do usuario (bigint) |
Query Parameters:
| Parametro | Tipo | Obrigatorio | Default | Valores |
|---|---|---|---|---|
page | number | Nao | 1 | Min: 1 |
limit | number | Nao | 20 | Min: 1, Max: 100 |
type | string | Nao | - | CREDIT, DEBIT |
Response (200):
{
"data": [
{
"id": "300",
"type": "CREDIT",
"amountCents": "50000",
"amountFormatted": "R$ 500,00",
"reason": "DEPOSIT",
"status": "COMPLETED",
"metadata": { "depositId": "123", "method": "PIX" },
"createdAt": "2026-02-10T14:00:00.000Z"
},
{
"id": "301",
"type": "DEBIT",
"amountCents": "1990",
"amountFormatted": "R$ 19,90",
"reason": "CASE_OPENING",
"status": "COMPLETED",
"metadata": { "caseId": "100", "caseName": "Star Case" },
"createdAt": "2026-02-10T14:05:00.000Z"
}
],
"total": 89,
"page": 1,
"limit": 20,
"totalPages": 5
}Gerenciamento de Caixas
O grupo de caixas expoe 7 endpoints para CRUD completo de caixas e gerenciamento de itens dentro delas, incluindo recalculo automatico de hash ranges.
GET /admin/cases
Lista todas as caixas com filtros, busca e ordenacao.
Query Parameters:
| Parametro | Tipo | Obrigatorio | Default | Valores |
|---|---|---|---|---|
page | number | Nao | 1 | Min: 1 |
limit | number | Nao | 20 | Min: 1, Max: 100 |
search | string | Nao | - | Busca por nome |
category | string | Nao | - | Filtrar por categoria |
isActive | boolean | Nao | - | Filtrar por status ativo |
sortBy | string | Nao | createdAt | Campo de ordenacao |
sortOrder | string | Nao | desc | asc, desc |
Response (200):
{
"data": [
{
"id": "100",
"name": "Star Case",
"slug": "star-case",
"description": "Uma caixa estelar com itens incriveis",
"imageUrl": "https://cdn.example.com/cases/star.png",
"bgColor": "#1a1a2e",
"priceCents": "1990",
"priceFormatted": "R$ 19,90",
"riskLevel": "MEDIUM",
"categoryId": "cat_1",
"categoryName": "Populares",
"isHot": true,
"isNew": false,
"order": 1,
"status": "ACTIVE",
"totalOpenings": 3421,
"itemCount": 20,
"createdAt": "2025-10-01T00:00:00.000Z",
"updatedAt": "2026-02-01T00:00:00.000Z"
}
],
"total": 15,
"page": 1,
"limit": 20,
"totalPages": 1
}GET /admin/cases/:id
Retorna detalhes completos de uma caixa incluindo todos os seus itens com probabilidades e hash ranges.
Path Parameters:
| Parametro | Tipo | Descricao |
|---|---|---|
id | string | ID da caixa (bigint) |
Response (200):
{
"id": "100",
"name": "Star Case",
"slug": "star-case",
"description": "Uma caixa estelar com itens incriveis",
"imageUrl": "https://cdn.example.com/cases/star.png",
"bgColor": "#1a1a2e",
"priceCents": "1990",
"priceFormatted": "R$ 19,90",
"riskLevel": "MEDIUM",
"categoryId": "cat_1",
"categoryName": "Populares",
"isHot": true,
"isNew": false,
"order": 1,
"status": "ACTIVE",
"totalOpenings": 3421,
"itemCount": 20,
"createdAt": "2025-10-01T00:00:00.000Z",
"updatedAt": "2026-02-01T00:00:00.000Z",
"items": [
{
"id": "ci_1",
"itemId": "500",
"name": "AK-47 | Redline",
"imageUrl": "https://cdn.example.com/items/ak47-redline.png",
"rarity": "CLASSIFIED",
"valueCents": "15900",
"valueFormatted": "R$ 159,00",
"weight": 100,
"dropChance": 1.0,
"isRare": false
},
{
"id": "ci_2",
"itemId": "501",
"name": "AWP | Dragon Lore",
"imageUrl": "https://cdn.example.com/items/awp-dlore.png",
"rarity": "COVERT",
"valueCents": "70765",
"valueFormatted": "R$ 707,65",
"weight": 5,
"dropChance": 0.05,
"isRare": true
}
]
}POST /admin/cases
Cria uma nova caixa.
Request Body:
class CreateCaseDto {
name: string; // obrigatorio
description?: string; // opcional
imageUrl: string; // obrigatorio
bgColor?: string; // opcional
priceCents: number; // obrigatorio, min: 1
riskLevel: 'LOW' | 'MEDIUM' | 'HIGH' | 'MAXIMUM'; // obrigatorio
categoryId: string; // obrigatorio
isHot?: boolean; // opcional
isNew?: boolean; // opcional
order?: number; // opcional
}Exemplo de Request:
{
"name": "Diamond Case",
"description": "A caixa mais rara do site",
"imageUrl": "https://cdn.example.com/cases/diamond.png",
"bgColor": "#2d1b69",
"priceCents": 4990,
"riskLevel": "HIGH",
"categoryId": "cat_premium",
"isHot": true,
"isNew": true,
"order": 0
}Response (201):
{
"id": "150",
"name": "Diamond Case",
"slug": "diamond-case",
"priceCents": "4990",
"priceFormatted": "R$ 49,90",
"riskLevel": "HIGH",
"status": "ACTIVE",
"createdAt": "2026-02-11T15:00:00.000Z"
}PATCH /admin/cases/:id
Atualiza campos de uma caixa existente. Todos os campos sao opcionais (partial update).
Path Parameters:
| Parametro | Tipo | Descricao |
|---|---|---|
id | string | ID da caixa (bigint) |
Request Body:
class UpdateCaseDto {
name?: string;
description?: string;
imageUrl?: string;
bgColor?: string;
priceCents?: number; // min: 1
riskLevel?: 'LOW' | 'MEDIUM' | 'HIGH' | 'MAXIMUM';
categoryId?: string;
isHot?: boolean;
isNew?: boolean;
order?: number;
status?: 'ACTIVE' | 'INACTIVE' | 'COMING_SOON';
}Exemplo de Request:
{
"priceCents": 3990,
"isHot": false,
"status": "INACTIVE"
}Response (200):
{
"id": "100",
"name": "Star Case",
"priceCents": "3990",
"priceFormatted": "R$ 39,90",
"status": "INACTIVE",
"updatedAt": "2026-02-11T15:30:00.000Z"
}DELETE /admin/cases/:id
Remove uma caixa do sistema. A caixa so pode ser deletada se nao houver aberturas vinculadas (integridade referencial).
Path Parameters:
| Parametro | Tipo | Descricao |
|---|---|---|
id | string | ID da caixa (bigint) |
Response (200):
{
"success": true
}POST /admin/cases/:caseId/items
Adiciona um item a uma caixa. Apos adicionar, recalcula automaticamente os hash ranges e drop chances de todos os itens da caixa com base nos pesos (weights).
Path Parameters:
| Parametro | Tipo | Descricao |
|---|---|---|
caseId | string | ID da caixa (bigint) |
Request Body:
class AddCaseItemDto {
itemId: string; // ID do item (bigint como string)
weight: number; // peso do item (min: 1), determina a probabilidade
}Exemplo de Request:
{
"itemId": "600",
"weight": 500
}Response (201):
{
"id": "ci_50",
"itemId": "600",
"name": "M4A4 | Howl",
"imageUrl": "https://cdn.example.com/items/m4a4-howl.png",
"rarity": "COVERT",
"valueCents": "35000",
"valueFormatted": "R$ 350,00",
"weight": 500,
"dropChance": 5.0,
"isRare": true
}Erros Possiveis:
// 404 - Caixa nao encontrada
{ "statusCode": 404, "message": "Caixa nao encontrada" }
// 404 - Item nao encontrado
{ "statusCode": 404, "message": "Item nao encontrado" }
// 400 - Item ja esta na caixa
{ "statusCode": 400, "message": "Este item ja esta na caixa" }Recalculo de Hash Ranges: Apos adicionar o item, o use case chama recalculateDropChances() que:
- Busca todos os itens da caixa ordenados por ID
- Calcula o peso total (
totalWeight) - Para cada item, calcula
dropChance = (weight / totalWeight) * 100 - Distribui os hash ranges de 0 ate
HASH_MAX(10.000.000) proporcionalmente - O ultimo item sempre termina em 10.000.000 (garante cobertura total)
private async recalculateDropChances(caseId: bigint) {
const HASH_MAX = 10000000;
const caseItems = await this.prisma.caseItem.findMany({
where: { caseId },
orderBy: { id: 'asc' },
});
const totalWeight = caseItems.reduce((sum, ci) => sum + ci.weight, 0);
let currentRangeStart = 0;
for (let i = 0; i < caseItems.length; i++) {
const ci = caseItems[i];
const dropChance = (ci.weight / totalWeight) * 100;
const rangeSize = Math.floor((ci.weight / totalWeight) * HASH_MAX);
const hashRangeEnd = i === caseItems.length - 1
? HASH_MAX
: currentRangeStart + rangeSize;
await this.prisma.caseItem.update({
where: { id: ci.id },
data: { dropChance, hashRangeStart: currentRangeStart, hashRangeEnd },
});
currentRangeStart = hashRangeEnd + 1;
}
}DELETE /admin/cases/:caseId/items/:itemId
Remove um item de uma caixa. Apos remover, recalcula os hash ranges e drop chances dos itens restantes.
Path Parameters:
| Parametro | Tipo | Descricao |
|---|---|---|
caseId | string | ID da caixa (bigint) |
itemId | string | ID do item (bigint) |
Response (200):
{
"success": true
}Gerenciamento de Itens
O grupo de itens expoe 5 endpoints para CRUD completo de itens (skins/armas).
GET /admin/items
Lista todos os itens com filtros por raridade, categoria e busca textual.
Query Parameters:
| Parametro | Tipo | Obrigatorio | Default | Valores |
|---|---|---|---|---|
page | number | Nao | 1 | Min: 1 |
limit | number | Nao | 20 | Min: 1, Max: 100 |
search | string | Nao | - | Busca por nome/marketHashName |
rarity | string | Nao | - | CONSUMER, INDUSTRIAL, MIL_SPEC, RESTRICTED, CLASSIFIED, COVERT, EXTRAORDINARY |
category | string | Nao | - | PISTOL, RIFLE, SMG, SNIPER, SHOTGUN, HEAVY, KNIFE, GLOVE, OTHER |
sortBy | string | Nao | createdAt | Campo de ordenacao |
sortOrder | string | Nao | desc | asc, desc |
Response (200):
{
"data": [
{
"id": "500",
"name": "AK-47 | Redline",
"marketHashName": "AK-47 | Redline (Field-Tested)",
"description": "Uma skin classica do CS",
"imageUrl": "https://cdn.example.com/items/ak47-redline.png",
"rarity": "CLASSIFIED",
"valueCents": "15900",
"valueFormatted": "R$ 159,00",
"weaponCategory": "RIFLE",
"collection": "Phoenix Collection",
"exterior": "FIELD_TESTED",
"isStatTrak": false,
"createdAt": "2025-09-15T00:00:00.000Z",
"updatedAt": "2026-01-20T00:00:00.000Z"
}
],
"total": 350,
"page": 1,
"limit": 20,
"totalPages": 18
}GET /admin/items/:id
Retorna detalhes completos de um item.
Path Parameters:
| Parametro | Tipo | Descricao |
|---|---|---|
id | string | ID do item (bigint) |
Response (200):
{
"id": "500",
"name": "AK-47 | Redline",
"marketHashName": "AK-47 | Redline (Field-Tested)",
"description": "Uma skin classica do CS",
"imageUrl": "https://cdn.example.com/items/ak47-redline.png",
"rarity": "CLASSIFIED",
"valueCents": "15900",
"valueFormatted": "R$ 159,00",
"weaponCategory": "RIFLE",
"collection": "Phoenix Collection",
"exterior": "FIELD_TESTED",
"isStatTrak": false,
"createdAt": "2025-09-15T00:00:00.000Z",
"updatedAt": "2026-01-20T00:00:00.000Z"
}POST /admin/items
Cria um novo item no catalogo.
Request Body:
class CreateItemDto {
name: string; // obrigatorio
marketHashName: string; // obrigatorio (nome unico do mercado Steam)
description?: string; // opcional
imageUrl: string; // obrigatorio
rarity: 'CONSUMER' | 'INDUSTRIAL' | 'MIL_SPEC' | 'RESTRICTED'
| 'CLASSIFIED' | 'COVERT' | 'EXTRAORDINARY'; // obrigatorio
valueCents: number; // obrigatorio, min: 1
weaponCategory: 'PISTOL' | 'RIFLE' | 'SMG' | 'SNIPER'
| 'SHOTGUN' | 'HEAVY' | 'KNIFE' | 'GLOVE' | 'OTHER'; // obrigatorio
collection?: string; // opcional
exterior?: 'FACTORY_NEW' | 'MINIMAL_WEAR' | 'FIELD_TESTED'
| 'WELL_WORN' | 'BATTLE_SCARRED' | 'NOT_PAINTED'; // opcional
isStatTrak?: boolean; // opcional, default: false
}Exemplo de Request:
{
"name": "AWP | Asiimov",
"marketHashName": "AWP | Asiimov (Field-Tested)",
"imageUrl": "https://cdn.example.com/items/awp-asiimov.png",
"rarity": "COVERT",
"valueCents": 25000,
"weaponCategory": "SNIPER",
"collection": "Operation Phoenix Weapon Case",
"exterior": "FIELD_TESTED",
"isStatTrak": false
}Response (201):
{
"id": "700",
"name": "AWP | Asiimov",
"marketHashName": "AWP | Asiimov (Field-Tested)",
"imageUrl": "https://cdn.example.com/items/awp-asiimov.png",
"rarity": "COVERT",
"valueCents": "25000",
"valueFormatted": "R$ 250,00",
"weaponCategory": "SNIPER",
"collection": "Operation Phoenix Weapon Case",
"exterior": "FIELD_TESTED",
"isStatTrak": false,
"createdAt": "2026-02-11T16:00:00.000Z",
"updatedAt": "2026-02-11T16:00:00.000Z"
}PATCH /admin/items/:id
Atualiza campos de um item existente. Todos os campos sao opcionais (partial update).
Path Parameters:
| Parametro | Tipo | Descricao |
|---|---|---|
id | string | ID do item (bigint) |
Request Body:
class UpdateItemDto {
name?: string;
description?: string;
imageUrl?: string;
rarity?: 'CONSUMER' | 'INDUSTRIAL' | 'MIL_SPEC' | 'RESTRICTED'
| 'CLASSIFIED' | 'COVERT' | 'EXTRAORDINARY';
valueCents?: number; // min: 1
weaponCategory?: 'PISTOL' | 'RIFLE' | 'SMG' | 'SNIPER'
| 'SHOTGUN' | 'HEAVY' | 'KNIFE' | 'GLOVE' | 'OTHER';
collection?: string;
exterior?: 'FACTORY_NEW' | 'MINIMAL_WEAR' | 'FIELD_TESTED'
| 'WELL_WORN' | 'BATTLE_SCARRED' | 'NOT_PAINTED';
isStatTrak?: boolean;
}Exemplo de Request:
{
"valueCents": 28000,
"rarity": "COVERT"
}Response (200):
{
"id": "700",
"name": "AWP | Asiimov",
"valueCents": "28000",
"valueFormatted": "R$ 280,00",
"rarity": "COVERT",
"updatedAt": "2026-02-11T16:30:00.000Z"
}DELETE /admin/items/:id
Remove um item do catalogo. O item so pode ser deletado se nao estiver vinculado a nenhuma caixa ou inventario de usuario (integridade referencial).
Path Parameters:
| Parametro | Tipo | Descricao |
|---|---|---|
id | string | ID do item (bigint) |
Response (200):
{
"success": true
}Gerenciamento de Batalhas
O grupo de batalhas expoe 3 endpoints para visualizacao e cancelamento de batalhas.
GET /admin/battles
Lista todas as batalhas com filtros por status, tipo e modo.
Query Parameters:
| Parametro | Tipo | Obrigatorio | Default | Valores |
|---|---|---|---|---|
page | number | Nao | 1 | Min: 1 |
limit | number | Nao | 20 | Min: 1, Max: 100 |
status | string | Nao | - | WAITING, IN_PROGRESS, FINISHED, CANCELLED |
type | string | Nao | - | ONE_VS_ONE, TWO_VS_TWO, THREE_VS_THREE, FOUR_VS_FOUR, ONE_VS_ONE_VS_ONE, ONE_VS_ONE_VS_ONE_VS_ONE |
mode | string | Nao | - | NORMAL, FLIP |
sortBy | string | Nao | createdAt | Campo de ordenacao |
sortOrder | string | Nao | desc | asc, desc |
Response (200):
{
"data": [
{
"id": "200",
"mode": "NORMAL",
"type": "ONE_VS_ONE",
"status": "FINISHED",
"creatorId": "1",
"creatorUsername": "PlayerOne",
"creatorAvatarUrl": "https://avatars.steamstatic.com/abc.jpg",
"winnerId": "2",
"winnerUsername": "PlayerTwo",
"winnerTeam": 2,
"totalValueCents": "5970",
"totalValueFormatted": "R$ 59,70",
"isPrivate": false,
"playerCount": 2,
"caseCount": 3,
"createdAt": "2026-02-09T12:00:00.000Z",
"startedAt": "2026-02-09T12:01:00.000Z",
"finishedAt": "2026-02-09T12:05:00.000Z"
}
],
"total": 203,
"page": 1,
"limit": 20,
"totalPages": 11
}GET /admin/battles/:id
Retorna detalhes completos de uma batalha incluindo jogadores, caixas e rodadas.
Path Parameters:
| Parametro | Tipo | Descricao |
|---|---|---|
id | string | ID da batalha (bigint) |
Response (200):
interface BattleDetailResponseDto {
id: string;
mode: string;
type: string;
status: string;
creatorId: string;
creatorUsername: string;
creatorAvatarUrl?: string;
winnerId?: string;
winnerUsername?: string;
winnerTeam?: number;
totalValueCents: string;
totalValueFormatted: string;
isPrivate: boolean;
playerCount: number;
caseCount: number;
createdAt: string;
startedAt?: string;
finishedAt?: string;
players: BattlePlayerResponseDto[];
cases: BattleCaseResponseDto[];
rounds: BattleRoundResponseDto[];
}Exemplo de Response:
{
"id": "200",
"mode": "NORMAL",
"type": "ONE_VS_ONE",
"status": "FINISHED",
"creatorId": "1",
"creatorUsername": "PlayerOne",
"winnerId": "2",
"winnerUsername": "PlayerTwo",
"winnerTeam": 2,
"totalValueCents": "5970",
"totalValueFormatted": "R$ 59,70",
"isPrivate": false,
"playerCount": 2,
"caseCount": 3,
"createdAt": "2026-02-09T12:00:00.000Z",
"startedAt": "2026-02-09T12:01:00.000Z",
"finishedAt": "2026-02-09T12:05:00.000Z",
"players": [
{
"id": "bp_1",
"odUserId": "1",
"odUsername": "PlayerOne",
"odAvatarUrl": "https://avatars.steamstatic.com/abc.jpg",
"team": 1,
"totalProfitCents": "-5970",
"totalProfitFormatted": "-R$ 59,70",
"position": 2,
"joinedAt": "2026-02-09T12:00:00.000Z"
},
{
"id": "bp_2",
"odUserId": "2",
"odUsername": "PlayerTwo",
"team": 2,
"totalProfitCents": "5970",
"totalProfitFormatted": "R$ 59,70",
"position": 1,
"joinedAt": "2026-02-09T12:00:30.000Z"
}
],
"cases": [
{
"id": "bc_1",
"caseId": "100",
"caseName": "Star Case",
"caseImageUrl": "https://cdn.example.com/cases/star.png",
"order": 1
}
],
"rounds": [
{
"id": "br_1",
"roundNumber": 1,
"playerId": "bp_1",
"playerUsername": "PlayerOne",
"itemId": "500",
"itemName": "AK-47 | Redline",
"itemImage": "https://cdn.example.com/items/ak47-redline.png",
"itemRarity": "CLASSIFIED",
"itemValueCents": "15900",
"itemValueFormatted": "R$ 159,00",
"isFlip": false,
"createdAt": "2026-02-09T12:02:00.000Z"
}
]
}POST /admin/battles/:id/cancel
Cancela uma batalha e reembolsa todos os jogadores participantes. O valor reembolsado e o custo total das caixas selecionadas.
Path Parameters:
| Parametro | Tipo | Descricao |
|---|---|---|
id | string | ID da batalha (bigint) |
Request Body:
class CancelBattleDto {
reason: string;
}Exemplo de Request:
{
"reason": "Batalha com problema tecnico"
}Response (200):
{
"success": true,
"reason": "Batalha com problema tecnico",
"refundedPlayers": 2
}Erros Possiveis:
// 404 - Batalha nao encontrada
{ "statusCode": 404, "message": "Batalha nao encontrada" }
// 400 - Batalha ja finalizada
{ "statusCode": 400, "message": "Nao e possivel cancelar uma batalha ja finalizada" }
// 400 - Batalha ja cancelada
{ "statusCode": 400, "message": "Batalha ja esta cancelada" }Comportamento Interno:
- Verifica se a batalha existe e nao esta FINISHED ou CANCELLED
- Atualiza status para
CANCELLEDe definefinishedAt - Calcula o custo total das caixas (
totalCaseCost) - Para cada jogador, cria uma transacao de credito via
TransactionService.credit()com reasonBATTLE_CANCELLED_REFUND - Metadata da transacao inclui
battleId,reasoneadminUserIdpara auditoria
Gerenciamento Financeiro
O grupo financeiro expoe 8 endpoints para gerenciamento de depositos, saques e transacoes.
GET /admin/deposits
Lista todos os depositos com filtros.
Query Parameters:
| Parametro | Tipo | Obrigatorio | Default | Valores |
|---|---|---|---|---|
page | number | Nao | 1 | Min: 1 |
limit | number | Nao | 20 | Min: 1, Max: 100 |
status | string | Nao | - | PENDING, CONFIRMED, FAILED, EXPIRED |
method | string | Nao | - | PIX, CREDIT_CARD, CRYPTO |
userId | string | Nao | - | Filtrar por usuario |
sortBy | string | Nao | createdAt | Campo de ordenacao |
sortOrder | string | Nao | desc | asc, desc |
Response (200):
{
"data": [
{
"id": "dep_1",
"userId": "1",
"username": "PlayerOne",
"avatarUrl": "https://avatars.steamstatic.com/abc.jpg",
"amountCents": "50000",
"amountFormatted": "R$ 500,00",
"method": "PIX",
"status": "PENDING",
"externalId": "pix_abc123",
"createdAt": "2026-02-11T14:00:00.000Z",
"confirmedAt": null
}
],
"total": 156,
"page": 1,
"limit": 20,
"totalPages": 8
}POST /admin/deposits/:id/confirm
Confirma manualmente um deposito pendente, creditando o saldo no usuario.
Path Parameters:
| Parametro | Tipo | Descricao |
|---|---|---|
id | string | ID do deposito (bigint) |
Response (200):
{
"success": true
}POST /admin/deposits/:id/reject
Rejeita um deposito pendente.
Path Parameters:
| Parametro | Tipo | Descricao |
|---|---|---|
id | string | ID do deposito (bigint) |
Request Body:
class RejectDepositDto {
reason: string;
}Exemplo de Request:
{
"reason": "Pagamento nao identificado"
}Response (200):
{
"success": true
}GET /admin/withdrawals
Lista todos os saques com filtros.
Query Parameters:
| Parametro | Tipo | Obrigatorio | Default | Valores |
|---|---|---|---|---|
page | number | Nao | 1 | Min: 1 |
limit | number | Nao | 20 | Min: 1, Max: 100 |
status | string | Nao | - | PENDING_2FA, PROCESSING, COMPLETED, FAILED, CANCELLED |
method | string | Nao | - | STEAM_TRADE, PIX |
userId | string | Nao | - | Filtrar por usuario |
sortBy | string | Nao | createdAt | Campo de ordenacao |
sortOrder | string | Nao | desc | asc, desc |
Response (200):
{
"data": [
{
"id": "wd_1",
"userId": "1",
"username": "PlayerOne",
"avatarUrl": "https://avatars.steamstatic.com/abc.jpg",
"amountCents": "30000",
"amountFormatted": "R$ 300,00",
"method": "PIX",
"status": "PROCESSING",
"destination": "email@example.com",
"failureReason": null,
"createdAt": "2026-02-11T10:00:00.000Z",
"processedAt": null
}
],
"total": 45,
"page": 1,
"limit": 20,
"totalPages": 3
}POST /admin/withdrawals/:id/approve
Aprova um saque pendente, movendo-o para o status de processamento.
Path Parameters:
| Parametro | Tipo | Descricao |
|---|---|---|
id | string | ID do saque (bigint) |
Response (200):
{
"success": true
}POST /admin/withdrawals/:id/reject
Rejeita um saque e reembolsa o usuario (credita o valor de volta no saldo).
Path Parameters:
| Parametro | Tipo | Descricao |
|---|---|---|
id | string | ID do saque (bigint) |
Request Body:
class RejectWithdrawalDto {
reason: string;
}Exemplo de Request:
{
"reason": "Dados de conta invalidos"
}Response (200):
{
"success": true
}POST /admin/withdrawals/:id/complete
Marca um saque como concluido apos o pagamento ter sido efetivamente realizado.
Path Parameters:
| Parametro | Tipo | Descricao |
|---|---|---|
id | string | ID do saque (bigint) |
Response (200):
{
"success": true
}GET /admin/transactions
Lista todas as transacoes financeiras da plataforma com filtros.
Query Parameters:
| Parametro | Tipo | Obrigatorio | Default | Valores |
|---|---|---|---|---|
page | number | Nao | 1 | Min: 1 |
limit | number | Nao | 20 | Min: 1, Max: 100 |
type | string | Nao | - | CREDIT, DEBIT |
userId | string | Nao | - | Filtrar por usuario |
reason | string | Nao | - | Filtrar por motivo (ex: DEPOSIT, CASE_OPENING) |
sortBy | string | Nao | createdAt | Campo de ordenacao |
sortOrder | string | Nao | desc | asc, desc |
Response (200):
{
"data": [
{
"id": "tx_1",
"userId": "1",
"username": "PlayerOne",
"avatarUrl": "https://avatars.steamstatic.com/abc.jpg",
"type": "CREDIT",
"amountCents": "50000",
"amountFormatted": "R$ 500,00",
"reason": "DEPOSIT",
"status": "COMPLETED",
"metadata": {
"depositId": "dep_1",
"method": "PIX"
},
"createdAt": "2026-02-10T14:00:00.000Z"
},
{
"id": "tx_2",
"userId": "1",
"username": "PlayerOne",
"type": "DEBIT",
"amountCents": "1990",
"amountFormatted": "R$ 19,90",
"reason": "CASE_OPENING",
"status": "COMPLETED",
"metadata": {
"caseId": "100",
"caseOpeningId": "98712345678"
},
"createdAt": "2026-02-10T14:05:00.000Z"
}
],
"total": 5234,
"page": 1,
"limit": 20,
"totalPages": 262
}Analytics
O grupo de analytics expoe 8 endpoints para analise detalhada de performance da plataforma, metricas de jogos e lucratividade.
GET /admin/analytics/rtp-by-case
Retorna o RTP (Return To Player) calculado para cada caixa com base nas aberturas reais, comparando com o RTP esperado (teorico).
Query Parameters:
| Parametro | Tipo | Obrigatorio | Default | Valores |
|---|---|---|---|---|
period | string | Nao | month | day, week, month, year |
Response (200):
interface RtpByCaseItem {
caseId: string;
caseName: string;
caseImageUrl: string;
totalOpenings: number;
totalWageredCents: string;
totalWageredFormatted: string;
totalPaidCents: string;
totalPaidFormatted: string;
profitCents: string;
profitFormatted: string;
rtpPercent: number; // RTP real (calculado das aberturas)
expectedRtpPercent: number | null; // RTP teorico (calculado dos weights)
}Exemplo de Response:
[
{
"caseId": "100",
"caseName": "Star Case",
"caseImageUrl": "https://cdn.example.com/cases/star.png",
"totalOpenings": 3421,
"totalWageredCents": "6807990",
"totalWageredFormatted": "R$ 68.079,90",
"totalPaidCents": "4776328",
"totalPaidFormatted": "R$ 47.763,28",
"profitCents": "2031662",
"profitFormatted": "R$ 20.316,62",
"rtpPercent": 70.16,
"expectedRtpPercent": 70.16
},
{
"caseId": "101",
"caseName": "Snow Case",
"caseImageUrl": "https://cdn.example.com/cases/snow.png",
"totalOpenings": 1523,
"totalWageredCents": "2421570",
"totalWageredFormatted": "R$ 24.215,70",
"totalPaidCents": "1533178",
"totalPaidFormatted": "R$ 15.331,78",
"profitCents": "888392",
"profitFormatted": "R$ 8.883,92",
"rtpPercent": 63.32,
"expectedRtpPercent": 63.32
}
]Calculo do RTP Real: rtpPercent = (totalPaidCents / totalWageredCents) * 100Calculo do RTP Esperado: soma de (dropChance / 100) * valueCents de cada item, dividido pelo preco da caixa
A lista e ordenada por profitCents decrescente (caixas mais lucrativas primeiro).
GET /admin/analytics/battle-rtp
Retorna estatisticas de RTP de batalhas com breakdown por tipo (1v1, 2v2, etc.), modo (NORMAL, FLIP) e combinacao (tipo+modo).
Query Parameters:
| Parametro | Tipo | Obrigatorio | Default | Valores |
|---|---|---|---|---|
period | string | Nao | month | day, week, month, year |
Response (200):
interface BattleRtpResponse {
totalBattles: number;
totalWageredCents: string;
totalWageredFormatted: string;
totalPaidCents: string;
totalPaidFormatted: string;
profitCents: string;
profitFormatted: string;
rtpPercent: number;
byType: Record<string, BreakdownEntry>;
byMode: Record<string, BreakdownEntry>;
byCombination: Record<string, BreakdownEntry>;
}
interface BreakdownEntry {
count: number;
wageredCents: string;
wageredFormatted: string;
profitCents: string;
profitFormatted: string;
rtpPercent: number;
}Exemplo de Response:
{
"totalBattles": 203,
"totalWageredCents": "2500000",
"totalWageredFormatted": "R$ 25.000,00",
"totalPaidCents": "2100000",
"totalPaidFormatted": "R$ 21.000,00",
"profitCents": "400000",
"profitFormatted": "R$ 4.000,00",
"rtpPercent": 84.0,
"byType": {
"ONE_VS_ONE": {
"count": 150,
"wageredCents": "1800000",
"wageredFormatted": "R$ 18.000,00",
"profitCents": "280000",
"profitFormatted": "R$ 2.800,00",
"rtpPercent": 84.44
},
"TWO_VS_TWO": {
"count": 53,
"wageredCents": "700000",
"wageredFormatted": "R$ 7.000,00",
"profitCents": "120000",
"profitFormatted": "R$ 1.200,00",
"rtpPercent": 82.86
}
},
"byMode": {
"NORMAL": {
"count": 180,
"wageredCents": "2200000",
"wageredFormatted": "R$ 22.000,00",
"profitCents": "350000",
"profitFormatted": "R$ 3.500,00",
"rtpPercent": 84.09
},
"FLIP": {
"count": 23,
"wageredCents": "300000",
"wageredFormatted": "R$ 3.000,00",
"profitCents": "50000",
"profitFormatted": "R$ 500,00",
"rtpPercent": 83.33
}
},
"byCombination": {
"ONE_VS_ONE . NORMAL": {
"count": 130,
"wageredCents": "1500000",
"wageredFormatted": "R$ 15.000,00",
"profitCents": "230000",
"profitFormatted": "R$ 2.300,00",
"rtpPercent": 84.67
}
}
}Calculo do Lucro de Batalhas: O lucro da plataforma em batalhas vem da diferenca entre itens removidos para o pool (REMOVED_TO_POOL) e itens adicionados do pool para compensacao (ADDED_FROM_POOL) via BattleSettlement.
GET /admin/analytics/upgrade-stats
Retorna estatisticas do sistema de upgrade incluindo taxa de sucesso, valores investidos e lucro.
Query Parameters:
| Parametro | Tipo | Obrigatorio | Default | Valores |
|---|---|---|---|---|
period | string | Nao | month | day, week, month, year |
Response (200):
interface UpgradeStatsResponse {
totalUpgrades: number;
successRate: number;
totalInvestedCents: string;
totalInvestedFormatted: string;
totalWonCents: string;
totalWonFormatted: string;
profitCents: string;
profitFormatted: string;
averageChance: number;
}Exemplo de Response:
{
"totalUpgrades": 890,
"successRate": 32.47,
"totalInvestedCents": "1200000",
"totalInvestedFormatted": "R$ 12.000,00",
"totalWonCents": "960000",
"totalWonFormatted": "R$ 9.600,00",
"profitCents": "240000",
"profitFormatted": "R$ 2.400,00",
"averageChance": 35.20
}Calculo:
totalInvested= soma devalueInvestedCents+balanceUsedCentsde todos os upgradestotalWon= soma detargetValueCentsdos upgrades comsuccess = truesuccessRate= (upgrades com sucesso / total de upgrades) * 100profitCents= totalInvested - totalWon
GET /admin/analytics/bot-profitability
Retorna a lucratividade dos bots em batalhas. Bots sao identificados pelo prefixo BOT_ no campo steamId.
Query Parameters:
| Parametro | Tipo | Obrigatorio | Default | Valores |
|---|---|---|---|---|
period | string | Nao | month | day, week, month, year |
Response (200):
interface BotProfitabilityItem {
userId: string;
username: string;
totalBattles: number;
totalProfitCents: string;
totalProfitFormatted: string;
winRate: number;
}Exemplo de Response:
[
{
"userId": "999",
"username": "BOT_Alpha",
"totalBattles": 45,
"totalProfitCents": "125000",
"totalProfitFormatted": "R$ 1.250,00",
"winRate": 55.56
},
{
"userId": "998",
"username": "BOT_Beta",
"totalBattles": 32,
"totalProfitCents": "-35000",
"totalProfitFormatted": "-R$ 350,00",
"winRate": 43.75
}
]A lista e ordenada por totalProfitCents decrescente (bots mais lucrativos primeiro). Retorna array vazio se nao houver bots cadastrados.
GET /admin/analytics/user-stats
Retorna estatisticas globais de usuarios: novos cadastros, usuarios ativos e metricas de receita por usuario.
Query Parameters: Nenhum
Response (200):
interface UserStatsResponse {
newUsersToday: number;
newUsersThisWeek: number;
newUsersThisMonth: number;
activeUsersToday: number;
depositingUsers: number;
arpuCents: string;
arpuFormatted: string;
}Exemplo de Response:
{
"newUsersToday": 12,
"newUsersThisWeek": 78,
"newUsersThisMonth": 245,
"activeUsersToday": 87,
"depositingUsers": 412,
"arpuCents": "8200",
"arpuFormatted": "R$ 82,00"
}Calculo:
activeUsersToday: usuarios comlastLoginAt >= inicio do diadepositingUsers: quantidade de usuarios distintos com pelo menos 1 deposito confirmadoarpu(Average Revenue Per User): total de depositos confirmados / total de usuarios
GET /admin/analytics/financial-overview
Retorna visao financeira detalhada com breakdown por metodo de pagamento, fluxo liquido e medias.
Query Parameters:
| Parametro | Tipo | Obrigatorio | Default | Valores |
|---|---|---|---|---|
period | string | Nao | month | day, week, month, year |
Response (200):
interface FinancialOverviewResponse {
depositsByMethod: Record<string, MethodBreakdown>;
withdrawalsByMethod: Record<string, MethodBreakdown>;
netFlowCents: string;
netFlowFormatted: string;
avgDepositCents: string;
avgDepositFormatted: string;
avgWithdrawalCents: string;
avgWithdrawalFormatted: string;
}
interface MethodBreakdown {
count: number;
totalCents: string;
totalFormatted: string;
}Exemplo de Response:
{
"depositsByMethod": {
"PIX": {
"count": 320,
"totalCents": "8500000",
"totalFormatted": "R$ 85.000,00"
},
"CREDIT_CARD": {
"count": 45,
"totalCents": "2300000",
"totalFormatted": "R$ 23.000,00"
},
"CRYPTO": {
"count": 12,
"totalCents": "1700000",
"totalFormatted": "R$ 17.000,00"
}
},
"withdrawalsByMethod": {
"PIX": {
"count": 89,
"totalCents": "4200000",
"totalFormatted": "R$ 42.000,00"
},
"STEAM_TRADE": {
"count": 34,
"totalCents": "1800000",
"totalFormatted": "R$ 18.000,00"
}
},
"netFlowCents": "6500000",
"netFlowFormatted": "R$ 65.000,00",
"avgDepositCents": "33156",
"avgDepositFormatted": "R$ 331,56",
"avgWithdrawalCents": "48780",
"avgWithdrawalFormatted": "R$ 487,80"
}Calculo:
netFlow= totalDepositos - totalSaquesavgDeposit= totalDepositos / quantidadeDepositosavgWithdrawal= totalSaques / quantidadeSaques- Apenas depositos
CONFIRMEDe saquesCOMPLETEDsao considerados
GET /admin/analytics/ggr
Retorna o GGR (Gross Gaming Revenue) com breakdown detalhado por tipo de jogo (caixas, upgrades, batalhas). Suporta filtro por usuario especifico.
Query Parameters:
| Parametro | Tipo | Obrigatorio | Default | Valores |
|---|---|---|---|---|
period | string | Nao | month | day, week, month, year |
userId | string | Nao | - | Filtrar GGR de usuario especifico |
Response (200):
interface GgrResponse {
totalGgrCents: string;
totalGgrFormatted: string;
caseOpeningGgr: {
wageredCents: string;
wageredFormatted: string;
paidCents: string;
paidFormatted: string;
ggrCents: string;
ggrFormatted: string;
count: number;
rtpPercent: number;
};
upgradeGgr: {
wageredCents: string;
wageredFormatted: string;
paidCents: string;
paidFormatted: string;
ggrCents: string;
ggrFormatted: string;
count: number;
rtpPercent: number;
};
battleGgr: {
removedToPoolCents: string;
removedToPoolFormatted: string;
addedFromPoolCents: string;
addedFromPoolFormatted: string;
ggrCents: string;
ggrFormatted: string;
count: number;
};
period: string;
}Exemplo de Response:
{
"totalGgrCents": "1250000",
"totalGgrFormatted": "R$ 12.500,00",
"caseOpeningGgr": {
"wageredCents": "6807990",
"wageredFormatted": "R$ 68.079,90",
"paidCents": "4776328",
"paidFormatted": "R$ 47.763,28",
"ggrCents": "2031662",
"ggrFormatted": "R$ 20.316,62",
"count": 3421,
"rtpPercent": 70.16
},
"upgradeGgr": {
"wageredCents": "1200000",
"wageredFormatted": "R$ 12.000,00",
"paidCents": "960000",
"paidFormatted": "R$ 9.600,00",
"ggrCents": "240000",
"ggrFormatted": "R$ 2.400,00",
"count": 890,
"rtpPercent": 80.0
},
"battleGgr": {
"removedToPoolCents": "500000",
"removedToPoolFormatted": "R$ 5.000,00",
"addedFromPoolCents": "420000",
"addedFromPoolFormatted": "R$ 4.200,00",
"ggrCents": "80000",
"ggrFormatted": "R$ 800,00",
"count": 156
},
"period": "month"
}Calculo por Tipo de Jogo:
- Caixas: GGR = soma(pricePaidCents) - soma(itemValueCents)
- Upgrades: GGR = soma(valueInvestedCents + balanceUsedCents) - soma(targetValueCents dos sucedidos)
- Batalhas (global): GGR = REMOVED_TO_POOL - ADDED_FROM_POOL (via BattleSettlement)
- Batalhas (por usuario): GGR = -(totalProfitCents do jogador)
Quando userId e fornecido, as batalhas sao calculadas pela perspectiva do jogador usando battlePlayer.totalProfitCents.
GET /admin/analytics/ggr-by-user
Retorna o ranking de GGR por usuario, mostrando quanto cada jogador contribuiu de receita para a plataforma por tipo de jogo.
Query Parameters:
| Parametro | Tipo | Obrigatorio | Default | Valores |
|---|---|---|---|---|
period | string | Nao | month | day, week, month, year |
page | number | Nao | 1 | Min: 1 |
limit | number | Nao | 20 | Min: 1, Max: 100 |
sortOrder | string | Nao | desc | asc, desc |
Response (200):
interface PaginatedResponse<GgrByUserItem> {
data: GgrByUserItem[];
total: number;
page: number;
limit: number;
totalPages: number;
}
interface GgrByUserItem {
userId: string;
username: string;
avatarUrl?: string;
caseGgrCents: string;
caseGgrFormatted: string;
upgradeGgrCents: string;
upgradeGgrFormatted: string;
battleGgrCents: string;
battleGgrFormatted: string;
totalGgrCents: string;
totalGgrFormatted: string;
}Exemplo de Response:
{
"data": [
{
"userId": "1",
"username": "HighRoller",
"avatarUrl": "https://avatars.steamstatic.com/abc.jpg",
"caseGgrCents": "350000",
"caseGgrFormatted": "R$ 3.500,00",
"upgradeGgrCents": "120000",
"upgradeGgrFormatted": "R$ 1.200,00",
"battleGgrCents": "80000",
"battleGgrFormatted": "R$ 800,00",
"totalGgrCents": "550000",
"totalGgrFormatted": "R$ 5.500,00"
},
{
"userId": "5",
"username": "LuckyPlayer",
"caseGgrCents": "-250000",
"caseGgrFormatted": "-R$ 2.500,00",
"upgradeGgrCents": "50000",
"upgradeGgrFormatted": "R$ 500,00",
"battleGgrCents": "-100000",
"battleGgrFormatted": "-R$ 1.000,00",
"totalGgrCents": "-300000",
"totalGgrFormatted": "-R$ 3.000,00"
}
],
"total": 412,
"page": 1,
"limit": 20,
"totalPages": 21
}Observacoes:
- GGR negativo indica que o jogador ganhou mais do que apostou (prejuizo para a casa)
- Bots (steamId com prefixo
BOT_) sao excluidos do ranking - A query usa CTEs (Common Table Expressions) com JOINs entre
case_openings,upgradesebattle_settlementspara calcular o GGR por tipo - Somente usuarios que participaram de pelo menos uma atividade no periodo sao listados
- Ordenacao padrao e por
total_ggr DESC(maiores geradores de receita primeiro)
DTOs e Interfaces de Resposta
Paginacao Padrao
Todos os endpoints de listagem seguem o mesmo padrao de paginacao:
class PaginationQueryDto {
page?: number = 1; // Min: 1
limit?: number = 20; // Min: 1, Max: 100
}
interface PaginatedResponse<T> {
data: T[];
total: number;
page: number;
limit: number;
totalPages: number;
}Periodos de Analise
Os endpoints de analytics aceitam o parametro period que determina o intervalo de dados analisados:
class AnalyticsQueryDto {
period?: 'day' | 'week' | 'month' | 'year' = 'month';
}| Periodo | Intervalo |
|---|---|
day | Ultimas 24 horas |
week | Ultimos 7 dias |
month | Ultimos 30 dias |
year | Ultimos 12 meses |
Formatacao Monetaria
Todos os valores monetarios sao retornados em dois formatos:
*Cents(string): Valor inteiro em centavos de real (BRL), como string para preservar precisao de BigInt*Formatted(string): Valor formatado em reais viaMoneyService.format(), no formatoR$ X.XXX,XX
Exemplo: "balanceCents": "186321" e "balanceFormatted": "R$ 1.863,21"
Enums Utilizados
Status do Usuario:
type UserStatus = 'ACTIVE' | 'SUSPENDED' | 'BANNED';Role do Usuario:
type UserRole = 'USER' | 'ADMIN' | 'SUPER_ADMIN';Raridade do Item:
type ItemRarity = 'CONSUMER' | 'INDUSTRIAL' | 'MIL_SPEC' | 'RESTRICTED'
| 'CLASSIFIED' | 'COVERT' | 'EXTRAORDINARY';Categoria de Arma:
type WeaponCategory = 'PISTOL' | 'RIFLE' | 'SMG' | 'SNIPER'
| 'SHOTGUN' | 'HEAVY' | 'KNIFE' | 'GLOVE' | 'OTHER';Exterior do Item:
type ItemExterior = 'FACTORY_NEW' | 'MINIMAL_WEAR' | 'FIELD_TESTED'
| 'WELL_WORN' | 'BATTLE_SCARRED' | 'NOT_PAINTED';Nivel de Risco:
type RiskLevel = 'LOW' | 'MEDIUM' | 'HIGH' | 'MAXIMUM';Status da Caixa:
type CaseStatus = 'ACTIVE' | 'INACTIVE' | 'COMING_SOON';Status da Batalha:
type BattleStatus = 'WAITING' | 'IN_PROGRESS' | 'FINISHED' | 'CANCELLED';Tipo de Batalha:
type BattleType = 'ONE_VS_ONE' | 'TWO_VS_TWO' | 'THREE_VS_THREE' | 'FOUR_VS_FOUR'
| 'ONE_VS_ONE_VS_ONE' | 'ONE_VS_ONE_VS_ONE_VS_ONE';Modo de Batalha:
type BattleMode = 'NORMAL' | 'FLIP' | 'SHARED';Status do Deposito:
type DepositStatus = 'PENDING' | 'CONFIRMED' | 'FAILED' | 'EXPIRED';Metodo de Deposito:
type DepositMethod = 'PIX' | 'CREDIT_CARD' | 'CRYPTO';Status do Saque:
type WithdrawalStatus = 'PENDING_2FA' | 'PROCESSING' | 'COMPLETED' | 'FAILED' | 'CANCELLED';Metodo de Saque:
type WithdrawalMethod = 'STEAM_TRADE' | 'PIX';Tipo de Transacao:
type TransactionType = 'CREDIT' | 'DEBIT';Codigos de Erro HTTP
| Codigo | Descricao | Quando |
|---|---|---|
| 200 | Sucesso | Operacao realizada |
| 201 | Criado | Novo recurso criado (POST) |
| 400 | Bad Request | Validacao falhou, dados invalidos |
| 401 | Unauthorized | Sessao invalida ou expirada |
| 403 | Forbidden | Usuario sem role ADMIN/SUPER_ADMIN |
| 404 | Not Found | Recurso nao encontrado |
| 409 | Conflict | Conflito de dados (ex: item duplicado em caixa) |
| 500 | Internal Server Error | Erro inesperado no servidor |
Arquivos Relacionados
| Arquivo | Descricao |
|---|---|
src/presentation/controllers/admin.controller.ts | Controller com 44 endpoints |
src/application/dto/admin.dto.ts | DTOs de request e interfaces de response |
src/presentation/guards/admin.guard.ts | Guard de autorizacao admin |
src/presentation/guards/auth.guard.ts | Guard de autenticacao por sessao |
src/application/services/money.service.ts | Formatacao monetaria BRL |
src/application/services/transaction.service.ts | Credito/debito com double-entry |
src/application/services/period.service.ts | Calculo de datas por periodo |
src/infrastructure/locks/lock.service.ts | Distributed locks via Redlock |
src/application/use-cases/admin/*.ts | 44 use cases individuais |
