From 916feac7f5e212b095ec1d2e8953d571a0d1c19f Mon Sep 17 00:00:00 2001 From: Hayden <64056131+hay-kot@users.noreply.github.com> Date: Fri, 6 Oct 2023 21:46:12 -0500 Subject: [PATCH] add action to auto-set images --- .../app/api/handlers/v1/v1_ctrl_actions.go | 13 +++++ backend/app/api/routes.go | 1 + backend/app/api/static/docs/docs.go | 25 ++++++++++ backend/app/api/static/docs/swagger.json | 25 ++++++++++ backend/app/api/static/docs/swagger.yaml | 15 ++++++ backend/internal/data/repo/repo_items.go | 48 +++++++++++++++++++ docs/docs/api/openapi-2.0.json | 25 ++++++++++ frontend/lib/api/classes/actions.ts | 6 +++ frontend/pages/tools.vue | 25 ++++++++++ 9 files changed, 183 insertions(+) diff --git a/backend/app/api/handlers/v1/v1_ctrl_actions.go b/backend/app/api/handlers/v1/v1_ctrl_actions.go index 3b31c85..75f39a5 100644 --- a/backend/app/api/handlers/v1/v1_ctrl_actions.go +++ b/backend/app/api/handlers/v1/v1_ctrl_actions.go @@ -68,3 +68,16 @@ func (ctrl *V1Controller) HandleEnsureImportRefs() errchain.HandlerFunc { func (ctrl *V1Controller) HandleItemDateZeroOut() errchain.HandlerFunc { return actionHandlerFactory("zero out date time", ctrl.repo.Items.ZeroOutTimeFields) } + +// HandleSetPrimaryPhotos godoc +// +// @Summary Set Primary Photos +// @Description Sets the first photo of each item as the primary photo +// @Tags Actions +// @Produce json +// @Success 200 {object} ActionAmountResult +// @Router /v1/actions/set-primary-photos [Post] +// @Security Bearer +func (ctrl *V1Controller) HandleSetPrimaryPhotos() errchain.HandlerFunc { + return actionHandlerFactory("ensure asset IDs", ctrl.repo.Items.SetPrimaryPhotos) +} diff --git a/backend/app/api/routes.go b/backend/app/api/routes.go index 0958341..50bbfb1 100644 --- a/backend/app/api/routes.go +++ b/backend/app/api/routes.go @@ -92,6 +92,7 @@ func (a *app) mountRoutes(r *chi.Mux, chain *errchain.ErrChain, repos *repo.AllR r.Post(v1Base("/actions/ensure-asset-ids"), chain.ToHandlerFunc(v1Ctrl.HandleEnsureAssetID(), userMW...)) r.Post(v1Base("/actions/zero-item-time-fields"), chain.ToHandlerFunc(v1Ctrl.HandleItemDateZeroOut(), userMW...)) r.Post(v1Base("/actions/ensure-import-refs"), chain.ToHandlerFunc(v1Ctrl.HandleEnsureImportRefs(), userMW...)) + r.Post(v1Base("/actions/set-primary-photos"), chain.ToHandlerFunc(v1Ctrl.HandleSetPrimaryPhotos(), userMW...)) r.Get(v1Base("/locations"), chain.ToHandlerFunc(v1Ctrl.HandleLocationGetAll(), userMW...)) r.Post(v1Base("/locations"), chain.ToHandlerFunc(v1Ctrl.HandleLocationCreate(), userMW...)) diff --git a/backend/app/api/static/docs/docs.go b/backend/app/api/static/docs/docs.go index 7e4a26e..66a6dd2 100644 --- a/backend/app/api/static/docs/docs.go +++ b/backend/app/api/static/docs/docs.go @@ -68,6 +68,31 @@ const docTemplate = `{ } } }, + "/v1/actions/set-primary-photos": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "Sets the first photo of each item as the primary photo", + "produces": [ + "application/json" + ], + "tags": [ + "Actions" + ], + "summary": "Set Primary Photos", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/v1.ActionAmountResult" + } + } + } + } + }, "/v1/actions/zero-item-time-fields": { "post": { "security": [ diff --git a/backend/app/api/static/docs/swagger.json b/backend/app/api/static/docs/swagger.json index 6a05b88..837c889 100644 --- a/backend/app/api/static/docs/swagger.json +++ b/backend/app/api/static/docs/swagger.json @@ -60,6 +60,31 @@ } } }, + "/v1/actions/set-primary-photos": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "Sets the first photo of each item as the primary photo", + "produces": [ + "application/json" + ], + "tags": [ + "Actions" + ], + "summary": "Set Primary Photos", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/v1.ActionAmountResult" + } + } + } + } + }, "/v1/actions/zero-item-time-fields": { "post": { "security": [ diff --git a/backend/app/api/static/docs/swagger.yaml b/backend/app/api/static/docs/swagger.yaml index a034534..aebb5d2 100644 --- a/backend/app/api/static/docs/swagger.yaml +++ b/backend/app/api/static/docs/swagger.yaml @@ -742,6 +742,21 @@ paths: summary: Ensures Import Refs tags: - Actions + /v1/actions/set-primary-photos: + post: + description: Sets the first photo of each item as the primary photo + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/v1.ActionAmountResult' + security: + - Bearer: [] + summary: Set Primary Photos + tags: + - Actions /v1/actions/zero-item-time-fields: post: description: Resets all item date fields to the beginning of the day diff --git a/backend/internal/data/repo/repo_items.go b/backend/internal/data/repo/repo_items.go index e087762..7dac856 100644 --- a/backend/internal/data/repo/repo_items.go +++ b/backend/internal/data/repo/repo_items.go @@ -841,3 +841,51 @@ func (e *ItemsRepository) ZeroOutTimeFields(ctx context.Context, GID uuid.UUID) return updated, nil } + +func (e *ItemsRepository) SetPrimaryPhotos(ctx context.Context, GID uuid.UUID) (int, error) { + // All items where there is no primary photo + itemIDs, err := e.db.Item.Query(). + Where( + item.HasGroupWith(group.ID(GID)), + item.HasAttachmentsWith( + attachment.Not( + attachment.And( + attachment.Primary(true), + attachment.TypeEQ(attachment.TypePhoto), + ), + ), + ), + ). + IDs(ctx) + if err != nil { + return -1, err + } + + updated := 0 + for _, id := range itemIDs { + // Find the first photo attachment + a, err := e.db.Attachment.Query(). + Where( + attachment.HasItemWith(item.ID(id)), + attachment.TypeEQ(attachment.TypePhoto), + attachment.Primary(false), + ). + First(ctx) + if err != nil { + return updated, err + } + + // Set it as primary + _, err = e.db.Attachment.UpdateOne(a). + SetPrimary(true). + Save(ctx) + + if err != nil { + return updated, err + } + + updated++ + } + + return updated, nil +} diff --git a/docs/docs/api/openapi-2.0.json b/docs/docs/api/openapi-2.0.json index 6a05b88..837c889 100644 --- a/docs/docs/api/openapi-2.0.json +++ b/docs/docs/api/openapi-2.0.json @@ -60,6 +60,31 @@ } } }, + "/v1/actions/set-primary-photos": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "Sets the first photo of each item as the primary photo", + "produces": [ + "application/json" + ], + "tags": [ + "Actions" + ], + "summary": "Set Primary Photos", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/v1.ActionAmountResult" + } + } + } + } + }, "/v1/actions/zero-item-time-fields": { "post": { "security": [ diff --git a/frontend/lib/api/classes/actions.ts b/frontend/lib/api/classes/actions.ts index f30e332..e75ecfb 100644 --- a/frontend/lib/api/classes/actions.ts +++ b/frontend/lib/api/classes/actions.ts @@ -19,4 +19,10 @@ export class ActionsAPI extends BaseAPI { url: route("/actions/ensure-import-refs"), }); } + + setPrimaryPhotos() { + return this.http.post({ + url: route("/actions/set-primary-photos"), + }); + } } diff --git a/frontend/pages/tools.vue b/frontend/pages/tools.vue index b9c673a..653ca3b 100644 --- a/frontend/pages/tools.vue +++ b/frontend/pages/tools.vue @@ -82,6 +82,12 @@ See Github Issue #236 for more details. + + + In version v0.10.0 of Homebox, the primary image field was added to attachments of type photo. This action + will set the primary image field to the first image in the attachments array in the database, if it is not + already set. See GitHub PR #576 + @@ -173,6 +179,25 @@ notify.success(`${result.data.completed} assets have been updated.`); } + + async function setPrimaryPhotos() { + const { isCanceled } = await confirm.open( + "Are you sure you want to set primary photos? This can take a while and cannot be undone." + ); + + if (isCanceled) { + return; + } + + const result = await api.actions.setPrimaryPhotos(); + + if (result.error) { + notify.error("Failed to set primary photos."); + return; + } + + notify.success(`${result.data.completed} assets have been updated.`); + }