backend for xcvr appview
1package handler
2
3import (
4 "github.com/gorilla/sessions"
5 "net/http"
6
7 "os"
8 "rvcx/internal/db"
9 "rvcx/internal/log"
10 "rvcx/internal/model"
11 "rvcx/internal/oauth"
12 "rvcx/internal/recordmanager"
13)
14
15type Handler struct {
16 db *db.Store
17 sessionStore *sessions.CookieStore
18 router *http.ServeMux
19 logger *log.Logger
20 oauth *oauth.Service
21 model *model.Model
22 rm *recordmanager.RecordManager
23}
24
25func New(db *db.Store, logger *log.Logger, oauthserv *oauth.Service, model *model.Model, recordmanager *recordmanager.RecordManager) *Handler {
26 mux := http.NewServeMux()
27 sessionStore := sessions.NewCookieStore([]byte(os.Getenv("SESSION_KEY")))
28 h := &Handler{db, sessionStore, mux, logger, oauthserv, model, recordmanager}
29 // lrc handlers
30 mux.HandleFunc("GET /lrc/{user}/{rkey}/ws", h.WithCORS(h.acceptWebsocket))
31 mux.HandleFunc("DELETE /lrc/{user}/{rkey}/ws", h.oauthMiddleware(h.deleteChannel))
32 mux.HandleFunc("POST /lrc/channel", h.oauthMiddleware(h.postChannel))
33 mux.HandleFunc("POST /lrc/message", h.oauthMiddleware(h.postMessage))
34 mux.HandleFunc("POST /lrc/image", h.oauthMiddleware(h.uploadImage))
35 mux.HandleFunc("POST /lrc/media", h.oauthMiddleware(h.postMedia))
36 mux.HandleFunc("GET /lrc/image", h.WithCORS(h.getImage))
37 mux.HandleFunc("POST /lrc/mymessage", h.postMyMessage)
38 // xcvr handlers
39 mux.HandleFunc("POST /xcvr/profile", h.oauthMiddleware(h.postProfile))
40 mux.HandleFunc("POST /xcvr/beep", h.oauthMiddleware(h.beep))
41 // lexicon handlers
42 mux.HandleFunc("GET /xrpc/org.xcvr.feed.getChannels", h.WithCORS(h.getChannels))
43 mux.HandleFunc("GET /xrpc/org.xcvr.feed.getChannel", h.WithCORS(h.getChannel))
44 mux.HandleFunc("GET /xrpc/org.xcvr.lrc.getMessages", h.WithCORS(h.getMessages))
45 mux.HandleFunc("GET /xrpc/org.xcvr.lrc.getHistory", h.WithCORS(h.getHistory))
46 mux.HandleFunc("GET /xrpc/org.xcvr.lrc.getImage", h.WithCORS(h.getImage))
47 mux.HandleFunc("GET /xrpc/org.xcvr.actor.resolveChannel", h.WithCORS(h.resolveChannel))
48 mux.HandleFunc("GET /xrpc/org.xcvr.actor.getProfileView", h.WithCORS(h.getProfileView))
49 mux.HandleFunc("GET /xrpc/org.xcvr.lrc.subscribeLexStream", h.WithCORS(h.subscribeLexStream))
50 mux.HandleFunc("GET /xrpc/org.xcvr.actor.getLastSeen", h.WithCORS(h.getLastSeen))
51 // backend metadata handlers
52 mux.HandleFunc(clientMetadataPath(), h.WithCORS(h.serveClientMetadata))
53 mux.HandleFunc(clientTOSPath(), h.WithCORS(h.serveTOS))
54 mux.HandleFunc(clientPolicyPath(), h.WithCORS(h.servePolicy))
55 // oauth handlers
56 mux.HandleFunc(oauthJWKSPath(), h.WithCORS(h.serveJWKS))
57 mux.HandleFunc("POST /oauth/login", h.oauthLogin)
58 mux.HandleFunc("POST /oauth/logout", h.oauthMiddleware(h.oauthLogout))
59 mux.HandleFunc("POST /oauth/ban", h.postBan)
60 mux.HandleFunc("GET /oauth/ban", h.getBan)
61 mux.HandleFunc("GET /oauth/whoami", h.getSession)
62 mux.HandleFunc(oauthCallbackPath(), h.WithCORS(h.oauthCallback))
63 return h
64}
65
66func (h *Handler) badRequest(w http.ResponseWriter, err error) {
67 h.logger.Deprintln(err.Error())
68 w.Header().Set("Content-Type", "application/json")
69 http.Error(w, `{
70 "error":"Invalid JSON",
71 "message":"Could not parse request body"
72 }`, http.StatusBadRequest)
73}
74
75func (h *Handler) serverError(w http.ResponseWriter, err error) {
76 h.logger.Println(err.Error())
77 w.Header().Set("Content-Type", "application/json")
78 http.Error(w, `{"error":"Internal server error", "message":"Something went wrong"}`, http.StatusInternalServerError)
79}
80
81func (h *Handler) notFound(w http.ResponseWriter, err error) {
82 h.logger.Println(err.Error())
83 w.Header().Set("Content-Type", "application/json")
84 http.Error(w, `{"error":"Not Found","message":"I couldn't find your resource"}`, http.StatusNotFound)
85}
86
87func (h *Handler) WithCORSAll() http.Handler {
88 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
89 h.logger.Deprintf("incoming request: %s %s", r.Method, r.URL.Path)
90 w.Header().Set("Access-Control-Allow-Origin", "http://localhost:5173")
91 w.Header().Set("Access-Control-Allow-Methods", "GET, POST, OPTIONS")
92 w.Header().Set("Access-Control-Allow-Headers", "Content-Type")
93 if r.Method == "OPTIONS" {
94 w.WriteHeader(http.StatusNoContent)
95 return
96 }
97 h.router.ServeHTTP(w, r)
98 })
99}
100
101func (h *Handler) WithCORS(f func(w http.ResponseWriter, r *http.Request)) func(w http.ResponseWriter, r *http.Request) {
102 return func(w http.ResponseWriter, r *http.Request) {
103 w.Header().Set("Access-Control-Allow-Origin", "*")
104 w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
105 w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorizaton, X-Requested-With, Sec-WebSocket-Protocol, Sec-WebSocket-Extensions, Sec-WebSocket-Key, Sec-WebSocket-Version")
106 w.Header().Set("Access-Control-Allow-Credentials", "true")
107 if r.Method == "Options" {
108 w.WriteHeader(http.StatusOK)
109 return
110 }
111 f(w, r)
112 }
113}
114
115func (h *Handler) Serve() http.Handler {
116 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
117 h.logger.Deprintf("incoming request: %s %s", r.Method, r.URL.Path)
118 h.router.ServeHTTP(w, r)
119 })
120}