replaced git.debros.io with github.com

This commit is contained in:
anonpenguin23 2025-09-18 15:27:53 +03:00
parent 82187de96c
commit ff3b15108d
29 changed files with 233 additions and 213 deletions

View File

@ -11,7 +11,7 @@ Thanks for helping improve the network! This guide covers setup, local dev, test
## Setup ## Setup
```bash ```bash
git clone https://git.debros.io/DeBros/network.git git clone https://github.com/DeBrosOfficial/network.git
cd network cd network
make deps make deps
``` ```

View File

@ -33,7 +33,7 @@ build: deps
go build -ldflags "$(LDFLAGS)" -o bin/node ./cmd/node go build -ldflags "$(LDFLAGS)" -o bin/node ./cmd/node
go build -ldflags "$(LDFLAGS)" -o bin/network-cli cmd/cli/main.go go build -ldflags "$(LDFLAGS)" -o bin/network-cli cmd/cli/main.go
# Inject gateway build metadata via pkg path variables # Inject gateway build metadata via pkg path variables
go build -ldflags "$(LDFLAGS) -X 'git.debros.io/DeBros/network/pkg/gateway.BuildVersion=$(VERSION)' -X 'git.debros.io/DeBros/network/pkg/gateway.BuildCommit=$(COMMIT)' -X 'git.debros.io/DeBros/network/pkg/gateway.BuildTime=$(DATE)'" -o bin/gateway ./cmd/gateway go build -ldflags "$(LDFLAGS) -X 'github.com/DeBrosOfficial/network/pkg/gateway.BuildVersion=$(VERSION)' -X 'github.com/DeBrosOfficial/network/pkg/gateway.BuildCommit=$(COMMIT)' -X 'github.com/DeBrosOfficial/network/pkg/gateway.BuildTime=$(DATE)'" -o bin/gateway ./cmd/gateway
@echo "Build complete! Run ./bin/network-cli version" @echo "Build complete! Run ./bin/network-cli version"
# Clean build artifacts # Clean build artifacts

View File

@ -94,7 +94,7 @@ A robust, decentralized peer-to-peer network built in Go, providing distributed
### 1. Clone and Setup ### 1. Clone and Setup
```bash ```bash
git clone https://git.debros.io/DeBros/network.git git clone https://github.com/DeBrosOfficial/network.git
cd network cd network
``` ```
@ -138,7 +138,7 @@ go run ./cmd/node --config configs/node.yaml
Run the install script for a secure, production-ready setup: Run the install script for a secure, production-ready setup:
```bash ```bash
curl -sSL https://git.debros.io/DeBros/network/raw/branch/main/scripts/install-debros-network.sh | sudo bash curl -sSL https://github.com/DeBrosOfficial/network/raw/branch/main/scripts/install-debros-network.sh | sudo bash
``` ```
**What the Script Does:** **What the Script Does:**

View File

@ -12,9 +12,9 @@ import (
"strings" "strings"
"time" "time"
"git.debros.io/DeBros/network/pkg/anyoneproxy" "github.com/DeBrosOfficial/network/pkg/anyoneproxy"
"git.debros.io/DeBros/network/pkg/auth" "github.com/DeBrosOfficial/network/pkg/auth"
"git.debros.io/DeBros/network/pkg/client" "github.com/DeBrosOfficial/network/pkg/client"
"github.com/libp2p/go-libp2p/core/crypto" "github.com/libp2p/go-libp2p/core/crypto"
"github.com/libp2p/go-libp2p/core/peer" "github.com/libp2p/go-libp2p/core/peer"
) )

View File

@ -5,8 +5,8 @@ import (
"os" "os"
"strings" "strings"
"git.debros.io/DeBros/network/pkg/gateway" "github.com/DeBrosOfficial/network/pkg/gateway"
"git.debros.io/DeBros/network/pkg/logging" "github.com/DeBrosOfficial/network/pkg/logging"
"go.uber.org/zap" "go.uber.org/zap"
) )

View File

@ -9,8 +9,8 @@ import (
"syscall" "syscall"
"time" "time"
"git.debros.io/DeBros/network/pkg/gateway" "github.com/DeBrosOfficial/network/pkg/gateway"
"git.debros.io/DeBros/network/pkg/logging" "github.com/DeBrosOfficial/network/pkg/logging"
"go.uber.org/zap" "go.uber.org/zap"
) )

View File

@ -11,10 +11,10 @@ import (
"path/filepath" "path/filepath"
"syscall" "syscall"
"git.debros.io/DeBros/network/pkg/anyoneproxy" "github.com/DeBrosOfficial/network/pkg/anyoneproxy"
"git.debros.io/DeBros/network/pkg/config" "github.com/DeBrosOfficial/network/pkg/config"
"git.debros.io/DeBros/network/pkg/logging" "github.com/DeBrosOfficial/network/pkg/logging"
"git.debros.io/DeBros/network/pkg/node" "github.com/DeBrosOfficial/network/pkg/node"
"go.uber.org/zap" "go.uber.org/zap"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
) )

View File

