stillbox/pkg/sources/http.go

141 lines
3.9 KiB
Go
Raw Normal View History

2024-08-01 01:01:08 -04:00
package sources
2024-07-24 08:38:18 -04:00
import (
"net/http"
2024-07-24 18:18:04 -04:00
"strings"
2024-07-24 08:38:18 -04:00
"time"
2024-07-27 19:25:16 -04:00
"dynatron.me/x/stillbox/internal/common"
2024-10-31 09:09:58 -04:00
"dynatron.me/x/stillbox/internal/forms"
2024-11-03 07:19:03 -05:00
"dynatron.me/x/stillbox/pkg/auth"
2024-11-03 08:09:49 -05:00
"dynatron.me/x/stillbox/pkg/calls"
2024-07-28 23:22:12 -04:00
"github.com/go-chi/chi/v5"
2024-07-25 09:37:27 -04:00
"github.com/rs/zerolog/log"
2024-07-24 22:42:30 -04:00
)
2024-07-24 14:07:24 -04:00
2024-08-01 01:01:08 -04:00
// RdioHTTP is an source that accepts calls using the rdio-scanner HTTP interface.
type RdioHTTP struct {
2024-07-29 00:58:32 -04:00
auth auth.Authenticator
2024-08-01 01:01:08 -04:00
ing Ingestor
}
func (r *RdioHTTP) SourceType() string {
return "rdio-http"
2024-07-28 23:22:12 -04:00
}
2024-07-29 00:29:16 -04:00
// NewHTTPIngestor creates a new HTTPIngestor. It requires an Authenticator.
2024-08-01 01:01:08 -04:00
func NewRdioHTTP(auth auth.Authenticator, ing Ingestor) *RdioHTTP {
return &RdioHTTP{
2024-07-29 00:29:16 -04:00
auth: auth,
2024-08-01 01:01:08 -04:00
ing: ing,
2024-07-29 00:29:16 -04:00
}
2024-07-28 23:22:12 -04:00
}
2024-08-01 01:01:08 -04:00
// InstallPublicRoutes installs the HTTP source's routes to the provided chi Router.
func (h *RdioHTTP) InstallPublicRoutes(r chi.Router) {
2024-07-28 23:22:12 -04:00
r.Post("/api/call-upload", h.routeCallUpload)
}
2024-11-18 14:27:12 -05:00
type CallUploadRequest struct {
2024-10-31 09:09:58 -04:00
Audio []byte `form:"audio" filenameField:"AudioName"`
2024-07-28 01:08:08 -04:00
AudioName string
2024-07-24 14:07:24 -04:00
AudioType string `form:"audioType"`
DateTime time.Time `form:"dateTime"`
2024-07-24 08:38:18 -04:00
Frequencies []int `form:"frequencies"`
Frequency int `form:"frequency"`
Key string `form:"key"`
2024-07-24 22:49:42 -04:00
Patches []int `form:"patches"`
2024-07-24 22:42:30 -04:00
Source int `form:"source"`
2024-07-24 22:49:42 -04:00
Sources []int `form:"sources"`
2024-07-24 22:42:30 -04:00
System int `form:"system"`
2024-07-24 08:38:18 -04:00
SystemLabel string `form:"systemLabel"`
Talkgroup int `form:"talkgroup"`
TalkgroupGroup string `form:"talkgroupGroup"`
TalkgroupLabel string `form:"talkgroupLabel"`
TalkgroupTag string `form:"talkgroupTag"`
2024-08-18 08:44:44 -04:00
DontStore bool `form:"dontStore"`
2024-07-24 08:38:18 -04:00
}
2024-11-18 14:27:12 -05:00
func (car *CallUploadRequest) mimeType() string {
2024-08-01 01:01:08 -04:00
// this is super naïve
fn := car.AudioName
switch {
case car.AudioType != "":
return car.AudioType
case strings.HasSuffix(fn, ".mp3"):
return "audio/mpeg"
case strings.HasSuffix(fn, ".wav"):
return "audio/wav"
}
return ""
}
2024-11-18 14:27:12 -05:00
func (car *CallUploadRequest) ToCall(submitter auth.UserID) (*calls.Call, error) {
2024-08-14 09:32:53 -04:00
return calls.Make(&calls.Call{
2024-08-01 01:01:08 -04:00
Submitter: &submitter,
System: car.System,
Talkgroup: car.Talkgroup,
DateTime: car.DateTime,
AudioName: car.AudioName,
Audio: car.Audio,
AudioType: car.mimeType(),
Frequency: car.Frequency,
Frequencies: car.Frequencies,
Patches: car.Patches,
TalkgroupLabel: common.PtrOrNull(car.TalkgroupLabel),
2024-08-23 14:28:47 -04:00
TGAlphaTag: common.PtrOrNull(car.TalkgroupTag),
2024-08-01 01:01:08 -04:00
TalkgroupGroup: common.PtrOrNull(car.TalkgroupGroup),
Source: car.Source,
2024-08-18 08:44:44 -04:00
}, !car.DontStore)
2024-07-25 09:37:27 -04:00
}
2024-08-01 01:01:08 -04:00
func (h *RdioHTTP) routeCallUpload(w http.ResponseWriter, r *http.Request) {
2024-07-24 22:42:30 -04:00
err := r.ParseMultipartForm(1024 * 1024 * 2) // 2MB
if err != nil {
http.Error(w, "cannot parse form "+err.Error(), http.StatusBadRequest)
return
}
2024-07-29 00:29:16 -04:00
ctx := r.Context()
2024-07-24 22:42:30 -04:00
2024-08-01 01:01:08 -04:00
submitter, err := h.auth.CheckAPIKey(ctx, r.Form.Get("key"))
2024-07-29 00:29:16 -04:00
if err != nil {
auth.ErrorResponse(w, err)
2024-07-24 22:42:30 -04:00
return
}
2024-07-28 01:08:08 -04:00
if strings.Trim(r.Form.Get("test"), "\r\n") == "1" {
// fudge the official response
http.Error(w, "incomplete call data: no talkgroup", http.StatusExpectationFailed)
2024-07-27 21:27:48 -04:00
return
}
2024-11-18 14:27:12 -05:00
cur := new(CallUploadRequest)
2024-10-31 09:09:58 -04:00
err = forms.Unmarshal(r, cur, forms.WithAcceptBlank())
2024-07-24 08:38:18 -04:00
if err != nil {
2024-07-28 01:08:08 -04:00
http.Error(w, "cannot bind upload "+err.Error(), http.StatusExpectationFailed)
2024-07-24 08:38:18 -04:00
return
}
2024-11-18 14:27:12 -05:00
call, err := cur.ToCall(*submitter)
2024-08-14 09:32:53 -04:00
if err != nil {
log.Error().Err(err).Msg("toCall failed")
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
err = h.ing.Ingest(ctx, call)
if err != nil {
log.Error().Err(err).Msg("ingest failed")
http.Error(w, "Call ingest failed.", http.StatusInternalServerError)
return
}
2024-07-27 19:25:16 -04:00
2024-08-01 01:01:08 -04:00
log.Info().Int("system", cur.System).Int("tgid", cur.Talkgroup).Msg("ingested")
2024-07-28 01:08:08 -04:00
2024-10-22 10:21:11 -04:00
written, err := w.Write([]byte("Call imported successfully."))
if err != nil {
log.Error().Err(err).Int("written", written).Msg("upload response failed")
}
2024-07-24 08:38:18 -04:00
}