refactor: http interfaces (#114)

* implement custom http handler interface

* implement trace_id

* normalize http method spacing for consistent logs

* fix failing test

* fix linter errors

* cleanup old dead code

* more route cleanup

* cleanup some inconsistent errors

* update and generate code

* make taskfile more consistent

* update task calls

* run tidy

* drop `@` tag for version

* use relative paths

* tidy

* fix auto-setting variables

* update build paths

* add contributing guide

* tidy
This commit is contained in:
Hayden 2022-10-29 18:15:35 -08:00 committed by GitHub
parent e2d93f8523
commit 6529549289
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
40 changed files with 984 additions and 808 deletions

View file

@ -76,9 +76,9 @@ func NewControllerV1(svc *services.AllServices, options ...func(*V1Controller))
// @Produce json
// @Success 200 {object} ApiSummary
// @Router /v1/status [GET]
func (ctrl *V1Controller) HandleBase(ready ReadyFunc, build Build) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
server.Respond(w, http.StatusOK, ApiSummary{
func (ctrl *V1Controller) HandleBase(ready ReadyFunc, build Build) server.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) error {
return server.Respond(w, http.StatusOK, ApiSummary{
Healthy: ready(),
Title: "Go API Template",
Message: "Welcome to the Go API Template Application!",

View file

@ -5,17 +5,23 @@ import (
"github.com/go-chi/chi/v5"
"github.com/google/uuid"
"github.com/hay-kot/homebox/backend/pkgs/server"
"github.com/rs/zerolog/log"
"github.com/hay-kot/homebox/backend/internal/sys/validate"
)
func (ctrl *V1Controller) routeID(w http.ResponseWriter, r *http.Request) (uuid.UUID, error) {
ID, err := uuid.Parse(chi.URLParam(r, "id"))
if err != nil {
log.Err(err).Msg("failed to parse id")
server.RespondError(w, http.StatusBadRequest, err)
return uuid.Nil, err
}
// routeID extracts the ID from the request URL. If the ID is not in a valid
// format, an error is returned. If a error is returned, it can be directly returned
// from the handler. the validate.ErrInvalidID error is known by the error middleware
// and will be handled accordingly.
//
// Example: /api/v1/ac614db5-d8b8-4659-9b14-6e913a6eb18a -> uuid.UUID{ac614db5-d8b8-4659-9b14-6e913a6eb18a}
func (ctrl *V1Controller) routeID(r *http.Request) (uuid.UUID, error) {
return ctrl.routeUUID(r, "id")
}
func (ctrl *V1Controller) routeUUID(r *http.Request, key string) (uuid.UUID, error) {
ID, err := uuid.Parse(chi.URLParam(r, key))
if err != nil {
return uuid.Nil, validate.NewInvalidRouteKeyError(key)
}
return ID, nil
}

View file

@ -6,6 +6,7 @@ import (
"time"
"github.com/hay-kot/homebox/backend/internal/services"
"github.com/hay-kot/homebox/backend/internal/sys/validate"
"github.com/hay-kot/homebox/backend/pkgs/server"
"github.com/rs/zerolog/log"
)
@ -32,17 +33,15 @@ type (
// @Produce json
// @Success 200 {object} TokenResponse
// @Router /v1/users/login [POST]
func (ctrl *V1Controller) HandleAuthLogin() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
func (ctrl *V1Controller) HandleAuthLogin() server.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) error {
loginForm := &LoginForm{}
switch r.Header.Get("Content-Type") {
case server.ContentFormUrlEncoded:
err := r.ParseForm()
if err != nil {
server.Respond(w, http.StatusBadRequest, server.Wrap(err))
log.Error().Err(err).Msg("failed to parse form")
return
return server.Respond(w, http.StatusBadRequest, server.Wrap(err))
}
loginForm.Username = r.PostFormValue("username")
@ -52,27 +51,31 @@ func (ctrl *V1Controller) HandleAuthLogin() http.HandlerFunc {
if err != nil {
log.Err(err).Msg("failed to decode login form")
server.Respond(w, http.StatusBadRequest, server.Wrap(err))
return
}
default:
server.Respond(w, http.StatusBadRequest, errors.New("invalid content type"))
return
return server.Respond(w, http.StatusBadRequest, errors.New("invalid content type"))
}
if loginForm.Username == "" || loginForm.Password == "" {
server.RespondError(w, http.StatusBadRequest, errors.New("username and password are required"))
return
return validate.NewFieldErrors(
validate.FieldError{
Field: "username",
Error: "username or password is empty",
},
validate.FieldError{
Field: "password",
Error: "username or password is empty",
},
)
}
newToken, err := ctrl.svc.User.Login(r.Context(), loginForm.Username, loginForm.Password)
if err != nil {
server.RespondError(w, http.StatusInternalServerError, err)
return
return validate.NewRequestError(errors.New("authentication failed"), http.StatusInternalServerError)
}
server.Respond(w, http.StatusOK, TokenResponse{
return server.Respond(w, http.StatusOK, TokenResponse{
Token: "Bearer " + newToken.Raw,
ExpiresAt: newToken.ExpiresAt,
})
@ -85,23 +88,19 @@ func (ctrl *V1Controller) HandleAuthLogin() http.HandlerFunc {
// @Success 204
// @Router /v1/users/logout [POST]
// @Security Bearer
func (ctrl *V1Controller) HandleAuthLogout() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
func (ctrl *V1Controller) HandleAuthLogout() server.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) error {
token := services.UseTokenCtx(r.Context())
if token == "" {
server.RespondError(w, http.StatusUnauthorized, errors.New("no token within request context"))
return
return validate.NewRequestError(errors.New("no token within request context"), http.StatusUnauthorized)
}
err := ctrl.svc.User.Logout(r.Context(), token)
if err != nil {
server.RespondError(w, http.StatusInternalServerError, err)
return
return validate.NewRequestError(err, http.StatusInternalServerError)
}
server.Respond(w, http.StatusNoContent, nil)
return server.Respond(w, http.StatusNoContent, nil)
}
}
@ -113,22 +112,18 @@ func (ctrl *V1Controller) HandleAuthLogout() http.HandlerFunc {
// @Success 200
// @Router /v1/users/refresh [GET]
// @Security Bearer
func (ctrl *V1Controller) HandleAuthRefresh() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
func (ctrl *V1Controller) HandleAuthRefresh() server.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) error {
requestToken := services.UseTokenCtx(r.Context())
if requestToken == "" {
server.RespondError(w, http.StatusUnauthorized, errors.New("no user token found"))
return
return validate.NewRequestError(errors.New("no token within request context"), http.StatusUnauthorized)
}
newToken, err := ctrl.svc.User.RenewToken(r.Context(), requestToken)
if err != nil {
server.RespondUnauthorized(w)
return
return validate.NewUnauthorizedError()
}
server.Respond(w, http.StatusOK, newToken)
return server.Respond(w, http.StatusOK, newToken)
}
}

View file

@ -7,6 +7,7 @@ import (
"github.com/hay-kot/homebox/backend/internal/repo"
"github.com/hay-kot/homebox/backend/internal/services"
"github.com/hay-kot/homebox/backend/internal/sys/validate"
"github.com/hay-kot/homebox/backend/pkgs/server"
"github.com/rs/zerolog/log"
)
@ -31,7 +32,7 @@ type (
// @Success 200 {object} repo.Group
// @Router /v1/groups [Get]
// @Security Bearer
func (ctrl *V1Controller) HandleGroupGet() http.HandlerFunc {
func (ctrl *V1Controller) HandleGroupGet() server.HandlerFunc {
return ctrl.handleGroupGeneral()
}
@ -43,12 +44,12 @@ func (ctrl *V1Controller) HandleGroupGet() http.HandlerFunc {
// @Success 200 {object} repo.Group
// @Router /v1/groups [Put]
// @Security Bearer
func (ctrl *V1Controller) HandleGroupUpdate() http.HandlerFunc {
func (ctrl *V1Controller) HandleGroupUpdate() server.HandlerFunc {
return ctrl.handleGroupGeneral()
}
func (ctrl *V1Controller) handleGroupGeneral() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
func (ctrl *V1Controller) handleGroupGeneral() server.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) error {
ctx := services.NewContext(r.Context())
switch r.Method {
@ -56,29 +57,28 @@ func (ctrl *V1Controller) handleGroupGeneral() http.HandlerFunc {
group, err := ctrl.svc.Group.Get(ctx)
if err != nil {
log.Err(err).Msg("failed to get group")
server.RespondError(w, http.StatusInternalServerError, err)
return
return validate.NewRequestError(err, http.StatusInternalServerError)
}
group.Currency = strings.ToUpper(group.Currency) // TODO: Hack to fix the currency enums being lower caseÍ
server.Respond(w, http.StatusOK, group)
return server.Respond(w, http.StatusOK, group)
case http.MethodPut:
data := repo.GroupUpdate{}
if err := server.Decode(r, &data); err != nil {
server.RespondError(w, http.StatusBadRequest, err)
return
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")
server.RespondError(w, http.StatusInternalServerError, err)
return
return validate.NewRequestError(err, http.StatusInternalServerError)
}
group.Currency = strings.ToUpper(group.Currency) // TODO: Hack to fix the currency enums being lower case
server.Respond(w, http.StatusOK, group)
return server.Respond(w, http.StatusOK, group)
}
return nil
}
}
@ -90,13 +90,12 @@ func (ctrl *V1Controller) handleGroupGeneral() http.HandlerFunc {
// @Success 200 {object} GroupInvitation
// @Router /v1/groups/invitations [Post]
// @Security Bearer
func (ctrl *V1Controller) HandleGroupInvitationsCreate() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
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")
server.RespondError(w, http.StatusBadRequest, err)
return
return validate.NewRequestError(err, http.StatusBadRequest)
}
if data.ExpiresAt.IsZero() {
@ -108,11 +107,10 @@ func (ctrl *V1Controller) HandleGroupInvitationsCreate() http.HandlerFunc {
token, err := ctrl.svc.Group.NewInvitation(ctx, data.Uses, data.ExpiresAt)
if err != nil {
log.Err(err).Msg("failed to create new token")
server.RespondError(w, http.StatusInternalServerError, err)
return
return validate.NewRequestError(err, http.StatusInternalServerError)
}
server.Respond(w, http.StatusCreated, GroupInvitation{
return server.Respond(w, http.StatusCreated, GroupInvitation{
Token: token,
ExpiresAt: data.ExpiresAt,
Uses: data.Uses,

View file

@ -9,6 +9,7 @@ import (
"github.com/google/uuid"
"github.com/hay-kot/homebox/backend/internal/repo"
"github.com/hay-kot/homebox/backend/internal/services"
"github.com/hay-kot/homebox/backend/internal/sys/validate"
"github.com/hay-kot/homebox/backend/pkgs/server"
"github.com/rs/zerolog/log"
)
@ -25,7 +26,7 @@ import (
// @Success 200 {object} repo.PaginationResult[repo.ItemSummary]{}
// @Router /v1/items [GET]
// @Security Bearer
func (ctrl *V1Controller) HandleItemsGetAll() http.HandlerFunc {
func (ctrl *V1Controller) HandleItemsGetAll() server.HandlerFunc {
uuidList := func(params url.Values, key string) []uuid.UUID {
var ids []uuid.UUID
for _, id := range params[key] {
@ -58,15 +59,14 @@ func (ctrl *V1Controller) HandleItemsGetAll() http.HandlerFunc {
}
}
return func(w http.ResponseWriter, r *http.Request) {
return func(w http.ResponseWriter, r *http.Request) error {
ctx := services.NewContext(r.Context())
items, err := ctrl.svc.Items.Query(ctx, extractQuery(r))
if err != nil {
log.Err(err).Msg("failed to get items")
server.RespondServerError(w)
return
return validate.NewRequestError(err, http.StatusInternalServerError)
}
server.Respond(w, http.StatusOK, items)
return server.Respond(w, http.StatusOK, items)
}
}
@ -78,24 +78,22 @@ func (ctrl *V1Controller) HandleItemsGetAll() http.HandlerFunc {
// @Success 200 {object} repo.ItemSummary
// @Router /v1/items [POST]
// @Security Bearer
func (ctrl *V1Controller) HandleItemsCreate() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
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")
server.RespondError(w, http.StatusInternalServerError, err)
return
return validate.NewRequestError(err, http.StatusInternalServerError)
}
user := services.UseUserCtx(r.Context())
item, err := ctrl.svc.Items.Create(r.Context(), user.GroupID, createData)
if err != nil {
log.Err(err).Msg("failed to create item")
server.RespondServerError(w)
return
return validate.NewRequestError(err, http.StatusInternalServerError)
}
server.Respond(w, http.StatusCreated, item)
return server.Respond(w, http.StatusCreated, item)
}
}
@ -107,7 +105,7 @@ func (ctrl *V1Controller) HandleItemsCreate() http.HandlerFunc {
// @Success 200 {object} repo.ItemOut
// @Router /v1/items/{id} [GET]
// @Security Bearer
func (ctrl *V1Controller) HandleItemGet() http.HandlerFunc {
func (ctrl *V1Controller) HandleItemGet() server.HandlerFunc {
return ctrl.handleItemsGeneral()
}
@ -119,7 +117,7 @@ func (ctrl *V1Controller) HandleItemGet() http.HandlerFunc {
// @Success 204
// @Router /v1/items/{id} [DELETE]
// @Security Bearer
func (ctrl *V1Controller) HandleItemDelete() http.HandlerFunc {
func (ctrl *V1Controller) HandleItemDelete() server.HandlerFunc {
return ctrl.handleItemsGeneral()
}
@ -132,16 +130,16 @@ func (ctrl *V1Controller) HandleItemDelete() http.HandlerFunc {
// @Success 200 {object} repo.ItemOut
// @Router /v1/items/{id} [PUT]
// @Security Bearer
func (ctrl *V1Controller) HandleItemUpdate() http.HandlerFunc {
func (ctrl *V1Controller) HandleItemUpdate() server.HandlerFunc {
return ctrl.handleItemsGeneral()
}
func (ctrl *V1Controller) handleItemsGeneral() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
func (ctrl *V1Controller) handleItemsGeneral() server.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) error {
ctx := services.NewContext(r.Context())
ID, err := ctrl.routeID(w, r)
ID, err := ctrl.routeID(r)
if err != nil {
return
return err
}
switch r.Method {
@ -149,37 +147,32 @@ func (ctrl *V1Controller) handleItemsGeneral() http.HandlerFunc {
items, err := ctrl.svc.Items.GetOne(r.Context(), ctx.GID, ID)
if err != nil {
log.Err(err).Msg("failed to get item")
server.RespondServerError(w)
return
return validate.NewRequestError(err, http.StatusInternalServerError)
}
server.Respond(w, http.StatusOK, items)
return
return server.Respond(w, http.StatusOK, items)
case http.MethodDelete:
err = ctrl.svc.Items.Delete(r.Context(), ctx.GID, ID)
if err != nil {
log.Err(err).Msg("failed to delete item")
server.RespondServerError(w)
return
return validate.NewRequestError(err, http.StatusInternalServerError)
}
server.Respond(w, http.StatusNoContent, nil)
return
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")
server.RespondError(w, http.StatusInternalServerError, err)
return
return validate.NewRequestError(err, http.StatusInternalServerError)
}
body.ID = ID
result, err := ctrl.svc.Items.Update(r.Context(), ctx.GID, body)
if err != nil {
log.Err(err).Msg("failed to update item")
server.RespondServerError(w)
return
return validate.NewRequestError(err, http.StatusInternalServerError)
}
server.Respond(w, http.StatusOK, result)
return server.Respond(w, http.StatusOK, result)
}
return nil
}
}
@ -191,29 +184,26 @@ func (ctrl *V1Controller) handleItemsGeneral() http.HandlerFunc {
// @Param csv formData file true "Image to upload"
// @Router /v1/items/import [Post]
// @Security Bearer
func (ctrl *V1Controller) HandleItemsImport() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
func (ctrl *V1Controller) HandleItemsImport() server.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) error {
err := r.ParseMultipartForm(ctrl.maxUploadSize << 20)
if err != nil {
log.Err(err).Msg("failed to parse multipart form")
server.RespondServerError(w)
return
return validate.NewRequestError(err, http.StatusInternalServerError)
}
file, _, err := r.FormFile("csv")
if err != nil {
log.Err(err).Msg("failed to get file from form")
server.RespondServerError(w)
return
return validate.NewRequestError(err, http.StatusInternalServerError)
}
reader := csv.NewReader(file)
data, err := reader.ReadAll()
if err != nil {
log.Err(err).Msg("failed to read csv")
server.RespondServerError(w)
return
return validate.NewRequestError(err, http.StatusInternalServerError)
}
user := services.UseUserCtx(r.Context())
@ -221,10 +211,9 @@ func (ctrl *V1Controller) HandleItemsImport() http.HandlerFunc {
_, err = ctrl.svc.Items.CsvImport(r.Context(), user.GroupID, data)
if err != nil {
log.Err(err).Msg("failed to import items")
server.RespondServerError(w)
return
return validate.NewRequestError(err, http.StatusInternalServerError)
}
server.Respond(w, http.StatusNoContent, nil)
return server.Respond(w, http.StatusNoContent, nil)
}
}

View file

@ -5,11 +5,10 @@ import (
"fmt"
"net/http"
"github.com/go-chi/chi/v5"
"github.com/google/uuid"
"github.com/hay-kot/homebox/backend/ent/attachment"
"github.com/hay-kot/homebox/backend/internal/repo"
"github.com/hay-kot/homebox/backend/internal/services"
"github.com/hay-kot/homebox/backend/internal/sys/validate"
"github.com/hay-kot/homebox/backend/pkgs/server"
"github.com/rs/zerolog/log"
)
@ -29,19 +28,19 @@ 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.ValidationError
// @Failure 422 {object} server.ErrorResponse
// @Router /v1/items/{id}/attachments [POST]
// @Security Bearer
func (ctrl *V1Controller) HandleItemAttachmentCreate() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
func (ctrl *V1Controller) HandleItemAttachmentCreate() server.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) error {
err := r.ParseMultipartForm(ctrl.maxUploadSize << 20)
if err != nil {
log.Err(err).Msg("failed to parse multipart form")
server.RespondError(w, http.StatusBadRequest, errors.New("failed to parse multipart form"))
return
return validate.NewRequestError(errors.New("failed to parse multipart form"), http.StatusBadRequest)
}
errs := make(server.ValidationErrors, 0)
errs := validate.NewFieldErrors()
file, _, err := r.FormFile("file")
if err != nil {
@ -51,8 +50,7 @@ func (ctrl *V1Controller) HandleItemAttachmentCreate() http.HandlerFunc {
errs = errs.Append("file", "file is required")
default:
log.Err(err).Msg("failed to get file from form")
server.RespondServerError(w)
return
return validate.NewRequestError(err, http.StatusInternalServerError)
}
}
@ -62,9 +60,8 @@ func (ctrl *V1Controller) HandleItemAttachmentCreate() http.HandlerFunc {
errs = errs.Append("name", "name is required")
}
if errs.HasErrors() {
server.Respond(w, http.StatusUnprocessableEntity, errs)
return
if !errs.Nil() {
return server.Respond(w, http.StatusUnprocessableEntity, errs)
}
attachmentType := r.FormValue("type")
@ -72,9 +69,9 @@ func (ctrl *V1Controller) HandleItemAttachmentCreate() http.HandlerFunc {
attachmentType = attachment.TypeAttachment.String()
}
id, err := ctrl.routeID(w, r)
id, err := ctrl.routeID(r)
if err != nil {
return
return err
}
ctx := services.NewContext(r.Context())
@ -89,11 +86,10 @@ func (ctrl *V1Controller) HandleItemAttachmentCreate() http.HandlerFunc {
if err != nil {
log.Err(err).Msg("failed to add attachment")
server.RespondServerError(w)
return
return validate.NewRequestError(err, http.StatusInternalServerError)
}
server.Respond(w, http.StatusCreated, item)
return server.Respond(w, http.StatusCreated, item)
}
}
@ -106,21 +102,21 @@ func (ctrl *V1Controller) HandleItemAttachmentCreate() http.HandlerFunc {
// @Success 200
// @Router /v1/items/{id}/attachments/download [GET]
// @Security Bearer
func (ctrl *V1Controller) HandleItemAttachmentDownload() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
func (ctrl *V1Controller) HandleItemAttachmentDownload() server.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) error {
token := server.GetParam(r, "token", "")
doc, err := ctrl.svc.Items.AttachmentPath(r.Context(), token)
if err != nil {
log.Err(err).Msg("failed to get attachment")
server.RespondServerError(w)
return
return validate.NewRequestError(err, http.StatusInternalServerError)
}
w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", doc.Title))
w.Header().Set("Content-Type", "application/octet-stream")
http.ServeFile(w, r, doc.Path)
return nil
}
}
@ -133,7 +129,7 @@ func (ctrl *V1Controller) HandleItemAttachmentDownload() http.HandlerFunc {
// @Success 200 {object} ItemAttachmentToken
// @Router /v1/items/{id}/attachments/{attachment_id} [GET]
// @Security Bearer
func (ctrl *V1Controller) HandleItemAttachmentToken() http.HandlerFunc {
func (ctrl *V1Controller) HandleItemAttachmentToken() server.HandlerFunc {
return ctrl.handleItemAttachmentsHandler
}
@ -145,7 +141,7 @@ func (ctrl *V1Controller) HandleItemAttachmentToken() http.HandlerFunc {
// @Success 204
// @Router /v1/items/{id}/attachments/{attachment_id} [DELETE]
// @Security Bearer
func (ctrl *V1Controller) HandleItemAttachmentDelete() http.HandlerFunc {
func (ctrl *V1Controller) HandleItemAttachmentDelete() server.HandlerFunc {
return ctrl.handleItemAttachmentsHandler
}
@ -158,66 +154,60 @@ func (ctrl *V1Controller) HandleItemAttachmentDelete() http.HandlerFunc {
// @Success 200 {object} repo.ItemOut
// @Router /v1/items/{id}/attachments/{attachment_id} [PUT]
// @Security Bearer
func (ctrl *V1Controller) HandleItemAttachmentUpdate() http.HandlerFunc {
func (ctrl *V1Controller) HandleItemAttachmentUpdate() server.HandlerFunc {
return ctrl.handleItemAttachmentsHandler
}
func (ctrl *V1Controller) handleItemAttachmentsHandler(w http.ResponseWriter, r *http.Request) {
ID, err := ctrl.routeID(w, r)
func (ctrl *V1Controller) handleItemAttachmentsHandler(w http.ResponseWriter, r *http.Request) error {
ID, err := ctrl.routeID(r)
if err != nil {
return
return err
}
attachmentId, err := uuid.Parse(chi.URLParam(r, "attachment_id"))
attachmentID, err := ctrl.routeUUID(r, "attachment_id")
if err != nil {
log.Err(err).Msg("failed to parse attachment_id param")
server.RespondError(w, http.StatusBadRequest, err)
return
return err
}
ctx := services.NewContext(r.Context())
switch r.Method {
// Token Handler
case http.MethodGet:
token, err := ctrl.svc.Items.AttachmentToken(ctx, ID, attachmentId)
token, err := ctrl.svc.Items.AttachmentToken(ctx, ID, attachmentID)
if err != nil {
switch err {
case services.ErrNotFound:
log.Err(err).
Str("id", attachmentId.String()).
Str("id", attachmentID.String()).
Msg("failed to find attachment with id")
server.RespondError(w, http.StatusNotFound, err)
return validate.NewRequestError(err, http.StatusNotFound)
case services.ErrFileNotFound:
log.Err(err).
Str("id", attachmentId.String()).
Str("id", attachmentID.String()).
Msg("failed to find file path for attachment with id")
log.Warn().Msg("attachment with no file path removed from database")
server.RespondError(w, http.StatusNotFound, err)
return validate.NewRequestError(err, http.StatusNotFound)
default:
log.Err(err).Msg("failed to get attachment")
server.RespondServerError(w)
return
return validate.NewRequestError(err, http.StatusInternalServerError)
}
}
server.Respond(w, http.StatusOK, ItemAttachmentToken{Token: token})
return server.Respond(w, http.StatusOK, ItemAttachmentToken{Token: token})
// Delete Attachment Handler
case http.MethodDelete:
err = ctrl.svc.Items.AttachmentDelete(r.Context(), ctx.GID, ID, attachmentId)
err = ctrl.svc.Items.AttachmentDelete(r.Context(), ctx.GID, ID, attachmentID)
if err != nil {
log.Err(err).Msg("failed to delete attachment")
server.RespondServerError(w)
return
return validate.NewRequestError(err, http.StatusInternalServerError)
}
server.Respond(w, http.StatusNoContent, nil)
return server.Respond(w, http.StatusNoContent, nil)
// Update Attachment Handler
case http.MethodPut:
@ -225,18 +215,18 @@ func (ctrl *V1Controller) handleItemAttachmentsHandler(w http.ResponseWriter, r
err = server.Decode(r, &attachment)
if err != nil {
log.Err(err).Msg("failed to decode attachment")
server.RespondError(w, http.StatusBadRequest, err)
return
return validate.NewRequestError(err, http.StatusBadRequest)
}
attachment.ID = attachmentId
attachment.ID = attachmentID
val, err := ctrl.svc.Items.AttachmentUpdate(ctx, ID, &attachment)
if err != nil {
log.Err(err).Msg("failed to delete attachment")
server.RespondServerError(w)
return
return validate.NewRequestError(err, http.StatusInternalServerError)
}
server.Respond(w, http.StatusOK, val)
return server.Respond(w, http.StatusOK, val)
}
return nil
}

View file

@ -6,6 +6,7 @@ import (
"github.com/hay-kot/homebox/backend/ent"
"github.com/hay-kot/homebox/backend/internal/repo"
"github.com/hay-kot/homebox/backend/internal/services"
"github.com/hay-kot/homebox/backend/internal/sys/validate"
"github.com/hay-kot/homebox/backend/pkgs/server"
"github.com/rs/zerolog/log"
)
@ -17,16 +18,15 @@ import (
// @Success 200 {object} server.Results{items=[]repo.LabelOut}
// @Router /v1/labels [GET]
// @Security Bearer
func (ctrl *V1Controller) HandleLabelsGetAll() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
func (ctrl *V1Controller) HandleLabelsGetAll() server.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) error {
user := services.UseUserCtx(r.Context())
labels, err := ctrl.svc.Labels.GetAll(r.Context(), user.GroupID)
if err != nil {
log.Err(err).Msg("error getting labels")
server.RespondServerError(w)
return
return validate.NewRequestError(err, http.StatusInternalServerError)
}
server.Respond(w, http.StatusOK, server.Results{Items: labels})
return server.Respond(w, http.StatusOK, server.Results{Items: labels})
}
}
@ -38,24 +38,22 @@ func (ctrl *V1Controller) HandleLabelsGetAll() http.HandlerFunc {
// @Success 200 {object} repo.LabelSummary
// @Router /v1/labels [POST]
// @Security Bearer
func (ctrl *V1Controller) HandleLabelsCreate() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
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")
server.RespondError(w, http.StatusInternalServerError, err)
return
return validate.NewRequestError(err, http.StatusInternalServerError)
}
user := services.UseUserCtx(r.Context())
label, err := ctrl.svc.Labels.Create(r.Context(), user.GroupID, createData)
if err != nil {
log.Err(err).Msg("error creating label")
server.RespondServerError(w)
return
return validate.NewRequestError(err, http.StatusInternalServerError)
}
server.Respond(w, http.StatusCreated, label)
return server.Respond(w, http.StatusCreated, label)
}
}
@ -67,7 +65,7 @@ func (ctrl *V1Controller) HandleLabelsCreate() http.HandlerFunc {
// @Success 204
// @Router /v1/labels/{id} [DELETE]
// @Security Bearer
func (ctrl *V1Controller) HandleLabelDelete() http.HandlerFunc {
func (ctrl *V1Controller) HandleLabelDelete() server.HandlerFunc {
return ctrl.handleLabelsGeneral()
}
@ -79,7 +77,7 @@ func (ctrl *V1Controller) HandleLabelDelete() http.HandlerFunc {
// @Success 200 {object} repo.LabelOut
// @Router /v1/labels/{id} [GET]
// @Security Bearer
func (ctrl *V1Controller) HandleLabelGet() http.HandlerFunc {
func (ctrl *V1Controller) HandleLabelGet() server.HandlerFunc {
return ctrl.handleLabelsGeneral()
}
@ -91,16 +89,16 @@ func (ctrl *V1Controller) HandleLabelGet() http.HandlerFunc {
// @Success 200 {object} repo.LabelOut
// @Router /v1/labels/{id} [PUT]
// @Security Bearer
func (ctrl *V1Controller) HandleLabelUpdate() http.HandlerFunc {
func (ctrl *V1Controller) HandleLabelUpdate() server.HandlerFunc {
return ctrl.handleLabelsGeneral()
}
func (ctrl *V1Controller) handleLabelsGeneral() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
func (ctrl *V1Controller) handleLabelsGeneral() server.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) error {
ctx := services.NewContext(r.Context())
ID, err := ctrl.routeID(w, r)
ID, err := ctrl.routeID(r)
if err != nil {
return
return err
}
switch r.Method {
@ -111,40 +109,37 @@ func (ctrl *V1Controller) handleLabelsGeneral() http.HandlerFunc {
log.Err(err).
Str("id", ID.String()).
Msg("label not found")
server.RespondError(w, http.StatusNotFound, err)
return
return validate.NewRequestError(err, http.StatusNotFound)
}
log.Err(err).Msg("error getting label")
server.RespondServerError(w)
return
return validate.NewRequestError(err, http.StatusInternalServerError)
}
server.Respond(w, http.StatusOK, labels)
return server.Respond(w, http.StatusOK, labels)
case http.MethodDelete:
err = ctrl.svc.Labels.Delete(r.Context(), ctx.GID, ID)
if err != nil {
log.Err(err).Msg("error deleting label")
server.RespondServerError(w)
return
return validate.NewRequestError(err, http.StatusInternalServerError)
}
server.Respond(w, http.StatusNoContent, nil)
return server.Respond(w, http.StatusNoContent, nil)
case http.MethodPut:
body := repo.LabelUpdate{}
if err := server.Decode(r, &body); err != nil {
log.Err(err).Msg("error decoding label update data")
server.RespondError(w, http.StatusInternalServerError, err)
return
return validate.NewRequestError(err, http.StatusInternalServerError)
}
body.ID = ID
result, err := ctrl.svc.Labels.Update(r.Context(), ctx.GID, body)
if err != nil {
log.Err(err).Msg("error updating label")
server.RespondServerError(w)
return
return validate.NewRequestError(err, http.StatusInternalServerError)
}
server.Respond(w, http.StatusOK, result)
return server.Respond(w, http.StatusOK, result)
}
return nil
}
}

View file

@ -6,6 +6,7 @@ import (
"github.com/hay-kot/homebox/backend/ent"
"github.com/hay-kot/homebox/backend/internal/repo"
"github.com/hay-kot/homebox/backend/internal/services"
"github.com/hay-kot/homebox/backend/internal/sys/validate"
"github.com/hay-kot/homebox/backend/pkgs/server"
"github.com/rs/zerolog/log"
)
@ -17,17 +18,16 @@ import (
// @Success 200 {object} server.Results{items=[]repo.LocationOutCount}
// @Router /v1/locations [GET]
// @Security Bearer
func (ctrl *V1Controller) HandleLocationGetAll() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
func (ctrl *V1Controller) HandleLocationGetAll() server.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) error {
user := services.UseUserCtx(r.Context())
locations, err := ctrl.svc.Location.GetAll(r.Context(), user.GroupID)
if err != nil {
log.Err(err).Msg("failed to get locations")
server.RespondServerError(w)
return
return validate.NewRequestError(err, http.StatusInternalServerError)
}
server.Respond(w, http.StatusOK, server.Results{Items: locations})
return server.Respond(w, http.StatusOK, server.Results{Items: locations})
}
}
@ -39,24 +39,22 @@ func (ctrl *V1Controller) HandleLocationGetAll() http.HandlerFunc {
// @Success 200 {object} repo.LocationSummary
// @Router /v1/locations [POST]
// @Security Bearer
func (ctrl *V1Controller) HandleLocationCreate() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
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")
server.RespondError(w, http.StatusInternalServerError, err)
return
return validate.NewRequestError(err, http.StatusInternalServerError)
}
user := services.UseUserCtx(r.Context())
location, err := ctrl.svc.Location.Create(r.Context(), user.GroupID, createData)
if err != nil {
log.Err(err).Msg("failed to create location")
server.RespondServerError(w)
return
return validate.NewRequestError(err, http.StatusInternalServerError)
}
server.Respond(w, http.StatusCreated, location)
return server.Respond(w, http.StatusCreated, location)
}
}
@ -68,7 +66,7 @@ func (ctrl *V1Controller) HandleLocationCreate() http.HandlerFunc {
// @Success 204
// @Router /v1/locations/{id} [DELETE]
// @Security Bearer
func (ctrl *V1Controller) HandleLocationDelete() http.HandlerFunc {
func (ctrl *V1Controller) HandleLocationDelete() server.HandlerFunc {
return ctrl.handleLocationGeneral()
}
@ -80,7 +78,7 @@ func (ctrl *V1Controller) HandleLocationDelete() http.HandlerFunc {
// @Success 200 {object} repo.LocationOut
// @Router /v1/locations/{id} [GET]
// @Security Bearer
func (ctrl *V1Controller) HandleLocationGet() http.HandlerFunc {
func (ctrl *V1Controller) HandleLocationGet() server.HandlerFunc {
return ctrl.handleLocationGeneral()
}
@ -93,16 +91,16 @@ func (ctrl *V1Controller) HandleLocationGet() http.HandlerFunc {
// @Success 200 {object} repo.LocationOut
// @Router /v1/locations/{id} [PUT]
// @Security Bearer
func (ctrl *V1Controller) HandleLocationUpdate() http.HandlerFunc {
func (ctrl *V1Controller) HandleLocationUpdate() server.HandlerFunc {
return ctrl.handleLocationGeneral()
}
func (ctrl *V1Controller) handleLocationGeneral() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
func (ctrl *V1Controller) handleLocationGeneral() server.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) error {
ctx := services.NewContext(r.Context())
ID, err := ctrl.routeID(w, r)
ID, err := ctrl.routeID(r)
if err != nil {
return
return err
}
switch r.Method {
@ -115,21 +113,18 @@ func (ctrl *V1Controller) handleLocationGeneral() http.HandlerFunc {
if ent.IsNotFound(err) {
l.Msg("location not found")
server.RespondError(w, http.StatusNotFound, err)
return
return validate.NewRequestError(err, http.StatusNotFound)
}
l.Msg("failed to get location")
server.RespondServerError(w)
return
return validate.NewRequestError(err, http.StatusInternalServerError)
}
server.Respond(w, http.StatusOK, location)
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")
server.RespondError(w, http.StatusInternalServerError, err)
return
return validate.NewRequestError(err, http.StatusInternalServerError)
}
body.ID = ID
@ -137,18 +132,17 @@ func (ctrl *V1Controller) handleLocationGeneral() http.HandlerFunc {
result, err := ctrl.svc.Location.Update(r.Context(), ctx.GID, body)
if err != nil {
log.Err(err).Msg("failed to update location")
server.RespondServerError(w)
return
return validate.NewRequestError(err, http.StatusInternalServerError)
}
server.Respond(w, http.StatusOK, result)
return server.Respond(w, http.StatusOK, result)
case http.MethodDelete:
err = ctrl.svc.Location.Delete(r.Context(), ctx.GID, ID)
if err != nil {
log.Err(err).Msg("failed to delete location")
server.RespondServerError(w)
return
return validate.NewRequestError(err, http.StatusInternalServerError)
}
server.Respond(w, http.StatusNoContent, nil)
return server.Respond(w, http.StatusNoContent, nil)
}
return nil
}
}

View file

@ -6,6 +6,7 @@ import (
"github.com/google/uuid"
"github.com/hay-kot/homebox/backend/internal/repo"
"github.com/hay-kot/homebox/backend/internal/services"
"github.com/hay-kot/homebox/backend/internal/sys/validate"
"github.com/hay-kot/homebox/backend/pkgs/server"
"github.com/rs/zerolog/log"
)
@ -17,29 +18,26 @@ import (
// @Param payload body services.UserRegistration true "User Data"
// @Success 204
// @Router /v1/users/register [Post]
func (ctrl *V1Controller) HandleUserRegistration() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
func (ctrl *V1Controller) HandleUserRegistration() server.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) error {
regData := services.UserRegistration{}
if err := server.Decode(r, &regData); err != nil {
log.Err(err).Msg("failed to decode user registration data")
server.RespondError(w, http.StatusInternalServerError, err)
return
return validate.NewRequestError(err, http.StatusInternalServerError)
}
if !ctrl.allowRegistration && regData.GroupToken == "" {
server.RespondError(w, http.StatusForbidden, nil)
return
return validate.NewRequestError(nil, http.StatusForbidden)
}
_, err := ctrl.svc.User.RegisterUser(r.Context(), regData)
if err != nil {
log.Err(err).Msg("failed to register user")
server.RespondError(w, http.StatusInternalServerError, err)
return
return validate.NewRequestError(err, http.StatusInternalServerError)
}
server.Respond(w, http.StatusNoContent, nil)
return server.Respond(w, http.StatusNoContent, nil)
}
}
@ -50,17 +48,16 @@ func (ctrl *V1Controller) HandleUserRegistration() http.HandlerFunc {
// @Success 200 {object} server.Result{item=repo.UserOut}
// @Router /v1/users/self [GET]
// @Security Bearer
func (ctrl *V1Controller) HandleUserSelf() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
func (ctrl *V1Controller) HandleUserSelf() server.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) error {
token := services.UseTokenCtx(r.Context())
usr, err := ctrl.svc.User.GetSelf(r.Context(), token)
if usr.ID == uuid.Nil || err != nil {
log.Err(err).Msg("failed to get user")
server.RespondServerError(w)
return
return validate.NewRequestError(err, http.StatusInternalServerError)
}
server.Respond(w, http.StatusOK, server.Wrap(usr))
return server.Respond(w, http.StatusOK, server.Wrap(usr))
}
}
@ -72,24 +69,22 @@ func (ctrl *V1Controller) HandleUserSelf() http.HandlerFunc {
// @Success 200 {object} server.Result{item=repo.UserUpdate}
// @Router /v1/users/self [PUT]
// @Security Bearer
func (ctrl *V1Controller) HandleUserSelfUpdate() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
func (ctrl *V1Controller) HandleUserSelfUpdate() server.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) error {
updateData := repo.UserUpdate{}
if err := server.Decode(r, &updateData); err != nil {
log.Err(err).Msg("failed to decode user update data")
server.RespondError(w, http.StatusBadRequest, err)
return
return validate.NewRequestError(err, http.StatusBadRequest)
}
actor := services.UseUserCtx(r.Context())
newData, err := ctrl.svc.User.UpdateSelf(r.Context(), actor.ID, updateData)
if err != nil {
server.RespondError(w, http.StatusInternalServerError, err)
return
return validate.NewRequestError(err, http.StatusInternalServerError)
}
server.Respond(w, http.StatusOK, server.Wrap(newData))
return server.Respond(w, http.StatusOK, server.Wrap(newData))
}
}
@ -100,20 +95,18 @@ func (ctrl *V1Controller) HandleUserSelfUpdate() http.HandlerFunc {
// @Success 204
// @Router /v1/users/self [DELETE]
// @Security Bearer
func (ctrl *V1Controller) HandleUserSelfDelete() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
func (ctrl *V1Controller) HandleUserSelfDelete() server.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) error {
if ctrl.isDemo {
server.RespondError(w, http.StatusForbidden, nil)
return
return validate.NewRequestError(nil, http.StatusForbidden)
}
actor := services.UseUserCtx(r.Context())
if err := ctrl.svc.User.DeleteSelf(r.Context(), actor.ID); err != nil {
server.RespondError(w, http.StatusInternalServerError, err)
return
return validate.NewRequestError(err, http.StatusInternalServerError)
}
server.Respond(w, http.StatusNoContent, nil)
return server.Respond(w, http.StatusNoContent, nil)
}
}
@ -131,11 +124,10 @@ type (
// @Param payload body ChangePassword true "Password Payload"
// @Router /v1/users/change-password [PUT]
// @Security Bearer
func (ctrl *V1Controller) HandleUserSelfChangePassword() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
func (ctrl *V1Controller) HandleUserSelfChangePassword() server.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) error {
if ctrl.isDemo {
server.RespondError(w, http.StatusForbidden, nil)
return
return validate.NewRequestError(nil, http.StatusForbidden)
}
var cp ChangePassword
@ -148,10 +140,9 @@ func (ctrl *V1Controller) HandleUserSelfChangePassword() http.HandlerFunc {
ok := ctrl.svc.User.ChangePassword(ctx, cp.Current, cp.New)
if !ok {
server.RespondError(w, http.StatusInternalServerError, err)
return
return validate.NewRequestError(err, http.StatusInternalServerError)
}
server.Respond(w, http.StatusNoContent, nil)
return server.Respond(w, http.StatusNoContent, nil)
}
}