underbbs/server/api.go

217 lines
6 KiB
Go

package server
import (
"encoding/json"
"errors"
"fmt"
"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"
"log"
"net/http"
"strings"
)
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
log.Print("looking for subscriber in map...")
for s, _ := range subscribers {
if s.key == key {
ptr = s
}
}
if ptr != nil {
log.Print("setting adaters for the found subscriber: " + ptr.key)
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{}
case "mastodon":
a = &adapter.MastoAdapter{}
case "misskey":
a = &adapter.MisskeyAdapter{}
default:
break
}
err := a.Init(s, &subscriber.data)
if err != nil {
util.AddContextValue(req, "data", err.Error())
w.WriteHeader(500)
next.ServeHTTP(w, req)
return
}
log.Print("adapter initialized - subscribing with default filter")
errs := a.Subscribe(a.DefaultSubscriptionFilter())
if errs != nil {
errMsg := ""
for _, e := range errs {
log.Print("processing an error")
errMsg += fmt.Sprintf("- %s\n", e.Error())
}
util.AddContextValue(req, "data", errMsg)
w.WriteHeader(500)
next.ServeHTTP(w, req)
return
}
log.Print("adapter ready for use; adding to array")
adapters = append(adapters, a)
log.Print("adapter added to array")
}
// 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)
})
}
func apiGetAdapters(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
w.WriteHeader(201)
next.ServeHTTP(w, req)
})
}
func apiAdapterSubscribe(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
w.WriteHeader(201)
next.ServeHTTP(w, req)
})
}
func ProtectWithSubscriberKey(next http.Handler, subscribers map[*Subscriber][]adapter.Adapter) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
authHeader := req.Header.Get("Authorization")
if strings.HasPrefix(authHeader, "Bearer ") {
subscriberKey := strings.Split(authHeader, "Bearer ")[1]
if getSubscriberByKey(subscriberKey, subscribers) != nil {
next.ServeHTTP(w, req)
return
}
}
w.WriteHeader(http.StatusUnauthorized)
})
}
func apiAdapterFetch(next http.Handler, subscribers map[*Subscriber][]adapter.Adapter) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
authHeader := req.Header.Get("Authorization")
if strings.HasPrefix(authHeader, "Bearer ") {
subscriberKey := strings.Split(authHeader, "Bearer ")[1]
s := getSubscriberByKey(subscriberKey, subscribers)
if s != nil {
apiParams := req.Context().Value("params").(map[string]string)
queryParams := req.URL.Query()
for _, a := range subscribers[s] {
if a.Name() == apiParams["adapter_id"] {
err := a.Fetch(queryParams["entity_type"][0], queryParams["entity_id"])
if err != nil {
log.Print(err.Error())
w.WriteHeader(http.StatusInternalServerError)
} else {
w.WriteHeader(http.StatusAccepted)
}
next.ServeHTTP(w, req)
return
}
}
}
}
w.WriteHeader(http.StatusUnauthorized)
})
}
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")
}
rtr := &router.Router{
Fallback: *errTemplate,
}
// adapters (POST & GET)
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")),
self.subscribers,
))
rtr.Get(`/adapters/(?P<adapter_id>\S+)/fetch`, ProtectWithSubscriberKey(
apiAdapterFetch(renderer.JSON("data"), self.subscribers),
self.subscribers,
))
return http.HandlerFunc(rtr.ServeHTTP)
}