Incidents calls

This commit is contained in:
Daniel Ponte 2024-12-29 15:08:06 -05:00
parent 80d3377187
commit 82d2d5c340
8 changed files with 123 additions and 30 deletions

View file

@ -1,3 +1,6 @@
# this is used to compose URLs, for example in M3U responses.
# set it to what users access the instance as.
baseURL: "https://stillbox.example.com/"
db: db:
connect: 'postgres://postgres:password@localhost:5432/example' connect: 'postgres://postgres:password@localhost:5432/example'
partition: partition:

57
internal/jsontypes/url.go Normal file
View file

@ -0,0 +1,57 @@
package jsontypes
import (
"encoding/json"
"net/url"
"gopkg.in/yaml.v3"
)
type URL url.URL
func (u *URL) URL() url.URL {
return url.URL(*u)
}
func (u *URL) UnmarshalJSON(b []byte) error {
var s string
err := json.Unmarshal(b, &s)
if err != nil {
return err
}
ur, err := url.Parse(s)
if err != nil {
return err
}
*u = URL(*ur)
return nil
}
func (u *URL) UnmarshalYAML(n *yaml.Node) error {
var s string
err := n.Decode(&s)
if err != nil {
return err
}
ur, err := url.Parse(s)
if err != nil {
return err
}
*u = URL(*ur)
return nil
}
func (u *URL) UnmarshalText(t []byte) error {
ur, err := url.Parse(string(t))
if err != nil {
return err
}
*u = URL(*ur)
return nil
}

View file

@ -1,6 +1,8 @@
package jsontypes package jsontypes
import ( import (
"encoding/json"
"github.com/google/uuid" "github.com/google/uuid"
) )
@ -17,6 +19,28 @@ func (u *UUIDs) UUIDs() []uuid.UUID {
return r return r
} }
func (u *UUIDs) UnmarshalJSON(b []byte) error {
var ss []string
err := json.Unmarshal(b, &ss)
if err != nil {
return err
}
usl := make([]UUID, 0, len(ss))
for _, s := range ss {
uu, err := uuid.Parse(s)
if err != nil {
return err
}
usl = append(usl, UUID(uu))
}
*u = usl
return nil
}
func (u UUID) UUID() uuid.UUID { func (u UUID) UUID() uuid.UUID {
return uuid.UUID(u) return uuid.UUID(u)
} }
@ -26,12 +50,12 @@ func (u *UUID) MarshalJSON() ([]byte, error) {
} }
func (u *UUID) UnmarshalJSON(b []byte) error { func (u *UUID) UnmarshalJSON(b []byte) error {
id, err := uuid.Parse(string(b[:])) id, err := uuid.Parse(string(b[:]))
if err != nil { if err != nil {
return err return err
} }
*u = UUID(id) *u = UUID(id)
return nil return nil
} }
func (u *UUID) UnmarshalText(t []byte) error { func (u *UUID) UnmarshalText(t []byte) error {

View file

@ -16,16 +16,17 @@ type Configuration struct {
} }
type Config struct { type Config struct {
DB DB `yaml:"db"` BaseURL jsontypes.URL `yaml:"baseURL"`
CORS CORS `yaml:"cors"` DB DB `yaml:"db"`
Auth Auth `yaml:"auth"` CORS CORS `yaml:"cors"`
Alerting Alerting `yaml:"alerting"` Auth Auth `yaml:"auth"`
Log []Logger `yaml:"log"` Alerting Alerting `yaml:"alerting"`
Listen string `yaml:"listen"` Log []Logger `yaml:"log"`
Public bool `yaml:"public"` Listen string `yaml:"listen"`
RateLimit RateLimit `yaml:"rateLimit"` Public bool `yaml:"public"`
Notify Notify `yaml:"notify"` RateLimit RateLimit `yaml:"rateLimit"`
Relay []Relay `yaml:"relay"` Notify Notify `yaml:"notify"`
Relay []Relay `yaml:"relay"`
} }
type Auth struct { type Auth struct {

View file

@ -62,7 +62,7 @@ func (c *Configuration) read() error {
}) })
if err != nil { if err != nil {
return fmt.Errorf("unmarshal err: %w", err) return fmt.Errorf("config: %w", err)
} }
return nil return nil

View file

@ -3,6 +3,7 @@ package rest
import ( import (
"errors" "errors"
"net/http" "net/http"
"net/url"
"dynatron.me/x/stillbox/pkg/talkgroups/tgstore" "dynatron.me/x/stillbox/pkg/talkgroups/tgstore"
@ -19,10 +20,11 @@ type API interface {
} }
type api struct { type api struct {
baseURL url.URL
} }
func New() *api { func New(baseURL url.URL) *api {
s := new(api) s := &api{baseURL}
return s return s
} }
@ -33,7 +35,7 @@ func (a *api) Subrouter() http.Handler {
r.Mount("/talkgroup", new(talkgroupAPI).Subrouter()) r.Mount("/talkgroup", new(talkgroupAPI).Subrouter())
r.Mount("/call", new(callsAPI).Subrouter()) r.Mount("/call", new(callsAPI).Subrouter())
r.Mount("/user", new(usersAPI).Subrouter()) r.Mount("/user", new(usersAPI).Subrouter())
r.Mount("/incident", new(incidentsAPI).Subrouter()) r.Mount("/incident", newIncidentsAPI(&a.baseURL).Subrouter())
return r return r
} }

