package rest import ( "errors" "net/http" "net/url" "time" "dynatron.me/x/stillbox/internal/forms" "dynatron.me/x/stillbox/pkg/rbac" "dynatron.me/x/stillbox/pkg/shares" "github.com/go-chi/chi/v5" "github.com/google/uuid" ) var ( ErrBadShare = errors.New("bad share request type") ) type ShareRequestType string const ( ShareRequestCall ShareRequestType = "call" ShareRequestCallDL ShareRequestType = "callDL" ShareRequestIncident ShareRequestType = "incident" ShareRequestIncidentM3U ShareRequestType = "m3u" ) func (rt ShareRequestType) IsValid() bool { switch rt { case ShareRequestCall, ShareRequestCallDL, ShareRequestIncident, ShareRequestIncidentM3U: return true } return false } func (rt ShareRequestType) IsValidSubtype() bool { switch rt { case ShareRequestCall, ShareRequestCallDL: return true } return false } type HandlerFunc func(id uuid.UUID, share *shares.Share, w http.ResponseWriter, r *http.Request) type ShareHandlers map[ShareRequestType]HandlerFunc type shareAPI struct { baseURL *url.URL shnd ShareHandlers } func newShareAPI(baseURL *url.URL, shnd ShareHandlers) *shareAPI { return &shareAPI{ baseURL: baseURL, shnd: shnd, } } func (sa *shareAPI) Subrouter() http.Handler { r := chi.NewMux() r.Post(`/create`, sa.createShare) r.Delete(`/{id:[A-Za-z0-9_-]{20,}}`, sa.deleteShare) return r } func (sa *shareAPI) RootRouter() http.Handler { r := chi.NewMux() r.Get("/{type}/{shareId:[A-Za-z0-9_-]{20,}}", sa.routeShare) r.Get("/{type}/{shareId:[A-Za-z0-9_-]{20,}}/{subType}/{subID}", sa.routeShare) return r } func (sa *shareAPI) createShare(w http.ResponseWriter, r *http.Request) { ctx := r.Context() shs := shares.FromCtx(ctx) p := shares.CreateShareParams{} err := forms.Unmarshal(r, &p, forms.WithTag("json"), forms.WithAcceptBlank(), forms.WithOmitEmpty()) if err != nil { wErr(w, r, badRequest(err)) return } sh, err := shs.NewShare(ctx, p) if err != nil { wErr(w, r, autoError(err)) return } respond(w, r, sh) } func (sa *shareAPI) routeShare(w http.ResponseWriter, r *http.Request) { ctx := r.Context() shs := shares.FromCtx(ctx) params := struct { Type string `param:"type"` ID string `param:"shareId"` SubType *string `param:"subType"` SubID *string `param:"subID"` }{} err := decodeParams(¶ms, r) if err != nil { wErr(w, r, autoError(err)) return } rType := ShareRequestType(params.Type) id := params.ID if !rType.IsValid() { wErr(w, r, autoError(ErrBadShare)) return } sh, err := shs.GetShare(ctx, id) if err != nil { wErr(w, r, autoError(err)) return } if sh.Expiration != nil && sh.Expiration.Time().Before(time.Now()) { wErr(w, r, autoError(shares.ErrNoShare)) return } ctx = rbac.CtxWithSubject(ctx, sh) r = r.WithContext(ctx) if params.SubType != nil { if params.SubID == nil { // probably can't happen wErr(w, r, autoError(ErrBadShare)) return } subT := ShareRequestType(*params.SubType) if !subT.IsValidSubtype() { wErr(w, r, autoError(ErrBadShare)) return } subIDU, err := uuid.Parse(*params.SubID) if err != nil { wErr(w, r, badRequest(err)) return } sa.shnd[subT](subIDU, sh, w, r) return } sa.shnd[rType](sh.EntityID, sh, w, r) } func (sa *shareAPI) deleteShare(w http.ResponseWriter, r *http.Request) { }