Get rid of using headers to identify share type

This commit is contained in:
Daniel Ponte 2025-02-01 14:28:01 -05:00
parent 493913112d
commit 1232b6887a
8 changed files with 96 additions and 48 deletions

View file

@ -145,7 +145,7 @@ func (s *store) CallAudio(ctx context.Context, id uuid.UUID) (*calls.CallAudio,
} }
func (s *store) Call(ctx context.Context, id uuid.UUID) (*calls.Call, error) { func (s *store) Call(ctx context.Context, id uuid.UUID) (*calls.Call, error) {
_, err := rbac.Check(ctx, rbac.UseResource(entities.ResourceCall), rbac.WithActions(entities.ActionRead)) _, err := rbac.Check(ctx, &calls.Call{ID: id}, rbac.WithActions(entities.ActionRead))
if err != nil { if err != nil {
return nil, err return nil, err
} }

View file

@ -51,8 +51,9 @@ func New(baseURL url.URL) *api {
s.shares = newShareAPI(&baseURL, s.shares = newShareAPI(&baseURL,
ShareHandlers{ ShareHandlers{
ShareRequestCall: s.calls.shareCallRoute, ShareRequestCall: s.calls.shareCallRoute,
ShareRequestCallInfo: respondShareHandler(s.calls.getCallInfo),
ShareRequestCallDL: s.calls.shareCallDLRoute, ShareRequestCallDL: s.calls.shareCallDLRoute,
ShareRequestIncident: s.incidents.getIncident, ShareRequestIncident: respondShareHandler(s.incidents.getIncident),
ShareRequestIncidentM3U: s.incidents.getCallsM3U, ShareRequestIncidentM3U: s.incidents.getCallsM3U,
ShareRequestTalkgroups: s.tgs.getTGsShareRoute, ShareRequestTalkgroups: s.tgs.getTGsShareRoute,
}, },

View file

@ -1,6 +1,7 @@
package rest package rest
import ( import (
"context"
"errors" "errors"
"fmt" "fmt"
"mime" "mime"
@ -11,7 +12,6 @@ import (
"dynatron.me/x/stillbox/internal/forms" "dynatron.me/x/stillbox/internal/forms"
"dynatron.me/x/stillbox/pkg/calls/callstore" "dynatron.me/x/stillbox/pkg/calls/callstore"
"dynatron.me/x/stillbox/pkg/database" "dynatron.me/x/stillbox/pkg/database"
"dynatron.me/x/stillbox/pkg/shares"
"github.com/go-chi/chi/v5" "github.com/go-chi/chi/v5"
"github.com/google/uuid" "github.com/google/uuid"
@ -102,7 +102,12 @@ func (ca *callsAPI) getAudio(p getAudioParams, w http.ResponseWriter, r *http.Re
_, _ = w.Write(call.AudioBlob) _, _ = w.Write(call.AudioBlob)
} }
func (ca *callsAPI) shareCallRoute(id ID, _ *shares.Share, w http.ResponseWriter, r *http.Request) { func (ca *callsAPI) getCallInfo(ctx context.Context, id ID) (SharedItem, error) {
cs := callstore.FromCtx(ctx)
return cs.Call(ctx, id.(uuid.UUID))
}
func (ca *callsAPI) shareCallRoute(id ID, w http.ResponseWriter, r *http.Request) {
p := getAudioParams{ p := getAudioParams{
CallID: common.PtrTo(id.(uuid.UUID)), CallID: common.PtrTo(id.(uuid.UUID)),
} }
@ -110,7 +115,7 @@ func (ca *callsAPI) shareCallRoute(id ID, _ *shares.Share, w http.ResponseWriter
ca.getAudio(p, w, r) ca.getAudio(p, w, r)
} }
func (ca *callsAPI) shareCallDLRoute(id ID, _ *shares.Share, w http.ResponseWriter, r *http.Request) { func (ca *callsAPI) shareCallDLRoute(id ID, w http.ResponseWriter, r *http.Request) {
p := getAudioParams{ p := getAudioParams{
CallID: common.PtrTo(id.(uuid.UUID)), CallID: common.PtrTo(id.(uuid.UUID)),
Download: common.PtrTo("download"), Download: common.PtrTo("download"),

View file

@ -2,6 +2,7 @@ package rest
import ( import (
"bytes" "bytes"
"context"
"encoding/json" "encoding/json"
"fmt" "fmt"
"net/http" "net/http"
@ -12,7 +13,6 @@ import (
"dynatron.me/x/stillbox/internal/jsontypes" "dynatron.me/x/stillbox/internal/jsontypes"
"dynatron.me/x/stillbox/pkg/incidents" "dynatron.me/x/stillbox/pkg/incidents"
"dynatron.me/x/stillbox/pkg/incidents/incstore" "dynatron.me/x/stillbox/pkg/incidents/incstore"
"dynatron.me/x/stillbox/pkg/shares"
"dynatron.me/x/stillbox/pkg/talkgroups/tgstore" "dynatron.me/x/stillbox/pkg/talkgroups/tgstore"
"github.com/go-chi/chi/v5" "github.com/go-chi/chi/v5"
@ -91,22 +91,22 @@ func (ia *incidentsAPI) createIncident(w http.ResponseWriter, r *http.Request) {
func (ia *incidentsAPI) getIncidentRoute(w http.ResponseWriter, r *http.Request) { func (ia *incidentsAPI) getIncidentRoute(w http.ResponseWriter, r *http.Request) {
id, err := idOnlyParam(w, r) id, err := idOnlyParam(w, r)
if err != nil { if err != nil {
wErr(w, r, autoError(err))
return return
} }
ia.getIncident(id, nil, w, r) e, err := ia.getIncident(r.Context(), id)
}
func (ia *incidentsAPI) getIncident(id ID, share *shares.Share, w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
incs := incstore.FromCtx(ctx)
inc, err := incs.Incident(ctx, id.(uuid.UUID))
if err != nil { if err != nil {
wErr(w, r, autoError(err)) wErr(w, r, autoError(err))
return return
} }
respond(w, r, inc) respond(w, r, e)
}
func (ia *incidentsAPI) getIncident(ctx context.Context, id ID) (SharedItem, error) {
incs := incstore.FromCtx(ctx)
return incs.Incident(ctx, id.(uuid.UUID))
} }
func (ia *incidentsAPI) updateIncident(w http.ResponseWriter, r *http.Request) { func (ia *incidentsAPI) updateIncident(w http.ResponseWriter, r *http.Request) {
@ -195,10 +195,10 @@ func (ia *incidentsAPI) getCallsM3URoute(w http.ResponseWriter, r *http.Request)
return return
} }
ia.getCallsM3U(id, nil, w, r) ia.getCallsM3U(id, w, r)
} }
func (ia *incidentsAPI) getCallsM3U(id ID, share *shares.Share, w http.ResponseWriter, r *http.Request) { func (ia *incidentsAPI) getCallsM3U(id ID, w http.ResponseWriter, r *http.Request) {
ctx := r.Context() ctx := r.Context()
incs := incstore.FromCtx(ctx) incs := incstore.FromCtx(ctx)
tgst := tgstore.FromCtx(ctx) tgst := tgstore.FromCtx(ctx)
@ -214,6 +214,7 @@ func (ia *incidentsAPI) getCallsM3U(id ID, share *shares.Share, w http.ResponseW
callUrl := common.PtrTo(*ia.baseURL) callUrl := common.PtrTo(*ia.baseURL)
urlRoot := "/api/call" urlRoot := "/api/call"
filename := inc.PlaylistFilename() filename := inc.PlaylistFilename()
share := ShareFrom(ctx)
if share != nil { if share != nil {
urlRoot = fmt.Sprintf("/share/%s/call/", share.ID) urlRoot = fmt.Sprintf("/share/%s/call/", share.ID)
filename += "_" + share.ID filename += "_" + share.ID

View file

@ -1,6 +1,7 @@
package rest package rest
import ( import (
"context"
"errors" "errors"
"net/http" "net/http"
"net/url" "net/url"
@ -23,6 +24,7 @@ type ShareRequestType string
const ( const (
ShareRequestCall ShareRequestType = "call" ShareRequestCall ShareRequestType = "call"
ShareRequestCallInfo ShareRequestType = "callinfo"
ShareRequestCallDL ShareRequestType = "callDL" ShareRequestCallDL ShareRequestType = "callDL"
ShareRequestIncident ShareRequestType = "incident" ShareRequestIncident ShareRequestType = "incident"
ShareRequestIncidentM3U ShareRequestType = "m3u" ShareRequestIncidentM3U ShareRequestType = "m3u"
@ -31,7 +33,7 @@ const (
func (rt ShareRequestType) IsValid() bool { func (rt ShareRequestType) IsValid() bool {
switch rt { switch rt {
case ShareRequestCall, ShareRequestCallDL, ShareRequestIncident, case ShareRequestCall, ShareRequestCallInfo, ShareRequestCallDL, ShareRequestIncident,
ShareRequestIncidentM3U, ShareRequestTalkgroups: ShareRequestIncidentM3U, ShareRequestTalkgroups:
return true return true
} }
@ -39,25 +41,59 @@ func (rt ShareRequestType) IsValid() bool {
return false return false
} }
func (rt ShareRequestType) IsValidSubtype() bool {
switch rt {
case ShareRequestCall, ShareRequestCallDL, ShareRequestTalkgroups:
return true
}
return false
}
type ID interface { type ID interface {
} }
type HandlerFunc func(id ID, share *shares.Share, w http.ResponseWriter, r *http.Request) type ShareHandlerFunc func(id ID, w http.ResponseWriter, r *http.Request)
type ShareHandlers map[ShareRequestType]HandlerFunc type ShareHandlers map[ShareRequestType]ShareHandlerFunc
type shareAPI struct { type shareAPI struct {
baseURL *url.URL baseURL *url.URL
shnd ShareHandlers shnd ShareHandlers
} }
type EntityFunc func(ctx context.Context, id ID) (SharedItem, error)
type SharedItem interface {
}
type shareResponse struct {
ID ID `json:"id"`
Type shares.EntityType `json:"type"`
Item SharedItem `json:"item,omitempty"`
}
func ShareFrom(ctx context.Context) *shares.Share {
if share, hasShare := entities.SubjectFrom(ctx).(*shares.Share); hasShare {
return share
}
return nil
}
func respondShareHandler(ie EntityFunc) ShareHandlerFunc {
return func(id ID, w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
share := ShareFrom(ctx)
if share == nil {
wErr(w, r, autoError(ErrBadShare))
return
}
res, err := ie(r.Context(), id)
if err != nil {
wErr(w, r, autoError(err))
return
}
sRes := shareResponse{
ID: id,
Type: share.Type,
Item: res,
}
respond(w, r, sRes)
}
}
func newShareAPI(baseURL *url.URL, shnd ShareHandlers) *shareAPI { func newShareAPI(baseURL *url.URL, shnd ShareHandlers) *shareAPI {
return &shareAPI{ return &shareAPI{
baseURL: baseURL, baseURL: baseURL,
@ -135,12 +171,11 @@ func (sa *shareAPI) routeShare(w http.ResponseWriter, r *http.Request) {
} else { } else {
switch sh.Type { switch sh.Type {
case shares.EntityCall: case shares.EntityCall:
rType = ShareRequestCall rType = ShareRequestCallInfo
params.SubID = common.PtrTo(sh.EntityID.String()) params.SubID = common.PtrTo(sh.EntityID.String())
case shares.EntityIncident: case shares.EntityIncident:
rType = ShareRequestIncident rType = ShareRequestIncident
} }
w.Header().Set("X-Share-Type", string(rType))
} }
if !rType.IsValid() { if !rType.IsValid() {
@ -158,21 +193,22 @@ func (sa *shareAPI) routeShare(w http.ResponseWriter, r *http.Request) {
switch rType { switch rType {
case ShareRequestTalkgroups: case ShareRequestTalkgroups:
sa.shnd[rType](nil, sh, w, r) sa.shnd[rType](nil, w, r)
case ShareRequestCall, ShareRequestCallDL: case ShareRequestCall, ShareRequestCallInfo, ShareRequestCallDL:
if params.SubID == nil { var subIDU uuid.UUID
wErr(w, r, autoError(ErrBadShare)) if params.SubID != nil {
return subIDU, err = uuid.Parse(*params.SubID)
}
subIDU, err := uuid.Parse(*params.SubID)
if err != nil { if err != nil {
wErr(w, r, badRequest(err)) wErr(w, r, badRequest(err))
return return
} }
sa.shnd[rType](subIDU, sh, w, r) } else {
subIDU = sh.EntityID
}
sa.shnd[rType](subIDU, w, r)
case ShareRequestIncident, ShareRequestIncidentM3U: case ShareRequestIncident, ShareRequestIncidentM3U:
sa.shnd[rType](sh.EntityID, sh, w, r) sa.shnd[rType](sh.EntityID, w, r)
} }
} }

View file

@ -8,7 +8,6 @@ import (
"dynatron.me/x/stillbox/internal/forms" "dynatron.me/x/stillbox/internal/forms"
"dynatron.me/x/stillbox/pkg/database" "dynatron.me/x/stillbox/pkg/database"
"dynatron.me/x/stillbox/pkg/incidents/incstore" "dynatron.me/x/stillbox/pkg/incidents/incstore"
"dynatron.me/x/stillbox/pkg/shares"
"dynatron.me/x/stillbox/pkg/talkgroups" "dynatron.me/x/stillbox/pkg/talkgroups"
"dynatron.me/x/stillbox/pkg/talkgroups/tgstore" "dynatron.me/x/stillbox/pkg/talkgroups/tgstore"
"dynatron.me/x/stillbox/pkg/talkgroups/xport" "dynatron.me/x/stillbox/pkg/talkgroups/xport"
@ -161,10 +160,16 @@ func (tga *talkgroupAPI) postPaginated(w http.ResponseWriter, r *http.Request) {
respond(w, r, res) respond(w, r, res)
} }
func (tga *talkgroupAPI) getTGsShareRoute(_ ID, share *shares.Share, w http.ResponseWriter, r *http.Request) { func (tga *talkgroupAPI) getTGsShareRoute(_ ID, w http.ResponseWriter, r *http.Request) {
ctx := r.Context() ctx := r.Context()
tgs := tgstore.FromCtx(ctx) tgs := tgstore.FromCtx(ctx)
share := ShareFrom(ctx)
if share == nil {
wErr(w, r, autoError(ErrBadShare))
return
}
tgIDs, err := incstore.FromCtx(ctx).TGsIn(ctx, share.EntityID) tgIDs, err := incstore.FromCtx(ctx).TGsIn(ctx, share.EntityID)
if err != nil { if err != nil {
wErr(w, r, autoError(err)) wErr(w, r, autoError(err))