first pass on updating handlers to use adapters

This commit is contained in:
Hayden 2023-03-08 09:44:42 -09:00
parent 844dc5e856
commit 866c1784a8
No known key found for this signature in database
GPG key ID: 17CF79474E257545
8 changed files with 206 additions and 458 deletions

View file

@ -6,14 +6,13 @@ import (
"github.com/hay-kot/homebox/backend/internal/core/services" "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/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/homebox/backend/pkgs/server"
"github.com/rs/zerolog/log"
) )
type ( type (
GroupInvitationCreate struct { GroupInvitationCreate struct {
Uses int `json:"uses"` Uses int `json:"uses" validate:"required,min=1,max=100"`
ExpiresAt time.Time `json:"expiresAt"` ExpiresAt time.Time `json:"expiresAt"`
} }
@ -33,7 +32,12 @@ type (
// @Router /v1/groups [Get] // @Router /v1/groups [Get]
// @Security Bearer // @Security Bearer
func (ctrl *V1Controller) HandleGroupGet() server.HandlerFunc { 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 // HandleGroupUpdate godoc
@ -46,40 +50,12 @@ func (ctrl *V1Controller) HandleGroupGet() server.HandlerFunc {
// @Router /v1/groups [Put] // @Router /v1/groups [Put]
// @Security Bearer // @Security Bearer
func (ctrl *V1Controller) HandleGroupUpdate() server.HandlerFunc { func (ctrl *V1Controller) HandleGroupUpdate() server.HandlerFunc {
return ctrl.handleGroupGeneral() fn := func(r *http.Request, body repo.GroupUpdate) (repo.Group, error) {
auth := services.NewContext(r.Context())
return ctrl.svc.Group.UpdateGroup(auth, body)
} }
func (ctrl *V1Controller) handleGroupGeneral() server.HandlerFunc { return adapters.Action(fn, http.StatusOK)
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
}
} }
// HandleGroupInvitationsCreate godoc // HandleGroupInvitationsCreate godoc
@ -92,29 +68,21 @@ func (ctrl *V1Controller) handleGroupGeneral() server.HandlerFunc {
// @Router /v1/groups/invitations [Post] // @Router /v1/groups/invitations [Post]
// @Security Bearer // @Security Bearer
func (ctrl *V1Controller) HandleGroupInvitationsCreate() server.HandlerFunc { func (ctrl *V1Controller) HandleGroupInvitationsCreate() server.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) error { fn := func(r *http.Request, body GroupInvitationCreate) (GroupInvitation, error) {
data := GroupInvitationCreate{} if body.ExpiresAt.IsZero() {
if err := server.Decode(r, &data); err != nil { body.ExpiresAt = time.Now().Add(time.Hour * 24)
log.Err(err).Msg("failed to decode user registration data")
return validate.NewRequestError(err, http.StatusBadRequest)
} }
if data.ExpiresAt.IsZero() { auth := services.NewContext(r.Context())
data.ExpiresAt = time.Now().Add(time.Hour * 24)
}
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) return GroupInvitation{
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{
Token: token, Token: token,
ExpiresAt: data.ExpiresAt, ExpiresAt: body.ExpiresAt,
Uses: data.Uses, Uses: body.Uses,
}) }, err
} }
return adapters.Action(fn, http.StatusCreated)
} }

View file

@ -7,9 +7,11 @@ import (
"net/http" "net/http"
"strings" "strings"
"github.com/google/uuid"
"github.com/hay-kot/homebox/backend/internal/core/services" "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/data/repo"
"github.com/hay-kot/homebox/backend/internal/sys/validate" "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/homebox/backend/pkgs/server"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
) )
@ -93,26 +95,15 @@ func (ctrl *V1Controller) HandleItemsGetAll() server.HandlerFunc {
// @Tags Items // @Tags Items
// @Produce json // @Produce json
// @Param payload body repo.ItemCreate true "Item Data" // @Param payload body repo.ItemCreate true "Item Data"
// @Success 200 {object} repo.ItemSummary // @Success 201 {object} repo.ItemSummary
// @Router /v1/items [POST] // @Router /v1/items [POST]
// @Security Bearer // @Security Bearer
func (ctrl *V1Controller) HandleItemsCreate() server.HandlerFunc { func (ctrl *V1Controller) HandleItemsCreate() server.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) error { fn := func(r *http.Request, body repo.ItemCreate) (repo.ItemOut, error) {
createData := repo.ItemCreate{} return ctrl.svc.Items.Create(services.NewContext(r.Context()), body)
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()) return adapters.Action(fn, http.StatusCreated)
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)
}
} }
// HandleItemGet godocs // HandleItemGet godocs
@ -125,7 +116,13 @@ func (ctrl *V1Controller) HandleItemsCreate() server.HandlerFunc {
// @Router /v1/items/{id} [GET] // @Router /v1/items/{id} [GET]
// @Security Bearer // @Security Bearer
func (ctrl *V1Controller) HandleItemGet() server.HandlerFunc { 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 // HandleItemDelete godocs
@ -138,7 +135,13 @@ func (ctrl *V1Controller) HandleItemGet() server.HandlerFunc {
// @Router /v1/items/{id} [DELETE] // @Router /v1/items/{id} [DELETE]
// @Security Bearer // @Security Bearer
func (ctrl *V1Controller) HandleItemDelete() server.HandlerFunc { 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 // HandleItemUpdate godocs
@ -152,49 +155,14 @@ func (ctrl *V1Controller) HandleItemDelete() server.HandlerFunc {
// @Router /v1/items/{id} [PUT] // @Router /v1/items/{id} [PUT]
// @Security Bearer // @Security Bearer
func (ctrl *V1Controller) HandleItemUpdate() server.HandlerFunc { 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 body.ID = ID
result, err := ctrl.repo.Items.UpdateByGroup(r.Context(), ctx.GID, body) return ctrl.repo.Items.UpdateByGroup(auth, auth.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 return adapters.ActionID("id", fn, http.StatusOK)
}
} }
// HandleGetAllCustomFieldNames godocs // HandleGetAllCustomFieldNames godocs
@ -207,16 +175,12 @@ func (ctrl *V1Controller) handleItemsGeneral() server.HandlerFunc {
// @Success 200 {object} []string // @Success 200 {object} []string
// @Security Bearer // @Security Bearer
func (ctrl *V1Controller) HandleGetAllCustomFieldNames() server.HandlerFunc { func (ctrl *V1Controller) HandleGetAllCustomFieldNames() server.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) error { fn := func(r *http.Request) ([]string, error) {
ctx := services.NewContext(r.Context()) auth := services.NewContext(r.Context())
return ctrl.repo.Items.GetAllCustomFieldNames(auth, auth.GID)
v, err := ctrl.repo.Items.GetAllCustomFieldNames(r.Context(), ctx.GID)
if err != nil {
return err
} }
return server.Respond(w, http.StatusOK, v) return adapters.Command(fn, http.StatusOK)
}
} }
// HandleGetAllCustomFieldValues godocs // HandleGetAllCustomFieldValues godocs
@ -229,16 +193,17 @@ func (ctrl *V1Controller) HandleGetAllCustomFieldNames() server.HandlerFunc {
// @Success 200 {object} []string // @Success 200 {object} []string
// @Security Bearer // @Security Bearer
func (ctrl *V1Controller) HandleGetAllCustomFieldValues() server.HandlerFunc { func (ctrl *V1Controller) HandleGetAllCustomFieldValues() server.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) error { type query struct {
ctx := services.NewContext(r.Context()) Field string `schema:"field" validate:"required"`
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) 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 // HandleItemsImport godocs

View file

@ -3,12 +3,11 @@ package v1
import ( import (
"net/http" "net/http"
"github.com/google/uuid"
"github.com/hay-kot/homebox/backend/internal/core/services" "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/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/homebox/backend/pkgs/server"
"github.com/rs/zerolog/log"
) )
// HandleLabelsGetAll godoc // HandleLabelsGetAll godoc
@ -20,15 +19,12 @@ import (
// @Router /v1/labels [GET] // @Router /v1/labels [GET]
// @Security Bearer // @Security Bearer
func (ctrl *V1Controller) HandleLabelsGetAll() server.HandlerFunc { func (ctrl *V1Controller) HandleLabelsGetAll() server.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) error { fn := func(r *http.Request) ([]repo.LabelSummary, error) {
user := services.UseUserCtx(r.Context()) auth := services.NewContext(r.Context())
labels, err := ctrl.repo.Labels.GetAll(r.Context(), user.GroupID) return ctrl.repo.Labels.GetAll(auth, auth.GID)
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})
} }
return adapters.Command(fn, http.StatusOK)
} }
// HandleLabelsCreate godoc // HandleLabelsCreate godoc
@ -41,22 +37,12 @@ func (ctrl *V1Controller) HandleLabelsGetAll() server.HandlerFunc {
// @Router /v1/labels [POST] // @Router /v1/labels [POST]
// @Security Bearer // @Security Bearer
func (ctrl *V1Controller) HandleLabelsCreate() server.HandlerFunc { func (ctrl *V1Controller) HandleLabelsCreate() server.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) error { fn := func(r *http.Request, data repo.LabelCreate) (repo.LabelOut, error) {
createData := repo.LabelCreate{} auth := services.NewContext(r.Context())
if err := server.Decode(r, &createData); err != nil { return ctrl.repo.Labels.Create(auth, auth.GID, data)
log.Err(err).Msg("error decoding label create data")
return validate.NewRequestError(err, http.StatusInternalServerError)
} }
user := services.UseUserCtx(r.Context()) return adapters.Action(fn, http.StatusCreated)
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)
}
} }
// HandleLabelDelete godocs // HandleLabelDelete godocs
@ -69,7 +55,13 @@ func (ctrl *V1Controller) HandleLabelsCreate() server.HandlerFunc {
// @Router /v1/labels/{id} [DELETE] // @Router /v1/labels/{id} [DELETE]
// @Security Bearer // @Security Bearer
func (ctrl *V1Controller) HandleLabelDelete() server.HandlerFunc { 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 // HandleLabelGet godocs
@ -82,7 +74,12 @@ func (ctrl *V1Controller) HandleLabelDelete() server.HandlerFunc {
// @Router /v1/labels/{id} [GET] // @Router /v1/labels/{id} [GET]
// @Security Bearer // @Security Bearer
func (ctrl *V1Controller) HandleLabelGet() server.HandlerFunc { 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 // HandleLabelUpdate godocs
@ -95,54 +92,11 @@ func (ctrl *V1Controller) HandleLabelGet() server.HandlerFunc {
// @Router /v1/labels/{id} [PUT] // @Router /v1/labels/{id} [PUT]
// @Security Bearer // @Security Bearer
func (ctrl *V1Controller) HandleLabelUpdate() server.HandlerFunc { func (ctrl *V1Controller) HandleLabelUpdate() server.HandlerFunc {
return ctrl.handleLabelsGeneral() 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)
} }
func (ctrl *V1Controller) handleLabelsGeneral() server.HandlerFunc { return adapters.ActionID("id", fn, http.StatusOK)
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
}
} }

View file

@ -3,15 +3,14 @@ package v1
import ( import (
"net/http" "net/http"
"github.com/google/uuid"
"github.com/hay-kot/homebox/backend/internal/core/services" "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/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/homebox/backend/pkgs/server"
"github.com/rs/zerolog/log"
) )
// HandleLocationTreeQuery godoc // HandleLocationTreeQuery
// //
// @Summary Get Locations Tree // @Summary Get Locations Tree
// @Tags Locations // @Tags Locations
@ -21,30 +20,15 @@ import (
// @Router /v1/locations/tree [GET] // @Router /v1/locations/tree [GET]
// @Security Bearer // @Security Bearer
func (ctrl *V1Controller) HandleLocationTreeQuery() server.HandlerFunc { func (ctrl *V1Controller) HandleLocationTreeQuery() server.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) error { fn := func(r *http.Request, query repo.TreeQuery) ([]repo.TreeItem, error) {
user := services.UseUserCtx(r.Context()) auth := services.NewContext(r.Context())
return ctrl.repo.Locations.Tree(auth, auth.GID, query)
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}) return adapters.Query(fn, http.StatusOK)
}
} }
// HandleLocationGetAll godoc // HandleLocationGetAll
// //
// @Summary Get All Locations // @Summary Get All Locations
// @Tags Locations // @Tags Locations
@ -54,26 +38,15 @@ func (ctrl *V1Controller) HandleLocationTreeQuery() server.HandlerFunc {
// @Router /v1/locations [GET] // @Router /v1/locations [GET]
// @Security Bearer // @Security Bearer
func (ctrl *V1Controller) HandleLocationGetAll() server.HandlerFunc { func (ctrl *V1Controller) HandleLocationGetAll() server.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) error { fn := func(r *http.Request, q repo.LocationQuery) ([]repo.LocationOutCount, error) {
user := services.UseUserCtx(r.Context()) auth := services.NewContext(r.Context())
return ctrl.repo.Locations.GetAll(auth, auth.GID, q)
q := r.URL.Query()
filter := repo.LocationQuery{
FilterChildren: queryBool(q.Get("filterChildren")),
} }
locations, err := ctrl.repo.Locations.GetAll(r.Context(), user.GroupID, filter) return adapters.Query(fn, http.StatusOK)
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}) // HandleLocationCreate
}
}
// HandleLocationCreate godoc
// //
// @Summary Create Location // @Summary Create Location
// @Tags Locations // @Tags Locations
@ -83,25 +56,15 @@ func (ctrl *V1Controller) HandleLocationGetAll() server.HandlerFunc {
// @Router /v1/locations [POST] // @Router /v1/locations [POST]
// @Security Bearer // @Security Bearer
func (ctrl *V1Controller) HandleLocationCreate() server.HandlerFunc { func (ctrl *V1Controller) HandleLocationCreate() server.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) error { fn := func(r *http.Request, createData repo.LocationCreate) (repo.LocationOut, error) {
createData := repo.LocationCreate{} auth := services.NewContext(r.Context())
if err := server.Decode(r, &createData); err != nil { return ctrl.repo.Locations.Create(auth, auth.GID, createData)
log.Err(err).Msg("failed to decode location create data")
return validate.NewRequestError(err, http.StatusInternalServerError)
} }
user := services.UseUserCtx(r.Context()) return adapters.Action(fn, http.StatusCreated)
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) // HandleLocationDelete
}
}
// HandleLocationDelete godocs
// //
// @Summary Delete Location // @Summary Delete Location
// @Tags Locations // @Tags Locations
@ -111,10 +74,16 @@ func (ctrl *V1Controller) HandleLocationCreate() server.HandlerFunc {
// @Router /v1/locations/{id} [DELETE] // @Router /v1/locations/{id} [DELETE]
// @Security Bearer // @Security Bearer
func (ctrl *V1Controller) HandleLocationDelete() server.HandlerFunc { 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
} }
// HandleLocationGet godocs return adapters.CommandID("id", fn, http.StatusNoContent)
}
// HandleLocationGet
// //
// @Summary Get Location // @Summary Get Location
// @Tags Locations // @Tags Locations
@ -124,10 +93,15 @@ func (ctrl *V1Controller) HandleLocationDelete() server.HandlerFunc {
// @Router /v1/locations/{id} [GET] // @Router /v1/locations/{id} [GET]
// @Security Bearer // @Security Bearer
func (ctrl *V1Controller) HandleLocationGet() server.HandlerFunc { 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)
} }
// HandleLocationUpdate godocs return adapters.CommandID("id", fn, http.StatusOK)
}
// HandleLocationUpdate
// //
// @Summary Update Location // @Summary Update Location
// @Tags Locations // @Tags Locations
@ -138,57 +112,11 @@ func (ctrl *V1Controller) HandleLocationGet() server.HandlerFunc {
// @Router /v1/locations/{id} [PUT] // @Router /v1/locations/{id} [PUT]
// @Security Bearer // @Security Bearer
func (ctrl *V1Controller) HandleLocationUpdate() server.HandlerFunc { func (ctrl *V1Controller) HandleLocationUpdate() server.HandlerFunc {
return ctrl.handleLocationGeneral() fn := func(r *http.Request, ID uuid.UUID, body repo.LocationUpdate) (repo.LocationOut, error) {
} auth := services.NewContext(r.Context())
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 body.ID = ID
return ctrl.repo.Locations.UpdateByGroup(auth, auth.GID, ID, body)
}
result, err := ctrl.repo.Locations.UpdateOneByGroup(r.Context(), ctx.GID, ID, body) return adapters.ActionID("id", fn, http.StatusOK)
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
}
} }

