2024-11-10 14:44:52 -05:00
|
|
|
package rest
|
2024-11-04 11:15:24 -05:00
|
|
|
|
|
|
|
import (
|
|
|
|
"errors"
|
|
|
|
"net/http"
|
|
|
|
|
2024-11-20 19:59:24 -05:00
|
|
|
"dynatron.me/x/stillbox/pkg/talkgroups/tgstore"
|
2024-11-04 11:15:24 -05:00
|
|
|
|
|
|
|
"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-12-01 03:01:09 -05:00
|
|
|
func New() *api {
|
2024-11-09 13:14:31 -05:00
|
|
|
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-12-02 17:53:43 -05:00
|
|
|
r.Mount("/call", new(callsAPI).Subrouter())
|
2024-12-16 13:34:07 -05:00
|
|
|
r.Mount("/user", new(usersAPI).Subrouter())
|
2024-12-28 18:32:13 -05:00
|
|
|
r.Mount("/incident", new(incidentsAPI).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 {
|
|
|
|
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",
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-11-24 00:06:43 -05:00
|
|
|
func badRequestErrText(err error) render.Renderer {
|
|
|
|
return &errResponse{
|
|
|
|
Err: err,
|
|
|
|
Code: http.StatusBadRequest,
|
|
|
|
Error: "Bad request: " + err.Error(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-12-16 13:34:07 -05:00
|
|
|
func unauthErrText(err error) render.Renderer {
|
|
|
|
return &errResponse{
|
|
|
|
Err: err,
|
|
|
|
Code: http.StatusUnauthorized,
|
|
|
|
Error: "Unauthorized: " + err.Error(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-12-10 19:10:25 -05:00
|
|
|
func constraintErrText(err error) render.Renderer {
|
|
|
|
return &errResponse{
|
|
|
|
Err: err,
|
|
|
|
Code: http.StatusConflict,
|
|
|
|
Error: "Constraint violation: " + err.Error(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-11-10 14:40:50 -05:00
|
|
|
func recordNotFound(err error) render.Renderer {
|
|
|
|
return &errResponse{
|
|
|
|
Err: err,
|
|
|
|
Code: http.StatusNotFound,
|
|
|
|
Error: "Record not found",
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-11-24 00:06:43 -05:00
|
|
|
func notFoundErrText(err error) render.Renderer {
|
2024-11-20 16:51:19 -05:00
|
|
|
return &errResponse{
|
|
|
|
Err: err,
|
|
|
|
Code: http.StatusNotFound,
|
|
|
|
Error: "Record not found: " + err.Error(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-11-10 14:40:50 -05:00
|
|
|
func internalError(err error) render.Renderer {
|
|
|
|
return &errResponse{
|
|
|
|
Err: err,
|
2024-11-20 07:26:59 -05:00
|
|
|
Code: http.StatusInternalServerError,
|
2024-11-10 14:40:50 -05:00
|
|
|
Error: "Internal server error",
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
type errResponder func(error) render.Renderer
|
|
|
|
|
|
|
|
var statusMapping = map[error]errResponder{
|
2024-11-24 00:06:43 -05:00
|
|
|
tgstore.ErrNoSuchSystem: notFoundErrText,
|
|
|
|
tgstore.ErrNotFound: notFoundErrText,
|
|
|
|
tgstore.ErrInvalidOrderBy: badRequestErrText,
|
2024-12-17 21:17:10 -05:00
|
|
|
tgstore.ErrBadDirection: badRequestErrText,
|
|
|
|
tgstore.ErrBadOrder: badRequestErrText,
|
2024-11-24 00:06:43 -05:00
|
|
|
pgx.ErrNoRows: recordNotFound,
|
2024-12-10 19:10:25 -05:00
|
|
|
ErrMissingTGSys: badRequestErrText,
|
|
|
|
ErrTGIDMismatch: badRequestErrText,
|
|
|
|
ErrSysMismatch: badRequestErrText,
|
|
|
|
tgstore.ErrReference: constraintErrText,
|
2024-12-16 13:34:07 -05:00
|
|
|
ErrBadUID: unauthErrText,
|
|
|
|
ErrBadAppName: unauthErrText,
|
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,
|
2024-12-02 17:53:43 -05:00
|
|
|
DecodeHook: mapstructure.ComposeDecodeHookFunc(
|
|
|
|
mapstructure.StringToTimeDurationHookFunc(),
|
|
|
|
mapstructure.TextUnmarshallerHookFunc(),
|
|
|
|
),
|
2024-11-04 11:15:24 -05:00
|
|
|
})
|
|
|
|
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
|
|
|
}
|