feat: primary images (#576)

* add support for primary images

* fix locked loading state issue

* add action to auto-set images
This commit is contained in:
Hayden 2023-10-06 21:51:08 -05:00 committed by GitHub
parent 63a966c526
commit 318b8be192
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
34 changed files with 649 additions and 207 deletions

View file

@ -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
}

View file

@ -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)

View file

@ -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
}

View file

@ -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)
}

View file

@ -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))