From 318b8be192d62e819e4d3499fdb5dc3c40321754 Mon Sep 17 00:00:00 2001 From: Hayden <64056131+hay-kot@users.noreply.github.com> Date: Fri, 6 Oct 2023 21:51:08 -0500 Subject: [PATCH] feat: primary images (#576) * add support for primary images * fix locked loading state issue * 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 | 49 +++++-- backend/app/api/static/docs/swagger.json | 49 +++++-- backend/app/api/static/docs/swagger.yaml | 31 +++-- backend/go.sum | 7 + .../services/service_items_attachments.go | 2 +- backend/internal/data/ent/attachment.go | 13 ++ .../data/ent/attachment/attachment.go | 10 ++ backend/internal/data/ent/attachment/where.go | 15 +++ .../internal/data/ent/attachment_create.go | 25 ++++ .../internal/data/ent/attachment_update.go | 34 +++++ backend/internal/data/ent/group/group.go | 3 +- backend/internal/data/ent/migrate/schema.go | 7 +- backend/internal/data/ent/mutation.go | 56 +++++++- backend/internal/data/ent/runtime.go | 4 + .../internal/data/ent/schema/attachment.go | 2 + ...1006213457_add_primary_attachment_flag.sql | 12 ++ .../data/migrations/migrations/atlas.sum | 3 +- .../data/repo/repo_item_attachments.go | 41 +++++- .../data/repo/repo_item_attachments_test.go | 5 +- backend/internal/data/repo/repo_items.go | 95 ++++++++++++-- backend/internal/data/repo/repo_labels.go | 8 +- backend/internal/data/repo/repo_locations.go | 8 -- docs/docs/api/openapi-2.0.json | 49 +++++-- frontend/components/Item/Card.vue | 35 +++-- .../global/DetailsSection/DetailsSection.vue | 7 +- frontend/lib/api/classes/actions.ts | 6 + frontend/lib/api/types/data-contracts.ts | 6 +- frontend/pages/item/[id]/index/edit.vue | 12 +- frontend/pages/label/[id].vue | 100 ++++++++------ frontend/pages/location/[id].vue | 123 +++++++++--------- frontend/pages/tools.vue | 25 ++++ frontend/public/no-image.jpg | Bin 0 -> 91039 bytes 34 files changed, 649 insertions(+), 207 deletions(-) create mode 100644 backend/internal/data/migrations/migrations/20231006213457_add_primary_attachment_flag.sql create mode 100644 frontend/public/no-image.jpg 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 c5ec127..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": [ @@ -1879,6 +1904,9 @@ const docTemplate = `{ "id": { "type": "string" }, + "primary": { + "type": "boolean" + }, "type": { "type": "string" }, @@ -1890,6 +1918,9 @@ const docTemplate = `{ "repo.ItemAttachmentUpdate": { "type": "object", "properties": { + "primary": { + "type": "boolean" + }, "title": { "type": "string" }, @@ -1989,6 +2020,9 @@ const docTemplate = `{ "id": { "type": "string" }, + "imageId": { + "type": "string" + }, "insured": { "type": "boolean" }, @@ -2096,6 +2130,9 @@ const docTemplate = `{ "id": { "type": "string" }, + "imageId": { + "type": "string" + }, "insured": { "type": "boolean" }, @@ -2255,12 +2292,6 @@ const docTemplate = `{ "id": { "type": "string" }, - "items": { - "type": "array", - "items": { - "$ref": "#/definitions/repo.ItemSummary" - } - }, "name": { "type": "string" }, @@ -2322,12 +2353,6 @@ const docTemplate = `{ "id": { "type": "string" }, - "items": { - "type": "array", - "items": { - "$ref": "#/definitions/repo.ItemSummary" - } - }, "name": { "type": "string" }, diff --git a/backend/app/api/static/docs/swagger.json b/backend/app/api/static/docs/swagger.json index 5bb08df..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": [ @@ -1871,6 +1896,9 @@ "id": { "type": "string" }, + "primary": { + "type": "boolean" + }, "type": { "type": "string" }, @@ -1882,6 +1910,9 @@ "repo.ItemAttachmentUpdate": { "type": "object", "properties": { + "primary": { + "type": "boolean" + }, "title": { "type": "string" }, @@ -1981,6 +2012,9 @@ "id": { "type": "string" }, + "imageId": { + "type": "string" + }, "insured": { "type": "boolean" }, @@ -2088,6 +2122,9 @@ "id": { "type": "string" }, + "imageId": { + "type": "string" + }, "insured": { "type": "boolean" }, @@ -2247,12 +2284,6 @@ "id": { "type": "string" }, - "items": { - "type": "array", - "items": { - "$ref": "#/definitions/repo.ItemSummary" - } - }, "name": { "type": "string" }, @@ -2314,12 +2345,6 @@ "id": { "type": "string" }, - "items": { - "type": "array", - "items": { - "$ref": "#/definitions/repo.ItemSummary" - } - }, "name": { "type": "string" }, diff --git a/backend/app/api/static/docs/swagger.yaml b/backend/app/api/static/docs/swagger.yaml index e414655..aebb5d2 100644 --- a/backend/app/api/static/docs/swagger.yaml +++ b/backend/app/api/static/docs/swagger.yaml @@ -52,6 +52,8 @@ definitions: $ref: '#/definitions/repo.DocumentOut' id: type: string + primary: + type: boolean type: type: string updatedAt: @@ -59,6 +61,8 @@ definitions: type: object repo.ItemAttachmentUpdate: properties: + primary: + type: boolean title: type: string type: @@ -126,6 +130,8 @@ definitions: type: array id: type: string + imageId: + type: string insured: type: boolean labels: @@ -201,6 +207,8 @@ definitions: type: string id: type: string + imageId: + type: string insured: type: boolean labels: @@ -312,10 +320,6 @@ definitions: type: string id: type: string - items: - items: - $ref: '#/definitions/repo.ItemSummary' - type: array name: type: string updatedAt: @@ -356,10 +360,6 @@ definitions: type: string id: type: string - items: - items: - $ref: '#/definitions/repo.ItemSummary' - type: array name: type: string parent: @@ -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/go.sum b/backend/go.sum index b995802..d851b03 100644 --- a/backend/go.sum +++ b/backend/go.sum @@ -518,6 +518,8 @@ github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27k github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0= +github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-sqlite3 v1.14.17 h1:mCRHCLDUBXgpKAqIKsaAaAsrAlbkeomtRFKXh2L6YIM= github.com/mattn/go-sqlite3 v1.14.17/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= @@ -545,6 +547,8 @@ github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= github.com/olahol/melody v1.1.4 h1:RQHfKZkQmDxI0+SLZRNBCn4LiXdqxLKRGSkT8Dyoe/E= github.com/olahol/melody v1.1.4/go.mod h1:GgkTl6Y7yWj/HtfD48Q5vLKPVoZOH+Qqgfa7CvJgJM4= +github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= +github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc= @@ -618,7 +622,10 @@ github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasO github.com/spf13/afero v1.9.2/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y= github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU= github.com/spf13/cobra v1.6.1/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY= +github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I= +github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.14.0/go.mod h1:WT//axPky3FdvXHzGw33dNdXXXfFQqmEalje+egj8As= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= diff --git a/backend/internal/core/services/service_items_attachments.go b/backend/internal/core/services/service_items_attachments.go index 4a7b197..6783f2b 100644 --- a/backend/internal/core/services/service_items_attachments.go +++ b/backend/internal/core/services/service_items_attachments.go @@ -23,7 +23,7 @@ func (svc *ItemService) AttachmentPath(ctx context.Context, attachmentId uuid.UU func (svc *ItemService) AttachmentUpdate(ctx Context, itemId uuid.UUID, data *repo.ItemAttachmentUpdate) (repo.ItemOut, error) { // Update Attachment - attachment, err := svc.repo.Attachments.Update(ctx, data.ID, attachment.Type(data.Type)) + attachment, err := svc.repo.Attachments.Update(ctx, data.ID, data) if err != nil { return repo.ItemOut{}, err } diff --git a/backend/internal/data/ent/attachment.go b/backend/internal/data/ent/attachment.go index 385f823..bfb7de2 100644 --- a/backend/internal/data/ent/attachment.go +++ b/backend/internal/data/ent/attachment.go @@ -26,6 +26,8 @@ type Attachment struct { UpdatedAt time.Time `json:"updated_at,omitempty"` // Type holds the value of the "type" field. Type attachment.Type `json:"type,omitempty"` + // Primary holds the value of the "primary" field. + Primary bool `json:"primary,omitempty"` // Edges holds the relations/edges for other nodes in the graph. // The values are being populated by the AttachmentQuery when eager-loading is set. Edges AttachmentEdges `json:"edges"` @@ -76,6 +78,8 @@ func (*Attachment) scanValues(columns []string) ([]any, error) { values := make([]any, len(columns)) for i := range columns { switch columns[i] { + case attachment.FieldPrimary: + values[i] = new(sql.NullBool) case attachment.FieldType: values[i] = new(sql.NullString) case attachment.FieldCreatedAt, attachment.FieldUpdatedAt: @@ -125,6 +129,12 @@ func (a *Attachment) assignValues(columns []string, values []any) error { } else if value.Valid { a.Type = attachment.Type(value.String) } + case attachment.FieldPrimary: + if value, ok := values[i].(*sql.NullBool); !ok { + return fmt.Errorf("unexpected type %T for field primary", values[i]) + } else if value.Valid { + a.Primary = value.Bool + } case attachment.ForeignKeys[0]: if value, ok := values[i].(*sql.NullScanner); !ok { return fmt.Errorf("unexpected type %T for field document_attachments", values[i]) @@ -193,6 +203,9 @@ func (a *Attachment) String() string { builder.WriteString(", ") builder.WriteString("type=") builder.WriteString(fmt.Sprintf("%v", a.Type)) + builder.WriteString(", ") + builder.WriteString("primary=") + builder.WriteString(fmt.Sprintf("%v", a.Primary)) builder.WriteByte(')') return builder.String() } diff --git a/backend/internal/data/ent/attachment/attachment.go b/backend/internal/data/ent/attachment/attachment.go index 3ec20fc..4bbac72 100644 --- a/backend/internal/data/ent/attachment/attachment.go +++ b/backend/internal/data/ent/attachment/attachment.go @@ -22,6 +22,8 @@ const ( FieldUpdatedAt = "updated_at" // FieldType holds the string denoting the type field in the database. FieldType = "type" + // FieldPrimary holds the string denoting the primary field in the database. + FieldPrimary = "primary" // EdgeItem holds the string denoting the item edge name in mutations. EdgeItem = "item" // EdgeDocument holds the string denoting the document edge name in mutations. @@ -50,6 +52,7 @@ var Columns = []string{ FieldCreatedAt, FieldUpdatedAt, FieldType, + FieldPrimary, } // ForeignKeys holds the SQL foreign-keys that are owned by the "attachments" @@ -81,6 +84,8 @@ var ( DefaultUpdatedAt func() time.Time // UpdateDefaultUpdatedAt holds the default value on update for the "updated_at" field. UpdateDefaultUpdatedAt func() time.Time + // DefaultPrimary holds the default value on creation for the "primary" field. + DefaultPrimary bool // DefaultID holds the default value on creation for the "id" field. DefaultID func() uuid.UUID ) @@ -137,6 +142,11 @@ func ByType(opts ...sql.OrderTermOption) OrderOption { return sql.OrderByField(FieldType, opts...).ToFunc() } +// ByPrimary orders the results by the primary field. +func ByPrimary(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldPrimary, opts...).ToFunc() +} + // ByItemField orders the results by item field. func ByItemField(field string, opts ...sql.OrderTermOption) OrderOption { return func(s *sql.Selector) { diff --git a/backend/internal/data/ent/attachment/where.go b/backend/internal/data/ent/attachment/where.go index 32086bd..b911718 100644 --- a/backend/internal/data/ent/attachment/where.go +++ b/backend/internal/data/ent/attachment/where.go @@ -66,6 +66,11 @@ func UpdatedAt(v time.Time) predicate.Attachment { return predicate.Attachment(sql.FieldEQ(FieldUpdatedAt, v)) } +// Primary applies equality check predicate on the "primary" field. It's identical to PrimaryEQ. +func Primary(v bool) predicate.Attachment { + return predicate.Attachment(sql.FieldEQ(FieldPrimary, v)) +} + // CreatedAtEQ applies the EQ predicate on the "created_at" field. func CreatedAtEQ(v time.Time) predicate.Attachment { return predicate.Attachment(sql.FieldEQ(FieldCreatedAt, v)) @@ -166,6 +171,16 @@ func TypeNotIn(vs ...Type) predicate.Attachment { return predicate.Attachment(sql.FieldNotIn(FieldType, vs...)) } +// PrimaryEQ applies the EQ predicate on the "primary" field. +func PrimaryEQ(v bool) predicate.Attachment { + return predicate.Attachment(sql.FieldEQ(FieldPrimary, v)) +} + +// PrimaryNEQ applies the NEQ predicate on the "primary" field. +func PrimaryNEQ(v bool) predicate.Attachment { + return predicate.Attachment(sql.FieldNEQ(FieldPrimary, v)) +} + // HasItem applies the HasEdge predicate on the "item" edge. func HasItem() predicate.Attachment { return predicate.Attachment(func(s *sql.Selector) { diff --git a/backend/internal/data/ent/attachment_create.go b/backend/internal/data/ent/attachment_create.go index 6edfdf4..fd89284 100644 --- a/backend/internal/data/ent/attachment_create.go +++ b/backend/internal/data/ent/attachment_create.go @@ -65,6 +65,20 @@ func (ac *AttachmentCreate) SetNillableType(a *attachment.Type) *AttachmentCreat return ac } +// SetPrimary sets the "primary" field. +func (ac *AttachmentCreate) SetPrimary(b bool) *AttachmentCreate { + ac.mutation.SetPrimary(b) + return ac +} + +// SetNillablePrimary sets the "primary" field if the given value is not nil. +func (ac *AttachmentCreate) SetNillablePrimary(b *bool) *AttachmentCreate { + if b != nil { + ac.SetPrimary(*b) + } + return ac +} + // SetID sets the "id" field. func (ac *AttachmentCreate) SetID(u uuid.UUID) *AttachmentCreate { ac.mutation.SetID(u) @@ -148,6 +162,10 @@ func (ac *AttachmentCreate) defaults() { v := attachment.DefaultType ac.mutation.SetType(v) } + if _, ok := ac.mutation.Primary(); !ok { + v := attachment.DefaultPrimary + ac.mutation.SetPrimary(v) + } if _, ok := ac.mutation.ID(); !ok { v := attachment.DefaultID() ac.mutation.SetID(v) @@ -170,6 +188,9 @@ func (ac *AttachmentCreate) check() error { return &ValidationError{Name: "type", err: fmt.Errorf(`ent: validator failed for field "Attachment.type": %w`, err)} } } + if _, ok := ac.mutation.Primary(); !ok { + return &ValidationError{Name: "primary", err: errors.New(`ent: missing required field "Attachment.primary"`)} + } if _, ok := ac.mutation.ItemID(); !ok { return &ValidationError{Name: "item", err: errors.New(`ent: missing required edge "Attachment.item"`)} } @@ -223,6 +244,10 @@ func (ac *AttachmentCreate) createSpec() (*Attachment, *sqlgraph.CreateSpec) { _spec.SetField(attachment.FieldType, field.TypeEnum, value) _node.Type = value } + if value, ok := ac.mutation.Primary(); ok { + _spec.SetField(attachment.FieldPrimary, field.TypeBool, value) + _node.Primary = value + } if nodes := ac.mutation.ItemIDs(); len(nodes) > 0 { edge := &sqlgraph.EdgeSpec{ Rel: sqlgraph.M2O, diff --git a/backend/internal/data/ent/attachment_update.go b/backend/internal/data/ent/attachment_update.go index 6e8f82c..bdf10a5 100644 --- a/backend/internal/data/ent/attachment_update.go +++ b/backend/internal/data/ent/attachment_update.go @@ -51,6 +51,20 @@ func (au *AttachmentUpdate) SetNillableType(a *attachment.Type) *AttachmentUpdat return au } +// SetPrimary sets the "primary" field. +func (au *AttachmentUpdate) SetPrimary(b bool) *AttachmentUpdate { + au.mutation.SetPrimary(b) + return au +} + +// SetNillablePrimary sets the "primary" field if the given value is not nil. +func (au *AttachmentUpdate) SetNillablePrimary(b *bool) *AttachmentUpdate { + if b != nil { + au.SetPrimary(*b) + } + return au +} + // SetItemID sets the "item" edge to the Item entity by ID. func (au *AttachmentUpdate) SetItemID(id uuid.UUID) *AttachmentUpdate { au.mutation.SetItemID(id) @@ -160,6 +174,9 @@ func (au *AttachmentUpdate) sqlSave(ctx context.Context) (n int, err error) { if value, ok := au.mutation.GetType(); ok { _spec.SetField(attachment.FieldType, field.TypeEnum, value) } + if value, ok := au.mutation.Primary(); ok { + _spec.SetField(attachment.FieldPrimary, field.TypeBool, value) + } if au.mutation.ItemCleared() { edge := &sqlgraph.EdgeSpec{ Rel: sqlgraph.M2O, @@ -258,6 +275,20 @@ func (auo *AttachmentUpdateOne) SetNillableType(a *attachment.Type) *AttachmentU return auo } +// SetPrimary sets the "primary" field. +func (auo *AttachmentUpdateOne) SetPrimary(b bool) *AttachmentUpdateOne { + auo.mutation.SetPrimary(b) + return auo +} + +// SetNillablePrimary sets the "primary" field if the given value is not nil. +func (auo *AttachmentUpdateOne) SetNillablePrimary(b *bool) *AttachmentUpdateOne { + if b != nil { + auo.SetPrimary(*b) + } + return auo +} + // SetItemID sets the "item" edge to the Item entity by ID. func (auo *AttachmentUpdateOne) SetItemID(id uuid.UUID) *AttachmentUpdateOne { auo.mutation.SetItemID(id) @@ -397,6 +428,9 @@ func (auo *AttachmentUpdateOne) sqlSave(ctx context.Context) (_node *Attachment, if value, ok := auo.mutation.GetType(); ok { _spec.SetField(attachment.FieldType, field.TypeEnum, value) } + if value, ok := auo.mutation.Primary(); ok { + _spec.SetField(attachment.FieldPrimary, field.TypeBool, value) + } if auo.mutation.ItemCleared() { edge := &sqlgraph.EdgeSpec{ Rel: sqlgraph.M2O, diff --git a/backend/internal/data/ent/group/group.go b/backend/internal/data/ent/group/group.go index 5e92129..47bceac 100644 --- a/backend/internal/data/ent/group/group.go +++ b/backend/internal/data/ent/group/group.go @@ -137,7 +137,6 @@ const ( CurrencyBrl Currency = "brl" CurrencyCad Currency = "cad" CurrencyChf Currency = "chf" - CurrencyCny Currency = "cny" CurrencyCzk Currency = "czk" CurrencyDkk Currency = "dkk" CurrencyEur Currency = "eur" @@ -172,7 +171,7 @@ func (c Currency) String() string { // CurrencyValidator is a validator for the "currency" field enum values. It is called by the builders before save. func CurrencyValidator(c Currency) error { switch c { - case CurrencyAed, CurrencyAud, CurrencyBgn, CurrencyBrl, CurrencyCad, CurrencyChf, CurrencyCny, CurrencyCzk, CurrencyDkk, CurrencyEur, CurrencyGbp, CurrencyHkd, CurrencyIdr, CurrencyInr, CurrencyJpy, CurrencyKrw, CurrencyMxn, CurrencyNok, CurrencyNzd, CurrencyPln, CurrencyRmb, CurrencyRon, CurrencyRub, CurrencySar, CurrencySek, CurrencySgd, CurrencyThb, CurrencyTry, CurrencyUsd, CurrencyXag, CurrencyXau, CurrencyZar: + case CurrencyAed, CurrencyAud, CurrencyBgn, CurrencyBrl, CurrencyCad, CurrencyChf, CurrencyCzk, CurrencyDkk, CurrencyEur, CurrencyGbp, CurrencyHkd, CurrencyIdr, CurrencyInr, CurrencyJpy, CurrencyKrw, CurrencyMxn, CurrencyNok, CurrencyNzd, CurrencyPln, CurrencyRmb, CurrencyRon, CurrencyRub, CurrencySar, CurrencySek, CurrencySgd, CurrencyThb, CurrencyTry, CurrencyUsd, CurrencyXag, CurrencyXau, CurrencyZar: return nil default: return fmt.Errorf("group: invalid enum value for currency field: %q", c) diff --git a/backend/internal/data/ent/migrate/schema.go b/backend/internal/data/ent/migrate/schema.go index 7d77d7d..265e2ea 100644 --- a/backend/internal/data/ent/migrate/schema.go +++ b/backend/internal/data/ent/migrate/schema.go @@ -14,6 +14,7 @@ var ( {Name: "created_at", Type: field.TypeTime}, {Name: "updated_at", Type: field.TypeTime}, {Name: "type", Type: field.TypeEnum, Enums: []string{"photo", "manual", "warranty", "attachment", "receipt"}, Default: "attachment"}, + {Name: "primary", Type: field.TypeBool, Default: false}, {Name: "document_attachments", Type: field.TypeUUID}, {Name: "item_attachments", Type: field.TypeUUID}, } @@ -25,13 +26,13 @@ var ( ForeignKeys: []*schema.ForeignKey{ { Symbol: "attachments_documents_attachments", - Columns: []*schema.Column{AttachmentsColumns[4]}, + Columns: []*schema.Column{AttachmentsColumns[5]}, RefColumns: []*schema.Column{DocumentsColumns[0]}, OnDelete: schema.Cascade, }, { Symbol: "attachments_items_attachments", - Columns: []*schema.Column{AttachmentsColumns[5]}, + Columns: []*schema.Column{AttachmentsColumns[6]}, RefColumns: []*schema.Column{ItemsColumns[0]}, OnDelete: schema.Cascade, }, @@ -116,7 +117,7 @@ var ( {Name: "created_at", Type: field.TypeTime}, {Name: "updated_at", Type: field.TypeTime}, {Name: "name", Type: field.TypeString, Size: 255}, - {Name: "currency", Type: field.TypeEnum, Enums: []string{"aed", "aud", "bgn", "brl", "cad", "chf", "cny", "czk", "dkk", "eur", "gbp", "hkd", "idr", "inr", "jpy", "krw", "mxn", "nok", "nzd", "pln", "rmb", "ron", "rub", "sar", "sek", "sgd", "thb", "try", "usd", "zar"}, Default: "usd"}, + {Name: "currency", Type: field.TypeEnum, Enums: []string{"aed", "aud", "bgn", "brl", "cad", "chf", "czk", "dkk", "eur", "gbp", "hkd", "idr", "inr", "jpy", "krw", "mxn", "nok", "nzd", "pln", "rmb", "ron", "rub", "sar", "sek", "sgd", "thb", "try", "usd", "xag", "xau", "zar"}, Default: "usd"}, } // GroupsTable holds the schema information for the "groups" table. GroupsTable = &schema.Table{ diff --git a/backend/internal/data/ent/mutation.go b/backend/internal/data/ent/mutation.go index bc38aeb..496437d 100644 --- a/backend/internal/data/ent/mutation.go +++ b/backend/internal/data/ent/mutation.go @@ -61,6 +61,7 @@ type AttachmentMutation struct { created_at *time.Time updated_at *time.Time _type *attachment.Type + primary *bool clearedFields map[string]struct{} item *uuid.UUID cleareditem bool @@ -283,6 +284,42 @@ func (m *AttachmentMutation) ResetType() { m._type = nil } +// SetPrimary sets the "primary" field. +func (m *AttachmentMutation) SetPrimary(b bool) { + m.primary = &b +} + +// Primary returns the value of the "primary" field in the mutation. +func (m *AttachmentMutation) Primary() (r bool, exists bool) { + v := m.primary + if v == nil { + return + } + return *v, true +} + +// OldPrimary returns the old "primary" field's value of the Attachment entity. +// If the Attachment object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *AttachmentMutation) OldPrimary(ctx context.Context) (v bool, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldPrimary is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldPrimary requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldPrimary: %w", err) + } + return oldValue.Primary, nil +} + +// ResetPrimary resets all changes to the "primary" field. +func (m *AttachmentMutation) ResetPrimary() { + m.primary = nil +} + // SetItemID sets the "item" edge to the Item entity by id. func (m *AttachmentMutation) SetItemID(id uuid.UUID) { m.item = &id @@ -395,7 +432,7 @@ func (m *AttachmentMutation) Type() string { // order to get all numeric fields that were incremented/decremented, call // AddedFields(). func (m *AttachmentMutation) Fields() []string { - fields := make([]string, 0, 3) + fields := make([]string, 0, 4) if m.created_at != nil { fields = append(fields, attachment.FieldCreatedAt) } @@ -405,6 +442,9 @@ func (m *AttachmentMutation) Fields() []string { if m._type != nil { fields = append(fields, attachment.FieldType) } + if m.primary != nil { + fields = append(fields, attachment.FieldPrimary) + } return fields } @@ -419,6 +459,8 @@ func (m *AttachmentMutation) Field(name string) (ent.Value, bool) { return m.UpdatedAt() case attachment.FieldType: return m.GetType() + case attachment.FieldPrimary: + return m.Primary() } return nil, false } @@ -434,6 +476,8 @@ func (m *AttachmentMutation) OldField(ctx context.Context, name string) (ent.Val return m.OldUpdatedAt(ctx) case attachment.FieldType: return m.OldType(ctx) + case attachment.FieldPrimary: + return m.OldPrimary(ctx) } return nil, fmt.Errorf("unknown Attachment field %s", name) } @@ -464,6 +508,13 @@ func (m *AttachmentMutation) SetField(name string, value ent.Value) error { } m.SetType(v) return nil + case attachment.FieldPrimary: + v, ok := value.(bool) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetPrimary(v) + return nil } return fmt.Errorf("unknown Attachment field %s", name) } @@ -522,6 +573,9 @@ func (m *AttachmentMutation) ResetField(name string) error { case attachment.FieldType: m.ResetType() return nil + case attachment.FieldPrimary: + m.ResetPrimary() + return nil } return fmt.Errorf("unknown Attachment field %s", name) } diff --git a/backend/internal/data/ent/runtime.go b/backend/internal/data/ent/runtime.go index 9edc90b..d32ae7d 100644 --- a/backend/internal/data/ent/runtime.go +++ b/backend/internal/data/ent/runtime.go @@ -40,6 +40,10 @@ func init() { attachment.DefaultUpdatedAt = attachmentDescUpdatedAt.Default.(func() time.Time) // attachment.UpdateDefaultUpdatedAt holds the default value on update for the updated_at field. attachment.UpdateDefaultUpdatedAt = attachmentDescUpdatedAt.UpdateDefault.(func() time.Time) + // attachmentDescPrimary is the schema descriptor for primary field. + attachmentDescPrimary := attachmentFields[1].Descriptor() + // attachment.DefaultPrimary holds the default value on creation for the primary field. + attachment.DefaultPrimary = attachmentDescPrimary.Default.(bool) // attachmentDescID is the schema descriptor for id field. attachmentDescID := attachmentMixinFields0[0].Descriptor() // attachment.DefaultID holds the default value on creation for the id field. diff --git a/backend/internal/data/ent/schema/attachment.go b/backend/internal/data/ent/schema/attachment.go index 7f4673a..589b684 100644 --- a/backend/internal/data/ent/schema/attachment.go +++ b/backend/internal/data/ent/schema/attachment.go @@ -24,6 +24,8 @@ func (Attachment) Fields() []ent.Field { field.Enum("type"). Values("photo", "manual", "warranty", "attachment", "receipt"). Default("attachment"), + field.Bool("primary"). + Default(false), } } diff --git a/backend/internal/data/migrations/migrations/20231006213457_add_primary_attachment_flag.sql b/backend/internal/data/migrations/migrations/20231006213457_add_primary_attachment_flag.sql new file mode 100644 index 0000000..b7506c1 --- /dev/null +++ b/backend/internal/data/migrations/migrations/20231006213457_add_primary_attachment_flag.sql @@ -0,0 +1,12 @@ +-- Disable the enforcement of foreign-keys constraints +PRAGMA foreign_keys = off; +-- Create "new_attachments" table +CREATE TABLE `new_attachments` (`id` uuid NOT NULL, `created_at` datetime NOT NULL, `updated_at` datetime NOT NULL, `type` text NOT NULL DEFAULT 'attachment', `primary` bool NOT NULL DEFAULT false, `document_attachments` uuid NOT NULL, `item_attachments` uuid NOT NULL, PRIMARY KEY (`id`), CONSTRAINT `attachments_documents_attachments` FOREIGN KEY (`document_attachments`) REFERENCES `documents` (`id`) ON DELETE CASCADE, CONSTRAINT `attachments_items_attachments` FOREIGN KEY (`item_attachments`) REFERENCES `items` (`id`) ON DELETE CASCADE); +-- Copy rows from old table "attachments" to new temporary table "new_attachments" +INSERT INTO `new_attachments` (`id`, `created_at`, `updated_at`, `type`, `document_attachments`, `item_attachments`) SELECT `id`, `created_at`, `updated_at`, `type`, `document_attachments`, `item_attachments` FROM `attachments`; +-- Drop "attachments" table after copying rows +DROP TABLE `attachments`; +-- Rename temporary table "new_attachments" to "attachments" +ALTER TABLE `new_attachments` RENAME TO `attachments`; +-- Enable back the enforcement of foreign-keys constraints +PRAGMA foreign_keys = on; diff --git a/backend/internal/data/migrations/migrations/atlas.sum b/backend/internal/data/migrations/migrations/atlas.sum index 84c48d2..e8d99a6 100644 --- a/backend/internal/data/migrations/migrations/atlas.sum +++ b/backend/internal/data/migrations/migrations/atlas.sum @@ -1,4 +1,4 @@ -h1:VjVLPBHzJ8N1Hiw+Aeitb0alnVn9UFilnajCzc+pie8= +h1:sjJCTAqc9FG8BKBIzh5ZynYD/Ilz6vnLqM4XX83WQ4M= 20220929052825_init.sql h1:ZlCqm1wzjDmofeAcSX3jE4h4VcdTNGpRg2eabztDy9Q= 20221001210956_group_invitations.sql h1:YQKJFtE39wFOcRNbZQ/d+ZlHwrcfcsZlcv/pLEYdpjw= 20221009173029_add_user_roles.sql h1:vWmzAfgEWQeGk0Vn70zfVPCcfEZth3E0JcvyKTjpYyU= @@ -12,3 +12,4 @@ h1:VjVLPBHzJ8N1Hiw+Aeitb0alnVn9UFilnajCzc+pie8= 20230227024134_add_scheduled_date.sql h1:8qO5OBZ0AzsfYEQOAQQrYIjyhSwM+v1A+/ylLSoiyoc= 20230305065819_add_notifier_types.sql h1:r5xrgCKYQ2o9byBqYeAX1zdp94BLdaxf4vq9OmGHNl0= 20230305071524_add_group_id_to_notifiers.sql h1:xDShqbyClcFhvJbwclOHdczgXbdffkxXNWjV61hL/t4= +20231006213457_add_primary_attachment_flag.sql h1:J4tMSJQFa7vaj0jpnh8YKTssdyIjRyq6RXDXZIzDDu4= diff --git a/backend/internal/data/repo/repo_item_attachments.go b/backend/internal/data/repo/repo_item_attachments.go index a034369..a015e71 100644 --- a/backend/internal/data/repo/repo_item_attachments.go +++ b/backend/internal/data/repo/repo_item_attachments.go @@ -7,6 +7,7 @@ import ( "github.com/google/uuid" "github.com/hay-kot/homebox/backend/internal/data/ent" "github.com/hay-kot/homebox/backend/internal/data/ent/attachment" + "github.com/hay-kot/homebox/backend/internal/data/ent/item" ) // AttachmentRepo is a repository for Attachments table that links Items to Documents @@ -24,12 +25,14 @@ type ( UpdatedAt time.Time `json:"updatedAt"` Type string `json:"type"` Document DocumentOut `json:"document"` + Primary bool `json:"primary"` } ItemAttachmentUpdate struct { - ID uuid.UUID `json:"-"` - Type string `json:"type"` - Title string `json:"title"` + ID uuid.UUID `json:"-"` + Type string `json:"type"` + Title string `json:"title"` + Primary bool `json:"primary"` } ) @@ -39,6 +42,7 @@ func ToItemAttachment(attachment *ent.Attachment) ItemAttachment { CreatedAt: attachment.CreatedAt, UpdatedAt: attachment.UpdatedAt, Type: attachment.Type.String(), + Primary: attachment.Primary, Document: DocumentOut{ ID: attachment.Edges.Document.ID, Title: attachment.Edges.Document.Title, @@ -64,10 +68,33 @@ func (r *AttachmentRepo) Get(ctx context.Context, id uuid.UUID) (*ent.Attachment Only(ctx) } -func (r *AttachmentRepo) Update(ctx context.Context, itemId uuid.UUID, typ attachment.Type) (*ent.Attachment, error) { - itm, err := r.db.Attachment.UpdateOneID(itemId). - SetType(typ). - Save(ctx) +func (r *AttachmentRepo) Update(ctx context.Context, itemId uuid.UUID, data *ItemAttachmentUpdate) (*ent.Attachment, error) { + // TODO: execute within Tx + typ := attachment.Type(data.Type) + + bldr := r.db.Attachment.UpdateOneID(itemId). + SetType(typ) + + // Primary only applies to photos + if typ == attachment.TypePhoto { + bldr = bldr.SetPrimary(data.Primary) + } else { + bldr = bldr.SetPrimary(false) + } + + itm, err := bldr.Save(ctx) + if err != nil { + return nil, err + } + + // Ensure all other attachments are not primary + err = r.db.Attachment.Update(). + Where( + attachment.HasItemWith(item.ID(itemId)), + attachment.IDNEQ(itm.ID), + ). + SetPrimary(false). + Exec(ctx) if err != nil { return nil, err } diff --git a/backend/internal/data/repo/repo_item_attachments_test.go b/backend/internal/data/repo/repo_item_attachments_test.go index 4c9d77d..1c20e92 100644 --- a/backend/internal/data/repo/repo_item_attachments_test.go +++ b/backend/internal/data/repo/repo_item_attachments_test.go @@ -110,7 +110,10 @@ func TestAttachmentRepo_Update(t *testing.T) { for _, typ := range []attachment.Type{"photo", "manual", "warranty", "attachment"} { t.Run(string(typ), func(t *testing.T) { - _, err := tRepos.Attachments.Update(context.Background(), entity.ID, typ) + _, err := tRepos.Attachments.Update(context.Background(), entity.ID, &ItemAttachmentUpdate{ + Type: string(typ), + }) + assert.NoError(t, err) updated, err := tRepos.Attachments.Get(context.Background(), entity.ID) diff --git a/backend/internal/data/repo/repo_items.go b/backend/internal/data/repo/repo_items.go index 2431736..7dac856 100644 --- a/backend/internal/data/repo/repo_items.go +++ b/backend/internal/data/repo/repo_items.go @@ -8,6 +8,7 @@ import ( "github.com/google/uuid" "github.com/hay-kot/homebox/backend/internal/core/services/reporting/eventbus" "github.com/hay-kot/homebox/backend/internal/data/ent" + "github.com/hay-kot/homebox/backend/internal/data/ent/attachment" "github.com/hay-kot/homebox/backend/internal/data/ent/group" "github.com/hay-kot/homebox/backend/internal/data/ent/item" "github.com/hay-kot/homebox/backend/internal/data/ent/itemfield" @@ -125,6 +126,8 @@ type ( // Edges Location *LocationSummary `json:"location,omitempty" extensions:"x-nullable,x-omitempty"` Labels []LabelSummary `json:"labels"` + + ImageID *uuid.UUID `json:"imageId,omitempty"` } ItemOut struct { @@ -174,6 +177,16 @@ func mapItemSummary(item *ent.Item) ItemSummary { labels = mapEach(item.Edges.Label, mapLabelSummary) } + var imageID *uuid.UUID + if item.Edges.Attachments != nil { + for _, a := range item.Edges.Attachments { + if a.Primary && a.Edges.Document != nil { + imageID = &a.ID + break + } + } + } + return ItemSummary{ ID: item.ID, Name: item.Name, @@ -191,6 +204,7 @@ func mapItemSummary(item *ent.Item) ItemSummary { // Warranty Insured: item.Insured, + ImageID: imageID, } } @@ -407,7 +421,13 @@ func (e *ItemsRepository) QueryByGroup(ctx context.Context, gid uuid.UUID, q Ite qb = qb. WithLabel(). - WithLocation() + WithLocation(). + WithAttachments(func(aq *ent.AttachmentQuery) { + aq.Where( + attachment.Primary(true), + ). + WithDocument() + }) if q.Page != -1 || q.PageSize != -1 { qb = qb. @@ -533,13 +553,13 @@ func (e *ItemsRepository) Create(ctx context.Context, gid uuid.UUID, data ItemCr } func (e *ItemsRepository) Delete(ctx context.Context, id uuid.UUID) error { - err := e.db.Item.DeleteOneID(id).Exec(ctx) - if err != nil { - return err - } + err := e.db.Item.DeleteOneID(id).Exec(ctx) + if err != nil { + return err + } - e.publishMutationEvent(id) - return nil + e.publishMutationEvent(id) + return nil } func (e *ItemsRepository) DeleteByGroup(ctx context.Context, gid, id uuid.UUID) error { @@ -549,12 +569,11 @@ func (e *ItemsRepository) DeleteByGroup(ctx context.Context, gid, id uuid.UUID) item.ID(id), item.HasGroupWith(group.ID(gid)), ).Exec(ctx) - - if err != nil { - return err - } + if err != nil { + return err + } - e.publishMutationEvent(gid) + e.publishMutationEvent(gid) return err } @@ -670,7 +689,7 @@ func (e *ItemsRepository) UpdateByGroup(ctx context.Context, GID uuid.UUID, data } } - e.publishMutationEvent(GID) + e.publishMutationEvent(GID) return e.GetOne(ctx, data.ID) } @@ -709,7 +728,7 @@ func (e *ItemsRepository) Patch(ctx context.Context, GID, ID uuid.UUID, data Ite q.SetQuantity(*data.Quantity) } - e.publishMutationEvent(GID) + e.publishMutationEvent(GID) return q.Exec(ctx) } @@ -822,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/backend/internal/data/repo/repo_labels.go b/backend/internal/data/repo/repo_labels.go index 7814577..4de1a9e 100644 --- a/backend/internal/data/repo/repo_labels.go +++ b/backend/internal/data/repo/repo_labels.go @@ -8,7 +8,6 @@ import ( "github.com/hay-kot/homebox/backend/internal/core/services/reporting/eventbus" "github.com/hay-kot/homebox/backend/internal/data/ent" "github.com/hay-kot/homebox/backend/internal/data/ent/group" - "github.com/hay-kot/homebox/backend/internal/data/ent/item" "github.com/hay-kot/homebox/backend/internal/data/ent/label" "github.com/hay-kot/homebox/backend/internal/data/ent/predicate" ) @@ -42,7 +41,6 @@ type ( LabelOut struct { LabelSummary - Items []ItemSummary `json:"items"` } ) @@ -64,7 +62,6 @@ var ( func mapLabelOut(label *ent.Label) LabelOut { return LabelOut{ LabelSummary: mapLabelSummary(label), - Items: mapEach(label.Edges.Items, mapItemSummary), } } @@ -78,9 +75,6 @@ func (r *LabelRepository) getOne(ctx context.Context, where ...predicate.Label) return mapLabelOutErr(r.db.Label.Query(). Where(where...). WithGroup(). - WithItems(func(iq *ent.ItemQuery) { - iq.Where(item.Archived(false)) - }). Only(ctx), ) } @@ -142,7 +136,7 @@ func (r *LabelRepository) UpdateByGroup(ctx context.Context, GID uuid.UUID, data } // delete removes the label from the database. This should only be used when -// the label's ownership is already confirmed/validated. +// the label's ownership is already confirmed/validated. func (r *LabelRepository) delete(ctx context.Context, id uuid.UUID) error { return r.db.Label.DeleteOneID(id).Exec(ctx) } diff --git a/backend/internal/data/repo/repo_locations.go b/backend/internal/data/repo/repo_locations.go index 8c0ebfc..261ff50 100644 --- a/backend/internal/data/repo/repo_locations.go +++ b/backend/internal/data/repo/repo_locations.go @@ -9,7 +9,6 @@ import ( "github.com/hay-kot/homebox/backend/internal/core/services/reporting/eventbus" "github.com/hay-kot/homebox/backend/internal/data/ent" "github.com/hay-kot/homebox/backend/internal/data/ent/group" - "github.com/hay-kot/homebox/backend/internal/data/ent/item" "github.com/hay-kot/homebox/backend/internal/data/ent/location" "github.com/hay-kot/homebox/backend/internal/data/ent/predicate" ) @@ -49,7 +48,6 @@ type ( LocationOut struct { Parent *LocationSummary `json:"parent,omitempty"` LocationSummary - Items []ItemSummary `json:"items"` Children []LocationSummary `json:"children"` } ) @@ -88,7 +86,6 @@ func mapLocationOut(location *ent.Location) LocationOut { CreatedAt: location.CreatedAt, UpdatedAt: location.UpdatedAt, }, - Items: mapEach(location.Edges.Items, mapItemSummary), } } @@ -164,11 +161,6 @@ func (r *LocationRepository) getOne(ctx context.Context, where ...predicate.Loca return mapLocationOutErr(r.db.Location.Query(). Where(where...). WithGroup(). - WithItems(func(iq *ent.ItemQuery) { - iq.Where(item.Archived(false)). - Order(ent.Asc(item.FieldName)). - WithLabel() - }). WithParent(). WithChildren(). Only(ctx)) diff --git a/docs/docs/api/openapi-2.0.json b/docs/docs/api/openapi-2.0.json index 5bb08df..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": [ @@ -1871,6 +1896,9 @@ "id": { "type": "string" }, + "primary": { + "type": "boolean" + }, "type": { "type": "string" }, @@ -1882,6 +1910,9 @@ "repo.ItemAttachmentUpdate": { "type": "object", "properties": { + "primary": { + "type": "boolean" + }, "title": { "type": "string" }, @@ -1981,6 +2012,9 @@ "id": { "type": "string" }, + "imageId": { + "type": "string" + }, "insured": { "type": "boolean" }, @@ -2088,6 +2122,9 @@ "id": { "type": "string" }, + "imageId": { + "type": "string" + }, "insured": { "type": "boolean" }, @@ -2247,12 +2284,6 @@ "id": { "type": "string" }, - "items": { - "type": "array", - "items": { - "$ref": "#/definitions/repo.ItemSummary" - } - }, "name": { "type": "string" }, @@ -2314,12 +2345,6 @@ "id": { "type": "string" }, - "items": { - "type": "array", - "items": { - "$ref": "#/definitions/repo.ItemSummary" - } - }, "name": { "type": "string" }, diff --git a/frontend/components/Item/Card.vue b/frontend/components/Item/Card.vue index c0b5b88..7d6de95 100644 --- a/frontend/components/Item/Card.vue +++ b/frontend/components/Item/Card.vue @@ -1,21 +1,21 @@