7 Commits

Author SHA1 Message Date
edmand46 a217f255b8 v2.0.0: module path /v2, quest slugs, per-environment admin keys, regenerated models
CI / build (push) Successful in 12s
Claude-Session: https://claude.ai/code/session_01SMCvdwDmuxoaqGgvGBLk1V
2026-09-06 22:25:32 +03:00
edmand46 9b41c844f3 Regenerate player models with avatarUrl
CI / build (push) Successful in 19s
2026-09-04 12:40:54 +03:00
edmand46 e8cec78cda models: player identities on admin player details
CI / build (push) Successful in 44s
2026-09-01 19:59:23 +03:00
edmand46 304d6c12c1 Add agent skill (SKILL.md + per-service reference)
CI / build (push) Successful in 51s
2026-08-29 11:46:23 +03:00
edmand46 b57f170ba4 sdk: Players.VerifyAuth and VerifyWebhookSignature helper
CI / build (push) Successful in 55s
2026-08-28 13:36:03 +03:00
edmand46 989bd7646c v0.7.0: public domain types in rudder, hide OpenAPI names from callers
CI / build (push) Successful in 51s
2026-08-27 16:10:27 +03:00
edmand46 2619291b20 v0.6.0: drop Metrics service from the server SDK
CI / build (push) Successful in 50s
2026-08-27 09:53:44 +03:00
35 changed files with 769 additions and 173 deletions
+32
View File
@@ -0,0 +1,32 @@
# Changelog
## 2.0.0
Breaking change — major bump, required by the backend environments release.
The module path gains the Go major-version suffix: import
`hub.rudder.build/rudder/rudder-go-sdk/v2` and pull it with
`go get hub.rudder.build/rudder/rudder-go-sdk/v2@latest`. Nothing else about
the import changes: the package is still `rudder`.
Admin keys are now per environment. A project has exactly two environments,
`staging` and `prod`, and each has its own Admin Key, so the key you configure
decides which environment every call reads and writes. There is no environment
option on the client or on any method, and there is nothing to migrate in code
— but the key itself changed: the old project Admin Key became the `prod` key
and a separate `staging` key was generated, both visible under project
Settings. Players are per environment too, so a player id from one environment
does not resolve in the other, and the same holds for wallets, inventory,
storage, counters, quests, runs, battle pass, purchases and leaderboard
entries.
Quests are addressed by slug, because ids differ between staging and prod
while slugs are stable. `PlayerQuestParameters.QuestID` is now
`PlayerQuestParameters.QuestSlug`, `PlayerQuestRef` carries `QuestSlug`
instead of `QuestID`, `PlayerQuest` reports `QuestSlug`, and `Quest`
(the catalog definition) gained `Slug` alongside its `ID`.
`Players.VerifyAuth` additionally returns `Environment`, which always matches
the admin key's environment.
Environment create, delete and merge, and project-level admin key
regeneration, were removed from the platform. This SDK never exposed them, so
nothing is gone from its surface.
+56 -29
View File
@@ -10,7 +10,7 @@
```bash
go env -w GOPRIVATE=hub.rudder.build/*
go get hub.rudder.build/rudder/rudder-go-sdk@latest
go get hub.rudder.build/rudder/rudder-go-sdk/v2@latest
```
Требуется Go 1.21+. Токен не нужен — репозиторий публичный.
@@ -18,15 +18,18 @@ go get hub.rudder.build/rudder/rudder-go-sdk@latest
## Версионирование
Потребители фиксируют версию по git-тегу
(`go get hub.rudder.build/rudder/rudder-go-sdk@vX.Y.Z`). Первый тег `v0.1.0`
будет создан при релизе; до него версионирования нет.
(`go get hub.rudder.build/rudder/rudder-go-sdk/v2@vX.Y.Z`). Текущая мажорная
версия — `v2`, поэтому путь модуля содержит суффикс `/v2`, а импорты идут
через `hub.rudder.build/rudder/rudder-go-sdk/v2`.
## Доступ
`AdminKey` — это per-project Admin Key: поле «Admin Key» в настройках проекта
в дашборде (app.rudder.build → проект → Settings). Ключ сам определяет
проект, отдельный project ID не нужен. Храните его только на сервере —
никогда не вшивайте в игровые клиенты.
`AdminKey` — это Admin Key окружения: у проекта их два, для `staging` и для
`prod` (app.rudder.build → проект → Settings → Environments). Ключ сам
определяет и проект, и окружение, поэтому ни project ID, ни параметр
окружения передавать не нужно; игроки и контент разных окружений полностью
изолированы. Храните ключ только на сервере — никогда не вшивайте в игровые
клиенты.
## Usage
@@ -39,9 +42,11 @@ if err != nil {
}
players, err := client.Players.List(ctx, rudder.ListPlayersParameters{Limit: 100})
metrics, err := client.Metrics.Get(ctx)
```
Публичные типы живут в пакете `rudder`. Пакет `models` — сгенерированный
wire-формат, в прикладном коде его импортировать не нужно.
## Handles
`client.Player(id)` и `client.Leaderboard(slug)` создают handle без
@@ -74,7 +79,7 @@ updated, err := player.Storage().Upsert(ctx, rudder.UpsertPlayerStorageParameter
err = player.Storage().Delete(ctx, rudder.DeletePlayerStorageParameters{Type: "settings"})
quests, err := player.Quests().List(ctx)
err = player.Quests().Reset(ctx, rudder.PlayerQuestParameters{QuestID: "daily-1"})
err = player.Quests().Reset(ctx, rudder.PlayerQuestParameters{QuestSlug: "daily-1"})
// ForceComplete и ForceClaim — аналогично
updatedPlayer, err := player.Update(ctx, rudder.UpdatePlayerParameters{Nickname: "Neo"})
@@ -88,13 +93,13 @@ err = player.Delete(ctx, "")
board := client.Leaderboard("weekly")
entries, err := board.List(ctx, rudder.ListLeaderboardEntriesParameters{Limit: 100})
// entries.Entries []models.RankEntry, entries.NextCursor
// entries.Entries []rudder.Rank, entries.NextCursor
entry, err := board.Get(ctx, "p1") // *models.RankEntry с rank
around, err := board.GetAround(ctx, "p1", 10) // []models.RankEntry вокруг игрока
entry, err := board.Get(ctx, "p1") // *rudder.Rank с rank
around, err := board.GetAround(ctx, "p1", 10) // []rudder.Rank вокруг игрока
err = board.Submit(ctx, []models.BatchLeaderboardEntry{{PlayerID: "p1", Score: 1500}})
err = board.Update(ctx, []models.BatchLeaderboardEntry{{PlayerID: "p1", Score: 1600}})
err = board.Submit(ctx, []rudder.LeaderboardScore{{PlayerID: "p1", Score: 1500}})
err = board.Update(ctx, []rudder.LeaderboardScore{{PlayerID: "p1", Score: 1600}})
err = board.Delete(ctx, []string{"p1", "p2"})
```
@@ -107,52 +112,73 @@ err = board.Delete(ctx, []string{"p1", "p2"})
```go
wallets, err := client.Wallet.Adjust(ctx, rudder.AdjustWalletParameters{
IdempotencyKey: "grant-001",
Items: []models.BatchAdjustPlayerWalletsItem{
Items: []rudder.WalletAdjustment{
{PlayerID: "p1", CurrencyCode: "coins", Amount: 100, Reason: "compensation"},
{PlayerID: "p2", CurrencyCode: "coins", Amount: 100, Reason: "compensation"},
},
})
items, err := client.Inventory.Adjust(ctx, rudder.AdjustInventoryParameters{
Items: []models.BatchAdjustPlayerInventoryItemsItem{
Items: []rudder.InventoryAdjustment{
{PlayerID: "p1", Slug: "sword", Amount: 1, Reason: "quest reward"},
},
})
updated, err := client.Players.Update(ctx, rudder.UpdatePlayersParameters{
Items: []models.BatchUpdatePlayersItem{
Items: []rudder.PlayerUpdate{
{PlayerID: "p1", Nickname: "Neo"},
},
})
banned, err := client.Players.Ban(ctx, rudder.BanPlayersParameters{
Items: []models.BatchBanPlayersItem{
Items: []rudder.PlayerBan{
{PlayerID: "p2", Reason: "cheating", BannedUntil: "2030-01-01T00:00:00Z"},
},
})
unbanned, err := client.Players.Unban(ctx, rudder.UnbanPlayersParameters{
Items: []models.BatchPlayerItem{{PlayerID: "p2"}},
PlayerIDs: []string{"p2"},
})
err = client.Players.Delete(ctx, rudder.DeletePlayersParameters{
Items: []models.BatchPlayerItem{{PlayerID: "p3"}},
PlayerIDs: []string{"p3"},
})
```
err = client.Quests.Reset(ctx, rudder.ResetPlayerQuestsParameters{
Items: []models.BatchPlayerQuestsItem{
{PlayerID: "p1", QuestID: "daily-1"},
## Верификация авторизации
`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")) {
// подпись не сошлась — отклонить запрос
}
```
err = client.Quests.Reset(ctx, rudder.PlayerQuestsParameters{
Items: []rudder.PlayerQuestRef{
{PlayerID: "p1", QuestSlug: "daily-1"},
},
})
// ForceComplete и ForceClaim — аналогично
err = client.Leaderboards.Submit(ctx, "weekly", []models.BatchLeaderboardEntry{
err = client.Leaderboards.Submit(ctx, "weekly", []rudder.LeaderboardScore{
{PlayerID: "p1", Score: 1500},
})
// Update и Delete — аналогично
err = client.Storage.Upsert(ctx, rudder.UpsertStorageParameters{
Items: []models.AdminProjectStorageItem{
Items: []rudder.ProjectStorageInput{
{Type: "config", Data: `{"event":"x2"}`},
},
})
@@ -169,24 +195,24 @@ err = client.Storage.Delete(ctx, rudder.DeleteStorageParameters{
```go
boards, err := client.Leaderboards.List(ctx, rudder.ListLeaderboardsParameters{Limit: 100})
// boards.Items []models.LeaderboardDefinition, boards.NextCursor
// boards.Items []rudder.Leaderboard, boards.NextCursor
quests, err := client.Quests.List(ctx, rudder.ListQuestsParameters{
Status: &status, // "active" или "archived", optional
Limit: 100,
})
// quests.Items []models.QuestDefinition, quests.NextCursor
// quests.Items []rudder.Quest, quests.NextCursor
history, err := client.Wallet.History(ctx, playerID, rudder.WalletHistoryParameters{
Currency: &currency, // optional
Limit: 50,
})
// history.Entries []models.WalletAudit, history.NextCursor
// history.Entries []rudder.WalletTransaction, history.NextCursor
inventory, err := client.Inventory.List(ctx, playerID, rudder.ListInventoryParameters{
Limit: 50,
})
// inventory.Items []models.InventoryItem, inventory.NextCursor
// inventory.Items []rudder.InventoryItem, inventory.NextCursor
storage, err := client.Storage.List(ctx, rudder.ListStorageParameters{
Limit: 100,
@@ -210,3 +236,4 @@ if errors.As(err, &apiErr) {
`models/` генерируется apigen'ом из `liveops-gateway/openapi.yaml`
(`make generate-openapi` в liveops-gateway). Руками не править.
Публичные имена — aliases в корне пакета `rudder` (`types.go`).
-2
View File
@@ -21,7 +21,6 @@ type Client struct {
Inventory *InventoryService
Quests *QuestsService
Storage *StorageService
Metrics *MetricsService
transport *transport
}
@@ -54,7 +53,6 @@ func New(config Config) (*Client, error) {
Inventory: &InventoryService{transport: transport},
Quests: &QuestsService{transport: transport},
Storage: &StorageService{transport: transport},
Metrics: &MetricsService{transport: transport},
transport: transport,
}, nil
+1 -1
View File
@@ -1,3 +1,3 @@
module hub.rudder.build/rudder/rudder-go-sdk
module hub.rudder.build/rudder/rudder-go-sdk/v2
go 1.21
+5 -5
View File
@@ -6,7 +6,7 @@ import (
"net/url"
"strconv"
"hub.rudder.build/rudder/rudder-go-sdk/models"
"hub.rudder.build/rudder/rudder-go-sdk/v2/models"
)
type InventoryService struct {
@@ -15,10 +15,10 @@ type InventoryService struct {
type AdjustInventoryParameters struct {
IdempotencyKey string
Items []models.BatchAdjustPlayerInventoryItemsItem
Items []InventoryAdjustment
}
func (s *InventoryService) Adjust(ctx context.Context, parameters AdjustInventoryParameters) ([]models.InventoryItem, error) {
func (s *InventoryService) Adjust(ctx context.Context, parameters AdjustInventoryParameters) ([]InventoryItem, error) {
body := models.BatchAdjustPlayerInventoryItemsRequest{
IdempotencyKey: parameters.IdempotencyKey,
Items: parameters.Items,
@@ -36,7 +36,7 @@ type ListInventoryParameters struct {
Cursor *string
}
func (s *InventoryService) List(ctx context.Context, playerID string, parameters ListInventoryParameters) (*models.GetPlayerInventoryResponse, error) {
func (s *InventoryService) List(ctx context.Context, playerID string, parameters ListInventoryParameters) (*InventoryList, error) {
query := url.Values{}
if parameters.Limit > 0 {
query.Set("limit", strconv.Itoa(parameters.Limit))
@@ -45,7 +45,7 @@ func (s *InventoryService) List(ctx context.Context, playerID string, parameters
query.Set("cursor", *parameters.Cursor)
}
var response models.GetPlayerInventoryResponse
var response InventoryList
if err := s.transport.do(ctx, http.MethodGet, "/players/"+url.PathEscape(playerID)+"/inventory", query, nil, &response); err != nil {
return nil, err
}
+8 -8
View File
@@ -6,7 +6,7 @@ import (
"net/url"
"strconv"
"hub.rudder.build/rudder/rudder-go-sdk/models"
"hub.rudder.build/rudder/rudder-go-sdk/v2/models"
)
type LeaderboardHandle struct {
@@ -20,7 +20,7 @@ type ListLeaderboardEntriesParameters struct {
Cursor *string
}
func (h *LeaderboardHandle) List(ctx context.Context, parameters ListLeaderboardEntriesParameters) (*models.ListLeaderboardEntriesResponse, error) {
func (h *LeaderboardHandle) List(ctx context.Context, parameters ListLeaderboardEntriesParameters) (*RankList, error) {
query := url.Values{}
if parameters.Limit > 0 {
query.Set("limit", strconv.Itoa(parameters.Limit))
@@ -29,22 +29,22 @@ func (h *LeaderboardHandle) List(ctx context.Context, parameters ListLeaderboard
query.Set("cursor", *parameters.Cursor)
}
var response models.ListLeaderboardEntriesResponse
var response RankList
if err := h.transport.do(ctx, http.MethodGet, "/leaderboards/"+url.PathEscape(h.slug)+"/entries", query, nil, &response); err != nil {
return nil, err
}
return &response, nil
}
func (h *LeaderboardHandle) Get(ctx context.Context, playerID string) (*models.RankEntry, error) {
var response models.RankEntry
func (h *LeaderboardHandle) Get(ctx context.Context, playerID string) (*Rank, error) {
var response Rank
if err := h.transport.do(ctx, http.MethodGet, "/leaderboards/"+url.PathEscape(h.slug)+"/entries/"+url.PathEscape(playerID), nil, nil, &response); err != nil {
return nil, err
}
return &response, nil
}
func (h *LeaderboardHandle) GetAround(ctx context.Context, playerID string, limit int) ([]models.RankEntry, error) {
func (h *LeaderboardHandle) GetAround(ctx context.Context, playerID string, limit int) ([]Rank, error) {
query := url.Values{}
if limit > 0 {
query.Set("limit", strconv.Itoa(limit))
@@ -57,11 +57,11 @@ func (h *LeaderboardHandle) GetAround(ctx context.Context, playerID string, limi
return response.Entries, nil
}
func (h *LeaderboardHandle) Submit(ctx context.Context, items []models.BatchLeaderboardEntry) error {
func (h *LeaderboardHandle) Submit(ctx context.Context, items []LeaderboardScore) error {
return h.leaderboards.Submit(ctx, h.slug, items)
}
func (h *LeaderboardHandle) Update(ctx context.Context, items []models.BatchLeaderboardEntry) error {
func (h *LeaderboardHandle) Update(ctx context.Context, items []LeaderboardScore) error {
return h.leaderboards.Update(ctx, h.slug, items)
}
+5 -5
View File
@@ -6,7 +6,7 @@ import (
"net/url"
"strconv"
"hub.rudder.build/rudder/rudder-go-sdk/models"
"hub.rudder.build/rudder/rudder-go-sdk/v2/models"
)
type LeaderboardsService struct {
@@ -18,7 +18,7 @@ type ListLeaderboardsParameters struct {
Cursor *string
}
func (s *LeaderboardsService) List(ctx context.Context, parameters ListLeaderboardsParameters) (*models.ListLeaderboardsResponse, error) {
func (s *LeaderboardsService) List(ctx context.Context, parameters ListLeaderboardsParameters) (*LeaderboardList, error) {
query := url.Values{}
if parameters.Limit > 0 {
query.Set("limit", strconv.Itoa(parameters.Limit))
@@ -27,21 +27,21 @@ func (s *LeaderboardsService) List(ctx context.Context, parameters ListLeaderboa
query.Set("cursor", *parameters.Cursor)
}
var response models.ListLeaderboardsResponse
var response LeaderboardList
if err := s.transport.do(ctx, http.MethodGet, "/leaderboards", query, nil, &response); err != nil {
return nil, err
}
return &response, nil
}
func (s *LeaderboardsService) Submit(ctx context.Context, slug string, items []models.BatchLeaderboardEntry) error {
func (s *LeaderboardsService) Submit(ctx context.Context, slug string, items []LeaderboardScore) error {
body := models.BatchSubmitLeaderboardEntriesRequest{
Items: items,
}
return s.transport.do(ctx, http.MethodPost, "/leaderboards/"+url.PathEscape(slug)+"/entries", nil, body, nil)
}
func (s *LeaderboardsService) Update(ctx context.Context, slug string, items []models.BatchLeaderboardEntry) error {
func (s *LeaderboardsService) Update(ctx context.Context, slug string, items []LeaderboardScore) error {
body := models.BatchUpdateLeaderboardEntriesRequest{
Items: items,
}
-20
View File
@@ -1,20 +0,0 @@
package rudder
import (
"context"
"net/http"
"hub.rudder.build/rudder/rudder-go-sdk/models"
)
type MetricsService struct {
transport *transport
}
func (s *MetricsService) Get(ctx context.Context) (*models.GetProjectMetricsResponse, error) {
var response models.GetProjectMetricsResponse
if err := s.transport.do(ctx, http.MethodGet, "/metrics", nil, nil, &response); err != nil {
return nil, err
}
return &response, nil
}
+1
View File
@@ -5,6 +5,7 @@ package models
import "encoding/json"
type AdminPlayer struct {
AvatarURL string `json:"avatarUrl,omitempty"`
BanReason string `json:"banReason,omitempty"`
BannedAt string `json:"bannedAt,omitempty"`
BannedUntil string `json:"bannedUntil,omitempty"`
+6 -5
View File
@@ -3,9 +3,10 @@
package models
type AdminPlayerDetails struct {
Inventory []InventoryItem `json:"inventory,omitempty"`
Player AdminPlayer `json:"player,omitempty"`
Storages []PlayerStorage `json:"storages,omitempty"`
WalletTransactions []WalletAudit `json:"walletTransactions,omitempty"`
Wallets []WalletDetails `json:"wallets,omitempty"`
Identities []PlayerIdentity `json:"identities,omitempty"`
Inventory []InventoryItem `json:"inventory,omitempty"`
Player AdminPlayer `json:"player,omitempty"`
Storages []PlayerStorage `json:"storages,omitempty"`
WalletTransactions []WalletAudit `json:"walletTransactions,omitempty"`
Wallets []WalletDetails `json:"wallets,omitempty"`
}
+1 -1
View File
@@ -5,6 +5,6 @@ package models
type AdminPlayerQuest struct {
Name string `json:"name,omitempty"`
Objectives []AdminPlayerQuestObjective `json:"objectives,omitempty"`
QuestID string `json:"questId,omitempty"`
QuestSlug string `json:"questSlug,omitempty"`
Status string `json:"status,omitempty"`
}
+2 -2
View File
@@ -3,6 +3,6 @@
package models
type BatchPlayerQuestsItem struct {
PlayerID string `json:"playerId,omitempty"`
QuestID string `json:"questId,omitempty"`
PlayerID string `json:"playerId,omitempty"`
QuestSlug string `json:"questSlug,omitempty"`
}
+10
View File
@@ -0,0 +1,10 @@
// Code generated by apigen. DO NOT EDIT.
package models
type EnvironmentName string
const (
EnvironmentNameStaging EnvironmentName = "staging"
EnvironmentNameProd EnvironmentName = "prod"
)
+10
View File
@@ -0,0 +1,10 @@
// Code generated by apigen. DO NOT EDIT.
package models
type PlayerIdentity struct {
CreatedAt string `json:"createdAt,omitempty"`
ID string `json:"id,omitempty"`
Provider string `json:"provider,omitempty"`
Subject string `json:"subject,omitempty"`
}
+1
View File
@@ -8,5 +8,6 @@ type QuestDefinition struct {
NextQuestID string `json:"nextQuestId,omitempty"`
Objectives []QuestObjective `json:"objectives,omitempty"`
Rewards []QuestRewardDefinition `json:"rewards,omitempty"`
Slug string `json:"slug,omitempty"`
Status string `json:"status,omitempty"`
}
+7
View File
@@ -0,0 +1,7 @@
// Code generated by apigen. DO NOT EDIT.
package models
type VerifyPlayerAuthRequest struct {
AccessToken string `json:"accessToken,omitempty"`
}
+10
View File
@@ -0,0 +1,10 @@
// Code generated by apigen. DO NOT EDIT.
package models
type VerifyPlayerAuthResponse struct {
Environment EnvironmentName `json:"environment,omitempty"`
PlayerID string `json:"playerId,omitempty"`
ProjectID string `json:"projectId,omitempty"`
Status string `json:"status,omitempty"`
}
+7 -9
View File
@@ -3,8 +3,6 @@ package rudder
import (
"context"
"encoding/json"
"hub.rudder.build/rudder/rudder-go-sdk/models"
)
type PlayerHandle struct {
@@ -38,10 +36,10 @@ type UpdatePlayerParameters struct {
Data json.RawMessage
}
func (h *PlayerHandle) Update(ctx context.Context, parameters UpdatePlayerParameters) (*models.AdminPlayer, error) {
func (h *PlayerHandle) Update(ctx context.Context, parameters UpdatePlayerParameters) (*Player, error) {
players, err := h.players.Update(ctx, UpdatePlayersParameters{
IdempotencyKey: parameters.IdempotencyKey,
Items: []models.BatchUpdatePlayersItem{{
Items: []PlayerUpdate{{
PlayerID: h.playerID,
Nickname: parameters.Nickname,
Data: parameters.Data,
@@ -59,10 +57,10 @@ type BanPlayerParameters struct {
BannedUntil string
}
func (h *PlayerHandle) Ban(ctx context.Context, parameters BanPlayerParameters) (*models.AdminPlayer, error) {
func (h *PlayerHandle) Ban(ctx context.Context, parameters BanPlayerParameters) (*Player, error) {
players, err := h.players.Ban(ctx, BanPlayersParameters{
IdempotencyKey: parameters.IdempotencyKey,
Items: []models.BatchBanPlayersItem{{
Items: []PlayerBan{{
PlayerID: h.playerID,
Reason: parameters.Reason,
BannedUntil: parameters.BannedUntil,
@@ -74,10 +72,10 @@ func (h *PlayerHandle) Ban(ctx context.Context, parameters BanPlayerParameters)
return &players[0], nil
}
func (h *PlayerHandle) Unban(ctx context.Context, idempotencyKey string) (*models.AdminPlayer, error) {
func (h *PlayerHandle) Unban(ctx context.Context, idempotencyKey string) (*Player, error) {
players, err := h.players.Unban(ctx, UnbanPlayersParameters{
IdempotencyKey: idempotencyKey,
Items: []models.BatchPlayerItem{{PlayerID: h.playerID}},
PlayerIDs: []string{h.playerID},
})
if err != nil {
return nil, err
@@ -88,6 +86,6 @@ func (h *PlayerHandle) Unban(ctx context.Context, idempotencyKey string) (*model
func (h *PlayerHandle) Delete(ctx context.Context, idempotencyKey string) error {
return h.players.Delete(ctx, DeletePlayersParameters{
IdempotencyKey: idempotencyKey,
Items: []models.BatchPlayerItem{{PlayerID: h.playerID}},
PlayerIDs: []string{h.playerID},
})
}
+4 -8
View File
@@ -1,10 +1,6 @@
package rudder
import (
"context"
"hub.rudder.build/rudder/rudder-go-sdk/models"
)
import "context"
type PlayerInventoryHandle struct {
playerID string
@@ -18,10 +14,10 @@ type AdjustPlayerInventoryParameters struct {
Reason string
}
func (h *PlayerInventoryHandle) Adjust(ctx context.Context, parameters AdjustPlayerInventoryParameters) (*models.InventoryItem, error) {
func (h *PlayerInventoryHandle) Adjust(ctx context.Context, parameters AdjustPlayerInventoryParameters) (*InventoryItem, error) {
items, err := h.inventory.Adjust(ctx, AdjustInventoryParameters{
IdempotencyKey: parameters.IdempotencyKey,
Items: []models.BatchAdjustPlayerInventoryItemsItem{{
Items: []InventoryAdjustment{{
PlayerID: h.playerID,
Slug: parameters.Slug,
Amount: parameters.Amount,
@@ -34,6 +30,6 @@ func (h *PlayerInventoryHandle) Adjust(ctx context.Context, parameters AdjustPla
return &items[0], nil
}
func (h *PlayerInventoryHandle) List(ctx context.Context, parameters ListInventoryParameters) (*models.GetPlayerInventoryResponse, error) {
func (h *PlayerInventoryHandle) List(ctx context.Context, parameters ListInventoryParameters) (*InventoryList, error) {
return h.inventory.List(ctx, h.playerID, parameters)
}
+9 -9
View File
@@ -5,7 +5,7 @@ import (
"net/http"
"net/url"
"hub.rudder.build/rudder/rudder-go-sdk/models"
"hub.rudder.build/rudder/rudder-go-sdk/v2/models"
)
type PlayerQuestsHandle struct {
@@ -14,7 +14,7 @@ type PlayerQuestsHandle struct {
quests *QuestsService
}
func (h *PlayerQuestsHandle) List(ctx context.Context) ([]models.AdminPlayerQuest, error) {
func (h *PlayerQuestsHandle) List(ctx context.Context) ([]PlayerQuest, error) {
var response models.ListPlayerQuestsResponse
if err := h.transport.do(ctx, http.MethodGet, "/players/"+url.PathEscape(h.playerID)+"/quests", nil, nil, &response); err != nil {
return nil, err
@@ -24,26 +24,26 @@ func (h *PlayerQuestsHandle) List(ctx context.Context) ([]models.AdminPlayerQues
type PlayerQuestParameters struct {
IdempotencyKey string
QuestID string
QuestSlug string
}
func (h *PlayerQuestsHandle) Reset(ctx context.Context, parameters PlayerQuestParameters) error {
return h.quests.Reset(ctx, ResetPlayerQuestsParameters{
return h.quests.Reset(ctx, PlayerQuestsParameters{
IdempotencyKey: parameters.IdempotencyKey,
Items: []models.BatchPlayerQuestsItem{{PlayerID: h.playerID, QuestID: parameters.QuestID}},
Items: []PlayerQuestRef{{PlayerID: h.playerID, QuestSlug: parameters.QuestSlug}},
})
}
func (h *PlayerQuestsHandle) ForceComplete(ctx context.Context, parameters PlayerQuestParameters) error {
return h.quests.ForceComplete(ctx, ForceCompletePlayerQuestsParameters{
return h.quests.ForceComplete(ctx, PlayerQuestsParameters{
IdempotencyKey: parameters.IdempotencyKey,
Items: []models.BatchPlayerQuestsItem{{PlayerID: h.playerID, QuestID: parameters.QuestID}},
Items: []PlayerQuestRef{{PlayerID: h.playerID, QuestSlug: parameters.QuestSlug}},
})
}
func (h *PlayerQuestsHandle) ForceClaim(ctx context.Context, parameters PlayerQuestParameters) error {
return h.quests.ForceClaim(ctx, ForceClaimPlayerQuestsParameters{
return h.quests.ForceClaim(ctx, PlayerQuestsParameters{
IdempotencyKey: parameters.IdempotencyKey,
Items: []models.BatchPlayerQuestsItem{{PlayerID: h.playerID, QuestID: parameters.QuestID}},
Items: []PlayerQuestRef{{PlayerID: h.playerID, QuestSlug: parameters.QuestSlug}},
})
}
+4 -4
View File
@@ -5,7 +5,7 @@ import (
"net/http"
"net/url"
"hub.rudder.build/rudder/rudder-go-sdk/models"
"hub.rudder.build/rudder/rudder-go-sdk/v2/models"
)
type PlayerStorageHandle struct {
@@ -13,8 +13,8 @@ type PlayerStorageHandle struct {
transport *transport
}
func (h *PlayerStorageHandle) Get(ctx context.Context, storageType string) (*models.PlayerStorage, error) {
var response models.PlayerStorage
func (h *PlayerStorageHandle) Get(ctx context.Context, storageType string) (*PlayerStorage, error) {
var response PlayerStorage
if err := h.transport.do(ctx, http.MethodGet, "/players/"+url.PathEscape(h.playerID)+"/storage/"+url.PathEscape(storageType), nil, nil, &response); err != nil {
return nil, err
}
@@ -28,7 +28,7 @@ type UpsertPlayerStorageParameters struct {
ExpiresAt string
}
func (h *PlayerStorageHandle) Upsert(ctx context.Context, parameters UpsertPlayerStorageParameters) (*models.PlayerStorage, error) {
func (h *PlayerStorageHandle) Upsert(ctx context.Context, parameters UpsertPlayerStorageParameters) (*PlayerStorage, error) {
body := models.BatchUpsertPlayerStoragesRequest{
IdempotencyKey: parameters.IdempotencyKey,
Items: []models.BatchUpsertPlayerStoragesItem{{
+4 -8
View File
@@ -1,10 +1,6 @@
package rudder
import (
"context"
"hub.rudder.build/rudder/rudder-go-sdk/models"
)
import "context"
type PlayerWalletHandle struct {
playerID string
@@ -18,10 +14,10 @@ type AdjustPlayerWalletParameters struct {
Reason string
}
func (h *PlayerWalletHandle) Adjust(ctx context.Context, parameters AdjustPlayerWalletParameters) (*models.WalletDetails, error) {
func (h *PlayerWalletHandle) Adjust(ctx context.Context, parameters AdjustPlayerWalletParameters) (*WalletBalance, error) {
wallets, err := h.wallet.Adjust(ctx, AdjustWalletParameters{
IdempotencyKey: parameters.IdempotencyKey,
Items: []models.BatchAdjustPlayerWalletsItem{{
Items: []WalletAdjustment{{
PlayerID: h.playerID,
CurrencyCode: parameters.CurrencyCode,
Amount: parameters.Amount,
@@ -34,6 +30,6 @@ func (h *PlayerWalletHandle) Adjust(ctx context.Context, parameters AdjustPlayer
return &wallets[0], nil
}
func (h *PlayerWalletHandle) History(ctx context.Context, parameters WalletHistoryParameters) (*models.GetWalletHistoryResponse, error) {
func (h *PlayerWalletHandle) History(ctx context.Context, parameters WalletHistoryParameters) (*WalletHistory, error) {
return h.wallet.History(ctx, h.playerID, parameters)
}
+32 -14
View File
@@ -6,7 +6,7 @@ import (
"net/url"
"strconv"
"hub.rudder.build/rudder/rudder-go-sdk/models"
"hub.rudder.build/rudder/rudder-go-sdk/v2/models"
)
type PlayersService struct {
@@ -19,7 +19,7 @@ type ListPlayersParameters struct {
Search string
}
func (s *PlayersService) List(ctx context.Context, parameters ListPlayersParameters) (*models.ListPlayersResponse, error) {
func (s *PlayersService) List(ctx context.Context, parameters ListPlayersParameters) (*PlayerList, error) {
query := url.Values{}
if parameters.Limit > 0 {
query.Set("limit", strconv.Itoa(parameters.Limit))
@@ -31,27 +31,37 @@ func (s *PlayersService) List(ctx context.Context, parameters ListPlayersParamet
query.Set("search", parameters.Search)
}
var response models.ListPlayersResponse
var response PlayerList
if err := s.transport.do(ctx, http.MethodGet, "/players/list", query, nil, &response); err != nil {
return nil, err
}
return &response, nil
}
func (s *PlayersService) Get(ctx context.Context, playerID string) (*models.AdminPlayerDetails, error) {
var response models.AdminPlayerDetails
func (s *PlayersService) Get(ctx context.Context, playerID string) (*PlayerDetails, error) {
var response PlayerDetails
if err := s.transport.do(ctx, http.MethodGet, "/players/"+url.PathEscape(playerID), nil, nil, &response); err != nil {
return nil, err
}
return &response, nil
}
func (s *PlayersService) VerifyAuth(ctx context.Context, accessToken string) (*PlayerAuthVerification, error) {
body := models.VerifyPlayerAuthRequest{AccessToken: accessToken}
var response PlayerAuthVerification
if err := s.transport.do(ctx, http.MethodPost, "/players/auth/verify", nil, body, &response); err != nil {
return nil, err
}
return &response, nil
}
type UpdatePlayersParameters struct {
IdempotencyKey string
Items []models.BatchUpdatePlayersItem
Items []PlayerUpdate
}
func (s *PlayersService) Update(ctx context.Context, parameters UpdatePlayersParameters) ([]models.AdminPlayer, error) {
func (s *PlayersService) Update(ctx context.Context, parameters UpdatePlayersParameters) ([]Player, error) {
body := models.BatchUpdatePlayersRequest{
IdempotencyKey: parameters.IdempotencyKey,
Items: parameters.Items,
@@ -66,10 +76,10 @@ func (s *PlayersService) Update(ctx context.Context, parameters UpdatePlayersPar
type BanPlayersParameters struct {
IdempotencyKey string
Items []models.BatchBanPlayersItem
Items []PlayerBan
}
func (s *PlayersService) Ban(ctx context.Context, parameters BanPlayersParameters) ([]models.AdminPlayer, error) {
func (s *PlayersService) Ban(ctx context.Context, parameters BanPlayersParameters) ([]Player, error) {
body := models.BatchBanPlayersRequest{
IdempotencyKey: parameters.IdempotencyKey,
Items: parameters.Items,
@@ -84,13 +94,13 @@ func (s *PlayersService) Ban(ctx context.Context, parameters BanPlayersParameter
type UnbanPlayersParameters struct {
IdempotencyKey string
Items []models.BatchPlayerItem
PlayerIDs []string
}
func (s *PlayersService) Unban(ctx context.Context, parameters UnbanPlayersParameters) ([]models.AdminPlayer, error) {
func (s *PlayersService) Unban(ctx context.Context, parameters UnbanPlayersParameters) ([]Player, error) {
body := models.BatchUnbanPlayersRequest{
IdempotencyKey: parameters.IdempotencyKey,
Items: parameters.Items,
Items: batchPlayerItems(parameters.PlayerIDs),
}
var response models.BatchPlayersResponse
@@ -102,13 +112,21 @@ func (s *PlayersService) Unban(ctx context.Context, parameters UnbanPlayersParam
type DeletePlayersParameters struct {
IdempotencyKey string
Items []models.BatchPlayerItem
PlayerIDs []string
}
func (s *PlayersService) Delete(ctx context.Context, parameters DeletePlayersParameters) error {
body := models.BatchDeletePlayersRequest{
IdempotencyKey: parameters.IdempotencyKey,
Items: parameters.Items,
Items: batchPlayerItems(parameters.PlayerIDs),
}
return s.transport.do(ctx, http.MethodDelete, "/players", nil, body, nil)
}
func batchPlayerItems(playerIDs []string) []models.BatchPlayerItem {
items := make([]models.BatchPlayerItem, len(playerIDs))
for i, id := range playerIDs {
items[i] = models.BatchPlayerItem{PlayerID: id}
}
return items
}
+19 -33
View File
@@ -6,7 +6,7 @@ import (
"net/url"
"strconv"
"hub.rudder.build/rudder/rudder-go-sdk/models"
"hub.rudder.build/rudder/rudder-go-sdk/v2/models"
)
type QuestsService struct {
@@ -19,7 +19,7 @@ type ListQuestsParameters struct {
Cursor *string
}
func (s *QuestsService) List(ctx context.Context, parameters ListQuestsParameters) (*models.AdminListQuestsResponse, error) {
func (s *QuestsService) List(ctx context.Context, parameters ListQuestsParameters) (*QuestList, error) {
query := url.Values{}
if parameters.Status != nil {
query.Set("status", *parameters.Status)
@@ -31,48 +31,34 @@ func (s *QuestsService) List(ctx context.Context, parameters ListQuestsParameter
query.Set("cursor", *parameters.Cursor)
}
var response models.AdminListQuestsResponse
var response QuestList
if err := s.transport.do(ctx, http.MethodGet, "/quests", query, nil, &response); err != nil {
return nil, err
}
return &response, nil
}
type ResetPlayerQuestsParameters struct {
type PlayerQuestsParameters struct {
IdempotencyKey string
Items []models.BatchPlayerQuestsItem
Items []PlayerQuestRef
}
func (s *QuestsService) Reset(ctx context.Context, parameters ResetPlayerQuestsParameters) error {
func (s *QuestsService) Reset(ctx context.Context, parameters PlayerQuestsParameters) error {
return s.postPlayerQuests(ctx, "/players/quests/reset", parameters)
}
func (s *QuestsService) ForceComplete(ctx context.Context, parameters PlayerQuestsParameters) error {
return s.postPlayerQuests(ctx, "/players/quests/force-complete", parameters)
}
func (s *QuestsService) ForceClaim(ctx context.Context, parameters PlayerQuestsParameters) error {
return s.postPlayerQuests(ctx, "/players/quests/force-claim", parameters)
}
func (s *QuestsService) postPlayerQuests(ctx context.Context, path string, parameters PlayerQuestsParameters) error {
body := models.BatchPlayerQuestsRequest{
IdempotencyKey: parameters.IdempotencyKey,
Items: parameters.Items,
}
return s.transport.do(ctx, http.MethodPost, "/players/quests/reset", nil, body, nil)
}
type ForceCompletePlayerQuestsParameters struct {
IdempotencyKey string
Items []models.BatchPlayerQuestsItem
}
func (s *QuestsService) ForceComplete(ctx context.Context, parameters ForceCompletePlayerQuestsParameters) error {
body := models.BatchPlayerQuestsRequest{
IdempotencyKey: parameters.IdempotencyKey,
Items: parameters.Items,
}
return s.transport.do(ctx, http.MethodPost, "/players/quests/force-complete", nil, body, nil)
}
type ForceClaimPlayerQuestsParameters struct {
IdempotencyKey string
Items []models.BatchPlayerQuestsItem
}
func (s *QuestsService) ForceClaim(ctx context.Context, parameters ForceClaimPlayerQuestsParameters) error {
body := models.BatchPlayerQuestsRequest{
IdempotencyKey: parameters.IdempotencyKey,
Items: parameters.Items,
}
return s.transport.do(ctx, http.MethodPost, "/players/quests/force-claim", nil, body, nil)
return s.transport.do(ctx, http.MethodPost, path, nil, body, nil)
}
+120
View File
@@ -0,0 +1,120 @@
---
name: rudder-go-sdk
description: Use when working with the Rudder Go server SDK (package rudder, module hub.rudder.build/rudder/rudder-go-sdk/v2) for server-side admin operations — players, wallet, inventory, storage, quests, leaderboards, webhook signature verification.
---
# Rudder Go server SDK
Server-side admin SDK for the Rudder LiveOps platform. Covers the `/game/v1`
admin surface, authenticated with `X-API-Key`. Module:
`hub.rudder.build/rudder/rudder-go-sdk/v2`, package `rudder`. Requires Go 1.21+.
## Install
The module is served from a self-hosted Gitea, bypassing proxy.golang.org:
```bash
go env -w GOPRIVATE=hub.rudder.build/*
go get hub.rudder.build/rudder/rudder-go-sdk/v2@latest
```
## Client init
`AdminKey` is the Admin Key of one environment. A project has exactly two,
`staging` and `prod` (app.rudder.build → project → Settings → Environments).
The key identifies both the project and the environment, so no project ID and
no environment argument are ever passed; players, wallets, inventory, storage,
quests and leaderboard entries of the two environments are fully isolated.
Keep it server-side only, never in game clients.
```go
import rudder "hub.rudder.build/rudder/rudder-go-sdk/v2"
client, err := rudder.New(rudder.Config{
AdminKey: os.Getenv("RUDDER_ADMIN_KEY"),
// BaseURL defaults to https://api.rudder.build
// Timeout defaults to 30s; HTTPClient optional
})
```
`client` exposes services: `Players`, `Leaderboards`, `Wallet`, `Inventory`,
`Quests`, `Storage`. All methods take `context.Context` first.
Public types live in package `rudder` (aliases in `types.go`). The `models`
package is generated wire format — never import it in application code and
never edit it by hand (regenerate via `make generate-openapi` in
liveops-backend).
## Batch-first mutation model
All top-level mutating operations are batch-only. Request body is
`{idempotencyKey?, items:[...]}` with `playerId` inside each item
(leaderboard delete takes `{playerIds:[...]}`, project-storage delete
`{types:[...]}`). Rules:
- at most 100 items per batch
- all-or-nothing in one transaction — one failing item fails the whole batch
- one optional `IdempotencyKey` per batch; retrying with the same key is safe
- errors report the index and playerId of the failing item
Method naming is uniform: verb + entity (`List`/`Get`/`Submit`/`Update`/
`Upsert`/`Delete`/`Adjust`/`History`).
## Handles
`client.Player(id)` and `client.Leaderboard(slug)` return handles without any
HTTP request. Handles are sugar over the batch operations with n=1: the
handle injects `playerID`/`slug` into a single-item batch and unwraps the
first result.
```go
player := client.Player("p1")
wallet, err := player.Wallet().Adjust(ctx, rudder.AdjustPlayerWalletParameters{
IdempotencyKey: "grant-001",
CurrencyCode: "coins",
Amount: 100,
Reason: "compensation",
})
board := client.Leaderboard("weekly")
err = board.Submit(ctx, []rudder.LeaderboardScore{{PlayerID: "p1", Score: 1500}})
```
`client.Player(id)` exposes sub-handles `Wallet()`, `Inventory()`,
`Storage()`, `Quests()` plus `Update`/`Ban`/`Unban`/`Delete`.
`client.Leaderboard(slug)` exposes `List`/`Get`/`GetAround`/`Submit`/
`Update`/`Delete`.
## Top-level Storage vs PlayerHandle.Storage()
`client.Storage` is the **global (project) storage** — shared key/value items
keyed by `Type`, with permissions and versioning. Player storage is reachable
**only** through `client.Player(id).Storage()` — per-player items keyed by
`Type`. They are different backends with different item shapes; do not
confuse them.
## Errors
All API failures return `*rudder.APIError` (`Status`, `Code`, `Message`,
`RequestID`); use `errors.As`. Client-side misconfiguration returns
`Code: "sdk/invalid-options"` (`rudder.ErrorCodeInvalidOptions`). Scenario
engine codes surfaced in `Code` include `early_completion`, `forbidden`,
`level_not_reached`, `node_not_active`, `objectives_incomplete`,
`run_expired`, `run_not_active`, `scenario_not_active`, `unknown_run`.
## Webhook verification
```go
if !rudder.VerifyWebhookSignature(secret, body, r.Header.Get("X-Signature")) {
// reject: signature is hex HMAC-SHA256 of the raw body
}
```
## Reference (exact signatures per service area)
- `reference/players.md` — list/get/verify-auth, batch update/ban/unban/delete, PlayerHandle
- `reference/wallet.md` — batch adjust, history, PlayerWalletHandle
- `reference/inventory.md` — batch adjust, list, PlayerInventoryHandle
- `reference/storage.md` — project storage (top-level) and player storage (handle)
- `reference/quests.md` — quest definitions list, batch reset/force-complete/force-claim, PlayerQuestsHandle
- `reference/leaderboards.md` — definitions list, LeaderboardHandle entries
@@ -0,0 +1,43 @@
# Inventory
Source: `inventory.go`, `player_inventory.go`. Types: `InventoryItem`, `InventoryList`, `InventoryAdjustment`.
## Batch adjust
```go
func (s *InventoryService) Adjust(ctx context.Context, parameters AdjustInventoryParameters) ([]InventoryItem, error)
type AdjustInventoryParameters struct {
IdempotencyKey string
Items []InventoryAdjustment
}
// InventoryAdjustment: {PlayerID, Slug, Amount int64, Reason}
// Negative Amount removes items. Returns updated items in item order.
```
## List
```go
func (s *InventoryService) List(ctx context.Context, playerID string, parameters ListInventoryParameters) (*InventoryList, error)
type ListInventoryParameters struct {
Limit int
Cursor *string
}
// InventoryList: {Items []InventoryItem, NextCursor string}
// InventoryItem: {ID, Slug, Amount int64, UpdatedAt}
```
## PlayerInventoryHandle
`client.Player(id).Inventory()`:
```go
func (h *PlayerInventoryHandle) Adjust(ctx context.Context, parameters AdjustPlayerInventoryParameters) (*InventoryItem, error)
type AdjustPlayerInventoryParameters struct {
IdempotencyKey string
Slug string
Amount int64
Reason string
}
func (h *PlayerInventoryHandle) List(ctx context.Context, parameters ListInventoryParameters) (*InventoryList, error)
```
@@ -0,0 +1,54 @@
# Leaderboards
Source: `leaderboards.go` (definitions + batch entry mutations), `leaderboard.go` (handle).
Types: `Leaderboard`, `LeaderboardList`, `LeaderboardScore`, `Rank`, `RankList`.
## Definitions
```go
func (s *LeaderboardsService) List(ctx context.Context, parameters ListLeaderboardsParameters) (*LeaderboardList, error)
type ListLeaderboardsParameters struct {
Limit int
Cursor *string
}
// LeaderboardList: {Items []Leaderboard, NextCursor string}
// Leaderboard: {Slug, Name, Metric, ResetPeriod, SortingOrder, MaxEntries int64}
```
## Batch entry mutations (service level)
Note: leaderboard entry mutations do NOT take an IdempotencyKey — the generated
batch request bodies carry only items/playerIds.
```go
func (s *LeaderboardsService) Submit(ctx context.Context, slug string, items []LeaderboardScore) error
func (s *LeaderboardsService) Update(ctx context.Context, slug string, items []LeaderboardScore) error
// LeaderboardScore: {PlayerID, Score float64}
func (s *LeaderboardsService) Delete(ctx context.Context, slug string, playerIDs []string) error
// body: {playerIds: [...]}
```
## LeaderboardHandle
`client.Leaderboard(slug)` — no request at creation.
```go
func (h *LeaderboardHandle) List(ctx context.Context, parameters ListLeaderboardEntriesParameters) (*RankList, error)
type ListLeaderboardEntriesParameters struct {
Limit int
Cursor *string
}
// RankList: {Entries []Rank, NextCursor string}
func (h *LeaderboardHandle) Get(ctx context.Context, playerID string) (*Rank, error)
func (h *LeaderboardHandle) GetAround(ctx context.Context, playerID string, limit int) ([]Rank, error)
// entries around the player's rank
func (h *LeaderboardHandle) Submit(ctx context.Context, items []LeaderboardScore) error
func (h *LeaderboardHandle) Update(ctx context.Context, items []LeaderboardScore) error
func (h *LeaderboardHandle) Delete(ctx context.Context, playerIDs []string) error
```
`Rank`: `{PlayerID, PlayerName, Rank int64, Score float64}`.
+88
View File
@@ -0,0 +1,88 @@
# Players
Source: `players.go`, `player.go`. Types: `types.go` (`Player`, `PlayerDetails`, `PlayerList`, `PlayerUpdate`, `PlayerBan`, `PlayerAuthVerification`).
## Reads
```go
func (s *PlayersService) List(ctx context.Context, parameters ListPlayersParameters) (*PlayerList, error)
type ListPlayersParameters struct {
Limit int // offset-based pagination
Offset int
Search string
}
// PlayerList: {Players []Player, Total int, Limit int, Offset int}
func (s *PlayersService) Get(ctx context.Context, playerID string) (*PlayerDetails, error)
// PlayerDetails: {Player Player, Wallets []WalletBalance, Inventory []InventoryItem,
// Storages []PlayerStorage, WalletTransactions []WalletTransaction}
func (s *PlayersService) VerifyAuth(ctx context.Context, accessToken string) (*PlayerAuthVerification, error)
// Single (non-batch) call: POST /players/auth/verify.
// PlayerAuthVerification: {PlayerID, ProjectID, Environment, Status} — Status is "active"/"banned"/"deleted".
// Environment ("staging"/"prod") is the environment the token was issued for; it always
// matches the admin key, since both come from the same environment.
```
## Batch mutations
```go
func (s *PlayersService) Update(ctx context.Context, parameters UpdatePlayersParameters) ([]Player, error)
type UpdatePlayersParameters struct {
IdempotencyKey string
Items []PlayerUpdate // {PlayerID, Nickname, Data json.RawMessage}
}
func (s *PlayersService) Ban(ctx context.Context, parameters BanPlayersParameters) ([]Player, error)
type BanPlayersParameters struct {
IdempotencyKey string
Items []PlayerBan // {PlayerID, Reason, BannedUntil string}
}
func (s *PlayersService) Unban(ctx context.Context, parameters UnbanPlayersParameters) ([]Player, error)
type UnbanPlayersParameters struct {
IdempotencyKey string
PlayerIDs []string
}
func (s *PlayersService) Delete(ctx context.Context, parameters DeletePlayersParameters) error
type DeletePlayersParameters struct {
IdempotencyKey string
PlayerIDs []string
}
```
## PlayerHandle
`client.Player(id)` — no request at creation.
```go
h.Wallet() *PlayerWalletHandle
h.Inventory() *PlayerInventoryHandle
h.Storage() *PlayerStorageHandle
h.Quests() *PlayerQuestsHandle
func (h *PlayerHandle) Update(ctx context.Context, parameters UpdatePlayerParameters) (*Player, error)
type UpdatePlayerParameters struct {
IdempotencyKey string
Nickname string
Data json.RawMessage
}
func (h *PlayerHandle) Ban(ctx context.Context, parameters BanPlayerParameters) (*Player, error)
type BanPlayerParameters struct {
IdempotencyKey string
Reason string
BannedUntil string
}
func (h *PlayerHandle) Unban(ctx context.Context, idempotencyKey string) (*Player, error)
func (h *PlayerHandle) Delete(ctx context.Context, idempotencyKey string) error
```
## Player fields
`Player`: `ID`, `StableID`, `ProjectID`, `Nickname`, `Data json.RawMessage`,
`Language`, `Region`, `PayerFlag bool`, `BanReason`, `BannedAt`,
`BannedUntil`, `CreatedAt`, `UpdatedAt`, `LastSeen`, `DeletedAt` (all
string unless noted).
+52
View File
@@ -0,0 +1,52 @@
# Quests
Source: `quests.go`, `player_quests.go`. Types: `Quest`, `QuestObjective`, `QuestReward`, `QuestList`, `PlayerQuest`, `PlayerQuestObjective`, `PlayerQuestRef`.
## Quest definitions (catalog)
```go
func (s *QuestsService) List(ctx context.Context, parameters ListQuestsParameters) (*QuestList, error)
type ListQuestsParameters struct {
Status *string // optional: "active" or "archived"
Limit int
Cursor *string
}
// QuestList: {Items []Quest, NextCursor string}
// Quest: {ID, Slug, Name, Status, NextQuestID, Objectives []QuestObjective, Rewards []QuestReward}
// QuestObjective: {ID, Metric, Target int64}
// QuestReward: {Amount int64, Currency, ItemID}
```
## Batch player-quest operations
All three share the same parameters and return only `error`:
```go
func (s *QuestsService) Reset(ctx context.Context, parameters PlayerQuestsParameters) error
func (s *QuestsService) ForceComplete(ctx context.Context, parameters PlayerQuestsParameters) error
func (s *QuestsService) ForceClaim(ctx context.Context, parameters PlayerQuestsParameters) error
type PlayerQuestsParameters struct {
IdempotencyKey string
Items []PlayerQuestRef // {PlayerID, QuestSlug}
}
```
## PlayerQuestsHandle
`client.Player(id).Quests()`:
```go
func (h *PlayerQuestsHandle) List(ctx context.Context) ([]PlayerQuest, error)
// PlayerQuest: {QuestSlug, Name, Status, Objectives []PlayerQuestObjective}
// PlayerQuestObjective: {ObjectiveID, Current int64, Target int64}
func (h *PlayerQuestsHandle) Reset(ctx context.Context, parameters PlayerQuestParameters) error
func (h *PlayerQuestsHandle) ForceComplete(ctx context.Context, parameters PlayerQuestParameters) error
func (h *PlayerQuestsHandle) ForceClaim(ctx context.Context, parameters PlayerQuestParameters) error
type PlayerQuestParameters struct {
IdempotencyKey string
QuestSlug string
}
```
+59
View File
@@ -0,0 +1,59 @@
# Storage
Source: `storage.go` (project storage, top-level), `player_storage.go` (player storage, handle only).
Types: `ProjectStorage`, `ProjectStorageList`, `ProjectStorageInput`, `PlayerStorage`.
Top-level `client.Storage` is **project (global) storage**. Player storage is reachable only via `client.Player(id).Storage()`.
## Project storage (client.Storage)
```go
func (s *StorageService) List(ctx context.Context, parameters ListStorageParameters) (*ProjectStorageList, error)
type ListStorageParameters struct {
Limit int
Cursor string // plain string, not *string
Search string
}
// ProjectStorageList: {Items []ProjectStorage, NextCursor string}
func (s *StorageService) Get(ctx context.Context, itemType string) (*ProjectStorage, error)
func (s *StorageService) Upsert(ctx context.Context, parameters UpsertStorageParameters) error
type UpsertStorageParameters struct {
IdempotencyKey string
Items []ProjectStorageInput
}
// ProjectStorageInput: {Type, Data, ExpiresAt, ReadPermission, WritePermission}
func (s *StorageService) Delete(ctx context.Context, parameters DeleteStorageParameters) error
type DeleteStorageParameters struct {
IdempotencyKey string
Types []string // batch delete by item types
}
```
`ProjectStorage` (returned item): `{ID, Type, Data, Size int64, Version int64, ReadPermission, WritePermission, ExpiresAt, UpdatedAt}`.
## Player storage (client.Player(id).Storage())
```go
func (h *PlayerStorageHandle) Get(ctx context.Context, storageType string) (*PlayerStorage, error)
func (h *PlayerStorageHandle) Upsert(ctx context.Context, parameters UpsertPlayerStorageParameters) (*PlayerStorage, error)
type UpsertPlayerStorageParameters struct {
IdempotencyKey string
Type string
Data string
ExpiresAt string
}
func (h *PlayerStorageHandle) Delete(ctx context.Context, parameters DeletePlayerStorageParameters) error
type DeletePlayerStorageParameters struct {
IdempotencyKey string
Type string
}
```
`PlayerStorage`: `{ID, PlayerID, Type, Data, ExpiresAt, UpdatedAt}`.
The handle's Upsert/Delete send single-item batches to `PUT`/`DELETE /players/storage` — the same batch rules (idempotency key, all-or-nothing) apply.
+50
View File
@@ -0,0 +1,50 @@
# Wallet
Source: `wallet.go`, `player_wallet.go`. Types: `WalletBalance`, `WalletTransaction`, `WalletHistory`, `WalletAdjustment`.
## Batch adjust
```go
func (s *WalletService) Adjust(ctx context.Context, parameters AdjustWalletParameters) ([]WalletBalance, error)
type AdjustWalletParameters struct {
IdempotencyKey string
Items []WalletAdjustment
}
// WalletAdjustment: {PlayerID, CurrencyCode, Amount int64, Reason}
// Negative Amount subtracts. Returns updated balances in item order.
```
## History
```go
func (s *WalletService) History(ctx context.Context, playerID string, parameters WalletHistoryParameters) (*WalletHistory, error)
type WalletHistoryParameters struct {
Currency *string // optional filter
Limit int
Cursor *string
}
// WalletHistory: {Entries []WalletTransaction, NextCursor string}
// WalletTransaction: {ID, WalletID, PlayerID, CurrencyCode, Amount int64,
// BalanceBefore int64, BalanceAfter int64, Reason, Source,
// Metadata json.RawMessage, CreatedAt}
```
## PlayerWalletHandle
`client.Player(id).Wallet()`:
```go
func (h *PlayerWalletHandle) Adjust(ctx context.Context, parameters AdjustPlayerWalletParameters) (*WalletBalance, error)
type AdjustPlayerWalletParameters struct {
IdempotencyKey string
CurrencyCode string
Amount int64
Reason string
}
func (h *PlayerWalletHandle) History(ctx context.Context, parameters WalletHistoryParameters) (*WalletHistory, error)
```
## WalletBalance fields
`{ID, PlayerID, CurrencyCode, Balance int64, UpdatedAt}`.
+5 -5
View File
@@ -6,7 +6,7 @@ import (
"net/url"
"strconv"
"hub.rudder.build/rudder/rudder-go-sdk/models"
"hub.rudder.build/rudder/rudder-go-sdk/v2/models"
)
type StorageService struct {
@@ -19,7 +19,7 @@ type ListStorageParameters struct {
Search string
}
func (s *StorageService) List(ctx context.Context, parameters ListStorageParameters) (*models.GetProjectStorageResponse, error) {
func (s *StorageService) List(ctx context.Context, parameters ListStorageParameters) (*ProjectStorageList, error) {
query := url.Values{}
if parameters.Limit > 0 {
query.Set("limit", strconv.Itoa(parameters.Limit))
@@ -31,14 +31,14 @@ func (s *StorageService) List(ctx context.Context, parameters ListStorageParamet
query.Set("search", parameters.Search)
}
var response models.GetProjectStorageResponse
var response ProjectStorageList
if err := s.transport.do(ctx, http.MethodGet, "/project-storage", query, nil, &response); err != nil {
return nil, err
}
return &response, nil
}
func (s *StorageService) Get(ctx context.Context, itemType string) (*models.ProjectStorageItem, error) {
func (s *StorageService) Get(ctx context.Context, itemType string) (*ProjectStorage, error) {
var response models.AdminGetProjectStorageResponse
if err := s.transport.do(ctx, http.MethodGet, "/project-storage/"+url.PathEscape(itemType), nil, nil, &response); err != nil {
return nil, err
@@ -48,7 +48,7 @@ func (s *StorageService) Get(ctx context.Context, itemType string) (*models.Proj
type UpsertStorageParameters struct {
IdempotencyKey string
Items []models.AdminProjectStorageItem
Items []ProjectStorageInput
}
func (s *StorageService) Upsert(ctx context.Context, parameters UpsertStorageParameters) error {
+41
View File
@@ -0,0 +1,41 @@
package rudder
import "hub.rudder.build/rudder/rudder-go-sdk/v2/models"
// Domain types are aliases of generated OpenAPI models so callers import
// only this package. models/ is wire format and is not part of the public API.
type Player = models.AdminPlayer
type PlayerDetails = models.AdminPlayerDetails
type PlayerList = models.ListPlayersResponse
type PlayerUpdate = models.BatchUpdatePlayersItem
type PlayerBan = models.BatchBanPlayersItem
type PlayerAuthVerification = models.VerifyPlayerAuthResponse
type WalletBalance = models.WalletDetails
type WalletTransaction = models.WalletAudit
type WalletHistory = models.GetWalletHistoryResponse
type WalletAdjustment = models.BatchAdjustPlayerWalletsItem
type InventoryItem = models.InventoryItem
type InventoryList = models.GetPlayerInventoryResponse
type InventoryAdjustment = models.BatchAdjustPlayerInventoryItemsItem
type PlayerStorage = models.PlayerStorage
type ProjectStorage = models.ProjectStorageItem
type ProjectStorageList = models.GetProjectStorageResponse
type ProjectStorageInput = models.AdminProjectStorageItem
type PlayerQuest = models.AdminPlayerQuest
type PlayerQuestObjective = models.AdminPlayerQuestObjective
type PlayerQuestRef = models.BatchPlayerQuestsItem
type Quest = models.QuestDefinition
type QuestObjective = models.QuestObjective
type QuestReward = models.QuestRewardDefinition
type QuestList = models.AdminListQuestsResponse
type Leaderboard = models.LeaderboardDefinition
type LeaderboardList = models.ListLeaderboardsResponse
type LeaderboardScore = models.BatchLeaderboardEntry
type Rank = models.RankEntry
type RankList = models.ListLeaderboardEntriesResponse
+5 -5
View File
@@ -6,7 +6,7 @@ import (
"net/url"
"strconv"
"hub.rudder.build/rudder/rudder-go-sdk/models"
"hub.rudder.build/rudder/rudder-go-sdk/v2/models"
)
type WalletService struct {
@@ -15,10 +15,10 @@ type WalletService struct {
type AdjustWalletParameters struct {
IdempotencyKey string
Items []models.BatchAdjustPlayerWalletsItem
Items []WalletAdjustment
}
func (s *WalletService) Adjust(ctx context.Context, parameters AdjustWalletParameters) ([]models.WalletDetails, error) {
func (s *WalletService) Adjust(ctx context.Context, parameters AdjustWalletParameters) ([]WalletBalance, error) {
body := models.BatchAdjustPlayerWalletsRequest{
IdempotencyKey: parameters.IdempotencyKey,
Items: parameters.Items,
@@ -37,7 +37,7 @@ type WalletHistoryParameters struct {
Cursor *string
}
func (s *WalletService) History(ctx context.Context, playerID string, parameters WalletHistoryParameters) (*models.GetWalletHistoryResponse, error) {
func (s *WalletService) History(ctx context.Context, playerID string, parameters WalletHistoryParameters) (*WalletHistory, error) {
query := url.Values{}
if parameters.Currency != nil {
query.Set("currency", *parameters.Currency)
@@ -49,7 +49,7 @@ func (s *WalletService) History(ctx context.Context, playerID string, parameters
query.Set("cursor", *parameters.Cursor)
}
var response models.GetWalletHistoryResponse
var response WalletHistory
if err := s.transport.do(ctx, http.MethodGet, "/players/"+url.PathEscape(playerID)+"/wallet/history", query, nil, &response); err != nil {
return nil, err
}
+18
View File
@@ -0,0 +1,18 @@
package rudder
import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
)
func VerifyWebhookSignature(secret string, body []byte, signature string) bool {
mac := hmac.New(sha256.New, []byte(secret))
mac.Write(body)
expected, err := hex.DecodeString(signature)
if err != nil {
return false
}
return hmac.Equal(mac.Sum(nil), expected)
}