View file

@ -5,8 +5,11 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"net/http" "net/http"
"net/url"
"dynatron.me/x/stillbox/internal/common"
"dynatron.me/x/stillbox/internal/forms" "dynatron.me/x/stillbox/internal/forms"
"dynatron.me/x/stillbox/internal/jsontypes"
"dynatron.me/x/stillbox/pkg/database" "dynatron.me/x/stillbox/pkg/database"
"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"
@ -17,6 +20,11 @@ import (
) )
type incidentsAPI struct { type incidentsAPI struct {
baseURL *url.URL
}
func newIncidentsAPI(baseURL *url.URL) API {
return &incidentsAPI{baseURL}
} }
func (ia *incidentsAPI) Subrouter() http.Handler { func (ia *incidentsAPI) Subrouter() http.Handler {
@ -25,13 +33,13 @@ func (ia *incidentsAPI) Subrouter() http.Handler {
r.Get(`/{id:[a-f0-9-]+}`, ia.getIncident) r.Get(`/{id:[a-f0-9-]+}`, ia.getIncident)
r.Get(`/{id:[a-f0-9-]+}.m3u`, ia.getCallsM3U) r.Get(`/{id:[a-f0-9-]+}.m3u`, ia.getCallsM3U)
r.Post(`/create`, ia.createIncident) r.Post(`/new`, ia.createIncident)
r.Post(`/`, ia.listIncidents) r.Post(`/`, ia.listIncidents)
r.Post(`/{id:[a-f0-9-]+}/calls`, ia.postCalls) r.Post(`/{id:[a-f0-9-]+}/calls`, ia.postCalls)
r.Put(`/{id:[a-f0-9]+}`, ia.updateIncident) r.Put(`/{id:[a-f0-9]+}`, ia.updateIncident)
r.Delete(`/{id:[a-f0-9]+}`, ia.deleteIncident) r.Delete(`/{id:[a-f0-9-]+}`, ia.deleteIncident)
return r return r
} }
@ -148,10 +156,10 @@ func (ia *incidentsAPI) deleteIncident(w http.ResponseWriter, r *http.Request) {
} }
type CallIncidentParams struct { type CallIncidentParams struct {
Add []uuid.UUID `json:"add"` Add jsontypes.UUIDs `json:"add"`
Notes json.RawMessage `json:"notes"` Notes json.RawMessage `json:"notes"`
Remove []uuid.UUID `json:"remove"` Remove jsontypes.UUIDs `json:"remove"`
} }
func (ia *incidentsAPI) postCalls(w http.ResponseWriter, r *http.Request) { func (ia *incidentsAPI) postCalls(w http.ResponseWriter, r *http.Request) {
@ -170,7 +178,7 @@ func (ia *incidentsAPI) postCalls(w http.ResponseWriter, r *http.Request) {
return return
} }
err = incs.AddRemoveIncidentCalls(ctx, id, p.Add, p.Notes, p.Remove) err = incs.AddRemoveIncidentCalls(ctx, id, p.Add.UUIDs(), p.Notes, p.Remove.UUIDs())
if err != nil { if err != nil {
wErr(w, r, autoError(err)) wErr(w, r, autoError(err))
return return
@ -197,9 +205,7 @@ func (ia *incidentsAPI) getCallsM3U(w http.ResponseWriter, r *http.Request) {
var b bytes.Buffer var b bytes.Buffer
callUrl := r.URL callUrl := common.PtrTo(*ia.baseURL)
callUrl.RawQuery = ""
callUrl.Fragment = ""
b.WriteString("#EXTM3U\n\n") b.WriteString("#EXTM3U\n\n")
for _, c := range inc.Calls { for _, c := range inc.Calls {
@ -213,7 +219,7 @@ func (ia *incidentsAPI) getCallsM3U(w http.ResponseWriter, r *http.Request) {
from = fmt.Sprintf(" from %d", c.Source) from = fmt.Sprintf(" from %d", c.Source)
} }
callUrl.Path = "/api/call/%s" + c.ID.String() callUrl.Path = "/api/call/" + c.ID.String()
fmt.Fprintf(w, "#EXTINF:%d,%s%s (%s)\n%s\n\n", fmt.Fprintf(w, "#EXTINF:%d,%s%s (%s)\n%s\n\n",
c.Duration.Seconds(), c.Duration.Seconds(),

View file

@ -71,7 +71,7 @@ func New(ctx context.Context, cfg *config.Configuration) (*Server, error) {
} }
tgCache := tgstore.NewCache() tgCache := tgstore.NewCache()
api := rest.New() api := rest.New(cfg.BaseURL.URL())
srv := &Server{ srv := &Server{
auth: authenticator, auth: authenticator,