Skip to content

Commit 3cec2b5

Browse files
feat: add ipfs upload and download commands (#3)
1 parent 677ace5 commit 3cec2b5

File tree

2 files changed

+159
-0
lines changed

2 files changed

+159
-0
lines changed

cmd/ipfs/ipfs.go

Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
package ipfs
2+
3+
import (
4+
"fmt"
5+
"io"
6+
"mime/multipart"
7+
"net/http"
8+
"os"
9+
"path/filepath"
10+
11+
"github.com/spf13/cobra"
12+
)
13+
14+
var (
15+
nodeURL string
16+
)
17+
18+
var IPFSCmd = &cobra.Command{
19+
Use: "ipfs",
20+
Short: "Interact with IPFS",
21+
}
22+
23+
func init() {
24+
IPFSCmd.PersistentFlags().StringVarP(&nodeURL, "node", "n", "https://ipfs-gateway.v8-bellecour.iex.ec", "IPFS node API URL")
25+
IPFSCmd.AddCommand(ipfsUploadCommand())
26+
IPFSCmd.AddCommand(ipfsDownloadCommand())
27+
}
28+
29+
func ipfsUploadCommand() *cobra.Command {
30+
var filePath string
31+
32+
uploadCmd := &cobra.Command{
33+
Use: "upload",
34+
Short: "Upload a local file to IPFS",
35+
RunE: func(cmd *cobra.Command, args []string) error {
36+
if filePath == "" {
37+
return fmt.Errorf("❌ you must provide a file path with --file")
38+
}
39+
40+
fmt.Printf("📁 File: %s\n🔗 Node: %s\n", filePath, nodeURL)
41+
42+
file, err := os.Open(filePath)
43+
if err != nil {
44+
return fmt.Errorf("cannot open file: %w", err)
45+
}
46+
defer file.Close()
47+
48+
pr, pw := io.Pipe()
49+
multipartWriter := multipart.NewWriter(pw)
50+
51+
errChan := make(chan error, 1)
52+
53+
go func() {
54+
defer pw.Close()
55+
defer multipartWriter.Close()
56+
57+
part, errCreateFormFile := multipartWriter.CreateFormFile("file", filepath.Base(file.Name()))
58+
if errCreateFormFile != nil {
59+
errChan <- fmt.Errorf("failed to create form file: %w", errCreateFormFile)
60+
return
61+
}
62+
63+
if _, errCopyFile := io.Copy(part, file); errCopyFile != nil {
64+
errChan <- fmt.Errorf("failed to copy file data: %w", errCopyFile)
65+
return
66+
}
67+
68+
errChan <- nil
69+
}()
70+
71+
req, err := http.NewRequest("POST", fmt.Sprintf("%s/api/v0/add", nodeURL), pr)
72+
if err != nil {
73+
return fmt.Errorf("failed to create request: %w", err)
74+
}
75+
req.Header.Set("Content-Type", multipartWriter.FormDataContentType())
76+
77+
client := &http.Client{}
78+
resp, err := client.Do(req)
79+
if err != nil {
80+
return fmt.Errorf("failed to send request: %w", err)
81+
}
82+
defer resp.Body.Close()
83+
84+
if goroutineErr := <-errChan; goroutineErr != nil {
85+
return goroutineErr
86+
}
87+
88+
body, _ := io.ReadAll(resp.Body)
89+
if resp.StatusCode != http.StatusOK {
90+
return fmt.Errorf("IPFS upload failed: %s", string(body))
91+
}
92+
93+
fmt.Println("✅ IPFS Response:", string(body))
94+
return nil
95+
},
96+
}
97+
98+
uploadCmd.Flags().StringVarP(&filePath, "file", "f", "", "Path to the local file to upload")
99+
100+
return uploadCmd
101+
}
102+
103+
func ipfsDownloadCommand() *cobra.Command {
104+
var cid string
105+
var output string
106+
107+
downloadCmd := &cobra.Command{
108+
Use: "download",
109+
Short: "Download a file from IPFS using its CID",
110+
RunE: func(cmd *cobra.Command, args []string) error {
111+
if cid == "" {
112+
return fmt.Errorf("❌ you must provide a CID with --cid")
113+
}
114+
if output == "" {
115+
output = cid
116+
}
117+
118+
url := fmt.Sprintf("%s/ipfs/%s", nodeURL, cid)
119+
120+
fmt.Printf("🔗 Downloading from: %s\n📁 Saving to: %s\n", url, output)
121+
122+
req, err := http.NewRequest(http.MethodGet, url, nil)
123+
if err != nil {
124+
return fmt.Errorf("failed to create HTTP request: %w", err)
125+
}
126+
127+
resp, err := http.DefaultClient.Do(req)
128+
if err != nil {
129+
return fmt.Errorf("failed to download from IPFS: %w", err)
130+
}
131+
defer resp.Body.Close()
132+
133+
if resp.StatusCode != http.StatusOK {
134+
body, _ := io.ReadAll(resp.Body)
135+
return fmt.Errorf("IPFS download failed: %s", string(body))
136+
}
137+
138+
outFile, err := os.Create(output)
139+
if err != nil {
140+
return fmt.Errorf("cannot create output file: %w", err)
141+
}
142+
defer outFile.Close()
143+
144+
if _, err := io.Copy(outFile, resp.Body); err != nil {
145+
return fmt.Errorf("error saving file: %w", err)
146+
}
147+
148+
fmt.Println("✅ Download complete")
149+
return nil
150+
},
151+
}
152+
153+
downloadCmd.Flags().StringVarP(&cid, "cid", "c", "", "CID of the file to download (required)")
154+
downloadCmd.Flags().StringVarP(&output, "out", "o", "", "Output file path (optional)")
155+
156+
return downloadCmd
157+
}

cmd/root.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package cmd
22

33
import (
4+
"github.com/thewhitewizard/web3data-cli/cmd/ipfs"
45
"github.com/thewhitewizard/web3data-cli/cmd/version"
56

67
"github.com/spf13/cobra"
@@ -16,5 +17,6 @@ func Execute() {
1617
}
1718

1819
func init() {
20+
rootCmd.AddCommand(ipfs.IPFSCmd)
1921
rootCmd.AddCommand(version.VersionCmd)
2022
}

0 commit comments

Comments
 (0)