2024-11-10 14:44:52 -05:00
|
|
|
package rest
|
2024-11-04 11:15:24 -05:00
|
|
|
|
|
|
|
import (
|
|
|
|
"errors"
|
|
|
|
"net/http"
|
|
|
|
|
|
|
|
"dynatron.me/x/stillbox/pkg/talkgroups"
|
|
|
|
|
|
|
|
"github.com/go-chi/chi/v5"
|
2024-11-10 14:40:50 -05:00
|
|
|
"github.com/go-chi/render"
|
2024-11-04 11:15:24 -05:00
|
|
|
"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-10 14:40:50 -05:00
|
|
|
r.Mount("/talkgroup", new(talkgroupAPI).Subrouter())
|
2024-11-09 13:14:31 -05:00
|
|
|
|
2024-11-04 11:15:24 -05:00
|
|
|
return r
|
|
|
|
}
|
|
|
|
|
2024-11-10 10:13:38 -05:00
|
|
|
type errResponse struct {
|
2024-11-10 14:40:50 -05:00
|
|
|
Err error `json:"-"`
|
|
|
|
Code int `json:"-"`
|
|
|
|
Error string `json:"error"`
|
2024-11-04 11:15:24 -05:00
|
|
|
}
|
|
|
|
|
2024-11-10 14:40:50 -05:00
|
|
|
func (e *errResponse) Render(w http.ResponseWriter, r *http.Request) error {
|
|
|
|
switch e.Code {
|
2024-11-15 10:51:25 -05:00
|
|
|
case http.StatusNotFound:
|
2024-11-10 14:40:50 -05:00
|
|
|
default:
|
|
|
|
log.Error().Str("path", r.URL.Path).Err(e.Err).Int("code", e.Code).Str("msg", e.Error).Msg("request failed")
|
|
|
|
}
|
|
|
|
|
|
|
|
render.Status(r, e.Code)
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func badRequest(err error) render.Renderer {
|
|
|
|
return &errResponse{
|
|
|
|
Err: err,
|
|
|
|
Code: http.StatusBadRequest,
|
|
|
|
Error: "Bad request",
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func recordNotFound(err error) render.Renderer {
|
|
|
|
return &errResponse{
|
|
|
|
Err: err,
|
|
|
|
Code: http.StatusNotFound,
|
|
|
|
Error: "Record not found",
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func internalError(err error) render.Renderer {
|
|
|
|
return &errResponse{
|
|
|
|
Err: err,
|
|
|
|
Code: http.StatusNotFound,
|
|
|
|
Error: "Internal server error",
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
type errResponder func(error) render.Renderer
|
|
|
|
|
|
|
|
var statusMapping = map[error]errResponder{
|
2024-11-13 09:24:11 -05:00
|
|
|
talkgroups.ErrNoSuchSystem: recordNotFound,
|
|
|
|
talkgroups.ErrNotFound: recordNotFound,
|
|
|
|
pgx.ErrNoRows: recordNotFound,
|
2024-11-10 10:13:38 -05:00
|
|
|
}
|
|
|
|
|
2024-11-10 14:40:50 -05:00
|
|
|
func autoError(err error) render.Renderer {
|
2024-11-04 11:15:24 -05:00
|
|
|
c, ok := statusMapping[err]
|
|
|
|
if ok {
|
2024-11-10 14:40:50 -05:00
|
|
|
c(err)
|
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 14:40:50 -05:00
|
|
|
return c(err)
|
2024-11-04 11:15:24 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-11-10 14:40:50 -05:00
|
|
|
return internalError(err)
|
2024-11-04 11:15:24 -05:00
|
|
|
}
|
|
|
|
|
2024-11-10 14:40:50 -05:00
|
|
|
func wErr(w http.ResponseWriter, r *http.Request, v render.Renderer) {
|
|
|
|
err := render.Render(w, r, v)
|
2024-11-04 11:15:24 -05:00
|
|
|
if err != nil {
|
2024-11-10 14:40:50 -05:00
|
|
|
log.Error().Err(err).Msg("wErr render error")
|
2024-11-04 11:15:24 -05:00
|
|
|
}
|
2024-11-09 21:04:16 -05:00
|
|
|
}
|
|
|
|
|
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-10 14:40:50 -05:00
|
|
|
func respond(w http.ResponseWriter, r *http.Request, v interface{}) {
|
|
|
|
render.DefaultResponder(w, r, v)
|
2024-11-04 11:15:24 -05:00
|
|
|
}
|