synor/sdk/go/hosting/client.go
Gulshan Yadav 74b82d2bb2 Add Synor Storage and Wallet SDKs for Swift
- Implement SynorStorage class for decentralized storage operations including upload, download, pinning, and CAR file management.
- Create supporting types and models for storage operations such as UploadOptions, Pin, and StorageConfig.
- Implement SynorWallet class for wallet operations including wallet creation, address generation, transaction signing, and balance queries.
- Create supporting types and models for wallet operations such as Wallet, Address, and Transaction.
- Introduce error handling for both storage and wallet operations.
2026-01-27 01:56:45 +05:30

470 lines
12 KiB
Go

package hosting
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"time"
)
// Client is the Synor Hosting SDK client
type Client struct {
config Config
httpClient *http.Client
closed bool
}
// New creates a new Hosting client
func New(config Config) *Client {
if config.Endpoint == "" {
config.Endpoint = "https://hosting.synor.io/v1"
}
if config.Timeout == 0 {
config.Timeout = 60 * time.Second
}
if config.Retries == 0 {
config.Retries = 3
}
return &Client{
config: config,
httpClient: &http.Client{
Timeout: config.Timeout,
},
}
}
// ==================== Domain Operations ====================
// CheckAvailability checks domain availability
func (c *Client) CheckAvailability(ctx context.Context, name string) (*DomainAvailability, error) {
var result DomainAvailability
err := c.request(ctx, "GET", "/domains/check/"+url.PathEscape(name), nil, &result)
if err != nil {
return nil, err
}
return &result, nil
}
// RegisterDomain registers a new domain
func (c *Client) RegisterDomain(ctx context.Context, name string, opts *RegisterDomainOptions) (*Domain, error) {
body := map[string]interface{}{
"name": name,
}
if opts != nil {
if opts.Years > 0 {
body["years"] = opts.Years
}
body["auto_renew"] = opts.AutoRenew
if opts.Records != nil {
body["records"] = opts.Records
}
}
var result Domain
err := c.request(ctx, "POST", "/domains", body, &result)
if err != nil {
return nil, err
}
return &result, nil
}
// GetDomain gets domain information
func (c *Client) GetDomain(ctx context.Context, name string) (*Domain, error) {
var result Domain
err := c.request(ctx, "GET", "/domains/"+url.PathEscape(name), nil, &result)
if err != nil {
return nil, err
}
return &result, nil
}
// ListDomains lists all domains
func (c *Client) ListDomains(ctx context.Context) ([]Domain, error) {
var result struct {
Domains []Domain `json:"domains"`
}
err := c.request(ctx, "GET", "/domains", nil, &result)
if err != nil {
return nil, err
}
return result.Domains, nil
}
// UpdateDomainRecord updates domain record
func (c *Client) UpdateDomainRecord(ctx context.Context, name string, record *DomainRecord) (*Domain, error) {
var result Domain
err := c.request(ctx, "PUT", "/domains/"+url.PathEscape(name)+"/record", record, &result)
if err != nil {
return nil, err
}
return &result, nil
}
// ResolveDomain resolves a domain
func (c *Client) ResolveDomain(ctx context.Context, name string) (*DomainRecord, error) {
var result DomainRecord
err := c.request(ctx, "GET", "/domains/"+url.PathEscape(name)+"/resolve", nil, &result)
if err != nil {
return nil, err
}
return &result, nil
}
// RenewDomain renews a domain
func (c *Client) RenewDomain(ctx context.Context, name string, years int) (*Domain, error) {
var result Domain
err := c.request(ctx, "POST", "/domains/"+url.PathEscape(name)+"/renew", map[string]interface{}{"years": years}, &result)
if err != nil {
return nil, err
}
return &result, nil
}
// ==================== DNS Operations ====================
// GetDnsZone gets DNS zone for a domain
func (c *Client) GetDnsZone(ctx context.Context, domain string) (*DnsZone, error) {
var result DnsZone
err := c.request(ctx, "GET", "/dns/"+url.PathEscape(domain), nil, &result)
if err != nil {
return nil, err
}
return &result, nil
}
// SetDnsRecords sets DNS records for a domain
func (c *Client) SetDnsRecords(ctx context.Context, domain string, records []DnsRecord) (*DnsZone, error) {
var result DnsZone
err := c.request(ctx, "PUT", "/dns/"+url.PathEscape(domain), map[string]interface{}{"records": records}, &result)
if err != nil {
return nil, err
}
return &result, nil
}
// AddDnsRecord adds a DNS record
func (c *Client) AddDnsRecord(ctx context.Context, domain string, record DnsRecord) (*DnsZone, error) {
var result DnsZone
err := c.request(ctx, "POST", "/dns/"+url.PathEscape(domain)+"/records", record, &result)
if err != nil {
return nil, err
}
return &result, nil
}
// DeleteDnsRecord deletes a DNS record
func (c *Client) DeleteDnsRecord(ctx context.Context, domain, recordType, name string) (*DnsZone, error) {
var result DnsZone
path := fmt.Sprintf("/dns/%s/records/%s/%s", url.PathEscape(domain), recordType, url.PathEscape(name))
err := c.request(ctx, "DELETE", path, nil, &result)
if err != nil {
return nil, err
}
return &result, nil
}
// ==================== Deployment Operations ====================
// Deploy deploys a site from CID
func (c *Client) Deploy(ctx context.Context, cid string, opts *DeployOptions) (*Deployment, error) {
body := map[string]interface{}{
"cid": cid,
}
if opts != nil {
if opts.Domain != "" {
body["domain"] = opts.Domain
}
if opts.Subdomain != "" {
body["subdomain"] = opts.Subdomain
}
if opts.Headers != nil {
body["headers"] = opts.Headers
}
if opts.Redirects != nil {
body["redirects"] = opts.Redirects
}
body["spa"] = opts.SPA
body["clean_urls"] = opts.CleanURLs
body["trailing_slash"] = opts.TrailingSlash
}
var result Deployment
err := c.request(ctx, "POST", "/deployments", body, &result)
if err != nil {
return nil, err
}
return &result, nil
}
// GetDeployment gets deployment by ID
func (c *Client) GetDeployment(ctx context.Context, id string) (*Deployment, error) {
var result Deployment
err := c.request(ctx, "GET", "/deployments/"+url.PathEscape(id), nil, &result)
if err != nil {
return nil, err
}
return &result, nil
}
// ListDeployments lists deployments
func (c *Client) ListDeployments(ctx context.Context, domain string) ([]Deployment, error) {
path := "/deployments"
if domain != "" {
path += "?domain=" + url.QueryEscape(domain)
}
var result struct {
Deployments []Deployment `json:"deployments"`
}
err := c.request(ctx, "GET", path, nil, &result)
if err != nil {
return nil, err
}
return result.Deployments, nil
}
// Rollback rolls back to a previous deployment
func (c *Client) Rollback(ctx context.Context, domain, deploymentID string) (*Deployment, error) {
var result Deployment
err := c.request(ctx, "POST", "/deployments/"+url.PathEscape(deploymentID)+"/rollback", map[string]interface{}{"domain": domain}, &result)
if err != nil {
return nil, err
}
return &result, nil
}
// DeleteDeployment deletes a deployment
func (c *Client) DeleteDeployment(ctx context.Context, id string) error {
return c.request(ctx, "DELETE", "/deployments/"+url.PathEscape(id), nil, nil)
}
// GetDeploymentStats gets deployment stats
func (c *Client) GetDeploymentStats(ctx context.Context, id, period string) (*DeploymentStats, error) {
path := fmt.Sprintf("/deployments/%s/stats?period=%s", url.PathEscape(id), url.QueryEscape(period))
var result DeploymentStats
err := c.request(ctx, "GET", path, nil, &result)
if err != nil {
return nil, err
}
return &result, nil
}
// ==================== SSL Operations ====================
// ProvisionSSL provisions SSL certificate
func (c *Client) ProvisionSSL(ctx context.Context, domain string, opts *ProvisionSSLOptions) (*Certificate, error) {
body := make(map[string]interface{})
if opts != nil {
body["include_www"] = opts.IncludeWWW
body["auto_renew"] = opts.AutoRenew
}
var result Certificate
err := c.request(ctx, "POST", "/ssl/"+url.PathEscape(domain), body, &result)
if err != nil {
return nil, err
}
return &result, nil
}
// GetCertificate gets certificate status
func (c *Client) GetCertificate(ctx context.Context, domain string) (*Certificate, error) {
var result Certificate
err := c.request(ctx, "GET", "/ssl/"+url.PathEscape(domain), nil, &result)
if err != nil {
return nil, err
}
return &result, nil
}
// RenewCertificate renews SSL certificate
func (c *Client) RenewCertificate(ctx context.Context, domain string) (*Certificate, error) {
var result Certificate
err := c.request(ctx, "POST", "/ssl/"+url.PathEscape(domain)+"/renew", nil, &result)
if err != nil {
return nil, err
}
return &result, nil
}
// DeleteCertificate deletes/revokes SSL certificate
func (c *Client) DeleteCertificate(ctx context.Context, domain string) error {
return c.request(ctx, "DELETE", "/ssl/"+url.PathEscape(domain), nil, nil)
}
// ==================== Site Configuration ====================
// GetSiteConfig gets site configuration
func (c *Client) GetSiteConfig(ctx context.Context, domain string) (*SiteConfig, error) {
var result SiteConfig
err := c.request(ctx, "GET", "/sites/"+url.PathEscape(domain)+"/config", nil, &result)
if err != nil {
return nil, err
}
return &result, nil
}
// UpdateSiteConfig updates site configuration
func (c *Client) UpdateSiteConfig(ctx context.Context, domain string, config map[string]interface{}) (*SiteConfig, error) {
var result SiteConfig
err := c.request(ctx, "PATCH", "/sites/"+url.PathEscape(domain)+"/config", config, &result)
if err != nil {
return nil, err
}
return &result, nil
}
// PurgeCache purges CDN cache
func (c *Client) PurgeCache(ctx context.Context, domain string, paths []string) (int, error) {
body := make(map[string]interface{})
if paths != nil {
body["paths"] = paths
}
var result struct {
Purged int `json:"purged"`
}
err := c.request(ctx, "DELETE", "/sites/"+url.PathEscape(domain)+"/cache", body, &result)
if err != nil {
return 0, err
}
return result.Purged, nil
}
// ==================== Analytics ====================
// GetAnalytics gets site analytics
func (c *Client) GetAnalytics(ctx context.Context, domain string, opts *AnalyticsOptions) (*AnalyticsData, error) {
params := url.Values{}
if opts != nil {
if opts.Period != "" {
params.Set("period", opts.Period)
}
if opts.StartDate != "" {
params.Set("start", opts.StartDate)
}
if opts.EndDate != "" {
params.Set("end", opts.EndDate)
}
}
path := "/sites/" + url.PathEscape(domain) + "/analytics"
if len(params) > 0 {
path += "?" + params.Encode()
}
var result AnalyticsData
err := c.request(ctx, "GET", path, nil, &result)
if err != nil {
return nil, err
}
return &result, nil
}
// ==================== Lifecycle ====================
// Close closes the client
func (c *Client) Close() {
c.closed = true
}
// IsClosed returns whether the client is closed
func (c *Client) IsClosed() bool {
return c.closed
}
// HealthCheck performs a health check
func (c *Client) HealthCheck(ctx context.Context) bool {
var result struct {
Status string `json:"status"`
}
err := c.request(ctx, "GET", "/health", nil, &result)
return err == nil && result.Status == "healthy"
}
func (c *Client) request(ctx context.Context, method, path string, body interface{}, result interface{}) error {
if c.closed {
return NewHostingError("Client has been closed", "CLIENT_CLOSED", 0)
}
var lastErr error
for attempt := 0; attempt < c.config.Retries; attempt++ {
err := c.doRequest(ctx, method, path, body, result)
if err == nil {
return nil
}
lastErr = err
if c.config.Debug {
fmt.Printf("Attempt %d failed: %v\n", attempt+1, err)
}
if attempt < c.config.Retries-1 {
time.Sleep(time.Duration(attempt+1) * time.Second)
}
}
return lastErr
}
func (c *Client) doRequest(ctx context.Context, method, path string, body interface{}, result interface{}) error {
var bodyReader io.Reader
if body != nil {
jsonBody, err := json.Marshal(body)
if err != nil {
return err
}
bodyReader = bytes.NewReader(jsonBody)
}
req, err := http.NewRequestWithContext(ctx, method, c.config.Endpoint+path, bodyReader)
if err != nil {
return err
}
req.Header.Set("Authorization", "Bearer "+c.config.APIKey)
req.Header.Set("Content-Type", "application/json")
req.Header.Set("X-SDK-Version", "go/0.1.0")
resp, err := c.httpClient.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
respBody, err := io.ReadAll(resp.Body)
if err != nil {
return err
}
if resp.StatusCode >= 400 {
var errResp struct {
Message string `json:"message"`
Error string `json:"error"`
Code string `json:"code"`
}
json.Unmarshal(respBody, &errResp)
msg := errResp.Message
if msg == "" {
msg = errResp.Error
}
if msg == "" {
msg = fmt.Sprintf("HTTP %d", resp.StatusCode)
}
return NewHostingError(msg, errResp.Code, resp.StatusCode)
}
if result != nil && len(respBody) > 0 {
return json.Unmarshal(respBody, result)
}
return nil
}