From c95e3f2f9666f2e00970829fa2f8d877b4b1ac27 Mon Sep 17 00:00:00 2001 From: Hayden <64056131+hay-kot@users.noreply.github.com> Date: Mon, 20 Mar 2023 20:27:50 -0800 Subject: [PATCH] rework API to use external server package --- backend/app/api/app.go | 2 +- backend/app/api/handlers/v1/controller.go | 15 +- .../app/api/handlers/v1/v1_ctrl_actions.go | 13 +- backend/app/api/handlers/v1/v1_ctrl_assets.go | 11 +- backend/app/api/handlers/v1/v1_ctrl_auth.go | 24 +- backend/app/api/handlers/v1/v1_ctrl_group.go | 8 +- backend/app/api/handlers/v1/v1_ctrl_items.go | 27 +-- .../handlers/v1/v1_ctrl_items_attachments.go | 21 +- backend/app/api/handlers/v1/v1_ctrl_labels.go | 14 +- .../app/api/handlers/v1/v1_ctrl_locations.go | 18 +- .../api/handlers/v1/v1_ctrl_maint_entry.go | 10 +- .../app/api/handlers/v1/v1_ctrl_notifiers.go | 14 +- backend/app/api/handlers/v1/v1_ctrl_qrcode.go | 4 +- .../app/api/handlers/v1/v1_ctrl_reporting.go | 4 +- .../app/api/handlers/v1/v1_ctrl_statistics.go | 13 +- backend/app/api/handlers/v1/v1_ctrl_user.go | 27 +-- backend/app/api/main.go | 33 +-- backend/app/api/middleware.go | 12 +- backend/app/api/routes.go | 139 ++++++------ backend/app/api/static/docs/docs.go | 67 +++--- backend/app/api/static/docs/swagger.json | 67 +++--- backend/app/api/static/docs/swagger.yaml | 49 ++-- backend/go.mod | 1 + backend/go.sum | 2 + backend/internal/web/adapters/actions.go | 11 +- backend/internal/web/adapters/command.go | 13 +- backend/internal/web/adapters/decoders.go | 2 +- backend/internal/web/adapters/query.go | 11 +- backend/internal/web/mid/errors.go | 35 +-- backend/internal/web/mid/logger.go | 101 ++------- backend/internal/web/mid/panic.go | 33 --- backend/pkgs/server/constants.go | 8 - backend/pkgs/server/errors.go | 23 -- backend/pkgs/server/handler.go | 25 --- backend/pkgs/server/middleware.go | 37 --- backend/pkgs/server/mux.go | 102 --------- backend/pkgs/server/request.go | 48 ---- backend/pkgs/server/request_test.go | 210 ------------------ backend/pkgs/server/response.go | 39 ---- backend/pkgs/server/response_test.go | 40 ---- backend/pkgs/server/result.go | 19 -- backend/pkgs/server/server.go | 144 ------------ backend/pkgs/server/server_options.go | 54 ----- backend/pkgs/server/server_test.go | 101 --------- backend/pkgs/server/worker.go | 21 -- docs/docs/api/openapi-2.0.json | 67 +++--- frontend/lib/api/types/data-contracts.ts | 25 +-- 47 files changed, 387 insertions(+), 1377 deletions(-) delete mode 100644 backend/internal/web/mid/panic.go delete mode 100644 backend/pkgs/server/constants.go delete mode 100644 backend/pkgs/server/errors.go delete mode 100644 backend/pkgs/server/handler.go delete mode 100644 backend/pkgs/server/middleware.go delete mode 100644 backend/pkgs/server/mux.go delete mode 100644 backend/pkgs/server/request.go delete mode 100644 backend/pkgs/server/request_test.go delete mode 100644 backend/pkgs/server/response.go delete mode 100644 backend/pkgs/server/response_test.go delete mode 100644 backend/pkgs/server/result.go delete mode 100644 backend/pkgs/server/server.go delete mode 100644 backend/pkgs/server/server_options.go delete mode 100644 backend/pkgs/server/server_test.go delete mode 100644 backend/pkgs/server/worker.go diff --git a/backend/app/api/app.go b/backend/app/api/app.go index 854c4e5..a84b9ae 100644 --- a/backend/app/api/app.go +++ b/backend/app/api/app.go @@ -8,7 +8,7 @@ import ( "github.com/hay-kot/homebox/backend/internal/data/repo" "github.com/hay-kot/homebox/backend/internal/sys/config" "github.com/hay-kot/homebox/backend/pkgs/mailer" - "github.com/hay-kot/homebox/backend/pkgs/server" + "github.com/hay-kot/safeserve/server" ) type app struct { diff --git a/backend/app/api/handlers/v1/controller.go b/backend/app/api/handlers/v1/controller.go index 51fb02e..ff03bfe 100644 --- a/backend/app/api/handlers/v1/controller.go +++ b/backend/app/api/handlers/v1/controller.go @@ -5,9 +5,18 @@ import ( "github.com/hay-kot/homebox/backend/internal/core/services" "github.com/hay-kot/homebox/backend/internal/data/repo" - "github.com/hay-kot/homebox/backend/pkgs/server" + "github.com/hay-kot/safeserve/errchain" + "github.com/hay-kot/safeserve/server" ) +type Wrapped struct { + Item interface{} `json:"item"` +} + +func Wrap(v any) Wrapped { + return Wrapped{Item: v} +} + func WithMaxUploadSize(maxUploadSize int64) func(*V1Controller) { return func(ctrl *V1Controller) { ctrl.maxUploadSize = maxUploadSize @@ -81,9 +90,9 @@ func NewControllerV1(svc *services.AllServices, repos *repo.AllRepos, options .. // @Produce json // @Success 200 {object} ApiSummary // @Router /v1/status [GET] -func (ctrl *V1Controller) HandleBase(ready ReadyFunc, build Build) server.HandlerFunc { +func (ctrl *V1Controller) HandleBase(ready ReadyFunc, build Build) errchain.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) error { - return server.Respond(w, http.StatusOK, ApiSummary{ + return server.JSON(w, http.StatusOK, ApiSummary{ Healthy: ready(), Title: "Homebox", Message: "Track, Manage, and Organize your shit", diff --git a/backend/app/api/handlers/v1/v1_ctrl_actions.go b/backend/app/api/handlers/v1/v1_ctrl_actions.go index d131738..10c9f2e 100644 --- a/backend/app/api/handlers/v1/v1_ctrl_actions.go +++ b/backend/app/api/handlers/v1/v1_ctrl_actions.go @@ -7,7 +7,8 @@ import ( "github.com/google/uuid" "github.com/hay-kot/homebox/backend/internal/core/services" "github.com/hay-kot/homebox/backend/internal/sys/validate" - "github.com/hay-kot/homebox/backend/pkgs/server" + "github.com/hay-kot/safeserve/errchain" + "github.com/hay-kot/safeserve/server" "github.com/rs/zerolog/log" ) @@ -15,7 +16,7 @@ type ActionAmountResult struct { Completed int `json:"completed"` } -func actionHandlerFactory(ref string, fn func(context.Context, uuid.UUID) (int, error)) server.HandlerFunc { +func actionHandlerFactory(ref string, fn func(context.Context, uuid.UUID) (int, error)) errchain.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) error { ctx := services.NewContext(r.Context()) @@ -25,7 +26,7 @@ func actionHandlerFactory(ref string, fn func(context.Context, uuid.UUID) (int, return validate.NewRequestError(err, http.StatusInternalServerError) } - return server.Respond(w, http.StatusOK, ActionAmountResult{Completed: totalCompleted}) + return server.JSON(w, http.StatusOK, ActionAmountResult{Completed: totalCompleted}) } } @@ -38,7 +39,7 @@ func actionHandlerFactory(ref string, fn func(context.Context, uuid.UUID) (int, // @Success 200 {object} ActionAmountResult // @Router /v1/actions/ensure-asset-ids [Post] // @Security Bearer -func (ctrl *V1Controller) HandleEnsureAssetID() server.HandlerFunc { +func (ctrl *V1Controller) HandleEnsureAssetID() errchain.HandlerFunc { return actionHandlerFactory("ensure asset IDs", ctrl.svc.Items.EnsureAssetID) } @@ -51,7 +52,7 @@ func (ctrl *V1Controller) HandleEnsureAssetID() server.HandlerFunc { // @Success 200 {object} ActionAmountResult // @Router /v1/actions/ensure-import-refs [Post] // @Security Bearer -func (ctrl *V1Controller) HandleEnsureImportRefs() server.HandlerFunc { +func (ctrl *V1Controller) HandleEnsureImportRefs() errchain.HandlerFunc { return actionHandlerFactory("ensure import refs", ctrl.svc.Items.EnsureImportRef) } @@ -64,6 +65,6 @@ func (ctrl *V1Controller) HandleEnsureImportRefs() server.HandlerFunc { // @Success 200 {object} ActionAmountResult // @Router /v1/actions/zero-item-time-fields [Post] // @Security Bearer -func (ctrl *V1Controller) HandleItemDateZeroOut() server.HandlerFunc { +func (ctrl *V1Controller) HandleItemDateZeroOut() errchain.HandlerFunc { return actionHandlerFactory("zero out date time", ctrl.repo.Items.ZeroOutTimeFields) } diff --git a/backend/app/api/handlers/v1/v1_ctrl_assets.go b/backend/app/api/handlers/v1/v1_ctrl_assets.go index 6bc596a..117ebe4 100644 --- a/backend/app/api/handlers/v1/v1_ctrl_assets.go +++ b/backend/app/api/handlers/v1/v1_ctrl_assets.go @@ -9,7 +9,8 @@ import ( "github.com/hay-kot/homebox/backend/internal/core/services" "github.com/hay-kot/homebox/backend/internal/data/repo" "github.com/hay-kot/homebox/backend/internal/sys/validate" - "github.com/hay-kot/homebox/backend/pkgs/server" + "github.com/hay-kot/safeserve/errchain" + "github.com/hay-kot/safeserve/server" "github.com/rs/zerolog/log" ) @@ -23,7 +24,7 @@ import ( // @Success 200 {object} repo.PaginationResult[repo.ItemSummary]{} // @Router /v1/assets/{id} [GET] // @Security Bearer -func (ctrl *V1Controller) HandleAssetGet() server.HandlerFunc { +func (ctrl *V1Controller) HandleAssetGet() errchain.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) error { ctx := services.NewContext(r.Context()) assetIdParam := chi.URLParam(r, "id") @@ -38,7 +39,7 @@ func (ctrl *V1Controller) HandleAssetGet() server.HandlerFunc { if pageParam != "" { page, err = strconv.ParseInt(pageParam, 10, 64) if err != nil { - return server.Respond(w, http.StatusBadRequest, "Invalid page number") + return server.JSON(w, http.StatusBadRequest, "Invalid page number") } } @@ -47,7 +48,7 @@ func (ctrl *V1Controller) HandleAssetGet() server.HandlerFunc { if pageSizeParam != "" { pageSize, err = strconv.ParseInt(pageSizeParam, 10, 64) if err != nil { - return server.Respond(w, http.StatusBadRequest, "Invalid page size") + return server.JSON(w, http.StatusBadRequest, "Invalid page size") } } @@ -56,6 +57,6 @@ func (ctrl *V1Controller) HandleAssetGet() server.HandlerFunc { log.Err(err).Msg("failed to get item") return validate.NewRequestError(err, http.StatusInternalServerError) } - return server.Respond(w, http.StatusOK, items) + return server.JSON(w, http.StatusOK, items) } } diff --git a/backend/app/api/handlers/v1/v1_ctrl_auth.go b/backend/app/api/handlers/v1/v1_ctrl_auth.go index bdfcdf6..f6b723b 100644 --- a/backend/app/api/handlers/v1/v1_ctrl_auth.go +++ b/backend/app/api/handlers/v1/v1_ctrl_auth.go @@ -8,7 +8,8 @@ import ( "github.com/hay-kot/homebox/backend/internal/core/services" "github.com/hay-kot/homebox/backend/internal/sys/validate" - "github.com/hay-kot/homebox/backend/pkgs/server" + "github.com/hay-kot/safeserve/errchain" + "github.com/hay-kot/safeserve/server" "github.com/rs/zerolog/log" ) @@ -36,26 +37,27 @@ type ( // @Produce json // @Success 200 {object} TokenResponse // @Router /v1/users/login [POST] -func (ctrl *V1Controller) HandleAuthLogin() server.HandlerFunc { +func (ctrl *V1Controller) HandleAuthLogin() errchain.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) error { loginForm := &LoginForm{} switch r.Header.Get("Content-Type") { - case server.ContentFormUrlEncoded: + case "application/x-www-form-urlencoded": err := r.ParseForm() if err != nil { - return server.Respond(w, http.StatusBadRequest, server.Wrap(err)) + return errors.New("failed to parse form") } loginForm.Username = r.PostFormValue("username") loginForm.Password = r.PostFormValue("password") - case server.ContentJSON: + case "application/json": err := server.Decode(r, loginForm) if err != nil { log.Err(err).Msg("failed to decode login form") + return errors.New("failed to decode login form") } default: - return server.Respond(w, http.StatusBadRequest, errors.New("invalid content type")) + return server.JSON(w, http.StatusBadRequest, errors.New("invalid content type")) } if loginForm.Username == "" || loginForm.Password == "" { @@ -76,7 +78,7 @@ func (ctrl *V1Controller) HandleAuthLogin() server.HandlerFunc { return validate.NewRequestError(errors.New("authentication failed"), http.StatusInternalServerError) } - return server.Respond(w, http.StatusOK, TokenResponse{ + return server.JSON(w, http.StatusOK, TokenResponse{ Token: "Bearer " + newToken.Raw, ExpiresAt: newToken.ExpiresAt, AttachmentToken: newToken.AttachmentToken, @@ -91,7 +93,7 @@ func (ctrl *V1Controller) HandleAuthLogin() server.HandlerFunc { // @Success 204 // @Router /v1/users/logout [POST] // @Security Bearer -func (ctrl *V1Controller) HandleAuthLogout() server.HandlerFunc { +func (ctrl *V1Controller) HandleAuthLogout() errchain.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) error { token := services.UseTokenCtx(r.Context()) if token == "" { @@ -103,7 +105,7 @@ func (ctrl *V1Controller) HandleAuthLogout() server.HandlerFunc { return validate.NewRequestError(err, http.StatusInternalServerError) } - return server.Respond(w, http.StatusNoContent, nil) + return server.JSON(w, http.StatusNoContent, nil) } } @@ -116,7 +118,7 @@ func (ctrl *V1Controller) HandleAuthLogout() server.HandlerFunc { // @Success 200 // @Router /v1/users/refresh [GET] // @Security Bearer -func (ctrl *V1Controller) HandleAuthRefresh() server.HandlerFunc { +func (ctrl *V1Controller) HandleAuthRefresh() errchain.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) error { requestToken := services.UseTokenCtx(r.Context()) if requestToken == "" { @@ -128,6 +130,6 @@ func (ctrl *V1Controller) HandleAuthRefresh() server.HandlerFunc { return validate.NewUnauthorizedError() } - return server.Respond(w, http.StatusOK, newToken) + return server.JSON(w, http.StatusOK, newToken) } } diff --git a/backend/app/api/handlers/v1/v1_ctrl_group.go b/backend/app/api/handlers/v1/v1_ctrl_group.go index 37e9b85..bd9ff4c 100644 --- a/backend/app/api/handlers/v1/v1_ctrl_group.go +++ b/backend/app/api/handlers/v1/v1_ctrl_group.go @@ -7,7 +7,7 @@ import ( "github.com/hay-kot/homebox/backend/internal/core/services" "github.com/hay-kot/homebox/backend/internal/data/repo" "github.com/hay-kot/homebox/backend/internal/web/adapters" - "github.com/hay-kot/homebox/backend/pkgs/server" + "github.com/hay-kot/safeserve/errchain" ) type ( @@ -31,7 +31,7 @@ type ( // @Success 200 {object} repo.Group // @Router /v1/groups [Get] // @Security Bearer -func (ctrl *V1Controller) HandleGroupGet() server.HandlerFunc { +func (ctrl *V1Controller) HandleGroupGet() errchain.HandlerFunc { fn := func(r *http.Request) (repo.Group, error) { auth := services.NewContext(r.Context()) return ctrl.repo.Groups.GroupByID(auth, auth.GID) @@ -49,7 +49,7 @@ func (ctrl *V1Controller) HandleGroupGet() server.HandlerFunc { // @Success 200 {object} repo.Group // @Router /v1/groups [Put] // @Security Bearer -func (ctrl *V1Controller) HandleGroupUpdate() server.HandlerFunc { +func (ctrl *V1Controller) HandleGroupUpdate() errchain.HandlerFunc { fn := func(r *http.Request, body repo.GroupUpdate) (repo.Group, error) { auth := services.NewContext(r.Context()) return ctrl.svc.Group.UpdateGroup(auth, body) @@ -67,7 +67,7 @@ func (ctrl *V1Controller) HandleGroupUpdate() server.HandlerFunc { // @Success 200 {object} GroupInvitation // @Router /v1/groups/invitations [Post] // @Security Bearer -func (ctrl *V1Controller) HandleGroupInvitationsCreate() server.HandlerFunc { +func (ctrl *V1Controller) HandleGroupInvitationsCreate() errchain.HandlerFunc { fn := func(r *http.Request, body GroupInvitationCreate) (GroupInvitation, error) { if body.ExpiresAt.IsZero() { body.ExpiresAt = time.Now().Add(time.Hour * 24) diff --git a/backend/app/api/handlers/v1/v1_ctrl_items.go b/backend/app/api/handlers/v1/v1_ctrl_items.go index dc557f6..d38c037 100644 --- a/backend/app/api/handlers/v1/v1_ctrl_items.go +++ b/backend/app/api/handlers/v1/v1_ctrl_items.go @@ -12,7 +12,8 @@ import ( "github.com/hay-kot/homebox/backend/internal/data/repo" "github.com/hay-kot/homebox/backend/internal/sys/validate" "github.com/hay-kot/homebox/backend/internal/web/adapters" - "github.com/hay-kot/homebox/backend/pkgs/server" + "github.com/hay-kot/safeserve/errchain" + "github.com/hay-kot/safeserve/server" "github.com/rs/zerolog/log" ) @@ -29,7 +30,7 @@ import ( // @Success 200 {object} repo.PaginationResult[repo.ItemSummary]{} // @Router /v1/items [GET] // @Security Bearer -func (ctrl *V1Controller) HandleItemsGetAll() server.HandlerFunc { +func (ctrl *V1Controller) HandleItemsGetAll() errchain.HandlerFunc { extractQuery := func(r *http.Request) repo.ItemQuery { params := r.URL.Query() @@ -78,14 +79,14 @@ func (ctrl *V1Controller) HandleItemsGetAll() server.HandlerFunc { items, err := ctrl.repo.Items.QueryByGroup(ctx, ctx.GID, extractQuery(r)) if err != nil { if errors.Is(err, sql.ErrNoRows) { - return server.Respond(w, http.StatusOK, repo.PaginationResult[repo.ItemSummary]{ + return server.JSON(w, http.StatusOK, repo.PaginationResult[repo.ItemSummary]{ Items: []repo.ItemSummary{}, }) } log.Err(err).Msg("failed to get items") return validate.NewRequestError(err, http.StatusInternalServerError) } - return server.Respond(w, http.StatusOK, items) + return server.JSON(w, http.StatusOK, items) } } @@ -98,7 +99,7 @@ func (ctrl *V1Controller) HandleItemsGetAll() server.HandlerFunc { // @Success 201 {object} repo.ItemSummary // @Router /v1/items [POST] // @Security Bearer -func (ctrl *V1Controller) HandleItemsCreate() server.HandlerFunc { +func (ctrl *V1Controller) HandleItemsCreate() errchain.HandlerFunc { fn := func(r *http.Request, body repo.ItemCreate) (repo.ItemOut, error) { return ctrl.svc.Items.Create(services.NewContext(r.Context()), body) } @@ -115,7 +116,7 @@ func (ctrl *V1Controller) HandleItemsCreate() server.HandlerFunc { // @Success 200 {object} repo.ItemOut // @Router /v1/items/{id} [GET] // @Security Bearer -func (ctrl *V1Controller) HandleItemGet() server.HandlerFunc { +func (ctrl *V1Controller) HandleItemGet() errchain.HandlerFunc { fn := func(r *http.Request, ID uuid.UUID) (repo.ItemOut, error) { auth := services.NewContext(r.Context()) @@ -134,7 +135,7 @@ func (ctrl *V1Controller) HandleItemGet() server.HandlerFunc { // @Success 204 // @Router /v1/items/{id} [DELETE] // @Security Bearer -func (ctrl *V1Controller) HandleItemDelete() server.HandlerFunc { +func (ctrl *V1Controller) HandleItemDelete() errchain.HandlerFunc { fn := func(r *http.Request, ID uuid.UUID) (any, error) { auth := services.NewContext(r.Context()) err := ctrl.repo.Items.DeleteByGroup(auth, auth.GID, ID) @@ -154,7 +155,7 @@ func (ctrl *V1Controller) HandleItemDelete() server.HandlerFunc { // @Success 200 {object} repo.ItemOut // @Router /v1/items/{id} [PUT] // @Security Bearer -func (ctrl *V1Controller) HandleItemUpdate() server.HandlerFunc { +func (ctrl *V1Controller) HandleItemUpdate() errchain.HandlerFunc { fn := func(r *http.Request, ID uuid.UUID, body repo.ItemUpdate) (repo.ItemOut, error) { auth := services.NewContext(r.Context()) @@ -174,7 +175,7 @@ func (ctrl *V1Controller) HandleItemUpdate() server.HandlerFunc { // @Router /v1/items/fields [GET] // @Success 200 {object} []string // @Security Bearer -func (ctrl *V1Controller) HandleGetAllCustomFieldNames() server.HandlerFunc { +func (ctrl *V1Controller) HandleGetAllCustomFieldNames() errchain.HandlerFunc { fn := func(r *http.Request) ([]string, error) { auth := services.NewContext(r.Context()) return ctrl.repo.Items.GetAllCustomFieldNames(auth, auth.GID) @@ -192,7 +193,7 @@ func (ctrl *V1Controller) HandleGetAllCustomFieldNames() server.HandlerFunc { // @Router /v1/items/fields/values [GET] // @Success 200 {object} []string // @Security Bearer -func (ctrl *V1Controller) HandleGetAllCustomFieldValues() server.HandlerFunc { +func (ctrl *V1Controller) HandleGetAllCustomFieldValues() errchain.HandlerFunc { type query struct { Field string `schema:"field" validate:"required"` } @@ -215,7 +216,7 @@ func (ctrl *V1Controller) HandleGetAllCustomFieldValues() server.HandlerFunc { // @Param csv formData file true "Image to upload" // @Router /v1/items/import [Post] // @Security Bearer -func (ctrl *V1Controller) HandleItemsImport() server.HandlerFunc { +func (ctrl *V1Controller) HandleItemsImport() errchain.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) error { err := r.ParseMultipartForm(ctrl.maxUploadSize << 20) if err != nil { @@ -237,7 +238,7 @@ func (ctrl *V1Controller) HandleItemsImport() server.HandlerFunc { return validate.NewRequestError(err, http.StatusInternalServerError) } - return server.Respond(w, http.StatusNoContent, nil) + return server.JSON(w, http.StatusNoContent, nil) } } @@ -248,7 +249,7 @@ func (ctrl *V1Controller) HandleItemsImport() server.HandlerFunc { // @Success 200 {string} string "text/csv" // @Router /v1/items/export [GET] // @Security Bearer -func (ctrl *V1Controller) HandleItemsExport() server.HandlerFunc { +func (ctrl *V1Controller) HandleItemsExport() errchain.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) error { ctx := services.NewContext(r.Context()) diff --git a/backend/app/api/handlers/v1/v1_ctrl_items_attachments.go b/backend/app/api/handlers/v1/v1_ctrl_items_attachments.go index b304bf5..9745538 100644 --- a/backend/app/api/handlers/v1/v1_ctrl_items_attachments.go +++ b/backend/app/api/handlers/v1/v1_ctrl_items_attachments.go @@ -8,7 +8,8 @@ import ( "github.com/hay-kot/homebox/backend/internal/data/ent/attachment" "github.com/hay-kot/homebox/backend/internal/data/repo" "github.com/hay-kot/homebox/backend/internal/sys/validate" - "github.com/hay-kot/homebox/backend/pkgs/server" + "github.com/hay-kot/safeserve/errchain" + "github.com/hay-kot/safeserve/server" "github.com/rs/zerolog/log" ) @@ -28,10 +29,10 @@ type ( // @Param type formData string true "Type of file" // @Param name formData string true "name of the file including extension" // @Success 200 {object} repo.ItemOut -// @Failure 422 {object} server.ErrorResponse +// @Failure 422 {object} mid.ErrorResponse // @Router /v1/items/{id}/attachments [POST] // @Security Bearer -func (ctrl *V1Controller) HandleItemAttachmentCreate() server.HandlerFunc { +func (ctrl *V1Controller) HandleItemAttachmentCreate() errchain.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) error { err := r.ParseMultipartForm(ctrl.maxUploadSize << 20) if err != nil { @@ -61,7 +62,7 @@ func (ctrl *V1Controller) HandleItemAttachmentCreate() server.HandlerFunc { } if !errs.Nil() { - return server.Respond(w, http.StatusUnprocessableEntity, errs) + return server.JSON(w, http.StatusUnprocessableEntity, errs) } attachmentType := r.FormValue("type") @@ -88,7 +89,7 @@ func (ctrl *V1Controller) HandleItemAttachmentCreate() server.HandlerFunc { return validate.NewRequestError(err, http.StatusInternalServerError) } - return server.Respond(w, http.StatusCreated, item) + return server.JSON(w, http.StatusCreated, item) } } @@ -102,7 +103,7 @@ func (ctrl *V1Controller) HandleItemAttachmentCreate() server.HandlerFunc { // @Success 200 {object} ItemAttachmentToken // @Router /v1/items/{id}/attachments/{attachment_id} [GET] // @Security Bearer -func (ctrl *V1Controller) HandleItemAttachmentGet() server.HandlerFunc { +func (ctrl *V1Controller) HandleItemAttachmentGet() errchain.HandlerFunc { return ctrl.handleItemAttachmentsHandler } @@ -115,7 +116,7 @@ func (ctrl *V1Controller) HandleItemAttachmentGet() server.HandlerFunc { // @Success 204 // @Router /v1/items/{id}/attachments/{attachment_id} [DELETE] // @Security Bearer -func (ctrl *V1Controller) HandleItemAttachmentDelete() server.HandlerFunc { +func (ctrl *V1Controller) HandleItemAttachmentDelete() errchain.HandlerFunc { return ctrl.handleItemAttachmentsHandler } @@ -129,7 +130,7 @@ func (ctrl *V1Controller) HandleItemAttachmentDelete() server.HandlerFunc { // @Success 200 {object} repo.ItemOut // @Router /v1/items/{id}/attachments/{attachment_id} [PUT] // @Security Bearer -func (ctrl *V1Controller) HandleItemAttachmentUpdate() server.HandlerFunc { +func (ctrl *V1Controller) HandleItemAttachmentUpdate() errchain.HandlerFunc { return ctrl.handleItemAttachmentsHandler } @@ -164,7 +165,7 @@ func (ctrl *V1Controller) handleItemAttachmentsHandler(w http.ResponseWriter, r return validate.NewRequestError(err, http.StatusInternalServerError) } - return server.Respond(w, http.StatusNoContent, nil) + return server.JSON(w, http.StatusNoContent, nil) // Update Attachment Handler case http.MethodPut: @@ -182,7 +183,7 @@ func (ctrl *V1Controller) handleItemAttachmentsHandler(w http.ResponseWriter, r return validate.NewRequestError(err, http.StatusInternalServerError) } - return server.Respond(w, http.StatusOK, val) + return server.JSON(w, http.StatusOK, val) } return nil diff --git a/backend/app/api/handlers/v1/v1_ctrl_labels.go b/backend/app/api/handlers/v1/v1_ctrl_labels.go index cbd8d6a..0dabe6f 100644 --- a/backend/app/api/handlers/v1/v1_ctrl_labels.go +++ b/backend/app/api/handlers/v1/v1_ctrl_labels.go @@ -7,7 +7,7 @@ import ( "github.com/hay-kot/homebox/backend/internal/core/services" "github.com/hay-kot/homebox/backend/internal/data/repo" "github.com/hay-kot/homebox/backend/internal/web/adapters" - "github.com/hay-kot/homebox/backend/pkgs/server" + "github.com/hay-kot/safeserve/errchain" ) // HandleLabelsGetAll godoc @@ -15,10 +15,10 @@ import ( // @Summary Get All Labels // @Tags Labels // @Produce json -// @Success 200 {object} server.Results{items=[]repo.LabelOut} +// @Success 200 {object} Wrapped{items=[]repo.LabelOut} // @Router /v1/labels [GET] // @Security Bearer -func (ctrl *V1Controller) HandleLabelsGetAll() server.HandlerFunc { +func (ctrl *V1Controller) HandleLabelsGetAll() errchain.HandlerFunc { fn := func(r *http.Request) ([]repo.LabelSummary, error) { auth := services.NewContext(r.Context()) return ctrl.repo.Labels.GetAll(auth, auth.GID) @@ -36,7 +36,7 @@ func (ctrl *V1Controller) HandleLabelsGetAll() server.HandlerFunc { // @Success 200 {object} repo.LabelSummary // @Router /v1/labels [POST] // @Security Bearer -func (ctrl *V1Controller) HandleLabelsCreate() server.HandlerFunc { +func (ctrl *V1Controller) HandleLabelsCreate() errchain.HandlerFunc { fn := func(r *http.Request, data repo.LabelCreate) (repo.LabelOut, error) { auth := services.NewContext(r.Context()) return ctrl.repo.Labels.Create(auth, auth.GID, data) @@ -54,7 +54,7 @@ func (ctrl *V1Controller) HandleLabelsCreate() server.HandlerFunc { // @Success 204 // @Router /v1/labels/{id} [DELETE] // @Security Bearer -func (ctrl *V1Controller) HandleLabelDelete() server.HandlerFunc { +func (ctrl *V1Controller) HandleLabelDelete() errchain.HandlerFunc { fn := func(r *http.Request, ID uuid.UUID) (any, error) { auth := services.NewContext(r.Context()) err := ctrl.repo.Labels.DeleteByGroup(auth, auth.GID, ID) @@ -73,7 +73,7 @@ func (ctrl *V1Controller) HandleLabelDelete() server.HandlerFunc { // @Success 200 {object} repo.LabelOut // @Router /v1/labels/{id} [GET] // @Security Bearer -func (ctrl *V1Controller) HandleLabelGet() server.HandlerFunc { +func (ctrl *V1Controller) HandleLabelGet() errchain.HandlerFunc { fn := func(r *http.Request, ID uuid.UUID) (repo.LabelOut, error) { auth := services.NewContext(r.Context()) return ctrl.repo.Labels.GetOneByGroup(auth, auth.GID, ID) @@ -91,7 +91,7 @@ func (ctrl *V1Controller) HandleLabelGet() server.HandlerFunc { // @Success 200 {object} repo.LabelOut // @Router /v1/labels/{id} [PUT] // @Security Bearer -func (ctrl *V1Controller) HandleLabelUpdate() server.HandlerFunc { +func (ctrl *V1Controller) HandleLabelUpdate() errchain.HandlerFunc { fn := func(r *http.Request, ID uuid.UUID, data repo.LabelUpdate) (repo.LabelOut, error) { auth := services.NewContext(r.Context()) data.ID = ID diff --git a/backend/app/api/handlers/v1/v1_ctrl_locations.go b/backend/app/api/handlers/v1/v1_ctrl_locations.go index c0a91a8..b495c66 100644 --- a/backend/app/api/handlers/v1/v1_ctrl_locations.go +++ b/backend/app/api/handlers/v1/v1_ctrl_locations.go @@ -7,7 +7,7 @@ import ( "github.com/hay-kot/homebox/backend/internal/core/services" "github.com/hay-kot/homebox/backend/internal/data/repo" "github.com/hay-kot/homebox/backend/internal/web/adapters" - "github.com/hay-kot/homebox/backend/pkgs/server" + "github.com/hay-kot/safeserve/errchain" ) // HandleLocationTreeQuery @@ -16,10 +16,10 @@ import ( // @Tags Locations // @Produce json // @Param withItems query bool false "include items in response tree" -// @Success 200 {object} server.Results{items=[]repo.TreeItem} +// @Success 200 {object} Wrapped{items=[]repo.TreeItem} // @Router /v1/locations/tree [GET] // @Security Bearer -func (ctrl *V1Controller) HandleLocationTreeQuery() server.HandlerFunc { +func (ctrl *V1Controller) HandleLocationTreeQuery() errchain.HandlerFunc { fn := func(r *http.Request, query repo.TreeQuery) ([]repo.TreeItem, error) { auth := services.NewContext(r.Context()) return ctrl.repo.Locations.Tree(auth, auth.GID, query) @@ -34,10 +34,10 @@ func (ctrl *V1Controller) HandleLocationTreeQuery() server.HandlerFunc { // @Tags Locations // @Produce json // @Param filterChildren query bool false "Filter locations with parents" -// @Success 200 {object} server.Results{items=[]repo.LocationOutCount} +// @Success 200 {object} Wrapped{items=[]repo.LocationOutCount} // @Router /v1/locations [GET] // @Security Bearer -func (ctrl *V1Controller) HandleLocationGetAll() server.HandlerFunc { +func (ctrl *V1Controller) HandleLocationGetAll() errchain.HandlerFunc { fn := func(r *http.Request, q repo.LocationQuery) ([]repo.LocationOutCount, error) { auth := services.NewContext(r.Context()) return ctrl.repo.Locations.GetAll(auth, auth.GID, q) @@ -55,7 +55,7 @@ func (ctrl *V1Controller) HandleLocationGetAll() server.HandlerFunc { // @Success 200 {object} repo.LocationSummary // @Router /v1/locations [POST] // @Security Bearer -func (ctrl *V1Controller) HandleLocationCreate() server.HandlerFunc { +func (ctrl *V1Controller) HandleLocationCreate() errchain.HandlerFunc { fn := func(r *http.Request, createData repo.LocationCreate) (repo.LocationOut, error) { auth := services.NewContext(r.Context()) return ctrl.repo.Locations.Create(auth, auth.GID, createData) @@ -73,7 +73,7 @@ func (ctrl *V1Controller) HandleLocationCreate() server.HandlerFunc { // @Success 204 // @Router /v1/locations/{id} [DELETE] // @Security Bearer -func (ctrl *V1Controller) HandleLocationDelete() server.HandlerFunc { +func (ctrl *V1Controller) HandleLocationDelete() errchain.HandlerFunc { fn := func(r *http.Request, ID uuid.UUID) (any, error) { auth := services.NewContext(r.Context()) err := ctrl.repo.Locations.DeleteByGroup(auth, auth.GID, ID) @@ -92,7 +92,7 @@ func (ctrl *V1Controller) HandleLocationDelete() server.HandlerFunc { // @Success 200 {object} repo.LocationOut // @Router /v1/locations/{id} [GET] // @Security Bearer -func (ctrl *V1Controller) HandleLocationGet() server.HandlerFunc { +func (ctrl *V1Controller) HandleLocationGet() errchain.HandlerFunc { fn := func(r *http.Request, ID uuid.UUID) (repo.LocationOut, error) { auth := services.NewContext(r.Context()) return ctrl.repo.Locations.GetOneByGroup(auth, auth.GID, ID) @@ -111,7 +111,7 @@ func (ctrl *V1Controller) HandleLocationGet() server.HandlerFunc { // @Success 200 {object} repo.LocationOut // @Router /v1/locations/{id} [PUT] // @Security Bearer -func (ctrl *V1Controller) HandleLocationUpdate() server.HandlerFunc { +func (ctrl *V1Controller) HandleLocationUpdate() errchain.HandlerFunc { fn := func(r *http.Request, ID uuid.UUID, body repo.LocationUpdate) (repo.LocationOut, error) { auth := services.NewContext(r.Context()) body.ID = ID diff --git a/backend/app/api/handlers/v1/v1_ctrl_maint_entry.go b/backend/app/api/handlers/v1/v1_ctrl_maint_entry.go index 9cdeb46..eeed717 100644 --- a/backend/app/api/handlers/v1/v1_ctrl_maint_entry.go +++ b/backend/app/api/handlers/v1/v1_ctrl_maint_entry.go @@ -7,7 +7,7 @@ import ( "github.com/hay-kot/homebox/backend/internal/core/services" "github.com/hay-kot/homebox/backend/internal/data/repo" "github.com/hay-kot/homebox/backend/internal/web/adapters" - "github.com/hay-kot/homebox/backend/pkgs/server" + "github.com/hay-kot/safeserve/errchain" ) // HandleMaintenanceGetLog godoc @@ -18,7 +18,7 @@ import ( // @Success 200 {object} repo.MaintenanceLog // @Router /v1/items/{id}/maintenance [GET] // @Security Bearer -func (ctrl *V1Controller) HandleMaintenanceLogGet() server.HandlerFunc { +func (ctrl *V1Controller) HandleMaintenanceLogGet() errchain.HandlerFunc { fn := func(r *http.Request, ID uuid.UUID, q repo.MaintenanceLogQuery) (repo.MaintenanceLog, error) { auth := services.NewContext(r.Context()) return ctrl.repo.MaintEntry.GetLog(auth, auth.GID, ID, q) @@ -36,7 +36,7 @@ func (ctrl *V1Controller) HandleMaintenanceLogGet() server.HandlerFunc { // @Success 201 {object} repo.MaintenanceEntry // @Router /v1/items/{id}/maintenance [POST] // @Security Bearer -func (ctrl *V1Controller) HandleMaintenanceEntryCreate() server.HandlerFunc { +func (ctrl *V1Controller) HandleMaintenanceEntryCreate() errchain.HandlerFunc { fn := func(r *http.Request, itemID uuid.UUID, body repo.MaintenanceEntryCreate) (repo.MaintenanceEntry, error) { auth := services.NewContext(r.Context()) return ctrl.repo.MaintEntry.Create(auth, itemID, body) @@ -53,7 +53,7 @@ func (ctrl *V1Controller) HandleMaintenanceEntryCreate() server.HandlerFunc { // @Success 204 // @Router /v1/items/{id}/maintenance/{entry_id} [DELETE] // @Security Bearer -func (ctrl *V1Controller) HandleMaintenanceEntryDelete() server.HandlerFunc { +func (ctrl *V1Controller) HandleMaintenanceEntryDelete() errchain.HandlerFunc { fn := func(r *http.Request, entryID uuid.UUID) (any, error) { auth := services.NewContext(r.Context()) err := ctrl.repo.MaintEntry.Delete(auth, entryID) @@ -72,7 +72,7 @@ func (ctrl *V1Controller) HandleMaintenanceEntryDelete() server.HandlerFunc { // @Success 200 {object} repo.MaintenanceEntry // @Router /v1/items/{id}/maintenance/{entry_id} [PUT] // @Security Bearer -func (ctrl *V1Controller) HandleMaintenanceEntryUpdate() server.HandlerFunc { +func (ctrl *V1Controller) HandleMaintenanceEntryUpdate() errchain.HandlerFunc { fn := func(r *http.Request, entryID uuid.UUID, body repo.MaintenanceEntryUpdate) (repo.MaintenanceEntry, error) { auth := services.NewContext(r.Context()) return ctrl.repo.MaintEntry.Update(auth, entryID, body) diff --git a/backend/app/api/handlers/v1/v1_ctrl_notifiers.go b/backend/app/api/handlers/v1/v1_ctrl_notifiers.go index 4ee5d32..51da4d7 100644 --- a/backend/app/api/handlers/v1/v1_ctrl_notifiers.go +++ b/backend/app/api/handlers/v1/v1_ctrl_notifiers.go @@ -8,7 +8,7 @@ import ( "github.com/hay-kot/homebox/backend/internal/core/services" "github.com/hay-kot/homebox/backend/internal/data/repo" "github.com/hay-kot/homebox/backend/internal/web/adapters" - "github.com/hay-kot/homebox/backend/pkgs/server" + "github.com/hay-kot/safeserve/errchain" ) // HandleGetUserNotifiers godoc @@ -16,10 +16,10 @@ import ( // @Summary Get Notifiers // @Tags Notifiers // @Produce json -// @Success 200 {object} server.Results{items=[]repo.NotifierOut} +// @Success 200 {object} Wrapped{items=[]repo.NotifierOut} // @Router /v1/notifiers [GET] // @Security Bearer -func (ctrl *V1Controller) HandleGetUserNotifiers() server.HandlerFunc { +func (ctrl *V1Controller) HandleGetUserNotifiers() errchain.HandlerFunc { fn := func(r *http.Request, _ struct{}) ([]repo.NotifierOut, error) { user := services.UseUserCtx(r.Context()) return ctrl.repo.Notifiers.GetByUser(r.Context(), user.ID) @@ -37,7 +37,7 @@ func (ctrl *V1Controller) HandleGetUserNotifiers() server.HandlerFunc { // @Success 200 {object} repo.NotifierOut // @Router /v1/notifiers [POST] // @Security Bearer -func (ctrl *V1Controller) HandleCreateNotifier() server.HandlerFunc { +func (ctrl *V1Controller) HandleCreateNotifier() errchain.HandlerFunc { fn := func(r *http.Request, in repo.NotifierCreate) (repo.NotifierOut, error) { auth := services.NewContext(r.Context()) return ctrl.repo.Notifiers.Create(auth, auth.GID, auth.UID, in) @@ -54,7 +54,7 @@ func (ctrl *V1Controller) HandleCreateNotifier() server.HandlerFunc { // @Success 204 // @Router /v1/notifiers/{id} [DELETE] // @Security Bearer -func (ctrl *V1Controller) HandleDeleteNotifier() server.HandlerFunc { +func (ctrl *V1Controller) HandleDeleteNotifier() errchain.HandlerFunc { fn := func(r *http.Request, ID uuid.UUID) (any, error) { auth := services.NewContext(r.Context()) return nil, ctrl.repo.Notifiers.Delete(auth, auth.UID, ID) @@ -72,7 +72,7 @@ func (ctrl *V1Controller) HandleDeleteNotifier() server.HandlerFunc { // @Success 200 {object} repo.NotifierOut // @Router /v1/notifiers/{id} [PUT] // @Security Bearer -func (ctrl *V1Controller) HandleUpdateNotifier() server.HandlerFunc { +func (ctrl *V1Controller) HandleUpdateNotifier() errchain.HandlerFunc { fn := func(r *http.Request, ID uuid.UUID, in repo.NotifierUpdate) (repo.NotifierOut, error) { auth := services.NewContext(r.Context()) return ctrl.repo.Notifiers.Update(auth, auth.UID, ID, in) @@ -91,7 +91,7 @@ func (ctrl *V1Controller) HandleUpdateNotifier() server.HandlerFunc { // @Success 204 // @Router /v1/notifiers/test [POST] // @Security Bearer -func (ctrl *V1Controller) HandlerNotifierTest() server.HandlerFunc { +func (ctrl *V1Controller) HandlerNotifierTest() errchain.HandlerFunc { type body struct { URL string `json:"url" validate:"required"` } diff --git a/backend/app/api/handlers/v1/v1_ctrl_qrcode.go b/backend/app/api/handlers/v1/v1_ctrl_qrcode.go index 8aa549f..1f06e2f 100644 --- a/backend/app/api/handlers/v1/v1_ctrl_qrcode.go +++ b/backend/app/api/handlers/v1/v1_ctrl_qrcode.go @@ -7,7 +7,7 @@ import ( "net/http" "github.com/hay-kot/homebox/backend/internal/web/adapters" - "github.com/hay-kot/homebox/backend/pkgs/server" + "github.com/hay-kot/safeserve/errchain" "github.com/yeqown/go-qrcode/v2" "github.com/yeqown/go-qrcode/writer/standard" @@ -26,7 +26,7 @@ var qrcodeLogo []byte // @Success 200 {string} string "image/jpeg" // @Router /v1/qrcode [GET] // @Security Bearer -func (ctrl *V1Controller) HandleGenerateQRCode() server.HandlerFunc { +func (ctrl *V1Controller) HandleGenerateQRCode() errchain.HandlerFunc { type query struct { // 4,296 characters is the maximum length of a QR code Data string `schema:"data" validate:"required,max=4296"` diff --git a/backend/app/api/handlers/v1/v1_ctrl_reporting.go b/backend/app/api/handlers/v1/v1_ctrl_reporting.go index b665edd..030a1ff 100644 --- a/backend/app/api/handlers/v1/v1_ctrl_reporting.go +++ b/backend/app/api/handlers/v1/v1_ctrl_reporting.go @@ -4,7 +4,7 @@ import ( "net/http" "github.com/hay-kot/homebox/backend/internal/core/services" - "github.com/hay-kot/homebox/backend/pkgs/server" + "github.com/hay-kot/safeserve/errchain" ) // HandleBillOfMaterialsExport godoc @@ -15,7 +15,7 @@ import ( // @Success 200 {string} string "text/csv" // @Router /v1/reporting/bill-of-materials [GET] // @Security Bearer -func (ctrl *V1Controller) HandleBillOfMaterialsExport() server.HandlerFunc { +func (ctrl *V1Controller) HandleBillOfMaterialsExport() errchain.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) error { actor := services.UseUserCtx(r.Context()) diff --git a/backend/app/api/handlers/v1/v1_ctrl_statistics.go b/backend/app/api/handlers/v1/v1_ctrl_statistics.go index 2505662..55a8dcb 100644 --- a/backend/app/api/handlers/v1/v1_ctrl_statistics.go +++ b/backend/app/api/handlers/v1/v1_ctrl_statistics.go @@ -8,7 +8,8 @@ import ( "github.com/hay-kot/homebox/backend/internal/data/repo" "github.com/hay-kot/homebox/backend/internal/sys/validate" "github.com/hay-kot/homebox/backend/internal/web/adapters" - "github.com/hay-kot/homebox/backend/pkgs/server" + "github.com/hay-kot/safeserve/errchain" + "github.com/hay-kot/safeserve/server" ) // HandleGroupGet godoc @@ -19,7 +20,7 @@ import ( // @Success 200 {object} []repo.TotalsByOrganizer // @Router /v1/groups/statistics/locations [GET] // @Security Bearer -func (ctrl *V1Controller) HandleGroupStatisticsLocations() server.HandlerFunc { +func (ctrl *V1Controller) HandleGroupStatisticsLocations() errchain.HandlerFunc { fn := func(r *http.Request) ([]repo.TotalsByOrganizer, error) { auth := services.NewContext(r.Context()) return ctrl.repo.Groups.StatsLocationsByPurchasePrice(auth, auth.GID) @@ -36,7 +37,7 @@ func (ctrl *V1Controller) HandleGroupStatisticsLocations() server.HandlerFunc { // @Success 200 {object} []repo.TotalsByOrganizer // @Router /v1/groups/statistics/labels [GET] // @Security Bearer -func (ctrl *V1Controller) HandleGroupStatisticsLabels() server.HandlerFunc { +func (ctrl *V1Controller) HandleGroupStatisticsLabels() errchain.HandlerFunc { fn := func(r *http.Request) ([]repo.TotalsByOrganizer, error) { auth := services.NewContext(r.Context()) return ctrl.repo.Groups.StatsLabelsByPurchasePrice(auth, auth.GID) @@ -53,7 +54,7 @@ func (ctrl *V1Controller) HandleGroupStatisticsLabels() server.HandlerFunc { // @Success 200 {object} repo.GroupStatistics // @Router /v1/groups/statistics [GET] // @Security Bearer -func (ctrl *V1Controller) HandleGroupStatistics() server.HandlerFunc { +func (ctrl *V1Controller) HandleGroupStatistics() errchain.HandlerFunc { fn := func(r *http.Request) (repo.GroupStatistics, error) { auth := services.NewContext(r.Context()) return ctrl.repo.Groups.StatsGroup(auth, auth.GID) @@ -72,7 +73,7 @@ func (ctrl *V1Controller) HandleGroupStatistics() server.HandlerFunc { // @Param end query string false "end date" // @Router /v1/groups/statistics/purchase-price [GET] // @Security Bearer -func (ctrl *V1Controller) HandleGroupStatisticsPriceOverTime() server.HandlerFunc { +func (ctrl *V1Controller) HandleGroupStatisticsPriceOverTime() errchain.HandlerFunc { parseDate := func(datestr string, defaultDate time.Time) (time.Time, error) { if datestr == "" { return defaultDate, nil @@ -98,6 +99,6 @@ func (ctrl *V1Controller) HandleGroupStatisticsPriceOverTime() server.HandlerFun return validate.NewRequestError(err, http.StatusInternalServerError) } - return server.Respond(w, http.StatusOK, stats) + return server.JSON(w, http.StatusOK, stats) } } diff --git a/backend/app/api/handlers/v1/v1_ctrl_user.go b/backend/app/api/handlers/v1/v1_ctrl_user.go index 8331496..cc57305 100644 --- a/backend/app/api/handlers/v1/v1_ctrl_user.go +++ b/backend/app/api/handlers/v1/v1_ctrl_user.go @@ -8,7 +8,8 @@ import ( "github.com/hay-kot/homebox/backend/internal/core/services" "github.com/hay-kot/homebox/backend/internal/data/repo" "github.com/hay-kot/homebox/backend/internal/sys/validate" - "github.com/hay-kot/homebox/backend/pkgs/server" + "github.com/hay-kot/safeserve/errchain" + "github.com/hay-kot/safeserve/server" "github.com/rs/zerolog/log" ) @@ -20,7 +21,7 @@ import ( // @Param payload body services.UserRegistration true "User Data" // @Success 204 // @Router /v1/users/register [Post] -func (ctrl *V1Controller) HandleUserRegistration() server.HandlerFunc { +func (ctrl *V1Controller) HandleUserRegistration() errchain.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) error { regData := services.UserRegistration{} @@ -39,7 +40,7 @@ func (ctrl *V1Controller) HandleUserRegistration() server.HandlerFunc { return validate.NewRequestError(err, http.StatusInternalServerError) } - return server.Respond(w, http.StatusNoContent, nil) + return server.JSON(w, http.StatusNoContent, nil) } } @@ -48,10 +49,10 @@ func (ctrl *V1Controller) HandleUserRegistration() server.HandlerFunc { // @Summary Get User Self // @Tags User // @Produce json -// @Success 200 {object} server.Result{item=repo.UserOut} +// @Success 200 {object} Wrapped{item=repo.UserOut} // @Router /v1/users/self [GET] // @Security Bearer -func (ctrl *V1Controller) HandleUserSelf() server.HandlerFunc { +func (ctrl *V1Controller) HandleUserSelf() errchain.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) error { token := services.UseTokenCtx(r.Context()) usr, err := ctrl.svc.User.GetSelf(r.Context(), token) @@ -60,7 +61,7 @@ func (ctrl *V1Controller) HandleUserSelf() server.HandlerFunc { return validate.NewRequestError(err, http.StatusInternalServerError) } - return server.Respond(w, http.StatusOK, server.Wrap(usr)) + return server.JSON(w, http.StatusOK, Wrap(usr)) } } @@ -70,10 +71,10 @@ func (ctrl *V1Controller) HandleUserSelf() server.HandlerFunc { // @Tags User // @Produce json // @Param payload body repo.UserUpdate true "User Data" -// @Success 200 {object} server.Result{item=repo.UserUpdate} +// @Success 200 {object} Wrapped{item=repo.UserUpdate} // @Router /v1/users/self [PUT] // @Security Bearer -func (ctrl *V1Controller) HandleUserSelfUpdate() server.HandlerFunc { +func (ctrl *V1Controller) HandleUserSelfUpdate() errchain.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) error { updateData := repo.UserUpdate{} if err := server.Decode(r, &updateData); err != nil { @@ -87,7 +88,7 @@ func (ctrl *V1Controller) HandleUserSelfUpdate() server.HandlerFunc { return validate.NewRequestError(err, http.StatusInternalServerError) } - return server.Respond(w, http.StatusOK, server.Wrap(newData)) + return server.JSON(w, http.StatusOK, Wrap(newData)) } } @@ -99,7 +100,7 @@ func (ctrl *V1Controller) HandleUserSelfUpdate() server.HandlerFunc { // @Success 204 // @Router /v1/users/self [DELETE] // @Security Bearer -func (ctrl *V1Controller) HandleUserSelfDelete() server.HandlerFunc { +func (ctrl *V1Controller) HandleUserSelfDelete() errchain.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) error { if ctrl.isDemo { return validate.NewRequestError(nil, http.StatusForbidden) @@ -110,7 +111,7 @@ func (ctrl *V1Controller) HandleUserSelfDelete() server.HandlerFunc { return validate.NewRequestError(err, http.StatusInternalServerError) } - return server.Respond(w, http.StatusNoContent, nil) + return server.JSON(w, http.StatusNoContent, nil) } } @@ -129,7 +130,7 @@ type ( // @Param payload body ChangePassword true "Password Payload" // @Router /v1/users/change-password [PUT] // @Security Bearer -func (ctrl *V1Controller) HandleUserSelfChangePassword() server.HandlerFunc { +func (ctrl *V1Controller) HandleUserSelfChangePassword() errchain.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) error { if ctrl.isDemo { return validate.NewRequestError(nil, http.StatusForbidden) @@ -148,6 +149,6 @@ func (ctrl *V1Controller) HandleUserSelfChangePassword() server.HandlerFunc { return validate.NewRequestError(err, http.StatusInternalServerError) } - return server.Respond(w, http.StatusNoContent, nil) + return server.JSON(w, http.StatusNoContent, nil) } } diff --git a/backend/app/api/main.go b/backend/app/api/main.go index e916fe5..c92572a 100644 --- a/backend/app/api/main.go +++ b/backend/app/api/main.go @@ -9,6 +9,9 @@ import ( atlas "ariga.io/atlas/sql/migrate" "entgo.io/ent/dialect/sql/schema" + "github.com/go-chi/chi/v5" + "github.com/go-chi/chi/v5/middleware" + "github.com/hay-kot/homebox/backend/app/api/static/docs" "github.com/hay-kot/homebox/backend/internal/core/services" "github.com/hay-kot/homebox/backend/internal/data/ent" @@ -16,7 +19,8 @@ import ( "github.com/hay-kot/homebox/backend/internal/data/repo" "github.com/hay-kot/homebox/backend/internal/sys/config" "github.com/hay-kot/homebox/backend/internal/web/mid" - "github.com/hay-kot/homebox/backend/pkgs/server" + "github.com/hay-kot/safeserve/errchain" + "github.com/hay-kot/safeserve/server" _ "github.com/mattn/go-sqlite3" "github.com/rs/zerolog" "github.com/rs/zerolog/log" @@ -123,25 +127,26 @@ func run(cfg *config.Config) error { // ========================================================================= // Start Server + logger := log.With().Caller().Logger() - mwLogger := mid.Logger(logger) - if app.conf.Mode == config.ModeDevelopment { - mwLogger = mid.SugarLogger(logger) - } + router := chi.NewMux() + router.Use( + middleware.RequestID, + middleware.RealIP, + mid.Logger(logger), + middleware.Recoverer, + middleware.StripSlashes, + ) + + chain := errchain.New(mid.Errors(app.server, logger)) + + app.mountRoutes(router, chain, app.repos) app.server = server.NewServer( server.WithHost(app.conf.Web.Host), server.WithPort(app.conf.Web.Port), - server.WithMiddleware( - mwLogger, - mid.Errors(logger), - mid.Panic(app.conf.Mode == config.ModeDevelopment), - ), ) - - app.mountRoutes(app.repos) - log.Info().Msgf("Starting HTTP Server on %s:%s", app.server.Host, app.server.Port) // ========================================================================= @@ -179,5 +184,5 @@ func run(cfg *config.Config) error { }() } - return app.server.Start() + return app.server.Start(router) } diff --git a/backend/app/api/middleware.go b/backend/app/api/middleware.go index c694618..3617c09 100644 --- a/backend/app/api/middleware.go +++ b/backend/app/api/middleware.go @@ -9,7 +9,7 @@ import ( "github.com/hay-kot/homebox/backend/internal/core/services" "github.com/hay-kot/homebox/backend/internal/sys/validate" - "github.com/hay-kot/homebox/backend/pkgs/server" + "github.com/hay-kot/safeserve/errchain" ) type tokenHasKey struct { @@ -30,9 +30,9 @@ const ( // the required roles, a 403 Forbidden will be returned. // // WARNING: This middleware _MUST_ be called after mwAuthToken or else it will panic -func (a *app) mwRoles(rm RoleMode, required ...string) server.Middleware { - return func(next server.Handler) server.Handler { - return server.HandlerFunc(func(w http.ResponseWriter, r *http.Request) error { +func (a *app) mwRoles(rm RoleMode, required ...string) errchain.Middleware { + return func(next errchain.Handler) errchain.Handler { + return errchain.HandlerFunc(func(w http.ResponseWriter, r *http.Request) error { ctx := r.Context() maybeToken := ctx.Value(hashedToken) @@ -116,8 +116,8 @@ func getCookie(r *http.Request) (string, error) { // - header = "Bearer 1234567890" // - query = "?access_token=1234567890" // - cookie = hb.auth.token = 1234567890 -func (a *app) mwAuthToken(next server.Handler) server.Handler { - return server.HandlerFunc(func(w http.ResponseWriter, r *http.Request) error { +func (a *app) mwAuthToken(next errchain.Handler) errchain.Handler { + return errchain.HandlerFunc(func(w http.ResponseWriter, r *http.Request) error { keyFuncs := [...]KeyFunc{ getBearer, getCookie, diff --git a/backend/app/api/routes.go b/backend/app/api/routes.go index 1b08cca..e36ee02 100644 --- a/backend/app/api/routes.go +++ b/backend/app/api/routes.go @@ -10,12 +10,13 @@ import ( "path" "path/filepath" + "github.com/go-chi/chi/v5" "github.com/hay-kot/homebox/backend/app/api/handlers/debughandlers" v1 "github.com/hay-kot/homebox/backend/app/api/handlers/v1" _ "github.com/hay-kot/homebox/backend/app/api/static/docs" "github.com/hay-kot/homebox/backend/internal/data/ent/authroles" "github.com/hay-kot/homebox/backend/internal/data/repo" - "github.com/hay-kot/homebox/backend/pkgs/server" + "github.com/hay-kot/safeserve/errchain" httpSwagger "github.com/swaggo/http-swagger" // http-swagger middleware ) @@ -36,12 +37,12 @@ func (a *app) debugRouter() *http.ServeMux { } // registerRoutes registers all the routes for the API -func (a *app) mountRoutes(repos *repo.AllRepos) { +func (a *app) mountRoutes(r *chi.Mux, chain *errchain.ErrChain, repos *repo.AllRepos) { registerMimes() - a.server.Get("/swagger/*", server.ToHandler(httpSwagger.Handler( + r.Get("/swagger/*", httpSwagger.Handler( httpSwagger.URL(fmt.Sprintf("%s://%s/swagger/doc.json", a.conf.Swagger.Scheme, a.conf.Swagger.Host)), - ))) + )) // ========================================================================= // API Version 1 @@ -56,99 +57,103 @@ func (a *app) mountRoutes(repos *repo.AllRepos) { v1.WithDemoStatus(a.conf.Demo), // Disable Password Change in Demo Mode ) - a.server.Get(v1Base("/status"), v1Ctrl.HandleBase(func() bool { return true }, v1.Build{ + r.Get(v1Base("/status"), chain.ToHandlerFunc(v1Ctrl.HandleBase(func() bool { return true }, v1.Build{ Version: version, Commit: commit, BuildTime: buildTime, - })) + }))) - a.server.Post(v1Base("/users/register"), v1Ctrl.HandleUserRegistration()) - a.server.Post(v1Base("/users/login"), v1Ctrl.HandleAuthLogin()) + r.Post(v1Base("/users/register"), chain.ToHandlerFunc(v1Ctrl.HandleUserRegistration())) + r.Post(v1Base("/users/login"), chain.ToHandlerFunc(v1Ctrl.HandleAuthLogin())) - userMW := []server.Middleware{ + userMW := []errchain.Middleware{ a.mwAuthToken, a.mwRoles(RoleModeOr, authroles.RoleUser.String()), } - a.server.Get(v1Base("/users/self"), v1Ctrl.HandleUserSelf(), userMW...) - a.server.Put(v1Base("/users/self"), v1Ctrl.HandleUserSelfUpdate(), userMW...) - a.server.Delete(v1Base("/users/self"), v1Ctrl.HandleUserSelfDelete(), userMW...) - a.server.Post(v1Base("/users/logout"), v1Ctrl.HandleAuthLogout(), userMW...) - a.server.Get(v1Base("/users/refresh"), v1Ctrl.HandleAuthRefresh(), userMW...) - a.server.Put(v1Base("/users/self/change-password"), v1Ctrl.HandleUserSelfChangePassword(), userMW...) + r.Get(v1Base("/users/self"), chain.ToHandlerFunc(v1Ctrl.HandleUserSelf(), userMW...)) + r.Put(v1Base("/users/self"), chain.ToHandlerFunc(v1Ctrl.HandleUserSelfUpdate(), userMW...)) + r.Delete(v1Base("/users/self"), chain.ToHandlerFunc(v1Ctrl.HandleUserSelfDelete(), userMW...)) + r.Post(v1Base("/users/logout"), chain.ToHandlerFunc(v1Ctrl.HandleAuthLogout(), userMW...)) + r.Get(v1Base("/users/refresh"), chain.ToHandlerFunc(v1Ctrl.HandleAuthRefresh(), userMW...)) + r.Put(v1Base("/users/self/change-password"), chain.ToHandlerFunc(v1Ctrl.HandleUserSelfChangePassword(), userMW...)) - a.server.Post(v1Base("/groups/invitations"), v1Ctrl.HandleGroupInvitationsCreate(), userMW...) - a.server.Get(v1Base("/groups/statistics"), v1Ctrl.HandleGroupStatistics(), userMW...) - a.server.Get(v1Base("/groups/statistics/purchase-price"), v1Ctrl.HandleGroupStatisticsPriceOverTime(), userMW...) - a.server.Get(v1Base("/groups/statistics/locations"), v1Ctrl.HandleGroupStatisticsLocations(), userMW...) - a.server.Get(v1Base("/groups/statistics/labels"), v1Ctrl.HandleGroupStatisticsLabels(), userMW...) + r.Post(v1Base("/groups/invitations"), chain.ToHandlerFunc(v1Ctrl.HandleGroupInvitationsCreate(), userMW...)) + r.Get(v1Base("/groups/statistics"), chain.ToHandlerFunc(v1Ctrl.HandleGroupStatistics(), userMW...)) + r.Get(v1Base("/groups/statistics/purchase-price"), chain.ToHandlerFunc(v1Ctrl.HandleGroupStatisticsPriceOverTime(), userMW...)) + r.Get(v1Base("/groups/statistics/locations"), chain.ToHandlerFunc(v1Ctrl.HandleGroupStatisticsLocations(), userMW...)) + r.Get(v1Base("/groups/statistics/labels"), chain.ToHandlerFunc(v1Ctrl.HandleGroupStatisticsLabels(), userMW...)) // TODO: I don't like /groups being the URL for users - a.server.Get(v1Base("/groups"), v1Ctrl.HandleGroupGet(), userMW...) - a.server.Put(v1Base("/groups"), v1Ctrl.HandleGroupUpdate(), userMW...) + r.Get(v1Base("/groups"), chain.ToHandlerFunc(v1Ctrl.HandleGroupGet(), userMW...)) + r.Put(v1Base("/groups"), chain.ToHandlerFunc(v1Ctrl.HandleGroupUpdate(), userMW...)) - a.server.Post(v1Base("/actions/ensure-asset-ids"), v1Ctrl.HandleEnsureAssetID(), userMW...) - a.server.Post(v1Base("/actions/zero-item-time-fields"), v1Ctrl.HandleItemDateZeroOut(), userMW...) - a.server.Post(v1Base("/actions/ensure-import-refs"), v1Ctrl.HandleEnsureImportRefs(), userMW...) + r.Post(v1Base("/actions/ensure-asset-ids"), chain.ToHandlerFunc(v1Ctrl.HandleEnsureAssetID(), userMW...)) + r.Post(v1Base("/actions/zero-item-time-fields"), chain.ToHandlerFunc(v1Ctrl.HandleItemDateZeroOut(), userMW...)) + r.Post(v1Base("/actions/ensure-import-refs"), chain.ToHandlerFunc(v1Ctrl.HandleEnsureImportRefs(), userMW...)) - a.server.Get(v1Base("/locations"), v1Ctrl.HandleLocationGetAll(), userMW...) - a.server.Post(v1Base("/locations"), v1Ctrl.HandleLocationCreate(), userMW...) - a.server.Get(v1Base("/locations/tree"), v1Ctrl.HandleLocationTreeQuery(), userMW...) - a.server.Get(v1Base("/locations/{id}"), v1Ctrl.HandleLocationGet(), userMW...) - a.server.Put(v1Base("/locations/{id}"), v1Ctrl.HandleLocationUpdate(), userMW...) - a.server.Delete(v1Base("/locations/{id}"), v1Ctrl.HandleLocationDelete(), userMW...) + r.Get(v1Base("/locations"), chain.ToHandlerFunc(v1Ctrl.HandleLocationGetAll(), userMW...)) + r.Post(v1Base("/locations"), chain.ToHandlerFunc(v1Ctrl.HandleLocationCreate(), userMW...)) + r.Get(v1Base("/locations/tree"), chain.ToHandlerFunc(v1Ctrl.HandleLocationTreeQuery(), userMW...)) + r.Get(v1Base("/locations/{id}"), chain.ToHandlerFunc(v1Ctrl.HandleLocationGet(), userMW...)) + r.Put(v1Base("/locations/{id}"), chain.ToHandlerFunc(v1Ctrl.HandleLocationUpdate(), userMW...)) + r.Delete(v1Base("/locations/{id}"), chain.ToHandlerFunc(v1Ctrl.HandleLocationDelete(), userMW...)) - a.server.Get(v1Base("/labels"), v1Ctrl.HandleLabelsGetAll(), userMW...) - a.server.Post(v1Base("/labels"), v1Ctrl.HandleLabelsCreate(), userMW...) - a.server.Get(v1Base("/labels/{id}"), v1Ctrl.HandleLabelGet(), userMW...) - a.server.Put(v1Base("/labels/{id}"), v1Ctrl.HandleLabelUpdate(), userMW...) - a.server.Delete(v1Base("/labels/{id}"), v1Ctrl.HandleLabelDelete(), userMW...) + r.Get(v1Base("/labels"), chain.ToHandlerFunc(v1Ctrl.HandleLabelsGetAll(), userMW...)) + r.Post(v1Base("/labels"), chain.ToHandlerFunc(v1Ctrl.HandleLabelsCreate(), userMW...)) + r.Get(v1Base("/labels/{id}"), chain.ToHandlerFunc(v1Ctrl.HandleLabelGet(), userMW...)) + r.Put(v1Base("/labels/{id}"), chain.ToHandlerFunc(v1Ctrl.HandleLabelUpdate(), userMW...)) + r.Delete(v1Base("/labels/{id}"), chain.ToHandlerFunc(v1Ctrl.HandleLabelDelete(), userMW...)) - a.server.Get(v1Base("/items"), v1Ctrl.HandleItemsGetAll(), userMW...) - a.server.Post(v1Base("/items"), v1Ctrl.HandleItemsCreate(), userMW...) - a.server.Post(v1Base("/items/import"), v1Ctrl.HandleItemsImport(), userMW...) - a.server.Get(v1Base("/items/export"), v1Ctrl.HandleItemsExport(), userMW...) - a.server.Get(v1Base("/items/fields"), v1Ctrl.HandleGetAllCustomFieldNames(), userMW...) - a.server.Get(v1Base("/items/fields/values"), v1Ctrl.HandleGetAllCustomFieldValues(), userMW...) + r.Get(v1Base("/items"), chain.ToHandlerFunc(v1Ctrl.HandleItemsGetAll(), userMW...)) + r.Post(v1Base("/items"), chain.ToHandlerFunc(v1Ctrl.HandleItemsCreate(), userMW...)) + r.Post(v1Base("/items/import"), chain.ToHandlerFunc(v1Ctrl.HandleItemsImport(), userMW...)) + r.Get(v1Base("/items/export"), chain.ToHandlerFunc(v1Ctrl.HandleItemsExport(), userMW...)) + r.Get(v1Base("/items/fields"), chain.ToHandlerFunc(v1Ctrl.HandleGetAllCustomFieldNames(), userMW...)) + r.Get(v1Base("/items/fields/values"), chain.ToHandlerFunc(v1Ctrl.HandleGetAllCustomFieldValues(), userMW...)) - a.server.Get(v1Base("/items/{id}"), v1Ctrl.HandleItemGet(), userMW...) - a.server.Put(v1Base("/items/{id}"), v1Ctrl.HandleItemUpdate(), userMW...) - a.server.Delete(v1Base("/items/{id}"), v1Ctrl.HandleItemDelete(), userMW...) + r.Get(v1Base("/items/{id}"), chain.ToHandlerFunc(v1Ctrl.HandleItemGet(), userMW...)) + r.Put(v1Base("/items/{id}"), chain.ToHandlerFunc(v1Ctrl.HandleItemUpdate(), userMW...)) + r.Delete(v1Base("/items/{id}"), chain.ToHandlerFunc(v1Ctrl.HandleItemDelete(), userMW...)) - a.server.Post(v1Base("/items/{id}/attachments"), v1Ctrl.HandleItemAttachmentCreate(), userMW...) - a.server.Put(v1Base("/items/{id}/attachments/{attachment_id}"), v1Ctrl.HandleItemAttachmentUpdate(), userMW...) - a.server.Delete(v1Base("/items/{id}/attachments/{attachment_id}"), v1Ctrl.HandleItemAttachmentDelete(), userMW...) + r.Post(v1Base("/items/{id}/attachments"), chain.ToHandlerFunc(v1Ctrl.HandleItemAttachmentCreate(), userMW...)) + r.Put(v1Base("/items/{id}/attachments/{attachment_id}"), chain.ToHandlerFunc(v1Ctrl.HandleItemAttachmentUpdate(), userMW...)) + r.Delete(v1Base("/items/{id}/attachments/{attachment_id}"), chain.ToHandlerFunc(v1Ctrl.HandleItemAttachmentDelete(), userMW...)) - a.server.Get(v1Base("/items/{id}/maintenance"), v1Ctrl.HandleMaintenanceLogGet(), userMW...) - a.server.Post(v1Base("/items/{id}/maintenance"), v1Ctrl.HandleMaintenanceEntryCreate(), userMW...) - a.server.Put(v1Base("/items/{id}/maintenance/{entry_id}"), v1Ctrl.HandleMaintenanceEntryUpdate(), userMW...) - a.server.Delete(v1Base("/items/{id}/maintenance/{entry_id}"), v1Ctrl.HandleMaintenanceEntryDelete(), userMW...) + r.Get(v1Base("/items/{id}/maintenance"), chain.ToHandlerFunc(v1Ctrl.HandleMaintenanceLogGet(), userMW...)) + r.Post(v1Base("/items/{id}/maintenance"), chain.ToHandlerFunc(v1Ctrl.HandleMaintenanceEntryCreate(), userMW...)) + r.Put(v1Base("/items/{id}/maintenance/{entry_id}"), chain.ToHandlerFunc(v1Ctrl.HandleMaintenanceEntryUpdate(), userMW...)) + r.Delete(v1Base("/items/{id}/maintenance/{entry_id}"), chain.ToHandlerFunc(v1Ctrl.HandleMaintenanceEntryDelete(), userMW...)) - a.server.Get(v1Base("/asset/{id}"), v1Ctrl.HandleAssetGet(), userMW...) + r.Get(v1Base("/asset/{id}"), chain.ToHandlerFunc(v1Ctrl.HandleAssetGet(), userMW...)) // Notifiers - a.server.Get(v1Base("/notifiers"), v1Ctrl.HandleGetUserNotifiers(), userMW...) - a.server.Post(v1Base("/notifiers"), v1Ctrl.HandleCreateNotifier(), userMW...) - a.server.Put(v1Base("/notifiers/{id}"), v1Ctrl.HandleUpdateNotifier(), userMW...) - a.server.Delete(v1Base("/notifiers/{id}"), v1Ctrl.HandleDeleteNotifier(), userMW...) - a.server.Post(v1Base("/notifiers/test"), v1Ctrl.HandlerNotifierTest(), userMW...) + r.Get(v1Base("/notifiers"), chain.ToHandlerFunc(v1Ctrl.HandleGetUserNotifiers(), userMW...)) + r.Post(v1Base("/notifiers"), chain.ToHandlerFunc(v1Ctrl.HandleCreateNotifier(), userMW...)) + r.Put(v1Base("/notifiers/{id}"), chain.ToHandlerFunc(v1Ctrl.HandleUpdateNotifier(), userMW...)) + r.Delete(v1Base("/notifiers/{id}"), chain.ToHandlerFunc(v1Ctrl.HandleDeleteNotifier(), userMW...)) + r.Post(v1Base("/notifiers/test"), chain.ToHandlerFunc(v1Ctrl.HandlerNotifierTest(), userMW...)) // Asset-Like endpoints - a.server.Get( + assetMW := []errchain.Middleware{ + a.mwAuthToken, + a.mwRoles(RoleModeOr, authroles.RoleUser.String(), authroles.RoleAttachments.String()), + } + + r.Get( v1Base("/qrcode"), - v1Ctrl.HandleGenerateQRCode(), - a.mwAuthToken, a.mwRoles(RoleModeOr, authroles.RoleUser.String(), authroles.RoleAttachments.String()), + chain.ToHandlerFunc(v1Ctrl.HandleGenerateQRCode(), assetMW...), ) - a.server.Get( + r.Get( v1Base("/items/{id}/attachments/{attachment_id}"), - v1Ctrl.HandleItemAttachmentGet(), - a.mwAuthToken, a.mwRoles(RoleModeOr, authroles.RoleUser.String(), authroles.RoleAttachments.String()), + chain.ToHandlerFunc(v1Ctrl.HandleItemAttachmentGet(), assetMW...), ) // Reporting Services - a.server.Get(v1Base("/reporting/bill-of-materials"), v1Ctrl.HandleBillOfMaterialsExport(), userMW...) + r.Get(v1Base("/reporting/bill-of-materials"), chain.ToHandlerFunc(v1Ctrl.HandleBillOfMaterialsExport(), userMW...)) + + r.NotFound(chain.ToHandlerFunc(notFoundHandler())) - a.server.NotFound(notFoundHandler()) } func registerMimes() { @@ -165,7 +170,7 @@ func registerMimes() { // notFoundHandler perform the main logic around handling the internal SPA embed and ensuring that // the client side routing is handled correctly. -func notFoundHandler() server.HandlerFunc { +func notFoundHandler() errchain.HandlerFunc { tryRead := func(fs embed.FS, prefix, requestedPath string, w http.ResponseWriter) error { f, err := fs.Open(path.Join(prefix, requestedPath)) if err != nil { diff --git a/backend/app/api/static/docs/docs.go b/backend/app/api/static/docs/docs.go index d660f08..5ad614f 100644 --- a/backend/app/api/static/docs/docs.go +++ b/backend/app/api/static/docs/docs.go @@ -694,7 +694,7 @@ const docTemplate = `{ "422": { "description": "Unprocessable Entity", "schema": { - "$ref": "#/definitions/server.ErrorResponse" + "$ref": "#/definitions/mid.ErrorResponse" } } } @@ -947,7 +947,7 @@ const docTemplate = `{ "schema": { "allOf": [ { - "$ref": "#/definitions/server.Results" + "$ref": "#/definitions/v1.Wrapped" }, { "type": "object", @@ -1119,7 +1119,7 @@ const docTemplate = `{ "schema": { "allOf": [ { - "$ref": "#/definitions/server.Results" + "$ref": "#/definitions/v1.Wrapped" }, { "type": "object", @@ -1199,7 +1199,7 @@ const docTemplate = `{ "schema": { "allOf": [ { - "$ref": "#/definitions/server.Results" + "$ref": "#/definitions/v1.Wrapped" }, { "type": "object", @@ -1339,7 +1339,7 @@ const docTemplate = `{ "schema": { "allOf": [ { - "$ref": "#/definitions/server.Results" + "$ref": "#/definitions/v1.Wrapped" }, { "type": "object", @@ -1719,7 +1719,7 @@ const docTemplate = `{ "schema": { "allOf": [ { - "$ref": "#/definitions/server.Result" + "$ref": "#/definitions/v1.Wrapped" }, { "type": "object", @@ -1764,7 +1764,7 @@ const docTemplate = `{ "schema": { "allOf": [ { - "$ref": "#/definitions/server.Result" + "$ref": "#/definitions/v1.Wrapped" }, { "type": "object", @@ -1801,6 +1801,20 @@ const docTemplate = `{ } }, "definitions": { + "mid.ErrorResponse": { + "type": "object", + "properties": { + "error": { + "type": "string" + }, + "fields": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + }, "repo.DocumentOut": { "type": "object", "properties": { @@ -2677,39 +2691,6 @@ const docTemplate = `{ } } }, - "server.ErrorResponse": { - "type": "object", - "properties": { - "error": { - "type": "string" - }, - "fields": { - "type": "object", - "additionalProperties": { - "type": "string" - } - } - } - }, - "server.Result": { - "type": "object", - "properties": { - "details": {}, - "error": { - "type": "boolean" - }, - "item": {}, - "message": { - "type": "string" - } - } - }, - "server.Results": { - "type": "object", - "properties": { - "items": {} - } - }, "services.UserRegistration": { "type": "object", "properties": { @@ -2840,6 +2821,12 @@ const docTemplate = `{ "type": "string" } } + }, + "v1.Wrapped": { + "type": "object", + "properties": { + "item": {} + } } }, "securityDefinitions": { diff --git a/backend/app/api/static/docs/swagger.json b/backend/app/api/static/docs/swagger.json index 4fa3dba..c12bd42 100644 --- a/backend/app/api/static/docs/swagger.json +++ b/backend/app/api/static/docs/swagger.json @@ -686,7 +686,7 @@ "422": { "description": "Unprocessable Entity", "schema": { - "$ref": "#/definitions/server.ErrorResponse" + "$ref": "#/definitions/mid.ErrorResponse" } } } @@ -939,7 +939,7 @@ "schema": { "allOf": [ { - "$ref": "#/definitions/server.Results" + "$ref": "#/definitions/v1.Wrapped" }, { "type": "object", @@ -1111,7 +1111,7 @@ "schema": { "allOf": [ { - "$ref": "#/definitions/server.Results" + "$ref": "#/definitions/v1.Wrapped" }, { "type": "object", @@ -1191,7 +1191,7 @@ "schema": { "allOf": [ { - "$ref": "#/definitions/server.Results" + "$ref": "#/definitions/v1.Wrapped" }, { "type": "object", @@ -1331,7 +1331,7 @@ "schema": { "allOf": [ { - "$ref": "#/definitions/server.Results" + "$ref": "#/definitions/v1.Wrapped" }, { "type": "object", @@ -1711,7 +1711,7 @@ "schema": { "allOf": [ { - "$ref": "#/definitions/server.Result" + "$ref": "#/definitions/v1.Wrapped" }, { "type": "object", @@ -1756,7 +1756,7 @@ "schema": { "allOf": [ { - "$ref": "#/definitions/server.Result" + "$ref": "#/definitions/v1.Wrapped" }, { "type": "object", @@ -1793,6 +1793,20 @@ } }, "definitions": { + "mid.ErrorResponse": { + "type": "object", + "properties": { + "error": { + "type": "string" + }, + "fields": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + }, "repo.DocumentOut": { "type": "object", "properties": { @@ -2669,39 +2683,6 @@ } } }, - "server.ErrorResponse": { - "type": "object", - "properties": { - "error": { - "type": "string" - }, - "fields": { - "type": "object", - "additionalProperties": { - "type": "string" - } - } - } - }, - "server.Result": { - "type": "object", - "properties": { - "details": {}, - "error": { - "type": "boolean" - }, - "item": {}, - "message": { - "type": "string" - } - } - }, - "server.Results": { - "type": "object", - "properties": { - "items": {} - } - }, "services.UserRegistration": { "type": "object", "properties": { @@ -2832,6 +2813,12 @@ "type": "string" } } + }, + "v1.Wrapped": { + "type": "object", + "properties": { + "item": {} + } } }, "securityDefinitions": { diff --git a/backend/app/api/static/docs/swagger.yaml b/backend/app/api/static/docs/swagger.yaml index 83cdadd..8a8b7f7 100644 --- a/backend/app/api/static/docs/swagger.yaml +++ b/backend/app/api/static/docs/swagger.yaml @@ -1,5 +1,14 @@ basePath: /api definitions: + mid.ErrorResponse: + properties: + error: + type: string + fields: + additionalProperties: + type: string + type: object + type: object repo.DocumentOut: properties: id: @@ -591,28 +600,6 @@ definitions: value: type: number type: object - server.ErrorResponse: - properties: - error: - type: string - fields: - additionalProperties: - type: string - type: object - type: object - server.Result: - properties: - details: {} - error: - type: boolean - item: {} - message: - type: string - type: object - server.Results: - properties: - items: {} - type: object services.UserRegistration: properties: email: @@ -698,6 +685,10 @@ definitions: token: type: string type: object + v1.Wrapped: + properties: + item: {} + type: object info: contact: name: Don't @@ -1052,7 +1043,7 @@ paths: "422": description: Unprocessable Entity schema: - $ref: '#/definitions/server.ErrorResponse' + $ref: '#/definitions/mid.ErrorResponse' security: - Bearer: [] summary: Create Item Attachment @@ -1268,7 +1259,7 @@ paths: description: OK schema: allOf: - - $ref: '#/definitions/server.Results' + - $ref: '#/definitions/v1.Wrapped' - properties: items: items: @@ -1370,7 +1361,7 @@ paths: description: OK schema: allOf: - - $ref: '#/definitions/server.Results' + - $ref: '#/definitions/v1.Wrapped' - properties: items: items: @@ -1478,7 +1469,7 @@ paths: description: OK schema: allOf: - - $ref: '#/definitions/server.Results' + - $ref: '#/definitions/v1.Wrapped' - properties: items: items: @@ -1499,7 +1490,7 @@ paths: description: OK schema: allOf: - - $ref: '#/definitions/server.Results' + - $ref: '#/definitions/v1.Wrapped' - properties: items: items: @@ -1741,7 +1732,7 @@ paths: description: OK schema: allOf: - - $ref: '#/definitions/server.Result' + - $ref: '#/definitions/v1.Wrapped' - properties: item: $ref: '#/definitions/repo.UserOut' @@ -1766,7 +1757,7 @@ paths: description: OK schema: allOf: - - $ref: '#/definitions/server.Result' + - $ref: '#/definitions/v1.Wrapped' - properties: item: $ref: '#/definitions/repo.UserUpdate' diff --git a/backend/go.mod b/backend/go.mod index a841b6d..f5be84f 100644 --- a/backend/go.mod +++ b/backend/go.mod @@ -12,6 +12,7 @@ require ( github.com/gocarina/gocsv v0.0.0-20230226133904-70c27cb2918a github.com/google/uuid v1.3.0 github.com/gorilla/schema v1.2.0 + github.com/hay-kot/safeserve v0.0.1 github.com/mattn/go-sqlite3 v1.14.16 github.com/pkg/errors v0.9.1 github.com/rs/zerolog v1.29.0 diff --git a/backend/go.sum b/backend/go.sum index 699f2b6..469f9e2 100644 --- a/backend/go.sum +++ b/backend/go.sum @@ -439,6 +439,8 @@ github.com/hashicorp/memberlist v0.3.0/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOn github.com/hashicorp/memberlist v0.3.1/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE= github.com/hashicorp/serf v0.9.7/go.mod h1:TXZNMjZQijwlDvp+r0b63xZ45H7JmCmgg4gpTwn9UV4= github.com/hashicorp/serf v0.9.8/go.mod h1:TXZNMjZQijwlDvp+r0b63xZ45H7JmCmgg4gpTwn9UV4= +github.com/hay-kot/safeserve v0.0.1 h1:9u8Ooyk8NNkqgxrqkLMWtMqauWEl/VZVtEUTLbHuAU8= +github.com/hay-kot/safeserve v0.0.1/go.mod h1:RUvwyfQTmbNgm5sHt+tQOqtdcpWadXWMhLty74Vedzw= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= diff --git a/backend/internal/web/adapters/actions.go b/backend/internal/web/adapters/actions.go index 7cb0be6..db9a410 100644 --- a/backend/internal/web/adapters/actions.go +++ b/backend/internal/web/adapters/actions.go @@ -3,7 +3,8 @@ package adapters import ( "net/http" - "github.com/hay-kot/homebox/backend/pkgs/server" + "github.com/hay-kot/safeserve/errchain" + "github.com/hay-kot/safeserve/server" ) // Action is a function that adapts a function to the server.Handler interface. @@ -22,7 +23,7 @@ import ( // } // // r.Post("/foo", adapters.Action(fn, http.StatusCreated)) -func Action[T any, Y any](f AdapterFunc[T, Y], ok int) server.HandlerFunc { +func Action[T any, Y any](f AdapterFunc[T, Y], ok int) errchain.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) error { v, err := DecodeBody[T](r) if err != nil { @@ -34,7 +35,7 @@ func Action[T any, Y any](f AdapterFunc[T, Y], ok int) server.HandlerFunc { return err } - return server.Respond(w, ok, res) + return server.JSON(w, ok, res) } } @@ -52,7 +53,7 @@ func Action[T any, Y any](f AdapterFunc[T, Y], ok int) server.HandlerFunc { // } // // r.Post("/foo/{id}", adapters.ActionID(fn, http.StatusCreated)) -func ActionID[T any, Y any](param string, f IDFunc[T, Y], ok int) server.HandlerFunc { +func ActionID[T any, Y any](param string, f IDFunc[T, Y], ok int) errchain.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) error { ID, err := RouteUUID(r, param) if err != nil { @@ -69,6 +70,6 @@ func ActionID[T any, Y any](param string, f IDFunc[T, Y], ok int) server.Handler return err } - return server.Respond(w, ok, res) + return server.JSON(w, ok, res) } } diff --git a/backend/internal/web/adapters/command.go b/backend/internal/web/adapters/command.go index 4683782..3d7eb06 100644 --- a/backend/internal/web/adapters/command.go +++ b/backend/internal/web/adapters/command.go @@ -4,13 +4,14 @@ import ( "net/http" "github.com/google/uuid" - "github.com/hay-kot/homebox/backend/pkgs/server" + "github.com/hay-kot/safeserve/errchain" + "github.com/hay-kot/safeserve/server" ) type CommandFunc[T any] func(*http.Request) (T, error) type CommandIDFunc[T any] func(*http.Request, uuid.UUID) (T, error) -// Command is an HandlerAdapter that returns a server.HandlerFunc that +// Command is an HandlerAdapter that returns a errchain.HandlerFunc that // The command adapters are used to handle commands that do not accept a body // or a query. You can think of them as a way to handle RPC style Rest Endpoints. // @@ -22,14 +23,14 @@ type CommandIDFunc[T any] func(*http.Request, uuid.UUID) (T, error) // } // // r.Get("/foo", adapters.Command(fn, http.NoContent)) -func Command[T any](f CommandFunc[T], ok int) server.HandlerFunc { +func Command[T any](f CommandFunc[T], ok int) errchain.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) error { res, err := f(r) if err != nil { return err } - return server.Respond(w, ok, res) + return server.JSON(w, ok, res) } } @@ -44,7 +45,7 @@ func Command[T any](f CommandFunc[T], ok int) server.HandlerFunc { // } // // r.Get("/foo/{id}", adapters.CommandID("id", fn, http.NoContent)) -func CommandID[T any](param string, f CommandIDFunc[T], ok int) server.HandlerFunc { +func CommandID[T any](param string, f CommandIDFunc[T], ok int) errchain.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) error { ID, err := RouteUUID(r, param) if err != nil { @@ -56,6 +57,6 @@ func CommandID[T any](param string, f CommandIDFunc[T], ok int) server.HandlerFu return err } - return server.Respond(w, ok, res) + return server.JSON(w, ok, res) } } diff --git a/backend/internal/web/adapters/decoders.go b/backend/internal/web/adapters/decoders.go index 88c9da3..d1444ef 100644 --- a/backend/internal/web/adapters/decoders.go +++ b/backend/internal/web/adapters/decoders.go @@ -9,7 +9,7 @@ import ( "github.com/google/uuid" "github.com/gorilla/schema" "github.com/hay-kot/homebox/backend/internal/sys/validate" - "github.com/hay-kot/homebox/backend/pkgs/server" + "github.com/hay-kot/safeserve/server" ) var queryDecoder = schema.NewDecoder() diff --git a/backend/internal/web/adapters/query.go b/backend/internal/web/adapters/query.go index 769c0ad..8f669f0 100644 --- a/backend/internal/web/adapters/query.go +++ b/backend/internal/web/adapters/query.go @@ -3,7 +3,8 @@ package adapters import ( "net/http" - "github.com/hay-kot/homebox/backend/pkgs/server" + "github.com/hay-kot/safeserve/errchain" + "github.com/hay-kot/safeserve/server" ) // Query is a server.Handler that decodes a query from the request and calls the provided function. @@ -20,7 +21,7 @@ import ( // } // // r.Get("/foo", adapters.Query(fn, http.StatusOK)) -func Query[T any, Y any](f AdapterFunc[T, Y], ok int) server.HandlerFunc { +func Query[T any, Y any](f AdapterFunc[T, Y], ok int) errchain.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) error { q, err := DecodeQuery[T](r) if err != nil { @@ -32,7 +33,7 @@ func Query[T any, Y any](f AdapterFunc[T, Y], ok int) server.HandlerFunc { return err } - return server.Respond(w, ok, res) + return server.JSON(w, ok, res) } } @@ -50,7 +51,7 @@ func Query[T any, Y any](f AdapterFunc[T, Y], ok int) server.HandlerFunc { // } // // r.Get("/foo/{id}", adapters.QueryID(fn, http.StatusOK)) -func QueryID[T any, Y any](param string, f IDFunc[T, Y], ok int) server.HandlerFunc { +func QueryID[T any, Y any](param string, f IDFunc[T, Y], ok int) errchain.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) error { ID, err := RouteUUID(r, param) if err != nil { @@ -67,6 +68,6 @@ func QueryID[T any, Y any](param string, f IDFunc[T, Y], ok int) server.HandlerF return err } - return server.Respond(w, ok, res) + return server.JSON(w, ok, res) } } diff --git a/backend/internal/web/mid/errors.go b/backend/internal/web/mid/errors.go index 70d9475..269387c 100644 --- a/backend/internal/web/mid/errors.go +++ b/backend/internal/web/mid/errors.go @@ -3,34 +3,42 @@ package mid import ( "net/http" + "github.com/go-chi/chi/v5/middleware" "github.com/hay-kot/homebox/backend/internal/data/ent" "github.com/hay-kot/homebox/backend/internal/sys/validate" - "github.com/hay-kot/homebox/backend/pkgs/server" + "github.com/hay-kot/safeserve/errchain" + "github.com/hay-kot/safeserve/server" "github.com/rs/zerolog" ) -func Errors(log zerolog.Logger) server.Middleware { - return func(h server.Handler) server.Handler { - return server.HandlerFunc(func(w http.ResponseWriter, r *http.Request) error { +type ErrorResponse struct { + Error string `json:"error"` + Fields map[string]string `json:"fields,omitempty"` +} + +func Errors(svr *server.Server, log zerolog.Logger) errchain.ErrorHandler { + return func(h errchain.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { err := h.ServeHTTP(w, r) if err != nil { - var resp server.ErrorResponse + var resp ErrorResponse var code int + traceID := r.Context().Value(middleware.RequestIDKey).(string) log.Err(err). Stack(). - Str("trace_id", server.GetTraceID(r.Context())). + Str("req_id", traceID). Msg("ERROR occurred") switch { case validate.IsUnauthorizedError(err): code = http.StatusUnauthorized - resp = server.ErrorResponse{ + resp = ErrorResponse{ Error: "unauthorized", } case validate.IsInvalidRouteKeyError(err): code = http.StatusBadRequest - resp = server.ErrorResponse{ + resp = ErrorResponse{ Error: err.Error(), } case validate.IsFieldError(err): @@ -60,17 +68,18 @@ func Errors(log zerolog.Logger) server.Middleware { code = http.StatusInternalServerError } - if err := server.Respond(w, code, resp); err != nil { - return err + if err := server.JSON(w, code, resp); err != nil { + log.Err(err).Msg("failed to write response") } // If Showdown error, return error if server.IsShutdownError(err) { - return err + err := svr.Shutdown(err.Error()) + if err != nil { + log.Err(err).Msg("failed to shutdown server") + } } } - - return nil }) } } diff --git a/backend/internal/web/mid/logger.go b/backend/internal/web/mid/logger.go index 9c577fa..d087c68 100644 --- a/backend/internal/web/mid/logger.go +++ b/backend/internal/web/mid/logger.go @@ -1,106 +1,33 @@ package mid import ( - "fmt" "net/http" - "github.com/hay-kot/homebox/backend/pkgs/server" + "github.com/go-chi/chi/v5/middleware" "github.com/rs/zerolog" ) -type statusRecorder struct { +type spy struct { http.ResponseWriter - Status int + status int } -func (r *statusRecorder) WriteHeader(status int) { - r.Status = status - r.ResponseWriter.WriteHeader(status) +func (s *spy) WriteHeader(status int) { + s.status = status + s.ResponseWriter.WriteHeader(status) } -func Logger(log zerolog.Logger) server.Middleware { - return func(next server.Handler) server.Handler { - return server.HandlerFunc(func(w http.ResponseWriter, r *http.Request) error { - traceId := server.GetTraceID(r.Context()) +func Logger(l zerolog.Logger) func(http.Handler) http.Handler { + return func(h http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + reqID := r.Context().Value(middleware.RequestIDKey).(string) - log.Info(). - Str("trace_id", traceId). - Str("method", r.Method). - Str("path", r.URL.Path). - Str("remove_address", r.RemoteAddr). - Msg("request started") + l.Info().Str("method", r.Method).Str("path", r.URL.Path).Str("rid", reqID).Msg("request received") - record := &statusRecorder{ResponseWriter: w, Status: http.StatusOK} + s := &spy{ResponseWriter: w} + h.ServeHTTP(s, r) - err := next.ServeHTTP(record, r) - - log.Info(). - Str("trace_id", traceId). - Str("method", r.Method). - Str("url", r.URL.Path). - Str("remote_address", r.RemoteAddr). - Int("status_code", record.Status). - Msg("request completed") - - return err - }) - } -} - -func SugarLogger(log zerolog.Logger) server.Middleware { - orange := func(s string) string { return "\033[33m" + s + "\033[0m" } - aqua := func(s string) string { return "\033[36m" + s + "\033[0m" } - red := func(s string) string { return "\033[31m" + s + "\033[0m" } - green := func(s string) string { return "\033[32m" + s + "\033[0m" } - - fmtCode := func(code int) string { - switch { - case code >= 500: - return red(fmt.Sprintf("%d", code)) - case code >= 400: - return orange(fmt.Sprintf("%d", code)) - case code >= 300: - return aqua(fmt.Sprintf("%d", code)) - default: - return green(fmt.Sprintf("%d", code)) - } - } - bold := func(s string) string { return "\033[1m" + s + "\033[0m" } - - atLeast6 := func(s string) string { - for len(s) <= 6 { - s += " " - } - return s - } - - return func(next server.Handler) server.Handler { - return server.HandlerFunc(func(w http.ResponseWriter, r *http.Request) error { - record := &statusRecorder{ResponseWriter: w, Status: http.StatusOK} - - url := fmt.Sprintf("%s %s", r.RequestURI, r.Proto) - - log.Info(). - Str("trace_id", server.GetTraceID(r.Context())). - Str("status", "started"). - Msgf("%s %s %s", - bold(fmtCode(record.Status)), - bold(orange(atLeast6(r.Method))), - aqua(url), - ) - - err := next.ServeHTTP(record, r) // Blocks until the next handler returns. - - log.Info(). - Str("trace_id", server.GetTraceID(r.Context())). - Str("status", "completed"). - Msgf("%s %s %s", - bold(fmtCode(record.Status)), - bold(orange(atLeast6(r.Method))), - aqua(url), - ) - - return err + l.Info().Str("method", r.Method).Str("path", r.URL.Path).Int("status", s.status).Str("rid", reqID).Msg("request finished") }) } } diff --git a/backend/internal/web/mid/panic.go b/backend/internal/web/mid/panic.go deleted file mode 100644 index 9879bb8..0000000 --- a/backend/internal/web/mid/panic.go +++ /dev/null @@ -1,33 +0,0 @@ -package mid - -import ( - "fmt" - "net/http" - "runtime/debug" - - "github.com/hay-kot/homebox/backend/pkgs/server" -) - -// Panic is a middleware that recovers from panics anywhere in the chain and wraps the error. -// and returns it up the middleware chain. -func Panic(develop bool) server.Middleware { - return func(h server.Handler) server.Handler { - return server.HandlerFunc(func(w http.ResponseWriter, r *http.Request) (err error) { - defer func() { - if rec := recover(); rec != nil { - trace := debug.Stack() - - if develop { - err = fmt.Errorf("PANIC [%v]", rec) - fmt.Printf("%s", string(trace)) - } else { - err = fmt.Errorf("PANIC [%v] TRACE[%s]", rec, string(trace)) - } - - } - }() - - return h.ServeHTTP(w, r) - }) - } -} diff --git a/backend/pkgs/server/constants.go b/backend/pkgs/server/constants.go deleted file mode 100644 index e083a57..0000000 --- a/backend/pkgs/server/constants.go +++ /dev/null @@ -1,8 +0,0 @@ -package server - -const ( - ContentType = "Content-Type" - ContentJSON = "application/json" - ContentXML = "application/xml" - ContentFormUrlEncoded = "application/x-www-form-urlencoded" -) diff --git a/backend/pkgs/server/errors.go b/backend/pkgs/server/errors.go deleted file mode 100644 index 5b1d60b..0000000 --- a/backend/pkgs/server/errors.go +++ /dev/null @@ -1,23 +0,0 @@ -package server - -import "errors" - -type shutdownError struct { - message string -} - -func (e *shutdownError) Error() string { - return e.message -} - -// ShutdownError returns an error that indicates that the server has lost -// integrity and should be shut down. -func ShutdownError(message string) error { - return &shutdownError{message} -} - -// IsShutdownError returns true if the error is a shutdown error. -func IsShutdownError(err error) bool { - var e *shutdownError - return errors.As(err, &e) -} diff --git a/backend/pkgs/server/handler.go b/backend/pkgs/server/handler.go deleted file mode 100644 index 76ae131..0000000 --- a/backend/pkgs/server/handler.go +++ /dev/null @@ -1,25 +0,0 @@ -package server - -import ( - "net/http" -) - -type HandlerFunc func(w http.ResponseWriter, r *http.Request) error - -func (f HandlerFunc) ServeHTTP(w http.ResponseWriter, r *http.Request) error { - return f(w, r) -} - -type Handler interface { - ServeHTTP(http.ResponseWriter, *http.Request) error -} - -// ToHandler converts a function to a customer implementation of the Handler interface. -// that returns an error. This wrapper around the handler function and simply -// returns the nil in all cases -func ToHandler(handler http.Handler) Handler { - return HandlerFunc(func(w http.ResponseWriter, r *http.Request) error { - handler.ServeHTTP(w, r) - return nil - }) -} diff --git a/backend/pkgs/server/middleware.go b/backend/pkgs/server/middleware.go deleted file mode 100644 index 8e3bb23..0000000 --- a/backend/pkgs/server/middleware.go +++ /dev/null @@ -1,37 +0,0 @@ -package server - -import ( - "net/http" - "strings" -) - -type Middleware func(Handler) Handler - -// wrapMiddleware creates a new handler by wrapping middleware around a final -// handler. The middlewares' Handlers will be executed by requests in the order -// they are provided. -func wrapMiddleware(mw []Middleware, handler Handler) Handler { - // Loop backwards through the middleware invoking each one. Replace the - // handler with the new wrapped handler. Looping backwards ensures that the - // first middleware of the slice is the first to be executed by requests. - for i := len(mw) - 1; i >= 0; i-- { - h := mw[i] - if h != nil { - handler = h(handler) - } - } - - return handler -} - -// StripTrailingSlash is a middleware that will strip trailing slashes from the request path. -// -// Example: /api/v1/ -> /api/v1 -func StripTrailingSlash() Middleware { - return func(h Handler) Handler { - return HandlerFunc(func(w http.ResponseWriter, r *http.Request) error { - r.URL.Path = strings.TrimSuffix(r.URL.Path, "/") - return h.ServeHTTP(w, r) - }) - } -} diff --git a/backend/pkgs/server/mux.go b/backend/pkgs/server/mux.go deleted file mode 100644 index 7f62ab7..0000000 --- a/backend/pkgs/server/mux.go +++ /dev/null @@ -1,102 +0,0 @@ -package server - -import ( - "context" - "net/http" - - "github.com/google/uuid" -) - -type vkey int - -const ( - // Key is the key for the server in the request context. - key vkey = 1 -) - -type Values struct { - TraceID string -} - -func GetTraceID(ctx context.Context) string { - v, ok := ctx.Value(key).(Values) - if !ok { - return "" - } - return v.TraceID -} - -func (s *Server) toHttpHandler(handler Handler, mw ...Middleware) http.HandlerFunc { - handler = wrapMiddleware(mw, handler) - - handler = wrapMiddleware(s.mw, handler) - - return func(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() - - // Add the trace ID to the context - ctx = context.WithValue(ctx, key, Values{ - TraceID: uuid.NewString(), - }) - - err := handler.ServeHTTP(w, r.WithContext(ctx)) - if err != nil { - if IsShutdownError(err) { - _ = s.Shutdown("SIGTERM") - } - } - } -} - -func (s *Server) handle(method, pattern string, handler Handler, mw ...Middleware) { - h := s.toHttpHandler(handler, mw...) - - switch method { - case http.MethodGet: - s.mux.Get(pattern, h) - case http.MethodPost: - s.mux.Post(pattern, h) - case http.MethodPut: - s.mux.Put(pattern, h) - case http.MethodDelete: - s.mux.Delete(pattern, h) - case http.MethodPatch: - s.mux.Patch(pattern, h) - case http.MethodHead: - s.mux.Head(pattern, h) - case http.MethodOptions: - s.mux.Options(pattern, h) - } -} - -func (s *Server) Get(pattern string, handler Handler, mw ...Middleware) { - s.handle(http.MethodGet, pattern, handler, mw...) -} - -func (s *Server) Post(pattern string, handler Handler, mw ...Middleware) { - s.handle(http.MethodPost, pattern, handler, mw...) -} - -func (s *Server) Put(pattern string, handler Handler, mw ...Middleware) { - s.handle(http.MethodPut, pattern, handler, mw...) -} - -func (s *Server) Delete(pattern string, handler Handler, mw ...Middleware) { - s.handle(http.MethodDelete, pattern, handler, mw...) -} - -func (s *Server) Patch(pattern string, handler Handler, mw ...Middleware) { - s.handle(http.MethodPatch, pattern, handler, mw...) -} - -func (s *Server) Head(pattern string, handler Handler, mw ...Middleware) { - s.handle(http.MethodHead, pattern, handler, mw...) -} - -func (s *Server) Options(pattern string, handler Handler, mw ...Middleware) { - s.handle(http.MethodOptions, pattern, handler, mw...) -} - -func (s *Server) NotFound(handler Handler) { - s.mux.NotFound(s.toHttpHandler(handler)) -} diff --git a/backend/pkgs/server/request.go b/backend/pkgs/server/request.go deleted file mode 100644 index 38c3189..0000000 --- a/backend/pkgs/server/request.go +++ /dev/null @@ -1,48 +0,0 @@ -package server - -import ( - "encoding/json" - "net/http" -) - -// Decode reads the body of an HTTP request looking for a JSON document. The -// body is decoded into the provided value. -func Decode(r *http.Request, val interface{}) error { - decoder := json.NewDecoder(r.Body) - // decoder.DisallowUnknownFields() - if err := decoder.Decode(val); err != nil { - return err - } - return nil -} - -// GetId is a shortcut to get the id from the request URL or return a default value -func GetParam(r *http.Request, key, d string) string { - val := r.URL.Query().Get(key) - - if val == "" { - return d - } - - return val -} - -// GetSkip is a shortcut to get the skip from the request URL parameters -func GetSkip(r *http.Request, d string) string { - return GetParam(r, "skip", d) -} - -// GetSkip is a shortcut to get the skip from the request URL parameters -func GetId(r *http.Request, d string) string { - return GetParam(r, "id", d) -} - -// GetLimit is a shortcut to get the limit from the request URL parameters -func GetLimit(r *http.Request, d string) string { - return GetParam(r, "limit", d) -} - -// GetQuery is a shortcut to get the sort from the request URL parameters -func GetQuery(r *http.Request, d string) string { - return GetParam(r, "query", d) -} diff --git a/backend/pkgs/server/request_test.go b/backend/pkgs/server/request_test.go deleted file mode 100644 index 05dc8c5..0000000 --- a/backend/pkgs/server/request_test.go +++ /dev/null @@ -1,210 +0,0 @@ -package server - -import ( - "net/http" - "net/http/httptest" - "strings" - "testing" -) - -type TestStruct struct { - Name string `json:"name"` - Data string `json:"data"` -} - -func TestDecode(t *testing.T) { - type args struct { - r *http.Request - val interface{} - } - tests := []struct { - name string - args args - wantErr bool - }{ - { - name: "check_error", - args: args{ - r: &http.Request{ - Body: http.NoBody, - }, - val: make(map[string]interface{}), - }, - wantErr: true, - }, - { - name: "check_success", - args: args{ - r: httptest.NewRequest("POST", "/", strings.NewReader(`{"name":"test","data":"test"}`)), - val: TestStruct{ - Name: "test", - Data: "test", - }, - }, - wantErr: false, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if err := Decode(tt.args.r, &tt.args.val); (err != nil) != tt.wantErr { - t.Errorf("Decode() error = %v, wantErr %v", err, tt.wantErr) - } - }) - } -} - -func TestGetParam(t *testing.T) { - type args struct { - r *http.Request - key string - d string - } - tests := []struct { - name string - args args - want string - }{ - { - name: "check_default", - args: args{ - r: httptest.NewRequest("POST", "/", strings.NewReader(`{"name":"test","data":"test"}`)), - key: "id", - d: "default", - }, - want: "default", - }, - { - name: "check_id", - args: args{ - r: httptest.NewRequest("POST", "/item?id=123", strings.NewReader(`{"name":"test","data":"test"}`)), - key: "id", - d: "", - }, - want: "123", - }, - { - name: "check_query", - args: args{ - r: httptest.NewRequest("POST", "/item?query=hello-world", strings.NewReader(`{"name":"test","data":"test"}`)), - key: "query", - d: "", - }, - want: "hello-world", - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := GetParam(tt.args.r, tt.args.key, tt.args.d); got != tt.want { - t.Errorf("GetParam() = %v, want %v", got, tt.want) - } - }) - } -} - -func TestGetSkip(t *testing.T) { - type args struct { - r *http.Request - d string - } - tests := []struct { - name string - args args - want string - }{ - { - name: "check_default", - args: args{ - r: httptest.NewRequest("POST", "/", strings.NewReader(`{"name":"test","data":"test"}`)), - d: "0", - }, - want: "0", - }, - { - name: "check_skip", - args: args{ - r: httptest.NewRequest("POST", "/item?skip=107", strings.NewReader(`{"name":"test","data":"test"}`)), - d: "0", - }, - want: "107", - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := GetSkip(tt.args.r, tt.args.d); got != tt.want { - t.Errorf("GetSkip() = %v, want %v", got, tt.want) - } - }) - } -} - -func TestGetLimit(t *testing.T) { - type args struct { - r *http.Request - d string - } - tests := []struct { - name string - args args - want string - }{ - { - name: "check_default", - args: args{ - r: httptest.NewRequest("POST", "/", strings.NewReader(`{"name":"test","data":"test"}`)), - d: "0", - }, - want: "0", - }, - { - name: "check_limit", - args: args{ - r: httptest.NewRequest("POST", "/item?limit=107", strings.NewReader(`{"name":"test","data":"test"}`)), - d: "0", - }, - want: "107", - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := GetLimit(tt.args.r, tt.args.d); got != tt.want { - t.Errorf("GetLimit() = %v, want %v", got, tt.want) - } - }) - } -} - -func TestGetQuery(t *testing.T) { - type args struct { - r *http.Request - d string - } - tests := []struct { - name string - args args - want string - }{ - { - name: "check_default", - args: args{ - r: httptest.NewRequest("POST", "/", strings.NewReader(`{"name":"test","data":"test"}`)), - d: "0", - }, - want: "0", - }, - { - name: "check_query", - args: args{ - r: httptest.NewRequest("POST", "/item?query=hello-query", strings.NewReader(`{"name":"test","data":"test"}`)), - d: "0", - }, - want: "hello-query", - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := GetQuery(tt.args.r, tt.args.d); got != tt.want { - t.Errorf("GetQuery() = %v, want %v", got, tt.want) - } - }) - } -} diff --git a/backend/pkgs/server/response.go b/backend/pkgs/server/response.go deleted file mode 100644 index 7d5880e..0000000 --- a/backend/pkgs/server/response.go +++ /dev/null @@ -1,39 +0,0 @@ -package server - -import ( - "encoding/json" - "net/http" -) - -type ErrorResponse struct { - Error string `json:"error"` - Fields map[string]string `json:"fields,omitempty"` -} - -// Respond converts a Go value to JSON and sends it to the client. -// Adapted from https://github.com/ardanlabs/service/tree/master/foundation/web -func Respond(w http.ResponseWriter, statusCode int, data interface{}) error { - if statusCode == http.StatusNoContent { - w.WriteHeader(statusCode) - return nil - } - - // Convert the response value to JSON. - jsonData, err := json.Marshal(data) - if err != nil { - panic(err) - } - - // Set the content type and headers once we know marshaling has succeeded. - w.Header().Set("Content-Type", ContentJSON) - - // Write the status code to the response. - w.WriteHeader(statusCode) - - // Send the result back to the client. - if _, err := w.Write(jsonData); err != nil { - return err - } - - return nil -} diff --git a/backend/pkgs/server/response_test.go b/backend/pkgs/server/response_test.go deleted file mode 100644 index 14e7a37..0000000 --- a/backend/pkgs/server/response_test.go +++ /dev/null @@ -1,40 +0,0 @@ -package server - -import ( - "net/http" - "net/http/httptest" - "testing" - - "github.com/stretchr/testify/assert" -) - -func Test_Respond_NoContent(t *testing.T) { - recorder := httptest.NewRecorder() - dummystruct := struct { - Name string - }{ - Name: "dummy", - } - - err := Respond(recorder, http.StatusNoContent, dummystruct) - assert.NoError(t, err) - - assert.Equal(t, http.StatusNoContent, recorder.Code) - assert.Empty(t, recorder.Body.String()) -} - -func Test_Respond_JSON(t *testing.T) { - recorder := httptest.NewRecorder() - dummystruct := struct { - Name string `json:"name"` - }{ - Name: "dummy", - } - - err := Respond(recorder, http.StatusCreated, dummystruct) - assert.NoError(t, err) - - assert.Equal(t, http.StatusCreated, recorder.Code) - assert.JSONEq(t, recorder.Body.String(), `{"name":"dummy"}`) - assert.Equal(t, "application/json", recorder.Header().Get("Content-Type")) -} diff --git a/backend/pkgs/server/result.go b/backend/pkgs/server/result.go deleted file mode 100644 index 69dcf81..0000000 --- a/backend/pkgs/server/result.go +++ /dev/null @@ -1,19 +0,0 @@ -package server - -type Result struct { - Error bool `json:"error,omitempty"` - Details interface{} `json:"details,omitempty"` - Message string `json:"message,omitempty"` - Item interface{} `json:"item,omitempty"` -} - -type Results struct { - Items any `json:"items"` -} - -// Wrap creates a Wrapper instance and adds the initial namespace and data to be returned. -func Wrap(data interface{}) Result { - return Result{ - Item: data, - } -} diff --git a/backend/pkgs/server/server.go b/backend/pkgs/server/server.go deleted file mode 100644 index d021b31..0000000 --- a/backend/pkgs/server/server.go +++ /dev/null @@ -1,144 +0,0 @@ -package server - -import ( - "context" - "errors" - "fmt" - "net/http" - "os" - "os/signal" - "sync" - "syscall" - "time" - - "github.com/go-chi/chi/v5" -) - -var ( - ErrServerNotStarted = errors.New("server not started") - ErrServerAlreadyStarted = errors.New("server already started") -) - -type Server struct { - Host string - Port string - Worker Worker - - wg sync.WaitGroup - mux *chi.Mux - - // mw is the global middleware chain for the server. - mw []Middleware - - started bool - activeServer *http.Server - - idleTimeout time.Duration - readTimeout time.Duration - writeTimeout time.Duration -} - -func NewServer(opts ...Option) *Server { - s := &Server{ - Host: "localhost", - Port: "8080", - mux: chi.NewRouter(), - Worker: NewSimpleWorker(), - idleTimeout: 30 * time.Second, - readTimeout: 10 * time.Second, - writeTimeout: 10 * time.Second, - } - - for _, opt := range opts { - err := opt(s) - if err != nil { - panic(err) - } - } - - return s -} - -func (s *Server) Shutdown(sig string) error { - if !s.started { - return ErrServerNotStarted - } - fmt.Printf("Received %s signal, shutting down\n", sig) - - // Create a context with a 5-second timeout. - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) - defer cancel() - - err := s.activeServer.Shutdown(ctx) - s.started = false - if err != nil { - return err - } - - fmt.Println("Http server shutdown, waiting for all tasks to finish") - s.wg.Wait() - - return nil -} - -func (s *Server) Start() error { - if s.started { - return ErrServerAlreadyStarted - } - - s.activeServer = &http.Server{ - Addr: s.Host + ":" + s.Port, - Handler: s.mux, - IdleTimeout: s.idleTimeout, - ReadTimeout: s.readTimeout, - WriteTimeout: s.writeTimeout, - } - - shutdownError := make(chan error) - - go func() { - // Create a quit channel which carries os.Signal values. - quit := make(chan os.Signal, 1) - - // Use signal.Notify() to listen for incoming SIGINT and SIGTERM signals and - // relay them to the quit channel. - signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) - - // Read the signal from the quit channel. block until received - sig := <-quit - - err := s.Shutdown(sig.String()) - if err != nil { - shutdownError <- err - } - - // Exit the application with a 0 (success) status code. - os.Exit(0) - }() - - s.started = true - err := s.activeServer.ListenAndServe() - - if !errors.Is(err, http.ErrServerClosed) { - return err - } - - err = <-shutdownError - if err != nil { - return err - } - - fmt.Println("Server shutdown successfully") - - return nil -} - -// Background starts a go routine that runs on the servers pool. In the event of a shutdown -// request, the server will wait until all open goroutines have finished before shutting down. -func (svr *Server) Background(task func()) { - svr.wg.Add(1) - svr.Worker.Add(func() { - defer svr.wg.Done() - task() - }) -} diff --git a/backend/pkgs/server/server_options.go b/backend/pkgs/server/server_options.go deleted file mode 100644 index 93b7781..0000000 --- a/backend/pkgs/server/server_options.go +++ /dev/null @@ -1,54 +0,0 @@ -package server - -import "time" - -type Option = func(s *Server) error - -func WithMiddleware(mw ...Middleware) Option { - return func(s *Server) error { - s.mw = append(s.mw, mw...) - return nil - } -} - -func WithWorker(w Worker) Option { - return func(s *Server) error { - s.Worker = w - return nil - } -} - -func WithHost(host string) Option { - return func(s *Server) error { - s.Host = host - return nil - } -} - -func WithPort(port string) Option { - return func(s *Server) error { - s.Port = port - return nil - } -} - -func WithReadTimeout(seconds int) Option { - return func(s *Server) error { - s.readTimeout = time.Duration(seconds) * time.Second - return nil - } -} - -func WithWriteTimeout(seconds int) Option { - return func(s *Server) error { - s.writeTimeout = time.Duration(seconds) * time.Second - return nil - } -} - -func WithIdleTimeout(seconds int) Option { - return func(s *Server) error { - s.idleTimeout = time.Duration(seconds) * time.Second - return nil - } -} diff --git a/backend/pkgs/server/server_test.go b/backend/pkgs/server/server_test.go deleted file mode 100644 index a5cb218..0000000 --- a/backend/pkgs/server/server_test.go +++ /dev/null @@ -1,101 +0,0 @@ -package server - -import ( - "net/http" - "sync/atomic" - "testing" - "time" - - "github.com/stretchr/testify/assert" -) - -func testServer(t *testing.T, r http.Handler) *Server { - svr := NewServer(WithHost("127.0.0.1"), WithPort("19245")) - - if r != nil { - svr.mux.Mount("/", r) - } - go func() { - err := svr.Start() - assert.NoError(t, err) - }() - - ping := func() error { - _, err := http.Get("http://127.0.0.1:19245") - return err - } - - for { - if err := ping(); err == nil { - break - } - time.Sleep(time.Millisecond * 100) - } - - return svr -} - -func Test_ServerShutdown_Error(t *testing.T) { - svr := NewServer(WithHost("127.0.0.1"), WithPort("19245")) - - err := svr.Shutdown("test") - assert.ErrorIs(t, err, ErrServerNotStarted) -} - -func Test_ServerStarts_Error(t *testing.T) { - svr := testServer(t, nil) - - err := svr.Start() - assert.ErrorIs(t, err, ErrServerAlreadyStarted) - - err = svr.Shutdown("test") - assert.NoError(t, err) -} - -func Test_ServerStarts(t *testing.T) { - svr := testServer(t, nil) - err := svr.Shutdown("test") - assert.NoError(t, err) -} - -func Test_GracefulServerShutdownWithWorkers(t *testing.T) { - isFinished := false - - svr := testServer(t, nil) - - svr.Background(func() { - time.Sleep(time.Second * 4) - isFinished = true - }) - - err := svr.Shutdown("test") - - assert.NoError(t, err) - assert.True(t, isFinished) -} - -func Test_GracefulServerShutdownWithRequests(t *testing.T) { - var isFinished atomic.Bool - - router := http.NewServeMux() - - // add long running handler func - router.HandleFunc("/test", func(rw http.ResponseWriter, r *http.Request) { - time.Sleep(time.Second * 3) - isFinished.Store(true) - }) - - svr := testServer(t, router) - - // Make request to "/test" - go func() { - _, _ = http.Get("http://127.0.0.1:19245/test") // This is probably bad? - }() - - time.Sleep(time.Second) // Hack to wait for the request to be made - - err := svr.Shutdown("test") - assert.NoError(t, err) - - assert.True(t, isFinished.Load()) -} diff --git a/backend/pkgs/server/worker.go b/backend/pkgs/server/worker.go deleted file mode 100644 index 9cf1cc7..0000000 --- a/backend/pkgs/server/worker.go +++ /dev/null @@ -1,21 +0,0 @@ -package server - -// TODO: #2 Implement Go routine pool/job queue - -type Worker interface { - Add(func()) -} - -// SimpleWorker is a simple background worker that implements -// the Worker interface and runs all tasks in a go routine without -// a pool or que or limits. It's useful for simple or small applications -// with minimal/short background tasks -type SimpleWorker struct{} - -func NewSimpleWorker() *SimpleWorker { - return &SimpleWorker{} -} - -func (sw *SimpleWorker) Add(task func()) { - go task() -} diff --git a/docs/docs/api/openapi-2.0.json b/docs/docs/api/openapi-2.0.json index 4fa3dba..c12bd42 100644 --- a/docs/docs/api/openapi-2.0.json +++ b/docs/docs/api/openapi-2.0.json @@ -686,7 +686,7 @@ "422": { "description": "Unprocessable Entity", "schema": { - "$ref": "#/definitions/server.ErrorResponse" + "$ref": "#/definitions/mid.ErrorResponse" } } } @@ -939,7 +939,7 @@ "schema": { "allOf": [ { - "$ref": "#/definitions/server.Results" + "$ref": "#/definitions/v1.Wrapped" }, { "type": "object", @@ -1111,7 +1111,7 @@ "schema": { "allOf": [ { - "$ref": "#/definitions/server.Results" + "$ref": "#/definitions/v1.Wrapped" }, { "type": "object", @@ -1191,7 +1191,7 @@ "schema": { "allOf": [ { - "$ref": "#/definitions/server.Results" + "$ref": "#/definitions/v1.Wrapped" }, { "type": "object", @@ -1331,7 +1331,7 @@ "schema": { "allOf": [ { - "$ref": "#/definitions/server.Results" + "$ref": "#/definitions/v1.Wrapped" }, { "type": "object", @@ -1711,7 +1711,7 @@ "schema": { "allOf": [ { - "$ref": "#/definitions/server.Result" + "$ref": "#/definitions/v1.Wrapped" }, { "type": "object", @@ -1756,7 +1756,7 @@ "schema": { "allOf": [ { - "$ref": "#/definitions/server.Result" + "$ref": "#/definitions/v1.Wrapped" }, { "type": "object", @@ -1793,6 +1793,20 @@ } }, "definitions": { + "mid.ErrorResponse": { + "type": "object", + "properties": { + "error": { + "type": "string" + }, + "fields": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + }, "repo.DocumentOut": { "type": "object", "properties": { @@ -2669,39 +2683,6 @@ } } }, - "server.ErrorResponse": { - "type": "object", - "properties": { - "error": { - "type": "string" - }, - "fields": { - "type": "object", - "additionalProperties": { - "type": "string" - } - } - } - }, - "server.Result": { - "type": "object", - "properties": { - "details": {}, - "error": { - "type": "boolean" - }, - "item": {}, - "message": { - "type": "string" - } - } - }, - "server.Results": { - "type": "object", - "properties": { - "items": {} - } - }, "services.UserRegistration": { "type": "object", "properties": { @@ -2832,6 +2813,12 @@ "type": "string" } } + }, + "v1.Wrapped": { + "type": "object", + "properties": { + "item": {} + } } }, "securityDefinitions": { diff --git a/frontend/lib/api/types/data-contracts.ts b/frontend/lib/api/types/data-contracts.ts index bd3e89c..b9be3ef 100644 --- a/frontend/lib/api/types/data-contracts.ts +++ b/frontend/lib/api/types/data-contracts.ts @@ -10,6 +10,11 @@ * --------------------------------------------------------------- */ +export interface MidErrorResponse { + error: string; + fields: Record; +} + export interface DocumentOut { id: string; path: string; @@ -359,22 +364,6 @@ export interface ValueOverTimeEntry { value: number; } -export interface ServerErrorResponse { - error: string; - fields: Record; -} - -export interface ServerResult { - details: any; - error: boolean; - item: any; - message: string; -} - -export interface ServerResults { - items: any; -} - export interface UserRegistration { email: string; name: string; @@ -431,3 +420,7 @@ export interface TokenResponse { expiresAt: Date | string; token: string; } + +export interface Wrapped { + item: any; +}