From b91da7eb736284db67dd91f3209b65de451856c2 Mon Sep 17 00:00:00 2001 From: vaibhavvvvv Date: Mon, 7 Oct 2024 01:24:41 +0530 Subject: [PATCH 1/3] Walrus File Storage APIs added --- api/v1/v1.go | 3 +- api/v1/walrusFileStorage/walrusFileStorage.go | 119 ++++++++++++++++++ config/dbconfig/dbconfig.go | 5 +- models/walrusFileStorage.go | 35 ++++++ 4 files changed, 159 insertions(+), 3 deletions(-) create mode 100644 api/v1/walrusFileStorage/walrusFileStorage.go create mode 100644 models/walrusFileStorage.go diff --git a/api/v1/v1.go b/api/v1/v1.go index d1b3a42..eddac84 100644 --- a/api/v1/v1.go +++ b/api/v1/v1.go @@ -5,10 +5,10 @@ import ( "github.com/NetSepio/erebrus-gateway/api/v1/client" nodedwifi "github.com/NetSepio/erebrus-gateway/api/v1/nodeDwifi" "github.com/NetSepio/erebrus-gateway/api/v1/nodeOperatorForm" - "github.com/NetSepio/erebrus-gateway/api/v1/nodes" "github.com/NetSepio/erebrus-gateway/api/v1/registerDwifi" "github.com/NetSepio/erebrus-gateway/api/v1/subscription" + "github.com/NetSepio/erebrus-gateway/api/v1/walrusFileStorage" "github.com/gin-gonic/gin" ) @@ -22,5 +22,6 @@ func ApplyRoutes(r *gin.RouterGroup) { nodeOperatorForm.ApplyRoutes(v1) registerDwifi.ApplyRoutes(v1) nodedwifi.ApplyRoutes(v1) + walrusFileStorage.ApplyRoutes(v1) } } diff --git a/api/v1/walrusFileStorage/walrusFileStorage.go b/api/v1/walrusFileStorage/walrusFileStorage.go new file mode 100644 index 0000000..fcdc7e4 --- /dev/null +++ b/api/v1/walrusFileStorage/walrusFileStorage.go @@ -0,0 +1,119 @@ +package walrusFileStorage + +import ( + "net/http" + + "github.com/NetSepio/erebrus-gateway/config/dbconfig" + "github.com/NetSepio/erebrus-gateway/models" + "github.com/gin-gonic/gin" + "gorm.io/gorm" +) + +func ApplyRoutes(r *gin.RouterGroup) { + g := r.Group("/walrusFileStorage") + { + g.POST("", upsertWalrusStorage) + g.DELETE("/:address", deleteWalrusStorage) + g.DELETE("/:address/blob/:blob_id", deleteBlobID) + g.GET("/:address", getAllWalrusStorage) + } +} + +func upsertWalrusStorage(c *gin.Context) { + var walrus models.WalrusStorage + if err := c.ShouldBindJSON(&walrus); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + db := dbconfig.GetDb() + + var existingWalrus models.WalrusStorage + result := db.Where("wallet_address = ?", walrus.WalletAddress).First(&existingWalrus) + + if result.Error == nil { + existingWalrus.FileBlobs = append(existingWalrus.FileBlobs, walrus.FileBlobs...) + if err := db.Save(&existingWalrus).Error; err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update wallet"}) + return + } + c.JSON(http.StatusOK, existingWalrus) + } else if result.Error == gorm.ErrRecordNotFound { + if err := db.Create(&walrus).Error; err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create wallet"}) + return + } + c.JSON(http.StatusCreated, walrus) + } else { + c.JSON(http.StatusInternalServerError, gin.H{"error": "Database error"}) + } +} + +func deleteBlobID(c *gin.Context) { + walletAddress := c.Param("address") + blobID := c.Param("blob_id") + db := dbconfig.GetDb() + + var walrus models.WalrusStorage + if err := db.Where("wallet_address = ?", walletAddress).First(&walrus).Error; err != nil { + c.JSON(http.StatusNotFound, gin.H{"error": "Wallet not found"}) + return + } + + index := -1 + for i, fileObj := range walrus.FileBlobs { + if fileObj.BlobID == blobID { + index = i + break + } + } + + if index == -1 { + c.JSON(http.StatusNotFound, gin.H{"error": "Blob ID not found"}) + return + } + + walrus.FileBlobs = append(walrus.FileBlobs[:index], walrus.FileBlobs[index+1:]...) + + if err := db.Save(&walrus).Error; err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update wallet"}) + return + } + + c.JSON(http.StatusOK, gin.H{"message": "Blob ID removed successfully"}) +} + +func deleteWalrusStorage(c *gin.Context) { + address := c.Param("address") + db := dbconfig.GetDb() + + var walrus models.WalrusStorage + if err := db.Where("wallet_address = ?", address).First(&walrus).Error; err != nil { + c.JSON(http.StatusNotFound, gin.H{"error": "Wallet not found"}) + return + } + + if err := db.Delete(&walrus).Error; err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to delete wallet"}) + return + } + + c.JSON(http.StatusOK, gin.H{"message": "Wallet deleted successfully"}) +} + +func getAllWalrusStorage(c *gin.Context) { + address := c.Param("address") + db := dbconfig.GetDb() + + var walrus models.WalrusStorage + if err := db.Where("wallet_address = ?", address).First(&walrus).Error; err != nil { + if err == gorm.ErrRecordNotFound { + c.JSON(http.StatusNotFound, gin.H{"error": "Wallet not found"}) + } else { + c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to retrieve wallet"}) + } + return + } + + c.JSON(http.StatusOK, gin.H{"file_blobs": walrus.FileBlobs}) +} diff --git a/config/dbconfig/dbconfig.go b/config/dbconfig/dbconfig.go index 1eb00fc..394c697 100644 --- a/config/dbconfig/dbconfig.go +++ b/config/dbconfig/dbconfig.go @@ -4,10 +4,11 @@ import ( "fmt" "os" - "github.com/NetSepio/erebrus-gateway/models" log "github.com/sirupsen/logrus" "gorm.io/gorm" + "github.com/NetSepio/erebrus-gateway/models" + "gorm.io/driver/postgres" ) @@ -51,7 +52,7 @@ func GetDb() *gorm.DB { func DbInit() error { db := GetDb() - if err := db.AutoMigrate(&models.User{}, &models.Erebrus{}, &models.Node{}, &models.Subscription{}, &models.FormData{}, &models.FlowId{}, &models.UserFeedback{}, &models.WifiNode{}, &models.NodeDwifi{}); err != nil { + if err := db.AutoMigrate(&models.User{}, &models.Erebrus{}, &models.Node{}, &models.Subscription{}, &models.FormData{}, &models.FlowId{}, &models.UserFeedback{}, &models.WifiNode{}, &models.NodeDwifi{}, &models.WalrusStorage{}); err != nil { log.Fatal(err) } return nil diff --git a/models/walrusFileStorage.go b/models/walrusFileStorage.go new file mode 100644 index 0000000..9f1ef99 --- /dev/null +++ b/models/walrusFileStorage.go @@ -0,0 +1,35 @@ +package models + +import ( + "database/sql/driver" + "encoding/json" + "fmt" + + "gorm.io/gorm" +) + +type WalrusStorage struct { + gorm.Model + WalletAddress string `json:"wallet_address"` + FileBlobs FileObjectArray `gorm:"type:jsonb" json:"file_blobs"` +} + +type FileObject struct { + Filename string `json:"filename"` + BlobID string `json:"blob_id"` +} + +type FileObjectArray []FileObject + +func (a FileObjectArray) Value() (driver.Value, error) { + return json.Marshal(a) +} + +func (a *FileObjectArray) Scan(value interface{}) error { + b, ok := value.([]byte) + if !ok { + return fmt.Errorf("type assertion to []byte failed") + } + + return json.Unmarshal(b, &a) +} From 58d4ea5ab0543e3bc1ac7326fd3d6c09ee090483 Mon Sep 17 00:00:00 2001 From: Shubham Prajapati Date: Wed, 18 Dec 2024 20:27:14 +0530 Subject: [PATCH 2/3] `Added new endpoint to fetch nodes by status and wallet address` --- api/v1/nodes/nodes.go | 84 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) diff --git a/api/v1/nodes/nodes.go b/api/v1/nodes/nodes.go index da89513..dbf9a26 100644 --- a/api/v1/nodes/nodes.go +++ b/api/v1/nodes/nodes.go @@ -2,6 +2,7 @@ package nodes import ( "encoding/json" + "fmt" "net/http" "github.com/NetSepio/erebrus-gateway/config/dbconfig" @@ -16,6 +17,7 @@ func ApplyRoutes(r *gin.RouterGroup) { { g.GET("/all", FetchAllNodes) g.GET("/:status", FetchAllNodesByStatus) + g.GET("/status_wallet_address/:status/:wallet_address", FetchAllNodesByStatusAndWalletAddress) } } @@ -145,3 +147,85 @@ func FetchAllNodesByStatus(c *gin.Context) { httpo.NewSuccessResponseP(200, "Nodes fetched succesfully", responses).SendD(c) } + +func FetchAllNodesByStatusAndWalletAddress(c *gin.Context) { + status := c.Param("status") // active , inactive + walletAddress := c.Param("wallet_address") // active , inactive + + fmt.Printf("status : %v , wallet address : %v ", status, walletAddress) + + if len(walletAddress) == 0 || walletAddress == "wallet_address" { + logwrapper.Errorf("please pass the wallet address : ", walletAddress) + httpo.NewErrorResponse(http.StatusBadRequest, "Please provide the wallet_address").SendD(c) + return + } + + db := dbconfig.GetDb() + var nodes *[]models.Node + // var node *models.Node + if len(status) == 0 || status == ":status" { + if err := db.Where("wallet_address = ?", walletAddress).Find(&nodes).Error; err != nil { + logwrapper.Errorf("failed to get nodes from DB: %s", err) + httpo.NewErrorResponse(http.StatusInternalServerError, err.Error()).SendD(c) + return + } + } else { + if err := db.Where("wallet_address = ? AND status = ? ", walletAddress, status).Find(&nodes).Error; err != nil { + logwrapper.Errorf("failed to get nodes from DB: %s", err) + httpo.NewErrorResponse(http.StatusInternalServerError, err.Error()).SendD(c) + return + } + } + + // Unmarshal SystemInfo into OSInfo struct + + var responses []models.NodeResponse + var response models.NodeResponse + + for _, i := range *nodes { + var osInfo models.OSInfo + if len(i.SystemInfo) > 0 { + err := json.Unmarshal([]byte(i.SystemInfo), &osInfo) + if err != nil { + logwrapper.Errorf("failed to get nodes from DB OSInfo: %s", err) + // httpo.NewErrorResponse(http.StatusInternalServerError, err.Error()).SendD(c) + } + } + // Unmarshal IpInfo into IPInfo struct + var ipGeoAddress models.IpGeoAddress + if len(i.IpGeoData) > 0 { + err := json.Unmarshal([]byte(i.IpGeoData), &ipGeoAddress) + if err != nil { + logwrapper.Errorf("failed to get nodes from DB IpGeoAddress: %s", err) + // httpo.NewErrorResponse(http.StatusInternalServerError, err.Error()).SendD(c) + } + } + + response.Id = i.PeerId + response.Name = i.Name + response.HttpPort = i.HttpPort + response.Domain = i.Host + response.NodeName = i.Name + response.Address = i.PeerAddress + response.Region = i.Region + response.Status = i.Status + response.DownloadSpeed = i.DownloadSpeed + response.UploadSpeed = i.UploadSpeed + response.StartTimeStamp = i.RegistrationTime + response.LastPingedTimeStamp = i.LastPing + response.Chain = i.Chain + response.WalletAddressSui = i.WalletAddress + response.WalletAddressSolana = i.WalletAddress + response.IpInfoIP = ipGeoAddress.IpInfoIP + response.IpInfoCity = ipGeoAddress.IpInfoCity + response.IpInfoCountry = ipGeoAddress.IpInfoCountry + response.IpInfoLocation = ipGeoAddress.IpInfoLocation + response.IpInfoOrg = ipGeoAddress.IpInfoOrg + response.IpInfoPostal = ipGeoAddress.IpInfoPostal + response.IpInfoTimezone = ipGeoAddress.IpInfoTimezone + + responses = append(responses, response) + } + + httpo.NewSuccessResponseP(200, "Nodes fetched succesfully", responses).SendD(c) +} From 8e537777f326a920a3b89066188ddd672ac97034 Mon Sep 17 00:00:00 2001 From: Shubham Prajapati Date: Wed, 18 Dec 2024 23:36:44 +0530 Subject: [PATCH 3/3] `feat: Added node activity tracking and updated node model to include total and today active duration` `diff: Added nodeactivity package and updated models/node.go, config/dbconfig/dbconfig.go, app/p2p-Node/p2p-node.go --- api/v1/nodes/nodeActivity/nodeActivity.go | 113 ++++++++++++++++++++++ api/v1/nodes/nodes.go | 17 ++++ app/p2p-Node/p2p-node.go | 3 + config/dbconfig/dbconfig.go | 14 ++- models/node.go | 52 ++++++---- 5 files changed, 179 insertions(+), 20 deletions(-) create mode 100644 api/v1/nodes/nodeActivity/nodeActivity.go diff --git a/api/v1/nodes/nodeActivity/nodeActivity.go b/api/v1/nodes/nodeActivity/nodeActivity.go new file mode 100644 index 0000000..7649b07 --- /dev/null +++ b/api/v1/nodes/nodeActivity/nodeActivity.go @@ -0,0 +1,113 @@ +package nodeactivity + +import ( + "errors" + "math" + "time" + + "github.com/NetSepio/erebrus-gateway/config/dbconfig" + "github.com/NetSepio/erebrus-gateway/models" + "github.com/NetSepio/erebrus-gateway/util/pkg/logwrapper" + "gorm.io/gorm" +) + +func CalculateTotalAndTodayActiveDuration(peerID string) (totalDurationHr, todayDurationHr float64) { + db := dbconfig.GetDb() + now := time.Now() + + // Fetch all activities for the given peer + var activities []models.NodeActivity + if err := db.Where("peer_id = ?", peerID).Find(&activities).Error; err != nil { + logwrapper.Errorf("failed to fetch activities for peer_id %s: %s", peerID, err) + return + } + + var ( + totalDuration int + todayDuration int + ) + + for _, activity := range activities { + // Calculate total active duration + if activity.EndTime != nil { + totalDuration += int(activity.EndTime.Sub(activity.StartTime).Seconds()) + } else { + // If the node is still active, calculate the duration until now + totalDuration += int(now.Sub(activity.StartTime).Seconds()) + } + + // Calculate today's active duration + startOfDay := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location()) + if activity.EndTime != nil { + // If the activity occurred today and ended today + if activity.StartTime.After(startOfDay) { + todayDuration += int(activity.EndTime.Sub(activity.StartTime).Seconds()) + } + } else { + // If the node is still active and the activity is today + if activity.StartTime.After(startOfDay) { + todayDuration += int(now.Sub(activity.StartTime).Seconds()) + } + } + } + + // Convert totalDuration and todayDuration from seconds to hours + // totalDurationHr = float64(totalDuration) / 3600 + // todayDurationHr = float64(todayDuration) / 3600 + + // math.Round(i.TotalActiveDuration*100) / 100 + + // float64(totalDuration) / 3600 + + // float64(todayDuration) / 3600 + + return math.Round((float64(totalDuration)/3600)*100) / 100, math.Round((float64(todayDuration)/3600)*100) / 100 +} + +func TrackNodeActivity(peerID string, isActive bool) { + db := dbconfig.GetDb() + now := time.Now() + + var activity models.NodeActivity + // Try to fetch an existing record with NULL end_time (active node) + err := db.Where("peer_id = ? AND end_time IS NULL", peerID). + First(&activity).Error + + if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { + logwrapper.Errorf("failed to fetch node activity: %s", err) + return + } + + if isActive { + // If the node is active, check if an active record exists + if errors.Is(err, gorm.ErrRecordNotFound) { + // No active record, create a new one + newActivity := models.NodeActivity{ + PeerID: peerID, + StartTime: now, + } + if err := db.Create(&newActivity).Error; err != nil { + logwrapper.Errorf("failed to create new node activity: %s", err) + } + } else { + // If node was already active, update the existing record + logwrapper.Infof("Node with peer_id %s is already active", peerID) + // Optionally, you can update any other fields if needed + } + } else { + // If the node becomes inactive + if !errors.Is(err, gorm.ErrRecordNotFound) { + // Update the activity record with the end time and duration + duration := int(now.Sub(activity.StartTime).Seconds()) // Duration in seconds + activity.EndTime = &now + activity.DurationSeconds = duration + + if err := db.Save(&activity).Error; err != nil { + logwrapper.Errorf("failed to update node activity: %s", err) + } + } else { + // If there's no active record and the node is inactive, log the event + logwrapper.Infof("No active record found for peer_id %s to mark as inactive", peerID) + } + } +} diff --git a/api/v1/nodes/nodes.go b/api/v1/nodes/nodes.go index dbf9a26..a82a9ef 100644 --- a/api/v1/nodes/nodes.go +++ b/api/v1/nodes/nodes.go @@ -5,6 +5,7 @@ import ( "fmt" "net/http" + nodeactivity "github.com/NetSepio/erebrus-gateway/api/v1/nodes/nodeActivity" "github.com/NetSepio/erebrus-gateway/config/dbconfig" "github.com/NetSepio/erebrus-gateway/models" "github.com/NetSepio/erebrus-gateway/util/pkg/logwrapper" @@ -77,6 +78,12 @@ func FetchAllNodes(c *gin.Context) { response.IpInfoOrg = ipGeoAddress.IpInfoOrg response.IpInfoPostal = ipGeoAddress.IpInfoPostal response.IpInfoTimezone = ipGeoAddress.IpInfoTimezone + // Round TotalActiveDuration and TodayActiveDuration to two decimal places + + response.TotalActiveDuration, response.TodayActiveDuration = nodeactivity.CalculateTotalAndTodayActiveDuration(i.PeerId) + + // response.TotalActiveDuration = math.Round(i.TotalActiveDuration*100) / 100 + // response.TodayActiveDuration = math.Round(i.TodayActiveDuration*100) / 100 responses = append(responses, response) } @@ -141,6 +148,11 @@ func FetchAllNodesByStatus(c *gin.Context) { response.IpInfoOrg = ipGeoAddress.IpInfoOrg response.IpInfoPostal = ipGeoAddress.IpInfoPostal response.IpInfoTimezone = ipGeoAddress.IpInfoTimezone + // Round TotalActiveDuration and TodayActiveDuration to two decimal places + // response.TotalActiveDuration = math.Round(i.TotalActiveDuration*100) / 100 + // response.TodayActiveDuration = math.Round(i.TodayActiveDuration*100) / 100 + + response.TotalActiveDuration, response.TodayActiveDuration = nodeactivity.CalculateTotalAndTodayActiveDuration(i.PeerId) responses = append(responses, response) } @@ -223,6 +235,11 @@ func FetchAllNodesByStatusAndWalletAddress(c *gin.Context) { response.IpInfoOrg = ipGeoAddress.IpInfoOrg response.IpInfoPostal = ipGeoAddress.IpInfoPostal response.IpInfoTimezone = ipGeoAddress.IpInfoTimezone + // Round TotalActiveDuration and TodayActiveDuration to two decimal places + // response.TotalActiveDuration = math.Round(i.TotalActiveDuration*100) / 100 + // response.TodayActiveDuration = math.Round(i.TodayActiveDuration*100) / 100 + + response.TotalActiveDuration, response.TodayActiveDuration = nodeactivity.CalculateTotalAndTodayActiveDuration(i.PeerId) responses = append(responses, response) } diff --git a/app/p2p-Node/p2p-node.go b/app/p2p-Node/p2p-node.go index e77d1cd..db4fbf8 100644 --- a/app/p2p-Node/p2p-node.go +++ b/app/p2p-Node/p2p-node.go @@ -6,6 +6,7 @@ import ( "log" "time" + nodeactivity "github.com/NetSepio/erebrus-gateway/api/v1/nodes/nodeActivity" p2pHost "github.com/NetSepio/erebrus-gateway/app/p2p-Node/host" "github.com/NetSepio/erebrus-gateway/app/p2p-Node/service" "github.com/NetSepio/erebrus-gateway/config/dbconfig" @@ -116,6 +117,7 @@ func Init() { // Attempt to connect to the peer if err := ha.Connect(ctx, *peerInfo); err != nil { node.Status = "inactive" + nodeactivity.TrackNodeActivity(node.PeerId, false) if err := db.Model(&models.Node{}).Where("peer_id = ?", node.PeerId).Save(&node).Error; err != nil { logrus.Error("failed to update node: ", err.Error()) continue @@ -131,6 +133,7 @@ func Init() { } } else { node.Status = "active" + nodeactivity.TrackNodeActivity(node.PeerId, true) node.LastPing = time.Now().Unix() if err := db.Model(&models.Node{}).Where("peer_id = ?", node.PeerId).Save(&node).Error; err != nil { logrus.Error("failed to update node: ", err.Error()) diff --git a/config/dbconfig/dbconfig.go b/config/dbconfig/dbconfig.go index 394c697..1b16696 100644 --- a/config/dbconfig/dbconfig.go +++ b/config/dbconfig/dbconfig.go @@ -52,7 +52,19 @@ func GetDb() *gorm.DB { func DbInit() error { db := GetDb() - if err := db.AutoMigrate(&models.User{}, &models.Erebrus{}, &models.Node{}, &models.Subscription{}, &models.FormData{}, &models.FlowId{}, &models.UserFeedback{}, &models.WifiNode{}, &models.NodeDwifi{}, &models.WalrusStorage{}); err != nil { + if err := db.AutoMigrate( + &models.Node{}, + &models.NodeActivity{}, + &models.User{}, + &models.Erebrus{}, + &models.Subscription{}, + &models.FormData{}, + &models.FlowId{}, + &models.UserFeedback{}, + &models.WifiNode{}, + &models.NodeDwifi{}, + &models.WalrusStorage{}, + ); err != nil { log.Fatal(err) } return nil diff --git a/models/node.go b/models/node.go index 4ac91ea..f7a99d9 100644 --- a/models/node.go +++ b/models/node.go @@ -1,6 +1,9 @@ package models -import "encoding/json" +import ( + "encoding/json" + "time" +) type NodeResponse struct { Id string `json:"id" gorm:"primaryKey"` @@ -25,6 +28,8 @@ type NodeResponse struct { IpInfoOrg string `json:"ipinfoorg"` IpInfoPostal string `json:"ipinfopostal"` IpInfoTimezone string `json:"ipinfotimezone"` + TotalActiveDuration float64 `json:"totalUptime"` + TodayActiveDuration float64 `json:"uptimeUnit"` } func ToJSON(data interface{}) string { @@ -37,24 +42,26 @@ func ToJSON(data interface{}) string { type Node struct { //using for db operation - PeerId string `json:"peerId" gorm:"primaryKey"` - Name string `json:"name"` - HttpPort string `json:"httpPort"` - Host string `json:"host"` //domain - PeerAddress string `json:"peerAddress"` - Region string `json:"region"` - Status string `json:"status"` // offline 1, online 2, maintainance 3,block 4 - DownloadSpeed float64 `json:"downloadSpeed"` - UploadSpeed float64 `json:"uploadSpeed"` - RegistrationTime int64 `json:"registrationTime"` //StartTimeStamp - LastPing int64 `json:"lastPing"` - Chain string `json:"chainName"` - WalletAddress string `json:"walletAddress"` - Version string `json:"version"` - CodeHash string `json:"codeHash"` - SystemInfo string `json:"systemInfo" gorm:"type:jsonb"` - IpInfo string `json:"ipinfo" gorm:"type:jsonb"` - IpGeoData string `json:"ipGeoData" gorm:"type:jsonb"` + PeerId string `json:"peerId" gorm:"primaryKey"` + Name string `json:"name"` + HttpPort string `json:"httpPort"` + Host string `json:"host"` //domain + PeerAddress string `json:"peerAddress"` + Region string `json:"region"` + Status string `json:"status"` // offline 1, online 2, maintainance 3,block 4 + DownloadSpeed float64 `json:"downloadSpeed"` + UploadSpeed float64 `json:"uploadSpeed"` + RegistrationTime int64 `json:"registrationTime"` //StartTimeStamp + LastPing int64 `json:"lastPing"` + Chain string `json:"chainName"` + WalletAddress string `json:"walletAddress"` + Version string `json:"version"` + CodeHash string `json:"codeHash"` + SystemInfo string `json:"systemInfo" gorm:"type:jsonb"` + IpInfo string `json:"ipinfo" gorm:"type:jsonb"` + IpGeoData string `json:"ipGeoData" gorm:"type:jsonb"` + TotalActiveDuration float64 `json:"totalDuration" gorm:"type:float"` + TodayActiveDuration float64 `json:"todayDuration" gorm:"type:float"` } type NodeAppends struct { @@ -99,3 +106,10 @@ type IpGeoAddress struct { IpInfoPostal string IpInfoTimezone string } + +type NodeActivity struct { + PeerID string `json:"peerId" gorm:"primaryKey;type:varchar(255)"` // Ensure peer_id is indexed as a primary key + StartTime time.Time `json:"startTime" gorm:"not null"` // Ensure StartTime is required + EndTime *time.Time `json:"endTime"` // EndTime can be nil if the node is still active + DurationSeconds int `json:"durationSeconds" gorm:"default:0"` // Duration in seconds, default to 0 +}