diff --git a/backend/cmd/seedrandomheroes/main.go b/backend/cmd/seedrandomheroes/main.go new file mode 100644 index 0000000..93c0c2f --- /dev/null +++ b/backend/cmd/seedrandomheroes/main.go @@ -0,0 +1,65 @@ +// Command seedrandomheroes inserts N level-1 heroes with random spawn/starter gear (not a migration). +// Usage: from backend/: go run ./cmd/seedrandomheroes -n 500 +// Requires DB env vars (same as server): DB_HOST, DB_PORT, DB_USER, DB_PASSWORD, DB_NAME. +package main + +import ( + "context" + "flag" + "fmt" + "log/slog" + "math/rand" + "os" + "time" + + "github.com/denisovdennis/autohero/internal/config" + "github.com/denisovdennis/autohero/internal/storage" +) + +func main() { + n := flag.Int("n", 500, "number of heroes to create") + telegramBase := flag.Int64("telegram-base", 8_100_000_000_000, "first synthetic telegram_id (each hero gets base+0..n-1)") + flag.Parse() + + logger := slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelInfo})) + cfg := config.Load() + ctx := context.Background() + + pool, err := storage.NewPostgres(ctx, cfg.DB, logger) + if err != nil { + logger.Error("postgres", "error", err) + os.Exit(1) + } + defer pool.Close() + + store := storage.NewHeroStore(pool, logger) + rng := rand.New(rand.NewSource(time.Now().UnixNano())) + + prefixes := []string{ + "Aldo", "Bree", "Cade", "Dara", "Ewan", "Fira", "Gorn", "Hett", "Ivor", "Jesa", + "Kael", "Lina", "Miro", "Nyx", "Orin", "Pike", "Quin", "Riva", "Sven", "Tess", + "Ulric", "Venn", "Wren", "Yara", "Zara", + } + + var ok, fail int + for i := 0; i < *n; i++ { + tg := *telegramBase + int64(i) + // Unique per batch: idx_heroes_name_lower — include telegramBase so re-runs with new -telegram-base never collide. + name := fmt.Sprintf("%s_%d_%05d", prefixes[rng.Intn(len(prefixes))], *telegramBase, i) + _, err := store.CreateHeroWithSpawn(ctx, tg, name) + if err != nil { + logger.Warn("create failed", "i", i, "telegram_id", tg, "name", name, "error", err) + fail++ + continue + } + ok++ + if ok%100 == 0 { + logger.Info("progress", "created", ok) + } + } + + logger.Info("done", "created", ok, "failed", fail, "requested", *n) + if fail > 0 { + os.Exit(1) + } +} diff --git a/backend/internal/handler/game.go b/backend/internal/handler/game.go index abdb287..366cb5a 100644 --- a/backend/internal/handler/game.go +++ b/backend/internal/handler/game.go @@ -141,21 +141,19 @@ func resolveTelegramID(r *http.Request) (int64, bool) { if id, ok := TelegramIDFromContext(r.Context()); ok { return id, true } - + // Dev fallback: accept telegramId query param. + idStr := r.URL.Query().Get("telegramId") + if idStr != "" { + id, err := strconv.ParseInt(idStr, 10, 64) + if err == nil { + return id, true + } + } + // Localhost fallback: default to telegram_id 1 for testing. host := r.Host if strings.HasPrefix(host, "localhost") || strings.HasPrefix(host, "127.0.0.1") || strings.HasPrefix(host, "192.168.0.53") { - // Dev fallback: accept telegramId query param. - idStr := r.URL.Query().Get("telegramId") - if idStr != "" { - id, err := strconv.ParseInt(idStr, 10, 64) - if err == nil { - return id, true - } - } - // Localhost fallback: default to telegram_id 1 for testing. return 1, true } - return 0, false } diff --git a/backend/internal/storage/hero_store.go b/backend/internal/storage/hero_store.go index 2d711bd..9012b71 100644 --- a/backend/internal/storage/hero_store.go +++ b/backend/internal/storage/hero_store.go @@ -661,7 +661,6 @@ func (s *HeroStore) ListHeroesForEngineBootstrap(ctx context.Context) ([]*model. query := heroSelectQuery + ` WHERE h.hp > 0 AND h.ws_disconnected_at IS NOT NULL ORDER BY h.updated_at ASC - LIMIT $1 ` rows, err := s.pool.Query(ctx, query) diff --git a/frontend/src/shared/telegram.ts b/frontend/src/shared/telegram.ts index d067255..675e191 100644 --- a/frontend/src/shared/telegram.ts +++ b/frontend/src/shared/telegram.ts @@ -131,16 +131,22 @@ function readDevTelegramIdFromQuery(): number | null { } /** - * Telegram user id for API/WS: dev query ?telegramId= on localhost/LAN, else initDataUnsafe.user.id. + * Telegram user id for API/WS: real Mini App user wins when present (must match initData on server). + * Dev-only: ?telegramId= on localhost/LAN when not in Telegram or before user is available. */ export function getTelegramUserId(): number | null { + const tg = getTelegramWebApp(); + const fromTg = tg + ? (tg.initDataUnsafe?.user as { id?: number } | undefined)?.id + : undefined; + if (fromTg != null && fromTg > 0) { + return fromTg; + } + const fromQuery = readDevTelegramIdFromQuery(); if (fromQuery != null) return fromQuery; - const tg = getTelegramWebApp(); - if (!tg) return null; - const user = tg.initDataUnsafe?.user as { id?: number } | undefined; - return user?.id ?? null; + return null; } /** Get viewport dimensions accounting for Telegram safe areas */