From 88c69f534c94a47df03c5015bd390893a63f7068 Mon Sep 17 00:00:00 2001 From: achmad Date: Fri, 29 May 2026 16:36:44 +0700 Subject: [PATCH] feat: add game and payments handlers Co-Authored-By: Claude Opus 4.8 (1M context) --- backend/src/lib/handlers/game.ts | 57 ++++++++++++++++++++++++++++ backend/src/lib/handlers/payments.ts | 51 +++++++++++++++++++++++++ 2 files changed, 108 insertions(+) create mode 100644 backend/src/lib/handlers/game.ts create mode 100644 backend/src/lib/handlers/payments.ts diff --git a/backend/src/lib/handlers/game.ts b/backend/src/lib/handlers/game.ts new file mode 100644 index 0000000..9ced4d0 --- /dev/null +++ b/backend/src/lib/handlers/game.ts @@ -0,0 +1,57 @@ +import { route, HandlerContext, HttpError } from '@/lib/router'; +import { getDb } from '@/lib/db'; + +route('game/start', ['POST'], (ctx: HandlerContext) => { + const { steam_id, hero, hero_level, difficulty, player_name, match_id, session_id, session_participants } = ctx.body as any; + if (!steam_id) throw new HttpError(400, 'steam_id required'); + const db = getDb(); + + const gameId = `game_${Date.now()}_${Math.floor(Math.random() * 100000)}`; + const newMatchId = match_id || Math.floor(Math.random() * 100000000); + + db.prepare('INSERT OR REPLACE INTO game_sessions (game_id, match_id, session_id, status) VALUES (?, ?, ?, \'active\')') + .run(gameId, newMatchId, session_id || ''); + + return { game_id: gameId, match_id: newMatchId }; +}); + +route('game/heartbeat', ['POST'], (ctx: HandlerContext) => { + return { success: true }; +}); + +route('game', ['POST'], (ctx: HandlerContext) => { + const { steam_id, result, duration, kills, deaths, score, outgoing_damage, incoming_damage, + hero, hero_level, items, modifiers, aghanim_scepter, aghanim_shard, gold_earned, + difficulty, session_id, game_id } = ctx.body as any; + if (!steam_id) throw new HttpError(400, 'steam_id required'); + + const db = getDb(); + db.prepare(` + INSERT INTO game_history (steam_id, game_id, result, duration, kills, deaths, score, + outgoing_damage, incoming_damage, hero, hero_level, items, modifiers, + aghanim_scepter, aghanim_shard, gold_earned, difficulty, session_id) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + `).run(steam_id, game_id || null, result || 'loss', duration || 0, kills || 0, deaths || 0, + score || 0, outgoing_damage || 0, incoming_damage || 0, hero || '', hero_level || 1, + items || '', modifiers || '', aghanim_scepter ? 1 : 0, aghanim_shard ? 1 : 0, + gold_earned || 0, difficulty || 'normal', session_id || ''); + + if (game_id) { + db.prepare("UPDATE game_sessions SET status = 'completed' WHERE game_id = ?").run(game_id); + } + + return { success: true }; +}); + +route('game/:id/players', ['GET'], (ctx: HandlerContext) => { + const db = getDb(); + const session = db.prepare('SELECT * FROM game_sessions WHERE game_id = ? OR match_id = ?') + .get(ctx.params.id, parseInt(ctx.params.id) || 0) as any; + if (!session) return { players: [] }; + + const players = db.prepare( + 'SELECT DISTINCT steam_id, hero, hero_level, result FROM game_history WHERE match_id = ? OR game_id = ?' + ).all(session.match_id, session.game_id); + + return { party_players: players, players }; +}); diff --git a/backend/src/lib/handlers/payments.ts b/backend/src/lib/handlers/payments.ts new file mode 100644 index 0000000..33b82c6 --- /dev/null +++ b/backend/src/lib/handlers/payments.ts @@ -0,0 +1,51 @@ +import { route, HandlerContext, HttpError } from '@/lib/router'; +import { getDb } from '@/lib/db'; + +// POST /payments/robokassa/link — Auto-grant purchased currency +route('payments/robokassa/link', ['POST'], (ctx: HandlerContext) => { + const { steam_id, amount_rub } = ctx.body as any; + if (!steam_id) throw new HttpError(400, 'steam_id required'); + const db = getDb(); + + const donateShards = (amount_rub || 100) * 10; + db.prepare('UPDATE players SET donate_currency = donate_currency + ?, updated_at = datetime(\'now\') WHERE steam_id = ?') + .run(donateShards, steam_id); + + return { + ok: true, + payment_url: '', + donate_shards: donateShards, + inv_id: Math.floor(Math.random() * 100000), + }; +}); + +// POST /payments/bundles/link — Auto-grant bundle items +route('payments/bundles/link', ['POST'], (ctx: HandlerContext) => { + const { steam_id, bundle_id } = ctx.body as any; + if (!steam_id) throw new HttpError(400, 'steam_id required'); + const db = getDb(); + + db.prepare('UPDATE players SET free_currency = free_currency + 500, donate_currency = donate_currency + 200, updated_at = datetime(\'now\') WHERE steam_id = ?') + .run(steam_id); + + return { + ok: true, + payment_url: '', + inv_id: Math.floor(Math.random() * 100000), + message: 'Bundle granted', + }; +}); + +// GET /payments/deals?steam_id= — Return deal catalog +route('payments/deals', ['GET'], (ctx: HandlerContext) => { + return { + ok: true, + bundles: [ + { id: 'starter_bundle', name: 'Starter Pack', description: 'Get started with 500 shards', price_free: 0, price_donate: 0, items: [{ item_id: 'starter_pack', name: 'Starter Pack' }] }, + { id: 'hero_bundle_1', name: 'Hero Bundle I', description: 'Unlock a random hero', price_free: 1000, price_donate: 0, items: [{ item_id: 'hero_bundle_1', name: 'Hero Bundle' }] }, + ], + daily: { available: true, items: [] }, + weekly: { available: true, items: [] }, + player_created_at_unix: Math.floor(Date.now() / 1000), + }; +});