Rework console pages and implement profile update

Signed-off-by: Émile Ré <emile@getprobo.com>
This commit is contained in:
Émile Ré
2026-02-06 19:50:42 +04:00
parent 091210dee5
commit 3a459f1992
35 changed files with 2197 additions and 108 deletions

View File

@@ -98,6 +98,7 @@ type Mutation {
@session(required: PRESENT)
deleteInvitation(input: DeleteInvitationInput!): DeleteInvitationPayload
@session(required: PRESENT)
updateProfile(input: UpdateProfileInput!): UpdateProfilePayload!
updateMembership(input: UpdateMembershipInput!): UpdateMembershipPayload!
removeMember(input: RemoveMemberInput!): RemoveMemberPayload
@session(required: PRESENT)
@@ -176,9 +177,20 @@ type Identity implements Node {
type MembershipProfile implements Node {
id: ID!
fullName: String!
additionalEmailAddresses: [EmailAddr!]!
kind: ProfileKind!
position: String
contractStartDate: Datetime
contractEndDate: Datetime
createdAt: Datetime!
updatedAt: Datetime!
identity: Identity @goField(forceResolver: true)
organization: Organization @goField(forceResolver: true)
# TODO: remove when memberships are under profile
membershipId: ID!
permission(action: String!): Boolean!
@goField(forceResolver: true)
@session(required: PRESENT)
@@ -229,6 +241,17 @@ type Organization implements Node {
@session(required: PRESENT)
}
enum ProfileKind @goModel(model: "go.probo.inc/probo/pkg/coredata.MembershipProfileKind") {
EMPLOYEE
@goEnum(value: "go.probo.inc/probo/pkg/coredata.MembershipProfileKindEmployee")
CONTRACTOR
@goEnum(value: "go.probo.inc/probo/pkg/coredata.MembershipProfileKindContractor")
SERVICE_ACCOUNT
@goEnum(
value: "go.probo.inc/probo/pkg/coredata.MembershipProfileKindServiceAccount"
)
}
enum MembershipRole
@goModel(model: "go.probo.inc/probo/pkg/coredata.MembershipRole") {
OWNER @goEnum(value: "go.probo.inc/probo/pkg/coredata.MembershipRoleOwner")
@@ -717,6 +740,16 @@ input InviteMemberInput {
role: MembershipRole!
}
input UpdateProfileInput {
id: ID!
fullName: String!
additionalEmailAddresses: [EmailAddr!]
kind: ProfileKind!
position: String @goField(omittable: true)
contractStartDate: Datetime @goField(omittable: true)
contractEndDate: Datetime @goField(omittable: true)
}
input UpdateMembershipInput {
organizationId: ID!
membershipId: ID!
@@ -868,6 +901,10 @@ type InviteMemberPayload {
invitationEdge: InvitationEdge!
}
type UpdateProfilePayload {
profile: MembershipProfile!
}
type UpdateMembershipPayload {
membership: Membership!
}

File diff suppressed because it is too large Load Diff

View File

@@ -18,9 +18,21 @@ import "go.probo.inc/probo/pkg/coredata"
func NewMembershipProfile(profile *coredata.MembershipProfile) *MembershipProfile {
return &MembershipProfile{
ID: profile.ID,
FullName: profile.FullName,
CreatedAt: profile.CreatedAt,
UpdatedAt: profile.UpdatedAt,
ID: profile.ID,
FullName: profile.FullName,
AdditionalEmailAddresses: profile.AdditionalEmailAddresses,
Kind: profile.Kind,
Position: profile.Position,
ContractStartDate: profile.ContractStartDate,
ContractEndDate: profile.ContractEndDate,
CreatedAt: profile.CreatedAt,
UpdatedAt: profile.UpdatedAt,
MembershipID: profile.MembershipID,
Identity: &Identity{
ID: profile.IdentityID,
},
Organization: &Organization{
ID: profile.OrganizationID,
},
}
}

View File

@@ -240,11 +240,19 @@ type MembershipEdge struct {
}
type MembershipProfile struct {
ID gid.GID `json:"id"`
FullName string `json:"fullName"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
Permission bool `json:"permission"`
ID gid.GID `json:"id"`
FullName string `json:"fullName"`
AdditionalEmailAddresses []mail.Addr `json:"additionalEmailAddresses"`
Kind coredata.MembershipProfileKind `json:"kind"`
Position *string `json:"position,omitempty"`
ContractStartDate *time.Time `json:"contractStartDate,omitempty"`
ContractEndDate *time.Time `json:"contractEndDate,omitempty"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
Identity *Identity `json:"identity,omitempty"`
Organization *Organization `json:"organization,omitempty"`
MembershipID gid.GID `json:"membershipId"`
Permission bool `json:"permission"`
}
func (MembershipProfile) IsNode() {}
@@ -545,6 +553,20 @@ type UpdateOrganizationPayload struct {
Organization *Organization `json:"organization,omitempty"`
}
type UpdateProfileInput struct {
ID gid.GID `json:"id"`
FullName string `json:"fullName"`
AdditionalEmailAddresses []mail.Addr `json:"additionalEmailAddresses,omitempty"`
Kind coredata.MembershipProfileKind `json:"kind"`
Position graphql.Omittable[*string] `json:"position,omitempty"`
ContractStartDate graphql.Omittable[*time.Time] `json:"contractStartDate,omitempty"`
ContractEndDate graphql.Omittable[*time.Time] `json:"contractEndDate,omitempty"`
}
type UpdateProfilePayload struct {
Profile *MembershipProfile `json:"profile"`
}
type UpdateSAMLConfigurationInput struct {
OrganizationID gid.GID `json:"organizationId"`
SamlConfigurationID gid.GID `json:"samlConfigurationId"`

View File

@@ -349,6 +349,53 @@ func (r *membershipConnectionResolver) TotalCount(ctx context.Context, obj *type
return nil, gqlutils.Internal(ctx)
}
// Identity is the resolver for the identity field.
func (r *membershipProfileResolver) Identity(ctx context.Context, obj *types.MembershipProfile) (*types.Identity, error) {
if err := r.authorize(
ctx,
obj.Identity.ID,
iam.ActionIdentityGet,
authz.WithAttr("organization_id", obj.Organization.ID.String()),
); err != nil {
return nil, err
}
if gqlutils.OnlyIDSelected(ctx) {
return &types.Identity{
ID: obj.Identity.ID,
}, nil
}
identity, err := r.iam.AccountService.GetIdentity(ctx, obj.Identity.ID)
if err != nil {
r.logger.ErrorCtx(ctx, "cannot get identity for membership", log.Error(err))
return nil, gqlutils.Internal(ctx)
}
return types.NewIdentity(identity), nil
}
// Organization is the resolver for the organization field.
func (r *membershipProfileResolver) Organization(ctx context.Context, obj *types.MembershipProfile) (*types.Organization, error) {
if err := r.authorize(ctx, obj.Organization.ID, iam.ActionOrganizationGet, authz.WithSkipAssumptionCheck()); err != nil {
return nil, err
}
if gqlutils.OnlyIDSelected(ctx) {
return &types.Organization{
ID: obj.Organization.ID,
}, nil
}
organization, err := r.iam.OrganizationService.GetOrganizationForMembership(ctx, obj.ID)
if err != nil {
r.logger.ErrorCtx(ctx, "cannot get organization for membership", log.Error(err))
return nil, gqlutils.Internal(ctx)
}
return types.NewOrganization(organization), nil
}
// Permission is the resolver for the permission field.
func (r *membershipProfileResolver) Permission(ctx context.Context, obj *types.MembershipProfile, action string) (bool, error) {
return r.Resolver.Permission(ctx, obj, action)
@@ -991,6 +1038,34 @@ func (r *mutationResolver) DeleteInvitation(ctx context.Context, input types.Del
return &types.DeleteInvitationPayload{DeletedInvitationID: input.InvitationID}, nil
}
// UpdateProfile is the resolver for the updateProfile field.
func (r *mutationResolver) UpdateProfile(ctx context.Context, input types.UpdateProfileInput) (*types.UpdateProfilePayload, error) {
if err := r.authorize(ctx, input.ID, iam.ActionMembershipProfileUpdate); err != nil {
return nil, err
}
profile, err := r.iam.OrganizationService.UpdateProfile(
ctx,
&iam.UpdateProfileRequest{
ID: input.ID,
FullName: input.FullName,
AdditionalEmailAddresses: input.AdditionalEmailAddresses,
Kind: input.Kind,
Position: gqlutils.UnwrapOmittable(input.Position),
ContractStartDate: gqlutils.UnwrapOmittable(input.ContractStartDate),
ContractEndDate: gqlutils.UnwrapOmittable(input.ContractEndDate),
},
)
if err != nil {
r.logger.ErrorCtx(ctx, "cannot update profile", log.Error(err))
return nil, gqlutils.Internal(ctx)
}
return &types.UpdateProfilePayload{
Profile: types.NewMembershipProfile(profile),
}, nil
}
// UpdateMembership is the resolver for the updateMembership field.
func (r *mutationResolver) UpdateMembership(ctx context.Context, input types.UpdateMembershipInput) (*types.UpdateMembershipPayload, error) {
if err := r.authorize(ctx, input.MembershipID, iam.ActionMembershipUpdate); err != nil {
@@ -1494,6 +1569,16 @@ func (r *queryResolver) Node(ctx context.Context, id gid.GID) (types.Node, error
return types.NewSession(session), nil
}
case coredata.MembershipProfileEntityType:
action = iam.ActionMembershipGet
loadNode = func(ctx context.Context, id gid.GID) (types.Node, error) {
profile, err := r.iam.OrganizationService.GetProfile(ctx, id)
if err != nil {
return nil, err
}
return types.NewMembershipProfile(profile), nil
}
case coredata.MembershipEntityType:
action = iam.ActionMembershipGet
loadNode = func(ctx context.Context, id gid.GID) (types.Node, error) {

View File

@@ -69,7 +69,7 @@ enum EvidenceState
@goEnum(value: "go.probo.inc/probo/pkg/coredata.EvidenceStateRequested")
}
enum MembershipProfileKind @goModel(model: "go.probo.inc/probo/pkg/coredata.MembershipProfileKind") {
enum ProfileKind @goModel(model: "go.probo.inc/probo/pkg/coredata.MembershipProfileKind") {
EMPLOYEE
@goEnum(value: "go.probo.inc/probo/pkg/coredata.MembershipProfileKindEmployee")
CONTRACTOR
@@ -1846,7 +1846,7 @@ type Profile implements Node {
fullName: String!
emailAddress: EmailAddr!
additionalEmailAddresses: [EmailAddr!]!
kind: MembershipProfileKind!
kind: ProfileKind!
position: String
contractStartDate: Datetime
contractEndDate: Datetime

View File

@@ -10347,7 +10347,7 @@ enum EvidenceState
@goEnum(value: "go.probo.inc/probo/pkg/coredata.EvidenceStateRequested")
}
enum MembershipProfileKind @goModel(model: "go.probo.inc/probo/pkg/coredata.MembershipProfileKind") {
enum ProfileKind @goModel(model: "go.probo.inc/probo/pkg/coredata.MembershipProfileKind") {
EMPLOYEE
@goEnum(value: "go.probo.inc/probo/pkg/coredata.MembershipProfileKindEmployee")
CONTRACTOR
@@ -12124,7 +12124,7 @@ type Profile implements Node {
fullName: String!
emailAddress: EmailAddr!
additionalEmailAddresses: [EmailAddr!]!
kind: MembershipProfileKind!
kind: ProfileKind!
position: String
contractStartDate: Datetime
contractEndDate: Datetime
@@ -44174,7 +44174,7 @@ func (ec *executionContext) _Profile_kind(ctx context.Context, field graphql.Col
return obj.Kind, nil
},
nil,
ec.marshalNMembershipProfileKind2goᚗproboᚗincᚋproboᚋpkgᚋcoredataᚐMembershipProfileKind,
ec.marshalNProfileKind2goᚗproboᚗincᚋproboᚋpkgᚋcoredataᚐMembershipProfileKind,
true,
true,
)
@@ -44187,7 +44187,7 @@ func (ec *executionContext) fieldContext_Profile_kind(_ context.Context, field g
IsMethod: false,
IsResolver: false,
Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
return nil, errors.New("field of type MembershipProfileKind does not have child fields")
return nil, errors.New("field of type ProfileKind does not have child fields")
},
}
return fc, nil
@@ -94083,36 +94083,6 @@ var (
}
)
func (ec *executionContext) unmarshalNMembershipProfileKind2goᚗproboᚗincᚋproboᚋpkgᚋcoredataᚐMembershipProfileKind(ctx context.Context, v any) (coredata.MembershipProfileKind, error) {
tmp, err := graphql.UnmarshalString(v)
res := unmarshalNMembershipProfileKind2goᚗproboᚗincᚋproboᚋpkgᚋcoredataᚐMembershipProfileKind[tmp]
return res, graphql.ErrorOnPath(ctx, err)
}
func (ec *executionContext) marshalNMembershipProfileKind2goᚗproboᚗincᚋproboᚋpkgᚋcoredataᚐMembershipProfileKind(ctx context.Context, sel ast.SelectionSet, v coredata.MembershipProfileKind) graphql.Marshaler {
_ = sel
res := graphql.MarshalString(marshalNMembershipProfileKind2goᚗproboᚗincᚋproboᚋpkgᚋcoredataᚐMembershipProfileKind[v])
if res == graphql.Null {
if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) {
graphql.AddErrorf(ctx, "the requested element is null which the schema does not allow")
}
}
return res
}
var (
unmarshalNMembershipProfileKind2goᚗproboᚗincᚋproboᚋpkgᚋcoredataᚐMembershipProfileKind = map[string]coredata.MembershipProfileKind{
"EMPLOYEE": coredata.MembershipProfileKindEmployee,
"CONTRACTOR": coredata.MembershipProfileKindContractor,
"SERVICE_ACCOUNT": coredata.MembershipProfileKindServiceAccount,
}
marshalNMembershipProfileKind2goᚗproboᚗincᚋproboᚋpkgᚋcoredataᚐMembershipProfileKind = map[coredata.MembershipProfileKind]string{
coredata.MembershipProfileKindEmployee: "EMPLOYEE",
coredata.MembershipProfileKindContractor: "CONTRACTOR",
coredata.MembershipProfileKindServiceAccount: "SERVICE_ACCOUNT",
}
)
func (ec *executionContext) marshalNNode2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐNode(ctx context.Context, sel ast.SelectionSet, v types.Node) graphql.Marshaler {
if v == nil {
if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) {
@@ -94885,6 +94855,36 @@ func (ec *executionContext) marshalNProfileEdge2ᚖgoᚗproboᚗincᚋproboᚋpk
return ec._ProfileEdge(ctx, sel, v)
}
func (ec *executionContext) unmarshalNProfileKind2goᚗproboᚗincᚋproboᚋpkgᚋcoredataᚐMembershipProfileKind(ctx context.Context, v any) (coredata.MembershipProfileKind, error) {
tmp, err := graphql.UnmarshalString(v)
res := unmarshalNProfileKind2goᚗproboᚗincᚋproboᚋpkgᚋcoredataᚐMembershipProfileKind[tmp]
return res, graphql.ErrorOnPath(ctx, err)
}
func (ec *executionContext) marshalNProfileKind2goᚗproboᚗincᚋproboᚋpkgᚋcoredataᚐMembershipProfileKind(ctx context.Context, sel ast.SelectionSet, v coredata.MembershipProfileKind) graphql.Marshaler {
_ = sel
res := graphql.MarshalString(marshalNProfileKind2goᚗproboᚗincᚋproboᚋpkgᚋcoredataᚐMembershipProfileKind[v])
if res == graphql.Null {
if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) {
graphql.AddErrorf(ctx, "the requested element is null which the schema does not allow")
}
}
return res
}
var (
unmarshalNProfileKind2goᚗproboᚗincᚋproboᚋpkgᚋcoredataᚐMembershipProfileKind = map[string]coredata.MembershipProfileKind{
"EMPLOYEE": coredata.MembershipProfileKindEmployee,
"CONTRACTOR": coredata.MembershipProfileKindContractor,
"SERVICE_ACCOUNT": coredata.MembershipProfileKindServiceAccount,
}
marshalNProfileKind2goᚗproboᚗincᚋproboᚋpkgᚋcoredataᚐMembershipProfileKind = map[coredata.MembershipProfileKind]string{
coredata.MembershipProfileKindEmployee: "EMPLOYEE",
coredata.MembershipProfileKindContractor: "CONTRACTOR",
coredata.MembershipProfileKindServiceAccount: "SERVICE_ACCOUNT",
}
)
func (ec *executionContext) unmarshalNProfileOrderField2goᚗproboᚗincᚋproboᚋpkgᚋcoredataᚐMembershipProfileOrderField(ctx context.Context, v any) (coredata.MembershipProfileOrderField, error) {
tmp, err := graphql.UnmarshalString(v)
res := unmarshalNProfileOrderField2goᚗproboᚗincᚋproboᚋpkgᚋcoredataᚐMembershipProfileOrderField[tmp]