- 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.
470 lines
12 KiB
Go
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
|
|
}
|