implement adapter configuration API
This commit is contained in:
parent
8f2b281d82
commit
63b79409f4
2 changed files with 125 additions and 22 deletions
122
server/api.go
122
server/api.go
|
@ -1,14 +1,101 @@
|
|||
package server
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"forge.lightcrystal.systems/lightcrystal/underbbs/adapter"
|
||||
"forge.lightcrystal.systems/lightcrystal/underbbs/models"
|
||||
"hacklab.nilfm.cc/quartzgun/renderer"
|
||||
"hacklab.nilfm.cc/quartzgun/router"
|
||||
"hacklab.nilfm.cc/quartzgun/util"
|
||||
"html/template"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func apiConfigureAdapters(next http.Handler) http.Handler {
|
||||
func getSubscriberKey(req *http.Request) (string, error) {
|
||||
authHeader := req.Header.Get("Authorization")
|
||||
if strings.HasPrefix(authHeader, "Bearer ") {
|
||||
return strings.Split(authHeader, "Bearer ")[1], nil
|
||||
}
|
||||
return "", errors.New("No subscriber key")
|
||||
}
|
||||
|
||||
func getSubscriberByKey(key string, subscribers map[*Subscriber][]adapter.Adapter) *Subscriber {
|
||||
for s, _ := range subscribers {
|
||||
if s.key == key {
|
||||
return s
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func setAdaptersForSubscriber(key string, adapters []adapter.Adapter, subscribers map[*Subscriber][]adapter.Adapter) error {
|
||||
var ptr *Subscriber = nil
|
||||
for s, _ := range subscribers {
|
||||
if s.key == key {
|
||||
ptr = s
|
||||
}
|
||||
}
|
||||
if ptr != nil {
|
||||
subscribers[ptr] = adapters
|
||||
return nil
|
||||
}
|
||||
return errors.New("subscriber not present in map")
|
||||
}
|
||||
|
||||
func apiConfigureAdapters(next http.Handler, subscribers map[*Subscriber][]adapter.Adapter) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
// get subscriber key
|
||||
skey, err := getSubscriberKey(req)
|
||||
if err != nil {
|
||||
w.WriteHeader(500)
|
||||
return
|
||||
}
|
||||
subscriber := getSubscriberByKey(skey, subscribers)
|
||||
if subscriber == nil {
|
||||
w.WriteHeader(404)
|
||||
return
|
||||
}
|
||||
// decode adapter config from request body
|
||||
settings := make([]models.Settings, 0)
|
||||
err = json.NewDecoder(req.Body).Decode(&settings)
|
||||
if err != nil {
|
||||
w.WriteHeader(400)
|
||||
next.ServeHTTP(w, req)
|
||||
return
|
||||
}
|
||||
// iterate through settings and create adapters
|
||||
adapters := make([]adapter.Adapter, 0)
|
||||
for _, s := range settings {
|
||||
var a adapter.Adapter
|
||||
switch s.Protocol {
|
||||
case "nostr":
|
||||
a = &adapter.NostrAdapter{}
|
||||
break
|
||||
default:
|
||||
break
|
||||
|
||||
}
|
||||
err := a.Init(s, subscriber.data)
|
||||
if err != nil {
|
||||
util.AddContextValue(req, "data", err.Error())
|
||||
w.WriteHeader(500)
|
||||
next.ServeHTTP(w, req)
|
||||
}
|
||||
|
||||
adapters = append(adapters, a)
|
||||
}
|
||||
|
||||
// TODO: cancel subscriptions on any existing adapters
|
||||
// store the adapters in the subscriber map
|
||||
err = setAdaptersForSubscriber(skey, adapters, subscribers)
|
||||
if err != nil {
|
||||
util.AddContextValue(req, "data", err.Error())
|
||||
w.WriteHeader(500)
|
||||
next.ServeHTTP(w, req)
|
||||
}
|
||||
|
||||
w.WriteHeader(201)
|
||||
next.ServeHTTP(w, req)
|
||||
})
|
||||
|
@ -28,14 +115,23 @@ func apiAdapterSubscribe(next http.Handler) http.Handler {
|
|||
})
|
||||
}
|
||||
|
||||
func ProtectWithSubscriberKey(next http.Handler) http.Handler {
|
||||
func ProtectWithSubscriberKey(next http.Handler, subscribers map[*Subscriber][]adapter.Adapter) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
w.WriteHeader(201)
|
||||
next.ServeHTTP(w, req)
|
||||
authHeader := req.Header.Get("Authorization")
|
||||
if strings.HasPrefix(authHeader, "Bearer ") {
|
||||
subscriberKey := strings.Split(authHeader, "Bearer ")[1]
|
||||
for s, _ := range subscribers {
|
||||
if s.key == subscriberKey {
|
||||
next.ServeHTTP(w, req)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
})
|
||||
}
|
||||
|
||||
func apiMux() http.Handler {
|
||||
func (self *BBSServer) apiMux() http.Handler {
|
||||
errTemplate, err := template.New("err").Parse("{{ $params := (.Context).Value \"params\" }}<html><body><h1>ERROR {{ $params.ErrorCode }}</h1><p class='error'>{{ $params.ErrorMessage }}</p></body></html>")
|
||||
if err != nil {
|
||||
panic("error template was malformed")
|
||||
|
@ -45,10 +141,20 @@ func apiMux() http.Handler {
|
|||
}
|
||||
|
||||
// adapters (POST & GET)
|
||||
rtr.Post("/adapters", ProtectWithSubscriberKey(apiConfigureAdapters(renderer.JSON("data"))))
|
||||
rtr.Get("/adapters", ProtectWithSubscriberKey(apiGetAdapters(renderer.JSON("data"))))
|
||||
rtr.Post("/adapters", ProtectWithSubscriberKey(
|
||||
apiConfigureAdapters(renderer.JSON("data"), self.subscribers),
|
||||
self.subscribers,
|
||||
))
|
||||
rtr.Get("/adapters", ProtectWithSubscriberKey(
|
||||
apiGetAdapters(renderer.JSON("data")),
|
||||
self.subscribers,
|
||||
))
|
||||
|
||||
// adapters/:name/subscribe
|
||||
rtr.Post(`/adapters/(?P<id>\S+)/subscribe`, ProtectWithSubscriberKey(apiAdapterSubscribe(renderer.JSON("data"))))
|
||||
rtr.Post(`/adapters/(?P<id>\S+)/subscribe`, ProtectWithSubscriberKey(
|
||||
apiAdapterSubscribe(renderer.JSON("data")),
|
||||
self.subscribers,
|
||||
))
|
||||
|
||||
return http.HandlerFunc(rtr.ServeHTTP)
|
||||
}
|
||||
|
|
|
@ -3,7 +3,6 @@ package server
|
|||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"forge.lightcrystal.systems/lightcrystal/underbbs/adapter"
|
||||
"forge.lightcrystal.systems/lightcrystal/underbbs/models"
|
||||
"golang.org/x/time/rate"
|
||||
|
@ -20,6 +19,7 @@ import (
|
|||
type Subscriber struct {
|
||||
key string
|
||||
msgs chan []byte
|
||||
data chan models.SocketData
|
||||
closeSlow func()
|
||||
}
|
||||
|
||||
|
@ -43,7 +43,7 @@ func New() *BBSServer {
|
|||
srvr.serveMux.Handle("/app/", http.StripPrefix("/app/", renderer.Subtree("./dist")))
|
||||
|
||||
// api
|
||||
srvr.serveMux.Handle("/api/", http.StripPrefix("/api", apiMux()))
|
||||
srvr.serveMux.Handle("/api/", http.StripPrefix("/api", srvr.apiMux()))
|
||||
|
||||
// websocket
|
||||
srvr.serveMux.HandleFunc("/subscribe", srvr.subscribeHandler)
|
||||
|
@ -66,27 +66,24 @@ func (self *BBSServer) subscribeHandler(w http.ResponseWriter, r *http.Request)
|
|||
return
|
||||
}
|
||||
|
||||
msgc := make(chan models.SocketData)
|
||||
|
||||
// attempt to initialize adapters
|
||||
adapters := make([]adapter.Adapter, 0, 4)
|
||||
|
||||
// keep reference to the adapters so we can execute commands on them later
|
||||
ctx := r.Context()
|
||||
ctx = c.CloseRead(ctx)
|
||||
|
||||
s := &Subscriber{
|
||||
key: cookie.GenToken(64),
|
||||
msgs: make(chan []byte, self.subscribeMessageBuffer),
|
||||
data: make(chan models.SocketData),
|
||||
closeSlow: func() {
|
||||
c.Close(websocket.StatusPolicyViolation, "connection too slow to keep up with messages")
|
||||
},
|
||||
}
|
||||
|
||||
// start with an empty set of adapters
|
||||
// we'll configure them separately
|
||||
adapters := make([]adapter.Adapter, 0, 4)
|
||||
self.addSubscriber(s, adapters)
|
||||
|
||||
fmt.Println("subscriber added to map")
|
||||
|
||||
// defer cleanup and write messages in the background
|
||||
defer self.deleteSubscriber(s)
|
||||
defer c.Close(websocket.StatusInternalError, "")
|
||||
|
||||
|
@ -102,11 +99,11 @@ func (self *BBSServer) subscribeHandler(w http.ResponseWriter, r *http.Request)
|
|||
}
|
||||
}()
|
||||
|
||||
fmt.Println("giving subscriber their key: " + s.key)
|
||||
s.msgs <- []byte("{ 'key':'" + s.key + " }")
|
||||
fmt.Println("subscriber key sent, listening on the data channel")
|
||||
// give user their key
|
||||
s.msgs <- []byte("{ 'key':'" + s.key + "' }")
|
||||
|
||||
listen([]chan models.SocketData{msgc}, s.msgs)
|
||||
// block on the data channel, serializing and passing the data to the subscriber
|
||||
listen([]chan models.SocketData{s.data}, s.msgs)
|
||||
|
||||
if errors.Is(err, context.Canceled) {
|
||||
return
|
||||
|
|
Loading…
Reference in a new issue