View file

@ -2,13 +2,12 @@ package v1
import ( import (
"net/http" "net/http"
"strconv"
"github.com/google/uuid"
"github.com/hay-kot/homebox/backend/internal/core/services" "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/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/homebox/backend/pkgs/server"
"github.com/rs/zerolog/log"
) )
// HandleMaintenanceGetLog godoc // HandleMaintenanceGetLog godoc
@ -20,7 +19,12 @@ import (
// @Router /v1/items/{id}/maintenance [GET] // @Router /v1/items/{id}/maintenance [GET]
// @Security Bearer // @Security Bearer
func (ctrl *V1Controller) HandleMaintenanceLogGet() server.HandlerFunc { 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 // HandleMaintenanceEntryCreate godoc
@ -29,11 +33,16 @@ func (ctrl *V1Controller) HandleMaintenanceLogGet() server.HandlerFunc {
// @Tags Maintenance // @Tags Maintenance
// @Produce json // @Produce json
// @Param payload body repo.MaintenanceEntryCreate true "Entry Data" // @Param payload body repo.MaintenanceEntryCreate true "Entry Data"
// @Success 200 {object} repo.MaintenanceEntry // @Success 201 {object} repo.MaintenanceEntry
// @Router /v1/items/{id}/maintenance [POST] // @Router /v1/items/{id}/maintenance [POST]
// @Security Bearer // @Security Bearer
func (ctrl *V1Controller) HandleMaintenanceEntryCreate() server.HandlerFunc { 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 // HandleMaintenanceEntryDelete godoc
@ -45,7 +54,13 @@ func (ctrl *V1Controller) HandleMaintenanceEntryCreate() server.HandlerFunc {
// @Router /v1/items/{id}/maintenance/{entry_id} [DELETE] // @Router /v1/items/{id}/maintenance/{entry_id} [DELETE]
// @Security Bearer // @Security Bearer
func (ctrl *V1Controller) HandleMaintenanceEntryDelete() server.HandlerFunc { 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 // HandleMaintenanceEntryUpdate godoc
@ -58,80 +73,10 @@ func (ctrl *V1Controller) HandleMaintenanceEntryDelete() server.HandlerFunc {
// @Router /v1/items/{id}/maintenance/{entry_id} [PUT] // @Router /v1/items/{id}/maintenance/{entry_id} [PUT]
// @Security Bearer // @Security Bearer
func (ctrl *V1Controller) HandleMaintenanceEntryUpdate() server.HandlerFunc { func (ctrl *V1Controller) HandleMaintenanceEntryUpdate() server.HandlerFunc {
return ctrl.handleMaintenanceLog() 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)
} }
func (ctrl *V1Controller) handleMaintenanceLog() server.HandlerFunc { return adapters.ActionID("entry_id", fn, http.StatusOK)
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
}
} }

View file

@ -1,7 +1,6 @@
package v1 package v1
import ( import (
"context"
"net/http" "net/http"
"github.com/containrrr/shoutrrr" "github.com/containrrr/shoutrrr"
@ -21,9 +20,9 @@ import (
// @Router /v1/notifiers [GET] // @Router /v1/notifiers [GET]
// @Security Bearer // @Security Bearer
func (ctrl *V1Controller) HandleGetUserNotifiers() server.HandlerFunc { func (ctrl *V1Controller) HandleGetUserNotifiers() server.HandlerFunc {
fn := func(ctx context.Context, _ struct{}) ([]repo.NotifierOut, error) { fn := func(r *http.Request, _ struct{}) ([]repo.NotifierOut, error) {
user := services.UseUserCtx(ctx) user := services.UseUserCtx(r.Context())
return ctrl.repo.Notifiers.GetByUser(ctx, user.ID) return ctrl.repo.Notifiers.GetByUser(r.Context(), user.ID)
} }
return adapters.Query(fn, http.StatusOK) return adapters.Query(fn, http.StatusOK)
@ -39,9 +38,9 @@ func (ctrl *V1Controller) HandleGetUserNotifiers() server.HandlerFunc {
// @Router /v1/notifiers [POST] // @Router /v1/notifiers [POST]
// @Security Bearer // @Security Bearer
func (ctrl *V1Controller) HandleCreateNotifier() server.HandlerFunc { func (ctrl *V1Controller) HandleCreateNotifier() server.HandlerFunc {
fn := func(ctx context.Context, in repo.NotifierCreate) (repo.NotifierOut, error) { fn := func(r *http.Request, in repo.NotifierCreate) (repo.NotifierOut, error) {
auth := services.NewContext(ctx) auth := services.NewContext(r.Context())
return ctrl.repo.Notifiers.Create(ctx, auth.GID, auth.UID, in) return ctrl.repo.Notifiers.Create(auth, auth.GID, auth.UID, in)
} }
return adapters.Action(fn, http.StatusCreated) return adapters.Action(fn, http.StatusCreated)
@ -56,9 +55,9 @@ func (ctrl *V1Controller) HandleCreateNotifier() server.HandlerFunc {
// @Router /v1/notifiers/{id} [DELETE] // @Router /v1/notifiers/{id} [DELETE]
// @Security Bearer // @Security Bearer
func (ctrl *V1Controller) HandleDeleteNotifier() server.HandlerFunc { func (ctrl *V1Controller) HandleDeleteNotifier() server.HandlerFunc {
fn := func(ctx context.Context, ID uuid.UUID) (any, error) { fn := func(r *http.Request, ID uuid.UUID) (any, error) {
auth := services.NewContext(ctx) auth := services.NewContext(r.Context())
return nil, ctrl.repo.Notifiers.Delete(ctx, auth.UID, ID) return nil, ctrl.repo.Notifiers.Delete(auth, auth.UID, ID)
} }
return adapters.CommandID("id", fn, http.StatusNoContent) return adapters.CommandID("id", fn, http.StatusNoContent)
@ -74,9 +73,9 @@ func (ctrl *V1Controller) HandleDeleteNotifier() server.HandlerFunc {
// @Router /v1/notifiers/{id} [PUT] // @Router /v1/notifiers/{id} [PUT]
// @Security Bearer // @Security Bearer
func (ctrl *V1Controller) HandleUpdateNotifier() server.HandlerFunc { func (ctrl *V1Controller) HandleUpdateNotifier() server.HandlerFunc {
fn := func(ctx context.Context, ID uuid.UUID, in repo.NotifierUpdate) (repo.NotifierOut, error) { fn := func(r *http.Request, ID uuid.UUID, in repo.NotifierUpdate) (repo.NotifierOut, error) {
auth := services.NewContext(ctx) auth := services.NewContext(r.Context())
return ctrl.repo.Notifiers.Update(ctx, auth.UID, ID, in) return ctrl.repo.Notifiers.Update(auth, auth.UID, ID, in)
} }
return adapters.ActionID("id", fn, http.StatusOK) return adapters.ActionID("id", fn, http.StatusOK)
@ -97,7 +96,7 @@ func (ctrl *V1Controller) HandlerNotifierTest() server.HandlerFunc {
URL string `json:"url" validate:"required"` 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") err := shoutrrr.Send(q.URL, "Test message from Homebox")
return nil, err return nil, err
} }

View file

@ -6,7 +6,7 @@ import (
"io" "io"
"net/http" "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/hay-kot/homebox/backend/pkgs/server"
"github.com/yeqown/go-qrcode/v2" "github.com/yeqown/go-qrcode/v2"
"github.com/yeqown/go-qrcode/writer/standard" "github.com/yeqown/go-qrcode/writer/standard"
@ -27,24 +27,23 @@ var qrcodeLogo []byte
// @Router /v1/qrcode [GET] // @Router /v1/qrcode [GET]
// @Security Bearer // @Security Bearer
func (ctrl *V1Controller) HandleGenerateQRCode() server.HandlerFunc { 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 { 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)) image, err := png.Decode(bytes.NewReader(qrcodeLogo))
if err != nil { if err != nil {
panic(err) panic(err)
} }
if len(data) > MaxLength { qrc, err := qrcode.New(q.Data)
return validate.NewFieldErrors(validate.FieldError{
Field: "data",
Error: "max length is 4,296 characters exceeded",
})
}
qrc, err := qrcode.New(data)
if err != nil { if err != nil {
return err return err
} }

View file

@ -5,7 +5,9 @@ import (
"time" "time"
"github.com/hay-kot/homebox/backend/internal/core/services" "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/sys/validate"
"github.com/hay-kot/homebox/backend/internal/web/adapters"
"github.com/hay-kot/homebox/backend/pkgs/server" "github.com/hay-kot/homebox/backend/pkgs/server"
) )
@ -18,16 +20,12 @@ import (
// @Router /v1/groups/statistics/locations [GET] // @Router /v1/groups/statistics/locations [GET]
// @Security Bearer // @Security Bearer
func (ctrl *V1Controller) HandleGroupStatisticsLocations() server.HandlerFunc { func (ctrl *V1Controller) HandleGroupStatisticsLocations() server.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) error { fn := func(r *http.Request) ([]repo.TotalsByOrganizer, error) {
ctx := services.NewContext(r.Context()) auth := services.NewContext(r.Context())
return ctrl.repo.Groups.StatsLocationsByPurchasePrice(auth, auth.GID)
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) return adapters.Command(fn, http.StatusOK)
}
} }
// HandleGroupStatisticsLabels godoc // HandleGroupStatisticsLabels godoc
@ -39,16 +37,12 @@ func (ctrl *V1Controller) HandleGroupStatisticsLocations() server.HandlerFunc {
// @Router /v1/groups/statistics/labels [GET] // @Router /v1/groups/statistics/labels [GET]
// @Security Bearer // @Security Bearer
func (ctrl *V1Controller) HandleGroupStatisticsLabels() server.HandlerFunc { func (ctrl *V1Controller) HandleGroupStatisticsLabels() server.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) error { fn := func(r *http.Request) ([]repo.TotalsByOrganizer, error) {
ctx := services.NewContext(r.Context()) auth := services.NewContext(r.Context())
return ctrl.repo.Groups.StatsLabelsByPurchasePrice(auth, auth.GID)
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) return adapters.Command(fn, http.StatusOK)
}
} }
// HandleGroupStatistics godoc // HandleGroupStatistics godoc
@ -60,16 +54,12 @@ func (ctrl *V1Controller) HandleGroupStatisticsLabels() server.HandlerFunc {
// @Router /v1/groups/statistics [GET] // @Router /v1/groups/statistics [GET]
// @Security Bearer // @Security Bearer
func (ctrl *V1Controller) HandleGroupStatistics() server.HandlerFunc { func (ctrl *V1Controller) HandleGroupStatistics() server.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) error { fn := func(r *http.Request) (repo.GroupStatistics, error) {
ctx := services.NewContext(r.Context()) auth := services.NewContext(r.Context())
return ctrl.repo.Groups.StatsGroup(auth, auth.GID)
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) return adapters.Command(fn, http.StatusOK)
}
} }
// HandleGroupStatisticsPriceOverTime godoc // HandleGroupStatisticsPriceOverTime godoc