15 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
edmand46 ef5bbe2df2 v0.5.0: batch-first API, player/leaderboard handles, unified naming, Storage is global project storage, catalog list methods
CI / build (push) Successful in 49s
2026-08-26 18:30:28 +03:00
edmand46 db1223c195 leaderboards: ListEntries/SubmitEntry/UpdateEntry/DeleteEntry against /entries
CI / build (push) Successful in 50s
2026-08-26 13:53:27 +03:00
edmand46 4b5922c92f quests: admin player-quest client (list/reset/force-complete/force-claim), generated models
CI / build (push) Successful in 56s
2026-08-25 16:25:29 +03:00
edmand46 cafc2bfc6a rename Config.APIKey to AdminKey, add search to project storage list
CI / build (push) Successful in 1m2s
2026-08-25 13:59:30 +03:00
edmand46 6e7c818f75 remove UGC surface, add CI, versioning docs
CI / build (push) Successful in 1m14s
2026-08-19 17:49:03 +03:00
edmand46 ae74fb22d3 add player data operations: update, ban/unban, delete, inventory adjust, storage
Players.Update/Ban/Unban/Delete + List search; new InventoryService.Adjust
and StorageService.Get/Upsert/Delete; IdempotencyKey on wallet adjust;
models regenerated via apigen
2026-08-13 08:47:14 +03:00
edmand46 d602a95baf Add ProjectStorageService (list/get/upsert/delete) 2026-08-12 23:58:34 +03:00
rudder 58eb477763 Per-project admin key auth, drop ProjectID; MIT license; go 1.21 2026-08-12 17:37:46 +03:00
86 changed files with 1966 additions and 212 deletions
+26
View File
@@ -0,0 +1,26 @@
name: CI
on:
push:
branches: [master, main]
jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: '1.21'
- name: Build
run: go build ./...
- name: Vet
run: go vet ./...
+1
View File
@@ -0,0 +1 @@
.idea/
+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.
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2026 Rudder
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+199 -15
View File
@@ -1,8 +1,7 @@
# liveops-go-sdk # rudder-go-sdk
Серверный Go SDK для LiveOps-платформы Rudder. Покрывает server-admin Серверный Go SDK для LiveOps-платформы Rudder. Покрывает server-admin
поверхность `/game/v1` (auth по `X-API-Key`, ключ — `GAME_ADMIN_API_KEY` поверхность `/game/v1` (auth по `X-API-Key`).
на gateway).
## Install ## Install
@@ -11,31 +10,215 @@
```bash ```bash
go env -w GOPRIVATE=hub.rudder.build/* 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.25+. Токен не нужен — репозиторий публичный. Требуется 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 ## Usage
```go ```go
client, err := rudder.New(rudder.Config{ client, err := rudder.New(rudder.Config{
APIKey: os.Getenv("GAME_ADMIN_API_KEY"), AdminKey: os.Getenv("RUDDER_ADMIN_KEY"),
ProjectID: "your-project-id",
}) })
if err != nil { if err != nil {
// *rudder.APIError с кодом sdk/invalid-options // *rudder.APIError с кодом sdk/invalid-options
} }
players, err := client.Players.List(ctx, rudder.ListPlayersParameters{Limit: 100}) players, err := client.Players.List(ctx, rudder.ListPlayersParameters{Limit: 100})
entries, err := client.Leaderboards.Results(ctx, "weekly") ```
wallet, err := client.Wallet.Adjust(ctx, playerID, rudder.AdjustWalletParameters{
CurrencyCode: "coins", Публичные типы живут в пакете `rudder`. Пакет `models` — сгенерированный
Amount: 100, wire-формат, в прикладном коде его импортировать не нужно.
Reason: "compensation",
## Handles
`client.Player(id)` и `client.Leaderboard(slug)` создают handle без
HTTP-запроса. Handles — сахар над batch-методами сервисов: каждая операция
отправляет batch из одного элемента с подставленным playerID/slug.
```go
player := client.Player("p1")
wallet, err := player.Wallet().Adjust(ctx, rudder.AdjustPlayerWalletParameters{
IdempotencyKey: "grant-001",
CurrencyCode: "coins",
Amount: 100,
Reason: "compensation",
}) })
metrics, err := client.Metrics.Get(ctx) history, err := player.Wallet().History(ctx, rudder.WalletHistoryParameters{Limit: 50})
page, err := client.Ugc.List(ctx, rudder.ListUgcParameters{Status: "pending"})
item, err := player.Inventory().Adjust(ctx, rudder.AdjustPlayerInventoryParameters{
Slug: "sword",
Amount: 1,
Reason: "quest reward",
})
inventory, err := player.Inventory().List(ctx, rudder.ListInventoryParameters{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"})
quests, err := player.Quests().List(ctx)
err = player.Quests().Reset(ctx, rudder.PlayerQuestParameters{QuestSlug: "daily-1"})
// ForceComplete и ForceClaim — аналогично
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, "")
board := client.Leaderboard("weekly")
entries, err := board.List(ctx, rudder.ListLeaderboardEntriesParameters{Limit: 100})
// entries.Entries []rudder.Rank, entries.NextCursor
entry, err := board.Get(ctx, "p1") // *rudder.Rank с rank
around, err := board.GetAround(ctx, "p1", 10) // []rudder.Rank вокруг игрока
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"})
```
## 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"},
},
})
items, err := client.Inventory.Adjust(ctx, rudder.AdjustInventoryParameters{
Items: []rudder.InventoryAdjustment{
{PlayerID: "p1", Slug: "sword", Amount: 1, Reason: "quest reward"},
},
})
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"},
})
```
## Верификация авторизации
`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", []rudder.LeaderboardScore{
{PlayerID: "p1", Score: 1500},
})
// Update и Delete — аналогично
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"},
})
```
## Каталоги и read-операции
Курсорная пагинация: передавайте `Limit`/`Cursor`, следующую страницу
запрашивайте по `NextCursor` из ответа.
```go
boards, err := client.Leaderboards.List(ctx, rudder.ListLeaderboardsParameters{Limit: 100})
// boards.Items []rudder.Leaderboard, boards.NextCursor
quests, err := client.Quests.List(ctx, rudder.ListQuestsParameters{
Status: &status, // "active" или "archived", optional
Limit: 100,
})
// quests.Items []rudder.Quest, quests.NextCursor
history, err := client.Wallet.History(ctx, playerID, rudder.WalletHistoryParameters{
Currency: &currency, // optional
Limit: 50,
})
// history.Entries []rudder.WalletTransaction, history.NextCursor
inventory, err := client.Inventory.List(ctx, playerID, rudder.ListInventoryParameters{
Limit: 50,
})
// inventory.Items []rudder.InventoryItem, inventory.NextCursor
storage, err := client.Storage.List(ctx, rudder.ListStorageParameters{
Limit: 100,
Search: "config", // optional
})
item, err := client.Storage.Get(ctx, "config")
``` ```
## Ошибки ## Ошибки
@@ -44,7 +227,7 @@ page, err := client.Ugc.List(ctx, rudder.ListUgcParameters{Status: "pending"})
var apiErr *rudder.APIError var apiErr *rudder.APIError
if errors.As(err, &apiErr) { if errors.As(err, &apiErr) {
apiErr.Status // HTTP status apiErr.Status // HTTP status
apiErr.Code // машинный код, константы в models.ErrorCode* apiErr.Code // машинный код из тела ошибки gateway
apiErr.RequestID // корреляция с логами gateway apiErr.RequestID // корреляция с логами gateway
} }
``` ```
@@ -53,3 +236,4 @@ if errors.As(err, &apiErr) {
`models/` генерируется apigen'ом из `liveops-gateway/openapi.yaml` `models/` генерируется apigen'ом из `liveops-gateway/openapi.yaml`
(`make generate-openapi` в liveops-gateway). Руками не править. (`make generate-openapi` в liveops-gateway). Руками не править.
Публичные имена — aliases в корне пакета `rudder` (`types.go`).
+33 -13
View File
@@ -8,8 +8,7 @@ import (
const DefaultBaseURL = "https://api.rudder.build" const DefaultBaseURL = "https://api.rudder.build"
type Config struct { type Config struct {
APIKey string AdminKey string
ProjectID string
BaseURL string BaseURL string
Timeout time.Duration Timeout time.Duration
HTTPClient *http.Client HTTPClient *http.Client
@@ -17,18 +16,18 @@ type Config struct {
type Client struct { type Client struct {
Players *PlayersService Players *PlayersService
Metrics *MetricsService
Leaderboards *LeaderboardsService Leaderboards *LeaderboardsService
Wallet *WalletService Wallet *WalletService
Ugc *UgcService Inventory *InventoryService
Quests *QuestsService
Storage *StorageService
transport *transport
} }
func New(config Config) (*Client, error) { func New(config Config) (*Client, error) {
if config.APIKey == "" { if config.AdminKey == "" {
return nil, &APIError{Code: ErrorCodeInvalidOptions, Message: "APIKey is required"} return nil, &APIError{Code: ErrorCodeInvalidOptions, Message: "AdminKey is required"}
}
if config.ProjectID == "" {
return nil, &APIError{Code: ErrorCodeInvalidOptions, Message: "ProjectID is required"}
} }
if config.BaseURL == "" { if config.BaseURL == "" {
@@ -43,16 +42,37 @@ func New(config Config) (*Client, error) {
transport := &transport{ transport := &transport{
baseURL: config.BaseURL, baseURL: config.BaseURL,
apiKey: config.APIKey, apiKey: config.AdminKey,
projectID: config.ProjectID,
httpClient: config.HTTPClient, httpClient: config.HTTPClient,
} }
return &Client{ return &Client{
Players: &PlayersService{transport: transport}, Players: &PlayersService{transport: transport},
Metrics: &MetricsService{transport: transport},
Leaderboards: &LeaderboardsService{transport: transport}, Leaderboards: &LeaderboardsService{transport: transport},
Wallet: &WalletService{transport: transport}, Wallet: &WalletService{transport: transport},
Ugc: &UgcService{transport: transport}, Inventory: &InventoryService{transport: transport},
Quests: &QuestsService{transport: transport},
Storage: &StorageService{transport: transport},
transport: transport,
}, nil }, nil
} }
func (c *Client) Player(playerID string) *PlayerHandle {
return &PlayerHandle{
playerID: playerID,
transport: c.transport,
players: c.Players,
wallet: c.Wallet,
inventory: c.Inventory,
quests: c.Quests,
}
}
func (c *Client) Leaderboard(slug string) *LeaderboardHandle {
return &LeaderboardHandle{
slug: slug,
transport: c.transport,
leaderboards: c.Leaderboards,
}
}
+2 -2
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.25.2 go 1.21
+53
View File
@@ -0,0 +1,53 @@
package rudder
import (
"context"
"net/http"
"net/url"
"strconv"
"hub.rudder.build/rudder/rudder-go-sdk/v2/models"
)
type InventoryService struct {
transport *transport
}
type AdjustInventoryParameters struct {
IdempotencyKey string
Items []InventoryAdjustment
}
func (s *InventoryService) Adjust(ctx context.Context, parameters AdjustInventoryParameters) ([]InventoryItem, error) {
body := models.BatchAdjustPlayerInventoryItemsRequest{
IdempotencyKey: parameters.IdempotencyKey,
Items: parameters.Items,
}
var response models.BatchAdjustPlayerInventoryItemsResponse
if err := s.transport.do(ctx, http.MethodPost, "/players/inventory/adjust", nil, body, &response); err != nil {
return nil, err
}
return response.Items, nil
}
type ListInventoryParameters struct {
Limit int
Cursor *string
}
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))
}
if parameters.Cursor != nil {
query.Set("cursor", *parameters.Cursor)
}
var response InventoryList
if err := s.transport.do(ctx, http.MethodGet, "/players/"+url.PathEscape(playerID)+"/inventory", query, nil, &response); err != nil {
return nil, err
}
return &response, nil
}
+70
View File
@@ -0,0 +1,70 @@
package rudder
import (
"context"
"net/http"
"net/url"
"strconv"
"hub.rudder.build/rudder/rudder-go-sdk/v2/models"
)
type LeaderboardHandle struct {
slug string
transport *transport
leaderboards *LeaderboardsService
}
type ListLeaderboardEntriesParameters struct {
Limit int
Cursor *string
}
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))
}
if parameters.Cursor != nil {
query.Set("cursor", *parameters.Cursor)
}
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) (*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) ([]Rank, error) {
query := url.Values{}
if limit > 0 {
query.Set("limit", strconv.Itoa(limit))
}
var response models.GetLeaderboardEntryAroundResponse
if err := h.transport.do(ctx, http.MethodGet, "/leaderboards/"+url.PathEscape(h.slug)+"/entries/"+url.PathEscape(playerID)+"/around", query, nil, &response); err != nil {
return nil, err
}
return response.Entries, nil
}
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 []LeaderboardScore) error {
return h.leaderboards.Update(ctx, h.slug, items)
}
func (h *LeaderboardHandle) Delete(ctx context.Context, playerIDs []string) error {
return h.leaderboards.Delete(ctx, h.slug, playerIDs)
}
+40 -5
View File
@@ -4,18 +4,53 @@ import (
"context" "context"
"net/http" "net/http"
"net/url" "net/url"
"strconv"
"hub.rudder.build/rudder/rudder-go-sdk/models" "hub.rudder.build/rudder/rudder-go-sdk/v2/models"
) )
type LeaderboardsService struct { type LeaderboardsService struct {
transport *transport transport *transport
} }
func (s *LeaderboardsService) Results(ctx context.Context, slug string) ([]models.RankEntry, error) { type ListLeaderboardsParameters struct {
var response models.GetLeaderboardResultsResponse Limit int
if err := s.transport.do(ctx, http.MethodGet, "/leaderboards/"+url.PathEscape(slug)+"/results", nil, nil, &response); err != nil { Cursor *string
}
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))
}
if parameters.Cursor != nil {
query.Set("cursor", *parameters.Cursor)
}
var response LeaderboardList
if err := s.transport.do(ctx, http.MethodGet, "/leaderboards", query, nil, &response); err != nil {
return nil, err return nil, err
} }
return response.Entries, nil return &response, nil
}
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 []LeaderboardScore) error {
body := models.BatchUpdateLeaderboardEntriesRequest{
Items: items,
}
return s.transport.do(ctx, http.MethodPatch, "/leaderboards/"+url.PathEscape(slug)+"/entries", nil, body, nil)
}
func (s *LeaderboardsService) Delete(ctx context.Context, slug string, playerIDs []string) error {
body := models.BatchDeleteLeaderboardEntriesRequest{
PlayerIds: playerIDs,
}
return s.transport.do(ctx, http.MethodDelete, "/leaderboards/"+url.PathEscape(slug)+"/entries", nil, body, nil)
} }
-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
}
+7
View File
@@ -0,0 +1,7 @@
// Code generated by apigen. DO NOT EDIT.
package models
type AdminGetProjectStorageResponse struct {
Item ProjectStorageItem `json:"item,omitempty"`
}
+8
View File
@@ -0,0 +1,8 @@
// Code generated by apigen. DO NOT EDIT.
package models
type AdminListQuestsResponse struct {
Items []QuestDefinition `json:"items,omitempty"`
NextCursor string `json:"nextCursor,omitempty"`
}
+16 -11
View File
@@ -5,15 +5,20 @@ package models
import "encoding/json" import "encoding/json"
type AdminPlayer struct { type AdminPlayer struct {
CreatedAt string `json:"createdAt,omitempty"` AvatarURL string `json:"avatarUrl,omitempty"`
Data json.RawMessage `json:"data,omitempty"` BanReason string `json:"banReason,omitempty"`
ID string `json:"id,omitempty"` BannedAt string `json:"bannedAt,omitempty"`
Language string `json:"language,omitempty"` BannedUntil string `json:"bannedUntil,omitempty"`
LastSeen string `json:"lastSeen,omitempty"` CreatedAt string `json:"createdAt,omitempty"`
Nickname string `json:"nickname,omitempty"` Data json.RawMessage `json:"data,omitempty"`
PayerFlag bool `json:"payerFlag,omitempty"` DeletedAt string `json:"deletedAt,omitempty"`
ProjectID string `json:"projectId,omitempty"` ID string `json:"id,omitempty"`
Region string `json:"region,omitempty"` Language string `json:"language,omitempty"`
StableID string `json:"stableId,omitempty"` LastSeen string `json:"lastSeen,omitempty"`
UpdatedAt string `json:"updatedAt,omitempty"` Nickname string `json:"nickname,omitempty"`
PayerFlag bool `json:"payerFlag,omitempty"`
ProjectID string `json:"projectId,omitempty"`
Region string `json:"region,omitempty"`
StableID string `json:"stableId,omitempty"`
UpdatedAt string `json:"updatedAt,omitempty"`
} }
+6 -5
View File
@@ -3,9 +3,10 @@
package models package models
type AdminPlayerDetails struct { type AdminPlayerDetails struct {
Inventory []InventoryItem `json:"inventory,omitempty"` Identities []PlayerIdentity `json:"identities,omitempty"`
Player AdminPlayer `json:"player,omitempty"` Inventory []InventoryItem `json:"inventory,omitempty"`
Storages []PlayerStorage `json:"storages,omitempty"` Player AdminPlayer `json:"player,omitempty"`
WalletTransactions []WalletAudit `json:"walletTransactions,omitempty"` Storages []PlayerStorage `json:"storages,omitempty"`
Wallets []WalletDetails `json:"wallets,omitempty"` WalletTransactions []WalletAudit `json:"walletTransactions,omitempty"`
Wallets []WalletDetails `json:"wallets,omitempty"`
} }
+10
View File
@@ -0,0 +1,10 @@
// Code generated by apigen. DO NOT EDIT.
package models
type AdminPlayerQuest struct {
Name string `json:"name,omitempty"`
Objectives []AdminPlayerQuestObjective `json:"objectives,omitempty"`
QuestSlug string `json:"questSlug,omitempty"`
Status string `json:"status,omitempty"`
}
+9
View File
@@ -0,0 +1,9 @@
// Code generated by apigen. DO NOT EDIT.
package models
type AdminPlayerQuestObjective struct {
Current int64 `json:"current,omitempty"`
ObjectiveID string `json:"objectiveId,omitempty"`
Target int64 `json:"target,omitempty"`
}
+11
View File
@@ -0,0 +1,11 @@
// Code generated by apigen. DO NOT EDIT.
package models
type AdminProjectStorageItem struct {
Data string `json:"data,omitempty"`
ExpiresAt string `json:"expiresAt,omitempty"`
ReadPermission string `json:"readPermission,omitempty"`
Type string `json:"type,omitempty"`
WritePermission string `json:"writePermission,omitempty"`
}
@@ -0,0 +1,10 @@
// Code generated by apigen. DO NOT EDIT.
package models
type BatchAdjustPlayerInventoryItemsItem struct {
Amount int64 `json:"amount,omitempty"`
PlayerID string `json:"playerId,omitempty"`
Reason string `json:"reason,omitempty"`
Slug string `json:"slug,omitempty"`
}
@@ -0,0 +1,8 @@
// Code generated by apigen. DO NOT EDIT.
package models
type BatchAdjustPlayerInventoryItemsRequest struct {
IdempotencyKey string `json:"idempotencyKey,omitempty"`
Items []BatchAdjustPlayerInventoryItemsItem `json:"items,omitempty"`
}
@@ -0,0 +1,7 @@
// Code generated by apigen. DO NOT EDIT.
package models
type BatchAdjustPlayerInventoryItemsResponse struct {
Items []InventoryItem `json:"items,omitempty"`
}
@@ -2,7 +2,7 @@
package models package models
type AdjustPlayerWalletRequest struct { type BatchAdjustPlayerWalletsItem struct {
Amount int64 `json:"amount,omitempty"` Amount int64 `json:"amount,omitempty"`
CurrencyCode string `json:"currencyCode,omitempty"` CurrencyCode string `json:"currencyCode,omitempty"`
PlayerID string `json:"playerId,omitempty"` PlayerID string `json:"playerId,omitempty"`
@@ -0,0 +1,8 @@
// Code generated by apigen. DO NOT EDIT.
package models
type BatchAdjustPlayerWalletsRequest struct {
IdempotencyKey string `json:"idempotencyKey,omitempty"`
Items []BatchAdjustPlayerWalletsItem `json:"items,omitempty"`
}
@@ -0,0 +1,7 @@
// Code generated by apigen. DO NOT EDIT.
package models
type BatchAdjustPlayerWalletsResponse struct {
Wallets []WalletDetails `json:"wallets,omitempty"`
}
+9
View File
@@ -0,0 +1,9 @@
// Code generated by apigen. DO NOT EDIT.
package models
type BatchBanPlayersItem struct {
BannedUntil string `json:"bannedUntil,omitempty"`
PlayerID string `json:"playerId,omitempty"`
Reason string `json:"reason,omitempty"`
}
+8
View File
@@ -0,0 +1,8 @@
// Code generated by apigen. DO NOT EDIT.
package models
type BatchBanPlayersRequest struct {
IdempotencyKey string `json:"idempotencyKey,omitempty"`
Items []BatchBanPlayersItem `json:"items,omitempty"`
}
@@ -0,0 +1,7 @@
// Code generated by apigen. DO NOT EDIT.
package models
type BatchDeleteLeaderboardEntriesRequest struct {
PlayerIds []string `json:"playerIds,omitempty"`
}
+8
View File
@@ -0,0 +1,8 @@
// Code generated by apigen. DO NOT EDIT.
package models
type BatchDeletePlayersRequest struct {
IdempotencyKey string `json:"idempotencyKey,omitempty"`
Items []BatchPlayerItem `json:"items,omitempty"`
}
+8
View File
@@ -0,0 +1,8 @@
// Code generated by apigen. DO NOT EDIT.
package models
type BatchDeletePlayerStoragesItem struct {
PlayerID string `json:"playerId,omitempty"`
Type string `json:"type,omitempty"`
}
@@ -0,0 +1,8 @@
// Code generated by apigen. DO NOT EDIT.
package models
type BatchDeletePlayerStoragesRequest struct {
IdempotencyKey string `json:"idempotencyKey,omitempty"`
Items []BatchDeletePlayerStoragesItem `json:"items,omitempty"`
}
@@ -0,0 +1,8 @@
// Code generated by apigen. DO NOT EDIT.
package models
type BatchDeleteProjectStorageRequest struct {
IdempotencyKey string `json:"idempotencyKey,omitempty"`
Types []string `json:"types,omitempty"`
}
+8
View File
@@ -0,0 +1,8 @@
// Code generated by apigen. DO NOT EDIT.
package models
type BatchLeaderboardEntry struct {
PlayerID string `json:"playerId,omitempty"`
Score float64 `json:"score,omitempty"`
}
+7
View File
@@ -0,0 +1,7 @@
// Code generated by apigen. DO NOT EDIT.
package models
type BatchPlayerItem struct {
PlayerID string `json:"playerId,omitempty"`
}
+8
View File
@@ -0,0 +1,8 @@
// Code generated by apigen. DO NOT EDIT.
package models
type BatchPlayerQuestsItem struct {
PlayerID string `json:"playerId,omitempty"`
QuestSlug string `json:"questSlug,omitempty"`
}
+8
View File
@@ -0,0 +1,8 @@
// Code generated by apigen. DO NOT EDIT.
package models
type BatchPlayerQuestsRequest struct {
IdempotencyKey string `json:"idempotencyKey,omitempty"`
Items []BatchPlayerQuestsItem `json:"items,omitempty"`
}
+7
View File
@@ -0,0 +1,7 @@
// Code generated by apigen. DO NOT EDIT.
package models
type BatchPlayersResponse struct {
Players []AdminPlayer `json:"players,omitempty"`
}
@@ -0,0 +1,7 @@
// Code generated by apigen. DO NOT EDIT.
package models
type BatchSubmitLeaderboardEntriesRequest struct {
Items []BatchLeaderboardEntry `json:"items,omitempty"`
}
+8
View File
@@ -0,0 +1,8 @@
// Code generated by apigen. DO NOT EDIT.
package models
type BatchUnbanPlayersRequest struct {
IdempotencyKey string `json:"idempotencyKey,omitempty"`
Items []BatchPlayerItem `json:"items,omitempty"`
}
@@ -0,0 +1,7 @@
// Code generated by apigen. DO NOT EDIT.
package models
type BatchUpdateLeaderboardEntriesRequest struct {
Items []BatchLeaderboardEntry `json:"items,omitempty"`
}
+11
View File
@@ -0,0 +1,11 @@
// Code generated by apigen. DO NOT EDIT.
package models
import "encoding/json"
type BatchUpdatePlayersItem struct {
Data json.RawMessage `json:"data,omitempty"`
Nickname string `json:"nickname,omitempty"`
PlayerID string `json:"playerId,omitempty"`
}
+8
View File
@@ -0,0 +1,8 @@
// Code generated by apigen. DO NOT EDIT.
package models
type BatchUpdatePlayersRequest struct {
IdempotencyKey string `json:"idempotencyKey,omitempty"`
Items []BatchUpdatePlayersItem `json:"items,omitempty"`
}
+10
View File
@@ -0,0 +1,10 @@
// Code generated by apigen. DO NOT EDIT.
package models
type BatchUpsertPlayerStoragesItem struct {
Data string `json:"data,omitempty"`
ExpiresAt string `json:"expiresAt,omitempty"`
PlayerID string `json:"playerId,omitempty"`
Type string `json:"type,omitempty"`
}
@@ -0,0 +1,8 @@
// Code generated by apigen. DO NOT EDIT.
package models
type BatchUpsertPlayerStoragesRequest struct {
IdempotencyKey string `json:"idempotencyKey,omitempty"`
Items []BatchUpsertPlayerStoragesItem `json:"items,omitempty"`
}
@@ -0,0 +1,7 @@
// Code generated by apigen. DO NOT EDIT.
package models
type BatchUpsertPlayerStoragesResponse struct {
Storages []PlayerStorage `json:"storages,omitempty"`
}
@@ -0,0 +1,8 @@
// Code generated by apigen. DO NOT EDIT.
package models
type BatchUpsertProjectStorageRequest struct {
IdempotencyKey string `json:"idempotencyKey,omitempty"`
Items []AdminProjectStorageItem `json:"items,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"
)
+11
View File
@@ -0,0 +1,11 @@
// Code generated by apigen. DO NOT EDIT.
package models
type ErrorResponse struct {
Code string `json:"code,omitempty"`
Error string `json:"error,omitempty"`
Index int `json:"index,omitempty"`
PlayerID string `json:"playerId,omitempty"`
RequestID string `json:"requestId,omitempty"`
}
@@ -2,6 +2,6 @@
package models package models
type GetLeaderboardResultsResponse struct { type GetLeaderboardEntryAroundResponse struct {
Entries []RankEntry `json:"entries,omitempty"` Entries []RankEntry `json:"entries,omitempty"`
} }
@@ -2,7 +2,7 @@
package models package models
type ListUgcResponse struct { type GetPlayerInventoryResponse struct {
Items []UgcSubmission `json:"items,omitempty"` Items []InventoryItem `json:"items,omitempty"`
NextCursor string `json:"nextCursor,omitempty"` NextCursor string `json:"nextCursor,omitempty"`
} }
+8
View File
@@ -0,0 +1,8 @@
// Code generated by apigen. DO NOT EDIT.
package models
type GetProjectStorageResponse struct {
Items []ProjectStorageItem `json:"items,omitempty"`
NextCursor string `json:"nextCursor,omitempty"`
}
+8
View File
@@ -0,0 +1,8 @@
// Code generated by apigen. DO NOT EDIT.
package models
type GetWalletHistoryResponse struct {
Entries []WalletAudit `json:"entries,omitempty"`
NextCursor string `json:"nextCursor,omitempty"`
}
+12
View File
@@ -0,0 +1,12 @@
// Code generated by apigen. DO NOT EDIT.
package models
type LeaderboardDefinition struct {
MaxEntries int64 `json:"maxEntries,omitempty"`
Metric string `json:"metric,omitempty"`
Name string `json:"name,omitempty"`
ResetPeriod string `json:"resetPeriod,omitempty"`
Slug string `json:"slug,omitempty"`
SortingOrder string `json:"sortingOrder,omitempty"`
}
+8
View File
@@ -0,0 +1,8 @@
// Code generated by apigen. DO NOT EDIT.
package models
type ListLeaderboardEntriesResponse struct {
Entries []RankEntry `json:"entries,omitempty"`
NextCursor string `json:"nextCursor,omitempty"`
}
+8
View File
@@ -0,0 +1,8 @@
// Code generated by apigen. DO NOT EDIT.
package models
type ListLeaderboardsResponse struct {
Items []LeaderboardDefinition `json:"items,omitempty"`
NextCursor string `json:"nextCursor,omitempty"`
}
+7
View File
@@ -0,0 +1,7 @@
// Code generated by apigen. DO NOT EDIT.
package models
type ListPlayerQuestsResponse struct {
Quests []AdminPlayerQuest `json:"quests,omitempty"`
}
+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"`
}
+15
View File
@@ -0,0 +1,15 @@
// Code generated by apigen. DO NOT EDIT.
package models
type ProjectStorageItem struct {
Data string `json:"data,omitempty"`
ExpiresAt string `json:"expiresAt,omitempty"`
ID string `json:"id,omitempty"`
ReadPermission string `json:"readPermission,omitempty"`
Size int64 `json:"size,omitempty"`
Type string `json:"type,omitempty"`
UpdatedAt string `json:"updatedAt,omitempty"`
Version int64 `json:"version,omitempty"`
WritePermission string `json:"writePermission,omitempty"`
}
+13
View File
@@ -0,0 +1,13 @@
// Code generated by apigen. DO NOT EDIT.
package models
type QuestDefinition struct {
ID string `json:"id,omitempty"`
Name string `json:"name,omitempty"`
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"`
}
+9
View File
@@ -0,0 +1,9 @@
// Code generated by apigen. DO NOT EDIT.
package models
type QuestObjective struct {
ID string `json:"id,omitempty"`
Metric string `json:"metric,omitempty"`
Target int64 `json:"target,omitempty"`
}
+9
View File
@@ -0,0 +1,9 @@
// Code generated by apigen. DO NOT EDIT.
package models
type QuestRewardDefinition struct {
Amount int64 `json:"amount,omitempty"`
Currency string `json:"currency,omitempty"`
ItemID string `json:"itemId,omitempty"`
}
-9
View File
@@ -1,9 +0,0 @@
// Code generated by apigen. DO NOT EDIT.
package models
type ReviewUgcRequest struct {
ID string `json:"id,omitempty"`
ReviewedBy string `json:"reviewedBy,omitempty"`
Status string `json:"status,omitempty"`
}
-8
View File
@@ -1,8 +0,0 @@
// Code generated by apigen. DO NOT EDIT.
package models
type SubmittedBy struct {
UserID string `json:"userId,omitempty"`
Username string `json:"username,omitempty"`
}
-19
View File
@@ -1,19 +0,0 @@
// Code generated by apigen. DO NOT EDIT.
package models
import "encoding/json"
type UgcSubmission struct {
Description string `json:"description,omitempty"`
FileSize int64 `json:"fileSize,omitempty"`
FileURL string `json:"fileUrl,omitempty"`
ID string `json:"id,omitempty"`
Metadata json.RawMessage `json:"metadata,omitempty"`
Name string `json:"name,omitempty"`
ReviewedAt string `json:"reviewedAt,omitempty"`
ReviewedBy string `json:"reviewedBy,omitempty"`
Status string `json:"status,omitempty"`
SubmittedAt string `json:"submittedAt,omitempty"`
SubmittedBy SubmittedBy `json:"submittedBy,omitempty"`
}
-10
View File
@@ -1,10 +0,0 @@
// Code generated by apigen. DO NOT EDIT.
package models
import "encoding/json"
type UpdateUgcMetadataRequest struct {
ID string `json:"id,omitempty"`
Metadata json.RawMessage `json:"metadata,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"`
}
+91
View File
@@ -0,0 +1,91 @@
package rudder
import (
"context"
"encoding/json"
)
type PlayerHandle struct {
playerID string
transport *transport
players *PlayersService
wallet *WalletService
inventory *InventoryService
quests *QuestsService
}
func (h *PlayerHandle) Wallet() *PlayerWalletHandle {
return &PlayerWalletHandle{playerID: h.playerID, wallet: h.wallet}
}
func (h *PlayerHandle) Inventory() *PlayerInventoryHandle {
return &PlayerInventoryHandle{playerID: h.playerID, inventory: h.inventory}
}
func (h *PlayerHandle) Storage() *PlayerStorageHandle {
return &PlayerStorageHandle{playerID: h.playerID, transport: h.transport}
}
func (h *PlayerHandle) Quests() *PlayerQuestsHandle {
return &PlayerQuestsHandle{playerID: h.playerID, transport: h.transport, quests: h.quests}
}
type UpdatePlayerParameters struct {
IdempotencyKey string
Nickname string
Data json.RawMessage
}
func (h *PlayerHandle) Update(ctx context.Context, parameters UpdatePlayerParameters) (*Player, error) {
players, err := h.players.Update(ctx, UpdatePlayersParameters{
IdempotencyKey: parameters.IdempotencyKey,
Items: []PlayerUpdate{{
PlayerID: h.playerID,
Nickname: parameters.Nickname,
Data: parameters.Data,
}},
})
if err != nil {
return nil, err
}
return &players[0], nil
}
type BanPlayerParameters struct {
IdempotencyKey string
Reason string
BannedUntil string
}
func (h *PlayerHandle) Ban(ctx context.Context, parameters BanPlayerParameters) (*Player, error) {
players, err := h.players.Ban(ctx, BanPlayersParameters{
IdempotencyKey: parameters.IdempotencyKey,
Items: []PlayerBan{{
PlayerID: h.playerID,
Reason: parameters.Reason,
BannedUntil: parameters.BannedUntil,
}},
})
if err != nil {
return nil, err
}
return &players[0], nil
}
func (h *PlayerHandle) Unban(ctx context.Context, idempotencyKey string) (*Player, error) {
players, err := h.players.Unban(ctx, UnbanPlayersParameters{
IdempotencyKey: idempotencyKey,
PlayerIDs: []string{h.playerID},
})
if err != nil {
return nil, err
}
return &players[0], nil
}
func (h *PlayerHandle) Delete(ctx context.Context, idempotencyKey string) error {
return h.players.Delete(ctx, DeletePlayersParameters{
IdempotencyKey: idempotencyKey,
PlayerIDs: []string{h.playerID},
})
}
+35
View File
@@ -0,0 +1,35 @@
package rudder
import "context"
type PlayerInventoryHandle struct {
playerID string
inventory *InventoryService
}
type AdjustPlayerInventoryParameters struct {
IdempotencyKey string
Slug string
Amount int64
Reason string
}
func (h *PlayerInventoryHandle) Adjust(ctx context.Context, parameters AdjustPlayerInventoryParameters) (*InventoryItem, error) {
items, err := h.inventory.Adjust(ctx, AdjustInventoryParameters{
IdempotencyKey: parameters.IdempotencyKey,
Items: []InventoryAdjustment{{
PlayerID: h.playerID,
Slug: parameters.Slug,
Amount: parameters.Amount,
Reason: parameters.Reason,
}},
})
if err != nil {
return nil, err
}
return &items[0], nil
}
func (h *PlayerInventoryHandle) List(ctx context.Context, parameters ListInventoryParameters) (*InventoryList, error) {
return h.inventory.List(ctx, h.playerID, parameters)
}
+49
View File
@@ -0,0 +1,49 @@
package rudder
import (
"context"
"net/http"
"net/url"
"hub.rudder.build/rudder/rudder-go-sdk/v2/models"
)
type PlayerQuestsHandle struct {
playerID string
transport *transport
quests *QuestsService
}
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
}
return response.Quests, nil
}
type PlayerQuestParameters struct {
IdempotencyKey string
QuestSlug string
}
func (h *PlayerQuestsHandle) Reset(ctx context.Context, parameters PlayerQuestParameters) error {
return h.quests.Reset(ctx, PlayerQuestsParameters{
IdempotencyKey: parameters.IdempotencyKey,
Items: []PlayerQuestRef{{PlayerID: h.playerID, QuestSlug: parameters.QuestSlug}},
})
}
func (h *PlayerQuestsHandle) ForceComplete(ctx context.Context, parameters PlayerQuestParameters) error {
return h.quests.ForceComplete(ctx, PlayerQuestsParameters{
IdempotencyKey: parameters.IdempotencyKey,
Items: []PlayerQuestRef{{PlayerID: h.playerID, QuestSlug: parameters.QuestSlug}},
})
}
func (h *PlayerQuestsHandle) ForceClaim(ctx context.Context, parameters PlayerQuestParameters) error {
return h.quests.ForceClaim(ctx, PlayerQuestsParameters{
IdempotencyKey: parameters.IdempotencyKey,
Items: []PlayerQuestRef{{PlayerID: h.playerID, QuestSlug: parameters.QuestSlug}},
})
}
+63
View File
@@ -0,0 +1,63 @@
package rudder
import (
"context"
"net/http"
"net/url"
"hub.rudder.build/rudder/rudder-go-sdk/v2/models"
)
type PlayerStorageHandle struct {
playerID string
transport *transport
}
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
}
return &response, nil
}
type UpsertPlayerStorageParameters struct {
IdempotencyKey string
Type string
Data string
ExpiresAt string
}
func (h *PlayerStorageHandle) Upsert(ctx context.Context, parameters UpsertPlayerStorageParameters) (*PlayerStorage, error) {
body := models.BatchUpsertPlayerStoragesRequest{
IdempotencyKey: parameters.IdempotencyKey,
Items: []models.BatchUpsertPlayerStoragesItem{{
PlayerID: h.playerID,
Type: parameters.Type,
Data: parameters.Data,
ExpiresAt: parameters.ExpiresAt,
}},
}
var response models.BatchUpsertPlayerStoragesResponse
if err := h.transport.do(ctx, http.MethodPut, "/players/storage", nil, body, &response); err != nil {
return nil, err
}
return &response.Storages[0], nil
}
type DeletePlayerStorageParameters struct {
IdempotencyKey string
Type string
}
func (h *PlayerStorageHandle) Delete(ctx context.Context, parameters DeletePlayerStorageParameters) error {
body := models.BatchDeletePlayerStoragesRequest{
IdempotencyKey: parameters.IdempotencyKey,
Items: []models.BatchDeletePlayerStoragesItem{{
PlayerID: h.playerID,
Type: parameters.Type,
}},
}
return h.transport.do(ctx, http.MethodDelete, "/players/storage", nil, body, nil)
}
+35
View File
@@ -0,0 +1,35 @@
package rudder
import "context"
type PlayerWalletHandle struct {
playerID string
wallet *WalletService
}
type AdjustPlayerWalletParameters struct {
IdempotencyKey string
CurrencyCode string
Amount int64
Reason string
}
func (h *PlayerWalletHandle) Adjust(ctx context.Context, parameters AdjustPlayerWalletParameters) (*WalletBalance, error) {
wallets, err := h.wallet.Adjust(ctx, AdjustWalletParameters{
IdempotencyKey: parameters.IdempotencyKey,
Items: []WalletAdjustment{{
PlayerID: h.playerID,
CurrencyCode: parameters.CurrencyCode,
Amount: parameters.Amount,
Reason: parameters.Reason,
}},
})
if err != nil {
return nil, err
}
return &wallets[0], nil
}
func (h *PlayerWalletHandle) History(ctx context.Context, parameters WalletHistoryParameters) (*WalletHistory, error) {
return h.wallet.History(ctx, h.playerID, parameters)
}
+94 -5
View File
@@ -6,7 +6,7 @@ import (
"net/url" "net/url"
"strconv" "strconv"
"hub.rudder.build/rudder/rudder-go-sdk/models" "hub.rudder.build/rudder/rudder-go-sdk/v2/models"
) )
type PlayersService struct { type PlayersService struct {
@@ -16,9 +16,10 @@ type PlayersService struct {
type ListPlayersParameters struct { type ListPlayersParameters struct {
Limit int Limit int
Offset int Offset int
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{} query := url.Values{}
if parameters.Limit > 0 { if parameters.Limit > 0 {
query.Set("limit", strconv.Itoa(parameters.Limit)) query.Set("limit", strconv.Itoa(parameters.Limit))
@@ -26,18 +27,106 @@ func (s *PlayersService) List(ctx context.Context, parameters ListPlayersParamet
if parameters.Offset > 0 { if parameters.Offset > 0 {
query.Set("offset", strconv.Itoa(parameters.Offset)) query.Set("offset", strconv.Itoa(parameters.Offset))
} }
if parameters.Search != "" {
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 { if err := s.transport.do(ctx, http.MethodGet, "/players/list", query, nil, &response); err != nil {
return nil, err return nil, err
} }
return &response, nil return &response, nil
} }
func (s *PlayersService) Get(ctx context.Context, playerID string) (*models.AdminPlayerDetails, error) { func (s *PlayersService) Get(ctx context.Context, playerID string) (*PlayerDetails, error) {
var response models.AdminPlayerDetails var response PlayerDetails
if err := s.transport.do(ctx, http.MethodGet, "/players/"+url.PathEscape(playerID), nil, nil, &response); err != nil { if err := s.transport.do(ctx, http.MethodGet, "/players/"+url.PathEscape(playerID), nil, nil, &response); err != nil {
return nil, err return nil, err
} }
return &response, nil 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 []PlayerUpdate
}
func (s *PlayersService) Update(ctx context.Context, parameters UpdatePlayersParameters) ([]Player, error) {
body := models.BatchUpdatePlayersRequest{
IdempotencyKey: parameters.IdempotencyKey,
Items: parameters.Items,
}
var response models.BatchPlayersResponse
if err := s.transport.do(ctx, http.MethodPatch, "/players", nil, body, &response); err != nil {
return nil, err
}
return response.Players, nil
}
type BanPlayersParameters struct {
IdempotencyKey string
Items []PlayerBan
}
func (s *PlayersService) Ban(ctx context.Context, parameters BanPlayersParameters) ([]Player, error) {
body := models.BatchBanPlayersRequest{
IdempotencyKey: parameters.IdempotencyKey,
Items: parameters.Items,
}
var response models.BatchPlayersResponse
if err := s.transport.do(ctx, http.MethodPost, "/players/ban", nil, body, &response); err != nil {
return nil, err
}
return response.Players, nil
}
type UnbanPlayersParameters struct {
IdempotencyKey string
PlayerIDs []string
}
func (s *PlayersService) Unban(ctx context.Context, parameters UnbanPlayersParameters) ([]Player, error) {
body := models.BatchUnbanPlayersRequest{
IdempotencyKey: parameters.IdempotencyKey,
Items: batchPlayerItems(parameters.PlayerIDs),
}
var response models.BatchPlayersResponse
if err := s.transport.do(ctx, http.MethodPost, "/players/unban", nil, body, &response); err != nil {
return nil, err
}
return response.Players, nil
}
type DeletePlayersParameters struct {
IdempotencyKey string
PlayerIDs []string
}
func (s *PlayersService) Delete(ctx context.Context, parameters DeletePlayersParameters) error {
body := models.BatchDeletePlayersRequest{
IdempotencyKey: parameters.IdempotencyKey,
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
}
+64
View File
@@ -0,0 +1,64 @@
package rudder
import (
"context"
"net/http"
"net/url"
"strconv"
"hub.rudder.build/rudder/rudder-go-sdk/v2/models"
)
type QuestsService struct {
transport *transport
}
type ListQuestsParameters struct {
Status *string
Limit int
Cursor *string
}
func (s *QuestsService) List(ctx context.Context, parameters ListQuestsParameters) (*QuestList, error) {
query := url.Values{}
if parameters.Status != nil {
query.Set("status", *parameters.Status)
}
if parameters.Limit > 0 {
query.Set("limit", strconv.Itoa(parameters.Limit))
}
if parameters.Cursor != nil {
query.Set("cursor", *parameters.Cursor)
}
var response QuestList
if err := s.transport.do(ctx, http.MethodGet, "/quests", query, nil, &response); err != nil {
return nil, err
}
return &response, nil
}
type PlayerQuestsParameters struct {
IdempotencyKey string
Items []PlayerQuestRef
}
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, 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}`.
+73
View File
@@ -0,0 +1,73 @@
package rudder
import (
"context"
"net/http"
"net/url"
"strconv"
"hub.rudder.build/rudder/rudder-go-sdk/v2/models"
)
type StorageService struct {
transport *transport
}
type ListStorageParameters struct {
Limit int
Cursor string
Search string
}
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))
}
if parameters.Cursor != "" {
query.Set("cursor", parameters.Cursor)
}
if parameters.Search != "" {
query.Set("search", parameters.Search)
}
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) (*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
}
return &response.Item, nil
}
type UpsertStorageParameters struct {
IdempotencyKey string
Items []ProjectStorageInput
}
func (s *StorageService) Upsert(ctx context.Context, parameters UpsertStorageParameters) error {
body := models.BatchUpsertProjectStorageRequest{
IdempotencyKey: parameters.IdempotencyKey,
Items: parameters.Items,
}
return s.transport.do(ctx, http.MethodPut, "/project-storage", nil, body, nil)
}
type DeleteStorageParameters struct {
IdempotencyKey string
Types []string
}
func (s *StorageService) Delete(ctx context.Context, parameters DeleteStorageParameters) error {
body := models.BatchDeleteProjectStorageRequest{
IdempotencyKey: parameters.IdempotencyKey,
Types: parameters.Types,
}
return s.transport.do(ctx, http.MethodDelete, "/project-storage", nil, body, nil)
}
+1 -2
View File
@@ -14,12 +14,11 @@ import (
type transport struct { type transport struct {
baseURL string baseURL string
apiKey string apiKey string
projectID string
httpClient *http.Client httpClient *http.Client
} }
func (t *transport) do(ctx context.Context, method, path string, query url.Values, body any, out any) error { func (t *transport) do(ctx context.Context, method, path string, query url.Values, body any, out any) error {
endpoint := strings.TrimSuffix(t.baseURL, "/") + "/game/v1/projects/" + t.projectID + path endpoint := strings.TrimSuffix(t.baseURL, "/") + "/game/v1" + path
if len(query) > 0 { if len(query) > 0 {
endpoint += "?" + query.Encode() endpoint += "?" + query.Encode()
} }
+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
-72
View File
@@ -1,72 +0,0 @@
package rudder
import (
"context"
"encoding/json"
"net/http"
"net/url"
"strconv"
"hub.rudder.build/rudder/rudder-go-sdk/models"
)
type UgcService struct {
transport *transport
}
type ListUgcParameters struct {
Status string
Limit int
Cursor string
}
func (s *UgcService) List(ctx context.Context, parameters ListUgcParameters) (*models.ListUgcResponse, error) {
query := url.Values{}
if parameters.Status != "" {
query.Set("status", parameters.Status)
}
if parameters.Limit > 0 {
query.Set("limit", strconv.Itoa(parameters.Limit))
}
if parameters.Cursor != "" {
query.Set("cursor", parameters.Cursor)
}
var response models.ListUgcResponse
if err := s.transport.do(ctx, http.MethodGet, "/ugc", query, nil, &response); err != nil {
return nil, err
}
return &response, nil
}
func (s *UgcService) UpdateMetadata(ctx context.Context, id string, metadata json.RawMessage) (*models.UgcSubmission, error) {
body := models.UpdateUgcMetadataRequest{
ID: id,
Metadata: metadata,
}
var response models.UgcSubmission
if err := s.transport.do(ctx, http.MethodPatch, "/ugc/"+url.PathEscape(id)+"/metadata", nil, body, &response); err != nil {
return nil, err
}
return &response, nil
}
type ReviewUgcParameters struct {
Status string
ReviewedBy string
}
func (s *UgcService) Review(ctx context.Context, id string, parameters ReviewUgcParameters) (*models.UgcSubmission, error) {
body := models.ReviewUgcRequest{
ID: id,
Status: parameters.Status,
ReviewedBy: parameters.ReviewedBy,
}
var response models.UgcSubmission
if err := s.transport.do(ctx, http.MethodPost, "/ugc/"+url.PathEscape(id)+"/review", nil, body, &response); err != nil {
return nil, err
}
return &response, nil
}
+35 -12
View File
@@ -4,8 +4,9 @@ import (
"context" "context"
"net/http" "net/http"
"net/url" "net/url"
"strconv"
"hub.rudder.build/rudder/rudder-go-sdk/models" "hub.rudder.build/rudder/rudder-go-sdk/v2/models"
) )
type WalletService struct { type WalletService struct {
@@ -13,21 +14,43 @@ type WalletService struct {
} }
type AdjustWalletParameters struct { type AdjustWalletParameters struct {
CurrencyCode string IdempotencyKey string
Amount int64 Items []WalletAdjustment
Reason string
} }
func (s *WalletService) Adjust(ctx context.Context, playerID string, parameters AdjustWalletParameters) (*models.WalletDetails, error) { func (s *WalletService) Adjust(ctx context.Context, parameters AdjustWalletParameters) ([]WalletBalance, error) {
body := models.AdjustPlayerWalletRequest{ body := models.BatchAdjustPlayerWalletsRequest{
PlayerID: playerID, IdempotencyKey: parameters.IdempotencyKey,
CurrencyCode: parameters.CurrencyCode, Items: parameters.Items,
Amount: parameters.Amount,
Reason: parameters.Reason,
} }
var response models.WalletDetails var response models.BatchAdjustPlayerWalletsResponse
if err := s.transport.do(ctx, http.MethodPost, "/players/"+url.PathEscape(playerID)+"/wallet/adjust", nil, body, &response); err != nil { if err := s.transport.do(ctx, http.MethodPost, "/players/wallet/adjust", nil, body, &response); err != nil {
return nil, err
}
return response.Wallets, nil
}
type WalletHistoryParameters struct {
Currency *string
Limit int
Cursor *string
}
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)
}
if parameters.Limit > 0 {
query.Set("limit", strconv.Itoa(parameters.Limit))
}
if parameters.Cursor != nil {
query.Set("cursor", *parameters.Cursor)
}
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 return nil, err
} }
return &response, nil return &response, nil
+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)
}