# rudder-go-sdk Серверный Go SDK для LiveOps-платформы Rudder. Покрывает server-admin поверхность `/game/v1` (auth по `X-API-Key`). ## Install Модуль раздаётся напрямую с self-hosted Gitea, минуя proxy.golang.org, поэтому Go должен пропускать его мимо checksum database: ```bash go env -w GOPRIVATE=hub.rudder.build/* go get hub.rudder.build/rudder/rudder-go-sdk/v2@latest ``` Требуется Go 1.21+. Токен не нужен — репозиторий публичный. ## Версионирование Потребители фиксируют версию по git-тегу (`go get hub.rudder.build/rudder/rudder-go-sdk/v2@vX.Y.Z`). Текущая мажорная версия — `v2`, поэтому путь модуля содержит суффикс `/v2`, а импорты идут через `hub.rudder.build/rudder/rudder-go-sdk/v2`. ## Доступ `AdminKey` — это Admin Key окружения: у проекта их два, для `staging` и для `prod` (app.rudder.build → проект → Settings → Environments). Ключ сам определяет и проект, и окружение, поэтому ни project ID, ни параметр окружения передавать не нужно; игроки и контент разных окружений полностью изолированы. Храните ключ только на сервере — никогда не вшивайте в игровые клиенты. ## Usage ```go client, err := rudder.New(rudder.Config{ AdminKey: os.Getenv("RUDDER_ADMIN_KEY"), }) if err != nil { // *rudder.APIError с кодом sdk/invalid-options } players, err := client.Players.List(ctx, rudder.ListPlayersParameters{Limit: 100}) ``` Публичные типы живут в пакете `rudder`. Пакет `models` — сгенерированный wire-формат, в прикладном коде его импортировать не нужно. ## Handles `client.Player(id)` создаёт handle без HTTP-запроса. Handle — сахар над batch-методами сервисов: каждая операция отправляет batch из одного элемента с подставленным playerID. ```go player := client.Player("p1") wallet, err := player.Wallet().Adjust(ctx, rudder.AdjustPlayerWalletParameters{ IdempotencyKey: "grant-001", CurrencyCode: "coins", Amount: 100, Reason: "compensation", }) history, err := player.Wallet().History(ctx, rudder.WalletHistoryParameters{Limit: 50}) storage, err := player.Storage().Get(ctx, "settings") updated, err := player.Storage().Upsert(ctx, rudder.UpsertPlayerStorageParameters{ Type: "settings", Data: `{"lang":"ru"}`, }) err = player.Storage().Delete(ctx, rudder.DeletePlayerStorageParameters{Type: "settings"}) updatedPlayer, err := player.Update(ctx, rudder.UpdatePlayerParameters{Nickname: "Neo"}) banned, err := player.Ban(ctx, rudder.BanPlayerParameters{ Reason: "cheating", BannedUntil: "2030-01-01T00:00:00Z", }) unbanned, err := player.Unban(ctx, "") err = player.Delete(ctx, "") ``` ## Batch-first API Все мутирующие операции верхнего уровня — batch-only: тело `{idempotencyKey?, items:[...]}` (`playerId` внутри элементов), до 100 элементов, all-or-nothing в одной транзакции, один idempotency key на батч. ```go wallets, err := client.Wallet.Adjust(ctx, rudder.AdjustWalletParameters{ IdempotencyKey: "grant-001", Items: []rudder.WalletAdjustment{ {PlayerID: "p1", CurrencyCode: "coins", Amount: 100, Reason: "compensation"}, {PlayerID: "p2", CurrencyCode: "coins", Amount: 100, Reason: "compensation"}, }, }) updated, err := client.Players.Update(ctx, rudder.UpdatePlayersParameters{ Items: []rudder.PlayerUpdate{ {PlayerID: "p1", Nickname: "Neo"}, }, }) banned, err := client.Players.Ban(ctx, rudder.BanPlayersParameters{ Items: []rudder.PlayerBan{ {PlayerID: "p2", Reason: "cheating", BannedUntil: "2030-01-01T00:00:00Z"}, }, }) unbanned, err := client.Players.Unban(ctx, rudder.UnbanPlayersParameters{ PlayerIDs: []string{"p2"}, }) err = client.Players.Delete(ctx, rudder.DeletePlayersParameters{ PlayerIDs: []string{"p3"}, }) err = client.Storage.Upsert(ctx, rudder.UpsertStorageParameters{ Items: []rudder.ProjectStorageInput{ {Type: "config", Data: `{"event":"x2"}`}, }, }) err = client.Storage.Delete(ctx, rudder.DeleteStorageParameters{ Types: []string{"config"}, }) rows, err := client.Resources.Create(ctx, "entries", []rudder.ResourceBatchItem{ {PlayerID: "p1", Data: map[string]any{"board": "weekly", "score": 1500}}, }) // Update и Delete — аналогично, строка задаётся полем ID ``` ## Модули Игровые фичи (инвентарь, квесты, лидерборды, магазины, battle pass, remote config) — это установленные модули. Их данные доступны через `client.Resources`, а функции с триггером `admin` вызываются через `client.Modules.Call` (`POST /game/v1/modules/{module}/{fn}`): ```go out, err := client.Modules.Call(ctx, rudder.CallParameters{ Module: "leaderboards", Fn: "resetNow", Args: map[string]any{"row": map[string]any{"id": boardID}}, }) // out.Result, out.Error, out.Log, out.Console; DryRun: true всегда откатывает транзакцию ``` Бизнес-ошибка модуля приходит как `*rudder.APIError` со статусом 422 и `Code`, который задаёт сам модуль (произвольная строка, констант для неё нет). ## Верификация авторизации `Players.VerifyAuth` — одиночный (не batch) вызов `POST /players/auth/verify`: проверяет access token игрока и возвращает `playerId`, `projectId`, `environment` (`staging`/`prod`) и `status` (`active`/`banned`/`deleted`). ```go verification, err := client.Players.VerifyAuth(ctx, accessToken) // verification.PlayerID, verification.ProjectID, verification.Environment, verification.Status ``` `VerifyWebhookSignature` — пакетный хелпер для приёма webhook'ов с подписью `X-Signature` (hex HMAC-SHA256 от сырого тела, ключ — webhook secret проекта): ```go if !rudder.VerifyWebhookSignature(secret, body, r.Header.Get("X-Signature")) { // подпись не сошлась — отклонить запрос } ``` ## Каталоги и read-операции Курсорная пагинация: передавайте `Limit`/`Cursor`, следующую страницу запрашивайте по `NextCursor` из ответа. ```go history, err := client.Wallet.History(ctx, playerID, rudder.WalletHistoryParameters{ Currency: ¤cy, // optional Limit: 50, }) // history.Entries []rudder.WalletTransaction, history.NextCursor storage, err := client.Storage.List(ctx, rudder.ListStorageParameters{ Limit: 100, Search: "config", // optional }) item, err := client.Storage.Get(ctx, "config") rows, err := client.Resources.List(ctx, "boards") // rows.Items, rows.Total ``` ## Ошибки ```go var apiErr *rudder.APIError if errors.As(err, &apiErr) { apiErr.Status // HTTP status apiErr.Code // машинный код из тела ошибки apiErr.RequestID // корреляция с логами сервера } ``` Сгенерированные коды сервера — `forbidden` и `row_referenced`. Бизнес-ошибки модулей (422) несут собственный код модуля. ## Генерация `models/` генерируется apigen'ом из `liveops-backend/openapi/openapi.yaml` (`make generate-openapi` в liveops-backend). Руками не править. Публичные имена — aliases в корне пакета `rudder` (`types.go`).