@ -10,7 +10,7 @@ import (
"testing" "testing"
"time" "time"
"git.debros.io/DeBros/network/pkg/client" "github.com/DeBrosOfficial/network/pkg/client"
) )
func getenv(k, def string) string { func getenv(k, def string) string {
@ -42,7 +42,9 @@ func TestClient_Database_CreateQueryMigrate(t *testing.T) {
var peers []string var peers []string
for _, p := range parts { for _, p := range parts {
p = strings.TrimSpace(p) p = strings.TrimSpace(p)
if p != "" { peers = append(peers, p) } if p != "" {
peers = append(peers, p)
}
} }
cfg.BootstrapPeers = peers cfg.BootstrapPeers = peers
} }
@ -52,9 +54,13 @@ func TestClient_Database_CreateQueryMigrate(t *testing.T) {
} }
c, err := client.NewClient(cfg) c, err := client.NewClient(cfg)
if err != nil { t.Fatalf("new client: %v", err) } if err != nil {
if err := c.Connect(); err != nil { t.Fatalf("connect: %v", err) } t.Fatalf("new client: %v", err)
t.Cleanup(func(){ _ = c.Disconnect() }) }
if err := c.Connect(); err != nil {
t.Fatalf("connect: %v", err)
}
t.Cleanup(func() { _ = c.Disconnect() })
// Unique table per run // Unique table per run
table := fmt.Sprintf("e2e_items_client_%d", time.Now().UnixNano()) table := fmt.Sprintf("e2e_items_client_%d", time.Now().UnixNano())
@ -78,6 +84,10 @@ func TestClient_Database_CreateQueryMigrate(t *testing.T) {
ctx3, cancel3 := context.WithTimeout(context.Background(), 10*time.Second) ctx3, cancel3 := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel3() defer cancel3()
res, err := c.Database().Query(ctx3, fmt.Sprintf("SELECT name FROM %s ORDER BY id", table)) res, err := c.Database().Query(ctx3, fmt.Sprintf("SELECT name FROM %s ORDER BY id", table))
if err != nil { t.Fatalf("query: %v", err) } if err != nil {
if res.Count < 2 { t.Fatalf("expected at least 2 rows, got %d", res.Count) } t.Fatalf("query: %v", err)
}
if res.Count < 2 {
t.Fatalf("expected at least 2 rows, got %d", res.Count)
}
} }

View File

@ -5,7 +5,7 @@ import (
"log" "log"
"time" "time"
"git.debros.io/DeBros/network/pkg/client" "github.com/DeBrosOfficial/network/pkg/client"
) )
func main() { func main() {

2
go.mod
View File

@ -1,4 +1,4 @@
module git.debros.io/DeBros/network module github.com/DeBrosOfficial/network
go 1.23.8 go 1.23.8

View File

@ -20,8 +20,8 @@ import (
libp2ppubsub "github.com/libp2p/go-libp2p-pubsub" libp2ppubsub "github.com/libp2p/go-libp2p-pubsub"
"git.debros.io/DeBros/network/pkg/anyoneproxy" "github.com/DeBrosOfficial/network/pkg/anyoneproxy"
"git.debros.io/DeBros/network/pkg/pubsub" "github.com/DeBrosOfficial/network/pkg/pubsub"
) )
// Client implements the NetworkClient interface // Client implements the NetworkClient interface

View File

@ -6,7 +6,7 @@ import (
"encoding/json" "encoding/json"
"testing" "testing"
"git.debros.io/DeBros/network/pkg/pubsub" "github.com/DeBrosOfficial/network/pkg/pubsub"
) )
// MakeJWT creates a minimal JWT-like token with a json payload // MakeJWT creates a minimal JWT-like token with a json payload

View File

@ -3,7 +3,7 @@ package client
import ( import (
"context" "context"
"git.debros.io/DeBros/network/pkg/pubsub" "github.com/DeBrosOfficial/network/pkg/pubsub"
) )
// contextKey for internal operations // contextKey for internal operations

View File

@ -5,7 +5,7 @@ import (
"strconv" "strconv"
"strings" "strings"
"git.debros.io/DeBros/network/pkg/config" "github.com/DeBrosOfficial/network/pkg/config"
"github.com/multiformats/go-multiaddr" "github.com/multiformats/go-multiaddr"
) )

View File

@ -7,7 +7,7 @@ import (
"sync" "sync"
"time" "time"
"git.debros.io/DeBros/network/pkg/anyoneproxy" "github.com/DeBrosOfficial/network/pkg/anyoneproxy"
"github.com/libp2p/go-libp2p/core/peer" "github.com/libp2p/go-libp2p/core/peer"
"github.com/multiformats/go-multiaddr" "github.com/multiformats/go-multiaddr"
"github.com/rqlite/gorqlite" "github.com/rqlite/gorqlite"

View File

@ -4,7 +4,7 @@ import (
"context" "context"
"fmt" "fmt"
"git.debros.io/DeBros/network/pkg/pubsub" "github.com/DeBrosOfficial/network/pkg/pubsub"
) )
// pubSubBridge bridges between our PubSubClient interface and the pubsub package // pubSubBridge bridges between our PubSubClient interface and the pubsub package

View File

@ -15,7 +15,7 @@ import (
"github.com/rqlite/gorqlite" "github.com/rqlite/gorqlite"
"go.uber.org/zap" "go.uber.org/zap"
"git.debros.io/DeBros/network/pkg/config" "github.com/DeBrosOfficial/network/pkg/config"
) )
// RQLiteManager manages an RQLite node instance // RQLiteManager manages an RQLite node instance

