From 50f0f7dd1572684f9a2000a59e44e81e500e94cc Mon Sep 17 00:00:00 2001 From: Hayden <64056131+hay-kot@users.noreply.github.com> Date: Sat, 12 Nov 2022 16:19:27 -0900 Subject: [PATCH 01/12] add schema --- backend/internal/data/ent/schema/item.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/backend/internal/data/ent/schema/item.go b/backend/internal/data/ent/schema/item.go index f7799f4..3945ebb 100644 --- a/backend/internal/data/ent/schema/item.go +++ b/backend/internal/data/ent/schema/item.go @@ -29,6 +29,7 @@ func (Item) Indexes() []ent.Index { index.Fields("model_number"), index.Fields("serial_number"), index.Fields("archived"), + index.Fields("group_id", "asset_id"), } } @@ -48,6 +49,8 @@ func (Item) Fields() []ent.Field { Default(false), field.Bool("archived"). Default(false), + field.Int("asset_id"). + Default(0), // ------------------------------------ // item identification From ab406baf3334f17e5f9ef2a2b6ed629b84c768b8 Mon Sep 17 00:00:00 2001 From: Hayden <64056131+hay-kot@users.noreply.github.com> Date: Sat, 12 Nov 2022 16:24:08 -0900 Subject: [PATCH 02/12] run db migration --- backend/internal/data/ent/item.go | 13 ++- backend/internal/data/ent/item/item.go | 5 ++ backend/internal/data/ent/item/where.go | 71 +++++++++++++++ backend/internal/data/ent/item_create.go | 29 ++++++ backend/internal/data/ent/item_update.go | 70 +++++++++++++++ backend/internal/data/ent/migrate/schema.go | 18 ++-- backend/internal/data/ent/mutation.go | 89 ++++++++++++++++++- backend/internal/data/ent/runtime.go | 20 +++-- backend/internal/data/ent/schema/item.go | 2 +- .../20221113012312_add_asset_id_field.sql | 24 +++++ .../data/migrations/migrations/atlas.sum | 3 +- 11 files changed, 326 insertions(+), 18 deletions(-) create mode 100644 backend/internal/data/migrations/migrations/20221113012312_add_asset_id_field.sql diff --git a/backend/internal/data/ent/item.go b/backend/internal/data/ent/item.go index 802acff..a780945 100644 --- a/backend/internal/data/ent/item.go +++ b/backend/internal/data/ent/item.go @@ -37,6 +37,8 @@ type Item struct { Insured bool `json:"insured,omitempty"` // Archived holds the value of the "archived" field. Archived bool `json:"archived,omitempty"` + // AssetID holds the value of the "asset_id" field. + AssetID int `json:"asset_id,omitempty"` // SerialNumber holds the value of the "serial_number" field. SerialNumber string `json:"serial_number,omitempty"` // ModelNumber holds the value of the "model_number" field. @@ -176,7 +178,7 @@ func (*Item) scanValues(columns []string) ([]any, error) { values[i] = new(sql.NullBool) case item.FieldPurchasePrice, item.FieldSoldPrice: values[i] = new(sql.NullFloat64) - case item.FieldQuantity: + case item.FieldQuantity, item.FieldAssetID: values[i] = new(sql.NullInt64) case item.FieldName, item.FieldDescription, item.FieldImportRef, item.FieldNotes, item.FieldSerialNumber, item.FieldModelNumber, item.FieldManufacturer, item.FieldWarrantyDetails, item.FieldPurchaseFrom, item.FieldSoldTo, item.FieldSoldNotes: values[i] = new(sql.NullString) @@ -265,6 +267,12 @@ func (i *Item) assignValues(columns []string, values []any) error { } else if value.Valid { i.Archived = value.Bool } + case item.FieldAssetID: + if value, ok := values[j].(*sql.NullInt64); !ok { + return fmt.Errorf("unexpected type %T for field asset_id", values[j]) + } else if value.Valid { + i.AssetID = int(value.Int64) + } case item.FieldSerialNumber: if value, ok := values[j].(*sql.NullString); !ok { return fmt.Errorf("unexpected type %T for field serial_number", values[j]) @@ -454,6 +462,9 @@ func (i *Item) String() string { builder.WriteString("archived=") builder.WriteString(fmt.Sprintf("%v", i.Archived)) builder.WriteString(", ") + builder.WriteString("asset_id=") + builder.WriteString(fmt.Sprintf("%v", i.AssetID)) + builder.WriteString(", ") builder.WriteString("serial_number=") builder.WriteString(i.SerialNumber) builder.WriteString(", ") diff --git a/backend/internal/data/ent/item/item.go b/backend/internal/data/ent/item/item.go index c2991da..ab3b43f 100644 --- a/backend/internal/data/ent/item/item.go +++ b/backend/internal/data/ent/item/item.go @@ -31,6 +31,8 @@ const ( FieldInsured = "insured" // FieldArchived holds the string denoting the archived field in the database. FieldArchived = "archived" + // FieldAssetID holds the string denoting the asset_id field in the database. + FieldAssetID = "asset_id" // FieldSerialNumber holds the string denoting the serial_number field in the database. FieldSerialNumber = "serial_number" // FieldModelNumber holds the string denoting the model_number field in the database. @@ -128,6 +130,7 @@ var Columns = []string{ FieldQuantity, FieldInsured, FieldArchived, + FieldAssetID, FieldSerialNumber, FieldModelNumber, FieldManufacturer, @@ -193,6 +196,8 @@ var ( DefaultInsured bool // DefaultArchived holds the default value on creation for the "archived" field. DefaultArchived bool + // DefaultAssetID holds the default value on creation for the "asset_id" field. + DefaultAssetID int // SerialNumberValidator is a validator for the "serial_number" field. It is called by the builders before save. SerialNumberValidator func(string) error // ModelNumberValidator is a validator for the "model_number" field. It is called by the builders before save. diff --git a/backend/internal/data/ent/item/where.go b/backend/internal/data/ent/item/where.go index 2897e35..2174432 100644 --- a/backend/internal/data/ent/item/where.go +++ b/backend/internal/data/ent/item/where.go @@ -145,6 +145,13 @@ func Archived(v bool) predicate.Item { }) } +// AssetID applies equality check predicate on the "asset_id" field. It's identical to AssetIDEQ. +func AssetID(v int) predicate.Item { + return predicate.Item(func(s *sql.Selector) { + s.Where(sql.EQ(s.C(FieldAssetID), v)) + }) +} + // SerialNumber applies equality check predicate on the "serial_number" field. It's identical to SerialNumberEQ. func SerialNumber(v string) predicate.Item { return predicate.Item(func(s *sql.Selector) { @@ -894,6 +901,70 @@ func ArchivedNEQ(v bool) predicate.Item { }) } +// AssetIDEQ applies the EQ predicate on the "asset_id" field. +func AssetIDEQ(v int) predicate.Item { + return predicate.Item(func(s *sql.Selector) { + s.Where(sql.EQ(s.C(FieldAssetID), v)) + }) +} + +// AssetIDNEQ applies the NEQ predicate on the "asset_id" field. +func AssetIDNEQ(v int) predicate.Item { + return predicate.Item(func(s *sql.Selector) { + s.Where(sql.NEQ(s.C(FieldAssetID), v)) + }) +} + +// AssetIDIn applies the In predicate on the "asset_id" field. +func AssetIDIn(vs ...int) predicate.Item { + v := make([]any, len(vs)) + for i := range v { + v[i] = vs[i] + } + return predicate.Item(func(s *sql.Selector) { + s.Where(sql.In(s.C(FieldAssetID), v...)) + }) +} + +// AssetIDNotIn applies the NotIn predicate on the "asset_id" field. +func AssetIDNotIn(vs ...int) predicate.Item { + v := make([]any, len(vs)) + for i := range v { + v[i] = vs[i] + } + return predicate.Item(func(s *sql.Selector) { + s.Where(sql.NotIn(s.C(FieldAssetID), v...)) + }) +} + +// AssetIDGT applies the GT predicate on the "asset_id" field. +func AssetIDGT(v int) predicate.Item { + return predicate.Item(func(s *sql.Selector) { + s.Where(sql.GT(s.C(FieldAssetID), v)) + }) +} + +// AssetIDGTE applies the GTE predicate on the "asset_id" field. +func AssetIDGTE(v int) predicate.Item { + return predicate.Item(func(s *sql.Selector) { + s.Where(sql.GTE(s.C(FieldAssetID), v)) + }) +} + +// AssetIDLT applies the LT predicate on the "asset_id" field. +func AssetIDLT(v int) predicate.Item { + return predicate.Item(func(s *sql.Selector) { + s.Where(sql.LT(s.C(FieldAssetID), v)) + }) +} + +// AssetIDLTE applies the LTE predicate on the "asset_id" field. +func AssetIDLTE(v int) predicate.Item { + return predicate.Item(func(s *sql.Selector) { + s.Where(sql.LTE(s.C(FieldAssetID), v)) + }) +} + // SerialNumberEQ applies the EQ predicate on the "serial_number" field. func SerialNumberEQ(v string) predicate.Item { return predicate.Item(func(s *sql.Selector) { diff --git a/backend/internal/data/ent/item_create.go b/backend/internal/data/ent/item_create.go index f4de18e..36c9720 100644 --- a/backend/internal/data/ent/item_create.go +++ b/backend/internal/data/ent/item_create.go @@ -144,6 +144,20 @@ func (ic *ItemCreate) SetNillableArchived(b *bool) *ItemCreate { return ic } +// SetAssetID sets the "asset_id" field. +func (ic *ItemCreate) SetAssetID(i int) *ItemCreate { + ic.mutation.SetAssetID(i) + return ic +} + +// SetNillableAssetID sets the "asset_id" field if the given value is not nil. +func (ic *ItemCreate) SetNillableAssetID(i *int) *ItemCreate { + if i != nil { + ic.SetAssetID(*i) + } + return ic +} + // SetSerialNumber sets the "serial_number" field. func (ic *ItemCreate) SetSerialNumber(s string) *ItemCreate { ic.mutation.SetSerialNumber(s) @@ -546,6 +560,10 @@ func (ic *ItemCreate) defaults() { v := item.DefaultArchived ic.mutation.SetArchived(v) } + if _, ok := ic.mutation.AssetID(); !ok { + v := item.DefaultAssetID + ic.mutation.SetAssetID(v) + } if _, ok := ic.mutation.LifetimeWarranty(); !ok { v := item.DefaultLifetimeWarranty ic.mutation.SetLifetimeWarranty(v) @@ -604,6 +622,9 @@ func (ic *ItemCreate) check() error { if _, ok := ic.mutation.Archived(); !ok { return &ValidationError{Name: "archived", err: errors.New(`ent: missing required field "Item.archived"`)} } + if _, ok := ic.mutation.AssetID(); !ok { + return &ValidationError{Name: "asset_id", err: errors.New(`ent: missing required field "Item.asset_id"`)} + } if v, ok := ic.mutation.SerialNumber(); ok { if err := item.SerialNumberValidator(v); err != nil { return &ValidationError{Name: "serial_number", err: fmt.Errorf(`ent: validator failed for field "Item.serial_number": %w`, err)} @@ -749,6 +770,14 @@ func (ic *ItemCreate) createSpec() (*Item, *sqlgraph.CreateSpec) { }) _node.Archived = value } + if value, ok := ic.mutation.AssetID(); ok { + _spec.Fields = append(_spec.Fields, &sqlgraph.FieldSpec{ + Type: field.TypeInt, + Value: value, + Column: item.FieldAssetID, + }) + _node.AssetID = value + } if value, ok := ic.mutation.SerialNumber(); ok { _spec.Fields = append(_spec.Fields, &sqlgraph.FieldSpec{ Type: field.TypeString, diff --git a/backend/internal/data/ent/item_update.go b/backend/internal/data/ent/item_update.go index 4307051..52970f5 100644 --- a/backend/internal/data/ent/item_update.go +++ b/backend/internal/data/ent/item_update.go @@ -135,6 +135,27 @@ func (iu *ItemUpdate) SetNillableArchived(b *bool) *ItemUpdate { return iu } +// SetAssetID sets the "asset_id" field. +func (iu *ItemUpdate) SetAssetID(i int) *ItemUpdate { + iu.mutation.ResetAssetID() + iu.mutation.SetAssetID(i) + return iu +} + +// SetNillableAssetID sets the "asset_id" field if the given value is not nil. +func (iu *ItemUpdate) SetNillableAssetID(i *int) *ItemUpdate { + if i != nil { + iu.SetAssetID(*i) + } + return iu +} + +// AddAssetID adds i to the "asset_id" field. +func (iu *ItemUpdate) AddAssetID(i int) *ItemUpdate { + iu.mutation.AddAssetID(i) + return iu +} + // SetSerialNumber sets the "serial_number" field. func (iu *ItemUpdate) SetSerialNumber(s string) *ItemUpdate { iu.mutation.SetSerialNumber(s) @@ -816,6 +837,20 @@ func (iu *ItemUpdate) sqlSave(ctx context.Context) (n int, err error) { Column: item.FieldArchived, }) } + if value, ok := iu.mutation.AssetID(); ok { + _spec.Fields.Set = append(_spec.Fields.Set, &sqlgraph.FieldSpec{ + Type: field.TypeInt, + Value: value, + Column: item.FieldAssetID, + }) + } + if value, ok := iu.mutation.AddedAssetID(); ok { + _spec.Fields.Add = append(_spec.Fields.Add, &sqlgraph.FieldSpec{ + Type: field.TypeInt, + Value: value, + Column: item.FieldAssetID, + }) + } if value, ok := iu.mutation.SerialNumber(); ok { _spec.Fields.Set = append(_spec.Fields.Set, &sqlgraph.FieldSpec{ Type: field.TypeString, @@ -1422,6 +1457,27 @@ func (iuo *ItemUpdateOne) SetNillableArchived(b *bool) *ItemUpdateOne { return iuo } +// SetAssetID sets the "asset_id" field. +func (iuo *ItemUpdateOne) SetAssetID(i int) *ItemUpdateOne { + iuo.mutation.ResetAssetID() + iuo.mutation.SetAssetID(i) + return iuo +} + +// SetNillableAssetID sets the "asset_id" field if the given value is not nil. +func (iuo *ItemUpdateOne) SetNillableAssetID(i *int) *ItemUpdateOne { + if i != nil { + iuo.SetAssetID(*i) + } + return iuo +} + +// AddAssetID adds i to the "asset_id" field. +func (iuo *ItemUpdateOne) AddAssetID(i int) *ItemUpdateOne { + iuo.mutation.AddAssetID(i) + return iuo +} + // SetSerialNumber sets the "serial_number" field. func (iuo *ItemUpdateOne) SetSerialNumber(s string) *ItemUpdateOne { iuo.mutation.SetSerialNumber(s) @@ -2133,6 +2189,20 @@ func (iuo *ItemUpdateOne) sqlSave(ctx context.Context) (_node *Item, err error) Column: item.FieldArchived, }) } + if value, ok := iuo.mutation.AssetID(); ok { + _spec.Fields.Set = append(_spec.Fields.Set, &sqlgraph.FieldSpec{ + Type: field.TypeInt, + Value: value, + Column: item.FieldAssetID, + }) + } + if value, ok := iuo.mutation.AddedAssetID(); ok { + _spec.Fields.Add = append(_spec.Fields.Add, &sqlgraph.FieldSpec{ + Type: field.TypeInt, + Value: value, + Column: item.FieldAssetID, + }) + } if value, ok := iuo.mutation.SerialNumber(); ok { _spec.Fields.Set = append(_spec.Fields.Set, &sqlgraph.FieldSpec{ Type: field.TypeString, diff --git a/backend/internal/data/ent/migrate/schema.go b/backend/internal/data/ent/migrate/schema.go index 6fd2d9e..464c3c6 100644 --- a/backend/internal/data/ent/migrate/schema.go +++ b/backend/internal/data/ent/migrate/schema.go @@ -171,6 +171,7 @@ var ( {Name: "quantity", Type: field.TypeInt, Default: 1}, {Name: "insured", Type: field.TypeBool, Default: false}, {Name: "archived", Type: field.TypeBool, Default: false}, + {Name: "asset_id", Type: field.TypeInt, Default: 0}, {Name: "serial_number", Type: field.TypeString, Nullable: true, Size: 255}, {Name: "model_number", Type: field.TypeString, Nullable: true, Size: 255}, {Name: "manufacturer", Type: field.TypeString, Nullable: true, Size: 255}, @@ -196,19 +197,19 @@ var ( ForeignKeys: []*schema.ForeignKey{ { Symbol: "items_groups_items", - Columns: []*schema.Column{ItemsColumns[23]}, + Columns: []*schema.Column{ItemsColumns[24]}, RefColumns: []*schema.Column{GroupsColumns[0]}, OnDelete: schema.Cascade, }, { Symbol: "items_items_children", - Columns: []*schema.Column{ItemsColumns[24]}, + Columns: []*schema.Column{ItemsColumns[25]}, RefColumns: []*schema.Column{ItemsColumns[0]}, OnDelete: schema.SetNull, }, { Symbol: "items_locations_items", - Columns: []*schema.Column{ItemsColumns[25]}, + Columns: []*schema.Column{ItemsColumns[26]}, RefColumns: []*schema.Column{LocationsColumns[0]}, OnDelete: schema.Cascade, }, @@ -222,23 +223,28 @@ var ( { Name: "item_manufacturer", Unique: false, - Columns: []*schema.Column{ItemsColumns[12]}, + Columns: []*schema.Column{ItemsColumns[13]}, }, { Name: "item_model_number", Unique: false, - Columns: []*schema.Column{ItemsColumns[11]}, + Columns: []*schema.Column{ItemsColumns[12]}, }, { Name: "item_serial_number", Unique: false, - Columns: []*schema.Column{ItemsColumns[10]}, + Columns: []*schema.Column{ItemsColumns[11]}, }, { Name: "item_archived", Unique: false, Columns: []*schema.Column{ItemsColumns[9]}, }, + { + Name: "item_asset_id", + Unique: false, + Columns: []*schema.Column{ItemsColumns[10]}, + }, }, } // ItemFieldsColumns holds the columns for the "item_fields" table. diff --git a/backend/internal/data/ent/mutation.go b/backend/internal/data/ent/mutation.go index 73ccbde..2f1ebdc 100644 --- a/backend/internal/data/ent/mutation.go +++ b/backend/internal/data/ent/mutation.go @@ -4134,6 +4134,8 @@ type ItemMutation struct { addquantity *int insured *bool archived *bool + asset_id *int + addasset_id *int serial_number *string model_number *string manufacturer *string @@ -4660,6 +4662,62 @@ func (m *ItemMutation) ResetArchived() { m.archived = nil } +// SetAssetID sets the "asset_id" field. +func (m *ItemMutation) SetAssetID(i int) { + m.asset_id = &i + m.addasset_id = nil +} + +// AssetID returns the value of the "asset_id" field in the mutation. +func (m *ItemMutation) AssetID() (r int, exists bool) { + v := m.asset_id + if v == nil { + return + } + return *v, true +} + +// OldAssetID returns the old "asset_id" field's value of the Item entity. +// If the Item 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 *ItemMutation) OldAssetID(ctx context.Context) (v int, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldAssetID is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldAssetID requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldAssetID: %w", err) + } + return oldValue.AssetID, nil +} + +// AddAssetID adds i to the "asset_id" field. +func (m *ItemMutation) AddAssetID(i int) { + if m.addasset_id != nil { + *m.addasset_id += i + } else { + m.addasset_id = &i + } +} + +// AddedAssetID returns the value that was added to the "asset_id" field in this mutation. +func (m *ItemMutation) AddedAssetID() (r int, exists bool) { + v := m.addasset_id + if v == nil { + return + } + return *v, true +} + +// ResetAssetID resets all changes to the "asset_id" field. +func (m *ItemMutation) ResetAssetID() { + m.asset_id = nil + m.addasset_id = nil +} + // SetSerialNumber sets the "serial_number" field. func (m *ItemMutation) SetSerialNumber(s string) { m.serial_number = &s @@ -5650,7 +5708,7 @@ func (m *ItemMutation) Type() string { // order to get all numeric fields that were incremented/decremented, call // AddedFields(). func (m *ItemMutation) Fields() []string { - fields := make([]string, 0, 22) + fields := make([]string, 0, 23) if m.created_at != nil { fields = append(fields, item.FieldCreatedAt) } @@ -5678,6 +5736,9 @@ func (m *ItemMutation) Fields() []string { if m.archived != nil { fields = append(fields, item.FieldArchived) } + if m.asset_id != nil { + fields = append(fields, item.FieldAssetID) + } if m.serial_number != nil { fields = append(fields, item.FieldSerialNumber) } @@ -5743,6 +5804,8 @@ func (m *ItemMutation) Field(name string) (ent.Value, bool) { return m.Insured() case item.FieldArchived: return m.Archived() + case item.FieldAssetID: + return m.AssetID() case item.FieldSerialNumber: return m.SerialNumber() case item.FieldModelNumber: @@ -5796,6 +5859,8 @@ func (m *ItemMutation) OldField(ctx context.Context, name string) (ent.Value, er return m.OldInsured(ctx) case item.FieldArchived: return m.OldArchived(ctx) + case item.FieldAssetID: + return m.OldAssetID(ctx) case item.FieldSerialNumber: return m.OldSerialNumber(ctx) case item.FieldModelNumber: @@ -5894,6 +5959,13 @@ func (m *ItemMutation) SetField(name string, value ent.Value) error { } m.SetArchived(v) return nil + case item.FieldAssetID: + v, ok := value.(int) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetAssetID(v) + return nil case item.FieldSerialNumber: v, ok := value.(string) if !ok { @@ -5996,6 +6068,9 @@ func (m *ItemMutation) AddedFields() []string { if m.addquantity != nil { fields = append(fields, item.FieldQuantity) } + if m.addasset_id != nil { + fields = append(fields, item.FieldAssetID) + } if m.addpurchase_price != nil { fields = append(fields, item.FieldPurchasePrice) } @@ -6012,6 +6087,8 @@ func (m *ItemMutation) AddedField(name string) (ent.Value, bool) { switch name { case item.FieldQuantity: return m.AddedQuantity() + case item.FieldAssetID: + return m.AddedAssetID() case item.FieldPurchasePrice: return m.AddedPurchasePrice() case item.FieldSoldPrice: @@ -6032,6 +6109,13 @@ func (m *ItemMutation) AddField(name string, value ent.Value) error { } m.AddQuantity(v) return nil + case item.FieldAssetID: + v, ok := value.(int) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.AddAssetID(v) + return nil case item.FieldPurchasePrice: v, ok := value.(float64) if !ok { @@ -6181,6 +6265,9 @@ func (m *ItemMutation) ResetField(name string) error { case item.FieldArchived: m.ResetArchived() return nil + case item.FieldAssetID: + m.ResetAssetID() + return nil case item.FieldSerialNumber: m.ResetSerialNumber() return nil diff --git a/backend/internal/data/ent/runtime.go b/backend/internal/data/ent/runtime.go index af5dc22..25a7680 100644 --- a/backend/internal/data/ent/runtime.go +++ b/backend/internal/data/ent/runtime.go @@ -275,36 +275,40 @@ func init() { itemDescArchived := itemFields[4].Descriptor() // item.DefaultArchived holds the default value on creation for the archived field. item.DefaultArchived = itemDescArchived.Default.(bool) + // itemDescAssetID is the schema descriptor for asset_id field. + itemDescAssetID := itemFields[5].Descriptor() + // item.DefaultAssetID holds the default value on creation for the asset_id field. + item.DefaultAssetID = itemDescAssetID.Default.(int) // itemDescSerialNumber is the schema descriptor for serial_number field. - itemDescSerialNumber := itemFields[5].Descriptor() + itemDescSerialNumber := itemFields[6].Descriptor() // item.SerialNumberValidator is a validator for the "serial_number" field. It is called by the builders before save. item.SerialNumberValidator = itemDescSerialNumber.Validators[0].(func(string) error) // itemDescModelNumber is the schema descriptor for model_number field. - itemDescModelNumber := itemFields[6].Descriptor() + itemDescModelNumber := itemFields[7].Descriptor() // item.ModelNumberValidator is a validator for the "model_number" field. It is called by the builders before save. item.ModelNumberValidator = itemDescModelNumber.Validators[0].(func(string) error) // itemDescManufacturer is the schema descriptor for manufacturer field. - itemDescManufacturer := itemFields[7].Descriptor() + itemDescManufacturer := itemFields[8].Descriptor() // item.ManufacturerValidator is a validator for the "manufacturer" field. It is called by the builders before save. item.ManufacturerValidator = itemDescManufacturer.Validators[0].(func(string) error) // itemDescLifetimeWarranty is the schema descriptor for lifetime_warranty field. - itemDescLifetimeWarranty := itemFields[8].Descriptor() + itemDescLifetimeWarranty := itemFields[9].Descriptor() // item.DefaultLifetimeWarranty holds the default value on creation for the lifetime_warranty field. item.DefaultLifetimeWarranty = itemDescLifetimeWarranty.Default.(bool) // itemDescWarrantyDetails is the schema descriptor for warranty_details field. - itemDescWarrantyDetails := itemFields[10].Descriptor() + itemDescWarrantyDetails := itemFields[11].Descriptor() // item.WarrantyDetailsValidator is a validator for the "warranty_details" field. It is called by the builders before save. item.WarrantyDetailsValidator = itemDescWarrantyDetails.Validators[0].(func(string) error) // itemDescPurchasePrice is the schema descriptor for purchase_price field. - itemDescPurchasePrice := itemFields[13].Descriptor() + itemDescPurchasePrice := itemFields[14].Descriptor() // item.DefaultPurchasePrice holds the default value on creation for the purchase_price field. item.DefaultPurchasePrice = itemDescPurchasePrice.Default.(float64) // itemDescSoldPrice is the schema descriptor for sold_price field. - itemDescSoldPrice := itemFields[16].Descriptor() + itemDescSoldPrice := itemFields[17].Descriptor() // item.DefaultSoldPrice holds the default value on creation for the sold_price field. item.DefaultSoldPrice = itemDescSoldPrice.Default.(float64) // itemDescSoldNotes is the schema descriptor for sold_notes field. - itemDescSoldNotes := itemFields[17].Descriptor() + itemDescSoldNotes := itemFields[18].Descriptor() // item.SoldNotesValidator is a validator for the "sold_notes" field. It is called by the builders before save. item.SoldNotesValidator = itemDescSoldNotes.Validators[0].(func(string) error) // itemDescID is the schema descriptor for id field. diff --git a/backend/internal/data/ent/schema/item.go b/backend/internal/data/ent/schema/item.go index 3945ebb..388566d 100644 --- a/backend/internal/data/ent/schema/item.go +++ b/backend/internal/data/ent/schema/item.go @@ -29,7 +29,7 @@ func (Item) Indexes() []ent.Index { index.Fields("model_number"), index.Fields("serial_number"), index.Fields("archived"), - index.Fields("group_id", "asset_id"), + index.Fields("asset_id"), } } diff --git a/backend/internal/data/migrations/migrations/20221113012312_add_asset_id_field.sql b/backend/internal/data/migrations/migrations/20221113012312_add_asset_id_field.sql new file mode 100644 index 0000000..5bcf3ad --- /dev/null +++ b/backend/internal/data/migrations/migrations/20221113012312_add_asset_id_field.sql @@ -0,0 +1,24 @@ +-- disable the enforcement of foreign-keys constraints +PRAGMA foreign_keys = off; +-- create "new_items" table +CREATE TABLE `new_items` (`id` uuid NOT NULL, `created_at` datetime NOT NULL, `updated_at` datetime NOT NULL, `name` text NOT NULL, `description` text NULL, `import_ref` text NULL, `notes` text NULL, `quantity` integer NOT NULL DEFAULT 1, `insured` bool NOT NULL DEFAULT false, `archived` bool NOT NULL DEFAULT false, `asset_id` integer NOT NULL DEFAULT 0, `serial_number` text NULL, `model_number` text NULL, `manufacturer` text NULL, `lifetime_warranty` bool NOT NULL DEFAULT false, `warranty_expires` datetime NULL, `warranty_details` text NULL, `purchase_time` datetime NULL, `purchase_from` text NULL, `purchase_price` real NOT NULL DEFAULT 0, `sold_time` datetime NULL, `sold_to` text NULL, `sold_price` real NOT NULL DEFAULT 0, `sold_notes` text NULL, `group_items` uuid NOT NULL, `item_children` uuid NULL, `location_items` uuid NULL, PRIMARY KEY (`id`), CONSTRAINT `items_groups_items` FOREIGN KEY (`group_items`) REFERENCES `groups` (`id`) ON DELETE CASCADE, CONSTRAINT `items_items_children` FOREIGN KEY (`item_children`) REFERENCES `items` (`id`) ON DELETE SET NULL, CONSTRAINT `items_locations_items` FOREIGN KEY (`location_items`) REFERENCES `locations` (`id`) ON DELETE CASCADE); +-- copy rows from old table "items" to new temporary table "new_items" +INSERT INTO `new_items` (`id`, `created_at`, `updated_at`, `name`, `description`, `import_ref`, `notes`, `quantity`, `insured`, `archived`, `serial_number`, `model_number`, `manufacturer`, `lifetime_warranty`, `warranty_expires`, `warranty_details`, `purchase_time`, `purchase_from`, `purchase_price`, `sold_time`, `sold_to`, `sold_price`, `sold_notes`, `group_items`, `item_children`, `location_items`) SELECT `id`, `created_at`, `updated_at`, `name`, `description`, `import_ref`, `notes`, `quantity`, `insured`, `archived`, `serial_number`, `model_number`, `manufacturer`, `lifetime_warranty`, `warranty_expires`, `warranty_details`, `purchase_time`, `purchase_from`, `purchase_price`, `sold_time`, `sold_to`, `sold_price`, `sold_notes`, `group_items`, `item_children`, `location_items` FROM `items`; +-- drop "items" table after copying rows +DROP TABLE `items`; +-- rename temporary table "new_items" to "items" +ALTER TABLE `new_items` RENAME TO `items`; +-- create index "item_name" to table: "items" +CREATE INDEX `item_name` ON `items` (`name`); +-- create index "item_manufacturer" to table: "items" +CREATE INDEX `item_manufacturer` ON `items` (`manufacturer`); +-- create index "item_model_number" to table: "items" +CREATE INDEX `item_model_number` ON `items` (`model_number`); +-- create index "item_serial_number" to table: "items" +CREATE INDEX `item_serial_number` ON `items` (`serial_number`); +-- create index "item_archived" to table: "items" +CREATE INDEX `item_archived` ON `items` (`archived`); +-- create index "item_asset_id" to table: "items" +CREATE INDEX `item_asset_id` ON `items` (`asset_id`); +-- 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 2916627..d9c72fb 100644 --- a/backend/internal/data/migrations/migrations/atlas.sum +++ b/backend/internal/data/migrations/migrations/atlas.sum @@ -1,6 +1,7 @@ -h1:i76VRMDIPdcmQtXTe9bzrgITAzLGjjVy9y8XaXIchAs= +h1:z1tbZ3fYByqxL78Z+ov8mfQVjXcwsZeEcT0i+2DZ8a8= 20220929052825_init.sql h1:ZlCqm1wzjDmofeAcSX3jE4h4VcdTNGpRg2eabztDy9Q= 20221001210956_group_invitations.sql h1:YQKJFtE39wFOcRNbZQ/d+ZlHwrcfcsZlcv/pLEYdpjw= 20221009173029_add_user_roles.sql h1:vWmzAfgEWQeGk0Vn70zfVPCcfEZth3E0JcvyKTjpYyU= 20221020043305_allow_nesting_types.sql h1:4AyJpZ7l7SSJtJAQETYY802FHJ64ufYPJTqvwdiGn3M= 20221101041931_add_archived_field.sql h1:L2WxiOh1svRn817cNURgqnEQg6DIcodZ1twK4tvxW94= +20221113012312_add_asset_id_field.sql h1:DjD7e1PS8OfxGBWic8h0nO/X6CNnHEMqQjDCaaQ3M3Q= From 567e12a1e94e9a9bc85bf46eae50c6bcb78c532e Mon Sep 17 00:00:00 2001 From: Hayden <64056131+hay-kot@users.noreply.github.com> Date: Sat, 12 Nov 2022 18:57:51 -0900 Subject: [PATCH 03/12] bulk seed asset IDs --- .../app/api/handlers/v1/v1_ctrl_actions.go | 35 ++++++++++ backend/app/api/routes.go | 2 + backend/app/api/static/docs/docs.go | 39 +++++++++++ backend/app/api/static/docs/swagger.json | 39 +++++++++++ backend/app/api/static/docs/swagger.yaml | 24 +++++++ .../internal/core/services/service_items.go | 26 +++++++ backend/internal/data/repo/repo_items.go | 69 ++++++++++++++++++- backend/internal/data/repo/repo_items_test.go | 28 ++++++++ frontend/lib/api/classes/actions.ts | 10 +++ frontend/lib/api/types/data-contracts.ts | 8 +++ frontend/lib/api/user.ts | 3 + frontend/pages/item/[id]/index.vue | 4 ++ frontend/pages/profile.vue | 45 ++++++++++++ 13 files changed, 331 insertions(+), 1 deletion(-) create mode 100644 backend/app/api/handlers/v1/v1_ctrl_actions.go create mode 100644 frontend/lib/api/classes/actions.ts diff --git a/backend/app/api/handlers/v1/v1_ctrl_actions.go b/backend/app/api/handlers/v1/v1_ctrl_actions.go new file mode 100644 index 0000000..37e2b72 --- /dev/null +++ b/backend/app/api/handlers/v1/v1_ctrl_actions.go @@ -0,0 +1,35 @@ +package v1 + +import ( + "net/http" + + "github.com/hay-kot/homebox/backend/internal/core/services" + "github.com/hay-kot/homebox/backend/internal/sys/validate" + "github.com/hay-kot/homebox/backend/pkgs/server" + "github.com/rs/zerolog/log" +) + +type EnsureAssetIDResult struct { + Completed int `json:"completed"` +} + +// HandleGroupInvitationsCreate godoc +// @Summary Get the current user +// @Tags Group +// @Produce json +// @Success 200 {object} EnsureAssetIDResult +// @Router /v1/actions/ensure-asset-ids [Post] +// @Security Bearer +func (ctrl *V1Controller) HandleEnsureAssetID() server.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) error { + ctx := services.NewContext(r.Context()) + + totalCompleted, err := ctrl.svc.Items.EnsureAssetID(ctx, ctx.GID) + if err != nil { + log.Err(err).Msg("failed to ensure asset id") + return validate.NewRequestError(err, http.StatusInternalServerError) + } + + return server.Respond(w, http.StatusOK, EnsureAssetIDResult{Completed: totalCompleted}) + } +} diff --git a/backend/app/api/routes.go b/backend/app/api/routes.go index 992e70e..beec706 100644 --- a/backend/app/api/routes.go +++ b/backend/app/api/routes.go @@ -82,6 +82,8 @@ func (a *app) mountRoutes(repos *repo.AllRepos) { a.server.Get(v1Base("/groups"), v1Ctrl.HandleGroupGet(), a.mwAuthToken) a.server.Put(v1Base("/groups"), v1Ctrl.HandleGroupUpdate(), a.mwAuthToken) + a.server.Post(v1Base("/actions/ensure-asset-ids"), v1Ctrl.HandleEnsureAssetID(), a.mwAuthToken) + a.server.Get(v1Base("/locations"), v1Ctrl.HandleLocationGetAll(), a.mwAuthToken) a.server.Post(v1Base("/locations"), v1Ctrl.HandleLocationCreate(), a.mwAuthToken) a.server.Get(v1Base("/locations/{id}"), v1Ctrl.HandleLocationGet(), a.mwAuthToken) diff --git a/backend/app/api/static/docs/docs.go b/backend/app/api/static/docs/docs.go index f2c877e..6e02411 100644 --- a/backend/app/api/static/docs/docs.go +++ b/backend/app/api/static/docs/docs.go @@ -21,6 +21,30 @@ const docTemplate = `{ "host": "{{.Host}}", "basePath": "{{.BasePath}}", "paths": { + "/v1/actions/ensure-asset-ids": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "produces": [ + "application/json" + ], + "tags": [ + "Group" + ], + "summary": "Get the current user", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/v1.EnsureAssetIDResult" + } + } + } + } + }, "/v1/groups": { "get": { "security": [ @@ -1326,6 +1350,10 @@ const docTemplate = `{ "archived": { "type": "boolean" }, + "assetId": { + "type": "string", + "example": "0" + }, "attachments": { "type": "array", "items": { @@ -1479,6 +1507,9 @@ const docTemplate = `{ "archived": { "type": "boolean" }, + "assetId": { + "type": "string" + }, "description": { "type": "string" }, @@ -1891,6 +1922,14 @@ const docTemplate = `{ } } }, + "v1.EnsureAssetIDResult": { + "type": "object", + "properties": { + "completed": { + "type": "integer" + } + } + }, "v1.GroupInvitation": { "type": "object", "properties": { diff --git a/backend/app/api/static/docs/swagger.json b/backend/app/api/static/docs/swagger.json index 7215fae..3be73bd 100644 --- a/backend/app/api/static/docs/swagger.json +++ b/backend/app/api/static/docs/swagger.json @@ -13,6 +13,30 @@ }, "basePath": "/api", "paths": { + "/v1/actions/ensure-asset-ids": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "produces": [ + "application/json" + ], + "tags": [ + "Group" + ], + "summary": "Get the current user", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/v1.EnsureAssetIDResult" + } + } + } + } + }, "/v1/groups": { "get": { "security": [ @@ -1318,6 +1342,10 @@ "archived": { "type": "boolean" }, + "assetId": { + "type": "string", + "example": "0" + }, "attachments": { "type": "array", "items": { @@ -1471,6 +1499,9 @@ "archived": { "type": "boolean" }, + "assetId": { + "type": "string" + }, "description": { "type": "string" }, @@ -1883,6 +1914,14 @@ } } }, + "v1.EnsureAssetIDResult": { + "type": "object", + "properties": { + "completed": { + "type": "integer" + } + } + }, "v1.GroupInvitation": { "type": "object", "properties": { diff --git a/backend/app/api/static/docs/swagger.yaml b/backend/app/api/static/docs/swagger.yaml index e479583..72563b9 100644 --- a/backend/app/api/static/docs/swagger.yaml +++ b/backend/app/api/static/docs/swagger.yaml @@ -98,6 +98,9 @@ definitions: properties: archived: type: boolean + assetId: + example: "0" + type: string attachments: items: $ref: '#/definitions/repo.ItemAttachment' @@ -204,6 +207,8 @@ definitions: properties: archived: type: boolean + assetId: + type: string description: type: string fields: @@ -477,6 +482,11 @@ definitions: new: type: string type: object + v1.EnsureAssetIDResult: + properties: + completed: + type: integer + type: object v1.GroupInvitation: properties: expiresAt: @@ -516,6 +526,20 @@ info: title: Go API Templates version: "1.0" paths: + /v1/actions/ensure-asset-ids: + post: + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/v1.EnsureAssetIDResult' + security: + - Bearer: [] + summary: Get the current user + tags: + - Group /v1/groups: get: produces: diff --git a/backend/internal/core/services/service_items.go b/backend/internal/core/services/service_items.go index 4af80c8..0541577 100644 --- a/backend/internal/core/services/service_items.go +++ b/backend/internal/core/services/service_items.go @@ -23,6 +23,32 @@ type ItemService struct { at attachmentTokens } +func (svc *ItemService) EnsureAssetID(ctx context.Context, GID uuid.UUID) (int, error) { + items, err := svc.repo.Items.GetAllZeroAssetID(ctx, GID) + + if err != nil { + return 0, err + } + + highest, err := svc.repo.Items.GetHighestAssetID(ctx, GID) + if err != nil { + return 0, err + } + + finished := 0 + for _, item := range items { + highest++ + + err = svc.repo.Items.SetAssetID(ctx, GID, item.ID, repo.AssetID(highest)) + if err != nil { + return 0, err + } + + finished++ + } + + return finished, nil +} func (svc *ItemService) CsvImport(ctx context.Context, GID uuid.UUID, data [][]string) (int, error) { loaded := []csvRow{} diff --git a/backend/internal/data/repo/repo_items.go b/backend/internal/data/repo/repo_items.go index 71619fa..e983abf 100644 --- a/backend/internal/data/repo/repo_items.go +++ b/backend/internal/data/repo/repo_items.go @@ -2,6 +2,9 @@ package repo import ( "context" + "fmt" + "strconv" + "strings" "time" "github.com/google/uuid" @@ -18,6 +21,30 @@ type ItemsRepository struct { db *ent.Client } +type AssetID int + +func (aid AssetID) MarshalJSON() ([]byte, error) { + str := fmt.Sprintf("%d", aid) + + for len(str) < 6 { + str = "0" + str + } + + return []byte(fmt.Sprintf(`"%s"`, str)), nil +} + +func (aid *AssetID) UnmarshalJSON(data []byte) error { + str := string(strings.Replace(string(data), `"`, "", -1)) + aidInt, err := strconv.Atoi(str) + if err != nil { + return err + } + + *aid = AssetID(aidInt) + return nil + +} + type ( ItemQuery struct { Page int @@ -52,6 +79,7 @@ type ( ItemUpdate struct { ParentID uuid.UUID `json:"parentId" extensions:"x-nullable,x-omitempty"` ID uuid.UUID `json:"id"` + AssetID AssetID `json:"assetId"` Name string `json:"name"` Description string `json:"description"` Quantity int `json:"quantity"` @@ -107,6 +135,7 @@ type ( ItemOut struct { Parent *ItemSummary `json:"parent,omitempty" extensions:"x-nullable,x-omitempty"` ItemSummary + AssetID AssetID `json:"assetId,string"` SerialNumber string `json:"serialNumber"` ModelNumber string `json:"modelNumber"` @@ -215,6 +244,7 @@ func mapItemOut(item *ent.Item) ItemOut { return ItemOut{ Parent: parent, + AssetID: AssetID(item.AssetID), ItemSummary: mapItemSummary(item), LifetimeWarranty: item.LifetimeWarranty, WarrantyExpires: item.WarrantyExpires, @@ -359,6 +389,42 @@ func (e *ItemsRepository) GetAll(ctx context.Context, gid uuid.UUID) ([]ItemSumm All(ctx)) } +func (e *ItemsRepository) GetAllZeroAssetID(ctx context.Context, GID uuid.UUID) ([]ItemSummary, error) { + q := e.db.Item.Query().Where( + item.HasGroupWith(group.ID(GID)), + item.AssetID(0), + ).Order( + ent.Asc(item.FieldCreatedAt), + ) + + return mapItemsSummaryErr(q.All(ctx)) +} + +func (e *ItemsRepository) GetHighestAssetID(ctx context.Context, GID uuid.UUID) (AssetID, error) { + q := e.db.Item.Query().Where( + item.HasGroupWith(group.ID(GID)), + ).Order( + ent.Desc(item.FieldAssetID), + ).Limit(1) + + result, err := q.First(ctx) + if err != nil { + return 0, err + } + + return AssetID(result.AssetID), nil +} + +func (e *ItemsRepository) SetAssetID(ctx context.Context, GID uuid.UUID, ID uuid.UUID, assetID AssetID) error { + q := e.db.Item.Update().Where( + item.HasGroupWith(group.ID(GID)), + item.ID(ID), + ) + + _, err := q.SetAssetID(int(assetID)).Save(ctx) + return err +} + func (e *ItemsRepository) Create(ctx context.Context, gid uuid.UUID, data ItemCreate) (ItemOut, error) { q := e.db.Item.Create(). SetImportRef(data.ImportRef). @@ -414,7 +480,8 @@ func (e *ItemsRepository) UpdateByGroup(ctx context.Context, gid uuid.UUID, data SetInsured(data.Insured). SetWarrantyExpires(data.WarrantyExpires). SetWarrantyDetails(data.WarrantyDetails). - SetQuantity(data.Quantity) + SetQuantity(data.Quantity). + SetAssetID(int(data.AssetID)) currentLabels, err := e.db.Item.Query().Where(item.ID(data.ID)).QueryLabel().All(ctx) if err != nil { diff --git a/backend/internal/data/repo/repo_items_test.go b/backend/internal/data/repo/repo_items_test.go index 4b958b0..bc9c91d 100644 --- a/backend/internal/data/repo/repo_items_test.go +++ b/backend/internal/data/repo/repo_items_test.go @@ -2,6 +2,7 @@ package repo import ( "context" + "encoding/json" "testing" "time" @@ -9,6 +10,33 @@ import ( "github.com/stretchr/testify/assert" ) +func TestAssetID_UnmarshalJSON(t *testing.T) { + rawjson := `{"aid":"000123"}` + + st := struct { + AID AssetID `json:"aid"` + }{ + AID: AssetID(0), + } + + err := json.Unmarshal([]byte(rawjson), &st) + assert.NoError(t, err) + assert.Equal(t, AssetID(123), st.AID) +} + +func TestAssetID_MarshalJSON(t *testing.T) { + st := struct { + AID AssetID `json:"aid"` + }{ + AID: AssetID(123), + } + + b, err := json.Marshal(st) + + assert.NoError(t, err) + assert.JSONEq(t, `{"aid":"000123"}`, string(b)) +} + func itemFactory() ItemCreate { return ItemCreate{ Name: fk.Str(10), diff --git a/frontend/lib/api/classes/actions.ts b/frontend/lib/api/classes/actions.ts new file mode 100644 index 0000000..be892b3 --- /dev/null +++ b/frontend/lib/api/classes/actions.ts @@ -0,0 +1,10 @@ +import { BaseAPI, route } from "../base"; +import { EnsureAssetIDResult } from "../types/data-contracts"; + +export class ActionsAPI extends BaseAPI { + ensureAssetIDs() { + return this.http.post({ + url: route("/actions/ensure-asset-ids"), + }); + } +} diff --git a/frontend/lib/api/types/data-contracts.ts b/frontend/lib/api/types/data-contracts.ts index 7fd055c..9829f14 100644 --- a/frontend/lib/api/types/data-contracts.ts +++ b/frontend/lib/api/types/data-contracts.ts @@ -71,6 +71,9 @@ export interface ItemField { export interface ItemOut { archived: boolean; + + /** @example 0 */ + assetId: string; attachments: ItemAttachment[]; children: ItemSummary[]; createdAt: Date; @@ -131,6 +134,7 @@ export interface ItemSummary { export interface ItemUpdate { archived: boolean; + assetId: string; description: string; fields: ItemField[]; id: string; @@ -300,6 +304,10 @@ export interface ChangePassword { new: string; } +export interface EnsureAssetIDResult { + completed: number; +} + export interface GroupInvitation { expiresAt: Date; token: string; diff --git a/frontend/lib/api/user.ts b/frontend/lib/api/user.ts index d714bef..5c4940f 100644 --- a/frontend/lib/api/user.ts +++ b/frontend/lib/api/user.ts @@ -4,6 +4,7 @@ import { LabelsApi } from "./classes/labels"; import { LocationsApi } from "./classes/locations"; import { GroupApi } from "./classes/group"; import { UserApi } from "./classes/users"; +import { ActionsAPI } from "./classes/actions"; import { Requests } from "~~/lib/requests"; export class UserClient extends BaseAPI { @@ -12,6 +13,7 @@ export class UserClient extends BaseAPI { items: ItemsApi; group: GroupApi; user: UserApi; + actions: ActionsAPI; constructor(requests: Requests) { super(requests); @@ -21,6 +23,7 @@ export class UserClient extends BaseAPI { this.items = new ItemsApi(requests); this.group = new GroupApi(requests); this.user = new UserApi(requests); + this.actions = new ActionsAPI(requests); Object.freeze(this); } diff --git a/frontend/pages/item/[id]/index.vue b/frontend/pages/item/[id]/index.vue index 03ddfc5..c92508e 100644 --- a/frontend/pages/item/[id]/index.vue +++ b/frontend/pages/item/[id]/index.vue @@ -100,6 +100,10 @@ name: "Notes", text: item.value?.notes, }, + { + name: "Asset ID", + text: item.value?.assetId, + }, ...item.value.fields.map(field => { /** * Support Special URL Syntax diff --git a/frontend/pages/profile.vue b/frontend/pages/profile.vue index 5abb0f0..f2f32fe 100644 --- a/frontend/pages/profile.vue +++ b/frontend/pages/profile.vue @@ -163,6 +163,25 @@ passwordChange.current = ""; passwordChange.loading = false; } + + async function ensureAssetIDs() { + const { isCanceled } = await confirm.open( + "Are you sure you want to ensure all assets have an ID? This will take a while and cannot be undone." + ); + + if (isCanceled) { + return; + } + + const result = await api.actions.ensureAssetIDs(); + + if (result.error) { + notify.error("Failed to ensure asset IDs."); + return; + } + + notify.success(`${result.data.completed} assets have been updated.`); + }