mirror of
https://github.com/hay-kot/homebox.git
synced 2025-07-16 23:50:30 +00:00
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:
parent
63a966c526
commit
318b8be192
34 changed files with 649 additions and 207 deletions
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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))
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue