Skip to content

Commit

Permalink
Merge pull request #25 from Nuklai/up-dev
Browse files Browse the repository at this point in the history
Sync dev branch with main
  • Loading branch information
Developerayo authored Feb 10, 2025
2 parents 401b034 + dd57889 commit 76bd46b
Show file tree
Hide file tree
Showing 22 changed files with 1,671 additions and 125 deletions.
2 changes: 1 addition & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,4 @@ DB_PASSWORD=postgres
DB_NAME=nuklaivm
DB_SSLMODE=require # Or "disable" if you don't want to use SSL
DB_RESET=true # Set to "true" to reset the database on every restart
GRPC_WHITELISTED_BLOCKCHAIN_NODES="172.17.1.56" # "127.0.0.1,localhost,::1" is already included by default. You can even include something like myblockchain.aws.com
GRPC_WHITELISTED_BLOCKCHAIN_NODES="127.0.0.1,localhost" # "127.0.0.1,localhost,::1" is already included by default. You can even include something like myblockchain.aws.com
70 changes: 70 additions & 0 deletions api/account.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
// Copyright (C) 2025, Nuklai. All rights reserved.
// See the file LICENSE for licensing terms.

package api

import (
"database/sql"
"log"
"net/http"

"github.com/gin-gonic/gin"
"github.com/nuklai/nuklaivm-external-subscriber/models"
)

// GetAccountStats retrieves all account stats
func GetAccountStats(db *sql.DB) gin.HandlerFunc {
return func(c *gin.Context) {
stats, err := models.FetchAccountStats(db)
if err != nil {
log.Printf("Error fetching account stats: %v", err)
c.JSON(http.StatusInternalServerError, gin.H{"error": "Unable to retrieve account stats"})
return
}

c.JSON(http.StatusOK, stats)
}
}

// GetAccountDetails retrieves details for a specific account/address
func GetAccountDetails(db *sql.DB) gin.HandlerFunc {
return func(c *gin.Context) {
address := c.Param("address")

details, err := models.FetchAccountByAddress(db, address)
if err != nil {
log.Printf("Error fetching account details: %v", err)
c.JSON(http.StatusNotFound, gin.H{"error": "Account not found"})
return
}

c.JSON(http.StatusOK, details)
}
}

// GetAllAccounts retrieves accounts address, balance, transaction count
func GetAllAccounts(db *sql.DB) gin.HandlerFunc {
return func(c *gin.Context) {
limit := c.DefaultQuery("limit", "20")
offset := c.DefaultQuery("offset", "0")

// Get the total count of accounts
totalCount, err := models.CountAccounts(db)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Unable to count accounts"})
return
}

accounts, err := models.FetchAllAccounts(db, limit, offset)
if err != nil {
log.Printf("Error fetching accounts: %v", err)
c.JSON(http.StatusInternalServerError, gin.H{"error": "Unable to retrieve accounts"})
return
}

c.JSON(http.StatusOK, gin.H{
"counter": totalCount,
"items": accounts,
})
}
}
25 changes: 23 additions & 2 deletions api/block.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"database/sql"
"log"
"net/http"
"strconv"

"github.com/nuklai/nuklaivm-external-subscriber/models"

