From a9f64f74fb444b460cc592d8873edcdcda4dec93 Mon Sep 17 00:00:00 2001 From: Daniel Ponte Date: Mon, 20 Jan 2025 16:31:43 -0500 Subject: [PATCH] wip --- pkg/rest/api.go | 52 +++++++++++++++++++------------- pkg/rest/calls.go | 9 ++++-- pkg/rest/incidents.go | 11 ++++--- pkg/rest/share.go | 69 +++++++++++++++++++++++++++++-------------- pkg/server/routes.go | 12 +++++++- pkg/server/server.go | 2 +- 6 files changed, 104 insertions(+), 51 deletions(-) diff --git a/pkg/rest/api.go b/pkg/rest/api.go index 500ab65..6b0c7ee 100644 --- a/pkg/rest/api.go +++ b/pkg/rest/api.go @@ -18,52 +18,64 @@ import ( "github.com/rs/zerolog/log" ) +type APIRoot interface { + API + Shares() ShareAPI + ShareSubroutes(chi.Router) +} + type API interface { Subrouter() http.Handler } -type PublicAPI interface { +type ShareableAPI interface { API - PublicRoutes(r chi.Router) + GETSubroutes(chi.Router) } type api struct { - baseURL url.URL - share publicAPI + baseURL *url.URL + shares ShareAPI + tgs API + calls ShareableAPI + users API + incidents ShareableAPI } -type publicAPI interface { - API - PublicRouter() http.Handler +func (a *api) Shares() ShareAPI { + return a.shares } func New(baseURL url.URL) *api { s := &api{ - baseURL: baseURL, + baseURL: &baseURL, + shares: newShareAPI(&baseURL), + tgs: new(talkgroupAPI), + calls: new(callsAPI), + incidents: newIncidentsAPI(&baseURL), + users: new(usersAPI), } return s } -func (a *api) PublicRoutes(r chi.Router) { - r.Mount("/share", a.share.PublicRouter()) -} - func (a *api) Subrouter() http.Handler { r := chi.NewMux() - r.Mount("/talkgroup", new(talkgroupAPI).Subrouter()) - r.Mount("/user", new(usersAPI).Subrouter()) - r.Mount("/call", new(callsAPI).Subrouter()) - r.Mount("/incident", newIncidentsAPI(&a.baseURL).Subrouter()) - - a.share = newShareAPI(&a.baseURL, r) - - r.Mount("/share", a.share.Subrouter()) + r.Mount("/talkgroup", a.tgs.Subrouter()) + r.Mount("/user", a.users.Subrouter()) + r.Mount("/call", a.calls.Subrouter()) + r.Mount("/incident", a.incidents.Subrouter()) + r.Mount("/share", a.shares.Subrouter()) return r } +func (a *api) ShareSubroutes(r chi.Router) { + r.Route("/calls", a.calls.GETSubroutes) + r.Route("/incidents", a.incidents.GETSubroutes) +} + type errResponse struct { Err error `json:"-"` Code int `json:"-"` diff --git a/pkg/rest/calls.go b/pkg/rest/calls.go index bc930fd..e6b411b 100644 --- a/pkg/rest/calls.go +++ b/pkg/rest/calls.go @@ -30,14 +30,17 @@ type callsAPI struct { func (ca *callsAPI) Subrouter() http.Handler { r := chi.NewMux() - r.Get(`/{call:[a-f0-9-]+}`, ca.getAudio) - r.Get(`/{call:[a-f0-9-]+}/{download:download}`, ca.getAudio) - + ca.GETSubroutes(r) r.Post(`/`, ca.listCalls) return r } +func (ca *callsAPI) GETSubroutes(r chi.Router) { + r.Get(`/{call:[a-f0-9-]+}`, ca.getAudio) + r.Get(`/{call:[a-f0-9-]+}/{download:download}`, ca.getAudio) +} + func (ca *callsAPI) getAudio(w http.ResponseWriter, r *http.Request) { p := struct { CallID *uuid.UUID `param:"call"` diff --git a/pkg/rest/incidents.go b/pkg/rest/incidents.go index 7488aeb..802d256 100644 --- a/pkg/rest/incidents.go +++ b/pkg/rest/incidents.go @@ -22,16 +22,14 @@ type incidentsAPI struct { baseURL *url.URL } -func newIncidentsAPI(baseURL *url.URL) API { +func newIncidentsAPI(baseURL *url.URL) ShareableAPI { return &incidentsAPI{baseURL} } func (ia *incidentsAPI) Subrouter() http.Handler { r := chi.NewMux() - r.Get(`/{id:[a-f0-9-]+}`, ia.getIncident) - r.Get(`/{id:[a-f0-9-]+}.m3u`, ia.getCallsM3U) - + ia.GETSubroutes(r) r.Post(`/new`, ia.createIncident) r.Post(`/`, ia.listIncidents) r.Post(`/{id:[a-f0-9-]+}/calls`, ia.postCalls) @@ -43,6 +41,11 @@ func (ia *incidentsAPI) Subrouter() http.Handler { return r } +func (ia *incidentsAPI) GETSubroutes(r chi.Router) { + r.Get(`/{id:[a-f0-9-]+}`, ia.getIncident) + r.Get(`/{id:[a-f0-9-]+}.m3u`, ia.getCallsM3U) +} + func (ia *incidentsAPI) listIncidents(w http.ResponseWriter, r *http.Request) { ctx := r.Context() incs := incstore.FromCtx(ctx) diff --git a/pkg/rest/share.go b/pkg/rest/share.go index 475e85b..08f0ef1 100644 --- a/pkg/rest/share.go +++ b/pkg/rest/share.go @@ -5,6 +5,7 @@ import ( "fmt" "net/http" "net/url" + "strings" "time" "dynatron.me/x/stillbox/internal/forms" @@ -38,14 +39,36 @@ func (rt ShareRequestType) IsValid() bool { type ShareHandlers map[shares.EntityType]http.Handler type shareAPI struct { baseURL *url.URL - - router http.Handler } -func newShareAPI(baseURL *url.URL, hand http.Handler) publicAPI { +type ShareAPI interface { + API + ShareMiddleware() func(http.Handler) http.Handler +} + +func newShareAPI(baseURL *url.URL) ShareAPI { return &shareAPI{ baseURL: baseURL, - router: hand, + } +} + +func (a *shareAPI) ShareMiddleware() func(http.Handler) http.Handler { + return func(next http.Handler) http.Handler { + fn := func(w http.ResponseWriter, r *http.Request) { + if !strings.HasPrefix(r.URL.Path, "/share/") { + next.ServeHTTP(w, r) + return + } + nr, err := a.getShare(r) + if err != nil { + wErr(w, r, autoError(err)) + return + } + + next.ServeHTTP(w, nr) + } + + return http.HandlerFunc(fn) } } @@ -58,14 +81,10 @@ func (sa *shareAPI) Subrouter() http.Handler { return r } -func (sa *shareAPI) PublicRouter() http.Handler { - r := chi.NewMux() - - r.Get(`/{type}/{id:[A-Za-z0-9_-]{20,}}`, sa.getShare) - r.Get(`/{type}/{id:[A-Za-z0-9_-]{20,}}*`, sa.getShare) - - return r -} +//func (sa *shareAPI) PublicRoutes(r chi.Router) http.Handler { +// r.Get(`/{type}/{id:[A-Za-z0-9_-]{20,}}`, sa.getShare) +// r.Get(`/{type}/{id:[A-Za-z0-9_-]{20,}}*`, sa.getShare) +//} func (sa *shareAPI) createShare(w http.ResponseWriter, r *http.Request) { ctx := r.Context() @@ -91,28 +110,34 @@ func (sa *shareAPI) createShare(w http.ResponseWriter, r *http.Request) { func (sa *shareAPI) deleteShare(w http.ResponseWriter, r *http.Request) { } -func (sa *shareAPI) getShare(w http.ResponseWriter, r *http.Request) { +func (sa *shareAPI) getShare(r *http.Request) (*http.Request, error) { ctx := r.Context() shs := shares.FromCtx(ctx) - rType, id, err := shareParams(w, r) + params := struct { + Type string `param:"type"` + ID string `param:"shareId"` + }{} + + err := decodeParams(¶ms, r) if err != nil { - return + return nil, err } + rType := ShareRequestType(params.Type) + id := params.ID + if !rType.IsValid() { - wErr(w, r, autoError(ErrBadShare)) + return nil, ErrBadShare } sh, err := shs.GetShare(ctx, id) if err != nil { - wErr(w, r, autoError(err)) - return + return nil, err } if sh.Expiration != nil && sh.Expiration.Time().Before(time.Now()) { - wErr(w, r, autoError(shares.ErrNoShare)) - return + return nil, shares.ErrNoShare } ctx = rbac.CtxWithSubject(ctx, sh) @@ -120,12 +145,12 @@ func (sa *shareAPI) getShare(w http.ResponseWriter, r *http.Request) { switch rType { case ShareRequestCall, ShareRequestIncident: - r.URL.Path = fmt.Sprintf("/%s/%s", rType, sh.EntityID.String()) + r.URL.Path += fmt.Sprintf("/%s/%s", rType, sh.EntityID.String()) case ShareRequestIncidentM3U: r.URL.Path = fmt.Sprintf("/incident/%s.m3u", sh.EntityID.String()) } - sa.router.ServeHTTP(w, r) + return r, nil } // idOnlyParam checks for a sole URL parameter, id, and writes an errorif this fails. diff --git a/pkg/server/routes.go b/pkg/server/routes.go index 094de1c..9d4ae96 100644 --- a/pkg/server/routes.go +++ b/pkg/server/routes.go @@ -2,6 +2,7 @@ package server import ( "errors" + "fmt" "io/fs" "net/http" "path" @@ -30,6 +31,7 @@ func (s *Server) setupRoutes() { s.installPprof() + r.Group(func(r chi.Router) { // authenticated routes r.Use(s.auth.VerifyMiddleware(), s.auth.AuthMiddleware()) @@ -39,6 +41,11 @@ func (s *Server) setupRoutes() { r.Mount("/api", s.rest.Subrouter()) }) + r.Route("/share/{type}/{shareId:[A-Za-z0-9_-]{20,}}", func(r chi.Router) { + r.Use(s.rest.Shares().ShareMiddleware()) + s.rest.ShareSubroutes(r) + }) + r.Group(func(r chi.Router) { s.rateLimit(r) r.Use(render.SetContentType(render.ContentTypeJSON)) @@ -51,7 +58,6 @@ func (s *Server) setupRoutes() { s.rateLimit(r) r.Use(render.SetContentType(render.ContentTypeJSON)) s.auth.PublicRoutes(r) - s.rest.PublicRoutes(r) }) r.Group(func(r chi.Router) { @@ -62,6 +68,10 @@ func (s *Server) setupRoutes() { s.clientRoute(r, clientRoot) }) + chi.Walk(r, func(method string, route string, handler http.Handler, middlewares ...func(http.Handler) http.Handler) error { + fmt.Printf("[%s]: '%s' has %d middlewares\n", method, route, len(middlewares)) + return nil +}) } // WithCtxStores is a middleware that installs all stores in the request context. diff --git a/pkg/server/server.go b/pkg/server/server.go index aa05bc5..a18aec4 100644 --- a/pkg/server/server.go +++ b/pkg/server/server.go @@ -45,7 +45,7 @@ type Server struct { notifier notify.Notifier hup chan os.Signal tgs tgstore.Store - rest rest.PublicAPI + rest rest.APIRoot partman partman.PartitionManager users users.Store calls callstore.Store