From 866c1784a8ba1b55b4f5a81384e7050aafedcd8d Mon Sep 17 00:00:00 2001 From: Hayden <64056131+hay-kot@users.noreply.github.com> Date: Wed, 8 Mar 2023 09:44:42 -0900 Subject: [PATCH] first pass on updating handlers to use adapters --- backend/app/api/handlers/v1/v1_ctrl_group.go | 80 +++------ backend/app/api/handlers/v1/v1_ctrl_items.go | 119 +++++-------- backend/app/api/handlers/v1/v1_ctrl_labels.go | 108 ++++-------- .../app/api/handlers/v1/v1_ctrl_locations.go | 156 +++++------------- .../api/handlers/v1/v1_ctrl_maint_entry.go | 109 +++--------- .../app/api/handlers/v1/v1_ctrl_notifiers.go | 27 ++- backend/app/api/handlers/v1/v1_ctrl_qrcode.go | 21 ++- .../app/api/handlers/v1/v1_ctrl_statistics.go | 44 ++--- 8 files changed, 206 insertions(+), 458 deletions(-) diff --git a/backend/app/api/handlers/v1/v1_ctrl_group.go b/backend/app/api/handlers/v1/v1_ctrl_group.go index 3e42d95..37e9b85 100644 --- a/backend/app/api/handlers/v1/v1_ctrl_group.go +++ b/backend/app/api/handlers/v1/v1_ctrl_group.go @@ -6,14 +6,13 @@ 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/internal/web/adapters" "github.com/hay-kot/homebox/backend/pkgs/server" - "github.com/rs/zerolog/log" ) type ( GroupInvitationCreate struct { - Uses int `json:"uses"` + Uses int `json:"uses" validate:"required,min=1,max=100"` ExpiresAt time.Time `json:"expiresAt"` } @@ -33,7 +32,12 @@ type ( // @Router /v1/groups [Get] // @Security Bearer func (ctrl *V1Controller) HandleGroupGet() server.HandlerFunc { - return ctrl.handleGroupGeneral() + fn := func(r *http.Request) (repo.Group, error) { + auth := services.NewContext(r.Context()) + return ctrl.repo.Groups.GroupByID(auth, auth.GID) + } + + return adapters.Command(fn, http.StatusOK) } // HandleGroupUpdate godoc @@ -46,40 +50,12 @@ func (ctrl *V1Controller) HandleGroupGet() server.HandlerFunc { // @Router /v1/groups [Put] // @Security Bearer func (ctrl *V1Controller) HandleGroupUpdate() server.HandlerFunc { - return ctrl.handleGroupGeneral() -} - -func (ctrl *V1Controller) handleGroupGeneral() server.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) error { - ctx := services.NewContext(r.Context()) - - switch r.Method { - case http.MethodGet: - group, err := ctrl.repo.Groups.GroupByID(ctx, ctx.GID) - if err != nil { - log.Err(err).Msg("failed to get group") - return validate.NewRequestError(err, http.StatusInternalServerError) - } - - return server.Respond(w, http.StatusOK, group) - - case http.MethodPut: - data := repo.GroupUpdate{} - if err := server.Decode(r, &data); err != nil { - return validate.NewRequestError(err, http.StatusBadRequest) - } - - group, err := ctrl.svc.Group.UpdateGroup(ctx, data) - if err != nil { - log.Err(err).Msg("failed to update group") - return validate.NewRequestError(err, http.StatusInternalServerError) - } - - return server.Respond(w, http.StatusOK, group) - } - - return nil + fn := func(r *http.Request, body repo.GroupUpdate) (repo.Group, error) { + auth := services.NewContext(r.Context()) + return ctrl.svc.Group.UpdateGroup(auth, body) } + + return adapters.Action(fn, http.StatusOK) } // HandleGroupInvitationsCreate godoc @@ -92,29 +68,21 @@ func (ctrl *V1Controller) handleGroupGeneral() server.HandlerFunc { // @Router /v1/groups/invitations [Post] // @Security Bearer func (ctrl *V1Controller) HandleGroupInvitationsCreate() server.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) error { - data := GroupInvitationCreate{} - if err := server.Decode(r, &data); err != nil { - log.Err(err).Msg("failed to decode user registration data") - return validate.NewRequestError(err, http.StatusBadRequest) + fn := func(r *http.Request, body GroupInvitationCreate) (GroupInvitation, error) { + if body.ExpiresAt.IsZero() { + body.ExpiresAt = time.Now().Add(time.Hour * 24) } - if data.ExpiresAt.IsZero() { - data.ExpiresAt = time.Now().Add(time.Hour * 24) - } + auth := services.NewContext(r.Context()) - ctx := services.NewContext(r.Context()) + token, err := ctrl.svc.Group.NewInvitation(auth, body.Uses, body.ExpiresAt) - token, err := ctrl.svc.Group.NewInvitation(ctx, data.Uses, data.ExpiresAt) - if err != nil { - log.Err(err).Msg("failed to create new token") - return validate.NewRequestError(err, http.StatusInternalServerError) - } - - return server.Respond(w, http.StatusCreated, GroupInvitation{ + return GroupInvitation{ Token: token, - ExpiresAt: data.ExpiresAt, - Uses: data.Uses, - }) + ExpiresAt: body.ExpiresAt, + Uses: body.Uses, + }, err } + + return adapters.Action(fn, http.StatusCreated) } diff --git a/backend/app/api/handlers/v1/v1_ctrl_items.go b/backend/app/api/handlers/v1/v1_ctrl_items.go index dd6a6a3..dc557f6 100644 --- a/backend/app/api/handlers/v1/v1_ctrl_items.go +++ b/backend/app/api/handlers/v1/v1_ctrl_items.go @@ -7,9 +7,11 @@ import ( "net/http" "strings" + "github.com/google/uuid" "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/internal/web/adapters" "github.com/hay-kot/homebox/backend/pkgs/server" "github.com/rs/zerolog/log" ) @@ -93,26 +95,15 @@ func (ctrl *V1Controller) HandleItemsGetAll() server.HandlerFunc { // @Tags Items // @Produce json // @Param payload body repo.ItemCreate true "Item Data" -// @Success 200 {object} repo.ItemSummary +// @Success 201 {object} repo.ItemSummary // @Router /v1/items [POST] // @Security Bearer func (ctrl *V1Controller) HandleItemsCreate() server.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) error { - createData := repo.ItemCreate{} - if err := server.Decode(r, &createData); err != nil { - log.Err(err).Msg("failed to decode request body") - return validate.NewRequestError(err, http.StatusInternalServerError) - } - - ctx := services.NewContext(r.Context()) - item, err := ctrl.svc.Items.Create(ctx, createData) - if err != nil { - log.Err(err).Msg("failed to create item") - return validate.NewRequestError(err, http.StatusInternalServerError) - } - - return server.Respond(w, http.StatusCreated, item) + fn := func(r *http.Request, body repo.ItemCreate) (repo.ItemOut, error) { + return ctrl.svc.Items.Create(services.NewContext(r.Context()), body) } + + return adapters.Action(fn, http.StatusCreated) } // HandleItemGet godocs @@ -125,7 +116,13 @@ func (ctrl *V1Controller) HandleItemsCreate() server.HandlerFunc { // @Router /v1/items/{id} [GET] // @Security Bearer func (ctrl *V1Controller) HandleItemGet() server.HandlerFunc { - return ctrl.handleItemsGeneral() + fn := func(r *http.Request, ID uuid.UUID) (repo.ItemOut, error) { + auth := services.NewContext(r.Context()) + + return ctrl.repo.Items.GetOneByGroup(auth, auth.GID, ID) + } + + return adapters.CommandID("id", fn, http.StatusOK) } // HandleItemDelete godocs @@ -138,7 +135,13 @@ func (ctrl *V1Controller) HandleItemGet() server.HandlerFunc { // @Router /v1/items/{id} [DELETE] // @Security Bearer func (ctrl *V1Controller) HandleItemDelete() server.HandlerFunc { - return ctrl.handleItemsGeneral() + fn := func(r *http.Request, ID uuid.UUID) (any, error) { + auth := services.NewContext(r.Context()) + err := ctrl.repo.Items.DeleteByGroup(auth, auth.GID, ID) + return nil, err + } + + return adapters.CommandID("id", fn, http.StatusNoContent) } // HandleItemUpdate godocs @@ -152,49 +155,14 @@ func (ctrl *V1Controller) HandleItemDelete() server.HandlerFunc { // @Router /v1/items/{id} [PUT] // @Security Bearer func (ctrl *V1Controller) HandleItemUpdate() server.HandlerFunc { - return ctrl.handleItemsGeneral() -} + fn := func(r *http.Request, ID uuid.UUID, body repo.ItemUpdate) (repo.ItemOut, error) { + auth := services.NewContext(r.Context()) -func (ctrl *V1Controller) handleItemsGeneral() server.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) error { - ctx := services.NewContext(r.Context()) - ID, err := ctrl.routeID(r) - if err != nil { - return err - } - - switch r.Method { - case http.MethodGet: - items, err := ctrl.repo.Items.GetOneByGroup(r.Context(), ctx.GID, ID) - if err != nil { - log.Err(err).Msg("failed to get item") - return validate.NewRequestError(err, http.StatusInternalServerError) - } - return server.Respond(w, http.StatusOK, items) - case http.MethodDelete: - err = ctrl.repo.Items.DeleteByGroup(r.Context(), ctx.GID, ID) - if err != nil { - log.Err(err).Msg("failed to delete item") - return validate.NewRequestError(err, http.StatusInternalServerError) - } - return server.Respond(w, http.StatusNoContent, nil) - case http.MethodPut: - body := repo.ItemUpdate{} - if err := server.Decode(r, &body); err != nil { - log.Err(err).Msg("failed to decode request body") - return validate.NewRequestError(err, http.StatusInternalServerError) - } - body.ID = ID - result, err := ctrl.repo.Items.UpdateByGroup(r.Context(), ctx.GID, body) - if err != nil { - log.Err(err).Msg("failed to update item") - return validate.NewRequestError(err, http.StatusInternalServerError) - } - return server.Respond(w, http.StatusOK, result) - } - - return nil + body.ID = ID + return ctrl.repo.Items.UpdateByGroup(auth, auth.GID, body) } + + return adapters.ActionID("id", fn, http.StatusOK) } // HandleGetAllCustomFieldNames godocs @@ -207,16 +175,12 @@ func (ctrl *V1Controller) handleItemsGeneral() server.HandlerFunc { // @Success 200 {object} []string // @Security Bearer func (ctrl *V1Controller) HandleGetAllCustomFieldNames() server.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) error { - ctx := services.NewContext(r.Context()) - - v, err := ctrl.repo.Items.GetAllCustomFieldNames(r.Context(), ctx.GID) - if err != nil { - return err - } - - return server.Respond(w, http.StatusOK, v) + fn := func(r *http.Request) ([]string, error) { + auth := services.NewContext(r.Context()) + return ctrl.repo.Items.GetAllCustomFieldNames(auth, auth.GID) } + + return adapters.Command(fn, http.StatusOK) } // HandleGetAllCustomFieldValues godocs @@ -229,16 +193,17 @@ func (ctrl *V1Controller) HandleGetAllCustomFieldNames() server.HandlerFunc { // @Success 200 {object} []string // @Security Bearer func (ctrl *V1Controller) HandleGetAllCustomFieldValues() server.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) error { - ctx := services.NewContext(r.Context()) - - v, err := ctrl.repo.Items.GetAllCustomFieldValues(r.Context(), ctx.GID, r.URL.Query().Get("field")) - if err != nil { - return err - } - - return server.Respond(w, http.StatusOK, v) + type query struct { + Field string `schema:"field" validate:"required"` } + + fn := func(r *http.Request, q query) ([]string, error) { + auth := services.NewContext(r.Context()) + return ctrl.repo.Items.GetAllCustomFieldValues(auth, auth.GID, q.Field) + } + + return adapters.Action(fn, http.StatusOK) + } // HandleItemsImport godocs diff --git a/backend/app/api/handlers/v1/v1_ctrl_labels.go b/backend/app/api/handlers/v1/v1_ctrl_labels.go index 8eaaff7..cbd8d6a 100644 --- a/backend/app/api/handlers/v1/v1_ctrl_labels.go +++ b/backend/app/api/handlers/v1/v1_ctrl_labels.go @@ -3,12 +3,11 @@ package v1 import ( "net/http" + "github.com/google/uuid" "github.com/hay-kot/homebox/backend/internal/core/services" - "github.com/hay-kot/homebox/backend/internal/data/ent" "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/rs/zerolog/log" ) // HandleLabelsGetAll godoc @@ -20,15 +19,12 @@ import ( // @Router /v1/labels [GET] // @Security Bearer func (ctrl *V1Controller) HandleLabelsGetAll() server.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) error { - user := services.UseUserCtx(r.Context()) - labels, err := ctrl.repo.Labels.GetAll(r.Context(), user.GroupID) - if err != nil { - log.Err(err).Msg("error getting labels") - return validate.NewRequestError(err, http.StatusInternalServerError) - } - return server.Respond(w, http.StatusOK, server.Results{Items: labels}) + fn := func(r *http.Request) ([]repo.LabelSummary, error) { + auth := services.NewContext(r.Context()) + return ctrl.repo.Labels.GetAll(auth, auth.GID) } + + return adapters.Command(fn, http.StatusOK) } // HandleLabelsCreate godoc @@ -41,22 +37,12 @@ func (ctrl *V1Controller) HandleLabelsGetAll() server.HandlerFunc { // @Router /v1/labels [POST] // @Security Bearer func (ctrl *V1Controller) HandleLabelsCreate() server.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) error { - createData := repo.LabelCreate{} - if err := server.Decode(r, &createData); err != nil { - log.Err(err).Msg("error decoding label create data") - return validate.NewRequestError(err, http.StatusInternalServerError) - } - - user := services.UseUserCtx(r.Context()) - label, err := ctrl.repo.Labels.Create(r.Context(), user.GroupID, createData) - if err != nil { - log.Err(err).Msg("error creating label") - return validate.NewRequestError(err, http.StatusInternalServerError) - } - - return server.Respond(w, http.StatusCreated, label) + 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) } + + return adapters.Action(fn, http.StatusCreated) } // HandleLabelDelete godocs @@ -69,7 +55,13 @@ func (ctrl *V1Controller) HandleLabelsCreate() server.HandlerFunc { // @Router /v1/labels/{id} [DELETE] // @Security Bearer func (ctrl *V1Controller) HandleLabelDelete() server.HandlerFunc { - return ctrl.handleLabelsGeneral() + fn := func(r *http.Request, ID uuid.UUID) (any, error) { + auth := services.NewContext(r.Context()) + err := ctrl.repo.Labels.DeleteByGroup(auth, auth.GID, ID) + return nil, err + } + + return adapters.CommandID("id", fn, http.StatusNoContent) } // HandleLabelGet godocs @@ -82,7 +74,12 @@ func (ctrl *V1Controller) HandleLabelDelete() server.HandlerFunc { // @Router /v1/labels/{id} [GET] // @Security Bearer func (ctrl *V1Controller) HandleLabelGet() server.HandlerFunc { - return ctrl.handleLabelsGeneral() + 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) + } + + return adapters.CommandID("id", fn, http.StatusOK) } // HandleLabelUpdate godocs @@ -95,54 +92,11 @@ func (ctrl *V1Controller) HandleLabelGet() server.HandlerFunc { // @Router /v1/labels/{id} [PUT] // @Security Bearer func (ctrl *V1Controller) HandleLabelUpdate() server.HandlerFunc { - return ctrl.handleLabelsGeneral() -} - -func (ctrl *V1Controller) handleLabelsGeneral() server.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) error { - ctx := services.NewContext(r.Context()) - ID, err := ctrl.routeID(r) - if err != nil { - return err - } - - switch r.Method { - case http.MethodGet: - labels, err := ctrl.repo.Labels.GetOneByGroup(r.Context(), ctx.GID, ID) - if err != nil { - if ent.IsNotFound(err) { - log.Err(err). - Str("id", ID.String()). - Msg("label not found") - return validate.NewRequestError(err, http.StatusNotFound) - } - log.Err(err).Msg("error getting label") - return validate.NewRequestError(err, http.StatusInternalServerError) - } - return server.Respond(w, http.StatusOK, labels) - - case http.MethodDelete: - err = ctrl.repo.Labels.DeleteByGroup(ctx, ctx.GID, ID) - if err != nil { - log.Err(err).Msg("error deleting label") - return validate.NewRequestError(err, http.StatusInternalServerError) - } - return server.Respond(w, http.StatusNoContent, nil) - - case http.MethodPut: - body := repo.LabelUpdate{} - if err := server.Decode(r, &body); err != nil { - return validate.NewRequestError(err, http.StatusInternalServerError) - } - - body.ID = ID - result, err := ctrl.repo.Labels.UpdateByGroup(ctx, ctx.GID, body) - if err != nil { - return validate.NewRequestError(err, http.StatusInternalServerError) - } - return server.Respond(w, http.StatusOK, result) - } - - return nil + fn := func(r *http.Request, ID uuid.UUID, data repo.LabelUpdate) (repo.LabelOut, error) { + auth := services.NewContext(r.Context()) + data.ID = ID + return ctrl.repo.Labels.UpdateByGroup(auth, auth.GID, data) } + + return adapters.ActionID("id", fn, http.StatusOK) } diff --git a/backend/app/api/handlers/v1/v1_ctrl_locations.go b/backend/app/api/handlers/v1/v1_ctrl_locations.go index c371b6e..c0a91a8 100644 --- a/backend/app/api/handlers/v1/v1_ctrl_locations.go +++ b/backend/app/api/handlers/v1/v1_ctrl_locations.go @@ -3,15 +3,14 @@ package v1 import ( "net/http" + "github.com/google/uuid" "github.com/hay-kot/homebox/backend/internal/core/services" - "github.com/hay-kot/homebox/backend/internal/data/ent" "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/rs/zerolog/log" ) -// HandleLocationTreeQuery godoc +// HandleLocationTreeQuery // // @Summary Get Locations Tree // @Tags Locations @@ -21,30 +20,15 @@ import ( // @Router /v1/locations/tree [GET] // @Security Bearer func (ctrl *V1Controller) HandleLocationTreeQuery() server.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) error { - user := services.UseUserCtx(r.Context()) - - q := r.URL.Query() - - withItems := queryBool(q.Get("withItems")) - - locTree, err := ctrl.repo.Locations.Tree( - r.Context(), - user.GroupID, - repo.TreeQuery{ - WithItems: withItems, - }, - ) - if err != nil { - log.Err(err).Msg("failed to get locations tree") - return validate.NewRequestError(err, http.StatusInternalServerError) - } - - return server.Respond(w, http.StatusOK, server.Results{Items: locTree}) + 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) } + + return adapters.Query(fn, http.StatusOK) } -// HandleLocationGetAll godoc +// HandleLocationGetAll // // @Summary Get All Locations // @Tags Locations @@ -54,26 +38,15 @@ func (ctrl *V1Controller) HandleLocationTreeQuery() server.HandlerFunc { // @Router /v1/locations [GET] // @Security Bearer func (ctrl *V1Controller) HandleLocationGetAll() server.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) error { - user := services.UseUserCtx(r.Context()) - - q := r.URL.Query() - - filter := repo.LocationQuery{ - FilterChildren: queryBool(q.Get("filterChildren")), - } - - locations, err := ctrl.repo.Locations.GetAll(r.Context(), user.GroupID, filter) - if err != nil { - log.Err(err).Msg("failed to get locations") - return validate.NewRequestError(err, http.StatusInternalServerError) - } - - return server.Respond(w, http.StatusOK, server.Results{Items: locations}) + 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) } + + return adapters.Query(fn, http.StatusOK) } -// HandleLocationCreate godoc +// HandleLocationCreate // // @Summary Create Location // @Tags Locations @@ -83,25 +56,15 @@ func (ctrl *V1Controller) HandleLocationGetAll() server.HandlerFunc { // @Router /v1/locations [POST] // @Security Bearer func (ctrl *V1Controller) HandleLocationCreate() server.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) error { - createData := repo.LocationCreate{} - if err := server.Decode(r, &createData); err != nil { - log.Err(err).Msg("failed to decode location create data") - return validate.NewRequestError(err, http.StatusInternalServerError) - } - - user := services.UseUserCtx(r.Context()) - location, err := ctrl.repo.Locations.Create(r.Context(), user.GroupID, createData) - if err != nil { - log.Err(err).Msg("failed to create location") - return validate.NewRequestError(err, http.StatusInternalServerError) - } - - return server.Respond(w, http.StatusCreated, location) + 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) } + + return adapters.Action(fn, http.StatusCreated) } -// HandleLocationDelete godocs +// HandleLocationDelete // // @Summary Delete Location // @Tags Locations @@ -111,10 +74,16 @@ func (ctrl *V1Controller) HandleLocationCreate() server.HandlerFunc { // @Router /v1/locations/{id} [DELETE] // @Security Bearer func (ctrl *V1Controller) HandleLocationDelete() server.HandlerFunc { - return ctrl.handleLocationGeneral() + fn := func(r *http.Request, ID uuid.UUID) (any, error) { + auth := services.NewContext(r.Context()) + err := ctrl.repo.Locations.DeleteByGroup(auth, auth.GID, ID) + return nil, err + } + + return adapters.CommandID("id", fn, http.StatusNoContent) } -// HandleLocationGet godocs +// HandleLocationGet // // @Summary Get Location // @Tags Locations @@ -124,10 +93,15 @@ func (ctrl *V1Controller) HandleLocationDelete() server.HandlerFunc { // @Router /v1/locations/{id} [GET] // @Security Bearer func (ctrl *V1Controller) HandleLocationGet() server.HandlerFunc { - return ctrl.handleLocationGeneral() + 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) + } + + return adapters.CommandID("id", fn, http.StatusOK) } -// HandleLocationUpdate godocs +// HandleLocationUpdate // // @Summary Update Location // @Tags Locations @@ -138,57 +112,11 @@ func (ctrl *V1Controller) HandleLocationGet() server.HandlerFunc { // @Router /v1/locations/{id} [PUT] // @Security Bearer func (ctrl *V1Controller) HandleLocationUpdate() server.HandlerFunc { - return ctrl.handleLocationGeneral() -} - -func (ctrl *V1Controller) handleLocationGeneral() server.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) error { - ctx := services.NewContext(r.Context()) - ID, err := ctrl.routeID(r) - if err != nil { - return err - } - - switch r.Method { - case http.MethodGet: - location, err := ctrl.repo.Locations.GetOneByGroup(r.Context(), ctx.GID, ID) - if err != nil { - l := log.Err(err). - Str("ID", ID.String()). - Str("GID", ctx.GID.String()) - - if ent.IsNotFound(err) { - l.Msg("location not found") - return validate.NewRequestError(err, http.StatusNotFound) - } - - l.Msg("failed to get location") - return validate.NewRequestError(err, http.StatusInternalServerError) - } - return server.Respond(w, http.StatusOK, location) - case http.MethodPut: - body := repo.LocationUpdate{} - if err := server.Decode(r, &body); err != nil { - log.Err(err).Msg("failed to decode location update data") - return validate.NewRequestError(err, http.StatusInternalServerError) - } - - body.ID = ID - - result, err := ctrl.repo.Locations.UpdateOneByGroup(r.Context(), ctx.GID, ID, body) - if err != nil { - log.Err(err).Msg("failed to update location") - return validate.NewRequestError(err, http.StatusInternalServerError) - } - return server.Respond(w, http.StatusOK, result) - case http.MethodDelete: - err = ctrl.repo.Locations.DeleteByGroup(r.Context(), ctx.GID, ID) - if err != nil { - log.Err(err).Msg("failed to delete location") - return validate.NewRequestError(err, http.StatusInternalServerError) - } - return server.Respond(w, http.StatusNoContent, nil) - } - return nil + fn := func(r *http.Request, ID uuid.UUID, body repo.LocationUpdate) (repo.LocationOut, error) { + auth := services.NewContext(r.Context()) + body.ID = ID + return ctrl.repo.Locations.UpdateByGroup(auth, auth.GID, ID, body) } + + return adapters.ActionID("id", fn, http.StatusOK) } 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 d7036f0..9cdeb46 100644 --- a/backend/app/api/handlers/v1/v1_ctrl_maint_entry.go +++ b/backend/app/api/handlers/v1/v1_ctrl_maint_entry.go @@ -2,13 +2,12 @@ package v1 import ( "net/http" - "strconv" + "github.com/google/uuid" "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/internal/web/adapters" "github.com/hay-kot/homebox/backend/pkgs/server" - "github.com/rs/zerolog/log" ) // HandleMaintenanceGetLog godoc @@ -20,7 +19,12 @@ import ( // @Router /v1/items/{id}/maintenance [GET] // @Security Bearer func (ctrl *V1Controller) HandleMaintenanceLogGet() server.HandlerFunc { - return ctrl.handleMaintenanceLog() + 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) + } + + return adapters.QueryID("id", fn, http.StatusOK) } // HandleMaintenanceEntryCreate godoc @@ -29,11 +33,16 @@ func (ctrl *V1Controller) HandleMaintenanceLogGet() server.HandlerFunc { // @Tags Maintenance // @Produce json // @Param payload body repo.MaintenanceEntryCreate true "Entry Data" -// @Success 200 {object} repo.MaintenanceEntry +// @Success 201 {object} repo.MaintenanceEntry // @Router /v1/items/{id}/maintenance [POST] // @Security Bearer func (ctrl *V1Controller) HandleMaintenanceEntryCreate() server.HandlerFunc { - return ctrl.handleMaintenanceLog() + 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) + } + + return adapters.ActionID("id", fn, http.StatusCreated) } // HandleMaintenanceEntryDelete godoc @@ -45,7 +54,13 @@ func (ctrl *V1Controller) HandleMaintenanceEntryCreate() server.HandlerFunc { // @Router /v1/items/{id}/maintenance/{entry_id} [DELETE] // @Security Bearer func (ctrl *V1Controller) HandleMaintenanceEntryDelete() server.HandlerFunc { - return ctrl.handleMaintenanceLog() + fn := func(r *http.Request, entryID uuid.UUID) (any, error) { + auth := services.NewContext(r.Context()) + err := ctrl.repo.MaintEntry.Delete(auth, entryID) + return nil, err + } + + return adapters.CommandID("entry_id", fn, http.StatusNoContent) } // HandleMaintenanceEntryUpdate godoc @@ -58,80 +73,10 @@ func (ctrl *V1Controller) HandleMaintenanceEntryDelete() server.HandlerFunc { // @Router /v1/items/{id}/maintenance/{entry_id} [PUT] // @Security Bearer func (ctrl *V1Controller) HandleMaintenanceEntryUpdate() server.HandlerFunc { - return ctrl.handleMaintenanceLog() -} - -func (ctrl *V1Controller) handleMaintenanceLog() server.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) error { - ctx := services.NewContext(r.Context()) - itemID, err := ctrl.routeID(r) - if err != nil { - return err - } - - switch r.Method { - case http.MethodGet: - completed, _ := strconv.ParseBool(r.URL.Query().Get("completed")) - scheduled, _ := strconv.ParseBool(r.URL.Query().Get("scheduled")) - query := repo.MaintenanceLogQuery{ - Completed: completed, - Scheduled: scheduled, - } - - mlog, err := ctrl.repo.MaintEntry.GetLog(ctx, itemID, query) - if err != nil { - log.Err(err).Msg("failed to get items") - return validate.NewRequestError(err, http.StatusInternalServerError) - } - return server.Respond(w, http.StatusOK, mlog) - case http.MethodPost: - var create repo.MaintenanceEntryCreate - err := server.Decode(r, &create) - if err != nil { - return validate.NewRequestError(err, http.StatusBadRequest) - } - - entry, err := ctrl.repo.MaintEntry.Create(ctx, itemID, create) - if err != nil { - log.Err(err).Msg("failed to create item") - return validate.NewRequestError(err, http.StatusInternalServerError) - } - - return server.Respond(w, http.StatusCreated, entry) - case http.MethodPut: - entryID, err := ctrl.routeUUID(r, "entry_id") - if err != nil { - return err - } - - var update repo.MaintenanceEntryUpdate - err = server.Decode(r, &update) - if err != nil { - return validate.NewRequestError(err, http.StatusBadRequest) - } - - entry, err := ctrl.repo.MaintEntry.Update(ctx, entryID, update) - if err != nil { - log.Err(err).Msg("failed to update item") - return validate.NewRequestError(err, http.StatusInternalServerError) - } - - return server.Respond(w, http.StatusOK, entry) - case http.MethodDelete: - entryID, err := ctrl.routeUUID(r, "entry_id") - if err != nil { - return err - } - - err = ctrl.repo.MaintEntry.Delete(ctx, entryID) - if err != nil { - log.Err(err).Msg("failed to delete item") - return validate.NewRequestError(err, http.StatusInternalServerError) - } - - return server.Respond(w, http.StatusNoContent, nil) - } - - return nil + 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) } + + return adapters.ActionID("entry_id", fn, http.StatusOK) } diff --git a/backend/app/api/handlers/v1/v1_ctrl_notifiers.go b/backend/app/api/handlers/v1/v1_ctrl_notifiers.go index a226249..4ee5d32 100644 --- a/backend/app/api/handlers/v1/v1_ctrl_notifiers.go +++ b/backend/app/api/handlers/v1/v1_ctrl_notifiers.go @@ -1,7 +1,6 @@ package v1 import ( - "context" "net/http" "github.com/containrrr/shoutrrr" @@ -21,9 +20,9 @@ import ( // @Router /v1/notifiers [GET] // @Security Bearer func (ctrl *V1Controller) HandleGetUserNotifiers() server.HandlerFunc { - fn := func(ctx context.Context, _ struct{}) ([]repo.NotifierOut, error) { - user := services.UseUserCtx(ctx) - return ctrl.repo.Notifiers.GetByUser(ctx, user.ID) + fn := func(r *http.Request, _ struct{}) ([]repo.NotifierOut, error) { + user := services.UseUserCtx(r.Context()) + return ctrl.repo.Notifiers.GetByUser(r.Context(), user.ID) } return adapters.Query(fn, http.StatusOK) @@ -39,9 +38,9 @@ func (ctrl *V1Controller) HandleGetUserNotifiers() server.HandlerFunc { // @Router /v1/notifiers [POST] // @Security Bearer func (ctrl *V1Controller) HandleCreateNotifier() server.HandlerFunc { - fn := func(ctx context.Context, in repo.NotifierCreate) (repo.NotifierOut, error) { - auth := services.NewContext(ctx) - return ctrl.repo.Notifiers.Create(ctx, auth.GID, auth.UID, in) + 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) } return adapters.Action(fn, http.StatusCreated) @@ -56,9 +55,9 @@ func (ctrl *V1Controller) HandleCreateNotifier() server.HandlerFunc { // @Router /v1/notifiers/{id} [DELETE] // @Security Bearer func (ctrl *V1Controller) HandleDeleteNotifier() server.HandlerFunc { - fn := func(ctx context.Context, ID uuid.UUID) (any, error) { - auth := services.NewContext(ctx) - return nil, ctrl.repo.Notifiers.Delete(ctx, auth.UID, ID) + 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) } return adapters.CommandID("id", fn, http.StatusNoContent) @@ -74,9 +73,9 @@ func (ctrl *V1Controller) HandleDeleteNotifier() server.HandlerFunc { // @Router /v1/notifiers/{id} [PUT] // @Security Bearer func (ctrl *V1Controller) HandleUpdateNotifier() server.HandlerFunc { - fn := func(ctx context.Context, ID uuid.UUID, in repo.NotifierUpdate) (repo.NotifierOut, error) { - auth := services.NewContext(ctx) - return ctrl.repo.Notifiers.Update(ctx, auth.UID, ID, in) + 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) } return adapters.ActionID("id", fn, http.StatusOK) @@ -97,7 +96,7 @@ func (ctrl *V1Controller) HandlerNotifierTest() server.HandlerFunc { URL string `json:"url" validate:"required"` } - fn := func(ctx context.Context, q body) (any, error) { + fn := func(r *http.Request, q body) (any, error) { err := shoutrrr.Send(q.URL, "Test message from Homebox") return nil, err } diff --git a/backend/app/api/handlers/v1/v1_ctrl_qrcode.go b/backend/app/api/handlers/v1/v1_ctrl_qrcode.go index 5084cb8..8aa549f 100644 --- a/backend/app/api/handlers/v1/v1_ctrl_qrcode.go +++ b/backend/app/api/handlers/v1/v1_ctrl_qrcode.go @@ -6,7 +6,7 @@ import ( "io" "net/http" - "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/yeqown/go-qrcode/v2" "github.com/yeqown/go-qrcode/writer/standard" @@ -27,24 +27,23 @@ var qrcodeLogo []byte // @Router /v1/qrcode [GET] // @Security Bearer func (ctrl *V1Controller) HandleGenerateQRCode() server.HandlerFunc { - const MaxLength = 4_296 // assume alphanumeric characters only + type query struct { + // 4,296 characters is the maximum length of a QR code + Data string `schema:"data" validate:"required,max=4296"` + } return func(w http.ResponseWriter, r *http.Request) error { - data := r.URL.Query().Get("data") + q, err := adapters.DecodeQuery[query](r) + if err != nil { + return err + } image, err := png.Decode(bytes.NewReader(qrcodeLogo)) if err != nil { panic(err) } - if len(data) > MaxLength { - return validate.NewFieldErrors(validate.FieldError{ - Field: "data", - Error: "max length is 4,296 characters exceeded", - }) - } - - qrc, err := qrcode.New(data) + qrc, err := qrcode.New(q.Data) if err != nil { return err } diff --git a/backend/app/api/handlers/v1/v1_ctrl_statistics.go b/backend/app/api/handlers/v1/v1_ctrl_statistics.go index 223f70a..2505662 100644 --- a/backend/app/api/handlers/v1/v1_ctrl_statistics.go +++ b/backend/app/api/handlers/v1/v1_ctrl_statistics.go @@ -5,7 +5,9 @@ import ( "time" "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/internal/web/adapters" "github.com/hay-kot/homebox/backend/pkgs/server" ) @@ -18,16 +20,12 @@ import ( // @Router /v1/groups/statistics/locations [GET] // @Security Bearer func (ctrl *V1Controller) HandleGroupStatisticsLocations() server.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) error { - ctx := services.NewContext(r.Context()) - - stats, err := ctrl.repo.Groups.StatsLocationsByPurchasePrice(ctx, ctx.GID) - if err != nil { - return validate.NewRequestError(err, http.StatusInternalServerError) - } - - return server.Respond(w, http.StatusOK, stats) + fn := func(r *http.Request) ([]repo.TotalsByOrganizer, error) { + auth := services.NewContext(r.Context()) + return ctrl.repo.Groups.StatsLocationsByPurchasePrice(auth, auth.GID) } + + return adapters.Command(fn, http.StatusOK) } // HandleGroupStatisticsLabels godoc @@ -39,16 +37,12 @@ func (ctrl *V1Controller) HandleGroupStatisticsLocations() server.HandlerFunc { // @Router /v1/groups/statistics/labels [GET] // @Security Bearer func (ctrl *V1Controller) HandleGroupStatisticsLabels() server.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) error { - ctx := services.NewContext(r.Context()) - - stats, err := ctrl.repo.Groups.StatsLabelsByPurchasePrice(ctx, ctx.GID) - if err != nil { - return validate.NewRequestError(err, http.StatusInternalServerError) - } - - return server.Respond(w, http.StatusOK, stats) + fn := func(r *http.Request) ([]repo.TotalsByOrganizer, error) { + auth := services.NewContext(r.Context()) + return ctrl.repo.Groups.StatsLabelsByPurchasePrice(auth, auth.GID) } + + return adapters.Command(fn, http.StatusOK) } // HandleGroupStatistics godoc @@ -60,16 +54,12 @@ func (ctrl *V1Controller) HandleGroupStatisticsLabels() server.HandlerFunc { // @Router /v1/groups/statistics [GET] // @Security Bearer func (ctrl *V1Controller) HandleGroupStatistics() server.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) error { - ctx := services.NewContext(r.Context()) - - stats, err := ctrl.repo.Groups.StatsGroup(ctx, ctx.GID) - if err != nil { - return validate.NewRequestError(err, http.StatusInternalServerError) - } - - return server.Respond(w, http.StatusOK, stats) + fn := func(r *http.Request) (repo.GroupStatistics, error) { + auth := services.NewContext(r.Context()) + return ctrl.repo.Groups.StatsGroup(auth, auth.GID) } + + return adapters.Command(fn, http.StatusOK) } // HandleGroupStatisticsPriceOverTime godoc