diff --git a/README.md b/README.md index f94f1e1..2d97ecf 100644 --- a/README.md +++ b/README.md @@ -36,3 +36,10 @@ atlas migrate diff \ ``` The `--format` flag uses a tab as indentation. If you want to manually edit this command you can use `ctrl + v + tab` to insert a tab character in your shell. + +Applying the migration: +```bash +atlas migrate apply \ + --dir file://internal/database/migrations \ + --url "postgres://mensatt:mensatt@localhost:5432/mensatt?search_path=public&sslmode=disable" +``` diff --git a/internal/database/ent/migrate/schema.go b/internal/database/ent/migrate/schema.go index b4abce1..d527662 100644 --- a/internal/database/ent/migrate/schema.go +++ b/internal/database/ent/migrate/schema.go @@ -159,6 +159,8 @@ var ( {Name: "id", Type: field.TypeUUID}, {Name: "email", Type: field.TypeString, Unique: true}, {Name: "password_hash", Type: field.TypeString}, + {Name: "username", Type: field.TypeString, Unique: true, Size: 32}, + {Name: "role", Type: field.TypeEnum, Nullable: true, Enums: []string{"ADMIN", "MOD"}}, {Name: "created_at", Type: field.TypeTime}, {Name: "updated_at", Type: field.TypeTime}, } diff --git a/internal/database/ent/mutation.go b/internal/database/ent/mutation.go index 89946f7..197093e 100644 --- a/internal/database/ent/mutation.go +++ b/internal/database/ent/mutation.go @@ -5670,6 +5670,8 @@ type UserMutation struct { id *uuid.UUID email *string password_hash *string + username *string + role *schema.UserRole created_at *time.Time updated_at *time.Time clearedFields map[string]struct{} @@ -5854,6 +5856,91 @@ func (m *UserMutation) ResetPasswordHash() { m.password_hash = nil } +// SetUsername sets the "username" field. +func (m *UserMutation) SetUsername(s string) { + m.username = &s +} + +// Username returns the value of the "username" field in the mutation. +func (m *UserMutation) Username() (r string, exists bool) { + v := m.username + if v == nil { + return + } + return *v, true +} + +// OldUsername returns the old "username" 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) OldUsername(ctx context.Context) (v string, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldUsername is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldUsername requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldUsername: %w", err) + } + return oldValue.Username, nil +} + +// ResetUsername resets all changes to the "username" field. +func (m *UserMutation) ResetUsername() { + m.username = nil +} + +// SetRole sets the "role" field. +func (m *UserMutation) SetRole(sr schema.UserRole) { + m.role = &sr +} + +// Role returns the value of the "role" field in the mutation. +func (m *UserMutation) Role() (r schema.UserRole, exists bool) { + v := m.role + if v == nil { + return + } + return *v, true +} + +// OldRole returns the old "role" 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) OldRole(ctx context.Context) (v *schema.UserRole, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldRole is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldRole requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldRole: %w", err) + } + return oldValue.Role, nil +} + +// ClearRole clears the value of the "role" field. +func (m *UserMutation) ClearRole() { + m.role = nil + m.clearedFields[user.FieldRole] = struct{}{} +} + +// RoleCleared returns if the "role" field was cleared in this mutation. +func (m *UserMutation) RoleCleared() bool { + _, ok := m.clearedFields[user.FieldRole] + return ok +} + +// ResetRole resets all changes to the "role" field. +func (m *UserMutation) ResetRole() { + m.role = nil + delete(m.clearedFields, user.FieldRole) +} + // SetCreatedAt sets the "created_at" field. func (m *UserMutation) SetCreatedAt(t time.Time) { m.created_at = &t @@ -5960,13 +6047,19 @@ 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, 4) + fields := make([]string, 0, 6) if m.email != nil { fields = append(fields, user.FieldEmail) } if m.password_hash != nil { fields = append(fields, user.FieldPasswordHash) } + if m.username != nil { + fields = append(fields, user.FieldUsername) + } + if m.role != nil { + fields = append(fields, user.FieldRole) + } if m.created_at != nil { fields = append(fields, user.FieldCreatedAt) } @@ -5985,6 +6078,10 @@ func (m *UserMutation) Field(name string) (ent.Value, bool) { return m.Email() case user.FieldPasswordHash: return m.PasswordHash() + case user.FieldUsername: + return m.Username() + case user.FieldRole: + return m.Role() case user.FieldCreatedAt: return m.CreatedAt() case user.FieldUpdatedAt: @@ -6002,6 +6099,10 @@ func (m *UserMutation) OldField(ctx context.Context, name string) (ent.Value, er return m.OldEmail(ctx) case user.FieldPasswordHash: return m.OldPasswordHash(ctx) + case user.FieldUsername: + return m.OldUsername(ctx) + case user.FieldRole: + return m.OldRole(ctx) case user.FieldCreatedAt: return m.OldCreatedAt(ctx) case user.FieldUpdatedAt: @@ -6029,6 +6130,20 @@ func (m *UserMutation) SetField(name string, value ent.Value) error { } m.SetPasswordHash(v) return nil + case user.FieldUsername: + v, ok := value.(string) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetUsername(v) + return nil + case user.FieldRole: + v, ok := value.(schema.UserRole) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetRole(v) + return nil case user.FieldCreatedAt: v, ok := value.(time.Time) if !ok { @@ -6072,7 +6187,11 @@ func (m *UserMutation) AddField(name string, value ent.Value) error { // ClearedFields returns all nullable fields that were cleared during this // mutation. func (m *UserMutation) ClearedFields() []string { - return nil + var fields []string + if m.FieldCleared(user.FieldRole) { + fields = append(fields, user.FieldRole) + } + return fields } // FieldCleared returns a boolean indicating if a field with the given name was @@ -6085,6 +6204,11 @@ func (m *UserMutation) FieldCleared(name string) bool { // ClearField clears the value of the field with the given name. It returns an // error if the field is not defined in the schema. func (m *UserMutation) ClearField(name string) error { + switch name { + case user.FieldRole: + m.ClearRole() + return nil + } return fmt.Errorf("unknown User nullable field %s", name) } @@ -6098,6 +6222,12 @@ func (m *UserMutation) ResetField(name string) error { case user.FieldPasswordHash: m.ResetPasswordHash() return nil + case user.FieldUsername: + m.ResetUsername() + return nil + case user.FieldRole: + m.ResetRole() + return nil case user.FieldCreatedAt: m.ResetCreatedAt() return nil diff --git a/internal/database/ent/runtime.go b/internal/database/ent/runtime.go index cb63f0a..b8917d8 100644 --- a/internal/database/ent/runtime.go +++ b/internal/database/ent/runtime.go @@ -159,12 +159,31 @@ func init() { userDescPasswordHash := userFields[2].Descriptor() // user.PasswordHashValidator is a validator for the "password_hash" field. It is called by the builders before save. user.PasswordHashValidator = userDescPasswordHash.Validators[0].(func(string) error) + // userDescUsername is the schema descriptor for username field. + userDescUsername := userFields[3].Descriptor() + // user.UsernameValidator is a validator for the "username" field. It is called by the builders before save. + user.UsernameValidator = func() func(string) error { + validators := userDescUsername.Validators + fns := [...]func(string) error{ + validators[0].(func(string) error), + validators[1].(func(string) error), + validators[2].(func(string) error), + } + return func(username string) error { + for _, fn := range fns { + if err := fn(username); err != nil { + return err + } + } + return nil + } + }() // userDescCreatedAt is the schema descriptor for created_at field. - userDescCreatedAt := userFields[3].Descriptor() + userDescCreatedAt := userFields[5].Descriptor() // user.DefaultCreatedAt holds the default value on creation for the created_at field. user.DefaultCreatedAt = userDescCreatedAt.Default.(func() time.Time) // userDescUpdatedAt is the schema descriptor for updated_at field. - userDescUpdatedAt := userFields[4].Descriptor() + userDescUpdatedAt := userFields[6].Descriptor() // user.DefaultUpdatedAt holds the default value on creation for the updated_at field. user.DefaultUpdatedAt = userDescUpdatedAt.Default.(func() time.Time) // user.UpdateDefaultUpdatedAt holds the default value on update for the updated_at field. diff --git a/internal/database/ent/user.go b/internal/database/ent/user.go index 1e8fc11..c460a42 100644 --- a/internal/database/ent/user.go +++ b/internal/database/ent/user.go @@ -11,6 +11,7 @@ import ( "entgo.io/ent/dialect/sql" "github.com/google/uuid" "github.com/mensatt/backend/internal/database/ent/user" + "github.com/mensatt/backend/internal/database/schema" ) // User is the model entity for the User schema. @@ -22,6 +23,10 @@ type User struct { Email string `json:"-"` // PasswordHash holds the value of the "password_hash" field. PasswordHash string `json:"-"` + // Username holds the value of the "username" field. + Username string `json:"username,omitempty"` + // Role holds the value of the "role" field. + Role *schema.UserRole `json:"role,omitempty"` // CreatedAt holds the value of the "created_at" field. CreatedAt time.Time `json:"created_at,omitempty"` // UpdatedAt holds the value of the "updated_at" field. @@ -34,7 +39,7 @@ func (*User) scanValues(columns []string) ([]any, error) { values := make([]any, len(columns)) for i := range columns { switch columns[i] { - case user.FieldEmail, user.FieldPasswordHash: + case user.FieldEmail, user.FieldPasswordHash, user.FieldUsername, user.FieldRole: values[i] = new(sql.NullString) case user.FieldCreatedAt, user.FieldUpdatedAt: values[i] = new(sql.NullTime) @@ -73,6 +78,19 @@ func (u *User) assignValues(columns []string, values []any) error { } else if value.Valid { u.PasswordHash = value.String } + case user.FieldUsername: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field username", values[i]) + } else if value.Valid { + u.Username = value.String + } + case user.FieldRole: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field role", values[i]) + } else if value.Valid { + u.Role = new(schema.UserRole) + *u.Role = schema.UserRole(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]) @@ -125,6 +143,14 @@ func (u *User) String() string { builder.WriteString(", ") builder.WriteString("password_hash=") builder.WriteString(", ") + builder.WriteString("username=") + builder.WriteString(u.Username) + builder.WriteString(", ") + if v := u.Role; v != nil { + builder.WriteString("role=") + builder.WriteString(fmt.Sprintf("%v", *v)) + } + builder.WriteString(", ") builder.WriteString("created_at=") builder.WriteString(u.CreatedAt.Format(time.ANSIC)) builder.WriteString(", ") diff --git a/internal/database/ent/user/user.go b/internal/database/ent/user/user.go index 4507374..65f2511 100644 --- a/internal/database/ent/user/user.go +++ b/internal/database/ent/user/user.go @@ -3,10 +3,12 @@ package user import ( + "fmt" "time" "entgo.io/ent/dialect/sql" "github.com/google/uuid" + "github.com/mensatt/backend/internal/database/schema" ) const ( @@ -18,6 +20,10 @@ const ( FieldEmail = "email" // FieldPasswordHash holds the string denoting the password_hash field in the database. FieldPasswordHash = "password_hash" + // FieldUsername holds the string denoting the username field in the database. + FieldUsername = "username" + // FieldRole holds the string denoting the role field in the database. + FieldRole = "role" // FieldCreatedAt holds the string denoting the created_at field in the database. FieldCreatedAt = "created_at" // FieldUpdatedAt holds the string denoting the updated_at field in the database. @@ -31,6 +37,8 @@ var Columns = []string{ FieldID, FieldEmail, FieldPasswordHash, + FieldUsername, + FieldRole, FieldCreatedAt, FieldUpdatedAt, } @@ -50,6 +58,8 @@ var ( EmailValidator func(string) error // PasswordHashValidator is a validator for the "password_hash" field. It is called by the builders before save. PasswordHashValidator func(string) error + // UsernameValidator is a validator for the "username" field. It is called by the builders before save. + UsernameValidator func(string) error // DefaultCreatedAt holds the default value on creation for the "created_at" field. DefaultCreatedAt func() time.Time // DefaultUpdatedAt holds the default value on creation for the "updated_at" field. @@ -60,6 +70,16 @@ var ( DefaultID func() uuid.UUID ) +// RoleValidator is a validator for the "role" field enum values. It is called by the builders before save. +func RoleValidator(r schema.UserRole) error { + switch r { + case "ADMIN", "MOD": + return nil + default: + return fmt.Errorf("user: invalid enum value for role field: %q", r) + } +} + // OrderOption defines the ordering options for the User queries. type OrderOption func(*sql.Selector) @@ -78,6 +98,16 @@ func ByPasswordHash(opts ...sql.OrderTermOption) OrderOption { return sql.OrderByField(FieldPasswordHash, opts...).ToFunc() } +// ByUsername orders the results by the username field. +func ByUsername(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldUsername, opts...).ToFunc() +} + +// ByRole orders the results by the role field. +func ByRole(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldRole, 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/internal/database/ent/user/where.go b/internal/database/ent/user/where.go index 4ff2bfa..c4c4f04 100644 --- a/internal/database/ent/user/where.go +++ b/internal/database/ent/user/where.go @@ -8,6 +8,7 @@ import ( "entgo.io/ent/dialect/sql" "github.com/google/uuid" "github.com/mensatt/backend/internal/database/ent/predicate" + "github.com/mensatt/backend/internal/database/schema" ) // ID filters vertices based on their ID field. @@ -65,6 +66,11 @@ func PasswordHash(v string) predicate.User { return predicate.User(sql.FieldEQ(FieldPasswordHash, v)) } +// Username applies equality check predicate on the "username" field. It's identical to UsernameEQ. +func Username(v string) predicate.User { + return predicate.User(sql.FieldEQ(FieldUsername, 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)) @@ -205,6 +211,111 @@ func PasswordHashContainsFold(v string) predicate.User { return predicate.User(sql.FieldContainsFold(FieldPasswordHash, v)) } +// UsernameEQ applies the EQ predicate on the "username" field. +func UsernameEQ(v string) predicate.User { + return predicate.User(sql.FieldEQ(FieldUsername, v)) +} + +// UsernameNEQ applies the NEQ predicate on the "username" field. +func UsernameNEQ(v string) predicate.User { + return predicate.User(sql.FieldNEQ(FieldUsername, v)) +} + +// UsernameIn applies the In predicate on the "username" field. +func UsernameIn(vs ...string) predicate.User { + return predicate.User(sql.FieldIn(FieldUsername, vs...)) +} + +// UsernameNotIn applies the NotIn predicate on the "username" field. +func UsernameNotIn(vs ...string) predicate.User { + return predicate.User(sql.FieldNotIn(FieldUsername, vs...)) +} + +// UsernameGT applies the GT predicate on the "username" field. +func UsernameGT(v string) predicate.User { + return predicate.User(sql.FieldGT(FieldUsername, v)) +} + +// UsernameGTE applies the GTE predicate on the "username" field. +func UsernameGTE(v string) predicate.User { + return predicate.User(sql.FieldGTE(FieldUsername, v)) +} + +// UsernameLT applies the LT predicate on the "username" field. +func UsernameLT(v string) predicate.User { + return predicate.User(sql.FieldLT(FieldUsername, v)) +} + +// UsernameLTE applies the LTE predicate on the "username" field. +func UsernameLTE(v string) predicate.User { + return predicate.User(sql.FieldLTE(FieldUsername, v)) +} + +// UsernameContains applies the Contains predicate on the "username" field. +func UsernameContains(v string) predicate.User { + return predicate.User(sql.FieldContains(FieldUsername, v)) +} + +// UsernameHasPrefix applies the HasPrefix predicate on the "username" field. +func UsernameHasPrefix(v string) predicate.User { + return predicate.User(sql.FieldHasPrefix(FieldUsername, v)) +} + +// UsernameHasSuffix applies the HasSuffix predicate on the "username" field. +func UsernameHasSuffix(v string) predicate.User { + return predicate.User(sql.FieldHasSuffix(FieldUsername, v)) +} + +// UsernameEqualFold applies the EqualFold predicate on the "username" field. +func UsernameEqualFold(v string) predicate.User { + return predicate.User(sql.FieldEqualFold(FieldUsername, v)) +} + +// UsernameContainsFold applies the ContainsFold predicate on the "username" field. +func UsernameContainsFold(v string) predicate.User { + return predicate.User(sql.FieldContainsFold(FieldUsername, v)) +} + +// RoleEQ applies the EQ predicate on the "role" field. +func RoleEQ(v schema.UserRole) predicate.User { + vc := v + return predicate.User(sql.FieldEQ(FieldRole, vc)) +} + +// RoleNEQ applies the NEQ predicate on the "role" field. +func RoleNEQ(v schema.UserRole) predicate.User { + vc := v + return predicate.User(sql.FieldNEQ(FieldRole, vc)) +} + +// RoleIn applies the In predicate on the "role" field. +func RoleIn(vs ...schema.UserRole) predicate.User { + v := make([]any, len(vs)) + for i := range v { + v[i] = vs[i] + } + return predicate.User(sql.FieldIn(FieldRole, v...)) +} + +// RoleNotIn applies the NotIn predicate on the "role" field. +func RoleNotIn(vs ...schema.UserRole) predicate.User { + v := make([]any, len(vs)) + for i := range v { + v[i] = vs[i] + } + return predicate.User(sql.FieldNotIn(FieldRole, v...)) +} + +// RoleIsNil applies the IsNil predicate on the "role" field. +func RoleIsNil() predicate.User { + return predicate.User(sql.FieldIsNull(FieldRole)) +} + +// RoleNotNil applies the NotNil predicate on the "role" field. +func RoleNotNil() predicate.User { + return predicate.User(sql.FieldNotNull(FieldRole)) +} + // 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/internal/database/ent/user_create.go b/internal/database/ent/user_create.go index c480830..e20ab00 100644 --- a/internal/database/ent/user_create.go +++ b/internal/database/ent/user_create.go @@ -14,6 +14,7 @@ import ( "entgo.io/ent/schema/field" "github.com/google/uuid" "github.com/mensatt/backend/internal/database/ent/user" + "github.com/mensatt/backend/internal/database/schema" ) // UserCreate is the builder for creating a User entity. @@ -36,6 +37,26 @@ func (uc *UserCreate) SetPasswordHash(s string) *UserCreate { return uc } +// SetUsername sets the "username" field. +func (uc *UserCreate) SetUsername(s string) *UserCreate { + uc.mutation.SetUsername(s) + return uc +} + +// SetRole sets the "role" field. +func (uc *UserCreate) SetRole(sr schema.UserRole) *UserCreate { + uc.mutation.SetRole(sr) + return uc +} + +// SetNillableRole sets the "role" field if the given value is not nil. +func (uc *UserCreate) SetNillableRole(sr *schema.UserRole) *UserCreate { + if sr != nil { + uc.SetRole(*sr) + } + return uc +} + // SetCreatedAt sets the "created_at" field. func (uc *UserCreate) SetCreatedAt(t time.Time) *UserCreate { uc.mutation.SetCreatedAt(t) @@ -145,6 +166,19 @@ func (uc *UserCreate) check() error { return &ValidationError{Name: "password_hash", err: fmt.Errorf(`ent: validator failed for field "User.password_hash": %w`, err)} } } + if _, ok := uc.mutation.Username(); !ok { + return &ValidationError{Name: "username", err: errors.New(`ent: missing required field "User.username"`)} + } + if v, ok := uc.mutation.Username(); ok { + if err := user.UsernameValidator(v); err != nil { + return &ValidationError{Name: "username", err: fmt.Errorf(`ent: validator failed for field "User.username": %w`, err)} + } + } + if v, ok := uc.mutation.Role(); ok { + if err := user.RoleValidator(v); err != nil { + return &ValidationError{Name: "role", err: fmt.Errorf(`ent: validator failed for field "User.role": %w`, err)} + } + } if _, ok := uc.mutation.CreatedAt(); !ok { return &ValidationError{Name: "created_at", err: errors.New(`ent: missing required field "User.created_at"`)} } @@ -195,6 +229,14 @@ func (uc *UserCreate) createSpec() (*User, *sqlgraph.CreateSpec) { _spec.SetField(user.FieldPasswordHash, field.TypeString, value) _node.PasswordHash = value } + if value, ok := uc.mutation.Username(); ok { + _spec.SetField(user.FieldUsername, field.TypeString, value) + _node.Username = value + } + if value, ok := uc.mutation.Role(); ok { + _spec.SetField(user.FieldRole, field.TypeEnum, value) + _node.Role = &value + } if value, ok := uc.mutation.CreatedAt(); ok { _spec.SetField(user.FieldCreatedAt, field.TypeTime, value) _node.CreatedAt = value @@ -279,6 +321,36 @@ func (u *UserUpsert) UpdatePasswordHash() *UserUpsert { return u } +// SetUsername sets the "username" field. +func (u *UserUpsert) SetUsername(v string) *UserUpsert { + u.Set(user.FieldUsername, v) + return u +} + +// UpdateUsername sets the "username" field to the value that was provided on create. +func (u *UserUpsert) UpdateUsername() *UserUpsert { + u.SetExcluded(user.FieldUsername) + return u +} + +// SetRole sets the "role" field. +func (u *UserUpsert) SetRole(v schema.UserRole) *UserUpsert { + u.Set(user.FieldRole, v) + return u +} + +// UpdateRole sets the "role" field to the value that was provided on create. +func (u *UserUpsert) UpdateRole() *UserUpsert { + u.SetExcluded(user.FieldRole) + return u +} + +// ClearRole clears the value of the "role" field. +func (u *UserUpsert) ClearRole() *UserUpsert { + u.SetNull(user.FieldRole) + return u +} + // SetUpdatedAt sets the "updated_at" field. func (u *UserUpsert) SetUpdatedAt(v time.Time) *UserUpsert { u.Set(user.FieldUpdatedAt, v) @@ -370,6 +442,41 @@ func (u *UserUpsertOne) UpdatePasswordHash() *UserUpsertOne { }) } +// SetUsername sets the "username" field. +func (u *UserUpsertOne) SetUsername(v string) *UserUpsertOne { + return u.Update(func(s *UserUpsert) { + s.SetUsername(v) + }) +} + +// UpdateUsername sets the "username" field to the value that was provided on create. +func (u *UserUpsertOne) UpdateUsername() *UserUpsertOne { + return u.Update(func(s *UserUpsert) { + s.UpdateUsername() + }) +} + +// SetRole sets the "role" field. +func (u *UserUpsertOne) SetRole(v schema.UserRole) *UserUpsertOne { + return u.Update(func(s *UserUpsert) { + s.SetRole(v) + }) +} + +// UpdateRole sets the "role" field to the value that was provided on create. +func (u *UserUpsertOne) UpdateRole() *UserUpsertOne { + return u.Update(func(s *UserUpsert) { + s.UpdateRole() + }) +} + +// ClearRole clears the value of the "role" field. +func (u *UserUpsertOne) ClearRole() *UserUpsertOne { + return u.Update(func(s *UserUpsert) { + s.ClearRole() + }) +} + // SetUpdatedAt sets the "updated_at" field. func (u *UserUpsertOne) SetUpdatedAt(v time.Time) *UserUpsertOne { return u.Update(func(s *UserUpsert) { @@ -626,6 +733,41 @@ func (u *UserUpsertBulk) UpdatePasswordHash() *UserUpsertBulk { }) } +// SetUsername sets the "username" field. +func (u *UserUpsertBulk) SetUsername(v string) *UserUpsertBulk { + return u.Update(func(s *UserUpsert) { + s.SetUsername(v) + }) +} + +// UpdateUsername sets the "username" field to the value that was provided on create. +func (u *UserUpsertBulk) UpdateUsername() *UserUpsertBulk { + return u.Update(func(s *UserUpsert) { + s.UpdateUsername() + }) +} + +// SetRole sets the "role" field. +func (u *UserUpsertBulk) SetRole(v schema.UserRole) *UserUpsertBulk { + return u.Update(func(s *UserUpsert) { + s.SetRole(v) + }) +} + +// UpdateRole sets the "role" field to the value that was provided on create. +func (u *UserUpsertBulk) UpdateRole() *UserUpsertBulk { + return u.Update(func(s *UserUpsert) { + s.UpdateRole() + }) +} + +// ClearRole clears the value of the "role" field. +func (u *UserUpsertBulk) ClearRole() *UserUpsertBulk { + return u.Update(func(s *UserUpsert) { + s.ClearRole() + }) +} + // SetUpdatedAt sets the "updated_at" field. func (u *UserUpsertBulk) SetUpdatedAt(v time.Time) *UserUpsertBulk { return u.Update(func(s *UserUpsert) { diff --git a/internal/database/ent/user_update.go b/internal/database/ent/user_update.go index 6901761..48e0205 100644 --- a/internal/database/ent/user_update.go +++ b/internal/database/ent/user_update.go @@ -13,6 +13,7 @@ import ( "entgo.io/ent/schema/field" "github.com/mensatt/backend/internal/database/ent/predicate" "github.com/mensatt/backend/internal/database/ent/user" + "github.com/mensatt/backend/internal/database/schema" ) // UserUpdate is the builder for updating User entities. @@ -40,6 +41,32 @@ func (uu *UserUpdate) SetPasswordHash(s string) *UserUpdate { return uu } +// SetUsername sets the "username" field. +func (uu *UserUpdate) SetUsername(s string) *UserUpdate { + uu.mutation.SetUsername(s) + return uu +} + +// SetRole sets the "role" field. +func (uu *UserUpdate) SetRole(sr schema.UserRole) *UserUpdate { + uu.mutation.SetRole(sr) + return uu +} + +// SetNillableRole sets the "role" field if the given value is not nil. +func (uu *UserUpdate) SetNillableRole(sr *schema.UserRole) *UserUpdate { + if sr != nil { + uu.SetRole(*sr) + } + return uu +} + +// ClearRole clears the value of the "role" field. +func (uu *UserUpdate) ClearRole() *UserUpdate { + uu.mutation.ClearRole() + return uu +} + // SetUpdatedAt sets the "updated_at" field. func (uu *UserUpdate) SetUpdatedAt(t time.Time) *UserUpdate { uu.mutation.SetUpdatedAt(t) @@ -99,6 +126,16 @@ func (uu *UserUpdate) check() error { return &ValidationError{Name: "password_hash", err: fmt.Errorf(`ent: validator failed for field "User.password_hash": %w`, err)} } } + if v, ok := uu.mutation.Username(); ok { + if err := user.UsernameValidator(v); err != nil { + return &ValidationError{Name: "username", err: fmt.Errorf(`ent: validator failed for field "User.username": %w`, err)} + } + } + if v, ok := uu.mutation.Role(); ok { + if err := user.RoleValidator(v); err != nil { + return &ValidationError{Name: "role", err: fmt.Errorf(`ent: validator failed for field "User.role": %w`, err)} + } + } return nil } @@ -120,6 +157,15 @@ func (uu *UserUpdate) sqlSave(ctx context.Context) (n int, err error) { if value, ok := uu.mutation.PasswordHash(); ok { _spec.SetField(user.FieldPasswordHash, field.TypeString, value) } + if value, ok := uu.mutation.Username(); ok { + _spec.SetField(user.FieldUsername, field.TypeString, value) + } + if value, ok := uu.mutation.Role(); ok { + _spec.SetField(user.FieldRole, field.TypeEnum, value) + } + if uu.mutation.RoleCleared() { + _spec.ClearField(user.FieldRole, field.TypeEnum) + } if value, ok := uu.mutation.UpdatedAt(); ok { _spec.SetField(user.FieldUpdatedAt, field.TypeTime, value) } @@ -155,6 +201,32 @@ func (uuo *UserUpdateOne) SetPasswordHash(s string) *UserUpdateOne { return uuo } +// SetUsername sets the "username" field. +func (uuo *UserUpdateOne) SetUsername(s string) *UserUpdateOne { + uuo.mutation.SetUsername(s) + return uuo +} + +// SetRole sets the "role" field. +func (uuo *UserUpdateOne) SetRole(sr schema.UserRole) *UserUpdateOne { + uuo.mutation.SetRole(sr) + return uuo +} + +// SetNillableRole sets the "role" field if the given value is not nil. +func (uuo *UserUpdateOne) SetNillableRole(sr *schema.UserRole) *UserUpdateOne { + if sr != nil { + uuo.SetRole(*sr) + } + return uuo +} + +// ClearRole clears the value of the "role" field. +func (uuo *UserUpdateOne) ClearRole() *UserUpdateOne { + uuo.mutation.ClearRole() + return uuo +} + // SetUpdatedAt sets the "updated_at" field. func (uuo *UserUpdateOne) SetUpdatedAt(t time.Time) *UserUpdateOne { uuo.mutation.SetUpdatedAt(t) @@ -227,6 +299,16 @@ func (uuo *UserUpdateOne) check() error { return &ValidationError{Name: "password_hash", err: fmt.Errorf(`ent: validator failed for field "User.password_hash": %w`, err)} } } + if v, ok := uuo.mutation.Username(); ok { + if err := user.UsernameValidator(v); err != nil { + return &ValidationError{Name: "username", err: fmt.Errorf(`ent: validator failed for field "User.username": %w`, err)} + } + } + if v, ok := uuo.mutation.Role(); ok { + if err := user.RoleValidator(v); err != nil { + return &ValidationError{Name: "role", err: fmt.Errorf(`ent: validator failed for field "User.role": %w`, err)} + } + } return nil } @@ -265,6 +347,15 @@ func (uuo *UserUpdateOne) sqlSave(ctx context.Context) (_node *User, err error) if value, ok := uuo.mutation.PasswordHash(); ok { _spec.SetField(user.FieldPasswordHash, field.TypeString, value) } + if value, ok := uuo.mutation.Username(); ok { + _spec.SetField(user.FieldUsername, field.TypeString, value) + } + if value, ok := uuo.mutation.Role(); ok { + _spec.SetField(user.FieldRole, field.TypeEnum, value) + } + if uuo.mutation.RoleCleared() { + _spec.ClearField(user.FieldRole, field.TypeEnum) + } if value, ok := uuo.mutation.UpdatedAt(); ok { _spec.SetField(user.FieldUpdatedAt, field.TypeTime, value) } diff --git a/internal/database/migrations/20231110113916_add_usernames_and_roles.sql b/internal/database/migrations/20231110113916_add_usernames_and_roles.sql new file mode 100644 index 0000000..aa327aa --- /dev/null +++ b/internal/database/migrations/20231110113916_add_usernames_and_roles.sql @@ -0,0 +1,6 @@ +-- Modify "user" table +ALTER TABLE "user" + ADD COLUMN "username" character varying NOT NULL DEFAULT SUBSTRING(gen_random_uuid()::text, 1, 16), + ADD COLUMN "role" character varying NULL; +-- Create index "user_username_key" to table: "user" +CREATE UNIQUE INDEX "user_username_key" ON "user" ("username"); diff --git a/internal/database/migrations/atlas.sum b/internal/database/migrations/atlas.sum index ae78527..6709701 100644 --- a/internal/database/migrations/atlas.sum +++ b/internal/database/migrations/atlas.sum @@ -1,2 +1,3 @@ -h1:9HWDZFhHBeSqK8XP3IplJznKaiTr+sgy0nIe3yEFdc8= -20231103235442_initial_database.sql h1:s/EkAbPVvRS/gPUIvag0qWgCZvzZwoTDA2MOsx7Z2s4= +h1:9MRfV/PdBVSuUB2wvqIUnhsPEeJaQTyiCgRVF9aJtoA= +20231103235442_initial_database.sql h1:rXmCB2eMDP8LJVllwOgeBpNNXC5UyUBDx0T/gUx2Oyo= +20231110113916_add_usernames_and_roles.sql h1:mSQrE1xImWV4w7kphzItUEL79fZox0HOrx5YcSPh6kU= diff --git a/internal/database/schema/enums.go b/internal/database/schema/enums.go index ba334bc..cd4de89 100644 --- a/internal/database/schema/enums.go +++ b/internal/database/schema/enums.go @@ -15,3 +15,17 @@ func (TagPriority) Values() (kinds []string) { } return } + +type UserRole string + +const ( + Admin UserRole = "ADMIN" + Mod UserRole = "MOD" +) + +func (UserRole) Values() (kinds []string) { + for _, s := range []UserRole{Admin, Mod} { + kinds = append(kinds, string(s)) + } + return +} diff --git a/internal/database/schema/user.go b/internal/database/schema/user.go index 574a337..0a498e8 100644 --- a/internal/database/schema/user.go +++ b/internal/database/schema/user.go @@ -27,6 +27,8 @@ func (User) Fields() []ent.Field { field.UUID("id", uuid.UUID{}).Default(uuid.New).Immutable(), field.String("email").Unique().NotEmpty().Sensitive(), field.String("password_hash").NotEmpty().Sensitive(), + field.String("username").Unique().NotEmpty().MinLen(3).MaxLen(32), + field.Enum("role").GoType(UserRole("")).Optional().Nillable(), field.Time("created_at").Default(time.Now).Immutable(), field.Time("updated_at").Default(time.Now).UpdateDefault(time.Now), } diff --git a/internal/database/seeds/users.go b/internal/database/seeds/users.go index 6636139..1148cf9 100644 --- a/internal/database/seeds/users.go +++ b/internal/database/seeds/users.go @@ -3,11 +3,16 @@ package seeds import ( "context" "github.com/mensatt/backend/internal/database/ent" + "github.com/mensatt/backend/internal/database/schema" ) func seedUsers(ctx context.Context, client *ent.Client) error { err := client.User.CreateBulk( - client.User.Create().SetEmail("admin@mensatt.de").SetPasswordHash("$2a$10$pdvY6v8k2McSYbFk3HRDl.h8QfMjOxfpm2CywkDDzfOzlYDZV8NUm"), + client.User.Create(). + SetEmail("admin@mensatt.de"). + SetPasswordHash("$2a$10$pdvY6v8k2McSYbFk3HRDl.h8QfMjOxfpm2CywkDDzfOzlYDZV8NUm"). + SetUsername("mensatt"). + SetRole(schema.Admin), ).OnConflict().DoNothing().Exec(ctx) return err } diff --git a/internal/graphql/directives/directive.go b/internal/graphql/directives/directive.go index 2bb2317..d7fc925 100644 --- a/internal/graphql/directives/directive.go +++ b/internal/graphql/directives/directive.go @@ -3,8 +3,8 @@ package directives import ( "context" "fmt" - "github.com/99designs/gqlgen/graphql" + "github.com/mensatt/backend/internal/database/schema" "github.com/mensatt/backend/internal/middleware" ) @@ -15,3 +15,16 @@ func Authenticated(ctx context.Context, obj interface{}, next graphql.Resolver) } return next(ctx) } + +func Auth(ctx context.Context, obj interface{}, next graphql.Resolver, requiredRole *schema.UserRole) (interface{}, error) { + user := middleware.GetUserIDFromCtx(ctx) + if user == nil { + return nil, fmt.Errorf("not authenticated") + } + + if requiredRole != nil && (user.Role == nil || *user.Role != *requiredRole) { + return nil, fmt.Errorf("not authorized") + } + + return next(ctx) +} diff --git a/internal/graphql/gqlgen.yml b/internal/graphql/gqlgen.yml index 8472d74..93ac871 100644 --- a/internal/graphql/gqlgen.yml +++ b/internal/graphql/gqlgen.yml @@ -67,6 +67,10 @@ models: model: - github.com/mensatt/backend/internal/database/schema.TagPriority + UserRole: + model: + - github.com/mensatt/backend/internal/database/schema.UserRole + # Other VcsBuildInfo: model: diff --git a/internal/graphql/gqlserver/generated.go b/internal/graphql/gqlserver/generated.go index 708e379..08affb6 100644 --- a/internal/graphql/gqlserver/generated.go +++ b/internal/graphql/gqlserver/generated.go @@ -55,6 +55,7 @@ type ResolverRoot interface { } type DirectiveRoot struct { + Auth func(ctx context.Context, obj interface{}, next graphql.Resolver, requires *schema.UserRole) (res interface{}, err error) Authenticated func(ctx context.Context, obj interface{}, next graphql.Resolver) (res interface{}, err error) } @@ -106,6 +107,7 @@ type ComplexityRoot struct { UpdateDish func(childComplexity int, input models.UpdateDishInput) int UpdateOccurrence func(childComplexity int, input models.UpdateOccurrenceInput) int UpdateReview func(childComplexity int, input models.UpdateReviewInput) int + UpdateUser func(childComplexity int, input models.UpdateUserInput) int } Occurrence struct { @@ -200,8 +202,10 @@ type ComplexityRoot struct { } User struct { - Email func(childComplexity int) int - ID func(childComplexity int) int + Email func(childComplexity int) int + ID func(childComplexity int) int + Role func(childComplexity int) int + Username func(childComplexity int) int } VcsBuildInfo struct { @@ -226,6 +230,7 @@ type ImageResolver interface { } type MutationResolver interface { LoginUser(ctx context.Context, input models.LoginUserInput) (string, error) + UpdateUser(ctx context.Context, input models.UpdateUserInput) (*ent.User, error) CreateTag(ctx context.Context, input models.CreateTagInput) (*ent.Tag, error) CreateDish(ctx context.Context, input models.CreateDishInput) (*ent.Dish, error) UpdateDish(ctx context.Context, input models.UpdateDishInput) (*ent.Dish, error) @@ -622,6 +627,18 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.Mutation.UpdateReview(childComplexity, args["input"].(models.UpdateReviewInput)), true + case "Mutation.updateUser": + if e.complexity.Mutation.UpdateUser == nil { + break + } + + args, err := ec.field_Mutation_updateUser_args(context.TODO(), rawArgs) + if err != nil { + return 0, false + } + + return e.complexity.Mutation.UpdateUser(childComplexity, args["input"].(models.UpdateUserInput)), true + case "Occurrence.carbohydrates": if e.complexity.Occurrence.Carbohydrates == nil { break @@ -1067,6 +1084,20 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.User.ID(childComplexity), true + case "User.role": + if e.complexity.User.Role == nil { + break + } + + return e.complexity.User.Role(childComplexity), true + + case "User.username": + if e.complexity.User.Username == nil { + break + } + + return e.complexity.User.Username(childComplexity), true + case "VcsBuildInfo.commit": if e.complexity.VcsBuildInfo.Commit == nil { break @@ -1119,6 +1150,7 @@ func (e *executableSchema) Exec(ctx context.Context) graphql.ResponseHandler { ec.unmarshalInputUpdateDishInput, ec.unmarshalInputUpdateOccurrenceInput, ec.unmarshalInputUpdateReviewInput, + ec.unmarshalInputUpdateUserInput, ) first := true @@ -1233,22 +1265,10 @@ func (ec *executionContext) introspectType(name string) (*introspection.Type, er } var sources = []*ast.Source{ - {Name: "../schema/directives.graphql", Input: `directive @goModel( - model: String - models: [String!] -) on OBJECT | INPUT_OBJECT | SCALAR | ENUM | INTERFACE | UNION + {Name: "../schema/directives.graphql", Input: `directive @auth(requires: UserRole = ADMIN) on OBJECT | FIELD_DEFINITION -directive @goField( - forceResolver: Boolean - name: String -) on INPUT_FIELD_DEFINITION | FIELD_DEFINITION - -directive @goTag( - key: String! - value: String -) on INPUT_FIELD_DEFINITION | FIELD_DEFINITION - -directive @authenticated on FIELD_DEFINITION`, BuiltIn: false}, +directive @authenticated on FIELD_DEFINITION +`, BuiltIn: false}, {Name: "../schema/inputs.graphql", Input: `# User input LoginUserInput { @@ -1256,6 +1276,14 @@ input LoginUserInput { password: String! } +input UpdateUserInput { + id: UUID! + email: String + password: String + username: String + role: UserRole +} + # Tag @@ -1437,33 +1465,34 @@ input LocationFilter { {Name: "../schema/mutations.graphql", Input: `type Mutation { # User loginUser(input: LoginUserInput!): String! + updateUser(input: UpdateUserInput!): User! # Tag - createTag(input: CreateTagInput!): Tag! @authenticated + createTag(input: CreateTagInput!): Tag! @auth(requires: ADMIN) # Dish - createDish(input: CreateDishInput!): Dish! @authenticated - updateDish(input: UpdateDishInput!): Dish! @authenticated + createDish(input: CreateDishInput!): Dish! @auth(requires: ADMIN) + updateDish(input: UpdateDishInput!): Dish! @auth(requires: ADMIN) # mergeDishes(input: MergeDishesInput!): Dish! @authenticated # DishAlias - createDishAlias(input: CreateDishAliasInput!): DishAlias! @authenticated - deleteDishAlias(input: DeleteDishAliasInput!): DishAlias! @authenticated + createDishAlias(input: CreateDishAliasInput!): DishAlias! @auth(requires: ADMIN) + deleteDishAlias(input: DeleteDishAliasInput!): DishAlias! @auth(requires: ADMIN) # Occurrence - createOccurrence(input: CreateOccurrenceInput!): Occurrence! @authenticated - updateOccurrence(input: UpdateOccurrenceInput!): Occurrence! @authenticated - deleteOccurrence(input: DeleteOccurrenceInput!): Occurrence! @authenticated + createOccurrence(input: CreateOccurrenceInput!): Occurrence! @auth(requires: ADMIN) + updateOccurrence(input: UpdateOccurrenceInput!): Occurrence! @auth(requires: ADMIN) + deleteOccurrence(input: DeleteOccurrenceInput!): Occurrence! @auth(requires: ADMIN) - addTagToOccurrence(input: AddTagToOccurrenceInput!): OccurrenceTag! @authenticated - removeTagFromOccurrence(input: RemoveTagFromOccurrenceInput!): OccurrenceTag! @authenticated - addSideDishToOccurrence(input: AddSideDishToOccurrenceInput!): OccurrenceSideDish! @authenticated - removeSideDishFromOccurrence(input: RemoveSideDishFromOccurrenceInput!): OccurrenceSideDish! @authenticated + addTagToOccurrence(input: AddTagToOccurrenceInput!): OccurrenceTag! @auth(requires: ADMIN) + removeTagFromOccurrence(input: RemoveTagFromOccurrenceInput!): OccurrenceTag! @auth(requires: ADMIN) + addSideDishToOccurrence(input: AddSideDishToOccurrenceInput!): OccurrenceSideDish! @auth(requires: ADMIN) + removeSideDishFromOccurrence(input: RemoveSideDishFromOccurrenceInput!): OccurrenceSideDish! @auth(requires: ADMIN) # Review createReview(input: CreateReviewInput!): Review! - updateReview(input: UpdateReviewInput!): Review! @authenticated - deleteReview(input: DeleteReviewInput!): Review! @authenticated + updateReview(input: UpdateReviewInput!): Review! @auth(requires: MOD) + deleteReview(input: DeleteReviewInput!): Review! @auth(requires: MOD) # Image addImagesToReview(input: AddImagesToReviewInput!): Review! @@ -1625,9 +1654,16 @@ type Image { imageUrl: String! } +enum UserRole { + ADMIN + MOD +} + type User { id: UUID! email: String! + username: String! + role: UserRole } type VcsBuildInfo { @@ -1643,6 +1679,21 @@ var parsedSchema = gqlparser.MustLoadSchema(sources...) // region ***************************** args.gotpl ***************************** +func (ec *executionContext) dir_auth_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { + var err error + args := map[string]interface{}{} + var arg0 *schema.UserRole + if tmp, ok := rawArgs["requires"]; ok { + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("requires")) + arg0, err = ec.unmarshalOUserRole2ᚖgithubᚗcomᚋmensattᚋbackendᚋinternalᚋdatabaseᚋschemaᚐUserRole(ctx, tmp) + if err != nil { + return nil, err + } + } + args["requires"] = arg0 + return args, nil +} + func (ec *executionContext) field_Dish_reviewData_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { var err error args := map[string]interface{}{} @@ -1928,6 +1979,21 @@ func (ec *executionContext) field_Mutation_updateReview_args(ctx context.Context return args, nil } +func (ec *executionContext) field_Mutation_updateUser_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { + var err error + args := map[string]interface{}{} + var arg0 models.UpdateUserInput + if tmp, ok := rawArgs["input"]; ok { + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("input")) + arg0, err = ec.unmarshalNUpdateUserInput2githubᚗcomᚋmensattᚋbackendᚋinternalᚋgraphqlᚋmodelsᚐUpdateUserInput(ctx, tmp) + if err != nil { + return nil, err + } + } + args["input"] = arg0 + return args, nil +} + func (ec *executionContext) field_Occurrence_reviewData_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { var err error args := map[string]interface{}{} @@ -2871,6 +2937,71 @@ func (ec *executionContext) fieldContext_Mutation_loginUser(ctx context.Context, return fc, nil } +func (ec *executionContext) _Mutation_updateUser(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_Mutation_updateUser(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return ec.resolvers.Mutation().UpdateUser(rctx, fc.Args["input"].(models.UpdateUserInput)) + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(*ent.User) + fc.Result = res + return ec.marshalNUser2ᚖgithubᚗcomᚋmensattᚋbackendᚋinternalᚋdatabaseᚋentᚐUser(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_Mutation_updateUser(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "Mutation", + Field: field, + IsMethod: true, + IsResolver: true, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + switch field.Name { + case "id": + return ec.fieldContext_User_id(ctx, field) + case "email": + return ec.fieldContext_User_email(ctx, field) + case "username": + return ec.fieldContext_User_username(ctx, field) + case "role": + return ec.fieldContext_User_role(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type User", field.Name) + }, + } + defer func() { + if r := recover(); r != nil { + err = ec.Recover(ctx, r) + ec.Error(ctx, err) + } + }() + ctx = graphql.WithFieldContext(ctx, fc) + if fc.Args, err = ec.field_Mutation_updateUser_args(ctx, field.ArgumentMap(ec.Variables)); err != nil { + ec.Error(ctx, err) + return fc, err + } + return fc, nil +} + func (ec *executionContext) _Mutation_createTag(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { fc, err := ec.fieldContext_Mutation_createTag(ctx, field) if err != nil { @@ -2889,10 +3020,14 @@ func (ec *executionContext) _Mutation_createTag(ctx context.Context, field graph return ec.resolvers.Mutation().CreateTag(rctx, fc.Args["input"].(models.CreateTagInput)) } directive1 := func(ctx context.Context) (interface{}, error) { - if ec.directives.Authenticated == nil { - return nil, errors.New("directive authenticated is not implemented") + requires, err := ec.unmarshalOUserRole2ᚖgithubᚗcomᚋmensattᚋbackendᚋinternalᚋdatabaseᚋschemaᚐUserRole(ctx, "ADMIN") + if err != nil { + return nil, err + } + if ec.directives.Auth == nil { + return nil, errors.New("directive auth is not implemented") } - return ec.directives.Authenticated(ctx, nil, directive0) + return ec.directives.Auth(ctx, nil, directive0, requires) } tmp, err := directive1(rctx) @@ -2978,10 +3113,14 @@ func (ec *executionContext) _Mutation_createDish(ctx context.Context, field grap return ec.resolvers.Mutation().CreateDish(rctx, fc.Args["input"].(models.CreateDishInput)) } directive1 := func(ctx context.Context) (interface{}, error) { - if ec.directives.Authenticated == nil { - return nil, errors.New("directive authenticated is not implemented") + requires, err := ec.unmarshalOUserRole2ᚖgithubᚗcomᚋmensattᚋbackendᚋinternalᚋdatabaseᚋschemaᚐUserRole(ctx, "ADMIN") + if err != nil { + return nil, err } - return ec.directives.Authenticated(ctx, nil, directive0) + if ec.directives.Auth == nil { + return nil, errors.New("directive auth is not implemented") + } + return ec.directives.Auth(ctx, nil, directive0, requires) } tmp, err := directive1(rctx) @@ -3065,10 +3204,14 @@ func (ec *executionContext) _Mutation_updateDish(ctx context.Context, field grap return ec.resolvers.Mutation().UpdateDish(rctx, fc.Args["input"].(models.UpdateDishInput)) } directive1 := func(ctx context.Context) (interface{}, error) { - if ec.directives.Authenticated == nil { - return nil, errors.New("directive authenticated is not implemented") + requires, err := ec.unmarshalOUserRole2ᚖgithubᚗcomᚋmensattᚋbackendᚋinternalᚋdatabaseᚋschemaᚐUserRole(ctx, "ADMIN") + if err != nil { + return nil, err } - return ec.directives.Authenticated(ctx, nil, directive0) + if ec.directives.Auth == nil { + return nil, errors.New("directive auth is not implemented") + } + return ec.directives.Auth(ctx, nil, directive0, requires) } tmp, err := directive1(rctx) @@ -3152,10 +3295,14 @@ func (ec *executionContext) _Mutation_createDishAlias(ctx context.Context, field return ec.resolvers.Mutation().CreateDishAlias(rctx, fc.Args["input"].(models.CreateDishAliasInput)) } directive1 := func(ctx context.Context) (interface{}, error) { - if ec.directives.Authenticated == nil { - return nil, errors.New("directive authenticated is not implemented") + requires, err := ec.unmarshalOUserRole2ᚖgithubᚗcomᚋmensattᚋbackendᚋinternalᚋdatabaseᚋschemaᚐUserRole(ctx, "ADMIN") + if err != nil { + return nil, err } - return ec.directives.Authenticated(ctx, nil, directive0) + if ec.directives.Auth == nil { + return nil, errors.New("directive auth is not implemented") + } + return ec.directives.Auth(ctx, nil, directive0, requires) } tmp, err := directive1(rctx) @@ -3235,10 +3382,14 @@ func (ec *executionContext) _Mutation_deleteDishAlias(ctx context.Context, field return ec.resolvers.Mutation().DeleteDishAlias(rctx, fc.Args["input"].(models.DeleteDishAliasInput)) } directive1 := func(ctx context.Context) (interface{}, error) { - if ec.directives.Authenticated == nil { - return nil, errors.New("directive authenticated is not implemented") + requires, err := ec.unmarshalOUserRole2ᚖgithubᚗcomᚋmensattᚋbackendᚋinternalᚋdatabaseᚋschemaᚐUserRole(ctx, "ADMIN") + if err != nil { + return nil, err } - return ec.directives.Authenticated(ctx, nil, directive0) + if ec.directives.Auth == nil { + return nil, errors.New("directive auth is not implemented") + } + return ec.directives.Auth(ctx, nil, directive0, requires) } tmp, err := directive1(rctx) @@ -3318,10 +3469,14 @@ func (ec *executionContext) _Mutation_createOccurrence(ctx context.Context, fiel return ec.resolvers.Mutation().CreateOccurrence(rctx, fc.Args["input"].(models.CreateOccurrenceInput)) } directive1 := func(ctx context.Context) (interface{}, error) { - if ec.directives.Authenticated == nil { - return nil, errors.New("directive authenticated is not implemented") + requires, err := ec.unmarshalOUserRole2ᚖgithubᚗcomᚋmensattᚋbackendᚋinternalᚋdatabaseᚋschemaᚐUserRole(ctx, "ADMIN") + if err != nil { + return nil, err + } + if ec.directives.Auth == nil { + return nil, errors.New("directive auth is not implemented") } - return ec.directives.Authenticated(ctx, nil, directive0) + return ec.directives.Auth(ctx, nil, directive0, requires) } tmp, err := directive1(rctx) @@ -3435,10 +3590,14 @@ func (ec *executionContext) _Mutation_updateOccurrence(ctx context.Context, fiel return ec.resolvers.Mutation().UpdateOccurrence(rctx, fc.Args["input"].(models.UpdateOccurrenceInput)) } directive1 := func(ctx context.Context) (interface{}, error) { - if ec.directives.Authenticated == nil { - return nil, errors.New("directive authenticated is not implemented") + requires, err := ec.unmarshalOUserRole2ᚖgithubᚗcomᚋmensattᚋbackendᚋinternalᚋdatabaseᚋschemaᚐUserRole(ctx, "ADMIN") + if err != nil { + return nil, err + } + if ec.directives.Auth == nil { + return nil, errors.New("directive auth is not implemented") } - return ec.directives.Authenticated(ctx, nil, directive0) + return ec.directives.Auth(ctx, nil, directive0, requires) } tmp, err := directive1(rctx) @@ -3552,10 +3711,14 @@ func (ec *executionContext) _Mutation_deleteOccurrence(ctx context.Context, fiel return ec.resolvers.Mutation().DeleteOccurrence(rctx, fc.Args["input"].(models.DeleteOccurrenceInput)) } directive1 := func(ctx context.Context) (interface{}, error) { - if ec.directives.Authenticated == nil { - return nil, errors.New("directive authenticated is not implemented") + requires, err := ec.unmarshalOUserRole2ᚖgithubᚗcomᚋmensattᚋbackendᚋinternalᚋdatabaseᚋschemaᚐUserRole(ctx, "ADMIN") + if err != nil { + return nil, err + } + if ec.directives.Auth == nil { + return nil, errors.New("directive auth is not implemented") } - return ec.directives.Authenticated(ctx, nil, directive0) + return ec.directives.Auth(ctx, nil, directive0, requires) } tmp, err := directive1(rctx) @@ -3669,10 +3832,14 @@ func (ec *executionContext) _Mutation_addTagToOccurrence(ctx context.Context, fi return ec.resolvers.Mutation().AddTagToOccurrence(rctx, fc.Args["input"].(models.AddTagToOccurrenceInput)) } directive1 := func(ctx context.Context) (interface{}, error) { - if ec.directives.Authenticated == nil { - return nil, errors.New("directive authenticated is not implemented") + requires, err := ec.unmarshalOUserRole2ᚖgithubᚗcomᚋmensattᚋbackendᚋinternalᚋdatabaseᚋschemaᚐUserRole(ctx, "ADMIN") + if err != nil { + return nil, err } - return ec.directives.Authenticated(ctx, nil, directive0) + if ec.directives.Auth == nil { + return nil, errors.New("directive auth is not implemented") + } + return ec.directives.Auth(ctx, nil, directive0, requires) } tmp, err := directive1(rctx) @@ -3750,10 +3917,14 @@ func (ec *executionContext) _Mutation_removeTagFromOccurrence(ctx context.Contex return ec.resolvers.Mutation().RemoveTagFromOccurrence(rctx, fc.Args["input"].(models.RemoveTagFromOccurrenceInput)) } directive1 := func(ctx context.Context) (interface{}, error) { - if ec.directives.Authenticated == nil { - return nil, errors.New("directive authenticated is not implemented") + requires, err := ec.unmarshalOUserRole2ᚖgithubᚗcomᚋmensattᚋbackendᚋinternalᚋdatabaseᚋschemaᚐUserRole(ctx, "ADMIN") + if err != nil { + return nil, err } - return ec.directives.Authenticated(ctx, nil, directive0) + if ec.directives.Auth == nil { + return nil, errors.New("directive auth is not implemented") + } + return ec.directives.Auth(ctx, nil, directive0, requires) } tmp, err := directive1(rctx) @@ -3831,10 +4002,14 @@ func (ec *executionContext) _Mutation_addSideDishToOccurrence(ctx context.Contex return ec.resolvers.Mutation().AddSideDishToOccurrence(rctx, fc.Args["input"].(models.AddSideDishToOccurrenceInput)) } directive1 := func(ctx context.Context) (interface{}, error) { - if ec.directives.Authenticated == nil { - return nil, errors.New("directive authenticated is not implemented") + requires, err := ec.unmarshalOUserRole2ᚖgithubᚗcomᚋmensattᚋbackendᚋinternalᚋdatabaseᚋschemaᚐUserRole(ctx, "ADMIN") + if err != nil { + return nil, err + } + if ec.directives.Auth == nil { + return nil, errors.New("directive auth is not implemented") } - return ec.directives.Authenticated(ctx, nil, directive0) + return ec.directives.Auth(ctx, nil, directive0, requires) } tmp, err := directive1(rctx) @@ -3912,10 +4087,14 @@ func (ec *executionContext) _Mutation_removeSideDishFromOccurrence(ctx context.C return ec.resolvers.Mutation().RemoveSideDishFromOccurrence(rctx, fc.Args["input"].(models.RemoveSideDishFromOccurrenceInput)) } directive1 := func(ctx context.Context) (interface{}, error) { - if ec.directives.Authenticated == nil { - return nil, errors.New("directive authenticated is not implemented") + requires, err := ec.unmarshalOUserRole2ᚖgithubᚗcomᚋmensattᚋbackendᚋinternalᚋdatabaseᚋschemaᚐUserRole(ctx, "ADMIN") + if err != nil { + return nil, err + } + if ec.directives.Auth == nil { + return nil, errors.New("directive auth is not implemented") } - return ec.directives.Authenticated(ctx, nil, directive0) + return ec.directives.Auth(ctx, nil, directive0, requires) } tmp, err := directive1(rctx) @@ -4068,10 +4247,14 @@ func (ec *executionContext) _Mutation_updateReview(ctx context.Context, field gr return ec.resolvers.Mutation().UpdateReview(rctx, fc.Args["input"].(models.UpdateReviewInput)) } directive1 := func(ctx context.Context) (interface{}, error) { - if ec.directives.Authenticated == nil { - return nil, errors.New("directive authenticated is not implemented") + requires, err := ec.unmarshalOUserRole2ᚖgithubᚗcomᚋmensattᚋbackendᚋinternalᚋdatabaseᚋschemaᚐUserRole(ctx, "MOD") + if err != nil { + return nil, err + } + if ec.directives.Auth == nil { + return nil, errors.New("directive auth is not implemented") } - return ec.directives.Authenticated(ctx, nil, directive0) + return ec.directives.Auth(ctx, nil, directive0, requires) } tmp, err := directive1(rctx) @@ -4163,10 +4346,14 @@ func (ec *executionContext) _Mutation_deleteReview(ctx context.Context, field gr return ec.resolvers.Mutation().DeleteReview(rctx, fc.Args["input"].(models.DeleteReviewInput)) } directive1 := func(ctx context.Context) (interface{}, error) { - if ec.directives.Authenticated == nil { - return nil, errors.New("directive authenticated is not implemented") + requires, err := ec.unmarshalOUserRole2ᚖgithubᚗcomᚋmensattᚋbackendᚋinternalᚋdatabaseᚋschemaᚐUserRole(ctx, "MOD") + if err != nil { + return nil, err + } + if ec.directives.Auth == nil { + return nil, errors.New("directive auth is not implemented") } - return ec.directives.Authenticated(ctx, nil, directive0) + return ec.directives.Auth(ctx, nil, directive0, requires) } tmp, err := directive1(rctx) @@ -5614,6 +5801,10 @@ func (ec *executionContext) fieldContext_Query_currentUser(ctx context.Context, return ec.fieldContext_User_id(ctx, field) case "email": return ec.fieldContext_User_email(ctx, field) + case "username": + return ec.fieldContext_User_username(ctx, field) + case "role": + return ec.fieldContext_User_role(ctx, field) } return nil, fmt.Errorf("no field named %q was found under type User", field.Name) }, @@ -7605,6 +7796,91 @@ func (ec *executionContext) fieldContext_User_email(ctx context.Context, field g return fc, nil } +func (ec *executionContext) _User_username(ctx context.Context, field graphql.CollectedField, obj *ent.User) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_User_username(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.Username, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(string) + fc.Result = res + return ec.marshalNString2string(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_User_username(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "User", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type String does not have child fields") + }, + } + return fc, nil +} + +func (ec *executionContext) _User_role(ctx context.Context, field graphql.CollectedField, obj *ent.User) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_User_role(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.Role, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + return graphql.Null + } + res := resTmp.(*schema.UserRole) + fc.Result = res + return ec.marshalOUserRole2ᚖgithubᚗcomᚋmensattᚋbackendᚋinternalᚋdatabaseᚋschemaᚐUserRole(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_User_role(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "User", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type UserRole does not have child fields") + }, + } + return fc, nil +} + func (ec *executionContext) _VcsBuildInfo_commitTime(ctx context.Context, field graphql.CollectedField, obj *utils.VCSBuildInfo) (ret graphql.Marshaler) { fc, err := ec.fieldContext_VcsBuildInfo_commitTime(ctx, field) if err != nil { @@ -10771,6 +11047,71 @@ func (ec *executionContext) unmarshalInputUpdateReviewInput(ctx context.Context, return it, nil } +func (ec *executionContext) unmarshalInputUpdateUserInput(ctx context.Context, obj interface{}) (models.UpdateUserInput, error) { + var it models.UpdateUserInput + asMap := map[string]interface{}{} + for k, v := range obj.(map[string]interface{}) { + asMap[k] = v + } + + fieldsInOrder := [...]string{"id", "email", "password", "username", "role"} + for _, k := range fieldsInOrder { + v, ok := asMap[k] + if !ok { + continue + } + switch k { + case "id": + var err error + + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("id")) + data, err := ec.unmarshalNUUID2githubᚗcomᚋgoogleᚋuuidᚐUUID(ctx, v) + if err != nil { + return it, err + } + it.ID = data + case "email": + var err error + + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("email")) + data, err := ec.unmarshalOString2ᚖstring(ctx, v) + if err != nil { + return it, err + } + it.Email = data + case "password": + var err error + + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("password")) + data, err := ec.unmarshalOString2ᚖstring(ctx, v) + if err != nil { + return it, err + } + it.Password = data + case "username": + var err error + + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("username")) + data, err := ec.unmarshalOString2ᚖstring(ctx, v) + if err != nil { + return it, err + } + it.Username = data + case "role": + var err error + + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("role")) + data, err := ec.unmarshalOUserRole2ᚖgithubᚗcomᚋmensattᚋbackendᚋinternalᚋdatabaseᚋschemaᚐUserRole(ctx, v) + if err != nil { + return it, err + } + it.Role = data + } + } + + return it, nil +} + // endregion **************************** input.gotpl ***************************** // region ************************** interface.gotpl *************************** @@ -11235,6 +11576,13 @@ func (ec *executionContext) _Mutation(ctx context.Context, sel ast.SelectionSet) if out.Values[i] == graphql.Null { out.Invalids++ } + case "updateUser": + out.Values[i] = ec.OperationContext.RootResolverMiddleware(innerCtx, func(ctx context.Context) (res graphql.Marshaler) { + return ec._Mutation_updateUser(ctx, field) + }) + if out.Values[i] == graphql.Null { + out.Invalids++ + } case "createTag": out.Values[i] = ec.OperationContext.RootResolverMiddleware(innerCtx, func(ctx context.Context) (res graphql.Marshaler) { return ec._Mutation_createTag(ctx, field) @@ -12360,6 +12708,13 @@ func (ec *executionContext) _User(ctx context.Context, sel ast.SelectionSet, obj if out.Values[i] == graphql.Null { out.Invalids++ } + case "username": + out.Values[i] = ec._User_username(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + case "role": + out.Values[i] = ec._User_role(ctx, field, obj) default: panic("unknown field " + strconv.Quote(field.Name)) } @@ -13458,6 +13813,11 @@ func (ec *executionContext) unmarshalNUpdateReviewInput2githubᚗcomᚋmensatt return res, graphql.ErrorOnPath(ctx, err) } +func (ec *executionContext) unmarshalNUpdateUserInput2githubᚗcomᚋmensattᚋbackendᚋinternalᚋgraphqlᚋmodelsᚐUpdateUserInput(ctx context.Context, v interface{}) (models.UpdateUserInput, error) { + res, err := ec.unmarshalInputUpdateUserInput(ctx, v) + return res, graphql.ErrorOnPath(ctx, err) +} + func (ec *executionContext) unmarshalNUpload2githubᚗcomᚋ99designsᚋgqlgenᚋgraphqlᚐUpload(ctx context.Context, v interface{}) (graphql.Upload, error) { res, err := graphql.UnmarshalUpload(v) return res, graphql.ErrorOnPath(ctx, err) @@ -13473,6 +13833,20 @@ func (ec *executionContext) marshalNUpload2githubᚗcomᚋ99designsᚋgqlgenᚋg return res } +func (ec *executionContext) marshalNUser2githubᚗcomᚋmensattᚋbackendᚋinternalᚋdatabaseᚋentᚐUser(ctx context.Context, sel ast.SelectionSet, v ent.User) graphql.Marshaler { + return ec._User(ctx, sel, &v) +} + +func (ec *executionContext) marshalNUser2ᚖgithubᚗcomᚋmensattᚋbackendᚋinternalᚋdatabaseᚋentᚐUser(ctx context.Context, sel ast.SelectionSet, v *ent.User) graphql.Marshaler { + if v == nil { + if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { + ec.Errorf(ctx, "the requested element is null which the schema does not allow") + } + return graphql.Null + } + return ec._User(ctx, sel, v) +} + func (ec *executionContext) marshalN__Directive2githubᚗcomᚋ99designsᚋgqlgenᚋgraphqlᚋintrospectionᚐDirective(ctx context.Context, sel ast.SelectionSet, v introspection.Directive) graphql.Marshaler { return ec.___Directive(ctx, sel, &v) } @@ -14045,6 +14419,23 @@ func (ec *executionContext) marshalOUser2ᚖgithubᚗcomᚋmensattᚋbackendᚋi return ec._User(ctx, sel, v) } +func (ec *executionContext) unmarshalOUserRole2ᚖgithubᚗcomᚋmensattᚋbackendᚋinternalᚋdatabaseᚋschemaᚐUserRole(ctx context.Context, v interface{}) (*schema.UserRole, error) { + if v == nil { + return nil, nil + } + tmp, err := graphql.UnmarshalString(v) + res := schema.UserRole(tmp) + return &res, graphql.ErrorOnPath(ctx, err) +} + +func (ec *executionContext) marshalOUserRole2ᚖgithubᚗcomᚋmensattᚋbackendᚋinternalᚋdatabaseᚋschemaᚐUserRole(ctx context.Context, sel ast.SelectionSet, v *schema.UserRole) graphql.Marshaler { + if v == nil { + return graphql.Null + } + res := graphql.MarshalString(string(*v)) + return res +} + func (ec *executionContext) marshalOVcsBuildInfo2ᚖgithubᚗcomᚋmensattᚋbackendᚋpkgᚋutilsᚐVCSBuildInfo(ctx context.Context, sel ast.SelectionSet, v *utils.VCSBuildInfo) graphql.Marshaler { if v == nil { return graphql.Null diff --git a/internal/graphql/graphql.go b/internal/graphql/graphql.go index 611485d..a0a74cf 100644 --- a/internal/graphql/graphql.go +++ b/internal/graphql/graphql.go @@ -59,6 +59,7 @@ func graphqlHandler(params *GraphQLParams) gin.HandlerFunc { }, Directives: gqlserver.DirectiveRoot{ Authenticated: directives.Authenticated, + Auth: directives.Auth, }, }, ), diff --git a/internal/graphql/models/generated.go b/internal/graphql/models/generated.go index 9fc927d..a38cc4a 100644 --- a/internal/graphql/models/generated.go +++ b/internal/graphql/models/generated.go @@ -200,3 +200,11 @@ type UpdateReviewInput struct { Text *string `json:"text,omitempty"` Approved *bool `json:"approved,omitempty"` } + +type UpdateUserInput struct { + ID uuid.UUID `json:"id"` + Email *string `json:"email,omitempty"` + Password *string `json:"password,omitempty"` + Username *string `json:"username,omitempty"` + Role *schema.UserRole `json:"role,omitempty"` +} diff --git a/internal/graphql/resolvers/helper.go b/internal/graphql/resolvers/helper.go index 3c36d58..8f663f5 100644 --- a/internal/graphql/resolvers/helper.go +++ b/internal/graphql/resolvers/helper.go @@ -3,11 +3,13 @@ package resolvers import ( "context" "fmt" + "github.com/google/uuid" ent "github.com/mensatt/backend/internal/database/ent" "github.com/mensatt/backend/internal/database/ent/dish" "github.com/mensatt/backend/internal/database/ent/location" "github.com/mensatt/backend/internal/database/ent/occurrence" "github.com/mensatt/backend/internal/database/ent/review" + "github.com/mensatt/backend/internal/database/schema" "github.com/mensatt/backend/internal/graphql/models" "io" ) @@ -146,3 +148,28 @@ func (r *queryResolver) filteredLocations(ctx context.Context, filter *models.Lo return queryBuilder.All(ctx) } + +func userHimself(authenticatedUser *ent.User, targetUserID uuid.UUID) bool { + if authenticatedUser.ID == targetUserID { + return true + } + return false +} + +func userHimselfOrRoles(authenticatedUser *ent.User, targetUserID uuid.UUID, roles []schema.UserRole) bool { + if userHimself(authenticatedUser, targetUserID) { + return true + } + + if authenticatedUser.Role == nil { + return false + } + + for _, role := range roles { + if *authenticatedUser.Role == role { + return true + } + } + + return false +} diff --git a/internal/graphql/resolvers/mutations.resolvers.go b/internal/graphql/resolvers/mutations.resolvers.go index bb31693..d80306d 100644 --- a/internal/graphql/resolvers/mutations.resolvers.go +++ b/internal/graphql/resolvers/mutations.resolvers.go @@ -13,8 +13,10 @@ import ( "github.com/mensatt/backend/internal/database/ent" "github.com/mensatt/backend/internal/database/ent/tag" "github.com/mensatt/backend/internal/database/ent/user" + "github.com/mensatt/backend/internal/database/schema" "github.com/mensatt/backend/internal/graphql/gqlserver" "github.com/mensatt/backend/internal/graphql/models" + "github.com/mensatt/backend/internal/middleware" "github.com/mensatt/backend/pkg/utils" ) @@ -32,6 +34,65 @@ func (r *mutationResolver) LoginUser(ctx context.Context, input models.LoginUser return r.JWTKeyStore.GenerateJWT(user.ID) } +// UpdateUser is the resolver for the updateUser field. +func (r *mutationResolver) UpdateUser(ctx context.Context, input models.UpdateUserInput) (*ent.User, error) { + authenticatedUser := middleware.GetUserIDFromCtx(ctx) + if authenticatedUser == nil { + return nil, fmt.Errorf("not authenticated") + } + + queryBuilder := r.Database.User.UpdateOneID(input.ID) + + if input.Email != nil { + if !userHimselfOrRoles(authenticatedUser, input.ID, []schema.UserRole{schema.Admin}) { + return nil, fmt.Errorf("not authorized") + } + + // check if email is already taken + _, err := r.Database.User.Query().Where(user.Email(*input.Email)).Only(ctx) + if err == nil { + return nil, fmt.Errorf("email already taken") + } + + queryBuilder = queryBuilder.SetEmail(*input.Email) + } + + if input.Password != nil { + if !userHimselfOrRoles(authenticatedUser, input.ID, []schema.UserRole{schema.Admin}) { + return nil, fmt.Errorf("not authorized") + } + + passwordHash, err := utils.HashPassword(*input.Password) + if err != nil { + return nil, err + } + queryBuilder = queryBuilder.SetPasswordHash(passwordHash) + } + + if input.Username != nil { + if !userHimselfOrRoles(authenticatedUser, input.ID, []schema.UserRole{schema.Mod, schema.Admin}) { + return nil, fmt.Errorf("not authorized") + } + + // check if username is already taken + _, err := r.Database.User.Query().Where(user.Username(*input.Username)).Only(ctx) + if err == nil { + return nil, fmt.Errorf("username already taken") + } + + queryBuilder = queryBuilder.SetUsername(*input.Username) + } + + if input.Role != nil { + if !userHimselfOrRoles(authenticatedUser, input.ID, []schema.UserRole{schema.Admin}) { + return nil, fmt.Errorf("not authorized") + } + queryBuilder = queryBuilder.SetRole(*input.Role) + } + + return queryBuilder.Save(ctx) +} + // CreateTag is the resolver for the createTag field. func (r *mutationResolver) CreateTag(ctx context.Context, input models.CreateTagInput) (*ent.Tag, error) { queryBuilder := r.Database.Tag.Create(). diff --git a/internal/graphql/schema/directives.graphql b/internal/graphql/schema/directives.graphql index df72191..117debd 100644 --- a/internal/graphql/schema/directives.graphql +++ b/internal/graphql/schema/directives.graphql @@ -1,16 +1,3 @@ -directive @goModel( - model: String - models: [String!] -) on OBJECT | INPUT_OBJECT | SCALAR | ENUM | INTERFACE | UNION +directive @auth(requires: UserRole = ADMIN) on OBJECT | FIELD_DEFINITION -directive @goField( - forceResolver: Boolean - name: String -) on INPUT_FIELD_DEFINITION | FIELD_DEFINITION - -directive @goTag( - key: String! - value: String -) on INPUT_FIELD_DEFINITION | FIELD_DEFINITION - -directive @authenticated on FIELD_DEFINITION \ No newline at end of file +directive @authenticated on FIELD_DEFINITION diff --git a/internal/graphql/schema/inputs.graphql b/internal/graphql/schema/inputs.graphql index 3e6a76d..4b3a3f7 100644 --- a/internal/graphql/schema/inputs.graphql +++ b/internal/graphql/schema/inputs.graphql @@ -5,6 +5,14 @@ input LoginUserInput { password: String! } +input UpdateUserInput { + id: UUID! + email: String + password: String + username: String + role: UserRole +} + # Tag diff --git a/internal/graphql/schema/mutations.graphql b/internal/graphql/schema/mutations.graphql index 646ff8c..f60c3ac 100644 --- a/internal/graphql/schema/mutations.graphql +++ b/internal/graphql/schema/mutations.graphql @@ -1,33 +1,34 @@ type Mutation { # User loginUser(input: LoginUserInput!): String! + updateUser(input: UpdateUserInput!): User! # Tag - createTag(input: CreateTagInput!): Tag! @authenticated + createTag(input: CreateTagInput!): Tag! @auth(requires: ADMIN) # Dish - createDish(input: CreateDishInput!): Dish! @authenticated - updateDish(input: UpdateDishInput!): Dish! @authenticated + createDish(input: CreateDishInput!): Dish! @auth(requires: ADMIN) + updateDish(input: UpdateDishInput!): Dish! @auth(requires: ADMIN) # mergeDishes(input: MergeDishesInput!): Dish! @authenticated # DishAlias - createDishAlias(input: CreateDishAliasInput!): DishAlias! @authenticated - deleteDishAlias(input: DeleteDishAliasInput!): DishAlias! @authenticated + createDishAlias(input: CreateDishAliasInput!): DishAlias! @auth(requires: ADMIN) + deleteDishAlias(input: DeleteDishAliasInput!): DishAlias! @auth(requires: ADMIN) # Occurrence - createOccurrence(input: CreateOccurrenceInput!): Occurrence! @authenticated - updateOccurrence(input: UpdateOccurrenceInput!): Occurrence! @authenticated - deleteOccurrence(input: DeleteOccurrenceInput!): Occurrence! @authenticated + createOccurrence(input: CreateOccurrenceInput!): Occurrence! @auth(requires: ADMIN) + updateOccurrence(input: UpdateOccurrenceInput!): Occurrence! @auth(requires: ADMIN) + deleteOccurrence(input: DeleteOccurrenceInput!): Occurrence! @auth(requires: ADMIN) - addTagToOccurrence(input: AddTagToOccurrenceInput!): OccurrenceTag! @authenticated - removeTagFromOccurrence(input: RemoveTagFromOccurrenceInput!): OccurrenceTag! @authenticated - addSideDishToOccurrence(input: AddSideDishToOccurrenceInput!): OccurrenceSideDish! @authenticated - removeSideDishFromOccurrence(input: RemoveSideDishFromOccurrenceInput!): OccurrenceSideDish! @authenticated + addTagToOccurrence(input: AddTagToOccurrenceInput!): OccurrenceTag! @auth(requires: ADMIN) + removeTagFromOccurrence(input: RemoveTagFromOccurrenceInput!): OccurrenceTag! @auth(requires: ADMIN) + addSideDishToOccurrence(input: AddSideDishToOccurrenceInput!): OccurrenceSideDish! @auth(requires: ADMIN) + removeSideDishFromOccurrence(input: RemoveSideDishFromOccurrenceInput!): OccurrenceSideDish! @auth(requires: ADMIN) # Review createReview(input: CreateReviewInput!): Review! - updateReview(input: UpdateReviewInput!): Review! @authenticated - deleteReview(input: DeleteReviewInput!): Review! @authenticated + updateReview(input: UpdateReviewInput!): Review! @auth(requires: MOD) + deleteReview(input: DeleteReviewInput!): Review! @auth(requires: MOD) # Image addImagesToReview(input: AddImagesToReviewInput!): Review! diff --git a/internal/graphql/schema/types.graphql b/internal/graphql/schema/types.graphql index 9c35c6d..a6b23b9 100644 --- a/internal/graphql/schema/types.graphql +++ b/internal/graphql/schema/types.graphql @@ -109,9 +109,16 @@ type Image { imageUrl: String! } +enum UserRole { + ADMIN + MOD +} + type User { id: UUID! email: String! + username: String! + role: UserRole } type VcsBuildInfo {