Files
oc-peer/routers/router.go
2026-04-29 11:43:52 +02:00

145 lines
3.7 KiB
Go

// @APIVersion 1.0.0
// @Title oc-peer
// @Description Manage OpenCloud peers
// @Contact admin@o-cloud.io
// @TermsOfServiceUrl http://cloud.o-forge.io/
// @License AGPL
// @LicenseUrl https://www.gnu.org/licenses/agpl-3.0.html
package routers
import (
"encoding/json"
"fmt"
"net/http"
"oc-peer/controllers"
"oc-peer/infrastructure"
"strings"
"time"
oclib "cloud.o-forge.io/core/oc-lib"
"cloud.o-forge.io/core/oc-lib/tools"
beego "github.com/beego/beego/v2/server/web"
"github.com/gorilla/websocket"
)
var upgrader = websocket.Upgrader{
CheckOrigin: func(r *http.Request) bool {
return true
},
}
func init() {
ns := beego.NewNamespace("/oc",
beego.NSNamespace("/status",
beego.NSInclude(
&controllers.StatusController{},
),
),
beego.NSInclude(
&controllers.PeerController{},
),
beego.NSNamespace("/version",
beego.NSInclude(
&controllers.VersionController{},
),
),
)
beego.AddNamespace(ns)
// WebSocket — peer search (returns found peers + online/offline updates for them)
beego.Handler("/oc/decentralized/search/:search", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
conn, err := upgrader.Upgrade(w, r, nil)
if err != nil {
return
}
defer conn.Close()
parts := strings.Split(strings.TrimSuffix(r.URL.Path, "/"), "/")
search := parts[len(parts)-1]
user, _, groups := oclib.ExtractTokenInfoWs(*r)
b, _ := json.Marshal(map[string]string{"search": search})
infrastructure.EmitNATS(user, groups, tools.PropalgationMessage{
Action: tools.PB_SEARCH,
DataType: tools.PEER.EnumIndex(),
Payload: b,
})
fmt.Println("SEARCH", search)
controllers.Websocket(r.Context(), user, conn)
}))
// WebSocket — dedicated online/offline feed, user-scoped.
//
// The frontend sends lists of ShallowPeer to watch; oc-peer forwards them
// to oc-discovery via NATS (with user identity). On any disconnect or error
// oc-peer emits NATS close for all peers watched in this session.
beego.Handler("/oc/decentralized/observe", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
conn, err := upgrader.Upgrade(w, r, nil)
if err != nil {
return
}
user, _, _ := oclib.ExtractTokenInfoWs(*r)
connID := fmt.Sprintf("%p", conn)
ch := make(chan infrastructure.WSMessage, 64)
fmt.Println("SUBSCRIBED TO", connID)
infrastructure.RegisterObserveSession(connID, user, ch)
done := make(chan struct{})
defer func() {
infrastructure.CloseObserveSession(connID)
close(done)
conn.Close()
}()
const pongWait = 60 * time.Second
const pingPeriod = 50 * time.Second
const writeWait = 10 * time.Second
conn.SetReadDeadline(time.Now().Add(pongWait))
conn.SetPongHandler(func(string) error {
return conn.SetReadDeadline(time.Now().Add(pongWait))
})
// Write loop: forward state-change messages and send keepalive pings.
go func() {
ticker := time.NewTicker(pingPeriod)
defer ticker.Stop()
for {
select {
case msg := <-ch:
fmt.Println("ONLINE", msg)
conn.SetWriteDeadline(time.Now().Add(writeWait))
if conn.WriteJSON(msg) != nil {
conn.Close()
return
}
case <-ticker.C:
conn.SetWriteDeadline(time.Now().Add(writeWait))
if conn.WriteMessage(websocket.PingMessage, nil) != nil {
conn.Close()
return
}
case <-done:
return
}
}
}()
// Read loop: receive peer lists from the frontend.
var req struct {
Peers []infrastructure.ShallowPeer `json:"peers"`
}
for {
if err := conn.ReadJSON(&req); err != nil {
return
}
// AddObservedPeers deduplicates, emits NATS observe, returns snapshot.
for _, msg := range infrastructure.AddObservedPeers(connID, req.Peers) {
select {
case ch <- msg:
default:
}
}
}
}))
}