Live video on the AT Protocol
1package spxrpc
2
3import (
4 "context"
5 "net/http"
6 "time"
7
8 "github.com/labstack/echo/v4"
9 "github.com/patrickmn/go-cache"
10 "github.com/slok/go-http-metrics/middleware"
11 echomiddleware "github.com/slok/go-http-metrics/middleware/echo"
12 "github.com/streamplace/oatproxy/pkg/oatproxy"
13 "stream.place/streamplace/pkg/atproto"
14 "stream.place/streamplace/pkg/config"
15 "stream.place/streamplace/pkg/log"
16 "stream.place/streamplace/pkg/model"
17 "stream.place/streamplace/pkg/statedb"
18)
19
20type Server struct {
21 e *echo.Echo
22 cli *config.CLI
23 model model.Model
24 OGImageCache *cache.Cache
25 ATSync *atproto.ATProtoSynchronizer
26 statefulDB *statedb.StatefulDB
27}
28
29func NewServer(ctx context.Context, cli *config.CLI, model model.Model, statefulDB *statedb.StatefulDB, op *oatproxy.OATProxy, mdlw middleware.Middleware, atsync *atproto.ATProtoSynchronizer) (*Server, error) {
30 e := echo.New()
31 s := &Server{
32 e: e,
33 cli: cli,
34 model: model,
35 OGImageCache: cache.New(5*time.Minute, 10*time.Minute), // 5min TTL, 10min cleanup
36 ATSync: atsync,
37 statefulDB: statefulDB,
38 }
39 e.Use(s.ErrorHandlingMiddleware())
40 e.Use(s.ContextPreservingMiddleware())
41 e.Use(echomiddleware.Handler("", mdlw))
42 e.Use(op.OAuthMiddleware)
43 err := s.RegisterHandlersPlaceStream(e)
44 if err != nil {
45 return nil, err
46 }
47 err = s.RegisterHandlersAppBsky(e)
48 if err != nil {
49 return nil, err
50 }
51 err = s.RegisterHandlersComAtproto(e)
52 if err != nil {
53 return nil, err
54 }
55 e.GET("/xrpc/_health", func(c echo.Context) error {
56 return c.JSON(http.StatusOK, map[string]string{"version": cli.Build.Version})
57 })
58 e.GET("/xrpc/com.atproto.sync.subscribeRepos", s.handleComAtprotoSyncSubscribeRepos)
59 e.GET("/xrpc/*", s.HandleWildcard)
60 e.POST("/xrpc/*", s.HandleWildcard)
61 return s, nil
62}
63
64func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
65 s.e.ServeHTTP(w, r)
66}
67
68func (s *Server) ErrorHandlingMiddleware() echo.MiddlewareFunc {
69 return func(next echo.HandlerFunc) echo.HandlerFunc {
70 return func(c echo.Context) error {
71 err := next(c)
72 if err == nil {
73 return nil
74 }
75 httpError, ok := err.(*echo.HTTPError)
76 if ok {
77 log.Error(c.Request().Context(), "http error", "code", httpError.Code, "message", httpError.Message, "internal", httpError.Internal)
78 return err
79 }
80 log.Error(c.Request().Context(), "unhandled error", "error", err)
81 return echo.NewHTTPError(http.StatusInternalServerError, err.Error())
82 }
83 }
84}
85
86// unique type to prevent assignment.
87type echoContextKeyType struct{}
88
89// singleton value to identify our logging metadata in context
90var echoContextKey = echoContextKeyType{}
91
92func (s *Server) ContextPreservingMiddleware() echo.MiddlewareFunc {
93 return func(next echo.HandlerFunc) echo.HandlerFunc {
94 return func(c echo.Context) error {
95 ctx := c.Request().Context()
96 if ctx == nil {
97 ctx = context.Background()
98 }
99 ctx = context.WithValue(ctx, echoContextKey, c)
100 c.SetRequest(c.Request().WithContext(ctx))
101 return next(c)
102 }
103 }
104}