From d67d96c6fe58a8c5f60a7b27fae39ab9883c2ce4 Mon Sep 17 00:00:00 2001 From: Hayden <64056131+hay-kot@users.noreply.github.com> Date: Fri, 23 Sep 2022 14:47:34 -0800 Subject: [PATCH] consolidate API code --- backend/app/api/docs/docs.go | 55 +++++++ backend/app/api/docs/swagger.json | 55 +++++++ backend/app/api/docs/swagger.yaml | 35 +++++ backend/app/api/routes.go | 1 + backend/app/api/v1/v1_ctrl_items.go | 113 +------------- .../app/api/v1/v1_ctrl_items_attachments.go | 140 ++++++++++++++++++ .../internal/repo/repo_item_attachments.go | 8 +- backend/internal/types/item_types.go | 6 + 8 files changed, 305 insertions(+), 108 deletions(-) create mode 100644 backend/app/api/v1/v1_ctrl_items_attachments.go diff --git a/backend/app/api/docs/docs.go b/backend/app/api/docs/docs.go index 8bc3007..24a0872 100644 --- a/backend/app/api/docs/docs.go +++ b/backend/app/api/docs/docs.go @@ -354,6 +354,50 @@ const docTemplate = `{ } } }, + "put": { + "security": [ + { + "Bearer": [] + } + ], + "tags": [ + "Items" + ], + "summary": "retrieves an attachment for an item", + "parameters": [ + { + "type": "string", + "description": "Item ID", + "name": "id", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "Attachment ID", + "name": "attachment_id", + "in": "path", + "required": true + }, + { + "description": "Attachment Update", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/types.ItemAttachmentUpdate" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/types.ItemOut" + } + } + } + }, "delete": { "security": [ { @@ -1060,6 +1104,17 @@ const docTemplate = `{ } } }, + "types.ItemAttachmentUpdate": { + "type": "object", + "properties": { + "title": { + "type": "string" + }, + "type": { + "type": "string" + } + } + }, "types.ItemCreate": { "type": "object", "properties": { diff --git a/backend/app/api/docs/swagger.json b/backend/app/api/docs/swagger.json index 00fb54e..af7ca1e 100644 --- a/backend/app/api/docs/swagger.json +++ b/backend/app/api/docs/swagger.json @@ -346,6 +346,50 @@ } } }, + "put": { + "security": [ + { + "Bearer": [] + } + ], + "tags": [ + "Items" + ], + "summary": "retrieves an attachment for an item", + "parameters": [ + { + "type": "string", + "description": "Item ID", + "name": "id", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "Attachment ID", + "name": "attachment_id", + "in": "path", + "required": true + }, + { + "description": "Attachment Update", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/types.ItemAttachmentUpdate" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/types.ItemOut" + } + } + } + }, "delete": { "security": [ { @@ -1052,6 +1096,17 @@ } } }, + "types.ItemAttachmentUpdate": { + "type": "object", + "properties": { + "title": { + "type": "string" + }, + "type": { + "type": "string" + } + } + }, "types.ItemCreate": { "type": "object", "properties": { diff --git a/backend/app/api/docs/swagger.yaml b/backend/app/api/docs/swagger.yaml index 1e6ca9c..b528576 100644 --- a/backend/app/api/docs/swagger.yaml +++ b/backend/app/api/docs/swagger.yaml @@ -65,6 +65,13 @@ definitions: token: type: string type: object + types.ItemAttachmentUpdate: + properties: + title: + type: string + type: + type: string + type: object types.ItemCreate: properties: description: @@ -586,6 +593,34 @@ paths: summary: retrieves an attachment for an item tags: - Items + put: + parameters: + - description: Item ID + in: path + name: id + required: true + type: string + - description: Attachment ID + in: path + name: attachment_id + required: true + type: string + - description: Attachment Update + in: body + name: payload + required: true + schema: + $ref: '#/definitions/types.ItemAttachmentUpdate' + responses: + "200": + description: OK + schema: + $ref: '#/definitions/types.ItemOut' + security: + - Bearer: [] + summary: retrieves an attachment for an item + tags: + - Items /v1/items/{id}/attachments/download: get: parameters: diff --git a/backend/app/api/routes.go b/backend/app/api/routes.go index d524905..284aecd 100644 --- a/backend/app/api/routes.go +++ b/backend/app/api/routes.go @@ -88,6 +88,7 @@ func (a *app) newRouter(repos *repo.AllRepos) *chi.Mux { r.Post(v1Base("/items/{id}/attachments"), v1Ctrl.HandleItemAttachmentCreate()) r.Get(v1Base("/items/{id}/attachments/{attachment_id}"), v1Ctrl.HandleItemAttachmentToken()) + r.Put(v1Base("/items/{id}/attachments/{attachment_id}"), v1Ctrl.HandleItemAttachmentUpdate()) r.Delete(v1Base("/items/{id}/attachments/{attachment_id}"), v1Ctrl.HandleItemAttachmentDelete()) }) } diff --git a/backend/app/api/v1/v1_ctrl_items.go b/backend/app/api/v1/v1_ctrl_items.go index 89622af..bbf9a63 100644 --- a/backend/app/api/v1/v1_ctrl_items.go +++ b/backend/app/api/v1/v1_ctrl_items.go @@ -3,12 +3,8 @@ package v1 import ( "encoding/csv" "errors" - "fmt" "net/http" - "path/filepath" - "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/services" "github.com/hay-kot/homebox/backend/internal/types" @@ -70,7 +66,7 @@ func (ctrl *V1Controller) HandleItemsCreate() http.HandlerFunc { // @Summary deletes a item // @Tags Items // @Produce json -// @Param id path string true "Item ID" +// @Param id path string true "Item ID" // @Success 204 // @Router /v1/items/{id} [DELETE] // @Security Bearer @@ -120,7 +116,7 @@ func (ctrl *V1Controller) HandleItemGet() http.HandlerFunc { // @Summary updates a item // @Tags Items // @Produce json -// @Param id path string true "Item ID" +// @Param id path string true "Item ID" // @Param payload body types.ItemUpdate true "Item Data" // @Success 200 {object} types.ItemOut // @Router /v1/items/{id} [PUT] @@ -233,14 +229,15 @@ func (ctrl *V1Controller) HandleItemAttachmentCreate() http.HandlerFunc { attachmentName = "attachment" } - uid, user, err := ctrl.partialParseIdAndUser(w, r) + uid, _, err := ctrl.partialParseIdAndUser(w, r) if err != nil { return } + ctx := services.NewServiceContext(r.Context()) + item, err := ctrl.svc.Items.AttachmentAdd( - r.Context(), - user.GroupID, + ctx, uid, attachmentName, attachment.Type(attachmentType), @@ -256,101 +253,3 @@ func (ctrl *V1Controller) HandleItemAttachmentCreate() http.HandlerFunc { server.Respond(w, http.StatusCreated, item) } } - -// HandleItemAttachmentGet godocs -// @Summary retrieves an attachment for an item -// @Tags Items -// @Produce application/octet-stream -// @Param id path string true "Item ID" -// @Param token query string true "Attachment token" -// @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) { - token := server.GetParam(r, "token", "") - - path, err := ctrl.svc.Items.AttachmentPath(r.Context(), token) - - if err != nil { - log.Err(err).Msg("failed to get attachment") - server.RespondServerError(w) - return - } - - w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", filepath.Base(path))) - w.Header().Set("Content-Type", "application/octet-stream") - http.ServeFile(w, r, path) - } -} - -// HandleItemAttachmentToken godocs -// @Summary retrieves an attachment for an item -// @Tags Items -// @Produce application/octet-stream -// @Param id path string true "Item ID" -// @Param attachment_id path string true "Attachment ID" -// @Success 200 {object} types.ItemAttachmentToken -// @Router /v1/items/{id}/attachments/{attachment_id} [GET] -// @Security Bearer -func (ctrl *V1Controller) HandleItemAttachmentToken() http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - uid, user, err := ctrl.partialParseIdAndUser(w, r) - if err != nil { - return - } - - attachmentId, err := uuid.Parse(chi.URLParam(r, "attachment_id")) - if err != nil { - log.Err(err).Msg("failed to parse attachment_id param") - server.RespondError(w, http.StatusBadRequest, err) - return - } - - token, err := ctrl.svc.Items.AttachmentToken(r.Context(), user.GroupID, uid, attachmentId) - - if err != nil { - log.Err(err).Msg("failed to get attachment") - server.RespondServerError(w) - return - } - - server.Respond(w, http.StatusOK, types.ItemAttachmentToken{ - Token: token, - }) - - } -} - -// HandleItemAttachmentDelete godocs -// @Summary retrieves an attachment for an item -// @Tags Items -// @Param id path string true "Item ID" -// @Param attachment_id path string true "Attachment ID" -// @Success 204 -// @Router /v1/items/{id}/attachments/{attachment_id} [DELETE] -// @Security Bearer -func (ctrl *V1Controller) HandleItemAttachmentDelete() http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - uid, user, err := ctrl.partialParseIdAndUser(w, r) - if err != nil { - return - } - - attachmentId, err := uuid.Parse(chi.URLParam(r, "attachment_id")) - if err != nil { - log.Err(err).Msg("failed to parse attachment_id param") - server.RespondError(w, http.StatusBadRequest, err) - return - } - - err = ctrl.svc.Items.AttachmentDelete(r.Context(), user.GroupID, uid, attachmentId) - if err != nil { - log.Err(err).Msg("failed to delete attachment") - server.RespondServerError(w) - return - } - - server.Respond(w, http.StatusNoContent, nil) - } -} diff --git a/backend/app/api/v1/v1_ctrl_items_attachments.go b/backend/app/api/v1/v1_ctrl_items_attachments.go new file mode 100644 index 0000000..700aa44 --- /dev/null +++ b/backend/app/api/v1/v1_ctrl_items_attachments.go @@ -0,0 +1,140 @@ +package v1 + +import ( + "fmt" + "net/http" + "path/filepath" + + "github.com/go-chi/chi/v5" + "github.com/google/uuid" + "github.com/hay-kot/homebox/backend/internal/services" + "github.com/hay-kot/homebox/backend/internal/types" + "github.com/hay-kot/homebox/backend/pkgs/server" + "github.com/rs/zerolog/log" +) + +// HandleItemAttachmentGet godocs +// @Summary retrieves an attachment for an item +// @Tags Items +// @Produce application/octet-stream +// @Param id path string true "Item ID" +// @Param token query string true "Attachment token" +// @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) { + token := server.GetParam(r, "token", "") + + path, err := ctrl.svc.Items.AttachmentPath(r.Context(), token) + + if err != nil { + log.Err(err).Msg("failed to get attachment") + server.RespondServerError(w) + return + } + + w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", filepath.Base(path))) + w.Header().Set("Content-Type", "application/octet-stream") + http.ServeFile(w, r, path) + } +} + +// HandleItemAttachmentToken godocs +// @Summary retrieves an attachment for an item +// @Tags Items +// @Produce application/octet-stream +// @Param id path string true "Item ID" +// @Param attachment_id path string true "Attachment ID" +// @Success 200 {object} types.ItemAttachmentToken +// @Router /v1/items/{id}/attachments/{attachment_id} [GET] +// @Security Bearer +func (ctrl *V1Controller) HandleItemAttachmentToken() http.HandlerFunc { + return ctrl.handleItemAttachmentsHandler +} + +// HandleItemAttachmentDelete godocs +// @Summary retrieves an attachment for an item +// @Tags Items +// @Param id path string true "Item ID" +// @Param attachment_id path string true "Attachment ID" +// @Success 204 +// @Router /v1/items/{id}/attachments/{attachment_id} [DELETE] +// @Security Bearer +func (ctrl *V1Controller) HandleItemAttachmentDelete() http.HandlerFunc { + return ctrl.handleItemAttachmentsHandler +} + +// HandleItemAttachmentUpdate godocs +// @Summary retrieves an attachment for an item +// @Tags Items +// @Param id path string true "Item ID" +// @Param attachment_id path string true "Attachment ID" +// @Param payload body types.ItemAttachmentUpdate true "Attachment Update" +// @Success 200 {object} types.ItemOut +// @Router /v1/items/{id}/attachments/{attachment_id} [PUT] +// @Security Bearer +func (ctrl *V1Controller) HandleItemAttachmentUpdate() http.HandlerFunc { + return ctrl.handleItemAttachmentsHandler +} + +func (ctrl *V1Controller) handleItemAttachmentsHandler(w http.ResponseWriter, r *http.Request) { + uid, user, err := ctrl.partialParseIdAndUser(w, r) + if err != nil { + return + } + + attachmentId, err := uuid.Parse(chi.URLParam(r, "attachment_id")) + if err != nil { + log.Err(err).Msg("failed to parse attachment_id param") + server.RespondError(w, http.StatusBadRequest, err) + return + } + + ctx := services.NewServiceContext(r.Context()) + + switch r.Method { + + // Token Handler + case http.MethodGet: + token, err := ctrl.svc.Items.AttachmentToken(ctx, uid, attachmentId) + if err != nil { + log.Err(err).Msg("failed to get attachment") + server.RespondServerError(w) + return + } + + server.Respond(w, http.StatusOK, types.ItemAttachmentToken{Token: token}) + + // Delete Attachment Handler + case http.MethodDelete: + err = ctrl.svc.Items.AttachmentDelete(r.Context(), user.GroupID, uid, attachmentId) + if err != nil { + log.Err(err).Msg("failed to delete attachment") + server.RespondServerError(w) + return + } + + server.Respond(w, http.StatusNoContent, nil) + + // Update Attachment Handler + case http.MethodPut: + var attachment types.ItemAttachmentUpdate + err = server.Decode(r, &attachment) + if err != nil { + log.Err(err).Msg("failed to decode attachment") + server.RespondError(w, http.StatusBadRequest, err) + return + } + + attachment.ID = attachmentId + val, err := ctrl.svc.Items.AttachmentUpdate(ctx, uid, &attachment) + if err != nil { + log.Err(err).Msg("failed to delete attachment") + server.RespondServerError(w) + return + } + + server.Respond(w, http.StatusOK, val) + } +} diff --git a/backend/internal/repo/repo_item_attachments.go b/backend/internal/repo/repo_item_attachments.go index c67f4e9..76b06d6 100644 --- a/backend/internal/repo/repo_item_attachments.go +++ b/backend/internal/repo/repo_item_attachments.go @@ -34,9 +34,15 @@ func (r *AttachmentRepo) Get(ctx context.Context, id uuid.UUID) (*ent.Attachment } func (r *AttachmentRepo) Update(ctx context.Context, itemId uuid.UUID, typ attachment.Type) (*ent.Attachment, error) { - return r.db.Attachment.UpdateOneID(itemId). + itm, err := r.db.Attachment.UpdateOneID(itemId). SetType(typ). Save(ctx) + + if err != nil { + return nil, err + } + + return r.Get(ctx, itm.ID) } func (r *AttachmentRepo) Delete(ctx context.Context, id uuid.UUID) error { diff --git a/backend/internal/types/item_types.go b/backend/internal/types/item_types.go index 7d21b4e..d86696f 100644 --- a/backend/internal/types/item_types.go +++ b/backend/internal/types/item_types.go @@ -110,3 +110,9 @@ type ItemAttachment struct { type ItemAttachmentToken struct { Token string `json:"token"` } + +type ItemAttachmentUpdate struct { + ID uuid.UUID `json:"-"` + Type string `json:"type"` + Title string `json:"title"` +}