Initial commit
This commit is contained in:
Generated
+10
@@ -0,0 +1,10 @@
|
|||||||
|
# Default ignored files
|
||||||
|
/shelf/
|
||||||
|
/workspace.xml
|
||||||
|
# Editor-based HTTP Client requests
|
||||||
|
/httpRequests/
|
||||||
|
# Ignored default folder with query files
|
||||||
|
/queries/
|
||||||
|
# Datasource local storage ignored files
|
||||||
|
/dataSources/
|
||||||
|
/dataSources.local.xml
|
||||||
Generated
+11
@@ -0,0 +1,11 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="GoImports">
|
||||||
|
<option name="excludedPackages">
|
||||||
|
<array>
|
||||||
|
<option value="github.com/pkg/errors" />
|
||||||
|
<option value="golang.org/x/net/context" />
|
||||||
|
</array>
|
||||||
|
</option>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
Generated
+9
@@ -0,0 +1,9 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<module type="WEB_MODULE" version="4">
|
||||||
|
<component name="Go" enabled="true" />
|
||||||
|
<component name="NewModuleRootManager">
|
||||||
|
<content url="file://$MODULE_DIR$" />
|
||||||
|
<orderEntry type="inheritedJdk" />
|
||||||
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
|
</component>
|
||||||
|
</module>
|
||||||
Generated
+4
@@ -0,0 +1,4 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="KubernetesApiProvider">{}</component>
|
||||||
|
</project>
|
||||||
Generated
+8
@@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ProjectModuleManager">
|
||||||
|
<modules>
|
||||||
|
<module fileurl="file://$PROJECT_DIR$/.idea/liveops-go-sdk.iml" filepath="$PROJECT_DIR$/.idea/liveops-go-sdk.iml" />
|
||||||
|
</modules>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
Generated
+6
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="VcsDirectoryMappings">
|
||||||
|
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
# liveops-go-sdk
|
||||||
|
|
||||||
|
Серверный Go SDK для LiveOps-платформы Rudder. Покрывает server-admin
|
||||||
|
поверхность `/game/v1` (auth по `X-API-Key`, ключ — `GAME_ADMIN_API_KEY`
|
||||||
|
на gateway).
|
||||||
|
|
||||||
|
## Install
|
||||||
|
|
||||||
|
Модуль раздаётся напрямую с self-hosted Gitea, минуя proxy.golang.org,
|
||||||
|
поэтому Go должен пропускать его мимо checksum database:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
go env -w GOPRIVATE=hub.rudder.build/*
|
||||||
|
go get hub.rudder.build/rudder/rudder-go-sdk@latest
|
||||||
|
```
|
||||||
|
|
||||||
|
Требуется Go 1.25+. Токен не нужен — репозиторий публичный.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```go
|
||||||
|
client, err := rudder.New(rudder.Config{
|
||||||
|
APIKey: os.Getenv("GAME_ADMIN_API_KEY"),
|
||||||
|
ProjectID: "your-project-id",
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
// *rudder.APIError с кодом sdk/invalid-options
|
||||||
|
}
|
||||||
|
|
||||||
|
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",
|
||||||
|
Amount: 100,
|
||||||
|
Reason: "compensation",
|
||||||
|
})
|
||||||
|
metrics, err := client.Metrics.Get(ctx)
|
||||||
|
page, err := client.Ugc.List(ctx, rudder.ListUgcParameters{Status: "pending"})
|
||||||
|
```
|
||||||
|
|
||||||
|
## Ошибки
|
||||||
|
|
||||||
|
```go
|
||||||
|
var apiErr *rudder.APIError
|
||||||
|
if errors.As(err, &apiErr) {
|
||||||
|
apiErr.Status // HTTP status
|
||||||
|
apiErr.Code // машинный код, константы в models.ErrorCode*
|
||||||
|
apiErr.RequestID // корреляция с логами gateway
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Генерация
|
||||||
|
|
||||||
|
`models/` генерируется apigen'ом из `liveops-gateway/openapi.yaml`
|
||||||
|
(`make generate-openapi` в liveops-gateway). Руками не править.
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
package rudder
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const DefaultBaseURL = "https://api.rudder.build"
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
APIKey string
|
||||||
|
ProjectID string
|
||||||
|
BaseURL string
|
||||||
|
Timeout time.Duration
|
||||||
|
HTTPClient *http.Client
|
||||||
|
}
|
||||||
|
|
||||||
|
type Client struct {
|
||||||
|
Players *PlayersService
|
||||||
|
Metrics *MetricsService
|
||||||
|
Leaderboards *LeaderboardsService
|
||||||
|
Wallet *WalletService
|
||||||
|
Ugc *UgcService
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(config Config) (*Client, error) {
|
||||||
|
if config.APIKey == "" {
|
||||||
|
return nil, &APIError{Code: ErrorCodeInvalidOptions, Message: "APIKey is required"}
|
||||||
|
}
|
||||||
|
if config.ProjectID == "" {
|
||||||
|
return nil, &APIError{Code: ErrorCodeInvalidOptions, Message: "ProjectID is required"}
|
||||||
|
}
|
||||||
|
|
||||||
|
if config.BaseURL == "" {
|
||||||
|
config.BaseURL = DefaultBaseURL
|
||||||
|
}
|
||||||
|
if config.Timeout == 0 {
|
||||||
|
config.Timeout = 30 * time.Second
|
||||||
|
}
|
||||||
|
if config.HTTPClient == nil {
|
||||||
|
config.HTTPClient = &http.Client{Timeout: config.Timeout}
|
||||||
|
}
|
||||||
|
|
||||||
|
transport := &transport{
|
||||||
|
baseURL: config.BaseURL,
|
||||||
|
apiKey: config.APIKey,
|
||||||
|
projectID: config.ProjectID,
|
||||||
|
httpClient: config.HTTPClient,
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Client{
|
||||||
|
Players: &PlayersService{transport: transport},
|
||||||
|
Metrics: &MetricsService{transport: transport},
|
||||||
|
Leaderboards: &LeaderboardsService{transport: transport},
|
||||||
|
Wallet: &WalletService{transport: transport},
|
||||||
|
Ugc: &UgcService{transport: transport},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
package rudder
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
const ErrorCodeInvalidOptions = "sdk/invalid-options"
|
||||||
|
|
||||||
|
type APIError struct {
|
||||||
|
Status int
|
||||||
|
Code string
|
||||||
|
Message string
|
||||||
|
RequestID string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *APIError) Error() string {
|
||||||
|
if e.RequestID != "" {
|
||||||
|
return fmt.Sprintf("rudder: status %d, code %q: %s (request %s)", e.Status, e.Code, e.Message, e.RequestID)
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("rudder: status %d, code %q: %s", e.Status, e.Code, e.Message)
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
package rudder
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
|
||||||
|
"hub.rudder.build/rudder/rudder-go-sdk/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
type LeaderboardsService struct {
|
||||||
|
transport *transport
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *LeaderboardsService) Results(ctx context.Context, slug string) ([]models.RankEntry, error) {
|
||||||
|
var response models.GetLeaderboardResultsResponse
|
||||||
|
if err := s.transport.do(ctx, http.MethodGet, "/leaderboards/"+url.PathEscape(slug)+"/results", nil, nil, &response); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return response.Entries, nil
|
||||||
|
}
|
||||||
+20
@@ -0,0 +1,20 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
// Code generated by apigen. DO NOT EDIT.
|
||||||
|
|
||||||
|
package models
|
||||||
|
|
||||||
|
type AdjustPlayerWalletRequest struct {
|
||||||
|
Amount int64 `json:"amount,omitempty"`
|
||||||
|
CurrencyCode string `json:"currencyCode,omitempty"`
|
||||||
|
PlayerID string `json:"playerId,omitempty"`
|
||||||
|
Reason string `json:"reason,omitempty"`
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
// Code generated by apigen. DO NOT EDIT.
|
||||||
|
|
||||||
|
package models
|
||||||
|
|
||||||
|
import "encoding/json"
|
||||||
|
|
||||||
|
type AdminPlayer struct {
|
||||||
|
CreatedAt string `json:"createdAt,omitempty"`
|
||||||
|
Data json.RawMessage `json:"data,omitempty"`
|
||||||
|
ID string `json:"id,omitempty"`
|
||||||
|
Language string `json:"language,omitempty"`
|
||||||
|
LastSeen string `json:"lastSeen,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"`
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
// Code generated by apigen. DO NOT EDIT.
|
||||||
|
|
||||||
|
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"`
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
// Code generated by apigen. DO NOT EDIT.
|
||||||
|
|
||||||
|
package models
|
||||||
|
|
||||||
|
const (
|
||||||
|
ErrorCodeEarlyCompletion = "early_completion"
|
||||||
|
ErrorCodeForbidden = "forbidden"
|
||||||
|
ErrorCodeLevelNotReached = "level_not_reached"
|
||||||
|
ErrorCodeNodeNotActive = "node_not_active"
|
||||||
|
ErrorCodeObjectivesIncomplete = "objectives_incomplete"
|
||||||
|
ErrorCodeRunExpired = "run_expired"
|
||||||
|
ErrorCodeRunNotActive = "run_not_active"
|
||||||
|
ErrorCodeScenarioNotActive = "scenario_not_active"
|
||||||
|
ErrorCodeUnknownRun = "unknown_run"
|
||||||
|
)
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
// Code generated by apigen. DO NOT EDIT.
|
||||||
|
|
||||||
|
package models
|
||||||
|
|
||||||
|
type GetLeaderboardResultsResponse struct {
|
||||||
|
Entries []RankEntry `json:"entries,omitempty"`
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
// Code generated by apigen. DO NOT EDIT.
|
||||||
|
|
||||||
|
package models
|
||||||
|
|
||||||
|
type GetProjectMetricsResponse struct {
|
||||||
|
Dau int `json:"dau,omitempty"`
|
||||||
|
DauChange float64 `json:"dauChange,omitempty"`
|
||||||
|
Mau int `json:"mau,omitempty"`
|
||||||
|
MauChange float64 `json:"mauChange,omitempty"`
|
||||||
|
Retention []RetentionPoint `json:"retention,omitempty"`
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
// Code generated by apigen. DO NOT EDIT.
|
||||||
|
|
||||||
|
package models
|
||||||
|
|
||||||
|
type InventoryItem struct {
|
||||||
|
Amount int64 `json:"amount,omitempty"`
|
||||||
|
ID string `json:"id,omitempty"`
|
||||||
|
Slug string `json:"slug,omitempty"`
|
||||||
|
UpdatedAt string `json:"updatedAt,omitempty"`
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
// Code generated by apigen. DO NOT EDIT.
|
||||||
|
|
||||||
|
package models
|
||||||
|
|
||||||
|
type ListPlayersResponse struct {
|
||||||
|
Limit int `json:"limit,omitempty"`
|
||||||
|
Offset int `json:"offset,omitempty"`
|
||||||
|
Players []AdminPlayer `json:"players,omitempty"`
|
||||||
|
Total int `json:"total,omitempty"`
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
// Code generated by apigen. DO NOT EDIT.
|
||||||
|
|
||||||
|
package models
|
||||||
|
|
||||||
|
type ListUgcResponse struct {
|
||||||
|
Items []UgcSubmission `json:"items,omitempty"`
|
||||||
|
NextCursor string `json:"nextCursor,omitempty"`
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
// Code generated by apigen. DO NOT EDIT.
|
||||||
|
|
||||||
|
package models
|
||||||
|
|
||||||
|
type PlayerStorage struct {
|
||||||
|
Data string `json:"data,omitempty"`
|
||||||
|
ExpiresAt string `json:"expiresAt,omitempty"`
|
||||||
|
ID string `json:"id,omitempty"`
|
||||||
|
PlayerID string `json:"playerId,omitempty"`
|
||||||
|
Type string `json:"type,omitempty"`
|
||||||
|
UpdatedAt string `json:"updatedAt,omitempty"`
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
// Code generated by apigen. DO NOT EDIT.
|
||||||
|
|
||||||
|
package models
|
||||||
|
|
||||||
|
type RankEntry struct {
|
||||||
|
PlayerID string `json:"playerId,omitempty"`
|
||||||
|
PlayerName string `json:"playerName,omitempty"`
|
||||||
|
Rank int64 `json:"rank,omitempty"`
|
||||||
|
Score float64 `json:"score,omitempty"`
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
// Code generated by apigen. DO NOT EDIT.
|
||||||
|
|
||||||
|
package models
|
||||||
|
|
||||||
|
type RetentionPoint struct {
|
||||||
|
Day int `json:"day,omitempty"`
|
||||||
|
Value float64 `json:"value,omitempty"`
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
// 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"`
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
// Code generated by apigen. DO NOT EDIT.
|
||||||
|
|
||||||
|
package models
|
||||||
|
|
||||||
|
type SubmittedBy struct {
|
||||||
|
UserID string `json:"userId,omitempty"`
|
||||||
|
Username string `json:"username,omitempty"`
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
// 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"`
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
// 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"`
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
// Code generated by apigen. DO NOT EDIT.
|
||||||
|
|
||||||
|
package models
|
||||||
|
|
||||||
|
import "encoding/json"
|
||||||
|
|
||||||
|
type WalletAudit struct {
|
||||||
|
Amount int64 `json:"amount,omitempty"`
|
||||||
|
BalanceAfter int64 `json:"balanceAfter,omitempty"`
|
||||||
|
BalanceBefore int64 `json:"balanceBefore,omitempty"`
|
||||||
|
CreatedAt string `json:"createdAt,omitempty"`
|
||||||
|
CurrencyCode string `json:"currencyCode,omitempty"`
|
||||||
|
ID string `json:"id,omitempty"`
|
||||||
|
Metadata json.RawMessage `json:"metadata,omitempty"`
|
||||||
|
PlayerID string `json:"playerId,omitempty"`
|
||||||
|
Reason string `json:"reason,omitempty"`
|
||||||
|
Source string `json:"source,omitempty"`
|
||||||
|
WalletID string `json:"walletId,omitempty"`
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
// Code generated by apigen. DO NOT EDIT.
|
||||||
|
|
||||||
|
package models
|
||||||
|
|
||||||
|
type WalletDetails struct {
|
||||||
|
Balance int64 `json:"balance,omitempty"`
|
||||||
|
CurrencyCode string `json:"currencyCode,omitempty"`
|
||||||
|
ID string `json:"id,omitempty"`
|
||||||
|
PlayerID string `json:"playerId,omitempty"`
|
||||||
|
UpdatedAt string `json:"updatedAt,omitempty"`
|
||||||
|
}
|
||||||
+43
@@ -0,0 +1,43 @@
|
|||||||
|
package rudder
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"hub.rudder.build/rudder/rudder-go-sdk/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
type PlayersService struct {
|
||||||
|
transport *transport
|
||||||
|
}
|
||||||
|
|
||||||
|
type ListPlayersParameters struct {
|
||||||
|
Limit int
|
||||||
|
Offset int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *PlayersService) List(ctx context.Context, parameters ListPlayersParameters) (*models.ListPlayersResponse, error) {
|
||||||
|
query := url.Values{}
|
||||||
|
if parameters.Limit > 0 {
|
||||||
|
query.Set("limit", strconv.Itoa(parameters.Limit))
|
||||||
|
}
|
||||||
|
if parameters.Offset > 0 {
|
||||||
|
query.Set("offset", strconv.Itoa(parameters.Offset))
|
||||||
|
}
|
||||||
|
|
||||||
|
var response models.ListPlayersResponse
|
||||||
|
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
|
||||||
|
if err := s.transport.do(ctx, http.MethodGet, "/players/"+url.PathEscape(playerID), nil, nil, &response); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &response, nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
package rudder
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type transport struct {
|
||||||
|
baseURL string
|
||||||
|
apiKey string
|
||||||
|
projectID string
|
||||||
|
httpClient *http.Client
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
if len(query) > 0 {
|
||||||
|
endpoint += "?" + query.Encode()
|
||||||
|
}
|
||||||
|
|
||||||
|
var reader io.Reader
|
||||||
|
if body != nil {
|
||||||
|
data, err := json.Marshal(body)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("rudder: encode request body: %w", err)
|
||||||
|
}
|
||||||
|
reader = bytes.NewReader(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
req, err := http.NewRequestWithContext(ctx, method, endpoint, reader)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("rudder: build request: %w", err)
|
||||||
|
}
|
||||||
|
req.Header.Set("X-API-Key", t.apiKey)
|
||||||
|
if body != nil {
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := t.httpClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("rudder: %s %s: %w", method, path, err)
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
data, err := io.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("rudder: read response body: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.StatusCode >= 400 {
|
||||||
|
apiErr := &APIError{Status: resp.StatusCode}
|
||||||
|
var payload struct {
|
||||||
|
Error string `json:"error"`
|
||||||
|
Code string `json:"code"`
|
||||||
|
RequestID string `json:"requestId"`
|
||||||
|
}
|
||||||
|
if json.Unmarshal(data, &payload) == nil {
|
||||||
|
apiErr.Code = payload.Code
|
||||||
|
apiErr.Message = payload.Error
|
||||||
|
apiErr.RequestID = payload.RequestID
|
||||||
|
}
|
||||||
|
if apiErr.Message == "" {
|
||||||
|
apiErr.Message = http.StatusText(resp.StatusCode)
|
||||||
|
}
|
||||||
|
return apiErr
|
||||||
|
}
|
||||||
|
|
||||||
|
if out == nil || len(data) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal(data, out); err != nil {
|
||||||
|
return fmt.Errorf("rudder: decode response: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
package rudder
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
|
||||||
|
"hub.rudder.build/rudder/rudder-go-sdk/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
type WalletService struct {
|
||||||
|
transport *transport
|
||||||
|
}
|
||||||
|
|
||||||
|
type AdjustWalletParameters struct {
|
||||||
|
CurrencyCode string
|
||||||
|
Amount int64
|
||||||
|
Reason string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *WalletService) Adjust(ctx context.Context, playerID string, parameters AdjustWalletParameters) (*models.WalletDetails, error) {
|
||||||
|
body := models.AdjustPlayerWalletRequest{
|
||||||
|
PlayerID: playerID,
|
||||||
|
CurrencyCode: parameters.CurrencyCode,
|
||||||
|
Amount: parameters.Amount,
|
||||||
|
Reason: parameters.Reason,
|
||||||
|
}
|
||||||
|
|
||||||
|
var response models.WalletDetails
|
||||||
|
if err := s.transport.do(ctx, http.MethodPost, "/players/"+url.PathEscape(playerID)+"/wallet/adjust", nil, body, &response); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &response, nil
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user