View File

@ -7,16 +7,16 @@ import (
"net/http" "net/http"
"strings" "strings"
"git.debros.io/DeBros/network/pkg/storage" "github.com/DeBrosOfficial/network/pkg/storage"
) )
// appsHandler implements minimal CRUD for apps within a namespace. // appsHandler implements minimal CRUD for apps within a namespace.
// Routes handled: // Routes handled:
// - GET /v1/apps -> list // - GET /v1/apps -> list
// - POST /v1/apps -> create // - POST /v1/apps -> create
// - GET /v1/apps/{app_id} -> fetch // - GET /v1/apps/{app_id} -> fetch
// - PUT /v1/apps/{app_id} -> update (name/public_key) // - PUT /v1/apps/{app_id} -> update (name/public_key)
// - DELETE /v1/apps/{app_id} -> delete // - DELETE /v1/apps/{app_id} -> delete
func (g *Gateway) appsHandler(w http.ResponseWriter, r *http.Request) { func (g *Gateway) appsHandler(w http.ResponseWriter, r *http.Request) {
if g.client == nil { if g.client == nil {
writeError(w, http.StatusServiceUnavailable, "client not initialized") writeError(w, http.StatusServiceUnavailable, "client not initialized")
@ -29,7 +29,9 @@ func (g *Gateway) appsHandler(w http.ResponseWriter, r *http.Request) {
ns = s ns = s
} }
} }
if strings.TrimSpace(ns) == "" { ns = "default" } if strings.TrimSpace(ns) == "" {
ns = "default"
}
db := g.client.Database() db := g.client.Database()
nsID, err := g.resolveNamespaceID(ctx, ns) nsID, err := g.resolveNamespaceID(ctx, ns)
if err != nil { if err != nil {

View File

@ -11,8 +11,8 @@ import (
"strings" "strings"
"time" "time"
"git.debros.io/DeBros/network/pkg/client" "github.com/DeBrosOfficial/network/pkg/client"
"git.debros.io/DeBros/network/pkg/storage" "github.com/DeBrosOfficial/network/pkg/storage"
ethcrypto "github.com/ethereum/go-ethereum/crypto" ethcrypto "github.com/ethereum/go-ethereum/crypto"
) )

View File

@ -3,7 +3,7 @@ package gateway
import ( import (
"context" "context"
"git.debros.io/DeBros/network/pkg/client" "github.com/DeBrosOfficial/network/pkg/client"
) )
func (g *Gateway) resolveNamespaceID(ctx context.Context, ns string) (interface{}, error) { func (g *Gateway) resolveNamespaceID(ctx context.Context, ns string) (interface{}, error) {

View File

@ -8,9 +8,8 @@ import (
"strconv" "strconv"
"time" "time"
"github.com/DeBrosOfficial/network/pkg/client"
"git.debros.io/DeBros/network/pkg/client" "github.com/DeBrosOfficial/network/pkg/logging"
"git.debros.io/DeBros/network/pkg/logging"
"go.uber.org/zap" "go.uber.org/zap"
) )
@ -59,7 +58,7 @@ func New(logger *logging.ColoredLogger, cfg *Config) (*Gateway, error) {
) )
logger.ComponentInfo(logging.ComponentGeneral, "Creating gateway instance...") logger.ComponentInfo(logging.ComponentGeneral, "Creating gateway instance...")
gw := &Gateway{ gw := &Gateway{
logger: logger, logger: logger,
cfg: cfg, cfg: cfg,
client: c, client: c,

View File

@ -9,9 +9,9 @@ import (
"strings" "strings"
"time" "time"
"git.debros.io/DeBros/network/pkg/client" "github.com/DeBrosOfficial/network/pkg/client"
"git.debros.io/DeBros/network/pkg/logging" "github.com/DeBrosOfficial/network/pkg/logging"
"git.debros.io/DeBros/network/pkg/storage" "github.com/DeBrosOfficial/network/pkg/storage"
"go.uber.org/zap" "go.uber.org/zap"
) )
@ -96,13 +96,13 @@ func (g *Gateway) authMiddleware(next http.Handler) http.Handler {
return return
} }
// Look up API key in DB and derive namespace // Look up API key in DB and derive namespace
db := g.client.Database() db := g.client.Database()
// Use internal auth for DB validation (auth not established yet) // Use internal auth for DB validation (auth not established yet)
internalCtx := client.WithInternalAuth(r.Context()) internalCtx := client.WithInternalAuth(r.Context())
// Join to namespaces to resolve name in one query // Join to namespaces to resolve name in one query
q := "SELECT namespaces.name FROM api_keys JOIN namespaces ON api_keys.namespace_id = namespaces.id WHERE api_keys.key = ? LIMIT 1" q := "SELECT namespaces.name FROM api_keys JOIN namespaces ON api_keys.namespace_id = namespaces.id WHERE api_keys.key = ? LIMIT 1"
res, err := db.Query(internalCtx, q, key) res, err := db.Query(internalCtx, q, key)
if err != nil || res == nil || res.Count == 0 || len(res.Rows) == 0 || len(res.Rows[0]) == 0 { if err != nil || res == nil || res.Count == 0 || len(res.Rows) == 0 || len(res.Rows[0]) == 0 {
w.Header().Set("WWW-Authenticate", "Bearer error=\"invalid_token\"") w.Header().Set("WWW-Authenticate", "Bearer error=\"invalid_token\"")
writeError(w, http.StatusUnauthorized, "invalid API key") writeError(w, http.StatusUnauthorized, "invalid API key")
@ -123,10 +123,10 @@ func (g *Gateway) authMiddleware(next http.Handler) http.Handler {
return return
} }
// Attach auth metadata to context for downstream use // Attach auth metadata to context for downstream use
reqCtx := context.WithValue(r.Context(), ctxKeyAPIKey, key) reqCtx := context.WithValue(r.Context(), ctxKeyAPIKey, key)
reqCtx = storage.WithNamespace(reqCtx, ns) reqCtx = storage.WithNamespace(reqCtx, ns)
next.ServeHTTP(w, r.WithContext(reqCtx)) next.ServeHTTP(w, r.WithContext(reqCtx))
}) })
} }
@ -203,25 +203,25 @@ func (g *Gateway) authorizationMiddleware(next http.Handler) http.Handler {
return return
} }
// Identify actor from context // Identify actor from context
ownerType := "" ownerType := ""
ownerID := "" ownerID := ""
if v := ctx.Value(ctxKeyJWT); v != nil { if v := ctx.Value(ctxKeyJWT); v != nil {
if claims, ok := v.(*jwtClaims); ok && claims != nil && strings.TrimSpace(claims.Sub) != "" { if claims, ok := v.(*jwtClaims); ok && claims != nil && strings.TrimSpace(claims.Sub) != "" {
// Determine subject type. // Determine subject type.
// If subject looks like an API key (e.g., ak_<random>:<namespace>), // If subject looks like an API key (e.g., ak_<random>:<namespace>),
// treat it as an API key owner; otherwise assume a wallet subject. // treat it as an API key owner; otherwise assume a wallet subject.
subj := strings.TrimSpace(claims.Sub) subj := strings.TrimSpace(claims.Sub)
lowerSubj := strings.ToLower(subj) lowerSubj := strings.ToLower(subj)
if strings.HasPrefix(lowerSubj, "ak_") || strings.Contains(subj, ":") { if strings.HasPrefix(lowerSubj, "ak_") || strings.Contains(subj, ":") {
ownerType = "api_key" ownerType = "api_key"
ownerID = subj ownerID = subj
} else { } else {
ownerType = "wallet" ownerType = "wallet"
ownerID = subj ownerID = subj
} }
} }
} }
if ownerType == "" && ownerID == "" { if ownerType == "" && ownerID == "" {
if v := ctx.Value(ctxKeyAPIKey); v != nil { if v := ctx.Value(ctxKeyAPIKey); v != nil {
if s, ok := v.(string); ok && strings.TrimSpace(s) != "" { if s, ok := v.(string); ok && strings.TrimSpace(s) != "" {
@ -236,23 +236,23 @@ func (g *Gateway) authorizationMiddleware(next http.Handler) http.Handler {
return return
} }
// Check ownership in DB using internal auth context // Check ownership in DB using internal auth context
db := g.client.Database() db := g.client.Database()
internalCtx := client.WithInternalAuth(ctx) internalCtx := client.WithInternalAuth(ctx)
// Ensure namespace exists and get id // Ensure namespace exists and get id
if _, err := db.Query(internalCtx, "INSERT OR IGNORE INTO namespaces(name) VALUES (?)", ns); err != nil { if _, err := db.Query(internalCtx, "INSERT OR IGNORE INTO namespaces(name) VALUES (?)", ns); err != nil {
writeError(w, http.StatusInternalServerError, err.Error()) writeError(w, http.StatusInternalServerError, err.Error())
return return
} }
nres, err := db.Query(internalCtx, "SELECT id FROM namespaces WHERE name = ? LIMIT 1", ns) nres, err := db.Query(internalCtx, "SELECT id FROM namespaces WHERE name = ? LIMIT 1", ns)
if err != nil || nres == nil || nres.Count == 0 || len(nres.Rows) == 0 || len(nres.Rows[0]) == 0 { if err != nil || nres == nil || nres.Count == 0 || len(nres.Rows) == 0 || len(nres.Rows[0]) == 0 {
writeError(w, http.StatusForbidden, "namespace not found") writeError(w, http.StatusForbidden, "namespace not found")
return return
} }
nsID := nres.Rows[0][0] nsID := nres.Rows[0][0]
q := "SELECT 1 FROM namespace_ownership WHERE namespace_id = ? AND owner_type = ? AND owner_id = ? LIMIT 1" q := "SELECT 1 FROM namespace_ownership WHERE namespace_id = ? AND owner_type = ? AND owner_id = ? LIMIT 1"
res, err := db.Query(internalCtx, q, nsID, ownerType, ownerID) res, err := db.Query(internalCtx, q, nsID, ownerType, ownerID)
if err != nil || res == nil || res.Count == 0 { if err != nil || res == nil || res.Count == 0 {
writeError(w, http.StatusForbidden, "forbidden: not an owner of namespace") writeError(w, http.StatusForbidden, "forbidden: not an owner of namespace")
return return
@ -274,9 +274,9 @@ func requiresNamespaceOwnership(p string) bool {
if strings.HasPrefix(p, "/v1/pubsub") { if strings.HasPrefix(p, "/v1/pubsub") {
return true return true
} }
if strings.HasPrefix(p, "/v1/db/") { if strings.HasPrefix(p, "/v1/db/") {
return true return true
} }
return false return false
} }

View File

@ -9,8 +9,8 @@ import (
"strconv" "strconv"
"strings" "strings"
"git.debros.io/DeBros/network/pkg/client" "github.com/DeBrosOfficial/network/pkg/client"
"git.debros.io/DeBros/network/pkg/logging" "github.com/DeBrosOfficial/network/pkg/logging"
"go.uber.org/zap" "go.uber.org/zap"
) )

View File

@ -1,15 +1,15 @@
package gateway package gateway
import ( import (
"encoding/base64"
"encoding/json"
"crypto/sha256" "crypto/sha256"
"encoding/base64"
"encoding/hex" "encoding/hex"
"encoding/json"
"net/http" "net/http"
"time" "time"
"git.debros.io/DeBros/network/pkg/client" "github.com/DeBrosOfficial/network/pkg/client"
"git.debros.io/DeBros/network/pkg/storage" "github.com/DeBrosOfficial/network/pkg/storage"
"github.com/gorilla/websocket" "github.com/gorilla/websocket"
) )
@ -25,51 +25,51 @@ var wsUpgrader = websocket.Upgrader{
// are published to the same namespaced topic. // are published to the same namespaced topic.
func (g *Gateway) pubsubWebsocketHandler(w http.ResponseWriter, r *http.Request) { func (g *Gateway) pubsubWebsocketHandler(w http.ResponseWriter, r *http.Request) {
if g.client == nil { if g.client == nil {
g.logger.ComponentWarn("gateway", "pubsub ws: client not initialized") g.logger.ComponentWarn("gateway", "pubsub ws: client not initialized")
writeError(w, http.StatusServiceUnavailable, "client not initialized") writeError(w, http.StatusServiceUnavailable, "client not initialized")
return return
} }
if r.Method != http.MethodGet { if r.Method != http.MethodGet {
g.logger.ComponentWarn("gateway", "pubsub ws: method not allowed",) g.logger.ComponentWarn("gateway", "pubsub ws: method not allowed")
writeError(w, http.StatusMethodNotAllowed, "method not allowed") writeError(w, http.StatusMethodNotAllowed, "method not allowed")
return return
} }
// Resolve namespace from auth context // Resolve namespace from auth context
ns := resolveNamespaceFromRequest(r) ns := resolveNamespaceFromRequest(r)
if ns == "" { if ns == "" {
g.logger.ComponentWarn("gateway", "pubsub ws: namespace not resolved") g.logger.ComponentWarn("gateway", "pubsub ws: namespace not resolved")
writeError(w, http.StatusForbidden, "namespace not resolved") writeError(w, http.StatusForbidden, "namespace not resolved")
return return
} }
topic := r.URL.Query().Get("topic") topic := r.URL.Query().Get("topic")
if topic == "" { if topic == "" {
g.logger.ComponentWarn("gateway", "pubsub ws: missing topic") g.logger.ComponentWarn("gateway", "pubsub ws: missing topic")
writeError(w, http.StatusBadRequest, "missing 'topic'") writeError(w, http.StatusBadRequest, "missing 'topic'")
return return
} }
conn, err := wsUpgrader.Upgrade(w, r, nil) conn, err := wsUpgrader.Upgrade(w, r, nil)
if err != nil { if err != nil {
g.logger.ComponentWarn("gateway", "pubsub ws: upgrade failed",) g.logger.ComponentWarn("gateway", "pubsub ws: upgrade failed")
return return
} }
defer conn.Close() defer conn.Close()
// Channel to deliver PubSub messages to WS writer // Channel to deliver PubSub messages to WS writer
msgs := make(chan []byte, 128) msgs := make(chan []byte, 128)
// Use internal auth context when interacting with client to avoid circular auth requirements // Use internal auth context when interacting with client to avoid circular auth requirements
ctx := client.WithInternalAuth(r.Context()) ctx := client.WithInternalAuth(r.Context())
// Subscribe to the topic; push data into msgs with simple per-connection de-dup // Subscribe to the topic; push data into msgs with simple per-connection de-dup
recent := make(map[string]time.Time) recent := make(map[string]time.Time)
h := func(_ string, data []byte) error { h := func(_ string, data []byte) error {
// Drop duplicates seen in the last 2 seconds // Drop duplicates seen in the last 2 seconds
sum := sha256.Sum256(data) sum := sha256.Sum256(data)
key := hex.EncodeToString(sum[:]) key := hex.EncodeToString(sum[:])
if t, ok := recent[key]; ok && time.Since(t) < 2*time.Second { if t, ok := recent[key]; ok && time.Since(t) < 2*time.Second {
return nil return nil
} }
recent[key] = time.Now() recent[key] = time.Now()
select { select {
case msgs <- data: case msgs <- data:
return nil return nil
@ -78,14 +78,14 @@ func (g *Gateway) pubsubWebsocketHandler(w http.ResponseWriter, r *http.Request)
return nil return nil
} }
} }
if err := g.client.PubSub().Subscribe(ctx, topic, h); err != nil { if err := g.client.PubSub().Subscribe(ctx, topic, h); err != nil {
g.logger.ComponentWarn("gateway", "pubsub ws: subscribe failed",) g.logger.ComponentWarn("gateway", "pubsub ws: subscribe failed")
writeError(w, http.StatusInternalServerError, err.Error()) writeError(w, http.StatusInternalServerError, err.Error())
return return
} }
defer func() { _ = g.client.PubSub().Unsubscribe(ctx, topic) }() defer func() { _ = g.client.PubSub().Unsubscribe(ctx, topic) }()
// no extra fan-out; rely on libp2p subscription // no extra fan-out; rely on libp2p subscription
// Writer loop // Writer loop
done := make(chan struct{}) done := make(chan struct{})
@ -116,7 +116,7 @@ func (g *Gateway) pubsubWebsocketHandler(w http.ResponseWriter, r *http.Request)
}() }()
// Reader loop: treat any client message as publish to the same topic // Reader loop: treat any client message as publish to the same topic
for { for {
mt, data, err := conn.ReadMessage() mt, data, err := conn.ReadMessage()
if err != nil { if err != nil {
break break
@ -124,7 +124,7 @@ func (g *Gateway) pubsubWebsocketHandler(w http.ResponseWriter, r *http.Request)
if mt != websocket.TextMessage && mt != websocket.BinaryMessage { if mt != websocket.TextMessage && mt != websocket.BinaryMessage {
continue continue
} }
if err := g.client.PubSub().Publish(ctx, topic, data); err != nil { if err := g.client.PubSub().Publish(ctx, topic, data); err != nil {
// Best-effort notify client // Best-effort notify client
_ = conn.WriteMessage(websocket.TextMessage, []byte("publish_error")) _ = conn.WriteMessage(websocket.TextMessage, []byte("publish_error"))
} }
@ -142,14 +142,14 @@ func (g *Gateway) pubsubPublishHandler(w http.ResponseWriter, r *http.Request) {
writeError(w, http.StatusMethodNotAllowed, "method not allowed") writeError(w, http.StatusMethodNotAllowed, "method not allowed")
return return
} }
ns := resolveNamespaceFromRequest(r) ns := resolveNamespaceFromRequest(r)
if ns == "" { if ns == "" {
writeError(w, http.StatusForbidden, "namespace not resolved") writeError(w, http.StatusForbidden, "namespace not resolved")
return return
} }
var body struct { var body struct {
Topic string `json:"topic"` Topic string `json:"topic"`
DataB64 string `json:"data_base64"` DataB64 string `json:"data_base64"`
} }
if err := json.NewDecoder(r.Body).Decode(&body); err != nil || body.Topic == "" || body.DataB64 == "" { if err := json.NewDecoder(r.Body).Decode(&body); err != nil || body.Topic == "" || body.DataB64 == "" {
writeError(w, http.StatusBadRequest, "invalid body: expected {topic,data_base64}") writeError(w, http.StatusBadRequest, "invalid body: expected {topic,data_base64}")
@ -160,11 +160,11 @@ func (g *Gateway) pubsubPublishHandler(w http.ResponseWriter, r *http.Request) {
writeError(w, http.StatusBadRequest, "invalid base64 data") writeError(w, http.StatusBadRequest, "invalid base64 data")
return return
} }
if err := g.client.PubSub().Publish(client.WithInternalAuth(r.Context()), body.Topic, data); err != nil { if err := g.client.PubSub().Publish(client.WithInternalAuth(r.Context()), body.Topic, data); err != nil {
writeError(w, http.StatusInternalServerError, err.Error()) writeError(w, http.StatusInternalServerError, err.Error())
return return
} }
// rely on libp2p to deliver to WS subscribers // rely on libp2p to deliver to WS subscribers
writeJSON(w, http.StatusOK, map[string]any{"status": "ok"}) writeJSON(w, http.StatusOK, map[string]any{"status": "ok"})
} }
@ -179,13 +179,13 @@ func (g *Gateway) pubsubTopicsHandler(w http.ResponseWriter, r *http.Request) {
writeError(w, http.StatusForbidden, "namespace not resolved") writeError(w, http.StatusForbidden, "namespace not resolved")
return return
} }
all, err := g.client.PubSub().ListTopics(client.WithInternalAuth(r.Context())) all, err := g.client.PubSub().ListTopics(client.WithInternalAuth(r.Context()))
if err != nil { if err != nil {
writeError(w, http.StatusInternalServerError, err.Error()) writeError(w, http.StatusInternalServerError, err.Error())
return return
} }
// Client returns topics already trimmed to its namespace; return as-is // Client returns topics already trimmed to its namespace; return as-is
writeJSON(w, http.StatusOK, map[string]any{"topics": all}) writeJSON(w, http.StatusOK, map[string]any{"topics": all})
} }
// resolveNamespaceFromRequest gets namespace from context set by auth middleware // resolveNamespaceFromRequest gets namespace from context set by auth middleware

View File

@ -5,8 +5,8 @@ import (
"net/http" "net/http"
"time" "time"
"git.debros.io/DeBros/network/pkg/client" "github.com/DeBrosOfficial/network/pkg/client"
"git.debros.io/DeBros/network/pkg/logging" "github.com/DeBrosOfficial/network/pkg/logging"
"go.uber.org/zap" "go.uber.org/zap"
) )

View File

@ -4,29 +4,32 @@ import (
"encoding/json" "encoding/json"
"net/http" "net/http"
"git.debros.io/DeBros/network/pkg/client" "github.com/DeBrosOfficial/network/pkg/client"
"git.debros.io/DeBros/network/pkg/pubsub" "github.com/DeBrosOfficial/network/pkg/pubsub"
) )
// Database HTTP handlers // Database HTTP handlers
func (g *Gateway) dbQueryHandler(w http.ResponseWriter, r *http.Request) { func (g *Gateway) dbQueryHandler(w http.ResponseWriter, r *http.Request) {
if g.client == nil { if g.client == nil {
writeError(w, http.StatusServiceUnavailable, "client not initialized"); writeError(w, http.StatusServiceUnavailable, "client not initialized")
return return
} }
if r.Method != http.MethodPost { if r.Method != http.MethodPost {
writeError(w, http.StatusMethodNotAllowed, "method not allowed"); writeError(w, http.StatusMethodNotAllowed, "method not allowed")
return return
} }
var body struct{ SQL string `json:"sql"`; Args []any `json:"args"` } var body struct {
SQL string `json:"sql"`
Args []any `json:"args"`
}
if err := json.NewDecoder(r.Body).Decode(&body); err != nil || body.SQL == "" { if err := json.NewDecoder(r.Body).Decode(&body); err != nil || body.SQL == "" {
writeError(w, http.StatusBadRequest, "invalid body: {sql, args?}"); writeError(w, http.StatusBadRequest, "invalid body: {sql, args?}")
return return
} }
ctx := client.WithInternalAuth(r.Context()) ctx := client.WithInternalAuth(r.Context())
res, err := g.client.Database().Query(ctx, body.SQL, body.Args...) res, err := g.client.Database().Query(ctx, body.SQL, body.Args...)
if err != nil { if err != nil {
writeError(w, http.StatusInternalServerError, err.Error()); writeError(w, http.StatusInternalServerError, err.Error())
return return
} }
writeJSON(w, http.StatusOK, res) writeJSON(w, http.StatusOK, res)
@ -34,39 +37,41 @@ func (g *Gateway) dbQueryHandler(w http.ResponseWriter, r *http.Request) {
func (g *Gateway) dbTransactionHandler(w http.ResponseWriter, r *http.Request) { func (g *Gateway) dbTransactionHandler(w http.ResponseWriter, r *http.Request) {
if g.client == nil { if g.client == nil {
writeError(w, http.StatusServiceUnavailable, "client not initialized"); writeError(w, http.StatusServiceUnavailable, "client not initialized")
return return
} }
if r.Method != http.MethodPost { if r.Method != http.MethodPost {
writeError(w, http.StatusMethodNotAllowed, "method not allowed"); writeError(w, http.StatusMethodNotAllowed, "method not allowed")
return return
} }
var body struct{ Statements []string `json:"statements"` } var body struct {
Statements []string `json:"statements"`
}
if err := json.NewDecoder(r.Body).Decode(&body); err != nil || len(body.Statements) == 0 { if err := json.NewDecoder(r.Body).Decode(&body); err != nil || len(body.Statements) == 0 {
writeError(w, http.StatusBadRequest, "invalid body: {statements:[...]}"); writeError(w, http.StatusBadRequest, "invalid body: {statements:[...]}")
return return
} }
ctx := client.WithInternalAuth(r.Context()) ctx := client.WithInternalAuth(r.Context())
if err := g.client.Database().Transaction(ctx, body.Statements); err != nil { if err := g.client.Database().Transaction(ctx, body.Statements); err != nil {
writeError(w, http.StatusInternalServerError, err.Error()); writeError(w, http.StatusInternalServerError, err.Error())
return return
} }
writeJSON(w, http.StatusOK, map[string]any{"status":"ok"}) writeJSON(w, http.StatusOK, map[string]any{"status": "ok"})
} }
func (g *Gateway) dbSchemaHandler(w http.ResponseWriter, r *http.Request) { func (g *Gateway) dbSchemaHandler(w http.ResponseWriter, r *http.Request) {
if g.client == nil { if g.client == nil {
writeError(w, http.StatusServiceUnavailable, "client not initialized"); writeError(w, http.StatusServiceUnavailable, "client not initialized")
return return
} }
if r.Method != http.MethodGet { if r.Method != http.MethodGet {
writeError(w, http.StatusMethodNotAllowed, "method not allowed"); writeError(w, http.StatusMethodNotAllowed, "method not allowed")
return return
} }
ctx := client.WithInternalAuth(r.Context()) ctx := client.WithInternalAuth(r.Context())
schema, err := g.client.Database().GetSchema(ctx) schema, err := g.client.Database().GetSchema(ctx)
if err != nil { if err != nil {
writeError(w, http.StatusInternalServerError, err.Error()); writeError(w, http.StatusInternalServerError, err.Error())
return return
} }
writeJSON(w, http.StatusOK, schema) writeJSON(w, http.StatusOK, schema)
@ -74,46 +79,50 @@ func (g *Gateway) dbSchemaHandler(w http.ResponseWriter, r *http.Request) {
func (g *Gateway) dbCreateTableHandler(w http.ResponseWriter, r *http.Request) { func (g *Gateway) dbCreateTableHandler(w http.ResponseWriter, r *http.Request) {
if g.client == nil { if g.client == nil {
writeError(w, http.StatusServiceUnavailable, "client not initialized"); writeError(w, http.StatusServiceUnavailable, "client not initialized")
return return
} }
if r.Method != http.MethodPost { if r.Method != http.MethodPost {
writeError(w, http.StatusMethodNotAllowed, "method not allowed"); writeError(w, http.StatusMethodNotAllowed, "method not allowed")
return return
} }
var body struct{ Schema string `json:"schema"` } var body struct {
Schema string `json:"schema"`
}
if err := json.NewDecoder(r.Body).Decode(&body); err != nil || body.Schema == "" { if err := json.NewDecoder(r.Body).Decode(&body); err != nil || body.Schema == "" {
writeError(w, http.StatusBadRequest, "invalid body: {schema}"); writeError(w, http.StatusBadRequest, "invalid body: {schema}")
return return
} }
ctx := client.WithInternalAuth(r.Context()) ctx := client.WithInternalAuth(r.Context())
if err := g.client.Database().CreateTable(ctx, body.Schema); err != nil { if err := g.client.Database().CreateTable(ctx, body.Schema); err != nil {
writeError(w, http.StatusInternalServerError, err.Error()); writeError(w, http.StatusInternalServerError, err.Error())
return return
} }
writeJSON(w, http.StatusCreated, map[string]any{"status":"ok"}) writeJSON(w, http.StatusCreated, map[string]any{"status": "ok"})
} }
func (g *Gateway) dbDropTableHandler(w http.ResponseWriter, r *http.Request) { func (g *Gateway) dbDropTableHandler(w http.ResponseWriter, r *http.Request) {
if g.client == nil { if g.client == nil {
writeError(w, http.StatusServiceUnavailable, "client not initialized"); writeError(w, http.StatusServiceUnavailable, "client not initialized")
return return
} }
if r.Method != http.MethodPost { if r.Method != http.MethodPost {
writeError(w, http.StatusMethodNotAllowed, "method not allowed"); writeError(w, http.StatusMethodNotAllowed, "method not allowed")
return return
} }
var body struct{ Table string `json:"table"` } var body struct {
Table string `json:"table"`
}
if err := json.NewDecoder(r.Body).Decode(&body); err != nil || body.Table == "" { if err := json.NewDecoder(r.Body).Decode(&body); err != nil || body.Table == "" {
writeError(w, http.StatusBadRequest, "invalid body: {table}"); writeError(w, http.StatusBadRequest, "invalid body: {table}")
return return
} }
ctx := client.WithInternalAuth(r.Context()) ctx := client.WithInternalAuth(r.Context())
if err := g.client.Database().DropTable(ctx, body.Table); err != nil { if err := g.client.Database().DropTable(ctx, body.Table); err != nil {
writeError(w, http.StatusInternalServerError, err.Error()); writeError(w, http.StatusInternalServerError, err.Error())
return return
} }
writeJSON(w, http.StatusOK, map[string]any{"status":"ok"}) writeJSON(w, http.StatusOK, map[string]any{"status": "ok"})
} }
func (g *Gateway) networkStatusHandler(w http.ResponseWriter, r *http.Request) { func (g *Gateway) networkStatusHandler(w http.ResponseWriter, r *http.Request) {

View File

@ -22,11 +22,11 @@ import (
"github.com/multiformats/go-multiaddr" "github.com/multiformats/go-multiaddr"
"go.uber.org/zap" "go.uber.org/zap"
"git.debros.io/DeBros/network/pkg/anyoneproxy" "github.com/DeBrosOfficial/network/pkg/anyoneproxy"
"git.debros.io/DeBros/network/pkg/config" "github.com/DeBrosOfficial/network/pkg/config"
"git.debros.io/DeBros/network/pkg/database" "github.com/DeBrosOfficial/network/pkg/database"
"git.debros.io/DeBros/network/pkg/logging" "github.com/DeBrosOfficial/network/pkg/logging"
"git.debros.io/DeBros/network/pkg/pubsub" "github.com/DeBrosOfficial/network/pkg/pubsub"
) )
// Node represents a network node with RQLite database // Node represents a network node with RQLite database

View File

@ -9,7 +9,7 @@ import (
"testing" "testing"
"time" "time"
"git.debros.io/DeBros/network/pkg/config" "github.com/DeBrosOfficial/network/pkg/config"
"github.com/libp2p/go-libp2p" "github.com/libp2p/go-libp2p"
"github.com/libp2p/go-libp2p/core/crypto" "github.com/libp2p/go-libp2p/core/crypto"
"github.com/libp2p/go-libp2p/core/network" "github.com/libp2p/go-libp2p/core/network"

View File

@ -17,7 +17,7 @@ NOCOLOR='\033[0m'
# Defaults # Defaults
INSTALL_DIR="/opt/debros" INSTALL_DIR="/opt/debros"
REPO_URL="https://git.debros.io/DeBros/network.git" REPO_URL="https://github.com/DeBrosOfficial/network.git"
MIN_GO_VERSION="1.21" MIN_GO_VERSION="1.21"
NODE_PORT="4001" NODE_PORT="4001"
RQLITE_PORT="5001" RQLITE_PORT="5001"