Files
HokmPlay/server
soroush.asadi 0790ad6fe0
CI/CD / CI - API (dotnet build + engine sim) (push) Successful in 2m4s
CI/CD / CI - Web (tsc + next build) (push) Successful in 1m9s
CI/CD / Deploy - local stack (db + server + web) (push) Successful in 2m11s
chore(prod): real leaderboard, prod guards, payment hardening
Production-readiness pass — remove mock-in-prod and harden the server:
- leaderboard: new DB-backed LeaderboardService + /api/leaderboard (ranked by
  rating, 30s cache, bounded scan); client now calls it instead of mock fake data.
- online count: client uses real /api/stats/online (dropped the fabricated ≥50 floor).
- boot guards (Production): refuse to start if Sms:ApiKey is missing (OTP would
  run in dev mode = fixed code for any phone) or Iab:AllowUnverified is true
  (forged tokens could mint coins).
- payments: ZarinPal + IAB HttpClients get 15s timeouts; ZarinPal/FlatPay gateway
  failures are now logged instead of silently swallowed.
- OTP: periodic prune of expired codes + stale rate-limit logs (was an unbounded
  in-memory leak over a long-running process).
- DB: EnableRetryOnFailure for Postgres (transient-fault resilience).
- docker-compose: ZarinPal sandbox now defaults to false (real payments).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 09:03:12 +03:30
..

Hokm — .NET 10 + SignalR backend

Authoritative realtime server for the Hokm game. The TypeScript engine (../src/lib/hokm) is ported to C# here so the server is the source of truth.

Projects

Project What
src/Hokm.Engine Pure C# rules + AI (port of src/lib/hokm) — deal, hakem, trump, tricks, scoring, Kot, bot
src/Hokm.Server ASP.NET Core + SignalR GameHub, in-memory matchmaking/rooms, JWT auth
tools/Hokm.Sim All-AI simulation that validates the engine port

NuGet sources

NuGet.config restores only from the configured mirrors (no nuget.org):

  • https://mirror.soroushasadi.com/repository/nuget-group/index.json (Nexus)
  • https://package-mirror.liara.ir/repository/nuget/index.json (Liara)

Directory.Build.props disables NuGet audit (avoids reaching api.nuget.org).

Run

cd server
dotnet run --project tools/Hokm.Sim -c Release   # validate the engine
dotnet run --project src/Hokm.Server -c Release  # → http://localhost:5005

API

Dev auth (replace with the V2 Identity Service + Kavenegar/SMS.ir later):

  • POST /api/auth/otp/request { phone }{ devCode: "1234" }
  • POST /api/auth/otp/verify { phone, code, name? }{ token, userId, name } (code 1234)
  • POST /api/auth/email { email, password, name? }{ token, userId, name }

SignalR hub: /hub/game (JWT required; pass ?access_token=<jwt>).

Client → server methods:

  • StartMatchmaking({ name, avatar, level, plan }) — pro skips the queue
  • CancelMatchmaking()
  • ChooseTrump(suit) · PlayCard(cardId) · SendReaction(reaction)

Server → client events:

  • matchmaking { phase, players, queuePosition }
  • matchFound { roomId, seat }
  • state GameStateDto (per-seat: only your own hand is included; others are counts)
  • reaction { seat, reaction }

The server runs server-side turn timers (20s → AI auto-plays), fills empty seats with bots, drives bot turns, and handles disconnect (the seat is marked offline and the timer auto-plays until they reconnect).

Wiring the Next.js client (next step)

Implement SignalrService in ../src/lib/online/signalr-service.ts against the existing OnlineService interface (@microsoft/signalr), then switch getService() in ../src/lib/online/service.ts from the mock to it. The hub's GameStateDto is shaped to map directly onto the client GameState.

Persistence (EF Core)

AppDbContext stores each profile as a JSON blob (ProfileRow) + a coin LedgerRow audit trail. Provider is config-driven:

// appsettings.json
"Database": { "Provider": "sqlite" },            // or "postgres"
"ConnectionStrings": { "Default": "Data Source=hokm.db" }
// Postgres (Supabase): Provider="postgres",
//   Default="Host=...;Database=...;Username=...;Password=...;SSL Mode=Require"

Schema is created at startup via EnsureCreated() (swap to EF migrations for prod).

Server-authoritative endpoints (JWT):

  • GET /api/profile · PUT /api/profile (displayName/avatar/title/cardFront/cardBack)
  • POST /api/profile/plan (upgrade to pro)
  • GET /api/coins/packs · POST /api/coins/buy { packId } (credits + ledger; real Zarinpal/IDPay TODO)
  • POST /api/match/result { MatchSummary } → computes rewards via Profiles/Gamification.cs (C# port of src/lib/online/gamification.ts), updates profile + ledger, returns RewardResult

Auth (/api/auth/...) upserts the profile on first sign-in.

TODO

  • Wire the Next client (SignalrService) to these endpoints for profile/coins/match (currently still mock-backed to avoid a half-migrated economy)
  • EF migrations; Postgres (Supabase) connection for prod
  • JWT issued by the V2 Identity Service; phone OTP via Kavenegar/SMS.ir
  • Private rooms + friend invites over the hub; server-side ranked entry deduction at match start