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

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

View File

@@ -119,6 +119,10 @@ type ComplexityRoot struct {
Token func(childComplexity int) int
}
CreateUserPayload struct {
ProfileEdge func(childComplexity int) int
}
DeleteInvitationPayload struct {
DeletedInvitationID func(childComplexity int) int
}
@@ -203,6 +207,7 @@ type ComplexityRoot struct {
CreatePersonalAPIKey func(childComplexity int, input types.CreatePersonalAPIKeyInput) int
CreateSAMLConfiguration func(childComplexity int, input types.CreateSAMLConfigurationInput) 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
DeleteOrganization func(childComplexity int, input types.DeleteOrganizationInput) 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)
DeleteOrganization(ctx context.Context, input types.DeleteOrganizationInput) (*types.DeleteOrganizationPayload, 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)
DeleteInvitation(ctx context.Context, input types.DeleteInvitationInput) (*types.DeleteInvitationPayload, 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
case "CreateUserPayload.profileEdge":
if e.complexity.CreateUserPayload.ProfileEdge == nil {
break
}
return e.complexity.CreateUserPayload.ProfileEdge(childComplexity), true
case "DeleteInvitationPayload.deletedInvitationId":
if e.complexity.DeleteInvitationPayload.DeletedInvitationID == nil {
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
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":
if e.complexity.Mutation.DeleteInvitation == nil {
break
@@ -2375,6 +2399,7 @@ func (e *executableSchema) Exec(ctx context.Context) graphql.ResponseHandler {
ec.unmarshalInputCreatePersonalAPIKeyInput,
ec.unmarshalInputCreateSAMLConfigurationInput,
ec.unmarshalInputCreateSCIMConfigurationInput,
ec.unmarshalInputCreateUserInput,
ec.unmarshalInputDeleteInvitationInput,
ec.unmarshalInputDeleteOrganizationHorizontalLogoInput,
ec.unmarshalInputDeleteOrganizationInput,
@@ -2594,6 +2619,8 @@ type Mutation {
input: DeleteOrganizationHorizontalLogoInput!
): DeleteOrganizationHorizontalLogoPayload @session(required: PRESENT)
createUser(input: CreateUserInput!): CreateUserPayload
@session(required: PRESENT)
inviteMember(input: InviteMemberInput!): InviteMemberPayload
@session(required: PRESENT)
deleteInvitation(input: DeleteInvitationInput!): DeleteInvitationPayload
@@ -3227,6 +3254,18 @@ input DeleteOrganizationHorizontalLogoInput {
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 {
organizationId: ID!
email: EmailAddr!
@@ -3239,7 +3278,7 @@ input UpdateProfileInput {
fullName: String!
additionalEmailAddresses: [EmailAddr!]
kind: ProfileKind!
position: String @goField(omittable: true)
position: String
contractStartDate: Datetime @goField(omittable: true)
contractEndDate: Datetime @goField(omittable: true)
}
@@ -3390,6 +3429,10 @@ type DeleteOrganizationHorizontalLogoPayload {
organization: Organization!
}
type CreateUserPayload {
profileEdge: ProfileEdge!
}
type InviteMemberPayload {
invitationEdge: InvitationEdge!
}
@@ -3775,6 +3818,17 @@ func (ec *executionContext) field_Mutation_createSCIMConfiguration_args(ctx cont
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) {
var err error
args := map[string]any{}
@@ -4985,6 +5039,41 @@ func (ec *executionContext) fieldContext_CreateSCIMConfigurationPayload_token(_
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) {
return graphql.ResolveField(
ctx,
@@ -7550,6 +7639,69 @@ func (ec *executionContext) fieldContext_Mutation_deleteOrganizationHorizontalLo
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) {
return graphql.ResolveField(
ctx,
@@ -15572,6 +15724,89 @@ func (ec *executionContext) unmarshalInputCreateSCIMConfigurationInput(ctx conte
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) {
var it types.DeleteInvitationInput
asMap := map[string]any{}
@@ -16431,7 +16666,7 @@ func (ec *executionContext) unmarshalInputUpdateProfileInput(ctx context.Context
if err != nil {
return it, err
}
it.Position = graphql.OmittableOf(data)
it.Position = data
case "contractStartDate":
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("contractStartDate"))
data, err := ec.unmarshalODatetime2ᚖtimeᚐTime(ctx, v)
@@ -17156,6 +17391,45 @@ func (ec *executionContext) _CreateSCIMConfigurationPayload(ctx context.Context,
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"}
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) {
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":
out.Values[i] = ec.OperationContext.RootResolverMiddleware(innerCtx, func(ctx context.Context) (res graphql.Marshaler) {
return ec._Mutation_inviteMember(ctx, field)
@@ -21688,6 +21966,11 @@ func (ec *executionContext) unmarshalNCreateSCIMConfigurationInput2goᚗproboᚗ
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) {
res, err := cursor.UnmarshalCursorKeyScalar(v)
return res, graphql.ErrorOnPath(ctx, err)
@@ -23175,6 +23458,13 @@ func (ec *executionContext) marshalOCreateSCIMConfigurationPayload2ᚖgoᚗprobo
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) {
if v == nil {
return nil, nil

View File

@@ -118,6 +118,22 @@ type CreateSCIMConfigurationPayload struct {
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 {
OrganizationID gid.GID `json:"organizationId"`
InvitationID gid.GID `json:"invitationId"`
@@ -555,7 +571,7 @@ type UpdateProfileInput struct {
FullName string `json:"fullName"`
AdditionalEmailAddresses []mail.Addr `json:"additionalEmailAddresses,omitempty"`
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"`
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"))
}
// 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.
func (r *mutationResolver) InviteMember(ctx context.Context, input types.InviteMemberInput) (*types.InviteMemberPayload, error) {
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,
AdditionalEmailAddresses: input.AdditionalEmailAddresses,
Kind: input.Kind,
Position: gqlutils.UnwrapOmittable(input.Position),
Position: input.Position,
ContractStartDate: gqlutils.UnwrapOmittable(input.ContractStartDate),
ContractEndDate: gqlutils.UnwrapOmittable(input.ContractEndDate),
},