package ipfs import ( "bytes" "context" "fmt" "io" "net/http" "time" shell "github.com/ipfs/go-ipfs-api" ) // Client wraps IPFS shell for easier use across services type Client struct { shell *shell.Shell } // Config holds IPFS client configuration type Config struct { Host string Port string Timeout time.Duration } // NewClient creates a new IPFS client func NewClient(cfg Config) *Client { if cfg.Timeout == 0 { cfg.Timeout = 30 * time.Second } // Create IPFS shell connection sh := shell.NewShell(fmt.Sprintf("%s:%s", cfg.Host, cfg.Port)) sh.SetTimeout(cfg.Timeout) return &Client{ shell: sh, } } // Add uploads data to IPFS and returns the CID func (c *Client) Add(ctx context.Context, data []byte) (string, error) { reader := bytes.NewReader(data) cid, err := c.shell.Add(reader) if err != nil { return "", fmt.Errorf("failed to add to IPFS: %w", err) } return cid, nil } // AddFile uploads a file to IPFS and returns the CID func (c *Client) AddFile(ctx context.Context, reader io.Reader) (string, error) { cid, err := c.shell.Add(reader) if err != nil { return "", fmt.Errorf("failed to add file to IPFS: %w", err) } return cid, nil } // Get retrieves data from IPFS by CID func (c *Client) Get(ctx context.Context, cid string) ([]byte, error) { reader, err := c.shell.Cat(cid) if err != nil { return nil, fmt.Errorf("failed to get from IPFS: %w", err) } defer reader.Close() data, err := io.ReadAll(reader) if err != nil { return nil, fmt.Errorf("failed to read IPFS data: %w", err) } return data, nil } // GetFile retrieves a file from IPFS by CID and returns a reader func (c *Client) GetFile(ctx context.Context, cid string) (io.ReadCloser, error) { reader, err := c.shell.Cat(cid) if err != nil { return nil, fmt.Errorf("failed to get file from IPFS: %w", err) } return reader, nil } // Pin pins a CID to ensure it's not garbage collected func (c *Client) Pin(ctx context.Context, cid string) error { err := c.shell.Pin(cid) if err != nil { return fmt.Errorf("failed to pin CID: %w", err) } return nil } // Unpin removes a pin from a CID func (c *Client) Unpin(ctx context.Context, cid string) error { err := c.shell.Unpin(cid) if err != nil { return fmt.Errorf("failed to unpin CID: %w", err) } return nil } // IsAvailable checks if the IPFS node is reachable func (c *Client) IsAvailable() bool { _, err := c.shell.ID() return err == nil } // GetGatewayURL returns the IPFS gateway URL for a CID func (c *Client) GetGatewayURL(cid string, gateway string) string { if gateway == "" { gateway = "https://ipfs.io" } return fmt.Sprintf("%s/ipfs/%s", gateway, cid) } // AddJSON uploads JSON data to IPFS func (c *Client) AddJSON(ctx context.Context, jsonData []byte) (string, error) { return c.Add(ctx, jsonData) } // VerifyHTTP checks if a CID is accessible via HTTP gateway func (c *Client) VerifyHTTP(cid string, gateway string) error { url := c.GetGatewayURL(cid, gateway) client := &http.Client{ Timeout: 10 * time.Second, } resp, err := client.Head(url) if err != nil { return fmt.Errorf("failed to verify CID via HTTP: %w", err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { return fmt.Errorf("CID not accessible, status: %d", resp.StatusCode) } return nil }