TG share
This commit is contained in:
parent
76a2214377
commit
f1a5f70a79
12 changed files with 201 additions and 85 deletions
|
@ -53,40 +53,6 @@ func (q *Queries) DeleteShare(ctx context.Context, id string) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
const getIncidentTalkgroups = `-- name: GetIncidentTalkgroups :many
|
|
||||||
SELECT DISTINCT
|
|
||||||
c.system,
|
|
||||||
c.talkgroup
|
|
||||||
FROM incidents_calls ic
|
|
||||||
JOIN calls c ON (c.id = ic.call_id AND c.call_date = ic.call_date)
|
|
||||||
WHERE ic.incident_id = $1
|
|
||||||
`
|
|
||||||
|
|
||||||
type GetIncidentTalkgroupsRow struct {
|
|
||||||
System int `json:"system"`
|
|
||||||
Talkgroup int `json:"talkgroup"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (q *Queries) GetIncidentTalkgroups(ctx context.Context, incidentID uuid.UUID) ([]GetIncidentTalkgroupsRow, error) {
|
|
||||||
rows, err := q.db.Query(ctx, getIncidentTalkgroups, incidentID)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer rows.Close()
|
|
||||||
var items []GetIncidentTalkgroupsRow
|
|
||||||
for rows.Next() {
|
|
||||||
var i GetIncidentTalkgroupsRow
|
|
||||||
if err := rows.Scan(&i.System, &i.Talkgroup); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
items = append(items, i)
|
|
||||||
}
|
|
||||||
if err := rows.Err(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return items, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
const getShare = `-- name: GetShare :one
|
const getShare = `-- name: GetShare :one
|
||||||
SELECT
|
SELECT
|
||||||
id,
|
id,
|
||||||
|
|
|
@ -10,6 +10,7 @@ import (
|
||||||
|
|
||||||
"dynatron.me/x/stillbox/internal/jsontypes"
|
"dynatron.me/x/stillbox/internal/jsontypes"
|
||||||
"dynatron.me/x/stillbox/pkg/alerting/rules"
|
"dynatron.me/x/stillbox/pkg/alerting/rules"
|
||||||
|
"github.com/google/uuid"
|
||||||
)
|
)
|
||||||
|
|
||||||
const addLearnedTalkgroup = `-- name: AddLearnedTalkgroup :one
|
const addLearnedTalkgroup = `-- name: AddLearnedTalkgroup :one
|
||||||
|
@ -117,6 +118,40 @@ func (q *Queries) GetAllTalkgroupTags(ctx context.Context) ([]string, error) {
|
||||||
return items, nil
|
return items, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getIncidentTalkgroups = `-- name: GetIncidentTalkgroups :many
|
||||||
|
SELECT DISTINCT
|
||||||
|
c.system,
|
||||||
|
c.talkgroup
|
||||||
|
FROM incidents_calls ic
|
||||||
|
JOIN calls c ON (c.id = ic.call_id AND c.call_date = ic.call_date)
|
||||||
|
WHERE ic.incident_id = $1
|
||||||
|
`
|
||||||
|
|
||||||
|
type GetIncidentTalkgroupsRow struct {
|
||||||
|
System int `json:"system"`
|
||||||
|
Talkgroup int `json:"talkgroup"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) GetIncidentTalkgroups(ctx context.Context, incidentID uuid.UUID) ([]GetIncidentTalkgroupsRow, error) {
|
||||||
|
rows, err := q.db.Query(ctx, getIncidentTalkgroups, incidentID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
var items []GetIncidentTalkgroupsRow
|
||||||
|
for rows.Next() {
|
||||||
|
var i GetIncidentTalkgroupsRow
|
||||||
|
if err := rows.Scan(&i.System, &i.Talkgroup); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
items = append(items, i)
|
||||||
|
}
|
||||||
|
if err := rows.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return items, nil
|
||||||
|
}
|
||||||
|
|
||||||
const getSystemName = `-- name: GetSystemName :one
|
const getSystemName = `-- name: GetSystemName :one
|
||||||
SELECT name FROM systems WHERE id = $1
|
SELECT name FROM systems WHERE id = $1
|
||||||
`
|
`
|
||||||
|
|
|
@ -11,6 +11,7 @@ import (
|
||||||
"dynatron.me/x/stillbox/pkg/incidents"
|
"dynatron.me/x/stillbox/pkg/incidents"
|
||||||
"dynatron.me/x/stillbox/pkg/rbac"
|
"dynatron.me/x/stillbox/pkg/rbac"
|
||||||
"dynatron.me/x/stillbox/pkg/rbac/entities"
|
"dynatron.me/x/stillbox/pkg/rbac/entities"
|
||||||
|
"dynatron.me/x/stillbox/pkg/talkgroups"
|
||||||
"dynatron.me/x/stillbox/pkg/users"
|
"dynatron.me/x/stillbox/pkg/users"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/jackc/pgx/v5"
|
"github.com/jackc/pgx/v5"
|
||||||
|
@ -53,9 +54,12 @@ type Store interface {
|
||||||
|
|
||||||
// CallIn returns whether an incident is in an call
|
// CallIn returns whether an incident is in an call
|
||||||
CallIn(ctx context.Context, inc uuid.UUID, call uuid.UUID) (bool, error)
|
CallIn(ctx context.Context, inc uuid.UUID, call uuid.UUID) (bool, error)
|
||||||
|
|
||||||
|
// TGsIn returns the talkgroups referenced by an incident as a map, primary for rbac use.
|
||||||
|
TGsIn(ctx context.Context, inc uuid.UUID) (talkgroups.PresenceMap, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type store struct {
|
type postgresStore struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type storeCtxKey string
|
type storeCtxKey string
|
||||||
|
@ -76,10 +80,10 @@ func FromCtx(ctx context.Context) Store {
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewStore() Store {
|
func NewStore() Store {
|
||||||
return &store{}
|
return &postgresStore{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *store) CreateIncident(ctx context.Context, inc incidents.Incident) (*incidents.Incident, error) {
|
func (s *postgresStore) CreateIncident(ctx context.Context, inc incidents.Incident) (*incidents.Incident, error) {
|
||||||
user, err := users.UserCheck(ctx, new(incidents.Incident), "create")
|
user, err := users.UserCheck(ctx, new(incidents.Incident), "create")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -138,7 +142,7 @@ func (s *store) CreateIncident(ctx context.Context, inc incidents.Incident) (*in
|
||||||
return &inc, nil
|
return &inc, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *store) AddRemoveIncidentCalls(ctx context.Context, incidentID uuid.UUID, addCallIDs []uuid.UUID, notes []byte, removeCallIDs []uuid.UUID) error {
|
func (s *postgresStore) AddRemoveIncidentCalls(ctx context.Context, incidentID uuid.UUID, addCallIDs []uuid.UUID, notes []byte, removeCallIDs []uuid.UUID) error {
|
||||||
inc, err := s.Owner(ctx, incidentID)
|
inc, err := s.Owner(ctx, incidentID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -176,7 +180,7 @@ func (s *store) AddRemoveIncidentCalls(ctx context.Context, incidentID uuid.UUID
|
||||||
}, pgx.TxOptions{})
|
}, pgx.TxOptions{})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *store) Incidents(ctx context.Context, p IncidentsParams) (incs []Incident, totalCount int, err error) {
|
func (s *postgresStore) Incidents(ctx context.Context, p IncidentsParams) (incs []Incident, totalCount int, err error) {
|
||||||
_, err = rbac.Check(ctx, new(incidents.Incident), rbac.WithActions(entities.ActionRead))
|
_, err = rbac.Check(ctx, new(incidents.Incident), rbac.WithActions(entities.ActionRead))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, 0, err
|
return nil, 0, err
|
||||||
|
@ -281,7 +285,7 @@ func fromDBCalls(d []database.GetIncidentCallsRow) []incidents.IncidentCall {
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *store) Incident(ctx context.Context, id uuid.UUID) (*incidents.Incident, error) {
|
func (s *postgresStore) Incident(ctx context.Context, id uuid.UUID) (*incidents.Incident, error) {
|
||||||
_, err := rbac.Check(ctx, &incidents.Incident{ID: id}, rbac.WithActions(entities.ActionRead))
|
_, err := rbac.Check(ctx, &incidents.Incident{ID: id}, rbac.WithActions(entities.ActionRead))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -332,7 +336,7 @@ func (uip UpdateIncidentParams) toDBUIP(id uuid.UUID) database.UpdateIncidentPar
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *store) UpdateIncident(ctx context.Context, id uuid.UUID, p UpdateIncidentParams) (*incidents.Incident, error) {
|
func (s *postgresStore) UpdateIncident(ctx context.Context, id uuid.UUID, p UpdateIncidentParams) (*incidents.Incident, error) {
|
||||||
ckinc, err := s.Owner(ctx, id)
|
ckinc, err := s.Owner(ctx, id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -355,7 +359,7 @@ func (s *store) UpdateIncident(ctx context.Context, id uuid.UUID, p UpdateIncide
|
||||||
return &inc, nil
|
return &inc, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *store) DeleteIncident(ctx context.Context, id uuid.UUID) error {
|
func (s *postgresStore) DeleteIncident(ctx context.Context, id uuid.UUID) error {
|
||||||
inc, err := s.Owner(ctx, id)
|
inc, err := s.Owner(ctx, id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -369,16 +373,39 @@ func (s *store) DeleteIncident(ctx context.Context, id uuid.UUID) error {
|
||||||
return database.FromCtx(ctx).DeleteIncident(ctx, id)
|
return database.FromCtx(ctx).DeleteIncident(ctx, id)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *store) UpdateNotes(ctx context.Context, incidentID uuid.UUID, callID uuid.UUID, notes []byte) error {
|
func (s *postgresStore) UpdateNotes(ctx context.Context, incidentID uuid.UUID, callID uuid.UUID, notes []byte) error {
|
||||||
return database.FromCtx(ctx).UpdateCallIncidentNotes(ctx, notes, incidentID, callID)
|
return database.FromCtx(ctx).UpdateCallIncidentNotes(ctx, notes, incidentID, callID)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *store) Owner(ctx context.Context, id uuid.UUID) (incidents.Incident, error) {
|
func (s *postgresStore) Owner(ctx context.Context, id uuid.UUID) (incidents.Incident, error) {
|
||||||
owner, err := database.FromCtx(ctx).GetIncidentOwner(ctx, id)
|
owner, err := database.FromCtx(ctx).GetIncidentOwner(ctx, id)
|
||||||
return incidents.Incident{ID: id, Owner: users.UserID(owner)}, err
|
return incidents.Incident{ID: id, Owner: users.UserID(owner)}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *store) CallIn(ctx context.Context, inc uuid.UUID, call uuid.UUID) (bool, error) {
|
func (s *postgresStore) CallIn(ctx context.Context, inc uuid.UUID, call uuid.UUID) (bool, error) {
|
||||||
db := database.FromCtx(ctx)
|
db := database.FromCtx(ctx)
|
||||||
return db.CallInIncident(ctx, inc, call)
|
return db.CallInIncident(ctx, inc, call)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *postgresStore) TGsIn(ctx context.Context, id uuid.UUID) (talkgroups.PresenceMap, error) {
|
||||||
|
_, err := rbac.Check(ctx, &incidents.Incident{ID: id}, rbac.WithActions(entities.ActionRead))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
db := database.FromCtx(ctx)
|
||||||
|
tgs, err := db.GetIncidentTalkgroups(ctx, id)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
m := make(talkgroups.PresenceMap, len(tgs))
|
||||||
|
for _, t := range tgs {
|
||||||
|
m.Put(talkgroups.ID{
|
||||||
|
System: uint32(t.System),
|
||||||
|
Talkgroup: uint32(t.Talkgroup),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return m, nil
|
||||||
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@ import (
|
||||||
"reflect"
|
"reflect"
|
||||||
|
|
||||||
"dynatron.me/x/stillbox/pkg/incidents/incstore"
|
"dynatron.me/x/stillbox/pkg/incidents/incstore"
|
||||||
|
"dynatron.me/x/stillbox/pkg/talkgroups"
|
||||||
|
|
||||||
"github.com/el-mike/restrict/v2"
|
"github.com/el-mike/restrict/v2"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
|
@ -16,8 +17,58 @@ const (
|
||||||
SubmitterEqualConditionType = "SUBMITTER_EQUAL"
|
SubmitterEqualConditionType = "SUBMITTER_EQUAL"
|
||||||
InMapConditionType = "IN_MAP"
|
InMapConditionType = "IN_MAP"
|
||||||
CallInIncidentConditionType = "CALL_IN_INCIDENT"
|
CallInIncidentConditionType = "CALL_IN_INCIDENT"
|
||||||
|
TGInIncidentConditionType = "TG_IN_INCIDENT"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type TGInIncidentCondition struct {
|
||||||
|
ID string `json:"name,omitempty" yaml:"name,omitempty"`
|
||||||
|
TG *restrict.ValueDescriptor `json:"tg" yaml:"tg"`
|
||||||
|
Incident *restrict.ValueDescriptor `json:"incident" yaml:"incident"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*TGInIncidentCondition) Type() string {
|
||||||
|
return TGInIncidentConditionType
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *TGInIncidentCondition) Check(r *restrict.AccessRequest) error {
|
||||||
|
tgVID, err := c.TG.GetValue(r)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
incVID, err := c.Incident.GetValue(r)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, hasCtx := r.Context["ctx"].(context.Context)
|
||||||
|
if !hasCtx {
|
||||||
|
return restrict.NewConditionNotSatisfiedError(c, r, fmt.Errorf("no context provided"))
|
||||||
|
}
|
||||||
|
|
||||||
|
incID, isUUID := incVID.(uuid.UUID)
|
||||||
|
if !isUUID {
|
||||||
|
return restrict.NewConditionNotSatisfiedError(c, r, errors.New("incident ID is not UUID"))
|
||||||
|
}
|
||||||
|
|
||||||
|
tgID, isTGID := tgVID.(talkgroups.ID)
|
||||||
|
if !isTGID {
|
||||||
|
return restrict.NewConditionNotSatisfiedError(c, r, errors.New("tg ID is not TGID"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// XXX: this should instead come from the access request context, for better reuse upstream
|
||||||
|
tgm, err := incstore.FromCtx(ctx).TGsIn(ctx, incID)
|
||||||
|
if err != nil {
|
||||||
|
return restrict.NewConditionNotSatisfiedError(c, r, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !tgm.Has(tgID) {
|
||||||
|
return restrict.NewConditionNotSatisfiedError(c, r, fmt.Errorf(`tg "%v" not in incident "%v"`, tgID, incID))
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
type CallInIncidentCondition struct {
|
type CallInIncidentCondition struct {
|
||||||
ID string `json:"name,omitempty" yaml:"name,omitempty"`
|
ID string `json:"name,omitempty" yaml:"name,omitempty"`
|
||||||
Call *restrict.ValueDescriptor `json:"call" yaml:"call"`
|
Call *restrict.ValueDescriptor `json:"call" yaml:"call"`
|
||||||
|
@ -60,7 +111,7 @@ func (c *CallInIncidentCondition) Check(r *restrict.AccessRequest) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
if !inCall {
|
if !inCall {
|
||||||
return restrict.NewConditionNotSatisfiedError(c, r, fmt.Errorf(`incident "%v" not in call "%v"`, incID, callID))
|
return restrict.NewConditionNotSatisfiedError(c, r, fmt.Errorf(`call "%v" not in incident "%v"`, callID, incID))
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
|
@ -54,6 +54,7 @@ func New(baseURL url.URL) *api {
|
||||||
ShareRequestCallDL: s.calls.shareCallDLRoute,
|
ShareRequestCallDL: s.calls.shareCallDLRoute,
|
||||||
ShareRequestIncident: s.incidents.getIncident,
|
ShareRequestIncident: s.incidents.getIncident,
|
||||||
ShareRequestIncidentM3U: s.incidents.getCallsM3U,
|
ShareRequestIncidentM3U: s.incidents.getCallsM3U,
|
||||||
|
ShareRequestTalkgroups: s.tgs.getTGsShareRoute,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -102,17 +102,17 @@ 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 uuid.UUID, _ *shares.Share, w http.ResponseWriter, r *http.Request) {
|
func (ca *callsAPI) shareCallRoute(id ID, _ *shares.Share, w http.ResponseWriter, r *http.Request) {
|
||||||
p := getAudioParams{
|
p := getAudioParams{
|
||||||
CallID: &id,
|
CallID: common.PtrTo(id.(uuid.UUID)),
|
||||||
}
|
}
|
||||||
|
|
||||||
ca.getAudio(p, w, r)
|
ca.getAudio(p, w, r)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ca *callsAPI) shareCallDLRoute(id uuid.UUID, _ *shares.Share, w http.ResponseWriter, r *http.Request) {
|
func (ca *callsAPI) shareCallDLRoute(id ID, _ *shares.Share, w http.ResponseWriter, r *http.Request) {
|
||||||
p := getAudioParams{
|
p := getAudioParams{
|
||||||
CallID: &id,
|
CallID: common.PtrTo(id.(uuid.UUID)),
|
||||||
Download: common.PtrTo("download"),
|
Download: common.PtrTo("download"),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -97,10 +97,10 @@ func (ia *incidentsAPI) getIncidentRoute(w http.ResponseWriter, r *http.Request)
|
||||||
ia.getIncident(id, nil, w, r)
|
ia.getIncident(id, nil, w, r)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ia *incidentsAPI) getIncident(id uuid.UUID, share *shares.Share, w http.ResponseWriter, r *http.Request) {
|
func (ia *incidentsAPI) getIncident(id ID, share *shares.Share, w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := r.Context()
|
ctx := r.Context()
|
||||||
incs := incstore.FromCtx(ctx)
|
incs := incstore.FromCtx(ctx)
|
||||||
inc, err := incs.Incident(ctx, id)
|
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
|
||||||
|
@ -198,12 +198,12 @@ func (ia *incidentsAPI) getCallsM3URoute(w http.ResponseWriter, r *http.Request)
|
||||||
ia.getCallsM3U(id, nil, w, r)
|
ia.getCallsM3U(id, nil, w, r)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ia *incidentsAPI) getCallsM3U(id uuid.UUID, share *shares.Share, w http.ResponseWriter, r *http.Request) {
|
func (ia *incidentsAPI) getCallsM3U(id ID, share *shares.Share, 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)
|
||||||
|
|
||||||
inc, err := incs.Incident(ctx, id)
|
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
|
||||||
|
@ -214,7 +214,7 @@ func (ia *incidentsAPI) getCallsM3U(id uuid.UUID, share *shares.Share, w http.Re
|
||||||
callUrl := common.PtrTo(*ia.baseURL)
|
callUrl := common.PtrTo(*ia.baseURL)
|
||||||
urlRoot := "/api/call"
|
urlRoot := "/api/call"
|
||||||
if share != nil {
|
if share != nil {
|
||||||
urlRoot = fmt.Sprintf("/share/%s/%s/call/", share.Type, share.ID)
|
urlRoot = fmt.Sprintf("/share/%s/call/", share.ID)
|
||||||
}
|
}
|
||||||
|
|
||||||
b.WriteString("#EXTM3U\n\n")
|
b.WriteString("#EXTM3U\n\n")
|
||||||
|
|
|
@ -25,11 +25,13 @@ const (
|
||||||
ShareRequestCallDL ShareRequestType = "callDL"
|
ShareRequestCallDL ShareRequestType = "callDL"
|
||||||
ShareRequestIncident ShareRequestType = "incident"
|
ShareRequestIncident ShareRequestType = "incident"
|
||||||
ShareRequestIncidentM3U ShareRequestType = "m3u"
|
ShareRequestIncidentM3U ShareRequestType = "m3u"
|
||||||
|
ShareRequestTalkgroups ShareRequestType = "talkgroups"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (rt ShareRequestType) IsValid() bool {
|
func (rt ShareRequestType) IsValid() bool {
|
||||||
switch rt {
|
switch rt {
|
||||||
case ShareRequestCall, ShareRequestCallDL, ShareRequestIncident, ShareRequestIncidentM3U:
|
case ShareRequestCall, ShareRequestCallDL, ShareRequestIncident,
|
||||||
|
ShareRequestIncidentM3U, ShareRequestTalkgroups:
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -38,14 +40,17 @@ func (rt ShareRequestType) IsValid() bool {
|
||||||
|
|
||||||
func (rt ShareRequestType) IsValidSubtype() bool {
|
func (rt ShareRequestType) IsValidSubtype() bool {
|
||||||
switch rt {
|
switch rt {
|
||||||
case ShareRequestCall, ShareRequestCallDL:
|
case ShareRequestCall, ShareRequestCallDL, ShareRequestTalkgroups:
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
type HandlerFunc func(id uuid.UUID, share *shares.Share, w http.ResponseWriter, r *http.Request)
|
type ID interface {
|
||||||
|
}
|
||||||
|
|
||||||
|
type HandlerFunc func(id ID, share *shares.Share, w http.ResponseWriter, r *http.Request)
|
||||||
type ShareHandlers map[ShareRequestType]HandlerFunc
|
type ShareHandlers map[ShareRequestType]HandlerFunc
|
||||||
type shareAPI struct {
|
type shareAPI struct {
|
||||||
baseURL *url.URL
|
baseURL *url.URL
|
||||||
|
@ -71,8 +76,8 @@ func (sa *shareAPI) Subrouter() http.Handler {
|
||||||
func (sa *shareAPI) RootRouter() http.Handler {
|
func (sa *shareAPI) RootRouter() http.Handler {
|
||||||
r := chi.NewMux()
|
r := chi.NewMux()
|
||||||
|
|
||||||
r.Get("/{type}/{shareId:[A-Za-z0-9_-]{20,}}", sa.routeShare)
|
r.Get("/{shareId:[A-Za-z0-9_-]{20,}}/{type}", sa.routeShare)
|
||||||
r.Get("/{type}/{shareId:[A-Za-z0-9_-]{20,}}/{subType}/{subID}", sa.routeShare)
|
r.Get("/{shareId:[A-Za-z0-9_-]{20,}}/{type}/{subID}", sa.routeShare)
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -104,7 +109,6 @@ func (sa *shareAPI) routeShare(w http.ResponseWriter, r *http.Request) {
|
||||||
params := struct {
|
params := struct {
|
||||||
Type string `param:"type"`
|
Type string `param:"type"`
|
||||||
ID string `param:"shareId"`
|
ID string `param:"shareId"`
|
||||||
SubType *string `param:"subType"`
|
|
||||||
SubID *string `param:"subID"`
|
SubID *string `param:"subID"`
|
||||||
}{}
|
}{}
|
||||||
|
|
||||||
|
@ -136,15 +140,11 @@ func (sa *shareAPI) routeShare(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx = entities.CtxWithSubject(ctx, sh)
|
ctx = entities.CtxWithSubject(ctx, sh)
|
||||||
r = r.WithContext(ctx)
|
r = r.WithContext(ctx)
|
||||||
|
|
||||||
if params.SubType != nil {
|
switch rType {
|
||||||
|
case ShareRequestTalkgroups:
|
||||||
|
sa.shnd[rType](nil, sh, w, r)
|
||||||
|
case ShareRequestCall, ShareRequestCallDL:
|
||||||
if params.SubID == 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))
|
wErr(w, r, autoError(ErrBadShare))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -154,13 +154,11 @@ func (sa *shareAPI) routeShare(w http.ResponseWriter, r *http.Request) {
|
||||||
wErr(w, r, badRequest(err))
|
wErr(w, r, badRequest(err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
sa.shnd[rType](subIDU, sh, w, r)
|
||||||
sa.shnd[subT](subIDU, sh, w, r)
|
case ShareRequestIncident, ShareRequestIncidentM3U:
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
sa.shnd[rType](sh.EntityID, sh, w, r)
|
sa.shnd[rType](sh.EntityID, sh, w, r)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (sa *shareAPI) deleteShare(w http.ResponseWriter, r *http.Request) {
|
func (sa *shareAPI) deleteShare(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,8 @@ 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/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"
|
||||||
|
@ -159,6 +161,30 @@ 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) {
|
||||||
|
ctx := r.Context()
|
||||||
|
tgs := tgstore.FromCtx(ctx)
|
||||||
|
|
||||||
|
tgIDs, err := incstore.FromCtx(ctx).TGsIn(ctx, share.EntityID)
|
||||||
|
if err != nil {
|
||||||
|
wErr(w, r, autoError(err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
idSl := make(talkgroups.IDs, 0, len(tgIDs))
|
||||||
|
for id := range tgIDs {
|
||||||
|
idSl = append(idSl, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
tgRes, err := tgs.TGs(ctx, idSl)
|
||||||
|
if err != nil {
|
||||||
|
wErr(w, r, autoError(err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
respond(w, r, tgRes)
|
||||||
|
}
|
||||||
|
|
||||||
func (tga *talkgroupAPI) put(w http.ResponseWriter, r *http.Request) {
|
func (tga *talkgroupAPI) put(w http.ResponseWriter, r *http.Request) {
|
||||||
var id tgParams
|
var id tgParams
|
||||||
err := decodeParams(&id, r)
|
err := decodeParams(&id, r)
|
||||||
|
|
|
@ -41,6 +41,18 @@ type ID struct {
|
||||||
Talkgroup uint32 `json:"tg"`
|
Talkgroup uint32 `json:"tg"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type PresenceMap map[ID]struct{}
|
||||||
|
|
||||||
|
func (t PresenceMap) Has(id ID) bool {
|
||||||
|
_, has := t[id]
|
||||||
|
|
||||||
|
return has
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t PresenceMap) Put(id ID) {
|
||||||
|
t[id] = struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
var _ encoding.TextUnmarshaler = (*ID)(nil)
|
var _ encoding.TextUnmarshaler = (*ID)(nil)
|
||||||
|
|
||||||
var ErrBadTG = errors.New("bad talkgroup format")
|
var ErrBadTG = errors.New("bad talkgroup format")
|
||||||
|
|
|
@ -24,11 +24,3 @@ DELETE FROM shares WHERE id = @id;
|
||||||
|
|
||||||
-- name: PruneShares :exec
|
-- name: PruneShares :exec
|
||||||
DELETE FROM shares WHERE expiration < NOW();
|
DELETE FROM shares WHERE expiration < NOW();
|
||||||
|
|
||||||
-- name: GetIncidentTalkgroups :many
|
|
||||||
SELECT DISTINCT
|
|
||||||
c.system,
|
|
||||||
c.talkgroup
|
|
||||||
FROM incidents_calls ic
|
|
||||||
JOIN calls c ON (c.id = ic.call_id AND c.call_date = ic.call_date)
|
|
||||||
WHERE ic.incident_id = @incident_id;
|
|
||||||
|
|
|
@ -281,3 +281,11 @@ INSERT INTO systems(id, name) VALUES(@id, @name);
|
||||||
|
|
||||||
-- name: DeleteSystem :exec
|
-- name: DeleteSystem :exec
|
||||||
DELETE FROM systems WHERE id = @id;
|
DELETE FROM systems WHERE id = @id;
|
||||||
|
|
||||||
|
-- name: GetIncidentTalkgroups :many
|
||||||
|
SELECT DISTINCT
|
||||||
|
c.system,
|
||||||
|
c.talkgroup
|
||||||
|
FROM incidents_calls ic
|
||||||
|
JOIN calls c ON (c.id = ic.call_id AND c.call_date = ic.call_date)
|
||||||
|
WHERE ic.incident_id = @incident_id;
|
||||||
|
|
Loading…
Reference in a new issue