diff --git a/api/proto/API.md b/api/proto/API.md index 88d10f1c..6bb3d394 100644 --- a/api/proto/API.md +++ b/api/proto/API.md @@ -483,6 +483,12 @@ - [user/messages/user_svc/add_role_response.proto](#user_messages_user_svc_add_role_response-proto) - [AddRoleResponse](#user-messages-user_svc-AddRoleResponse) +- [user/messages/user_svc/edit_request.proto](#user_messages_user_svc_edit_request-proto) + - [EditRequest](#user-messages-user_svc-EditRequest) + +- [user/messages/user_svc/edit_response.proto](#user_messages_user_svc_edit_response-proto) + - [EditResponse](#user-messages-user_svc-EditResponse) + - [user/messages/user_svc/get_request.proto](#user_messages_user_svc_get_request-proto) - [GetRequest](#user-messages-user_svc-GetRequest) @@ -789,6 +795,9 @@ See components/backend/internal/middleware/rbac.go. | email | [string](#string) | | | | roles | [GlobalRole](#user-entities-GlobalRole) | repeated | Populated from casbin on fetch; not persisted in ent DB. | | modified_at | [google.protobuf.Timestamp](#google-protobuf-Timestamp) | | | +| renku_url | [string](#string) | | User accounts links. | +| github_url | [string](#string) | | | +| linkedin_url | [string](#string) | | | @@ -5746,6 +5755,71 @@ casbin role for this hackathon; `is_waiting` is false once approved. + +

Top

+ +## user/messages/user_svc/edit_request.proto + + + + + +### EditRequest + + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| user_id | [string](#string) | | | +| renku_url | [string](#string) | optional | | +| github_url | [string](#string) | optional | | +| linkedin_url | [string](#string) | optional | | + + + + + + + + + + + + + + + + +

Top

+ +## user/messages/user_svc/edit_response.proto + + + + + +### EditResponse + + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| user | [user.entities.User](#user-entities-User) | | | + + + + + + + + + + + + + + +

Top

@@ -6064,6 +6138,7 @@ casbin role for this hackathon; `is_waiting` is false once approved. | ----------- | ------------ | ------------- | ------------| | List | [messages.user_svc.ListRequest](#user-messages-user_svc-ListRequest) | [messages.user_svc.ListResponse](#user-messages-user_svc-ListResponse) | | | Get | [messages.user_svc.GetRequest](#user-messages-user_svc-GetRequest) | [messages.user_svc.GetResponse](#user-messages-user_svc-GetResponse) | | +| Edit | [messages.user_svc.EditRequest](#user-messages-user_svc-EditRequest) | [messages.user_svc.EditResponse](#user-messages-user_svc-EditResponse) | | | WhoAmI | [messages.user_svc.WhoAmIRequest](#user-messages-user_svc-WhoAmIRequest) | [messages.user_svc.WhoAmIResponse](#user-messages-user_svc-WhoAmIResponse) | | | Register | [messages.user_svc.RegisterRequest](#user-messages-user_svc-RegisterRequest) | [messages.user_svc.RegisterResponse](#user-messages-user_svc-RegisterResponse) | | | AddRole | [messages.user_svc.AddRoleRequest](#user-messages-user_svc-AddRoleRequest) | [messages.user_svc.AddRoleResponse](#user-messages-user_svc-AddRoleResponse) | | diff --git a/api/proto/user/entities/user.proto b/api/proto/user/entities/user.proto index bfd08072..a8afaedd 100644 --- a/api/proto/user/entities/user.proto +++ b/api/proto/user/entities/user.proto @@ -17,4 +17,9 @@ message User { // Populated from casbin on fetch; not persisted in ent DB. repeated GlobalRole roles = 7; google.protobuf.Timestamp modified_at = 8; + + // User accounts links. + string renku_url = 9; + string github_url = 10; + string linkedin_url = 11; } diff --git a/api/proto/user/messages/user_svc/add_role_request.proto b/api/proto/user/messages/user_svc/add_role_request.proto index 34e07b2f..ed6a63a6 100644 --- a/api/proto/user/messages/user_svc/add_role_request.proto +++ b/api/proto/user/messages/user_svc/add_role_request.proto @@ -2,11 +2,12 @@ syntax = "proto3"; package user.messages.user_svc; +import "buf/validate/validate.proto"; import "user/entities/global_role.proto"; option go_package = "github.com/swissdatasciencecenter/hackagon/components/backend/internal/proto/user/messages/user_svc"; message AddRoleRequest { - string user_id = 1; + string user_id = 1 [(buf.validate.field).string.uuid = true]; user.entities.GlobalRole role = 2; } diff --git a/api/proto/user/messages/user_svc/edit_request.proto b/api/proto/user/messages/user_svc/edit_request.proto new file mode 100644 index 00000000..8c56c431 --- /dev/null +++ b/api/proto/user/messages/user_svc/edit_request.proto @@ -0,0 +1,14 @@ +syntax = "proto3"; + +package user.messages.user_svc; + +import "buf/validate/validate.proto"; + +option go_package = "github.com/swissdatasciencecenter/hackagon/components/backend/internal/proto/user/messages/user_svc"; + +message EditRequest { + string user_id = 1 [(buf.validate.field).string.uuid = true]; + optional string renku_url = 2 [(buf.validate.field).string.max_len = 2048]; + optional string github_url = 3 [(buf.validate.field).string.max_len = 2048]; + optional string linkedin_url = 4 [(buf.validate.field).string.max_len = 2048]; +} diff --git a/api/proto/user/messages/user_svc/edit_response.proto b/api/proto/user/messages/user_svc/edit_response.proto new file mode 100644 index 00000000..8006cddd --- /dev/null +++ b/api/proto/user/messages/user_svc/edit_response.proto @@ -0,0 +1,11 @@ +syntax = "proto3"; + +package user.messages.user_svc; + +import "user/entities/user.proto"; + +option go_package = "github.com/swissdatasciencecenter/hackagon/components/backend/internal/proto/user/messages/user_svc"; + +message EditResponse { + user.entities.User user = 1; +} diff --git a/api/proto/user/messages/user_svc/get_request.proto b/api/proto/user/messages/user_svc/get_request.proto index 41158f51..87def346 100644 --- a/api/proto/user/messages/user_svc/get_request.proto +++ b/api/proto/user/messages/user_svc/get_request.proto @@ -2,8 +2,10 @@ syntax = "proto3"; package user.messages.user_svc; +import "buf/validate/validate.proto"; + option go_package = "github.com/swissdatasciencecenter/hackagon/components/backend/internal/proto/user/messages/user_svc"; message GetRequest { - string user_id = 1; + string user_id = 1 [(buf.validate.field).string.uuid = true]; } diff --git a/api/proto/user/messages/user_svc/remove_role_request.proto b/api/proto/user/messages/user_svc/remove_role_request.proto index bf42e30d..3228993f 100644 --- a/api/proto/user/messages/user_svc/remove_role_request.proto +++ b/api/proto/user/messages/user_svc/remove_role_request.proto @@ -2,11 +2,12 @@ syntax = "proto3"; package user.messages.user_svc; +import "buf/validate/validate.proto"; import "user/entities/global_role.proto"; option go_package = "github.com/swissdatasciencecenter/hackagon/components/backend/internal/proto/user/messages/user_svc"; message RemoveRoleRequest { - string user_id = 1; + string user_id = 1 [(buf.validate.field).string.uuid = true]; user.entities.GlobalRole role = 2; } diff --git a/api/proto/user/user_service.proto b/api/proto/user/user_service.proto index de4e204c..b122599b 100644 --- a/api/proto/user/user_service.proto +++ b/api/proto/user/user_service.proto @@ -4,6 +4,8 @@ package user; import "user/messages/user_svc/add_role_request.proto"; import "user/messages/user_svc/add_role_response.proto"; +import "user/messages/user_svc/edit_request.proto"; +import "user/messages/user_svc/edit_response.proto"; import "user/messages/user_svc/get_request.proto"; import "user/messages/user_svc/get_response.proto"; import "user/messages/user_svc/list_request.proto"; @@ -20,6 +22,7 @@ option go_package = "github.com/swissdatasciencecenter/hackagon/components/backe service UserService { rpc List(user.messages.user_svc.ListRequest) returns (user.messages.user_svc.ListResponse); rpc Get(user.messages.user_svc.GetRequest) returns (user.messages.user_svc.GetResponse); + rpc Edit(user.messages.user_svc.EditRequest) returns (user.messages.user_svc.EditResponse); rpc WhoAmI(user.messages.user_svc.WhoAmIRequest) returns (user.messages.user_svc.WhoAmIResponse); rpc Register(user.messages.user_svc.RegisterRequest) returns (user.messages.user_svc.RegisterResponse); rpc AddRole(user.messages.user_svc.AddRoleRequest) returns (user.messages.user_svc.AddRoleResponse); diff --git a/components/backend/Schema.md b/components/backend/Schema.md index adc2f85d..d3942900 100644 --- a/components/backend/Schema.md +++ b/components/backend/Schema.md @@ -394,6 +394,9 @@ An authenticated user, synced from Keycloak on first login. | `keycloak_id` | string | yes | yes | no | no | Unique identifier from Keycloak (sub claim). | | `display_name` | string | no | no | no | yes | Preferred display name of the user. | | `email` | string | no | no | no | yes | Email of the user, same as in Keycloak | +| `renku` | string | no | no | no | yes | Renku account of the user. | +| `github` | string | no | no | no | yes | Github account of the user. | +| `linkedin` | string | no | no | no | yes | LinkedIn account of the user. | | `created_at` | time.Time | yes | no | yes | yes | Timestamp when the user was first seen. | | `modified_at` | time.Time | yes | no | no | yes | Timestamp of the last profile update. | diff --git a/components/backend/db/schema/user.go b/components/backend/db/schema/user.go index 80ced4ff..1be94629 100644 --- a/components/backend/db/schema/user.go +++ b/components/backend/db/schema/user.go @@ -32,6 +32,12 @@ func (User) Fields() []ent.Field { Comment("Preferred display name of the user."), field.String("email").Optional().Default(""). Comment("Email of the user, same as in Keycloak"), + field.String("renku").Optional().Default(""). + Comment("Renku account of the user."), + field.String("github").Optional().Default(""). + Comment("Github account of the user."), + field.String("linkedin").Optional().Default(""). + Comment("LinkedIn account of the user."), field.Time("created_at").Immutable().Default(time.Now). Comment("Timestamp when the user was first seen."), field.Time("modified_at"). diff --git a/components/backend/ent/migrate/schema.go b/components/backend/ent/migrate/schema.go index 09d1dbb4..045d768f 100644 --- a/components/backend/ent/migrate/schema.go +++ b/components/backend/ent/migrate/schema.go @@ -635,6 +635,9 @@ var ( {Name: "keycloak_id", Type: field.TypeString, Unique: true}, {Name: "display_name", Type: field.TypeString, Nullable: true, Default: ""}, {Name: "email", Type: field.TypeString, Nullable: true, Default: ""}, + {Name: "renku", Type: field.TypeString, Nullable: true, Default: ""}, + {Name: "github", Type: field.TypeString, Nullable: true, Default: ""}, + {Name: "linkedin", Type: field.TypeString, Nullable: true, Default: ""}, {Name: "created_at", Type: field.TypeTime}, {Name: "modified_at", Type: field.TypeTime}, } diff --git a/components/backend/ent/mutation.go b/components/backend/ent/mutation.go index 6acf257e..f8ba1589 100644 --- a/components/backend/ent/mutation.go +++ b/components/backend/ent/mutation.go @@ -12483,6 +12483,9 @@ type UserMutation struct { keycloak_id *string display_name *string email *string + renku *string + github *string + linkedin *string created_at *time.Time modified_at *time.Time clearedFields map[string]struct{} @@ -12843,6 +12846,153 @@ func (m *UserMutation) ResetEmail() { delete(m.clearedFields, user.FieldEmail) } +// SetRenku sets the "renku" field. +func (m *UserMutation) SetRenku(s string) { + m.renku = &s +} + +// Renku returns the value of the "renku" field in the mutation. +func (m *UserMutation) Renku() (r string, exists bool) { + v := m.renku + if v == nil { + return + } + return *v, true +} + +// OldRenku returns the old "renku" field's value of the User entity. +// If the User 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 *UserMutation) OldRenku(ctx context.Context) (v string, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldRenku is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldRenku requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldRenku: %w", err) + } + return oldValue.Renku, nil +} + +// ClearRenku clears the value of the "renku" field. +func (m *UserMutation) ClearRenku() { + m.renku = nil + m.clearedFields[user.FieldRenku] = struct{}{} +} + +// RenkuCleared returns if the "renku" field was cleared in this mutation. +func (m *UserMutation) RenkuCleared() bool { + _, ok := m.clearedFields[user.FieldRenku] + return ok +} + +// ResetRenku resets all changes to the "renku" field. +func (m *UserMutation) ResetRenku() { + m.renku = nil + delete(m.clearedFields, user.FieldRenku) +} + +// SetGithub sets the "github" field. +func (m *UserMutation) SetGithub(s string) { + m.github = &s +} + +// Github returns the value of the "github" field in the mutation. +func (m *UserMutation) Github() (r string, exists bool) { + v := m.github + if v == nil { + return + } + return *v, true +} + +// OldGithub returns the old "github" field's value of the User entity. +// If the User 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 *UserMutation) OldGithub(ctx context.Context) (v string, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldGithub is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldGithub requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldGithub: %w", err) + } + return oldValue.Github, nil +} + +// ClearGithub clears the value of the "github" field. +func (m *UserMutation) ClearGithub() { + m.github = nil + m.clearedFields[user.FieldGithub] = struct{}{} +} + +// GithubCleared returns if the "github" field was cleared in this mutation. +func (m *UserMutation) GithubCleared() bool { + _, ok := m.clearedFields[user.FieldGithub] + return ok +} + +// ResetGithub resets all changes to the "github" field. +func (m *UserMutation) ResetGithub() { + m.github = nil + delete(m.clearedFields, user.FieldGithub) +} + +// SetLinkedin sets the "linkedin" field. +func (m *UserMutation) SetLinkedin(s string) { + m.linkedin = &s +} + +// Linkedin returns the value of the "linkedin" field in the mutation. +func (m *UserMutation) Linkedin() (r string, exists bool) { + v := m.linkedin + if v == nil { + return + } + return *v, true +} + +// OldLinkedin returns the old "linkedin" field's value of the User entity. +// If the User 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 *UserMutation) OldLinkedin(ctx context.Context) (v string, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldLinkedin is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldLinkedin requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldLinkedin: %w", err) + } + return oldValue.Linkedin, nil +} + +// ClearLinkedin clears the value of the "linkedin" field. +func (m *UserMutation) ClearLinkedin() { + m.linkedin = nil + m.clearedFields[user.FieldLinkedin] = struct{}{} +} + +// LinkedinCleared returns if the "linkedin" field was cleared in this mutation. +func (m *UserMutation) LinkedinCleared() bool { + _, ok := m.clearedFields[user.FieldLinkedin] + return ok +} + +// ResetLinkedin resets all changes to the "linkedin" field. +func (m *UserMutation) ResetLinkedin() { + m.linkedin = nil + delete(m.clearedFields, user.FieldLinkedin) +} + // SetCreatedAt sets the "created_at" field. func (m *UserMutation) SetCreatedAt(t time.Time) { m.created_at = &t @@ -14353,7 +14503,7 @@ func (m *UserMutation) Type() string { // order to get all numeric fields that were incremented/decremented, call // AddedFields(). func (m *UserMutation) Fields() []string { - fields := make([]string, 0, 6) + fields := make([]string, 0, 9) if m.username != nil { fields = append(fields, user.FieldUsername) } @@ -14366,6 +14516,15 @@ func (m *UserMutation) Fields() []string { if m.email != nil { fields = append(fields, user.FieldEmail) } + if m.renku != nil { + fields = append(fields, user.FieldRenku) + } + if m.github != nil { + fields = append(fields, user.FieldGithub) + } + if m.linkedin != nil { + fields = append(fields, user.FieldLinkedin) + } if m.created_at != nil { fields = append(fields, user.FieldCreatedAt) } @@ -14388,6 +14547,12 @@ func (m *UserMutation) Field(name string) (ent.Value, bool) { return m.DisplayName() case user.FieldEmail: return m.Email() + case user.FieldRenku: + return m.Renku() + case user.FieldGithub: + return m.Github() + case user.FieldLinkedin: + return m.Linkedin() case user.FieldCreatedAt: return m.CreatedAt() case user.FieldModifiedAt: @@ -14409,6 +14574,12 @@ func (m *UserMutation) OldField(ctx context.Context, name string) (ent.Value, er return m.OldDisplayName(ctx) case user.FieldEmail: return m.OldEmail(ctx) + case user.FieldRenku: + return m.OldRenku(ctx) + case user.FieldGithub: + return m.OldGithub(ctx) + case user.FieldLinkedin: + return m.OldLinkedin(ctx) case user.FieldCreatedAt: return m.OldCreatedAt(ctx) case user.FieldModifiedAt: @@ -14450,6 +14621,27 @@ func (m *UserMutation) SetField(name string, value ent.Value) error { } m.SetEmail(v) return nil + case user.FieldRenku: + v, ok := value.(string) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetRenku(v) + return nil + case user.FieldGithub: + v, ok := value.(string) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetGithub(v) + return nil + case user.FieldLinkedin: + v, ok := value.(string) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetLinkedin(v) + return nil case user.FieldCreatedAt: v, ok := value.(time.Time) if !ok { @@ -14500,6 +14692,15 @@ func (m *UserMutation) ClearedFields() []string { if m.FieldCleared(user.FieldEmail) { fields = append(fields, user.FieldEmail) } + if m.FieldCleared(user.FieldRenku) { + fields = append(fields, user.FieldRenku) + } + if m.FieldCleared(user.FieldGithub) { + fields = append(fields, user.FieldGithub) + } + if m.FieldCleared(user.FieldLinkedin) { + fields = append(fields, user.FieldLinkedin) + } return fields } @@ -14520,6 +14721,15 @@ func (m *UserMutation) ClearField(name string) error { case user.FieldEmail: m.ClearEmail() return nil + case user.FieldRenku: + m.ClearRenku() + return nil + case user.FieldGithub: + m.ClearGithub() + return nil + case user.FieldLinkedin: + m.ClearLinkedin() + return nil } return fmt.Errorf("unknown User nullable field %s", name) } @@ -14540,6 +14750,15 @@ func (m *UserMutation) ResetField(name string) error { case user.FieldEmail: m.ResetEmail() return nil + case user.FieldRenku: + m.ResetRenku() + return nil + case user.FieldGithub: + m.ResetGithub() + return nil + case user.FieldLinkedin: + m.ResetLinkedin() + return nil case user.FieldCreatedAt: m.ResetCreatedAt() return nil diff --git a/components/backend/ent/runtime/runtime.go b/components/backend/ent/runtime/runtime.go index 594b5a6a..f2a3ea03 100644 --- a/components/backend/ent/runtime/runtime.go +++ b/components/backend/ent/runtime/runtime.go @@ -364,12 +364,24 @@ func init() { userDescEmail := userFields[3].Descriptor() // user.DefaultEmail holds the default value on creation for the email field. user.DefaultEmail = userDescEmail.Default.(string) + // userDescRenku is the schema descriptor for renku field. + userDescRenku := userFields[4].Descriptor() + // user.DefaultRenku holds the default value on creation for the renku field. + user.DefaultRenku = userDescRenku.Default.(string) + // userDescGithub is the schema descriptor for github field. + userDescGithub := userFields[5].Descriptor() + // user.DefaultGithub holds the default value on creation for the github field. + user.DefaultGithub = userDescGithub.Default.(string) + // userDescLinkedin is the schema descriptor for linkedin field. + userDescLinkedin := userFields[6].Descriptor() + // user.DefaultLinkedin holds the default value on creation for the linkedin field. + user.DefaultLinkedin = userDescLinkedin.Default.(string) // userDescCreatedAt is the schema descriptor for created_at field. - userDescCreatedAt := userFields[4].Descriptor() + userDescCreatedAt := userFields[7].Descriptor() // user.DefaultCreatedAt holds the default value on creation for the created_at field. user.DefaultCreatedAt = userDescCreatedAt.Default.(func() time.Time) // userDescModifiedAt is the schema descriptor for modified_at field. - userDescModifiedAt := userFields[5].Descriptor() + userDescModifiedAt := userFields[8].Descriptor() // user.DefaultModifiedAt holds the default value on creation for the modified_at field. user.DefaultModifiedAt = userDescModifiedAt.Default.(func() time.Time) // user.UpdateDefaultModifiedAt holds the default value on update for the modified_at field. diff --git a/components/backend/ent/user.go b/components/backend/ent/user.go index 5a780f0b..7adc6758 100644 --- a/components/backend/ent/user.go +++ b/components/backend/ent/user.go @@ -26,6 +26,12 @@ type User struct { DisplayName string `json:"display_name,omitempty"` // Email of the user, same as in Keycloak Email string `json:"email,omitempty"` + // Renku account of the user. + Renku string `json:"renku,omitempty"` + // Github account of the user. + Github string `json:"github,omitempty"` + // LinkedIn account of the user. + Linkedin string `json:"linkedin,omitempty"` // Timestamp when the user was first seen. CreatedAt time.Time `json:"created_at,omitempty"` // Timestamp of the last profile update. @@ -356,7 +362,7 @@ func (*User) scanValues(columns []string) ([]any, error) { values := make([]any, len(columns)) for i := range columns { switch columns[i] { - case user.FieldUsername, user.FieldKeycloakID, user.FieldDisplayName, user.FieldEmail: + case user.FieldUsername, user.FieldKeycloakID, user.FieldDisplayName, user.FieldEmail, user.FieldRenku, user.FieldGithub, user.FieldLinkedin: values[i] = new(sql.NullString) case user.FieldCreatedAt, user.FieldModifiedAt: values[i] = new(sql.NullTime) @@ -407,6 +413,24 @@ func (_m *User) assignValues(columns []string, values []any) error { } else if value.Valid { _m.Email = value.String } + case user.FieldRenku: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field renku", values[i]) + } else if value.Valid { + _m.Renku = value.String + } + case user.FieldGithub: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field github", values[i]) + } else if value.Valid { + _m.Github = value.String + } + case user.FieldLinkedin: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field linkedin", values[i]) + } else if value.Valid { + _m.Linkedin = value.String + } case user.FieldCreatedAt: if value, ok := values[i].(*sql.NullTime); !ok { return fmt.Errorf("unexpected type %T for field created_at", values[i]) @@ -607,6 +631,15 @@ func (_m *User) String() string { builder.WriteString("email=") builder.WriteString(_m.Email) builder.WriteString(", ") + builder.WriteString("renku=") + builder.WriteString(_m.Renku) + builder.WriteString(", ") + builder.WriteString("github=") + builder.WriteString(_m.Github) + builder.WriteString(", ") + builder.WriteString("linkedin=") + builder.WriteString(_m.Linkedin) + builder.WriteString(", ") builder.WriteString("created_at=") builder.WriteString(_m.CreatedAt.Format(time.ANSIC)) builder.WriteString(", ") diff --git a/components/backend/ent/user/user.go b/components/backend/ent/user/user.go index 6c31c2e7..be15183b 100644 --- a/components/backend/ent/user/user.go +++ b/components/backend/ent/user/user.go @@ -23,6 +23,12 @@ const ( FieldDisplayName = "display_name" // FieldEmail holds the string denoting the email field in the database. FieldEmail = "email" + // FieldRenku holds the string denoting the renku field in the database. + FieldRenku = "renku" + // FieldGithub holds the string denoting the github field in the database. + FieldGithub = "github" + // FieldLinkedin holds the string denoting the linkedin field in the database. + FieldLinkedin = "linkedin" // FieldCreatedAt holds the string denoting the created_at field in the database. FieldCreatedAt = "created_at" // FieldModifiedAt holds the string denoting the modified_at field in the database. @@ -280,6 +286,9 @@ var Columns = []string{ FieldKeycloakID, FieldDisplayName, FieldEmail, + FieldRenku, + FieldGithub, + FieldLinkedin, FieldCreatedAt, FieldModifiedAt, } @@ -319,6 +328,12 @@ var ( DefaultDisplayName string // DefaultEmail holds the default value on creation for the "email" field. DefaultEmail string + // DefaultRenku holds the default value on creation for the "renku" field. + DefaultRenku string + // DefaultGithub holds the default value on creation for the "github" field. + DefaultGithub string + // DefaultLinkedin holds the default value on creation for the "linkedin" field. + DefaultLinkedin string // DefaultCreatedAt holds the default value on creation for the "created_at" field. DefaultCreatedAt func() time.Time // DefaultModifiedAt holds the default value on creation for the "modified_at" field. @@ -357,6 +372,21 @@ func ByEmail(opts ...sql.OrderTermOption) OrderOption { return sql.OrderByField(FieldEmail, opts...).ToFunc() } +// ByRenku orders the results by the renku field. +func ByRenku(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldRenku, opts...).ToFunc() +} + +// ByGithub orders the results by the github field. +func ByGithub(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldGithub, opts...).ToFunc() +} + +// ByLinkedin orders the results by the linkedin field. +func ByLinkedin(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldLinkedin, opts...).ToFunc() +} + // ByCreatedAt orders the results by the created_at field. func ByCreatedAt(opts ...sql.OrderTermOption) OrderOption { return sql.OrderByField(FieldCreatedAt, opts...).ToFunc() diff --git a/components/backend/ent/user/where.go b/components/backend/ent/user/where.go index 7c254b90..9452e4fb 100644 --- a/components/backend/ent/user/where.go +++ b/components/backend/ent/user/where.go @@ -76,6 +76,21 @@ func Email(v string) predicate.User { return predicate.User(sql.FieldEQ(FieldEmail, v)) } +// Renku applies equality check predicate on the "renku" field. It's identical to RenkuEQ. +func Renku(v string) predicate.User { + return predicate.User(sql.FieldEQ(FieldRenku, v)) +} + +// Github applies equality check predicate on the "github" field. It's identical to GithubEQ. +func Github(v string) predicate.User { + return predicate.User(sql.FieldEQ(FieldGithub, v)) +} + +// Linkedin applies equality check predicate on the "linkedin" field. It's identical to LinkedinEQ. +func Linkedin(v string) predicate.User { + return predicate.User(sql.FieldEQ(FieldLinkedin, v)) +} + // CreatedAt applies equality check predicate on the "created_at" field. It's identical to CreatedAtEQ. func CreatedAt(v time.Time) predicate.User { return predicate.User(sql.FieldEQ(FieldCreatedAt, v)) @@ -366,6 +381,231 @@ func EmailContainsFold(v string) predicate.User { return predicate.User(sql.FieldContainsFold(FieldEmail, v)) } +// RenkuEQ applies the EQ predicate on the "renku" field. +func RenkuEQ(v string) predicate.User { + return predicate.User(sql.FieldEQ(FieldRenku, v)) +} + +// RenkuNEQ applies the NEQ predicate on the "renku" field. +func RenkuNEQ(v string) predicate.User { + return predicate.User(sql.FieldNEQ(FieldRenku, v)) +} + +// RenkuIn applies the In predicate on the "renku" field. +func RenkuIn(vs ...string) predicate.User { + return predicate.User(sql.FieldIn(FieldRenku, vs...)) +} + +// RenkuNotIn applies the NotIn predicate on the "renku" field. +func RenkuNotIn(vs ...string) predicate.User { + return predicate.User(sql.FieldNotIn(FieldRenku, vs...)) +} + +// RenkuGT applies the GT predicate on the "renku" field. +func RenkuGT(v string) predicate.User { + return predicate.User(sql.FieldGT(FieldRenku, v)) +} + +// RenkuGTE applies the GTE predicate on the "renku" field. +func RenkuGTE(v string) predicate.User { + return predicate.User(sql.FieldGTE(FieldRenku, v)) +} + +// RenkuLT applies the LT predicate on the "renku" field. +func RenkuLT(v string) predicate.User { + return predicate.User(sql.FieldLT(FieldRenku, v)) +} + +// RenkuLTE applies the LTE predicate on the "renku" field. +func RenkuLTE(v string) predicate.User { + return predicate.User(sql.FieldLTE(FieldRenku, v)) +} + +// RenkuContains applies the Contains predicate on the "renku" field. +func RenkuContains(v string) predicate.User { + return predicate.User(sql.FieldContains(FieldRenku, v)) +} + +// RenkuHasPrefix applies the HasPrefix predicate on the "renku" field. +func RenkuHasPrefix(v string) predicate.User { + return predicate.User(sql.FieldHasPrefix(FieldRenku, v)) +} + +// RenkuHasSuffix applies the HasSuffix predicate on the "renku" field. +func RenkuHasSuffix(v string) predicate.User { + return predicate.User(sql.FieldHasSuffix(FieldRenku, v)) +} + +// RenkuIsNil applies the IsNil predicate on the "renku" field. +func RenkuIsNil() predicate.User { + return predicate.User(sql.FieldIsNull(FieldRenku)) +} + +// RenkuNotNil applies the NotNil predicate on the "renku" field. +func RenkuNotNil() predicate.User { + return predicate.User(sql.FieldNotNull(FieldRenku)) +} + +// RenkuEqualFold applies the EqualFold predicate on the "renku" field. +func RenkuEqualFold(v string) predicate.User { + return predicate.User(sql.FieldEqualFold(FieldRenku, v)) +} + +// RenkuContainsFold applies the ContainsFold predicate on the "renku" field. +func RenkuContainsFold(v string) predicate.User { + return predicate.User(sql.FieldContainsFold(FieldRenku, v)) +} + +// GithubEQ applies the EQ predicate on the "github" field. +func GithubEQ(v string) predicate.User { + return predicate.User(sql.FieldEQ(FieldGithub, v)) +} + +// GithubNEQ applies the NEQ predicate on the "github" field. +func GithubNEQ(v string) predicate.User { + return predicate.User(sql.FieldNEQ(FieldGithub, v)) +} + +// GithubIn applies the In predicate on the "github" field. +func GithubIn(vs ...string) predicate.User { + return predicate.User(sql.FieldIn(FieldGithub, vs...)) +} + +// GithubNotIn applies the NotIn predicate on the "github" field. +func GithubNotIn(vs ...string) predicate.User { + return predicate.User(sql.FieldNotIn(FieldGithub, vs...)) +} + +// GithubGT applies the GT predicate on the "github" field. +func GithubGT(v string) predicate.User { + return predicate.User(sql.FieldGT(FieldGithub, v)) +} + +// GithubGTE applies the GTE predicate on the "github" field. +func GithubGTE(v string) predicate.User { + return predicate.User(sql.FieldGTE(FieldGithub, v)) +} + +// GithubLT applies the LT predicate on the "github" field. +func GithubLT(v string) predicate.User { + return predicate.User(sql.FieldLT(FieldGithub, v)) +} + +// GithubLTE applies the LTE predicate on the "github" field. +func GithubLTE(v string) predicate.User { + return predicate.User(sql.FieldLTE(FieldGithub, v)) +} + +// GithubContains applies the Contains predicate on the "github" field. +func GithubContains(v string) predicate.User { + return predicate.User(sql.FieldContains(FieldGithub, v)) +} + +// GithubHasPrefix applies the HasPrefix predicate on the "github" field. +func GithubHasPrefix(v string) predicate.User { + return predicate.User(sql.FieldHasPrefix(FieldGithub, v)) +} + +// GithubHasSuffix applies the HasSuffix predicate on the "github" field. +func GithubHasSuffix(v string) predicate.User { + return predicate.User(sql.FieldHasSuffix(FieldGithub, v)) +} + +// GithubIsNil applies the IsNil predicate on the "github" field. +func GithubIsNil() predicate.User { + return predicate.User(sql.FieldIsNull(FieldGithub)) +} + +// GithubNotNil applies the NotNil predicate on the "github" field. +func GithubNotNil() predicate.User { + return predicate.User(sql.FieldNotNull(FieldGithub)) +} + +// GithubEqualFold applies the EqualFold predicate on the "github" field. +func GithubEqualFold(v string) predicate.User { + return predicate.User(sql.FieldEqualFold(FieldGithub, v)) +} + +// GithubContainsFold applies the ContainsFold predicate on the "github" field. +func GithubContainsFold(v string) predicate.User { + return predicate.User(sql.FieldContainsFold(FieldGithub, v)) +} + +// LinkedinEQ applies the EQ predicate on the "linkedin" field. +func LinkedinEQ(v string) predicate.User { + return predicate.User(sql.FieldEQ(FieldLinkedin, v)) +} + +// LinkedinNEQ applies the NEQ predicate on the "linkedin" field. +func LinkedinNEQ(v string) predicate.User { + return predicate.User(sql.FieldNEQ(FieldLinkedin, v)) +} + +// LinkedinIn applies the In predicate on the "linkedin" field. +func LinkedinIn(vs ...string) predicate.User { + return predicate.User(sql.FieldIn(FieldLinkedin, vs...)) +} + +// LinkedinNotIn applies the NotIn predicate on the "linkedin" field. +func LinkedinNotIn(vs ...string) predicate.User { + return predicate.User(sql.FieldNotIn(FieldLinkedin, vs...)) +} + +// LinkedinGT applies the GT predicate on the "linkedin" field. +func LinkedinGT(v string) predicate.User { + return predicate.User(sql.FieldGT(FieldLinkedin, v)) +} + +// LinkedinGTE applies the GTE predicate on the "linkedin" field. +func LinkedinGTE(v string) predicate.User { + return predicate.User(sql.FieldGTE(FieldLinkedin, v)) +} + +// LinkedinLT applies the LT predicate on the "linkedin" field. +func LinkedinLT(v string) predicate.User { + return predicate.User(sql.FieldLT(FieldLinkedin, v)) +} + +// LinkedinLTE applies the LTE predicate on the "linkedin" field. +func LinkedinLTE(v string) predicate.User { + return predicate.User(sql.FieldLTE(FieldLinkedin, v)) +} + +// LinkedinContains applies the Contains predicate on the "linkedin" field. +func LinkedinContains(v string) predicate.User { + return predicate.User(sql.FieldContains(FieldLinkedin, v)) +} + +// LinkedinHasPrefix applies the HasPrefix predicate on the "linkedin" field. +func LinkedinHasPrefix(v string) predicate.User { + return predicate.User(sql.FieldHasPrefix(FieldLinkedin, v)) +} + +// LinkedinHasSuffix applies the HasSuffix predicate on the "linkedin" field. +func LinkedinHasSuffix(v string) predicate.User { + return predicate.User(sql.FieldHasSuffix(FieldLinkedin, v)) +} + +// LinkedinIsNil applies the IsNil predicate on the "linkedin" field. +func LinkedinIsNil() predicate.User { + return predicate.User(sql.FieldIsNull(FieldLinkedin)) +} + +// LinkedinNotNil applies the NotNil predicate on the "linkedin" field. +func LinkedinNotNil() predicate.User { + return predicate.User(sql.FieldNotNull(FieldLinkedin)) +} + +// LinkedinEqualFold applies the EqualFold predicate on the "linkedin" field. +func LinkedinEqualFold(v string) predicate.User { + return predicate.User(sql.FieldEqualFold(FieldLinkedin, v)) +} + +// LinkedinContainsFold applies the ContainsFold predicate on the "linkedin" field. +func LinkedinContainsFold(v string) predicate.User { + return predicate.User(sql.FieldContainsFold(FieldLinkedin, v)) +} + // CreatedAtEQ applies the EQ predicate on the "created_at" field. func CreatedAtEQ(v time.Time) predicate.User { return predicate.User(sql.FieldEQ(FieldCreatedAt, v)) diff --git a/components/backend/ent/user_create.go b/components/backend/ent/user_create.go index 1e0a9bdf..27ed6874 100644 --- a/components/backend/ent/user_create.go +++ b/components/backend/ent/user_create.go @@ -78,6 +78,48 @@ func (_c *UserCreate) SetNillableEmail(v *string) *UserCreate { return _c } +// SetRenku sets the "renku" field. +func (_c *UserCreate) SetRenku(v string) *UserCreate { + _c.mutation.SetRenku(v) + return _c +} + +// SetNillableRenku sets the "renku" field if the given value is not nil. +func (_c *UserCreate) SetNillableRenku(v *string) *UserCreate { + if v != nil { + _c.SetRenku(*v) + } + return _c +} + +// SetGithub sets the "github" field. +func (_c *UserCreate) SetGithub(v string) *UserCreate { + _c.mutation.SetGithub(v) + return _c +} + +// SetNillableGithub sets the "github" field if the given value is not nil. +func (_c *UserCreate) SetNillableGithub(v *string) *UserCreate { + if v != nil { + _c.SetGithub(*v) + } + return _c +} + +// SetLinkedin sets the "linkedin" field. +func (_c *UserCreate) SetLinkedin(v string) *UserCreate { + _c.mutation.SetLinkedin(v) + return _c +} + +// SetNillableLinkedin sets the "linkedin" field if the given value is not nil. +func (_c *UserCreate) SetNillableLinkedin(v *string) *UserCreate { + if v != nil { + _c.SetLinkedin(*v) + } + return _c +} + // SetCreatedAt sets the "created_at" field. func (_c *UserCreate) SetCreatedAt(v time.Time) *UserCreate { _c.mutation.SetCreatedAt(v) @@ -553,6 +595,18 @@ func (_c *UserCreate) defaults() { v := user.DefaultEmail _c.mutation.SetEmail(v) } + if _, ok := _c.mutation.Renku(); !ok { + v := user.DefaultRenku + _c.mutation.SetRenku(v) + } + if _, ok := _c.mutation.Github(); !ok { + v := user.DefaultGithub + _c.mutation.SetGithub(v) + } + if _, ok := _c.mutation.Linkedin(); !ok { + v := user.DefaultLinkedin + _c.mutation.SetLinkedin(v) + } if _, ok := _c.mutation.CreatedAt(); !ok { v := user.DefaultCreatedAt() _c.mutation.SetCreatedAt(v) @@ -638,6 +692,18 @@ func (_c *UserCreate) createSpec() (*User, *sqlgraph.CreateSpec) { _spec.SetField(user.FieldEmail, field.TypeString, value) _node.Email = value } + if value, ok := _c.mutation.Renku(); ok { + _spec.SetField(user.FieldRenku, field.TypeString, value) + _node.Renku = value + } + if value, ok := _c.mutation.Github(); ok { + _spec.SetField(user.FieldGithub, field.TypeString, value) + _node.Github = value + } + if value, ok := _c.mutation.Linkedin(); ok { + _spec.SetField(user.FieldLinkedin, field.TypeString, value) + _node.Linkedin = value + } if value, ok := _c.mutation.CreatedAt(); ok { _spec.SetField(user.FieldCreatedAt, field.TypeTime, value) _node.CreatedAt = value @@ -1182,6 +1248,60 @@ func (u *UserUpsert) ClearEmail() *UserUpsert { return u } +// SetRenku sets the "renku" field. +func (u *UserUpsert) SetRenku(v string) *UserUpsert { + u.Set(user.FieldRenku, v) + return u +} + +// UpdateRenku sets the "renku" field to the value that was provided on create. +func (u *UserUpsert) UpdateRenku() *UserUpsert { + u.SetExcluded(user.FieldRenku) + return u +} + +// ClearRenku clears the value of the "renku" field. +func (u *UserUpsert) ClearRenku() *UserUpsert { + u.SetNull(user.FieldRenku) + return u +} + +// SetGithub sets the "github" field. +func (u *UserUpsert) SetGithub(v string) *UserUpsert { + u.Set(user.FieldGithub, v) + return u +} + +// UpdateGithub sets the "github" field to the value that was provided on create. +func (u *UserUpsert) UpdateGithub() *UserUpsert { + u.SetExcluded(user.FieldGithub) + return u +} + +// ClearGithub clears the value of the "github" field. +func (u *UserUpsert) ClearGithub() *UserUpsert { + u.SetNull(user.FieldGithub) + return u +} + +// SetLinkedin sets the "linkedin" field. +func (u *UserUpsert) SetLinkedin(v string) *UserUpsert { + u.Set(user.FieldLinkedin, v) + return u +} + +// UpdateLinkedin sets the "linkedin" field to the value that was provided on create. +func (u *UserUpsert) UpdateLinkedin() *UserUpsert { + u.SetExcluded(user.FieldLinkedin) + return u +} + +// ClearLinkedin clears the value of the "linkedin" field. +func (u *UserUpsert) ClearLinkedin() *UserUpsert { + u.SetNull(user.FieldLinkedin) + return u +} + // SetModifiedAt sets the "modified_at" field. func (u *UserUpsert) SetModifiedAt(v time.Time) *UserUpsert { u.Set(user.FieldModifiedAt, v) @@ -1315,6 +1435,69 @@ func (u *UserUpsertOne) ClearEmail() *UserUpsertOne { }) } +// SetRenku sets the "renku" field. +func (u *UserUpsertOne) SetRenku(v string) *UserUpsertOne { + return u.Update(func(s *UserUpsert) { + s.SetRenku(v) + }) +} + +// UpdateRenku sets the "renku" field to the value that was provided on create. +func (u *UserUpsertOne) UpdateRenku() *UserUpsertOne { + return u.Update(func(s *UserUpsert) { + s.UpdateRenku() + }) +} + +// ClearRenku clears the value of the "renku" field. +func (u *UserUpsertOne) ClearRenku() *UserUpsertOne { + return u.Update(func(s *UserUpsert) { + s.ClearRenku() + }) +} + +// SetGithub sets the "github" field. +func (u *UserUpsertOne) SetGithub(v string) *UserUpsertOne { + return u.Update(func(s *UserUpsert) { + s.SetGithub(v) + }) +} + +// UpdateGithub sets the "github" field to the value that was provided on create. +func (u *UserUpsertOne) UpdateGithub() *UserUpsertOne { + return u.Update(func(s *UserUpsert) { + s.UpdateGithub() + }) +} + +// ClearGithub clears the value of the "github" field. +func (u *UserUpsertOne) ClearGithub() *UserUpsertOne { + return u.Update(func(s *UserUpsert) { + s.ClearGithub() + }) +} + +// SetLinkedin sets the "linkedin" field. +func (u *UserUpsertOne) SetLinkedin(v string) *UserUpsertOne { + return u.Update(func(s *UserUpsert) { + s.SetLinkedin(v) + }) +} + +// UpdateLinkedin sets the "linkedin" field to the value that was provided on create. +func (u *UserUpsertOne) UpdateLinkedin() *UserUpsertOne { + return u.Update(func(s *UserUpsert) { + s.UpdateLinkedin() + }) +} + +// ClearLinkedin clears the value of the "linkedin" field. +func (u *UserUpsertOne) ClearLinkedin() *UserUpsertOne { + return u.Update(func(s *UserUpsert) { + s.ClearLinkedin() + }) +} + // SetModifiedAt sets the "modified_at" field. func (u *UserUpsertOne) SetModifiedAt(v time.Time) *UserUpsertOne { return u.Update(func(s *UserUpsert) { @@ -1617,6 +1800,69 @@ func (u *UserUpsertBulk) ClearEmail() *UserUpsertBulk { }) } +// SetRenku sets the "renku" field. +func (u *UserUpsertBulk) SetRenku(v string) *UserUpsertBulk { + return u.Update(func(s *UserUpsert) { + s.SetRenku(v) + }) +} + +// UpdateRenku sets the "renku" field to the value that was provided on create. +func (u *UserUpsertBulk) UpdateRenku() *UserUpsertBulk { + return u.Update(func(s *UserUpsert) { + s.UpdateRenku() + }) +} + +// ClearRenku clears the value of the "renku" field. +func (u *UserUpsertBulk) ClearRenku() *UserUpsertBulk { + return u.Update(func(s *UserUpsert) { + s.ClearRenku() + }) +} + +// SetGithub sets the "github" field. +func (u *UserUpsertBulk) SetGithub(v string) *UserUpsertBulk { + return u.Update(func(s *UserUpsert) { + s.SetGithub(v) + }) +} + +// UpdateGithub sets the "github" field to the value that was provided on create. +func (u *UserUpsertBulk) UpdateGithub() *UserUpsertBulk { + return u.Update(func(s *UserUpsert) { + s.UpdateGithub() + }) +} + +// ClearGithub clears the value of the "github" field. +func (u *UserUpsertBulk) ClearGithub() *UserUpsertBulk { + return u.Update(func(s *UserUpsert) { + s.ClearGithub() + }) +} + +// SetLinkedin sets the "linkedin" field. +func (u *UserUpsertBulk) SetLinkedin(v string) *UserUpsertBulk { + return u.Update(func(s *UserUpsert) { + s.SetLinkedin(v) + }) +} + +// UpdateLinkedin sets the "linkedin" field to the value that was provided on create. +func (u *UserUpsertBulk) UpdateLinkedin() *UserUpsertBulk { + return u.Update(func(s *UserUpsert) { + s.UpdateLinkedin() + }) +} + +// ClearLinkedin clears the value of the "linkedin" field. +func (u *UserUpsertBulk) ClearLinkedin() *UserUpsertBulk { + return u.Update(func(s *UserUpsert) { + s.ClearLinkedin() + }) +} + // SetModifiedAt sets the "modified_at" field. func (u *UserUpsertBulk) SetModifiedAt(v time.Time) *UserUpsertBulk { return u.Update(func(s *UserUpsert) { diff --git a/components/backend/ent/user_update.go b/components/backend/ent/user_update.go index e4419bcc..495a2e61 100644 --- a/components/backend/ent/user_update.go +++ b/components/backend/ent/user_update.go @@ -111,6 +111,66 @@ func (_u *UserUpdate) ClearEmail() *UserUpdate { return _u } +// SetRenku sets the "renku" field. +func (_u *UserUpdate) SetRenku(v string) *UserUpdate { + _u.mutation.SetRenku(v) + return _u +} + +// SetNillableRenku sets the "renku" field if the given value is not nil. +func (_u *UserUpdate) SetNillableRenku(v *string) *UserUpdate { + if v != nil { + _u.SetRenku(*v) + } + return _u +} + +// ClearRenku clears the value of the "renku" field. +func (_u *UserUpdate) ClearRenku() *UserUpdate { + _u.mutation.ClearRenku() + return _u +} + +// SetGithub sets the "github" field. +func (_u *UserUpdate) SetGithub(v string) *UserUpdate { + _u.mutation.SetGithub(v) + return _u +} + +// SetNillableGithub sets the "github" field if the given value is not nil. +func (_u *UserUpdate) SetNillableGithub(v *string) *UserUpdate { + if v != nil { + _u.SetGithub(*v) + } + return _u +} + +// ClearGithub clears the value of the "github" field. +func (_u *UserUpdate) ClearGithub() *UserUpdate { + _u.mutation.ClearGithub() + return _u +} + +// SetLinkedin sets the "linkedin" field. +func (_u *UserUpdate) SetLinkedin(v string) *UserUpdate { + _u.mutation.SetLinkedin(v) + return _u +} + +// SetNillableLinkedin sets the "linkedin" field if the given value is not nil. +func (_u *UserUpdate) SetNillableLinkedin(v *string) *UserUpdate { + if v != nil { + _u.SetLinkedin(*v) + } + return _u +} + +// ClearLinkedin clears the value of the "linkedin" field. +func (_u *UserUpdate) ClearLinkedin() *UserUpdate { + _u.mutation.ClearLinkedin() + return _u +} + // SetModifiedAt sets the "modified_at" field. func (_u *UserUpdate) SetModifiedAt(v time.Time) *UserUpdate { _u.mutation.SetModifiedAt(v) @@ -1134,6 +1194,24 @@ func (_u *UserUpdate) sqlSave(ctx context.Context) (_node int, err error) { if _u.mutation.EmailCleared() { _spec.ClearField(user.FieldEmail, field.TypeString) } + if value, ok := _u.mutation.Renku(); ok { + _spec.SetField(user.FieldRenku, field.TypeString, value) + } + if _u.mutation.RenkuCleared() { + _spec.ClearField(user.FieldRenku, field.TypeString) + } + if value, ok := _u.mutation.Github(); ok { + _spec.SetField(user.FieldGithub, field.TypeString, value) + } + if _u.mutation.GithubCleared() { + _spec.ClearField(user.FieldGithub, field.TypeString) + } + if value, ok := _u.mutation.Linkedin(); ok { + _spec.SetField(user.FieldLinkedin, field.TypeString, value) + } + if _u.mutation.LinkedinCleared() { + _spec.ClearField(user.FieldLinkedin, field.TypeString) + } if value, ok := _u.mutation.ModifiedAt(); ok { _spec.SetField(user.FieldModifiedAt, field.TypeTime, value) } @@ -2419,6 +2497,66 @@ func (_u *UserUpdateOne) ClearEmail() *UserUpdateOne { return _u } +// SetRenku sets the "renku" field. +func (_u *UserUpdateOne) SetRenku(v string) *UserUpdateOne { + _u.mutation.SetRenku(v) + return _u +} + +// SetNillableRenku sets the "renku" field if the given value is not nil. +func (_u *UserUpdateOne) SetNillableRenku(v *string) *UserUpdateOne { + if v != nil { + _u.SetRenku(*v) + } + return _u +} + +// ClearRenku clears the value of the "renku" field. +func (_u *UserUpdateOne) ClearRenku() *UserUpdateOne { + _u.mutation.ClearRenku() + return _u +} + +// SetGithub sets the "github" field. +func (_u *UserUpdateOne) SetGithub(v string) *UserUpdateOne { + _u.mutation.SetGithub(v) + return _u +} + +// SetNillableGithub sets the "github" field if the given value is not nil. +func (_u *UserUpdateOne) SetNillableGithub(v *string) *UserUpdateOne { + if v != nil { + _u.SetGithub(*v) + } + return _u +} + +// ClearGithub clears the value of the "github" field. +func (_u *UserUpdateOne) ClearGithub() *UserUpdateOne { + _u.mutation.ClearGithub() + return _u +} + +// SetLinkedin sets the "linkedin" field. +func (_u *UserUpdateOne) SetLinkedin(v string) *UserUpdateOne { + _u.mutation.SetLinkedin(v) + return _u +} + +// SetNillableLinkedin sets the "linkedin" field if the given value is not nil. +func (_u *UserUpdateOne) SetNillableLinkedin(v *string) *UserUpdateOne { + if v != nil { + _u.SetLinkedin(*v) + } + return _u +} + +// ClearLinkedin clears the value of the "linkedin" field. +func (_u *UserUpdateOne) ClearLinkedin() *UserUpdateOne { + _u.mutation.ClearLinkedin() + return _u +} + // SetModifiedAt sets the "modified_at" field. func (_u *UserUpdateOne) SetModifiedAt(v time.Time) *UserUpdateOne { _u.mutation.SetModifiedAt(v) @@ -3472,6 +3610,24 @@ func (_u *UserUpdateOne) sqlSave(ctx context.Context) (_node *User, err error) { if _u.mutation.EmailCleared() { _spec.ClearField(user.FieldEmail, field.TypeString) } + if value, ok := _u.mutation.Renku(); ok { + _spec.SetField(user.FieldRenku, field.TypeString, value) + } + if _u.mutation.RenkuCleared() { + _spec.ClearField(user.FieldRenku, field.TypeString) + } + if value, ok := _u.mutation.Github(); ok { + _spec.SetField(user.FieldGithub, field.TypeString, value) + } + if _u.mutation.GithubCleared() { + _spec.ClearField(user.FieldGithub, field.TypeString) + } + if value, ok := _u.mutation.Linkedin(); ok { + _spec.SetField(user.FieldLinkedin, field.TypeString, value) + } + if _u.mutation.LinkedinCleared() { + _spec.ClearField(user.FieldLinkedin, field.TypeString) + } if value, ok := _u.mutation.ModifiedAt(); ok { _spec.SetField(user.FieldModifiedAt, field.TypeTime, value) } diff --git a/components/backend/internal/proto/user/entities/user.pb.go b/components/backend/internal/proto/user/entities/user.pb.go index 7a09b687..7a69d882 100644 --- a/components/backend/internal/proto/user/entities/user.pb.go +++ b/components/backend/internal/proto/user/entities/user.pb.go @@ -31,8 +31,12 @@ type User struct { DisplayName string `protobuf:"bytes,5,opt,name=display_name,json=displayName,proto3" json:"display_name,omitempty"` Email string `protobuf:"bytes,6,opt,name=email,proto3" json:"email,omitempty"` // Populated from casbin on fetch; not persisted in ent DB. - Roles []GlobalRole `protobuf:"varint,7,rep,packed,name=roles,proto3,enum=user.entities.GlobalRole" json:"roles,omitempty"` - ModifiedAt *timestamppb.Timestamp `protobuf:"bytes,8,opt,name=modified_at,json=modifiedAt,proto3" json:"modified_at,omitempty"` + Roles []GlobalRole `protobuf:"varint,7,rep,packed,name=roles,proto3,enum=user.entities.GlobalRole" json:"roles,omitempty"` + ModifiedAt *timestamppb.Timestamp `protobuf:"bytes,8,opt,name=modified_at,json=modifiedAt,proto3" json:"modified_at,omitempty"` + // User accounts links. + RenkuUrl string `protobuf:"bytes,9,opt,name=renku_url,json=renkuUrl,proto3" json:"renku_url,omitempty"` + GithubUrl string `protobuf:"bytes,10,opt,name=github_url,json=githubUrl,proto3" json:"github_url,omitempty"` + LinkedinUrl string `protobuf:"bytes,11,opt,name=linkedin_url,json=linkedinUrl,proto3" json:"linkedin_url,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -123,11 +127,32 @@ func (x *User) GetModifiedAt() *timestamppb.Timestamp { return nil } +func (x *User) GetRenkuUrl() string { + if x != nil { + return x.RenkuUrl + } + return "" +} + +func (x *User) GetGithubUrl() string { + if x != nil { + return x.GithubUrl + } + return "" +} + +func (x *User) GetLinkedinUrl() string { + if x != nil { + return x.LinkedinUrl + } + return "" +} + var File_user_entities_user_proto protoreflect.FileDescriptor const file_user_entities_user_proto_rawDesc = "" + "\n" + - "\x18user/entities/user.proto\x12\ruser.entities\x1a\x1fgoogle/protobuf/timestamp.proto\x1a\x1fuser/entities/global_role.proto\"\xb5\x02\n" + + "\x18user/entities/user.proto\x12\ruser.entities\x1a\x1fgoogle/protobuf/timestamp.proto\x1a\x1fuser/entities/global_role.proto\"\x94\x03\n" + "\x04User\x12\x0e\n" + "\x02id\x18\x01 \x01(\tR\x02id\x12\x1a\n" + "\busername\x18\x02 \x01(\tR\busername\x12\x1f\n" + @@ -139,7 +164,12 @@ const file_user_entities_user_proto_rawDesc = "" + "\x05email\x18\x06 \x01(\tR\x05email\x12/\n" + "\x05roles\x18\a \x03(\x0e2\x19.user.entities.GlobalRoleR\x05roles\x12;\n" + "\vmodified_at\x18\b \x01(\v2\x1a.google.protobuf.TimestampR\n" + - "modifiedAtB\\ZZgithub.com/swissdatasciencecenter/hackagon/components/backend/internal/proto/user/entitiesb\x06proto3" + "modifiedAt\x12\x1b\n" + + "\trenku_url\x18\t \x01(\tR\brenkuUrl\x12\x1d\n" + + "\n" + + "github_url\x18\n" + + " \x01(\tR\tgithubUrl\x12!\n" + + "\flinkedin_url\x18\v \x01(\tR\vlinkedinUrlB\\ZZgithub.com/swissdatasciencecenter/hackagon/components/backend/internal/proto/user/entitiesb\x06proto3" var ( file_user_entities_user_proto_rawDescOnce sync.Once diff --git a/components/backend/internal/proto/user/messages/user_svc/add_role_request.pb.go b/components/backend/internal/proto/user/messages/user_svc/add_role_request.pb.go index 5fdc6eb2..b8add87a 100644 --- a/components/backend/internal/proto/user/messages/user_svc/add_role_request.pb.go +++ b/components/backend/internal/proto/user/messages/user_svc/add_role_request.pb.go @@ -7,6 +7,7 @@ package user_svc import ( + _ "buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go/buf/validate" entities "github.com/swissdatasciencecenter/hackagon/components/backend/internal/proto/user/entities" protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" @@ -78,9 +79,9 @@ var File_user_messages_user_svc_add_role_request_proto protoreflect.FileDescript const file_user_messages_user_svc_add_role_request_proto_rawDesc = "" + "\n" + - "-user/messages/user_svc/add_role_request.proto\x12\x16user.messages.user_svc\x1a\x1fuser/entities/global_role.proto\"X\n" + - "\x0eAddRoleRequest\x12\x17\n" + - "\auser_id\x18\x01 \x01(\tR\x06userId\x12-\n" + + "-user/messages/user_svc/add_role_request.proto\x12\x16user.messages.user_svc\x1a\x1bbuf/validate/validate.proto\x1a\x1fuser/entities/global_role.proto\"b\n" + + "\x0eAddRoleRequest\x12!\n" + + "\auser_id\x18\x01 \x01(\tB\b\xbaH\x05r\x03\xb0\x01\x01R\x06userId\x12-\n" + "\x04role\x18\x02 \x01(\x0e2\x19.user.entities.GlobalRoleR\x04roleBeZcgithub.com/swissdatasciencecenter/hackagon/components/backend/internal/proto/user/messages/user_svcb\x06proto3" var ( diff --git a/components/backend/internal/proto/user/messages/user_svc/edit_request.pb.go b/components/backend/internal/proto/user/messages/user_svc/edit_request.pb.go new file mode 100644 index 00000000..85a94cba --- /dev/null +++ b/components/backend/internal/proto/user/messages/user_svc/edit_request.pb.go @@ -0,0 +1,156 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.36.12 +// protoc (unknown) +// source: user/messages/user_svc/edit_request.proto + +package user_svc + +import ( + _ "buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go/buf/validate" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" + unsafe "unsafe" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type EditRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + UserId string `protobuf:"bytes,1,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` + RenkuUrl *string `protobuf:"bytes,2,opt,name=renku_url,json=renkuUrl,proto3,oneof" json:"renku_url,omitempty"` + GithubUrl *string `protobuf:"bytes,3,opt,name=github_url,json=githubUrl,proto3,oneof" json:"github_url,omitempty"` + LinkedinUrl *string `protobuf:"bytes,4,opt,name=linkedin_url,json=linkedinUrl,proto3,oneof" json:"linkedin_url,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *EditRequest) Reset() { + *x = EditRequest{} + mi := &file_user_messages_user_svc_edit_request_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *EditRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*EditRequest) ProtoMessage() {} + +func (x *EditRequest) ProtoReflect() protoreflect.Message { + mi := &file_user_messages_user_svc_edit_request_proto_msgTypes[0] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use EditRequest.ProtoReflect.Descriptor instead. +func (*EditRequest) Descriptor() ([]byte, []int) { + return file_user_messages_user_svc_edit_request_proto_rawDescGZIP(), []int{0} +} + +func (x *EditRequest) GetUserId() string { + if x != nil { + return x.UserId + } + return "" +} + +func (x *EditRequest) GetRenkuUrl() string { + if x != nil && x.RenkuUrl != nil { + return *x.RenkuUrl + } + return "" +} + +func (x *EditRequest) GetGithubUrl() string { + if x != nil && x.GithubUrl != nil { + return *x.GithubUrl + } + return "" +} + +func (x *EditRequest) GetLinkedinUrl() string { + if x != nil && x.LinkedinUrl != nil { + return *x.LinkedinUrl + } + return "" +} + +var File_user_messages_user_svc_edit_request_proto protoreflect.FileDescriptor + +const file_user_messages_user_svc_edit_request_proto_rawDesc = "" + + "\n" + + ")user/messages/user_svc/edit_request.proto\x12\x16user.messages.user_svc\x1a\x1bbuf/validate/validate.proto\"\xea\x01\n" + + "\vEditRequest\x12!\n" + + "\auser_id\x18\x01 \x01(\tB\b\xbaH\x05r\x03\xb0\x01\x01R\x06userId\x12*\n" + + "\trenku_url\x18\x02 \x01(\tB\b\xbaH\x05r\x03\x18\x80\x10H\x00R\brenkuUrl\x88\x01\x01\x12,\n" + + "\n" + + "github_url\x18\x03 \x01(\tB\b\xbaH\x05r\x03\x18\x80\x10H\x01R\tgithubUrl\x88\x01\x01\x120\n" + + "\flinkedin_url\x18\x04 \x01(\tB\b\xbaH\x05r\x03\x18\x80\x10H\x02R\vlinkedinUrl\x88\x01\x01B\f\n" + + "\n" + + "_renku_urlB\r\n" + + "\v_github_urlB\x0f\n" + + "\r_linkedin_urlBeZcgithub.com/swissdatasciencecenter/hackagon/components/backend/internal/proto/user/messages/user_svcb\x06proto3" + +var ( + file_user_messages_user_svc_edit_request_proto_rawDescOnce sync.Once + file_user_messages_user_svc_edit_request_proto_rawDescData []byte +) + +func file_user_messages_user_svc_edit_request_proto_rawDescGZIP() []byte { + file_user_messages_user_svc_edit_request_proto_rawDescOnce.Do(func() { + file_user_messages_user_svc_edit_request_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_user_messages_user_svc_edit_request_proto_rawDesc), len(file_user_messages_user_svc_edit_request_proto_rawDesc))) + }) + return file_user_messages_user_svc_edit_request_proto_rawDescData +} + +var file_user_messages_user_svc_edit_request_proto_msgTypes = make([]protoimpl.MessageInfo, 1) +var file_user_messages_user_svc_edit_request_proto_goTypes = []any{ + (*EditRequest)(nil), // 0: user.messages.user_svc.EditRequest +} +var file_user_messages_user_svc_edit_request_proto_depIdxs = []int32{ + 0, // [0:0] is the sub-list for method output_type + 0, // [0:0] is the sub-list for method input_type + 0, // [0:0] is the sub-list for extension type_name + 0, // [0:0] is the sub-list for extension extendee + 0, // [0:0] is the sub-list for field type_name +} + +func init() { file_user_messages_user_svc_edit_request_proto_init() } +func file_user_messages_user_svc_edit_request_proto_init() { + if File_user_messages_user_svc_edit_request_proto != nil { + return + } + file_user_messages_user_svc_edit_request_proto_msgTypes[0].OneofWrappers = []any{} + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: unsafe.Slice(unsafe.StringData(file_user_messages_user_svc_edit_request_proto_rawDesc), len(file_user_messages_user_svc_edit_request_proto_rawDesc)), + NumEnums: 0, + NumMessages: 1, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_user_messages_user_svc_edit_request_proto_goTypes, + DependencyIndexes: file_user_messages_user_svc_edit_request_proto_depIdxs, + MessageInfos: file_user_messages_user_svc_edit_request_proto_msgTypes, + }.Build() + File_user_messages_user_svc_edit_request_proto = out.File + file_user_messages_user_svc_edit_request_proto_goTypes = nil + file_user_messages_user_svc_edit_request_proto_depIdxs = nil +} diff --git a/components/backend/internal/proto/user/messages/user_svc/edit_response.pb.go b/components/backend/internal/proto/user/messages/user_svc/edit_response.pb.go new file mode 100644 index 00000000..eaf7ab08 --- /dev/null +++ b/components/backend/internal/proto/user/messages/user_svc/edit_response.pb.go @@ -0,0 +1,125 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.36.12 +// protoc (unknown) +// source: user/messages/user_svc/edit_response.proto + +package user_svc + +import ( + entities "github.com/swissdatasciencecenter/hackagon/components/backend/internal/proto/user/entities" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" + unsafe "unsafe" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type EditResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + User *entities.User `protobuf:"bytes,1,opt,name=user,proto3" json:"user,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *EditResponse) Reset() { + *x = EditResponse{} + mi := &file_user_messages_user_svc_edit_response_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *EditResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*EditResponse) ProtoMessage() {} + +func (x *EditResponse) ProtoReflect() protoreflect.Message { + mi := &file_user_messages_user_svc_edit_response_proto_msgTypes[0] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use EditResponse.ProtoReflect.Descriptor instead. +func (*EditResponse) Descriptor() ([]byte, []int) { + return file_user_messages_user_svc_edit_response_proto_rawDescGZIP(), []int{0} +} + +func (x *EditResponse) GetUser() *entities.User { + if x != nil { + return x.User + } + return nil +} + +var File_user_messages_user_svc_edit_response_proto protoreflect.FileDescriptor + +const file_user_messages_user_svc_edit_response_proto_rawDesc = "" + + "\n" + + "*user/messages/user_svc/edit_response.proto\x12\x16user.messages.user_svc\x1a\x18user/entities/user.proto\"7\n" + + "\fEditResponse\x12'\n" + + "\x04user\x18\x01 \x01(\v2\x13.user.entities.UserR\x04userBeZcgithub.com/swissdatasciencecenter/hackagon/components/backend/internal/proto/user/messages/user_svcb\x06proto3" + +var ( + file_user_messages_user_svc_edit_response_proto_rawDescOnce sync.Once + file_user_messages_user_svc_edit_response_proto_rawDescData []byte +) + +func file_user_messages_user_svc_edit_response_proto_rawDescGZIP() []byte { + file_user_messages_user_svc_edit_response_proto_rawDescOnce.Do(func() { + file_user_messages_user_svc_edit_response_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_user_messages_user_svc_edit_response_proto_rawDesc), len(file_user_messages_user_svc_edit_response_proto_rawDesc))) + }) + return file_user_messages_user_svc_edit_response_proto_rawDescData +} + +var file_user_messages_user_svc_edit_response_proto_msgTypes = make([]protoimpl.MessageInfo, 1) +var file_user_messages_user_svc_edit_response_proto_goTypes = []any{ + (*EditResponse)(nil), // 0: user.messages.user_svc.EditResponse + (*entities.User)(nil), // 1: user.entities.User +} +var file_user_messages_user_svc_edit_response_proto_depIdxs = []int32{ + 1, // 0: user.messages.user_svc.EditResponse.user:type_name -> user.entities.User + 1, // [1:1] is the sub-list for method output_type + 1, // [1:1] is the sub-list for method input_type + 1, // [1:1] is the sub-list for extension type_name + 1, // [1:1] is the sub-list for extension extendee + 0, // [0:1] is the sub-list for field type_name +} + +func init() { file_user_messages_user_svc_edit_response_proto_init() } +func file_user_messages_user_svc_edit_response_proto_init() { + if File_user_messages_user_svc_edit_response_proto != nil { + return + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: unsafe.Slice(unsafe.StringData(file_user_messages_user_svc_edit_response_proto_rawDesc), len(file_user_messages_user_svc_edit_response_proto_rawDesc)), + NumEnums: 0, + NumMessages: 1, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_user_messages_user_svc_edit_response_proto_goTypes, + DependencyIndexes: file_user_messages_user_svc_edit_response_proto_depIdxs, + MessageInfos: file_user_messages_user_svc_edit_response_proto_msgTypes, + }.Build() + File_user_messages_user_svc_edit_response_proto = out.File + file_user_messages_user_svc_edit_response_proto_goTypes = nil + file_user_messages_user_svc_edit_response_proto_depIdxs = nil +} diff --git a/components/backend/internal/proto/user/messages/user_svc/get_request.pb.go b/components/backend/internal/proto/user/messages/user_svc/get_request.pb.go index 55ec7da1..add47b28 100644 --- a/components/backend/internal/proto/user/messages/user_svc/get_request.pb.go +++ b/components/backend/internal/proto/user/messages/user_svc/get_request.pb.go @@ -7,6 +7,7 @@ package user_svc import ( + _ "buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go/buf/validate" protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" reflect "reflect" @@ -69,10 +70,10 @@ var File_user_messages_user_svc_get_request_proto protoreflect.FileDescriptor const file_user_messages_user_svc_get_request_proto_rawDesc = "" + "\n" + - "(user/messages/user_svc/get_request.proto\x12\x16user.messages.user_svc\"%\n" + + "(user/messages/user_svc/get_request.proto\x12\x16user.messages.user_svc\x1a\x1bbuf/validate/validate.proto\"/\n" + "\n" + - "GetRequest\x12\x17\n" + - "\auser_id\x18\x01 \x01(\tR\x06userIdBeZcgithub.com/swissdatasciencecenter/hackagon/components/backend/internal/proto/user/messages/user_svcb\x06proto3" + "GetRequest\x12!\n" + + "\auser_id\x18\x01 \x01(\tB\b\xbaH\x05r\x03\xb0\x01\x01R\x06userIdBeZcgithub.com/swissdatasciencecenter/hackagon/components/backend/internal/proto/user/messages/user_svcb\x06proto3" var ( file_user_messages_user_svc_get_request_proto_rawDescOnce sync.Once diff --git a/components/backend/internal/proto/user/messages/user_svc/remove_role_request.pb.go b/components/backend/internal/proto/user/messages/user_svc/remove_role_request.pb.go index d606ae10..2191a633 100644 --- a/components/backend/internal/proto/user/messages/user_svc/remove_role_request.pb.go +++ b/components/backend/internal/proto/user/messages/user_svc/remove_role_request.pb.go @@ -7,6 +7,7 @@ package user_svc import ( + _ "buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go/buf/validate" entities "github.com/swissdatasciencecenter/hackagon/components/backend/internal/proto/user/entities" protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" @@ -78,9 +79,9 @@ var File_user_messages_user_svc_remove_role_request_proto protoreflect.FileDescr const file_user_messages_user_svc_remove_role_request_proto_rawDesc = "" + "\n" + - "0user/messages/user_svc/remove_role_request.proto\x12\x16user.messages.user_svc\x1a\x1fuser/entities/global_role.proto\"[\n" + - "\x11RemoveRoleRequest\x12\x17\n" + - "\auser_id\x18\x01 \x01(\tR\x06userId\x12-\n" + + "0user/messages/user_svc/remove_role_request.proto\x12\x16user.messages.user_svc\x1a\x1bbuf/validate/validate.proto\x1a\x1fuser/entities/global_role.proto\"e\n" + + "\x11RemoveRoleRequest\x12!\n" + + "\auser_id\x18\x01 \x01(\tB\b\xbaH\x05r\x03\xb0\x01\x01R\x06userId\x12-\n" + "\x04role\x18\x02 \x01(\x0e2\x19.user.entities.GlobalRoleR\x04roleBeZcgithub.com/swissdatasciencecenter/hackagon/components/backend/internal/proto/user/messages/user_svcb\x06proto3" var ( diff --git a/components/backend/internal/proto/user/user_service.pb.go b/components/backend/internal/proto/user/user_service.pb.go index ab499110..38b359d8 100644 --- a/components/backend/internal/proto/user/user_service.pb.go +++ b/components/backend/internal/proto/user/user_service.pb.go @@ -25,10 +25,11 @@ var File_user_user_service_proto protoreflect.FileDescriptor const file_user_user_service_proto_rawDesc = "" + "\n" + - "\x17user/user_service.proto\x12\x04user\x1a-user/messages/user_svc/add_role_request.proto\x1a.user/messages/user_svc/add_role_response.proto\x1a(user/messages/user_svc/get_request.proto\x1a)user/messages/user_svc/get_response.proto\x1a)user/messages/user_svc/list_request.proto\x1a*user/messages/user_svc/list_response.proto\x1a-user/messages/user_svc/register_request.proto\x1a.user/messages/user_svc/register_response.proto\x1a0user/messages/user_svc/remove_role_request.proto\x1a1user/messages/user_svc/remove_role_response.proto\x1a-user/messages/user_svc/who_am_i_request.proto\x1a.user/messages/user_svc/who_am_i_response.proto2\xa9\x04\n" + + "\x17user/user_service.proto\x12\x04user\x1a-user/messages/user_svc/add_role_request.proto\x1a.user/messages/user_svc/add_role_response.proto\x1a)user/messages/user_svc/edit_request.proto\x1a*user/messages/user_svc/edit_response.proto\x1a(user/messages/user_svc/get_request.proto\x1a)user/messages/user_svc/get_response.proto\x1a)user/messages/user_svc/list_request.proto\x1a*user/messages/user_svc/list_response.proto\x1a-user/messages/user_svc/register_request.proto\x1a.user/messages/user_svc/register_response.proto\x1a0user/messages/user_svc/remove_role_request.proto\x1a1user/messages/user_svc/remove_role_response.proto\x1a-user/messages/user_svc/who_am_i_request.proto\x1a.user/messages/user_svc/who_am_i_response.proto2\xfc\x04\n" + "\vUserService\x12Q\n" + "\x04List\x12#.user.messages.user_svc.ListRequest\x1a$.user.messages.user_svc.ListResponse\x12N\n" + - "\x03Get\x12\".user.messages.user_svc.GetRequest\x1a#.user.messages.user_svc.GetResponse\x12W\n" + + "\x03Get\x12\".user.messages.user_svc.GetRequest\x1a#.user.messages.user_svc.GetResponse\x12Q\n" + + "\x04Edit\x12#.user.messages.user_svc.EditRequest\x1a$.user.messages.user_svc.EditResponse\x12W\n" + "\x06WhoAmI\x12%.user.messages.user_svc.WhoAmIRequest\x1a&.user.messages.user_svc.WhoAmIResponse\x12]\n" + "\bRegister\x12'.user.messages.user_svc.RegisterRequest\x1a(.user.messages.user_svc.RegisterResponse\x12Z\n" + "\aAddRole\x12&.user.messages.user_svc.AddRoleRequest\x1a'.user.messages.user_svc.AddRoleResponse\x12c\n" + @@ -38,32 +39,36 @@ const file_user_user_service_proto_rawDesc = "" + var file_user_user_service_proto_goTypes = []any{ (*user_svc.ListRequest)(nil), // 0: user.messages.user_svc.ListRequest (*user_svc.GetRequest)(nil), // 1: user.messages.user_svc.GetRequest - (*user_svc.WhoAmIRequest)(nil), // 2: user.messages.user_svc.WhoAmIRequest - (*user_svc.RegisterRequest)(nil), // 3: user.messages.user_svc.RegisterRequest - (*user_svc.AddRoleRequest)(nil), // 4: user.messages.user_svc.AddRoleRequest - (*user_svc.RemoveRoleRequest)(nil), // 5: user.messages.user_svc.RemoveRoleRequest - (*user_svc.ListResponse)(nil), // 6: user.messages.user_svc.ListResponse - (*user_svc.GetResponse)(nil), // 7: user.messages.user_svc.GetResponse - (*user_svc.WhoAmIResponse)(nil), // 8: user.messages.user_svc.WhoAmIResponse - (*user_svc.RegisterResponse)(nil), // 9: user.messages.user_svc.RegisterResponse - (*user_svc.AddRoleResponse)(nil), // 10: user.messages.user_svc.AddRoleResponse - (*user_svc.RemoveRoleResponse)(nil), // 11: user.messages.user_svc.RemoveRoleResponse + (*user_svc.EditRequest)(nil), // 2: user.messages.user_svc.EditRequest + (*user_svc.WhoAmIRequest)(nil), // 3: user.messages.user_svc.WhoAmIRequest + (*user_svc.RegisterRequest)(nil), // 4: user.messages.user_svc.RegisterRequest + (*user_svc.AddRoleRequest)(nil), // 5: user.messages.user_svc.AddRoleRequest + (*user_svc.RemoveRoleRequest)(nil), // 6: user.messages.user_svc.RemoveRoleRequest + (*user_svc.ListResponse)(nil), // 7: user.messages.user_svc.ListResponse + (*user_svc.GetResponse)(nil), // 8: user.messages.user_svc.GetResponse + (*user_svc.EditResponse)(nil), // 9: user.messages.user_svc.EditResponse + (*user_svc.WhoAmIResponse)(nil), // 10: user.messages.user_svc.WhoAmIResponse + (*user_svc.RegisterResponse)(nil), // 11: user.messages.user_svc.RegisterResponse + (*user_svc.AddRoleResponse)(nil), // 12: user.messages.user_svc.AddRoleResponse + (*user_svc.RemoveRoleResponse)(nil), // 13: user.messages.user_svc.RemoveRoleResponse } var file_user_user_service_proto_depIdxs = []int32{ 0, // 0: user.UserService.List:input_type -> user.messages.user_svc.ListRequest 1, // 1: user.UserService.Get:input_type -> user.messages.user_svc.GetRequest - 2, // 2: user.UserService.WhoAmI:input_type -> user.messages.user_svc.WhoAmIRequest - 3, // 3: user.UserService.Register:input_type -> user.messages.user_svc.RegisterRequest - 4, // 4: user.UserService.AddRole:input_type -> user.messages.user_svc.AddRoleRequest - 5, // 5: user.UserService.RemoveRole:input_type -> user.messages.user_svc.RemoveRoleRequest - 6, // 6: user.UserService.List:output_type -> user.messages.user_svc.ListResponse - 7, // 7: user.UserService.Get:output_type -> user.messages.user_svc.GetResponse - 8, // 8: user.UserService.WhoAmI:output_type -> user.messages.user_svc.WhoAmIResponse - 9, // 9: user.UserService.Register:output_type -> user.messages.user_svc.RegisterResponse - 10, // 10: user.UserService.AddRole:output_type -> user.messages.user_svc.AddRoleResponse - 11, // 11: user.UserService.RemoveRole:output_type -> user.messages.user_svc.RemoveRoleResponse - 6, // [6:12] is the sub-list for method output_type - 0, // [0:6] is the sub-list for method input_type + 2, // 2: user.UserService.Edit:input_type -> user.messages.user_svc.EditRequest + 3, // 3: user.UserService.WhoAmI:input_type -> user.messages.user_svc.WhoAmIRequest + 4, // 4: user.UserService.Register:input_type -> user.messages.user_svc.RegisterRequest + 5, // 5: user.UserService.AddRole:input_type -> user.messages.user_svc.AddRoleRequest + 6, // 6: user.UserService.RemoveRole:input_type -> user.messages.user_svc.RemoveRoleRequest + 7, // 7: user.UserService.List:output_type -> user.messages.user_svc.ListResponse + 8, // 8: user.UserService.Get:output_type -> user.messages.user_svc.GetResponse + 9, // 9: user.UserService.Edit:output_type -> user.messages.user_svc.EditResponse + 10, // 10: user.UserService.WhoAmI:output_type -> user.messages.user_svc.WhoAmIResponse + 11, // 11: user.UserService.Register:output_type -> user.messages.user_svc.RegisterResponse + 12, // 12: user.UserService.AddRole:output_type -> user.messages.user_svc.AddRoleResponse + 13, // 13: user.UserService.RemoveRole:output_type -> user.messages.user_svc.RemoveRoleResponse + 7, // [7:14] is the sub-list for method output_type + 0, // [0:7] is the sub-list for method input_type 0, // [0:0] is the sub-list for extension type_name 0, // [0:0] is the sub-list for extension extendee 0, // [0:0] is the sub-list for field type_name diff --git a/components/backend/internal/proto/user/user_service_grpc.pb.go b/components/backend/internal/proto/user/user_service_grpc.pb.go index b96fb214..4b6375e1 100644 --- a/components/backend/internal/proto/user/user_service_grpc.pb.go +++ b/components/backend/internal/proto/user/user_service_grpc.pb.go @@ -22,6 +22,7 @@ const _ = grpc.SupportPackageIsVersion9 const ( UserService_List_FullMethodName = "/user.UserService/List" UserService_Get_FullMethodName = "/user.UserService/Get" + UserService_Edit_FullMethodName = "/user.UserService/Edit" UserService_WhoAmI_FullMethodName = "/user.UserService/WhoAmI" UserService_Register_FullMethodName = "/user.UserService/Register" UserService_AddRole_FullMethodName = "/user.UserService/AddRole" @@ -34,6 +35,7 @@ const ( type UserServiceClient interface { List(ctx context.Context, in *user_svc.ListRequest, opts ...grpc.CallOption) (*user_svc.ListResponse, error) Get(ctx context.Context, in *user_svc.GetRequest, opts ...grpc.CallOption) (*user_svc.GetResponse, error) + Edit(ctx context.Context, in *user_svc.EditRequest, opts ...grpc.CallOption) (*user_svc.EditResponse, error) WhoAmI(ctx context.Context, in *user_svc.WhoAmIRequest, opts ...grpc.CallOption) (*user_svc.WhoAmIResponse, error) Register(ctx context.Context, in *user_svc.RegisterRequest, opts ...grpc.CallOption) (*user_svc.RegisterResponse, error) AddRole(ctx context.Context, in *user_svc.AddRoleRequest, opts ...grpc.CallOption) (*user_svc.AddRoleResponse, error) @@ -68,6 +70,16 @@ func (c *userServiceClient) Get(ctx context.Context, in *user_svc.GetRequest, op return out, nil } +func (c *userServiceClient) Edit(ctx context.Context, in *user_svc.EditRequest, opts ...grpc.CallOption) (*user_svc.EditResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(user_svc.EditResponse) + err := c.cc.Invoke(ctx, UserService_Edit_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + func (c *userServiceClient) WhoAmI(ctx context.Context, in *user_svc.WhoAmIRequest, opts ...grpc.CallOption) (*user_svc.WhoAmIResponse, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(user_svc.WhoAmIResponse) @@ -114,6 +126,7 @@ func (c *userServiceClient) RemoveRole(ctx context.Context, in *user_svc.RemoveR type UserServiceServer interface { List(context.Context, *user_svc.ListRequest) (*user_svc.ListResponse, error) Get(context.Context, *user_svc.GetRequest) (*user_svc.GetResponse, error) + Edit(context.Context, *user_svc.EditRequest) (*user_svc.EditResponse, error) WhoAmI(context.Context, *user_svc.WhoAmIRequest) (*user_svc.WhoAmIResponse, error) Register(context.Context, *user_svc.RegisterRequest) (*user_svc.RegisterResponse, error) AddRole(context.Context, *user_svc.AddRoleRequest) (*user_svc.AddRoleResponse, error) @@ -134,6 +147,9 @@ func (UnimplementedUserServiceServer) List(context.Context, *user_svc.ListReques func (UnimplementedUserServiceServer) Get(context.Context, *user_svc.GetRequest) (*user_svc.GetResponse, error) { return nil, status.Error(codes.Unimplemented, "method Get not implemented") } +func (UnimplementedUserServiceServer) Edit(context.Context, *user_svc.EditRequest) (*user_svc.EditResponse, error) { + return nil, status.Error(codes.Unimplemented, "method Edit not implemented") +} func (UnimplementedUserServiceServer) WhoAmI(context.Context, *user_svc.WhoAmIRequest) (*user_svc.WhoAmIResponse, error) { return nil, status.Error(codes.Unimplemented, "method WhoAmI not implemented") } @@ -203,6 +219,24 @@ func _UserService_Get_Handler(srv interface{}, ctx context.Context, dec func(int return interceptor(ctx, in, info, handler) } +func _UserService_Edit_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(user_svc.EditRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(UserServiceServer).Edit(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: UserService_Edit_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(UserServiceServer).Edit(ctx, req.(*user_svc.EditRequest)) + } + return interceptor(ctx, in, info, handler) +} + func _UserService_WhoAmI_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(user_svc.WhoAmIRequest) if err := dec(in); err != nil { @@ -290,6 +324,10 @@ var UserService_ServiceDesc = grpc.ServiceDesc{ MethodName: "Get", Handler: _UserService_Get_Handler, }, + { + MethodName: "Edit", + Handler: _UserService_Edit_Handler, + }, { MethodName: "WhoAmI", Handler: _UserService_WhoAmI_Handler, diff --git a/components/backend/internal/service/mappers.go b/components/backend/internal/service/mappers.go index c6a31e56..d285dbe8 100644 --- a/components/backend/internal/service/mappers.go +++ b/components/backend/internal/service/mappers.go @@ -22,6 +22,9 @@ func userEntryFromEnt(u *ent.User) *userEnts.User { KeycloakId: u.KeycloakID, DisplayName: u.DisplayName, Email: u.Email, + RenkuUrl: renderRenkuURL(u.Renku), + GithubUrl: renderGithubURL(u.Github), + LinkedinUrl: renderLinkedinURL(u.Linkedin), CreatedAt: timestamppb.New(u.CreatedAt), ModifiedAt: timestamppb.New(u.ModifiedAt), } diff --git a/components/backend/internal/service/user_service.go b/components/backend/internal/service/user_service.go index f5b45f5a..e2dff071 100644 --- a/components/backend/internal/service/user_service.go +++ b/components/backend/internal/service/user_service.go @@ -61,6 +61,78 @@ func (s *UserService) List( return &msgs.ListResponse{Users: entries}, nil } +func (s *UserService) Edit( + ctx context.Context, + req *msgs.EditRequest, +) (*msgs.EditResponse, error) { + sub, _, err := m.RequireSubject(ctx) + if err != nil { + return nil, err + } + + if sub == m.AnonSubject { + return nil, status.Error(codes.Unauthenticated, "anonymous users cannot edit profiles") + } + + isAdmin, err := s.enforcer.Enforce(ctx, "", m.User, m.Write) + if err != nil { + slog.Error("Could not enforce permission.", "error", err.Error()) + + return nil, status.Errorf(codes.Internal, "internal server error") + } + + id, err := uuid.Parse(req.GetUserId()) + if err != nil { + return nil, status.Errorf(codes.InvalidArgument, "invalid user_id: %v", err) + } + + //nolint:protogetter // Needed because we pass an optional field. The getter wouldn't return a pointer. + linkedin, err := linkUsername(linkedInUsername, req.LinkedinUrl, "LinkedIn") + if err != nil { + return nil, err + } + //nolint:protogetter // Needed because we pass an optional field. The getter wouldn't return a pointer. + github, err := linkUsername(githubUsername, req.GithubUrl, "GitHub") + if err != nil { + return nil, err + } + //nolint:protogetter // Needed because we pass an optional field. The getter wouldn't return a pointer. + renku, err := linkUsername(renkuUsername, req.RenkuUrl, "Renku") + if err != nil { + return nil, err + } + + updateQuery := s.dbClient.User.UpdateOneID(id). + SetNillableLinkedin(linkedin). + SetNillableGithub(github). + SetNillableRenku(renku) + if !isAdmin { + // Non-admins can only edit their own user. + // NOTE: Here the ownership is only checked in the database and Casbin is only used to allow admin override. + // We do this on purpose, because using Casbin to tell whether a user can edit its info would introduce a lot + // of complexity and potential security issues (e.g., users would be able to change their own role, since + // AddRole/RemoveRole check the same User/Write permission). + updateQuery = updateQuery.Where(entuser.KeycloakIDEQ(sub)) + } + + u, err := updateQuery.Save(ctx) + if err != nil { + if ent.IsNotFound(err) { + return nil, status.Errorf(codes.NotFound, "user %s not found", req.GetUserId()) + } + slog.Error("query user", "err", err) + + return nil, status.Error(codes.Internal, "couldn't query database") + } + + entry, err := s.userWithRoles(u) + if err != nil { + return nil, err + } + + return &msgs.EditResponse{User: entry}, nil +} + func (s *UserService) Get( ctx context.Context, req *msgs.GetRequest, @@ -81,14 +153,11 @@ func (s *UserService) Get( return nil, status.Error(codes.Internal, "couldn't query database") } - globalRoles, err := s.enforcer.GetGlobalRoles(u.KeycloakID) - if err != nil { - slog.Error("get global roles", "err", err) - return nil, status.Error(codes.Internal, "couldn't resolve user roles") + entry, err := s.userWithRoles(u) + if err != nil { + return nil, err } - entry := userEntryFromEnt(u) - entry.Roles = append(entry.Roles, globalRoles...) return &msgs.GetResponse{User: entry}, nil } @@ -128,14 +197,10 @@ func (s *UserService) WhoAmI( } } - globalRoles, err := s.enforcer.GetGlobalRoles(u.KeycloakID) + entry, err := s.userWithRoles(u) if err != nil { - slog.Error("get global roles", "err", err) - - return nil, status.Error(codes.Internal, "couldn't resolve user roles") + return nil, err } - entry := userEntryFromEnt(u) - entry.Roles = append(entry.Roles, globalRoles...) return &msgs.WhoAmIResponse{User: entry}, nil } @@ -177,14 +242,10 @@ func (s *UserService) AddRole( return nil, status.Error(codes.Internal, "couldn't assign role") } - globalRoles, err := s.enforcer.GetGlobalRoles(u.KeycloakID) + entry, err := s.userWithRoles(u) if err != nil { - slog.Error("get global roles", "err", err) - - return nil, status.Error(codes.Internal, "couldn't resolve user roles") + return nil, err } - entry := userEntryFromEnt(u) - entry.Roles = append(entry.Roles, globalRoles...) return &msgs.AddRoleResponse{User: entry}, nil } @@ -232,14 +293,10 @@ func (s *UserService) RemoveRole( return nil, status.Errorf(codes.Internal, "couldn't remove user roles: %v", err) } - globalRoles, err := s.enforcer.GetGlobalRoles(u.KeycloakID) + entry, err := s.userWithRoles(u) if err != nil { - slog.Error("get global roles", "err", err) - - return nil, status.Error(codes.Internal, "couldn't resolve user roles") + return nil, err } - entry := userEntryFromEnt(u) - entry.Roles = append(entry.Roles, globalRoles...) return &msgs.RemoveRoleResponse{User: entry}, nil } @@ -308,3 +365,16 @@ func (s *UserService) Register( return &msgs.RegisterResponse{User: userEntryFromEnt(u)}, nil } + +func (s *UserService) userWithRoles(u *ent.User) (*ents.User, error) { + globalRoles, err := s.enforcer.GetGlobalRoles(u.KeycloakID) + if err != nil { + slog.Error("get global roles", "err", err) + + return nil, status.Error(codes.Internal, "couldn't resolve user roles") + } + entry := userEntryFromEnt(u) + entry.Roles = append(entry.Roles, globalRoles...) + + return entry, nil +} diff --git a/components/backend/internal/service/user_service_test.go b/components/backend/internal/service/user_service_test.go index 616757e0..81dc3619 100644 --- a/components/backend/internal/service/user_service_test.go +++ b/components/backend/internal/service/user_service_test.go @@ -5,6 +5,7 @@ package service_test import ( "context" "fmt" + "strings" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" @@ -51,6 +52,9 @@ var _ = Describe("UserService", func() { SetUsername("get-target-username"). SetDisplayName("Get Target"). SetEmail("get@example.com"). + SetRenku("bob-renku"). + SetLinkedin("bob-linkedin"). + SetGithub("bob-github"). Save(context.Background()) Expect(err).NotTo(HaveOccurred()) }) @@ -71,6 +75,9 @@ var _ = Describe("UserService", func() { Expect(u.GetUsername()).To(Equal("get-target-username")) Expect(u.GetDisplayName()).To(Equal("Get Target")) Expect(u.GetEmail()).To(Equal("get@example.com")) + Expect(u.GetRenkuUrl()).To(Equal("https://renkulab.io/u/bob-renku")) + Expect(u.GetLinkedinUrl()).To(Equal("https://linkedin.com/in/bob-linkedin")) + Expect(u.GetGithubUrl()).To(Equal("https://github.com/bob-github")) Expect(u.GetKeycloakId()).To(Equal("get-target")) Expect(u.GetCreatedAt()).NotTo(BeNil()) Expect(u.GetModifiedAt()).NotTo(BeNil()) @@ -853,4 +860,240 @@ var _ = Describe("UserService", func() { Expect(st.Code()).To(Equal(codes.PermissionDenied)) }) }) + + Describe("Edit", func() { + + var targetUser *ent.User + + oldRenkuUsr := "bob-renku" + oldLinkedinUsr := "bob-linkedin" + oldGithubUsr := "bob-github" + + oldRenku := "https://renkulab.io/u/" + oldRenkuUsr + oldGithub := "https://github.com/" + oldGithubUsr + oldLinkedin := "https://linkedin.com/in/" + oldLinkedinUsr + + newRenku := "https://renkulab.io/u/bob-renku-new" + newGithub := "https://github.com/bob-github-new" + newLinkedin := "https://linkedin.com/in/bob-linkedin-new" + + keycloakID := "new-user" + token := testutils.CreateTestJWTToken(keycloakID) + + BeforeEach(func() { + var err error + targetUser, err = dbClient.User.Create(). + SetKeycloakID(keycloakID). + SetUsername("get-target-username"). + SetDisplayName("Get Target"). + SetEmail("get@example.com"). + SetRenku(oldRenkuUsr). + SetLinkedin(oldLinkedinUsr). + SetGithub(oldGithubUsr). + Save(context.Background()) + Expect(err).NotTo(HaveOccurred()) + }) + + It("correctly changes the links values", func() { + ctx := metadata.NewOutgoingContext( + context.Background(), + metadata.Pairs("authorization", "Bearer "+token), + ) + + resp, err := userClient.Edit(ctx, &msgs.EditRequest{ + UserId: targetUser.ID.String(), + RenkuUrl: &newRenku, + GithubUrl: &newGithub, + LinkedinUrl: &newLinkedin, + }) + + Expect(err).NotTo(HaveOccurred()) + + u := resp.GetUser() + + Expect(u.GetRenkuUrl()).To(Equal(newRenku)) + Expect(u.GetGithubUrl()).To(Equal(newGithub)) + Expect(u.GetLinkedinUrl()).To(Equal(newLinkedin)) + }) + + It("lets admins edit users profiles", func() { + token := testutils.CreateTestJWTToken(testAdmin) + ctx := metadata.NewOutgoingContext( + context.Background(), + metadata.Pairs("authorization", "Bearer "+token), + ) + + resp, err := userClient.Edit(ctx, &msgs.EditRequest{ + UserId: targetUser.ID.String(), + RenkuUrl: &newRenku, + GithubUrl: &newGithub, + LinkedinUrl: &newLinkedin, + }) + + Expect(err).NotTo(HaveOccurred()) + + u := resp.GetUser() + + Expect(u.GetRenkuUrl()).To(Equal(newRenku)) + Expect(u.GetGithubUrl()).To(Equal(newGithub)) + Expect(u.GetLinkedinUrl()).To(Equal(newLinkedin)) + }) + + It("doesn't let users edit another user's profile", func() { + token := testutils.CreateTestJWTToken("another-user") + ctx := metadata.NewOutgoingContext( + context.Background(), + metadata.Pairs("authorization", "Bearer "+token), + ) + + _, err := userClient.Edit(ctx, &msgs.EditRequest{ + UserId: targetUser.ID.String(), + RenkuUrl: &newRenku, + GithubUrl: &newGithub, + LinkedinUrl: &newLinkedin, + }) + + Expect(status.Code(err)).To(Equal(codes.NotFound)) + + u, err := dbClient.User.Get(context.Background(), targetUser.ID) + Expect(err).NotTo(HaveOccurred()) + Expect(u.Renku).To(Equal(oldRenkuUsr)) + Expect(u.Github).To(Equal(oldGithubUsr)) + Expect(u.Linkedin).To(Equal(oldLinkedinUsr)) + }) + + It("rejects an invalid URL without writing any link", func() { + ctx := metadata.NewOutgoingContext( + context.Background(), + metadata.Pairs("authorization", "Bearer "+token), + ) + badLinkedin := "https://example.com/in/bob" + + _, err := userClient.Edit(ctx, &msgs.EditRequest{ + UserId: targetUser.ID.String(), + GithubUrl: &newGithub, + LinkedinUrl: &badLinkedin, + }) + + Expect(status.Code(err)).To(Equal(codes.InvalidArgument)) + + // The valid github_url must not be saved either. + u, err := dbClient.User.Get(context.Background(), targetUser.ID) + Expect(err).NotTo(HaveOccurred()) + Expect(u.Renku).To(Equal(oldRenkuUsr)) + Expect(u.Github).To(Equal(oldGithubUsr)) + Expect(u.Linkedin).To(Equal(oldLinkedinUsr)) + }) + + It("rejects a URL longer than 2048 characters", func() { + ctx := metadata.NewOutgoingContext( + context.Background(), + metadata.Pairs("authorization", "Bearer "+token), + ) + // The regex accepts any query string, so only max_len rejects this. + longGithub := "https://github.com/bob?" + strings.Repeat("a", 2048) + + _, err := userClient.Edit(ctx, &msgs.EditRequest{ + UserId: targetUser.ID.String(), + GithubUrl: &longGithub, + }) + + Expect(status.Code(err)).To(Equal(codes.InvalidArgument)) + }) + + It("rejects a malformed user_id", func() { + ctx := metadata.NewOutgoingContext( + context.Background(), + metadata.Pairs("authorization", "Bearer "+token), + ) + + _, err := userClient.Edit(ctx, &msgs.EditRequest{ + UserId: "not-a-uuid", + GithubUrl: &newGithub, + }) + + Expect(status.Code(err)).To(Equal(codes.InvalidArgument)) + }) + + It("rejects anonymous requests", func() { + _, err := userClient.Edit(context.Background(), &msgs.EditRequest{ + UserId: targetUser.ID.String(), + GithubUrl: &newGithub, + }) + + Expect(status.Code(err)).To(Equal(codes.Unauthenticated)) + }) + + It("returns NotFound for a non-existent user", func() { + adminToken := testutils.CreateTestJWTToken(testAdmin) + ctx := metadata.NewOutgoingContext( + context.Background(), + metadata.Pairs("authorization", "Bearer "+adminToken), + ) + + _, err := userClient.Edit(ctx, &msgs.EditRequest{ + UserId: uuid.NewString(), + GithubUrl: &newGithub, + }) + + Expect(status.Code(err)).To(Equal(codes.NotFound)) + }) + + type input struct { + renku *string + github *string + linkedin *string + } + + type expected struct { + renku string + github string + linkedin string + } + + emptyString := "" + + DescribeTable( + "removes empty string params but ignores nil params", + func(in input, want expected) { + token := testutils.CreateTestJWTToken(testAdmin) + ctx := metadata.NewOutgoingContext( + context.Background(), + metadata.Pairs("authorization", "Bearer "+token), + ) + + resp, err := userClient.Edit(ctx, &msgs.EditRequest{ + UserId: targetUser.ID.String(), + RenkuUrl: in.renku, + GithubUrl: in.github, + LinkedinUrl: in.linkedin, + }) + + Expect(err).NotTo(HaveOccurred()) + + u := resp.GetUser() + + Expect(u.GetRenkuUrl()).To(Equal(want.renku)) + Expect(u.GetGithubUrl()).To(Equal(want.github)) + Expect(u.GetLinkedinUrl()).To(Equal(want.linkedin)) + }, + Entry("all nil", input{}, expected{oldRenku, oldGithub, oldLinkedin}), + Entry("clears renku", input{renku: &emptyString}, expected{"", oldGithub, oldLinkedin}), + Entry( + "clears github", + input{github: &emptyString}, + expected{oldRenku, "", oldLinkedin}, + ), + Entry( + "clears linkedin", + input{linkedin: &emptyString}, + expected{oldRenku, oldGithub, ""}, + ), + Entry("sets, clears and keeps in one request", + input{renku: &newRenku, github: &emptyString}, + expected{newRenku, "", oldLinkedin}, + ), + ) + + }) }) diff --git a/components/backend/internal/service/userlinks.go b/components/backend/internal/service/userlinks.go new file mode 100644 index 00000000..fdac5243 --- /dev/null +++ b/components/backend/internal/service/userlinks.go @@ -0,0 +1,96 @@ +package service + +import ( + "fmt" + "net/url" + "regexp" + "strings" + + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" +) + +const ( + linkedInPrefix = "https://linkedin.com/in/" + githubPrefix = "https://github.com/" + renkuPrefix = "https://renkulab.io/u/" +) + +var ( + linkedInRe = regexp.MustCompile( + `(?i)^https?://(?:[a-z]{2,3}\.)?linkedin\.com/in/([\p{L}\p{N}-]{3,100})/?(?:[?#].*)?$`, + ) + githubRe = regexp.MustCompile( + `(?i)^https?://(?:www\.)?github\.com/([a-z0-9](?:[a-z0-9-]{0,98}[a-z0-9])?)/?(?:[?#].*)?$`, + ) + renkuRe = regexp.MustCompile( + `(?i)^https?://(?:www\.)?renkulab\.io/u/([a-z0-9](?:[a-z0-9._-]{0,98}[a-z0-9])?)/?(?:[?#].*)?$`, + ) +) + +func linkedInUsername(link string) (string, error) { + // We need to PathUnescape because LinkedIn allows some non-ASCII characters for usernames. + if s, err := url.PathUnescape(link); err == nil { + link = s + } + + return parseLink(linkedInRe, link) +} + +func githubUsername(link string) (string, error) { + return parseLink(githubRe, link) +} + +func renkuUsername(link string) (string, error) { + return parseLink(renkuRe, link) +} + +func parseLink(re *regexp.Regexp, link string) (string, error) { + link = strings.TrimSpace(link) + + m := re.FindStringSubmatch(link) + if m == nil { + return "", fmt.Errorf("url '%s' does not match regexp", link) + } + + return m[1], nil +} + +// linkUsername turns an optional link from a request into the username to +// store. +func linkUsername( + parse func(string) (string, error), + link *string, + label string, +) (*string, error) { + if link == nil || *link == "" { + return link, nil + } + + username, err := parse(*link) + if err != nil { + return nil, status.Errorf(codes.InvalidArgument, "invalid %s URL '%s'", label, *link) + } + + return &username, nil +} + +func renderLinkedinURL(username string) string { + return renderLink(linkedInPrefix, username) +} + +func renderGithubURL(username string) string { + return renderLink(githubPrefix, username) +} + +func renderRenkuURL(username string) string { + return renderLink(renkuPrefix, username) +} + +func renderLink(prefix, username string) string { + if username == "" { + return "" + } + + return prefix + url.PathEscape(username) +} diff --git a/components/backend/internal/service/userlinks_test.go b/components/backend/internal/service/userlinks_test.go new file mode 100644 index 00000000..61396e93 --- /dev/null +++ b/components/backend/internal/service/userlinks_test.go @@ -0,0 +1,74 @@ +//go:build test && unittest + +package service + +import ( + "strings" + "testing" +) + +func TestLinkUsernames(t *testing.T) { + cases := []struct { + parse func(string) (string, error) + in string + want string // "" means the URL must be rejected + }{ + {linkedInUsername, "https://linkedin.com/in/jane-doe", "jane-doe"}, + {linkedInUsername, "https://www.linkedin.com/in/jane-doe/", "jane-doe"}, + {linkedInUsername, "https://ch.linkedin.com/in/jane-doe/?originalSubdomain=ch", "jane-doe"}, + {linkedInUsername, " HTTP://WWW.LinkedIn.com/in/jane-doe#about ", "jane-doe"}, + {linkedInUsername, "https://www.linkedin.com/in/j%C3%B6rg-m%C3%BCller/", "jörg-müller"}, + {linkedInUsername, "https://www.linkedin.com/in/jörg-müller", "jörg-müller"}, + {linkedInUsername, "https://evil.com/linkedin.com/in/jane", ""}, + {linkedInUsername, "https://www.linkedin.com/company/sdsc", ""}, + {linkedInUsername, "https://www.linkedin.com/in/jane/details", ""}, + {githubUsername, "https://github.com/octo-cat", "octo-cat"}, + {githubUsername, "https://www.github.com/octo-cat/?tab=repositories", "octo-cat"}, + {githubUsername, "http://GitHub.com/octo-cat", "octo-cat"}, + {githubUsername, "https://github.com/octo-cat/repo", ""}, + {githubUsername, "https://github.com/-bad", ""}, + {githubUsername, "https://notgithub.com/octo-cat", ""}, + {githubUsername, "https://github.com/bad-", ""}, + {githubUsername, "https://github.com/a", "a"}, + { + githubUsername, + "https://github.com/" + strings.Repeat("a", 100), + strings.Repeat("a", 100), + }, + {githubUsername, "https://github.com/" + strings.Repeat("a", 101), ""}, + {renkuUsername, "https://renkulab.io/u/jane.doe", "jane.doe"}, + {renkuUsername, "https://renkulab.io/u/jane.doe/?x=1", "jane.doe"}, + {renkuUsername, "https://renkulab.io/u/jane/projects", ""}, + {renkuUsername, "https://renkulab.io/u/.jane", ""}, + { + renkuUsername, + "https://renkulab.io/u/" + strings.Repeat("a", 100), + strings.Repeat("a", 100), + }, + {renkuUsername, "https://renkulab.io/u/" + strings.Repeat("a", 101), ""}, + } + for _, c := range cases { + got, err := c.parse(c.in) + if c.want == "" { + if err == nil { + t.Errorf("%q: want error, got %q", c.in, got) + } + } else if err != nil || got != c.want { + t.Errorf("%q: want %q, got %q (err %v)", c.in, c.want, got, err) + } + } +} + +func TestRenderLink(t *testing.T) { + if got := renderLinkedinURL( + "jörg-müller", + ); got != "https://linkedin.com/in/j%C3%B6rg-m%C3%BCller" { + t.Errorf("got %q", got) + } + if got := renderRenkuURL("jane.doe_1"); got != "https://renkulab.io/u/jane.doe_1" { + t.Errorf("got %q", got) + } + if got := renderGithubURL(""); got != "" { + t.Errorf("got %q", got) + } +} diff --git a/components/frontend/src/lib/server/grpc/generated/user/entities/user.ts b/components/frontend/src/lib/server/grpc/generated/user/entities/user.ts index 48f41e1a..33c58471 100644 --- a/components/frontend/src/lib/server/grpc/generated/user/entities/user.ts +++ b/components/frontend/src/lib/server/grpc/generated/user/entities/user.ts @@ -20,7 +20,13 @@ export interface User { email: string; /** Populated from casbin on fetch; not persisted in ent DB. */ roles: GlobalRole[]; - modifiedAt: Date | undefined; + modifiedAt: + | Date + | undefined; + /** User accounts links. */ + renkuUrl: string; + githubUrl: string; + linkedinUrl: string; } function createBaseUser(): User { @@ -33,6 +39,9 @@ function createBaseUser(): User { email: "", roles: [], modifiedAt: undefined, + renkuUrl: "", + githubUrl: "", + linkedinUrl: "", }; } @@ -64,6 +73,15 @@ export const User: MessageFns = { if (message.modifiedAt !== undefined) { Timestamp.encode(toTimestamp(message.modifiedAt), writer.uint32(66).fork()).join(); } + if (message.renkuUrl !== "") { + writer.uint32(74).string(message.renkuUrl); + } + if (message.githubUrl !== "") { + writer.uint32(82).string(message.githubUrl); + } + if (message.linkedinUrl !== "") { + writer.uint32(90).string(message.linkedinUrl); + } return writer; }, @@ -148,6 +166,30 @@ export const User: MessageFns = { message.modifiedAt = fromTimestamp(Timestamp.decode(reader, reader.uint32())); continue; } + case 9: { + if (tag !== 74) { + break; + } + + message.renkuUrl = reader.string(); + continue; + } + case 10: { + if (tag !== 82) { + break; + } + + message.githubUrl = reader.string(); + continue; + } + case 11: { + if (tag !== 90) { + break; + } + + message.linkedinUrl = reader.string(); + continue; + } } if ((tag & 7) === 4 || tag === 0) { break; @@ -183,6 +225,21 @@ export const User: MessageFns = { : isSet(object.modified_at) ? fromJsonTimestamp(object.modified_at) : undefined, + renkuUrl: isSet(object.renkuUrl) + ? globalThis.String(object.renkuUrl) + : isSet(object.renku_url) + ? globalThis.String(object.renku_url) + : "", + githubUrl: isSet(object.githubUrl) + ? globalThis.String(object.githubUrl) + : isSet(object.github_url) + ? globalThis.String(object.github_url) + : "", + linkedinUrl: isSet(object.linkedinUrl) + ? globalThis.String(object.linkedinUrl) + : isSet(object.linkedin_url) + ? globalThis.String(object.linkedin_url) + : "", }; }, @@ -212,6 +269,15 @@ export const User: MessageFns = { if (message.modifiedAt !== undefined) { obj.modifiedAt = message.modifiedAt.toISOString(); } + if (message.renkuUrl !== "") { + obj.renkuUrl = message.renkuUrl; + } + if (message.githubUrl !== "") { + obj.githubUrl = message.githubUrl; + } + if (message.linkedinUrl !== "") { + obj.linkedinUrl = message.linkedinUrl; + } return obj; }, @@ -228,6 +294,9 @@ export const User: MessageFns = { message.email = object.email ?? ""; message.roles = object.roles?.map((e) => e) || []; message.modifiedAt = object.modifiedAt ?? undefined; + message.renkuUrl = object.renkuUrl ?? ""; + message.githubUrl = object.githubUrl ?? ""; + message.linkedinUrl = object.linkedinUrl ?? ""; return message; }, }; diff --git a/components/frontend/src/lib/server/grpc/generated/user/messages/user_svc/edit_request.ts b/components/frontend/src/lib/server/grpc/generated/user/messages/user_svc/edit_request.ts new file mode 100644 index 00000000..657215b9 --- /dev/null +++ b/components/frontend/src/lib/server/grpc/generated/user/messages/user_svc/edit_request.ts @@ -0,0 +1,162 @@ +// Code generated by protoc-gen-ts_proto. DO NOT EDIT. +// versions: +// protoc-gen-ts_proto v2.11.6 +// protoc unknown +// source: user/messages/user_svc/edit_request.proto + +/* eslint-disable */ +import { BinaryReader, BinaryWriter } from "@bufbuild/protobuf/wire"; + +export const protobufPackage = "user.messages.user_svc"; + +export interface EditRequest { + userId: string; + renkuUrl?: string | undefined; + githubUrl?: string | undefined; + linkedinUrl?: string | undefined; +} + +function createBaseEditRequest(): EditRequest { + return { userId: "", renkuUrl: undefined, githubUrl: undefined, linkedinUrl: undefined }; +} + +export const EditRequest: MessageFns = { + encode(message: EditRequest, writer: BinaryWriter = new BinaryWriter()): BinaryWriter { + if (message.userId !== "") { + writer.uint32(10).string(message.userId); + } + if (message.renkuUrl !== undefined) { + writer.uint32(18).string(message.renkuUrl); + } + if (message.githubUrl !== undefined) { + writer.uint32(26).string(message.githubUrl); + } + if (message.linkedinUrl !== undefined) { + writer.uint32(34).string(message.linkedinUrl); + } + return writer; + }, + + decode(input: BinaryReader | Uint8Array, length?: number): EditRequest { + const reader = input instanceof BinaryReader ? input : new BinaryReader(input); + const end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseEditRequest(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: { + if (tag !== 10) { + break; + } + + message.userId = reader.string(); + continue; + } + case 2: { + if (tag !== 18) { + break; + } + + message.renkuUrl = reader.string(); + continue; + } + case 3: { + if (tag !== 26) { + break; + } + + message.githubUrl = reader.string(); + continue; + } + case 4: { + if (tag !== 34) { + break; + } + + message.linkedinUrl = reader.string(); + continue; + } + } + if ((tag & 7) === 4 || tag === 0) { + break; + } + reader.skip(tag & 7); + } + return message; + }, + + fromJSON(object: any): EditRequest { + return { + userId: isSet(object.userId) + ? globalThis.String(object.userId) + : isSet(object.user_id) + ? globalThis.String(object.user_id) + : "", + renkuUrl: isSet(object.renkuUrl) + ? globalThis.String(object.renkuUrl) + : isSet(object.renku_url) + ? globalThis.String(object.renku_url) + : undefined, + githubUrl: isSet(object.githubUrl) + ? globalThis.String(object.githubUrl) + : isSet(object.github_url) + ? globalThis.String(object.github_url) + : undefined, + linkedinUrl: isSet(object.linkedinUrl) + ? globalThis.String(object.linkedinUrl) + : isSet(object.linkedin_url) + ? globalThis.String(object.linkedin_url) + : undefined, + }; + }, + + toJSON(message: EditRequest): unknown { + const obj: any = {}; + if (message.userId !== "") { + obj.userId = message.userId; + } + if (message.renkuUrl !== undefined) { + obj.renkuUrl = message.renkuUrl; + } + if (message.githubUrl !== undefined) { + obj.githubUrl = message.githubUrl; + } + if (message.linkedinUrl !== undefined) { + obj.linkedinUrl = message.linkedinUrl; + } + return obj; + }, + + create(base?: DeepPartial): EditRequest { + return EditRequest.fromPartial(base ?? {}); + }, + fromPartial(object: DeepPartial): EditRequest { + const message = createBaseEditRequest(); + message.userId = object.userId ?? ""; + message.renkuUrl = object.renkuUrl ?? undefined; + message.githubUrl = object.githubUrl ?? undefined; + message.linkedinUrl = object.linkedinUrl ?? undefined; + return message; + }, +}; + +type Builtin = Date | Function | Uint8Array | string | number | boolean | undefined; + +export type DeepPartial = T extends Builtin ? T + : T extends globalThis.Array ? globalThis.Array> + : T extends ReadonlyArray ? ReadonlyArray> + : T extends {} ? { [K in keyof T]?: DeepPartial } + : Partial; + +function isSet(value: any): boolean { + return value !== null && value !== undefined; +} + +export interface MessageFns { + encode(message: T, writer?: BinaryWriter): BinaryWriter; + decode(input: BinaryReader | Uint8Array, length?: number): T; + fromJSON(object: any): T; + toJSON(message: T): unknown; + create(base?: DeepPartial): T; + fromPartial(object: DeepPartial): T; +} diff --git a/components/frontend/src/lib/server/grpc/generated/user/messages/user_svc/edit_response.ts b/components/frontend/src/lib/server/grpc/generated/user/messages/user_svc/edit_response.ts new file mode 100644 index 00000000..0ce5bba6 --- /dev/null +++ b/components/frontend/src/lib/server/grpc/generated/user/messages/user_svc/edit_response.ts @@ -0,0 +1,94 @@ +// Code generated by protoc-gen-ts_proto. DO NOT EDIT. +// versions: +// protoc-gen-ts_proto v2.11.6 +// protoc unknown +// source: user/messages/user_svc/edit_response.proto + +/* eslint-disable */ +import { BinaryReader, BinaryWriter } from "@bufbuild/protobuf/wire"; +import { User } from "../../entities/user"; + +export const protobufPackage = "user.messages.user_svc"; + +export interface EditResponse { + user: User | undefined; +} + +function createBaseEditResponse(): EditResponse { + return { user: undefined }; +} + +export const EditResponse: MessageFns = { + encode(message: EditResponse, writer: BinaryWriter = new BinaryWriter()): BinaryWriter { + if (message.user !== undefined) { + User.encode(message.user, writer.uint32(10).fork()).join(); + } + return writer; + }, + + decode(input: BinaryReader | Uint8Array, length?: number): EditResponse { + const reader = input instanceof BinaryReader ? input : new BinaryReader(input); + const end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseEditResponse(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: { + if (tag !== 10) { + break; + } + + message.user = User.decode(reader, reader.uint32()); + continue; + } + } + if ((tag & 7) === 4 || tag === 0) { + break; + } + reader.skip(tag & 7); + } + return message; + }, + + fromJSON(object: any): EditResponse { + return { user: isSet(object.user) ? User.fromJSON(object.user) : undefined }; + }, + + toJSON(message: EditResponse): unknown { + const obj: any = {}; + if (message.user !== undefined) { + obj.user = User.toJSON(message.user); + } + return obj; + }, + + create(base?: DeepPartial): EditResponse { + return EditResponse.fromPartial(base ?? {}); + }, + fromPartial(object: DeepPartial): EditResponse { + const message = createBaseEditResponse(); + message.user = (object.user !== undefined && object.user !== null) ? User.fromPartial(object.user) : undefined; + return message; + }, +}; + +type Builtin = Date | Function | Uint8Array | string | number | boolean | undefined; + +export type DeepPartial = T extends Builtin ? T + : T extends globalThis.Array ? globalThis.Array> + : T extends ReadonlyArray ? ReadonlyArray> + : T extends {} ? { [K in keyof T]?: DeepPartial } + : Partial; + +function isSet(value: any): boolean { + return value !== null && value !== undefined; +} + +export interface MessageFns { + encode(message: T, writer?: BinaryWriter): BinaryWriter; + decode(input: BinaryReader | Uint8Array, length?: number): T; + fromJSON(object: any): T; + toJSON(message: T): unknown; + create(base?: DeepPartial): T; + fromPartial(object: DeepPartial): T; +} diff --git a/components/frontend/src/lib/server/grpc/generated/user/user_service.ts b/components/frontend/src/lib/server/grpc/generated/user/user_service.ts index 6358e763..d3313ede 100644 --- a/components/frontend/src/lib/server/grpc/generated/user/user_service.ts +++ b/components/frontend/src/lib/server/grpc/generated/user/user_service.ts @@ -8,6 +8,8 @@ import type { CallContext, CallOptions } from "nice-grpc-common"; import { AddRoleRequest } from "./messages/user_svc/add_role_request"; import { AddRoleResponse } from "./messages/user_svc/add_role_response"; +import { EditRequest } from "./messages/user_svc/edit_request"; +import { EditResponse } from "./messages/user_svc/edit_response"; import { GetRequest } from "./messages/user_svc/get_request"; import { GetResponse } from "./messages/user_svc/get_response"; import { ListRequest } from "./messages/user_svc/list_request"; @@ -42,6 +44,14 @@ export const UserServiceDefinition = { responseStream: false, options: {}, }, + edit: { + name: "Edit", + requestType: EditRequest as typeof EditRequest, + requestStream: false, + responseType: EditResponse as typeof EditResponse, + responseStream: false, + options: {}, + }, whoAmI: { name: "WhoAmI", requestType: WhoAmIRequest as typeof WhoAmIRequest, @@ -80,6 +90,7 @@ export const UserServiceDefinition = { export interface UserServiceImplementation { list(request: ListRequest, context: CallContext & CallContextExt): Promise>; get(request: GetRequest, context: CallContext & CallContextExt): Promise>; + edit(request: EditRequest, context: CallContext & CallContextExt): Promise>; whoAmI(request: WhoAmIRequest, context: CallContext & CallContextExt): Promise>; register(request: RegisterRequest, context: CallContext & CallContextExt): Promise>; addRole(request: AddRoleRequest, context: CallContext & CallContextExt): Promise>; @@ -92,6 +103,7 @@ export interface UserServiceImplementation { export interface UserServiceClient { list(request: DeepPartial, options?: CallOptions & CallOptionsExt): Promise; get(request: DeepPartial, options?: CallOptions & CallOptionsExt): Promise; + edit(request: DeepPartial, options?: CallOptions & CallOptionsExt): Promise; whoAmI(request: DeepPartial, options?: CallOptions & CallOptionsExt): Promise; register(request: DeepPartial, options?: CallOptions & CallOptionsExt): Promise; addRole(request: DeepPartial, options?: CallOptions & CallOptionsExt): Promise;