Implement create user

Signed-off-by: Émile Ré <emile@getprobo.com>
This commit is contained in:
Émile Ré
2026-02-16 12:15:50 +04:00
parent 7794563e94
commit 196738f876
7 changed files with 484 additions and 11 deletions

View File

@@ -22,6 +22,7 @@ import (
"time" "time"
"github.com/jackc/pgx/v5" "github.com/jackc/pgx/v5"
"github.com/jackc/pgx/v5/pgconn"
"go.gearno.de/kit/pg" "go.gearno.de/kit/pg"
"go.probo.inc/probo/pkg/gid" "go.probo.inc/probo/pkg/gid"
"go.probo.inc/probo/pkg/mail" "go.probo.inc/probo/pkg/mail"
@@ -904,6 +905,11 @@ VALUES (
_, err := conn.Exec(ctx, q, args) _, err := conn.Exec(ctx, q, args)
if err != nil { if err != nil {
var pgErr *pgconn.PgError
if errors.As(err, &pgErr) && pgErr.Code == "23505" {
return ErrResourceAlreadyExists
}
return fmt.Errorf("cannot insert profile: %w", err) return fmt.Errorf("cannot insert profile: %w", err)
} }

View File

@@ -54,6 +54,7 @@ const (
// Membership Profile actions // Membership Profile actions
ActionMembershipProfileGet = "iam:membership-profile:get" ActionMembershipProfileGet = "iam:membership-profile:get"
ActionMembershipProfileList = "iam:membership-profile:list" ActionMembershipProfileList = "iam:membership-profile:list"
ActionMembershipProfileCreate = "iam:membership-profile:create"
ActionMembershipProfileUpdate = "iam:membership-profile:update" ActionMembershipProfileUpdate = "iam:membership-profile:update"
// Personal API Key actions // Personal API Key actions

View File

@@ -98,12 +98,24 @@ type (
Role coredata.MembershipRole Role coredata.MembershipRole
} }
CreateUserRequest struct {
OrganizationID gid.GID
EmailAddress mail.Addr
Role coredata.MembershipRole
FullName string
AdditionalEmailAddresses mail.Addrs
Kind coredata.MembershipProfileKind
Position *string
ContractStartDate **time.Time
ContractEndDate **time.Time
}
UpdateProfileRequest struct { UpdateProfileRequest struct {
ID gid.GID ID gid.GID
FullName string FullName string
AdditionalEmailAddresses mail.Addrs AdditionalEmailAddresses mail.Addrs
Kind coredata.MembershipProfileKind Kind coredata.MembershipProfileKind
Position **string Position *string
ContractStartDate **time.Time ContractStartDate **time.Time
ContractEndDate **time.Time ContractEndDate **time.Time
} }
@@ -192,6 +204,22 @@ func (req UpdateOrganizationRequest) Validate() error {
return v.Error() return v.Error()
} }
func (cur *CreateUserRequest) Validate() error {
v := validator.New()
v.Check(cur.OrganizationID, "id", validator.Required(), validator.GID(coredata.OrganizationEntityType))
v.Check(cur.Kind, "kind", validator.OneOfSlice(coredata.MembershipProfileKinds()))
v.Check(cur.FullName, "full_name", validator.SafeTextNoNewLine(NameMaxLength))
v.CheckEach(cur.AdditionalEmailAddresses, "additional_email_addresses", func(index int, item any) {
v.Check(item, fmt.Sprintf("additional_email_addresses[%d]", index), validator.Required(), validator.NotEmpty())
})
v.Check(cur.Position, "position", validator.SafeText(TitleMaxLength))
v.Check(cur.ContractStartDate, "contract_start_date", validator.Before(cur.ContractEndDate))
v.Check(cur.ContractEndDate, "contract_end_date", validator.After(cur.ContractStartDate))
return v.Error()
}
func (upr *UpdateProfileRequest) Validate() error { func (upr *UpdateProfileRequest) Validate() error {
v := validator.New() v := validator.New()
@@ -922,6 +950,90 @@ func (s *OrganizationService) DeleteOrganization(ctx context.Context, organizati
) )
} }
func (s *OrganizationService) CreateUser(ctx context.Context, req *CreateUserRequest) (*coredata.MembershipProfile, error) {
if err := req.Validate(); err != nil {
return nil, fmt.Errorf("invalid request: %w", err)
}
var (
scope = coredata.NewScopeFromObjectID(req.OrganizationID)
profile *coredata.MembershipProfile
now = time.Now()
)
err := s.pg.WithConn(
ctx,
func(conn pg.Conn) error {
identity := &coredata.Identity{}
if err := identity.LoadByEmail(ctx, conn, req.EmailAddress); err != nil {
if !errors.Is(err, coredata.ErrResourceNotFound) {
return fmt.Errorf("cannot load identity: %w", err)
}
identity = &coredata.Identity{
ID: gid.New(gid.NilTenant, coredata.IdentityEntityType),
EmailAddress: req.EmailAddress,
FullName: req.FullName,
CreatedAt: now,
UpdatedAt: now,
}
if err := identity.Insert(ctx, conn); err != nil {
return fmt.Errorf("cannot insert identity: %w", err)
}
}
profile = &coredata.MembershipProfile{
ID: gid.New(req.OrganizationID.TenantID(), coredata.MembershipProfileEntityType),
FullName: req.FullName,
Kind: req.Kind,
AdditionalEmailAddresses: req.AdditionalEmailAddresses,
Position: req.Position,
// User is created inactive
State: coredata.ProfileStateInactive,
CreatedAt: now,
UpdatedAt: now,
}
if req.ContractStartDate != nil {
profile.ContractStartDate = *req.ContractStartDate
}
if req.ContractEndDate != nil {
profile.ContractEndDate = *req.ContractEndDate
}
if err := profile.Insert(ctx, conn); err != nil {
if errors.Is(err, coredata.ErrResourceAlreadyExists) {
return NewUserAlreadyExistsError(identity.ID, req.OrganizationID)
}
return fmt.Errorf("cannot insert profile: %w", err)
}
membership := &coredata.Membership{
ID: gid.New(req.OrganizationID.TenantID(), coredata.MembershipEntityType),
IdentityID: identity.ID,
OrganizationID: req.OrganizationID,
Role: req.Role,
CreatedAt: now,
UpdatedAt: now,
}
if err := membership.Insert(ctx, conn, scope); err != nil {
return fmt.Errorf("cannot insert membership: %w", err)
}
return nil
},
)
if err != nil {
return nil, err
}
return profile, nil
}
func (s *OrganizationService) UpdateProfile(ctx context.Context, req *UpdateProfileRequest) (*coredata.MembershipProfile, error) { func (s *OrganizationService) UpdateProfile(ctx context.Context, req *UpdateProfileRequest) (*coredata.MembershipProfile, error) {
if err := req.Validate(); err != nil { if err := req.Validate(); err != nil {
return nil, fmt.Errorf("invalid request: %w", err) return nil, fmt.Errorf("invalid request: %w", err)
@@ -941,12 +1053,8 @@ func (s *OrganizationService) UpdateProfile(ctx context.Context, req *UpdateProf
profile.FullName = req.FullName profile.FullName = req.FullName
profile.Kind = req.Kind profile.Kind = req.Kind
profile.AdditionalEmailAddresses = req.AdditionalEmailAddresses profile.AdditionalEmailAddresses = req.AdditionalEmailAddresses
profile.Position = req.Position
if req.Position != nil {
profile.Position = *req.Position
}
if req.ContractStartDate != nil { if req.ContractStartDate != nil {
profile.ContractStartDate = *req.ContractStartDate profile.ContractStartDate = *req.ContractStartDate

View File

@@ -94,6 +94,8 @@ type Mutation {
input: DeleteOrganizationHorizontalLogoInput! input: DeleteOrganizationHorizontalLogoInput!
): DeleteOrganizationHorizontalLogoPayload @session(required: PRESENT) ): DeleteOrganizationHorizontalLogoPayload @session(required: PRESENT)
createUser(input: CreateUserInput!): CreateUserPayload
@session(required: PRESENT)
inviteMember(input: InviteMemberInput!): InviteMemberPayload inviteMember(input: InviteMemberInput!): InviteMemberPayload
@session(required: PRESENT) @session(required: PRESENT)
deleteInvitation(input: DeleteInvitationInput!): DeleteInvitationPayload deleteInvitation(input: DeleteInvitationInput!): DeleteInvitationPayload
@@ -727,6 +729,18 @@ input DeleteOrganizationHorizontalLogoInput {
organizationId: ID! organizationId: ID!
} }
input CreateUserInput {
organizationId: ID!
fullName: String!
emailAddress: EmailAddr!
role: MembershipRole!
additionalEmailAddresses: [EmailAddr!]
kind: ProfileKind!
position: String
contractStartDate: Datetime @goField(omittable: true)
contractEndDate: Datetime @goField(omittable: true)
}
input InviteMemberInput { input InviteMemberInput {
organizationId: ID! organizationId: ID!
email: EmailAddr! email: EmailAddr!
@@ -739,7 +753,7 @@ input UpdateProfileInput {
fullName: String! fullName: String!
additionalEmailAddresses: [EmailAddr!] additionalEmailAddresses: [EmailAddr!]
kind: ProfileKind! kind: ProfileKind!
position: String @goField(omittable: true) position: String
contractStartDate: Datetime @goField(omittable: true) contractStartDate: Datetime @goField(omittable: true)
contractEndDate: Datetime @goField(omittable: true) contractEndDate: Datetime @goField(omittable: true)
} }
@@ -890,6 +904,10 @@ type DeleteOrganizationHorizontalLogoPayload {
organization: Organization! organization: Organization!
} }
type CreateUserPayload {
profileEdge: ProfileEdge!
}
type InviteMemberPayload { type InviteMemberPayload {
invitationEdge: InvitationEdge! invitationEdge: InvitationEdge!
} }

View File

@@ -119,6 +119,10 @@ type ComplexityRoot struct {
Token func(childComplexity int) int Token func(childComplexity int) int
} }
CreateUserPayload struct {
ProfileEdge func(childComplexity int) int
}
DeleteInvitationPayload struct { DeleteInvitationPayload struct {
DeletedInvitationID func(childComplexity int) int DeletedInvitationID func(childComplexity int) int
} }
@@ -203,6 +207,7 @@ type ComplexityRoot struct {
CreatePersonalAPIKey func(childComplexity int, input types.CreatePersonalAPIKeyInput) int CreatePersonalAPIKey func(childComplexity int, input types.CreatePersonalAPIKeyInput) int
CreateSAMLConfiguration func(childComplexity int, input types.CreateSAMLConfigurationInput) int CreateSAMLConfiguration func(childComplexity int, input types.CreateSAMLConfigurationInput) int
CreateSCIMConfiguration func(childComplexity int, input types.CreateSCIMConfigurationInput) int CreateSCIMConfiguration func(childComplexity int, input types.CreateSCIMConfigurationInput) int
CreateUser func(childComplexity int, input types.CreateUserInput) int
DeleteInvitation func(childComplexity int, input types.DeleteInvitationInput) int DeleteInvitation func(childComplexity int, input types.DeleteInvitationInput) int
DeleteOrganization func(childComplexity int, input types.DeleteOrganizationInput) int DeleteOrganization func(childComplexity int, input types.DeleteOrganizationInput) int
DeleteOrganizationHorizontalLogo func(childComplexity int, input types.DeleteOrganizationHorizontalLogoInput) int DeleteOrganizationHorizontalLogo func(childComplexity int, input types.DeleteOrganizationHorizontalLogoInput) int
@@ -536,6 +541,7 @@ type MutationResolver interface {
UpdateOrganization(ctx context.Context, input types.UpdateOrganizationInput) (*types.UpdateOrganizationPayload, error) UpdateOrganization(ctx context.Context, input types.UpdateOrganizationInput) (*types.UpdateOrganizationPayload, error)
DeleteOrganization(ctx context.Context, input types.DeleteOrganizationInput) (*types.DeleteOrganizationPayload, error) DeleteOrganization(ctx context.Context, input types.DeleteOrganizationInput) (*types.DeleteOrganizationPayload, error)
DeleteOrganizationHorizontalLogo(ctx context.Context, input types.DeleteOrganizationHorizontalLogoInput) (*types.DeleteOrganizationHorizontalLogoPayload, error) DeleteOrganizationHorizontalLogo(ctx context.Context, input types.DeleteOrganizationHorizontalLogoInput) (*types.DeleteOrganizationHorizontalLogoPayload, error)
CreateUser(ctx context.Context, input types.CreateUserInput) (*types.CreateUserPayload, error)
InviteMember(ctx context.Context, input types.InviteMemberInput) (*types.InviteMemberPayload, error) InviteMember(ctx context.Context, input types.InviteMemberInput) (*types.InviteMemberPayload, error)
DeleteInvitation(ctx context.Context, input types.DeleteInvitationInput) (*types.DeleteInvitationPayload, error) DeleteInvitation(ctx context.Context, input types.DeleteInvitationInput) (*types.DeleteInvitationPayload, error)
UpdateProfile(ctx context.Context, input types.UpdateProfileInput) (*types.UpdateProfilePayload, error) UpdateProfile(ctx context.Context, input types.UpdateProfileInput) (*types.UpdateProfilePayload, error)
@@ -760,6 +766,13 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin
return e.complexity.CreateSCIMConfigurationPayload.Token(childComplexity), true return e.complexity.CreateSCIMConfigurationPayload.Token(childComplexity), true
case "CreateUserPayload.profileEdge":
if e.complexity.CreateUserPayload.ProfileEdge == nil {
break
}
return e.complexity.CreateUserPayload.ProfileEdge(childComplexity), true
case "DeleteInvitationPayload.deletedInvitationId": case "DeleteInvitationPayload.deletedInvitationId":
if e.complexity.DeleteInvitationPayload.DeletedInvitationID == nil { if e.complexity.DeleteInvitationPayload.DeletedInvitationID == nil {
break break
@@ -1129,6 +1142,17 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin
} }
return e.complexity.Mutation.CreateSCIMConfiguration(childComplexity, args["input"].(types.CreateSCIMConfigurationInput)), true return e.complexity.Mutation.CreateSCIMConfiguration(childComplexity, args["input"].(types.CreateSCIMConfigurationInput)), true
case "Mutation.createUser":
if e.complexity.Mutation.CreateUser == nil {
break
}
args, err := ec.field_Mutation_createUser_args(ctx, rawArgs)
if err != nil {
return 0, false
}
return e.complexity.Mutation.CreateUser(childComplexity, args["input"].(types.CreateUserInput)), true
case "Mutation.deleteInvitation": case "Mutation.deleteInvitation":
if e.complexity.Mutation.DeleteInvitation == nil { if e.complexity.Mutation.DeleteInvitation == nil {
break break
@@ -2375,6 +2399,7 @@ func (e *executableSchema) Exec(ctx context.Context) graphql.ResponseHandler {
ec.unmarshalInputCreatePersonalAPIKeyInput, ec.unmarshalInputCreatePersonalAPIKeyInput,
ec.unmarshalInputCreateSAMLConfigurationInput, ec.unmarshalInputCreateSAMLConfigurationInput,
ec.unmarshalInputCreateSCIMConfigurationInput, ec.unmarshalInputCreateSCIMConfigurationInput,
ec.unmarshalInputCreateUserInput,
ec.unmarshalInputDeleteInvitationInput, ec.unmarshalInputDeleteInvitationInput,
ec.unmarshalInputDeleteOrganizationHorizontalLogoInput, ec.unmarshalInputDeleteOrganizationHorizontalLogoInput,
ec.unmarshalInputDeleteOrganizationInput, ec.unmarshalInputDeleteOrganizationInput,
@@ -2594,6 +2619,8 @@ type Mutation {
input: DeleteOrganizationHorizontalLogoInput! input: DeleteOrganizationHorizontalLogoInput!
): DeleteOrganizationHorizontalLogoPayload @session(required: PRESENT) ): DeleteOrganizationHorizontalLogoPayload @session(required: PRESENT)
createUser(input: CreateUserInput!): CreateUserPayload
@session(required: PRESENT)
inviteMember(input: InviteMemberInput!): InviteMemberPayload inviteMember(input: InviteMemberInput!): InviteMemberPayload
@session(required: PRESENT) @session(required: PRESENT)
deleteInvitation(input: DeleteInvitationInput!): DeleteInvitationPayload deleteInvitation(input: DeleteInvitationInput!): DeleteInvitationPayload
@@ -3227,6 +3254,18 @@ input DeleteOrganizationHorizontalLogoInput {
organizationId: ID! organizationId: ID!
} }
input CreateUserInput {
organizationId: ID!
fullName: String!
emailAddress: EmailAddr!
role: MembershipRole!
additionalEmailAddresses: [EmailAddr!]
kind: ProfileKind!
position: String
contractStartDate: Datetime @goField(omittable: true)
contractEndDate: Datetime @goField(omittable: true)
}
input InviteMemberInput { input InviteMemberInput {
organizationId: ID! organizationId: ID!
email: EmailAddr! email: EmailAddr!
@@ -3239,7 +3278,7 @@ input UpdateProfileInput {
fullName: String! fullName: String!
additionalEmailAddresses: [EmailAddr!] additionalEmailAddresses: [EmailAddr!]
kind: ProfileKind! kind: ProfileKind!
position: String @goField(omittable: true) position: String
contractStartDate: Datetime @goField(omittable: true) contractStartDate: Datetime @goField(omittable: true)
contractEndDate: Datetime @goField(omittable: true) contractEndDate: Datetime @goField(omittable: true)
} }
@@ -3390,6 +3429,10 @@ type DeleteOrganizationHorizontalLogoPayload {
organization: Organization! organization: Organization!
} }
type CreateUserPayload {
profileEdge: ProfileEdge!
}
type InviteMemberPayload { type InviteMemberPayload {
invitationEdge: InvitationEdge! invitationEdge: InvitationEdge!
} }
@@ -3775,6 +3818,17 @@ func (ec *executionContext) field_Mutation_createSCIMConfiguration_args(ctx cont
return args, nil return args, nil
} }
func (ec *executionContext) field_Mutation_createUser_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) {
var err error
args := map[string]any{}
arg0, err := graphql.ProcessArgField(ctx, rawArgs, "input", ec.unmarshalNCreateUserInput2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconnectᚋv1ᚋtypesᚐCreateUserInput)
if err != nil {
return nil, err
}
args["input"] = arg0
return args, nil
}
func (ec *executionContext) field_Mutation_deleteInvitation_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) { func (ec *executionContext) field_Mutation_deleteInvitation_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) {
var err error var err error
args := map[string]any{} args := map[string]any{}
@@ -4985,6 +5039,41 @@ func (ec *executionContext) fieldContext_CreateSCIMConfigurationPayload_token(_
return fc, nil return fc, nil
} }
func (ec *executionContext) _CreateUserPayload_profileEdge(ctx context.Context, field graphql.CollectedField, obj *types.CreateUserPayload) (ret graphql.Marshaler) {
return graphql.ResolveField(
ctx,
ec.OperationContext,
field,
ec.fieldContext_CreateUserPayload_profileEdge,
func(ctx context.Context) (any, error) {
return obj.ProfileEdge, nil
},
nil,
ec.marshalNProfileEdge2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconnectᚋv1ᚋtypesᚐProfileEdge,
true,
true,
)
}
func (ec *executionContext) fieldContext_CreateUserPayload_profileEdge(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
fc = &graphql.FieldContext{
Object: "CreateUserPayload",
Field: field,
IsMethod: false,
IsResolver: false,
Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
switch field.Name {
case "cursor":
return ec.fieldContext_ProfileEdge_cursor(ctx, field)
case "node":
return ec.fieldContext_ProfileEdge_node(ctx, field)
}
return nil, fmt.Errorf("no field named %q was found under type ProfileEdge", field.Name)
},
}
return fc, nil
}
func (ec *executionContext) _DeleteInvitationPayload_deletedInvitationId(ctx context.Context, field graphql.CollectedField, obj *types.DeleteInvitationPayload) (ret graphql.Marshaler) { func (ec *executionContext) _DeleteInvitationPayload_deletedInvitationId(ctx context.Context, field graphql.CollectedField, obj *types.DeleteInvitationPayload) (ret graphql.Marshaler) {
return graphql.ResolveField( return graphql.ResolveField(
ctx, ctx,
@@ -7550,6 +7639,69 @@ func (ec *executionContext) fieldContext_Mutation_deleteOrganizationHorizontalLo
return fc, nil return fc, nil
} }
func (ec *executionContext) _Mutation_createUser(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
return graphql.ResolveField(
ctx,
ec.OperationContext,
field,
ec.fieldContext_Mutation_createUser,
func(ctx context.Context) (any, error) {
fc := graphql.GetFieldContext(ctx)
return ec.resolvers.Mutation().CreateUser(ctx, fc.Args["input"].(types.CreateUserInput))
},
func(ctx context.Context, next graphql.Resolver) graphql.Resolver {
directive0 := next
directive1 := func(ctx context.Context) (any, error) {
required, err := ec.unmarshalNSessionRequirement2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋgqlutilsᚋdirectivesᚋsessionᚐSessionRequirement(ctx, "PRESENT")
if err != nil {
var zeroVal *types.CreateUserPayload
return zeroVal, err
}
if ec.directives.Session == nil {
var zeroVal *types.CreateUserPayload
return zeroVal, errors.New("directive session is not implemented")
}
return ec.directives.Session(ctx, nil, directive0, required)
}
next = directive1
return next
},
ec.marshalOCreateUserPayload2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconnectᚋv1ᚋtypesᚐCreateUserPayload,
true,
false,
)
}
func (ec *executionContext) fieldContext_Mutation_createUser(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 "profileEdge":
return ec.fieldContext_CreateUserPayload_profileEdge(ctx, field)
}
return nil, fmt.Errorf("no field named %q was found under type CreateUserPayload", 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_createUser_args(ctx, field.ArgumentMap(ec.Variables)); err != nil {
ec.Error(ctx, err)
return fc, err
}
return fc, nil
}
func (ec *executionContext) _Mutation_inviteMember(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { func (ec *executionContext) _Mutation_inviteMember(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
return graphql.ResolveField( return graphql.ResolveField(
ctx, ctx,
@@ -15572,6 +15724,89 @@ func (ec *executionContext) unmarshalInputCreateSCIMConfigurationInput(ctx conte
return it, nil return it, nil
} }
func (ec *executionContext) unmarshalInputCreateUserInput(ctx context.Context, obj any) (types.CreateUserInput, error) {
var it types.CreateUserInput
asMap := map[string]any{}
for k, v := range obj.(map[string]any) {
asMap[k] = v
}
fieldsInOrder := [...]string{"organizationId", "fullName", "emailAddress", "role", "additionalEmailAddresses", "kind", "position", "contractStartDate", "contractEndDate"}
for _, k := range fieldsInOrder {
v, ok := asMap[k]
if !ok {
continue
}
switch k {
case "organizationId":
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("organizationId"))
data, err := ec.unmarshalNID2goᚗproboᚗincᚋproboᚋpkgᚋgidᚐGID(ctx, v)
if err != nil {
return it, err
}
it.OrganizationID = data
case "fullName":
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("fullName"))
data, err := ec.unmarshalNString2string(ctx, v)
if err != nil {
return it, err
}
it.FullName = data
case "emailAddress":
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("emailAddress"))
data, err := ec.unmarshalNEmailAddr2goᚗproboᚗincᚋproboᚋpkgᚋmailᚐAddr(ctx, v)
if err != nil {
return it, err
}
it.EmailAddress = data
case "role":
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("role"))
data, err := ec.unmarshalNMembershipRole2goᚗproboᚗincᚋproboᚋpkgᚋcoredataᚐMembershipRole(ctx, v)
if err != nil {
return it, err
}
it.Role = data
case "additionalEmailAddresses":
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("additionalEmailAddresses"))
data, err := ec.unmarshalOEmailAddr2ᚕgoᚗproboᚗincᚋproboᚋpkgᚋmailᚐAddrᚄ(ctx, v)
if err != nil {
return it, err
}
it.AdditionalEmailAddresses = data
case "kind":
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("kind"))
data, err := ec.unmarshalNProfileKind2goᚗproboᚗincᚋproboᚋpkgᚋcoredataᚐMembershipProfileKind(ctx, v)
if err != nil {
return it, err
}
it.Kind = data
case "position":
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("position"))
data, err := ec.unmarshalOString2ᚖstring(ctx, v)
if err != nil {
return it, err
}
it.Position = data
case "contractStartDate":
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("contractStartDate"))
data, err := ec.unmarshalODatetime2ᚖtimeᚐTime(ctx, v)
if err != nil {
return it, err
}
it.ContractStartDate = graphql.OmittableOf(data)
case "contractEndDate":
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("contractEndDate"))
data, err := ec.unmarshalODatetime2ᚖtimeᚐTime(ctx, v)
if err != nil {
return it, err
}
it.ContractEndDate = graphql.OmittableOf(data)
}
}
return it, nil
}
func (ec *executionContext) unmarshalInputDeleteInvitationInput(ctx context.Context, obj any) (types.DeleteInvitationInput, error) { func (ec *executionContext) unmarshalInputDeleteInvitationInput(ctx context.Context, obj any) (types.DeleteInvitationInput, error) {
var it types.DeleteInvitationInput var it types.DeleteInvitationInput
asMap := map[string]any{} asMap := map[string]any{}
@@ -16431,7 +16666,7 @@ func (ec *executionContext) unmarshalInputUpdateProfileInput(ctx context.Context
if err != nil { if err != nil {
return it, err return it, err
} }
it.Position = graphql.OmittableOf(data) it.Position = data
case "contractStartDate": case "contractStartDate":
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("contractStartDate")) ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("contractStartDate"))
data, err := ec.unmarshalODatetime2ᚖtimeᚐTime(ctx, v) data, err := ec.unmarshalODatetime2ᚖtimeᚐTime(ctx, v)
@@ -17156,6 +17391,45 @@ func (ec *executionContext) _CreateSCIMConfigurationPayload(ctx context.Context,
return out return out
} }
var createUserPayloadImplementors = []string{"CreateUserPayload"}
func (ec *executionContext) _CreateUserPayload(ctx context.Context, sel ast.SelectionSet, obj *types.CreateUserPayload) graphql.Marshaler {
fields := graphql.CollectFields(ec.OperationContext, sel, createUserPayloadImplementors)
out := graphql.NewFieldSet(fields)
deferred := make(map[string]*graphql.FieldSet)
for i, field := range fields {
switch field.Name {
case "__typename":
out.Values[i] = graphql.MarshalString("CreateUserPayload")
case "profileEdge":
out.Values[i] = ec._CreateUserPayload_profileEdge(ctx, field, obj)
if out.Values[i] == graphql.Null {
out.Invalids++
}
default:
panic("unknown field " + strconv.Quote(field.Name))
}
}
out.Dispatch(ctx)
if out.Invalids > 0 {
return graphql.Null
}
atomic.AddInt32(&ec.deferred, int32(len(deferred)))
for label, dfs := range deferred {
ec.processDeferredGroup(graphql.DeferredGroup{
Label: label,
Path: graphql.GetPath(ctx),
FieldSet: dfs,
Context: ctx,
})
}
return out
}
var deleteInvitationPayloadImplementors = []string{"DeleteInvitationPayload"} var deleteInvitationPayloadImplementors = []string{"DeleteInvitationPayload"}
func (ec *executionContext) _DeleteInvitationPayload(ctx context.Context, sel ast.SelectionSet, obj *types.DeleteInvitationPayload) graphql.Marshaler { func (ec *executionContext) _DeleteInvitationPayload(ctx context.Context, sel ast.SelectionSet, obj *types.DeleteInvitationPayload) graphql.Marshaler {
@@ -18167,6 +18441,10 @@ func (ec *executionContext) _Mutation(ctx context.Context, sel ast.SelectionSet)
out.Values[i] = ec.OperationContext.RootResolverMiddleware(innerCtx, func(ctx context.Context) (res graphql.Marshaler) { out.Values[i] = ec.OperationContext.RootResolverMiddleware(innerCtx, func(ctx context.Context) (res graphql.Marshaler) {
return ec._Mutation_deleteOrganizationHorizontalLogo(ctx, field) return ec._Mutation_deleteOrganizationHorizontalLogo(ctx, field)
}) })
case "createUser":
out.Values[i] = ec.OperationContext.RootResolverMiddleware(innerCtx, func(ctx context.Context) (res graphql.Marshaler) {
return ec._Mutation_createUser(ctx, field)
})
case "inviteMember": case "inviteMember":
out.Values[i] = ec.OperationContext.RootResolverMiddleware(innerCtx, func(ctx context.Context) (res graphql.Marshaler) { out.Values[i] = ec.OperationContext.RootResolverMiddleware(innerCtx, func(ctx context.Context) (res graphql.Marshaler) {
return ec._Mutation_inviteMember(ctx, field) return ec._Mutation_inviteMember(ctx, field)
@@ -21688,6 +21966,11 @@ func (ec *executionContext) unmarshalNCreateSCIMConfigurationInput2goᚗproboᚗ
return res, graphql.ErrorOnPath(ctx, err) return res, graphql.ErrorOnPath(ctx, err)
} }
func (ec *executionContext) unmarshalNCreateUserInput2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconnectᚋv1ᚋtypesᚐCreateUserInput(ctx context.Context, v any) (types.CreateUserInput, error) {
res, err := ec.unmarshalInputCreateUserInput(ctx, v)
return res, graphql.ErrorOnPath(ctx, err)
}
func (ec *executionContext) unmarshalNCursorKey2goᚗproboᚗincᚋproboᚋpkgᚋpageᚐCursorKey(ctx context.Context, v any) (page.CursorKey, error) { func (ec *executionContext) unmarshalNCursorKey2goᚗproboᚗincᚋproboᚋpkgᚋpageᚐCursorKey(ctx context.Context, v any) (page.CursorKey, error) {
res, err := cursor.UnmarshalCursorKeyScalar(v) res, err := cursor.UnmarshalCursorKeyScalar(v)
return res, graphql.ErrorOnPath(ctx, err) return res, graphql.ErrorOnPath(ctx, err)
@@ -23175,6 +23458,13 @@ func (ec *executionContext) marshalOCreateSCIMConfigurationPayload2ᚖgoᚗprobo
return ec._CreateSCIMConfigurationPayload(ctx, sel, v) return ec._CreateSCIMConfigurationPayload(ctx, sel, v)
} }
func (ec *executionContext) marshalOCreateUserPayload2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconnectᚋv1ᚋtypesᚐCreateUserPayload(ctx context.Context, sel ast.SelectionSet, v *types.CreateUserPayload) graphql.Marshaler {
if v == nil {
return graphql.Null
}
return ec._CreateUserPayload(ctx, sel, v)
}
func (ec *executionContext) unmarshalOCursorKey2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋpageᚐCursorKey(ctx context.Context, v any) (*page.CursorKey, error) { func (ec *executionContext) unmarshalOCursorKey2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋpageᚐCursorKey(ctx context.Context, v any) (*page.CursorKey, error) {
if v == nil { if v == nil {
return nil, nil return nil, nil

View File

@@ -118,6 +118,22 @@ type CreateSCIMConfigurationPayload struct {
Token string `json:"token"` Token string `json:"token"`
} }
type CreateUserInput struct {
OrganizationID gid.GID `json:"organizationId"`
FullName string `json:"fullName"`
EmailAddress mail.Addr `json:"emailAddress"`
Role coredata.MembershipRole `json:"role"`
AdditionalEmailAddresses []mail.Addr `json:"additionalEmailAddresses,omitempty"`
Kind coredata.MembershipProfileKind `json:"kind"`
Position *string `json:"position,omitempty"`
ContractStartDate graphql.Omittable[*time.Time] `json:"contractStartDate,omitempty"`
ContractEndDate graphql.Omittable[*time.Time] `json:"contractEndDate,omitempty"`
}
type CreateUserPayload struct {
ProfileEdge *ProfileEdge `json:"profileEdge"`
}
type DeleteInvitationInput struct { type DeleteInvitationInput struct {
OrganizationID gid.GID `json:"organizationId"` OrganizationID gid.GID `json:"organizationId"`
InvitationID gid.GID `json:"invitationId"` InvitationID gid.GID `json:"invitationId"`
@@ -555,7 +571,7 @@ type UpdateProfileInput struct {
FullName string `json:"fullName"` FullName string `json:"fullName"`
AdditionalEmailAddresses []mail.Addr `json:"additionalEmailAddresses,omitempty"` AdditionalEmailAddresses []mail.Addr `json:"additionalEmailAddresses,omitempty"`
Kind coredata.MembershipProfileKind `json:"kind"` Kind coredata.MembershipProfileKind `json:"kind"`
Position graphql.Omittable[*string] `json:"position,omitempty"` Position *string `json:"position,omitempty"`
ContractStartDate graphql.Omittable[*time.Time] `json:"contractStartDate,omitempty"` ContractStartDate graphql.Omittable[*time.Time] `json:"contractStartDate,omitempty"`
ContractEndDate graphql.Omittable[*time.Time] `json:"contractEndDate,omitempty"` ContractEndDate graphql.Omittable[*time.Time] `json:"contractEndDate,omitempty"`
} }

View File

@@ -873,6 +873,40 @@ func (r *mutationResolver) DeleteOrganizationHorizontalLogo(ctx context.Context,
panic(fmt.Errorf("not implemented: DeleteOrganizationHorizontalLogo - deleteOrganizationHorizontalLogo")) panic(fmt.Errorf("not implemented: DeleteOrganizationHorizontalLogo - deleteOrganizationHorizontalLogo"))
} }
// CreateUser is the resolver for the createUser field.
func (r *mutationResolver) CreateUser(ctx context.Context, input types.CreateUserInput) (*types.CreateUserPayload, error) {
if err := r.authorize(ctx, input.OrganizationID, iam.ActionMembershipProfileCreate); err != nil {
return nil, err
}
profile, err := r.iam.OrganizationService.CreateUser(
ctx,
&iam.CreateUserRequest{
OrganizationID: input.OrganizationID,
EmailAddress: input.EmailAddress,
Role: input.Role,
FullName: input.FullName,
AdditionalEmailAddresses: input.AdditionalEmailAddresses,
Kind: input.Kind,
Position: input.Position,
ContractStartDate: gqlutils.UnwrapOmittable(input.ContractStartDate),
ContractEndDate: gqlutils.UnwrapOmittable(input.ContractEndDate),
},
)
if err != nil {
var errAlreadyExists *iam.ErrUserAlreadyExists
if errors.As(err, &errAlreadyExists) {
return nil, gqlutils.Conflictf(ctx, "user %q already exists", input.EmailAddress.String())
}
return nil, gqlutils.Internal(ctx)
}
return &types.CreateUserPayload{
ProfileEdge: types.NewProfileEdge(profile, coredata.MembershipProfileOrderFieldCreatedAt),
}, nil
}
// InviteMember is the resolver for the inviteMember field. // InviteMember is the resolver for the inviteMember field.
func (r *mutationResolver) InviteMember(ctx context.Context, input types.InviteMemberInput) (*types.InviteMemberPayload, error) { func (r *mutationResolver) InviteMember(ctx context.Context, input types.InviteMemberInput) (*types.InviteMemberPayload, error) {
if err := r.authorize(ctx, input.OrganizationID, iam.ActionInvitationCreate); err != nil { if err := r.authorize(ctx, input.OrganizationID, iam.ActionInvitationCreate); err != nil {
@@ -948,7 +982,7 @@ func (r *mutationResolver) UpdateProfile(ctx context.Context, input types.Update
FullName: input.FullName, FullName: input.FullName,
AdditionalEmailAddresses: input.AdditionalEmailAddresses, AdditionalEmailAddresses: input.AdditionalEmailAddresses,
Kind: input.Kind, Kind: input.Kind,
Position: gqlutils.UnwrapOmittable(input.Position), Position: input.Position,
ContractStartDate: gqlutils.UnwrapOmittable(input.ContractStartDate), ContractStartDate: gqlutils.UnwrapOmittable(input.ContractStartDate),
ContractEndDate: gqlutils.UnwrapOmittable(input.ContractEndDate), ContractEndDate: gqlutils.UnwrapOmittable(input.ContractEndDate),
}, },