2024-11-04 11:15:24 -05:00
|
|
|
package api
|
|
|
|
|
|
|
|
import (
|
|
|
|
"encoding/json"
|
|
|
|
"errors"
|
|
|
|
"net/http"
|
|
|
|
|
|
|
|
"dynatron.me/x/stillbox/pkg/talkgroups"
|
|
|
|
|
|
|
|
"github.com/go-chi/chi/v5"
|
|
|
|
"github.com/go-viper/mapstructure/v2"
|
|
|
|
"github.com/jackc/pgx/v5"
|
|
|
|
"github.com/rs/zerolog/log"
|
|
|
|
)
|
|
|
|
|
|
|
|
type API interface {
|
|
|
|
Subrouter() http.Handler
|
|
|
|
}
|
|
|
|
|
|
|
|
type api struct {
|
|
|
|
}
|
|
|
|
|
2024-11-09 13:14:31 -05:00
|
|
|
func New() API {
|
|
|
|
s := new(api)
|
2024-11-04 11:15:24 -05:00
|
|
|
|
|
|
|
return s
|
|
|
|
}
|
|
|
|
|
|
|
|
func (a *api) Subrouter() http.Handler {
|
|
|
|
r := chi.NewMux()
|
|
|
|
|
2024-11-09 13:14:31 -05:00
|
|
|
r.Mount("/talkgroup", new(talkgroupAPI).routes())
|
|
|
|
|
2024-11-04 11:15:24 -05:00
|
|
|
return r
|
|
|
|
}
|
|
|
|
|
2024-11-10 10:13:38 -05:00
|
|
|
type errResponse struct {
|
|
|
|
text string
|
|
|
|
code int
|
2024-11-04 11:15:24 -05:00
|
|
|
}
|
|
|
|
|
2024-11-10 10:13:38 -05:00
|
|
|
var statusMapping = map[error]errResponse{
|
|
|
|
talkgroups.ErrNotFound: {talkgroups.ErrNotFound.Error(), http.StatusNotFound},
|
|
|
|
pgx.ErrNoRows: {"no such record", http.StatusNotFound},
|
|
|
|
}
|
|
|
|
|
|
|
|
func httpCode(err error) (string, int) {
|
2024-11-04 11:15:24 -05:00
|
|
|
c, ok := statusMapping[err]
|
|
|
|
if ok {
|
2024-11-10 10:13:38 -05:00
|
|
|
return c.text, c.code
|
2024-11-04 11:15:24 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
for e, c := range statusMapping { // check if err wraps an error we know about
|
|
|
|
if errors.Is(err, e) {
|
2024-11-10 10:13:38 -05:00
|
|
|
return c.text, c.code
|
2024-11-04 11:15:24 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-11-10 10:13:38 -05:00
|
|
|
return err.Error(), http.StatusInternalServerError
|
2024-11-04 11:15:24 -05:00
|
|
|
}
|
|
|
|
|
2024-11-09 13:14:31 -05:00
|
|
|
func writeResponse(w http.ResponseWriter, r *http.Request, data interface{}, err error) {
|
2024-11-04 11:15:24 -05:00
|
|
|
if err != nil {
|
|
|
|
log.Error().Str("path", r.URL.Path).Err(err).Msg("request failed")
|
2024-11-10 10:13:38 -05:00
|
|
|
text, code := httpCode(err)
|
|
|
|
http.Error(w, text, code)
|
2024-11-04 11:15:24 -05:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2024-11-04 23:41:52 -05:00
|
|
|
w.Header().Set("Content-Type", "application/json")
|
2024-11-04 11:15:24 -05:00
|
|
|
enc := json.NewEncoder(w)
|
|
|
|
err = enc.Encode(data)
|
|
|
|
if err != nil {
|
|
|
|
log.Error().Str("path", r.URL.Path).Err(err).Msg("response marshal failed")
|
2024-11-10 10:13:38 -05:00
|
|
|
text, code := httpCode(err)
|
|
|
|
http.Error(w, text, code)
|
2024-11-04 11:15:24 -05:00
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-11-09 21:04:16 -05:00
|
|
|
func reqErr(w http.ResponseWriter, err error, code int) {
|
|
|
|
http.Error(w, err.Error(), code)
|
|
|
|
}
|
|
|
|
|
2024-11-04 11:15:24 -05:00
|
|
|
func decodeParams(d interface{}, r *http.Request) error {
|
|
|
|
params := chi.RouteContext(r.Context()).URLParams
|
|
|
|
m := make(map[string]string, len(params.Keys))
|
|
|
|
|
|
|
|
for i, k := range params.Keys {
|
|
|
|
m[k] = params.Values[i]
|
|
|
|
}
|
|
|
|
|
|
|
|
dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
|
2024-11-04 23:41:52 -05:00
|
|
|
Metadata: nil,
|
|
|
|
Result: d,
|
|
|
|
TagName: "param",
|
2024-11-04 11:15:24 -05:00
|
|
|
WeaklyTypedInput: true,
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return dec.Decode(m)
|
|
|
|
}
|
|
|
|
|
2024-11-09 13:14:31 -05:00
|
|
|
func badReq(w http.ResponseWriter, err error) {
|
2024-11-09 21:04:16 -05:00
|
|
|
reqErr(w, err, http.StatusBadRequest)
|
2024-11-04 11:15:24 -05:00
|
|
|
}
|