stillbox/pkg/rest/incidents.go

238 lines
4.9 KiB
Go
Raw Normal View History

2024-12-28 18:32:13 -05:00
package rest
import (
2024-12-29 09:30:24 -05:00
"bytes"
"encoding/json"
"fmt"
2024-12-28 18:32:13 -05:00
"net/http"
2024-12-29 15:08:06 -05:00
"net/url"
2024-12-28 18:32:13 -05:00
2024-12-29 15:08:06 -05:00
"dynatron.me/x/stillbox/internal/common"
2024-12-28 18:32:13 -05:00
"dynatron.me/x/stillbox/internal/forms"
2024-12-29 15:08:06 -05:00
"dynatron.me/x/stillbox/internal/jsontypes"
2024-12-28 18:32:13 -05:00
"dynatron.me/x/stillbox/pkg/incidents"
"dynatron.me/x/stillbox/pkg/incidents/incstore"
2024-12-29 09:30:24 -05:00
"dynatron.me/x/stillbox/pkg/talkgroups/tgstore"
2024-12-28 18:32:13 -05:00
"github.com/go-chi/chi/v5"
"github.com/google/uuid"
)
type incidentsAPI struct {
2024-12-29 15:08:06 -05:00
baseURL *url.URL
}
func newIncidentsAPI(baseURL *url.URL) API {
return &incidentsAPI{baseURL}
2024-12-28 18:32:13 -05:00
}
func (ia *incidentsAPI) Subrouter() http.Handler {
r := chi.NewMux()
r.Get(`/{id:[a-f0-9-]+}`, ia.getIncident)
2024-12-29 09:30:24 -05:00
r.Get(`/{id:[a-f0-9-]+}.m3u`, ia.getCallsM3U)
2024-12-28 18:32:13 -05:00
2024-12-29 15:08:06 -05:00
r.Post(`/new`, ia.createIncident)
2024-12-28 18:32:13 -05:00
r.Post(`/`, ia.listIncidents)
2024-12-29 09:30:24 -05:00
r.Post(`/{id:[a-f0-9-]+}/calls`, ia.postCalls)
2024-12-28 18:32:13 -05:00
2024-12-29 18:33:22 -05:00
r.Patch(`/{id:[a-f0-9-]+}`, ia.updateIncident)
2024-12-28 18:32:13 -05:00
2024-12-29 15:08:06 -05:00
r.Delete(`/{id:[a-f0-9-]+}`, ia.deleteIncident)
2024-12-28 18:32:13 -05:00
return r
}
func (ia *incidentsAPI) listIncidents(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
incs := incstore.FromCtx(ctx)
p := incstore.IncidentsParams{}
err := forms.Unmarshal(r, &p, forms.WithTag("json"), forms.WithAcceptBlank(), forms.WithOmitEmpty())
if err != nil {
wErr(w, r, badRequest(err))
return
}
res := struct {
2024-12-29 19:28:50 -05:00
Incidents []incstore.Incident `json:"incidents"`
2025-01-04 10:38:52 -05:00
Count int `json:"count"`
2024-12-28 18:32:13 -05:00
}{}
res.Incidents, res.Count, err = incs.Incidents(ctx, p)
if err != nil {
wErr(w, r, autoError(err))
return
}
respond(w, r, res)
}
func (ia *incidentsAPI) createIncident(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
incs := incstore.FromCtx(ctx)
p := incidents.Incident{}
err := forms.Unmarshal(r, &p, forms.WithTag("json"), forms.WithAcceptBlank(), forms.WithOmitEmpty())
if err != nil {
wErr(w, r, badRequest(err))
return
}
inc, err := incs.CreateIncident(ctx, p)
if err != nil {
wErr(w, r, autoError(err))
return
}
respond(w, r, inc)
}
func (ia *incidentsAPI) getIncident(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
incs := incstore.FromCtx(ctx)
2024-12-29 09:30:24 -05:00
id, err := idOnlyParam(w, r)
2024-12-28 18:32:13 -05:00
if err != nil {
return
}
2024-12-29 09:30:24 -05:00
inc, err := incs.Incident(ctx, id)
2024-12-28 18:32:13 -05:00
if err != nil {
wErr(w, r, autoError(err))
return
}
respond(w, r, inc)
}
func (ia *incidentsAPI) updateIncident(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
incs := incstore.FromCtx(ctx)
2024-12-29 09:30:24 -05:00
id, err := idOnlyParam(w, r)
2024-12-28 18:32:13 -05:00
if err != nil {
return
}
p := incstore.UpdateIncidentParams{}
err = forms.Unmarshal(r, &p, forms.WithTag("json"), forms.WithAcceptBlank(), forms.WithOmitEmpty())
if err != nil {
wErr(w, r, badRequest(err))
return
}
2024-12-29 09:30:24 -05:00
inc, err := incs.UpdateIncident(ctx, id, p)
2024-12-28 18:32:13 -05:00
if err != nil {
wErr(w, r, autoError(err))
return
}
respond(w, r, inc)
}
func (ia *incidentsAPI) deleteIncident(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
incs := incstore.FromCtx(ctx)
urlParams := struct {
ID uuid.UUID `param:"id"`
}{}
err := decodeParams(&urlParams, r)
if err != nil {
wErr(w, r, badRequest(err))
return
}
err = incs.DeleteIncident(ctx, urlParams.ID)
if err != nil {
wErr(w, r, autoError(err))
return
}
w.WriteHeader(http.StatusNoContent)
}
2024-12-29 09:30:24 -05:00
type CallIncidentParams struct {
2024-12-29 15:08:06 -05:00
Add jsontypes.UUIDs `json:"add"`
2024-12-29 09:30:24 -05:00
Notes json.RawMessage `json:"notes"`
2024-12-29 15:08:06 -05:00
Remove jsontypes.UUIDs `json:"remove"`
2024-12-29 09:30:24 -05:00
}
func (ia *incidentsAPI) postCalls(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
incs := incstore.FromCtx(ctx)
id, err := idOnlyParam(w, r)
if err != nil {
return
}
p := CallIncidentParams{}
err = forms.Unmarshal(r, &p, forms.WithTag("json"), forms.WithAcceptBlank(), forms.WithOmitEmpty())
if err != nil {
wErr(w, r, badRequest(err))
return
}
2024-12-29 15:08:06 -05:00
err = incs.AddRemoveIncidentCalls(ctx, id, p.Add.UUIDs(), p.Notes, p.Remove.UUIDs())
2024-12-29 09:30:24 -05:00
if err != nil {
wErr(w, r, autoError(err))
return
}
w.WriteHeader(http.StatusNoContent)
}
func (ia *incidentsAPI) getCallsM3U(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
incs := incstore.FromCtx(ctx)
tgst := tgstore.FromCtx(ctx)
id, err := idOnlyParam(w, r)
if err != nil {
return
}
inc, err := incs.Incident(ctx, id)
if err != nil {
wErr(w, r, autoError(err))
return
}
var b bytes.Buffer
2024-12-29 15:08:06 -05:00
callUrl := common.PtrTo(*ia.baseURL)
2024-12-29 09:30:24 -05:00
b.WriteString("#EXTM3U\n\n")
for _, c := range inc.Calls {
tg, err := tgst.TG(ctx, c.TalkgroupTuple())
if err != nil {
wErr(w, r, autoError(err))
return
}
var from string
if c.Source != 0 {
from = fmt.Sprintf(" from %d", c.Source)
}
2024-12-29 15:08:06 -05:00
callUrl.Path = "/api/call/" + c.ID.String()
2024-12-29 09:30:24 -05:00
fmt.Fprintf(w, "#EXTINF:%d,%s%s (%s)\n%s\n\n",
c.Duration.Seconds(),
tg.StringTag(true),
from,
c.DateTime.Format("15:04 01/02"),
callUrl,
)
}
// Not a lot of agreement on which MIME type to use for non-HLS m3u,
// let's hope this is good enough
w.Header().Set("Content-Type", "audio/x-mpegurl")
w.WriteHeader(http.StatusOK)
_, _ = b.WriteTo(w)
}