Expand All @@ -16,9 +17,23 @@ import (
// GetAllBlocks retrieves all blocks with pagination and total count
func GetAllBlocks(db *sql.DB) gin.HandlerFunc {
return func(c *gin.Context) {
blockHash := c.Query("block_hash")
blockHeight := c.Query("block_height")
limit := c.DefaultQuery("limit", "10")
offset := c.DefaultQuery("offset", "0")

// Request a specific block
if blockHash != "" || blockHeight != "" {
block, err := models.FetchBlock(db, blockHeight, blockHash)
if err != nil {
log.Printf("Error fetching block: %v", err)
c.JSON(http.StatusNotFound, gin.H{"error": "Block not found"})
return
}
c.JSON(http.StatusOK, block)
return
}

// Get total count of blocks
var totalCount int
err := db.QueryRow(`SELECT COUNT(*) FROM blocks`).Scan(&totalCount)
Expand Down Expand Up @@ -47,8 +62,14 @@ func GetBlock(db *sql.DB) gin.HandlerFunc {
return func(c *gin.Context) {
blockIdentifier := c.Param("identifier")

block, err := models.FetchBlock(db, blockIdentifier)
// Check for query errors
var height, hash string
if _, err := strconv.ParseInt(blockIdentifier, 10, 64); err == nil {
height = blockIdentifier
} else {
hash = blockIdentifier
}

block, err := models.FetchBlock(db, height, hash)
if err != nil {
log.Printf("Error fetching block: %v", err)
c.JSON(http.StatusNotFound, gin.H{"error": "Block not found"})
Expand Down
109 changes: 70 additions & 39 deletions api/health.go
Original file line number Diff line number Diff line change
@@ -1,62 +1,93 @@
// Copyright (C) 2024, Nuklai. All rights reserved.
// Copyright (C) 2025, Nuklai. All rights reserved.
// See the file LICENSE for licensing terms.

package api

import (
"database/sql"
"log"
"net"
"net/http"
"time"

"github.com/lib/pq"

"github.com/gin-gonic/gin"
"github.com/nuklai/nuklaivm-external-subscriber/models"
)

// checkGRPC checks the reachability of the gRPC server.
func checkGRPC(grpcPort string) string {
conn, err := net.DialTimeout("tcp", grpcPort, 2*time.Second)
if err != nil {
log.Printf("gRPC connection failed: %v", err)
return "unreachable"
}
defer conn.Close()
return "reachable"
}
// GetHealth retrieves the current health status of the system
func GetHealth(monitor *HealthMonitor) gin.HandlerFunc {
return func(c *gin.Context) {
status := monitor.GetHealthStatus()

// checkDatabase checks the reachability of the database.
func checkDatabase(db *sql.DB) string {
if err := db.Ping(); err != nil {
log.Printf("Database connection failed: %v", err)
return "unreachable"
httpStatus := http.StatusOK
if status.State == models.HealthStateRed {
httpStatus = http.StatusServiceUnavailable
}

c.JSON(httpStatus, status)
}
return "reachable"
}

// HealthCheck performs a comprehensive health check of the subscriber.
func HealthCheck(grpcPort string, db *sql.DB) gin.HandlerFunc {
// GetHealthHistory retrieves historical health events for insidents
func GetHealthHistory(db *sql.DB) gin.HandlerFunc {
return func(c *gin.Context) {
grpcStatus := checkGRPC(grpcPort)
databaseStatus := checkDatabase(db)

// Determine the overall status
status := "ok"
if grpcStatus == "unreachable" || databaseStatus == "unreachable" {
status = "service unavailable"
rows, err := db.Query(`
SELECT id, state, description, service_names,
start_time, end_time, COALESCE(duration, 0), timestamp
FROM health_events
ORDER BY timestamp DESC`)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Unable to fetch health history"})
return
}
defer rows.Close()

// Return the health status
httpStatusCode := http.StatusOK
if status == "service unavailable" {
httpStatusCode = http.StatusServiceUnavailable
var events []models.HealthEvent
for rows.Next() {
var event models.HealthEvent
var endTime sql.NullTime
var serviceNamesArray pq.StringArray
var duration sql.NullInt64

err := rows.Scan(
&event.ID,
&event.State,
&event.Description,
&serviceNamesArray,
&event.StartTime,
&endTime,
&duration,
&event.Timestamp,
)
if err != nil {
log.Printf("Error scanning health event: %v", err)
continue
}

if endTime.Valid {
event.EndTime = &endTime.Time
}
if duration.Valid {
event.Duration = duration.Int64
}
event.ServiceNames = []string(serviceNamesArray)
events = append(events, event)
}

c.JSON(httpStatusCode, gin.H{
"status": status,
"details": gin.H{
"database": databaseStatus,
"grpc": grpcStatus,
},
})
c.JSON(http.StatusOK, events)
}
}

// Get90DayHealth retrives a 90day health summary
func Get90DayHealth(db *sql.DB) gin.HandlerFunc {
return func(c *gin.Context) {
summaries, err := models.Fetch90DayHealth(db)
if err != nil {
log.Printf("Error fetching 90-day health history: %v", err)
c.JSON(http.StatusInternalServerError,
gin.H{"error": "Unable to retrieve health history"})
return
}
c.JSON(http.StatusOK, summaries)
}
}
Loading

0 comments on commit 76bd46b

Please sign in to comment.