2026-03-04 18:30:21 +00:00
|
|
|
package api
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"context"
|
|
|
|
|
"encoding/json"
|
2026-03-05 14:18:10 +00:00
|
|
|
"fmt"
|
2026-03-05 17:24:50 +00:00
|
|
|
"io"
|
2026-03-04 18:30:21 +00:00
|
|
|
"net/http"
|
|
|
|
|
|
|
|
|
|
"github.com/Gleipnir-Technology/nidus-sync/auth"
|
|
|
|
|
nhttp "github.com/Gleipnir-Technology/nidus-sync/http"
|
|
|
|
|
"github.com/Gleipnir-Technology/nidus-sync/platform"
|
2026-03-27 08:39:38 -07:00
|
|
|
"github.com/Gleipnir-Technology/nidus-sync/platform/file"
|
2026-04-01 18:12:46 +00:00
|
|
|
"github.com/Gleipnir-Technology/nidus-sync/resource"
|
2026-04-01 21:23:28 +00:00
|
|
|
"github.com/google/uuid"
|
2026-03-05 14:18:10 +00:00
|
|
|
"github.com/gorilla/schema"
|
2026-03-04 18:30:21 +00:00
|
|
|
"github.com/rs/zerolog/log"
|
|
|
|
|
)
|
|
|
|
|
|
2026-04-03 22:04:22 +00:00
|
|
|
type ErrorAPI struct {
|
|
|
|
|
Message string `json:"message"`
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-05 14:18:10 +00:00
|
|
|
var decoder = schema.NewDecoder()
|
|
|
|
|
|
2026-04-14 16:07:17 +00:00
|
|
|
type handlerBase func(context.Context, http.ResponseWriter, *http.Request) *nhttp.ErrorWithStatus
|
2026-04-16 17:14:57 +00:00
|
|
|
type handlerBaseAuthenticated func(context.Context, http.ResponseWriter, *http.Request, platform.User) *nhttp.ErrorWithStatus
|
2026-04-02 21:31:31 +00:00
|
|
|
type handlerFunctionDelete func(context.Context, *http.Request, platform.User) *nhttp.ErrorWithStatus
|
2026-04-06 16:53:26 +00:00
|
|
|
type handlerFunctionGet[T any] func(context.Context, *http.Request, resource.QueryParams) (*T, *nhttp.ErrorWithStatus)
|
|
|
|
|
type handlerFunctionGetAuthenticated[T any] func(context.Context, *http.Request, platform.User, resource.QueryParams) (*T, *nhttp.ErrorWithStatus)
|
2026-04-03 22:04:22 +00:00
|
|
|
type handlerFunctionGetImage func(context.Context, *http.Request, platform.User) (file.Collection, uuid.UUID, *nhttp.ErrorWithStatus)
|
|
|
|
|
type handlerFunctionGetSlice[T any] func(context.Context, *http.Request, resource.QueryParams) ([]*T, *nhttp.ErrorWithStatus)
|
|
|
|
|
type handlerFunctionGetSliceAuthenticated[T any] func(context.Context, *http.Request, platform.User, resource.QueryParams) ([]*T, *nhttp.ErrorWithStatus)
|
2026-04-06 16:53:26 +00:00
|
|
|
type handlerFunctionPost[RequestType any, ResponseType any] func(context.Context, *http.Request, RequestType) (ResponseType, *nhttp.ErrorWithStatus)
|
2026-04-03 22:04:22 +00:00
|
|
|
type handlerFunctionPostAuthenticated[RequestType any, ResponseType any] func(context.Context, *http.Request, platform.User, RequestType) (ResponseType, *nhttp.ErrorWithStatus)
|
|
|
|
|
type handlerFunctionPostFormMultipart[RequestType any, ResponseType any] func(context.Context, *http.Request, RequestType) (*ResponseType, *nhttp.ErrorWithStatus)
|
|
|
|
|
type handlerFunctionPutAuthenticated[RequestType any] func(context.Context, *http.Request, platform.User, RequestType) (string, *nhttp.ErrorWithStatus)
|
2026-04-02 21:31:31 +00:00
|
|
|
|
2026-04-16 17:14:57 +00:00
|
|
|
func authenticatedHandlerBasic(f handlerBaseAuthenticated) http.Handler {
|
|
|
|
|
return auth.NewEnsureAuth(func(w http.ResponseWriter, r *http.Request, u platform.User) {
|
|
|
|
|
ctx := r.Context()
|
|
|
|
|
e := f(ctx, w, r, u)
|
|
|
|
|
if e != nil {
|
|
|
|
|
respondErrorStatus(w, e)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
return
|
|
|
|
|
})
|
|
|
|
|
}
|
2026-04-02 21:31:31 +00:00
|
|
|
func authenticatedHandlerDelete(f handlerFunctionDelete) http.Handler {
|
|
|
|
|
return auth.NewEnsureAuth(func(w http.ResponseWriter, r *http.Request, u platform.User) {
|
|
|
|
|
ctx := r.Context()
|
|
|
|
|
e := f(ctx, r, u)
|
|
|
|
|
if e != nil {
|
2026-04-03 22:04:22 +00:00
|
|
|
respondErrorStatus(w, e)
|
2026-04-02 21:31:31 +00:00
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
http.Error(w, "", http.StatusNoContent)
|
|
|
|
|
return
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-01 21:23:28 +00:00
|
|
|
func authenticatedHandlerGetImage(f handlerFunctionGetImage) http.Handler {
|
|
|
|
|
return auth.NewEnsureAuth(func(w http.ResponseWriter, r *http.Request, u platform.User) {
|
|
|
|
|
ctx := r.Context()
|
|
|
|
|
collection, uid, e := f(ctx, r, u)
|
|
|
|
|
if e != nil {
|
2026-04-03 22:04:22 +00:00
|
|
|
respondErrorStatus(w, e)
|
2026-04-01 21:23:28 +00:00
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
file.ImageFileToWriter(collection, uid, w)
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-06 16:53:26 +00:00
|
|
|
func authenticatedHandlerJSON[T any](f handlerFunctionGetAuthenticated[T]) http.Handler {
|
2026-03-12 23:49:16 +00:00
|
|
|
return auth.NewEnsureAuth(func(w http.ResponseWriter, r *http.Request, u platform.User) {
|
2026-03-04 18:30:21 +00:00
|
|
|
ctx := r.Context()
|
|
|
|
|
var body []byte
|
2026-04-01 18:12:46 +00:00
|
|
|
var params resource.QueryParams
|
2026-03-12 23:49:16 +00:00
|
|
|
err := decoder.Decode(¶ms, r.URL.Query())
|
2026-03-06 14:12:47 +00:00
|
|
|
if err != nil {
|
2026-04-03 22:04:22 +00:00
|
|
|
respondErrorStatus(w, nhttp.NewBadRequest("failed to decode query: %w", err))
|
2026-03-06 14:12:47 +00:00
|
|
|
return
|
|
|
|
|
}
|
2026-03-12 23:49:16 +00:00
|
|
|
resp, e := f(ctx, r, u, params)
|
2026-03-04 18:30:21 +00:00
|
|
|
w.Header().Set("Content-Type", "application/json")
|
2026-04-01 20:22:15 +00:00
|
|
|
//log.Info().Str("template", template).Err(e).Msg("handler done")
|
|
|
|
|
if e != nil {
|
2026-04-03 22:04:22 +00:00
|
|
|
respondErrorStatus(w, e)
|
2026-04-01 20:22:15 +00:00
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
body, err = json.Marshal(resp)
|
|
|
|
|
if err != nil {
|
2026-04-03 22:04:22 +00:00
|
|
|
respondErrorStatus(w, nhttp.NewError("failed to marshal json: %w", err))
|
2026-04-01 20:22:15 +00:00
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
w.Write(body)
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-03 18:17:19 +00:00
|
|
|
func authenticatedHandlerJSONSlice[T any](f handlerFunctionGetSliceAuthenticated[T]) http.Handler {
|
2026-04-01 20:22:15 +00:00
|
|
|
return auth.NewEnsureAuth(func(w http.ResponseWriter, r *http.Request, u platform.User) {
|
|
|
|
|
ctx := r.Context()
|
|
|
|
|
var body []byte
|
|
|
|
|
var params resource.QueryParams
|
|
|
|
|
err := decoder.Decode(¶ms, r.URL.Query())
|
|
|
|
|
if err != nil {
|
2026-04-03 22:04:22 +00:00
|
|
|
respondErrorStatus(w, nhttp.NewBadRequest("failed to decode query: %w", err))
|
2026-04-01 20:22:15 +00:00
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
resp, e := f(ctx, r, u, params)
|
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
2026-03-04 18:30:21 +00:00
|
|
|
//log.Info().Str("template", template).Err(e).Msg("handler done")
|
|
|
|
|
if e != nil {
|
2026-04-03 22:04:22 +00:00
|
|
|
respondErrorStatus(w, e)
|
2026-03-04 18:30:21 +00:00
|
|
|
return
|
|
|
|
|
}
|
2026-04-21 21:55:48 +00:00
|
|
|
if resp == nil {
|
|
|
|
|
body, err = json.Marshal([]struct{}{})
|
|
|
|
|
} else {
|
|
|
|
|
body, err = json.Marshal(resp)
|
|
|
|
|
}
|
2026-03-04 18:30:21 +00:00
|
|
|
if err != nil {
|
2026-04-03 22:04:22 +00:00
|
|
|
respondErrorStatus(w, nhttp.NewError("failed to marshal json: %w", err))
|
2026-03-04 18:30:21 +00:00
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
w.Write(body)
|
|
|
|
|
})
|
|
|
|
|
}
|
2026-04-01 21:34:17 +00:00
|
|
|
func authenticatedHandlerJSONPost[RequestType any, ResponseType any](f handlerFunctionPostAuthenticated[RequestType, ResponseType]) http.Handler {
|
2026-03-12 23:49:16 +00:00
|
|
|
return auth.NewEnsureAuth(func(w http.ResponseWriter, r *http.Request, u platform.User) {
|
2026-03-05 14:18:10 +00:00
|
|
|
w.Header().Set("Content-Type", "application/json")
|
2026-04-01 21:34:17 +00:00
|
|
|
req, e := parseRequest[RequestType](r)
|
2026-03-29 17:09:01 -07:00
|
|
|
if e != nil {
|
|
|
|
|
serializeError(w, e)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
ctx := r.Context()
|
2026-04-01 21:34:17 +00:00
|
|
|
resp, e := f(ctx, r, u, *req)
|
2026-03-29 17:09:01 -07:00
|
|
|
if e != nil {
|
|
|
|
|
serializeError(w, e)
|
|
|
|
|
return
|
|
|
|
|
}
|
2026-04-01 21:34:17 +00:00
|
|
|
body, err := json.Marshal(resp)
|
|
|
|
|
if err != nil {
|
2026-04-03 22:04:22 +00:00
|
|
|
respondErrorStatus(w, nhttp.NewError("failed to marshal json: %w", err))
|
2026-04-01 21:34:17 +00:00
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
w.Write(body)
|
2026-03-29 17:09:01 -07:00
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-03 22:04:22 +00:00
|
|
|
func authenticatedHandlerJSONPut[RequestType any](f handlerFunctionPutAuthenticated[RequestType]) http.Handler {
|
2026-03-29 17:09:01 -07:00
|
|
|
return auth.NewEnsureAuth(func(w http.ResponseWriter, r *http.Request, u platform.User) {
|
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
2026-04-03 22:04:22 +00:00
|
|
|
req, e := parseRequest[RequestType](r)
|
2026-03-27 06:08:55 -07:00
|
|
|
if e != nil {
|
|
|
|
|
serializeError(w, e)
|
2026-03-05 14:18:10 +00:00
|
|
|
return
|
|
|
|
|
}
|
2026-03-27 06:08:55 -07:00
|
|
|
ctx := r.Context()
|
|
|
|
|
path, e := f(ctx, r, u, *req)
|
|
|
|
|
if e != nil {
|
|
|
|
|
serializeError(w, e)
|
|
|
|
|
return
|
|
|
|
|
}
|
2026-04-01 15:32:27 +00:00
|
|
|
if path == "" {
|
|
|
|
|
w.WriteHeader(http.StatusNoContent)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
w.Header().Set("Location", path)
|
|
|
|
|
http.Redirect(w, r, path, http.StatusCreated)
|
2026-03-27 06:08:55 -07:00
|
|
|
})
|
|
|
|
|
}
|
2026-04-01 21:34:17 +00:00
|
|
|
func authenticatedHandlerPostMultipart[ResponseType any](f handlerFunctionPostAuthenticated[[]file.Upload, ResponseType], collection file.Collection) http.Handler {
|
2026-03-27 06:08:55 -07:00
|
|
|
return auth.NewEnsureAuth(func(w http.ResponseWriter, r *http.Request, u platform.User) {
|
|
|
|
|
err := r.ParseMultipartForm(32 << 10) // 32 MB buffer
|
|
|
|
|
if err != nil {
|
|
|
|
|
respondError(w, http.StatusBadRequest, "Failed to parse form: %w ", err)
|
2026-03-05 14:18:10 +00:00
|
|
|
return
|
|
|
|
|
}
|
2026-03-27 08:39:38 -07:00
|
|
|
uploads, err := file.SaveFileUploads(r, collection)
|
|
|
|
|
if err != nil {
|
|
|
|
|
respondError(w, http.StatusInternalServerError, "failed to save uploads: %w", err)
|
|
|
|
|
return
|
|
|
|
|
}
|
2026-03-27 06:08:55 -07:00
|
|
|
|
2026-03-27 08:39:38 -07:00
|
|
|
/*
|
2026-03-27 14:06:50 -07:00
|
|
|
err = decoder.Decode(&content, r.PostForm)
|
|
|
|
|
if err != nil {
|
|
|
|
|
respondError(w, http.StatusBadRequest, "Failed to decode form: %w", err)
|
|
|
|
|
return
|
|
|
|
|
}
|
2026-03-27 08:39:38 -07:00
|
|
|
*/
|
2026-03-27 06:08:55 -07:00
|
|
|
ctx := r.Context()
|
2026-04-01 21:34:17 +00:00
|
|
|
resp, e := f(ctx, r, u, uploads)
|
2026-03-27 06:08:55 -07:00
|
|
|
if e != nil {
|
|
|
|
|
http.Error(w, e.Error(), e.Status)
|
|
|
|
|
return
|
|
|
|
|
}
|
2026-04-01 21:34:17 +00:00
|
|
|
body, err := json.Marshal(resp)
|
2026-03-27 11:33:21 -07:00
|
|
|
if err != nil {
|
|
|
|
|
log.Error().Err(err).Msg("failed to marshal json")
|
|
|
|
|
http.Error(w, "{\"message\": \"failed to marshal json\"}", http.StatusInternalServerError)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
w.Write(body)
|
2026-03-05 14:18:10 +00:00
|
|
|
})
|
|
|
|
|
}
|
2026-04-14 16:07:17 +00:00
|
|
|
func handlerBasic(f handlerBase) http.HandlerFunc {
|
|
|
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
|
ctx := r.Context()
|
|
|
|
|
e := f(ctx, w, r)
|
|
|
|
|
if e != nil {
|
|
|
|
|
respondErrorStatus(w, e)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-04-06 16:53:26 +00:00
|
|
|
func handlerJSON[T any](f handlerFunctionGet[T]) http.HandlerFunc {
|
|
|
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
|
ctx := r.Context()
|
|
|
|
|
var body []byte
|
|
|
|
|
var params resource.QueryParams
|
|
|
|
|
err := decoder.Decode(¶ms, r.URL.Query())
|
|
|
|
|
if err != nil {
|
|
|
|
|
respondErrorStatus(w, nhttp.NewBadRequest("failed to decode query: %w", err))
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
resp, e := f(ctx, r, params)
|
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
|
|
|
//log.Info().Str("template", template).Err(e).Msg("handler done")
|
|
|
|
|
if e != nil {
|
|
|
|
|
respondErrorStatus(w, e)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
body, err = json.Marshal(resp)
|
|
|
|
|
if err != nil {
|
|
|
|
|
respondErrorStatus(w, nhttp.NewError("failed to marshal json: %w", err))
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
w.Write(body)
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-04-03 22:04:22 +00:00
|
|
|
func handlerJSONSlice[T any](f handlerFunctionGetSlice[T]) http.HandlerFunc {
|
|
|
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
|
ctx := r.Context()
|
|
|
|
|
var body []byte
|
|
|
|
|
var params resource.QueryParams
|
|
|
|
|
err := decoder.Decode(¶ms, r.URL.Query())
|
|
|
|
|
if err != nil {
|
|
|
|
|
respondErrorStatus(w, nhttp.NewBadRequest("failed to decode query: %w", err))
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
resp, e := f(ctx, r, params)
|
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
|
|
|
//log.Info().Str("template", template).Err(e).Msg("handler done")
|
|
|
|
|
if e != nil {
|
|
|
|
|
respondErrorStatus(w, e)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
body, err = json.Marshal(resp)
|
|
|
|
|
if err != nil {
|
|
|
|
|
respondErrorStatus(w, nhttp.NewError("failed to marshal json: %w", err))
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
w.Write(body)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-06 16:53:26 +00:00
|
|
|
func handlerJSONPost[RequestType any, ResponseType any](f handlerFunctionPost[RequestType, ResponseType]) http.HandlerFunc {
|
2026-04-10 16:59:29 +00:00
|
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
|
|
|
req, e := parseRequest[RequestType](r)
|
|
|
|
|
if e != nil {
|
|
|
|
|
serializeError(w, e)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
ctx := r.Context()
|
|
|
|
|
resp, e := f(ctx, r, *req)
|
|
|
|
|
if e != nil {
|
|
|
|
|
serializeError(w, e)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
body, err := json.Marshal(resp)
|
|
|
|
|
if err != nil {
|
|
|
|
|
respondErrorStatus(w, nhttp.NewError("failed to marshal json: %w", err))
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
w.Write(body)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func handlerJSONPut[RequestType any, ResponseType any](f handlerFunctionPost[RequestType, ResponseType]) http.HandlerFunc {
|
2026-04-03 22:04:22 +00:00
|
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
|
|
|
req, e := parseRequest[RequestType](r)
|
|
|
|
|
if e != nil {
|
|
|
|
|
serializeError(w, e)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
ctx := r.Context()
|
2026-04-06 16:53:26 +00:00
|
|
|
resp, e := f(ctx, r, *req)
|
2026-04-03 22:04:22 +00:00
|
|
|
if e != nil {
|
2026-04-08 17:27:51 +00:00
|
|
|
serializeError(w, e)
|
2026-04-03 22:04:22 +00:00
|
|
|
return
|
|
|
|
|
}
|
2026-04-06 16:53:26 +00:00
|
|
|
body, err := json.Marshal(resp)
|
|
|
|
|
if err != nil {
|
|
|
|
|
respondErrorStatus(w, nhttp.NewError("failed to marshal json: %w", err))
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
w.Write(body)
|
2026-04-03 22:04:22 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
func handlerFormPost[RequestType any, ResponseType any](f handlerFunctionPostFormMultipart[RequestType, ResponseType]) http.HandlerFunc {
|
|
|
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
|
|
|
err := r.ParseMultipartForm(32 << 12) // 128 MB buffer
|
|
|
|
|
if err != nil {
|
|
|
|
|
respondErrorStatus(w, nhttp.NewBadRequest("bad form: %w", err))
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
var req RequestType
|
|
|
|
|
err = decoder.Decode(&req, r.PostForm)
|
|
|
|
|
if err != nil {
|
|
|
|
|
respondErrorStatus(w, nhttp.NewBadRequest("decode form: %w", err))
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
ctx := r.Context()
|
|
|
|
|
resp, e := f(ctx, r, req)
|
|
|
|
|
if e != nil {
|
|
|
|
|
serializeError(w, e)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
body, err := json.Marshal(resp)
|
|
|
|
|
if err != nil {
|
|
|
|
|
respondErrorStatus(w, nhttp.NewError("failed to marshal json: %w", err))
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
w.Write(body)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
func parseRequest[RequestType any](r *http.Request) (*RequestType, *nhttp.ErrorWithStatus) {
|
2026-04-21 22:48:31 +00:00
|
|
|
var err error
|
2026-04-03 22:04:22 +00:00
|
|
|
var req RequestType
|
2026-04-21 22:48:31 +00:00
|
|
|
content_type := r.Header.Get("Content-Type")
|
2026-05-01 20:49:37 +00:00
|
|
|
switch content_type {
|
|
|
|
|
case "application/json":
|
2026-04-21 22:48:31 +00:00
|
|
|
body, e := io.ReadAll(r.Body)
|
|
|
|
|
if e != nil {
|
|
|
|
|
return nil, nhttp.NewError("Failed to read body: %w", err)
|
|
|
|
|
}
|
|
|
|
|
err = json.Unmarshal(body, &req)
|
2026-05-01 20:49:37 +00:00
|
|
|
case "application/x-www-form-urlencoded":
|
2026-04-21 22:48:31 +00:00
|
|
|
e := r.ParseForm()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, nhttp.NewBadRequest("parsing form: %w", e)
|
|
|
|
|
}
|
|
|
|
|
err = decoder.Decode(&req, r.PostForm)
|
2026-05-01 20:49:37 +00:00
|
|
|
default:
|
2026-04-21 22:48:31 +00:00
|
|
|
return nil, nhttp.NewBadRequest("unrecognized content type '%s'", content_type)
|
2026-04-03 22:04:22 +00:00
|
|
|
}
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, nhttp.NewErrorStatus(http.StatusBadRequest, "Failed to decode request: %w", err)
|
|
|
|
|
}
|
|
|
|
|
return &req, nil
|
|
|
|
|
}
|
|
|
|
|
func serializeError(w http.ResponseWriter, e *nhttp.ErrorWithStatus) {
|
|
|
|
|
log.Warn().Int("status", e.Status).Err(e).Str("user message", e.Message).Msg("Responding with an error from api")
|
|
|
|
|
body, err := json.Marshal(ErrorAPI{Message: e.Error()})
|
|
|
|
|
if err != nil {
|
|
|
|
|
log.Error().Err(err).Msg("failed to marshal error")
|
|
|
|
|
http.Error(w, "{\"message\": \"boom. I can't even tell you what went wrong\"}", http.StatusInternalServerError)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
http.Error(w, string(body), e.Status)
|
|
|
|
|
return
|
|
|
|
|
}
|
2026-03-05 14:18:10 +00:00
|
|
|
func respondError(w http.ResponseWriter, status int, format string, args ...any) {
|
|
|
|
|
outer_err := fmt.Errorf(format, args...)
|
|
|
|
|
body, err := json.Marshal(ErrorAPI{
|
|
|
|
|
Message: outer_err.Error(),
|
|
|
|
|
})
|
|
|
|
|
if err != nil {
|
|
|
|
|
http.Error(w, "{\"message\": \"failed to marshal json\"}", http.StatusInternalServerError)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
http.Error(w, string(body), status)
|
|
|
|
|
}
|
2026-04-03 22:04:22 +00:00
|
|
|
func respondErrorStatus(w http.ResponseWriter, e *nhttp.ErrorWithStatus) {
|
|
|
|
|
log.Warn().Int("status", e.Status).Err(e).Str("user message", e.Message).Msg("Responding with an error from api")
|
|
|
|
|
body, err := json.Marshal(ErrorAPI{Message: e.Error()})
|
|
|
|
|
if err != nil {
|
|
|
|
|
log.Error().Err(err).Msg("failed to marshal error")
|
|
|
|
|
http.Error(w, "{\"message\": \"boom. I can't even tell you what went wrong\"}", http.StatusInternalServerError)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
http.Error(w, string(body), e.Status)
|
|
|
|
|
}
|