diff --git a/app/controlplane/pkg/data/ent/membership.go b/app/controlplane/pkg/data/ent/membership.go index 871176a99..a72097666 100644 --- a/app/controlplane/pkg/data/ent/membership.go +++ b/app/controlplane/pkg/data/ent/membership.go @@ -37,12 +37,14 @@ type Membership struct { ResourceType authz.ResourceType `json:"resource_type,omitempty"` // ResourceID holds the value of the "resource_id" field. ResourceID uuid.UUID `json:"resource_id,omitempty"` + // OrganizationID holds the value of the "organization_id" field. + OrganizationID uuid.UUID `json:"organization_id,omitempty"` + // UserID holds the value of the "user_id" field. + UserID uuid.UUID `json:"user_id,omitempty"` // Edges holds the relations/edges for other nodes in the graph. // The values are being populated by the MembershipQuery when eager-loading is set. - Edges MembershipEdges `json:"edges"` - organization_memberships *uuid.UUID - user_memberships *uuid.UUID - selectValues sql.SelectValues + Edges MembershipEdges `json:"edges"` + selectValues sql.SelectValues } // MembershipEdges holds the relations/edges for other nodes in the graph. @@ -89,12 +91,8 @@ func (*Membership) scanValues(columns []string) ([]any, error) { values[i] = new(sql.NullString) case membership.FieldCreatedAt, membership.FieldUpdatedAt: values[i] = new(sql.NullTime) - case membership.FieldID, membership.FieldMemberID, membership.FieldResourceID: + case membership.FieldID, membership.FieldMemberID, membership.FieldResourceID, membership.FieldOrganizationID, membership.FieldUserID: values[i] = new(uuid.UUID) - case membership.ForeignKeys[0]: // organization_memberships - values[i] = &sql.NullScanner{S: new(uuid.UUID)} - case membership.ForeignKeys[1]: // user_memberships - values[i] = &sql.NullScanner{S: new(uuid.UUID)} default: values[i] = new(sql.UnknownType) } @@ -164,19 +162,17 @@ func (m *Membership) assignValues(columns []string, values []any) error { } else if value != nil { m.ResourceID = *value } - case membership.ForeignKeys[0]: - if value, ok := values[i].(*sql.NullScanner); !ok { - return fmt.Errorf("unexpected type %T for field organization_memberships", values[i]) - } else if value.Valid { - m.organization_memberships = new(uuid.UUID) - *m.organization_memberships = *value.S.(*uuid.UUID) + case membership.FieldOrganizationID: + if value, ok := values[i].(*uuid.UUID); !ok { + return fmt.Errorf("unexpected type %T for field organization_id", values[i]) + } else if value != nil { + m.OrganizationID = *value } - case membership.ForeignKeys[1]: - if value, ok := values[i].(*sql.NullScanner); !ok { - return fmt.Errorf("unexpected type %T for field user_memberships", values[i]) - } else if value.Valid { - m.user_memberships = new(uuid.UUID) - *m.user_memberships = *value.S.(*uuid.UUID) + case membership.FieldUserID: + if value, ok := values[i].(*uuid.UUID); !ok { + return fmt.Errorf("unexpected type %T for field user_id", values[i]) + } else if value != nil { + m.UserID = *value } default: m.selectValues.Set(columns[i], values[i]) @@ -247,6 +243,12 @@ func (m *Membership) String() string { builder.WriteString(", ") builder.WriteString("resource_id=") builder.WriteString(fmt.Sprintf("%v", m.ResourceID)) + builder.WriteString(", ") + builder.WriteString("organization_id=") + builder.WriteString(fmt.Sprintf("%v", m.OrganizationID)) + builder.WriteString(", ") + builder.WriteString("user_id=") + builder.WriteString(fmt.Sprintf("%v", m.UserID)) builder.WriteByte(')') return builder.String() } diff --git a/app/controlplane/pkg/data/ent/membership/membership.go b/app/controlplane/pkg/data/ent/membership/membership.go index db3c87067..c967e2f59 100644 --- a/app/controlplane/pkg/data/ent/membership/membership.go +++ b/app/controlplane/pkg/data/ent/membership/membership.go @@ -33,6 +33,10 @@ const ( FieldResourceType = "resource_type" // FieldResourceID holds the string denoting the resource_id field in the database. FieldResourceID = "resource_id" + // FieldOrganizationID holds the string denoting the organization_id field in the database. + FieldOrganizationID = "organization_id" + // FieldUserID holds the string denoting the user_id field in the database. + FieldUserID = "user_id" // EdgeOrganization holds the string denoting the organization edge name in mutations. EdgeOrganization = "organization" // EdgeUser holds the string denoting the user edge name in mutations. @@ -45,14 +49,14 @@ const ( // It exists in this package in order to avoid circular dependency with the "organization" package. OrganizationInverseTable = "organizations" // OrganizationColumn is the table column denoting the organization relation/edge. - OrganizationColumn = "organization_memberships" + OrganizationColumn = "organization_id" // UserTable is the table that holds the user relation/edge. UserTable = "memberships" // UserInverseTable is the table name for the User entity. // It exists in this package in order to avoid circular dependency with the "user" package. UserInverseTable = "users" // UserColumn is the table column denoting the user relation/edge. - UserColumn = "user_memberships" + UserColumn = "user_id" ) // Columns holds all SQL columns for membership fields. @@ -66,13 +70,8 @@ var Columns = []string{ FieldMemberID, FieldResourceType, FieldResourceID, -} - -// ForeignKeys holds the SQL foreign-keys that are owned by the "memberships" -// table and are not defined as standalone fields in the schema. -var ForeignKeys = []string{ - "organization_memberships", - "user_memberships", + FieldOrganizationID, + FieldUserID, } // ValidColumn reports if the column name is valid (part of the table columns). @@ -82,11 +81,6 @@ func ValidColumn(column string) bool { return true } } - for i := range ForeignKeys { - if column == ForeignKeys[i] { - return true - } - } return false } @@ -179,6 +173,16 @@ func ByResourceID(opts ...sql.OrderTermOption) OrderOption { return sql.OrderByField(FieldResourceID, opts...).ToFunc() } +// ByOrganizationID orders the results by the organization_id field. +func ByOrganizationID(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldOrganizationID, opts...).ToFunc() +} + +// ByUserID orders the results by the user_id field. +func ByUserID(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldUserID, opts...).ToFunc() +} + // ByOrganizationField orders the results by organization field. func ByOrganizationField(field string, opts ...sql.OrderTermOption) OrderOption { return func(s *sql.Selector) { diff --git a/app/controlplane/pkg/data/ent/membership/where.go b/app/controlplane/pkg/data/ent/membership/where.go index 4fc5011da..946e4ee39 100644 --- a/app/controlplane/pkg/data/ent/membership/where.go +++ b/app/controlplane/pkg/data/ent/membership/where.go @@ -82,6 +82,16 @@ func ResourceID(v uuid.UUID) predicate.Membership { return predicate.Membership(sql.FieldEQ(FieldResourceID, v)) } +// OrganizationID applies equality check predicate on the "organization_id" field. It's identical to OrganizationIDEQ. +func OrganizationID(v uuid.UUID) predicate.Membership { + return predicate.Membership(sql.FieldEQ(FieldOrganizationID, v)) +} + +// UserID applies equality check predicate on the "user_id" field. It's identical to UserIDEQ. +func UserID(v uuid.UUID) predicate.Membership { + return predicate.Membership(sql.FieldEQ(FieldUserID, v)) +} + // CurrentEQ applies the EQ predicate on the "current" field. func CurrentEQ(v bool) predicate.Membership { return predicate.Membership(sql.FieldEQ(FieldCurrent, v)) @@ -382,6 +392,46 @@ func ResourceIDNotNil() predicate.Membership { return predicate.Membership(sql.FieldNotNull(FieldResourceID)) } +// OrganizationIDEQ applies the EQ predicate on the "organization_id" field. +func OrganizationIDEQ(v uuid.UUID) predicate.Membership { + return predicate.Membership(sql.FieldEQ(FieldOrganizationID, v)) +} + +// OrganizationIDNEQ applies the NEQ predicate on the "organization_id" field. +func OrganizationIDNEQ(v uuid.UUID) predicate.Membership { + return predicate.Membership(sql.FieldNEQ(FieldOrganizationID, v)) +} + +// OrganizationIDIn applies the In predicate on the "organization_id" field. +func OrganizationIDIn(vs ...uuid.UUID) predicate.Membership { + return predicate.Membership(sql.FieldIn(FieldOrganizationID, vs...)) +} + +// OrganizationIDNotIn applies the NotIn predicate on the "organization_id" field. +func OrganizationIDNotIn(vs ...uuid.UUID) predicate.Membership { + return predicate.Membership(sql.FieldNotIn(FieldOrganizationID, vs...)) +} + +// UserIDEQ applies the EQ predicate on the "user_id" field. +func UserIDEQ(v uuid.UUID) predicate.Membership { + return predicate.Membership(sql.FieldEQ(FieldUserID, v)) +} + +// UserIDNEQ applies the NEQ predicate on the "user_id" field. +func UserIDNEQ(v uuid.UUID) predicate.Membership { + return predicate.Membership(sql.FieldNEQ(FieldUserID, v)) +} + +// UserIDIn applies the In predicate on the "user_id" field. +func UserIDIn(vs ...uuid.UUID) predicate.Membership { + return predicate.Membership(sql.FieldIn(FieldUserID, vs...)) +} + +// UserIDNotIn applies the NotIn predicate on the "user_id" field. +func UserIDNotIn(vs ...uuid.UUID) predicate.Membership { + return predicate.Membership(sql.FieldNotIn(FieldUserID, vs...)) +} + // HasOrganization applies the HasEdge predicate on the "organization" edge. func HasOrganization() predicate.Membership { return predicate.Membership(func(s *sql.Selector) { diff --git a/app/controlplane/pkg/data/ent/membership_create.go b/app/controlplane/pkg/data/ent/membership_create.go index 2724a655a..5ffc349e1 100644 --- a/app/controlplane/pkg/data/ent/membership_create.go +++ b/app/controlplane/pkg/data/ent/membership_create.go @@ -131,6 +131,18 @@ func (mc *MembershipCreate) SetNillableResourceID(u *uuid.UUID) *MembershipCreat return mc } +// SetOrganizationID sets the "organization_id" field. +func (mc *MembershipCreate) SetOrganizationID(u uuid.UUID) *MembershipCreate { + mc.mutation.SetOrganizationID(u) + return mc +} + +// SetUserID sets the "user_id" field. +func (mc *MembershipCreate) SetUserID(u uuid.UUID) *MembershipCreate { + mc.mutation.SetUserID(u) + return mc +} + // SetID sets the "id" field. func (mc *MembershipCreate) SetID(u uuid.UUID) *MembershipCreate { mc.mutation.SetID(u) @@ -145,39 +157,11 @@ func (mc *MembershipCreate) SetNillableID(u *uuid.UUID) *MembershipCreate { return mc } -// SetOrganizationID sets the "organization" edge to the Organization entity by ID. -func (mc *MembershipCreate) SetOrganizationID(id uuid.UUID) *MembershipCreate { - mc.mutation.SetOrganizationID(id) - return mc -} - -// SetNillableOrganizationID sets the "organization" edge to the Organization entity by ID if the given value is not nil. -func (mc *MembershipCreate) SetNillableOrganizationID(id *uuid.UUID) *MembershipCreate { - if id != nil { - mc = mc.SetOrganizationID(*id) - } - return mc -} - // SetOrganization sets the "organization" edge to the Organization entity. func (mc *MembershipCreate) SetOrganization(o *Organization) *MembershipCreate { return mc.SetOrganizationID(o.ID) } -// SetUserID sets the "user" edge to the User entity by ID. -func (mc *MembershipCreate) SetUserID(id uuid.UUID) *MembershipCreate { - mc.mutation.SetUserID(id) - return mc -} - -// SetNillableUserID sets the "user" edge to the User entity by ID if the given value is not nil. -func (mc *MembershipCreate) SetNillableUserID(id *uuid.UUID) *MembershipCreate { - if id != nil { - mc = mc.SetUserID(*id) - } - return mc -} - // SetUser sets the "user" edge to the User entity. func (mc *MembershipCreate) SetUser(u *User) *MembershipCreate { return mc.SetUserID(u.ID) @@ -265,6 +249,18 @@ func (mc *MembershipCreate) check() error { return &ValidationError{Name: "resource_type", err: fmt.Errorf(`ent: validator failed for field "Membership.resource_type": %w`, err)} } } + if _, ok := mc.mutation.OrganizationID(); !ok { + return &ValidationError{Name: "organization_id", err: errors.New(`ent: missing required field "Membership.organization_id"`)} + } + if _, ok := mc.mutation.UserID(); !ok { + return &ValidationError{Name: "user_id", err: errors.New(`ent: missing required field "Membership.user_id"`)} + } + if len(mc.mutation.OrganizationIDs()) == 0 { + return &ValidationError{Name: "organization", err: errors.New(`ent: missing required edge "Membership.organization"`)} + } + if len(mc.mutation.UserIDs()) == 0 { + return &ValidationError{Name: "user", err: errors.New(`ent: missing required edge "Membership.user"`)} + } return nil } @@ -347,7 +343,7 @@ func (mc *MembershipCreate) createSpec() (*Membership, *sqlgraph.CreateSpec) { for _, k := range nodes { edge.Target.Nodes = append(edge.Target.Nodes, k) } - _node.organization_memberships = &nodes[0] + _node.OrganizationID = nodes[0] _spec.Edges = append(_spec.Edges, edge) } if nodes := mc.mutation.UserIDs(); len(nodes) > 0 { @@ -364,7 +360,7 @@ func (mc *MembershipCreate) createSpec() (*Membership, *sqlgraph.CreateSpec) { for _, k := range nodes { edge.Target.Nodes = append(edge.Target.Nodes, k) } - _node.user_memberships = &nodes[0] + _node.UserID = nodes[0] _spec.Edges = append(_spec.Edges, edge) } return _node, _spec @@ -527,6 +523,30 @@ func (u *MembershipUpsert) ClearResourceID() *MembershipUpsert { return u } +// SetOrganizationID sets the "organization_id" field. +func (u *MembershipUpsert) SetOrganizationID(v uuid.UUID) *MembershipUpsert { + u.Set(membership.FieldOrganizationID, v) + return u +} + +// UpdateOrganizationID sets the "organization_id" field to the value that was provided on create. +func (u *MembershipUpsert) UpdateOrganizationID() *MembershipUpsert { + u.SetExcluded(membership.FieldOrganizationID) + return u +} + +// SetUserID sets the "user_id" field. +func (u *MembershipUpsert) SetUserID(v uuid.UUID) *MembershipUpsert { + u.Set(membership.FieldUserID, v) + return u +} + +// UpdateUserID sets the "user_id" field to the value that was provided on create. +func (u *MembershipUpsert) UpdateUserID() *MembershipUpsert { + u.SetExcluded(membership.FieldUserID) + return u +} + // UpdateNewValues updates the mutable fields using the new values that were set on create except the ID field. // Using this option is equivalent to using: // @@ -704,6 +724,34 @@ func (u *MembershipUpsertOne) ClearResourceID() *MembershipUpsertOne { }) } +// SetOrganizationID sets the "organization_id" field. +func (u *MembershipUpsertOne) SetOrganizationID(v uuid.UUID) *MembershipUpsertOne { + return u.Update(func(s *MembershipUpsert) { + s.SetOrganizationID(v) + }) +} + +// UpdateOrganizationID sets the "organization_id" field to the value that was provided on create. +func (u *MembershipUpsertOne) UpdateOrganizationID() *MembershipUpsertOne { + return u.Update(func(s *MembershipUpsert) { + s.UpdateOrganizationID() + }) +} + +// SetUserID sets the "user_id" field. +func (u *MembershipUpsertOne) SetUserID(v uuid.UUID) *MembershipUpsertOne { + return u.Update(func(s *MembershipUpsert) { + s.SetUserID(v) + }) +} + +// UpdateUserID sets the "user_id" field to the value that was provided on create. +func (u *MembershipUpsertOne) UpdateUserID() *MembershipUpsertOne { + return u.Update(func(s *MembershipUpsert) { + s.UpdateUserID() + }) +} + // Exec executes the query. func (u *MembershipUpsertOne) Exec(ctx context.Context) error { if len(u.create.conflict) == 0 { @@ -1048,6 +1096,34 @@ func (u *MembershipUpsertBulk) ClearResourceID() *MembershipUpsertBulk { }) } +// SetOrganizationID sets the "organization_id" field. +func (u *MembershipUpsertBulk) SetOrganizationID(v uuid.UUID) *MembershipUpsertBulk { + return u.Update(func(s *MembershipUpsert) { + s.SetOrganizationID(v) + }) +} + +// UpdateOrganizationID sets the "organization_id" field to the value that was provided on create. +func (u *MembershipUpsertBulk) UpdateOrganizationID() *MembershipUpsertBulk { + return u.Update(func(s *MembershipUpsert) { + s.UpdateOrganizationID() + }) +} + +// SetUserID sets the "user_id" field. +func (u *MembershipUpsertBulk) SetUserID(v uuid.UUID) *MembershipUpsertBulk { + return u.Update(func(s *MembershipUpsert) { + s.SetUserID(v) + }) +} + +// UpdateUserID sets the "user_id" field to the value that was provided on create. +func (u *MembershipUpsertBulk) UpdateUserID() *MembershipUpsertBulk { + return u.Update(func(s *MembershipUpsert) { + s.UpdateUserID() + }) +} + // Exec executes the query. func (u *MembershipUpsertBulk) Exec(ctx context.Context) error { if u.create.err != nil { diff --git a/app/controlplane/pkg/data/ent/membership_query.go b/app/controlplane/pkg/data/ent/membership_query.go index 87033f9b5..ced393392 100644 --- a/app/controlplane/pkg/data/ent/membership_query.go +++ b/app/controlplane/pkg/data/ent/membership_query.go @@ -28,7 +28,6 @@ type MembershipQuery struct { predicates []predicate.Membership withOrganization *OrganizationQuery withUser *UserQuery - withFKs bool modifiers []func(*sql.Selector) // intermediate query (i.e. traversal path). sql *sql.Selector @@ -410,19 +409,12 @@ func (mq *MembershipQuery) prepareQuery(ctx context.Context) error { func (mq *MembershipQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*Membership, error) { var ( nodes = []*Membership{} - withFKs = mq.withFKs _spec = mq.querySpec() loadedTypes = [2]bool{ mq.withOrganization != nil, mq.withUser != nil, } ) - if mq.withOrganization != nil || mq.withUser != nil { - withFKs = true - } - if withFKs { - _spec.Node.Columns = append(_spec.Node.Columns, membership.ForeignKeys...) - } _spec.ScanValues = func(columns []string) ([]any, error) { return (*Membership).scanValues(nil, columns) } @@ -463,10 +455,7 @@ func (mq *MembershipQuery) loadOrganization(ctx context.Context, query *Organiza ids := make([]uuid.UUID, 0, len(nodes)) nodeids := make(map[uuid.UUID][]*Membership) for i := range nodes { - if nodes[i].organization_memberships == nil { - continue - } - fk := *nodes[i].organization_memberships + fk := nodes[i].OrganizationID if _, ok := nodeids[fk]; !ok { ids = append(ids, fk) } @@ -483,7 +472,7 @@ func (mq *MembershipQuery) loadOrganization(ctx context.Context, query *Organiza for _, n := range neighbors { nodes, ok := nodeids[n.ID] if !ok { - return fmt.Errorf(`unexpected foreign-key "organization_memberships" returned %v`, n.ID) + return fmt.Errorf(`unexpected foreign-key "organization_id" returned %v`, n.ID) } for i := range nodes { assign(nodes[i], n) @@ -495,10 +484,7 @@ func (mq *MembershipQuery) loadUser(ctx context.Context, query *UserQuery, nodes ids := make([]uuid.UUID, 0, len(nodes)) nodeids := make(map[uuid.UUID][]*Membership) for i := range nodes { - if nodes[i].user_memberships == nil { - continue - } - fk := *nodes[i].user_memberships + fk := nodes[i].UserID if _, ok := nodeids[fk]; !ok { ids = append(ids, fk) } @@ -515,7 +501,7 @@ func (mq *MembershipQuery) loadUser(ctx context.Context, query *UserQuery, nodes for _, n := range neighbors { nodes, ok := nodeids[n.ID] if !ok { - return fmt.Errorf(`unexpected foreign-key "user_memberships" returned %v`, n.ID) + return fmt.Errorf(`unexpected foreign-key "user_id" returned %v`, n.ID) } for i := range nodes { assign(nodes[i], n) @@ -552,6 +538,12 @@ func (mq *MembershipQuery) querySpec() *sqlgraph.QuerySpec { _spec.Node.Columns = append(_spec.Node.Columns, fields[i]) } } + if mq.withOrganization != nil { + _spec.Node.AddColumnOnce(membership.FieldOrganizationID) + } + if mq.withUser != nil { + _spec.Node.AddColumnOnce(membership.FieldUserID) + } } if ps := mq.predicates; len(ps) > 0 { _spec.Predicate = func(selector *sql.Selector) { diff --git a/app/controlplane/pkg/data/ent/membership_update.go b/app/controlplane/pkg/data/ent/membership_update.go index b0afedb85..f768b5bfb 100644 --- a/app/controlplane/pkg/data/ent/membership_update.go +++ b/app/controlplane/pkg/data/ent/membership_update.go @@ -155,39 +155,39 @@ func (mu *MembershipUpdate) ClearResourceID() *MembershipUpdate { return mu } -// SetOrganizationID sets the "organization" edge to the Organization entity by ID. -func (mu *MembershipUpdate) SetOrganizationID(id uuid.UUID) *MembershipUpdate { - mu.mutation.SetOrganizationID(id) +// SetOrganizationID sets the "organization_id" field. +func (mu *MembershipUpdate) SetOrganizationID(u uuid.UUID) *MembershipUpdate { + mu.mutation.SetOrganizationID(u) return mu } -// SetNillableOrganizationID sets the "organization" edge to the Organization entity by ID if the given value is not nil. -func (mu *MembershipUpdate) SetNillableOrganizationID(id *uuid.UUID) *MembershipUpdate { - if id != nil { - mu = mu.SetOrganizationID(*id) +// SetNillableOrganizationID sets the "organization_id" field if the given value is not nil. +func (mu *MembershipUpdate) SetNillableOrganizationID(u *uuid.UUID) *MembershipUpdate { + if u != nil { + mu.SetOrganizationID(*u) } return mu } -// SetOrganization sets the "organization" edge to the Organization entity. -func (mu *MembershipUpdate) SetOrganization(o *Organization) *MembershipUpdate { - return mu.SetOrganizationID(o.ID) -} - -// SetUserID sets the "user" edge to the User entity by ID. -func (mu *MembershipUpdate) SetUserID(id uuid.UUID) *MembershipUpdate { - mu.mutation.SetUserID(id) +// SetUserID sets the "user_id" field. +func (mu *MembershipUpdate) SetUserID(u uuid.UUID) *MembershipUpdate { + mu.mutation.SetUserID(u) return mu } -// SetNillableUserID sets the "user" edge to the User entity by ID if the given value is not nil. -func (mu *MembershipUpdate) SetNillableUserID(id *uuid.UUID) *MembershipUpdate { - if id != nil { - mu = mu.SetUserID(*id) +// SetNillableUserID sets the "user_id" field if the given value is not nil. +func (mu *MembershipUpdate) SetNillableUserID(u *uuid.UUID) *MembershipUpdate { + if u != nil { + mu.SetUserID(*u) } return mu } +// SetOrganization sets the "organization" edge to the Organization entity. +func (mu *MembershipUpdate) SetOrganization(o *Organization) *MembershipUpdate { + return mu.SetOrganizationID(o.ID) +} + // SetUser sets the "user" edge to the User entity. func (mu *MembershipUpdate) SetUser(u *User) *MembershipUpdate { return mu.SetUserID(u.ID) @@ -254,6 +254,12 @@ func (mu *MembershipUpdate) check() error { return &ValidationError{Name: "resource_type", err: fmt.Errorf(`ent: validator failed for field "Membership.resource_type": %w`, err)} } } + if mu.mutation.OrganizationCleared() && len(mu.mutation.OrganizationIDs()) > 0 { + return errors.New(`ent: clearing a required unique edge "Membership.organization"`) + } + if mu.mutation.UserCleared() && len(mu.mutation.UserIDs()) > 0 { + return errors.New(`ent: clearing a required unique edge "Membership.user"`) + } return nil } @@ -510,39 +516,39 @@ func (muo *MembershipUpdateOne) ClearResourceID() *MembershipUpdateOne { return muo } -// SetOrganizationID sets the "organization" edge to the Organization entity by ID. -func (muo *MembershipUpdateOne) SetOrganizationID(id uuid.UUID) *MembershipUpdateOne { - muo.mutation.SetOrganizationID(id) +// SetOrganizationID sets the "organization_id" field. +func (muo *MembershipUpdateOne) SetOrganizationID(u uuid.UUID) *MembershipUpdateOne { + muo.mutation.SetOrganizationID(u) return muo } -// SetNillableOrganizationID sets the "organization" edge to the Organization entity by ID if the given value is not nil. -func (muo *MembershipUpdateOne) SetNillableOrganizationID(id *uuid.UUID) *MembershipUpdateOne { - if id != nil { - muo = muo.SetOrganizationID(*id) +// SetNillableOrganizationID sets the "organization_id" field if the given value is not nil. +func (muo *MembershipUpdateOne) SetNillableOrganizationID(u *uuid.UUID) *MembershipUpdateOne { + if u != nil { + muo.SetOrganizationID(*u) } return muo } -// SetOrganization sets the "organization" edge to the Organization entity. -func (muo *MembershipUpdateOne) SetOrganization(o *Organization) *MembershipUpdateOne { - return muo.SetOrganizationID(o.ID) -} - -// SetUserID sets the "user" edge to the User entity by ID. -func (muo *MembershipUpdateOne) SetUserID(id uuid.UUID) *MembershipUpdateOne { - muo.mutation.SetUserID(id) +// SetUserID sets the "user_id" field. +func (muo *MembershipUpdateOne) SetUserID(u uuid.UUID) *MembershipUpdateOne { + muo.mutation.SetUserID(u) return muo } -// SetNillableUserID sets the "user" edge to the User entity by ID if the given value is not nil. -func (muo *MembershipUpdateOne) SetNillableUserID(id *uuid.UUID) *MembershipUpdateOne { - if id != nil { - muo = muo.SetUserID(*id) +// SetNillableUserID sets the "user_id" field if the given value is not nil. +func (muo *MembershipUpdateOne) SetNillableUserID(u *uuid.UUID) *MembershipUpdateOne { + if u != nil { + muo.SetUserID(*u) } return muo } +// SetOrganization sets the "organization" edge to the Organization entity. +func (muo *MembershipUpdateOne) SetOrganization(o *Organization) *MembershipUpdateOne { + return muo.SetOrganizationID(o.ID) +} + // SetUser sets the "user" edge to the User entity. func (muo *MembershipUpdateOne) SetUser(u *User) *MembershipUpdateOne { return muo.SetUserID(u.ID) @@ -622,6 +628,12 @@ func (muo *MembershipUpdateOne) check() error { return &ValidationError{Name: "resource_type", err: fmt.Errorf(`ent: validator failed for field "Membership.resource_type": %w`, err)} } } + if muo.mutation.OrganizationCleared() && len(muo.mutation.OrganizationIDs()) > 0 { + return errors.New(`ent: clearing a required unique edge "Membership.organization"`) + } + if muo.mutation.UserCleared() && len(muo.mutation.UserIDs()) > 0 { + return errors.New(`ent: clearing a required unique edge "Membership.user"`) + } return nil } diff --git a/app/controlplane/pkg/data/ent/migrate/migrations/20250710100006.sql b/app/controlplane/pkg/data/ent/migrate/migrations/20250710100006.sql new file mode 100644 index 000000000..8ca62e171 --- /dev/null +++ b/app/controlplane/pkg/data/ent/migrate/migrations/20250710100006.sql @@ -0,0 +1,3 @@ +-- Modify "memberships" table +ALTER TABLE "memberships" RENAME COLUMN "user_memberships" TO "user_id"; +ALTER TABLE "memberships" RENAME COLUMN "organization_memberships" TO "organization_id"; diff --git a/app/controlplane/pkg/data/ent/migrate/migrations/atlas.sum b/app/controlplane/pkg/data/ent/migrate/migrations/atlas.sum index c485b7ad1..f9296ac55 100644 --- a/app/controlplane/pkg/data/ent/migrate/migrations/atlas.sum +++ b/app/controlplane/pkg/data/ent/migrate/migrations/atlas.sum @@ -1,4 +1,4 @@ -h1:pewT4SIwZxo6E95TRjOvYvsO2AvVZDBRPljhvi6yN3c= +h1:jqekM7wUTJ+R77ZRh/qFYYzNt5dITpV38mu5CnSrFKU= 20230706165452_init-schema.sql h1:VvqbNFEQnCvUVyj2iDYVQQxDM0+sSXqocpt/5H64k8M= 20230710111950-cas-backend.sql h1:A8iBuSzZIEbdsv9ipBtscZQuaBp3V5/VMw7eZH6GX+g= 20230712094107-cas-backends-workflow-runs.sql h1:a5rzxpVGyd56nLRSsKrmCFc9sebg65RWzLghKHh5xvI= @@ -95,3 +95,4 @@ h1:pewT4SIwZxo6E95TRjOvYvsO2AvVZDBRPljhvi6yN3c= 20250702111701.sql h1:Ni7fuL1qU5RGjBzV0XgJi9NkZINGijF8BumPuQ9conM= 20250702112642.sql h1:wrjVS+5h2hs7KNwPRBece5LgAsoEzxN/zNfvCnjoIUw= 20250704090359.sql h1:a0ksfjy2dtzviJL16HbC4eT1xBxy2qFH5mNFOpYlUrA= +20250710100006.sql h1:f8QEuqLZx5+dW2fEEQSui2B6t5dmBnkkXbDJbRTgQCE= diff --git a/app/controlplane/pkg/data/ent/migrate/schema.go b/app/controlplane/pkg/data/ent/migrate/schema.go index 497ed716e..c033ca0cd 100644 --- a/app/controlplane/pkg/data/ent/migrate/schema.go +++ b/app/controlplane/pkg/data/ent/migrate/schema.go @@ -325,8 +325,8 @@ var ( {Name: "member_id", Type: field.TypeUUID, Nullable: true}, {Name: "resource_type", Type: field.TypeEnum, Nullable: true, Enums: []string{"organization", "project", "group"}}, {Name: "resource_id", Type: field.TypeUUID, Nullable: true}, - {Name: "organization_memberships", Type: field.TypeUUID, Nullable: true}, - {Name: "user_memberships", Type: field.TypeUUID, Nullable: true}, + {Name: "organization_id", Type: field.TypeUUID}, + {Name: "user_id", Type: field.TypeUUID}, } // MembershipsTable holds the schema information for the "memberships" table. MembershipsTable = &schema.Table{ @@ -349,7 +349,7 @@ var ( }, Indexes: []*schema.Index{ { - Name: "membership_organization_memberships_user_memberships", + Name: "membership_organization_id_user_id", Unique: false, Columns: []*schema.Column{MembershipsColumns[9], MembershipsColumns[10]}, }, diff --git a/app/controlplane/pkg/data/ent/mutation.go b/app/controlplane/pkg/data/ent/mutation.go index 736bfa536..35b85b004 100644 --- a/app/controlplane/pkg/data/ent/mutation.go +++ b/app/controlplane/pkg/data/ent/mutation.go @@ -7038,14 +7038,82 @@ func (m *MembershipMutation) ResetResourceID() { delete(m.clearedFields, membership.FieldResourceID) } -// SetOrganizationID sets the "organization" edge to the Organization entity by id. -func (m *MembershipMutation) SetOrganizationID(id uuid.UUID) { - m.organization = &id +// SetOrganizationID sets the "organization_id" field. +func (m *MembershipMutation) SetOrganizationID(u uuid.UUID) { + m.organization = &u +} + +// OrganizationID returns the value of the "organization_id" field in the mutation. +func (m *MembershipMutation) OrganizationID() (r uuid.UUID, exists bool) { + v := m.organization + if v == nil { + return + } + return *v, true +} + +// OldOrganizationID returns the old "organization_id" field's value of the Membership entity. +// If the Membership object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *MembershipMutation) OldOrganizationID(ctx context.Context) (v uuid.UUID, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldOrganizationID is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldOrganizationID requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldOrganizationID: %w", err) + } + return oldValue.OrganizationID, nil +} + +// ResetOrganizationID resets all changes to the "organization_id" field. +func (m *MembershipMutation) ResetOrganizationID() { + m.organization = nil +} + +// SetUserID sets the "user_id" field. +func (m *MembershipMutation) SetUserID(u uuid.UUID) { + m.user = &u +} + +// UserID returns the value of the "user_id" field in the mutation. +func (m *MembershipMutation) UserID() (r uuid.UUID, exists bool) { + v := m.user + if v == nil { + return + } + return *v, true +} + +// OldUserID returns the old "user_id" field's value of the Membership entity. +// If the Membership object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *MembershipMutation) OldUserID(ctx context.Context) (v uuid.UUID, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldUserID is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldUserID requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldUserID: %w", err) + } + return oldValue.UserID, nil +} + +// ResetUserID resets all changes to the "user_id" field. +func (m *MembershipMutation) ResetUserID() { + m.user = nil } // ClearOrganization clears the "organization" edge to the Organization entity. func (m *MembershipMutation) ClearOrganization() { m.clearedorganization = true + m.clearedFields[membership.FieldOrganizationID] = struct{}{} } // OrganizationCleared reports if the "organization" edge to the Organization entity was cleared. @@ -7053,14 +7121,6 @@ func (m *MembershipMutation) OrganizationCleared() bool { return m.clearedorganization } -// OrganizationID returns the "organization" edge ID in the mutation. -func (m *MembershipMutation) OrganizationID() (id uuid.UUID, exists bool) { - if m.organization != nil { - return *m.organization, true - } - return -} - // OrganizationIDs returns the "organization" edge IDs in the mutation. // Note that IDs always returns len(IDs) <= 1 for unique edges, and you should use // OrganizationID instead. It exists only for internal usage by the builders. @@ -7077,14 +7137,10 @@ func (m *MembershipMutation) ResetOrganization() { m.clearedorganization = false } -// SetUserID sets the "user" edge to the User entity by id. -func (m *MembershipMutation) SetUserID(id uuid.UUID) { - m.user = &id -} - // ClearUser clears the "user" edge to the User entity. func (m *MembershipMutation) ClearUser() { m.cleareduser = true + m.clearedFields[membership.FieldUserID] = struct{}{} } // UserCleared reports if the "user" edge to the User entity was cleared. @@ -7092,14 +7148,6 @@ func (m *MembershipMutation) UserCleared() bool { return m.cleareduser } -// UserID returns the "user" edge ID in the mutation. -func (m *MembershipMutation) UserID() (id uuid.UUID, exists bool) { - if m.user != nil { - return *m.user, true - } - return -} - // UserIDs returns the "user" edge IDs in the mutation. // Note that IDs always returns len(IDs) <= 1 for unique edges, and you should use // UserID instead. It exists only for internal usage by the builders. @@ -7150,7 +7198,7 @@ func (m *MembershipMutation) Type() string { // order to get all numeric fields that were incremented/decremented, call // AddedFields(). func (m *MembershipMutation) Fields() []string { - fields := make([]string, 0, 8) + fields := make([]string, 0, 10) if m.current != nil { fields = append(fields, membership.FieldCurrent) } @@ -7175,6 +7223,12 @@ func (m *MembershipMutation) Fields() []string { if m.resource_id != nil { fields = append(fields, membership.FieldResourceID) } + if m.organization != nil { + fields = append(fields, membership.FieldOrganizationID) + } + if m.user != nil { + fields = append(fields, membership.FieldUserID) + } return fields } @@ -7199,6 +7253,10 @@ func (m *MembershipMutation) Field(name string) (ent.Value, bool) { return m.ResourceType() case membership.FieldResourceID: return m.ResourceID() + case membership.FieldOrganizationID: + return m.OrganizationID() + case membership.FieldUserID: + return m.UserID() } return nil, false } @@ -7224,6 +7282,10 @@ func (m *MembershipMutation) OldField(ctx context.Context, name string) (ent.Val return m.OldResourceType(ctx) case membership.FieldResourceID: return m.OldResourceID(ctx) + case membership.FieldOrganizationID: + return m.OldOrganizationID(ctx) + case membership.FieldUserID: + return m.OldUserID(ctx) } return nil, fmt.Errorf("unknown Membership field %s", name) } @@ -7289,6 +7351,20 @@ func (m *MembershipMutation) SetField(name string, value ent.Value) error { } m.SetResourceID(v) return nil + case membership.FieldOrganizationID: + v, ok := value.(uuid.UUID) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetOrganizationID(v) + return nil + case membership.FieldUserID: + v, ok := value.(uuid.UUID) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetUserID(v) + return nil } return fmt.Errorf("unknown Membership field %s", name) } @@ -7389,6 +7465,12 @@ func (m *MembershipMutation) ResetField(name string) error { case membership.FieldResourceID: m.ResetResourceID() return nil + case membership.FieldOrganizationID: + m.ResetOrganizationID() + return nil + case membership.FieldUserID: + m.ResetUserID() + return nil } return fmt.Errorf("unknown Membership field %s", name) } diff --git a/app/controlplane/pkg/data/ent/organization/organization.go b/app/controlplane/pkg/data/ent/organization/organization.go index 6aa0ada04..a334ce1dd 100644 --- a/app/controlplane/pkg/data/ent/organization/organization.go +++ b/app/controlplane/pkg/data/ent/organization/organization.go @@ -45,7 +45,7 @@ const ( // It exists in this package in order to avoid circular dependency with the "membership" package. MembershipsInverseTable = "memberships" // MembershipsColumn is the table column denoting the memberships relation/edge. - MembershipsColumn = "organization_memberships" + MembershipsColumn = "organization_id" // WorkflowContractsTable is the table that holds the workflow_contracts relation/edge. WorkflowContractsTable = "workflow_contracts" // WorkflowContractsInverseTable is the table name for the WorkflowContract entity. diff --git a/app/controlplane/pkg/data/ent/organization_query.go b/app/controlplane/pkg/data/ent/organization_query.go index 75aeb023e..d6d941458 100644 --- a/app/controlplane/pkg/data/ent/organization_query.go +++ b/app/controlplane/pkg/data/ent/organization_query.go @@ -730,7 +730,9 @@ func (oq *OrganizationQuery) loadMemberships(ctx context.Context, query *Members init(nodes[i]) } } - query.withFKs = true + if len(query.ctx.Fields) > 0 { + query.ctx.AppendFieldOnce(membership.FieldOrganizationID) + } query.Where(predicate.Membership(func(s *sql.Selector) { s.Where(sql.InValues(s.C(organization.MembershipsColumn), fks...)) })) @@ -739,13 +741,10 @@ func (oq *OrganizationQuery) loadMemberships(ctx context.Context, query *Members return err } for _, n := range neighbors { - fk := n.organization_memberships - if fk == nil { - return fmt.Errorf(`foreign-key "organization_memberships" is nil for node %v`, n.ID) - } - node, ok := nodeids[*fk] + fk := n.OrganizationID + node, ok := nodeids[fk] if !ok { - return fmt.Errorf(`unexpected referenced foreign-key "organization_memberships" returned %v for node %v`, *fk, n.ID) + return fmt.Errorf(`unexpected referenced foreign-key "organization_id" returned %v for node %v`, fk, n.ID) } assign(node, n) } diff --git a/app/controlplane/pkg/data/ent/schema-viz.html b/app/controlplane/pkg/data/ent/schema-viz.html index 560c592f4..575a218b0 100644 --- a/app/controlplane/pkg/data/ent/schema-viz.html +++ b/app/controlplane/pkg/data/ent/schema-viz.html @@ -70,7 +70,7 @@ } - const entGraph = JSON.parse("{\"nodes\":[{\"id\":\"APIToken\",\"fields\":[{\"name\":\"name\",\"type\":\"string\"},{\"name\":\"description\",\"type\":\"string\"},{\"name\":\"created_at\",\"type\":\"time.Time\"},{\"name\":\"expires_at\",\"type\":\"time.Time\"},{\"name\":\"revoked_at\",\"type\":\"time.Time\"},{\"name\":\"last_used_at\",\"type\":\"time.Time\"},{\"name\":\"organization_id\",\"type\":\"uuid.UUID\"},{\"name\":\"project_id\",\"type\":\"uuid.UUID\"}]},{\"id\":\"Attestation\",\"fields\":[{\"name\":\"created_at\",\"type\":\"time.Time\"},{\"name\":\"bundle\",\"type\":\"[]byte\"},{\"name\":\"workflowrun_id\",\"type\":\"uuid.UUID\"}]},{\"id\":\"CASBackend\",\"fields\":[{\"name\":\"location\",\"type\":\"string\"},{\"name\":\"name\",\"type\":\"string\"},{\"name\":\"provider\",\"type\":\"biz.CASBackendProvider\"},{\"name\":\"description\",\"type\":\"string\"},{\"name\":\"secret_name\",\"type\":\"string\"},{\"name\":\"created_at\",\"type\":\"time.Time\"},{\"name\":\"validation_status\",\"type\":\"biz.CASBackendValidationStatus\"},{\"name\":\"validated_at\",\"type\":\"time.Time\"},{\"name\":\"default\",\"type\":\"bool\"},{\"name\":\"deleted_at\",\"type\":\"time.Time\"},{\"name\":\"fallback\",\"type\":\"bool\"},{\"name\":\"max_blob_size_bytes\",\"type\":\"int64\"}]},{\"id\":\"CASMapping\",\"fields\":[{\"name\":\"digest\",\"type\":\"string\"},{\"name\":\"created_at\",\"type\":\"time.Time\"},{\"name\":\"workflow_run_id\",\"type\":\"uuid.UUID\"},{\"name\":\"organization_id\",\"type\":\"uuid.UUID\"},{\"name\":\"project_id\",\"type\":\"uuid.UUID\"}]},{\"id\":\"Group\",\"fields\":[{\"name\":\"name\",\"type\":\"string\"},{\"name\":\"description\",\"type\":\"string\"},{\"name\":\"organization_id\",\"type\":\"uuid.UUID\"},{\"name\":\"created_at\",\"type\":\"time.Time\"},{\"name\":\"updated_at\",\"type\":\"time.Time\"},{\"name\":\"deleted_at\",\"type\":\"time.Time\"},{\"name\":\"member_count\",\"type\":\"int\"}]},{\"id\":\"GroupMembership\",\"fields\":[{\"name\":\"group_id\",\"type\":\"uuid.UUID\"},{\"name\":\"user_id\",\"type\":\"uuid.UUID\"},{\"name\":\"maintainer\",\"type\":\"bool\"},{\"name\":\"created_at\",\"type\":\"time.Time\"},{\"name\":\"updated_at\",\"type\":\"time.Time\"},{\"name\":\"deleted_at\",\"type\":\"time.Time\"}]},{\"id\":\"Integration\",\"fields\":[{\"name\":\"name\",\"type\":\"string\"},{\"name\":\"kind\",\"type\":\"string\"},{\"name\":\"description\",\"type\":\"string\"},{\"name\":\"secret_name\",\"type\":\"string\"},{\"name\":\"created_at\",\"type\":\"time.Time\"},{\"name\":\"configuration\",\"type\":\"[]byte\"},{\"name\":\"deleted_at\",\"type\":\"time.Time\"}]},{\"id\":\"IntegrationAttachment\",\"fields\":[{\"name\":\"created_at\",\"type\":\"time.Time\"},{\"name\":\"configuration\",\"type\":\"[]byte\"},{\"name\":\"deleted_at\",\"type\":\"time.Time\"},{\"name\":\"workflow_id\",\"type\":\"uuid.UUID\"}]},{\"id\":\"Membership\",\"fields\":[{\"name\":\"current\",\"type\":\"bool\"},{\"name\":\"created_at\",\"type\":\"time.Time\"},{\"name\":\"updated_at\",\"type\":\"time.Time\"},{\"name\":\"role\",\"type\":\"authz.Role\"},{\"name\":\"membership_type\",\"type\":\"authz.MembershipType\"},{\"name\":\"member_id\",\"type\":\"uuid.UUID\"},{\"name\":\"resource_type\",\"type\":\"authz.ResourceType\"},{\"name\":\"resource_id\",\"type\":\"uuid.UUID\"}]},{\"id\":\"OrgInvitation\",\"fields\":[{\"name\":\"receiver_email\",\"type\":\"string\"},{\"name\":\"status\",\"type\":\"biz.OrgInvitationStatus\"},{\"name\":\"created_at\",\"type\":\"time.Time\"},{\"name\":\"deleted_at\",\"type\":\"time.Time\"},{\"name\":\"organization_id\",\"type\":\"uuid.UUID\"},{\"name\":\"sender_id\",\"type\":\"uuid.UUID\"},{\"name\":\"role\",\"type\":\"authz.Role\"},{\"name\":\"context\",\"type\":\"biz.OrgInvitationContext\"}]},{\"id\":\"Organization\",\"fields\":[{\"name\":\"name\",\"type\":\"string\"},{\"name\":\"created_at\",\"type\":\"time.Time\"},{\"name\":\"block_on_policy_violation\",\"type\":\"bool\"}]},{\"id\":\"Project\",\"fields\":[{\"name\":\"name\",\"type\":\"string\"},{\"name\":\"description\",\"type\":\"string\"},{\"name\":\"created_at\",\"type\":\"time.Time\"},{\"name\":\"deleted_at\",\"type\":\"time.Time\"},{\"name\":\"organization_id\",\"type\":\"uuid.UUID\"}]},{\"id\":\"ProjectVersion\",\"fields\":[{\"name\":\"version\",\"type\":\"string\"},{\"name\":\"created_at\",\"type\":\"time.Time\"},{\"name\":\"updated_at\",\"type\":\"time.Time\"},{\"name\":\"deleted_at\",\"type\":\"time.Time\"},{\"name\":\"project_id\",\"type\":\"uuid.UUID\"},{\"name\":\"prerelease\",\"type\":\"bool\"},{\"name\":\"workflow_run_count\",\"type\":\"int\"},{\"name\":\"released_at\",\"type\":\"time.Time\"},{\"name\":\"latest\",\"type\":\"bool\"}]},{\"id\":\"Referrer\",\"fields\":[{\"name\":\"digest\",\"type\":\"string\"},{\"name\":\"kind\",\"type\":\"string\"},{\"name\":\"downloadable\",\"type\":\"bool\"},{\"name\":\"created_at\",\"type\":\"time.Time\"},{\"name\":\"metadata\",\"type\":\"map[string]string\"},{\"name\":\"annotations\",\"type\":\"map[string]string\"}]},{\"id\":\"RobotAccount\",\"fields\":[{\"name\":\"name\",\"type\":\"string\"},{\"name\":\"created_at\",\"type\":\"time.Time\"},{\"name\":\"revoked_at\",\"type\":\"time.Time\"}]},{\"id\":\"User\",\"fields\":[{\"name\":\"email\",\"type\":\"string\"},{\"name\":\"created_at\",\"type\":\"time.Time\"},{\"name\":\"has_restricted_access\",\"type\":\"bool\"},{\"name\":\"first_name\",\"type\":\"string\"},{\"name\":\"last_name\",\"type\":\"string\"}]},{\"id\":\"Workflow\",\"fields\":[{\"name\":\"name\",\"type\":\"string\"},{\"name\":\"project_old\",\"type\":\"string\"},{\"name\":\"team\",\"type\":\"string\"},{\"name\":\"runs_count\",\"type\":\"int\"},{\"name\":\"created_at\",\"type\":\"time.Time\"},{\"name\":\"updated_at\",\"type\":\"time.Time\"},{\"name\":\"deleted_at\",\"type\":\"time.Time\"},{\"name\":\"public\",\"type\":\"bool\"},{\"name\":\"organization_id\",\"type\":\"uuid.UUID\"},{\"name\":\"project_id\",\"type\":\"uuid.UUID\"},{\"name\":\"latest_run\",\"type\":\"uuid.UUID\"},{\"name\":\"description\",\"type\":\"string\"},{\"name\":\"metadata\",\"type\":\"map[string]interface {}\"}]},{\"id\":\"WorkflowContract\",\"fields\":[{\"name\":\"name\",\"type\":\"string\"},{\"name\":\"created_at\",\"type\":\"time.Time\"},{\"name\":\"deleted_at\",\"type\":\"time.Time\"},{\"name\":\"description\",\"type\":\"string\"}]},{\"id\":\"WorkflowContractVersion\",\"fields\":[{\"name\":\"body\",\"type\":\"[]byte\"},{\"name\":\"raw_body\",\"type\":\"[]byte\"},{\"name\":\"raw_body_format\",\"type\":\"unmarshal.RawFormat\"},{\"name\":\"revision\",\"type\":\"int\"},{\"name\":\"created_at\",\"type\":\"time.Time\"}]},{\"id\":\"WorkflowRun\",\"fields\":[{\"name\":\"created_at\",\"type\":\"time.Time\"},{\"name\":\"finished_at\",\"type\":\"time.Time\"},{\"name\":\"state\",\"type\":\"biz.WorkflowRunStatus\"},{\"name\":\"reason\",\"type\":\"string\"},{\"name\":\"run_url\",\"type\":\"string\"},{\"name\":\"runner_type\",\"type\":\"string\"},{\"name\":\"attestation\",\"type\":\"*dsse.Envelope\"},{\"name\":\"attestation_digest\",\"type\":\"string\"},{\"name\":\"attestation_state\",\"type\":\"[]byte\"},{\"name\":\"contract_revision_used\",\"type\":\"int\"},{\"name\":\"contract_revision_latest\",\"type\":\"int\"},{\"name\":\"version_id\",\"type\":\"uuid.UUID\"},{\"name\":\"workflow_id\",\"type\":\"uuid.UUID\"}]}],\"edges\":[{\"from\":\"APIToken\",\"to\":\"Project\",\"label\":\"project\"},{\"from\":\"CASMapping\",\"to\":\"CASBackend\",\"label\":\"cas_backend\"},{\"from\":\"CASMapping\",\"to\":\"Organization\",\"label\":\"organization\"},{\"from\":\"CASMapping\",\"to\":\"Project\",\"label\":\"project\"},{\"from\":\"Group\",\"to\":\"User\",\"label\":\"members\"},{\"from\":\"GroupMembership\",\"to\":\"Group\",\"label\":\"group\"},{\"from\":\"GroupMembership\",\"to\":\"User\",\"label\":\"user\"},{\"from\":\"IntegrationAttachment\",\"to\":\"Integration\",\"label\":\"integration\"},{\"from\":\"IntegrationAttachment\",\"to\":\"Workflow\",\"label\":\"workflow\"},{\"from\":\"OrgInvitation\",\"to\":\"Organization\",\"label\":\"organization\"},{\"from\":\"OrgInvitation\",\"to\":\"User\",\"label\":\"sender\"},{\"from\":\"Organization\",\"to\":\"Membership\",\"label\":\"memberships\"},{\"from\":\"Organization\",\"to\":\"WorkflowContract\",\"label\":\"workflow_contracts\"},{\"from\":\"Organization\",\"to\":\"Workflow\",\"label\":\"workflows\"},{\"from\":\"Organization\",\"to\":\"CASBackend\",\"label\":\"cas_backends\"},{\"from\":\"Organization\",\"to\":\"Integration\",\"label\":\"integrations\"},{\"from\":\"Organization\",\"to\":\"APIToken\",\"label\":\"api_tokens\"},{\"from\":\"Organization\",\"to\":\"Project\",\"label\":\"projects\"},{\"from\":\"Organization\",\"to\":\"Group\",\"label\":\"groups\"},{\"from\":\"Project\",\"to\":\"Workflow\",\"label\":\"workflows\"},{\"from\":\"Project\",\"to\":\"ProjectVersion\",\"label\":\"versions\"},{\"from\":\"ProjectVersion\",\"to\":\"WorkflowRun\",\"label\":\"runs\"},{\"from\":\"Referrer\",\"to\":\"Referrer\",\"label\":\"references\"},{\"from\":\"Referrer\",\"to\":\"Workflow\",\"label\":\"workflows\"},{\"from\":\"User\",\"to\":\"Membership\",\"label\":\"memberships\"},{\"from\":\"Workflow\",\"to\":\"RobotAccount\",\"label\":\"robotaccounts\"},{\"from\":\"Workflow\",\"to\":\"WorkflowRun\",\"label\":\"workflowruns\"},{\"from\":\"Workflow\",\"to\":\"WorkflowContract\",\"label\":\"contract\"},{\"from\":\"Workflow\",\"to\":\"WorkflowRun\",\"label\":\"latest_workflow_run\"},{\"from\":\"WorkflowContract\",\"to\":\"WorkflowContractVersion\",\"label\":\"versions\"},{\"from\":\"WorkflowRun\",\"to\":\"WorkflowContractVersion\",\"label\":\"contract_version\"},{\"from\":\"WorkflowRun\",\"to\":\"CASBackend\",\"label\":\"cas_backends\"},{\"from\":\"WorkflowRun\",\"to\":\"Attestation\",\"label\":\"attestation_bundle\"}]}"); + const entGraph = JSON.parse("{\"nodes\":[{\"id\":\"APIToken\",\"fields\":[{\"name\":\"name\",\"type\":\"string\"},{\"name\":\"description\",\"type\":\"string\"},{\"name\":\"created_at\",\"type\":\"time.Time\"},{\"name\":\"expires_at\",\"type\":\"time.Time\"},{\"name\":\"revoked_at\",\"type\":\"time.Time\"},{\"name\":\"last_used_at\",\"type\":\"time.Time\"},{\"name\":\"organization_id\",\"type\":\"uuid.UUID\"},{\"name\":\"project_id\",\"type\":\"uuid.UUID\"}]},{\"id\":\"Attestation\",\"fields\":[{\"name\":\"created_at\",\"type\":\"time.Time\"},{\"name\":\"bundle\",\"type\":\"[]byte\"},{\"name\":\"workflowrun_id\",\"type\":\"uuid.UUID\"}]},{\"id\":\"CASBackend\",\"fields\":[{\"name\":\"location\",\"type\":\"string\"},{\"name\":\"name\",\"type\":\"string\"},{\"name\":\"provider\",\"type\":\"biz.CASBackendProvider\"},{\"name\":\"description\",\"type\":\"string\"},{\"name\":\"secret_name\",\"type\":\"string\"},{\"name\":\"created_at\",\"type\":\"time.Time\"},{\"name\":\"validation_status\",\"type\":\"biz.CASBackendValidationStatus\"},{\"name\":\"validated_at\",\"type\":\"time.Time\"},{\"name\":\"default\",\"type\":\"bool\"},{\"name\":\"deleted_at\",\"type\":\"time.Time\"},{\"name\":\"fallback\",\"type\":\"bool\"},{\"name\":\"max_blob_size_bytes\",\"type\":\"int64\"}]},{\"id\":\"CASMapping\",\"fields\":[{\"name\":\"digest\",\"type\":\"string\"},{\"name\":\"created_at\",\"type\":\"time.Time\"},{\"name\":\"workflow_run_id\",\"type\":\"uuid.UUID\"},{\"name\":\"organization_id\",\"type\":\"uuid.UUID\"},{\"name\":\"project_id\",\"type\":\"uuid.UUID\"}]},{\"id\":\"Group\",\"fields\":[{\"name\":\"name\",\"type\":\"string\"},{\"name\":\"description\",\"type\":\"string\"},{\"name\":\"organization_id\",\"type\":\"uuid.UUID\"},{\"name\":\"created_at\",\"type\":\"time.Time\"},{\"name\":\"updated_at\",\"type\":\"time.Time\"},{\"name\":\"deleted_at\",\"type\":\"time.Time\"},{\"name\":\"member_count\",\"type\":\"int\"}]},{\"id\":\"GroupMembership\",\"fields\":[{\"name\":\"group_id\",\"type\":\"uuid.UUID\"},{\"name\":\"user_id\",\"type\":\"uuid.UUID\"},{\"name\":\"maintainer\",\"type\":\"bool\"},{\"name\":\"created_at\",\"type\":\"time.Time\"},{\"name\":\"updated_at\",\"type\":\"time.Time\"},{\"name\":\"deleted_at\",\"type\":\"time.Time\"}]},{\"id\":\"Integration\",\"fields\":[{\"name\":\"name\",\"type\":\"string\"},{\"name\":\"kind\",\"type\":\"string\"},{\"name\":\"description\",\"type\":\"string\"},{\"name\":\"secret_name\",\"type\":\"string\"},{\"name\":\"created_at\",\"type\":\"time.Time\"},{\"name\":\"configuration\",\"type\":\"[]byte\"},{\"name\":\"deleted_at\",\"type\":\"time.Time\"}]},{\"id\":\"IntegrationAttachment\",\"fields\":[{\"name\":\"created_at\",\"type\":\"time.Time\"},{\"name\":\"configuration\",\"type\":\"[]byte\"},{\"name\":\"deleted_at\",\"type\":\"time.Time\"},{\"name\":\"workflow_id\",\"type\":\"uuid.UUID\"}]},{\"id\":\"Membership\",\"fields\":[{\"name\":\"current\",\"type\":\"bool\"},{\"name\":\"created_at\",\"type\":\"time.Time\"},{\"name\":\"updated_at\",\"type\":\"time.Time\"},{\"name\":\"role\",\"type\":\"authz.Role\"},{\"name\":\"membership_type\",\"type\":\"authz.MembershipType\"},{\"name\":\"member_id\",\"type\":\"uuid.UUID\"},{\"name\":\"resource_type\",\"type\":\"authz.ResourceType\"},{\"name\":\"resource_id\",\"type\":\"uuid.UUID\"},{\"name\":\"organization_id\",\"type\":\"uuid.UUID\"},{\"name\":\"user_id\",\"type\":\"uuid.UUID\"}]},{\"id\":\"OrgInvitation\",\"fields\":[{\"name\":\"receiver_email\",\"type\":\"string\"},{\"name\":\"status\",\"type\":\"biz.OrgInvitationStatus\"},{\"name\":\"created_at\",\"type\":\"time.Time\"},{\"name\":\"deleted_at\",\"type\":\"time.Time\"},{\"name\":\"organization_id\",\"type\":\"uuid.UUID\"},{\"name\":\"sender_id\",\"type\":\"uuid.UUID\"},{\"name\":\"role\",\"type\":\"authz.Role\"},{\"name\":\"context\",\"type\":\"biz.OrgInvitationContext\"}]},{\"id\":\"Organization\",\"fields\":[{\"name\":\"name\",\"type\":\"string\"},{\"name\":\"created_at\",\"type\":\"time.Time\"},{\"name\":\"block_on_policy_violation\",\"type\":\"bool\"}]},{\"id\":\"Project\",\"fields\":[{\"name\":\"name\",\"type\":\"string\"},{\"name\":\"description\",\"type\":\"string\"},{\"name\":\"created_at\",\"type\":\"time.Time\"},{\"name\":\"deleted_at\",\"type\":\"time.Time\"},{\"name\":\"organization_id\",\"type\":\"uuid.UUID\"}]},{\"id\":\"ProjectVersion\",\"fields\":[{\"name\":\"version\",\"type\":\"string\"},{\"name\":\"created_at\",\"type\":\"time.Time\"},{\"name\":\"updated_at\",\"type\":\"time.Time\"},{\"name\":\"deleted_at\",\"type\":\"time.Time\"},{\"name\":\"project_id\",\"type\":\"uuid.UUID\"},{\"name\":\"prerelease\",\"type\":\"bool\"},{\"name\":\"workflow_run_count\",\"type\":\"int\"},{\"name\":\"released_at\",\"type\":\"time.Time\"},{\"name\":\"latest\",\"type\":\"bool\"}]},{\"id\":\"Referrer\",\"fields\":[{\"name\":\"digest\",\"type\":\"string\"},{\"name\":\"kind\",\"type\":\"string\"},{\"name\":\"downloadable\",\"type\":\"bool\"},{\"name\":\"created_at\",\"type\":\"time.Time\"},{\"name\":\"metadata\",\"type\":\"map[string]string\"},{\"name\":\"annotations\",\"type\":\"map[string]string\"}]},{\"id\":\"RobotAccount\",\"fields\":[{\"name\":\"name\",\"type\":\"string\"},{\"name\":\"created_at\",\"type\":\"time.Time\"},{\"name\":\"revoked_at\",\"type\":\"time.Time\"}]},{\"id\":\"User\",\"fields\":[{\"name\":\"email\",\"type\":\"string\"},{\"name\":\"created_at\",\"type\":\"time.Time\"},{\"name\":\"has_restricted_access\",\"type\":\"bool\"},{\"name\":\"first_name\",\"type\":\"string\"},{\"name\":\"last_name\",\"type\":\"string\"}]},{\"id\":\"Workflow\",\"fields\":[{\"name\":\"name\",\"type\":\"string\"},{\"name\":\"project_old\",\"type\":\"string\"},{\"name\":\"team\",\"type\":\"string\"},{\"name\":\"runs_count\",\"type\":\"int\"},{\"name\":\"created_at\",\"type\":\"time.Time\"},{\"name\":\"updated_at\",\"type\":\"time.Time\"},{\"name\":\"deleted_at\",\"type\":\"time.Time\"},{\"name\":\"public\",\"type\":\"bool\"},{\"name\":\"organization_id\",\"type\":\"uuid.UUID\"},{\"name\":\"project_id\",\"type\":\"uuid.UUID\"},{\"name\":\"latest_run\",\"type\":\"uuid.UUID\"},{\"name\":\"description\",\"type\":\"string\"},{\"name\":\"metadata\",\"type\":\"map[string]interface {}\"}]},{\"id\":\"WorkflowContract\",\"fields\":[{\"name\":\"name\",\"type\":\"string\"},{\"name\":\"created_at\",\"type\":\"time.Time\"},{\"name\":\"deleted_at\",\"type\":\"time.Time\"},{\"name\":\"description\",\"type\":\"string\"}]},{\"id\":\"WorkflowContractVersion\",\"fields\":[{\"name\":\"body\",\"type\":\"[]byte\"},{\"name\":\"raw_body\",\"type\":\"[]byte\"},{\"name\":\"raw_body_format\",\"type\":\"unmarshal.RawFormat\"},{\"name\":\"revision\",\"type\":\"int\"},{\"name\":\"created_at\",\"type\":\"time.Time\"}]},{\"id\":\"WorkflowRun\",\"fields\":[{\"name\":\"created_at\",\"type\":\"time.Time\"},{\"name\":\"finished_at\",\"type\":\"time.Time\"},{\"name\":\"state\",\"type\":\"biz.WorkflowRunStatus\"},{\"name\":\"reason\",\"type\":\"string\"},{\"name\":\"run_url\",\"type\":\"string\"},{\"name\":\"runner_type\",\"type\":\"string\"},{\"name\":\"attestation\",\"type\":\"*dsse.Envelope\"},{\"name\":\"attestation_digest\",\"type\":\"string\"},{\"name\":\"attestation_state\",\"type\":\"[]byte\"},{\"name\":\"contract_revision_used\",\"type\":\"int\"},{\"name\":\"contract_revision_latest\",\"type\":\"int\"},{\"name\":\"version_id\",\"type\":\"uuid.UUID\"},{\"name\":\"workflow_id\",\"type\":\"uuid.UUID\"}]}],\"edges\":[{\"from\":\"APIToken\",\"to\":\"Project\",\"label\":\"project\"},{\"from\":\"CASMapping\",\"to\":\"CASBackend\",\"label\":\"cas_backend\"},{\"from\":\"CASMapping\",\"to\":\"Organization\",\"label\":\"organization\"},{\"from\":\"CASMapping\",\"to\":\"Project\",\"label\":\"project\"},{\"from\":\"Group\",\"to\":\"User\",\"label\":\"members\"},{\"from\":\"GroupMembership\",\"to\":\"Group\",\"label\":\"group\"},{\"from\":\"GroupMembership\",\"to\":\"User\",\"label\":\"user\"},{\"from\":\"IntegrationAttachment\",\"to\":\"Integration\",\"label\":\"integration\"},{\"from\":\"IntegrationAttachment\",\"to\":\"Workflow\",\"label\":\"workflow\"},{\"from\":\"OrgInvitation\",\"to\":\"Organization\",\"label\":\"organization\"},{\"from\":\"OrgInvitation\",\"to\":\"User\",\"label\":\"sender\"},{\"from\":\"Organization\",\"to\":\"Membership\",\"label\":\"memberships\"},{\"from\":\"Organization\",\"to\":\"WorkflowContract\",\"label\":\"workflow_contracts\"},{\"from\":\"Organization\",\"to\":\"Workflow\",\"label\":\"workflows\"},{\"from\":\"Organization\",\"to\":\"CASBackend\",\"label\":\"cas_backends\"},{\"from\":\"Organization\",\"to\":\"Integration\",\"label\":\"integrations\"},{\"from\":\"Organization\",\"to\":\"APIToken\",\"label\":\"api_tokens\"},{\"from\":\"Organization\",\"to\":\"Project\",\"label\":\"projects\"},{\"from\":\"Organization\",\"to\":\"Group\",\"label\":\"groups\"},{\"from\":\"Project\",\"to\":\"Workflow\",\"label\":\"workflows\"},{\"from\":\"Project\",\"to\":\"ProjectVersion\",\"label\":\"versions\"},{\"from\":\"ProjectVersion\",\"to\":\"WorkflowRun\",\"label\":\"runs\"},{\"from\":\"Referrer\",\"to\":\"Referrer\",\"label\":\"references\"},{\"from\":\"Referrer\",\"to\":\"Workflow\",\"label\":\"workflows\"},{\"from\":\"User\",\"to\":\"Membership\",\"label\":\"memberships\"},{\"from\":\"Workflow\",\"to\":\"RobotAccount\",\"label\":\"robotaccounts\"},{\"from\":\"Workflow\",\"to\":\"WorkflowRun\",\"label\":\"workflowruns\"},{\"from\":\"Workflow\",\"to\":\"WorkflowContract\",\"label\":\"contract\"},{\"from\":\"Workflow\",\"to\":\"WorkflowRun\",\"label\":\"latest_workflow_run\"},{\"from\":\"WorkflowContract\",\"to\":\"WorkflowContractVersion\",\"label\":\"versions\"},{\"from\":\"WorkflowRun\",\"to\":\"WorkflowContractVersion\",\"label\":\"contract_version\"},{\"from\":\"WorkflowRun\",\"to\":\"CASBackend\",\"label\":\"cas_backends\"},{\"from\":\"WorkflowRun\",\"to\":\"Attestation\",\"label\":\"attestation_bundle\"}]}"); const nodes = new vis.DataSet((entGraph.nodes || []).map(n => ({ id: n.id, diff --git a/app/controlplane/pkg/data/ent/schema/membership.go b/app/controlplane/pkg/data/ent/schema/membership.go index 76e26a96b..c6296410b 100644 --- a/app/controlplane/pkg/data/ent/schema/membership.go +++ b/app/controlplane/pkg/data/ent/schema/membership.go @@ -1,5 +1,5 @@ // -// Copyright 2023 The Chainloop Authors. +// Copyright 2023-2025 The Chainloop Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -56,15 +56,26 @@ func (Membership) Fields() []ent.Field { field.Enum("resource_type").GoType(authz.ResourceType("")).Optional(), field.UUID("resource_id", uuid.UUID{}).Optional(), + + field.UUID("organization_id", uuid.UUID{}), + field.UUID("user_id", uuid.UUID{}), } } func (Membership) Edges() []ent.Edge { return []ent.Edge{ // Deprecated: use polymorphic membership instead - edge.From("organization", Organization.Type).Ref("memberships").Unique(), + edge.From("organization", Organization.Type). + Field("organization_id"). + Ref("memberships"). + Unique(). + Required(), // Deprecated: use polymorphic membership instead - edge.From("user", User.Type).Ref("memberships").Unique(), + edge.From("user", User.Type). + Field("user_id"). + Ref("memberships"). + Unique(). + Required(), } } diff --git a/app/controlplane/pkg/data/ent/user/user.go b/app/controlplane/pkg/data/ent/user/user.go index 4fdcdf9d0..909249627 100644 --- a/app/controlplane/pkg/data/ent/user/user.go +++ b/app/controlplane/pkg/data/ent/user/user.go @@ -39,7 +39,7 @@ const ( // It exists in this package in order to avoid circular dependency with the "membership" package. MembershipsInverseTable = "memberships" // MembershipsColumn is the table column denoting the memberships relation/edge. - MembershipsColumn = "user_memberships" + MembershipsColumn = "user_id" // GroupTable is the table that holds the group relation/edge. The primary key declared below. GroupTable = "group_memberships" // GroupInverseTable is the table name for the Group entity. diff --git a/app/controlplane/pkg/data/ent/user_query.go b/app/controlplane/pkg/data/ent/user_query.go index 7b208b01b..1ed1a6dc1 100644 --- a/app/controlplane/pkg/data/ent/user_query.go +++ b/app/controlplane/pkg/data/ent/user_query.go @@ -508,7 +508,9 @@ func (uq *UserQuery) loadMemberships(ctx context.Context, query *MembershipQuery init(nodes[i]) } } - query.withFKs = true + if len(query.ctx.Fields) > 0 { + query.ctx.AppendFieldOnce(membership.FieldUserID) + } query.Where(predicate.Membership(func(s *sql.Selector) { s.Where(sql.InValues(s.C(user.MembershipsColumn), fks...)) })) @@ -517,13 +519,10 @@ func (uq *UserQuery) loadMemberships(ctx context.Context, query *MembershipQuery return err } for _, n := range neighbors { - fk := n.user_memberships - if fk == nil { - return fmt.Errorf(`foreign-key "user_memberships" is nil for node %v`, n.ID) - } - node, ok := nodeids[*fk] + fk := n.UserID + node, ok := nodeids[fk] if !ok { - return fmt.Errorf(`unexpected referenced foreign-key "user_memberships" returned %v for node %v`, *fk, n.ID) + return fmt.Errorf(`unexpected referenced foreign-key "user_id" returned %v for node %v`, fk, n.ID) } assign(node, n) }