Add identity profile

Signed-off-by: Bryan Frimin <bryan@getprobo.com>
This commit is contained in:
Bryan Frimin
2025-12-22 10:52:28 +01:00
parent e0225cbbbc
commit 2f7a3a5f76
23 changed files with 1158 additions and 2697 deletions

View File

@@ -138,6 +138,8 @@ type Identity implements Node {
createdAt: Datetime!
updatedAt: Datetime!
defaultProfile: IdentityProfile @goField(forceResolver: true) @isViewer
memberships(
first: Int
after: CursorKey
@@ -168,27 +170,12 @@ type Identity implements Node {
last: Int
before: CursorKey
): PersonalAPIKeyConnection @goField(forceResolver: true) @isViewer
profileFor(organizationId: ID!): IdentityProfile @isViewer
}
type IdentityProfile implements Node {
id: ID!
displayName: String!
firstName: String
lastName: String
jobTitle: String
department: String
phoneNumber: String
avatarUrl: String
manager: IdentityProfile
timezone: String
locale: String
customAttributes: [CustomAttribute!]!
provisionedBy: ProvisioningSource!
externalId: String
fullName: String!
identity: Identity!
organization: Organization!
createdAt: Datetime!
updatedAt: Datetime!
}
@@ -244,8 +231,8 @@ enum MembershipRole
type Membership implements Node {
id: ID!
createdAt: Datetime!
profile: IdentityProfile!
identity: Identity @goField(forceResolver: true)
profile: IdentityProfile @goField(forceResolver: true) @isViewer
organization: Organization @goField(forceResolver: true)
role: MembershipRole!
permissions: [Permission!]!
@@ -382,12 +369,6 @@ enum SAMLEnforcementPolicy
)
}
enum AuthMethod {
PASSWORD
SAML
RECOVERY_CODE
}
enum TokenScope {
READ_ORGANIZATION
WRITE_ORGANIZATION
@@ -578,14 +559,7 @@ input AssumeOrganizationSessionInput {
input UpdateIdentityProfileInput {
membershipId: ID!
displayName: String
firstName: String
lastName: String
jobTitle: String
department: String
phoneNumber: String
timezone: String
locale: String
fullName: String
}
input RevokeSessionInput {
@@ -750,10 +724,6 @@ type DeactivateAccountPayload {
success: Boolean!
}
type DeleteAccountPayload {
success: Boolean!
}
type UpdateIdentityProfilePayload {
profile: IdentityProfile
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,26 @@
// Copyright (c) 2025 Probo Inc <hello@getprobo.com>.
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
package types
import "go.probo.inc/probo/pkg/coredata"
func NewIdentityProfile(profile *coredata.IdentityProfile) *IdentityProfile {
return &IdentityProfile{
ID: profile.ID,
FullName: profile.FullName,
CreatedAt: profile.CreatedAt,
UpdatedAt: profile.UpdatedAt,
}
}

View File

@@ -71,5 +71,8 @@ func NewInvitation(invitation *coredata.Invitation) *Invitation {
AcceptedAt: invitation.AcceptedAt,
CreatedAt: invitation.CreatedAt,
Status: invitation.Status,
Organization: &Organization{
ID: invitation.OrganizationID,
},
}
}

View File

@@ -61,9 +61,14 @@ func NewMembershipEdge(membership *coredata.Membership, orderField coredata.Memb
func NewMembership(membership *coredata.Membership) *Membership {
return &Membership{
ID: membership.ID,
IdentityID: membership.IdentityID,
CreatedAt: membership.CreatedAt,
ID: membership.ID,
CreatedAt: membership.CreatedAt,
Identity: &Identity{
ID: membership.IdentityID,
},
Organization: &Organization{
ID: membership.OrganizationID,
},
// Permissions: membership.Permissions,
// ProvisionedBy: membership.ProvisionedBy,
// Active: membership.Active,

View File

@@ -61,12 +61,14 @@ func NewSessionEdge(session *coredata.Session, orderField coredata.SessionOrderF
func NewSession(session *coredata.Session) *Session {
return &Session{
ID: session.ID,
IPAddress: session.IPAddress.String(),
IdentityID: session.IdentityID,
UserAgent: session.UserAgent,
UpdatedAt: session.UpdatedAt,
CreatedAt: session.CreatedAt,
ExpiresAt: session.ExpiredAt,
ID: session.ID,
Identity: &Identity{
ID: session.IdentityID,
},
IPAddress: session.IPAddress.String(),
UserAgent: session.UserAgent,
UpdatedAt: session.UpdatedAt,
CreatedAt: session.CreatedAt,
ExpiresAt: session.ExpiredAt,
}
}

View File

@@ -33,12 +33,6 @@ type AcceptInvitationPayload struct {
MembershipEdge *MembershipEdge `json:"membershipEdge"`
}
type AddIPAllowlistEntryInput struct {
OrganizationID gid.GID `json:"organizationId"`
Cidr string `json:"cidr"`
Description *string `json:"description,omitempty"`
}
type Application struct {
ID ApplicationID `json:"id"`
Name string `json:"name"`
@@ -108,28 +102,10 @@ type CreateSAMLConfigurationPayload struct {
SamlConfigurationEdge *SAMLConfigurationEdge `json:"samlConfigurationEdge"`
}
type CustomAttribute struct {
Key string `json:"key"`
Value string `json:"value"`
}
type DeactivateAccountInput struct {
Password string `json:"password"`
}
type DeactivateAccountPayload struct {
Success bool `json:"success"`
}
type DeleteAccountInput struct {
Password string `json:"password"`
Confirmation string `json:"confirmation"`
}
type DeleteAccountPayload struct {
Success bool `json:"success"`
}
type DeleteInvitationInput struct {
OrganizationID gid.GID `json:"organizationId"`
InvitationID gid.GID `json:"invitationId"`
@@ -178,35 +154,22 @@ type Identity struct {
EmailVerified bool `json:"emailVerified"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
DefaultProfile *IdentityProfile `json:"defaultProfile,omitempty"`
Memberships *MembershipConnection `json:"memberships,omitempty"`
PendingInvitations *InvitationConnection `json:"pendingInvitations,omitempty"`
Sessions *SessionConnection `json:"sessions,omitempty"`
PersonalAPIKeys *PersonalAPIKeyConnection `json:"personalAPIKeys,omitempty"`
ProfileFor *IdentityProfile `json:"profileFor,omitempty"`
}
func (Identity) IsNode() {}
func (this Identity) GetID() gid.GID { return this.ID }
type IdentityProfile struct {
ID gid.GID `json:"id"`
DisplayName string `json:"displayName"`
FirstName *string `json:"firstName,omitempty"`
LastName *string `json:"lastName,omitempty"`
JobTitle *string `json:"jobTitle,omitempty"`
Department *string `json:"department,omitempty"`
PhoneNumber *string `json:"phoneNumber,omitempty"`
AvatarURL *string `json:"avatarUrl,omitempty"`
Manager *IdentityProfile `json:"manager,omitempty"`
Timezone *string `json:"timezone,omitempty"`
Locale *string `json:"locale,omitempty"`
CustomAttributes []*CustomAttribute `json:"customAttributes"`
ProvisionedBy ProvisioningSource `json:"provisionedBy"`
ExternalID *string `json:"externalId,omitempty"`
Identity *Identity `json:"identity"`
Organization *Organization `json:"organization"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
ID gid.GID `json:"id"`
FullName string `json:"fullName"`
Identity *Identity `json:"identity"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
}
func (IdentityProfile) IsNode() {}
@@ -242,18 +205,14 @@ type InviteMemberPayload struct {
}
type Membership struct {
ID gid.GID `json:"id"`
IdentityID gid.GID `json:"identityId"`
CreatedAt time.Time `json:"createdAt"`
Profile *IdentityProfile `json:"profile"`
Identity *Identity `json:"identity,omitempty"`
Organization *Organization `json:"organization,omitempty"`
Role coredata.MembershipRole `json:"role"`
Permissions []*Permission `json:"permissions"`
ProvisionedBy ProvisioningSource `json:"provisionedBy"`
Active bool `json:"active"`
LastSyncedAt *time.Time `json:"lastSyncedAt,omitempty"`
LastSession *Session `json:"lastSession,omitempty"`
ID gid.GID `json:"id"`
CreatedAt time.Time `json:"createdAt"`
Identity *Identity `json:"identity,omitempty"`
Profile *IdentityProfile `json:"profile,omitempty"`
Organization *Organization `json:"organization,omitempty"`
Role coredata.MembershipRole `json:"role"`
Permissions []*Permission `json:"permissions"`
LastSession *Session `json:"lastSession,omitempty"`
}
func (Membership) IsNode() {}
@@ -346,10 +305,6 @@ type PersonalAPIKeyEdge struct {
type Query struct {
}
type RemoveIPAllowlistEntryInput struct {
EntryID gid.GID `json:"entryId"`
}
type RemoveMemberInput struct {
OrganizationID gid.GID `json:"organizationId"`
MembershipID gid.GID `json:"membershipId"`
@@ -442,13 +397,13 @@ type SSOAvailability struct {
}
type Session struct {
ID gid.GID `json:"id"`
IdentityID gid.GID `json:"identityId"`
IPAddress string `json:"ipAddress"`
UserAgent string `json:"userAgent"`
UpdatedAt time.Time `json:"updatedAt"`
CreatedAt time.Time `json:"createdAt"`
ExpiresAt time.Time `json:"expiresAt"`
ID gid.GID `json:"id"`
Identity *Identity `json:"identity,omitempty"`
IPAddress string `json:"ipAddress"`
UserAgent string `json:"userAgent"`
UpdatedAt time.Time `json:"updatedAt"`
CreatedAt time.Time `json:"createdAt"`
ExpiresAt time.Time `json:"expiresAt"`
}
func (Session) IsNode() {}
@@ -464,20 +419,6 @@ type SessionOrder struct {
Field coredata.SessionOrderField `json:"field"`
}
type SessionPolicy struct {
MaxSessionDurationHours int `json:"maxSessionDurationHours"`
IdleTimeoutMinutes int `json:"idleTimeoutMinutes"`
MaxConcurrentSessions *int `json:"maxConcurrentSessions,omitempty"`
RequireReauthForSensitiveActions bool `json:"requireReauthForSensitiveActions"`
}
type SessionPolicyInput struct {
MaxSessionDurationHours *int `json:"maxSessionDurationHours,omitempty"`
IdleTimeoutMinutes *int `json:"idleTimeoutMinutes,omitempty"`
MaxConcurrentSessions *int `json:"maxConcurrentSessions,omitempty"`
RequireReauthForSensitiveActions *bool `json:"requireReauthForSensitiveActions,omitempty"`
}
type SignInInput struct {
Email mail.Addr `json:"email"`
Password string `json:"password"`
@@ -513,14 +454,7 @@ type SignUpPayload struct {
type UpdateIdentityProfileInput struct {
MembershipID gid.GID `json:"membershipId"`
DisplayName *string `json:"displayName,omitempty"`
FirstName *string `json:"firstName,omitempty"`
LastName *string `json:"lastName,omitempty"`
JobTitle *string `json:"jobTitle,omitempty"`
Department *string `json:"department,omitempty"`
PhoneNumber *string `json:"phoneNumber,omitempty"`
Timezone *string `json:"timezone,omitempty"`
Locale *string `json:"locale,omitempty"`
FullName *string `json:"fullName,omitempty"`
}
type UpdateIdentityProfilePayload struct {
@@ -699,63 +633,6 @@ func (e ApplicationID) MarshalJSON() ([]byte, error) {
return buf.Bytes(), nil
}
type AuthMethod string
const (
AuthMethodPassword AuthMethod = "PASSWORD"
AuthMethodSaml AuthMethod = "SAML"
AuthMethodRecoveryCode AuthMethod = "RECOVERY_CODE"
)
var AllAuthMethod = []AuthMethod{
AuthMethodPassword,
AuthMethodSaml,
AuthMethodRecoveryCode,
}
func (e AuthMethod) IsValid() bool {
switch e {
case AuthMethodPassword, AuthMethodSaml, AuthMethodRecoveryCode:
return true
}
return false
}
func (e AuthMethod) String() string {
return string(e)
}
func (e *AuthMethod) UnmarshalGQL(v any) error {
str, ok := v.(string)
if !ok {
return fmt.Errorf("enums must be strings")
}
*e = AuthMethod(str)
if !e.IsValid() {
return fmt.Errorf("%s is not a valid AuthMethod", str)
}
return nil
}
func (e AuthMethod) MarshalGQL(w io.Writer) {
fmt.Fprint(w, strconv.Quote(e.String()))
}
func (e *AuthMethod) UnmarshalJSON(b []byte) error {
s, err := strconv.Unquote(string(b))
if err != nil {
return err
}
return e.UnmarshalGQL(s)
}
func (e AuthMethod) MarshalJSON() ([]byte, error) {
var buf bytes.Buffer
e.MarshalGQL(&buf)
return buf.Bytes(), nil
}
type PrincipalType string
const (
@@ -811,63 +688,6 @@ func (e PrincipalType) MarshalJSON() ([]byte, error) {
return buf.Bytes(), nil
}
type ProvisioningSource string
const (
ProvisioningSourceManual ProvisioningSource = "MANUAL"
ProvisioningSourceInvitation ProvisioningSource = "INVITATION"
ProvisioningSourceSaml ProvisioningSource = "SAML"
)
var AllProvisioningSource = []ProvisioningSource{
ProvisioningSourceManual,
ProvisioningSourceInvitation,
ProvisioningSourceSaml,
}
func (e ProvisioningSource) IsValid() bool {
switch e {
case ProvisioningSourceManual, ProvisioningSourceInvitation, ProvisioningSourceSaml:
return true
}
return false
}
func (e ProvisioningSource) String() string {
return string(e)
}
func (e *ProvisioningSource) UnmarshalGQL(v any) error {
str, ok := v.(string)
if !ok {
return fmt.Errorf("enums must be strings")
}
*e = ProvisioningSource(str)
if !e.IsValid() {
return fmt.Errorf("%s is not a valid ProvisioningSource", str)
}
return nil
}
func (e ProvisioningSource) MarshalGQL(w io.Writer) {
fmt.Fprint(w, strconv.Quote(e.String()))
}
func (e *ProvisioningSource) UnmarshalJSON(b []byte) error {
s, err := strconv.Unquote(string(b))
if err != nil {
return err
}
return e.UnmarshalGQL(s)
}
func (e ProvisioningSource) MarshalJSON() ([]byte, error) {
var buf bytes.Buffer
e.MarshalGQL(&buf)
return buf.Bytes(), nil
}
type ReauthenticationReason string
const (

View File

@@ -24,6 +24,17 @@ import (
"go.probo.inc/probo/pkg/server/gqlutils/types/cursor"
)
// DefaultProfile is the resolver for the defaultProfile field.
func (r *identityResolver) DefaultProfile(ctx context.Context, obj *types.Identity) (*types.IdentityProfile, error) {
profile, err := r.iam.AccountService.GetDefaultProfile(ctx, obj.ID)
if err != nil {
r.logger.ErrorCtx(ctx, "cannot get default profile", log.Error(err))
return nil, gqlutils.InternalServerError(ctx)
}
return types.NewIdentityProfile(profile), nil
}
// Memberships is the resolver for the memberships field.
func (r *identityResolver) Memberships(ctx context.Context, obj *types.Identity, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.MembershipOrderBy) (*types.MembershipConnection, error) {
pageOrderBy := page.OrderBy[coredata.MembershipOrderField]{
@@ -41,7 +52,8 @@ func (r *identityResolver) Memberships(ctx context.Context, obj *types.Identity,
page, err := r.iam.AccountService.ListMemberships(ctx, obj.ID, cursor)
if err != nil {
panic(fmt.Errorf("cannot list memberships: %w", err))
r.logger.ErrorCtx(ctx, "cannot list memberships", log.Error(err))
return nil, gqlutils.InternalServerError(ctx)
}
return types.NewMembershipConnection(page, r, obj.ID), nil
@@ -58,7 +70,8 @@ func (r *identityResolver) PendingInvitations(ctx context.Context, obj *types.Id
page, err := r.iam.AccountService.ListPendingInvitations(ctx, obj.ID, cursor)
if err != nil {
panic(fmt.Errorf("cannot list pending invitations: %w", err))
r.logger.ErrorCtx(ctx, "cannot list pending invitations", log.Error(err))
return nil, gqlutils.InternalServerError(ctx)
}
return types.NewInvitationConnection(page, r, obj.ID, nil), nil
@@ -81,7 +94,8 @@ func (r *identityResolver) Sessions(ctx context.Context, obj *types.Identity, fi
page, err := r.iam.AccountService.ListSessions(ctx, obj.ID, cursor)
if err != nil {
panic(fmt.Errorf("cannot list sessions: %w", err))
r.logger.ErrorCtx(ctx, "cannot list sessions", log.Error(err))
return nil, gqlutils.InternalServerError(ctx)
}
return types.NewSessionConnection(page, r, obj.ID), nil
@@ -98,7 +112,8 @@ func (r *identityResolver) PersonalAPIKeys(ctx context.Context, obj *types.Ident
page, err := r.iam.AccountService.ListPersonalAPIKeys(ctx, obj.ID, cursor)
if err != nil {
panic(fmt.Errorf("cannot list personal api keys: %w", err))
r.logger.ErrorCtx(ctx, "cannot list personal api keys", log.Error(err))
return nil, gqlutils.InternalServerError(ctx)
}
return types.NewPersonalAPIKeyConnection(page, r, obj.ID), nil
@@ -108,7 +123,8 @@ func (r *identityResolver) PersonalAPIKeys(ctx context.Context, obj *types.Ident
func (r *invitationResolver) Organization(ctx context.Context, obj *types.Invitation) (*types.Organization, error) {
organization, err := r.iam.OrganizationService.GetOrganizationForInvitation(ctx, obj.ID)
if err != nil {
panic(fmt.Errorf("cannot get organization for invitation: %w", err))
r.logger.ErrorCtx(ctx, "cannot get organization for invitation", log.Error(err))
return nil, gqlutils.InternalServerError(ctx)
}
return types.NewOrganization(organization), nil
@@ -120,31 +136,51 @@ func (r *invitationConnectionResolver) TotalCount(ctx context.Context, obj *type
case *organizationResolver:
count, err := r.iam.OrganizationService.CountInvitations(ctx, obj.ParentID, obj.Filters)
if err != nil {
panic(fmt.Errorf("cannot count invitations: %w", err))
r.logger.ErrorCtx(ctx, "cannot count invitations", log.Error(err))
return nil, gqlutils.InternalServerError(ctx)
}
return &count, nil
case *identityResolver:
count, err := r.iam.AccountService.CountPendingInvitations(ctx, obj.ParentID)
if err != nil {
panic(fmt.Errorf("cannot count invitations: %w", err))
r.logger.ErrorCtx(ctx, "cannot count invitations", log.Error(err))
return nil, gqlutils.InternalServerError(ctx)
}
return &count, nil
}
panic(fmt.Errorf("unsupported resolver: %T", obj.Resolver))
r.logger.ErrorCtx(ctx, "unsupported resolver", log.Any("resolver", obj.Resolver))
return nil, gqlutils.InternalServerError(ctx)
}
// Identity is the resolver for the identity field.
func (r *membershipResolver) Identity(ctx context.Context, obj *types.Membership) (*types.Identity, error) {
identity, err := r.iam.AccountService.GetIdentityForMembership(ctx, obj.ID)
if err != nil {
panic(fmt.Errorf("cannot get identity: %w", err))
r.logger.ErrorCtx(ctx, "cannot get identity for membership", log.Error(err))
return nil, gqlutils.InternalServerError(ctx)
}
return types.NewIdentity(identity), nil
}
// Profile is the resolver for the profile field.
func (r *membershipResolver) Profile(ctx context.Context, obj *types.Membership) (*types.IdentityProfile, error) {
profile, err := r.iam.AccountService.GetProfileForMembership(ctx, obj.ID)
if err != nil {
var errProfileNotFound *iam.ErrProfileNotFound
if errors.As(err, &errProfileNotFound) {
return nil, nil
}
r.logger.ErrorCtx(ctx, "cannot get profile for membership", log.Error(err))
return nil, gqlutils.InternalServerError(ctx)
}
return types.NewIdentityProfile(profile), nil
}
// Organization is the resolver for the organization field.
func (r *membershipResolver) Organization(ctx context.Context, obj *types.Membership) (*types.Organization, error) {
organization, err := r.iam.OrganizationService.GetOrganizationForMembership(ctx, obj.ID)
@@ -517,7 +553,29 @@ func (r *mutationResolver) AssumeOrganizationSession(ctx context.Context, input
// UpdateIdentityProfile is the resolver for the updateIdentityProfile field.
func (r *mutationResolver) UpdateIdentityProfile(ctx context.Context, input types.UpdateIdentityProfileInput) (*types.UpdateIdentityProfilePayload, error) {
panic(fmt.Errorf("not implemented: UpdateIdentityProfile - updateIdentityProfile"))
identity := IdentityFromContext(ctx)
profile, err := r.iam.AccountService.UpdateIdentityProfile(
ctx,
identity.ID,
&iam.UpdateIdentityProfileRequest{
MembershipID: input.MembershipID,
FullName: input.FullName,
},
)
if err != nil {
var errMembershipNotFound *iam.ErrMembershipNotFound
if errors.As(err, &errMembershipNotFound) {
return nil, gqlutils.NotFound(err)
}
r.logger.ErrorCtx(ctx, "cannot update identity profile", log.Error(err))
return nil, gqlutils.InternalServerError(ctx)
}
return &types.UpdateIdentityProfilePayload{
Profile: types.NewIdentityProfile(profile),
}, nil
}
// RevokeSession is the resolver for the revokeSession field.
@@ -801,7 +859,8 @@ func (r *mutationResolver) CreateSAMLConfiguration(ctx context.Context, input ty
)
if err != nil {
panic(fmt.Errorf("cannot create saml configuration: %w", err))
r.logger.ErrorCtx(ctx, "cannot create saml configuration", log.Error(err))
return nil, gqlutils.InternalServerError(ctx)
}
return &types.CreateSAMLConfigurationPayload{
@@ -1095,6 +1154,17 @@ func (r *sAMLConfigurationConnectionResolver) TotalCount(ctx context.Context, ob
return nil, gqlutils.InternalServerError(ctx)
}
// Identity is the resolver for the identity field.
func (r *sessionResolver) Identity(ctx context.Context, obj *types.Session) (*types.Identity, error) {
identity, err := r.iam.AccountService.GetIdentity(ctx, obj.Identity.ID)
if err != nil {
r.logger.ErrorCtx(ctx, "cannot get identity for session", log.Error(err))
return nil, gqlutils.InternalServerError(ctx)
}
return types.NewIdentity(identity), nil
}
// TotalCount is the resolver for the totalCount field.
func (r *sessionConnectionResolver) TotalCount(ctx context.Context, obj *types.SessionConnection) (*int, error) {
switch obj.Resolver.(type) {
@@ -1150,6 +1220,9 @@ func (r *Resolver) SAMLConfigurationConnection() schema.SAMLConfigurationConnect
return &sAMLConfigurationConnectionResolver{r}
}
// Session returns schema.SessionResolver implementation.
func (r *Resolver) Session() schema.SessionResolver { return &sessionResolver{r} }
// SessionConnection returns schema.SessionConnectionResolver implementation.
func (r *Resolver) SessionConnection() schema.SessionConnectionResolver {
return &sessionConnectionResolver{r}
@@ -1165,4 +1238,5 @@ type organizationResolver struct{ *Resolver }
type personalAPIKeyConnectionResolver struct{ *Resolver }
type queryResolver struct{ *Resolver }
type sAMLConfigurationConnectionResolver struct{ *Resolver }
type sessionResolver struct{ *Resolver }
type sessionConnectionResolver struct{ *Resolver }

View File

@@ -2167,14 +2167,21 @@ func (r *mutationResolver) ExportFramework(ctx context.Context, input types.Expo
prb := r.ProboService(ctx, input.FrameworkID.TenantID())
identity := connect_v1.IdentityFromContext(ctx)
err, exportJobID := prb.Frameworks.RequestExport(
// Load default profile to get the full name
recipientName := ""
profile, err := r.iam.AccountService.GetDefaultProfile(ctx, identity.ID)
if err == nil {
recipientName = profile.FullName
}
exportErr, exportJobID := prb.Frameworks.RequestExport(
ctx,
input.FrameworkID,
identity.EmailAddress,
identity.FullName,
recipientName,
)
if err != nil {
panic(fmt.Errorf("cannot export framework: %w", err))
if exportErr != nil {
panic(fmt.Errorf("cannot export framework: %w", exportErr))
}
return &types.ExportFrameworkPayload{
@@ -3345,15 +3352,22 @@ func (r *mutationResolver) BulkExportDocuments(ctx context.Context, input types.
identity := connect_v1.IdentityFromContext(ctx)
// Load default profile to get the full name
recipientName := ""
profile, err := r.iam.AccountService.GetDefaultProfile(ctx, identity.ID)
if err == nil {
recipientName = profile.FullName
}
options := probo.ExportPDFOptions{
WithWatermark: input.WithWatermark,
WithSignatures: input.WithSignatures,
WatermarkEmail: input.WatermarkEmail,
}
documentExport, err := prb.Documents.RequestExport(ctx, input.DocumentIds, identity.EmailAddress, identity.FullName, options)
if err != nil {
panic(fmt.Errorf("cannot request document export: %w", err))
documentExport, exportErr := prb.Documents.RequestExport(ctx, input.DocumentIds, identity.EmailAddress, recipientName, options)
if exportErr != nil {
panic(fmt.Errorf("cannot request document export: %w", exportErr))
}
return &types.BulkExportDocumentsPayload{