From a4b4fe34544da3101711cc091a52a28f339db233 Mon Sep 17 00:00:00 2001 From: Hayden <64056131+hay-kot@users.noreply.github.com> Date: Sun, 23 Oct 2022 20:54:39 -0800 Subject: [PATCH] feat: allow nested relationships for locations and items (#102) Basic implementation that allows organizing Locations and Items within each other. --- backend/app/api/docs/docs.go | 61 +++- backend/app/api/docs/swagger.json | 61 +++- backend/app/api/docs/swagger.yaml | 44 ++- backend/app/api/v1/v1_ctrl_locations.go | 5 +- backend/ent/client.go | 64 ++++ backend/ent/item.go | 60 +++- backend/ent/item/item.go | 13 + backend/ent/item/where.go | 56 +++ backend/ent/item_create.go | 73 ++++ backend/ent/item_query.go | 149 +++++++- backend/ent/item_update.go | 300 ++++++++++++++++ backend/ent/location.go | 56 ++- backend/ent/location/location.go | 13 + backend/ent/location/where.go | 56 +++ backend/ent/location_create.go | 73 ++++ backend/ent/location_query.go | 181 +++++++++- backend/ent/location_update.go | 300 ++++++++++++++++ backend/ent/migrate/schema.go | 20 +- backend/ent/mutation.go | 328 ++++++++++++++++-- backend/ent/schema/item.go | 3 + backend/ent/schema/location.go | 3 + backend/go.sum | 4 + .../20221020043305_allow_nesting_types.sql | 28 ++ .../internal/migrations/migrations/atlas.sum | 3 +- backend/internal/repo/repo_items.go | 45 ++- backend/internal/repo/repo_items_test.go | 36 ++ backend/internal/repo/repo_locations.go | 33 +- frontend/components/Base/Button.vue | 3 +- frontend/components/Form/Autocomplete.vue | 149 ++++++++ frontend/components/Form/Select.vue | 55 +-- frontend/components/Location/Card.vue | 20 +- frontend/composables/use-item-search.ts | 36 ++ frontend/lib/api/classes/locations.ts | 4 +- frontend/lib/api/types/data-contracts.ts | 19 +- frontend/pages/item/[id]/edit.vue | 31 +- frontend/pages/item/[id]/index.vue | 32 +- frontend/pages/location/[id].vue | 38 +- 37 files changed, 2329 insertions(+), 126 deletions(-) create mode 100644 backend/internal/migrations/migrations/20221020043305_allow_nesting_types.sql create mode 100644 frontend/components/Form/Autocomplete.vue create mode 100644 frontend/composables/use-item-search.ts diff --git a/backend/app/api/docs/docs.go b/backend/app/api/docs/docs.go index 2b6483d..2bd7e41 100644 --- a/backend/app/api/docs/docs.go +++ b/backend/app/api/docs/docs.go @@ -845,6 +845,15 @@ const docTemplate = `{ "name": "id", "in": "path", "required": true + }, + { + "description": "Location Data", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/repo.LocationUpdate" + } } ], "responses": { @@ -1232,6 +1241,10 @@ const docTemplate = `{ }, "name": { "type": "string" + }, + "parentId": { + "type": "string", + "x-nullable": true } } }, @@ -1270,6 +1283,12 @@ const docTemplate = `{ "$ref": "#/definitions/repo.ItemAttachment" } }, + "children": { + "type": "array", + "items": { + "$ref": "#/definitions/repo.ItemSummary" + } + }, "createdAt": { "type": "string" }, @@ -1277,7 +1296,6 @@ const docTemplate = `{ "type": "string" }, "fields": { - "description": "Future", "type": "array", "items": { "$ref": "#/definitions/repo.ItemField" @@ -1301,6 +1319,8 @@ const docTemplate = `{ }, "location": { "description": "Edges", + "x-nullable": true, + "x-omitempty": true, "$ref": "#/definitions/repo.LocationSummary" }, "manufacturer": { @@ -1316,6 +1336,11 @@ const docTemplate = `{ "description": "Extras", "type": "string" }, + "parent": { + "x-nullable": true, + "x-omitempty": true, + "$ref": "#/definitions/repo.ItemSummary" + }, "purchaseFrom": { "type": "string" }, @@ -1381,6 +1406,8 @@ const docTemplate = `{ }, "location": { "description": "Edges", + "x-nullable": true, + "x-omitempty": true, "$ref": "#/definitions/repo.LocationSummary" }, "name": { @@ -1439,6 +1466,11 @@ const docTemplate = `{ "description": "Extras", "type": "string" }, + "parentId": { + "type": "string", + "x-nullable": true, + "x-omitempty": true + }, "purchaseFrom": { "type": "string" }, @@ -1553,6 +1585,12 @@ const docTemplate = `{ "repo.LocationOut": { "type": "object", "properties": { + "children": { + "type": "array", + "items": { + "$ref": "#/definitions/repo.LocationSummary" + } + }, "createdAt": { "type": "string" }, @@ -1571,6 +1609,9 @@ const docTemplate = `{ "name": { "type": "string" }, + "parent": { + "$ref": "#/definitions/repo.LocationSummary" + }, "updatedAt": { "type": "string" } @@ -1619,6 +1660,24 @@ const docTemplate = `{ } } }, + "repo.LocationUpdate": { + "type": "object", + "properties": { + "description": { + "type": "string" + }, + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "parentId": { + "type": "string", + "x-nullable": true + } + } + }, "repo.PaginationResult-repo_ItemSummary": { "type": "object", "properties": { diff --git a/backend/app/api/docs/swagger.json b/backend/app/api/docs/swagger.json index 9aab46d..139990c 100644 --- a/backend/app/api/docs/swagger.json +++ b/backend/app/api/docs/swagger.json @@ -837,6 +837,15 @@ "name": "id", "in": "path", "required": true + }, + { + "description": "Location Data", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/repo.LocationUpdate" + } } ], "responses": { @@ -1224,6 +1233,10 @@ }, "name": { "type": "string" + }, + "parentId": { + "type": "string", + "x-nullable": true } } }, @@ -1262,6 +1275,12 @@ "$ref": "#/definitions/repo.ItemAttachment" } }, + "children": { + "type": "array", + "items": { + "$ref": "#/definitions/repo.ItemSummary" + } + }, "createdAt": { "type": "string" }, @@ -1269,7 +1288,6 @@ "type": "string" }, "fields": { - "description": "Future", "type": "array", "items": { "$ref": "#/definitions/repo.ItemField" @@ -1293,6 +1311,8 @@ }, "location": { "description": "Edges", + "x-nullable": true, + "x-omitempty": true, "$ref": "#/definitions/repo.LocationSummary" }, "manufacturer": { @@ -1308,6 +1328,11 @@ "description": "Extras", "type": "string" }, + "parent": { + "x-nullable": true, + "x-omitempty": true, + "$ref": "#/definitions/repo.ItemSummary" + }, "purchaseFrom": { "type": "string" }, @@ -1373,6 +1398,8 @@ }, "location": { "description": "Edges", + "x-nullable": true, + "x-omitempty": true, "$ref": "#/definitions/repo.LocationSummary" }, "name": { @@ -1431,6 +1458,11 @@ "description": "Extras", "type": "string" }, + "parentId": { + "type": "string", + "x-nullable": true, + "x-omitempty": true + }, "purchaseFrom": { "type": "string" }, @@ -1545,6 +1577,12 @@ "repo.LocationOut": { "type": "object", "properties": { + "children": { + "type": "array", + "items": { + "$ref": "#/definitions/repo.LocationSummary" + } + }, "createdAt": { "type": "string" }, @@ -1563,6 +1601,9 @@ "name": { "type": "string" }, + "parent": { + "$ref": "#/definitions/repo.LocationSummary" + }, "updatedAt": { "type": "string" } @@ -1611,6 +1652,24 @@ } } }, + "repo.LocationUpdate": { + "type": "object", + "properties": { + "description": { + "type": "string" + }, + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "parentId": { + "type": "string", + "x-nullable": true + } + } + }, "repo.PaginationResult-repo_ItemSummary": { "type": "object", "properties": { diff --git a/backend/app/api/docs/swagger.yaml b/backend/app/api/docs/swagger.yaml index 3892c77..bda3d97 100644 --- a/backend/app/api/docs/swagger.yaml +++ b/backend/app/api/docs/swagger.yaml @@ -62,6 +62,9 @@ definitions: type: string name: type: string + parentId: + type: string + x-nullable: true type: object repo.ItemField: properties: @@ -86,12 +89,15 @@ definitions: items: $ref: '#/definitions/repo.ItemAttachment' type: array + children: + items: + $ref: '#/definitions/repo.ItemSummary' + type: array createdAt: type: string description: type: string fields: - description: Future items: $ref: '#/definitions/repo.ItemField' type: array @@ -109,6 +115,8 @@ definitions: location: $ref: '#/definitions/repo.LocationSummary' description: Edges + x-nullable: true + x-omitempty: true manufacturer: type: string modelNumber: @@ -118,6 +126,10 @@ definitions: notes: description: Extras type: string + parent: + $ref: '#/definitions/repo.ItemSummary' + x-nullable: true + x-omitempty: true purchaseFrom: type: string purchasePrice: @@ -164,6 +176,8 @@ definitions: location: $ref: '#/definitions/repo.LocationSummary' description: Edges + x-nullable: true + x-omitempty: true name: type: string quantity: @@ -202,6 +216,10 @@ definitions: notes: description: Extras type: string + parentId: + type: string + x-nullable: true + x-omitempty: true purchaseFrom: type: string purchasePrice: @@ -278,6 +296,10 @@ definitions: type: object repo.LocationOut: properties: + children: + items: + $ref: '#/definitions/repo.LocationSummary' + type: array createdAt: type: string description: @@ -290,6 +312,8 @@ definitions: type: array name: type: string + parent: + $ref: '#/definitions/repo.LocationSummary' updatedAt: type: string type: object @@ -321,6 +345,18 @@ definitions: updatedAt: type: string type: object + repo.LocationUpdate: + properties: + description: + type: string + id: + type: string + name: + type: string + parentId: + type: string + x-nullable: true + type: object repo.PaginationResult-repo_ItemSummary: properties: items: @@ -976,6 +1012,12 @@ paths: name: id required: true type: string + - description: Location Data + in: body + name: payload + required: true + schema: + $ref: '#/definitions/repo.LocationUpdate' produces: - application/json responses: diff --git a/backend/app/api/v1/v1_ctrl_locations.go b/backend/app/api/v1/v1_ctrl_locations.go index 4b8bedb..7a9e525 100644 --- a/backend/app/api/v1/v1_ctrl_locations.go +++ b/backend/app/api/v1/v1_ctrl_locations.go @@ -88,8 +88,9 @@ func (ctrl *V1Controller) HandleLocationGet() http.HandlerFunc { // @Summary updates a location // @Tags Locations // @Produce json -// @Param id path string true "Location ID" -// @Success 200 {object} repo.LocationOut +// @Param id path string true "Location ID" +// @Param payload body repo.LocationUpdate true "Location Data" +// @Success 200 {object} repo.LocationOut // @Router /v1/locations/{id} [PUT] // @Security Bearer func (ctrl *V1Controller) HandleLocationUpdate() http.HandlerFunc { diff --git a/backend/ent/client.go b/backend/ent/client.go index 54a0d47..a9a8168 100644 --- a/backend/ent/client.go +++ b/backend/ent/client.go @@ -1043,6 +1043,38 @@ func (c *ItemClient) GetX(ctx context.Context, id uuid.UUID) *Item { return obj } +// QueryParent queries the parent edge of a Item. +func (c *ItemClient) QueryParent(i *Item) *ItemQuery { + query := &ItemQuery{config: c.config} + query.path = func(ctx context.Context) (fromV *sql.Selector, _ error) { + id := i.ID + step := sqlgraph.NewStep( + sqlgraph.From(item.Table, item.FieldID, id), + sqlgraph.To(item.Table, item.FieldID), + sqlgraph.Edge(sqlgraph.M2O, true, item.ParentTable, item.ParentColumn), + ) + fromV = sqlgraph.Neighbors(i.driver.Dialect(), step) + return fromV, nil + } + return query +} + +// QueryChildren queries the children edge of a Item. +func (c *ItemClient) QueryChildren(i *Item) *ItemQuery { + query := &ItemQuery{config: c.config} + query.path = func(ctx context.Context) (fromV *sql.Selector, _ error) { + id := i.ID + step := sqlgraph.NewStep( + sqlgraph.From(item.Table, item.FieldID, id), + sqlgraph.To(item.Table, item.FieldID), + sqlgraph.Edge(sqlgraph.O2M, false, item.ChildrenTable, item.ChildrenColumn), + ) + fromV = sqlgraph.Neighbors(i.driver.Dialect(), step) + return fromV, nil + } + return query +} + // QueryGroup queries the group edge of a Item. func (c *ItemClient) QueryGroup(i *Item) *GroupQuery { query := &GroupQuery{config: c.config} @@ -1441,6 +1473,38 @@ func (c *LocationClient) GetX(ctx context.Context, id uuid.UUID) *Location { return obj } +// QueryParent queries the parent edge of a Location. +func (c *LocationClient) QueryParent(l *Location) *LocationQuery { + query := &LocationQuery{config: c.config} + query.path = func(ctx context.Context) (fromV *sql.Selector, _ error) { + id := l.ID + step := sqlgraph.NewStep( + sqlgraph.From(location.Table, location.FieldID, id), + sqlgraph.To(location.Table, location.FieldID), + sqlgraph.Edge(sqlgraph.M2O, true, location.ParentTable, location.ParentColumn), + ) + fromV = sqlgraph.Neighbors(l.driver.Dialect(), step) + return fromV, nil + } + return query +} + +// QueryChildren queries the children edge of a Location. +func (c *LocationClient) QueryChildren(l *Location) *LocationQuery { + query := &LocationQuery{config: c.config} + query.path = func(ctx context.Context) (fromV *sql.Selector, _ error) { + id := l.ID + step := sqlgraph.NewStep( + sqlgraph.From(location.Table, location.FieldID, id), + sqlgraph.To(location.Table, location.FieldID), + sqlgraph.Edge(sqlgraph.O2M, false, location.ChildrenTable, location.ChildrenColumn), + ) + fromV = sqlgraph.Neighbors(l.driver.Dialect(), step) + return fromV, nil + } + return query +} + // QueryGroup queries the group edge of a Location. func (c *LocationClient) QueryGroup(l *Location) *GroupQuery { query := &GroupQuery{config: c.config} diff --git a/backend/ent/item.go b/backend/ent/item.go index 3aad369..c1b1eff 100644 --- a/backend/ent/item.go +++ b/backend/ent/item.go @@ -65,11 +65,16 @@ type Item struct { // The values are being populated by the ItemQuery when eager-loading is set. Edges ItemEdges `json:"edges"` group_items *uuid.UUID + item_children *uuid.UUID location_items *uuid.UUID } // ItemEdges holds the relations/edges for other nodes in the graph. type ItemEdges struct { + // Parent holds the value of the parent edge. + Parent *Item `json:"parent,omitempty"` + // Children holds the value of the children edge. + Children []*Item `json:"children,omitempty"` // Group holds the value of the group edge. Group *Group `json:"group,omitempty"` // Label holds the value of the label edge. @@ -82,13 +87,35 @@ type ItemEdges struct { Attachments []*Attachment `json:"attachments,omitempty"` // loadedTypes holds the information for reporting if a // type was loaded (or requested) in eager-loading or not. - loadedTypes [5]bool + loadedTypes [7]bool +} + +// ParentOrErr returns the Parent value or an error if the edge +// was not loaded in eager-loading, or loaded but was not found. +func (e ItemEdges) ParentOrErr() (*Item, error) { + if e.loadedTypes[0] { + if e.Parent == nil { + // Edge was loaded but was not found. + return nil, &NotFoundError{label: item.Label} + } + return e.Parent, nil + } + return nil, &NotLoadedError{edge: "parent"} +} + +// ChildrenOrErr returns the Children value or an error if the edge +// was not loaded in eager-loading. +func (e ItemEdges) ChildrenOrErr() ([]*Item, error) { + if e.loadedTypes[1] { + return e.Children, nil + } + return nil, &NotLoadedError{edge: "children"} } // GroupOrErr returns the Group value or an error if the edge // was not loaded in eager-loading, or loaded but was not found. func (e ItemEdges) GroupOrErr() (*Group, error) { - if e.loadedTypes[0] { + if e.loadedTypes[2] { if e.Group == nil { // Edge was loaded but was not found. return nil, &NotFoundError{label: group.Label} @@ -101,7 +128,7 @@ func (e ItemEdges) GroupOrErr() (*Group, error) { // LabelOrErr returns the Label value or an error if the edge // was not loaded in eager-loading. func (e ItemEdges) LabelOrErr() ([]*Label, error) { - if e.loadedTypes[1] { + if e.loadedTypes[3] { return e.Label, nil } return nil, &NotLoadedError{edge: "label"} @@ -110,7 +137,7 @@ func (e ItemEdges) LabelOrErr() ([]*Label, error) { // LocationOrErr returns the Location value or an error if the edge // was not loaded in eager-loading, or loaded but was not found. func (e ItemEdges) LocationOrErr() (*Location, error) { - if e.loadedTypes[2] { + if e.loadedTypes[4] { if e.Location == nil { // Edge was loaded but was not found. return nil, &NotFoundError{label: location.Label} @@ -123,7 +150,7 @@ func (e ItemEdges) LocationOrErr() (*Location, error) { // FieldsOrErr returns the Fields value or an error if the edge // was not loaded in eager-loading. func (e ItemEdges) FieldsOrErr() ([]*ItemField, error) { - if e.loadedTypes[3] { + if e.loadedTypes[5] { return e.Fields, nil } return nil, &NotLoadedError{edge: "fields"} @@ -132,7 +159,7 @@ func (e ItemEdges) FieldsOrErr() ([]*ItemField, error) { // AttachmentsOrErr returns the Attachments value or an error if the edge // was not loaded in eager-loading. func (e ItemEdges) AttachmentsOrErr() ([]*Attachment, error) { - if e.loadedTypes[4] { + if e.loadedTypes[6] { return e.Attachments, nil } return nil, &NotLoadedError{edge: "attachments"} @@ -157,7 +184,9 @@ func (*Item) scanValues(columns []string) ([]any, error) { values[i] = new(uuid.UUID) case item.ForeignKeys[0]: // group_items values[i] = &sql.NullScanner{S: new(uuid.UUID)} - case item.ForeignKeys[1]: // location_items + case item.ForeignKeys[1]: // item_children + values[i] = &sql.NullScanner{S: new(uuid.UUID)} + case item.ForeignKeys[2]: // location_items values[i] = &sql.NullScanner{S: new(uuid.UUID)} default: return nil, fmt.Errorf("unexpected column %q for type Item", columns[i]) @@ -314,6 +343,13 @@ func (i *Item) assignValues(columns []string, values []any) error { *i.group_items = *value.S.(*uuid.UUID) } case item.ForeignKeys[1]: + if value, ok := values[j].(*sql.NullScanner); !ok { + return fmt.Errorf("unexpected type %T for field item_children", values[j]) + } else if value.Valid { + i.item_children = new(uuid.UUID) + *i.item_children = *value.S.(*uuid.UUID) + } + case item.ForeignKeys[2]: if value, ok := values[j].(*sql.NullScanner); !ok { return fmt.Errorf("unexpected type %T for field location_items", values[j]) } else if value.Valid { @@ -325,6 +361,16 @@ func (i *Item) assignValues(columns []string, values []any) error { return nil } +// QueryParent queries the "parent" edge of the Item entity. +func (i *Item) QueryParent() *ItemQuery { + return (&ItemClient{config: i.config}).QueryParent(i) +} + +// QueryChildren queries the "children" edge of the Item entity. +func (i *Item) QueryChildren() *ItemQuery { + return (&ItemClient{config: i.config}).QueryChildren(i) +} + // QueryGroup queries the "group" edge of the Item entity. func (i *Item) QueryGroup() *GroupQuery { return (&ItemClient{config: i.config}).QueryGroup(i) diff --git a/backend/ent/item/item.go b/backend/ent/item/item.go index 6b9f38c..7592f5b 100644 --- a/backend/ent/item/item.go +++ b/backend/ent/item/item.go @@ -55,6 +55,10 @@ const ( FieldSoldPrice = "sold_price" // FieldSoldNotes holds the string denoting the sold_notes field in the database. FieldSoldNotes = "sold_notes" + // EdgeParent holds the string denoting the parent edge name in mutations. + EdgeParent = "parent" + // EdgeChildren holds the string denoting the children edge name in mutations. + EdgeChildren = "children" // EdgeGroup holds the string denoting the group edge name in mutations. EdgeGroup = "group" // EdgeLabel holds the string denoting the label edge name in mutations. @@ -67,6 +71,14 @@ const ( EdgeAttachments = "attachments" // Table holds the table name of the item in the database. Table = "items" + // ParentTable is the table that holds the parent relation/edge. + ParentTable = "items" + // ParentColumn is the table column denoting the parent relation/edge. + ParentColumn = "item_children" + // ChildrenTable is the table that holds the children relation/edge. + ChildrenTable = "items" + // ChildrenColumn is the table column denoting the children relation/edge. + ChildrenColumn = "item_children" // GroupTable is the table that holds the group relation/edge. GroupTable = "items" // GroupInverseTable is the table name for the Group entity. @@ -132,6 +144,7 @@ var Columns = []string{ // table and are not defined as standalone fields in the schema. var ForeignKeys = []string{ "group_items", + "item_children", "location_items", } diff --git a/backend/ent/item/where.go b/backend/ent/item/where.go index d0d191e..ec74275 100644 --- a/backend/ent/item/where.go +++ b/backend/ent/item/where.go @@ -2040,6 +2040,62 @@ func SoldNotesContainsFold(v string) predicate.Item { }) } +// HasParent applies the HasEdge predicate on the "parent" edge. +func HasParent() predicate.Item { + return predicate.Item(func(s *sql.Selector) { + step := sqlgraph.NewStep( + sqlgraph.From(Table, FieldID), + sqlgraph.To(ParentTable, FieldID), + sqlgraph.Edge(sqlgraph.M2O, true, ParentTable, ParentColumn), + ) + sqlgraph.HasNeighbors(s, step) + }) +} + +// HasParentWith applies the HasEdge predicate on the "parent" edge with a given conditions (other predicates). +func HasParentWith(preds ...predicate.Item) predicate.Item { + return predicate.Item(func(s *sql.Selector) { + step := sqlgraph.NewStep( + sqlgraph.From(Table, FieldID), + sqlgraph.To(Table, FieldID), + sqlgraph.Edge(sqlgraph.M2O, true, ParentTable, ParentColumn), + ) + sqlgraph.HasNeighborsWith(s, step, func(s *sql.Selector) { + for _, p := range preds { + p(s) + } + }) + }) +} + +// HasChildren applies the HasEdge predicate on the "children" edge. +func HasChildren() predicate.Item { + return predicate.Item(func(s *sql.Selector) { + step := sqlgraph.NewStep( + sqlgraph.From(Table, FieldID), + sqlgraph.To(ChildrenTable, FieldID), + sqlgraph.Edge(sqlgraph.O2M, false, ChildrenTable, ChildrenColumn), + ) + sqlgraph.HasNeighbors(s, step) + }) +} + +// HasChildrenWith applies the HasEdge predicate on the "children" edge with a given conditions (other predicates). +func HasChildrenWith(preds ...predicate.Item) predicate.Item { + return predicate.Item(func(s *sql.Selector) { + step := sqlgraph.NewStep( + sqlgraph.From(Table, FieldID), + sqlgraph.To(Table, FieldID), + sqlgraph.Edge(sqlgraph.O2M, false, ChildrenTable, ChildrenColumn), + ) + sqlgraph.HasNeighborsWith(s, step, func(s *sql.Selector) { + for _, p := range preds { + p(s) + } + }) + }) +} + // HasGroup applies the HasEdge predicate on the "group" edge. func HasGroup() predicate.Item { return predicate.Item(func(s *sql.Selector) { diff --git a/backend/ent/item_create.go b/backend/ent/item_create.go index 3aee458..6134705 100644 --- a/backend/ent/item_create.go +++ b/backend/ent/item_create.go @@ -326,6 +326,40 @@ func (ic *ItemCreate) SetNillableID(u *uuid.UUID) *ItemCreate { return ic } +// SetParentID sets the "parent" edge to the Item entity by ID. +func (ic *ItemCreate) SetParentID(id uuid.UUID) *ItemCreate { + ic.mutation.SetParentID(id) + return ic +} + +// SetNillableParentID sets the "parent" edge to the Item entity by ID if the given value is not nil. +func (ic *ItemCreate) SetNillableParentID(id *uuid.UUID) *ItemCreate { + if id != nil { + ic = ic.SetParentID(*id) + } + return ic +} + +// SetParent sets the "parent" edge to the Item entity. +func (ic *ItemCreate) SetParent(i *Item) *ItemCreate { + return ic.SetParentID(i.ID) +} + +// AddChildIDs adds the "children" edge to the Item entity by IDs. +func (ic *ItemCreate) AddChildIDs(ids ...uuid.UUID) *ItemCreate { + ic.mutation.AddChildIDs(ids...) + return ic +} + +// AddChildren adds the "children" edges to the Item entity. +func (ic *ItemCreate) AddChildren(i ...*Item) *ItemCreate { + ids := make([]uuid.UUID, len(i)) + for j := range i { + ids[j] = i[j].ID + } + return ic.AddChildIDs(ids...) +} + // SetGroupID sets the "group" edge to the Group entity by ID. func (ic *ItemCreate) SetGroupID(id uuid.UUID) *ItemCreate { ic.mutation.SetGroupID(id) @@ -790,6 +824,45 @@ func (ic *ItemCreate) createSpec() (*Item, *sqlgraph.CreateSpec) { }) _node.SoldNotes = value } + if nodes := ic.mutation.ParentIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.M2O, + Inverse: true, + Table: item.ParentTable, + Columns: []string{item.ParentColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: &sqlgraph.FieldSpec{ + Type: field.TypeUUID, + Column: item.FieldID, + }, + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _node.item_children = &nodes[0] + _spec.Edges = append(_spec.Edges, edge) + } + if nodes := ic.mutation.ChildrenIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: false, + Table: item.ChildrenTable, + Columns: []string{item.ChildrenColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: &sqlgraph.FieldSpec{ + Type: field.TypeUUID, + Column: item.FieldID, + }, + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges = append(_spec.Edges, edge) + } if nodes := ic.mutation.GroupIDs(); len(nodes) > 0 { edge := &sqlgraph.EdgeSpec{ Rel: sqlgraph.M2O, diff --git a/backend/ent/item_query.go b/backend/ent/item_query.go index 3bf5877..e6d1173 100644 --- a/backend/ent/item_query.go +++ b/backend/ent/item_query.go @@ -30,6 +30,8 @@ type ItemQuery struct { order []OrderFunc fields []string predicates []predicate.Item + withParent *ItemQuery + withChildren *ItemQuery withGroup *GroupQuery withLabel *LabelQuery withLocation *LocationQuery @@ -72,6 +74,50 @@ func (iq *ItemQuery) Order(o ...OrderFunc) *ItemQuery { return iq } +// QueryParent chains the current query on the "parent" edge. +func (iq *ItemQuery) QueryParent() *ItemQuery { + query := &ItemQuery{config: iq.config} + query.path = func(ctx context.Context) (fromU *sql.Selector, err error) { + if err := iq.prepareQuery(ctx); err != nil { + return nil, err + } + selector := iq.sqlQuery(ctx) + if err := selector.Err(); err != nil { + return nil, err + } + step := sqlgraph.NewStep( + sqlgraph.From(item.Table, item.FieldID, selector), + sqlgraph.To(item.Table, item.FieldID), + sqlgraph.Edge(sqlgraph.M2O, true, item.ParentTable, item.ParentColumn), + ) + fromU = sqlgraph.SetNeighbors(iq.driver.Dialect(), step) + return fromU, nil + } + return query +} + +// QueryChildren chains the current query on the "children" edge. +func (iq *ItemQuery) QueryChildren() *ItemQuery { + query := &ItemQuery{config: iq.config} + query.path = func(ctx context.Context) (fromU *sql.Selector, err error) { + if err := iq.prepareQuery(ctx); err != nil { + return nil, err + } + selector := iq.sqlQuery(ctx) + if err := selector.Err(); err != nil { + return nil, err + } + step := sqlgraph.NewStep( + sqlgraph.From(item.Table, item.FieldID, selector), + sqlgraph.To(item.Table, item.FieldID), + sqlgraph.Edge(sqlgraph.O2M, false, item.ChildrenTable, item.ChildrenColumn), + ) + fromU = sqlgraph.SetNeighbors(iq.driver.Dialect(), step) + return fromU, nil + } + return query +} + // QueryGroup chains the current query on the "group" edge. func (iq *ItemQuery) QueryGroup() *GroupQuery { query := &GroupQuery{config: iq.config} @@ -363,6 +409,8 @@ func (iq *ItemQuery) Clone() *ItemQuery { offset: iq.offset, order: append([]OrderFunc{}, iq.order...), predicates: append([]predicate.Item{}, iq.predicates...), + withParent: iq.withParent.Clone(), + withChildren: iq.withChildren.Clone(), withGroup: iq.withGroup.Clone(), withLabel: iq.withLabel.Clone(), withLocation: iq.withLocation.Clone(), @@ -375,6 +423,28 @@ func (iq *ItemQuery) Clone() *ItemQuery { } } +// WithParent tells the query-builder to eager-load the nodes that are connected to +// the "parent" edge. The optional arguments are used to configure the query builder of the edge. +func (iq *ItemQuery) WithParent(opts ...func(*ItemQuery)) *ItemQuery { + query := &ItemQuery{config: iq.config} + for _, opt := range opts { + opt(query) + } + iq.withParent = query + return iq +} + +// WithChildren tells the query-builder to eager-load the nodes that are connected to +// the "children" edge. The optional arguments are used to configure the query builder of the edge. +func (iq *ItemQuery) WithChildren(opts ...func(*ItemQuery)) *ItemQuery { + query := &ItemQuery{config: iq.config} + for _, opt := range opts { + opt(query) + } + iq.withChildren = query + return iq +} + // WithGroup tells the query-builder to eager-load the nodes that are connected to // the "group" edge. The optional arguments are used to configure the query builder of the edge. func (iq *ItemQuery) WithGroup(opts ...func(*GroupQuery)) *ItemQuery { @@ -499,7 +569,9 @@ func (iq *ItemQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*Item, e nodes = []*Item{} withFKs = iq.withFKs _spec = iq.querySpec() - loadedTypes = [5]bool{ + loadedTypes = [7]bool{ + iq.withParent != nil, + iq.withChildren != nil, iq.withGroup != nil, iq.withLabel != nil, iq.withLocation != nil, @@ -507,7 +579,7 @@ func (iq *ItemQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*Item, e iq.withAttachments != nil, } ) - if iq.withGroup != nil || iq.withLocation != nil { + if iq.withParent != nil || iq.withGroup != nil || iq.withLocation != nil { withFKs = true } if withFKs { @@ -531,6 +603,19 @@ func (iq *ItemQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*Item, e if len(nodes) == 0 { return nodes, nil } + if query := iq.withParent; query != nil { + if err := iq.loadParent(ctx, query, nodes, nil, + func(n *Item, e *Item) { n.Edges.Parent = e }); err != nil { + return nil, err + } + } + if query := iq.withChildren; query != nil { + if err := iq.loadChildren(ctx, query, nodes, + func(n *Item) { n.Edges.Children = []*Item{} }, + func(n *Item, e *Item) { n.Edges.Children = append(n.Edges.Children, e) }); err != nil { + return nil, err + } + } if query := iq.withGroup; query != nil { if err := iq.loadGroup(ctx, query, nodes, nil, func(n *Item, e *Group) { n.Edges.Group = e }); err != nil { @@ -567,6 +652,66 @@ func (iq *ItemQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*Item, e return nodes, nil } +func (iq *ItemQuery) loadParent(ctx context.Context, query *ItemQuery, nodes []*Item, init func(*Item), assign func(*Item, *Item)) error { + ids := make([]uuid.UUID, 0, len(nodes)) + nodeids := make(map[uuid.UUID][]*Item) + for i := range nodes { + if nodes[i].item_children == nil { + continue + } + fk := *nodes[i].item_children + if _, ok := nodeids[fk]; !ok { + ids = append(ids, fk) + } + nodeids[fk] = append(nodeids[fk], nodes[i]) + } + query.Where(item.IDIn(ids...)) + neighbors, err := query.All(ctx) + if err != nil { + return err + } + for _, n := range neighbors { + nodes, ok := nodeids[n.ID] + if !ok { + return fmt.Errorf(`unexpected foreign-key "item_children" returned %v`, n.ID) + } + for i := range nodes { + assign(nodes[i], n) + } + } + return nil +} +func (iq *ItemQuery) loadChildren(ctx context.Context, query *ItemQuery, nodes []*Item, init func(*Item), assign func(*Item, *Item)) error { + fks := make([]driver.Value, 0, len(nodes)) + nodeids := make(map[uuid.UUID]*Item) + for i := range nodes { + fks = append(fks, nodes[i].ID) + nodeids[nodes[i].ID] = nodes[i] + if init != nil { + init(nodes[i]) + } + } + query.withFKs = true + query.Where(predicate.Item(func(s *sql.Selector) { + s.Where(sql.InValues(item.ChildrenColumn, fks...)) + })) + neighbors, err := query.All(ctx) + if err != nil { + return err + } + for _, n := range neighbors { + fk := n.item_children + if fk == nil { + return fmt.Errorf(`foreign-key "item_children" is nil for node %v`, n.ID) + } + node, ok := nodeids[*fk] + if !ok { + return fmt.Errorf(`unexpected foreign-key "item_children" returned %v for node %v`, *fk, n.ID) + } + assign(node, n) + } + return nil +} func (iq *ItemQuery) loadGroup(ctx context.Context, query *GroupQuery, nodes []*Item, init func(*Item), assign func(*Item, *Group)) error { ids := make([]uuid.UUID, 0, len(nodes)) nodeids := make(map[uuid.UUID][]*Item) diff --git a/backend/ent/item_update.go b/backend/ent/item_update.go index a5b880d..5b08da7 100644 --- a/backend/ent/item_update.go +++ b/backend/ent/item_update.go @@ -377,6 +377,40 @@ func (iu *ItemUpdate) ClearSoldNotes() *ItemUpdate { return iu } +// SetParentID sets the "parent" edge to the Item entity by ID. +func (iu *ItemUpdate) SetParentID(id uuid.UUID) *ItemUpdate { + iu.mutation.SetParentID(id) + return iu +} + +// SetNillableParentID sets the "parent" edge to the Item entity by ID if the given value is not nil. +func (iu *ItemUpdate) SetNillableParentID(id *uuid.UUID) *ItemUpdate { + if id != nil { + iu = iu.SetParentID(*id) + } + return iu +} + +// SetParent sets the "parent" edge to the Item entity. +func (iu *ItemUpdate) SetParent(i *Item) *ItemUpdate { + return iu.SetParentID(i.ID) +} + +// AddChildIDs adds the "children" edge to the Item entity by IDs. +func (iu *ItemUpdate) AddChildIDs(ids ...uuid.UUID) *ItemUpdate { + iu.mutation.AddChildIDs(ids...) + return iu +} + +// AddChildren adds the "children" edges to the Item entity. +func (iu *ItemUpdate) AddChildren(i ...*Item) *ItemUpdate { + ids := make([]uuid.UUID, len(i)) + for j := range i { + ids[j] = i[j].ID + } + return iu.AddChildIDs(ids...) +} + // SetGroupID sets the "group" edge to the Group entity by ID. func (iu *ItemUpdate) SetGroupID(id uuid.UUID) *ItemUpdate { iu.mutation.SetGroupID(id) @@ -457,6 +491,33 @@ func (iu *ItemUpdate) Mutation() *ItemMutation { return iu.mutation } +// ClearParent clears the "parent" edge to the Item entity. +func (iu *ItemUpdate) ClearParent() *ItemUpdate { + iu.mutation.ClearParent() + return iu +} + +// ClearChildren clears all "children" edges to the Item entity. +func (iu *ItemUpdate) ClearChildren() *ItemUpdate { + iu.mutation.ClearChildren() + return iu +} + +// RemoveChildIDs removes the "children" edge to Item entities by IDs. +func (iu *ItemUpdate) RemoveChildIDs(ids ...uuid.UUID) *ItemUpdate { + iu.mutation.RemoveChildIDs(ids...) + return iu +} + +// RemoveChildren removes "children" edges to Item entities. +func (iu *ItemUpdate) RemoveChildren(i ...*Item) *ItemUpdate { + ids := make([]uuid.UUID, len(i)) + for j := range i { + ids[j] = i[j].ID + } + return iu.RemoveChildIDs(ids...) +} + // ClearGroup clears the "group" edge to the Group entity. func (iu *ItemUpdate) ClearGroup() *ItemUpdate { iu.mutation.ClearGroup() @@ -899,6 +960,95 @@ func (iu *ItemUpdate) sqlSave(ctx context.Context) (n int, err error) { Column: item.FieldSoldNotes, }) } + if iu.mutation.ParentCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.M2O, + Inverse: true, + Table: item.ParentTable, + Columns: []string{item.ParentColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: &sqlgraph.FieldSpec{ + Type: field.TypeUUID, + Column: item.FieldID, + }, + }, + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := iu.mutation.ParentIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.M2O, + Inverse: true, + Table: item.ParentTable, + Columns: []string{item.ParentColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: &sqlgraph.FieldSpec{ + Type: field.TypeUUID, + Column: item.FieldID, + }, + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Add = append(_spec.Edges.Add, edge) + } + if iu.mutation.ChildrenCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: false, + Table: item.ChildrenTable, + Columns: []string{item.ChildrenColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: &sqlgraph.FieldSpec{ + Type: field.TypeUUID, + Column: item.FieldID, + }, + }, + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := iu.mutation.RemovedChildrenIDs(); len(nodes) > 0 && !iu.mutation.ChildrenCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: false, + Table: item.ChildrenTable, + Columns: []string{item.ChildrenColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: &sqlgraph.FieldSpec{ + Type: field.TypeUUID, + Column: item.FieldID, + }, + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := iu.mutation.ChildrenIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: false, + Table: item.ChildrenTable, + Columns: []string{item.ChildrenColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: &sqlgraph.FieldSpec{ + Type: field.TypeUUID, + Column: item.FieldID, + }, + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Add = append(_spec.Edges.Add, edge) + } if iu.mutation.GroupCleared() { edge := &sqlgraph.EdgeSpec{ Rel: sqlgraph.M2O, @@ -1493,6 +1643,40 @@ func (iuo *ItemUpdateOne) ClearSoldNotes() *ItemUpdateOne { return iuo } +// SetParentID sets the "parent" edge to the Item entity by ID. +func (iuo *ItemUpdateOne) SetParentID(id uuid.UUID) *ItemUpdateOne { + iuo.mutation.SetParentID(id) + return iuo +} + +// SetNillableParentID sets the "parent" edge to the Item entity by ID if the given value is not nil. +func (iuo *ItemUpdateOne) SetNillableParentID(id *uuid.UUID) *ItemUpdateOne { + if id != nil { + iuo = iuo.SetParentID(*id) + } + return iuo +} + +// SetParent sets the "parent" edge to the Item entity. +func (iuo *ItemUpdateOne) SetParent(i *Item) *ItemUpdateOne { + return iuo.SetParentID(i.ID) +} + +// AddChildIDs adds the "children" edge to the Item entity by IDs. +func (iuo *ItemUpdateOne) AddChildIDs(ids ...uuid.UUID) *ItemUpdateOne { + iuo.mutation.AddChildIDs(ids...) + return iuo +} + +// AddChildren adds the "children" edges to the Item entity. +func (iuo *ItemUpdateOne) AddChildren(i ...*Item) *ItemUpdateOne { + ids := make([]uuid.UUID, len(i)) + for j := range i { + ids[j] = i[j].ID + } + return iuo.AddChildIDs(ids...) +} + // SetGroupID sets the "group" edge to the Group entity by ID. func (iuo *ItemUpdateOne) SetGroupID(id uuid.UUID) *ItemUpdateOne { iuo.mutation.SetGroupID(id) @@ -1573,6 +1757,33 @@ func (iuo *ItemUpdateOne) Mutation() *ItemMutation { return iuo.mutation } +// ClearParent clears the "parent" edge to the Item entity. +func (iuo *ItemUpdateOne) ClearParent() *ItemUpdateOne { + iuo.mutation.ClearParent() + return iuo +} + +// ClearChildren clears all "children" edges to the Item entity. +func (iuo *ItemUpdateOne) ClearChildren() *ItemUpdateOne { + iuo.mutation.ClearChildren() + return iuo +} + +// RemoveChildIDs removes the "children" edge to Item entities by IDs. +func (iuo *ItemUpdateOne) RemoveChildIDs(ids ...uuid.UUID) *ItemUpdateOne { + iuo.mutation.RemoveChildIDs(ids...) + return iuo +} + +// RemoveChildren removes "children" edges to Item entities. +func (iuo *ItemUpdateOne) RemoveChildren(i ...*Item) *ItemUpdateOne { + ids := make([]uuid.UUID, len(i)) + for j := range i { + ids[j] = i[j].ID + } + return iuo.RemoveChildIDs(ids...) +} + // ClearGroup clears the "group" edge to the Group entity. func (iuo *ItemUpdateOne) ClearGroup() *ItemUpdateOne { iuo.mutation.ClearGroup() @@ -2045,6 +2256,95 @@ func (iuo *ItemUpdateOne) sqlSave(ctx context.Context) (_node *Item, err error) Column: item.FieldSoldNotes, }) } + if iuo.mutation.ParentCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.M2O, + Inverse: true, + Table: item.ParentTable, + Columns: []string{item.ParentColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: &sqlgraph.FieldSpec{ + Type: field.TypeUUID, + Column: item.FieldID, + }, + }, + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := iuo.mutation.ParentIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.M2O, + Inverse: true, + Table: item.ParentTable, + Columns: []string{item.ParentColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: &sqlgraph.FieldSpec{ + Type: field.TypeUUID, + Column: item.FieldID, + }, + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Add = append(_spec.Edges.Add, edge) + } + if iuo.mutation.ChildrenCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: false, + Table: item.ChildrenTable, + Columns: []string{item.ChildrenColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: &sqlgraph.FieldSpec{ + Type: field.TypeUUID, + Column: item.FieldID, + }, + }, + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := iuo.mutation.RemovedChildrenIDs(); len(nodes) > 0 && !iuo.mutation.ChildrenCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: false, + Table: item.ChildrenTable, + Columns: []string{item.ChildrenColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: &sqlgraph.FieldSpec{ + Type: field.TypeUUID, + Column: item.FieldID, + }, + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := iuo.mutation.ChildrenIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: false, + Table: item.ChildrenTable, + Columns: []string{item.ChildrenColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: &sqlgraph.FieldSpec{ + Type: field.TypeUUID, + Column: item.FieldID, + }, + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Add = append(_spec.Edges.Add, edge) + } if iuo.mutation.GroupCleared() { edge := &sqlgraph.EdgeSpec{ Rel: sqlgraph.M2O, diff --git a/backend/ent/location.go b/backend/ent/location.go index 85b0b16..337126b 100644 --- a/backend/ent/location.go +++ b/backend/ent/location.go @@ -28,25 +28,52 @@ type Location struct { Description string `json:"description,omitempty"` // Edges holds the relations/edges for other nodes in the graph. // The values are being populated by the LocationQuery when eager-loading is set. - Edges LocationEdges `json:"edges"` - group_locations *uuid.UUID + Edges LocationEdges `json:"edges"` + group_locations *uuid.UUID + location_children *uuid.UUID } // LocationEdges holds the relations/edges for other nodes in the graph. type LocationEdges struct { + // Parent holds the value of the parent edge. + Parent *Location `json:"parent,omitempty"` + // Children holds the value of the children edge. + Children []*Location `json:"children,omitempty"` // Group holds the value of the group edge. Group *Group `json:"group,omitempty"` // Items holds the value of the items edge. Items []*Item `json:"items,omitempty"` // loadedTypes holds the information for reporting if a // type was loaded (or requested) in eager-loading or not. - loadedTypes [2]bool + loadedTypes [4]bool +} + +// ParentOrErr returns the Parent value or an error if the edge +// was not loaded in eager-loading, or loaded but was not found. +func (e LocationEdges) ParentOrErr() (*Location, error) { + if e.loadedTypes[0] { + if e.Parent == nil { + // Edge was loaded but was not found. + return nil, &NotFoundError{label: location.Label} + } + return e.Parent, nil + } + return nil, &NotLoadedError{edge: "parent"} +} + +// ChildrenOrErr returns the Children value or an error if the edge +// was not loaded in eager-loading. +func (e LocationEdges) ChildrenOrErr() ([]*Location, error) { + if e.loadedTypes[1] { + return e.Children, nil + } + return nil, &NotLoadedError{edge: "children"} } // GroupOrErr returns the Group value or an error if the edge // was not loaded in eager-loading, or loaded but was not found. func (e LocationEdges) GroupOrErr() (*Group, error) { - if e.loadedTypes[0] { + if e.loadedTypes[2] { if e.Group == nil { // Edge was loaded but was not found. return nil, &NotFoundError{label: group.Label} @@ -59,7 +86,7 @@ func (e LocationEdges) GroupOrErr() (*Group, error) { // ItemsOrErr returns the Items value or an error if the edge // was not loaded in eager-loading. func (e LocationEdges) ItemsOrErr() ([]*Item, error) { - if e.loadedTypes[1] { + if e.loadedTypes[3] { return e.Items, nil } return nil, &NotLoadedError{edge: "items"} @@ -78,6 +105,8 @@ func (*Location) scanValues(columns []string) ([]any, error) { values[i] = new(uuid.UUID) case location.ForeignKeys[0]: // group_locations values[i] = &sql.NullScanner{S: new(uuid.UUID)} + case location.ForeignKeys[1]: // location_children + values[i] = &sql.NullScanner{S: new(uuid.UUID)} default: return nil, fmt.Errorf("unexpected column %q for type Location", columns[i]) } @@ -130,11 +159,28 @@ func (l *Location) assignValues(columns []string, values []any) error { l.group_locations = new(uuid.UUID) *l.group_locations = *value.S.(*uuid.UUID) } + case location.ForeignKeys[1]: + if value, ok := values[i].(*sql.NullScanner); !ok { + return fmt.Errorf("unexpected type %T for field location_children", values[i]) + } else if value.Valid { + l.location_children = new(uuid.UUID) + *l.location_children = *value.S.(*uuid.UUID) + } } } return nil } +// QueryParent queries the "parent" edge of the Location entity. +func (l *Location) QueryParent() *LocationQuery { + return (&LocationClient{config: l.config}).QueryParent(l) +} + +// QueryChildren queries the "children" edge of the Location entity. +func (l *Location) QueryChildren() *LocationQuery { + return (&LocationClient{config: l.config}).QueryChildren(l) +} + // QueryGroup queries the "group" edge of the Location entity. func (l *Location) QueryGroup() *GroupQuery { return (&LocationClient{config: l.config}).QueryGroup(l) diff --git a/backend/ent/location/location.go b/backend/ent/location/location.go index 322658e..96cb75c 100644 --- a/backend/ent/location/location.go +++ b/backend/ent/location/location.go @@ -21,12 +21,24 @@ const ( FieldName = "name" // FieldDescription holds the string denoting the description field in the database. FieldDescription = "description" + // EdgeParent holds the string denoting the parent edge name in mutations. + EdgeParent = "parent" + // EdgeChildren holds the string denoting the children edge name in mutations. + EdgeChildren = "children" // EdgeGroup holds the string denoting the group edge name in mutations. EdgeGroup = "group" // EdgeItems holds the string denoting the items edge name in mutations. EdgeItems = "items" // Table holds the table name of the location in the database. Table = "locations" + // ParentTable is the table that holds the parent relation/edge. + ParentTable = "locations" + // ParentColumn is the table column denoting the parent relation/edge. + ParentColumn = "location_children" + // ChildrenTable is the table that holds the children relation/edge. + ChildrenTable = "locations" + // ChildrenColumn is the table column denoting the children relation/edge. + ChildrenColumn = "location_children" // GroupTable is the table that holds the group relation/edge. GroupTable = "locations" // GroupInverseTable is the table name for the Group entity. @@ -56,6 +68,7 @@ var Columns = []string{ // table and are not defined as standalone fields in the schema. var ForeignKeys = []string{ "group_locations", + "location_children", } // ValidColumn reports if the column name is valid (part of the table columns). diff --git a/backend/ent/location/where.go b/backend/ent/location/where.go index 819c893..20738b7 100644 --- a/backend/ent/location/where.go +++ b/backend/ent/location/where.go @@ -450,6 +450,62 @@ func DescriptionContainsFold(v string) predicate.Location { }) } +// HasParent applies the HasEdge predicate on the "parent" edge. +func HasParent() predicate.Location { + return predicate.Location(func(s *sql.Selector) { + step := sqlgraph.NewStep( + sqlgraph.From(Table, FieldID), + sqlgraph.To(ParentTable, FieldID), + sqlgraph.Edge(sqlgraph.M2O, true, ParentTable, ParentColumn), + ) + sqlgraph.HasNeighbors(s, step) + }) +} + +// HasParentWith applies the HasEdge predicate on the "parent" edge with a given conditions (other predicates). +func HasParentWith(preds ...predicate.Location) predicate.Location { + return predicate.Location(func(s *sql.Selector) { + step := sqlgraph.NewStep( + sqlgraph.From(Table, FieldID), + sqlgraph.To(Table, FieldID), + sqlgraph.Edge(sqlgraph.M2O, true, ParentTable, ParentColumn), + ) + sqlgraph.HasNeighborsWith(s, step, func(s *sql.Selector) { + for _, p := range preds { + p(s) + } + }) + }) +} + +// HasChildren applies the HasEdge predicate on the "children" edge. +func HasChildren() predicate.Location { + return predicate.Location(func(s *sql.Selector) { + step := sqlgraph.NewStep( + sqlgraph.From(Table, FieldID), + sqlgraph.To(ChildrenTable, FieldID), + sqlgraph.Edge(sqlgraph.O2M, false, ChildrenTable, ChildrenColumn), + ) + sqlgraph.HasNeighbors(s, step) + }) +} + +// HasChildrenWith applies the HasEdge predicate on the "children" edge with a given conditions (other predicates). +func HasChildrenWith(preds ...predicate.Location) predicate.Location { + return predicate.Location(func(s *sql.Selector) { + step := sqlgraph.NewStep( + sqlgraph.From(Table, FieldID), + sqlgraph.To(Table, FieldID), + sqlgraph.Edge(sqlgraph.O2M, false, ChildrenTable, ChildrenColumn), + ) + sqlgraph.HasNeighborsWith(s, step, func(s *sql.Selector) { + for _, p := range preds { + p(s) + } + }) + }) +} + // HasGroup applies the HasEdge predicate on the "group" edge. func HasGroup() predicate.Location { return predicate.Location(func(s *sql.Selector) { diff --git a/backend/ent/location_create.go b/backend/ent/location_create.go index df6a88e..ceff804 100644 --- a/backend/ent/location_create.go +++ b/backend/ent/location_create.go @@ -85,6 +85,40 @@ func (lc *LocationCreate) SetNillableID(u *uuid.UUID) *LocationCreate { return lc } +// SetParentID sets the "parent" edge to the Location entity by ID. +func (lc *LocationCreate) SetParentID(id uuid.UUID) *LocationCreate { + lc.mutation.SetParentID(id) + return lc +} + +// SetNillableParentID sets the "parent" edge to the Location entity by ID if the given value is not nil. +func (lc *LocationCreate) SetNillableParentID(id *uuid.UUID) *LocationCreate { + if id != nil { + lc = lc.SetParentID(*id) + } + return lc +} + +// SetParent sets the "parent" edge to the Location entity. +func (lc *LocationCreate) SetParent(l *Location) *LocationCreate { + return lc.SetParentID(l.ID) +} + +// AddChildIDs adds the "children" edge to the Location entity by IDs. +func (lc *LocationCreate) AddChildIDs(ids ...uuid.UUID) *LocationCreate { + lc.mutation.AddChildIDs(ids...) + return lc +} + +// AddChildren adds the "children" edges to the Location entity. +func (lc *LocationCreate) AddChildren(l ...*Location) *LocationCreate { + ids := make([]uuid.UUID, len(l)) + for i := range l { + ids[i] = l[i].ID + } + return lc.AddChildIDs(ids...) +} + // SetGroupID sets the "group" edge to the Group entity by ID. func (lc *LocationCreate) SetGroupID(id uuid.UUID) *LocationCreate { lc.mutation.SetGroupID(id) @@ -294,6 +328,45 @@ func (lc *LocationCreate) createSpec() (*Location, *sqlgraph.CreateSpec) { }) _node.Description = value } + if nodes := lc.mutation.ParentIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.M2O, + Inverse: true, + Table: location.ParentTable, + Columns: []string{location.ParentColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: &sqlgraph.FieldSpec{ + Type: field.TypeUUID, + Column: location.FieldID, + }, + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _node.location_children = &nodes[0] + _spec.Edges = append(_spec.Edges, edge) + } + if nodes := lc.mutation.ChildrenIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: false, + Table: location.ChildrenTable, + Columns: []string{location.ChildrenColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: &sqlgraph.FieldSpec{ + Type: field.TypeUUID, + Column: location.FieldID, + }, + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges = append(_spec.Edges, edge) + } if nodes := lc.mutation.GroupIDs(); len(nodes) > 0 { edge := &sqlgraph.EdgeSpec{ Rel: sqlgraph.M2O, diff --git a/backend/ent/location_query.go b/backend/ent/location_query.go index 0097cb5..9cf0b55 100644 --- a/backend/ent/location_query.go +++ b/backend/ent/location_query.go @@ -21,15 +21,17 @@ import ( // LocationQuery is the builder for querying Location entities. type LocationQuery struct { config - limit *int - offset *int - unique *bool - order []OrderFunc - fields []string - predicates []predicate.Location - withGroup *GroupQuery - withItems *ItemQuery - withFKs bool + limit *int + offset *int + unique *bool + order []OrderFunc + fields []string + predicates []predicate.Location + withParent *LocationQuery + withChildren *LocationQuery + withGroup *GroupQuery + withItems *ItemQuery + withFKs bool // intermediate query (i.e. traversal path). sql *sql.Selector path func(context.Context) (*sql.Selector, error) @@ -66,6 +68,50 @@ func (lq *LocationQuery) Order(o ...OrderFunc) *LocationQuery { return lq } +// QueryParent chains the current query on the "parent" edge. +func (lq *LocationQuery) QueryParent() *LocationQuery { + query := &LocationQuery{config: lq.config} + query.path = func(ctx context.Context) (fromU *sql.Selector, err error) { + if err := lq.prepareQuery(ctx); err != nil { + return nil, err + } + selector := lq.sqlQuery(ctx) + if err := selector.Err(); err != nil { + return nil, err + } + step := sqlgraph.NewStep( + sqlgraph.From(location.Table, location.FieldID, selector), + sqlgraph.To(location.Table, location.FieldID), + sqlgraph.Edge(sqlgraph.M2O, true, location.ParentTable, location.ParentColumn), + ) + fromU = sqlgraph.SetNeighbors(lq.driver.Dialect(), step) + return fromU, nil + } + return query +} + +// QueryChildren chains the current query on the "children" edge. +func (lq *LocationQuery) QueryChildren() *LocationQuery { + query := &LocationQuery{config: lq.config} + query.path = func(ctx context.Context) (fromU *sql.Selector, err error) { + if err := lq.prepareQuery(ctx); err != nil { + return nil, err + } + selector := lq.sqlQuery(ctx) + if err := selector.Err(); err != nil { + return nil, err + } + step := sqlgraph.NewStep( + sqlgraph.From(location.Table, location.FieldID, selector), + sqlgraph.To(location.Table, location.FieldID), + sqlgraph.Edge(sqlgraph.O2M, false, location.ChildrenTable, location.ChildrenColumn), + ) + fromU = sqlgraph.SetNeighbors(lq.driver.Dialect(), step) + return fromU, nil + } + return query +} + // QueryGroup chains the current query on the "group" edge. func (lq *LocationQuery) QueryGroup() *GroupQuery { query := &GroupQuery{config: lq.config} @@ -286,13 +332,15 @@ func (lq *LocationQuery) Clone() *LocationQuery { return nil } return &LocationQuery{ - config: lq.config, - limit: lq.limit, - offset: lq.offset, - order: append([]OrderFunc{}, lq.order...), - predicates: append([]predicate.Location{}, lq.predicates...), - withGroup: lq.withGroup.Clone(), - withItems: lq.withItems.Clone(), + config: lq.config, + limit: lq.limit, + offset: lq.offset, + order: append([]OrderFunc{}, lq.order...), + predicates: append([]predicate.Location{}, lq.predicates...), + withParent: lq.withParent.Clone(), + withChildren: lq.withChildren.Clone(), + withGroup: lq.withGroup.Clone(), + withItems: lq.withItems.Clone(), // clone intermediate query. sql: lq.sql.Clone(), path: lq.path, @@ -300,6 +348,28 @@ func (lq *LocationQuery) Clone() *LocationQuery { } } +// WithParent tells the query-builder to eager-load the nodes that are connected to +// the "parent" edge. The optional arguments are used to configure the query builder of the edge. +func (lq *LocationQuery) WithParent(opts ...func(*LocationQuery)) *LocationQuery { + query := &LocationQuery{config: lq.config} + for _, opt := range opts { + opt(query) + } + lq.withParent = query + return lq +} + +// WithChildren tells the query-builder to eager-load the nodes that are connected to +// the "children" edge. The optional arguments are used to configure the query builder of the edge. +func (lq *LocationQuery) WithChildren(opts ...func(*LocationQuery)) *LocationQuery { + query := &LocationQuery{config: lq.config} + for _, opt := range opts { + opt(query) + } + lq.withChildren = query + return lq +} + // WithGroup tells the query-builder to eager-load the nodes that are connected to // the "group" edge. The optional arguments are used to configure the query builder of the edge. func (lq *LocationQuery) WithGroup(opts ...func(*GroupQuery)) *LocationQuery { @@ -391,12 +461,14 @@ func (lq *LocationQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*Loc nodes = []*Location{} withFKs = lq.withFKs _spec = lq.querySpec() - loadedTypes = [2]bool{ + loadedTypes = [4]bool{ + lq.withParent != nil, + lq.withChildren != nil, lq.withGroup != nil, lq.withItems != nil, } ) - if lq.withGroup != nil { + if lq.withParent != nil || lq.withGroup != nil { withFKs = true } if withFKs { @@ -420,6 +492,19 @@ func (lq *LocationQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*Loc if len(nodes) == 0 { return nodes, nil } + if query := lq.withParent; query != nil { + if err := lq.loadParent(ctx, query, nodes, nil, + func(n *Location, e *Location) { n.Edges.Parent = e }); err != nil { + return nil, err + } + } + if query := lq.withChildren; query != nil { + if err := lq.loadChildren(ctx, query, nodes, + func(n *Location) { n.Edges.Children = []*Location{} }, + func(n *Location, e *Location) { n.Edges.Children = append(n.Edges.Children, e) }); err != nil { + return nil, err + } + } if query := lq.withGroup; query != nil { if err := lq.loadGroup(ctx, query, nodes, nil, func(n *Location, e *Group) { n.Edges.Group = e }); err != nil { @@ -436,6 +521,66 @@ func (lq *LocationQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*Loc return nodes, nil } +func (lq *LocationQuery) loadParent(ctx context.Context, query *LocationQuery, nodes []*Location, init func(*Location), assign func(*Location, *Location)) error { + ids := make([]uuid.UUID, 0, len(nodes)) + nodeids := make(map[uuid.UUID][]*Location) + for i := range nodes { + if nodes[i].location_children == nil { + continue + } + fk := *nodes[i].location_children + if _, ok := nodeids[fk]; !ok { + ids = append(ids, fk) + } + nodeids[fk] = append(nodeids[fk], nodes[i]) + } + query.Where(location.IDIn(ids...)) + neighbors, err := query.All(ctx) + if err != nil { + return err + } + for _, n := range neighbors { + nodes, ok := nodeids[n.ID] + if !ok { + return fmt.Errorf(`unexpected foreign-key "location_children" returned %v`, n.ID) + } + for i := range nodes { + assign(nodes[i], n) + } + } + return nil +} +func (lq *LocationQuery) loadChildren(ctx context.Context, query *LocationQuery, nodes []*Location, init func(*Location), assign func(*Location, *Location)) error { + fks := make([]driver.Value, 0, len(nodes)) + nodeids := make(map[uuid.UUID]*Location) + for i := range nodes { + fks = append(fks, nodes[i].ID) + nodeids[nodes[i].ID] = nodes[i] + if init != nil { + init(nodes[i]) + } + } + query.withFKs = true + query.Where(predicate.Location(func(s *sql.Selector) { + s.Where(sql.InValues(location.ChildrenColumn, fks...)) + })) + neighbors, err := query.All(ctx) + if err != nil { + return err + } + for _, n := range neighbors { + fk := n.location_children + if fk == nil { + return fmt.Errorf(`foreign-key "location_children" is nil for node %v`, n.ID) + } + node, ok := nodeids[*fk] + if !ok { + return fmt.Errorf(`unexpected foreign-key "location_children" returned %v for node %v`, *fk, n.ID) + } + assign(node, n) + } + return nil +} func (lq *LocationQuery) loadGroup(ctx context.Context, query *GroupQuery, nodes []*Location, init func(*Location), assign func(*Location, *Group)) error { ids := make([]uuid.UUID, 0, len(nodes)) nodeids := make(map[uuid.UUID][]*Location) diff --git a/backend/ent/location_update.go b/backend/ent/location_update.go index 064634c..785036a 100644 --- a/backend/ent/location_update.go +++ b/backend/ent/location_update.go @@ -63,6 +63,40 @@ func (lu *LocationUpdate) ClearDescription() *LocationUpdate { return lu } +// SetParentID sets the "parent" edge to the Location entity by ID. +func (lu *LocationUpdate) SetParentID(id uuid.UUID) *LocationUpdate { + lu.mutation.SetParentID(id) + return lu +} + +// SetNillableParentID sets the "parent" edge to the Location entity by ID if the given value is not nil. +func (lu *LocationUpdate) SetNillableParentID(id *uuid.UUID) *LocationUpdate { + if id != nil { + lu = lu.SetParentID(*id) + } + return lu +} + +// SetParent sets the "parent" edge to the Location entity. +func (lu *LocationUpdate) SetParent(l *Location) *LocationUpdate { + return lu.SetParentID(l.ID) +} + +// AddChildIDs adds the "children" edge to the Location entity by IDs. +func (lu *LocationUpdate) AddChildIDs(ids ...uuid.UUID) *LocationUpdate { + lu.mutation.AddChildIDs(ids...) + return lu +} + +// AddChildren adds the "children" edges to the Location entity. +func (lu *LocationUpdate) AddChildren(l ...*Location) *LocationUpdate { + ids := make([]uuid.UUID, len(l)) + for i := range l { + ids[i] = l[i].ID + } + return lu.AddChildIDs(ids...) +} + // SetGroupID sets the "group" edge to the Group entity by ID. func (lu *LocationUpdate) SetGroupID(id uuid.UUID) *LocationUpdate { lu.mutation.SetGroupID(id) @@ -94,6 +128,33 @@ func (lu *LocationUpdate) Mutation() *LocationMutation { return lu.mutation } +// ClearParent clears the "parent" edge to the Location entity. +func (lu *LocationUpdate) ClearParent() *LocationUpdate { + lu.mutation.ClearParent() + return lu +} + +// ClearChildren clears all "children" edges to the Location entity. +func (lu *LocationUpdate) ClearChildren() *LocationUpdate { + lu.mutation.ClearChildren() + return lu +} + +// RemoveChildIDs removes the "children" edge to Location entities by IDs. +func (lu *LocationUpdate) RemoveChildIDs(ids ...uuid.UUID) *LocationUpdate { + lu.mutation.RemoveChildIDs(ids...) + return lu +} + +// RemoveChildren removes "children" edges to Location entities. +func (lu *LocationUpdate) RemoveChildren(l ...*Location) *LocationUpdate { + ids := make([]uuid.UUID, len(l)) + for i := range l { + ids[i] = l[i].ID + } + return lu.RemoveChildIDs(ids...) +} + // ClearGroup clears the "group" edge to the Group entity. func (lu *LocationUpdate) ClearGroup() *LocationUpdate { lu.mutation.ClearGroup() @@ -253,6 +314,95 @@ func (lu *LocationUpdate) sqlSave(ctx context.Context) (n int, err error) { Column: location.FieldDescription, }) } + if lu.mutation.ParentCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.M2O, + Inverse: true, + Table: location.ParentTable, + Columns: []string{location.ParentColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: &sqlgraph.FieldSpec{ + Type: field.TypeUUID, + Column: location.FieldID, + }, + }, + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := lu.mutation.ParentIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.M2O, + Inverse: true, + Table: location.ParentTable, + Columns: []string{location.ParentColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: &sqlgraph.FieldSpec{ + Type: field.TypeUUID, + Column: location.FieldID, + }, + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Add = append(_spec.Edges.Add, edge) + } + if lu.mutation.ChildrenCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: false, + Table: location.ChildrenTable, + Columns: []string{location.ChildrenColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: &sqlgraph.FieldSpec{ + Type: field.TypeUUID, + Column: location.FieldID, + }, + }, + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := lu.mutation.RemovedChildrenIDs(); len(nodes) > 0 && !lu.mutation.ChildrenCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: false, + Table: location.ChildrenTable, + Columns: []string{location.ChildrenColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: &sqlgraph.FieldSpec{ + Type: field.TypeUUID, + Column: location.FieldID, + }, + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := lu.mutation.ChildrenIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: false, + Table: location.ChildrenTable, + Columns: []string{location.ChildrenColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: &sqlgraph.FieldSpec{ + Type: field.TypeUUID, + Column: location.FieldID, + }, + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Add = append(_spec.Edges.Add, edge) + } if lu.mutation.GroupCleared() { edge := &sqlgraph.EdgeSpec{ Rel: sqlgraph.M2O, @@ -393,6 +543,40 @@ func (luo *LocationUpdateOne) ClearDescription() *LocationUpdateOne { return luo } +// SetParentID sets the "parent" edge to the Location entity by ID. +func (luo *LocationUpdateOne) SetParentID(id uuid.UUID) *LocationUpdateOne { + luo.mutation.SetParentID(id) + return luo +} + +// SetNillableParentID sets the "parent" edge to the Location entity by ID if the given value is not nil. +func (luo *LocationUpdateOne) SetNillableParentID(id *uuid.UUID) *LocationUpdateOne { + if id != nil { + luo = luo.SetParentID(*id) + } + return luo +} + +// SetParent sets the "parent" edge to the Location entity. +func (luo *LocationUpdateOne) SetParent(l *Location) *LocationUpdateOne { + return luo.SetParentID(l.ID) +} + +// AddChildIDs adds the "children" edge to the Location entity by IDs. +func (luo *LocationUpdateOne) AddChildIDs(ids ...uuid.UUID) *LocationUpdateOne { + luo.mutation.AddChildIDs(ids...) + return luo +} + +// AddChildren adds the "children" edges to the Location entity. +func (luo *LocationUpdateOne) AddChildren(l ...*Location) *LocationUpdateOne { + ids := make([]uuid.UUID, len(l)) + for i := range l { + ids[i] = l[i].ID + } + return luo.AddChildIDs(ids...) +} + // SetGroupID sets the "group" edge to the Group entity by ID. func (luo *LocationUpdateOne) SetGroupID(id uuid.UUID) *LocationUpdateOne { luo.mutation.SetGroupID(id) @@ -424,6 +608,33 @@ func (luo *LocationUpdateOne) Mutation() *LocationMutation { return luo.mutation } +// ClearParent clears the "parent" edge to the Location entity. +func (luo *LocationUpdateOne) ClearParent() *LocationUpdateOne { + luo.mutation.ClearParent() + return luo +} + +// ClearChildren clears all "children" edges to the Location entity. +func (luo *LocationUpdateOne) ClearChildren() *LocationUpdateOne { + luo.mutation.ClearChildren() + return luo +} + +// RemoveChildIDs removes the "children" edge to Location entities by IDs. +func (luo *LocationUpdateOne) RemoveChildIDs(ids ...uuid.UUID) *LocationUpdateOne { + luo.mutation.RemoveChildIDs(ids...) + return luo +} + +// RemoveChildren removes "children" edges to Location entities. +func (luo *LocationUpdateOne) RemoveChildren(l ...*Location) *LocationUpdateOne { + ids := make([]uuid.UUID, len(l)) + for i := range l { + ids[i] = l[i].ID + } + return luo.RemoveChildIDs(ids...) +} + // ClearGroup clears the "group" edge to the Group entity. func (luo *LocationUpdateOne) ClearGroup() *LocationUpdateOne { luo.mutation.ClearGroup() @@ -613,6 +824,95 @@ func (luo *LocationUpdateOne) sqlSave(ctx context.Context) (_node *Location, err Column: location.FieldDescription, }) } + if luo.mutation.ParentCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.M2O, + Inverse: true, + Table: location.ParentTable, + Columns: []string{location.ParentColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: &sqlgraph.FieldSpec{ + Type: field.TypeUUID, + Column: location.FieldID, + }, + }, + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := luo.mutation.ParentIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.M2O, + Inverse: true, + Table: location.ParentTable, + Columns: []string{location.ParentColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: &sqlgraph.FieldSpec{ + Type: field.TypeUUID, + Column: location.FieldID, + }, + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Add = append(_spec.Edges.Add, edge) + } + if luo.mutation.ChildrenCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: false, + Table: location.ChildrenTable, + Columns: []string{location.ChildrenColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: &sqlgraph.FieldSpec{ + Type: field.TypeUUID, + Column: location.FieldID, + }, + }, + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := luo.mutation.RemovedChildrenIDs(); len(nodes) > 0 && !luo.mutation.ChildrenCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: false, + Table: location.ChildrenTable, + Columns: []string{location.ChildrenColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: &sqlgraph.FieldSpec{ + Type: field.TypeUUID, + Column: location.FieldID, + }, + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := luo.mutation.ChildrenIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: false, + Table: location.ChildrenTable, + Columns: []string{location.ChildrenColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: &sqlgraph.FieldSpec{ + Type: field.TypeUUID, + Column: location.FieldID, + }, + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Add = append(_spec.Edges.Add, edge) + } if luo.mutation.GroupCleared() { edge := &sqlgraph.EdgeSpec{ Rel: sqlgraph.M2O, diff --git a/backend/ent/migrate/schema.go b/backend/ent/migrate/schema.go index 901b3bd..5aa35b5 100644 --- a/backend/ent/migrate/schema.go +++ b/backend/ent/migrate/schema.go @@ -184,6 +184,7 @@ var ( {Name: "sold_price", Type: field.TypeFloat64, Default: 0}, {Name: "sold_notes", Type: field.TypeString, Nullable: true, Size: 1000}, {Name: "group_items", Type: field.TypeUUID}, + {Name: "item_children", Type: field.TypeUUID, Nullable: true}, {Name: "location_items", Type: field.TypeUUID, Nullable: true}, } // ItemsTable holds the schema information for the "items" table. @@ -199,8 +200,14 @@ var ( OnDelete: schema.Cascade, }, { - Symbol: "items_locations_items", + Symbol: "items_items_children", Columns: []*schema.Column{ItemsColumns[23]}, + RefColumns: []*schema.Column{ItemsColumns[0]}, + OnDelete: schema.SetNull, + }, + { + Symbol: "items_locations_items", + Columns: []*schema.Column{ItemsColumns[24]}, RefColumns: []*schema.Column{LocationsColumns[0]}, OnDelete: schema.Cascade, }, @@ -288,6 +295,7 @@ var ( {Name: "name", Type: field.TypeString, Size: 255}, {Name: "description", Type: field.TypeString, Nullable: true, Size: 1000}, {Name: "group_locations", Type: field.TypeUUID}, + {Name: "location_children", Type: field.TypeUUID, Nullable: true}, } // LocationsTable holds the schema information for the "locations" table. LocationsTable = &schema.Table{ @@ -301,6 +309,12 @@ var ( RefColumns: []*schema.Column{GroupsColumns[0]}, OnDelete: schema.Cascade, }, + { + Symbol: "locations_locations_children", + Columns: []*schema.Column{LocationsColumns[6]}, + RefColumns: []*schema.Column{LocationsColumns[0]}, + OnDelete: schema.SetNull, + }, }, } // UsersColumns holds the columns for the "users" table. @@ -381,10 +395,12 @@ func init() { DocumentTokensTable.ForeignKeys[0].RefTable = DocumentsTable GroupInvitationTokensTable.ForeignKeys[0].RefTable = GroupsTable ItemsTable.ForeignKeys[0].RefTable = GroupsTable - ItemsTable.ForeignKeys[1].RefTable = LocationsTable + ItemsTable.ForeignKeys[1].RefTable = ItemsTable + ItemsTable.ForeignKeys[2].RefTable = LocationsTable ItemFieldsTable.ForeignKeys[0].RefTable = ItemsTable LabelsTable.ForeignKeys[0].RefTable = GroupsTable LocationsTable.ForeignKeys[0].RefTable = GroupsTable + LocationsTable.ForeignKeys[1].RefTable = LocationsTable UsersTable.ForeignKeys[0].RefTable = GroupsTable LabelItemsTable.ForeignKeys[0].RefTable = LabelsTable LabelItemsTable.ForeignKeys[1].RefTable = ItemsTable diff --git a/backend/ent/mutation.go b/backend/ent/mutation.go index 9155ee6..eb2e853 100644 --- a/backend/ent/mutation.go +++ b/backend/ent/mutation.go @@ -4149,6 +4149,11 @@ type ItemMutation struct { addsold_price *float64 sold_notes *string clearedFields map[string]struct{} + parent *uuid.UUID + clearedparent bool + children map[uuid.UUID]struct{} + removedchildren map[uuid.UUID]struct{} + clearedchildren bool group *uuid.UUID clearedgroup bool label map[uuid.UUID]struct{} @@ -5256,6 +5261,99 @@ func (m *ItemMutation) ResetSoldNotes() { delete(m.clearedFields, item.FieldSoldNotes) } +// SetParentID sets the "parent" edge to the Item entity by id. +func (m *ItemMutation) SetParentID(id uuid.UUID) { + m.parent = &id +} + +// ClearParent clears the "parent" edge to the Item entity. +func (m *ItemMutation) ClearParent() { + m.clearedparent = true +} + +// ParentCleared reports if the "parent" edge to the Item entity was cleared. +func (m *ItemMutation) ParentCleared() bool { + return m.clearedparent +} + +// ParentID returns the "parent" edge ID in the mutation. +func (m *ItemMutation) ParentID() (id uuid.UUID, exists bool) { + if m.parent != nil { + return *m.parent, true + } + return +} + +// ParentIDs returns the "parent" edge IDs in the mutation. +// Note that IDs always returns len(IDs) <= 1 for unique edges, and you should use +// ParentID instead. It exists only for internal usage by the builders. +func (m *ItemMutation) ParentIDs() (ids []uuid.UUID) { + if id := m.parent; id != nil { + ids = append(ids, *id) + } + return +} + +// ResetParent resets all changes to the "parent" edge. +func (m *ItemMutation) ResetParent() { + m.parent = nil + m.clearedparent = false +} + +// AddChildIDs adds the "children" edge to the Item entity by ids. +func (m *ItemMutation) AddChildIDs(ids ...uuid.UUID) { + if m.children == nil { + m.children = make(map[uuid.UUID]struct{}) + } + for i := range ids { + m.children[ids[i]] = struct{}{} + } +} + +// ClearChildren clears the "children" edge to the Item entity. +func (m *ItemMutation) ClearChildren() { + m.clearedchildren = true +} + +// ChildrenCleared reports if the "children" edge to the Item entity was cleared. +func (m *ItemMutation) ChildrenCleared() bool { + return m.clearedchildren +} + +// RemoveChildIDs removes the "children" edge to the Item entity by IDs. +func (m *ItemMutation) RemoveChildIDs(ids ...uuid.UUID) { + if m.removedchildren == nil { + m.removedchildren = make(map[uuid.UUID]struct{}) + } + for i := range ids { + delete(m.children, ids[i]) + m.removedchildren[ids[i]] = struct{}{} + } +} + +// RemovedChildren returns the removed IDs of the "children" edge to the Item entity. +func (m *ItemMutation) RemovedChildrenIDs() (ids []uuid.UUID) { + for id := range m.removedchildren { + ids = append(ids, id) + } + return +} + +// ChildrenIDs returns the "children" edge IDs in the mutation. +func (m *ItemMutation) ChildrenIDs() (ids []uuid.UUID) { + for id := range m.children { + ids = append(ids, id) + } + return +} + +// ResetChildren resets all changes to the "children" edge. +func (m *ItemMutation) ResetChildren() { + m.children = nil + m.clearedchildren = false + m.removedchildren = nil +} + // SetGroupID sets the "group" edge to the Group entity by id. func (m *ItemMutation) SetGroupID(id uuid.UUID) { m.group = &id @@ -6074,7 +6172,13 @@ func (m *ItemMutation) ResetField(name string) error { // AddedEdges returns all edge names that were set/added in this mutation. func (m *ItemMutation) AddedEdges() []string { - edges := make([]string, 0, 5) + edges := make([]string, 0, 7) + if m.parent != nil { + edges = append(edges, item.EdgeParent) + } + if m.children != nil { + edges = append(edges, item.EdgeChildren) + } if m.group != nil { edges = append(edges, item.EdgeGroup) } @@ -6097,6 +6201,16 @@ func (m *ItemMutation) AddedEdges() []string { // name in this mutation. func (m *ItemMutation) AddedIDs(name string) []ent.Value { switch name { + case item.EdgeParent: + if id := m.parent; id != nil { + return []ent.Value{*id} + } + case item.EdgeChildren: + ids := make([]ent.Value, 0, len(m.children)) + for id := range m.children { + ids = append(ids, id) + } + return ids case item.EdgeGroup: if id := m.group; id != nil { return []ent.Value{*id} @@ -6129,7 +6243,10 @@ func (m *ItemMutation) AddedIDs(name string) []ent.Value { // RemovedEdges returns all edge names that were removed in this mutation. func (m *ItemMutation) RemovedEdges() []string { - edges := make([]string, 0, 5) + edges := make([]string, 0, 7) + if m.removedchildren != nil { + edges = append(edges, item.EdgeChildren) + } if m.removedlabel != nil { edges = append(edges, item.EdgeLabel) } @@ -6146,6 +6263,12 @@ func (m *ItemMutation) RemovedEdges() []string { // the given name in this mutation. func (m *ItemMutation) RemovedIDs(name string) []ent.Value { switch name { + case item.EdgeChildren: + ids := make([]ent.Value, 0, len(m.removedchildren)) + for id := range m.removedchildren { + ids = append(ids, id) + } + return ids case item.EdgeLabel: ids := make([]ent.Value, 0, len(m.removedlabel)) for id := range m.removedlabel { @@ -6170,7 +6293,13 @@ func (m *ItemMutation) RemovedIDs(name string) []ent.Value { // ClearedEdges returns all edge names that were cleared in this mutation. func (m *ItemMutation) ClearedEdges() []string { - edges := make([]string, 0, 5) + edges := make([]string, 0, 7) + if m.clearedparent { + edges = append(edges, item.EdgeParent) + } + if m.clearedchildren { + edges = append(edges, item.EdgeChildren) + } if m.clearedgroup { edges = append(edges, item.EdgeGroup) } @@ -6193,6 +6322,10 @@ func (m *ItemMutation) ClearedEdges() []string { // was cleared in this mutation. func (m *ItemMutation) EdgeCleared(name string) bool { switch name { + case item.EdgeParent: + return m.clearedparent + case item.EdgeChildren: + return m.clearedchildren case item.EdgeGroup: return m.clearedgroup case item.EdgeLabel: @@ -6211,6 +6344,9 @@ func (m *ItemMutation) EdgeCleared(name string) bool { // if that edge is not defined in the schema. func (m *ItemMutation) ClearEdge(name string) error { switch name { + case item.EdgeParent: + m.ClearParent() + return nil case item.EdgeGroup: m.ClearGroup() return nil @@ -6225,6 +6361,12 @@ func (m *ItemMutation) ClearEdge(name string) error { // It returns an error if the edge is not defined in the schema. func (m *ItemMutation) ResetEdge(name string) error { switch name { + case item.EdgeParent: + m.ResetParent() + return nil + case item.EdgeChildren: + m.ResetChildren() + return nil case item.EdgeGroup: m.ResetGroup() return nil @@ -7886,22 +8028,27 @@ func (m *LabelMutation) ResetEdge(name string) error { // LocationMutation represents an operation that mutates the Location nodes in the graph. type LocationMutation struct { config - op Op - typ string - id *uuid.UUID - created_at *time.Time - updated_at *time.Time - name *string - description *string - clearedFields map[string]struct{} - group *uuid.UUID - clearedgroup bool - items map[uuid.UUID]struct{} - removeditems map[uuid.UUID]struct{} - cleareditems bool - done bool - oldValue func(context.Context) (*Location, error) - predicates []predicate.Location + op Op + typ string + id *uuid.UUID + created_at *time.Time + updated_at *time.Time + name *string + description *string + clearedFields map[string]struct{} + parent *uuid.UUID + clearedparent bool + children map[uuid.UUID]struct{} + removedchildren map[uuid.UUID]struct{} + clearedchildren bool + group *uuid.UUID + clearedgroup bool + items map[uuid.UUID]struct{} + removeditems map[uuid.UUID]struct{} + cleareditems bool + done bool + oldValue func(context.Context) (*Location, error) + predicates []predicate.Location } var _ ent.Mutation = (*LocationMutation)(nil) @@ -8165,6 +8312,99 @@ func (m *LocationMutation) ResetDescription() { delete(m.clearedFields, location.FieldDescription) } +// SetParentID sets the "parent" edge to the Location entity by id. +func (m *LocationMutation) SetParentID(id uuid.UUID) { + m.parent = &id +} + +// ClearParent clears the "parent" edge to the Location entity. +func (m *LocationMutation) ClearParent() { + m.clearedparent = true +} + +// ParentCleared reports if the "parent" edge to the Location entity was cleared. +func (m *LocationMutation) ParentCleared() bool { + return m.clearedparent +} + +// ParentID returns the "parent" edge ID in the mutation. +func (m *LocationMutation) ParentID() (id uuid.UUID, exists bool) { + if m.parent != nil { + return *m.parent, true + } + return +} + +// ParentIDs returns the "parent" edge IDs in the mutation. +// Note that IDs always returns len(IDs) <= 1 for unique edges, and you should use +// ParentID instead. It exists only for internal usage by the builders. +func (m *LocationMutation) ParentIDs() (ids []uuid.UUID) { + if id := m.parent; id != nil { + ids = append(ids, *id) + } + return +} + +// ResetParent resets all changes to the "parent" edge. +func (m *LocationMutation) ResetParent() { + m.parent = nil + m.clearedparent = false +} + +// AddChildIDs adds the "children" edge to the Location entity by ids. +func (m *LocationMutation) AddChildIDs(ids ...uuid.UUID) { + if m.children == nil { + m.children = make(map[uuid.UUID]struct{}) + } + for i := range ids { + m.children[ids[i]] = struct{}{} + } +} + +// ClearChildren clears the "children" edge to the Location entity. +func (m *LocationMutation) ClearChildren() { + m.clearedchildren = true +} + +// ChildrenCleared reports if the "children" edge to the Location entity was cleared. +func (m *LocationMutation) ChildrenCleared() bool { + return m.clearedchildren +} + +// RemoveChildIDs removes the "children" edge to the Location entity by IDs. +func (m *LocationMutation) RemoveChildIDs(ids ...uuid.UUID) { + if m.removedchildren == nil { + m.removedchildren = make(map[uuid.UUID]struct{}) + } + for i := range ids { + delete(m.children, ids[i]) + m.removedchildren[ids[i]] = struct{}{} + } +} + +// RemovedChildren returns the removed IDs of the "children" edge to the Location entity. +func (m *LocationMutation) RemovedChildrenIDs() (ids []uuid.UUID) { + for id := range m.removedchildren { + ids = append(ids, id) + } + return +} + +// ChildrenIDs returns the "children" edge IDs in the mutation. +func (m *LocationMutation) ChildrenIDs() (ids []uuid.UUID) { + for id := range m.children { + ids = append(ids, id) + } + return +} + +// ResetChildren resets all changes to the "children" edge. +func (m *LocationMutation) ResetChildren() { + m.children = nil + m.clearedchildren = false + m.removedchildren = nil +} + // SetGroupID sets the "group" edge to the Group entity by id. func (m *LocationMutation) SetGroupID(id uuid.UUID) { m.group = &id @@ -8436,7 +8676,13 @@ func (m *LocationMutation) ResetField(name string) error { // AddedEdges returns all edge names that were set/added in this mutation. func (m *LocationMutation) AddedEdges() []string { - edges := make([]string, 0, 2) + edges := make([]string, 0, 4) + if m.parent != nil { + edges = append(edges, location.EdgeParent) + } + if m.children != nil { + edges = append(edges, location.EdgeChildren) + } if m.group != nil { edges = append(edges, location.EdgeGroup) } @@ -8450,6 +8696,16 @@ func (m *LocationMutation) AddedEdges() []string { // name in this mutation. func (m *LocationMutation) AddedIDs(name string) []ent.Value { switch name { + case location.EdgeParent: + if id := m.parent; id != nil { + return []ent.Value{*id} + } + case location.EdgeChildren: + ids := make([]ent.Value, 0, len(m.children)) + for id := range m.children { + ids = append(ids, id) + } + return ids case location.EdgeGroup: if id := m.group; id != nil { return []ent.Value{*id} @@ -8466,7 +8722,10 @@ func (m *LocationMutation) AddedIDs(name string) []ent.Value { // RemovedEdges returns all edge names that were removed in this mutation. func (m *LocationMutation) RemovedEdges() []string { - edges := make([]string, 0, 2) + edges := make([]string, 0, 4) + if m.removedchildren != nil { + edges = append(edges, location.EdgeChildren) + } if m.removeditems != nil { edges = append(edges, location.EdgeItems) } @@ -8477,6 +8736,12 @@ func (m *LocationMutation) RemovedEdges() []string { // the given name in this mutation. func (m *LocationMutation) RemovedIDs(name string) []ent.Value { switch name { + case location.EdgeChildren: + ids := make([]ent.Value, 0, len(m.removedchildren)) + for id := range m.removedchildren { + ids = append(ids, id) + } + return ids case location.EdgeItems: ids := make([]ent.Value, 0, len(m.removeditems)) for id := range m.removeditems { @@ -8489,7 +8754,13 @@ func (m *LocationMutation) RemovedIDs(name string) []ent.Value { // ClearedEdges returns all edge names that were cleared in this mutation. func (m *LocationMutation) ClearedEdges() []string { - edges := make([]string, 0, 2) + edges := make([]string, 0, 4) + if m.clearedparent { + edges = append(edges, location.EdgeParent) + } + if m.clearedchildren { + edges = append(edges, location.EdgeChildren) + } if m.clearedgroup { edges = append(edges, location.EdgeGroup) } @@ -8503,6 +8774,10 @@ func (m *LocationMutation) ClearedEdges() []string { // was cleared in this mutation. func (m *LocationMutation) EdgeCleared(name string) bool { switch name { + case location.EdgeParent: + return m.clearedparent + case location.EdgeChildren: + return m.clearedchildren case location.EdgeGroup: return m.clearedgroup case location.EdgeItems: @@ -8515,6 +8790,9 @@ func (m *LocationMutation) EdgeCleared(name string) bool { // if that edge is not defined in the schema. func (m *LocationMutation) ClearEdge(name string) error { switch name { + case location.EdgeParent: + m.ClearParent() + return nil case location.EdgeGroup: m.ClearGroup() return nil @@ -8526,6 +8804,12 @@ func (m *LocationMutation) ClearEdge(name string) error { // It returns an error if the edge is not defined in the schema. func (m *LocationMutation) ResetEdge(name string) error { switch name { + case location.EdgeParent: + m.ResetParent() + return nil + case location.EdgeChildren: + m.ResetChildren() + return nil case location.EdgeGroup: m.ResetGroup() return nil diff --git a/backend/ent/schema/item.go b/backend/ent/schema/item.go index b9fcd9a..25531d0 100644 --- a/backend/ent/schema/item.go +++ b/backend/ent/schema/item.go @@ -94,6 +94,9 @@ func (Item) Fields() []ent.Field { // Edges of the Item. func (Item) Edges() []ent.Edge { return []ent.Edge{ + edge.To("children", Item.Type). + From("parent"). + Unique(), edge.From("group", Group.Type). Ref("items"). Required(). diff --git a/backend/ent/schema/location.go b/backend/ent/schema/location.go index 74e0abb..e8f2237 100644 --- a/backend/ent/schema/location.go +++ b/backend/ent/schema/location.go @@ -27,6 +27,9 @@ func (Location) Fields() []ent.Field { // Edges of the Location. func (Location) Edges() []ent.Edge { return []ent.Edge{ + edge.To("children", Location.Type). + From("parent"). + Unique(), edge.From("group", Group.Type). Ref("locations"). Unique(). diff --git a/backend/go.sum b/backend/go.sum index 3018171..3c11bcb 100644 --- a/backend/go.sum +++ b/backend/go.sum @@ -61,11 +61,13 @@ github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovk github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0= github.com/mattn/go-sqlite3 v1.14.15 h1:vfoHhTN1af61xCRSWzFIWzx2YskyMTwHLrExkBOjvxI= github.com/mattn/go-sqlite3 v1.14.15/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -75,6 +77,8 @@ github.com/rs/xid v1.4.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= github.com/rs/zerolog v1.28.0 h1:MirSo27VyNi7RJYP3078AA1+Cyzd2GB66qy3aUHvsWY= github.com/rs/zerolog v1.28.0/go.mod h1:NILgTygv/Uej1ra5XxGf82ZFSLk58MFGAUS2o6usyD0= github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ= +github.com/spf13/cobra v1.5.0 h1:X+jTBEBqF0bHN+9cSMgmfuvv2VHJ9ezmFNf9Y/XstYU= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= diff --git a/backend/internal/migrations/migrations/20221020043305_allow_nesting_types.sql b/backend/internal/migrations/migrations/20221020043305_allow_nesting_types.sql new file mode 100644 index 0000000..7f24658 --- /dev/null +++ b/backend/internal/migrations/migrations/20221020043305_allow_nesting_types.sql @@ -0,0 +1,28 @@ +-- 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, `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`, `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`, `location_items`) SELECT `id`, `created_at`, `updated_at`, `name`, `description`, `import_ref`, `notes`, `quantity`, `insured`, `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`, `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 "new_locations" table +CREATE TABLE `new_locations` (`id` uuid NOT NULL, `created_at` datetime NOT NULL, `updated_at` datetime NOT NULL, `name` text NOT NULL, `description` text NULL, `group_locations` uuid NOT NULL, `location_children` uuid NULL, PRIMARY KEY (`id`), CONSTRAINT `locations_groups_locations` FOREIGN KEY (`group_locations`) REFERENCES `groups` (`id`) ON DELETE CASCADE, CONSTRAINT `locations_locations_children` FOREIGN KEY (`location_children`) REFERENCES `locations` (`id`) ON DELETE SET NULL); +-- copy rows from old table "locations" to new temporary table "new_locations" +INSERT INTO `new_locations` (`id`, `created_at`, `updated_at`, `name`, `description`, `group_locations`) SELECT `id`, `created_at`, `updated_at`, `name`, `description`, `group_locations` FROM `locations`; +-- drop "locations" table after copying rows +DROP TABLE `locations`; +-- rename temporary table "new_locations" to "locations" +ALTER TABLE `new_locations` RENAME TO `locations`; +-- enable back the enforcement of foreign-keys constraints +PRAGMA foreign_keys = on; diff --git a/backend/internal/migrations/migrations/atlas.sum b/backend/internal/migrations/migrations/atlas.sum index 0bc29a5..04a34fb 100644 --- a/backend/internal/migrations/migrations/atlas.sum +++ b/backend/internal/migrations/migrations/atlas.sum @@ -1,4 +1,5 @@ -h1:nN8ZUHScToap+2yJKsb+AnKu8o2DubUoiKc9neQJ93M= +h1:mYTnmyrnBDST/r93NGJM33mIJqhp/U9qR440zI99eqQ= 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= diff --git a/backend/internal/repo/repo_items.go b/backend/internal/repo/repo_items.go index c7ca5e6..27ddc4c 100644 --- a/backend/internal/repo/repo_items.go +++ b/backend/internal/repo/repo_items.go @@ -39,15 +39,17 @@ type ( } ItemCreate struct { - ImportRef string `json:"-"` - Name string `json:"name"` - Description string `json:"description"` + ImportRef string `json:"-"` + ParentID uuid.UUID `json:"parentId" extensions:"x-nullable"` + Name string `json:"name"` + Description string `json:"description"` // Edges LocationID uuid.UUID `json:"locationId"` LabelIDs []uuid.UUID `json:"labelIds"` } ItemUpdate struct { + ParentID uuid.UUID `json:"parentId" extensions:"x-nullable,x-omitempty"` ID uuid.UUID `json:"id"` Name string `json:"name"` Description string `json:"description"` @@ -95,11 +97,12 @@ type ( UpdatedAt time.Time `json:"updatedAt"` // Edges - Location LocationSummary `json:"location"` - Labels []LabelSummary `json:"labels"` + Location *LocationSummary `json:"location,omitempty" extensions:"x-nullable,x-omitempty"` + Labels []LabelSummary `json:"labels"` } ItemOut struct { + Parent *ItemSummary `json:"parent,omitempty" extensions:"x-nullable,x-omitempty"` ItemSummary SerialNumber string `json:"serialNumber"` @@ -126,8 +129,8 @@ type ( Notes string `json:"notes"` Attachments []ItemAttachment `json:"attachments"` - // Future - Fields []ItemField `json:"fields"` + Fields []ItemField `json:"fields"` + Children []ItemSummary `json:"children"` } ) @@ -136,12 +139,13 @@ var ( ) func mapItemSummary(item *ent.Item) ItemSummary { - var location LocationSummary + var location *LocationSummary if item.Edges.Location != nil { - location = mapLocationSummary(item.Edges.Location) + loc := mapLocationSummary(item.Edges.Location) + location = &loc } - var labels []LabelSummary + labels := make([]LabelSummary, len(item.Edges.Label)) if item.Edges.Label != nil { labels = mapEach(item.Edges.Label, mapLabelSummary) } @@ -194,7 +198,19 @@ func mapItemOut(item *ent.Item) ItemOut { fields = mapFields(item.Edges.Fields) } + var children []ItemSummary + if item.Edges.Children != nil { + children = mapEach(item.Edges.Children, mapItemSummary) + } + + var parent *ItemSummary + if item.Edges.Parent != nil { + v := mapItemSummary(item.Edges.Parent) + parent = &v + } + return ItemOut{ + Parent: parent, ItemSummary: mapItemSummary(item), LifetimeWarranty: item.LifetimeWarranty, WarrantyExpires: item.WarrantyExpires, @@ -220,6 +236,7 @@ func mapItemOut(item *ent.Item) ItemOut { Notes: item.Notes, Attachments: attachments, Fields: fields, + Children: children, } } @@ -231,6 +248,8 @@ func (e *ItemsRepository) getOne(ctx context.Context, where ...predicate.Item) ( WithLabel(). WithLocation(). WithGroup(). + WithChildren(). + WithParent(). WithAttachments(func(aq *ent.AttachmentQuery) { aq.WithDocument() }). @@ -398,6 +417,12 @@ func (e *ItemsRepository) UpdateByGroup(ctx context.Context, gid uuid.UUID, data q.RemoveLabelIDs(set.Slice()...) } + if data.ParentID != uuid.Nil { + q.SetParentID(data.ParentID) + } else { + q.ClearParent() + } + err = q.Exec(ctx) if err != nil { return ItemOut{}, err diff --git a/backend/internal/repo/repo_items_test.go b/backend/internal/repo/repo_items_test.go index f600ad1..4b958b0 100644 --- a/backend/internal/repo/repo_items_test.go +++ b/backend/internal/repo/repo_items_test.go @@ -41,6 +41,42 @@ func useItems(t *testing.T, len int) []ItemOut { return items } +func TestItemsRepository_RecursiveRelationships(t *testing.T) { + parent := useItems(t, 1)[0] + + children := useItems(t, 3) + + for _, child := range children { + update := ItemUpdate{ + ID: child.ID, + ParentID: parent.ID, + Name: "note-important", + Description: "This is a note", + LocationID: child.Location.ID, + } + + // Append Parent ID + _, err := tRepos.Items.UpdateByGroup(context.Background(), tGroup.ID, update) + assert.NoError(t, err) + + // Check Parent ID + updated, err := tRepos.Items.GetOne(context.Background(), child.ID) + assert.NoError(t, err) + assert.Equal(t, parent.ID, updated.Parent.ID) + + // Remove Parent ID + update.ParentID = uuid.Nil + _, err = tRepos.Items.UpdateByGroup(context.Background(), tGroup.ID, update) + assert.NoError(t, err) + + // Check Parent ID + updated, err = tRepos.Items.GetOne(context.Background(), child.ID) + assert.NoError(t, err) + assert.Nil(t, updated.Parent) + + } +} + func TestItemsRepository_GetOne(t *testing.T) { entity := useItems(t, 3) diff --git a/backend/internal/repo/repo_locations.go b/backend/internal/repo/repo_locations.go index 8a60202..f927431 100644 --- a/backend/internal/repo/repo_locations.go +++ b/backend/internal/repo/repo_locations.go @@ -22,6 +22,7 @@ type ( } LocationUpdate struct { + ParentID uuid.UUID `json:"parentId" extensions:"x-nullable"` ID uuid.UUID `json:"id"` Name string `json:"name"` Description string `json:"description"` @@ -41,8 +42,10 @@ type ( } LocationOut struct { + Parent *LocationSummary `json:"parent,omitempty"` LocationSummary - Items []ItemSummary `json:"items"` + Items []ItemSummary `json:"items"` + Children []LocationSummary `json:"children"` } ) @@ -61,7 +64,20 @@ var ( ) func mapLocationOut(location *ent.Location) LocationOut { + var parent *LocationSummary + if location.Edges.Parent != nil { + p := mapLocationSummary(location.Edges.Parent) + parent = &p + } + + children := make([]LocationSummary, 0, len(location.Edges.Children)) + for _, c := range location.Edges.Children { + children = append(children, mapLocationSummary(c)) + } + return LocationOut{ + Parent: parent, + Children: children, LocationSummary: LocationSummary{ ID: location.ID, Name: location.Name, @@ -125,6 +141,8 @@ func (r *LocationRepository) getOne(ctx context.Context, where ...predicate.Loca WithItems(func(iq *ent.ItemQuery) { iq.WithLabel() }). + WithParent(). + WithChildren(). Only(ctx)) } @@ -152,10 +170,17 @@ func (r *LocationRepository) Create(ctx context.Context, gid uuid.UUID, data Loc } func (r *LocationRepository) Update(ctx context.Context, data LocationUpdate) (LocationOut, error) { - _, err := r.db.Location.UpdateOneID(data.ID). + q := r.db.Location.UpdateOneID(data.ID). SetName(data.Name). - SetDescription(data.Description). - Save(ctx) + SetDescription(data.Description) + + if data.ParentID != uuid.Nil { + q.SetParentID(data.ParentID) + } else { + q.ClearParent() + } + + _, err := q.Save(ctx) if err != nil { return LocationOut{}, err diff --git a/frontend/components/Base/Button.vue b/frontend/components/Base/Button.vue index d2b8541..1fcb2f2 100644 --- a/frontend/components/Base/Button.vue +++ b/frontend/components/Base/Button.vue @@ -3,7 +3,6 @@ v-if="to" v-bind="attributes" ref="submitBtn" - :to="to" class="btn" :class="{ loading: loading, @@ -64,7 +63,7 @@ const attributes = computed(() => { if (props.to) { return { - href: props.to, + to: props.to, }; } return { diff --git a/frontend/components/Form/Autocomplete.vue b/frontend/components/Form/Autocomplete.vue new file mode 100644 index 0000000..4a43dff --- /dev/null +++ b/frontend/components/Form/Autocomplete.vue @@ -0,0 +1,149 @@ + + + diff --git a/frontend/components/Form/Select.vue b/frontend/components/Form/Select.vue index e3616e5..09faed9 100644 --- a/frontend/components/Form/Select.vue +++ b/frontend/components/Form/Select.vue @@ -45,6 +45,10 @@ type: String, default: "", }, + compareKey: { + type: String, + default: null, + }, }); const selectedIdx = ref(-1); @@ -52,36 +56,34 @@ const internalSelected = useVModel(props, "modelValue", emit); const internalValue = useVModel(props, "value", emit); - watch(selectedIdx, newVal => { - internalSelected.value = props.items[newVal]; - }); - - watch(selectedIdx, newVal => { - if (props.valueKey) { - internalValue.value = props.items[newVal][props.valueKey]; - } - }); - watch( - internalSelected, - () => { - const idx = props.items.findIndex(item => compare(item, internalSelected.value)); - selectedIdx.value = idx; + selectedIdx, + newVal => { + if (newVal === -1) { + return; + } + + if (props.value) { + internalValue.value = props.items[newVal][props.valueKey]; + } + + internalSelected.value = props.items[newVal]; }, - { - immediate: true, - } + { immediate: true } ); watch( - internalValue, + [internalSelected, () => props.value], () => { - const idx = props.items.findIndex(item => compare(item[props.valueKey], internalValue.value)); + if (props.valueKey) { + const idx = props.items.findIndex(item => compare(item, internalValue.value)); + selectedIdx.value = idx; + return; + } + const idx = props.items.findIndex(item => compare(item, internalSelected.value)); selectedIdx.value = idx; }, - { - immediate: true, - } + { immediate: true } ); // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -90,8 +92,13 @@ return true; } - if (!a || !b) { - return false; + if (props.valueKey) { + return a[props.valueKey] === b; + } + + // Try compare key + if (props.compareKey && a && b) { + return a[props.compareKey] === b[props.compareKey]; } return JSON.stringify(a) === JSON.stringify(b); diff --git a/frontend/components/Location/Card.vue b/frontend/components/Location/Card.vue index 56b36a2..bb5f1f7 100644 --- a/frontend/components/Location/Card.vue +++ b/frontend/components/Location/Card.vue @@ -17,20 +17,18 @@ {{ location.name }} - - {{ location.itemCount }} + {{ count }} diff --git a/frontend/pages/location/[id].vue b/frontend/pages/location/[id].vue index a8c97ab..092ace3 100644 --- a/frontend/pages/location/[id].vue +++ b/frontend/pages/location/[id].vue @@ -1,5 +1,7 @@ @@ -152,11 +177,18 @@ -
+
Items
+ +
+ Child Locations +
+ +
+