-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathmain.go
258 lines (217 loc) · 7.01 KB
/
main.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
// @title 0G Storage API Sandbox
// @version 1.0
// @description Upload and download files using 0G Storage network. Click "Try it out" on any endpoint to test it.
// @host localhost:8080
// @BasePath /api/v1
// @schemes http https
package main
import (
"context"
"fmt"
"log"
"net/http"
"os"
"path/filepath"
"time"
"github.com/0glabs/0g-storage-client/common/blockchain"
"github.com/0glabs/0g-storage-client/indexer"
"github.com/0glabs/0g-storage-client/transfer"
_ "github.com/0glabs/0g-storage-starter/docs"
"github.com/gin-gonic/gin"
"github.com/joho/godotenv"
"github.com/openweb3/web3go"
swaggerFiles "github.com/swaggo/files"
ginSwagger "github.com/swaggo/gin-swagger"
)
// Network configuration for 0G Testnet
const (
EvmRPC = "https://evmrpc-testnet.0g.ai"
IndexerRPCStandard = "https://indexer-storage-testnet-standard.0g.ai"
IndexerRPCTurbo = "https://indexer-storage-testnet-turbo.0g.ai"
DefaultReplicas = 1
)
type StorageClient struct {
web3Client *web3go.Client
indexerClient *indexer.Client
ctx context.Context
}
type UploadResponse struct {
RootHash string `json:"root_hash"`
TxHash string `json:"tx_hash"`
}
// @Summary Upload a file to 0G Storage
// @Description Upload a file to 0G Storage network
// @Accept multipart/form-data
// @Produce json
// @Param file formData file true "File to upload"
// @Success 200 {object} UploadResponse
// @Router /upload [post]
func (s *Server) handleUpload(c *gin.Context) {
file, err := c.FormFile("file")
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "No file provided"})
return
}
// Create temp file
tempFile := filepath.Join(os.TempDir(), file.Filename)
if err := c.SaveUploadedFile(file, tempFile); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to save file"})
return
}
defer os.Remove(tempFile)
// Upload to 0G Storage
txHash, rootHash, err := s.client.UploadFile(tempFile)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, UploadResponse{
RootHash: rootHash,
TxHash: txHash,
})
}
// @Summary Download a file from 0G Storage
// @Description Download a file using its root hash
// @Produce octet-stream
// @Param root_hash path string true "Root hash of the file"
// @Success 200 {file} binary
// @Router /download/{root_hash} [get]
func (s *Server) handleDownload(c *gin.Context) {
rootHash := c.Param("root_hash")
if rootHash == "" {
c.JSON(http.StatusBadRequest, gin.H{"error": "Root hash is required"})
return
}
tempFile := filepath.Join(os.TempDir(), rootHash)
defer os.Remove(tempFile)
if err := s.client.DownloadFile(rootHash, tempFile); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.File(tempFile)
}
type Server struct {
client *StorageClient
}
func NewStorageClient(ctx context.Context, privateKey string, useTurbo bool) (*StorageClient, error) {
web3Client := blockchain.MustNewWeb3(EvmRPC, privateKey)
indexerRPC := IndexerRPCStandard
if useTurbo {
indexerRPC = IndexerRPCTurbo
}
indexerClient, err := indexer.NewClient(indexerRPC)
if err != nil {
web3Client.Close()
return nil, fmt.Errorf("failed to create indexer client: %v", err)
}
return &StorageClient{
web3Client: web3Client,
indexerClient: indexerClient,
ctx: ctx,
}, nil
}
func (c *StorageClient) Close() {
if c.web3Client != nil {
c.web3Client.Close()
}
}
func (c *StorageClient) UploadFile(filePath string) (string, string, error) {
nodes, err := c.indexerClient.SelectNodes(c.ctx, 1, DefaultReplicas, nil)
if err != nil {
return "", "", fmt.Errorf("failed to select storage nodes: %v", err)
}
uploader, err := transfer.NewUploader(c.ctx, c.web3Client, nodes)
if err != nil {
return "", "", fmt.Errorf("failed to create uploader: %v", err)
}
ctx, cancel := context.WithTimeout(c.ctx, 5*time.Minute)
defer cancel()
txHash, rootHash, err := uploader.UploadFile(ctx, filePath)
if err != nil {
return "", "", fmt.Errorf("upload failed: %v", err)
}
return txHash.String(), rootHash.String(), nil
}
func (c *StorageClient) DownloadFile(rootHash, outputPath string) error {
nodes, err := c.indexerClient.SelectNodes(c.ctx, 1, DefaultReplicas, nil)
if err != nil {
return fmt.Errorf("failed to select storage nodes: %v", err)
}
downloader, err := transfer.NewDownloader(nodes)
if err != nil {
return fmt.Errorf("failed to create downloader: %v", err)
}
if err := os.MkdirAll(filepath.Dir(outputPath), 0755); err != nil {
return fmt.Errorf("failed to create output directory: %v", err)
}
ctx, cancel := context.WithTimeout(c.ctx, 5*time.Minute)
defer cancel()
if err := downloader.Download(ctx, rootHash, outputPath, true); err != nil {
return fmt.Errorf("download failed: %v", err)
}
return nil
}
func main() {
// Load .env file
if err := godotenv.Load(); err != nil {
log.Println("⚠️ No .env file found. Please create one with your PRIVATE_KEY")
log.Println("📝 Example .env file content:")
log.Println("PRIVATE_KEY=your_private_key_here")
}
privateKey := os.Getenv("PRIVATE_KEY")
if privateKey == "" {
log.Fatal("❌ PRIVATE_KEY environment variable is required. Please add it to .env file")
}
ctx := context.Background()
client, err := NewStorageClient(ctx, privateKey, false)
if err != nil {
log.Fatalf("Failed to initialize storage client: %v", err)
}
defer client.Close()
server := &Server{client: client}
// Initialize Gin router
gin.SetMode(gin.ReleaseMode)
r := gin.New()
r.Use(gin.Recovery())
r.Use(gin.LoggerWithConfig(gin.LoggerConfig{
SkipPaths: []string{"/swagger/*"},
}))
// CORS middleware for CodeSandbox
r.Use(func(c *gin.Context) {
c.Writer.Header().Set("Access-Control-Allow-Origin", "*")
c.Writer.Header().Set("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE")
c.Writer.Header().Set("Access-Control-Allow-Headers", "Accept, Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization")
if c.Request.Method == "OPTIONS" {
c.AbortWithStatus(204)
return
}
c.Next()
})
v1 := r.Group("/api/v1")
{
v1.POST("/upload", server.handleUpload)
v1.GET("/download/:root_hash", server.handleDownload)
}
// Swagger documentation endpoint with custom config
r.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler, ginSwagger.URL("/swagger/doc.json")))
// Add welcome page with better instructions
r.GET("/", func(c *gin.Context) {
c.Header("Content-Type", "text/html")
c.String(http.StatusOK, `
<html>
<head>
<title>0G Storage API Sandbox</title>
<meta http-equiv="refresh" content="0;url=/swagger/index.html">
</head>
<body>
<p>Redirecting to Swagger UI...</p>
</body>
</html>
`)
})
port := ":8080"
log.Printf("🚀 Server starting on http://localhost%s", port)
log.Printf("📚 API Documentation: http://localhost%s/swagger/index.html", port)
log.Printf("💡 Tip: Click 'Open in New Window' in the browser preview to use Swagger UI")
log.Fatal(r.Run(port))
}