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
|
||||
}
|
||||
|
||||
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
|
||||
SELECT
|
||||
id,
|
||||
|
|
|
@ -10,6 +10,7 @@ import (
|
|||
|
||||
"dynatron.me/x/stillbox/internal/jsontypes"
|
||||
"dynatron.me/x/stillbox/pkg/alerting/rules"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
const addLearnedTalkgroup = `-- name: AddLearnedTalkgroup :one
|
||||
|
@ -117,6 +118,40 @@ func (q *Queries) GetAllTalkgroupTags(ctx context.Context) ([]string, error) {
|
|||
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
|
||||
SELECT name FROM systems WHERE id = $1
|
||||
`
|
||||
|
|
|
@ -11,6 +11,7 @@ import (
|
|||
"dynatron.me/x/stillbox/pkg/incidents"
|
||||
"dynatron.me/x/stillbox/pkg/rbac"
|
||||
"dynatron.me/x/stillbox/pkg/rbac/entities"
|
||||
"dynatron.me/x/stillbox/pkg/talkgroups"
|
||||
"dynatron.me/x/stillbox/pkg/users"
|
||||
"github.com/google/uuid"
|
||||
"github.com/jackc/pgx/v5"
|
||||
|
@ -53,9 +54,12 @@ type Store interface {
|
|||
|
||||
// CallIn returns whether an incident is in an call
|
||||
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
|
||||
|
@ -76,10 +80,10 @@ func FromCtx(ctx context.Context) 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")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -138,7 +142,7 @@ func (s *store) CreateIncident(ctx context.Context, inc incidents.Incident) (*in
|
|||
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)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -176,7 +180,7 @@ func (s *store) AddRemoveIncidentCalls(ctx context.Context, incidentID uuid.UUID
|
|||
}, 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))
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
|
@ -281,7 +285,7 @@ func fromDBCalls(d []database.GetIncidentCallsRow) []incidents.IncidentCall {
|
|||
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))
|
||||
if err != nil {
|
||||
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)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -355,7 +359,7 @@ func (s *store) UpdateIncident(ctx context.Context, id uuid.UUID, p UpdateIncide
|
|||
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)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -369,16 +373,39 @@ func (s *store) DeleteIncident(ctx context.Context, id uuid.UUID) error {
|
|||
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)
|
||||
}
|
||||
|
||||
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)
|
||||
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)
|
||||
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"
|
||||
|
||||
"dynatron.me/x/stillbox/pkg/incidents/incstore"
|
||||
"dynatron.me/x/stillbox/pkg/talkgroups"
|
||||
|
||||
"github.com/el-mike/restrict/v2"
|
||||
"github.com/google/uuid"
|
||||
|
@ -16,8 +17,58 @@ const (
|
|||
SubmitterEqualConditionType = "SUBMITTER_EQUAL"
|
||||
InMapConditionType = "IN_MAP"
|
||||
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 {
|
||||
ID string `json:"name,omitempty" yaml:"name,omitempty"`
|
||||
Call *restrict.ValueDescriptor `json:"call" yaml:"call"`
|
||||
|
@ -60,7 +111,7 @@ func (c *CallInIncidentCondition) Check(r *restrict.AccessRequest) error {
|
|||
}
|
||||
|
||||
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
|
||||
|
|
|
@ -54,6 +54,7 @@ func New(baseURL url.URL) *api {
|
|||
ShareRequestCallDL: s.calls.shareCallDLRoute,
|
||||
ShareRequestIncident: s.incidents.getIncident,
|
||||
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)
|
||||
}
|
||||
|
||||
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{
|
||||
CallID: &id,
|
||||
CallID: common.PtrTo(id.(uuid.UUID)),
|
||||
}
|
||||
|
||||
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{
|
||||
CallID: &id,
|
||||
CallID: common.PtrTo(id.(uuid.UUID)),
|
||||
Download: common.PtrTo("download"),
|
||||
}
|
||||
|
||||
|
|
|
@ -97,10 +97,10 @@ func (ia *incidentsAPI) getIncidentRoute(w http.ResponseWriter, r *http.Request)
|
|||
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()
|
||||
incs := incstore.FromCtx(ctx)
|
||||
inc, err := incs.Incident(ctx, id)
|
||||
inc, err := incs.Incident(ctx, id.(uuid.UUID))
|
||||
if err != nil {
|
||||
wErr(w, r, autoError(err))
|
||||
return
|
||||
|
@ -198,12 +198,12 @@ func (ia *incidentsAPI) getCallsM3URoute(w http.ResponseWriter, r *http.Request)
|
|||
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()
|
||||
incs := incstore.FromCtx(ctx)
|
||||
tgst := tgstore.FromCtx(ctx)
|
||||
|
||||
inc, err := incs.Incident(ctx, id)
|
||||
inc, err := incs.Incident(ctx, id.(uuid.UUID))
|
||||
if err != nil {
|
||||
wErr(w, r, autoError(err))
|
||||
return
|
||||
|
@ -214,7 +214,7 @@ func (ia *incidentsAPI) getCallsM3U(id uuid.UUID, share *shares.Share, w http.Re
|
|||
callUrl := common.PtrTo(*ia.baseURL)
|
||||
urlRoot := "/api/call"
|
||||
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")
|
||||
|
|
|
@ -25,11 +25,13 @@ const (
|
|||
ShareRequestCallDL ShareRequestType = "callDL"
|
||||
ShareRequestIncident ShareRequestType = "incident"
|
||||
ShareRequestIncidentM3U ShareRequestType = "m3u"
|
||||
ShareRequestTalkgroups ShareRequestType = "talkgroups"
|
||||
)
|
||||
|
||||
func (rt ShareRequestType) IsValid() bool {
|
||||
switch rt {
|
||||
case ShareRequestCall, ShareRequestCallDL, ShareRequestIncident, ShareRequestIncidentM3U:
|
||||
case ShareRequestCall, ShareRequestCallDL, ShareRequestIncident,
|
||||
ShareRequestIncidentM3U, ShareRequestTalkgroups:
|
||||
return true
|
||||
}
|
||||
|
||||
|
@ -38,14 +40,17 @@ func (rt ShareRequestType) IsValid() bool {
|
|||
|
||||
func (rt ShareRequestType) IsValidSubtype() bool {
|
||||
switch rt {
|
||||
case ShareRequestCall, ShareRequestCallDL:
|
||||
case ShareRequestCall, ShareRequestCallDL, ShareRequestTalkgroups:
|
||||
return true
|
||||
}
|
||||
|
||||
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 shareAPI struct {
|
||||
baseURL *url.URL
|
||||
|
@ -71,8 +76,8 @@ func (sa *shareAPI) Subrouter() http.Handler {
|
|||
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)
|
||||
r.Get("/{shareId:[A-Za-z0-9_-]{20,}}/{type}", sa.routeShare)
|
||||
r.Get("/{shareId:[A-Za-z0-9_-]{20,}}/{type}/{subID}", sa.routeShare)
|
||||
return r
|
||||
}
|
||||
|
||||
|
@ -104,7 +109,6 @@ func (sa *shareAPI) routeShare(w http.ResponseWriter, r *http.Request) {
|
|||
params := struct {
|
||||
Type string `param:"type"`
|
||||
ID string `param:"shareId"`
|
||||
SubType *string `param:"subType"`
|
||||
SubID *string `param:"subID"`
|
||||
}{}
|
||||
|
||||
|
@ -136,15 +140,11 @@ func (sa *shareAPI) routeShare(w http.ResponseWriter, r *http.Request) {
|
|||
ctx = entities.CtxWithSubject(ctx, sh)
|
||||
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 {
|
||||
// probably can't happen
|
||||
wErr(w, r, autoError(ErrBadShare))
|
||||
return
|
||||
}
|
||||
|
||||
subT := ShareRequestType(*params.SubType)
|
||||
if !subT.IsValidSubtype() {
|
||||
wErr(w, r, autoError(ErrBadShare))
|
||||
return
|
||||
}
|
||||
|
@ -154,13 +154,11 @@ func (sa *shareAPI) routeShare(w http.ResponseWriter, r *http.Request) {
|
|||
wErr(w, r, badRequest(err))
|
||||
return
|
||||
}
|
||||
|
||||
sa.shnd[subT](subIDU, sh, w, r)
|
||||
return
|
||||
}
|
||||
|
||||
sa.shnd[rType](subIDU, sh, w, r)
|
||||
case ShareRequestIncident, ShareRequestIncidentM3U:
|
||||
sa.shnd[rType](sh.EntityID, sh, w, r)
|
||||
}
|
||||
}
|
||||
|
||||
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/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/tgstore"
|
||||
"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)
|
||||
}
|
||||
|
||||
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) {
|
||||
var id tgParams
|
||||
err := decodeParams(&id, r)
|
||||
|
|
|
@ -41,6 +41,18 @@ type ID struct {
|
|||
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 ErrBadTG = errors.New("bad talkgroup format")
|
||||
|
|
|
@ -24,11 +24,3 @@ DELETE FROM shares WHERE id = @id;
|
|||
|
||||
-- name: PruneShares :exec
|
||||
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
|
||||
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