implement adapter configuration API
This commit is contained in:
parent
8f2b281d82
commit
63b79409f4
2 changed files with 125 additions and 22 deletions
120
server/api.go
120
server/api.go
|
@ -1,14 +1,101 @@
|
||||||
package server
|
package server
|
||||||
|
|
||||||
import (
|
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/renderer"
|
||||||
"hacklab.nilfm.cc/quartzgun/router"
|
"hacklab.nilfm.cc/quartzgun/router"
|
||||||
|
"hacklab.nilfm.cc/quartzgun/util"
|
||||||
"html/template"
|
"html/template"
|
||||||
"net/http"
|
"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) {
|
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)
|
w.WriteHeader(201)
|
||||||
next.ServeHTTP(w, req)
|
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) {
|
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||||
w.WriteHeader(201)
|
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)
|
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>")
|
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 {
|
if err != nil {
|
||||||
panic("error template was malformed")
|
panic("error template was malformed")
|
||||||
|
@ -45,10 +141,20 @@ func apiMux() http.Handler {
|
||||||
}
|
}
|
||||||
|
|
||||||
// adapters (POST & GET)
|
// adapters (POST & GET)
|
||||||
rtr.Post("/adapters", ProtectWithSubscriberKey(apiConfigureAdapters(renderer.JSON("data"))))
|
rtr.Post("/adapters", ProtectWithSubscriberKey(
|
||||||
rtr.Get("/adapters", ProtectWithSubscriberKey(apiGetAdapters(renderer.JSON("data"))))
|
apiConfigureAdapters(renderer.JSON("data"), self.subscribers),
|
||||||
|
self.subscribers,
|
||||||
|
))
|
||||||
|
rtr.Get("/adapters", ProtectWithSubscriberKey(
|
||||||
|
apiGetAdapters(renderer.JSON("data")),
|
||||||
|
self.subscribers,
|
||||||
|
))
|
||||||
|
|
||||||
// adapters/:name/subscribe
|
// 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)
|
return http.HandlerFunc(rtr.ServeHTTP)
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,6 @@ package server
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
|
||||||
"forge.lightcrystal.systems/lightcrystal/underbbs/adapter"
|
"forge.lightcrystal.systems/lightcrystal/underbbs/adapter"
|
||||||
"forge.lightcrystal.systems/lightcrystal/underbbs/models"
|
"forge.lightcrystal.systems/lightcrystal/underbbs/models"
|
||||||
"golang.org/x/time/rate"
|
"golang.org/x/time/rate"
|
||||||
|
@ -20,6 +19,7 @@ import (
|
||||||
type Subscriber struct {
|
type Subscriber struct {
|
||||||
key string
|
key string
|
||||||
msgs chan []byte
|
msgs chan []byte
|
||||||
|
data chan models.SocketData
|
||||||
closeSlow func()
|
closeSlow func()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -43,7 +43,7 @@ func New() *BBSServer {
|
||||||
srvr.serveMux.Handle("/app/", http.StripPrefix("/app/", renderer.Subtree("./dist")))
|
srvr.serveMux.Handle("/app/", http.StripPrefix("/app/", renderer.Subtree("./dist")))
|
||||||
|
|
||||||
// api
|
// api
|
||||||
srvr.serveMux.Handle("/api/", http.StripPrefix("/api", apiMux()))
|
srvr.serveMux.Handle("/api/", http.StripPrefix("/api", srvr.apiMux()))
|
||||||
|
|
||||||
// websocket
|
// websocket
|
||||||
srvr.serveMux.HandleFunc("/subscribe", srvr.subscribeHandler)
|
srvr.serveMux.HandleFunc("/subscribe", srvr.subscribeHandler)
|
||||||
|
@ -66,27 +66,24 @@ func (self *BBSServer) subscribeHandler(w http.ResponseWriter, r *http.Request)
|
||||||
return
|
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 := r.Context()
|
||||||
ctx = c.CloseRead(ctx)
|
ctx = c.CloseRead(ctx)
|
||||||
|
|
||||||
s := &Subscriber{
|
s := &Subscriber{
|
||||||
key: cookie.GenToken(64),
|
key: cookie.GenToken(64),
|
||||||
msgs: make(chan []byte, self.subscribeMessageBuffer),
|
msgs: make(chan []byte, self.subscribeMessageBuffer),
|
||||||
|
data: make(chan models.SocketData),
|
||||||
closeSlow: func() {
|
closeSlow: func() {
|
||||||
c.Close(websocket.StatusPolicyViolation, "connection too slow to keep up with messages")
|
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)
|
self.addSubscriber(s, adapters)
|
||||||
|
|
||||||
fmt.Println("subscriber added to map")
|
// defer cleanup and write messages in the background
|
||||||
|
|
||||||
defer self.deleteSubscriber(s)
|
defer self.deleteSubscriber(s)
|
||||||
defer c.Close(websocket.StatusInternalError, "")
|
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)
|
// give user their key
|
||||||
s.msgs <- []byte("{ 'key':'" + s.key + " }")
|
s.msgs <- []byte("{ 'key':'" + s.key + "' }")
|
||||||
fmt.Println("subscriber key sent, listening on the data channel")
|
|
||||||
|
|
||||||
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) {
|
if errors.Is(err, context.Canceled) {
|
||||||
return
|
return
|
||||||
|
|
Loading…
Reference in a new issue