diff --git a/contrib/claude/graphql.md b/contrib/claude/graphql.md index 16a692631..fb56e9f72 100644 --- a/contrib/claude/graphql.md +++ b/contrib/claude/graphql.md @@ -1,6 +1,15 @@ # GraphQL (Go Backend — gqlgen) -Schema-first GraphQL using [gqlgen](https://gqlgen.com/). The schema is hand-written; Go types and resolvers are generated. +Schema-first GraphQL using [gqlgen](https://gqlgen.com/). The schema is hand-written and split into per-entity files under `graphql/`; Go types and resolvers are generated. + +## Schema file organization + +Each API's schema lives in `pkg/server/api/{api}/v1/graphql/` as multiple `.graphql` files, one per coredata model: + +- `base.graphql` — directives, scalars, Node interface, PageInfo, OrderDirection, root Query/Mutation/Organization types +- Entity files (e.g., `vendor.graphql`, `control.graphql`) — use `extend type Organization`, `extend type Mutation`, etc. + +gqlgen's `follow-schema` layout generates one resolver file per schema file (e.g., `vendor.resolvers.go`). Types that get extended across files (Organization, Mutation, Viewer, TrustCenter) must be defined in `base.graphql`. ## Connection types and `@goModel` @@ -40,13 +49,15 @@ enum VendorOrderField ## Schema directives -| Directive | Target | Purpose | -|-----------|--------|---------| -| `@goModel(model: "...")` | `OBJECT`, `ENUM`, `INPUT_OBJECT`, `SCALAR`, `INTERFACE`, `UNION` | Map GraphQL type to existing Go type | -| `@goEnum(value: "...")` | `ENUM_VALUE` | Map enum value to Go constant | -| `@goField(forceResolver: true)` | `FIELD_DEFINITION` | Force a resolver function instead of struct field | -| `@goField(name: "...")` | `FIELD_DEFINITION`, `INPUT_FIELD_DEFINITION` | Override Go field name | -| `@goField(omittable: true)` | `INPUT_FIELD_DEFINITION` | Use `graphql.Omittable[T]` for distinguishing null vs absent | + +| Directive | Target | Purpose | +| ------------------------------- | ---------------------------------------------------------------- | ------------------------------------------------------------ | +| `@goModel(model: "...")` | `OBJECT`, `ENUM`, `INPUT_OBJECT`, `SCALAR`, `INTERFACE`, `UNION` | Map GraphQL type to existing Go type | +| `@goEnum(value: "...")` | `ENUM_VALUE` | Map enum value to Go constant | +| `@goField(forceResolver: true)` | `FIELD_DEFINITION` | Force a resolver function instead of struct field | +| `@goField(name: "...")` | `FIELD_DEFINITION`, `INPUT_FIELD_DEFINITION` | Override Go field name | +| `@goField(omittable: true)` | `INPUT_FIELD_DEFINITION` | Use `graphql.Omittable[T]` for distinguishing null vs absent | + ## Cursor pagination schema types @@ -158,7 +169,8 @@ Default page size is **25** when neither `first` nor `last` is provided. ## Adding a new paginated field — checklist 1. **Schema** — add `enum XxxOrderField` (with `@goModel`/`@goEnum`), `input XxxOrder`, `type XxxConnection` (with `@goModel` and `totalCount` using `@goField(forceResolver: true)`), `type XxxEdge`, and the connection field with Relay arguments on the parent type -2. **Coredata** — add `*_order_field.go` (with `Column()`, `IsValid()`, marshaling), `CursorKey(field)` method on the entity, and the `LoadAllBy*` query using cursor SQL fragments + `page.NewPage()` +2. **Coredata** — add `*_order_field.go` (with `Column()`, `IsValid()`, marshaling), `CursorKey(field)` method on the entity, and the `LoadAllBy`* query using cursor SQL fragments + `page.NewPage()` 3. **API types** — add `*_connection.go` with `OrderBy` alias, connection struct, `NewXxxConnection`, `NewXxxEdge` 4. **Resolver** — implement the resolver (authorize, build order, build cursor, call service, build connection) 5. **Codegen** — run `go generate` for the relevant API package + diff --git a/contrib/claude/relay.md b/contrib/claude/relay.md index f89760776..f3224b0aa 100644 --- a/contrib/claude/relay.md +++ b/contrib/claude/relay.md @@ -6,16 +6,18 @@ The console app uses [Relay](https://relay.dev/) as its GraphQL client. All Grap Two Relay environments connect to two separate GraphQL APIs: -| Environment | Endpoint | Purpose | -|-------------|----------|---------| -| `coreEnvironment` | `/api/console/v1/graphql` | Main application data | -| `iamEnvironment` | `/api/connect/v1/graphql` | Authentication / identity | + +| Environment | Endpoint | Purpose | +| ----------------- | ------------------------- | ------------------------- | +| `coreEnvironment` | `/api/console/v1/graphql` | Main application data | +| `iamEnvironment` | `/api/connect/v1/graphql` | Authentication / identity | + Configured in `apps/console/src/environments.ts`. Each has its own store with 1-minute query cache expiration. ## Relay compiler -Config lives in `relay.config.json` at the repo root with three projects (`core`, `iam`, `trust`) mapped to different source directories and schemas. Generated files go into `__generated__/` directories. +Config lives in `relay.config.json` at the repo root with three projects (`core`, `iam`, `trust`) mapped to different source directories and schemas. Each project uses `schema` pointing to `base.graphql` and `schemaExtensions` pointing to the `graphql/` directory containing the per-entity schema files. Generated files go into `__generated__/` directories. ```sh npm run relay # clean + compile (from repo root) @@ -381,7 +383,7 @@ return () => { ## File organization -GraphQL operations are colocated with the components that use them. See [`contrib/claude/app-arborescence.md`](app-arborescence.md) for the full folder layout. +GraphQL operations are colocated with the components that use them. See `[contrib/claude/app-arborescence.md](app-arborescence.md)` for the full folder layout. ``` pages/organizations/vendors/ @@ -394,4 +396,4 @@ pages/organizations/vendors/ VendorComplianceTab.tsx ``` -Component-specific operations (queries, fragments, mutations) are defined inline in the component file that uses them. Shared sub-components live in `_components/` next to the page (scoped to the nearest common ancestor). +Component-specific operations (queries, fragments, mutations) are defined inline in the component file that uses them. Shared sub-components live in `_components/` next to the page (scoped to the nearest common ancestor). \ No newline at end of file diff --git a/pkg/server/api/connect/v1/audit_log.resolvers.go b/pkg/server/api/connect/v1/audit_log.resolvers.go new file mode 100644 index 000000000..03e6f9814 --- /dev/null +++ b/pkg/server/api/connect/v1/audit_log.resolvers.go @@ -0,0 +1,100 @@ +package connect_v1 + +// This file will be automatically regenerated based on the schema, any resolver +// implementations +// will be copied through when generating and any unknown code will be moved to the end. +// Code generated by github.com/99designs/gqlgen version v0.17.87 + +import ( + "context" + + "go.gearno.de/kit/log" + "go.probo.inc/probo/pkg/coredata" + "go.probo.inc/probo/pkg/iam" + "go.probo.inc/probo/pkg/page" + "go.probo.inc/probo/pkg/server/api/connect/v1/schema" + "go.probo.inc/probo/pkg/server/api/connect/v1/types" + "go.probo.inc/probo/pkg/server/gqlutils" + "go.probo.inc/probo/pkg/server/gqlutils/types/cursor" +) + +// Organization is the resolver for the organization field. +func (r *auditLogEntryResolver) Organization(ctx context.Context, obj *types.AuditLogEntry) (*types.Organization, error) { + return obj.Organization, nil +} + +// Permission is the resolver for the permission field. +func (r *auditLogEntryResolver) Permission(ctx context.Context, obj *types.AuditLogEntry, action string) (bool, error) { + return r.Resolver.Permission(ctx, obj, action) +} + +// TotalCount is the resolver for the totalCount field. +func (r *auditLogEntryConnectionResolver) TotalCount(ctx context.Context, obj *types.AuditLogEntryConnection) (int, error) { + filter := coredata.NewAuditLogEntryFilter() + if obj.Filter != nil { + filter = obj.Filter + } + + count, err := r.iam.OrganizationService.CountAuditLogEntries(ctx, obj.ParentID, filter) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot count audit log entries", log.Error(err)) + return 0, gqlutils.Internal(ctx) + } + + return count, nil +} + +// AuditLogEntries is the resolver for the auditLogEntries field. +func (r *organizationResolver) AuditLogEntries(ctx context.Context, obj *types.Organization, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.AuditLogEntryOrderBy, filter *types.AuditLogEntryFilter) (*types.AuditLogEntryConnection, error) { + if err := r.authorize(ctx, obj.ID, iam.ActionAuditLogEntryList); err != nil { + return nil, err + } + + pageOrderBy := page.OrderBy[coredata.AuditLogEntryOrderField]{ + Field: coredata.AuditLogEntryOrderFieldCreatedAt, + Direction: page.OrderDirectionDesc, + } + if orderBy != nil { + pageOrderBy = page.OrderBy[coredata.AuditLogEntryOrderField]{ + Field: orderBy.Field, + Direction: orderBy.Direction, + } + } + + c := cursor.NewCursor(first, after, last, before, pageOrderBy) + + coredataFilter := coredata.NewAuditLogEntryFilter() + if filter != nil { + if filter.Action != nil { + coredataFilter.WithAction(*filter.Action) + } + if filter.ActorID != nil { + coredataFilter.WithActorID(*filter.ActorID) + } + if filter.ResourceType != nil { + coredataFilter.WithResourceType(*filter.ResourceType) + } + if filter.ResourceID != nil { + coredataFilter.WithResourceID(*filter.ResourceID) + } + } + + p, err := r.iam.OrganizationService.ListAuditLogEntries(ctx, obj.ID, c, coredataFilter) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot list audit log entries", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return types.NewAuditLogEntryConnection(p, r, obj.ID, coredataFilter), nil +} + +// AuditLogEntry returns schema.AuditLogEntryResolver implementation. +func (r *Resolver) AuditLogEntry() schema.AuditLogEntryResolver { return &auditLogEntryResolver{r} } + +// AuditLogEntryConnection returns schema.AuditLogEntryConnectionResolver implementation. +func (r *Resolver) AuditLogEntryConnection() schema.AuditLogEntryConnectionResolver { + return &auditLogEntryConnectionResolver{r} +} + +type auditLogEntryResolver struct{ *Resolver } +type auditLogEntryConnectionResolver struct{ *Resolver } diff --git a/pkg/server/api/connect/v1/base.resolvers.go b/pkg/server/api/connect/v1/base.resolvers.go new file mode 100644 index 000000000..90529bc47 --- /dev/null +++ b/pkg/server/api/connect/v1/base.resolvers.go @@ -0,0 +1,809 @@ +package connect_v1 + +// This file will be automatically regenerated based on the schema, any resolver +// implementations +// will be copied through when generating and any unknown code will be moved to the end. +// Code generated by github.com/99designs/gqlgen version v0.17.87 + +import ( + "context" + "errors" + "fmt" + "strings" + "time" + + "github.com/99designs/gqlgen/graphql" + "github.com/vektah/gqlparser/v2/gqlerror" + "go.gearno.de/kit/log" + "go.probo.inc/probo/pkg/coredata" + "go.probo.inc/probo/pkg/gid" + "go.probo.inc/probo/pkg/iam" + "go.probo.inc/probo/pkg/mail" + "go.probo.inc/probo/pkg/server/api/authn" + "go.probo.inc/probo/pkg/server/api/authz" + "go.probo.inc/probo/pkg/server/api/connect/v1/schema" + "go.probo.inc/probo/pkg/server/api/connect/v1/types" + "go.probo.inc/probo/pkg/server/gqlutils" +) + +// SsoLoginURL is the resolver for the ssoLoginURL field. +func (r *identityResolver) SsoLoginURL(ctx context.Context, obj *types.Identity) (*string, error) { + if err := r.authorize(ctx, obj.ID, iam.ActionIdentityGet); err != nil { + return nil, err + } + + identity := authn.IdentityFromContext(ctx) + + count, err := r.iam.AccountService.CountSAMLConfigurationsForEmail(ctx, identity.EmailAddress) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot count SAML configurations for email", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + if count != 1 { + if count == 0 { + return nil, graphql.ErrorOnPath( + ctx, + fmt.Errorf("no SAML configuration for email"), + ) + } + + return nil, graphql.ErrorOnPath( + ctx, + fmt.Errorf("multiple SSO configurations found for this domain. Please use your organization-specific SSO login URL"), + ) + } + + samlConfigs, err := r.iam.AccountService.ListSAMLConfigurationsForEmail(ctx, identity.EmailAddress) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot list SAML configurations for email", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + if len(samlConfigs) == 0 { + r.logger.ErrorCtx(ctx, "cannot find SAML config") + return nil, gqlutils.NotFoundf(ctx, "cannot find SAML config") + } + samlConfig := samlConfigs[0] + + loginURL := r.SSOLoginURL(samlConfig.ID) + return &loginURL, nil +} + +// Permission is the resolver for the permission field. +func (r *identityResolver) Permission(ctx context.Context, obj *types.Identity, action string) (bool, error) { + return r.Resolver.Permission(ctx, obj, action) +} + +// SignIn is the resolver for the signIn field. +func (r *mutationResolver) SignIn(ctx context.Context, input types.SignInInput) (*types.SignInPayload, error) { + identity, err := r.iam.AuthService.CheckCredentials(ctx, input.Email, input.Password) + if err != nil { + var errInvalidPassword *iam.ErrInvalidPassword + if errors.As(err, &errInvalidPassword) { + return nil, gqlutils.Invalid(ctx, err) + } + + var errInvalidCredentials *iam.ErrInvalidCredentials + if errors.As(err, &errInvalidCredentials) { + return nil, &gqlerror.Error{ + Message: err.Error(), + Extensions: map[string]any{ + "code": "INVALID_CREDENTIALS", + }, + } + } + + r.logger.ErrorCtx(ctx, "cannot check credentials", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + session := authn.SessionFromContext(ctx) + + switch { + case session == nil: + var err error + session, err = r.iam.AuthService.OpenSessionWithPassword( + ctx, + identity.ID, + ) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot create session", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + case session.IdentityID != identity.ID: + if err := r.iam.SessionService.CloseSession(ctx, session.ID); err != nil { + r.logger.ErrorCtx(ctx, "cannot close session", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + session, err = r.iam.AuthService.OpenSessionWithPassword( + ctx, + identity.ID, + ) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot create session", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + } + + w := gqlutils.HTTPResponseWriterFromContext(ctx) + r.sessionCookie.Set(w, session) + + if input.OrganizationID != nil { + var err error + _, _, err = r.iam.SessionService.OpenPasswordChildSessionForOrganization(ctx, session.ID, *input.OrganizationID) + if err != nil { + // Here session middleware already took care of expired/nil root session so we only handle membership related errors + var errMembershipNotFound *iam.ErrMembershipNotFound + var errUserInactive *iam.ErrUserInactive + + if errors.As(err, &errMembershipNotFound) || errors.As(err, &errUserInactive) { + return nil, gqlutils.Forbiddenf(ctx, "forbidden") + } + + r.logger.ErrorCtx(ctx, "cannot assume organization", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + } + + return &types.SignInPayload{ + Identity: types.NewIdentity(identity), + Session: types.NewSession(session), + }, nil +} + +// SignUp is the resolver for the signUp field. +func (r *mutationResolver) SignUp(ctx context.Context, input types.SignUpInput) (*types.SignUpPayload, error) { + identity, session, err := r.iam.AuthService.CreateIdentityWithPassword( + ctx, + &iam.CreateIdentityWithPasswordRequest{ + Email: input.Email, + Password: input.Password, + FullName: input.FullName, + }, + ) + if err != nil { + var errIdentityAlreadyExists *iam.ErrIdentityAlreadyExists + if errors.As(err, &errIdentityAlreadyExists) { + return nil, gqlutils.Invalid(ctx, err) + } + + var errSignupDisabled *iam.ErrSignupDisabled + if errors.As(err, &errSignupDisabled) { + return nil, gqlutils.Forbidden(ctx, err) + } + + r.logger.ErrorCtx(ctx, "cannot create identity with password", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + w := gqlutils.HTTPResponseWriterFromContext(ctx) + r.sessionCookie.Set(w, session) + + return &types.SignUpPayload{ + Identity: types.NewIdentity(identity), + }, nil +} + +// SignOut is the resolver for the signOut field. +func (r *mutationResolver) SignOut(ctx context.Context) (*types.SignOutPayload, error) { + session := authn.SessionFromContext(ctx) + + err := r.iam.SessionService.CloseSession(ctx, session.ID) + if err != nil { + var ErrSessionNotFound *iam.ErrSessionNotFound + if errors.As(err, &ErrSessionNotFound) { + return &types.SignOutPayload{}, nil + } + + r.logger.ErrorCtx(ctx, "cannot close session", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + w := gqlutils.HTTPResponseWriterFromContext(ctx) + r.sessionCookie.Clear(w) + + return &types.SignOutPayload{Success: true}, nil +} + +// ActivateAccount is the resolver for the activateAccount field. +func (r *mutationResolver) ActivateAccount(ctx context.Context, input types.ActivateAccountInput) (*types.ActivateAccountPayload, error) { + session := authn.SessionFromContext(ctx) + + if session != nil { + // Sign out any other account before activating a new one + err := r.iam.SessionService.CloseSession(ctx, session.ID) + if err != nil { + var ErrSessionNotFound *iam.ErrSessionNotFound + if !errors.As(err, &ErrSessionNotFound) { + r.logger.ErrorCtx(ctx, "cannot close session", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + } + + w := gqlutils.HTTPResponseWriterFromContext(ctx) + r.sessionCookie.Clear(w) + } + + identity, user, err := r.iam.AuthService.ActivateAccount( + ctx, + &iam.ActivateAccountRequest{ + InvitationToken: input.Token, + }, + ) + if err != nil { + var ( + errInvalidToken *iam.ErrInvalidToken + errInvitationNotFound *iam.ErrInvitationNotFound + errInvitationExpired *iam.ErrInvitationExpired + + isInvalidErr = errors.As(err, &errInvalidToken) || + errors.As(err, &errInvitationNotFound) || + errors.As(err, &errInvitationExpired) + ) + + if isInvalidErr { + return nil, gqlutils.Invalid(ctx, err) + } + + if _, ok := errors.AsType[*iam.ErrInvitationAlreadyAccepted](err); ok { + return nil, gqlutils.AccountAlreadyActivated(ctx, err) + } + + r.logger.ErrorCtx(ctx, "cannot activate account from invitation", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + var ssoLoginURL *string + samlConfigs, err := r.iam.AccountService.ListSAMLConfigurationsForEmail(ctx, user.EmailAddress) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot list saml configurations", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + for _, samlConfig := range samlConfigs { + if samlConfig.OrganizationID != user.OrganizationID { + continue + } + + ssoLoginURL = new(r.SSOLoginURL(samlConfig.ID)) + } + + if ssoLoginURL != nil { + return &types.ActivateAccountPayload{ + CreatePasswordToken: nil, + SsoLoginURL: ssoLoginURL, + Profile: types.NewProfile(user), + }, nil + } + + var createPasswordToken *string + if identity.HashedPassword == nil { + token, err := r.iam.AuthService.GetResetPasswordToken(ctx, identity.EmailAddress) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot generate password create token", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + createPasswordToken = &token + } + + return &types.ActivateAccountPayload{ + CreatePasswordToken: createPasswordToken, + SsoLoginURL: nil, + Profile: types.NewProfile(user), + }, nil +} + +// ForgotPassword is the resolver for the forgotPassword field. +func (r *mutationResolver) ForgotPassword(ctx context.Context, input types.ForgotPasswordInput) (*types.ForgotPasswordPayload, error) { + err := r.iam.AuthService.SendPasswordResetInstructionByEmail( + ctx, + input.Email, + ) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot send password reset instruction by email", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.ForgotPasswordPayload{ + Success: true, + }, nil +} + +// ResetPassword is the resolver for the resetPassword field. +func (r *mutationResolver) ResetPassword(ctx context.Context, input types.ResetPasswordInput) (*types.ResetPasswordPayload, error) { + err := r.iam.AuthService.ResetPassword( + ctx, + &iam.ResetPasswordRequest{ + Token: input.Token, + Password: input.Password, + }, + ) + if err != nil { + var errInvalidToken *iam.ErrInvalidToken + if errors.As(err, &errInvalidToken) { + return nil, gqlutils.Invalid(ctx, err) + } + + r.logger.ErrorCtx(ctx, "cannot reset password", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.ResetPasswordPayload{ + Success: true, + }, nil +} + +// VerifyEmail is the resolver for the verifyEmail field. +func (r *mutationResolver) VerifyEmail(ctx context.Context, input types.VerifyEmailInput) (*types.VerifyEmailPayload, error) { + err := r.iam.AccountService.VerifyEmail(ctx, input.Token) + if err != nil { + var ( + errInvalidToken *iam.ErrInvalidToken + errIdentityNotFound *iam.ErrIdentityNotFound + errEmailAlreadyVerified *iam.ErrEmailAlreadyVerified + errEmailVerificationMismatch *iam.ErrEmailVerificationMismatch + + isInvalidErr = errors.As(err, &errInvalidToken) || + errors.As(err, &errEmailVerificationMismatch) + ) + + if isInvalidErr { + return nil, gqlutils.Invalid(ctx, err) + } + + if errors.As(err, &errEmailAlreadyVerified) { + return nil, gqlutils.Conflict(ctx, err) + } + + if errors.As(err, &errIdentityNotFound) { + return nil, gqlutils.NotFound(ctx, err) + } + + r.logger.ErrorCtx(ctx, "cannot verify email", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.VerifyEmailPayload{ + Success: true, + }, nil +} + +// ChangePassword is the resolver for the changePassword field. +func (r *mutationResolver) ChangePassword(ctx context.Context, input types.ChangePasswordInput) (*types.ChangePasswordPayload, error) { + identity := authn.IdentityFromContext(ctx) + + err := r.iam.AccountService.ChangePassword( + ctx, + identity.ID, + &iam.ChangePasswordRequest{ + CurrentPassword: input.CurrentPassword, + NewPassword: input.NewPassword, + }, + ) + if err != nil { + var ( + errInvalidPassword *iam.ErrInvalidPassword + errIdentityNotFound *iam.ErrIdentityNotFound + ) + + if errors.As(err, &errInvalidPassword) { + return nil, gqlutils.Invalid(ctx, err) + } + + if errors.As(err, &errIdentityNotFound) { + return nil, gqlutils.NotFound(ctx, err) + } + + r.logger.ErrorCtx(ctx, "cannot change password", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.ChangePasswordPayload{ + Success: true, + }, nil +} + +// ChangeEmail is the resolver for the changeEmail field. +func (r *mutationResolver) ChangeEmail(ctx context.Context, input types.ChangeEmailInput) (*types.ChangeEmailPayload, error) { + identity := authn.IdentityFromContext(ctx) + + err := r.iam.AccountService.ChangeEmail( + ctx, + identity.ID, + &iam.ChangeEmailRequest{ + NewEmail: input.NewEmail, + Password: input.Password, + }, + ) + if err != nil { + var ( + errInvalidPassword *iam.ErrInvalidPassword + errIdentityNotFound *iam.ErrIdentityNotFound + ) + + if errors.As(err, &errInvalidPassword) { + return nil, gqlutils.Invalid(ctx, err) + } + + if errors.As(err, &errIdentityNotFound) { + return nil, gqlutils.NotFound(ctx, err) + } + + r.logger.ErrorCtx(ctx, "cannot change email", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.ChangeEmailPayload{ + Success: true, + }, nil +} + +// AssumeOrganizationSession is the resolver for the assumeOrganizationSession field. +func (r *mutationResolver) AssumeOrganizationSession(ctx context.Context, input types.AssumeOrganizationSessionInput) (*types.AssumeOrganizationSessionPayload, error) { + rootSession := authn.SessionFromContext(ctx) + + childSession, membership, err := r.iam.SessionService.AssumeOrganizationSession(ctx, rootSession.ID, input.OrganizationID, input.Continue) + if err != nil { + var ( + errMembershipNotFound *iam.ErrMembershipNotFound + errPasswordAuthenticationRequired *iam.ErrPasswordAuthenticationRequired + errSAMLAuthenticationRequired *iam.ErrSAMLAuthenticationRequired + ) + + switch { + case errors.As(err, &errMembershipNotFound): + return nil, gqlutils.NotFound(ctx, err) + + case errors.As(err, &errPasswordAuthenticationRequired): + return &types.AssumeOrganizationSessionPayload{ + Result: types.PasswordRequired{ + Reason: types.ReauthenticationReason(errPasswordAuthenticationRequired.Reason), + }, + }, nil + + case errors.As(err, &errSAMLAuthenticationRequired): + return &types.AssumeOrganizationSessionPayload{ + Result: types.SAMLAuthenticationRequired{ + Reason: types.ReauthenticationReason(errSAMLAuthenticationRequired.Reason), + }, + }, nil + + default: + r.logger.ErrorCtx(ctx, "cannot assume organization session", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + } + + return &types.AssumeOrganizationSessionPayload{ + Result: types.OrganizationSessionCreated{ + Session: types.NewSession(childSession), + Membership: types.NewMembership(membership), + }, + }, nil +} + +// RevokeSession is the resolver for the revokeSession field. +func (r *mutationResolver) RevokeSession(ctx context.Context, input types.RevokeSessionInput) (*types.RevokeSessionPayload, error) { + if err := r.authorize(ctx, input.SessionID, iam.ActionSessionRevoke); err != nil { + return nil, err + } + + identity := authn.IdentityFromContext(ctx) + + err := r.iam.SessionService.RevokeSession(ctx, identity.ID, input.SessionID) + if err != nil { + var ErrSessionExpired *iam.ErrSessionExpired + if errors.As(err, &ErrSessionExpired) { + return &types.RevokeSessionPayload{Success: true}, nil + } + + r.logger.ErrorCtx(ctx, "cannot revoke session", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.RevokeSessionPayload{Success: true}, nil +} + +// RevokeAllSessions is the resolver for the revokeAllSessions field. +func (r *mutationResolver) RevokeAllSessions(ctx context.Context) (*types.RevokeAllSessionsPayload, error) { + if err := r.authorize(ctx, authn.SessionFromContext(ctx).ID, iam.ActionSessionRevokeAll); err != nil { + return nil, err + } + + session := authn.SessionFromContext(ctx) + + revokedCount, err := r.iam.SessionService.RevokeAllSessions(ctx, session.ID) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot revoke all sessions", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.RevokeAllSessionsPayload{RevokedCount: int(revokedCount)}, nil +} + +// LogoURL is the resolver for the logoUrl field. +func (r *organizationResolver) LogoURL(ctx context.Context, obj *types.Organization) (*string, error) { + if err := r.authorize(ctx, obj.ID, iam.ActionOrganizationGet, authz.WithSkipAssumptionCheck()); err != nil { + return nil, err + } + + presignedURL, err := r.iam.OrganizationService.GenerateLogoURL(ctx, obj.ID, 1*time.Hour) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot generate logo URL", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return presignedURL, nil +} + +// HorizontalLogoURL is the resolver for the horizontalLogoUrl field. +func (r *organizationResolver) HorizontalLogoURL(ctx context.Context, obj *types.Organization) (*string, error) { + if err := r.authorize(ctx, obj.ID, iam.ActionOrganizationGet); err != nil { + return nil, err + } + + presignedURL, err := r.iam.OrganizationService.GenerateHorizontalLogoURL(ctx, obj.ID, 1*time.Hour) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot generate horizontal logo URL", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return presignedURL, nil +} + +// Viewer is the resolver for the viewer field. +func (r *organizationResolver) Viewer(ctx context.Context, obj *types.Organization) (*types.Profile, error) { + if err := r.authorize(ctx, obj.ID, iam.ActionMembershipProfileGet); err != nil { + return nil, err + } + + identity := authn.IdentityFromContext(ctx) + + profile, err := r.iam.OrganizationService.GetProfileForIdentityAndOrganization(ctx, identity.ID, obj.ID) + if err != nil { + var errNotFound *iam.ErrProfileNotFound + if errors.As(err, &errNotFound) { + return nil, gqlutils.NotFound(ctx, err) + } + + r.logger.ErrorCtx(ctx, "cannot get profile", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return types.NewProfile(profile), nil +} + +// Permission is the resolver for the permission field. +func (r *organizationResolver) Permission(ctx context.Context, obj *types.Organization, action string) (bool, error) { + return r.Resolver.Permission(ctx, obj, action) +} + +// Node is the resolver for the node field. +func (r *queryResolver) Node(ctx context.Context, id gid.GID) (types.Node, error) { + var ( + loadNode func(ctx context.Context, id gid.GID) (types.Node, error) + action string + ) + + switch id.EntityType() { + case coredata.OrganizationEntityType: + action = iam.ActionOrganizationGet + loadNode = func(ctx context.Context, id gid.GID) (types.Node, error) { + organization, err := r.iam.OrganizationService.GetOrganization(ctx, id) + if err != nil { + return nil, err + } + return types.NewOrganization(organization), nil + } + case coredata.IdentityEntityType: + action = iam.ActionIdentityGet + loadNode = func(ctx context.Context, id gid.GID) (types.Node, error) { + identity, err := r.iam.AccountService.GetIdentity(ctx, id) + if err != nil { + return nil, err + } + + return types.NewIdentity(identity), nil + } + case coredata.SessionEntityType: + action = iam.ActionSessionGet + loadNode = func(ctx context.Context, id gid.GID) (types.Node, error) { + session, err := r.iam.GetSession(ctx, id) + if err != nil { + return nil, err + } + + 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.NewProfile(profile), nil + } + case coredata.MembershipEntityType: + action = iam.ActionMembershipGet + loadNode = func(ctx context.Context, id gid.GID) (types.Node, error) { + membership, err := r.iam.GetMembership(ctx, id) + if err != nil { + return nil, err + } + + return types.NewMembership(membership), nil + } + case coredata.InvitationEntityType: + action = iam.ActionInvitationGet + loadNode = func(ctx context.Context, id gid.GID) (types.Node, error) { + invitation, err := r.iam.GetInvitation(ctx, id) + if err != nil { + return nil, err + } + + return types.NewInvitation(invitation), nil + } + case coredata.SAMLConfigurationEntityType: + action = iam.ActionSAMLConfigurationGet + loadNode = func(ctx context.Context, id gid.GID) (types.Node, error) { + samlConfiguration, err := r.iam.GetSAMLconfiguration(ctx, id) + if err != nil { + return nil, err + } + + return types.NewSAMLConfiguration(samlConfiguration), nil + } + case coredata.PersonalAPIKeyEntityType: + action = iam.ActionPersonalAPIKeyGet + loadNode = func(ctx context.Context, id gid.GID) (types.Node, error) { + personalAPIKey, err := r.iam.GetPersonalAPIKey(ctx, id) + if err != nil { + return nil, err + } + return types.NewPersonalAPIKey(personalAPIKey), nil + } + case coredata.SCIMConfigurationEntityType: + action = iam.ActionSCIMConfigurationGet + loadNode = func(ctx context.Context, id gid.GID) (types.Node, error) { + scimConfiguration, err := r.iam.GetSCIMConfiguration(ctx, id) + if err != nil { + return nil, err + } + return types.NewSCIMConfiguration(scimConfiguration), nil + } + case coredata.SCIMEventEntityType: + action = iam.ActionSCIMEventGet + loadNode = func(ctx context.Context, id gid.GID) (types.Node, error) { + scimEvent, err := r.iam.GetSCIMEvent(ctx, id) + if err != nil { + return nil, err + } + return types.NewSCIMEvent(scimEvent), nil + } + default: + return nil, fmt.Errorf("unsupported entity type: %d", id.EntityType()) + } + + if err := r.authorize(ctx, id, action); err != nil { + return nil, err + } + + node, err := loadNode(ctx, id) + if err != nil { + var ( + errOrganizationNotFound *iam.ErrOrganizationNotFound + errIdentityNotFound *iam.ErrIdentityNotFound + errSessionNotFound *iam.ErrSessionNotFound + errProfileNotFound *iam.ErrProfileNotFound + errMembershipNotFound *iam.ErrMembershipNotFound + errInvitationNotFound *iam.ErrInvitationNotFound + + isNotFoundErr = errors.As(err, &errOrganizationNotFound) || + errors.As(err, &errIdentityNotFound) || + errors.As(err, &errSessionNotFound) || + errors.As(err, &errProfileNotFound) || + errors.As(err, &errMembershipNotFound) || + errors.As(err, &errInvitationNotFound) + ) + + if isNotFoundErr { + return nil, gqlutils.NotFound(ctx, err) + } + + r.logger.ErrorCtx(ctx, "cannot load node", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return node, nil +} + +// Viewer is the resolver for the viewer field. +func (r *queryResolver) Viewer(ctx context.Context) (*types.Identity, error) { + identity := authn.IdentityFromContext(ctx) + + return &types.Identity{ + ID: identity.ID, + Email: identity.EmailAddress, + EmailVerified: identity.EmailAddressVerified, + FullName: identity.FullName, + CreatedAt: identity.CreatedAt, + UpdatedAt: identity.UpdatedAt, + }, nil +} + +// SsoLoginURL is the resolver for the ssoLoginURL field. +func (r *queryResolver) SsoLoginURL(ctx context.Context, email mail.Addr) (*string, error) { + count, err := r.iam.AccountService.CountSAMLConfigurationsForEmail(ctx, email) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot count SAML configurations for email", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + if count != 1 { + if count == 0 { + return nil, graphql.ErrorOnPath( + ctx, + fmt.Errorf("no SAML configuration for email"), + ) + } + + return nil, graphql.ErrorOnPath( + ctx, + fmt.Errorf("multiple SSO configurations found for this domain. Please use your organization-specific SSO login URL"), + ) + } + + samlConfigs, err := r.iam.AccountService.ListSAMLConfigurationsForEmail(ctx, email) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot list SAML configurations for email", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + samlConfig := samlConfigs[0] + loginURL := r.SSOLoginURL(samlConfig.ID) + + return &loginURL, nil +} + +// OidcProviders is the resolver for the oidcProviders field. +func (r *queryResolver) OidcProviders(ctx context.Context) ([]*types.OIDCProviderInfo, error) { + providers := r.iam.OIDCService.EnabledProviders() + result := make([]*types.OIDCProviderInfo, 0, len(providers)) + + for _, p := range providers { + result = append(result, &types.OIDCProviderInfo{ + Name: strings.ToLower(p.String()), + LoginURL: r.baseURL.WithPath("/api/connect/v1/oidc/" + strings.ToLower(p.String()) + "/login").MustString(), + }) + } + + return result, nil +} + +// SignUpEnabled is the resolver for the signUpEnabled field. +func (r *queryResolver) SignUpEnabled(ctx context.Context) (bool, error) { + return r.iam.IsSignUpEnabled(), nil +} + +// Identity returns schema.IdentityResolver implementation. +func (r *Resolver) Identity() schema.IdentityResolver { return &identityResolver{r} } + +// Mutation returns schema.MutationResolver implementation. +func (r *Resolver) Mutation() schema.MutationResolver { return &mutationResolver{r} } + +// Organization returns schema.OrganizationResolver implementation. +func (r *Resolver) Organization() schema.OrganizationResolver { return &organizationResolver{r} } + +// Query returns schema.QueryResolver implementation. +func (r *Resolver) Query() schema.QueryResolver { return &queryResolver{r} } + +type identityResolver struct{ *Resolver } +type mutationResolver struct{ *Resolver } +type organizationResolver struct{ *Resolver } +type queryResolver struct{ *Resolver } diff --git a/pkg/server/api/connect/v1/gqlgen.yaml b/pkg/server/api/connect/v1/gqlgen.yaml index 5b7b4b926..80847b026 100644 --- a/pkg/server/api/connect/v1/gqlgen.yaml +++ b/pkg/server/api/connect/v1/gqlgen.yaml @@ -1,5 +1,5 @@ schema: - - "schema.graphql" + - "graphql/*.graphql" - "../../../gqlutils/directives/session/schema.graphql" exec: @@ -14,7 +14,7 @@ resolver: layout: "follow-schema" dir: "." package: "connect_v1" - filename_template: "v1_resolver.go" + filename_template: "{name}.resolvers.go" autobind: [] call_argument_directives_with_null: true diff --git a/pkg/server/api/connect/v1/graphql/audit_log.graphql b/pkg/server/api/connect/v1/graphql/audit_log.graphql new file mode 100644 index 000000000..72286a37c --- /dev/null +++ b/pkg/server/api/connect/v1/graphql/audit_log.graphql @@ -0,0 +1,83 @@ +extend type Organization { + auditLogEntries( + first: Int + after: CursorKey + last: Int + before: CursorKey + orderBy: AuditLogEntryOrder + filter: AuditLogEntryFilter + ): AuditLogEntryConnection! @goField(forceResolver: true) +} + +enum AuditLogActorType + @goModel( + model: "go.probo.inc/probo/pkg/coredata.AuditLogActorType" + ) { + USER + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.AuditLogActorTypeUser" + ) + API_KEY + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.AuditLogActorTypeAPIKey" + ) + SYSTEM + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.AuditLogActorTypeSystem" + ) +} + +enum AuditLogEntryOrderField + @goModel( + model: "go.probo.inc/probo/pkg/coredata.AuditLogEntryOrderField" + ) { + CREATED_AT + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.AuditLogEntryOrderFieldCreatedAt" + ) +} + +input AuditLogEntryOrder + @goModel( + model: "go.probo.inc/probo/pkg/server/api/connect/v1/types.AuditLogEntryOrderBy" + ) { + field: AuditLogEntryOrderField! + direction: OrderDirection! +} + +input AuditLogEntryFilter { + action: String + actorId: ID + resourceType: String + resourceId: ID +} + +type AuditLogEntry implements Node { + id: ID! + organization: Organization @goField(forceResolver: true) + actorId: ID! + actorType: AuditLogActorType! + action: String! + resourceType: String! + resourceId: ID! + metadata: String + createdAt: Datetime! + + permission(action: String!): Boolean! + @goField(forceResolver: true) + @session(required: PRESENT) +} + +type AuditLogEntryConnection + @goModel( + model: "go.probo.inc/probo/pkg/server/api/connect/v1/types.AuditLogEntryConnection" + ) { + edges: [AuditLogEntryEdge!]! + pageInfo: PageInfo! + totalCount: Int! @goField(forceResolver: true) +} + +type AuditLogEntryEdge { + cursor: CursorKey! + node: AuditLogEntry! +} diff --git a/pkg/server/api/connect/v1/graphql/base.graphql b/pkg/server/api/connect/v1/graphql/base.graphql new file mode 100644 index 000000000..d1e822fc5 --- /dev/null +++ b/pkg/server/api/connect/v1/graphql/base.graphql @@ -0,0 +1,121 @@ +directive @goField( + forceResolver: Boolean + name: String + omittable: Boolean +) on INPUT_FIELD_DEFINITION | FIELD_DEFINITION + +directive @goModel( + model: String + models: [String!] +) on OBJECT | INPUT_OBJECT | SCALAR | ENUM | INTERFACE | UNION + +directive @goEnum(value: String) on ENUM_VALUE + +scalar CursorKey +scalar Datetime +scalar Upload +scalar EmailAddr + +enum OrderDirection + @goModel(model: "go.probo.inc/probo/pkg/page.OrderDirection") { + ASC @goEnum(value: "go.probo.inc/probo/pkg/page.OrderDirectionAsc") + DESC @goEnum(value: "go.probo.inc/probo/pkg/page.OrderDirectionDesc") +} + +interface Node { + id: ID! +} + +type PageInfo { + hasNextPage: Boolean! + hasPreviousPage: Boolean! + startCursor: CursorKey + endCursor: CursorKey +} + +type OIDCProviderInfo { + name: String! + loginURL: String! +} + +enum ReauthenticationReason { + SESSION_EXPIRED + SENSITIVE_ACTION + POLICY_REQUIREMENT +} + +type Query { + node(id: ID!): Node @session(required: PRESENT) + viewer: Identity @session(required: PRESENT) + ssoLoginURL(email: EmailAddr!): String + @goField(forceResolver: true) + @session(required: OPTIONAL) + oidcProviders: [OIDCProviderInfo!]! + @goField(forceResolver: true) + @session(required: OPTIONAL) + signUpEnabled: Boolean! + @goField(forceResolver: true) + @session(required: OPTIONAL) +} + +type Identity implements Node { + id: ID! + email: EmailAddr! + fullName: String! + emailVerified: Boolean! + createdAt: Datetime! + updatedAt: Datetime! + + ssoLoginURL: String + @goField(forceResolver: true) + @session(required: PRESENT) + + permission(action: String!): Boolean! + @goField(forceResolver: true) + @session(required: PRESENT) +} + +type Organization implements Node { + id: ID! + name: String! + logoUrl: String @goField(forceResolver: true) + horizontalLogoUrl: String @goField(forceResolver: true) + email: String + description: String + websiteUrl: String + headquarterAddress: String + createdAt: Datetime! + updatedAt: Datetime! + + viewer: Profile @goField(forceResolver: true) + + permission(action: String!): Boolean! + @goField(forceResolver: true) + @session(required: PRESENT) +} + +type Mutation { + signIn(input: SignInInput!): SignInPayload @session(required: OPTIONAL) + signUp(input: SignUpInput!): SignUpPayload @session(required: NONE) + signOut: SignOutPayload @session(required: PRESENT) + activateAccount( + input: ActivateAccountInput! + ): ActivateAccountPayload @session(required: OPTIONAL) + forgotPassword(input: ForgotPasswordInput!): ForgotPasswordPayload + @session(required: NONE) + resetPassword(input: ResetPasswordInput!): ResetPasswordPayload + @session(required: NONE) + verifyEmail(input: VerifyEmailInput!): VerifyEmailPayload + @session(required: OPTIONAL) + changePassword(input: ChangePasswordInput!): ChangePasswordPayload + @session(required: PRESENT) + changeEmail(input: ChangeEmailInput!): ChangeEmailPayload + @session(required: PRESENT) + assumeOrganizationSession( + input: AssumeOrganizationSessionInput! + ): AssumeOrganizationSessionPayload @session(required: PRESENT) + + revokeSession(input: RevokeSessionInput!): RevokeSessionPayload! + @session(required: PRESENT) + revokeAllSessions: RevokeAllSessionsPayload @session(required: PRESENT) +} diff --git a/pkg/server/api/connect/v1/graphql/organization.graphql b/pkg/server/api/connect/v1/graphql/organization.graphql new file mode 100644 index 000000000..c395338f1 --- /dev/null +++ b/pkg/server/api/connect/v1/graphql/organization.graphql @@ -0,0 +1,56 @@ +extend type Mutation { + createOrganization( + input: CreateOrganizationInput! + ): CreateOrganizationPayload @session(required: PRESENT) + updateOrganization( + input: UpdateOrganizationInput! + ): UpdateOrganizationPayload @session(required: PRESENT) + deleteOrganization( + input: DeleteOrganizationInput! + ): DeleteOrganizationPayload @session(required: PRESENT) + deleteOrganizationHorizontalLogo( + input: DeleteOrganizationHorizontalLogoInput! + ): DeleteOrganizationHorizontalLogoPayload @session(required: PRESENT) +} + +input CreateOrganizationInput { + name: String! + logoFile: Upload + horizontalLogoFile: Upload +} + +input UpdateOrganizationInput { + organizationId: ID! + name: String + logoFile: Upload + horizontalLogoFile: Upload + description: String @goField(omittable: true) + websiteUrl: String @goField(omittable: true) + email: String @goField(omittable: true) + headquarterAddress: String @goField(omittable: true) +} + +input DeleteOrganizationInput { + organizationId: ID! +} + +input DeleteOrganizationHorizontalLogoInput { + organizationId: ID! +} + +type CreateOrganizationPayload { + organization: Organization + profile: Profile! +} + +type UpdateOrganizationPayload { + organization: Organization +} + +type DeleteOrganizationPayload { + deletedOrganizationId: ID! +} + +type DeleteOrganizationHorizontalLogoPayload { + organization: Organization! +} diff --git a/pkg/server/api/connect/v1/graphql/personal_api_key.graphql b/pkg/server/api/connect/v1/graphql/personal_api_key.graphql new file mode 100644 index 000000000..f47745020 --- /dev/null +++ b/pkg/server/api/connect/v1/graphql/personal_api_key.graphql @@ -0,0 +1,63 @@ +extend type Identity { + personalAPIKeys( + first: Int + after: CursorKey + last: Int + before: CursorKey + ): PersonalAPIKeyConnection @goField(forceResolver: true) +} + +type PersonalAPIKey implements Node { + id: ID! + name: String! + expiresAt: Datetime! + lastUsedAt: Datetime + createdAt: Datetime! + + token: String @goField(forceResolver: true) + + permission(action: String!): Boolean! + @goField(forceResolver: true) + @session(required: PRESENT) +} + +type PersonalAPIKeyConnection + @goModel( + model: "go.probo.inc/probo/pkg/server/api/connect/v1/types.PersonalAPIKeyConnection" + ) { + edges: [PersonalAPIKeyEdge!]! + pageInfo: PageInfo! + totalCount: Int @goField(forceResolver: true) +} + +type PersonalAPIKeyEdge { + node: PersonalAPIKey! + cursor: CursorKey! +} + +extend type Mutation { + createPersonalAPIKey( + input: CreatePersonalAPIKeyInput! + ): CreatePersonalAPIKeyPayload @session(required: PRESENT) + revokePersonalAPIKey( + input: RevokePersonalAPIKeyInput! + ): RevokePersonalAPIKeyPayload @session(required: PRESENT) +} + +input CreatePersonalAPIKeyInput { + name: String! + expiresAt: Datetime! +} + +input RevokePersonalAPIKeyInput { + personalAPIKeyId: ID! +} + +type CreatePersonalAPIKeyPayload { + personalAPIKeyEdge: PersonalAPIKeyEdge! + token: String! +} + +type RevokePersonalAPIKeyPayload { + personalAPIKeyId: ID! +} diff --git a/pkg/server/api/connect/v1/graphql/profile.graphql b/pkg/server/api/connect/v1/graphql/profile.graphql new file mode 100644 index 000000000..f27688ebc --- /dev/null +++ b/pkg/server/api/connect/v1/graphql/profile.graphql @@ -0,0 +1,271 @@ +extend type Identity { + profiles( + first: Int + after: CursorKey + last: Int + before: CursorKey + orderBy: ProfileOrder + filter: ProfileFilter + ): ProfileConnection @goField(forceResolver: true) +} + +extend type Organization { + profiles( + first: Int + after: CursorKey + last: Int + before: CursorKey + orderBy: ProfileOrder + ): ProfileConnection @goField(forceResolver: true) +} + +type Profile implements Node { + id: ID! + fullName: String! + emailAddress: EmailAddr! + source: String! + state: ProfileState! + additionalEmailAddresses: [EmailAddr!]! + kind: String + position: String + contractStartDate: Datetime + contractEndDate: Datetime + createdAt: Datetime! + updatedAt: Datetime! + + identity: Identity @goField(forceResolver: true) + organization: Organization @goField(forceResolver: true) + membership: Membership @goField(forceResolver: true) + pendingInvitations( + first: Int + after: CursorKey + last: Int + before: CursorKey + orderBy: InvitationOrder + ): InvitationConnection @goField(forceResolver: true) + + permission(action: String!): Boolean! + @goField(forceResolver: true) + @session(required: PRESENT) +} + +enum ProfileState + @goModel(model: "go.probo.inc/probo/pkg/coredata.ProfileState") { + ACTIVE @goEnum(value: "go.probo.inc/probo/pkg/coredata.ProfileStateActive") + INACTIVE + @goEnum(value: "go.probo.inc/probo/pkg/coredata.ProfileStateInactive") +} + +enum ProfileSource + @goModel(model: "go.probo.inc/probo/pkg/coredata.ProfileSource") { + MANUAL + @goEnum(value: "go.probo.inc/probo/pkg/coredata.ProfileSourceManual") + SAML @goEnum(value: "go.probo.inc/probo/pkg/coredata.ProfileSourceSAML") + SCIM @goEnum(value: "go.probo.inc/probo/pkg/coredata.ProfileSourceSCIM") +} + +enum MembershipRole + @goModel(model: "go.probo.inc/probo/pkg/coredata.MembershipRole") { + OWNER @goEnum(value: "go.probo.inc/probo/pkg/coredata.MembershipRoleOwner") + ADMIN @goEnum(value: "go.probo.inc/probo/pkg/coredata.MembershipRoleAdmin") + EMPLOYEE + @goEnum(value: "go.probo.inc/probo/pkg/coredata.MembershipRoleEmployee") + VIEWER @goEnum(value: "go.probo.inc/probo/pkg/coredata.MembershipRoleViewer") + AUDITOR + @goEnum(value: "go.probo.inc/probo/pkg/coredata.MembershipRoleAuditor") +} + +type Membership implements Node { + id: ID! + createdAt: Datetime! + role: MembershipRole! + + lastSession: Session @goField(forceResolver: true) + + permission(action: String!): Boolean! + @goField(forceResolver: true) + @session(required: PRESENT) +} + +type Invitation implements Node { + id: ID! + expiresAt: Datetime! + acceptedAt: Datetime + createdAt: Datetime! + status: InvitationStatus! + + permission(action: String!): Boolean! + @goField(forceResolver: true) + @session(required: PRESENT) +} + +enum InvitationStatus + @goModel(model: "go.probo.inc/probo/pkg/coredata.InvitationStatus") { + PENDING + @goEnum(value: "go.probo.inc/probo/pkg/coredata.InvitationStatusPending") + ACCEPTED + @goEnum(value: "go.probo.inc/probo/pkg/coredata.InvitationStatusAccepted") + EXPIRED + @goEnum(value: "go.probo.inc/probo/pkg/coredata.InvitationStatusExpired") +} + +enum InvitationOrderField + @goModel(model: "go.probo.inc/probo/pkg/coredata.InvitationOrderField") { + CREATED_AT + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.InvitationOrderFieldCreatedAt" + ) +} + +input InvitationOrder + @goModel( + model: "go.probo.inc/probo/pkg/server/api/connect/v1/types.InvitationOrderBy" + ) { + direction: OrderDirection! + field: InvitationOrderField! +} + +type InvitationConnection + @goModel( + model: "go.probo.inc/probo/pkg/server/api/connect/v1/types.InvitationConnection" + ) { + edges: [InvitationEdge!]! + pageInfo: PageInfo! +} + +type InvitationEdge { + node: Invitation! + cursor: CursorKey! +} + +enum ProfileOrderField + @goModel(model: "go.probo.inc/probo/pkg/coredata.MembershipProfileOrderField") { + FULL_NAME + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.MembershipProfileOrderFieldFullName" + ) + CREATED_AT + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.MembershipProfileOrderFieldCreatedAt" + ) + KIND @goEnum(value: "go.probo.inc/probo/pkg/coredata.MembershipProfileOrderFieldKind") + ORGANIZATION_NAME + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.MembershipProfileOrderFieldOrganizationName" + ) + STATE + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.MembershipProfileOrderFieldState" + ) +} + +input ProfileFilter { + excludeContractEnded: Boolean + state: ProfileState +} + +input ProfileOrder + @goModel( + model: "go.probo.inc/probo/pkg/server/api/connect/v1/types.ProfileOrderBy" + ) { + direction: OrderDirection! + field: ProfileOrderField! +} + +type ProfileConnection + @goModel( + model: "go.probo.inc/probo/pkg/server/api/connect/v1/types.ProfileConnection" + ) { + totalCount: Int @goField(forceResolver: true) + edges: [ProfileEdge!]! + pageInfo: PageInfo! +} + +type ProfileEdge { + cursor: CursorKey! + node: Profile! +} + +extend type Mutation { + createUser(input: CreateUserInput!): CreateUserPayload + @session(required: PRESENT) + inviteUser(input: InviteUserInput!): InviteUserPayload + @session(required: PRESENT) + deactivateUser(input: DeactivateUserInput!): DeactivateUserPayload + updateUser(input: UpdateUserInput!): UpdateUserPayload! + updateMembership(input: UpdateMembershipInput!): UpdateMembershipPayload! + removeUser(input: RemoveUserInput!): RemoveUserPayload + @session(required: PRESENT) +} + +input CreateUserInput { + organizationId: ID! + fullName: String! + emailAddress: EmailAddr! + role: MembershipRole! + additionalEmailAddresses: [EmailAddr!] + kind: String + position: String + contractStartDate: Datetime @goField(omittable: true) + contractEndDate: Datetime @goField(omittable: true) +} + +input InviteUserInput { + organizationId: ID! + profileId: ID! +} + +input ActivateUserInput { + organizationId: ID! + profileId: ID! +} + +input DeactivateUserInput { + organizationId: ID! + profileId: ID! +} + +input UpdateUserInput { + id: ID! + fullName: String! + additionalEmailAddresses: [EmailAddr!] + kind: String + position: String + contractStartDate: Datetime @goField(omittable: true) + contractEndDate: Datetime @goField(omittable: true) +} + +input UpdateMembershipInput { + organizationId: ID! + membershipId: ID! + role: MembershipRole! +} + +input RemoveUserInput { + organizationId: ID! + profileId: ID! +} + +type CreateUserPayload { + profileEdge: ProfileEdge! +} + +type InviteUserPayload { + invitationEdge: InvitationEdge! +} + +type DeactivateUserPayload { + success: Boolean! +} + +type UpdateUserPayload { + profile: Profile! +} + +type UpdateMembershipPayload { + membership: Membership! +} + +type RemoveUserPayload { + deletedProfileId: ID! +} diff --git a/pkg/server/api/connect/v1/graphql/saml.graphql b/pkg/server/api/connect/v1/graphql/saml.graphql new file mode 100644 index 000000000..7a46c4a09 --- /dev/null +++ b/pkg/server/api/connect/v1/graphql/saml.graphql @@ -0,0 +1,119 @@ +extend type Organization { + samlConfigurations( + first: Int + after: CursorKey + last: Int + before: CursorKey + ): SAMLConfigurationConnection @goField(forceResolver: true) +} + +type SAMLConfiguration implements Node { + id: ID! + emailDomain: String! + enforcementPolicy: SAMLEnforcementPolicy! + domainVerifiedAt: Datetime + domainVerificationToken: String + idpEntityId: String! + idpSsoUrl: String! + idpCertificate: String! + autoSignupEnabled: Boolean! + createdAt: Datetime! + updatedAt: Datetime! + testLoginUrl: String! @goField(forceResolver: true) + attributeMappings: SAMLAttributeMappings! + + permission(action: String!): Boolean! + @goField(forceResolver: true) + @session(required: PRESENT) +} + +type SAMLAttributeMappings { + email: String! + firstName: String! + lastName: String! + role: String! +} + +enum SAMLEnforcementPolicy + @goModel(model: "go.probo.inc/probo/pkg/coredata.SAMLEnforcementPolicy") { + OFF @goEnum(value: "go.probo.inc/probo/pkg/coredata.SAMLEnforcementPolicyOff") + OPTIONAL + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.SAMLEnforcementPolicyOptional" + ) + REQUIRED + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.SAMLEnforcementPolicyRequired" + ) +} + +type SAMLConfigurationConnection + @goModel( + model: "go.probo.inc/probo/pkg/server/api/connect/v1/types.SAMLConfigurationConnection" + ) { + edges: [SAMLConfigurationEdge!]! + pageInfo: PageInfo! + totalCount: Int @goField(forceResolver: true) +} + +type SAMLConfigurationEdge { + node: SAMLConfiguration! + cursor: CursorKey! +} + +extend type Mutation { + createSAMLConfiguration( + input: CreateSAMLConfigurationInput! + ): CreateSAMLConfigurationPayload @session(required: PRESENT) + updateSAMLConfiguration( + input: UpdateSAMLConfigurationInput! + ): UpdateSAMLConfigurationPayload @session(required: PRESENT) + deleteSAMLConfiguration( + input: DeleteSAMLConfigurationInput! + ): DeleteSAMLConfigurationPayload @session(required: PRESENT) +} + +input CreateSAMLConfigurationInput { + organizationId: ID! + emailDomain: String! + idpEntityId: String! + idpSsoUrl: String! + idpCertificate: String! + autoSignupEnabled: Boolean! + attributeMappings: SAMLAttributeMappingsInput +} + +input SAMLAttributeMappingsInput { + email: String + firstName: String + lastName: String + role: String +} + +input UpdateSAMLConfigurationInput { + organizationId: ID! + samlConfigurationId: ID! + idpEntityId: String + idpSsoUrl: String + idpCertificate: String + autoSignupEnabled: Boolean + enforcementPolicy: SAMLEnforcementPolicy! + attributeMappings: SAMLAttributeMappingsInput +} + +input DeleteSAMLConfigurationInput { + organizationId: ID! + samlConfigurationId: ID! +} + +type CreateSAMLConfigurationPayload { + samlConfigurationEdge: SAMLConfigurationEdge! +} + +type UpdateSAMLConfigurationPayload { + samlConfiguration: SAMLConfiguration +} + +type DeleteSAMLConfigurationPayload { + deletedSamlConfigurationId: ID! +} diff --git a/pkg/server/api/connect/v1/graphql/scim.graphql b/pkg/server/api/connect/v1/graphql/scim.graphql new file mode 100644 index 000000000..a8eb7b4d0 --- /dev/null +++ b/pkg/server/api/connect/v1/graphql/scim.graphql @@ -0,0 +1,182 @@ +extend type Organization { + scimConfiguration: SCIMConfiguration @goField(forceResolver: true) + scimBridgeTypes: [SCIMBridgeTypeInfo!]! @goField(forceResolver: true) +} + +type SCIMConfiguration implements Node { + id: ID! + endpointUrl: String! @goField(forceResolver: true) + createdAt: Datetime! + updatedAt: Datetime! + organization: Organization @goField(forceResolver: true) + + bridge: SCIMBridge @goField(forceResolver: true) + + events( + first: Int + after: CursorKey + last: Int + before: CursorKey + orderBy: SCIMEventOrder + ): SCIMEventConnection @goField(forceResolver: true) + + permission(action: String!): Boolean! + @goField(forceResolver: true) + @session(required: PRESENT) +} + +type SCIMBridge implements Node { + id: ID! + state: SCIMBridgeState! + scimConfiguration: SCIMConfiguration @goField(forceResolver: true) + connector: Connector @goField(forceResolver: true) + type: SCIMBridgeType! + excludedUserNames: [String!]! + createdAt: Datetime! + updatedAt: Datetime! + + permission(action: String!): Boolean! + @goField(forceResolver: true) + @session(required: PRESENT) +} + +type Connector implements Node { + id: ID! + provider: ConnectorProvider! + createdAt: Datetime! + updatedAt: Datetime! + + permission(action: String!): Boolean! + @goField(forceResolver: true) + @session(required: PRESENT) +} + +enum ConnectorProvider + @goModel(model: "go.probo.inc/probo/pkg/coredata.ConnectorProvider") { + SLACK @goEnum(value: "go.probo.inc/probo/pkg/coredata.ConnectorProviderSlack") + GOOGLE_WORKSPACE + @goEnum(value: "go.probo.inc/probo/pkg/coredata.ConnectorProviderGoogleWorkspace") + BREX @goEnum(value: "go.probo.inc/probo/pkg/coredata.ConnectorProviderBrex") + TALLY @goEnum(value: "go.probo.inc/probo/pkg/coredata.ConnectorProviderTally") + CLOUDFLARE + @goEnum(value: "go.probo.inc/probo/pkg/coredata.ConnectorProviderCloudflare") +} + +enum SCIMBridgeType + @goModel(model: "go.probo.inc/probo/pkg/coredata.SCIMBridgeType") { + GOOGLE_WORKSPACE @goEnum(value: "go.probo.inc/probo/pkg/coredata.SCIMBridgeTypeGoogleWorkspace") +} + +type SCIMBridgeTypeInfo { + type: SCIMBridgeType! + oauth2Scopes: [String!]! +} + +enum SCIMBridgeState + @goModel(model: "go.probo.inc/probo/pkg/coredata.SCIMBridgeState") { + PENDING @goEnum(value: "go.probo.inc/probo/pkg/coredata.SCIMBridgeStatePending") + ACTIVE @goEnum(value: "go.probo.inc/probo/pkg/coredata.SCIMBridgeStateActive") + FAILED @goEnum(value: "go.probo.inc/probo/pkg/coredata.SCIMBridgeStateFailed") +} + +type SCIMEvent implements Node { + id: ID! + method: String! + path: String! + statusCode: Int! + requestBody: String + responseBody: String + errorMessage: String + userName: String! + ipAddress: String! + createdAt: Datetime! + + permission(action: String!): Boolean! + @goField(forceResolver: true) + @session(required: PRESENT) +} + +enum SCIMEventOrderField + @goModel(model: "go.probo.inc/probo/pkg/coredata.SCIMEventOrderField") { + CREATED_AT + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.SCIMEventOrderFieldCreatedAt" + ) +} + +input SCIMEventOrder + @goModel( + model: "go.probo.inc/probo/pkg/server/api/connect/v1/types.SCIMEventOrderBy" + ) { + direction: OrderDirection! + field: SCIMEventOrderField! +} + +type SCIMEventConnection + @goModel( + model: "go.probo.inc/probo/pkg/server/api/connect/v1/types.SCIMEventConnection" + ) { + edges: [SCIMEventEdge!]! + pageInfo: PageInfo! + totalCount: Int @goField(forceResolver: true) +} + +type SCIMEventEdge { + node: SCIMEvent! + cursor: CursorKey! +} + +extend type Mutation { + createSCIMConfiguration( + input: CreateSCIMConfigurationInput! + ): CreateSCIMConfigurationPayload @session(required: PRESENT) + deleteSCIMConfiguration( + input: DeleteSCIMConfigurationInput! + ): DeleteSCIMConfigurationPayload @session(required: PRESENT) + regenerateSCIMToken( + input: RegenerateSCIMTokenInput! + ): RegenerateSCIMTokenPayload @session(required: PRESENT) + updateSCIMBridge( + input: UpdateSCIMBridgeInput! + ): UpdateSCIMBridgePayload @session(required: PRESENT) +} + +input CreateSCIMConfigurationInput { + organizationId: ID! + connectorId: ID +} + +input DeleteSCIMConfigurationInput { + organizationId: ID! + scimConfigurationId: ID! +} + +input RegenerateSCIMTokenInput { + organizationId: ID! + scimConfigurationId: ID! +} + +input UpdateSCIMBridgeInput { + organizationId: ID! + scimBridgeId: ID! + excludedUserNames: [String!]! +} + +type CreateSCIMConfigurationPayload { + scimConfiguration: SCIMConfiguration! + scimBridge: SCIMBridge + token: String! +} + +type DeleteSCIMConfigurationPayload { + deletedScimConfigurationId: ID! +} + +type RegenerateSCIMTokenPayload { + scimConfiguration: SCIMConfiguration! + token: String! +} + +type UpdateSCIMBridgePayload { + scimBridge: SCIMBridge! +} diff --git a/pkg/server/api/connect/v1/graphql/session.graphql b/pkg/server/api/connect/v1/graphql/session.graphql new file mode 100644 index 000000000..29b04da72 --- /dev/null +++ b/pkg/server/api/connect/v1/graphql/session.graphql @@ -0,0 +1,169 @@ +enum SessionOrderField + @goModel(model: "go.probo.inc/probo/pkg/coredata.SessionOrderField") { + CREATED_AT + @goEnum(value: "go.probo.inc/probo/pkg/coredata.SessionOrderFieldCreatedAt") + EXPIRED_AT + @goEnum(value: "go.probo.inc/probo/pkg/coredata.SessionOrderFieldExpiredAt") + UPDATED_AT + @goEnum(value: "go.probo.inc/probo/pkg/coredata.SessionOrderFieldUpdatedAt") +} + +input SessionOrder { + direction: OrderDirection! + field: SessionOrderField! +} + +extend type Identity { + sessions( + first: Int + after: CursorKey + last: Int + before: CursorKey + orderBy: SessionOrder + ): SessionConnection @goField(forceResolver: true) +} + +type Session implements Node { + id: ID! + identity: Identity @goField(forceResolver: true) + ipAddress: String! + userAgent: String! + updatedAt: Datetime! + createdAt: Datetime! + expiresAt: Datetime! + + permission(action: String!): Boolean! + @goField(forceResolver: true) + @session(required: PRESENT) +} + +type SessionConnection + @goModel( + model: "go.probo.inc/probo/pkg/server/api/connect/v1/types.SessionConnection" + ) { + edges: [SessionEdge!]! + pageInfo: PageInfo! + totalCount: Int @goField(forceResolver: true) +} + +type SessionEdge { + node: Session! + cursor: CursorKey! +} + +input SignInInput { + organizationId: ID + email: EmailAddr! + password: String! +} + +input SignUpInput { + email: EmailAddr! + password: String! + fullName: String! +} + +input ActivateAccountInput { + token: String! +} + +input ForgotPasswordInput { + email: EmailAddr! +} + +input ResetPasswordInput { + token: String! + password: String! +} + +input VerifyEmailInput { + token: String! +} + +input ChangePasswordInput { + currentPassword: String! + newPassword: String! +} + +input ChangeEmailInput { + newEmail: EmailAddr! + password: String! +} + +input AssumeOrganizationSessionInput { + organizationId: ID! + continue: String! +} + +input RevokeSessionInput { + sessionId: ID! +} + +type SignInPayload { + identity: Identity + session: Session +} + +type SignUpPayload { + identity: Identity +} + +type SignOutPayload { + success: Boolean! +} + +type ActivateAccountPayload { + createPasswordToken: String + ssoLoginUrl: String + profile: Profile +} + +type ForgotPasswordPayload { + success: Boolean! +} + +type ResetPasswordPayload { + success: Boolean! +} + +type VerifyEmailPayload { + success: Boolean! +} + +type ChangePasswordPayload { + success: Boolean! +} + +type ChangeEmailPayload { + success: Boolean! +} + +union AssumeOrganizationSessionResult = + | OrganizationSessionCreated + | PasswordRequired + | SAMLAuthenticationRequired + +type OrganizationSessionCreated { + session: Session! + membership: Membership! +} + +type PasswordRequired { + reason: ReauthenticationReason! +} + +type SAMLAuthenticationRequired { + reason: ReauthenticationReason! +} + +type AssumeOrganizationSessionPayload { + result: AssumeOrganizationSessionResult! +} + +type RevokeSessionPayload { + success: Boolean! +} + +type RevokeAllSessionsPayload { + revokedCount: Int! +} diff --git a/pkg/server/api/connect/v1/organization.resolvers.go b/pkg/server/api/connect/v1/organization.resolvers.go new file mode 100644 index 000000000..d8e31cbe6 --- /dev/null +++ b/pkg/server/api/connect/v1/organization.resolvers.go @@ -0,0 +1,150 @@ +package connect_v1 + +// This file will be automatically regenerated based on the schema, any resolver +// implementations +// will be copied through when generating and any unknown code will be moved to the end. +// Code generated by github.com/99designs/gqlgen version v0.17.87 + +import ( + "context" + "errors" + "fmt" + + "go.gearno.de/kit/log" + "go.probo.inc/probo/pkg/coredata" + "go.probo.inc/probo/pkg/iam" + "go.probo.inc/probo/pkg/server/api/authn" + "go.probo.inc/probo/pkg/server/api/connect/v1/types" + "go.probo.inc/probo/pkg/server/gqlutils" +) + +// CreateOrganization is the resolver for the createOrganization field. +func (r *mutationResolver) CreateOrganization(ctx context.Context, input types.CreateOrganizationInput) (*types.CreateOrganizationPayload, error) { + identity := authn.IdentityFromContext(ctx) + + // FIXME check email domain and related IDP config + // if ok := r.authorize(ctx, identity.ID, iam.ActionOrganizationCreate); !ok { + // return nil, nil + // } + + var ( + logoFile *iam.UploadedFile + horizontalLogoFile *iam.UploadedFile + ) + + if input.LogoFile != nil { + logoFile = &iam.UploadedFile{ + Content: input.LogoFile.File, + Filename: input.LogoFile.Filename, + ContentType: input.LogoFile.ContentType, + Size: input.LogoFile.Size, + } + } + + if input.HorizontalLogoFile != nil { + horizontalLogoFile = &iam.UploadedFile{ + Content: input.HorizontalLogoFile.File, + Filename: input.HorizontalLogoFile.Filename, + ContentType: input.HorizontalLogoFile.ContentType, + Size: input.HorizontalLogoFile.Size, + } + } + organization, profile, err := r.iam.OrganizationService.CreateOrganization( + ctx, + identity.ID, + &iam.CreateOrganizationRequest{ + Name: input.Name, + LogoFile: logoFile, + HorizontalLogoFile: horizontalLogoFile, + }, + ) + if err != nil { + if errors.Is(err, coredata.ErrResourceAlreadyExists) { + return nil, gqlutils.Conflict(ctx, err) + } + + r.logger.ErrorCtx(ctx, "cannot create organization", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.CreateOrganizationPayload{ + Organization: types.NewOrganization(organization), + Profile: types.NewProfile(profile), + }, nil +} + +// UpdateOrganization is the resolver for the updateOrganization field. +func (r *mutationResolver) UpdateOrganization(ctx context.Context, input types.UpdateOrganizationInput) (*types.UpdateOrganizationPayload, error) { + if err := r.authorize(ctx, input.OrganizationID, iam.ActionOrganizationUpdate); err != nil { + return nil, err + } + + req := &iam.UpdateOrganizationRequest{ + Name: input.Name, + Description: gqlutils.UnwrapOmittable(input.Description), + WebsiteURL: gqlutils.UnwrapOmittable(input.WebsiteURL), + Email: gqlutils.UnwrapOmittable(input.Email), + HeadquarterAddress: gqlutils.UnwrapOmittable(input.HeadquarterAddress), + } + + if input.LogoFile != nil { + req.LogoFile = &iam.UploadedFile{ + Filename: input.LogoFile.Filename, + ContentType: input.LogoFile.ContentType, + Size: input.LogoFile.Size, + Content: input.LogoFile.File, + } + } + + if input.HorizontalLogoFile != nil { + req.HorizontalLogoFile = &iam.UploadedFile{ + Filename: input.HorizontalLogoFile.Filename, + ContentType: input.HorizontalLogoFile.ContentType, + Size: input.HorizontalLogoFile.Size, + Content: input.HorizontalLogoFile.File, + } + } + + organization, err := r.iam.OrganizationService.UpdateOrganization( + ctx, + input.OrganizationID, + req, + ) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot update organization", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.UpdateOrganizationPayload{ + Organization: &types.Organization{ + ID: organization.ID, + Name: organization.Name, + Description: organization.Description, + WebsiteURL: organization.WebsiteURL, + Email: organization.Email, + HeadquarterAddress: organization.HeadquarterAddress, + CreatedAt: organization.CreatedAt, + UpdatedAt: organization.UpdatedAt, + }, + }, nil +} + +// DeleteOrganization is the resolver for the deleteOrganization field. +func (r *mutationResolver) DeleteOrganization(ctx context.Context, input types.DeleteOrganizationInput) (*types.DeleteOrganizationPayload, error) { + if err := r.authorize(ctx, input.OrganizationID, iam.ActionOrganizationDelete); err != nil { + return nil, err + } + + err := r.iam.OrganizationService.DeleteOrganization(ctx, input.OrganizationID) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot delete organization", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.DeleteOrganizationPayload{DeletedOrganizationID: input.OrganizationID}, nil +} + +// DeleteOrganizationHorizontalLogo is the resolver for the deleteOrganizationHorizontalLogo field. +func (r *mutationResolver) DeleteOrganizationHorizontalLogo(ctx context.Context, input types.DeleteOrganizationHorizontalLogoInput) (*types.DeleteOrganizationHorizontalLogoPayload, error) { + panic(fmt.Errorf("not implemented: DeleteOrganizationHorizontalLogo - deleteOrganizationHorizontalLogo")) +} diff --git a/pkg/server/api/connect/v1/personal_api_key.resolvers.go b/pkg/server/api/connect/v1/personal_api_key.resolvers.go new file mode 100644 index 000000000..2a92e67a6 --- /dev/null +++ b/pkg/server/api/connect/v1/personal_api_key.resolvers.go @@ -0,0 +1,145 @@ +package connect_v1 + +// This file will be automatically regenerated based on the schema, any resolver +// implementations +// will be copied through when generating and any unknown code will be moved to the end. +// Code generated by github.com/99designs/gqlgen version v0.17.87 + +import ( + "context" + + "go.gearno.de/kit/log" + "go.probo.inc/probo/pkg/coredata" + "go.probo.inc/probo/pkg/iam" + "go.probo.inc/probo/pkg/page" + "go.probo.inc/probo/pkg/server/api/authn" + "go.probo.inc/probo/pkg/server/api/connect/v1/schema" + "go.probo.inc/probo/pkg/server/api/connect/v1/types" + "go.probo.inc/probo/pkg/server/gqlutils" + "go.probo.inc/probo/pkg/server/gqlutils/types/cursor" +) + +// PersonalAPIKeys is the resolver for the personalAPIKeys field. +func (r *identityResolver) PersonalAPIKeys(ctx context.Context, obj *types.Identity, first *int, after *page.CursorKey, last *int, before *page.CursorKey) (*types.PersonalAPIKeyConnection, error) { + if err := r.authorize(ctx, obj.ID, iam.ActionPersonalAPIKeyList); err != nil { + return nil, err + } + + if gqlutils.OnlyTotalCountSelected(ctx) { + return &types.PersonalAPIKeyConnection{ + Resolver: r, + ParentID: obj.ID, + }, nil + } + + pageOrderBy := page.OrderBy[coredata.PersonalAPIKeyOrderField]{ + Field: coredata.PersonalAPIKeyOrderFieldCreatedAt, + Direction: page.OrderDirectionDesc, + } + + cursor := cursor.NewCursor(first, after, last, before, pageOrderBy) + + page, err := r.iam.AccountService.ListPersonalAPIKeys(ctx, obj.ID, cursor) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot list personal api keys", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return types.NewPersonalAPIKeyConnection(page, r, obj.ID), nil +} + +// CreatePersonalAPIKey is the resolver for the createPersonalAPIKey field. +func (r *mutationResolver) CreatePersonalAPIKey(ctx context.Context, input types.CreatePersonalAPIKeyInput) (*types.CreatePersonalAPIKeyPayload, error) { + identity := authn.IdentityFromContext(ctx) + + if err := r.authorize(ctx, identity.ID, iam.ActionPersonalAPIKeyCreate); err != nil { + return nil, err + } + + userAPIKey, token, err := r.iam.AccountService.CreatePersonalAPIKey( + ctx, + identity.ID, + input.Name, + input.ExpiresAt, + ) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot create personal api key", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.CreatePersonalAPIKeyPayload{ + PersonalAPIKeyEdge: types.NewPersonalAPIKeyEdge(userAPIKey, coredata.PersonalAPIKeyOrderFieldCreatedAt), + Token: token, + }, nil +} + +// RevokePersonalAPIKey is the resolver for the revokePersonalAPIKey field. +func (r *mutationResolver) RevokePersonalAPIKey(ctx context.Context, input types.RevokePersonalAPIKeyInput) (*types.RevokePersonalAPIKeyPayload, error) { + if err := r.authorize(ctx, input.PersonalAPIKeyID, iam.ActionPersonalAPIKeyDelete); err != nil { + return nil, err + } + + identity := authn.IdentityFromContext(ctx) + + err := r.iam.AccountService.DeletePersonalAPIKey(ctx, identity.ID, input.PersonalAPIKeyID) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot delete personal api key", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.RevokePersonalAPIKeyPayload{PersonalAPIKeyID: input.PersonalAPIKeyID}, nil +} + +// Token is the resolver for the token field. +func (r *personalAPIKeyResolver) Token(ctx context.Context, obj *types.PersonalAPIKey) (*string, error) { + if err := r.authorize(ctx, obj.ID, iam.ActionPersonalAPIKeyGet); err != nil { + return nil, err + } + + identity := authn.IdentityFromContext(ctx) + + token, err := r.iam.AccountService.RevealPersonalAPIKeyToken(ctx, identity.ID, obj.ID) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot reveal personal api key token", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &token, nil +} + +// Permission is the resolver for the permission field. +func (r *personalAPIKeyResolver) Permission(ctx context.Context, obj *types.PersonalAPIKey, action string) (bool, error) { + return r.Resolver.Permission(ctx, obj, action) +} + +// TotalCount is the resolver for the totalCount field. +func (r *personalAPIKeyConnectionResolver) TotalCount(ctx context.Context, obj *types.PersonalAPIKeyConnection) (*int, error) { + switch obj.Resolver.(type) { + case *identityResolver: + if err := r.authorize(ctx, obj.ParentID, iam.ActionPersonalAPIKeyList); err != nil { + return nil, err + } + + count, err := r.iam.AccountService.CountPersonalAPIKeys(ctx, obj.ParentID) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot count personal api keys", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &count, nil + } + + r.logger.ErrorCtx(ctx, "unsupported resolver", log.Any("resolver", obj.Resolver)) + return nil, gqlutils.Internal(ctx) +} + +// PersonalAPIKey returns schema.PersonalAPIKeyResolver implementation. +func (r *Resolver) PersonalAPIKey() schema.PersonalAPIKeyResolver { return &personalAPIKeyResolver{r} } + +// PersonalAPIKeyConnection returns schema.PersonalAPIKeyConnectionResolver implementation. +func (r *Resolver) PersonalAPIKeyConnection() schema.PersonalAPIKeyConnectionResolver { + return &personalAPIKeyConnectionResolver{r} +} + +type personalAPIKeyResolver struct{ *Resolver } +type personalAPIKeyConnectionResolver struct{ *Resolver } diff --git a/pkg/server/api/connect/v1/profile.resolvers.go b/pkg/server/api/connect/v1/profile.resolvers.go new file mode 100644 index 000000000..c9438a49a --- /dev/null +++ b/pkg/server/api/connect/v1/profile.resolvers.go @@ -0,0 +1,441 @@ +package connect_v1 + +// This file will be automatically regenerated based on the schema, any resolver +// implementations +// will be copied through when generating and any unknown code will be moved to the end. +// Code generated by github.com/99designs/gqlgen version v0.17.87 + +import ( + "context" + "errors" + + "go.gearno.de/kit/log" + "go.probo.inc/probo/pkg/coredata" + "go.probo.inc/probo/pkg/iam" + "go.probo.inc/probo/pkg/page" + "go.probo.inc/probo/pkg/server/api/authn" + "go.probo.inc/probo/pkg/server/api/authz" + "go.probo.inc/probo/pkg/server/api/connect/v1/schema" + "go.probo.inc/probo/pkg/server/api/connect/v1/types" + "go.probo.inc/probo/pkg/server/gqlutils" + "go.probo.inc/probo/pkg/server/gqlutils/types/cursor" +) + +// Profiles is the resolver for the profiles field. +func (r *identityResolver) Profiles(ctx context.Context, obj *types.Identity, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.ProfileOrderBy, filter *types.ProfileFilter) (*types.ProfileConnection, error) { + if err := r.authorize(ctx, obj.ID, iam.ActionMembershipProfileList, authz.WithSkipAssumptionCheck()); err != nil { + return nil, err + } + + filters := coredata.NewMembershipProfileFilter(nil).WithMembership() + if filter != nil { + filters = coredata.NewMembershipProfileFilter(filter.ExcludeContractEnded).WithMembership() + if filter.State != nil { + filters.WithState(*filter.State) + } + } + + if gqlutils.OnlyTotalCountSelected(ctx) { + return &types.ProfileConnection{ + Resolver: r, + ParentID: obj.ID, + Filters: filters, + }, nil + } + + pageOrderBy := page.OrderBy[coredata.MembershipProfileOrderField]{ + Field: coredata.MembershipProfileOrderFieldFullName, + Direction: page.OrderDirectionAsc, + } + if orderBy != nil { + pageOrderBy = page.OrderBy[coredata.MembershipProfileOrderField]{ + Field: orderBy.Field, + Direction: orderBy.Direction, + } + } + + cursor := cursor.NewCursor(first, after, last, before, pageOrderBy) + + page, err := r.iam.AccountService.ListProfilesForIdentity(ctx, obj.ID, cursor, filters) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot list profiles", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return types.NewProfileConnection(page, r, obj.ID, filters), nil +} + +// Permission is the resolver for the permission field. +func (r *invitationResolver) Permission(ctx context.Context, obj *types.Invitation, action string) (bool, error) { + return r.Resolver.Permission(ctx, obj, action) +} + +// LastSession is the resolver for the lastSession field. +func (r *membershipResolver) LastSession(ctx context.Context, obj *types.Membership) (*types.Session, error) { + if err := r.authorize(ctx, obj.ID, iam.ActionMembershipGet, authz.WithSkipAssumptionCheck()); err != nil { + return nil, err + } + + session := authn.SessionFromContext(ctx) + if session == nil { + return nil, nil + } + + childSession, err := r.iam.SessionService.GetActiveSessionForMembership(ctx, session.ID, obj.ID) + if err != nil { + var errSessionNotFound *iam.ErrSessionNotFound + if errors.As(err, &errSessionNotFound) { + return nil, nil + } + + r.logger.ErrorCtx(ctx, "cannot get active session for membership", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return types.NewSession(childSession), nil +} + +// Permission is the resolver for the permission field. +func (r *membershipResolver) Permission(ctx context.Context, obj *types.Membership, action string) (bool, error) { + return r.Resolver.Permission(ctx, obj, action) +} + +// 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.Conflict(ctx, err) + } + + r.logger.ErrorCtx(ctx, "cannot create user", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.CreateUserPayload{ + ProfileEdge: types.NewProfileEdge(profile, coredata.MembershipProfileOrderFieldCreatedAt), + }, nil +} + +// InviteUser is the resolver for the inviteUser field. +func (r *mutationResolver) InviteUser(ctx context.Context, input types.InviteUserInput) (*types.InviteUserPayload, error) { + if err := r.authorize(ctx, input.ProfileID, iam.ActionInvitationCreate); err != nil { + return nil, err + } + + invitation, err := r.iam.OrganizationService.InviteUser( + ctx, + &iam.CreateInvitationRequest{ + OrganizationID: input.OrganizationID, + ProfileID: input.ProfileID, + }, + ) + if err != nil { + var errOrganizationNotFound *iam.ErrOrganizationNotFound + var errUserAlreadyExists *iam.ErrUserAlreadyExists + + if errors.As(err, &errOrganizationNotFound) { + return nil, gqlutils.NotFound(ctx, err) + } + + if errors.As(err, &errUserAlreadyExists) { + return nil, gqlutils.Conflict(ctx, err) + } + + r.logger.ErrorCtx(ctx, "cannot invite user", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.InviteUserPayload{ + InvitationEdge: types.NewInvitationEdge(invitation, coredata.InvitationOrderFieldCreatedAt), + }, nil +} + +// DeactivateUser is the resolver for the deactivateUser field. +func (r *mutationResolver) DeactivateUser(ctx context.Context, input types.DeactivateUserInput) (*types.DeactivateUserPayload, error) { + if err := r.authorize(ctx, input.ProfileID, iam.ActionMembershipProfileDeactivate); err != nil { + return nil, err + } + + _, err := r.iam.OrganizationService.UpdateUserState( + ctx, + input.ProfileID, + coredata.ProfileStateInactive, + ) + + if err != nil { + r.logger.ErrorCtx(ctx, "cannot deactivate profile", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.DeactivateUserPayload{ + Success: true, + }, nil +} + +// UpdateUser is the resolver for the updateUser field. +func (r *mutationResolver) UpdateUser(ctx context.Context, input types.UpdateUserInput) (*types.UpdateUserPayload, error) { + if err := r.authorize(ctx, input.ID, iam.ActionMembershipProfileUpdate); err != nil { + return nil, err + } + + profile, err := r.iam.OrganizationService.UpdateUser( + ctx, + &iam.UpdateUserRequest{ + ID: input.ID, + 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 { + r.logger.ErrorCtx(ctx, "cannot update profile", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.UpdateUserPayload{ + Profile: types.NewProfile(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 { + return nil, err + } + + if input.Role == coredata.MembershipRoleOwner { + if err := r.authorize(ctx, input.MembershipID, iam.ActionMembershipRoleSetOwner); err != nil { + return nil, err + } + } + + membership, err := r.iam.OrganizationService.UpdateMempership(ctx, input.OrganizationID, input.MembershipID, input.Role) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot update membership", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.UpdateMembershipPayload{ + Membership: types.NewMembership(membership), + }, nil +} + +// RemoveUser is the resolver for the removeUser field. +func (r *mutationResolver) RemoveUser(ctx context.Context, input types.RemoveUserInput) (*types.RemoveUserPayload, error) { + if err := r.authorize(ctx, input.ProfileID, iam.ActionMembershipProfileDelete); err != nil { + return nil, err + } + + err := r.iam.OrganizationService.RemoveUser(ctx, input.OrganizationID, input.ProfileID) + if err != nil { + var errManagedBySCIM *iam.ErrUserManagedBySCIM + var errLastActiveOwner *iam.ErrLastActiveOwner + + if errors.As(err, &errManagedBySCIM) { + return nil, gqlutils.Conflict(ctx, err) + } + + if errors.As(err, &errLastActiveOwner) { + return nil, gqlutils.Conflict(ctx, err) + } + + r.logger.ErrorCtx(ctx, "cannot remove user from organization", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.RemoveUserPayload{DeletedProfileID: input.ProfileID}, nil +} + +// Profiles is the resolver for the profiles field. +func (r *organizationResolver) Profiles(ctx context.Context, obj *types.Organization, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.ProfileOrderBy) (*types.ProfileConnection, error) { + if err := r.authorize(ctx, obj.ID, iam.ActionMembershipProfileList); err != nil { + return nil, err + } + + filter := coredata.NewMembershipProfileFilter(nil).WithMembership() + + if gqlutils.OnlyTotalCountSelected(ctx) { + return &types.ProfileConnection{ + Resolver: r, + ParentID: obj.ID, + Filters: filter, + }, nil + } + + pageOrderBy := page.OrderBy[coredata.MembershipProfileOrderField]{ + Field: coredata.MembershipProfileOrderFieldFullName, + Direction: page.OrderDirectionAsc, + } + if orderBy != nil { + pageOrderBy = page.OrderBy[coredata.MembershipProfileOrderField]{ + Field: orderBy.Field, + Direction: orderBy.Direction, + } + } + + cursor := cursor.NewCursor(first, after, last, before, pageOrderBy) + + page, err := r.iam.OrganizationService.ListProfiles(ctx, obj.ID, cursor, filter) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot list profiles", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return types.NewProfileConnection(page, r, obj.ID, filter), nil +} + +// Identity is the resolver for the identity field. +func (r *profileResolver) Identity(ctx context.Context, obj *types.Profile) (*types.Identity, error) { + if err := r.authorize( + ctx, + obj.ID, + iam.ActionMembershipProfileGet, + authz.WithSkipAssumptionCheck(), + ); err != nil { + return nil, err + } + + identity, err := r.iam.AccountService.GetIdentity(ctx, obj.Identity.ID) + if err != nil { + var errNotFound *iam.ErrIdentityNotFound + if errors.As(err, &errNotFound) { + return nil, gqlutils.NotFound(ctx, err) + } + + r.logger.ErrorCtx(ctx, "cannot get identity", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return types.NewIdentity(identity), nil +} + +// Organization is the resolver for the organization field. +func (r *profileResolver) Organization(ctx context.Context, obj *types.Profile) (*types.Organization, error) { + if err := r.authorize(ctx, obj.Organization.ID, iam.ActionOrganizationGet, authz.WithSkipAssumptionCheck()); err != nil { + return nil, err + } + + organization, err := r.iam.OrganizationService.GetOrganization(ctx, obj.Organization.ID) + if err != nil { + var errNotFound *iam.ErrOrganizationNotFound + if errors.As(err, &errNotFound) { + return nil, gqlutils.NotFound(ctx, err) + } + + r.logger.ErrorCtx(ctx, "cannot get organization", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return types.NewOrganization(organization), nil +} + +// Membership is the resolver for the membership field. +func (r *profileResolver) Membership(ctx context.Context, obj *types.Profile) (*types.Membership, error) { + if err := r.authorize(ctx, obj.ID, iam.ActionMembershipGet, authz.WithSkipAssumptionCheck()); err != nil { + return nil, err + } + + membership, err := r.iam.AccountService.GetMembershipForOrganization(ctx, obj.Identity.ID, obj.Organization.ID) + if err != nil { + var errNotFound *iam.ErrMembershipNotFound + if errors.As(err, &errNotFound) { + return nil, gqlutils.NotFound(ctx, err) + } + + r.logger.ErrorCtx(ctx, "cannot get membership", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return types.NewMembership(membership), nil +} + +// PendingInvitations is the resolver for the pendingInvitations field. +func (r *profileResolver) PendingInvitations(ctx context.Context, obj *types.Profile, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.InvitationOrderBy) (*types.InvitationConnection, error) { + if err := r.authorize(ctx, obj.ID, iam.ActionInvitationList); err != nil { + return nil, err + } + + pageOrderBy := page.OrderBy[coredata.InvitationOrderField]{ + Field: coredata.InvitationOrderFieldCreatedAt, + Direction: page.OrderDirectionDesc, + } + + cursor := cursor.NewCursor(first, after, last, before, pageOrderBy) + + page, err := r.iam.AccountService.ListPendingInvitations(ctx, obj.ID, cursor) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot list pending invitations", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return types.NewInvitationConnection(page, r, obj.ID, nil), nil +} + +// Permission is the resolver for the permission field. +func (r *profileResolver) Permission(ctx context.Context, obj *types.Profile, action string) (bool, error) { + return r.Resolver.Permission(ctx, obj, action) +} + +// TotalCount is the resolver for the totalCount field. +func (r *profileConnectionResolver) TotalCount(ctx context.Context, obj *types.ProfileConnection) (*int, error) { + switch obj.Resolver.(type) { + case *identityResolver: + count, err := r.iam.AccountService.CountProfiles(ctx, obj.ParentID, obj.Filters) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot count profiles", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + return &count, nil + case *organizationResolver: + count, err := r.iam.OrganizationService.CountProfiles(ctx, obj.ParentID, obj.Filters) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot count profiles", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + return &count, nil + } + + r.logger.ErrorCtx(ctx, "unsupported resolver", log.Any("resolver", obj.Resolver)) + return nil, gqlutils.Internal(ctx) +} + +// Invitation returns schema.InvitationResolver implementation. +func (r *Resolver) Invitation() schema.InvitationResolver { return &invitationResolver{r} } + +// Membership returns schema.MembershipResolver implementation. +func (r *Resolver) Membership() schema.MembershipResolver { return &membershipResolver{r} } + +// Profile returns schema.ProfileResolver implementation. +func (r *Resolver) Profile() schema.ProfileResolver { return &profileResolver{r} } + +// ProfileConnection returns schema.ProfileConnectionResolver implementation. +func (r *Resolver) ProfileConnection() schema.ProfileConnectionResolver { + return &profileConnectionResolver{r} +} + +type invitationResolver struct{ *Resolver } +type membershipResolver struct{ *Resolver } +type profileResolver struct{ *Resolver } +type profileConnectionResolver struct{ *Resolver } diff --git a/pkg/server/api/connect/v1/saml.resolvers.go b/pkg/server/api/connect/v1/saml.resolvers.go new file mode 100644 index 000000000..c5a43daf4 --- /dev/null +++ b/pkg/server/api/connect/v1/saml.resolvers.go @@ -0,0 +1,185 @@ +package connect_v1 + +// This file will be automatically regenerated based on the schema, any resolver +// implementations +// will be copied through when generating and any unknown code will be moved to the end. +// Code generated by github.com/99designs/gqlgen version v0.17.87 + +import ( + "context" + "errors" + + "go.gearno.de/kit/log" + "go.probo.inc/probo/pkg/coredata" + "go.probo.inc/probo/pkg/iam" + "go.probo.inc/probo/pkg/page" + "go.probo.inc/probo/pkg/server/api/connect/v1/schema" + "go.probo.inc/probo/pkg/server/api/connect/v1/types" + "go.probo.inc/probo/pkg/server/gqlutils" + "go.probo.inc/probo/pkg/server/gqlutils/types/cursor" +) + +// CreateSAMLConfiguration is the resolver for the createSAMLConfiguration field. +func (r *mutationResolver) CreateSAMLConfiguration(ctx context.Context, input types.CreateSAMLConfigurationInput) (*types.CreateSAMLConfigurationPayload, error) { + if err := r.authorize(ctx, input.OrganizationID, iam.ActionSAMLConfigurationCreate); err != nil { + return nil, err + } + + req := &iam.CreateSAMLConfigurationRequest{ + EmailDomain: input.EmailDomain, + IdPEntityID: input.IdpEntityID, + IdPSsoURL: input.IdpSsoURL, + IdPCertificate: input.IdpCertificate, + AutoSignupEnabled: input.AutoSignupEnabled, + } + + if input.AttributeMappings != nil { + req.AttributeEmail = input.AttributeMappings.Email + req.AttributeFirstname = input.AttributeMappings.FirstName + req.AttributeLastname = input.AttributeMappings.LastName + req.AttributeRole = input.AttributeMappings.Role + } + + samlConfiguration, err := r.iam.OrganizationService.CreateSAMLConfiguration( + ctx, + input.OrganizationID, + req, + ) + + if err != nil { + var errSAMLConfigurationEmailDomainAlreadyExists *iam.ErrSAMLConfigurationEmailDomainAlreadyExists + if errors.As(err, &errSAMLConfigurationEmailDomainAlreadyExists) { + return nil, gqlutils.Conflict(ctx, err) + } + + r.logger.ErrorCtx(ctx, "cannot create saml configuration", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.CreateSAMLConfigurationPayload{ + SamlConfigurationEdge: types.NewSAMLConfigurationEdge( + samlConfiguration, + coredata.SAMLConfigurationOrderFieldCreatedAt, + ), + }, nil +} + +// UpdateSAMLConfiguration is the resolver for the updateSAMLConfiguration field. +func (r *mutationResolver) UpdateSAMLConfiguration(ctx context.Context, input types.UpdateSAMLConfigurationInput) (*types.UpdateSAMLConfigurationPayload, error) { + if err := r.authorize(ctx, input.SamlConfigurationID, iam.ActionSAMLConfigurationUpdate); err != nil { + return nil, err + } + + req := &iam.UpdateSAMLConfigurationRequest{ + IdPEntityID: input.IdpEntityID, + IdPSsoURL: input.IdpSsoURL, + IdPCertificate: input.IdpCertificate, + AutoSignupEnabled: input.AutoSignupEnabled, + EnforcementPolicy: &input.EnforcementPolicy, + } + + if input.AttributeMappings != nil { + req.AttributeEmail = input.AttributeMappings.Email + req.AttributeFirstname = input.AttributeMappings.FirstName + req.AttributeLastname = input.AttributeMappings.LastName + req.AttributeRole = input.AttributeMappings.Role + } + + samlConfiguration, err := r.iam.OrganizationService.UpdateSAMLConfiguration( + ctx, + input.OrganizationID, + input.SamlConfigurationID, + req, + ) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot update saml configuration", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.UpdateSAMLConfigurationPayload{ + SamlConfiguration: types.NewSAMLConfiguration(samlConfiguration), + }, nil +} + +// DeleteSAMLConfiguration is the resolver for the deleteSAMLConfiguration field. +func (r *mutationResolver) DeleteSAMLConfiguration(ctx context.Context, input types.DeleteSAMLConfigurationInput) (*types.DeleteSAMLConfigurationPayload, error) { + if err := r.authorize(ctx, input.OrganizationID, iam.ActionSAMLConfigurationDelete); err != nil { + return nil, err + } + + err := r.iam.OrganizationService.DeleteSAMLConfiguration(ctx, input.OrganizationID, input.SamlConfigurationID) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot delete saml configuration", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.DeleteSAMLConfigurationPayload{DeletedSamlConfigurationID: input.SamlConfigurationID}, nil +} + +// SamlConfigurations is the resolver for the samlConfigurations field. +func (r *organizationResolver) SamlConfigurations(ctx context.Context, obj *types.Organization, first *int, after *page.CursorKey, last *int, before *page.CursorKey) (*types.SAMLConfigurationConnection, error) { + if err := r.authorize(ctx, obj.ID, iam.ActionSAMLConfigurationList); err != nil { + return nil, err + } + + if gqlutils.OnlyTotalCountSelected(ctx) { + return &types.SAMLConfigurationConnection{ + Resolver: r, + ParentID: obj.ID, + }, nil + } + + pageOrderBy := page.OrderBy[coredata.SAMLConfigurationOrderField]{ + Field: coredata.SAMLConfigurationOrderFieldCreatedAt, + Direction: page.OrderDirectionDesc, + } + + cursor := cursor.NewCursor(first, after, last, before, pageOrderBy) + + page, err := r.iam.OrganizationService.ListSAMLConfigurations(ctx, obj.ID, cursor) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot list saml configurations", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return types.NewSAMLConfigurationConnection(page, r, obj.ID), nil +} + +// TestLoginURL is the resolver for the testLoginUrl field. +func (r *sAMLConfigurationResolver) TestLoginURL(ctx context.Context, obj *types.SAMLConfiguration) (string, error) { + return r.baseURL.WithPath("/api/connect/v1/saml/2.0/" + obj.ID.String()).MustString(), nil +} + +// Permission is the resolver for the permission field. +func (r *sAMLConfigurationResolver) Permission(ctx context.Context, obj *types.SAMLConfiguration, action string) (bool, error) { + return r.Resolver.Permission(ctx, obj, action) +} + +// TotalCount is the resolver for the totalCount field. +func (r *sAMLConfigurationConnectionResolver) TotalCount(ctx context.Context, obj *types.SAMLConfigurationConnection) (*int, error) { + switch obj.Resolver.(type) { + case *organizationResolver: + count, err := r.iam.OrganizationService.CountSAMLConfigurations(ctx, obj.ParentID) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot count saml configurations", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + return &count, nil + } + + r.logger.ErrorCtx(ctx, "unsupported resolver", log.Any("resolver", obj.Resolver)) + return nil, gqlutils.Internal(ctx) +} + +// SAMLConfiguration returns schema.SAMLConfigurationResolver implementation. +func (r *Resolver) SAMLConfiguration() schema.SAMLConfigurationResolver { + return &sAMLConfigurationResolver{r} +} + +// SAMLConfigurationConnection returns schema.SAMLConfigurationConnectionResolver implementation. +func (r *Resolver) SAMLConfigurationConnection() schema.SAMLConfigurationConnectionResolver { + return &sAMLConfigurationConnectionResolver{r} +} + +type sAMLConfigurationResolver struct{ *Resolver } +type sAMLConfigurationConnectionResolver struct{ *Resolver } diff --git a/pkg/server/api/connect/v1/schema.graphql b/pkg/server/api/connect/v1/schema.graphql deleted file mode 100644 index 408126867..000000000 --- a/pkg/server/api/connect/v1/schema.graphql +++ /dev/null @@ -1,1048 +0,0 @@ -directive @goField( - forceResolver: Boolean - name: String - omittable: Boolean -) on INPUT_FIELD_DEFINITION | FIELD_DEFINITION - -directive @goModel( - model: String - models: [String!] -) on OBJECT | INPUT_OBJECT | SCALAR | ENUM | INTERFACE | UNION - -directive @goEnum(value: String) on ENUM_VALUE - -scalar CursorKey -scalar Datetime -scalar Upload -scalar EmailAddr - -enum OrderDirection - @goModel(model: "go.probo.inc/probo/pkg/page.OrderDirection") { - ASC @goEnum(value: "go.probo.inc/probo/pkg/page.OrderDirectionAsc") - DESC @goEnum(value: "go.probo.inc/probo/pkg/page.OrderDirectionDesc") -} - -enum SessionOrderField - @goModel(model: "go.probo.inc/probo/pkg/coredata.SessionOrderField") { - CREATED_AT - @goEnum(value: "go.probo.inc/probo/pkg/coredata.SessionOrderFieldCreatedAt") - EXPIRED_AT - @goEnum(value: "go.probo.inc/probo/pkg/coredata.SessionOrderFieldExpiredAt") - UPDATED_AT - @goEnum(value: "go.probo.inc/probo/pkg/coredata.SessionOrderFieldUpdatedAt") -} - -input SessionOrder { - direction: OrderDirection! - field: SessionOrderField! -} - -interface Node { - id: ID! -} - -type OIDCProviderInfo { - name: String! - loginURL: String! -} - -type Query { - node(id: ID!): Node @session(required: PRESENT) - viewer: Identity @session(required: PRESENT) - ssoLoginURL(email: EmailAddr!): String - @goField(forceResolver: true) - @session(required: OPTIONAL) - oidcProviders: [OIDCProviderInfo!]! - @goField(forceResolver: true) - @session(required: OPTIONAL) - signUpEnabled: Boolean! - @goField(forceResolver: true) - @session(required: OPTIONAL) -} - -type Mutation { - signIn(input: SignInInput!): SignInPayload @session(required: OPTIONAL) - signUp(input: SignUpInput!): SignUpPayload @session(required: NONE) - signOut: SignOutPayload @session(required: PRESENT) - activateAccount( - input: ActivateAccountInput! - ): ActivateAccountPayload @session(required: OPTIONAL) - forgotPassword(input: ForgotPasswordInput!): ForgotPasswordPayload - @session(required: NONE) - resetPassword(input: ResetPasswordInput!): ResetPasswordPayload - @session(required: NONE) - verifyEmail(input: VerifyEmailInput!): VerifyEmailPayload - @session(required: OPTIONAL) - changePassword(input: ChangePasswordInput!): ChangePasswordPayload - @session(required: PRESENT) - changeEmail(input: ChangeEmailInput!): ChangeEmailPayload - @session(required: PRESENT) - assumeOrganizationSession( - input: AssumeOrganizationSessionInput! - ): AssumeOrganizationSessionPayload @session(required: PRESENT) - - revokeSession(input: RevokeSessionInput!): RevokeSessionPayload! - @session(required: PRESENT) - revokeAllSessions: RevokeAllSessionsPayload @session(required: PRESENT) - - createPersonalAPIKey( - input: CreatePersonalAPIKeyInput! - ): CreatePersonalAPIKeyPayload @session(required: PRESENT) - revokePersonalAPIKey( - input: RevokePersonalAPIKeyInput! - ): RevokePersonalAPIKeyPayload @session(required: PRESENT) - - createOrganization( - input: CreateOrganizationInput! - ): CreateOrganizationPayload @session(required: PRESENT) - updateOrganization( - input: UpdateOrganizationInput! - ): UpdateOrganizationPayload @session(required: PRESENT) - deleteOrganization( - input: DeleteOrganizationInput! - ): DeleteOrganizationPayload @session(required: PRESENT) - deleteOrganizationHorizontalLogo( - input: DeleteOrganizationHorizontalLogoInput! - ): DeleteOrganizationHorizontalLogoPayload @session(required: PRESENT) - - createUser(input: CreateUserInput!): CreateUserPayload - @session(required: PRESENT) - inviteUser(input: InviteUserInput!): InviteUserPayload - @session(required: PRESENT) - deactivateUser(input: DeactivateUserInput!): DeactivateUserPayload - updateUser(input: UpdateUserInput!): UpdateUserPayload! - updateMembership(input: UpdateMembershipInput!): UpdateMembershipPayload! - removeUser(input: RemoveUserInput!): RemoveUserPayload - @session(required: PRESENT) - - createSAMLConfiguration( - input: CreateSAMLConfigurationInput! - ): CreateSAMLConfigurationPayload @session(required: PRESENT) - updateSAMLConfiguration( - input: UpdateSAMLConfigurationInput! - ): UpdateSAMLConfigurationPayload @session(required: PRESENT) - deleteSAMLConfiguration( - input: DeleteSAMLConfigurationInput! - ): DeleteSAMLConfigurationPayload @session(required: PRESENT) - - createSCIMConfiguration( - input: CreateSCIMConfigurationInput! - ): CreateSCIMConfigurationPayload @session(required: PRESENT) - deleteSCIMConfiguration( - input: DeleteSCIMConfigurationInput! - ): DeleteSCIMConfigurationPayload @session(required: PRESENT) - regenerateSCIMToken( - input: RegenerateSCIMTokenInput! - ): RegenerateSCIMTokenPayload @session(required: PRESENT) - updateSCIMBridge( - input: UpdateSCIMBridgeInput! - ): UpdateSCIMBridgePayload @session(required: PRESENT) -} - -type Identity implements Node { - id: ID! - email: EmailAddr! - fullName: String! - emailVerified: Boolean! - createdAt: Datetime! - updatedAt: Datetime! - - profiles( - first: Int - after: CursorKey - last: Int - before: CursorKey - orderBy: ProfileOrder - filter: ProfileFilter - ): ProfileConnection @goField(forceResolver: true) - - sessions( - first: Int - after: CursorKey - last: Int - before: CursorKey - orderBy: SessionOrder - ): SessionConnection @goField(forceResolver: true) - - personalAPIKeys( - first: Int - after: CursorKey - last: Int - before: CursorKey - ): PersonalAPIKeyConnection @goField(forceResolver: true) - - ssoLoginURL: String - @goField(forceResolver: true) - @session(required: PRESENT) - - permission(action: String!): Boolean! - @goField(forceResolver: true) - @session(required: PRESENT) -} - -type Profile implements Node { - id: ID! - fullName: String! - emailAddress: EmailAddr! - source: String! - state: ProfileState! - additionalEmailAddresses: [EmailAddr!]! - kind: String - position: String - contractStartDate: Datetime - contractEndDate: Datetime - createdAt: Datetime! - updatedAt: Datetime! - - identity: Identity @goField(forceResolver: true) - organization: Organization @goField(forceResolver: true) - membership: Membership @goField(forceResolver: true) - pendingInvitations( - first: Int - after: CursorKey - last: Int - before: CursorKey - orderBy: InvitationOrder - ): InvitationConnection @goField(forceResolver: true) - - permission(action: String!): Boolean! - @goField(forceResolver: true) - @session(required: PRESENT) -} - -enum ProfileState - @goModel(model: "go.probo.inc/probo/pkg/coredata.ProfileState") { - ACTIVE @goEnum(value: "go.probo.inc/probo/pkg/coredata.ProfileStateActive") - INACTIVE - @goEnum(value: "go.probo.inc/probo/pkg/coredata.ProfileStateInactive") -} - -enum ProfileSource - @goModel(model: "go.probo.inc/probo/pkg/coredata.ProfileSource") { - MANUAL - @goEnum(value: "go.probo.inc/probo/pkg/coredata.ProfileSourceManual") - SAML @goEnum(value: "go.probo.inc/probo/pkg/coredata.ProfileSourceSAML") - SCIM @goEnum(value: "go.probo.inc/probo/pkg/coredata.ProfileSourceSCIM") -} - -type Organization implements Node { - id: ID! - name: String! - logoUrl: String @goField(forceResolver: true) - horizontalLogoUrl: String @goField(forceResolver: true) - email: String - description: String - websiteUrl: String - headquarterAddress: String - createdAt: Datetime! - updatedAt: Datetime! - - profiles( - first: Int - after: CursorKey - last: Int - before: CursorKey - orderBy: ProfileOrder - ): ProfileConnection @goField(forceResolver: true) - - samlConfigurations( - first: Int - after: CursorKey - last: Int - before: CursorKey - ): SAMLConfigurationConnection @goField(forceResolver: true) - - scimConfiguration: SCIMConfiguration @goField(forceResolver: true) - scimBridgeTypes: [SCIMBridgeTypeInfo!]! @goField(forceResolver: true) - - auditLogEntries( - first: Int - after: CursorKey - last: Int - before: CursorKey - orderBy: AuditLogEntryOrder - filter: AuditLogEntryFilter - ): AuditLogEntryConnection! @goField(forceResolver: true) - - viewer: Profile @goField(forceResolver: true) - - permission(action: String!): Boolean! - @goField(forceResolver: true) - @session(required: PRESENT) -} - -enum MembershipRole - @goModel(model: "go.probo.inc/probo/pkg/coredata.MembershipRole") { - OWNER @goEnum(value: "go.probo.inc/probo/pkg/coredata.MembershipRoleOwner") - ADMIN @goEnum(value: "go.probo.inc/probo/pkg/coredata.MembershipRoleAdmin") - EMPLOYEE - @goEnum(value: "go.probo.inc/probo/pkg/coredata.MembershipRoleEmployee") - VIEWER @goEnum(value: "go.probo.inc/probo/pkg/coredata.MembershipRoleViewer") - AUDITOR - @goEnum(value: "go.probo.inc/probo/pkg/coredata.MembershipRoleAuditor") -} - -type Membership implements Node { - id: ID! - createdAt: Datetime! - role: MembershipRole! - - lastSession: Session @goField(forceResolver: true) - - permission(action: String!): Boolean! - @goField(forceResolver: true) - @session(required: PRESENT) -} - -type Invitation implements Node { - id: ID! - expiresAt: Datetime! - acceptedAt: Datetime - createdAt: Datetime! - status: InvitationStatus! - - permission(action: String!): Boolean! - @goField(forceResolver: true) - @session(required: PRESENT) -} - -type Session implements Node { - id: ID! - identity: Identity @goField(forceResolver: true) - ipAddress: String! - userAgent: String! - updatedAt: Datetime! - createdAt: Datetime! - expiresAt: Datetime! - - permission(action: String!): Boolean! - @goField(forceResolver: true) - @session(required: PRESENT) -} - -type PersonalAPIKey implements Node { - id: ID! - name: String! - expiresAt: Datetime! - lastUsedAt: Datetime - createdAt: Datetime! - - token: String @goField(forceResolver: true) - - permission(action: String!): Boolean! - @goField(forceResolver: true) - @session(required: PRESENT) -} - -type SAMLConfiguration implements Node { - id: ID! - emailDomain: String! - enforcementPolicy: SAMLEnforcementPolicy! - domainVerifiedAt: Datetime - domainVerificationToken: String - idpEntityId: String! - idpSsoUrl: String! - idpCertificate: String! - autoSignupEnabled: Boolean! - createdAt: Datetime! - updatedAt: Datetime! - testLoginUrl: String! @goField(forceResolver: true) - attributeMappings: SAMLAttributeMappings! - - permission(action: String!): Boolean! - @goField(forceResolver: true) - @session(required: PRESENT) -} - -type SAMLAttributeMappings { - email: String! - firstName: String! - lastName: String! - role: String! -} - -type SCIMConfiguration implements Node { - id: ID! - endpointUrl: String! @goField(forceResolver: true) - createdAt: Datetime! - updatedAt: Datetime! - organization: Organization @goField(forceResolver: true) - - bridge: SCIMBridge @goField(forceResolver: true) - - events( - first: Int - after: CursorKey - last: Int - before: CursorKey - orderBy: SCIMEventOrder - ): SCIMEventConnection @goField(forceResolver: true) - - permission(action: String!): Boolean! - @goField(forceResolver: true) - @session(required: PRESENT) -} - -type SCIMBridge implements Node { - id: ID! - state: SCIMBridgeState! - scimConfiguration: SCIMConfiguration @goField(forceResolver: true) - connector: Connector @goField(forceResolver: true) - type: SCIMBridgeType! - excludedUserNames: [String!]! - createdAt: Datetime! - updatedAt: Datetime! - - permission(action: String!): Boolean! - @goField(forceResolver: true) - @session(required: PRESENT) -} - -type Connector implements Node { - id: ID! - provider: ConnectorProvider! - createdAt: Datetime! - updatedAt: Datetime! - - permission(action: String!): Boolean! - @goField(forceResolver: true) - @session(required: PRESENT) -} - -enum ConnectorProvider - @goModel(model: "go.probo.inc/probo/pkg/coredata.ConnectorProvider") { - SLACK @goEnum(value: "go.probo.inc/probo/pkg/coredata.ConnectorProviderSlack") - GOOGLE_WORKSPACE - @goEnum(value: "go.probo.inc/probo/pkg/coredata.ConnectorProviderGoogleWorkspace") - BREX @goEnum(value: "go.probo.inc/probo/pkg/coredata.ConnectorProviderBrex") - TALLY @goEnum(value: "go.probo.inc/probo/pkg/coredata.ConnectorProviderTally") - CLOUDFLARE - @goEnum(value: "go.probo.inc/probo/pkg/coredata.ConnectorProviderCloudflare") -} - -enum SCIMBridgeType - @goModel(model: "go.probo.inc/probo/pkg/coredata.SCIMBridgeType") { - GOOGLE_WORKSPACE @goEnum(value: "go.probo.inc/probo/pkg/coredata.SCIMBridgeTypeGoogleWorkspace") -} - -type SCIMBridgeTypeInfo { - type: SCIMBridgeType! - oauth2Scopes: [String!]! -} - -enum SCIMBridgeState - @goModel(model: "go.probo.inc/probo/pkg/coredata.SCIMBridgeState") { - PENDING @goEnum(value: "go.probo.inc/probo/pkg/coredata.SCIMBridgeStatePending") - ACTIVE @goEnum(value: "go.probo.inc/probo/pkg/coredata.SCIMBridgeStateActive") - FAILED @goEnum(value: "go.probo.inc/probo/pkg/coredata.SCIMBridgeStateFailed") -} - -type SCIMEvent implements Node { - id: ID! - method: String! - path: String! - statusCode: Int! - requestBody: String - responseBody: String - errorMessage: String - userName: String! - ipAddress: String! - createdAt: Datetime! - - permission(action: String!): Boolean! - @goField(forceResolver: true) - @session(required: PRESENT) -} - -enum InvitationStatus - @goModel(model: "go.probo.inc/probo/pkg/coredata.InvitationStatus") { - PENDING - @goEnum(value: "go.probo.inc/probo/pkg/coredata.InvitationStatusPending") - ACCEPTED - @goEnum(value: "go.probo.inc/probo/pkg/coredata.InvitationStatusAccepted") - EXPIRED - @goEnum(value: "go.probo.inc/probo/pkg/coredata.InvitationStatusExpired") -} - -enum SAMLEnforcementPolicy - @goModel(model: "go.probo.inc/probo/pkg/coredata.SAMLEnforcementPolicy") { - OFF @goEnum(value: "go.probo.inc/probo/pkg/coredata.SAMLEnforcementPolicyOff") - OPTIONAL - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.SAMLEnforcementPolicyOptional" - ) - REQUIRED - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.SAMLEnforcementPolicyRequired" - ) -} - -enum ReauthenticationReason { - SESSION_EXPIRED - SENSITIVE_ACTION - POLICY_REQUIREMENT -} - -enum InvitationOrderField - @goModel(model: "go.probo.inc/probo/pkg/coredata.InvitationOrderField") { - CREATED_AT - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.InvitationOrderFieldCreatedAt" - ) -} - -input InvitationOrder - @goModel( - model: "go.probo.inc/probo/pkg/server/api/connect/v1/types.InvitationOrderBy" - ) { - direction: OrderDirection! - field: InvitationOrderField! -} - -type InvitationConnection - @goModel( - model: "go.probo.inc/probo/pkg/server/api/connect/v1/types.InvitationConnection" - ) { - edges: [InvitationEdge!]! - pageInfo: PageInfo! -} - -type InvitationEdge { - node: Invitation! - cursor: CursorKey! -} - -enum ProfileOrderField - @goModel(model: "go.probo.inc/probo/pkg/coredata.MembershipProfileOrderField") { - FULL_NAME - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.MembershipProfileOrderFieldFullName" - ) - CREATED_AT - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.MembershipProfileOrderFieldCreatedAt" - ) - KIND @goEnum(value: "go.probo.inc/probo/pkg/coredata.MembershipProfileOrderFieldKind") - ORGANIZATION_NAME - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.MembershipProfileOrderFieldOrganizationName" - ) - STATE - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.MembershipProfileOrderFieldState" - ) -} - -input ProfileFilter { - excludeContractEnded: Boolean - state: ProfileState -} - -input ProfileOrder - @goModel( - model: "go.probo.inc/probo/pkg/server/api/connect/v1/types.ProfileOrderBy" - ) { - direction: OrderDirection! - field: ProfileOrderField! -} - -type ProfileConnection - @goModel( - model: "go.probo.inc/probo/pkg/server/api/connect/v1/types.ProfileConnection" - ) { - totalCount: Int @goField(forceResolver: true) - edges: [ProfileEdge!]! - pageInfo: PageInfo! -} - -type ProfileEdge { - cursor: CursorKey! - node: Profile! -} - -type SessionConnection - @goModel( - model: "go.probo.inc/probo/pkg/server/api/connect/v1/types.SessionConnection" - ) { - edges: [SessionEdge!]! - pageInfo: PageInfo! - totalCount: Int @goField(forceResolver: true) -} - -type SessionEdge { - node: Session! - cursor: CursorKey! -} - -type PersonalAPIKeyConnection - @goModel( - model: "go.probo.inc/probo/pkg/server/api/connect/v1/types.PersonalAPIKeyConnection" - ) { - edges: [PersonalAPIKeyEdge!]! - pageInfo: PageInfo! - totalCount: Int @goField(forceResolver: true) -} - -type PersonalAPIKeyEdge { - node: PersonalAPIKey! - cursor: CursorKey! -} - -type SAMLConfigurationConnection - @goModel( - model: "go.probo.inc/probo/pkg/server/api/connect/v1/types.SAMLConfigurationConnection" - ) { - edges: [SAMLConfigurationEdge!]! - pageInfo: PageInfo! - totalCount: Int @goField(forceResolver: true) -} - -type SAMLConfigurationEdge { - node: SAMLConfiguration! - cursor: CursorKey! -} - -enum SCIMEventOrderField - @goModel(model: "go.probo.inc/probo/pkg/coredata.SCIMEventOrderField") { - CREATED_AT - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.SCIMEventOrderFieldCreatedAt" - ) -} - -input SCIMEventOrder - @goModel( - model: "go.probo.inc/probo/pkg/server/api/connect/v1/types.SCIMEventOrderBy" - ) { - direction: OrderDirection! - field: SCIMEventOrderField! -} - -type SCIMEventConnection - @goModel( - model: "go.probo.inc/probo/pkg/server/api/connect/v1/types.SCIMEventConnection" - ) { - edges: [SCIMEventEdge!]! - pageInfo: PageInfo! - totalCount: Int @goField(forceResolver: true) -} - -type SCIMEventEdge { - node: SCIMEvent! - cursor: CursorKey! -} - -enum AuditLogActorType - @goModel( - model: "go.probo.inc/probo/pkg/coredata.AuditLogActorType" - ) { - USER - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.AuditLogActorTypeUser" - ) - API_KEY - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.AuditLogActorTypeAPIKey" - ) - SYSTEM - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.AuditLogActorTypeSystem" - ) -} - -enum AuditLogEntryOrderField - @goModel( - model: "go.probo.inc/probo/pkg/coredata.AuditLogEntryOrderField" - ) { - CREATED_AT - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.AuditLogEntryOrderFieldCreatedAt" - ) -} - -input AuditLogEntryOrder - @goModel( - model: "go.probo.inc/probo/pkg/server/api/connect/v1/types.AuditLogEntryOrderBy" - ) { - field: AuditLogEntryOrderField! - direction: OrderDirection! -} - -input AuditLogEntryFilter { - action: String - actorId: ID - resourceType: String - resourceId: ID -} - -type AuditLogEntry implements Node { - id: ID! - organization: Organization @goField(forceResolver: true) - actorId: ID! - actorType: AuditLogActorType! - action: String! - resourceType: String! - resourceId: ID! - metadata: String - createdAt: Datetime! - - permission(action: String!): Boolean! - @goField(forceResolver: true) - @session(required: PRESENT) -} - -type AuditLogEntryConnection - @goModel( - model: "go.probo.inc/probo/pkg/server/api/connect/v1/types.AuditLogEntryConnection" - ) { - edges: [AuditLogEntryEdge!]! - pageInfo: PageInfo! - totalCount: Int! @goField(forceResolver: true) -} - -type AuditLogEntryEdge { - cursor: CursorKey! - node: AuditLogEntry! -} - -type PageInfo { - hasNextPage: Boolean! - hasPreviousPage: Boolean! - startCursor: CursorKey - endCursor: CursorKey -} - -input SignInInput { - # When assuming an org with a password auth method - organizationId: ID - email: EmailAddr! - password: String! -} - -input SignUpInput { - email: EmailAddr! - password: String! - fullName: String! -} - -input ActivateAccountInput { - token: String! -} - -input ForgotPasswordInput { - email: EmailAddr! -} - -input ResetPasswordInput { - token: String! - password: String! -} - -input VerifyEmailInput { - token: String! -} - -input ChangePasswordInput { - currentPassword: String! - newPassword: String! -} - -input ChangeEmailInput { - newEmail: EmailAddr! - password: String! -} - -input AssumeOrganizationSessionInput { - organizationId: ID! - continue: String! -} - -input RevokeSessionInput { - sessionId: ID! -} - -input CreatePersonalAPIKeyInput { - name: String! - expiresAt: Datetime! -} - -input RevokePersonalAPIKeyInput { - personalAPIKeyId: ID! -} - -input CreateOrganizationInput { - name: String! - logoFile: Upload - horizontalLogoFile: Upload -} - -input UpdateOrganizationInput { - organizationId: ID! - name: String - logoFile: Upload - horizontalLogoFile: Upload - description: String @goField(omittable: true) - websiteUrl: String @goField(omittable: true) - email: String @goField(omittable: true) - headquarterAddress: String @goField(omittable: true) -} - -input DeleteOrganizationInput { - organizationId: ID! -} - -input DeleteOrganizationHorizontalLogoInput { - organizationId: ID! -} - -input CreateUserInput { - organizationId: ID! - fullName: String! - emailAddress: EmailAddr! - role: MembershipRole! - additionalEmailAddresses: [EmailAddr!] - kind: String - position: String - contractStartDate: Datetime @goField(omittable: true) - contractEndDate: Datetime @goField(omittable: true) -} - -input InviteUserInput { - organizationId: ID! - profileId: ID! -} - -input ActivateUserInput { - organizationId: ID! - profileId: ID! -} - -input DeactivateUserInput { - organizationId: ID! - profileId: ID! -} - -input UpdateUserInput { - id: ID! - fullName: String! - additionalEmailAddresses: [EmailAddr!] - kind: String - position: String - contractStartDate: Datetime @goField(omittable: true) - contractEndDate: Datetime @goField(omittable: true) -} - -input UpdateMembershipInput { - organizationId: ID! - membershipId: ID! - role: MembershipRole! -} - -input RemoveUserInput { - organizationId: ID! - profileId: ID! -} - -input CreateSAMLConfigurationInput { - organizationId: ID! - emailDomain: String! - idpEntityId: String! - idpSsoUrl: String! - idpCertificate: String! - autoSignupEnabled: Boolean! - attributeMappings: SAMLAttributeMappingsInput -} - -input SAMLAttributeMappingsInput { - email: String - firstName: String - lastName: String - role: String -} - -input UpdateSAMLConfigurationInput { - organizationId: ID! - samlConfigurationId: ID! - idpEntityId: String - idpSsoUrl: String - idpCertificate: String - autoSignupEnabled: Boolean - enforcementPolicy: SAMLEnforcementPolicy! - attributeMappings: SAMLAttributeMappingsInput -} - -input DeleteSAMLConfigurationInput { - organizationId: ID! - samlConfigurationId: ID! -} - -type SignInPayload { - identity: Identity - session: Session -} - -type SignUpPayload { - identity: Identity -} - -type SignOutPayload { - success: Boolean! -} - -type ActivateAccountPayload { - createPasswordToken: String - ssoLoginUrl: String - profile: Profile -} - -type ForgotPasswordPayload { - success: Boolean! -} - -type ResetPasswordPayload { - success: Boolean! -} - -type VerifyEmailPayload { - success: Boolean! -} - -type ChangePasswordPayload { - success: Boolean! -} - -type ChangeEmailPayload { - success: Boolean! -} - -union AssumeOrganizationSessionResult = - | OrganizationSessionCreated - | PasswordRequired - | SAMLAuthenticationRequired - -type OrganizationSessionCreated { - session: Session! - membership: Membership! -} - -type PasswordRequired { - reason: ReauthenticationReason! -} - -type SAMLAuthenticationRequired { - reason: ReauthenticationReason! -} - -type AssumeOrganizationSessionPayload { - result: AssumeOrganizationSessionResult! -} - -type RevokeSessionPayload { - success: Boolean! -} - -type RevokeAllSessionsPayload { - revokedCount: Int! -} - -type CreatePersonalAPIKeyPayload { - personalAPIKeyEdge: PersonalAPIKeyEdge! - token: String! -} - -type RevokePersonalAPIKeyPayload { - personalAPIKeyId: ID! -} - -type CreateOrganizationPayload { - organization: Organization - profile: Profile! -} - -type UpdateOrganizationPayload { - organization: Organization -} - -type DeleteOrganizationPayload { - deletedOrganizationId: ID! -} - -type DeleteOrganizationHorizontalLogoPayload { - organization: Organization! -} - -type CreateUserPayload { - profileEdge: ProfileEdge! -} - -type InviteUserPayload { - invitationEdge: InvitationEdge! -} - -type DeactivateUserPayload { - success: Boolean! -} - -type UpdateUserPayload { - profile: Profile! -} - -type UpdateMembershipPayload { - membership: Membership! -} - -type RemoveUserPayload { - deletedProfileId: ID! -} - -type CreateSAMLConfigurationPayload { - samlConfigurationEdge: SAMLConfigurationEdge! -} - -type UpdateSAMLConfigurationPayload { - samlConfiguration: SAMLConfiguration -} - -type DeleteSAMLConfigurationPayload { - deletedSamlConfigurationId: ID! -} - -input CreateSCIMConfigurationInput { - organizationId: ID! - connectorId: ID -} - -input DeleteSCIMConfigurationInput { - organizationId: ID! - scimConfigurationId: ID! -} - -input RegenerateSCIMTokenInput { - organizationId: ID! - scimConfigurationId: ID! -} - -input UpdateSCIMBridgeInput { - organizationId: ID! - scimBridgeId: ID! - excludedUserNames: [String!]! -} - -type CreateSCIMConfigurationPayload { - scimConfiguration: SCIMConfiguration! - scimBridge: SCIMBridge - token: String! -} - -type DeleteSCIMConfigurationPayload { - deletedScimConfigurationId: ID! -} - -type RegenerateSCIMTokenPayload { - scimConfiguration: SCIMConfiguration! - token: String! -} - -type UpdateSCIMBridgePayload { - scimBridge: SCIMBridge! -} diff --git a/pkg/server/api/connect/v1/scim.resolvers.go b/pkg/server/api/connect/v1/scim.resolvers.go new file mode 100644 index 000000000..98c4bd512 --- /dev/null +++ b/pkg/server/api/connect/v1/scim.resolvers.go @@ -0,0 +1,331 @@ +package connect_v1 + +// This file will be automatically regenerated based on the schema, any resolver +// implementations +// will be copied through when generating and any unknown code will be moved to the end. +// Code generated by github.com/99designs/gqlgen version v0.17.87 + +import ( + "context" + "errors" + + "go.gearno.de/kit/log" + "go.probo.inc/probo/pkg/coredata" + "go.probo.inc/probo/pkg/iam" + "go.probo.inc/probo/pkg/iam/scim/bridge/provider/googleworkspace" + "go.probo.inc/probo/pkg/page" + "go.probo.inc/probo/pkg/server/api/connect/v1/schema" + "go.probo.inc/probo/pkg/server/api/connect/v1/types" + "go.probo.inc/probo/pkg/server/gqlutils" + "go.probo.inc/probo/pkg/server/gqlutils/types/cursor" +) + +// Permission is the resolver for the permission field. +func (r *connectorResolver) Permission(ctx context.Context, obj *types.Connector, action string) (bool, error) { + return r.Resolver.Permission(ctx, obj, action) +} + +// CreateSCIMConfiguration is the resolver for the createSCIMConfiguration field. +func (r *mutationResolver) CreateSCIMConfiguration(ctx context.Context, input types.CreateSCIMConfigurationInput) (*types.CreateSCIMConfigurationPayload, error) { + if err := r.authorize(ctx, input.OrganizationID, iam.ActionSCIMConfigurationCreate); err != nil { + return nil, err + } + + config, token, err := r.iam.OrganizationService.CreateSCIMConfiguration(ctx, input.OrganizationID) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot create scim configuration", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + var bridge *types.SCIMBridge + + if input.ConnectorID != nil { + scimBridge, err := r.iam.OrganizationService.CreateSCIMBridge(ctx, input.OrganizationID, config.ID, *input.ConnectorID) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot create scim bridge", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + bridge = types.NewSCIMBridge(scimBridge) + } + + payload := &types.CreateSCIMConfigurationPayload{ + ScimConfiguration: types.NewSCIMConfiguration(config), + ScimBridge: bridge, + Token: token, + } + + return payload, nil +} + +// DeleteSCIMConfiguration is the resolver for the deleteSCIMConfiguration field. +func (r *mutationResolver) DeleteSCIMConfiguration(ctx context.Context, input types.DeleteSCIMConfigurationInput) (*types.DeleteSCIMConfigurationPayload, error) { + if err := r.authorize(ctx, input.OrganizationID, iam.ActionSCIMConfigurationDelete); err != nil { + return nil, err + } + + err := r.iam.OrganizationService.DeleteSCIMConfiguration(ctx, input.OrganizationID, input.ScimConfigurationID) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot delete scim configuration", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.DeleteSCIMConfigurationPayload{DeletedScimConfigurationID: input.ScimConfigurationID}, nil +} + +// RegenerateSCIMToken is the resolver for the regenerateSCIMToken field. +func (r *mutationResolver) RegenerateSCIMToken(ctx context.Context, input types.RegenerateSCIMTokenInput) (*types.RegenerateSCIMTokenPayload, error) { + if err := r.authorize(ctx, input.ScimConfigurationID, iam.ActionSCIMConfigurationUpdate); err != nil { + return nil, err + } + + config, token, err := r.iam.OrganizationService.RegenerateSCIMToken(ctx, input.OrganizationID, input.ScimConfigurationID) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot regenerate scim token", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.RegenerateSCIMTokenPayload{ + ScimConfiguration: types.NewSCIMConfiguration(config), + Token: token, + }, nil +} + +// UpdateSCIMBridge is the resolver for the updateSCIMBridge field. +func (r *mutationResolver) UpdateSCIMBridge(ctx context.Context, input types.UpdateSCIMBridgeInput) (*types.UpdateSCIMBridgePayload, error) { + if err := r.authorize(ctx, input.ScimBridgeID, iam.ActionSCIMBridgeUpdate); err != nil { + return nil, err + } + + bridge, err := r.iam.OrganizationService.UpdateSCIMBridge(ctx, input.OrganizationID, input.ScimBridgeID, input.ExcludedUserNames) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot update scim bridge excluded user names", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.UpdateSCIMBridgePayload{ + ScimBridge: types.NewSCIMBridge(bridge), + }, nil +} + +// ScimConfiguration is the resolver for the scimConfiguration field. +func (r *organizationResolver) ScimConfiguration(ctx context.Context, obj *types.Organization) (*types.SCIMConfiguration, error) { + if err := r.authorize(ctx, obj.ID, iam.ActionSCIMConfigurationGet); err != nil { + return nil, err + } + + config, err := r.iam.OrganizationService.GetSCIMConfiguration(ctx, obj.ID) + if err != nil { + var notFound *iam.ErrNoSCIMConfigurationFound + if errors.As(err, ¬Found) { + return nil, nil + } + + r.logger.ErrorCtx(ctx, "cannot get scim configuration", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return types.NewSCIMConfiguration(config), nil +} + +// ScimBridgeTypes is the resolver for the scimBridgeTypes field. +func (r *organizationResolver) ScimBridgeTypes(ctx context.Context, obj *types.Organization) ([]*types.SCIMBridgeTypeInfo, error) { + return []*types.SCIMBridgeTypeInfo{ + { + Type: coredata.SCIMBridgeTypeGoogleWorkspace, + Oauth2Scopes: googleworkspace.OAuth2Scopes, + }, + }, nil +} + +// ScimConfiguration is the resolver for the scimConfiguration field. +func (r *sCIMBridgeResolver) ScimConfiguration(ctx context.Context, obj *types.SCIMBridge) (*types.SCIMConfiguration, error) { + if err := r.authorize(ctx, obj.ScimConfiguration.ID, iam.ActionSCIMConfigurationGet); err != nil { + return nil, err + } + + if gqlutils.OnlyIDSelected(ctx) { + return &types.SCIMConfiguration{ + ID: obj.ScimConfiguration.ID, + }, nil + } + + scimConfiguration, err := r.iam.GetSCIMConfiguration(ctx, obj.ScimConfiguration.ID) + if err != nil { + var errNoSCIMConfigurationFound *iam.ErrNoSCIMConfigurationFound + if errors.As(err, &errNoSCIMConfigurationFound) { + return nil, nil + } + + return nil, err + } + + return types.NewSCIMConfiguration(scimConfiguration), nil +} + +// Connector is the resolver for the connector field. +func (r *sCIMBridgeResolver) Connector(ctx context.Context, obj *types.SCIMBridge) (*types.Connector, error) { + if obj.Connector == nil { + return nil, nil + } + + // Authorize based on the SCIM configuration (connector accessed via bridge is a sub-resource) + if err := r.authorize(ctx, obj.ScimConfiguration.ID, iam.ActionSCIMConfigurationGet); err != nil { + return nil, err + } + + if gqlutils.OnlyIDSelected(ctx) { + return &types.Connector{ + ID: obj.Connector.ID, + }, nil + } + + // Use metadata-only loading since we don't need the encrypted connection data + connector, err := r.iam.OrganizationService.GetConnectorMetadataByID(ctx, obj.Connector.ID) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot get connector", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return types.NewConnector(connector), nil +} + +// Permission is the resolver for the permission field. +func (r *sCIMBridgeResolver) Permission(ctx context.Context, obj *types.SCIMBridge, action string) (bool, error) { + return r.Resolver.Permission(ctx, obj, action) +} + +// EndpointURL is the resolver for the endpointUrl field. +func (r *sCIMConfigurationResolver) EndpointURL(ctx context.Context, obj *types.SCIMConfiguration) (string, error) { + return r.baseURL.WithPath("/api/connect/v1/scim/2.0").MustString(), nil +} + +// Organization is the resolver for the organization field. +func (r *sCIMConfigurationResolver) Organization(ctx context.Context, obj *types.SCIMConfiguration) (*types.Organization, error) { + if err := r.authorize(ctx, obj.Organization.ID, iam.ActionOrganizationGet); err != nil { + return nil, err + } + + if gqlutils.OnlyIDSelected(ctx) { + return &types.Organization{ + ID: obj.Organization.ID, + }, nil + } + + organization, err := r.iam.OrganizationService.GetOrganization(ctx, obj.Organization.ID) + if err != nil { + var errOrganizationNotFound *iam.ErrOrganizationNotFound + if errors.As(err, &errOrganizationNotFound) { + return nil, nil + } + + r.logger.ErrorCtx(ctx, "cannot get organization for scim configuration", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return types.NewOrganization(organization), nil +} + +// Bridge is the resolver for the bridge field. +func (r *sCIMConfigurationResolver) Bridge(ctx context.Context, obj *types.SCIMConfiguration) (*types.SCIMBridge, error) { + if obj.Bridge == nil { + return nil, nil + } + + if err := r.authorize(ctx, obj.ID, iam.ActionSCIMConfigurationGet); err != nil { + return nil, err + } + + bridge, err := r.iam.OrganizationService.GetSCIMBridgeByID(ctx, obj.Bridge.ID) + if err != nil { + var errSCIMBridgeNotFound *iam.ErrSCIMBridgeNotFound + if errors.As(err, &errSCIMBridgeNotFound) { + return nil, nil + } + + r.logger.ErrorCtx(ctx, "cannot get scim bridge", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return types.NewSCIMBridge(bridge), nil +} + +// Events is the resolver for the events field. +func (r *sCIMConfigurationResolver) Events(ctx context.Context, obj *types.SCIMConfiguration, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.SCIMEventOrderBy) (*types.SCIMEventConnection, error) { + if err := r.authorize(ctx, obj.ID, iam.ActionSCIMEventList); err != nil { + return nil, err + } + + pageOrderBy := page.OrderBy[coredata.SCIMEventOrderField]{ + Field: coredata.SCIMEventOrderFieldCreatedAt, + Direction: page.OrderDirectionDesc, + } + if orderBy != nil { + pageOrderBy.Field = coredata.SCIMEventOrderField(orderBy.Field) + pageOrderBy.Direction = page.OrderDirection(orderBy.Direction) + } + + cursor := cursor.NewCursor(first, after, last, before, pageOrderBy) + + events, err := r.iam.OrganizationService.ListSCIMEventsByConfigID(ctx, obj.ID, cursor) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot list scim events", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return types.NewSCIMEventConnection(events, r, obj.ID), nil +} + +// Permission is the resolver for the permission field. +func (r *sCIMConfigurationResolver) Permission(ctx context.Context, obj *types.SCIMConfiguration, action string) (bool, error) { + return r.Resolver.Permission(ctx, obj, action) +} + +// Permission is the resolver for the permission field. +func (r *sCIMEventResolver) Permission(ctx context.Context, obj *types.SCIMEvent, action string) (bool, error) { + return r.Resolver.Permission(ctx, obj, action) +} + +// TotalCount is the resolver for the totalCount field. +func (r *sCIMEventConnectionResolver) TotalCount(ctx context.Context, obj *types.SCIMEventConnection) (*int, error) { + if err := r.authorize(ctx, obj.ParentID, iam.ActionSCIMEventList); err != nil { + return nil, err + } + + switch obj.Resolver.(type) { + case *sCIMConfigurationResolver: + count, err := r.iam.OrganizationService.CountSCIMEvents(ctx, obj.ParentID) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot count scim events", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + return &count, nil + } + + r.logger.ErrorCtx(ctx, "unsupported resolver", log.Any("resolver", obj.Resolver)) + return nil, gqlutils.Internal(ctx) +} + +// Connector returns schema.ConnectorResolver implementation. +func (r *Resolver) Connector() schema.ConnectorResolver { return &connectorResolver{r} } + +// SCIMBridge returns schema.SCIMBridgeResolver implementation. +func (r *Resolver) SCIMBridge() schema.SCIMBridgeResolver { return &sCIMBridgeResolver{r} } + +// SCIMConfiguration returns schema.SCIMConfigurationResolver implementation. +func (r *Resolver) SCIMConfiguration() schema.SCIMConfigurationResolver { + return &sCIMConfigurationResolver{r} +} + +// SCIMEvent returns schema.SCIMEventResolver implementation. +func (r *Resolver) SCIMEvent() schema.SCIMEventResolver { return &sCIMEventResolver{r} } + +// SCIMEventConnection returns schema.SCIMEventConnectionResolver implementation. +func (r *Resolver) SCIMEventConnection() schema.SCIMEventConnectionResolver { + return &sCIMEventConnectionResolver{r} +} + +type connectorResolver struct{ *Resolver } +type sCIMBridgeResolver struct{ *Resolver } +type sCIMConfigurationResolver struct{ *Resolver } +type sCIMEventResolver struct{ *Resolver } +type sCIMEventConnectionResolver struct{ *Resolver } diff --git a/pkg/server/api/connect/v1/session.resolvers.go b/pkg/server/api/connect/v1/session.resolvers.go new file mode 100644 index 000000000..b848f749b --- /dev/null +++ b/pkg/server/api/connect/v1/session.resolvers.go @@ -0,0 +1,104 @@ +package connect_v1 + +// This file will be automatically regenerated based on the schema, any resolver +// implementations +// will be copied through when generating and any unknown code will be moved to the end. +// Code generated by github.com/99designs/gqlgen version v0.17.87 + +import ( + "context" + + "go.gearno.de/kit/log" + "go.probo.inc/probo/pkg/coredata" + "go.probo.inc/probo/pkg/iam" + "go.probo.inc/probo/pkg/page" + "go.probo.inc/probo/pkg/server/api/connect/v1/schema" + "go.probo.inc/probo/pkg/server/api/connect/v1/types" + "go.probo.inc/probo/pkg/server/gqlutils" + "go.probo.inc/probo/pkg/server/gqlutils/types/cursor" +) + +// Sessions is the resolver for the sessions field. +func (r *identityResolver) Sessions(ctx context.Context, obj *types.Identity, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.SessionOrder) (*types.SessionConnection, error) { + if err := r.authorize(ctx, obj.ID, iam.ActionSessionList); err != nil { + return nil, err + } + + if gqlutils.OnlyTotalCountSelected(ctx) { + return &types.SessionConnection{ + Resolver: r, + ParentID: obj.ID, + }, nil + } + + pageOrderBy := page.OrderBy[coredata.SessionOrderField]{ + Field: coredata.SessionOrderFieldCreatedAt, + Direction: page.OrderDirectionDesc, + } + if orderBy != nil { + pageOrderBy = page.OrderBy[coredata.SessionOrderField]{ + Field: orderBy.Field, + Direction: orderBy.Direction, + } + } + + cursor := cursor.NewCursor(first, after, last, before, pageOrderBy) + + page, err := r.iam.AccountService.ListSessions(ctx, obj.ID, cursor) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot list sessions", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return types.NewSessionConnection(page, r, obj.ID), nil +} + +// Identity is the resolver for the identity field. +func (r *sessionResolver) Identity(ctx context.Context, obj *types.Session) (*types.Identity, error) { + 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 session", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return types.NewIdentity(identity), nil +} + +// Permission is the resolver for the permission field. +func (r *sessionResolver) Permission(ctx context.Context, obj *types.Session, action string) (bool, error) { + return r.Resolver.Permission(ctx, obj, action) +} + +// TotalCount is the resolver for the totalCount field. +func (r *sessionConnectionResolver) TotalCount(ctx context.Context, obj *types.SessionConnection) (*int, error) { + switch obj.Resolver.(type) { + case *identityResolver: + count, err := r.iam.AccountService.CountSessions(ctx, obj.ParentID) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot count sessions", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &count, nil + } + + r.logger.ErrorCtx(ctx, "unsupported resolver", log.Any("resolver", obj.Resolver)) + return nil, gqlutils.Internal(ctx) +} + +// 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} +} + +type sessionResolver struct{ *Resolver } +type sessionConnectionResolver struct{ *Resolver } diff --git a/pkg/server/api/connect/v1/v1_resolver.go b/pkg/server/api/connect/v1/v1_resolver.go deleted file mode 100644 index d49642043..000000000 --- a/pkg/server/api/connect/v1/v1_resolver.go +++ /dev/null @@ -1,2122 +0,0 @@ -package connect_v1 - -// This file will be automatically regenerated based on the schema, any resolver -// implementations -// will be copied through when generating and any unknown code will be moved to the end. -// Code generated by github.com/99designs/gqlgen version v0.17.87 - -import ( - "context" - "errors" - "fmt" - "strings" - "time" - - "github.com/99designs/gqlgen/graphql" - "github.com/vektah/gqlparser/v2/gqlerror" - "go.gearno.de/kit/log" - "go.probo.inc/probo/pkg/coredata" - "go.probo.inc/probo/pkg/gid" - "go.probo.inc/probo/pkg/iam" - "go.probo.inc/probo/pkg/iam/scim/bridge/provider/googleworkspace" - "go.probo.inc/probo/pkg/mail" - "go.probo.inc/probo/pkg/page" - "go.probo.inc/probo/pkg/server/api/authn" - "go.probo.inc/probo/pkg/server/api/authz" - "go.probo.inc/probo/pkg/server/api/connect/v1/schema" - "go.probo.inc/probo/pkg/server/api/connect/v1/types" - "go.probo.inc/probo/pkg/server/gqlutils" - "go.probo.inc/probo/pkg/server/gqlutils/types/cursor" -) - -// Organization is the resolver for the organization field. -func (r *auditLogEntryResolver) Organization(ctx context.Context, obj *types.AuditLogEntry) (*types.Organization, error) { - return obj.Organization, nil -} - -// Permission is the resolver for the permission field. -func (r *auditLogEntryResolver) Permission(ctx context.Context, obj *types.AuditLogEntry, action string) (bool, error) { - return r.Resolver.Permission(ctx, obj, action) -} - -// TotalCount is the resolver for the totalCount field. -func (r *auditLogEntryConnectionResolver) TotalCount(ctx context.Context, obj *types.AuditLogEntryConnection) (int, error) { - filter := coredata.NewAuditLogEntryFilter() - if obj.Filter != nil { - filter = obj.Filter - } - - count, err := r.iam.OrganizationService.CountAuditLogEntries(ctx, obj.ParentID, filter) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot count audit log entries", log.Error(err)) - return 0, gqlutils.Internal(ctx) - } - - return count, nil -} - -// Permission is the resolver for the permission field. -func (r *connectorResolver) Permission(ctx context.Context, obj *types.Connector, action string) (bool, error) { - return r.Resolver.Permission(ctx, obj, action) -} - -// Profiles is the resolver for the profiles field. -func (r *identityResolver) Profiles(ctx context.Context, obj *types.Identity, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.ProfileOrderBy, filter *types.ProfileFilter) (*types.ProfileConnection, error) { - if err := r.authorize(ctx, obj.ID, iam.ActionMembershipProfileList, authz.WithSkipAssumptionCheck()); err != nil { - return nil, err - } - - filters := coredata.NewMembershipProfileFilter(nil).WithMembership() - if filter != nil { - filters = coredata.NewMembershipProfileFilter(filter.ExcludeContractEnded).WithMembership() - if filter.State != nil { - filters.WithState(*filter.State) - } - } - - if gqlutils.OnlyTotalCountSelected(ctx) { - return &types.ProfileConnection{ - Resolver: r, - ParentID: obj.ID, - Filters: filters, - }, nil - } - - pageOrderBy := page.OrderBy[coredata.MembershipProfileOrderField]{ - Field: coredata.MembershipProfileOrderFieldFullName, - Direction: page.OrderDirectionAsc, - } - if orderBy != nil { - pageOrderBy = page.OrderBy[coredata.MembershipProfileOrderField]{ - Field: orderBy.Field, - Direction: orderBy.Direction, - } - } - - cursor := cursor.NewCursor(first, after, last, before, pageOrderBy) - - page, err := r.iam.AccountService.ListProfilesForIdentity(ctx, obj.ID, cursor, filters) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot list profiles", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return types.NewProfileConnection(page, r, obj.ID, filters), nil -} - -// Sessions is the resolver for the sessions field. -func (r *identityResolver) Sessions(ctx context.Context, obj *types.Identity, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.SessionOrder) (*types.SessionConnection, error) { - if err := r.authorize(ctx, obj.ID, iam.ActionSessionList); err != nil { - return nil, err - } - - if gqlutils.OnlyTotalCountSelected(ctx) { - return &types.SessionConnection{ - Resolver: r, - ParentID: obj.ID, - }, nil - } - - pageOrderBy := page.OrderBy[coredata.SessionOrderField]{ - Field: coredata.SessionOrderFieldCreatedAt, - Direction: page.OrderDirectionDesc, - } - if orderBy != nil { - pageOrderBy = page.OrderBy[coredata.SessionOrderField]{ - Field: orderBy.Field, - Direction: orderBy.Direction, - } - } - - cursor := cursor.NewCursor(first, after, last, before, pageOrderBy) - - page, err := r.iam.AccountService.ListSessions(ctx, obj.ID, cursor) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot list sessions", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return types.NewSessionConnection(page, r, obj.ID), nil -} - -// PersonalAPIKeys is the resolver for the personalAPIKeys field. -func (r *identityResolver) PersonalAPIKeys(ctx context.Context, obj *types.Identity, first *int, after *page.CursorKey, last *int, before *page.CursorKey) (*types.PersonalAPIKeyConnection, error) { - if err := r.authorize(ctx, obj.ID, iam.ActionPersonalAPIKeyList); err != nil { - return nil, err - } - - if gqlutils.OnlyTotalCountSelected(ctx) { - return &types.PersonalAPIKeyConnection{ - Resolver: r, - ParentID: obj.ID, - }, nil - } - - pageOrderBy := page.OrderBy[coredata.PersonalAPIKeyOrderField]{ - Field: coredata.PersonalAPIKeyOrderFieldCreatedAt, - Direction: page.OrderDirectionDesc, - } - - cursor := cursor.NewCursor(first, after, last, before, pageOrderBy) - - page, err := r.iam.AccountService.ListPersonalAPIKeys(ctx, obj.ID, cursor) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot list personal api keys", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return types.NewPersonalAPIKeyConnection(page, r, obj.ID), nil -} - -// SsoLoginURL is the resolver for the ssoLoginURL field. -func (r *identityResolver) SsoLoginURL(ctx context.Context, obj *types.Identity) (*string, error) { - if err := r.authorize(ctx, obj.ID, iam.ActionIdentityGet); err != nil { - return nil, err - } - - identity := authn.IdentityFromContext(ctx) - - count, err := r.iam.AccountService.CountSAMLConfigurationsForEmail(ctx, identity.EmailAddress) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot count SAML configurations for email", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - if count != 1 { - if count == 0 { - return nil, graphql.ErrorOnPath( - ctx, - fmt.Errorf("no SAML configuration for email"), - ) - } - - return nil, graphql.ErrorOnPath( - ctx, - fmt.Errorf("multiple SSO configurations found for this domain. Please use your organization-specific SSO login URL"), - ) - } - - samlConfigs, err := r.iam.AccountService.ListSAMLConfigurationsForEmail(ctx, identity.EmailAddress) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot list SAML configurations for email", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - if len(samlConfigs) == 0 { - r.logger.ErrorCtx(ctx, "cannot find SAML config") - return nil, gqlutils.NotFoundf(ctx, "cannot find SAML config") - } - samlConfig := samlConfigs[0] - - loginURL := r.SSOLoginURL(samlConfig.ID) - return &loginURL, nil -} - -// Permission is the resolver for the permission field. -func (r *identityResolver) Permission(ctx context.Context, obj *types.Identity, action string) (bool, error) { - return r.Resolver.Permission(ctx, obj, action) -} - -// Permission is the resolver for the permission field. -func (r *invitationResolver) Permission(ctx context.Context, obj *types.Invitation, action string) (bool, error) { - return r.Resolver.Permission(ctx, obj, action) -} - -// LastSession is the resolver for the lastSession field. -func (r *membershipResolver) LastSession(ctx context.Context, obj *types.Membership) (*types.Session, error) { - if err := r.authorize(ctx, obj.ID, iam.ActionMembershipGet, authz.WithSkipAssumptionCheck()); err != nil { - return nil, err - } - - session := authn.SessionFromContext(ctx) - if session == nil { - return nil, nil - } - - childSession, err := r.iam.SessionService.GetActiveSessionForMembership(ctx, session.ID, obj.ID) - if err != nil { - var errSessionNotFound *iam.ErrSessionNotFound - if errors.As(err, &errSessionNotFound) { - return nil, nil - } - - r.logger.ErrorCtx(ctx, "cannot get active session for membership", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return types.NewSession(childSession), nil -} - -// Permission is the resolver for the permission field. -func (r *membershipResolver) Permission(ctx context.Context, obj *types.Membership, action string) (bool, error) { - return r.Resolver.Permission(ctx, obj, action) -} - -// SignIn is the resolver for the signIn field. -func (r *mutationResolver) SignIn(ctx context.Context, input types.SignInInput) (*types.SignInPayload, error) { - identity, err := r.iam.AuthService.CheckCredentials(ctx, input.Email, input.Password) - if err != nil { - var errInvalidPassword *iam.ErrInvalidPassword - if errors.As(err, &errInvalidPassword) { - return nil, gqlutils.Invalid(ctx, err) - } - - var errInvalidCredentials *iam.ErrInvalidCredentials - if errors.As(err, &errInvalidCredentials) { - return nil, &gqlerror.Error{ - Message: err.Error(), - Extensions: map[string]any{ - "code": "INVALID_CREDENTIALS", - }, - } - } - - r.logger.ErrorCtx(ctx, "cannot check credentials", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - session := authn.SessionFromContext(ctx) - - switch { - case session == nil: - var err error - session, err = r.iam.AuthService.OpenSessionWithPassword( - ctx, - identity.ID, - ) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot create session", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - case session.IdentityID != identity.ID: - if err := r.iam.SessionService.CloseSession(ctx, session.ID); err != nil { - r.logger.ErrorCtx(ctx, "cannot close session", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - session, err = r.iam.AuthService.OpenSessionWithPassword( - ctx, - identity.ID, - ) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot create session", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - } - - w := gqlutils.HTTPResponseWriterFromContext(ctx) - r.sessionCookie.Set(w, session) - - if input.OrganizationID != nil { - var err error - _, _, err = r.iam.SessionService.OpenPasswordChildSessionForOrganization(ctx, session.ID, *input.OrganizationID) - if err != nil { - // Here session middleware already took care of expired/nil root session so we only handle membership related errors - var errMembershipNotFound *iam.ErrMembershipNotFound - var errUserInactive *iam.ErrUserInactive - - if errors.As(err, &errMembershipNotFound) || errors.As(err, &errUserInactive) { - return nil, gqlutils.Forbiddenf(ctx, "forbidden") - } - - r.logger.ErrorCtx(ctx, "cannot assume organization", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - } - - return &types.SignInPayload{ - Identity: types.NewIdentity(identity), - Session: types.NewSession(session), - }, nil -} - -// SignUp is the resolver for the signUp field. -func (r *mutationResolver) SignUp(ctx context.Context, input types.SignUpInput) (*types.SignUpPayload, error) { - identity, session, err := r.iam.AuthService.CreateIdentityWithPassword( - ctx, - &iam.CreateIdentityWithPasswordRequest{ - Email: input.Email, - Password: input.Password, - FullName: input.FullName, - }, - ) - if err != nil { - var errIdentityAlreadyExists *iam.ErrIdentityAlreadyExists - if errors.As(err, &errIdentityAlreadyExists) { - return nil, gqlutils.Invalid(ctx, err) - } - - var errSignupDisabled *iam.ErrSignupDisabled - if errors.As(err, &errSignupDisabled) { - return nil, gqlutils.Forbidden(ctx, err) - } - - r.logger.ErrorCtx(ctx, "cannot create identity with password", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - w := gqlutils.HTTPResponseWriterFromContext(ctx) - r.sessionCookie.Set(w, session) - - return &types.SignUpPayload{ - Identity: types.NewIdentity(identity), - }, nil -} - -// SignOut is the resolver for the signOut field. -func (r *mutationResolver) SignOut(ctx context.Context) (*types.SignOutPayload, error) { - session := authn.SessionFromContext(ctx) - - err := r.iam.SessionService.CloseSession(ctx, session.ID) - if err != nil { - var ErrSessionNotFound *iam.ErrSessionNotFound - if errors.As(err, &ErrSessionNotFound) { - return &types.SignOutPayload{}, nil - } - - r.logger.ErrorCtx(ctx, "cannot close session", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - w := gqlutils.HTTPResponseWriterFromContext(ctx) - r.sessionCookie.Clear(w) - - return &types.SignOutPayload{Success: true}, nil -} - -// ActivateAccount is the resolver for the activateAccount field. -func (r *mutationResolver) ActivateAccount(ctx context.Context, input types.ActivateAccountInput) (*types.ActivateAccountPayload, error) { - session := authn.SessionFromContext(ctx) - - if session != nil { - // Sign out any other account before activating a new one - err := r.iam.SessionService.CloseSession(ctx, session.ID) - if err != nil { - var ErrSessionNotFound *iam.ErrSessionNotFound - if !errors.As(err, &ErrSessionNotFound) { - r.logger.ErrorCtx(ctx, "cannot close session", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - } - - w := gqlutils.HTTPResponseWriterFromContext(ctx) - r.sessionCookie.Clear(w) - } - - identity, user, err := r.iam.AuthService.ActivateAccount( - ctx, - &iam.ActivateAccountRequest{ - InvitationToken: input.Token, - }, - ) - if err != nil { - var ( - errInvalidToken *iam.ErrInvalidToken - errInvitationNotFound *iam.ErrInvitationNotFound - errInvitationExpired *iam.ErrInvitationExpired - - isInvalidErr = errors.As(err, &errInvalidToken) || - errors.As(err, &errInvitationNotFound) || - errors.As(err, &errInvitationExpired) - ) - - if isInvalidErr { - return nil, gqlutils.Invalid(ctx, err) - } - - if _, ok := errors.AsType[*iam.ErrInvitationAlreadyAccepted](err); ok { - return nil, gqlutils.AccountAlreadyActivated(ctx, err) - } - - r.logger.ErrorCtx(ctx, "cannot activate account from invitation", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - var ssoLoginURL *string - samlConfigs, err := r.iam.AccountService.ListSAMLConfigurationsForEmail(ctx, user.EmailAddress) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot list saml configurations", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - for _, samlConfig := range samlConfigs { - if samlConfig.OrganizationID != user.OrganizationID { - continue - } - - ssoLoginURL = new(r.SSOLoginURL(samlConfig.ID)) - } - - if ssoLoginURL != nil { - return &types.ActivateAccountPayload{ - CreatePasswordToken: nil, - SsoLoginURL: ssoLoginURL, - Profile: types.NewProfile(user), - }, nil - } - - var createPasswordToken *string - if identity.HashedPassword == nil { - token, err := r.iam.AuthService.GetResetPasswordToken(ctx, identity.EmailAddress) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot generate password create token", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - createPasswordToken = &token - } - - return &types.ActivateAccountPayload{ - CreatePasswordToken: createPasswordToken, - SsoLoginURL: nil, - Profile: types.NewProfile(user), - }, nil -} - -// ForgotPassword is the resolver for the forgotPassword field. -func (r *mutationResolver) ForgotPassword(ctx context.Context, input types.ForgotPasswordInput) (*types.ForgotPasswordPayload, error) { - err := r.iam.AuthService.SendPasswordResetInstructionByEmail( - ctx, - input.Email, - ) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot send password reset instruction by email", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.ForgotPasswordPayload{ - Success: true, - }, nil -} - -// ResetPassword is the resolver for the resetPassword field. -func (r *mutationResolver) ResetPassword(ctx context.Context, input types.ResetPasswordInput) (*types.ResetPasswordPayload, error) { - err := r.iam.AuthService.ResetPassword( - ctx, - &iam.ResetPasswordRequest{ - Token: input.Token, - Password: input.Password, - }, - ) - if err != nil { - var errInvalidToken *iam.ErrInvalidToken - if errors.As(err, &errInvalidToken) { - return nil, gqlutils.Invalid(ctx, err) - } - - r.logger.ErrorCtx(ctx, "cannot reset password", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.ResetPasswordPayload{ - Success: true, - }, nil -} - -// VerifyEmail is the resolver for the verifyEmail field. -func (r *mutationResolver) VerifyEmail(ctx context.Context, input types.VerifyEmailInput) (*types.VerifyEmailPayload, error) { - err := r.iam.AccountService.VerifyEmail(ctx, input.Token) - if err != nil { - var ( - errInvalidToken *iam.ErrInvalidToken - errIdentityNotFound *iam.ErrIdentityNotFound - errEmailAlreadyVerified *iam.ErrEmailAlreadyVerified - errEmailVerificationMismatch *iam.ErrEmailVerificationMismatch - - isInvalidErr = errors.As(err, &errInvalidToken) || - errors.As(err, &errEmailVerificationMismatch) - ) - - if isInvalidErr { - return nil, gqlutils.Invalid(ctx, err) - } - - if errors.As(err, &errEmailAlreadyVerified) { - return nil, gqlutils.Conflict(ctx, err) - } - - if errors.As(err, &errIdentityNotFound) { - return nil, gqlutils.NotFound(ctx, err) - } - - r.logger.ErrorCtx(ctx, "cannot verify email", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.VerifyEmailPayload{ - Success: true, - }, nil -} - -// ChangePassword is the resolver for the changePassword field. -func (r *mutationResolver) ChangePassword(ctx context.Context, input types.ChangePasswordInput) (*types.ChangePasswordPayload, error) { - identity := authn.IdentityFromContext(ctx) - - err := r.iam.AccountService.ChangePassword( - ctx, - identity.ID, - &iam.ChangePasswordRequest{ - CurrentPassword: input.CurrentPassword, - NewPassword: input.NewPassword, - }, - ) - if err != nil { - var ( - errInvalidPassword *iam.ErrInvalidPassword - errIdentityNotFound *iam.ErrIdentityNotFound - ) - - if errors.As(err, &errInvalidPassword) { - return nil, gqlutils.Invalid(ctx, err) - } - - if errors.As(err, &errIdentityNotFound) { - return nil, gqlutils.NotFound(ctx, err) - } - - r.logger.ErrorCtx(ctx, "cannot change password", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.ChangePasswordPayload{ - Success: true, - }, nil -} - -// ChangeEmail is the resolver for the changeEmail field. -func (r *mutationResolver) ChangeEmail(ctx context.Context, input types.ChangeEmailInput) (*types.ChangeEmailPayload, error) { - identity := authn.IdentityFromContext(ctx) - - err := r.iam.AccountService.ChangeEmail( - ctx, - identity.ID, - &iam.ChangeEmailRequest{ - NewEmail: input.NewEmail, - Password: input.Password, - }, - ) - if err != nil { - var ( - errInvalidPassword *iam.ErrInvalidPassword - errIdentityNotFound *iam.ErrIdentityNotFound - ) - - if errors.As(err, &errInvalidPassword) { - return nil, gqlutils.Invalid(ctx, err) - } - - if errors.As(err, &errIdentityNotFound) { - return nil, gqlutils.NotFound(ctx, err) - } - - r.logger.ErrorCtx(ctx, "cannot change email", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.ChangeEmailPayload{ - Success: true, - }, nil -} - -// AssumeOrganizationSession is the resolver for the assumeOrganizationSession field. -func (r *mutationResolver) AssumeOrganizationSession(ctx context.Context, input types.AssumeOrganizationSessionInput) (*types.AssumeOrganizationSessionPayload, error) { - rootSession := authn.SessionFromContext(ctx) - - childSession, membership, err := r.iam.SessionService.AssumeOrganizationSession(ctx, rootSession.ID, input.OrganizationID, input.Continue) - if err != nil { - var ( - errMembershipNotFound *iam.ErrMembershipNotFound - errPasswordAuthenticationRequired *iam.ErrPasswordAuthenticationRequired - errSAMLAuthenticationRequired *iam.ErrSAMLAuthenticationRequired - ) - - switch { - case errors.As(err, &errMembershipNotFound): - return nil, gqlutils.NotFound(ctx, err) - - case errors.As(err, &errPasswordAuthenticationRequired): - return &types.AssumeOrganizationSessionPayload{ - Result: types.PasswordRequired{ - Reason: types.ReauthenticationReason(errPasswordAuthenticationRequired.Reason), - }, - }, nil - - case errors.As(err, &errSAMLAuthenticationRequired): - return &types.AssumeOrganizationSessionPayload{ - Result: types.SAMLAuthenticationRequired{ - Reason: types.ReauthenticationReason(errSAMLAuthenticationRequired.Reason), - }, - }, nil - - default: - r.logger.ErrorCtx(ctx, "cannot assume organization session", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - } - - return &types.AssumeOrganizationSessionPayload{ - Result: types.OrganizationSessionCreated{ - Session: types.NewSession(childSession), - Membership: types.NewMembership(membership), - }, - }, nil -} - -// RevokeSession is the resolver for the revokeSession field. -func (r *mutationResolver) RevokeSession(ctx context.Context, input types.RevokeSessionInput) (*types.RevokeSessionPayload, error) { - if err := r.authorize(ctx, input.SessionID, iam.ActionSessionRevoke); err != nil { - return nil, err - } - - identity := authn.IdentityFromContext(ctx) - - err := r.iam.SessionService.RevokeSession(ctx, identity.ID, input.SessionID) - if err != nil { - var ErrSessionExpired *iam.ErrSessionExpired - if errors.As(err, &ErrSessionExpired) { - return &types.RevokeSessionPayload{Success: true}, nil - } - - r.logger.ErrorCtx(ctx, "cannot revoke session", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.RevokeSessionPayload{Success: true}, nil -} - -// RevokeAllSessions is the resolver for the revokeAllSessions field. -func (r *mutationResolver) RevokeAllSessions(ctx context.Context) (*types.RevokeAllSessionsPayload, error) { - if err := r.authorize(ctx, authn.SessionFromContext(ctx).ID, iam.ActionSessionRevokeAll); err != nil { - return nil, err - } - - session := authn.SessionFromContext(ctx) - - revokedCount, err := r.iam.SessionService.RevokeAllSessions(ctx, session.ID) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot revoke all sessions", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.RevokeAllSessionsPayload{RevokedCount: int(revokedCount)}, nil -} - -// CreatePersonalAPIKey is the resolver for the createPersonalAPIKey field. -func (r *mutationResolver) CreatePersonalAPIKey(ctx context.Context, input types.CreatePersonalAPIKeyInput) (*types.CreatePersonalAPIKeyPayload, error) { - identity := authn.IdentityFromContext(ctx) - - if err := r.authorize(ctx, identity.ID, iam.ActionPersonalAPIKeyCreate); err != nil { - return nil, err - } - - userAPIKey, token, err := r.iam.AccountService.CreatePersonalAPIKey( - ctx, - identity.ID, - input.Name, - input.ExpiresAt, - ) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot create personal api key", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.CreatePersonalAPIKeyPayload{ - PersonalAPIKeyEdge: types.NewPersonalAPIKeyEdge(userAPIKey, coredata.PersonalAPIKeyOrderFieldCreatedAt), - Token: token, - }, nil -} - -// RevokePersonalAPIKey is the resolver for the revokePersonalAPIKey field. -func (r *mutationResolver) RevokePersonalAPIKey(ctx context.Context, input types.RevokePersonalAPIKeyInput) (*types.RevokePersonalAPIKeyPayload, error) { - if err := r.authorize(ctx, input.PersonalAPIKeyID, iam.ActionPersonalAPIKeyDelete); err != nil { - return nil, err - } - - identity := authn.IdentityFromContext(ctx) - - err := r.iam.AccountService.DeletePersonalAPIKey(ctx, identity.ID, input.PersonalAPIKeyID) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot delete personal api key", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.RevokePersonalAPIKeyPayload{PersonalAPIKeyID: input.PersonalAPIKeyID}, nil -} - -// CreateOrganization is the resolver for the createOrganization field. -func (r *mutationResolver) CreateOrganization(ctx context.Context, input types.CreateOrganizationInput) (*types.CreateOrganizationPayload, error) { - identity := authn.IdentityFromContext(ctx) - - // FIXME check email domain and related IDP config - // if ok := r.authorize(ctx, identity.ID, iam.ActionOrganizationCreate); !ok { - // return nil, nil - // } - - var ( - logoFile *iam.UploadedFile - horizontalLogoFile *iam.UploadedFile - ) - - if input.LogoFile != nil { - logoFile = &iam.UploadedFile{ - Content: input.LogoFile.File, - Filename: input.LogoFile.Filename, - ContentType: input.LogoFile.ContentType, - Size: input.LogoFile.Size, - } - } - - if input.HorizontalLogoFile != nil { - horizontalLogoFile = &iam.UploadedFile{ - Content: input.HorizontalLogoFile.File, - Filename: input.HorizontalLogoFile.Filename, - ContentType: input.HorizontalLogoFile.ContentType, - Size: input.HorizontalLogoFile.Size, - } - } - organization, profile, err := r.iam.OrganizationService.CreateOrganization( - ctx, - identity.ID, - &iam.CreateOrganizationRequest{ - Name: input.Name, - LogoFile: logoFile, - HorizontalLogoFile: horizontalLogoFile, - }, - ) - if err != nil { - if errors.Is(err, coredata.ErrResourceAlreadyExists) { - return nil, gqlutils.Conflict(ctx, err) - } - - r.logger.ErrorCtx(ctx, "cannot create organization", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.CreateOrganizationPayload{ - Organization: types.NewOrganization(organization), - Profile: types.NewProfile(profile), - }, nil -} - -// UpdateOrganization is the resolver for the updateOrganization field. -func (r *mutationResolver) UpdateOrganization(ctx context.Context, input types.UpdateOrganizationInput) (*types.UpdateOrganizationPayload, error) { - if err := r.authorize(ctx, input.OrganizationID, iam.ActionOrganizationUpdate); err != nil { - return nil, err - } - - req := &iam.UpdateOrganizationRequest{ - Name: input.Name, - Description: gqlutils.UnwrapOmittable(input.Description), - WebsiteURL: gqlutils.UnwrapOmittable(input.WebsiteURL), - Email: gqlutils.UnwrapOmittable(input.Email), - HeadquarterAddress: gqlutils.UnwrapOmittable(input.HeadquarterAddress), - } - - if input.LogoFile != nil { - req.LogoFile = &iam.UploadedFile{ - Filename: input.LogoFile.Filename, - ContentType: input.LogoFile.ContentType, - Size: input.LogoFile.Size, - Content: input.LogoFile.File, - } - } - - if input.HorizontalLogoFile != nil { - req.HorizontalLogoFile = &iam.UploadedFile{ - Filename: input.HorizontalLogoFile.Filename, - ContentType: input.HorizontalLogoFile.ContentType, - Size: input.HorizontalLogoFile.Size, - Content: input.HorizontalLogoFile.File, - } - } - - organization, err := r.iam.OrganizationService.UpdateOrganization( - ctx, - input.OrganizationID, - req, - ) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot update organization", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.UpdateOrganizationPayload{ - Organization: &types.Organization{ - ID: organization.ID, - Name: organization.Name, - Description: organization.Description, - WebsiteURL: organization.WebsiteURL, - Email: organization.Email, - HeadquarterAddress: organization.HeadquarterAddress, - CreatedAt: organization.CreatedAt, - UpdatedAt: organization.UpdatedAt, - }, - }, nil -} - -// DeleteOrganization is the resolver for the deleteOrganization field. -func (r *mutationResolver) DeleteOrganization(ctx context.Context, input types.DeleteOrganizationInput) (*types.DeleteOrganizationPayload, error) { - if err := r.authorize(ctx, input.OrganizationID, iam.ActionOrganizationDelete); err != nil { - return nil, err - } - - err := r.iam.OrganizationService.DeleteOrganization(ctx, input.OrganizationID) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot delete organization", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.DeleteOrganizationPayload{DeletedOrganizationID: input.OrganizationID}, nil -} - -// DeleteOrganizationHorizontalLogo is the resolver for the deleteOrganizationHorizontalLogo field. -func (r *mutationResolver) DeleteOrganizationHorizontalLogo(ctx context.Context, input types.DeleteOrganizationHorizontalLogoInput) (*types.DeleteOrganizationHorizontalLogoPayload, error) { - 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.Conflict(ctx, err) - } - - r.logger.ErrorCtx(ctx, "cannot create user", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.CreateUserPayload{ - ProfileEdge: types.NewProfileEdge(profile, coredata.MembershipProfileOrderFieldCreatedAt), - }, nil -} - -// InviteUser is the resolver for the inviteUser field. -func (r *mutationResolver) InviteUser(ctx context.Context, input types.InviteUserInput) (*types.InviteUserPayload, error) { - if err := r.authorize(ctx, input.ProfileID, iam.ActionInvitationCreate); err != nil { - return nil, err - } - - invitation, err := r.iam.OrganizationService.InviteUser( - ctx, - &iam.CreateInvitationRequest{ - OrganizationID: input.OrganizationID, - ProfileID: input.ProfileID, - }, - ) - if err != nil { - var errOrganizationNotFound *iam.ErrOrganizationNotFound - var errUserAlreadyExists *iam.ErrUserAlreadyExists - - if errors.As(err, &errOrganizationNotFound) { - return nil, gqlutils.NotFound(ctx, err) - } - - if errors.As(err, &errUserAlreadyExists) { - return nil, gqlutils.Conflict(ctx, err) - } - - r.logger.ErrorCtx(ctx, "cannot invite user", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.InviteUserPayload{ - InvitationEdge: types.NewInvitationEdge(invitation, coredata.InvitationOrderFieldCreatedAt), - }, nil -} - -// DeactivateUser is the resolver for the deactivateUser field. -func (r *mutationResolver) DeactivateUser(ctx context.Context, input types.DeactivateUserInput) (*types.DeactivateUserPayload, error) { - if err := r.authorize(ctx, input.ProfileID, iam.ActionMembershipProfileDeactivate); err != nil { - return nil, err - } - - _, err := r.iam.OrganizationService.UpdateUserState( - ctx, - input.ProfileID, - coredata.ProfileStateInactive, - ) - - if err != nil { - r.logger.ErrorCtx(ctx, "cannot deactivate profile", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.DeactivateUserPayload{ - Success: true, - }, nil -} - -// UpdateUser is the resolver for the updateUser field. -func (r *mutationResolver) UpdateUser(ctx context.Context, input types.UpdateUserInput) (*types.UpdateUserPayload, error) { - if err := r.authorize(ctx, input.ID, iam.ActionMembershipProfileUpdate); err != nil { - return nil, err - } - - profile, err := r.iam.OrganizationService.UpdateUser( - ctx, - &iam.UpdateUserRequest{ - ID: input.ID, - 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 { - r.logger.ErrorCtx(ctx, "cannot update profile", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.UpdateUserPayload{ - Profile: types.NewProfile(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 { - return nil, err - } - - if input.Role == coredata.MembershipRoleOwner { - if err := r.authorize(ctx, input.MembershipID, iam.ActionMembershipRoleSetOwner); err != nil { - return nil, err - } - } - - membership, err := r.iam.OrganizationService.UpdateMempership(ctx, input.OrganizationID, input.MembershipID, input.Role) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot update membership", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.UpdateMembershipPayload{ - Membership: types.NewMembership(membership), - }, nil -} - -// RemoveUser is the resolver for the removeUser field. -func (r *mutationResolver) RemoveUser(ctx context.Context, input types.RemoveUserInput) (*types.RemoveUserPayload, error) { - if err := r.authorize(ctx, input.ProfileID, iam.ActionMembershipProfileDelete); err != nil { - return nil, err - } - - err := r.iam.OrganizationService.RemoveUser(ctx, input.OrganizationID, input.ProfileID) - if err != nil { - var errManagedBySCIM *iam.ErrUserManagedBySCIM - var errLastActiveOwner *iam.ErrLastActiveOwner - - if errors.As(err, &errManagedBySCIM) { - return nil, gqlutils.Conflict(ctx, err) - } - - if errors.As(err, &errLastActiveOwner) { - return nil, gqlutils.Conflict(ctx, err) - } - - r.logger.ErrorCtx(ctx, "cannot remove user from organization", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.RemoveUserPayload{DeletedProfileID: input.ProfileID}, nil -} - -// CreateSAMLConfiguration is the resolver for the createSAMLConfiguration field. -func (r *mutationResolver) CreateSAMLConfiguration(ctx context.Context, input types.CreateSAMLConfigurationInput) (*types.CreateSAMLConfigurationPayload, error) { - if err := r.authorize(ctx, input.OrganizationID, iam.ActionSAMLConfigurationCreate); err != nil { - return nil, err - } - - req := &iam.CreateSAMLConfigurationRequest{ - EmailDomain: input.EmailDomain, - IdPEntityID: input.IdpEntityID, - IdPSsoURL: input.IdpSsoURL, - IdPCertificate: input.IdpCertificate, - AutoSignupEnabled: input.AutoSignupEnabled, - } - - if input.AttributeMappings != nil { - req.AttributeEmail = input.AttributeMappings.Email - req.AttributeFirstname = input.AttributeMappings.FirstName - req.AttributeLastname = input.AttributeMappings.LastName - req.AttributeRole = input.AttributeMappings.Role - } - - samlConfiguration, err := r.iam.OrganizationService.CreateSAMLConfiguration( - ctx, - input.OrganizationID, - req, - ) - - if err != nil { - var errSAMLConfigurationEmailDomainAlreadyExists *iam.ErrSAMLConfigurationEmailDomainAlreadyExists - if errors.As(err, &errSAMLConfigurationEmailDomainAlreadyExists) { - return nil, gqlutils.Conflict(ctx, err) - } - - r.logger.ErrorCtx(ctx, "cannot create saml configuration", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.CreateSAMLConfigurationPayload{ - SamlConfigurationEdge: types.NewSAMLConfigurationEdge( - samlConfiguration, - coredata.SAMLConfigurationOrderFieldCreatedAt, - ), - }, nil -} - -// UpdateSAMLConfiguration is the resolver for the updateSAMLConfiguration field. -func (r *mutationResolver) UpdateSAMLConfiguration(ctx context.Context, input types.UpdateSAMLConfigurationInput) (*types.UpdateSAMLConfigurationPayload, error) { - if err := r.authorize(ctx, input.SamlConfigurationID, iam.ActionSAMLConfigurationUpdate); err != nil { - return nil, err - } - - req := &iam.UpdateSAMLConfigurationRequest{ - IdPEntityID: input.IdpEntityID, - IdPSsoURL: input.IdpSsoURL, - IdPCertificate: input.IdpCertificate, - AutoSignupEnabled: input.AutoSignupEnabled, - EnforcementPolicy: &input.EnforcementPolicy, - } - - if input.AttributeMappings != nil { - req.AttributeEmail = input.AttributeMappings.Email - req.AttributeFirstname = input.AttributeMappings.FirstName - req.AttributeLastname = input.AttributeMappings.LastName - req.AttributeRole = input.AttributeMappings.Role - } - - samlConfiguration, err := r.iam.OrganizationService.UpdateSAMLConfiguration( - ctx, - input.OrganizationID, - input.SamlConfigurationID, - req, - ) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot update saml configuration", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.UpdateSAMLConfigurationPayload{ - SamlConfiguration: types.NewSAMLConfiguration(samlConfiguration), - }, nil -} - -// DeleteSAMLConfiguration is the resolver for the deleteSAMLConfiguration field. -func (r *mutationResolver) DeleteSAMLConfiguration(ctx context.Context, input types.DeleteSAMLConfigurationInput) (*types.DeleteSAMLConfigurationPayload, error) { - if err := r.authorize(ctx, input.OrganizationID, iam.ActionSAMLConfigurationDelete); err != nil { - return nil, err - } - - err := r.iam.OrganizationService.DeleteSAMLConfiguration(ctx, input.OrganizationID, input.SamlConfigurationID) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot delete saml configuration", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.DeleteSAMLConfigurationPayload{DeletedSamlConfigurationID: input.SamlConfigurationID}, nil -} - -// CreateSCIMConfiguration is the resolver for the createSCIMConfiguration field. -func (r *mutationResolver) CreateSCIMConfiguration(ctx context.Context, input types.CreateSCIMConfigurationInput) (*types.CreateSCIMConfigurationPayload, error) { - if err := r.authorize(ctx, input.OrganizationID, iam.ActionSCIMConfigurationCreate); err != nil { - return nil, err - } - - config, token, err := r.iam.OrganizationService.CreateSCIMConfiguration(ctx, input.OrganizationID) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot create scim configuration", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - var bridge *types.SCIMBridge - - if input.ConnectorID != nil { - scimBridge, err := r.iam.OrganizationService.CreateSCIMBridge(ctx, input.OrganizationID, config.ID, *input.ConnectorID) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot create scim bridge", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - bridge = types.NewSCIMBridge(scimBridge) - } - - payload := &types.CreateSCIMConfigurationPayload{ - ScimConfiguration: types.NewSCIMConfiguration(config), - ScimBridge: bridge, - Token: token, - } - - return payload, nil -} - -// DeleteSCIMConfiguration is the resolver for the deleteSCIMConfiguration field. -func (r *mutationResolver) DeleteSCIMConfiguration(ctx context.Context, input types.DeleteSCIMConfigurationInput) (*types.DeleteSCIMConfigurationPayload, error) { - if err := r.authorize(ctx, input.OrganizationID, iam.ActionSCIMConfigurationDelete); err != nil { - return nil, err - } - - err := r.iam.OrganizationService.DeleteSCIMConfiguration(ctx, input.OrganizationID, input.ScimConfigurationID) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot delete scim configuration", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.DeleteSCIMConfigurationPayload{DeletedScimConfigurationID: input.ScimConfigurationID}, nil -} - -// RegenerateSCIMToken is the resolver for the regenerateSCIMToken field. -func (r *mutationResolver) RegenerateSCIMToken(ctx context.Context, input types.RegenerateSCIMTokenInput) (*types.RegenerateSCIMTokenPayload, error) { - if err := r.authorize(ctx, input.ScimConfigurationID, iam.ActionSCIMConfigurationUpdate); err != nil { - return nil, err - } - - config, token, err := r.iam.OrganizationService.RegenerateSCIMToken(ctx, input.OrganizationID, input.ScimConfigurationID) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot regenerate scim token", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.RegenerateSCIMTokenPayload{ - ScimConfiguration: types.NewSCIMConfiguration(config), - Token: token, - }, nil -} - -// UpdateSCIMBridge is the resolver for the updateSCIMBridge field. -func (r *mutationResolver) UpdateSCIMBridge(ctx context.Context, input types.UpdateSCIMBridgeInput) (*types.UpdateSCIMBridgePayload, error) { - if err := r.authorize(ctx, input.ScimBridgeID, iam.ActionSCIMBridgeUpdate); err != nil { - return nil, err - } - - bridge, err := r.iam.OrganizationService.UpdateSCIMBridge(ctx, input.OrganizationID, input.ScimBridgeID, input.ExcludedUserNames) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot update scim bridge excluded user names", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.UpdateSCIMBridgePayload{ - ScimBridge: types.NewSCIMBridge(bridge), - }, nil -} - -// LogoURL is the resolver for the logoUrl field. -func (r *organizationResolver) LogoURL(ctx context.Context, obj *types.Organization) (*string, error) { - if err := r.authorize(ctx, obj.ID, iam.ActionOrganizationGet, authz.WithSkipAssumptionCheck()); err != nil { - return nil, err - } - - presignedURL, err := r.iam.OrganizationService.GenerateLogoURL(ctx, obj.ID, 1*time.Hour) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot generate logo URL", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return presignedURL, nil -} - -// HorizontalLogoURL is the resolver for the horizontalLogoUrl field. -func (r *organizationResolver) HorizontalLogoURL(ctx context.Context, obj *types.Organization) (*string, error) { - if err := r.authorize(ctx, obj.ID, iam.ActionOrganizationGet); err != nil { - return nil, err - } - - presignedURL, err := r.iam.OrganizationService.GenerateHorizontalLogoURL(ctx, obj.ID, 1*time.Hour) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot generate horizontal logo URL", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return presignedURL, nil -} - -// Profiles is the resolver for the profiles field. -func (r *organizationResolver) Profiles(ctx context.Context, obj *types.Organization, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.ProfileOrderBy) (*types.ProfileConnection, error) { - if err := r.authorize(ctx, obj.ID, iam.ActionMembershipProfileList); err != nil { - return nil, err - } - - filter := coredata.NewMembershipProfileFilter(nil).WithMembership() - - if gqlutils.OnlyTotalCountSelected(ctx) { - return &types.ProfileConnection{ - Resolver: r, - ParentID: obj.ID, - Filters: filter, - }, nil - } - - pageOrderBy := page.OrderBy[coredata.MembershipProfileOrderField]{ - Field: coredata.MembershipProfileOrderFieldFullName, - Direction: page.OrderDirectionAsc, - } - if orderBy != nil { - pageOrderBy = page.OrderBy[coredata.MembershipProfileOrderField]{ - Field: orderBy.Field, - Direction: orderBy.Direction, - } - } - - cursor := cursor.NewCursor(first, after, last, before, pageOrderBy) - - page, err := r.iam.OrganizationService.ListProfiles(ctx, obj.ID, cursor, filter) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot list profiles", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return types.NewProfileConnection(page, r, obj.ID, filter), nil -} - -// SamlConfigurations is the resolver for the samlConfigurations field. -func (r *organizationResolver) SamlConfigurations(ctx context.Context, obj *types.Organization, first *int, after *page.CursorKey, last *int, before *page.CursorKey) (*types.SAMLConfigurationConnection, error) { - if err := r.authorize(ctx, obj.ID, iam.ActionSAMLConfigurationList); err != nil { - return nil, err - } - - if gqlutils.OnlyTotalCountSelected(ctx) { - return &types.SAMLConfigurationConnection{ - Resolver: r, - ParentID: obj.ID, - }, nil - } - - pageOrderBy := page.OrderBy[coredata.SAMLConfigurationOrderField]{ - Field: coredata.SAMLConfigurationOrderFieldCreatedAt, - Direction: page.OrderDirectionDesc, - } - - cursor := cursor.NewCursor(first, after, last, before, pageOrderBy) - - page, err := r.iam.OrganizationService.ListSAMLConfigurations(ctx, obj.ID, cursor) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot list saml configurations", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return types.NewSAMLConfigurationConnection(page, r, obj.ID), nil -} - -// ScimConfiguration is the resolver for the scimConfiguration field. -func (r *organizationResolver) ScimConfiguration(ctx context.Context, obj *types.Organization) (*types.SCIMConfiguration, error) { - if err := r.authorize(ctx, obj.ID, iam.ActionSCIMConfigurationGet); err != nil { - return nil, err - } - - config, err := r.iam.OrganizationService.GetSCIMConfiguration(ctx, obj.ID) - if err != nil { - var notFound *iam.ErrNoSCIMConfigurationFound - if errors.As(err, ¬Found) { - return nil, nil - } - - r.logger.ErrorCtx(ctx, "cannot get scim configuration", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return types.NewSCIMConfiguration(config), nil -} - -// ScimBridgeTypes is the resolver for the scimBridgeTypes field. -func (r *organizationResolver) ScimBridgeTypes(ctx context.Context, obj *types.Organization) ([]*types.SCIMBridgeTypeInfo, error) { - return []*types.SCIMBridgeTypeInfo{ - { - Type: coredata.SCIMBridgeTypeGoogleWorkspace, - Oauth2Scopes: googleworkspace.OAuth2Scopes, - }, - }, nil -} - -// AuditLogEntries is the resolver for the auditLogEntries field. -func (r *organizationResolver) AuditLogEntries(ctx context.Context, obj *types.Organization, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.AuditLogEntryOrderBy, filter *types.AuditLogEntryFilter) (*types.AuditLogEntryConnection, error) { - if err := r.authorize(ctx, obj.ID, iam.ActionAuditLogEntryList); err != nil { - return nil, err - } - - pageOrderBy := page.OrderBy[coredata.AuditLogEntryOrderField]{ - Field: coredata.AuditLogEntryOrderFieldCreatedAt, - Direction: page.OrderDirectionDesc, - } - if orderBy != nil { - pageOrderBy = page.OrderBy[coredata.AuditLogEntryOrderField]{ - Field: orderBy.Field, - Direction: orderBy.Direction, - } - } - - c := cursor.NewCursor(first, after, last, before, pageOrderBy) - - coredataFilter := coredata.NewAuditLogEntryFilter() - if filter != nil { - if filter.Action != nil { - coredataFilter.WithAction(*filter.Action) - } - if filter.ActorID != nil { - coredataFilter.WithActorID(*filter.ActorID) - } - if filter.ResourceType != nil { - coredataFilter.WithResourceType(*filter.ResourceType) - } - if filter.ResourceID != nil { - coredataFilter.WithResourceID(*filter.ResourceID) - } - } - - p, err := r.iam.OrganizationService.ListAuditLogEntries(ctx, obj.ID, c, coredataFilter) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot list audit log entries", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return types.NewAuditLogEntryConnection(p, r, obj.ID, coredataFilter), nil -} - -// Viewer is the resolver for the viewer field. -func (r *organizationResolver) Viewer(ctx context.Context, obj *types.Organization) (*types.Profile, error) { - if err := r.authorize(ctx, obj.ID, iam.ActionMembershipProfileGet); err != nil { - return nil, err - } - - identity := authn.IdentityFromContext(ctx) - - profile, err := r.iam.OrganizationService.GetProfileForIdentityAndOrganization(ctx, identity.ID, obj.ID) - if err != nil { - var errNotFound *iam.ErrProfileNotFound - if errors.As(err, &errNotFound) { - return nil, gqlutils.NotFound(ctx, err) - } - - r.logger.ErrorCtx(ctx, "cannot get profile", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return types.NewProfile(profile), nil -} - -// Permission is the resolver for the permission field. -func (r *organizationResolver) Permission(ctx context.Context, obj *types.Organization, action string) (bool, error) { - return r.Resolver.Permission(ctx, obj, action) -} - -// Token is the resolver for the token field. -func (r *personalAPIKeyResolver) Token(ctx context.Context, obj *types.PersonalAPIKey) (*string, error) { - if err := r.authorize(ctx, obj.ID, iam.ActionPersonalAPIKeyGet); err != nil { - return nil, err - } - - identity := authn.IdentityFromContext(ctx) - - token, err := r.iam.AccountService.RevealPersonalAPIKeyToken(ctx, identity.ID, obj.ID) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot reveal personal api key token", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &token, nil -} - -// Permission is the resolver for the permission field. -func (r *personalAPIKeyResolver) Permission(ctx context.Context, obj *types.PersonalAPIKey, action string) (bool, error) { - return r.Resolver.Permission(ctx, obj, action) -} - -// TotalCount is the resolver for the totalCount field. -func (r *personalAPIKeyConnectionResolver) TotalCount(ctx context.Context, obj *types.PersonalAPIKeyConnection) (*int, error) { - switch obj.Resolver.(type) { - case *identityResolver: - if err := r.authorize(ctx, obj.ParentID, iam.ActionPersonalAPIKeyList); err != nil { - return nil, err - } - - count, err := r.iam.AccountService.CountPersonalAPIKeys(ctx, obj.ParentID) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot count personal api keys", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &count, nil - } - - r.logger.ErrorCtx(ctx, "unsupported resolver", log.Any("resolver", obj.Resolver)) - return nil, gqlutils.Internal(ctx) -} - -// Identity is the resolver for the identity field. -func (r *profileResolver) Identity(ctx context.Context, obj *types.Profile) (*types.Identity, error) { - if err := r.authorize( - ctx, - obj.ID, - iam.ActionMembershipProfileGet, - authz.WithSkipAssumptionCheck(), - ); err != nil { - return nil, err - } - - identity, err := r.iam.AccountService.GetIdentity(ctx, obj.Identity.ID) - if err != nil { - var errNotFound *iam.ErrIdentityNotFound - if errors.As(err, &errNotFound) { - return nil, gqlutils.NotFound(ctx, err) - } - - r.logger.ErrorCtx(ctx, "cannot get identity", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return types.NewIdentity(identity), nil -} - -// Organization is the resolver for the organization field. -func (r *profileResolver) Organization(ctx context.Context, obj *types.Profile) (*types.Organization, error) { - if err := r.authorize(ctx, obj.Organization.ID, iam.ActionOrganizationGet, authz.WithSkipAssumptionCheck()); err != nil { - return nil, err - } - - organization, err := r.iam.OrganizationService.GetOrganization(ctx, obj.Organization.ID) - if err != nil { - var errNotFound *iam.ErrOrganizationNotFound - if errors.As(err, &errNotFound) { - return nil, gqlutils.NotFound(ctx, err) - } - - r.logger.ErrorCtx(ctx, "cannot get organization", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return types.NewOrganization(organization), nil -} - -// Membership is the resolver for the membership field. -func (r *profileResolver) Membership(ctx context.Context, obj *types.Profile) (*types.Membership, error) { - if err := r.authorize(ctx, obj.ID, iam.ActionMembershipGet, authz.WithSkipAssumptionCheck()); err != nil { - return nil, err - } - - membership, err := r.iam.AccountService.GetMembershipForOrganization(ctx, obj.Identity.ID, obj.Organization.ID) - if err != nil { - var errNotFound *iam.ErrMembershipNotFound - if errors.As(err, &errNotFound) { - return nil, gqlutils.NotFound(ctx, err) - } - - r.logger.ErrorCtx(ctx, "cannot get membership", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return types.NewMembership(membership), nil -} - -// PendingInvitations is the resolver for the pendingInvitations field. -func (r *profileResolver) PendingInvitations(ctx context.Context, obj *types.Profile, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.InvitationOrderBy) (*types.InvitationConnection, error) { - if err := r.authorize(ctx, obj.ID, iam.ActionInvitationList); err != nil { - return nil, err - } - - pageOrderBy := page.OrderBy[coredata.InvitationOrderField]{ - Field: coredata.InvitationOrderFieldCreatedAt, - Direction: page.OrderDirectionDesc, - } - - cursor := cursor.NewCursor(first, after, last, before, pageOrderBy) - - page, err := r.iam.AccountService.ListPendingInvitations(ctx, obj.ID, cursor) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot list pending invitations", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return types.NewInvitationConnection(page, r, obj.ID, nil), nil -} - -// Permission is the resolver for the permission field. -func (r *profileResolver) Permission(ctx context.Context, obj *types.Profile, action string) (bool, error) { - return r.Resolver.Permission(ctx, obj, action) -} - -// TotalCount is the resolver for the totalCount field. -func (r *profileConnectionResolver) TotalCount(ctx context.Context, obj *types.ProfileConnection) (*int, error) { - switch obj.Resolver.(type) { - case *identityResolver: - count, err := r.iam.AccountService.CountProfiles(ctx, obj.ParentID, obj.Filters) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot count profiles", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - return &count, nil - case *organizationResolver: - count, err := r.iam.OrganizationService.CountProfiles(ctx, obj.ParentID, obj.Filters) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot count profiles", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - return &count, nil - } - - r.logger.ErrorCtx(ctx, "unsupported resolver", log.Any("resolver", obj.Resolver)) - return nil, gqlutils.Internal(ctx) -} - -// Node is the resolver for the node field. -func (r *queryResolver) Node(ctx context.Context, id gid.GID) (types.Node, error) { - var ( - loadNode func(ctx context.Context, id gid.GID) (types.Node, error) - action string - ) - - switch id.EntityType() { - case coredata.OrganizationEntityType: - action = iam.ActionOrganizationGet - loadNode = func(ctx context.Context, id gid.GID) (types.Node, error) { - organization, err := r.iam.OrganizationService.GetOrganization(ctx, id) - if err != nil { - return nil, err - } - return types.NewOrganization(organization), nil - } - case coredata.IdentityEntityType: - action = iam.ActionIdentityGet - loadNode = func(ctx context.Context, id gid.GID) (types.Node, error) { - identity, err := r.iam.AccountService.GetIdentity(ctx, id) - if err != nil { - return nil, err - } - - return types.NewIdentity(identity), nil - } - case coredata.SessionEntityType: - action = iam.ActionSessionGet - loadNode = func(ctx context.Context, id gid.GID) (types.Node, error) { - session, err := r.iam.GetSession(ctx, id) - if err != nil { - return nil, err - } - - 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.NewProfile(profile), nil - } - case coredata.MembershipEntityType: - action = iam.ActionMembershipGet - loadNode = func(ctx context.Context, id gid.GID) (types.Node, error) { - membership, err := r.iam.GetMembership(ctx, id) - if err != nil { - return nil, err - } - - return types.NewMembership(membership), nil - } - case coredata.InvitationEntityType: - action = iam.ActionInvitationGet - loadNode = func(ctx context.Context, id gid.GID) (types.Node, error) { - invitation, err := r.iam.GetInvitation(ctx, id) - if err != nil { - return nil, err - } - - return types.NewInvitation(invitation), nil - } - case coredata.SAMLConfigurationEntityType: - action = iam.ActionSAMLConfigurationGet - loadNode = func(ctx context.Context, id gid.GID) (types.Node, error) { - samlConfiguration, err := r.iam.GetSAMLconfiguration(ctx, id) - if err != nil { - return nil, err - } - - return types.NewSAMLConfiguration(samlConfiguration), nil - } - case coredata.PersonalAPIKeyEntityType: - action = iam.ActionPersonalAPIKeyGet - loadNode = func(ctx context.Context, id gid.GID) (types.Node, error) { - personalAPIKey, err := r.iam.GetPersonalAPIKey(ctx, id) - if err != nil { - return nil, err - } - return types.NewPersonalAPIKey(personalAPIKey), nil - } - case coredata.SCIMConfigurationEntityType: - action = iam.ActionSCIMConfigurationGet - loadNode = func(ctx context.Context, id gid.GID) (types.Node, error) { - scimConfiguration, err := r.iam.GetSCIMConfiguration(ctx, id) - if err != nil { - return nil, err - } - return types.NewSCIMConfiguration(scimConfiguration), nil - } - case coredata.SCIMEventEntityType: - action = iam.ActionSCIMEventGet - loadNode = func(ctx context.Context, id gid.GID) (types.Node, error) { - scimEvent, err := r.iam.GetSCIMEvent(ctx, id) - if err != nil { - return nil, err - } - return types.NewSCIMEvent(scimEvent), nil - } - default: - return nil, fmt.Errorf("unsupported entity type: %d", id.EntityType()) - } - - if err := r.authorize(ctx, id, action); err != nil { - return nil, err - } - - node, err := loadNode(ctx, id) - if err != nil { - var ( - errOrganizationNotFound *iam.ErrOrganizationNotFound - errIdentityNotFound *iam.ErrIdentityNotFound - errSessionNotFound *iam.ErrSessionNotFound - errProfileNotFound *iam.ErrProfileNotFound - errMembershipNotFound *iam.ErrMembershipNotFound - errInvitationNotFound *iam.ErrInvitationNotFound - - isNotFoundErr = errors.As(err, &errOrganizationNotFound) || - errors.As(err, &errIdentityNotFound) || - errors.As(err, &errSessionNotFound) || - errors.As(err, &errProfileNotFound) || - errors.As(err, &errMembershipNotFound) || - errors.As(err, &errInvitationNotFound) - ) - - if isNotFoundErr { - return nil, gqlutils.NotFound(ctx, err) - } - - r.logger.ErrorCtx(ctx, "cannot load node", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return node, nil -} - -// Viewer is the resolver for the viewer field. -func (r *queryResolver) Viewer(ctx context.Context) (*types.Identity, error) { - identity := authn.IdentityFromContext(ctx) - - return &types.Identity{ - ID: identity.ID, - Email: identity.EmailAddress, - EmailVerified: identity.EmailAddressVerified, - FullName: identity.FullName, - CreatedAt: identity.CreatedAt, - UpdatedAt: identity.UpdatedAt, - }, nil -} - -// SsoLoginURL is the resolver for the ssoLoginURL field. -func (r *queryResolver) SsoLoginURL(ctx context.Context, email mail.Addr) (*string, error) { - count, err := r.iam.AccountService.CountSAMLConfigurationsForEmail(ctx, email) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot count SAML configurations for email", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - if count != 1 { - if count == 0 { - return nil, graphql.ErrorOnPath( - ctx, - fmt.Errorf("no SAML configuration for email"), - ) - } - - return nil, graphql.ErrorOnPath( - ctx, - fmt.Errorf("multiple SSO configurations found for this domain. Please use your organization-specific SSO login URL"), - ) - } - - samlConfigs, err := r.iam.AccountService.ListSAMLConfigurationsForEmail(ctx, email) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot list SAML configurations for email", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - samlConfig := samlConfigs[0] - loginURL := r.SSOLoginURL(samlConfig.ID) - - return &loginURL, nil -} - -// OidcProviders is the resolver for the oidcProviders field. -func (r *queryResolver) OidcProviders(ctx context.Context) ([]*types.OIDCProviderInfo, error) { - providers := r.iam.OIDCService.EnabledProviders() - result := make([]*types.OIDCProviderInfo, 0, len(providers)) - - for _, p := range providers { - result = append(result, &types.OIDCProviderInfo{ - Name: strings.ToLower(p.String()), - LoginURL: r.baseURL.WithPath("/api/connect/v1/oidc/" + strings.ToLower(p.String()) + "/login").MustString(), - }) - } - - return result, nil -} - -// SignUpEnabled is the resolver for the signUpEnabled field. -func (r *queryResolver) SignUpEnabled(ctx context.Context) (bool, error) { - return r.iam.IsSignUpEnabled(), nil -} - -// TestLoginURL is the resolver for the testLoginUrl field. -func (r *sAMLConfigurationResolver) TestLoginURL(ctx context.Context, obj *types.SAMLConfiguration) (string, error) { - return r.baseURL.WithPath("/api/connect/v1/saml/2.0/" + obj.ID.String()).MustString(), nil -} - -// Permission is the resolver for the permission field. -func (r *sAMLConfigurationResolver) Permission(ctx context.Context, obj *types.SAMLConfiguration, action string) (bool, error) { - return r.Resolver.Permission(ctx, obj, action) -} - -// TotalCount is the resolver for the totalCount field. -func (r *sAMLConfigurationConnectionResolver) TotalCount(ctx context.Context, obj *types.SAMLConfigurationConnection) (*int, error) { - switch obj.Resolver.(type) { - case *organizationResolver: - count, err := r.iam.OrganizationService.CountSAMLConfigurations(ctx, obj.ParentID) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot count saml configurations", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - return &count, nil - } - - r.logger.ErrorCtx(ctx, "unsupported resolver", log.Any("resolver", obj.Resolver)) - return nil, gqlutils.Internal(ctx) -} - -// ScimConfiguration is the resolver for the scimConfiguration field. -func (r *sCIMBridgeResolver) ScimConfiguration(ctx context.Context, obj *types.SCIMBridge) (*types.SCIMConfiguration, error) { - if err := r.authorize(ctx, obj.ScimConfiguration.ID, iam.ActionSCIMConfigurationGet); err != nil { - return nil, err - } - - if gqlutils.OnlyIDSelected(ctx) { - return &types.SCIMConfiguration{ - ID: obj.ScimConfiguration.ID, - }, nil - } - - scimConfiguration, err := r.iam.GetSCIMConfiguration(ctx, obj.ScimConfiguration.ID) - if err != nil { - var errNoSCIMConfigurationFound *iam.ErrNoSCIMConfigurationFound - if errors.As(err, &errNoSCIMConfigurationFound) { - return nil, nil - } - - return nil, err - } - - return types.NewSCIMConfiguration(scimConfiguration), nil -} - -// Connector is the resolver for the connector field. -func (r *sCIMBridgeResolver) Connector(ctx context.Context, obj *types.SCIMBridge) (*types.Connector, error) { - if obj.Connector == nil { - return nil, nil - } - - // Authorize based on the SCIM configuration (connector accessed via bridge is a sub-resource) - if err := r.authorize(ctx, obj.ScimConfiguration.ID, iam.ActionSCIMConfigurationGet); err != nil { - return nil, err - } - - if gqlutils.OnlyIDSelected(ctx) { - return &types.Connector{ - ID: obj.Connector.ID, - }, nil - } - - // Use metadata-only loading since we don't need the encrypted connection data - connector, err := r.iam.OrganizationService.GetConnectorMetadataByID(ctx, obj.Connector.ID) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot get connector", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return types.NewConnector(connector), nil -} - -// Permission is the resolver for the permission field. -func (r *sCIMBridgeResolver) Permission(ctx context.Context, obj *types.SCIMBridge, action string) (bool, error) { - return r.Resolver.Permission(ctx, obj, action) -} - -// EndpointURL is the resolver for the endpointUrl field. -func (r *sCIMConfigurationResolver) EndpointURL(ctx context.Context, obj *types.SCIMConfiguration) (string, error) { - return r.baseURL.WithPath("/api/connect/v1/scim/2.0").MustString(), nil -} - -// Organization is the resolver for the organization field. -func (r *sCIMConfigurationResolver) Organization(ctx context.Context, obj *types.SCIMConfiguration) (*types.Organization, error) { - if err := r.authorize(ctx, obj.Organization.ID, iam.ActionOrganizationGet); err != nil { - return nil, err - } - - if gqlutils.OnlyIDSelected(ctx) { - return &types.Organization{ - ID: obj.Organization.ID, - }, nil - } - - organization, err := r.iam.OrganizationService.GetOrganization(ctx, obj.Organization.ID) - if err != nil { - var errOrganizationNotFound *iam.ErrOrganizationNotFound - if errors.As(err, &errOrganizationNotFound) { - return nil, nil - } - - r.logger.ErrorCtx(ctx, "cannot get organization for scim configuration", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return types.NewOrganization(organization), nil -} - -// Bridge is the resolver for the bridge field. -func (r *sCIMConfigurationResolver) Bridge(ctx context.Context, obj *types.SCIMConfiguration) (*types.SCIMBridge, error) { - if obj.Bridge == nil { - return nil, nil - } - - if err := r.authorize(ctx, obj.ID, iam.ActionSCIMConfigurationGet); err != nil { - return nil, err - } - - bridge, err := r.iam.OrganizationService.GetSCIMBridgeByID(ctx, obj.Bridge.ID) - if err != nil { - var errSCIMBridgeNotFound *iam.ErrSCIMBridgeNotFound - if errors.As(err, &errSCIMBridgeNotFound) { - return nil, nil - } - - r.logger.ErrorCtx(ctx, "cannot get scim bridge", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return types.NewSCIMBridge(bridge), nil -} - -// Events is the resolver for the events field. -func (r *sCIMConfigurationResolver) Events(ctx context.Context, obj *types.SCIMConfiguration, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.SCIMEventOrderBy) (*types.SCIMEventConnection, error) { - if err := r.authorize(ctx, obj.ID, iam.ActionSCIMEventList); err != nil { - return nil, err - } - - pageOrderBy := page.OrderBy[coredata.SCIMEventOrderField]{ - Field: coredata.SCIMEventOrderFieldCreatedAt, - Direction: page.OrderDirectionDesc, - } - if orderBy != nil { - pageOrderBy.Field = coredata.SCIMEventOrderField(orderBy.Field) - pageOrderBy.Direction = page.OrderDirection(orderBy.Direction) - } - - cursor := cursor.NewCursor(first, after, last, before, pageOrderBy) - - events, err := r.iam.OrganizationService.ListSCIMEventsByConfigID(ctx, obj.ID, cursor) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot list scim events", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return types.NewSCIMEventConnection(events, r, obj.ID), nil -} - -// Permission is the resolver for the permission field. -func (r *sCIMConfigurationResolver) Permission(ctx context.Context, obj *types.SCIMConfiguration, action string) (bool, error) { - return r.Resolver.Permission(ctx, obj, action) -} - -// Permission is the resolver for the permission field. -func (r *sCIMEventResolver) Permission(ctx context.Context, obj *types.SCIMEvent, action string) (bool, error) { - return r.Resolver.Permission(ctx, obj, action) -} - -// TotalCount is the resolver for the totalCount field. -func (r *sCIMEventConnectionResolver) TotalCount(ctx context.Context, obj *types.SCIMEventConnection) (*int, error) { - if err := r.authorize(ctx, obj.ParentID, iam.ActionSCIMEventList); err != nil { - return nil, err - } - - switch obj.Resolver.(type) { - case *sCIMConfigurationResolver: - count, err := r.iam.OrganizationService.CountSCIMEvents(ctx, obj.ParentID) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot count scim events", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - return &count, nil - } - - r.logger.ErrorCtx(ctx, "unsupported resolver", log.Any("resolver", obj.Resolver)) - return nil, gqlutils.Internal(ctx) -} - -// Identity is the resolver for the identity field. -func (r *sessionResolver) Identity(ctx context.Context, obj *types.Session) (*types.Identity, error) { - 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 session", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return types.NewIdentity(identity), nil -} - -// Permission is the resolver for the permission field. -func (r *sessionResolver) Permission(ctx context.Context, obj *types.Session, action string) (bool, error) { - return r.Resolver.Permission(ctx, obj, action) -} - -// TotalCount is the resolver for the totalCount field. -func (r *sessionConnectionResolver) TotalCount(ctx context.Context, obj *types.SessionConnection) (*int, error) { - switch obj.Resolver.(type) { - case *identityResolver: - count, err := r.iam.AccountService.CountSessions(ctx, obj.ParentID) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot count sessions", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &count, nil - } - - r.logger.ErrorCtx(ctx, "unsupported resolver", log.Any("resolver", obj.Resolver)) - return nil, gqlutils.Internal(ctx) -} - -// AuditLogEntry returns schema.AuditLogEntryResolver implementation. -func (r *Resolver) AuditLogEntry() schema.AuditLogEntryResolver { return &auditLogEntryResolver{r} } - -// AuditLogEntryConnection returns schema.AuditLogEntryConnectionResolver implementation. -func (r *Resolver) AuditLogEntryConnection() schema.AuditLogEntryConnectionResolver { - return &auditLogEntryConnectionResolver{r} -} - -// Connector returns schema.ConnectorResolver implementation. -func (r *Resolver) Connector() schema.ConnectorResolver { return &connectorResolver{r} } - -// Identity returns schema.IdentityResolver implementation. -func (r *Resolver) Identity() schema.IdentityResolver { return &identityResolver{r} } - -// Invitation returns schema.InvitationResolver implementation. -func (r *Resolver) Invitation() schema.InvitationResolver { return &invitationResolver{r} } - -// Membership returns schema.MembershipResolver implementation. -func (r *Resolver) Membership() schema.MembershipResolver { return &membershipResolver{r} } - -// Mutation returns schema.MutationResolver implementation. -func (r *Resolver) Mutation() schema.MutationResolver { return &mutationResolver{r} } - -// Organization returns schema.OrganizationResolver implementation. -func (r *Resolver) Organization() schema.OrganizationResolver { return &organizationResolver{r} } - -// PersonalAPIKey returns schema.PersonalAPIKeyResolver implementation. -func (r *Resolver) PersonalAPIKey() schema.PersonalAPIKeyResolver { return &personalAPIKeyResolver{r} } - -// PersonalAPIKeyConnection returns schema.PersonalAPIKeyConnectionResolver implementation. -func (r *Resolver) PersonalAPIKeyConnection() schema.PersonalAPIKeyConnectionResolver { - return &personalAPIKeyConnectionResolver{r} -} - -// Profile returns schema.ProfileResolver implementation. -func (r *Resolver) Profile() schema.ProfileResolver { return &profileResolver{r} } - -// ProfileConnection returns schema.ProfileConnectionResolver implementation. -func (r *Resolver) ProfileConnection() schema.ProfileConnectionResolver { - return &profileConnectionResolver{r} -} - -// Query returns schema.QueryResolver implementation. -func (r *Resolver) Query() schema.QueryResolver { return &queryResolver{r} } - -// SAMLConfiguration returns schema.SAMLConfigurationResolver implementation. -func (r *Resolver) SAMLConfiguration() schema.SAMLConfigurationResolver { - return &sAMLConfigurationResolver{r} -} - -// SAMLConfigurationConnection returns schema.SAMLConfigurationConnectionResolver implementation. -func (r *Resolver) SAMLConfigurationConnection() schema.SAMLConfigurationConnectionResolver { - return &sAMLConfigurationConnectionResolver{r} -} - -// SCIMBridge returns schema.SCIMBridgeResolver implementation. -func (r *Resolver) SCIMBridge() schema.SCIMBridgeResolver { return &sCIMBridgeResolver{r} } - -// SCIMConfiguration returns schema.SCIMConfigurationResolver implementation. -func (r *Resolver) SCIMConfiguration() schema.SCIMConfigurationResolver { - return &sCIMConfigurationResolver{r} -} - -// SCIMEvent returns schema.SCIMEventResolver implementation. -func (r *Resolver) SCIMEvent() schema.SCIMEventResolver { return &sCIMEventResolver{r} } - -// SCIMEventConnection returns schema.SCIMEventConnectionResolver implementation. -func (r *Resolver) SCIMEventConnection() schema.SCIMEventConnectionResolver { - return &sCIMEventConnectionResolver{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} -} - -type auditLogEntryResolver struct{ *Resolver } -type auditLogEntryConnectionResolver struct{ *Resolver } -type connectorResolver struct{ *Resolver } -type identityResolver struct{ *Resolver } -type invitationResolver struct{ *Resolver } -type membershipResolver struct{ *Resolver } -type mutationResolver struct{ *Resolver } -type organizationResolver struct{ *Resolver } -type personalAPIKeyResolver struct{ *Resolver } -type personalAPIKeyConnectionResolver struct{ *Resolver } -type profileResolver struct{ *Resolver } -type profileConnectionResolver struct{ *Resolver } -type queryResolver struct{ *Resolver } -type sAMLConfigurationResolver struct{ *Resolver } -type sAMLConfigurationConnectionResolver struct{ *Resolver } -type sCIMBridgeResolver struct{ *Resolver } -type sCIMConfigurationResolver struct{ *Resolver } -type sCIMEventResolver struct{ *Resolver } -type sCIMEventConnectionResolver struct{ *Resolver } -type sessionResolver struct{ *Resolver } -type sessionConnectionResolver struct{ *Resolver } diff --git a/pkg/server/api/console/v1/CLAUDE.md b/pkg/server/api/console/v1/CLAUDE.md index d571c7343..d928142e6 100644 --- a/pkg/server/api/console/v1/CLAUDE.md +++ b/pkg/server/api/console/v1/CLAUDE.md @@ -6,18 +6,26 @@ GraphQL API using `gqlgen`. Schema-first approach. | File | Type | Notes | |------|------|-------| -| `schema.graphql` | Hand-written | GraphQL schema definition | +| `graphql/*.graphql` | Hand-written | GraphQL schema split by entity (one file per coredata model) | | `gqlgen.yaml` | Hand-written | Codegen config | | `resolver.go` | Hand-written | Root `Resolver` struct and `NewMux` | | `graphql_handler.go` | Hand-written | Handler setup | -| `v1_resolver.go` | Generated stubs | Resolver method implementations (edit the bodies) | +| `*.resolvers.go` | Generated stubs | Per-entity resolver files (edit the bodies) | | `schema/schema.go` | **Generated — DO NOT EDIT** | Executable schema | | `types/types.go` | **Generated — DO NOT EDIT** | Type definitions | +## Schema file organization + +Schema files live in `graphql/` and are split by coredata model: +- `base.graphql` — directives, scalars, Node, PageInfo, root Query/Mutation/Organization/Viewer types +- Entity files (e.g., `vendor.graphql`, `control.graphql`) — use `extend type Organization`, `extend type Mutation`, etc. to add fields + +When adding a new entity, create a new `.graphql` file in `graphql/`. Types that get extended across files (Organization, Mutation, Viewer) must be defined in `base.graphql`. + ## Important rules -- **Never edit generated files** (`schema/schema.go`, `types/types.go`). Only edit `schema.graphql` and resolver bodies. -- **After any change to `schema.graphql`**, always run codegen: +- **Never edit generated files** (`schema/schema.go`, `types/types.go`). Only edit `graphql/*.graphql` and resolver bodies. +- **After any change to `graphql/*.graphql`**, always run codegen: ``` go generate ./pkg/server/api/console/v1 diff --git a/pkg/server/api/console/v1/access_review_campaign.resolvers.go b/pkg/server/api/console/v1/access_review_campaign.resolvers.go new file mode 100644 index 000000000..8668bad66 --- /dev/null +++ b/pkg/server/api/console/v1/access_review_campaign.resolvers.go @@ -0,0 +1,1057 @@ +package console_v1 + +// This file will be automatically regenerated based on the schema, any resolver +// implementations +// will be copied through when generating and any unknown code will be moved to the end. +// Code generated by github.com/99designs/gqlgen version v0.17.87 + +import ( + "context" + "errors" + "fmt" + + "go.probo.inc/probo/pkg/accessreview" + "go.probo.inc/probo/pkg/coredata" + "go.probo.inc/probo/pkg/gid" + "go.probo.inc/probo/pkg/page" + "go.probo.inc/probo/pkg/probo" + "go.probo.inc/probo/pkg/server/api/authn" + "go.probo.inc/probo/pkg/server/api/console/v1/schema" + "go.probo.inc/probo/pkg/server/api/console/v1/types" + "go.probo.inc/probo/pkg/server/gqlutils" +) + +// Campaign is the resolver for the campaign field. +func (r *accessEntryResolver) Campaign(ctx context.Context, obj *types.AccessEntry) (*types.AccessReviewCampaign, error) { + if err := r.authorize(ctx, obj.Campaign.ID, probo.ActionAccessReviewCampaignGet); err != nil { + return nil, err + } + + scope := coredata.NewScopeFromObjectID(obj.Campaign.ID) + + campaign, err := r.accessReview.Campaigns(scope).Get(ctx, obj.Campaign.ID) + if err != nil { + if errors.Is(err, coredata.ErrResourceNotFound) { + return nil, gqlutils.NotFound(ctx, err) + } + panic(fmt.Errorf("cannot get access review campaign: %w", err)) + } + + return types.NewAccessReviewCampaign(campaign), nil +} + +// AccessSource is the resolver for the accessSource field. +func (r *accessEntryResolver) AccessSource(ctx context.Context, obj *types.AccessEntry) (*types.AccessSource, error) { + if err := r.authorize(ctx, obj.AccessSource.ID, probo.ActionAccessSourceGet); err != nil { + return nil, err + } + + scope := coredata.NewScopeFromObjectID(obj.AccessSource.ID) + + source, err := r.accessReview.Sources(scope).Get(ctx, obj.AccessSource.ID) + if err != nil { + if errors.Is(err, coredata.ErrResourceNotFound) { + return nil, gqlutils.NotFound(ctx, err) + } + panic(fmt.Errorf("cannot get access source: %w", err)) + } + + return types.NewAccessSource(source), nil +} + +// DecisionHistory is the resolver for the decisionHistory field. +func (r *accessEntryResolver) DecisionHistory(ctx context.Context, obj *types.AccessEntry) ([]*types.AccessEntryDecisionHistoryEntry, error) { + if err := r.authorize(ctx, obj.ID, probo.ActionAccessEntryGet); err != nil { + return nil, err + } + + scope := coredata.NewScopeFromObjectID(obj.ID) + + histories, err := r.accessReview.Entries(scope).DecisionHistory(ctx, obj.ID) + if err != nil { + panic(fmt.Errorf("cannot get decision history: %w", err)) + } + + result := make([]*types.AccessEntryDecisionHistoryEntry, len(histories)) + for i, h := range histories { + result[i] = types.NewAccessEntryDecisionHistoryEntry(h) + } + + return result, nil +} + +// Permission is the resolver for the permission field. +func (r *accessEntryResolver) Permission(ctx context.Context, obj *types.AccessEntry, action string) (bool, error) { + return r.Resolver.Permission(ctx, obj, action) +} + +// TotalCount is the resolver for the totalCount field. +func (r *accessEntryConnectionResolver) TotalCount(ctx context.Context, obj *types.AccessEntryConnection) (int, error) { + scope := coredata.NewScopeFromObjectID(obj.ParentID) + + switch obj.Resolver.(type) { + case *accessReviewCampaignResolver: + if obj.SourceID != nil { + count, err := r.accessReview.Entries(scope).CountForCampaignIDAndSourceID(ctx, obj.ParentID, *obj.SourceID, obj.Filter) + if err != nil { + panic(fmt.Errorf("cannot count access entries: %w", err)) + } + return count, nil + } + count, err := r.accessReview.Entries(scope).CountForCampaignID(ctx, obj.ParentID, obj.Filter) + if err != nil { + panic(fmt.Errorf("cannot count access entries: %w", err)) + } + return count, nil + } + + panic(fmt.Errorf("unsupported resolver: %T", obj.Resolver)) +} + +// Organization is the resolver for the organization field. +func (r *accessReviewResolver) Organization(ctx context.Context, obj *types.AccessReview) (*types.Organization, error) { + return obj.Organization, nil +} + +// IdentitySource is the resolver for the identitySource field. +func (r *accessReviewResolver) IdentitySource(ctx context.Context, obj *types.AccessReview) (*types.AccessSource, error) { + return obj.IdentitySource, nil +} + +// AccessSources is the resolver for the accessSources field. +func (r *accessReviewResolver) AccessSources(ctx context.Context, obj *types.AccessReview, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.AccessSourceOrder) (*types.AccessSourceConnection, error) { + if err := r.authorize(ctx, obj.Organization.ID, probo.ActionAccessSourceList); err != nil { + return nil, err + } + + scope := coredata.NewScopeFromObjectID(obj.Organization.ID) + + pageOrderBy := page.OrderBy[coredata.AccessSourceOrderField]{ + Field: coredata.AccessSourceOrderFieldCreatedAt, + Direction: page.OrderDirectionDesc, + } + if orderBy != nil { + pageOrderBy = page.OrderBy[coredata.AccessSourceOrderField]{ + Field: orderBy.Field, + Direction: orderBy.Direction, + } + } + + cursor := types.NewCursor(first, after, last, before, pageOrderBy) + + p, err := r.accessReview.Sources(scope).ListForOrganizationID(ctx, obj.Organization.ID, cursor) + if err != nil { + panic(fmt.Errorf("cannot list access sources: %w", err)) + } + + return types.NewAccessSourceConnection(p, r, obj.Organization.ID), nil +} + +// Campaigns is the resolver for the campaigns field. +func (r *accessReviewResolver) Campaigns(ctx context.Context, obj *types.AccessReview, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.AccessReviewCampaignOrder) (*types.AccessReviewCampaignConnection, error) { + if err := r.authorize(ctx, obj.Organization.ID, probo.ActionAccessReviewCampaignList); err != nil { + return nil, err + } + + scope := coredata.NewScopeFromObjectID(obj.Organization.ID) + + pageOrderBy := page.OrderBy[coredata.AccessReviewCampaignOrderField]{ + Field: coredata.AccessReviewCampaignOrderFieldCreatedAt, + Direction: page.OrderDirectionDesc, + } + if orderBy != nil { + pageOrderBy = page.OrderBy[coredata.AccessReviewCampaignOrderField]{ + Field: orderBy.Field, + Direction: orderBy.Direction, + } + } + + cursor := types.NewCursor(first, after, last, before, pageOrderBy) + + p, err := r.accessReview.Campaigns(scope).ListForOrganizationID(ctx, obj.Organization.ID, cursor) + if err != nil { + panic(fmt.Errorf("cannot list access review campaigns: %w", err)) + } + + return types.NewAccessReviewCampaignConnection(p, r, obj.Organization.ID), nil +} + +// Permission is the resolver for the permission field. +func (r *accessReviewResolver) Permission(ctx context.Context, obj *types.AccessReview, action string) (bool, error) { + return r.Resolver.Permission(ctx, obj, action) +} + +// Organization is the resolver for the organization field. +func (r *accessReviewCampaignResolver) Organization(ctx context.Context, obj *types.AccessReviewCampaign) (*types.Organization, error) { + return obj.Organization, nil +} + +// ScopeSources is the resolver for the scopeSources field. +func (r *accessReviewCampaignResolver) ScopeSources(ctx context.Context, obj *types.AccessReviewCampaign) ([]*types.AccessReviewCampaignScopeSource, error) { + if err := r.authorize(ctx, obj.ID, probo.ActionAccessSourceList); err != nil { + return nil, err + } + + scope := coredata.NewScopeFromObjectID(obj.ID) + + sources, err := r.accessReview.Sources(scope).ListScopeSourcesForCampaignID(ctx, obj.ID) + if err != nil { + panic(fmt.Errorf("cannot list scope sources: %w", err)) + } + + fetches, err := r.accessReview.Campaigns(scope).ListSourceFetches(ctx, obj.ID) + if err != nil { + panic(fmt.Errorf("cannot list source fetch states: %w", err)) + } + + fetchBySourceID := make(map[gid.GID]*coredata.AccessReviewCampaignSourceFetch, len(fetches)) + for _, fetch := range fetches { + fetchBySourceID[fetch.AccessSourceID] = fetch + } + + result := make([]*types.AccessReviewCampaignScopeSource, len(sources)) + for i, s := range sources { + result[i] = types.NewAccessReviewCampaignScopeSource(obj.ID, s, fetchBySourceID[s.ID]) + } + + return result, nil +} + +// Entries is the resolver for the entries field. +func (r *accessReviewCampaignResolver) Entries(ctx context.Context, obj *types.AccessReviewCampaign, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.AccessEntryOrder, accessSourceID *gid.GID, filter *coredata.AccessEntryFilter) (*types.AccessEntryConnection, error) { + if err := r.authorize(ctx, obj.ID, probo.ActionAccessEntryList); err != nil { + return nil, err + } + + scope := coredata.NewScopeFromObjectID(obj.ID) + + pageOrderBy := page.OrderBy[coredata.AccessEntryOrderField]{ + Field: coredata.AccessEntryOrderFieldCreatedAt, + Direction: page.OrderDirectionDesc, + } + if orderBy != nil { + pageOrderBy = page.OrderBy[coredata.AccessEntryOrderField]{ + Field: orderBy.Field, + Direction: orderBy.Direction, + } + } + + cursor := types.NewCursor(first, after, last, before, pageOrderBy) + + var ( + p *page.Page[*coredata.AccessEntry, coredata.AccessEntryOrderField] + err error + ) + + if accessSourceID != nil { + p, err = r.accessReview.Entries(scope).ListForCampaignIDAndSourceID(ctx, obj.ID, *accessSourceID, cursor, filter) + } else { + p, err = r.accessReview.Entries(scope).ListForCampaignID(ctx, obj.ID, cursor, filter) + } + if err != nil { + panic(fmt.Errorf("cannot list access entries: %w", err)) + } + + return types.NewAccessEntryConnection(p, r, obj.ID, accessSourceID, filter), nil +} + +// PendingEntryCount is the resolver for the pendingEntryCount field. +func (r *accessReviewCampaignResolver) PendingEntryCount(ctx context.Context, obj *types.AccessReviewCampaign) (int, error) { + if err := r.authorize(ctx, obj.ID, probo.ActionAccessEntryList); err != nil { + return 0, err + } + + scope := coredata.NewScopeFromObjectID(obj.ID) + + count, err := r.accessReview.Entries(scope).CountPendingForCampaignID(ctx, obj.ID) + if err != nil { + panic(fmt.Errorf("cannot count pending access entries: %w", err)) + } + + return count, nil +} + +// Statistics is the resolver for the statistics field. +func (r *accessReviewCampaignResolver) Statistics(ctx context.Context, obj *types.AccessReviewCampaign) (*types.AccessReviewCampaignStatistics, error) { + if err := r.authorize(ctx, obj.ID, probo.ActionAccessEntryList); err != nil { + return nil, err + } + + scope := coredata.NewScopeFromObjectID(obj.ID) + + stats, err := r.accessReview.Entries(scope).Statistics(ctx, obj.ID) + if err != nil { + panic(fmt.Errorf("cannot get campaign statistics: %w", err)) + } + + return types.NewAccessReviewCampaignStatistics(stats), nil +} + +// Permission is the resolver for the permission field. +func (r *accessReviewCampaignResolver) Permission(ctx context.Context, obj *types.AccessReviewCampaign, action string) (bool, error) { + return r.Resolver.Permission(ctx, obj, action) +} + +// TotalCount is the resolver for the totalCount field. +func (r *accessReviewCampaignConnectionResolver) TotalCount(ctx context.Context, obj *types.AccessReviewCampaignConnection) (int, error) { + scope := coredata.NewScopeFromObjectID(obj.ParentID) + + switch obj.Resolver.(type) { + case *organizationResolver: + count, err := r.accessReview.Campaigns(scope).CountForOrganizationID(ctx, obj.ParentID) + if err != nil { + panic(fmt.Errorf("cannot count access review campaigns: %w", err)) + } + return count, nil + } + + panic(fmt.Errorf("unsupported resolver: %T", obj.Resolver)) +} + +// Entries is the resolver for the entries field. +func (r *accessReviewCampaignScopeSourceResolver) Entries(ctx context.Context, obj *types.AccessReviewCampaignScopeSource, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.AccessEntryOrder, filter *coredata.AccessEntryFilter) (*types.AccessEntryConnection, error) { + if err := r.authorize(ctx, obj.CampaignID, probo.ActionAccessEntryList); err != nil { + return nil, err + } + + scope := coredata.NewScopeFromObjectID(obj.CampaignID) + + pageOrderBy := page.OrderBy[coredata.AccessEntryOrderField]{ + Field: coredata.AccessEntryOrderFieldCreatedAt, + Direction: page.OrderDirectionDesc, + } + if orderBy != nil { + pageOrderBy = page.OrderBy[coredata.AccessEntryOrderField]{ + Field: orderBy.Field, + Direction: orderBy.Direction, + } + } + + cursor := types.NewCursor(first, after, last, before, pageOrderBy) + + p, err := r.accessReview.Entries(scope).ListForCampaignIDAndSourceID(ctx, obj.CampaignID, obj.ID, cursor, filter) + if err != nil { + panic(fmt.Errorf("cannot list access entries: %w", err)) + } + + sourceID := obj.ID + return types.NewAccessEntryConnection(p, r, obj.CampaignID, &sourceID, filter), nil +} + +// Statistics is the resolver for the statistics field. +func (r *accessReviewCampaignScopeSourceResolver) Statistics(ctx context.Context, obj *types.AccessReviewCampaignScopeSource) (*types.AccessReviewCampaignStatistics, error) { + if err := r.authorize(ctx, obj.CampaignID, probo.ActionAccessEntryList); err != nil { + return nil, err + } + + scope := coredata.NewScopeFromObjectID(obj.CampaignID) + + stats, err := r.accessReview.Entries(scope).StatisticsForSource(ctx, obj.CampaignID, obj.ID) + if err != nil { + panic(fmt.Errorf("cannot get source statistics: %w", err)) + } + + return types.NewAccessReviewCampaignStatistics(stats), nil +} + +// Organization is the resolver for the organization field. +func (r *accessSourceResolver) Organization(ctx context.Context, obj *types.AccessSource) (*types.Organization, error) { + return obj.Organization, nil +} + +// Connector is the resolver for the connector field. +func (r *accessSourceResolver) Connector(ctx context.Context, obj *types.AccessSource) (*types.Connector, error) { + if obj.ConnectorID == nil { + return nil, nil + } + + prb := r.ProboService(ctx, obj.ID.TenantID()) + + connector, err := prb.Connectors.Get(ctx, *obj.ConnectorID) + if err != nil { + if errors.Is(err, coredata.ErrResourceNotFound) { + return nil, nil + } + panic(fmt.Errorf("cannot get connector: %w", err)) + } + + return types.NewConnector(connector), nil +} + +// ProviderOrganizations is the resolver for the providerOrganizations field. +func (r *accessSourceResolver) ProviderOrganizations(ctx context.Context, obj *types.AccessSource) ([]*types.ProviderOrganization, error) { + if err := r.authorize(ctx, obj.ID, probo.ActionAccessSourceGet); err != nil { + return nil, err + } + + if obj.ConnectorID == nil { + return []*types.ProviderOrganization{}, nil + } + + scope := coredata.NewScopeFromObjectID(obj.ID) + + httpClient, dbConnector, err := r.accessReview.Sources(scope).ConnectorHTTPClient(ctx, *obj.ConnectorID) + if err != nil { + if errors.Is(err, coredata.ErrResourceNotFound) { + return []*types.ProviderOrganization{}, nil + } + return nil, fmt.Errorf("cannot get connector HTTP client: %w", err) + } + + switch dbConnector.Provider { + case coredata.ConnectorProviderGitHub: + orgs, err := fetchGitHubOrganizations(ctx, httpClient) + if err != nil { + return nil, fmt.Errorf("cannot fetch github organizations: %w", err) + } + return orgs, nil + case coredata.ConnectorProviderSentry: + orgs, err := fetchSentryOrganizations(ctx, httpClient) + if err != nil { + return nil, fmt.Errorf("cannot fetch sentry organizations: %w", err) + } + return orgs, nil + default: + return []*types.ProviderOrganization{}, nil + } +} + +// NeedsConfiguration is the resolver for the needsConfiguration field. +func (r *accessSourceResolver) NeedsConfiguration(ctx context.Context, obj *types.AccessSource) (bool, error) { + if obj.ConnectorID == nil { + return false, nil + } + + prb := r.ProboService(ctx, obj.ID.TenantID()) + + dbConnector, err := prb.Connectors.Get(ctx, *obj.ConnectorID) + if err != nil { + if errors.Is(err, coredata.ErrResourceNotFound) { + return false, nil + } + panic(fmt.Errorf("cannot get connector: %w", err)) + } + + switch dbConnector.Provider { + case coredata.ConnectorProviderGitHub: + settings, _ := dbConnector.GitHubSettings() + return settings.Organization == "", nil + case coredata.ConnectorProviderSentry: + settings, _ := dbConnector.SentrySettings() + return settings.OrganizationSlug == "", nil + default: + return false, nil + } +} + +// ConnectionStatus is the resolver for the connectionStatus field. +func (r *accessSourceResolver) ConnectionStatus(ctx context.Context, obj *types.AccessSource) (types.AccessSourceConnectionStatus, error) { + if obj.ConnectorID == nil { + return types.AccessSourceConnectionStatusNotApplicable, nil + } + + scope := coredata.NewScopeFromObjectID(obj.ID) + + httpClient, dbConnector, err := r.accessReview.Sources(scope).ConnectorHTTPClient(ctx, *obj.ConnectorID) + if err != nil { + if errors.Is(err, coredata.ErrResourceNotFound) { + return types.AccessSourceConnectionStatusNotApplicable, nil + } + return types.AccessSourceConnectionStatusDisconnected, nil + } + + if dbConnector.Protocol != coredata.ConnectorProtocolOAuth2 { + return types.AccessSourceConnectionStatusConnected, nil + } + + // Creating an HTTP client may succeed even with an expired token + // (e.g. no refresh token available). Make a lightweight probe + // request to verify the token is actually valid. + probeURL := r.connectorRegistry.GetProbeURL(string(dbConnector.Provider)) + if err := probeConnection(ctx, httpClient, probeURL); err != nil { + return types.AccessSourceConnectionStatusDisconnected, nil + } + + return types.AccessSourceConnectionStatusConnected, nil +} + +// SelectedOrganization is the resolver for the selectedOrganization field. +func (r *accessSourceResolver) SelectedOrganization(ctx context.Context, obj *types.AccessSource) (*string, error) { + if obj.ConnectorID == nil { + return nil, nil + } + + prb := r.ProboService(ctx, obj.ID.TenantID()) + + dbConnector, err := prb.Connectors.Get(ctx, *obj.ConnectorID) + if err != nil { + if errors.Is(err, coredata.ErrResourceNotFound) { + return nil, nil + } + panic(fmt.Errorf("cannot get connector: %w", err)) + } + + switch dbConnector.Provider { + case coredata.ConnectorProviderGitHub: + settings, _ := dbConnector.GitHubSettings() + if settings.Organization != "" { + return &settings.Organization, nil + } + case coredata.ConnectorProviderSentry: + settings, _ := dbConnector.SentrySettings() + if settings.OrganizationSlug != "" { + return &settings.OrganizationSlug, nil + } + } + + return nil, nil +} + +// Permission is the resolver for the permission field. +func (r *accessSourceResolver) Permission(ctx context.Context, obj *types.AccessSource, action string) (bool, error) { + return r.Resolver.Permission(ctx, obj, action) +} + +// TotalCount is the resolver for the totalCount field. +func (r *accessSourceConnectionResolver) TotalCount(ctx context.Context, obj *types.AccessSourceConnection) (int, error) { + scope := coredata.NewScopeFromObjectID(obj.ParentID) + + switch obj.Resolver.(type) { + case *organizationResolver: + count, err := r.accessReview.Sources(scope).CountForOrganizationID(ctx, obj.ParentID) + if err != nil { + panic(fmt.Errorf("cannot count access sources: %w", err)) + } + return count, nil + } + + panic(fmt.Errorf("unsupported resolver: %T", obj.Resolver)) +} + +// CreateAccessSource is the resolver for the createAccessSource field. +func (r *mutationResolver) CreateAccessSource(ctx context.Context, input types.CreateAccessSourceInput) (*types.CreateAccessSourcePayload, error) { + if err := r.authorize(ctx, input.OrganizationID, probo.ActionAccessSourceCreate); err != nil { + return nil, err + } + + scope := coredata.NewScopeFromObjectID(input.OrganizationID) + + source, err := r.accessReview.Sources(scope).Create(ctx, accessreview.CreateAccessSourceRequest{ + OrganizationID: input.OrganizationID, + ConnectorID: input.ConnectorID, + Name: input.Name, + Category: coredata.AccessSourceCategorySaaS, + CsvData: input.CSVData, + }) + if err != nil { + panic(fmt.Errorf("cannot create access source: %w", err)) + } + + return &types.CreateAccessSourcePayload{ + AccessSourceEdge: types.NewAccessSourceEdge(source, coredata.AccessSourceOrderFieldCreatedAt), + }, nil +} + +// UpdateAccessSource is the resolver for the updateAccessSource field. +func (r *mutationResolver) UpdateAccessSource(ctx context.Context, input types.UpdateAccessSourceInput) (*types.UpdateAccessSourcePayload, error) { + if err := r.authorize(ctx, input.AccessSourceID, probo.ActionAccessSourceUpdate); err != nil { + return nil, err + } + + scope := coredata.NewScopeFromObjectID(input.AccessSourceID) + + req := accessreview.UpdateAccessSourceRequest{ + AccessSourceID: input.AccessSourceID, + } + if input.Name.IsSet() { + req.Name = input.Name.Value() + } + if input.ConnectorID.IsSet() { + req.ConnectorID = gqlutils.UnwrapOmittable(input.ConnectorID) + } + if input.CSVData.IsSet() { + req.CsvData = gqlutils.UnwrapOmittable(input.CSVData) + } + + source, err := r.accessReview.Sources(scope).Update(ctx, req) + if err != nil { + if errors.Is(err, coredata.ErrResourceNotFound) { + return nil, gqlutils.NotFound(ctx, err) + } + panic(fmt.Errorf("cannot update access source: %w", err)) + } + + return &types.UpdateAccessSourcePayload{ + AccessSource: types.NewAccessSource(source), + }, nil +} + +// DeleteAccessSource is the resolver for the deleteAccessSource field. +func (r *mutationResolver) DeleteAccessSource(ctx context.Context, input types.DeleteAccessSourceInput) (*types.DeleteAccessSourcePayload, error) { + if err := r.authorize(ctx, input.AccessSourceID, probo.ActionAccessSourceDelete); err != nil { + return nil, err + } + + scope := coredata.NewScopeFromObjectID(input.AccessSourceID) + + if err := r.accessReview.Sources(scope).Delete(ctx, input.AccessSourceID); err != nil { + if errors.Is(err, coredata.ErrResourceNotFound) { + return nil, gqlutils.NotFound(ctx, err) + } + panic(fmt.Errorf("cannot delete access source: %w", err)) + } + + return &types.DeleteAccessSourcePayload{ + DeletedAccessSourceID: input.AccessSourceID, + }, nil +} + +// ConfigureAccessSource is the resolver for the configureAccessSource field. +func (r *mutationResolver) ConfigureAccessSource(ctx context.Context, input types.ConfigureAccessSourceInput) (*types.ConfigureAccessSourcePayload, error) { + if err := r.authorize(ctx, input.AccessSourceID, probo.ActionAccessSourceUpdate); err != nil { + return nil, err + } + + scope := coredata.NewScopeFromObjectID(input.AccessSourceID) + + source, err := r.accessReview.Sources(scope).ConfigureAccessSource( + ctx, + accessreview.ConfigureAccessSourceRequest{ + AccessSourceID: input.AccessSourceID, + OrganizationSlug: input.OrganizationSlug, + }, + ) + if err != nil { + if errors.Is(err, coredata.ErrResourceNotFound) { + return nil, gqlutils.NotFound(ctx, err) + } + panic(fmt.Errorf("cannot configure access source: %w", err)) + } + + return &types.ConfigureAccessSourcePayload{ + AccessSource: types.NewAccessSource(source), + }, nil +} + +// CreateAccessReviewCampaign is the resolver for the createAccessReviewCampaign field. +func (r *mutationResolver) CreateAccessReviewCampaign(ctx context.Context, input types.CreateAccessReviewCampaignInput) (*types.CreateAccessReviewCampaignPayload, error) { + if err := r.authorize(ctx, input.OrganizationID, probo.ActionAccessReviewCampaignCreate); err != nil { + return nil, err + } + + scope := coredata.NewScopeFromObjectID(input.OrganizationID) + + var description string + if input.Description != nil { + description = *input.Description + } + + campaign, err := r.accessReview.Campaigns(scope).Create(ctx, accessreview.CreateAccessReviewCampaignRequest{ + OrganizationID: input.OrganizationID, + Name: input.Name, + Description: description, + FrameworkControls: input.FrameworkControls, + AccessSourceIDs: input.AccessSourceIds, + }) + if err != nil { + panic(fmt.Errorf("cannot create access review campaign: %w", err)) + } + + return &types.CreateAccessReviewCampaignPayload{ + AccessReviewCampaignEdge: types.NewAccessReviewCampaignEdge(campaign, coredata.AccessReviewCampaignOrderFieldCreatedAt), + }, nil +} + +// UpdateAccessReviewCampaign is the resolver for the updateAccessReviewCampaign field. +func (r *mutationResolver) UpdateAccessReviewCampaign(ctx context.Context, input types.UpdateAccessReviewCampaignInput) (*types.UpdateAccessReviewCampaignPayload, error) { + if err := r.authorize(ctx, input.AccessReviewCampaignID, probo.ActionAccessReviewCampaignUpdate); err != nil { + return nil, err + } + + scope := coredata.NewScopeFromObjectID(input.AccessReviewCampaignID) + + req := accessreview.UpdateAccessReviewCampaignRequest{ + CampaignID: input.AccessReviewCampaignID, + } + if input.Name.IsSet() { + req.Name = input.Name.Value() + } + if input.Description.IsSet() { + req.Description = input.Description.Value() + } + if input.FrameworkControls.IsSet() { + controls := input.FrameworkControls.Value() + req.FrameworkControls = &controls + } + + campaign, err := r.accessReview.Campaigns(scope).Update(ctx, req) + if err != nil { + if errors.Is(err, coredata.ErrResourceNotFound) { + return nil, gqlutils.NotFound(ctx, err) + } + panic(fmt.Errorf("cannot update access review campaign: %w", err)) + } + + return &types.UpdateAccessReviewCampaignPayload{ + AccessReviewCampaign: types.NewAccessReviewCampaign(campaign), + }, nil +} + +// DeleteAccessReviewCampaign is the resolver for the deleteAccessReviewCampaign field. +func (r *mutationResolver) DeleteAccessReviewCampaign(ctx context.Context, input types.DeleteAccessReviewCampaignInput) (*types.DeleteAccessReviewCampaignPayload, error) { + if err := r.authorize(ctx, input.AccessReviewCampaignID, probo.ActionAccessReviewCampaignDelete); err != nil { + return nil, err + } + + scope := coredata.NewScopeFromObjectID(input.AccessReviewCampaignID) + + if err := r.accessReview.Campaigns(scope).Delete(ctx, input.AccessReviewCampaignID); err != nil { + if errors.Is(err, coredata.ErrResourceNotFound) { + return nil, gqlutils.NotFound(ctx, err) + } + panic(fmt.Errorf("cannot delete access review campaign: %w", err)) + } + + return &types.DeleteAccessReviewCampaignPayload{ + DeletedAccessReviewCampaignID: input.AccessReviewCampaignID, + }, nil +} + +// StartAccessReviewCampaign is the resolver for the startAccessReviewCampaign field. +func (r *mutationResolver) StartAccessReviewCampaign(ctx context.Context, input types.StartAccessReviewCampaignInput) (*types.StartAccessReviewCampaignPayload, error) { + if err := r.authorize(ctx, input.AccessReviewCampaignID, probo.ActionAccessReviewCampaignStart); err != nil { + return nil, err + } + + scope := coredata.NewScopeFromObjectID(input.AccessReviewCampaignID) + + campaign, err := r.accessReview.Campaigns(scope).Start(ctx, input.AccessReviewCampaignID) + if err != nil { + panic(fmt.Errorf("cannot start access review campaign: %w", err)) + } + + return &types.StartAccessReviewCampaignPayload{ + AccessReviewCampaign: types.NewAccessReviewCampaign(campaign), + }, nil +} + +// CloseAccessReviewCampaign is the resolver for the closeAccessReviewCampaign field. +func (r *mutationResolver) CloseAccessReviewCampaign(ctx context.Context, input types.CloseAccessReviewCampaignInput) (*types.CloseAccessReviewCampaignPayload, error) { + if err := r.authorize(ctx, input.AccessReviewCampaignID, probo.ActionAccessReviewCampaignClose); err != nil { + return nil, err + } + + scope := coredata.NewScopeFromObjectID(input.AccessReviewCampaignID) + + campaign, err := r.accessReview.Campaigns(scope).Close(ctx, input.AccessReviewCampaignID) + if err != nil { + panic(fmt.Errorf("cannot close access review campaign: %w", err)) + } + + return &types.CloseAccessReviewCampaignPayload{ + AccessReviewCampaign: types.NewAccessReviewCampaign(campaign), + }, nil +} + +// CancelAccessReviewCampaign is the resolver for the cancelAccessReviewCampaign field. +func (r *mutationResolver) CancelAccessReviewCampaign(ctx context.Context, input types.CancelAccessReviewCampaignInput) (*types.CancelAccessReviewCampaignPayload, error) { + if err := r.authorize(ctx, input.AccessReviewCampaignID, probo.ActionAccessReviewCampaignCancel); err != nil { + return nil, err + } + + scope := coredata.NewScopeFromObjectID(input.AccessReviewCampaignID) + + campaign, err := r.accessReview.Campaigns(scope).Cancel(ctx, input.AccessReviewCampaignID) + if err != nil { + panic(fmt.Errorf("cannot cancel access review campaign: %w", err)) + } + + return &types.CancelAccessReviewCampaignPayload{ + AccessReviewCampaign: types.NewAccessReviewCampaign(campaign), + }, nil +} + +// AddAccessReviewCampaignScopeSource is the resolver for the addAccessReviewCampaignScopeSource field. +func (r *mutationResolver) AddAccessReviewCampaignScopeSource(ctx context.Context, input types.AddAccessReviewCampaignScopeSourceInput) (*types.AddAccessReviewCampaignScopeSourcePayload, error) { + if err := r.authorize(ctx, input.AccessReviewCampaignID, probo.ActionAccessReviewCampaignAddScopeSource); err != nil { + return nil, err + } + + scope := coredata.NewScopeFromObjectID(input.AccessReviewCampaignID) + + campaign, err := r.accessReview.Campaigns(scope).AddScopeSource(ctx, accessreview.AddCampaignScopeSourceRequest{ + CampaignID: input.AccessReviewCampaignID, + AccessSourceID: input.AccessSourceID, + }) + if err != nil { + panic(fmt.Errorf("cannot add scope source to access review campaign: %w", err)) + } + + return &types.AddAccessReviewCampaignScopeSourcePayload{ + AccessReviewCampaign: types.NewAccessReviewCampaign(campaign), + }, nil +} + +// RemoveAccessReviewCampaignScopeSource is the resolver for the removeAccessReviewCampaignScopeSource field. +func (r *mutationResolver) RemoveAccessReviewCampaignScopeSource(ctx context.Context, input types.RemoveAccessReviewCampaignScopeSourceInput) (*types.RemoveAccessReviewCampaignScopeSourcePayload, error) { + if err := r.authorize(ctx, input.AccessReviewCampaignID, probo.ActionAccessReviewCampaignRemoveScopeSource); err != nil { + return nil, err + } + + scope := coredata.NewScopeFromObjectID(input.AccessReviewCampaignID) + + campaign, err := r.accessReview.Campaigns(scope).RemoveScopeSource(ctx, accessreview.RemoveCampaignScopeSourceRequest{ + CampaignID: input.AccessReviewCampaignID, + AccessSourceID: input.AccessSourceID, + }) + if err != nil { + panic(fmt.Errorf("cannot remove scope source from access review campaign: %w", err)) + } + + return &types.RemoveAccessReviewCampaignScopeSourcePayload{ + AccessReviewCampaign: types.NewAccessReviewCampaign(campaign), + }, nil +} + +// RecordAccessEntryDecision is the resolver for the recordAccessEntryDecision field. +func (r *mutationResolver) RecordAccessEntryDecision(ctx context.Context, input types.RecordAccessEntryDecisionInput) (*types.RecordAccessEntryDecisionPayload, error) { + if err := r.authorize(ctx, input.AccessEntryID, probo.ActionAccessEntryDecide); err != nil { + return nil, err + } + + scope := coredata.NewScopeFromObjectID(input.AccessEntryID) + + // Resolve the profile ID from the session's identity. + // The profile may not exist for every identity, in which + // case decided_by will be left nil. + identity := authn.IdentityFromContext(ctx) + if identity == nil { + return nil, fmt.Errorf("no identity in context") + } + + req := accessreview.RecordAccessEntryDecisionRequest{ + EntryID: input.AccessEntryID, + Decision: input.Decision, + DecisionNote: input.DecisionNote, + } + + organizationID, err := r.accessReview.ResolveEntryOrganizationID(ctx, input.AccessEntryID) + if err == nil { + profile, err := r.iam.OrganizationService.GetProfileForIdentityAndOrganization(ctx, identity.ID, organizationID) + if err == nil { + req.DecidedByID = &profile.ID + } + } + + entry, err := r.accessReview.Entries(scope).RecordDecision(ctx, req) + if err != nil { + if errors.Is(err, coredata.ErrResourceNotFound) { + return nil, gqlutils.NotFound(ctx, err) + } + panic(fmt.Errorf("cannot record access entry decision: %w", err)) + } + + return &types.RecordAccessEntryDecisionPayload{ + AccessEntry: types.NewAccessEntry(entry), + }, nil +} + +// RecordAccessEntryDecisions is the resolver for the recordAccessEntryDecisions field. +func (r *mutationResolver) RecordAccessEntryDecisions(ctx context.Context, input types.RecordAccessEntryDecisionsInput) (*types.RecordAccessEntryDecisionsPayload, error) { + if len(input.Decisions) == 0 { + return &types.RecordAccessEntryDecisionsPayload{ + AccessEntries: []*types.AccessEntry{}, + }, nil + } + + const maxBatchSize = 100 + if len(input.Decisions) > maxBatchSize { + return nil, fmt.Errorf("cannot record decisions: batch size %d exceeds maximum of %d", len(input.Decisions), maxBatchSize) + } + + // Authorize each entry individually to prevent cross-org bypass. + for _, d := range input.Decisions { + if err := r.authorize(ctx, d.AccessEntryID, probo.ActionAccessEntryDecide); err != nil { + return nil, err + } + } + + identity := authn.IdentityFromContext(ctx) + if identity == nil { + return nil, fmt.Errorf("no identity in context") + } + + tenantID := input.Decisions[0].AccessEntryID.TenantID() + scope := coredata.NewScope(tenantID) + + // Cache profile lookups per organization so we resolve the correct + // decidedByID for each entry even when a batch spans multiple orgs. + profileCache := make(map[gid.GID]*gid.GID) + + decisions := make([]accessreview.RecordAccessEntryDecisionRequest, len(input.Decisions)) + for i, d := range input.Decisions { + var decidedByID *gid.GID + organizationID, err := r.accessReview.ResolveEntryOrganizationID(ctx, d.AccessEntryID) + if err == nil { + if cached, ok := profileCache[organizationID]; ok { + decidedByID = cached + } else { + profile, err := r.iam.OrganizationService.GetProfileForIdentityAndOrganization(ctx, identity.ID, organizationID) + if err == nil { + decidedByID = &profile.ID + } + profileCache[organizationID] = decidedByID + } + } + + decisions[i] = accessreview.RecordAccessEntryDecisionRequest{ + EntryID: d.AccessEntryID, + Decision: d.Decision, + DecisionNote: d.DecisionNote, + DecidedByID: decidedByID, + } + } + + entries, err := r.accessReview.Entries(scope).RecordDecisions(ctx, decisions) + if err != nil { + if errors.Is(err, coredata.ErrResourceNotFound) { + return nil, gqlutils.NotFound(ctx, err) + } + panic(fmt.Errorf("cannot record access entry decisions: %w", err)) + } + + accessEntries := make([]*types.AccessEntry, len(entries)) + for i, e := range entries { + accessEntries[i] = types.NewAccessEntry(e) + } + + return &types.RecordAccessEntryDecisionsPayload{ + AccessEntries: accessEntries, + }, nil +} + +// FlagAccessEntry is the resolver for the flagAccessEntry field. +func (r *mutationResolver) FlagAccessEntry(ctx context.Context, input types.FlagAccessEntryInput) (*types.FlagAccessEntryPayload, error) { + if err := r.authorize(ctx, input.AccessEntryID, probo.ActionAccessEntryFlag); err != nil { + return nil, err + } + + scope := coredata.NewScopeFromObjectID(input.AccessEntryID) + + entry, err := r.accessReview.Entries(scope).FlagEntry(ctx, accessreview.FlagAccessEntryRequest{ + EntryID: input.AccessEntryID, + Flags: input.Flags, + FlagReasons: input.FlagReasons, + }) + if err != nil { + if errors.Is(err, coredata.ErrResourceNotFound) { + return nil, gqlutils.NotFound(ctx, err) + } + panic(fmt.Errorf("cannot flag access entry: %w", err)) + } + + return &types.FlagAccessEntryPayload{ + AccessEntry: types.NewAccessEntry(entry), + }, nil +} + +// AccessSources is the resolver for the accessSources field. +func (r *organizationResolver) AccessSources(ctx context.Context, obj *types.Organization, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.AccessSourceOrder) (*types.AccessSourceConnection, error) { + if err := r.authorize(ctx, obj.ID, probo.ActionAccessSourceList); err != nil { + return nil, err + } + + scope := coredata.NewScopeFromObjectID(obj.ID) + + pageOrderBy := page.OrderBy[coredata.AccessSourceOrderField]{ + Field: coredata.AccessSourceOrderFieldCreatedAt, + Direction: page.OrderDirectionDesc, + } + if orderBy != nil { + pageOrderBy = page.OrderBy[coredata.AccessSourceOrderField]{ + Field: orderBy.Field, + Direction: orderBy.Direction, + } + } + + cursor := types.NewCursor(first, after, last, before, pageOrderBy) + + p, err := r.accessReview.Sources(scope).ListForOrganizationID(ctx, obj.ID, cursor) + if err != nil { + panic(fmt.Errorf("cannot list access sources: %w", err)) + } + + return types.NewAccessSourceConnection(p, r, obj.ID), nil +} + +// AccessReviewCampaigns is the resolver for the accessReviewCampaigns field. +func (r *organizationResolver) AccessReviewCampaigns(ctx context.Context, obj *types.Organization, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.AccessReviewCampaignOrder) (*types.AccessReviewCampaignConnection, error) { + if err := r.authorize(ctx, obj.ID, probo.ActionAccessReviewCampaignList); err != nil { + return nil, err + } + + scope := coredata.NewScopeFromObjectID(obj.ID) + + pageOrderBy := page.OrderBy[coredata.AccessReviewCampaignOrderField]{ + Field: coredata.AccessReviewCampaignOrderFieldCreatedAt, + Direction: page.OrderDirectionDesc, + } + if orderBy != nil { + pageOrderBy = page.OrderBy[coredata.AccessReviewCampaignOrderField]{ + Field: orderBy.Field, + Direction: orderBy.Direction, + } + } + + cursor := types.NewCursor(first, after, last, before, pageOrderBy) + + p, err := r.accessReview.Campaigns(scope).ListForOrganizationID(ctx, obj.ID, cursor) + if err != nil { + panic(fmt.Errorf("cannot list access review campaigns: %w", err)) + } + + return types.NewAccessReviewCampaignConnection(p, r, obj.ID), nil +} + +// AccessEntry returns schema.AccessEntryResolver implementation. +func (r *Resolver) AccessEntry() schema.AccessEntryResolver { return &accessEntryResolver{r} } + +// AccessEntryConnection returns schema.AccessEntryConnectionResolver implementation. +func (r *Resolver) AccessEntryConnection() schema.AccessEntryConnectionResolver { + return &accessEntryConnectionResolver{r} +} + +// AccessReview returns schema.AccessReviewResolver implementation. +func (r *Resolver) AccessReview() schema.AccessReviewResolver { return &accessReviewResolver{r} } + +// AccessReviewCampaign returns schema.AccessReviewCampaignResolver implementation. +func (r *Resolver) AccessReviewCampaign() schema.AccessReviewCampaignResolver { + return &accessReviewCampaignResolver{r} +} + +// AccessReviewCampaignConnection returns schema.AccessReviewCampaignConnectionResolver implementation. +func (r *Resolver) AccessReviewCampaignConnection() schema.AccessReviewCampaignConnectionResolver { + return &accessReviewCampaignConnectionResolver{r} +} + +// AccessReviewCampaignScopeSource returns schema.AccessReviewCampaignScopeSourceResolver implementation. +func (r *Resolver) AccessReviewCampaignScopeSource() schema.AccessReviewCampaignScopeSourceResolver { + return &accessReviewCampaignScopeSourceResolver{r} +} + +// AccessSource returns schema.AccessSourceResolver implementation. +func (r *Resolver) AccessSource() schema.AccessSourceResolver { return &accessSourceResolver{r} } + +// AccessSourceConnection returns schema.AccessSourceConnectionResolver implementation. +func (r *Resolver) AccessSourceConnection() schema.AccessSourceConnectionResolver { + return &accessSourceConnectionResolver{r} +} + +type accessEntryResolver struct{ *Resolver } +type accessEntryConnectionResolver struct{ *Resolver } +type accessReviewResolver struct{ *Resolver } +type accessReviewCampaignResolver struct{ *Resolver } +type accessReviewCampaignConnectionResolver struct{ *Resolver } +type accessReviewCampaignScopeSourceResolver struct{ *Resolver } +type accessSourceResolver struct{ *Resolver } +type accessSourceConnectionResolver struct{ *Resolver } diff --git a/pkg/server/api/console/v1/asset.resolvers.go b/pkg/server/api/console/v1/asset.resolvers.go new file mode 100644 index 000000000..0f1d203a1 --- /dev/null +++ b/pkg/server/api/console/v1/asset.resolvers.go @@ -0,0 +1,497 @@ +package console_v1 + +// This file will be automatically regenerated based on the schema, any resolver +// implementations +// will be copied through when generating and any unknown code will be moved to the end. +// Code generated by github.com/99designs/gqlgen version v0.17.87 + +import ( + "context" + "errors" + "fmt" + + "github.com/vikstrous/dataloadgen" + "go.gearno.de/kit/log" + "go.probo.inc/probo/pkg/coredata" + "go.probo.inc/probo/pkg/iam" + "go.probo.inc/probo/pkg/page" + "go.probo.inc/probo/pkg/probo" + "go.probo.inc/probo/pkg/server/api/console/v1/dataloader" + "go.probo.inc/probo/pkg/server/api/console/v1/schema" + "go.probo.inc/probo/pkg/server/api/console/v1/types" + "go.probo.inc/probo/pkg/server/gqlutils" + "go.probo.inc/probo/pkg/validator" +) + +// Owner is the resolver for the owner field. +func (r *assetResolver) Owner(ctx context.Context, obj *types.Asset) (*types.Profile, error) { + if err := r.authorize(ctx, obj.ID, iam.ActionMembershipProfileGet); err != nil { + return nil, err + } + + loaders := dataloader.FromContext(ctx) + + owner, err := loaders.Profile.Load(ctx, obj.Owner.ID) + if err != nil { + if errors.Is(err, coredata.ErrResourceNotFound) || errors.Is(err, dataloadgen.ErrNotFound) { + return nil, gqlutils.NotFound(ctx, err) + } + + r.logger.ErrorCtx(ctx, "cannot get owner", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return types.NewProfile(owner), nil +} + +// Vendors is the resolver for the vendors field. +func (r *assetResolver) Vendors(ctx context.Context, obj *types.Asset, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.VendorOrderBy) (*types.VendorConnection, error) { + if err := r.authorize(ctx, obj.ID, probo.ActionVendorList); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, obj.ID.TenantID()) + + pageOrderBy := page.OrderBy[coredata.VendorOrderField]{ + Field: coredata.VendorOrderFieldCreatedAt, + Direction: page.OrderDirectionDesc, + } + if orderBy != nil { + pageOrderBy = page.OrderBy[coredata.VendorOrderField]{ + Field: orderBy.Field, + Direction: orderBy.Direction, + } + } + + cursor := types.NewCursor(first, after, last, before, pageOrderBy) + + page, err := prb.Vendors.ListForAssetID(ctx, obj.ID, cursor) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot list asset vendors", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return types.NewVendorConnection(page, r, obj.ID), nil +} + +// Organization is the resolver for the organization field. +func (r *assetResolver) Organization(ctx context.Context, obj *types.Asset) (*types.Organization, error) { + if err := r.authorize(ctx, obj.ID, probo.ActionOrganizationGet); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, obj.ID.TenantID()) + + asset, err := prb.Assets.Get(ctx, obj.ID) + if err != nil { + + r.logger.ErrorCtx(ctx, "cannot load audit", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + org, err := prb.Organizations.Get(ctx, asset.OrganizationID) + if err != nil { + if errors.Is(err, coredata.ErrResourceNotFound) { + return nil, gqlutils.NotFound(ctx, err) + } + + r.logger.ErrorCtx(ctx, "cannot get organization", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return types.NewOrganization(org), nil +} + +// Permission is the resolver for the permission field. +func (r *assetResolver) Permission(ctx context.Context, obj *types.Asset, action string) (bool, error) { + return r.Resolver.Permission(ctx, obj, action) +} + +// TotalCount is the resolver for the totalCount field. +func (r *assetConnectionResolver) TotalCount(ctx context.Context, obj *types.AssetConnection) (int, error) { + if err := r.authorize(ctx, obj.ParentID, probo.ActionAssetList); err != nil { + return 0, err + } + + prb := r.ProboService(ctx, obj.ParentID.TenantID()) + + switch obj.Resolver.(type) { + case *organizationResolver: + assetFilter := coredata.NewAssetFilter(nil) + if obj.Filter != nil { + assetFilter = coredata.NewAssetFilter(&obj.Filter.SnapshotID) + } + + count, err := prb.Assets.CountForOrganizationID(ctx, obj.ParentID, assetFilter) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot count assets", log.Error(err)) + return 0, gqlutils.Internal(ctx) + } + return count, nil + } + + r.logger.ErrorCtx(ctx, "unsupported resolver") + return 0, gqlutils.Internal(ctx) +} + +// Owner is the resolver for the owner field. +func (r *datumResolver) Owner(ctx context.Context, obj *types.Datum) (*types.Profile, error) { + if err := r.authorize(ctx, obj.ID, iam.ActionMembershipProfileGet); err != nil { + return nil, err + } + + loaders := dataloader.FromContext(ctx) + + owner, err := loaders.Profile.Load(ctx, obj.Owner.ID) + if err != nil { + if errors.Is(err, coredata.ErrResourceNotFound) || errors.Is(err, dataloadgen.ErrNotFound) { + return nil, gqlutils.NotFound(ctx, err) + } + + return nil, fmt.Errorf("cannot get owner: %w", err) + } + + return types.NewProfile(owner), nil +} + +// Vendors is the resolver for the vendors field. +func (r *datumResolver) Vendors(ctx context.Context, obj *types.Datum, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.VendorOrderBy) (*types.VendorConnection, error) { + if err := r.authorize(ctx, obj.ID, probo.ActionVendorList); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, obj.ID.TenantID()) + + pageOrderBy := page.OrderBy[coredata.VendorOrderField]{ + Field: coredata.VendorOrderFieldCreatedAt, + Direction: page.OrderDirectionDesc, + } + if orderBy != nil { + pageOrderBy = page.OrderBy[coredata.VendorOrderField]{ + Field: orderBy.Field, + Direction: orderBy.Direction, + } + } + + cursor := types.NewCursor(first, after, last, before, pageOrderBy) + + page, err := prb.Data.ListVendors(ctx, obj.ID, cursor) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot list data vendors", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return types.NewVendorConnection(page, r, obj.ID), nil +} + +// Organization is the resolver for the organization field. +func (r *datumResolver) Organization(ctx context.Context, obj *types.Datum) (*types.Organization, error) { + if err := r.authorize(ctx, obj.ID, probo.ActionOrganizationGet); err != nil { + return nil, err + } + + loaders := dataloader.FromContext(ctx) + + org, err := loaders.Organization.Load(ctx, obj.OrganizationID) + if err != nil { + if errors.Is(err, coredata.ErrResourceNotFound) || errors.Is(err, dataloadgen.ErrNotFound) { + return nil, gqlutils.NotFound(ctx, err) + } + + r.logger.ErrorCtx(ctx, "cannot get organization", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return types.NewOrganization(org), nil +} + +// Permission is the resolver for the permission field. +func (r *datumResolver) Permission(ctx context.Context, obj *types.Datum, action string) (bool, error) { + return r.Resolver.Permission(ctx, obj, action) +} + +// TotalCount is the resolver for the totalCount field. +func (r *datumConnectionResolver) TotalCount(ctx context.Context, obj *types.DatumConnection) (int, error) { + if err := r.authorize(ctx, obj.ParentID, probo.ActionDatumList); err != nil { + return 0, err + } + + prb := r.ProboService(ctx, obj.ParentID.TenantID()) + + switch obj.Resolver.(type) { + case *organizationResolver: + datumFilter := coredata.NewDatumFilter(nil) + if obj.Filter != nil { + datumFilter = coredata.NewDatumFilter(&obj.Filter.SnapshotID) + } + + count, err := prb.Data.CountForOrganizationID(ctx, obj.ParentID, datumFilter) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot count data", log.Error(err)) + return 0, gqlutils.Internal(ctx) + } + return count, nil + } + + r.logger.ErrorCtx(ctx, "unsupported resolver") + return 0, gqlutils.Internal(ctx) +} + +// CreateAsset is the resolver for the createAsset field. +func (r *mutationResolver) CreateAsset(ctx context.Context, input types.CreateAssetInput) (*types.CreateAssetPayload, error) { + if err := r.authorize(ctx, input.OrganizationID, probo.ActionAssetCreate); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, input.OrganizationID.TenantID()) + + asset, err := prb.Assets.Create( + ctx, + probo.CreateAssetRequest{ + OrganizationID: input.OrganizationID, + Name: input.Name, + Amount: input.Amount, + OwnerID: input.OwnerID, + AssetType: input.AssetType, + DataTypesStored: input.DataTypesStored, + VendorIDs: input.VendorIds, + }, + ) + + if err != nil { + if validationErrors, ok := errors.AsType[validator.ValidationErrors](err); ok { + return nil, gqlutils.InvalidValidationErrors(ctx, validationErrors) + } + r.logger.ErrorCtx(ctx, "cannot create asset", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.CreateAssetPayload{ + AssetEdge: types.NewAssetEdge(asset, coredata.AssetOrderFieldCreatedAt), + }, nil +} + +// UpdateAsset is the resolver for the updateAsset field. +func (r *mutationResolver) UpdateAsset(ctx context.Context, input types.UpdateAssetInput) (*types.UpdateAssetPayload, error) { + if err := r.authorize(ctx, input.ID, probo.ActionAssetUpdate); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, input.ID.TenantID()) + + asset, err := prb.Assets.Update( + ctx, + probo.UpdateAssetRequest{ + ID: input.ID, + Name: input.Name, + Amount: input.Amount, + OwnerID: input.OwnerID, + AssetType: input.AssetType, + DataTypesStored: input.DataTypesStored, + VendorIDs: input.VendorIds, + }, + ) + if err != nil { + if validationErrors, ok := errors.AsType[validator.ValidationErrors](err); ok { + return nil, gqlutils.InvalidValidationErrors(ctx, validationErrors) + } + r.logger.ErrorCtx(ctx, "cannot update asset", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.UpdateAssetPayload{ + Asset: types.NewAsset(asset), + }, nil +} + +// DeleteAsset is the resolver for the deleteAsset field. +func (r *mutationResolver) DeleteAsset(ctx context.Context, input types.DeleteAssetInput) (*types.DeleteAssetPayload, error) { + if err := r.authorize(ctx, input.AssetID, probo.ActionAssetDelete); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, input.AssetID.TenantID()) + + err := prb.Assets.Delete(ctx, input.AssetID) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot delete asset", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.DeleteAssetPayload{ + DeletedAssetID: input.AssetID, + }, nil +} + +// CreateDatum is the resolver for the createDatum field. +func (r *mutationResolver) CreateDatum(ctx context.Context, input types.CreateDatumInput) (*types.CreateDatumPayload, error) { + if err := r.authorize(ctx, input.OrganizationID, probo.ActionDatumCreate); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, input.OrganizationID.TenantID()) + + data, err := prb.Data.Create( + ctx, + probo.CreateDatumRequest{ + OrganizationID: input.OrganizationID, + Name: input.Name, + DataClassification: input.DataClassification, + OwnerID: input.OwnerID, + VendorIDs: input.VendorIds, + }, + ) + + if err != nil { + if validationErrors, ok := errors.AsType[validator.ValidationErrors](err); ok { + return nil, gqlutils.InvalidValidationErrors(ctx, validationErrors) + } + r.logger.ErrorCtx(ctx, "cannot create datum", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.CreateDatumPayload{ + DatumEdge: types.NewDatumEdge(data, coredata.DatumOrderFieldCreatedAt), + }, nil +} + +// UpdateDatum is the resolver for the updateDatum field. +func (r *mutationResolver) UpdateDatum(ctx context.Context, input types.UpdateDatumInput) (*types.UpdateDatumPayload, error) { + if err := r.authorize(ctx, input.ID, probo.ActionDatumUpdate); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, input.ID.TenantID()) + + datum, err := prb.Data.Update( + ctx, + probo.UpdateDatumRequest{ + ID: input.ID, + Name: input.Name, + DataClassification: input.DataClassification, + OwnerID: input.OwnerID, + VendorIDs: input.VendorIds, + }, + ) + + if err != nil { + if validationErrors, ok := errors.AsType[validator.ValidationErrors](err); ok { + return nil, gqlutils.InvalidValidationErrors(ctx, validationErrors) + } + r.logger.ErrorCtx(ctx, "cannot update datum", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.UpdateDatumPayload{ + Datum: types.NewDatum(datum), + }, nil +} + +// DeleteDatum is the resolver for the deleteDatum field. +func (r *mutationResolver) DeleteDatum(ctx context.Context, input types.DeleteDatumInput) (*types.DeleteDatumPayload, error) { + if err := r.authorize(ctx, input.DatumID, probo.ActionDatumDelete); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, input.DatumID.TenantID()) + + if err := prb.Data.Delete(ctx, input.DatumID); err != nil { + r.logger.ErrorCtx(ctx, "cannot delete datum", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.DeleteDatumPayload{ + DeletedDatumID: input.DatumID, + }, nil +} + +// Assets is the resolver for the assets field. +func (r *organizationResolver) Assets(ctx context.Context, obj *types.Organization, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.AssetOrderBy, filter *types.AssetFilter) (*types.AssetConnection, error) { + if err := r.authorize(ctx, obj.ID, probo.ActionAssetList); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, obj.ID.TenantID()) + + pageOrderBy := page.OrderBy[coredata.AssetOrderField]{ + Field: coredata.AssetOrderFieldCreatedAt, + Direction: page.OrderDirectionDesc, + } + if orderBy != nil { + pageOrderBy = page.OrderBy[coredata.AssetOrderField]{ + Field: orderBy.Field, + Direction: orderBy.Direction, + } + } + + cursor := types.NewCursor(first, after, last, before, pageOrderBy) + + assetFilter := coredata.NewAssetFilter(nil) + if filter != nil { + assetFilter = coredata.NewAssetFilter(&filter.SnapshotID) + } + + page, err := prb.Assets.ListForOrganizationID(ctx, obj.ID, cursor, assetFilter) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot list organization assets", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return types.NewAssetConnection(page, r, obj.ID, filter), nil +} + +// Assets is the resolver for the assets field. +func (r *organizationResolver) Data(ctx context.Context, obj *types.Organization, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.DatumOrderBy, filter *types.DatumFilter) (*types.DatumConnection, error) { + if err := r.authorize(ctx, obj.ID, probo.ActionDatumList); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, obj.ID.TenantID()) + + pageOrderBy := page.OrderBy[coredata.DatumOrderField]{ + Field: coredata.DatumOrderFieldCreatedAt, + Direction: page.OrderDirectionDesc, + } + if orderBy != nil { + pageOrderBy = page.OrderBy[coredata.DatumOrderField]{ + Field: orderBy.Field, + Direction: orderBy.Direction, + } + } + + cursor := types.NewCursor(first, after, last, before, pageOrderBy) + + datumFilter := coredata.NewDatumFilter(nil) + if filter != nil { + datumFilter = coredata.NewDatumFilter(&filter.SnapshotID) + } + + page, err := prb.Data.ListForOrganizationID(ctx, obj.ID, cursor, datumFilter) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot list organization data", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return types.NewDataConnection(page, r, obj.ID, filter), nil +} + +// Asset returns schema.AssetResolver implementation. +func (r *Resolver) Asset() schema.AssetResolver { return &assetResolver{r} } + +// AssetConnection returns schema.AssetConnectionResolver implementation. +func (r *Resolver) AssetConnection() schema.AssetConnectionResolver { + return &assetConnectionResolver{r} +} + +// Datum returns schema.DatumResolver implementation. +func (r *Resolver) Datum() schema.DatumResolver { return &datumResolver{r} } + +// DatumConnection returns schema.DatumConnectionResolver implementation. +func (r *Resolver) DatumConnection() schema.DatumConnectionResolver { + return &datumConnectionResolver{r} +} + +type assetResolver struct{ *Resolver } +type assetConnectionResolver struct{ *Resolver } +type datumResolver struct{ *Resolver } +type datumConnectionResolver struct{ *Resolver } diff --git a/pkg/server/api/console/v1/audit.resolvers.go b/pkg/server/api/console/v1/audit.resolvers.go new file mode 100644 index 000000000..1ecde0d02 --- /dev/null +++ b/pkg/server/api/console/v1/audit.resolvers.go @@ -0,0 +1,821 @@ +package console_v1 + +// This file will be automatically regenerated based on the schema, any resolver +// implementations +// will be copied through when generating and any unknown code will be moved to the end. +// Code generated by github.com/99designs/gqlgen version v0.17.87 + +import ( + "context" + "errors" + "time" + + "github.com/vikstrous/dataloadgen" + "go.gearno.de/kit/log" + "go.probo.inc/probo/pkg/coredata" + "go.probo.inc/probo/pkg/gid" + "go.probo.inc/probo/pkg/iam" + "go.probo.inc/probo/pkg/page" + "go.probo.inc/probo/pkg/probo" + "go.probo.inc/probo/pkg/server/api/console/v1/dataloader" + "go.probo.inc/probo/pkg/server/api/console/v1/schema" + "go.probo.inc/probo/pkg/server/api/console/v1/types" + "go.probo.inc/probo/pkg/server/gqlutils" + "go.probo.inc/probo/pkg/validator" +) + +// Organization is the resolver for the organization field. +func (r *auditResolver) Organization(ctx context.Context, obj *types.Audit) (*types.Organization, error) { + if err := r.authorize(ctx, obj.ID, probo.ActionOrganizationGet); err != nil { + return nil, err + } + + loaders := dataloader.FromContext(ctx) + + organization, err := loaders.Organization.Load(ctx, obj.Organization.ID) + if err != nil { + if errors.Is(err, coredata.ErrResourceNotFound) || errors.Is(err, dataloadgen.ErrNotFound) { + return nil, gqlutils.NotFound(ctx, err) + } + + r.logger.ErrorCtx(ctx, "cannot load organization", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return types.NewOrganization(organization), nil +} + +// Framework is the resolver for the framework field. +func (r *auditResolver) Framework(ctx context.Context, obj *types.Audit) (*types.Framework, error) { + if err := r.authorize(ctx, obj.ID, probo.ActionFrameworkGet); err != nil { + return nil, err + } + + loaders := dataloader.FromContext(ctx) + + framework, err := loaders.Framework.Load(ctx, obj.Framework.ID) + if err != nil { + if errors.Is(err, coredata.ErrResourceNotFound) || errors.Is(err, dataloadgen.ErrNotFound) { + return nil, gqlutils.NotFound(ctx, err) + } + + r.logger.ErrorCtx(ctx, "cannot load framework", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return types.NewFramework(framework), nil +} + +// Report is the resolver for the report field. +func (r *auditResolver) Report(ctx context.Context, obj *types.Audit) (*types.Report, error) { + if err := r.authorize(ctx, obj.ID, probo.ActionReportGet); err != nil { + return nil, err + } + + if obj.Report == nil { + return nil, nil + } + + loaders := dataloader.FromContext(ctx) + + report, err := loaders.Report.Load(ctx, obj.Report.ID) + if err != nil { + if errors.Is(err, dataloadgen.ErrNotFound) { + return nil, gqlutils.NotFound(ctx, err) + } + + r.logger.ErrorCtx(ctx, "cannot load report", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return types.NewReport(report), nil +} + +// ReportURL is the resolver for the reportUrl field. +func (r *auditResolver) ReportURL(ctx context.Context, obj *types.Audit) (*string, error) { + if err := r.authorize(ctx, obj.ID, probo.ActionReportGetReportUrl); err != nil { + return nil, err + } + + if obj.Report == nil { + return nil, nil + } + + prb := r.ProboService(ctx, obj.ID.TenantID()) + + url, err := prb.Audits.GenerateReportURL(ctx, obj.ID, 15*time.Minute) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot generate report URL", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return url, nil +} + +// Controls is the resolver for the controls field. +func (r *auditResolver) Controls(ctx context.Context, obj *types.Audit, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.ControlOrderBy, filter *types.ControlFilter) (*types.ControlConnection, error) { + if err := r.authorize(ctx, obj.ID, probo.ActionControlList); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, obj.ID.TenantID()) + + pageOrderBy := page.OrderBy[coredata.ControlOrderField]{ + Field: coredata.ControlOrderFieldCreatedAt, + Direction: page.OrderDirectionDesc, + } + if orderBy != nil { + pageOrderBy = page.OrderBy[coredata.ControlOrderField]{ + Field: orderBy.Field, + Direction: orderBy.Direction, + } + } + + cursor := types.NewCursor(first, after, last, before, pageOrderBy) + + var controlFilter = coredata.NewControlFilter(nil) + if filter != nil { + controlFilter = coredata.NewControlFilter(filter.Query) + } + + page, err := prb.Controls.ListForAuditID(ctx, obj.ID, cursor, controlFilter) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot list audit controls", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return types.NewControlConnection(page, r, obj.ID, controlFilter), nil +} + +// Findings is the resolver for the findings field. +func (r *auditResolver) Findings(ctx context.Context, obj *types.Audit, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.FindingOrder, filter *types.FindingFilter) (*types.FindingConnection, error) { + if err := r.authorize(ctx, obj.ID, probo.ActionFindingList); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, obj.ID.TenantID()) + + pageOrderBy := page.OrderBy[coredata.FindingOrderField]{ + Field: coredata.FindingOrderFieldCreatedAt, + Direction: page.OrderDirectionDesc, + } + if orderBy != nil { + pageOrderBy = page.OrderBy[coredata.FindingOrderField]{ + Field: orderBy.Field, + Direction: orderBy.Direction, + } + } + + cursor := types.NewCursor(first, after, last, before, pageOrderBy) + + var ( + kind *coredata.FindingKind + status *coredata.FindingStatus + priority *coredata.FindingPriority + ownerID *gid.GID + ) + if filter != nil { + kind = filter.Kind + status = filter.Status + priority = filter.Priority + ownerID = filter.OwnerID + } + + findingFilter := coredata.NewFindingFilter(nil, kind, status, priority, ownerID) + if filter != nil { + findingFilter = coredata.NewFindingFilter(&filter.SnapshotID, kind, status, priority, ownerID) + } + + p, err := prb.Findings.ListForAuditID(ctx, obj.ID, cursor, findingFilter) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot list audit findings", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return types.NewFindingConnection(p, r, obj.ID, filter), nil +} + +// Permission is the resolver for the permission field. +func (r *auditResolver) Permission(ctx context.Context, obj *types.Audit, action string) (bool, error) { + return r.Resolver.Permission(ctx, obj, action) +} + +// TotalCount is the resolver for the totalCount field. +func (r *auditConnectionResolver) TotalCount(ctx context.Context, obj *types.AuditConnection) (int, error) { + if err := r.authorize(ctx, obj.ParentID, probo.ActionAuditList); err != nil { + return 0, err + } + + prb := r.ProboService(ctx, obj.ParentID.TenantID()) + + switch obj.Resolver.(type) { + case *organizationResolver: + count, err := prb.Audits.CountForOrganizationID(ctx, obj.ParentID) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot count audits", log.Error(err)) + return 0, gqlutils.Internal(ctx) + } + return count, nil + case *findingResolver: + count, err := prb.Audits.CountForFindingID(ctx, obj.ParentID) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot count audits", log.Error(err)) + return 0, gqlutils.Internal(ctx) + } + return count, nil + case *controlResolver: + count, err := prb.Audits.CountForControlID(ctx, obj.ParentID) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot count audits", log.Error(err)) + return 0, gqlutils.Internal(ctx) + } + return count, nil + default: + r.logger.ErrorCtx(ctx, "unsupported resolver", log.Any("resolver", obj.Resolver)) + return 0, gqlutils.Internal(ctx) + } +} + +// Organization is the resolver for the organization field. +func (r *findingResolver) Organization(ctx context.Context, obj *types.Finding) (*types.Organization, error) { + if err := r.authorize(ctx, obj.ID, probo.ActionOrganizationGet); err != nil { + return nil, err + } + + loaders := dataloader.FromContext(ctx) + + organization, err := loaders.Organization.Load(ctx, obj.Organization.ID) + if err != nil { + if errors.Is(err, coredata.ErrResourceNotFound) || errors.Is(err, dataloadgen.ErrNotFound) { + return nil, gqlutils.NotFound(ctx, err) + } + + r.logger.ErrorCtx(ctx, "cannot get finding organization", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return types.NewOrganization(organization), nil +} + +// Audits is the resolver for the audits field. +func (r *findingResolver) Audits(ctx context.Context, obj *types.Finding, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.AuditOrderBy) (*types.AuditConnection, error) { + if err := r.authorize(ctx, obj.ID, probo.ActionAuditList); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, obj.ID.TenantID()) + + pageOrderBy := page.OrderBy[coredata.AuditOrderField]{ + Field: coredata.AuditOrderFieldCreatedAt, + Direction: page.OrderDirectionDesc, + } + if orderBy != nil { + pageOrderBy = page.OrderBy[coredata.AuditOrderField]{ + Field: orderBy.Field, + Direction: orderBy.Direction, + } + } + + cursor := types.NewCursor(first, after, last, before, pageOrderBy) + + p, err := prb.Audits.ListForFindingID(ctx, obj.ID, cursor) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot list finding audits", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return types.NewAuditConnection(p, r, obj.ID), nil +} + +// Owner is the resolver for the owner field. +func (r *findingResolver) Owner(ctx context.Context, obj *types.Finding) (*types.Profile, error) { + if obj.Owner == nil { + return nil, nil + } + + if err := r.authorize(ctx, obj.ID, iam.ActionMembershipProfileGet); err != nil { + return nil, err + } + + loaders := dataloader.FromContext(ctx) + + owner, err := loaders.Profile.Load(ctx, obj.Owner.ID) + if err != nil { + if errors.Is(err, coredata.ErrResourceNotFound) || errors.Is(err, dataloadgen.ErrNotFound) { + return nil, gqlutils.NotFound(ctx, err) + } + + r.logger.ErrorCtx(ctx, "cannot get finding owner", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return types.NewProfile(owner), nil +} + +// Risk is the resolver for the risk field. +func (r *findingResolver) Risk(ctx context.Context, obj *types.Finding) (*types.Risk, error) { + if obj.Risk == nil { + return nil, nil + } + + if err := r.authorize(ctx, obj.ID, probo.ActionRiskGet); err != nil { + return nil, err + } + + loaders := dataloader.FromContext(ctx) + + risk, err := loaders.Risk.Load(ctx, obj.Risk.ID) + if err != nil { + if errors.Is(err, coredata.ErrResourceNotFound) || errors.Is(err, dataloadgen.ErrNotFound) { + return nil, gqlutils.NotFound(ctx, err) + } + + r.logger.ErrorCtx(ctx, "cannot get finding risk", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return types.NewRisk(risk), nil +} + +// Permission is the resolver for the permission field. +func (r *findingResolver) Permission(ctx context.Context, obj *types.Finding, action string) (bool, error) { + return r.Resolver.Permission(ctx, obj, action) +} + +// TotalCount is the resolver for the totalCount field. +func (r *findingConnectionResolver) TotalCount(ctx context.Context, obj *types.FindingConnection) (int, error) { + if err := r.authorize(ctx, obj.ParentID, probo.ActionFindingList); err != nil { + return 0, err + } + + prb := r.ProboService(ctx, obj.ParentID.TenantID()) + + var ( + kind *coredata.FindingKind + status *coredata.FindingStatus + priority *coredata.FindingPriority + ownerID *gid.GID + ) + if obj.Filter != nil { + kind = obj.Filter.Kind + status = obj.Filter.Status + priority = obj.Filter.Priority + ownerID = obj.Filter.OwnerID + } + + findingFilter := coredata.NewFindingFilter(nil, kind, status, priority, ownerID) + if obj.Filter != nil { + findingFilter = coredata.NewFindingFilter(&obj.Filter.SnapshotID, kind, status, priority, ownerID) + } + + switch obj.Resolver.(type) { + case *organizationResolver: + count, err := prb.Findings.CountForOrganizationID(ctx, obj.ParentID, findingFilter) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot count findings", log.Error(err)) + return 0, gqlutils.Internal(ctx) + } + return count, nil + case *auditResolver: + count, err := prb.Findings.CountForAuditID(ctx, obj.ParentID, findingFilter) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot count findings", log.Error(err)) + return 0, gqlutils.Internal(ctx) + } + return count, nil + } + + r.logger.ErrorCtx(ctx, "unsupported resolver", log.Any("resolver", obj.Resolver)) + return 0, gqlutils.Internal(ctx) +} + +// CreateAudit is the resolver for the createAudit field. +func (r *mutationResolver) CreateAudit(ctx context.Context, input types.CreateAuditInput) (*types.CreateAuditPayload, error) { + if err := r.authorize(ctx, input.OrganizationID, probo.ActionAuditCreate); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, input.OrganizationID.TenantID()) + + req := probo.CreateAuditRequest{ + OrganizationID: input.OrganizationID, + FrameworkID: input.FrameworkID, + Name: input.Name, + ValidFrom: input.ValidFrom, + ValidUntil: input.ValidUntil, + State: input.State, + TrustCenterVisibility: input.TrustCenterVisibility, + } + + audit, err := prb.Audits.Create(ctx, &req) + if err != nil { + if validationErrors, ok := errors.AsType[validator.ValidationErrors](err); ok { + return nil, gqlutils.InvalidValidationErrors(ctx, validationErrors) + } + r.logger.ErrorCtx(ctx, "cannot create audit", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + if input.File != nil { + uploadReq := probo.UploadAuditReportRequest{ + AuditID: audit.ID, + File: probo.File{ + Content: input.File.File, + Filename: input.File.Filename, + Size: input.File.Size, + ContentType: input.File.ContentType, + }, + } + + audit, err = prb.Audits.UploadReport(ctx, uploadReq) + if err != nil { + if validationErrors, ok := errors.AsType[validator.ValidationErrors](err); ok { + return nil, gqlutils.InvalidValidationErrors(ctx, validationErrors) + } + r.logger.ErrorCtx(ctx, "cannot upload audit report", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + } + + return &types.CreateAuditPayload{ + AuditEdge: types.NewAuditEdge(audit, coredata.AuditOrderFieldCreatedAt), + }, nil +} + +// UpdateAudit is the resolver for the updateAudit field. +func (r *mutationResolver) UpdateAudit(ctx context.Context, input types.UpdateAuditInput) (*types.UpdateAuditPayload, error) { + if err := r.authorize(ctx, input.ID, probo.ActionAuditUpdate); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, input.ID.TenantID()) + + req := probo.UpdateAuditRequest{ + ID: input.ID, + Name: gqlutils.UnwrapOmittable(input.Name), + ValidFrom: input.ValidFrom, + ValidUntil: input.ValidUntil, + State: input.State, + TrustCenterVisibility: input.TrustCenterVisibility, + } + + audit, err := prb.Audits.Update(ctx, &req) + if err != nil { + if validationErrors, ok := errors.AsType[validator.ValidationErrors](err); ok { + return nil, gqlutils.InvalidValidationErrors(ctx, validationErrors) + } + r.logger.ErrorCtx(ctx, "cannot update audit", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.UpdateAuditPayload{ + Audit: types.NewAudit(audit), + }, nil +} + +// DeleteAudit is the resolver for the deleteAudit field. +func (r *mutationResolver) DeleteAudit(ctx context.Context, input types.DeleteAuditInput) (*types.DeleteAuditPayload, error) { + if err := r.authorize(ctx, input.AuditID, probo.ActionAuditDelete); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, input.AuditID.TenantID()) + + err := prb.Audits.Delete(ctx, input.AuditID) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot delete audit", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.DeleteAuditPayload{ + DeletedAuditID: &input.AuditID, + }, nil +} + +// UploadAuditReport is the resolver for the uploadAuditReport field. +func (r *mutationResolver) UploadAuditReport(ctx context.Context, input types.UploadAuditReportInput) (*types.UploadAuditReportPayload, error) { + if err := r.authorize(ctx, input.AuditID, probo.ActionAuditReportUpload); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, input.AuditID.TenantID()) + + req := probo.UploadAuditReportRequest{ + AuditID: input.AuditID, + File: probo.File{ + Content: input.File.File, + Filename: input.File.Filename, + Size: input.File.Size, + ContentType: input.File.ContentType, + }, + } + + audit, err := prb.Audits.UploadReport(ctx, req) + if err != nil { + if validationErrors, ok := errors.AsType[validator.ValidationErrors](err); ok { + return nil, gqlutils.InvalidValidationErrors(ctx, validationErrors) + } + r.logger.ErrorCtx(ctx, "cannot upload audit report", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.UploadAuditReportPayload{ + Audit: types.NewAudit(audit), + }, nil +} + +// DeleteAuditReport is the resolver for the deleteAuditReport field. +func (r *mutationResolver) DeleteAuditReport(ctx context.Context, input types.DeleteAuditReportInput) (*types.DeleteAuditReportPayload, error) { + if err := r.authorize(ctx, input.AuditID, probo.ActionAuditReportDelete); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, input.AuditID.TenantID()) + + audit, err := prb.Audits.DeleteReport(ctx, input.AuditID) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot delete audit report", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.DeleteAuditReportPayload{ + Audit: types.NewAudit(audit), + }, nil +} + +// CreateFinding is the resolver for the createFinding field. +func (r *mutationResolver) CreateFinding(ctx context.Context, input types.CreateFindingInput) (*types.CreateFindingPayload, error) { + if err := r.authorize(ctx, input.OrganizationID, probo.ActionFindingCreate); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, input.OrganizationID.TenantID()) + + req := probo.CreateFindingRequest{ + OrganizationID: input.OrganizationID, + Kind: input.Kind, + Description: input.Description, + Source: input.Source, + IdentifiedOn: input.IdentifiedOn, + RootCause: input.RootCause, + CorrectiveAction: input.CorrectiveAction, + OwnerID: input.OwnerID, + DueDate: input.DueDate, + Status: &input.Status, + Priority: &input.Priority, + RiskID: input.RiskID, + EffectivenessCheck: input.EffectivenessCheck, + } + + finding, err := prb.Findings.Create(ctx, &req) + if err != nil { + if validationErrors, ok := errors.AsType[validator.ValidationErrors](err); ok { + return nil, gqlutils.InvalidValidationErrors(ctx, validationErrors) + } + r.logger.ErrorCtx(ctx, "cannot create finding", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.CreateFindingPayload{ + FindingEdge: types.NewFindingEdge(finding, coredata.FindingOrderFieldCreatedAt), + }, nil +} + +// UpdateFinding is the resolver for the updateFinding field. +func (r *mutationResolver) UpdateFinding(ctx context.Context, input types.UpdateFindingInput) (*types.UpdateFindingPayload, error) { + if err := r.authorize(ctx, input.ID, probo.ActionFindingUpdate); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, input.ID.TenantID()) + + req := probo.UpdateFindingRequest{ + ID: input.ID, + Description: gqlutils.UnwrapOmittable(input.Description), + Source: gqlutils.UnwrapOmittable(input.Source), + IdentifiedOn: gqlutils.UnwrapOmittable(input.IdentifiedOn), + RootCause: gqlutils.UnwrapOmittable(input.RootCause), + CorrectiveAction: gqlutils.UnwrapOmittable(input.CorrectiveAction), + OwnerID: input.OwnerID, + DueDate: gqlutils.UnwrapOmittable(input.DueDate), + Status: input.Status, + Priority: input.Priority, + RiskID: gqlutils.UnwrapOmittable(input.RiskID), + EffectivenessCheck: gqlutils.UnwrapOmittable(input.EffectivenessCheck), + } + + finding, err := prb.Findings.Update(ctx, &req) + if err != nil { + if validationErrors, ok := errors.AsType[validator.ValidationErrors](err); ok { + return nil, gqlutils.InvalidValidationErrors(ctx, validationErrors) + } + r.logger.ErrorCtx(ctx, "cannot update finding", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.UpdateFindingPayload{ + Finding: types.NewFinding(finding), + }, nil +} + +// DeleteFinding is the resolver for the deleteFinding field. +func (r *mutationResolver) DeleteFinding(ctx context.Context, input types.DeleteFindingInput) (*types.DeleteFindingPayload, error) { + if err := r.authorize(ctx, input.FindingID, probo.ActionFindingDelete); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, input.FindingID.TenantID()) + + err := prb.Findings.Delete(ctx, input.FindingID) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot delete finding", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.DeleteFindingPayload{ + DeletedFindingID: &input.FindingID, + }, nil +} + +// CreateFindingAuditMapping is the resolver for the createFindingAuditMapping field. +func (r *mutationResolver) CreateFindingAuditMapping(ctx context.Context, input types.CreateFindingAuditMappingInput) (*types.CreateFindingAuditMappingPayload, error) { + if err := r.authorize(ctx, input.FindingID, probo.ActionFindingAuditMappingCreate); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, input.FindingID.TenantID()) + + finding, audit, err := prb.Findings.CreateAuditMapping(ctx, input.FindingID, input.AuditID, input.ReferenceID) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot create finding audit mapping", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.CreateFindingAuditMappingPayload{ + FindingEdge: types.NewFindingEdge(finding, coredata.FindingOrderFieldCreatedAt), + AuditEdge: types.NewAuditEdge(audit, coredata.AuditOrderFieldCreatedAt), + }, nil +} + +// DeleteFindingAuditMapping is the resolver for the deleteFindingAuditMapping field. +func (r *mutationResolver) DeleteFindingAuditMapping(ctx context.Context, input types.DeleteFindingAuditMappingInput) (*types.DeleteFindingAuditMappingPayload, error) { + if err := r.authorize(ctx, input.FindingID, probo.ActionFindingAuditMappingDelete); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, input.FindingID.TenantID()) + + finding, audit, err := prb.Findings.DeleteAuditMapping(ctx, input.FindingID, input.AuditID) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot delete finding audit mapping", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.DeleteFindingAuditMappingPayload{ + DeletedFindingID: &finding.ID, + DeletedAuditID: &audit.ID, + }, nil +} + +// Audits is the resolver for the audits field. +func (r *organizationResolver) Audits(ctx context.Context, obj *types.Organization, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.AuditOrderBy) (*types.AuditConnection, error) { + if err := r.authorize(ctx, obj.ID, probo.ActionAuditList); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, obj.ID.TenantID()) + + pageOrderBy := page.OrderBy[coredata.AuditOrderField]{ + Field: coredata.AuditOrderFieldCreatedAt, + Direction: page.OrderDirectionDesc, + } + if orderBy != nil { + pageOrderBy = page.OrderBy[coredata.AuditOrderField]{ + Field: orderBy.Field, + Direction: orderBy.Direction, + } + } + + cursor := types.NewCursor(first, after, last, before, pageOrderBy) + + page, err := prb.Audits.ListForOrganizationID(ctx, obj.ID, cursor) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot list organization audits", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return types.NewAuditConnection(page, r, obj.ID), nil +} + +// Findings is the resolver for the findings field. +func (r *organizationResolver) Findings(ctx context.Context, obj *types.Organization, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.FindingOrder, filter *types.FindingFilter) (*types.FindingConnection, error) { + if err := r.authorize(ctx, obj.ID, probo.ActionFindingList); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, obj.ID.TenantID()) + + pageOrderBy := page.OrderBy[coredata.FindingOrderField]{ + Field: coredata.FindingOrderFieldCreatedAt, + Direction: page.OrderDirectionDesc, + } + + if orderBy != nil { + pageOrderBy = page.OrderBy[coredata.FindingOrderField]{ + Field: orderBy.Field, + Direction: orderBy.Direction, + } + } + + cursor := types.NewCursor(first, after, last, before, pageOrderBy) + + var ( + kind *coredata.FindingKind + status *coredata.FindingStatus + priority *coredata.FindingPriority + ownerID *gid.GID + ) + if filter != nil { + kind = filter.Kind + status = filter.Status + priority = filter.Priority + ownerID = filter.OwnerID + } + + findingFilter := coredata.NewFindingFilter(nil, kind, status, priority, ownerID) + if filter != nil { + findingFilter = coredata.NewFindingFilter(&filter.SnapshotID, kind, status, priority, ownerID) + } + + page, err := prb.Findings.ListForOrganizationID(ctx, obj.ID, cursor, findingFilter) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot list organization findings", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return types.NewFindingConnection(page, r, obj.ID, filter), nil +} + +// DownloadURL is the resolver for the downloadUrl field. +func (r *reportResolver) DownloadURL(ctx context.Context, obj *types.Report) (*string, error) { + if err := r.authorize(ctx, obj.ID, probo.ActionReportDownloadUrlGet); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, obj.ID.TenantID()) + + url, err := prb.Reports.GenerateDownloadURL(ctx, obj.ID, 15*time.Minute) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot generate download URL", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return url, nil +} + +// Audit is the resolver for the audit field. +func (r *reportResolver) Audit(ctx context.Context, obj *types.Report) (*types.Audit, error) { + if err := r.authorize(ctx, obj.ID, probo.ActionAuditGet); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, obj.ID.TenantID()) + + audit, err := prb.Audits.GetByReportID(ctx, obj.ID) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot load audit for report", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return types.NewAudit(audit), nil +} + +// Permission is the resolver for the permission field. +func (r *reportResolver) Permission(ctx context.Context, obj *types.Report, action string) (bool, error) { + return r.Resolver.Permission(ctx, obj, action) +} + +// Audit returns schema.AuditResolver implementation. +func (r *Resolver) Audit() schema.AuditResolver { return &auditResolver{r} } + +// AuditConnection returns schema.AuditConnectionResolver implementation. +func (r *Resolver) AuditConnection() schema.AuditConnectionResolver { + return &auditConnectionResolver{r} +} + +// Finding returns schema.FindingResolver implementation. +func (r *Resolver) Finding() schema.FindingResolver { return &findingResolver{r} } + +// FindingConnection returns schema.FindingConnectionResolver implementation. +func (r *Resolver) FindingConnection() schema.FindingConnectionResolver { + return &findingConnectionResolver{r} +} + +// Report returns schema.ReportResolver implementation. +func (r *Resolver) Report() schema.ReportResolver { return &reportResolver{r} } + +type auditResolver struct{ *Resolver } +type auditConnectionResolver struct{ *Resolver } +type findingResolver struct{ *Resolver } +type findingConnectionResolver struct{ *Resolver } +type reportResolver struct{ *Resolver } diff --git a/pkg/server/api/console/v1/audit_log.resolvers.go b/pkg/server/api/console/v1/audit_log.resolvers.go new file mode 100644 index 000000000..0e9fa4e1e --- /dev/null +++ b/pkg/server/api/console/v1/audit_log.resolvers.go @@ -0,0 +1,99 @@ +package console_v1 + +// This file will be automatically regenerated based on the schema, any resolver +// implementations +// will be copied through when generating and any unknown code will be moved to the end. +// Code generated by github.com/99designs/gqlgen version v0.17.87 + +import ( + "context" + + "go.gearno.de/kit/log" + "go.probo.inc/probo/pkg/coredata" + "go.probo.inc/probo/pkg/iam" + "go.probo.inc/probo/pkg/page" + "go.probo.inc/probo/pkg/server/api/console/v1/schema" + "go.probo.inc/probo/pkg/server/api/console/v1/types" + "go.probo.inc/probo/pkg/server/gqlutils" +) + +// Organization is the resolver for the organization field. +func (r *auditLogEntryResolver) Organization(ctx context.Context, obj *types.AuditLogEntry) (*types.Organization, error) { + return obj.Organization, nil +} + +// Permission is the resolver for the permission field. +func (r *auditLogEntryResolver) Permission(ctx context.Context, obj *types.AuditLogEntry, action string) (bool, error) { + return r.Resolver.Permission(ctx, obj, action) +} + +// TotalCount is the resolver for the totalCount field. +func (r *auditLogEntryConnectionResolver) TotalCount(ctx context.Context, obj *types.AuditLogEntryConnection) (int, error) { + filter := coredata.NewAuditLogEntryFilter() + if obj.Filter != nil { + filter = obj.Filter + } + + count, err := r.iam.OrganizationService.CountAuditLogEntries(ctx, obj.ParentID, filter) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot count audit log entries", log.Error(err)) + return 0, gqlutils.Internal(ctx) + } + + return count, nil +} + +// AuditLogEntries is the resolver for the auditLogEntries field. +func (r *organizationResolver) AuditLogEntries(ctx context.Context, obj *types.Organization, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.AuditLogEntryOrderBy, filter *types.AuditLogEntryFilter) (*types.AuditLogEntryConnection, error) { + if err := r.authorize(ctx, obj.ID, iam.ActionAuditLogEntryList); err != nil { + return nil, err + } + + pageOrderBy := page.OrderBy[coredata.AuditLogEntryOrderField]{ + Field: coredata.AuditLogEntryOrderFieldCreatedAt, + Direction: page.OrderDirectionDesc, + } + if orderBy != nil { + pageOrderBy = page.OrderBy[coredata.AuditLogEntryOrderField]{ + Field: orderBy.Field, + Direction: orderBy.Direction, + } + } + + cursor := types.NewCursor(first, after, last, before, pageOrderBy) + + coredataFilter := coredata.NewAuditLogEntryFilter() + if filter != nil { + if filter.Action != nil { + coredataFilter.WithAction(*filter.Action) + } + if filter.ActorID != nil { + coredataFilter.WithActorID(*filter.ActorID) + } + if filter.ResourceType != nil { + coredataFilter.WithResourceType(*filter.ResourceType) + } + if filter.ResourceID != nil { + coredataFilter.WithResourceID(*filter.ResourceID) + } + } + + p, err := r.iam.OrganizationService.ListAuditLogEntries(ctx, obj.ID, cursor, coredataFilter) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot list audit log entries", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return types.NewAuditLogEntryConnection(p, r, obj.ID, coredataFilter), nil +} + +// AuditLogEntry returns schema.AuditLogEntryResolver implementation. +func (r *Resolver) AuditLogEntry() schema.AuditLogEntryResolver { return &auditLogEntryResolver{r} } + +// AuditLogEntryConnection returns schema.AuditLogEntryConnectionResolver implementation. +func (r *Resolver) AuditLogEntryConnection() schema.AuditLogEntryConnectionResolver { + return &auditLogEntryConnectionResolver{r} +} + +type auditLogEntryResolver struct{ *Resolver } +type auditLogEntryConnectionResolver struct{ *Resolver } diff --git a/pkg/server/api/console/v1/base.resolvers.go b/pkg/server/api/console/v1/base.resolvers.go new file mode 100644 index 000000000..fdb906e3a --- /dev/null +++ b/pkg/server/api/console/v1/base.resolvers.go @@ -0,0 +1,565 @@ +package console_v1 + +// This file will be automatically regenerated based on the schema, any resolver +// implementations +// will be copied through when generating and any unknown code will be moved to the end. +// Code generated by github.com/99designs/gqlgen version v0.17.87 + +import ( + "context" + "errors" + "fmt" + "time" + + "go.gearno.de/kit/log" + "go.probo.inc/probo/pkg/coredata" + "go.probo.inc/probo/pkg/gid" + "go.probo.inc/probo/pkg/iam" + "go.probo.inc/probo/pkg/page" + "go.probo.inc/probo/pkg/probo" + "go.probo.inc/probo/pkg/server/api/authn" + "go.probo.inc/probo/pkg/server/api/console/v1/schema" + "go.probo.inc/probo/pkg/server/api/console/v1/types" + "go.probo.inc/probo/pkg/server/gqlutils" + "go.probo.inc/probo/pkg/server/gqlutils/types/cursor" + "go.probo.inc/probo/pkg/validator" +) + +// DownloadURL is the resolver for the downloadUrl field. +func (r *fileResolver) DownloadURL(ctx context.Context, obj *types.File) (string, error) { + if err := r.authorize(ctx, obj.ID, probo.ActionFileDownloadUrl); err != nil { + return "", err + } + + prb := r.ProboService(ctx, obj.ID.TenantID()) + + downloadUrl, err := prb.Files.GenerateFileTempURL(ctx, obj.ID, 60*time.Second) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot generate download URL", log.Error(err)) + return "", gqlutils.Internal(ctx) + } + + return downloadUrl, nil +} + +// UpdateOrganizationContext is the resolver for the updateOrganizationContext field. +func (r *mutationResolver) UpdateOrganizationContext(ctx context.Context, input types.UpdateOrganizationContextInput) (*types.UpdateOrganizationContextPayload, error) { + if err := r.authorize(ctx, input.OrganizationID, probo.ActionOrganizationContextUpdate); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, input.OrganizationID.TenantID()) + + req := probo.UpdateOrganizationContextRequest{ + OrganizationID: input.OrganizationID, + Product: gqlutils.UnwrapOmittable(input.Product), + Architecture: gqlutils.UnwrapOmittable(input.Architecture), + Team: gqlutils.UnwrapOmittable(input.Team), + Processes: gqlutils.UnwrapOmittable(input.Processes), + Customers: gqlutils.UnwrapOmittable(input.Customers), + } + + organizationContext, err := prb.Organizations.UpdateContext(ctx, req) + if err != nil { + if validationErrors, ok := errors.AsType[validator.ValidationErrors](err); ok { + return nil, gqlutils.InvalidValidationErrors(ctx, validationErrors) + } + r.logger.ErrorCtx(ctx, "cannot update organization context", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.UpdateOrganizationContextPayload{ + Context: types.NewOrganizationContext(organizationContext), + }, nil +} + +// LogoURL is the resolver for the logoUrl field. +func (r *organizationResolver) LogoURL(ctx context.Context, obj *types.Organization) (*string, error) { + if err := r.authorize(ctx, obj.ID, probo.ActionOrganizationGetLogoUrl); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, obj.ID.TenantID()) + + logoURL, err := prb.Organizations.GenerateLogoURL(ctx, obj.ID, 1*time.Hour) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot generate logo url", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return logoURL, nil +} + +// HorizontalLogoURL is the resolver for the horizontalLogoUrl field. +func (r *organizationResolver) HorizontalLogoURL(ctx context.Context, obj *types.Organization) (*string, error) { + if err := r.authorize(ctx, obj.ID, probo.ActionOrganizationGetHorizontalLogoUrl); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, obj.ID.TenantID()) + + horizontalLogoURL, err := prb.Organizations.GenerateHorizontalLogoURL(ctx, obj.ID, 1*time.Hour) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot generate horizontal logo url", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return horizontalLogoURL, nil +} + +// Context is the resolver for the context field. +func (r *organizationResolver) Context(ctx context.Context, obj *types.Organization) (*types.OrganizationContext, error) { + if err := r.authorize(ctx, obj.ID, probo.ActionOrganizationContextGet); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, obj.ID.TenantID()) + + orgContext, err := prb.Organizations.GetContext(ctx, obj.ID) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot load organization context", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return types.NewOrganizationContext(orgContext), nil +} + +// Profiles is the resolver for the profiles field. +func (r *organizationResolver) Profiles(ctx context.Context, obj *types.Organization, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.ProfileOrderBy, filter *types.ProfileFilter) (*types.ProfileConnection, error) { + if err := r.authorize(ctx, obj.ID, iam.ActionMembershipProfileList); err != nil { + return nil, err + } + + if gqlutils.OnlyTotalCountSelected(ctx) { + return &types.ProfileConnection{ + Resolver: r, + ParentID: obj.ID, + }, nil + } + + filters := coredata.NewMembershipProfileFilter(nil).WithMembership() + if filter != nil { + filters = coredata.NewMembershipProfileFilter(filter.ExcludeContractEnded).WithMembership() + } + + pageOrderBy := page.OrderBy[coredata.MembershipProfileOrderField]{ + Field: coredata.MembershipProfileOrderFieldCreatedAt, + Direction: page.OrderDirectionDesc, + } + if orderBy != nil { + pageOrderBy.Field = coredata.MembershipProfileOrderField(orderBy.Field) + pageOrderBy.Direction = page.OrderDirection(orderBy.Direction) + } + + cursor := cursor.NewCursor(first, after, last, before, pageOrderBy) + + page, err := r.iam.OrganizationService.ListProfiles(ctx, obj.ID, cursor, filters) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot list profiles", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return types.NewProfileConnection(page, r, obj.ID, filters), nil +} + +// MeasureCategories is the resolver for the measureCategories field. +func (r *organizationResolver) MeasureCategories(ctx context.Context, obj *types.Organization) ([]string, error) { + if err := r.authorize(ctx, obj.ID, probo.ActionMeasureList); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, obj.ID.TenantID()) + + categories, err := prb.Measures.ListDistinctCategoriesForOrganizationID(ctx, obj.ID) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot list measure categories", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return categories, nil +} + +// Permission is the resolver for the permission field. +func (r *organizationResolver) Permission(ctx context.Context, obj *types.Organization, action string) (bool, error) { + return r.Resolver.Permission(ctx, obj, action) +} + +// Node is the resolver for the node field. +func (r *queryResolver) Node(ctx context.Context, id gid.GID) (types.Node, error) { + var ( + loadNode func(ctx context.Context, id gid.GID) (types.Node, error) + action string + prb = r.ProboService(ctx, id.TenantID()) + ) + + switch id.EntityType() { + case coredata.OrganizationEntityType: + action = iam.ActionOrganizationGet + loadNode = func(ctx context.Context, id gid.GID) (types.Node, error) { + organization, err := prb.Organizations.Get(ctx, id) + if err != nil { + return nil, err + } + return types.NewOrganization(organization), nil + } + case coredata.VendorEntityType: + action = probo.ActionVendorGet + loadNode = func(ctx context.Context, id gid.GID) (types.Node, error) { + vendor, err := prb.Vendors.Get(ctx, id) + if err != nil { + return nil, err + } + return types.NewVendor(vendor), nil + } + case coredata.FrameworkEntityType: + action = probo.ActionFrameworkGet + loadNode = func(ctx context.Context, id gid.GID) (types.Node, error) { + framework, err := prb.Frameworks.Get(ctx, id) + if err != nil { + return nil, err + } + return types.NewFramework(framework), nil + } + case coredata.MeasureEntityType: + action = probo.ActionMeasureGet + loadNode = func(ctx context.Context, id gid.GID) (types.Node, error) { + measure, err := prb.Measures.Get(ctx, id) + if err != nil { + return nil, err + } + return types.NewMeasure(measure), nil + } + case coredata.TaskEntityType: + action = probo.ActionTaskGet + loadNode = func(ctx context.Context, id gid.GID) (types.Node, error) { + task, err := prb.Tasks.Get(ctx, id) + if err != nil { + return nil, err + } + return types.NewTask(task), nil + } + case coredata.EvidenceEntityType: + action = probo.ActionEvidenceList + loadNode = func(ctx context.Context, id gid.GID) (types.Node, error) { + evidence, err := prb.Evidences.Get(ctx, id) + if err != nil { + return nil, err + } + return types.NewEvidence(evidence), nil + } + case coredata.DocumentEntityType: + action = probo.ActionDocumentGet + loadNode = func(ctx context.Context, id gid.GID) (types.Node, error) { + document, err := prb.Documents.Get(ctx, id) + if err != nil { + return nil, err + } + return types.NewDocument(document), nil + } + case coredata.ControlEntityType: + action = probo.ActionControlList + loadNode = func(ctx context.Context, id gid.GID) (types.Node, error) { + control, err := prb.Controls.Get(ctx, id) + if err != nil { + return nil, err + } + return types.NewControl(control), nil + } + case coredata.RiskEntityType: + action = probo.ActionRiskGet + loadNode = func(ctx context.Context, id gid.GID) (types.Node, error) { + risk, err := prb.Risks.Get(ctx, id) + if err != nil { + return nil, err + } + return types.NewRisk(risk), nil + } + case coredata.VendorComplianceReportEntityType: + action = probo.ActionVendorComplianceReportGet + loadNode = func(ctx context.Context, id gid.GID) (types.Node, error) { + vendorComplianceReport, err := prb.VendorComplianceReports.Get(ctx, id) + if err != nil { + return nil, err + } + return types.NewVendorComplianceReport(vendorComplianceReport), nil + } + case coredata.VendorContactEntityType: + action = probo.ActionVendorContactGet + loadNode = func(ctx context.Context, id gid.GID) (types.Node, error) { + vendorContact, err := prb.VendorContacts.Get(ctx, id) + if err != nil { + return nil, err + } + return types.NewVendorContact(vendorContact), nil + } + case coredata.VendorServiceEntityType: + action = probo.ActionVendorServiceGet + loadNode = func(ctx context.Context, id gid.GID) (types.Node, error) { + vendorService, err := prb.VendorServices.Get(ctx, id) + if err != nil { + return nil, err + } + return types.NewVendorService(vendorService), nil + } + case coredata.DocumentVersionEntityType: + action = probo.ActionDocumentVersionList + loadNode = func(ctx context.Context, id gid.GID) (types.Node, error) { + documentVersion, err := prb.Documents.GetVersion(ctx, id) + if err != nil { + return nil, err + } + return types.NewDocumentVersion(documentVersion), nil + } + case coredata.DocumentVersionSignatureEntityType: + action = probo.ActionDocumentVersionSignatureList + loadNode = func(ctx context.Context, id gid.GID) (types.Node, error) { + documentVersionSignature, err := prb.Documents.GetVersionSignature(ctx, id) + if err != nil { + return nil, err + } + return types.NewDocumentVersionSignature(documentVersionSignature), nil + } + case coredata.AssetEntityType: + action = probo.ActionAssetList + loadNode = func(ctx context.Context, id gid.GID) (types.Node, error) { + asset, err := prb.Assets.Get(ctx, id) + if err != nil { + return nil, err + } + return types.NewAsset(asset), nil + } + case coredata.DatumEntityType: + action = probo.ActionDatumList + loadNode = func(ctx context.Context, id gid.GID) (types.Node, error) { + datum, err := prb.Data.Get(ctx, id) + if err != nil { + return nil, err + } + return types.NewDatum(datum), nil + } + case coredata.AuditEntityType: + action = probo.ActionAuditList + loadNode = func(ctx context.Context, id gid.GID) (types.Node, error) { + audit, err := prb.Audits.Get(ctx, id) + if err != nil { + return nil, err + } + return types.NewAudit(audit), nil + } + case coredata.FindingEntityType: + action = probo.ActionFindingList + loadNode = func(ctx context.Context, id gid.GID) (types.Node, error) { + finding, err := prb.Findings.Get(ctx, id) + if err != nil { + return nil, err + } + return types.NewFinding(finding), nil + } + case coredata.ObligationEntityType: + action = probo.ActionObligationList + loadNode = func(ctx context.Context, id gid.GID) (types.Node, error) { + obligation, err := prb.Obligations.Get(ctx, id) + if err != nil { + return nil, err + } + return types.NewObligation(obligation), nil + } + case coredata.ReportEntityType: + action = probo.ActionReportGet + loadNode = func(ctx context.Context, id gid.GID) (types.Node, error) { + report, err := prb.Reports.Get(ctx, id) + if err != nil { + return nil, err + } + return types.NewReport(report), nil + } + case coredata.ProcessingActivityEntityType: + action = probo.ActionProcessingActivityList + loadNode = func(ctx context.Context, id gid.GID) (types.Node, error) { + processingActivity, err := prb.ProcessingActivities.Get(ctx, id) + if err != nil { + return nil, err + } + return types.NewProcessingActivity(processingActivity), nil + } + case coredata.DataProtectionImpactAssessmentEntityType: + // TODO: add action + // action = probo.ActionDataProtectionImpactAssessmentGet + loadNode = func(ctx context.Context, id gid.GID) (types.Node, error) { + dpia, err := prb.DataProtectionImpactAssessments.Get(ctx, id) + if err != nil { + return nil, err + } + return types.NewDataProtectionImpactAssessment(dpia), nil + } + case coredata.TransferImpactAssessmentEntityType: + // TODO: add action + //action = probo.ActionTransferImpactAssessmentGet + loadNode = func(ctx context.Context, id gid.GID) (types.Node, error) { + tia, err := prb.TransferImpactAssessments.Get(ctx, id) + if err != nil { + return nil, err + } + return types.NewTransferImpactAssessment(tia), nil + } + case coredata.SnapshotEntityType: + action = probo.ActionSnapshotList + loadNode = func(ctx context.Context, id gid.GID) (types.Node, error) { + snapshot, err := prb.Snapshots.Get(ctx, id) + if err != nil { + return nil, err + } + return types.NewSnapshot(snapshot), nil + } + case coredata.TrustCenterEntityType: + action = probo.ActionTrustCenterGet + loadNode = func(ctx context.Context, id gid.GID) (types.Node, error) { + trustCenter, err := prb.TrustCenters.Get(ctx, id) + if err != nil { + return nil, err + } + + var file *coredata.File + if trustCenter.NonDisclosureAgreementFileID != nil { + file, err = prb.Files.Get(ctx, *trustCenter.NonDisclosureAgreementFileID) + if err != nil { + return nil, fmt.Errorf("cannot get NDA file: %w", err) + } + } + + return types.NewTrustCenter(trustCenter, file), nil + } + case coredata.TrustCenterAccessEntityType: + action = probo.ActionTrustCenterAccessGet + loadNode = func(ctx context.Context, id gid.GID) (types.Node, error) { + trustCenterAccess, err := prb.TrustCenterAccesses.Get(ctx, id) + if err != nil { + return nil, err + } + return types.NewTrustCenterAccess(trustCenterAccess), nil + } + case coredata.MeetingEntityType: + action = probo.ActionMeetingGet + loadNode = func(ctx context.Context, id gid.GID) (types.Node, error) { + meeting, err := prb.Meetings.Get(ctx, id) + if err != nil { + return nil, err + } + return types.NewMeeting(meeting), nil + } + case coredata.RightsRequestEntityType: + action = probo.ActionRightsRequestGet + loadNode = func(ctx context.Context, id gid.GID) (types.Node, error) { + rightsRequest, err := prb.RightsRequests.Get(ctx, id) + if err != nil { + return nil, err + } + return types.NewRightsRequest(rightsRequest), nil + } + case coredata.StatementOfApplicabilityEntityType: + action = probo.ActionStatementOfApplicabilityGet + loadNode = func(ctx context.Context, id gid.GID) (types.Node, error) { + statementOfApplicability, err := prb.StatementsOfApplicability.Get(ctx, id) + if err != nil { + return nil, err + } + return types.NewStatementOfApplicability(statementOfApplicability), nil + } + case coredata.WebhookSubscriptionEntityType: + action = probo.ActionWebhookSubscriptionGet + loadNode = func(ctx context.Context, id gid.GID) (types.Node, error) { + wc, err := prb.WebhookSubscriptions.Get(ctx, id) + if err != nil { + return nil, err + } + return types.NewWebhookSubscription(wc), nil + } + case coredata.AccessReviewCampaignEntityType: + action = probo.ActionAccessReviewCampaignGet + loadNode = func(ctx context.Context, id gid.GID) (types.Node, error) { + scope := coredata.NewScopeFromObjectID(id) + campaign, err := r.accessReview.Campaigns(scope).Get(ctx, id) + if err != nil { + return nil, err + } + return types.NewAccessReviewCampaign(campaign), nil + } + case coredata.AccessSourceEntityType: + action = probo.ActionAccessSourceGet + loadNode = func(ctx context.Context, id gid.GID) (types.Node, error) { + scope := coredata.NewScopeFromObjectID(id) + source, err := r.accessReview.Sources(scope).Get(ctx, id) + if err != nil { + return nil, err + } + return types.NewAccessSource(source), nil + } + case coredata.AccessEntryEntityType: + action = probo.ActionAccessEntryGet + loadNode = func(ctx context.Context, id gid.GID) (types.Node, error) { + scope := coredata.NewScopeFromObjectID(id) + entry, err := r.accessReview.Entries(scope).Get(ctx, id) + if err != nil { + return nil, err + } + return types.NewAccessEntry(entry), nil + } + default: + } + + if err := r.authorize(ctx, id, action); err != nil { + return nil, err + } + + node, err := loadNode(ctx, id) + if err != nil { + if errors.Is(err, coredata.ErrResourceNotFound) { + return nil, gqlutils.NotFound(ctx, err) + } + + r.logger.ErrorCtx(ctx, "cannot load node", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return node, nil +} + +// Viewer is the resolver for the viewer field. +func (r *queryResolver) Viewer(ctx context.Context) (*types.Viewer, error) { + identity := authn.IdentityFromContext(ctx) + + session := authn.SessionFromContext(ctx) + apiKey := authn.APIKeyFromContext(ctx) + + var viewerID gid.GID + if session != nil { + viewerID = session.ID + } else if apiKey != nil { + viewerID = apiKey.ID + } else { + viewerID = identity.ID + } + + return &types.Viewer{ID: viewerID}, nil +} + +// File returns schema.FileResolver implementation. +func (r *Resolver) File() schema.FileResolver { return &fileResolver{r} } + +// Mutation returns schema.MutationResolver implementation. +func (r *Resolver) Mutation() schema.MutationResolver { return &mutationResolver{r} } + +// Organization returns schema.OrganizationResolver implementation. +func (r *Resolver) Organization() schema.OrganizationResolver { return &organizationResolver{r} } + +// Query returns schema.QueryResolver implementation. +func (r *Resolver) Query() schema.QueryResolver { return &queryResolver{r} } + +// Viewer returns schema.ViewerResolver implementation. +func (r *Resolver) Viewer() schema.ViewerResolver { return &viewerResolver{r} } + +type fileResolver struct{ *Resolver } +type mutationResolver struct{ *Resolver } +type organizationResolver struct{ *Resolver } +type queryResolver struct{ *Resolver } +type viewerResolver struct{ *Resolver } diff --git a/pkg/server/api/console/v1/connector.resolvers.go b/pkg/server/api/console/v1/connector.resolvers.go new file mode 100644 index 000000000..43bd3b540 --- /dev/null +++ b/pkg/server/api/console/v1/connector.resolvers.go @@ -0,0 +1,274 @@ +package console_v1 + +// This file will be automatically regenerated based on the schema, any resolver +// implementations +// will be copied through when generating and any unknown code will be moved to the end. +// Code generated by github.com/99designs/gqlgen version v0.17.87 + +import ( + "context" + "errors" + "fmt" + + "go.gearno.de/kit/log" + "go.probo.inc/probo/pkg/accessreview/drivers" + "go.probo.inc/probo/pkg/connector" + "go.probo.inc/probo/pkg/coredata" + "go.probo.inc/probo/pkg/page" + "go.probo.inc/probo/pkg/probo" + "go.probo.inc/probo/pkg/server/api/console/v1/schema" + "go.probo.inc/probo/pkg/server/api/console/v1/types" + "go.probo.inc/probo/pkg/server/gqlutils" + "go.probo.inc/probo/pkg/slack" +) + +// Oauth2Scopes is the resolver for the oauth2Scopes field. +func (r *connectorResolver) Oauth2Scopes(ctx context.Context, obj *types.Connector) ([]string, error) { + scopes := drivers.ProviderOAuth2Scopes(obj.Provider) + if scopes == nil { + return []string{}, nil + } + return scopes, nil +} + +// CreateAPIKeyConnector is the resolver for the createAPIKeyConnector field. +func (r *mutationResolver) CreateAPIKeyConnector(ctx context.Context, input types.CreateAPIKeyConnectorInput) (*types.CreateAPIKeyConnectorPayload, error) { + if err := r.authorize(ctx, input.OrganizationID, probo.ActionConnectorCreate); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, input.OrganizationID.TenantID()) + + req := probo.CreateConnectorRequest{ + OrganizationID: input.OrganizationID, + Provider: input.Provider, + Protocol: coredata.ConnectorProtocolAPIKey, + Connection: &connector.APIKeyConnection{APIKey: input.APIKey}, + } + + if input.TallyOrganizationID != nil { + req.TallySettings = &coredata.TallyConnectorSettings{ + OrganizationID: *input.TallyOrganizationID, + } + } + if input.SentryOrganizationSlug != nil { + req.SentrySettings = &coredata.SentryConnectorSettings{ + OrganizationSlug: *input.SentryOrganizationSlug, + } + } + if input.SupabaseOrganizationSlug != nil { + req.SupabaseSettings = &coredata.SupabaseConnectorSettings{ + OrganizationSlug: *input.SupabaseOrganizationSlug, + } + } + if input.GithubOrganization != nil { + req.GitHubSettings = &coredata.GitHubConnectorSettings{ + Organization: *input.GithubOrganization, + } + } + if input.OnePasswordScimBridgeURL != nil { + req.OnePasswordSettings = &coredata.OnePasswordConnectorSettings{ + SCIMBridgeURL: *input.OnePasswordScimBridgeURL, + } + } + cnnctr, err := prb.Connectors.Create(ctx, req) + if err != nil { + if errors.Is(err, coredata.ErrResourceAlreadyExists) { + return nil, gqlutils.Conflict(ctx, err) + } + + panic(fmt.Errorf("cannot create API key connector: %w", err)) + } + + return &types.CreateAPIKeyConnectorPayload{ + Connector: types.NewConnector(cnnctr), + }, nil +} + +// CreateClientCredentialsConnector is the resolver for the createClientCredentialsConnector field. +func (r *mutationResolver) CreateClientCredentialsConnector(ctx context.Context, input types.CreateClientCredentialsConnectorInput) (*types.CreateClientCredentialsConnectorPayload, error) { + if err := r.authorize(ctx, input.OrganizationID, probo.ActionConnectorCreate); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, input.OrganizationID.TenantID()) + + oauth2Conn := &connector.OAuth2Connection{ + GrantType: connector.OAuth2GrantTypeClientCredentials, + ClientID: input.ClientID, + ClientSecret: input.ClientSecret, + TokenURL: input.TokenURL, + } + if input.Scope != nil { + oauth2Conn.Scope = *input.Scope + } + + req := probo.CreateConnectorRequest{ + OrganizationID: input.OrganizationID, + Provider: input.Provider, + Protocol: coredata.ConnectorProtocolOAuth2, + Connection: oauth2Conn, + } + + if input.OnePasswordAccountID != nil && input.OnePasswordRegion != nil { + req.OnePasswordUsersAPISettings = &coredata.OnePasswordUsersAPISettings{ + AccountID: *input.OnePasswordAccountID, + Region: *input.OnePasswordRegion, + } + } + + cnnctr, err := prb.Connectors.Create(ctx, req) + if err != nil { + if errors.Is(err, coredata.ErrResourceAlreadyExists) { + return nil, gqlutils.Conflict(ctx, err) + } + + panic(fmt.Errorf("cannot create client credentials connector: %w", err)) + } + + return &types.CreateClientCredentialsConnectorPayload{ + Connector: types.NewConnector(cnnctr), + }, nil +} + +// DeleteConnector is the resolver for the deleteConnector field. +func (r *mutationResolver) DeleteConnector(ctx context.Context, input types.DeleteConnectorInput) (*types.DeleteConnectorPayload, error) { + if err := r.authorize(ctx, input.ConnectorID, probo.ActionConnectorDelete); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, input.ConnectorID.TenantID()) + + if err := prb.Connectors.Delete(ctx, input.ConnectorID); err != nil { + panic(fmt.Errorf("cannot delete connector: %w", err)) + } + + return &types.DeleteConnectorPayload{ + DeletedConnectorID: input.ConnectorID, + }, nil +} + +// DeleteSlackConnection is the resolver for the deleteSlackConnection field. +func (r *mutationResolver) DeleteSlackConnection(ctx context.Context, input types.DeleteSlackConnectionInput) (*types.DeleteSlackConnectionPayload, error) { + if err := r.authorize(ctx, input.SlackConnectionID, probo.ActionConnectorDelete); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, input.SlackConnectionID.TenantID()) + + err := prb.Connectors.Delete(ctx, input.SlackConnectionID) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot delete slack connection", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.DeleteSlackConnectionPayload{ + DeletedSlackConnectionID: input.SlackConnectionID, + }, nil +} + +// SlackConnections is the resolver for the slackConnections field. +func (r *organizationResolver) SlackConnections(ctx context.Context, obj *types.Organization, first *int, after *page.CursorKey, last *int, before *page.CursorKey) (*types.SlackConnectionConnection, error) { + if err := r.authorize(ctx, obj.ID, probo.ActionSlackConnectionList); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, obj.ID.TenantID()) + + slackProvider := coredata.ConnectorProviderSlack + filter := coredata.NewConnectorProviderFilter(&slackProvider) + + pageOrderBy := page.OrderBy[coredata.ConnectorOrderField]{ + Field: coredata.ConnectorOrderFieldCreatedAt, + Direction: page.OrderDirectionDesc, + } + + cursor := types.NewCursor(first, after, last, before, pageOrderBy) + + page, err := prb.Connectors.ListForOrganizationID(ctx, obj.ID, cursor, filter) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot list organization slack connections", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return types.NewSlackConnectionConnection(page), nil +} + +// SlackOAuth2Scopes is the resolver for the slackOAuth2Scopes field. +func (r *organizationResolver) SlackOAuth2Scopes(ctx context.Context, obj *types.Organization) ([]string, error) { + return slack.OAuth2Scopes, nil +} + +// Connectors is the resolver for the connectors field. +func (r *organizationResolver) Connectors(ctx context.Context, obj *types.Organization, filter *types.ConnectorFilter) ([]*types.Connector, error) { + if err := r.authorize(ctx, obj.ID, probo.ActionConnectorList); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, obj.ID.TenantID()) + + connectors, err := prb.Connectors.ListAllForOrganizationID(ctx, obj.ID) + if err != nil { + panic(fmt.Errorf("cannot list organization connectors: %w", err)) + } + + if filter != nil && len(filter.Providers) > 0 { + allowed := make(map[coredata.ConnectorProvider]struct{}, len(filter.Providers)) + for _, provider := range filter.Providers { + allowed[provider] = struct{}{} + } + + filtered := make(coredata.Connectors, 0, len(connectors)) + for _, cnnctr := range connectors { + if _, ok := allowed[cnnctr.Provider]; ok { + filtered = append(filtered, cnnctr) + } + } + connectors = filtered + } + + return types.NewConnectors(connectors), nil +} + +// ConnectorProviderInfos is the resolver for the connectorProviderInfos field. +func (r *organizationResolver) ConnectorProviderInfos(ctx context.Context, obj *types.Organization) ([]*types.ConnectorProviderInfo, error) { + if err := r.authorize(ctx, obj.ID, probo.ActionConnectorList); err != nil { + return nil, err + } + + var infos []*types.ConnectorProviderInfo + for _, provider := range coredata.ConnectorProviders() { + _, oauthErr := r.connectorRegistry.Get(string(provider)) + scopes := drivers.ProviderOAuth2Scopes(provider) + if scopes == nil { + scopes = []string{} + } + info := &types.ConnectorProviderInfo{ + Provider: provider, + DisplayName: providerDisplayName(provider), + OauthConfigured: oauthErr == nil, + APIKeySupported: providerSupportsAPIKey(provider), + ClientCredentialsSupported: providerSupportsClientCredentials(provider), + Oauth2Scopes: scopes, + ExtraSettings: providerExtraSettings(provider), + } + infos = append(infos, info) + } + return infos, nil +} + +// Permission is the resolver for the permission field. +func (r *slackConnectionResolver) Permission(ctx context.Context, obj *types.SlackConnection, action string) (bool, error) { + return r.Resolver.Permission(ctx, obj, action) +} + +// Connector returns schema.ConnectorResolver implementation. +func (r *Resolver) Connector() schema.ConnectorResolver { return &connectorResolver{r} } + +// SlackConnection returns schema.SlackConnectionResolver implementation. +func (r *Resolver) SlackConnection() schema.SlackConnectionResolver { + return &slackConnectionResolver{r} +} + +type connectorResolver struct{ *Resolver } +type slackConnectionResolver struct{ *Resolver } diff --git a/pkg/server/api/console/v1/control.resolvers.go b/pkg/server/api/console/v1/control.resolvers.go new file mode 100644 index 000000000..ee5125179 --- /dev/null +++ b/pkg/server/api/console/v1/control.resolvers.go @@ -0,0 +1,1064 @@ +package console_v1 + +// This file will be automatically regenerated based on the schema, any resolver +// implementations +// will be copied through when generating and any unknown code will be moved to the end. +// Code generated by github.com/99designs/gqlgen version v0.17.87 + +import ( + "context" + "encoding/base64" + "errors" + "fmt" + + "github.com/vikstrous/dataloadgen" + "go.gearno.de/kit/log" + "go.probo.inc/probo/pkg/coredata" + "go.probo.inc/probo/pkg/gid" + "go.probo.inc/probo/pkg/iam" + "go.probo.inc/probo/pkg/page" + "go.probo.inc/probo/pkg/probo" + "go.probo.inc/probo/pkg/server/api/console/v1/dataloader" + "go.probo.inc/probo/pkg/server/api/console/v1/schema" + "go.probo.inc/probo/pkg/server/api/console/v1/types" + "go.probo.inc/probo/pkg/server/gqlutils" + "go.probo.inc/probo/pkg/validator" +) + +// StatementOfApplicability is the resolver for the statementOfApplicability field. +func (r *applicabilityStatementResolver) StatementOfApplicability(ctx context.Context, obj *types.ApplicabilityStatement) (*types.StatementOfApplicability, error) { + if err := r.authorize(ctx, obj.StatementOfApplicability.ID, probo.ActionStatementOfApplicabilityGet); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, obj.StatementOfApplicability.ID.TenantID()) + + soa, err := prb.StatementsOfApplicability.Get(ctx, obj.StatementOfApplicability.ID) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot get statement of applicability", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return types.NewStatementOfApplicability(soa), nil +} + +// Control is the resolver for the control field. +func (r *applicabilityStatementResolver) Control(ctx context.Context, obj *types.ApplicabilityStatement) (*types.Control, error) { + if err := r.authorize(ctx, obj.ID, probo.ActionControlGet); err != nil { + return nil, err + } + + loaders := dataloader.FromContext(ctx) + + control, err := loaders.Control.Load(ctx, obj.Control.ID) + if err != nil { + if errors.Is(err, dataloadgen.ErrNotFound) { + return nil, gqlutils.NotFound(ctx, err) + } + + r.logger.ErrorCtx(ctx, "cannot get control", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return types.NewControl(control), nil +} + +// Permission is the resolver for the permission field. +func (r *applicabilityStatementResolver) Permission(ctx context.Context, obj *types.ApplicabilityStatement, action string) (bool, error) { + return r.Resolver.Permission(ctx, obj, action) +} + +// TotalCount is the resolver for the totalCount field. +func (r *applicabilityStatementConnectionResolver) TotalCount(ctx context.Context, obj *types.ApplicabilityStatementConnection) (int, error) { + if err := r.authorize(ctx, obj.ParentID, probo.ActionApplicabilityStatementList); err != nil { + return 0, err + } + + prb := r.ProboService(ctx, obj.ParentID.TenantID()) + + switch obj.Resolver.(type) { + case *statementOfApplicabilityResolver: + count, err := prb.StatementsOfApplicability.CountApplicabilityStatements(ctx, obj.ParentID) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot count applicability statements", log.Error(err)) + return 0, gqlutils.Internal(ctx) + } + return count, nil + } + + r.logger.ErrorCtx(ctx, "unsupported resolver for applicability statement connection", log.String("resolver", fmt.Sprintf("%T", obj.Resolver))) + return 0, gqlutils.Internal(ctx) +} + +// Organization is the resolver for the organization field. +func (r *controlResolver) Organization(ctx context.Context, obj *types.Control) (*types.Organization, error) { + if err := r.authorize(ctx, obj.ID, probo.ActionOrganizationGet); err != nil { + return nil, err + } + + loaders := dataloader.FromContext(ctx) + + organization, err := loaders.Organization.Load(ctx, obj.Organization.ID) + if err != nil { + if errors.Is(err, coredata.ErrResourceNotFound) || errors.Is(err, dataloadgen.ErrNotFound) { + return nil, gqlutils.NotFound(ctx, err) + } + + r.logger.ErrorCtx(ctx, "cannot get organization", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + return types.NewOrganization(organization), nil +} + +// Regulatory is the resolver for the regulatory field. +func (r *controlResolver) Regulatory(ctx context.Context, obj *types.Control) (bool, error) { + prb := r.ProboService(ctx, obj.ID.TenantID()) + + hasRegulatory, err := prb.Controls.HasRegulatoryObligation(ctx, obj.ID) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot check regulatory obligation", log.Error(err)) + return false, gqlutils.Internal(ctx) + } + + return hasRegulatory, nil +} + +// Contractual is the resolver for the contractual field. +func (r *controlResolver) Contractual(ctx context.Context, obj *types.Control) (bool, error) { + prb := r.ProboService(ctx, obj.ID.TenantID()) + + hasContractual, err := prb.Controls.HasContractualObligation(ctx, obj.ID) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot check contractual obligation", log.Error(err)) + return false, gqlutils.Internal(ctx) + } + + return hasContractual, nil +} + +// RiskAssessment is the resolver for the riskAssessment field. +func (r *controlResolver) RiskAssessment(ctx context.Context, obj *types.Control) (bool, error) { + prb := r.ProboService(ctx, obj.ID.TenantID()) + + hasRisk, err := prb.Controls.HasRiskAssessment(ctx, obj.ID) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot check risk assessment", log.Error(err)) + return false, gqlutils.Internal(ctx) + } + + return hasRisk, nil +} + +// Framework is the resolver for the framework field. +func (r *controlResolver) Framework(ctx context.Context, obj *types.Control) (*types.Framework, error) { + if err := r.authorize(ctx, obj.ID, probo.ActionFrameworkGet); err != nil { + return nil, err + } + + loaders := dataloader.FromContext(ctx) + + framework, err := loaders.Framework.Load(ctx, obj.Framework.ID) + if err != nil { + if errors.Is(err, coredata.ErrResourceNotFound) || errors.Is(err, dataloadgen.ErrNotFound) { + return nil, gqlutils.NotFound(ctx, err) + } + + r.logger.ErrorCtx(ctx, "cannot get framework", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return types.NewFramework(framework), nil +} + +// Measures is the resolver for the measures field. +func (r *controlResolver) Measures(ctx context.Context, obj *types.Control, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.MeasureOrderBy, filter *types.MeasureFilter) (*types.MeasureConnection, error) { + if err := r.authorize(ctx, obj.ID, probo.ActionMeasureList); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, obj.ID.TenantID()) + + pageOrderBy := page.OrderBy[coredata.MeasureOrderField]{ + Field: coredata.MeasureOrderFieldCreatedAt, + Direction: page.OrderDirectionDesc, + } + if orderBy != nil { + pageOrderBy = page.OrderBy[coredata.MeasureOrderField]{ + Field: orderBy.Field, + Direction: orderBy.Direction, + } + } + + cursor := types.NewCursor(first, after, last, before, pageOrderBy) + + var measureFilter = coredata.NewMeasureFilter(nil, nil, nil) + if filter != nil { + measureFilter = coredata.NewMeasureFilter(filter.Query, filter.State, filter.Category) + } + + page, err := prb.Measures.ListForControlID(ctx, obj.ID, cursor, measureFilter) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot list measures", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return types.NewMeasureConnection(page, r, obj.ID, measureFilter), nil +} + +// Documents is the resolver for the documents field. +func (r *controlResolver) Documents(ctx context.Context, obj *types.Control, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.DocumentOrderBy, filter *types.DocumentFilter) (*types.DocumentConnection, error) { + if err := r.authorize(ctx, obj.ID, probo.ActionDocumentList); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, obj.ID.TenantID()) + + pageOrderBy := page.OrderBy[coredata.DocumentOrderField]{ + Field: coredata.DocumentOrderFieldCreatedAt, + Direction: page.OrderDirectionDesc, + } + if orderBy != nil { + pageOrderBy = page.OrderBy[coredata.DocumentOrderField]{ + Field: orderBy.Field, + Direction: orderBy.Direction, + } + } + + cursor := types.NewCursor(first, after, last, before, pageOrderBy) + + var documentFilter = coredata.NewDocumentFilter(nil) + if filter != nil { + documentFilter = coredata.NewDocumentFilter(filter.Query). + WithDocumentTypes(filter.DocumentTypes). + WithClassifications(filter.Classifications) + } + + page, err := prb.Documents.ListForControlID(ctx, obj.ID, cursor, documentFilter) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot list documents", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return types.NewDocumentConnection(page, r, obj.ID, documentFilter), nil +} + +// Audits is the resolver for the audits field. +func (r *controlResolver) Audits(ctx context.Context, obj *types.Control, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.AuditOrderBy) (*types.AuditConnection, error) { + if err := r.authorize(ctx, obj.ID, probo.ActionAuditList); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, obj.ID.TenantID()) + + pageOrderBy := page.OrderBy[coredata.AuditOrderField]{ + Field: coredata.AuditOrderFieldCreatedAt, + Direction: page.OrderDirectionDesc, + } + if orderBy != nil { + pageOrderBy = page.OrderBy[coredata.AuditOrderField]{ + Field: orderBy.Field, + Direction: orderBy.Direction, + } + } + + cursor := types.NewCursor(first, after, last, before, pageOrderBy) + + page, err := prb.Audits.ListForControlID(ctx, obj.ID, cursor) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot list control audits", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return types.NewAuditConnection(page, r, obj.ID), nil +} + +// Obligations is the resolver for the obligations field. +func (r *controlResolver) Obligations(ctx context.Context, obj *types.Control, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.ObligationOrderBy, filter *types.ObligationFilter) (*types.ObligationConnection, error) { + if err := r.authorize(ctx, obj.ID, probo.ActionObligationList); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, obj.ID.TenantID()) + + pageOrderBy := page.OrderBy[coredata.ObligationOrderField]{ + Field: coredata.ObligationOrderFieldCreatedAt, + Direction: page.OrderDirectionDesc, + } + if orderBy != nil { + pageOrderBy = page.OrderBy[coredata.ObligationOrderField]{ + Field: orderBy.Field, + Direction: orderBy.Direction, + } + } + + cursor := types.NewCursor(first, after, last, before, pageOrderBy) + + var snapshotID **gid.GID + if filter != nil { + snapshotID = &filter.SnapshotID + } + obligationFilter := coredata.NewObligationFilter(snapshotID) + page, err := prb.Obligations.ListForControlID(ctx, obj.ID, cursor, obligationFilter) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot list control obligations", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return types.NewObligationConnection(page, r, obj.ID, filter), nil +} + +// Snapshots is the resolver for the snapshots field. +func (r *controlResolver) Snapshots(ctx context.Context, obj *types.Control, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.SnapshotOrderBy) (*types.SnapshotConnection, error) { + if err := r.authorize(ctx, obj.ID, probo.ActionSnapshotList); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, obj.ID.TenantID()) + + pageOrderBy := page.OrderBy[coredata.SnapshotOrderField]{ + Field: coredata.SnapshotOrderFieldCreatedAt, + Direction: page.OrderDirectionDesc, + } + + if orderBy != nil { + pageOrderBy = page.OrderBy[coredata.SnapshotOrderField]{ + Field: orderBy.Field, + Direction: orderBy.Direction, + } + } + + cursor := types.NewCursor(first, after, last, before, pageOrderBy) + + page, err := prb.Snapshots.ListForControlID(ctx, obj.ID, cursor) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot list control snapshots", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return types.NewSnapshotConnection(page, r, obj.ID), nil +} + +// Permission is the resolver for the permission field. +func (r *controlResolver) Permission(ctx context.Context, obj *types.Control, action string) (bool, error) { + return r.Resolver.Permission(ctx, obj, action) +} + +// TotalCount is the resolver for the totalCount field. +func (r *controlConnectionResolver) TotalCount(ctx context.Context, obj *types.ControlConnection) (int, error) { + if err := r.authorize(ctx, obj.ParentID, probo.ActionControlList); err != nil { + return 0, err + } + + prb := r.ProboService(ctx, obj.ParentID.TenantID()) + + switch obj.Resolver.(type) { + case *organizationResolver: + count, err := prb.Controls.CountForOrganizationID(ctx, obj.ParentID, obj.Filters) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot count controls", log.Error(err)) + return 0, gqlutils.Internal(ctx) + } + return count, nil + case *frameworkResolver: + count, err := prb.Controls.CountForFrameworkID(ctx, obj.ParentID, obj.Filters) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot count controls", log.Error(err)) + return 0, gqlutils.Internal(ctx) + } + return count, nil + case *documentResolver: + count, err := prb.Controls.CountForDocumentID(ctx, obj.ParentID, obj.Filters) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot count controls", log.Error(err)) + return 0, gqlutils.Internal(ctx) + } + return count, nil + case *measureResolver: + count, err := prb.Controls.CountForMeasureID(ctx, obj.ParentID, obj.Filters) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot count controls", log.Error(err)) + return 0, gqlutils.Internal(ctx) + } + return count, nil + case *riskResolver: + count, err := prb.Controls.CountForRiskID(ctx, obj.ParentID, obj.Filters) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot count controls", log.Error(err)) + return 0, gqlutils.Internal(ctx) + } + return count, nil + case *statementOfApplicabilityResolver: + count, err := prb.Controls.CountForStatementOfApplicabilityID(ctx, obj.ParentID, obj.Filters) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot count controls", log.Error(err)) + return 0, gqlutils.Internal(ctx) + } + return count, nil + } + + r.logger.ErrorCtx(ctx, "unsupported resolver") + return 0, gqlutils.Internal(ctx) +} + +// CreateControl is the resolver for the createControl field. +func (r *mutationResolver) CreateControl(ctx context.Context, input types.CreateControlInput) (*types.CreateControlPayload, error) { + if err := r.authorize(ctx, input.FrameworkID, probo.ActionControlCreate); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, input.FrameworkID.TenantID()) + + control, err := prb.Controls.Create( + ctx, + probo.CreateControlRequest{ + FrameworkID: input.FrameworkID, + Name: input.Name, + Description: input.Description, + SectionTitle: input.SectionTitle, + BestPractice: input.BestPractice, + Implemented: input.Implemented, + NotImplementedJustification: input.NotImplementedJustification, + }, + ) + if err != nil { + if errors.Is(err, coredata.ErrResourceAlreadyExists) { + return nil, gqlutils.Conflict(ctx, err) + } + + if validationErrors, ok := errors.AsType[validator.ValidationErrors](err); ok { + return nil, gqlutils.InvalidValidationErrors(ctx, validationErrors) + } + r.logger.ErrorCtx(ctx, "cannot create control", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.CreateControlPayload{ + ControlEdge: types.NewControlEdge(control, coredata.ControlOrderFieldCreatedAt), + }, nil +} + +// UpdateControl is the resolver for the updateControl field. +func (r *mutationResolver) UpdateControl(ctx context.Context, input types.UpdateControlInput) (*types.UpdateControlPayload, error) { + if err := r.authorize(ctx, input.ID, probo.ActionControlUpdate); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, input.ID.TenantID()) + + control, err := prb.Controls.Update( + ctx, + probo.UpdateControlRequest{ + ID: input.ID, + Name: input.Name, + Description: gqlutils.UnwrapOmittable(input.Description), + SectionTitle: input.SectionTitle, + BestPractice: input.BestPractice, + Implemented: input.Implemented, + NotImplementedJustification: gqlutils.UnwrapOmittable(input.NotImplementedJustification), + }, + ) + + if err != nil { + if errors.Is(err, coredata.ErrResourceAlreadyExists) { + return nil, gqlutils.Conflict(ctx, err) + } + + if validationErrors, ok := errors.AsType[validator.ValidationErrors](err); ok { + return nil, gqlutils.InvalidValidationErrors(ctx, validationErrors) + } + r.logger.ErrorCtx(ctx, "cannot update control", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.UpdateControlPayload{ + Control: types.NewControl(control), + }, nil +} + +// DeleteControl is the resolver for the deleteControl field. +func (r *mutationResolver) DeleteControl(ctx context.Context, input types.DeleteControlInput) (*types.DeleteControlPayload, error) { + if err := r.authorize(ctx, input.ControlID, probo.ActionControlDelete); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, input.ControlID.TenantID()) + + err := prb.Controls.Delete(ctx, input.ControlID) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot delete control", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.DeleteControlPayload{ + DeletedControlID: input.ControlID, + }, nil +} + +// CreateControlMeasureMapping is the resolver for the createControlMeasureMapping field. +func (r *mutationResolver) CreateControlMeasureMapping(ctx context.Context, input types.CreateControlMeasureMappingInput) (*types.CreateControlMeasureMappingPayload, error) { + if err := r.authorize(ctx, input.ControlID, probo.ActionControlMeasureMappingCreate); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, input.MeasureID.TenantID()) + + control, measure, err := prb.Controls.CreateMeasureMapping(ctx, input.ControlID, input.MeasureID) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot create control measure mapping", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.CreateControlMeasureMappingPayload{ + ControlEdge: types.NewControlEdge(control, coredata.ControlOrderFieldCreatedAt), + MeasureEdge: types.NewMeasureEdge(measure, coredata.MeasureOrderFieldCreatedAt), + }, nil +} + +// CreateControlDocumentMapping is the resolver for the createControlDocumentMapping field. +func (r *mutationResolver) CreateControlDocumentMapping(ctx context.Context, input types.CreateControlDocumentMappingInput) (*types.CreateControlDocumentMappingPayload, error) { + if err := r.authorize(ctx, input.ControlID, probo.ActionControlDocumentMappingCreate); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, input.DocumentID.TenantID()) + + control, document, err := prb.Controls.CreateDocumentMapping(ctx, input.ControlID, input.DocumentID) + if err != nil { + if errors.Is(err, coredata.ErrResourceAlreadyExists) { + return nil, gqlutils.Conflict(ctx, err) + } + + r.logger.ErrorCtx(ctx, "cannot create control document mapping", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.CreateControlDocumentMappingPayload{ + ControlEdge: types.NewControlEdge(control, coredata.ControlOrderFieldCreatedAt), + DocumentEdge: types.NewDocumentEdge(document, coredata.DocumentOrderFieldTitle), + }, nil +} + +// DeleteControlMeasureMapping is the resolver for the deleteControlMeasureMapping field. +func (r *mutationResolver) DeleteControlMeasureMapping(ctx context.Context, input types.DeleteControlMeasureMappingInput) (*types.DeleteControlMeasureMappingPayload, error) { + if err := r.authorize(ctx, input.ControlID, probo.ActionControlMeasureMappingDelete); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, input.MeasureID.TenantID()) + + control, measure, err := prb.Controls.DeleteMeasureMapping(ctx, input.ControlID, input.MeasureID) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot delete control measure mapping", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.DeleteControlMeasureMappingPayload{ + DeletedControlID: control.ID, + DeletedMeasureID: measure.ID, + }, nil +} + +// DeleteControlDocumentMapping is the resolver for the deleteControlDocumentMapping field. +func (r *mutationResolver) DeleteControlDocumentMapping(ctx context.Context, input types.DeleteControlDocumentMappingInput) (*types.DeleteControlDocumentMappingPayload, error) { + if err := r.authorize(ctx, input.ControlID, probo.ActionControlDocumentMappingDelete); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, input.DocumentID.TenantID()) + + control, document, err := prb.Controls.DeleteDocumentMapping(ctx, input.ControlID, input.DocumentID) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot delete control document mapping", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.DeleteControlDocumentMappingPayload{ + DeletedControlID: control.ID, + DeletedDocumentID: document.ID, + }, nil +} + +// CreateApplicabilityStatement is the resolver for the createApplicabilityStatement field. +func (r *mutationResolver) CreateApplicabilityStatement(ctx context.Context, input types.CreateApplicabilityStatementInput) (*types.CreateApplicabilityStatementPayload, error) { + if err := r.authorize(ctx, input.ControlID, probo.ActionApplicabilityStatementCreate); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, input.StatementOfApplicabilityID.TenantID()) + + applicabilityStatement, err := prb.StatementsOfApplicability.CreateApplicabilityStatement(ctx, input.StatementOfApplicabilityID, input.ControlID, input.Applicability, input.Justification) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot create applicability statement", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.CreateApplicabilityStatementPayload{ + ApplicabilityStatementEdge: types.NewApplicabilityStatementEdge(applicabilityStatement, coredata.ApplicabilityStatementOrderFieldCreatedAt), + }, nil +} + +// UpdateApplicabilityStatement is the resolver for the updateApplicabilityStatement field. +func (r *mutationResolver) UpdateApplicabilityStatement(ctx context.Context, input types.UpdateApplicabilityStatementInput) (*types.UpdateApplicabilityStatementPayload, error) { + if err := r.authorize(ctx, input.ApplicabilityStatementID, probo.ActionApplicabilityStatementUpdate); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, input.ApplicabilityStatementID.TenantID()) + + applicabilityStatement, err := prb.StatementsOfApplicability.UpdateApplicabilityStatement(ctx, input.ApplicabilityStatementID, input.Applicability, input.Justification) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot update applicability statement", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.UpdateApplicabilityStatementPayload{ + ApplicabilityStatement: types.NewApplicabilityStatement(applicabilityStatement), + }, nil +} + +// DeleteApplicabilityStatement is the resolver for the deleteApplicabilityStatement field. +func (r *mutationResolver) DeleteApplicabilityStatement(ctx context.Context, input types.DeleteApplicabilityStatementInput) (*types.DeleteApplicabilityStatementPayload, error) { + if err := r.authorize(ctx, input.ApplicabilityStatementID, probo.ActionApplicabilityStatementDelete); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, input.ApplicabilityStatementID.TenantID()) + + err := prb.StatementsOfApplicability.DeleteApplicabilityStatement(ctx, input.ApplicabilityStatementID) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot delete applicability statement", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.DeleteApplicabilityStatementPayload{ + DeletedApplicabilityStatementID: input.ApplicabilityStatementID, + }, nil +} + +// CreateControlAuditMapping is the resolver for the createControlAuditMapping field. +func (r *mutationResolver) CreateControlAuditMapping(ctx context.Context, input types.CreateControlAuditMappingInput) (*types.CreateControlAuditMappingPayload, error) { + if err := r.authorize(ctx, input.ControlID, probo.ActionControlAuditMappingCreate); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, input.AuditID.TenantID()) + + control, audit, err := prb.Controls.CreateAuditMapping(ctx, input.ControlID, input.AuditID) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot create control audit mapping", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.CreateControlAuditMappingPayload{ + ControlEdge: types.NewControlEdge(control, coredata.ControlOrderFieldCreatedAt), + AuditEdge: types.NewAuditEdge(audit, coredata.AuditOrderFieldCreatedAt), + }, nil +} + +// DeleteControlAuditMapping is the resolver for the deleteControlAuditMapping field. +func (r *mutationResolver) DeleteControlAuditMapping(ctx context.Context, input types.DeleteControlAuditMappingInput) (*types.DeleteControlAuditMappingPayload, error) { + if err := r.authorize(ctx, input.ControlID, probo.ActionControlAuditMappingDelete); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, input.AuditID.TenantID()) + + control, audit, err := prb.Controls.DeleteAuditMapping(ctx, input.ControlID, input.AuditID) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot delete control audit mapping", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.DeleteControlAuditMappingPayload{ + DeletedControlID: &control.ID, + DeletedAuditID: &audit.ID, + }, nil +} + +// CreateControlObligationMapping is the resolver for the createControlObligationMapping field. +func (r *mutationResolver) CreateControlObligationMapping(ctx context.Context, input types.CreateControlObligationMappingInput) (*types.CreateControlObligationMappingPayload, error) { + if err := r.authorize(ctx, input.ControlID, probo.ActionControlObligationMappingCreate); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, input.ObligationID.TenantID()) + + control, obligation, err := prb.Controls.CreateObligationMapping(ctx, input.ControlID, input.ObligationID) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot create control obligation mapping", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.CreateControlObligationMappingPayload{ + ControlEdge: types.NewControlEdge(control, coredata.ControlOrderFieldCreatedAt), + ObligationEdge: types.NewObligationEdge(obligation, coredata.ObligationOrderFieldCreatedAt), + }, nil +} + +// DeleteControlObligationMapping is the resolver for the deleteControlObligationMapping field. +func (r *mutationResolver) DeleteControlObligationMapping(ctx context.Context, input types.DeleteControlObligationMappingInput) (*types.DeleteControlObligationMappingPayload, error) { + if err := r.authorize(ctx, input.ControlID, probo.ActionControlObligationMappingDelete); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, input.ObligationID.TenantID()) + + control, obligation, err := prb.Controls.DeleteObligationMapping(ctx, input.ControlID, input.ObligationID) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot delete control obligation mapping", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.DeleteControlObligationMappingPayload{ + DeletedControlID: control.ID, + DeletedObligationID: obligation.ID, + }, nil +} + +// CreateControlSnapshotMapping is the resolver for the createControlSnapshotMapping field. +func (r *mutationResolver) CreateControlSnapshotMapping(ctx context.Context, input types.CreateControlSnapshotMappingInput) (*types.CreateControlSnapshotMappingPayload, error) { + if err := r.authorize(ctx, input.ControlID, probo.ActionControlSnapshotMappingCreate); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, input.SnapshotID.TenantID()) + + control, snapshot, err := prb.Controls.CreateSnapshotMapping(ctx, input.ControlID, input.SnapshotID) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot create control snapshot mapping", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.CreateControlSnapshotMappingPayload{ + ControlEdge: types.NewControlEdge(control, coredata.ControlOrderFieldCreatedAt), + SnapshotEdge: types.NewSnapshotEdge(snapshot, coredata.SnapshotOrderFieldCreatedAt), + }, nil +} + +// DeleteControlSnapshotMapping is the resolver for the deleteControlSnapshotMapping field. +func (r *mutationResolver) DeleteControlSnapshotMapping(ctx context.Context, input types.DeleteControlSnapshotMappingInput) (*types.DeleteControlSnapshotMappingPayload, error) { + if err := r.authorize(ctx, input.ControlID, probo.ActionControlSnapshotMappingDelete); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, input.SnapshotID.TenantID()) + + control, snapshot, err := prb.Controls.DeleteSnapshotMapping(ctx, input.ControlID, input.SnapshotID) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot delete control snapshot mapping", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.DeleteControlSnapshotMappingPayload{ + DeletedControlID: control.ID, + DeletedSnapshotID: snapshot.ID, + }, nil +} + +// CreateStatementOfApplicability is the resolver for the createStatementOfApplicability field. +func (r *mutationResolver) CreateStatementOfApplicability(ctx context.Context, input types.CreateStatementOfApplicabilityInput) (*types.CreateStatementOfApplicabilityPayload, error) { + if err := r.authorize(ctx, input.OrganizationID, probo.ActionStatementOfApplicabilityCreate); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, input.OrganizationID.TenantID()) + + statementOfApplicability, err := prb.StatementsOfApplicability.Create( + ctx, + probo.CreateStatementOfApplicabilityRequest{ + OrganizationID: input.OrganizationID, + Name: input.Name, + OwnerID: input.OwnerID, + }, + ) + if err != nil { + if errors.Is(err, coredata.ErrResourceAlreadyExists) { + return nil, gqlutils.Conflict(ctx, err) + } + if validationErrors, ok := errors.AsType[validator.ValidationErrors](err); ok { + return nil, gqlutils.InvalidValidationErrors(ctx, validationErrors) + } + r.logger.ErrorCtx(ctx, "cannot create statement_of_applicability", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.CreateStatementOfApplicabilityPayload{ + StatementOfApplicabilityEdge: types.NewStatementOfApplicabilityEdge(statementOfApplicability, coredata.StatementOfApplicabilityOrderFieldCreatedAt), + }, nil +} + +// UpdateStatementOfApplicability is the resolver for the updateStatementOfApplicability field. +func (r *mutationResolver) UpdateStatementOfApplicability(ctx context.Context, input types.UpdateStatementOfApplicabilityInput) (*types.UpdateStatementOfApplicabilityPayload, error) { + if err := r.authorize(ctx, input.ID, probo.ActionStatementOfApplicabilityUpdate); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, input.ID.TenantID()) + + var name *string + if input.Name != nil { + name = input.Name + } + + statementOfApplicability, err := prb.StatementsOfApplicability.Update( + ctx, + probo.UpdateStatementOfApplicabilityRequest{ + StatementOfApplicabilityID: input.ID, + Name: name, + OwnerID: input.OwnerID, + }, + ) + if err != nil { + if errors.Is(err, coredata.ErrResourceAlreadyExists) { + return nil, gqlutils.Conflict(ctx, err) + } + if validationErrors, ok := errors.AsType[validator.ValidationErrors](err); ok { + return nil, gqlutils.InvalidValidationErrors(ctx, validationErrors) + } + r.logger.ErrorCtx(ctx, "cannot update statement_of_applicability", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.UpdateStatementOfApplicabilityPayload{ + StatementOfApplicability: types.NewStatementOfApplicability(statementOfApplicability), + }, nil +} + +// DeleteStatementOfApplicability is the resolver for the deleteStatementOfApplicability field. +func (r *mutationResolver) DeleteStatementOfApplicability(ctx context.Context, input types.DeleteStatementOfApplicabilityInput) (*types.DeleteStatementOfApplicabilityPayload, error) { + if err := r.authorize(ctx, input.StatementOfApplicabilityID, probo.ActionStatementOfApplicabilityDelete); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, input.StatementOfApplicabilityID.TenantID()) + + err := prb.StatementsOfApplicability.Delete(ctx, input.StatementOfApplicabilityID) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot delete statement_of_applicability", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.DeleteStatementOfApplicabilityPayload{ + DeletedStatementOfApplicabilityID: input.StatementOfApplicabilityID, + }, nil +} + +// ExportStatementOfApplicabilityPDF is the resolver for the exportStatementOfApplicabilityPDF field. +func (r *mutationResolver) ExportStatementOfApplicabilityPDF(ctx context.Context, input types.ExportStatementOfApplicabilityPDFInput) (*types.ExportStatementOfApplicabilityPDFPayload, error) { + if err := r.authorize(ctx, input.StatementOfApplicabilityID, probo.ActionStatementOfApplicabilityExport); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, input.StatementOfApplicabilityID.TenantID()) + + pdfData, err := prb.StatementsOfApplicability.ExportPDF(ctx, input.StatementOfApplicabilityID) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot export statement of applicability PDF", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + base64Data := base64.StdEncoding.EncodeToString(pdfData) + dataURI := fmt.Sprintf("data:application/pdf;base64,%s", base64Data) + + return &types.ExportStatementOfApplicabilityPDFPayload{ + Data: dataURI, + }, nil +} + +// Controls is the resolver for the controls field. +func (r *organizationResolver) Controls(ctx context.Context, obj *types.Organization, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.ControlOrderBy, filter *types.ControlFilter) (*types.ControlConnection, error) { + if err := r.authorize(ctx, obj.ID, probo.ActionControlList); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, obj.ID.TenantID()) + + pageOrderBy := page.OrderBy[coredata.ControlOrderField]{ + Field: coredata.ControlOrderFieldCreatedAt, + Direction: page.OrderDirectionDesc, + } + if orderBy != nil { + pageOrderBy = page.OrderBy[coredata.ControlOrderField]{ + Field: orderBy.Field, + Direction: orderBy.Direction, + } + } + + cursor := types.NewCursor(first, after, last, before, pageOrderBy) + + var controlFilter = coredata.NewControlFilter(nil) + if filter != nil { + controlFilter = coredata.NewControlFilter(filter.Query) + } + + page, err := prb.Controls.ListForOrganizationID(ctx, obj.ID, cursor, controlFilter) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot list controls", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return types.NewControlConnection(page, r, obj.ID, controlFilter), nil +} + +// StatementsOfApplicability is the resolver for the statementsOfApplicability field. +func (r *organizationResolver) StatementsOfApplicability(ctx context.Context, obj *types.Organization, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.StatementOfApplicabilityOrderBy, filter *types.StatementOfApplicabilityFilter) (*types.StatementOfApplicabilityConnection, error) { + if err := r.authorize(ctx, obj.ID, probo.ActionStatementOfApplicabilityList); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, obj.ID.TenantID()) + + pageOrderBy := page.OrderBy[coredata.StatementOfApplicabilityOrderField]{ + Field: coredata.StatementOfApplicabilityOrderFieldCreatedAt, + Direction: page.OrderDirectionDesc, + } + if orderBy != nil { + pageOrderBy = page.OrderBy[coredata.StatementOfApplicabilityOrderField]{ + Field: orderBy.Field, + Direction: orderBy.Direction, + } + } + + cursor := types.NewCursor(first, after, last, before, pageOrderBy) + + var statementOfApplicabilityFilter = coredata.NewStatementOfApplicabilityFilter(nil) + if filter != nil { + statementOfApplicabilityFilter = coredata.NewStatementOfApplicabilityFilter(&filter.SnapshotID) + } + + page, err := prb.StatementsOfApplicability.ListForOrganizationID(ctx, obj.ID, cursor, statementOfApplicabilityFilter) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot list organization statements_of_applicability", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return types.NewStatementOfApplicabilityConnection(page, r, obj.ID, statementOfApplicabilityFilter), nil +} + +// Organization is the resolver for the organization field. +func (r *statementOfApplicabilityResolver) Organization(ctx context.Context, obj *types.StatementOfApplicability) (*types.Organization, error) { + if err := r.authorize(ctx, obj.ID, probo.ActionOrganizationGet); err != nil { + return nil, err + } + + loaders := dataloader.FromContext(ctx) + + organization, err := loaders.Organization.Load(ctx, obj.Organization.ID) + if err != nil { + if errors.Is(err, coredata.ErrResourceNotFound) || errors.Is(err, dataloadgen.ErrNotFound) { + return nil, gqlutils.NotFound(ctx, err) + } + r.logger.ErrorCtx(ctx, "cannot load organization", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return types.NewOrganization(organization), nil +} + +// Owner is the resolver for the owner field. +func (r *statementOfApplicabilityResolver) Owner(ctx context.Context, obj *types.StatementOfApplicability) (*types.Profile, error) { + if err := r.authorize(ctx, obj.ID, iam.ActionMembershipProfileGet); err != nil { + return nil, err + } + + loaders := dataloader.FromContext(ctx) + + owner, err := loaders.Profile.Load(ctx, obj.Owner.ID) + if err != nil { + if errors.Is(err, coredata.ErrResourceNotFound) || errors.Is(err, dataloadgen.ErrNotFound) { + return nil, gqlutils.NotFound(ctx, err) + } + r.logger.ErrorCtx(ctx, "cannot load owner", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return types.NewProfile(owner), nil +} + +// ApplicabilityStatements is the resolver for the applicabilityStatements field. +func (r *statementOfApplicabilityResolver) ApplicabilityStatements(ctx context.Context, obj *types.StatementOfApplicability, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.ApplicabilityStatementOrderBy) (*types.ApplicabilityStatementConnection, error) { + if err := r.authorize(ctx, obj.ID, probo.ActionApplicabilityStatementList); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, obj.ID.TenantID()) + + pageOrderBy := page.OrderBy[coredata.ApplicabilityStatementOrderField]{ + Field: coredata.ApplicabilityStatementOrderFieldCreatedAt, + Direction: page.OrderDirectionAsc, + } + if orderBy != nil { + pageOrderBy = page.OrderBy[coredata.ApplicabilityStatementOrderField]{ + Field: coredata.ApplicabilityStatementOrderField(orderBy.Field), + Direction: orderBy.Direction, + } + } + + cursor := types.NewCursor(first, after, last, before, pageOrderBy) + + p, err := prb.StatementsOfApplicability.ListApplicabilityStatements(ctx, obj.ID, cursor) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot list applicability statements", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return types.NewApplicabilityStatementConnection(p, r, obj.ID), nil +} + +// Permission is the resolver for the permission field. +func (r *statementOfApplicabilityResolver) Permission(ctx context.Context, obj *types.StatementOfApplicability, action string) (bool, error) { + return r.Resolver.Permission(ctx, obj, action) +} + +// TotalCount is the resolver for the totalCount field. +func (r *statementOfApplicabilityConnectionResolver) TotalCount(ctx context.Context, obj *types.StatementOfApplicabilityConnection) (int, error) { + prb := r.ProboService(ctx, obj.ParentID.TenantID()) + + switch obj.Resolver.(type) { + case *organizationResolver: + count, err := prb.StatementsOfApplicability.CountForOrganizationID(ctx, obj.ParentID, obj.Filters) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot count statements_of_applicability", log.Error(err)) + return 0, gqlutils.Internal(ctx) + } + return count, nil + } + + r.logger.ErrorCtx(ctx, "unsupported resolver") + return 0, gqlutils.Internal(ctx) +} + +// ApplicabilityStatement returns schema.ApplicabilityStatementResolver implementation. +func (r *Resolver) ApplicabilityStatement() schema.ApplicabilityStatementResolver { + return &applicabilityStatementResolver{r} +} + +// ApplicabilityStatementConnection returns schema.ApplicabilityStatementConnectionResolver implementation. +func (r *Resolver) ApplicabilityStatementConnection() schema.ApplicabilityStatementConnectionResolver { + return &applicabilityStatementConnectionResolver{r} +} + +// Control returns schema.ControlResolver implementation. +func (r *Resolver) Control() schema.ControlResolver { return &controlResolver{r} } + +// ControlConnection returns schema.ControlConnectionResolver implementation. +func (r *Resolver) ControlConnection() schema.ControlConnectionResolver { + return &controlConnectionResolver{r} +} + +// StatementOfApplicability returns schema.StatementOfApplicabilityResolver implementation. +func (r *Resolver) StatementOfApplicability() schema.StatementOfApplicabilityResolver { + return &statementOfApplicabilityResolver{r} +} + +// StatementOfApplicabilityConnection returns schema.StatementOfApplicabilityConnectionResolver implementation. +func (r *Resolver) StatementOfApplicabilityConnection() schema.StatementOfApplicabilityConnectionResolver { + return &statementOfApplicabilityConnectionResolver{r} +} + +type applicabilityStatementResolver struct{ *Resolver } +type applicabilityStatementConnectionResolver struct{ *Resolver } +type controlResolver struct{ *Resolver } +type controlConnectionResolver struct{ *Resolver } +type statementOfApplicabilityResolver struct{ *Resolver } +type statementOfApplicabilityConnectionResolver struct{ *Resolver } diff --git a/pkg/server/api/console/v1/data_protection_impact_assessment.resolvers.go b/pkg/server/api/console/v1/data_protection_impact_assessment.resolvers.go new file mode 100644 index 000000000..4505bcd8c --- /dev/null +++ b/pkg/server/api/console/v1/data_protection_impact_assessment.resolvers.go @@ -0,0 +1,489 @@ +package console_v1 + +// This file will be automatically regenerated based on the schema, any resolver +// implementations +// will be copied through when generating and any unknown code will be moved to the end. +// Code generated by github.com/99designs/gqlgen version v0.17.87 + +import ( + "context" + "encoding/base64" + "errors" + "fmt" + + "github.com/vikstrous/dataloadgen" + "go.gearno.de/kit/log" + "go.probo.inc/probo/pkg/coredata" + "go.probo.inc/probo/pkg/gid" + "go.probo.inc/probo/pkg/page" + "go.probo.inc/probo/pkg/probo" + "go.probo.inc/probo/pkg/server/api/console/v1/dataloader" + "go.probo.inc/probo/pkg/server/api/console/v1/schema" + "go.probo.inc/probo/pkg/server/api/console/v1/types" + "go.probo.inc/probo/pkg/server/gqlutils" + "go.probo.inc/probo/pkg/validator" +) + +// ProcessingActivity is the resolver for the processingActivity field. +func (r *dataProtectionImpactAssessmentResolver) ProcessingActivity(ctx context.Context, obj *types.DataProtectionImpactAssessment) (*types.ProcessingActivity, error) { + if err := r.authorize(ctx, obj.ID, probo.ActionProcessingActivityList); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, obj.ID.TenantID()) + + dpia, err := prb.DataProtectionImpactAssessments.Get(ctx, obj.ID) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot get processing activity dpia", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + processingActivity, err := prb.ProcessingActivities.Get(ctx, dpia.ProcessingActivityID) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot get processing activity", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return types.NewProcessingActivity(processingActivity), nil +} + +// Organization is the resolver for the organization field. +func (r *dataProtectionImpactAssessmentResolver) Organization(ctx context.Context, obj *types.DataProtectionImpactAssessment) (*types.Organization, error) { + if err := r.authorize(ctx, obj.ID, probo.ActionOrganizationGet); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, obj.ID.TenantID()) + + dpia, err := prb.DataProtectionImpactAssessments.Get(ctx, obj.ID) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot get processing activity dpia", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + organization, err := prb.Organizations.Get(ctx, dpia.OrganizationID) + if err != nil { + if errors.Is(err, coredata.ErrResourceNotFound) { + return nil, gqlutils.NotFound(ctx, err) + } + r.logger.ErrorCtx(ctx, "cannot get organization", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return types.NewOrganization(organization), nil +} + +// Permission is the resolver for the permission field. +func (r *dataProtectionImpactAssessmentResolver) Permission(ctx context.Context, obj *types.DataProtectionImpactAssessment, action string) (bool, error) { + return r.Resolver.Permission(ctx, obj, action) +} + +// TotalCount is the resolver for the totalCount field. +func (r *dataProtectionImpactAssessmentConnectionResolver) TotalCount(ctx context.Context, obj *types.DataProtectionImpactAssessmentConnection) (int, error) { + if err := r.authorize(ctx, obj.ParentID, probo.ActionDataProtectionImpactAssessmentList); err != nil { + return 0, err + } + + prb := r.ProboService(ctx, obj.ParentID.TenantID()) + + switch obj.Resolver.(type) { + case *organizationResolver: + count, err := prb.DataProtectionImpactAssessments.CountForOrganizationID(ctx, obj.ParentID, obj.Filter) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot count organization data protection impact assessments", log.Error(err)) + return 0, gqlutils.Internal(ctx) + } + return count, nil + } + + r.logger.ErrorCtx(ctx, "unsupported resolver") + return 0, gqlutils.Internal(ctx) +} + +// CreateDataProtectionImpactAssessment is the resolver for the createDataProtectionImpactAssessment field. +func (r *mutationResolver) CreateDataProtectionImpactAssessment(ctx context.Context, input types.CreateDataProtectionImpactAssessmentInput) (*types.CreateDataProtectionImpactAssessmentPayload, error) { + if err := r.authorize(ctx, input.ProcessingActivityID, probo.ActionDataProtectionImpactAssessmentCreate); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, input.ProcessingActivityID.TenantID()) + + req := probo.CreateDataProtectionImpactAssessmentRequest{ + ProcessingActivityID: input.ProcessingActivityID, + Description: input.Description, + NecessityAndProportionality: input.NecessityAndProportionality, + PotentialRisk: input.PotentialRisk, + Mitigations: input.Mitigations, + ResidualRisk: input.ResidualRisk, + } + + dpia, err := prb.DataProtectionImpactAssessments.Create(ctx, &req) + if err != nil { + if errors.Is(err, coredata.ErrResourceAlreadyExists) { + return nil, gqlutils.Conflict(ctx, err) + } + + if validationErrors, ok := errors.AsType[validator.ValidationErrors](err); ok { + return nil, gqlutils.InvalidValidationErrors(ctx, validationErrors) + } + r.logger.ErrorCtx(ctx, "cannot create data protection impact assessment", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.CreateDataProtectionImpactAssessmentPayload{ + DataProtectionImpactAssessment: types.NewDataProtectionImpactAssessment(dpia), + }, nil +} + +// UpdateDataProtectionImpactAssessment is the resolver for the updateDataProtectionImpactAssessment field. +func (r *mutationResolver) UpdateDataProtectionImpactAssessment(ctx context.Context, input types.UpdateDataProtectionImpactAssessmentInput) (*types.UpdateDataProtectionImpactAssessmentPayload, error) { + if err := r.authorize(ctx, input.ID, probo.ActionDataProtectionImpactAssessmentUpdate); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, input.ID.TenantID()) + + req := probo.UpdateDataProtectionImpactAssessmentRequest{ + ID: input.ID, + Description: gqlutils.UnwrapOmittable(input.Description), + NecessityAndProportionality: gqlutils.UnwrapOmittable(input.NecessityAndProportionality), + PotentialRisk: gqlutils.UnwrapOmittable(input.PotentialRisk), + Mitigations: gqlutils.UnwrapOmittable(input.Mitigations), + ResidualRisk: input.ResidualRisk, + } + + dpia, err := prb.DataProtectionImpactAssessments.Update(ctx, &req) + if err != nil { + if validationErrors, ok := errors.AsType[validator.ValidationErrors](err); ok { + return nil, gqlutils.InvalidValidationErrors(ctx, validationErrors) + } + r.logger.ErrorCtx(ctx, "cannot update data protection impact assessment", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.UpdateDataProtectionImpactAssessmentPayload{ + DataProtectionImpactAssessment: types.NewDataProtectionImpactAssessment(dpia), + }, nil +} + +// DeleteDataProtectionImpactAssessment is the resolver for the deleteDataProtectionImpactAssessment field. +func (r *mutationResolver) DeleteDataProtectionImpactAssessment(ctx context.Context, input types.DeleteDataProtectionImpactAssessmentInput) (*types.DeleteDataProtectionImpactAssessmentPayload, error) { + if err := r.authorize(ctx, input.DataProtectionImpactAssessmentID, probo.ActionDataProtectionImpactAssessmentDelete); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, input.DataProtectionImpactAssessmentID.TenantID()) + + err := prb.DataProtectionImpactAssessments.Delete(ctx, input.DataProtectionImpactAssessmentID) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot delete data protection impact assessment", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.DeleteDataProtectionImpactAssessmentPayload{ + DeletedDataProtectionImpactAssessmentID: input.DataProtectionImpactAssessmentID, + }, nil +} + +// CreateTransferImpactAssessment is the resolver for the createTransferImpactAssessment field. +func (r *mutationResolver) CreateTransferImpactAssessment(ctx context.Context, input types.CreateTransferImpactAssessmentInput) (*types.CreateTransferImpactAssessmentPayload, error) { + if err := r.authorize(ctx, input.ProcessingActivityID, probo.ActionTransferImpactAssessmentCreate); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, input.ProcessingActivityID.TenantID()) + + req := probo.CreateTransferImpactAssessmentRequest{ + ProcessingActivityID: input.ProcessingActivityID, + DataSubjects: input.DataSubjects, + LegalMechanism: input.LegalMechanism, + Transfer: input.Transfer, + LocalLawRisk: input.LocalLawRisk, + SupplementaryMeasures: input.SupplementaryMeasures, + } + + tia, err := prb.TransferImpactAssessments.Create(ctx, &req) + if err != nil { + if errors.Is(err, coredata.ErrResourceAlreadyExists) { + return nil, gqlutils.Conflict(ctx, err) + } + + if validationErrors, ok := errors.AsType[validator.ValidationErrors](err); ok { + return nil, gqlutils.InvalidValidationErrors(ctx, validationErrors) + } + r.logger.ErrorCtx(ctx, "cannot create transfer impact assessment", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.CreateTransferImpactAssessmentPayload{ + TransferImpactAssessment: types.NewTransferImpactAssessment(tia), + }, nil +} + +// UpdateTransferImpactAssessment is the resolver for the updateTransferImpactAssessment field. +func (r *mutationResolver) UpdateTransferImpactAssessment(ctx context.Context, input types.UpdateTransferImpactAssessmentInput) (*types.UpdateTransferImpactAssessmentPayload, error) { + if err := r.authorize(ctx, input.ID, probo.ActionTransferImpactAssessmentUpdate); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, input.ID.TenantID()) + + req := probo.UpdateTransferImpactAssessmentRequest{ + ID: input.ID, + DataSubjects: gqlutils.UnwrapOmittable(input.DataSubjects), + LegalMechanism: gqlutils.UnwrapOmittable(input.LegalMechanism), + Transfer: gqlutils.UnwrapOmittable(input.Transfer), + LocalLawRisk: gqlutils.UnwrapOmittable(input.LocalLawRisk), + SupplementaryMeasures: gqlutils.UnwrapOmittable(input.SupplementaryMeasures), + } + + tia, err := prb.TransferImpactAssessments.Update(ctx, &req) + if err != nil { + if validationErrors, ok := errors.AsType[validator.ValidationErrors](err); ok { + return nil, gqlutils.InvalidValidationErrors(ctx, validationErrors) + } + r.logger.ErrorCtx(ctx, "cannot update transfer impact assessment", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.UpdateTransferImpactAssessmentPayload{ + TransferImpactAssessment: types.NewTransferImpactAssessment(tia), + }, nil +} + +// DeleteTransferImpactAssessment is the resolver for the deleteTransferImpactAssessment field. +func (r *mutationResolver) DeleteTransferImpactAssessment(ctx context.Context, input types.DeleteTransferImpactAssessmentInput) (*types.DeleteTransferImpactAssessmentPayload, error) { + if err := r.authorize(ctx, input.TransferImpactAssessmentID, probo.ActionTransferImpactAssessmentDelete); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, input.TransferImpactAssessmentID.TenantID()) + + err := prb.TransferImpactAssessments.Delete(ctx, input.TransferImpactAssessmentID) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot delete transfer impact assessment", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.DeleteTransferImpactAssessmentPayload{ + DeletedTransferImpactAssessmentID: input.TransferImpactAssessmentID, + }, nil +} + +// ExportDataProtectionImpactAssessmentsPDF is the resolver for the exportDataProtectionImpactAssessmentsPDF field. +func (r *mutationResolver) ExportDataProtectionImpactAssessmentsPDF(ctx context.Context, input types.ExportDataProtectionImpactAssessmentsPDFInput) (*types.ExportDataProtectionImpactAssessmentsPDFPayload, error) { + if err := r.authorize(ctx, input.OrganizationID, probo.ActionDataProtectionImpactAssessmentExport); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, input.OrganizationID.TenantID()) + + var snapshotIDPtr *gid.GID + if input.Filter != nil { + snapshotIDPtr = input.Filter.SnapshotID + } + dpiaFilter := coredata.NewDataProtectionImpactAssessmentFilter(&snapshotIDPtr) + + pdf, err := prb.DataProtectionImpactAssessments.ExportPDF(ctx, input.OrganizationID, dpiaFilter) + if err != nil { + if errors.Is(err, coredata.ErrResourceNotFound) { + return nil, gqlutils.NotFound(ctx, err) + } + r.logger.ErrorCtx(ctx, "cannot export data protection impact assessments PDF", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.ExportDataProtectionImpactAssessmentsPDFPayload{ + Data: fmt.Sprintf("data:application/pdf;base64,%s", base64.StdEncoding.EncodeToString(pdf)), + }, nil +} + +// ExportTransferImpactAssessmentsPDF is the resolver for the exportTransferImpactAssessmentsPDF field. +func (r *mutationResolver) ExportTransferImpactAssessmentsPDF(ctx context.Context, input types.ExportTransferImpactAssessmentsPDFInput) (*types.ExportTransferImpactAssessmentsPDFPayload, error) { + if err := r.authorize(ctx, input.OrganizationID, probo.ActionTransferImpactAssessmentExport); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, input.OrganizationID.TenantID()) + + var snapshotIDPtr *gid.GID + if input.Filter != nil { + snapshotIDPtr = input.Filter.SnapshotID + } + tiaFilter := coredata.NewTransferImpactAssessmentFilter(&snapshotIDPtr) + + pdf, err := prb.TransferImpactAssessments.ExportPDF(ctx, input.OrganizationID, tiaFilter) + if err != nil { + if errors.Is(err, coredata.ErrResourceNotFound) { + return nil, gqlutils.NotFound(ctx, err) + } + r.logger.ErrorCtx(ctx, "cannot export transfer impact assessments PDF", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.ExportTransferImpactAssessmentsPDFPayload{ + Data: fmt.Sprintf("data:application/pdf;base64,%s", base64.StdEncoding.EncodeToString(pdf)), + }, nil +} + +// DataProtectionImpactAssessments is the resolver for the dataProtectionImpactAssessments field. +func (r *organizationResolver) DataProtectionImpactAssessments(ctx context.Context, obj *types.Organization, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.DataProtectionImpactAssessmentOrderBy, filter *types.DataProtectionImpactAssessmentFilter) (*types.DataProtectionImpactAssessmentConnection, error) { + if err := r.authorize(ctx, obj.ID, probo.ActionDataProtectionImpactAssessmentList); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, obj.ID.TenantID()) + + pageOrderBy := page.OrderBy[coredata.DataProtectionImpactAssessmentOrderField]{ + Field: coredata.DataProtectionImpactAssessmentOrderFieldCreatedAt, + Direction: page.OrderDirectionDesc, + } + + if orderBy != nil { + pageOrderBy = page.OrderBy[coredata.DataProtectionImpactAssessmentOrderField]{ + Field: orderBy.Field, + Direction: orderBy.Direction, + } + } + + cursor := types.NewCursor(first, after, last, before, pageOrderBy) + + dpiaFilter := coredata.NewDataProtectionImpactAssessmentFilter(nil) + if filter != nil { + dpiaFilter = coredata.NewDataProtectionImpactAssessmentFilter(&filter.SnapshotID) + } + + page, err := prb.DataProtectionImpactAssessments.ListForOrganizationID(ctx, obj.ID, cursor, dpiaFilter) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot list organization data protection impact assessments", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return types.NewDataProtectionImpactAssessmentConnection(page, r, obj.ID, dpiaFilter), nil +} + +// TransferImpactAssessments is the resolver for the transferImpactAssessments field. +func (r *organizationResolver) TransferImpactAssessments(ctx context.Context, obj *types.Organization, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.TransferImpactAssessmentOrderBy, filter *types.TransferImpactAssessmentFilter) (*types.TransferImpactAssessmentConnection, error) { + if err := r.authorize(ctx, obj.ID, probo.ActionTransferImpactAssessmentList); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, obj.ID.TenantID()) + + pageOrderBy := page.OrderBy[coredata.TransferImpactAssessmentOrderField]{ + Field: coredata.TransferImpactAssessmentOrderFieldCreatedAt, + Direction: page.OrderDirectionDesc, + } + + if orderBy != nil { + pageOrderBy = page.OrderBy[coredata.TransferImpactAssessmentOrderField]{ + Field: orderBy.Field, + Direction: orderBy.Direction, + } + } + + cursor := types.NewCursor(first, after, last, before, pageOrderBy) + + tiaFilter := coredata.NewTransferImpactAssessmentFilter(nil) + if filter != nil { + tiaFilter = coredata.NewTransferImpactAssessmentFilter(&filter.SnapshotID) + } + + page, err := prb.TransferImpactAssessments.ListForOrganizationID(ctx, obj.ID, cursor, tiaFilter) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot list organization transfer impact assessments", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return types.NewTransferImpactAssessmentConnection(page, r, obj.ID, tiaFilter), nil +} + +// ProcessingActivity is the resolver for the processingActivity field. +func (r *transferImpactAssessmentResolver) ProcessingActivity(ctx context.Context, obj *types.TransferImpactAssessment) (*types.ProcessingActivity, error) { + if err := r.authorize(ctx, obj.ID, probo.ActionProcessingActivityGet); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, obj.ID.TenantID()) + + processingActivity, err := prb.ProcessingActivities.Get(ctx, obj.ProcessingActivity.ID) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot get processing activity", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return types.NewProcessingActivity(processingActivity), nil +} + +// Organization is the resolver for the organization field. +func (r *transferImpactAssessmentResolver) Organization(ctx context.Context, obj *types.TransferImpactAssessment) (*types.Organization, error) { + if err := r.authorize(ctx, obj.ID, probo.ActionOrganizationGet); err != nil { + return nil, err + } + + loaders := dataloader.FromContext(ctx) + + organization, err := loaders.Organization.Load(ctx, obj.Organization.ID) + if err != nil { + if errors.Is(err, coredata.ErrResourceNotFound) || errors.Is(err, dataloadgen.ErrNotFound) { + return nil, gqlutils.NotFound(ctx, err) + } + + r.logger.ErrorCtx(ctx, "cannot get organization", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return types.NewOrganization(organization), nil +} + +// Permission is the resolver for the permission field. +func (r *transferImpactAssessmentResolver) Permission(ctx context.Context, obj *types.TransferImpactAssessment, action string) (bool, error) { + return r.Resolver.Permission(ctx, obj, action) +} + +// TotalCount is the resolver for the totalCount field. +func (r *transferImpactAssessmentConnectionResolver) TotalCount(ctx context.Context, obj *types.TransferImpactAssessmentConnection) (int, error) { + if err := r.authorize(ctx, obj.ParentID, probo.ActionTransferImpactAssessmentList); err != nil { + return 0, err + } + + prb := r.ProboService(ctx, obj.ParentID.TenantID()) + + switch obj.Resolver.(type) { + case *organizationResolver: + count, err := prb.TransferImpactAssessments.CountForOrganizationID(ctx, obj.ParentID, obj.Filter) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot count organization transfer impact assessments", log.Error(err)) + return 0, gqlutils.Internal(ctx) + } + return count, nil + } + + r.logger.ErrorCtx(ctx, "unsupported resolver") + return 0, gqlutils.Internal(ctx) +} + +// DataProtectionImpactAssessment returns schema.DataProtectionImpactAssessmentResolver implementation. +func (r *Resolver) DataProtectionImpactAssessment() schema.DataProtectionImpactAssessmentResolver { + return &dataProtectionImpactAssessmentResolver{r} +} + +// DataProtectionImpactAssessmentConnection returns schema.DataProtectionImpactAssessmentConnectionResolver implementation. +func (r *Resolver) DataProtectionImpactAssessmentConnection() schema.DataProtectionImpactAssessmentConnectionResolver { + return &dataProtectionImpactAssessmentConnectionResolver{r} +} + +// TransferImpactAssessment returns schema.TransferImpactAssessmentResolver implementation. +func (r *Resolver) TransferImpactAssessment() schema.TransferImpactAssessmentResolver { + return &transferImpactAssessmentResolver{r} +} + +// TransferImpactAssessmentConnection returns schema.TransferImpactAssessmentConnectionResolver implementation. +func (r *Resolver) TransferImpactAssessmentConnection() schema.TransferImpactAssessmentConnectionResolver { + return &transferImpactAssessmentConnectionResolver{r} +} + +type dataProtectionImpactAssessmentResolver struct{ *Resolver } +type dataProtectionImpactAssessmentConnectionResolver struct{ *Resolver } +type transferImpactAssessmentResolver struct{ *Resolver } +type transferImpactAssessmentConnectionResolver struct{ *Resolver } diff --git a/pkg/server/api/console/v1/document.resolvers.go b/pkg/server/api/console/v1/document.resolvers.go new file mode 100644 index 000000000..c87905bf1 --- /dev/null +++ b/pkg/server/api/console/v1/document.resolvers.go @@ -0,0 +1,1901 @@ +package console_v1 + +// This file will be automatically regenerated based on the schema, any resolver +// implementations +// will be copied through when generating and any unknown code will be moved to the end. +// Code generated by github.com/99designs/gqlgen version v0.17.87 + +import ( + "context" + "encoding/base64" + "errors" + "fmt" + "net" + + "github.com/vikstrous/dataloadgen" + "go.gearno.de/kit/log" + "go.probo.inc/probo/pkg/coredata" + "go.probo.inc/probo/pkg/gid" + "go.probo.inc/probo/pkg/iam" + "go.probo.inc/probo/pkg/page" + "go.probo.inc/probo/pkg/probo" + "go.probo.inc/probo/pkg/server/api/authn" + "go.probo.inc/probo/pkg/server/api/console/v1/dataloader" + "go.probo.inc/probo/pkg/server/api/console/v1/schema" + "go.probo.inc/probo/pkg/server/api/console/v1/types" + "go.probo.inc/probo/pkg/server/gqlutils" + "go.probo.inc/probo/pkg/validator" +) + +// Organization is the resolver for the organization field. +func (r *documentResolver) Organization(ctx context.Context, obj *types.Document) (*types.Organization, error) { + if err := r.authorize(ctx, obj.ID, probo.ActionOrganizationGet); err != nil { + return nil, err + } + + loaders := dataloader.FromContext(ctx) + + organization, err := loaders.Organization.Load(ctx, obj.Organization.ID) + if err != nil { + if errors.Is(err, coredata.ErrResourceNotFound) || errors.Is(err, dataloadgen.ErrNotFound) { + return nil, gqlutils.NotFound(ctx, err) + } + + r.logger.ErrorCtx(ctx, "cannot get organization", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return types.NewOrganization(organization), nil +} + +// Versions is the resolver for the versions field. +func (r *documentResolver) Versions(ctx context.Context, obj *types.Document, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.DocumentVersionOrderBy, filter *types.DocumentVersionFilter) (*types.DocumentVersionConnection, error) { + if err := r.authorize(ctx, obj.ID, probo.ActionDocumentVersionList); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, obj.ID.TenantID()) + + pageOrderBy := page.OrderBy[coredata.DocumentVersionOrderField]{ + Field: coredata.DocumentVersionOrderFieldCreatedAt, + Direction: page.OrderDirectionDesc, + } + if orderBy != nil { + pageOrderBy = page.OrderBy[coredata.DocumentVersionOrderField]{ + Field: orderBy.Field, + Direction: orderBy.Direction, + } + } + + cursor := types.NewCursor(first, after, last, before, pageOrderBy) + + versionFilter := coredata.NewDocumentVersionFilter() + if filter != nil && len(filter.Statuses) > 0 { + versionFilter = versionFilter.WithStatuses(filter.Statuses...) + } + + page, err := prb.Documents.ListVersions(ctx, obj.ID, cursor, versionFilter) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot list document versions", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return types.NewDocumentVersionConnection(page, r, obj.ID), nil +} + +// Controls is the resolver for the controls field. +func (r *documentResolver) Controls(ctx context.Context, obj *types.Document, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.ControlOrderBy, filter *types.ControlFilter) (*types.ControlConnection, error) { + if err := r.authorize(ctx, obj.ID, probo.ActionControlList); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, obj.ID.TenantID()) + + pageOrderBy := page.OrderBy[coredata.ControlOrderField]{ + Field: coredata.ControlOrderFieldCreatedAt, + Direction: page.OrderDirectionDesc, + } + if orderBy != nil { + pageOrderBy = page.OrderBy[coredata.ControlOrderField]{ + Field: orderBy.Field, + Direction: orderBy.Direction, + } + } + + cursor := types.NewCursor(first, after, last, before, pageOrderBy) + + var controlFilter = coredata.NewControlFilter(nil) + if filter != nil { + controlFilter = coredata.NewControlFilter(filter.Query) + } + + page, err := prb.Controls.ListForDocumentID(ctx, obj.ID, cursor, controlFilter) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot list document controls", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return types.NewControlConnection(page, r, obj.ID, controlFilter), nil +} + +// DefaultApprovers is the resolver for the defaultApprovers field. +func (r *documentResolver) DefaultApprovers(ctx context.Context, obj *types.Document) ([]*types.Profile, error) { + if err := r.authorize(ctx, obj.ID, probo.ActionDocumentGet); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, obj.ID.TenantID()) + + profiles, err := prb.Documents.GetDefaultApprovers(ctx, obj.ID) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot get default approvers", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + result := make([]*types.Profile, len(profiles)) + for i, p := range profiles { + result[i] = types.NewProfile(p) + } + + return result, nil +} + +// Permission is the resolver for the permission field. +func (r *documentResolver) Permission(ctx context.Context, obj *types.Document, action string) (bool, error) { + return r.Resolver.Permission(ctx, obj, action) +} + +// TotalCount is the resolver for the totalCount field. +func (r *documentConnectionResolver) TotalCount(ctx context.Context, obj *types.DocumentConnection) (int, error) { + if err := r.authorize(ctx, obj.ParentID, probo.ActionDocumentList); err != nil { + return 0, err + } + + prb := r.ProboService(ctx, obj.ParentID.TenantID()) + + switch obj.Resolver.(type) { + case *controlResolver: + count, err := prb.Documents.CountForControlID(ctx, obj.ParentID, obj.Filters) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot count controls", log.Error(err)) + return 0, gqlutils.Internal(ctx) + } + return count, nil + case *organizationResolver: + count, err := prb.Documents.CountForOrganizationID(ctx, obj.ParentID, obj.Filters) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot count documents", log.Error(err)) + return 0, gqlutils.Internal(ctx) + } + return count, nil + case *riskResolver: + count, err := prb.Documents.CountForRiskID(ctx, obj.ParentID, obj.Filters) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot count risks", log.Error(err)) + return 0, gqlutils.Internal(ctx) + } + return count, nil + case *measureResolver: + count, err := prb.Documents.CountForMeasureID(ctx, obj.ParentID, obj.Filters) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot count documents", log.Error(err)) + return 0, gqlutils.Internal(ctx) + } + return count, nil + } + + r.logger.ErrorCtx(ctx, "unsupported resolver") + return 0, gqlutils.Internal(ctx) +} + +// Document is the resolver for the document field. +func (r *documentVersionResolver) Document(ctx context.Context, obj *types.DocumentVersion) (*types.Document, error) { + if err := r.authorize(ctx, obj.ID, probo.ActionDocumentGet); err != nil { + return nil, err + } + + loaders := dataloader.FromContext(ctx) + + document, err := loaders.Document.Load(ctx, obj.Document.ID) + if err != nil { + if errors.Is(err, coredata.ErrResourceNotFound) || errors.Is(err, dataloadgen.ErrNotFound) { + return nil, gqlutils.NotFound(ctx, err) + } + + r.logger.ErrorCtx(ctx, "cannot get document", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return types.NewDocument(document), nil +} + +// Approvers is the resolver for the approvers field. +func (r *documentVersionResolver) Approvers(ctx context.Context, obj *types.DocumentVersion, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.ProfileOrderBy) (*types.ProfileConnection, error) { + if err := r.authorize(ctx, obj.ID, iam.ActionMembershipProfileList); err != nil { + return nil, err + } + + if gqlutils.OnlyTotalCountSelected(ctx) { + return &types.ProfileConnection{ + Resolver: r, + ParentID: obj.ID, + }, nil + } + + prb := r.ProboService(ctx, obj.ID.TenantID()) + + pageOrderBy := page.OrderBy[coredata.MembershipProfileOrderField]{ + Field: coredata.MembershipProfileOrderFieldCreatedAt, + Direction: page.OrderDirectionDesc, + } + if orderBy != nil { + pageOrderBy.Field = coredata.MembershipProfileOrderField(orderBy.Field) + pageOrderBy.Direction = page.OrderDirection(orderBy.Direction) + } + + c := types.NewCursor(first, after, last, before, pageOrderBy) + + p, err := prb.Documents.ListVersionApprovers(ctx, obj.ID, c) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot list document version approvers", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return types.NewProfileConnection(p, r, obj.ID, nil), nil +} + +// Signatures is the resolver for the signatures field. +func (r *documentVersionResolver) Signatures(ctx context.Context, obj *types.DocumentVersion, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.DocumentVersionSignatureOrder, filter *types.DocumentVersionSignatureFilter) (*types.DocumentVersionSignatureConnection, error) { + if err := r.authorize(ctx, obj.ID, probo.ActionDocumentVersionSignatureList); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, obj.ID.TenantID()) + + pageOrderBy := page.OrderBy[coredata.DocumentVersionSignatureOrderField]{ + Field: coredata.DocumentVersionSignatureOrderFieldCreatedAt, + Direction: page.OrderDirectionDesc, + } + if orderBy != nil { + pageOrderBy = page.OrderBy[coredata.DocumentVersionSignatureOrderField]{ + Field: orderBy.Field, + Direction: orderBy.Direction, + } + } + + var signatureStates []coredata.DocumentVersionSignatureState + var activeContract *bool + if filter != nil { + if filter.States != nil { + signatureStates = filter.States + } + if filter.ActiveContract != nil { + activeContract = filter.ActiveContract + } + } + signatureFilter := coredata.NewDocumentVersionSignatureFilter(signatureStates, activeContract) + + cursor := types.NewCursor(first, after, last, before, pageOrderBy) + + page, err := prb.Documents.ListSignatures(ctx, obj.ID, cursor, signatureFilter) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot list document version signatures", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return types.NewDocumentVersionSignatureConnection(page, r, obj.ID, signatureFilter), nil +} + +// ApprovalQuorums is the resolver for the approvalQuorums field. +func (r *documentVersionResolver) ApprovalQuorums(ctx context.Context, obj *types.DocumentVersion, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.DocumentVersionApprovalQuorumOrder) (*types.DocumentVersionApprovalQuorumConnection, error) { + if err := r.authorize(ctx, obj.ID, probo.ActionDocumentVersionApprovalList); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, obj.ID.TenantID()) + + pageOrderBy := page.OrderBy[coredata.DocumentVersionApprovalQuorumOrderField]{ + Field: coredata.DocumentVersionApprovalQuorumOrderFieldCreatedAt, + Direction: page.OrderDirectionDesc, + } + if orderBy != nil { + pageOrderBy = page.OrderBy[coredata.DocumentVersionApprovalQuorumOrderField]{ + Field: orderBy.Field, + Direction: orderBy.Direction, + } + } + + cursor := types.NewCursor(first, after, last, before, pageOrderBy) + + p, err := prb.DocumentApprovals.ListQuorums(ctx, obj.ID, cursor) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot list approval quorums", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return types.NewDocumentVersionApprovalQuorumConnection(p, r, obj.ID), nil +} + +// Signed is the resolver for the signed field. +func (r *documentVersionResolver) Signed(ctx context.Context, obj *types.DocumentVersion) (bool, error) { + if err := r.authorize(ctx, obj.ID, probo.ActionDocumentVersionGet); err != nil { + return false, err + } + + identity := authn.IdentityFromContext(ctx) + + prb := r.ProboService(ctx, obj.ID.TenantID()) + + signed, err := prb.Documents.IsVersionSignedByUserEmail(ctx, obj.ID, identity.EmailAddress) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot check if document version is signed", log.Error(err)) + return false, gqlutils.Internal(ctx) + } + + return signed, nil +} + +// Permission is the resolver for the permission field. +func (r *documentVersionResolver) Permission(ctx context.Context, obj *types.DocumentVersion, action string) (bool, error) { + return r.Resolver.Permission(ctx, obj, action) +} + +// Quorum is the resolver for the quorum field. +func (r *documentVersionApprovalDecisionResolver) Quorum(ctx context.Context, obj *types.DocumentVersionApprovalDecision) (*types.DocumentVersionApprovalQuorum, error) { + if err := r.authorize(ctx, obj.ID, probo.ActionDocumentVersionApprovalList); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, obj.ID.TenantID()) + + quorum, err := prb.DocumentApprovals.GetQuorum(ctx, obj.Quorum.ID) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot get approval quorum", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return types.NewDocumentVersionApprovalQuorum(quorum), nil +} + +// DocumentVersion is the resolver for the documentVersion field. +func (r *documentVersionApprovalDecisionResolver) DocumentVersion(ctx context.Context, obj *types.DocumentVersionApprovalDecision) (*types.DocumentVersion, error) { + if err := r.authorize(ctx, obj.ID, probo.ActionDocumentVersionGet); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, obj.ID.TenantID()) + + quorum, err := prb.DocumentApprovals.GetQuorum(ctx, obj.Quorum.ID) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot get approval quorum", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + documentVersion, err := prb.Documents.GetVersion(ctx, quorum.VersionID) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot get document version", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return types.NewDocumentVersion(documentVersion), nil +} + +// Approver is the resolver for the approver field. +func (r *documentVersionApprovalDecisionResolver) Approver(ctx context.Context, obj *types.DocumentVersionApprovalDecision) (*types.Profile, error) { + if err := r.authorize(ctx, obj.ID, iam.ActionMembershipProfileGet); err != nil { + return nil, err + } + + profile, err := r.iam.OrganizationService.GetProfile(ctx, obj.Approver.ID) + if err != nil { + if errors.Is(err, coredata.ErrResourceNotFound) { + return nil, gqlutils.NotFound(ctx, err) + } + + r.logger.ErrorCtx(ctx, "cannot get approver profile", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return types.NewProfile(profile), nil +} + +// Permission is the resolver for the permission field. +func (r *documentVersionApprovalDecisionResolver) Permission(ctx context.Context, obj *types.DocumentVersionApprovalDecision, action string) (bool, error) { + // Approve and reject actions are only allowed for the viewer's own decision. + if action == probo.ActionDocumentVersionApprove || action == probo.ActionDocumentVersionReject { + identity := authn.IdentityFromContext(ctx) + + profile, err := r.iam.OrganizationService.GetProfile(ctx, obj.Approver.ID) + if err != nil { + if errors.Is(err, coredata.ErrResourceNotFound) { + return false, nil + } + + return false, gqlutils.Internal(ctx) + } + + if profile.IdentityID != identity.ID { + return false, nil + } + } + + return r.Resolver.Permission(ctx, obj, action) +} + +// TotalCount is the resolver for the totalCount field. +func (r *documentVersionApprovalDecisionConnectionResolver) TotalCount(ctx context.Context, obj *types.DocumentVersionApprovalDecisionConnection) (int, error) { + if obj.ParentID.EntityType() != coredata.DocumentVersionApprovalQuorumEntityType { + return 0, nil + } + + if err := r.authorize(ctx, obj.ParentID, probo.ActionDocumentVersionApprovalList); err != nil { + return 0, err + } + + prb := r.ProboService(ctx, obj.ParentID.TenantID()) + + filter := coredata.NewDocumentVersionApprovalDecisionFilter(nil) + if obj.Filters != nil { + filter = obj.Filters + } + + count, err := prb.DocumentApprovals.CountDecisions(ctx, obj.ParentID, filter) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot count approval decisions", log.Error(err)) + return 0, gqlutils.Internal(ctx) + } + + return count, nil +} + +// DocumentVersion is the resolver for the documentVersion field. +func (r *documentVersionApprovalQuorumResolver) DocumentVersion(ctx context.Context, obj *types.DocumentVersionApprovalQuorum) (*types.DocumentVersion, error) { + if err := r.authorize(ctx, obj.ID, probo.ActionDocumentVersionGet); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, obj.ID.TenantID()) + + documentVersion, err := prb.Documents.GetVersion(ctx, obj.DocumentVersion.ID) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot get document version", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return types.NewDocumentVersion(documentVersion), nil +} + +// Decisions is the resolver for the decisions field. +func (r *documentVersionApprovalQuorumResolver) Decisions(ctx context.Context, obj *types.DocumentVersionApprovalQuorum, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.DocumentVersionApprovalDecisionOrder, filter *types.DocumentVersionApprovalDecisionFilter) (*types.DocumentVersionApprovalDecisionConnection, error) { + if err := r.authorize(ctx, obj.ID, probo.ActionDocumentVersionApprovalList); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, obj.ID.TenantID()) + + pageOrderBy := page.OrderBy[coredata.DocumentVersionApprovalDecisionOrderField]{ + Field: coredata.DocumentVersionApprovalDecisionOrderFieldCreatedAt, + Direction: page.OrderDirectionDesc, + } + if orderBy != nil { + pageOrderBy = page.OrderBy[coredata.DocumentVersionApprovalDecisionOrderField]{ + Field: orderBy.Field, + Direction: orderBy.Direction, + } + } + + var approvalStates []coredata.DocumentVersionApprovalDecisionState + if filter != nil && filter.States != nil { + approvalStates = filter.States + } + approvalFilter := coredata.NewDocumentVersionApprovalDecisionFilter(approvalStates) + + cursor := types.NewCursor(first, after, last, before, pageOrderBy) + + p, err := prb.DocumentApprovals.ListDecisions(ctx, obj.ID, cursor, approvalFilter) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot list approval decisions", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return types.NewDocumentVersionApprovalDecisionConnection(p, r, obj.ID, approvalFilter), nil +} + +// Permission is the resolver for the permission field. +func (r *documentVersionApprovalQuorumResolver) Permission(ctx context.Context, obj *types.DocumentVersionApprovalQuorum, action string) (bool, error) { + return r.Resolver.Permission(ctx, obj, action) +} + +// TotalCount is the resolver for the totalCount field. +func (r *documentVersionApprovalQuorumConnectionResolver) TotalCount(ctx context.Context, obj *types.DocumentVersionApprovalQuorumConnection) (int, error) { + if err := r.authorize(ctx, obj.ParentID, probo.ActionDocumentVersionApprovalList); err != nil { + return 0, err + } + + prb := r.ProboService(ctx, obj.ParentID.TenantID()) + + count, err := prb.DocumentApprovals.CountQuorums(ctx, obj.ParentID) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot count approval quorums", log.Error(err)) + return 0, gqlutils.Internal(ctx) + } + + return count, nil +} + +// TotalCount is the resolver for the totalCount field. +func (r *documentVersionConnectionResolver) TotalCount(ctx context.Context, obj *types.DocumentVersionConnection) (int, error) { + if err := r.authorize(ctx, obj.ParentID, probo.ActionDocumentVersionList); err != nil { + return 0, err + } + + prb := r.ProboService(ctx, obj.ParentID.TenantID()) + + switch obj.Resolver.(type) { + case *documentResolver: + filter := &coredata.DocumentVersionFilter{} + if obj.Filters != nil { + filter = obj.Filters + } + count, err := prb.Documents.CountVersionsForDocumentID(ctx, obj.ParentID, filter) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot count document versions", log.Error(err)) + return 0, gqlutils.Internal(ctx) + } + return count, nil + } + + r.logger.ErrorCtx(ctx, "unsupported resolver") + return 0, gqlutils.Internal(ctx) +} + +// DocumentVersion is the resolver for the documentVersion field. +func (r *documentVersionSignatureResolver) DocumentVersion(ctx context.Context, obj *types.DocumentVersionSignature) (*types.DocumentVersion, error) { + if err := r.authorize(ctx, obj.ID, probo.ActionDocumentVersionGet); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, obj.ID.TenantID()) + + documentVersion, err := prb.Documents.GetVersion(ctx, obj.DocumentVersion.ID) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot get document version", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return types.NewDocumentVersion(documentVersion), nil +} + +// SignedBy is the resolver for the signedBy field. +func (r *documentVersionSignatureResolver) SignedBy(ctx context.Context, obj *types.DocumentVersionSignature) (*types.Profile, error) { + if err := r.authorize(ctx, obj.ID, iam.ActionMembershipProfileGet); err != nil { + return nil, err + } + + loaders := dataloader.FromContext(ctx) + + signatory, err := loaders.Profile.Load(ctx, obj.SignedBy.ID) + if err != nil { + if errors.Is(err, coredata.ErrResourceNotFound) || errors.Is(err, dataloadgen.ErrNotFound) { + return nil, gqlutils.NotFound(ctx, err) + } + + r.logger.ErrorCtx(ctx, "cannot get people", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return types.NewProfile(signatory), nil +} + +// Permission is the resolver for the permission field. +func (r *documentVersionSignatureResolver) Permission(ctx context.Context, obj *types.DocumentVersionSignature, action string) (bool, error) { + return r.Resolver.Permission(ctx, obj, action) +} + +// TotalCount is the resolver for the totalCount field. +func (r *documentVersionSignatureConnectionResolver) TotalCount(ctx context.Context, obj *types.DocumentVersionSignatureConnection) (int, error) { + if err := r.authorize(ctx, obj.ParentID, probo.ActionDocumentVersionSignatureList); err != nil { + return 0, err + } + + prb := r.ProboService(ctx, obj.ParentID.TenantID()) + + switch obj.Resolver.(type) { + case *documentVersionResolver: + filter := &coredata.DocumentVersionSignatureFilter{} + if obj.Filters != nil { + filter = obj.Filters + } + count, err := prb.Documents.CountSignaturesForVersionID(ctx, obj.ParentID, filter) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot count signatures", log.Error(err)) + return 0, gqlutils.Internal(ctx) + } + return count, nil + } + + r.logger.ErrorCtx(ctx, "unsupported resolver") + return 0, gqlutils.Internal(ctx) +} + +// Signed is the resolver for the signed field. +func (r *employeeDocumentResolver) Signed(ctx context.Context, obj *types.EmployeeDocument) (*bool, error) { + if err := r.authorize(ctx, obj.ID, probo.ActionEmployeeDocumentGet); err != nil { + return nil, err + } + + identity := authn.IdentityFromContext(ctx) + + prb := r.ProboService(ctx, obj.ID.TenantID()) + + signed, err := prb.Documents.IsSigned(ctx, obj.ID, identity.EmailAddress) + if err != nil { + if errors.Is(err, coredata.ErrResourceNotFound) { + return nil, nil + } + r.logger.ErrorCtx(ctx, "cannot check if document is signed", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &signed, nil +} + +// ApprovalState is the resolver for the approvalState field. +func (r *employeeDocumentResolver) ApprovalState(ctx context.Context, obj *types.EmployeeDocument) (*coredata.DocumentVersionApprovalDecisionState, error) { + if err := r.authorize(ctx, obj.ID, probo.ActionEmployeeDocumentGet); err != nil { + return nil, err + } + + identity := authn.IdentityFromContext(ctx) + + prb := r.ProboService(ctx, obj.ID.TenantID()) + + state, err := prb.Documents.GetViewerApprovalState(ctx, obj.ID, identity.ID) + if err != nil { + if errors.Is(err, coredata.ErrResourceNotFound) { + return nil, nil + } + r.logger.ErrorCtx(ctx, "cannot get viewer approval state", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &state, nil +} + +// Versions is the resolver for the versions field. +func (r *employeeDocumentResolver) Versions(ctx context.Context, obj *types.EmployeeDocument, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.DocumentVersionOrderBy) (*types.EmployeeDocumentVersionConnection, error) { + if err := r.authorize(ctx, obj.ID, probo.ActionEmployeeDocumentGet); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, obj.ID.TenantID()) + + pageOrderBy := page.OrderBy[coredata.DocumentVersionOrderField]{ + Field: coredata.DocumentVersionOrderFieldCreatedAt, + Direction: page.OrderDirectionDesc, + } + if orderBy != nil { + pageOrderBy = page.OrderBy[coredata.DocumentVersionOrderField]{ + Field: orderBy.Field, + Direction: orderBy.Direction, + } + } + + cursor := types.NewCursor(first, after, last, before, pageOrderBy) + + identity := authn.IdentityFromContext(ctx) + + var filterMode coredata.EmployeeFilterMode + switch obj.FilterMode { + case types.EmployeeDocumentFilterModeSignature: + filterMode = coredata.EmployeeFilterModeSignature + case types.EmployeeDocumentFilterModeApproval: + filterMode = coredata.EmployeeFilterModeApproval + default: + r.logger.ErrorCtx(ctx, "unsupported employee document filter mode", log.String("filter_mode", string(obj.FilterMode))) + return nil, gqlutils.Internal(ctx) + } + + versionFilter := coredata.NewDocumentVersionFilter(). + WithEmployeeIdentityID(&identity.ID, filterMode) + + versionsPage, err := prb.Documents.ListVersions(ctx, obj.ID, cursor, versionFilter) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot list employee document versions", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + employeeVersions := make([]*types.EmployeeDocumentVersion, len(versionsPage.Data)) + for i, v := range versionsPage.Data { + employeeVersions[i] = &types.EmployeeDocumentVersion{ + ID: v.ID, + DocumentID: obj.ID, + OrganizationID: v.OrganizationID, + Major: v.Major, + Minor: v.Minor, + Status: v.Status, + Classification: v.Classification, + DocumentType: v.DocumentType, + PublishedAt: v.PublishedAt, + CreatedAt: v.CreatedAt, + UpdatedAt: v.UpdatedAt, + } + } + + p := page.NewPage(employeeVersions, versionsPage.Cursor) + + return types.NewEmployeeDocumentVersionConnection(p), nil +} + +// Signed is the resolver for the signed field. +func (r *employeeDocumentVersionResolver) Signed(ctx context.Context, obj *types.EmployeeDocumentVersion) (bool, error) { + if err := r.authorize(ctx, obj.DocumentID, probo.ActionEmployeeDocumentGet); err != nil { + return false, err + } + + identity := authn.IdentityFromContext(ctx) + + prb := r.ProboService(ctx, obj.ID.TenantID()) + + signed, err := prb.Documents.IsVersionSignedByUserEmail(ctx, obj.ID, identity.EmailAddress) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot check if version is signed", log.Error(err)) + return false, gqlutils.Internal(ctx) + } + + return signed, nil +} + +// ApprovalDecision is the resolver for the approvalDecision field. +func (r *employeeDocumentVersionResolver) ApprovalDecision(ctx context.Context, obj *types.EmployeeDocumentVersion) (*types.DocumentVersionApprovalDecision, error) { + if err := r.authorize(ctx, obj.DocumentID, probo.ActionEmployeeDocumentGet); err != nil { + return nil, err + } + + identity := authn.IdentityFromContext(ctx) + prb := r.ProboService(ctx, obj.ID.TenantID()) + + decision, err := prb.DocumentApprovals.GetViewerDecision(ctx, obj.ID, identity.ID) + if err != nil { + if errors.Is(err, coredata.ErrResourceNotFound) { + return nil, nil + } + + r.logger.ErrorCtx(ctx, "cannot get viewer approval decision", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return types.NewDocumentVersionApprovalDecision(decision), nil +} + +// CreateDocument is the resolver for the createDocument field. +func (r *mutationResolver) CreateDocument(ctx context.Context, input types.CreateDocumentInput) (*types.CreateDocumentPayload, error) { + if err := r.authorize(ctx, input.OrganizationID, probo.ActionDocumentCreate); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, input.OrganizationID.TenantID()) + + var content string + if input.Content != nil { + content = *input.Content + } + + document, documentVersion, err := prb.Documents.Create( + ctx, + probo.CreateDocumentRequest{ + OrganizationID: input.OrganizationID, + Title: input.Title, + Content: content, + Classification: input.Classification, + DocumentType: input.DocumentType, + TrustCenterVisibility: input.TrustCenterVisibility, + DefaultApproverIDs: input.DefaultApproverIds, + }, + ) + if err != nil { + if errors.Is(err, coredata.ErrResourceAlreadyExists) { + return nil, gqlutils.Conflict(ctx, err) + } + + if validationErrors, ok := errors.AsType[validator.ValidationErrors](err); ok { + return nil, gqlutils.InvalidValidationErrors(ctx, validationErrors) + } + r.logger.ErrorCtx(ctx, "cannot create document", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.CreateDocumentPayload{ + DocumentEdge: types.NewDocumentEdge(document, coredata.DocumentOrderFieldTitle), + DocumentVersionEdge: types.NewDocumentVersionEdge(documentVersion, coredata.DocumentVersionOrderFieldCreatedAt), + }, nil +} + +// UpdateDocument is the resolver for the updateDocument field. +func (r *mutationResolver) UpdateDocument(ctx context.Context, input types.UpdateDocumentInput) (*types.UpdateDocumentPayload, error) { + if err := r.authorize(ctx, input.ID, probo.ActionDocumentUpdate); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, input.ID.TenantID()) + + var defaultApproverIDs *[]gid.GID + if input.DefaultApproverIds != nil { + defaultApproverIDs = &input.DefaultApproverIds + } + + document, documentVersion, draftCreated, err := prb.Documents.Update( + ctx, + probo.UpdateDocumentRequest{ + DocumentID: input.ID, + Title: input.Title, + Content: input.Content, + Classification: input.Classification, + DocumentType: input.DocumentType, + TrustCenterVisibility: input.TrustCenterVisibility, + DefaultApproverIDs: defaultApproverIDs, + }, + ) + + if err != nil { + if errors.Is(err, coredata.ErrResourceNotFound) { + return nil, gqlutils.NotFound(ctx, err) + } + if errArchived, ok := errors.AsType[*probo.ErrDocumentArchived](err); ok { + return nil, gqlutils.Conflict(ctx, errArchived) + } + if validationErrors, ok := errors.AsType[validator.ValidationErrors](err); ok { + return nil, gqlutils.InvalidValidationErrors(ctx, validationErrors) + } + r.logger.ErrorCtx(ctx, "cannot update document", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + payload := &types.UpdateDocumentPayload{ + Document: types.NewDocument(document), + } + + if documentVersion != nil { + payload.DocumentVersion = types.NewDocumentVersion(documentVersion) + } + + if draftCreated { + payload.DocumentVersionEdge = types.NewDocumentVersionEdge( + documentVersion, + coredata.DocumentVersionOrderFieldCreatedAt, + ) + } + + return payload, nil +} + +// DeleteDocumentDraft is the resolver for the deleteDocumentDraft field. +func (r *mutationResolver) DeleteDocumentDraft(ctx context.Context, input types.DeleteDocumentDraftInput) (*types.DeleteDocumentDraftPayload, error) { + if err := r.authorize(ctx, input.DocumentID, probo.ActionDocumentDeleteDraft); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, input.DocumentID.TenantID()) + + document, err := prb.Documents.DeleteDraft(ctx, input.DocumentID) + if err != nil { + if errors.Is(err, coredata.ErrResourceNotFound) { + return nil, gqlutils.NotFound(ctx, err) + } + if errNotDeletable, ok := errors.AsType[*probo.ErrDocumentDraftNotDeletable](err); ok { + return nil, gqlutils.Conflict(ctx, errNotDeletable) + } + if errArchived, ok := errors.AsType[*probo.ErrDocumentArchived](err); ok { + return nil, gqlutils.Conflict(ctx, errArchived) + } + r.logger.ErrorCtx(ctx, "cannot delete document draft", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.DeleteDocumentDraftPayload{ + Document: types.NewDocument(document), + }, nil +} + +// ArchiveDocument is the resolver for the archiveDocument field. +func (r *mutationResolver) ArchiveDocument(ctx context.Context, input types.ArchiveDocumentInput) (*types.ArchiveDocumentPayload, error) { + if err := r.authorize(ctx, input.DocumentID, probo.ActionDocumentArchive); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, input.DocumentID.TenantID()) + + document, err := prb.Documents.Archive(ctx, input.DocumentID) + if err != nil { + if errArchived, ok := errors.AsType[*probo.ErrDocumentArchived](err); ok { + return nil, gqlutils.Conflict(ctx, errArchived) + } + r.logger.ErrorCtx(ctx, "cannot archive document", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.ArchiveDocumentPayload{ + Document: types.NewDocument(document), + }, nil +} + +// UnarchiveDocument is the resolver for the unarchiveDocument field. +func (r *mutationResolver) UnarchiveDocument(ctx context.Context, input types.UnarchiveDocumentInput) (*types.UnarchiveDocumentPayload, error) { + if err := r.authorize(ctx, input.DocumentID, probo.ActionDocumentUnarchive); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, input.DocumentID.TenantID()) + + document, err := prb.Documents.Unarchive(ctx, input.DocumentID) + if err != nil { + if errNotArchived, ok := errors.AsType[*probo.ErrDocumentNotArchived](err); ok { + return nil, gqlutils.Conflict(ctx, errNotArchived) + } + r.logger.ErrorCtx(ctx, "cannot unarchive document", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.UnarchiveDocumentPayload{ + Document: types.NewDocument(document), + }, nil +} + +// DeleteDocument is the resolver for the deleteDocument field. +func (r *mutationResolver) DeleteDocument(ctx context.Context, input types.DeleteDocumentInput) (*types.DeleteDocumentPayload, error) { + if err := r.authorize(ctx, input.DocumentID, probo.ActionDocumentDelete); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, input.DocumentID.TenantID()) + + err := prb.Documents.SoftDelete(ctx, input.DocumentID) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot soft delete document", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.DeleteDocumentPayload{ + DeletedDocumentID: input.DocumentID, + }, nil +} + +// PublishMajorDocumentVersion is the resolver for the publishMajorDocumentVersion field. +func (r *mutationResolver) PublishMajorDocumentVersion(ctx context.Context, input types.PublishMajorDocumentVersionInput) (*types.PublishDocumentVersionPayload, error) { + if err := r.authorize(ctx, input.DocumentID, probo.ActionDocumentVersionPublish); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, input.DocumentID.TenantID()) + + document, documentVersion, err := prb.Documents.PublishMajorVersion( + ctx, + input.DocumentID, + authn.IdentityFromContext(ctx).ID, + input.Changelog, + ) + if err != nil { + if errArchived, ok := errors.AsType[*probo.ErrDocumentArchived](err); ok { + return nil, gqlutils.Conflict(ctx, errArchived) + } + + if errNotDraft, ok := errors.AsType[*probo.ErrDocumentVersionNotDraft](err); ok { + return nil, gqlutils.Invalid(ctx, errNotDraft) + } + + if errPending, ok := errors.AsType[*probo.ErrDocumentVersionPendingApproval](err); ok { + return nil, gqlutils.Conflict(ctx, errPending) + } + + r.logger.ErrorCtx(ctx, "cannot publish major document version", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.PublishDocumentVersionPayload{ + Document: types.NewDocument(document), + DocumentVersion: types.NewDocumentVersion(documentVersion), + }, nil +} + +// PublishMinorDocumentVersion is the resolver for the publishMinorDocumentVersion field. +func (r *mutationResolver) PublishMinorDocumentVersion(ctx context.Context, input types.PublishMinorDocumentVersionInput) (*types.PublishDocumentVersionPayload, error) { + if err := r.authorize(ctx, input.DocumentID, probo.ActionDocumentVersionPublish); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, input.DocumentID.TenantID()) + + document, documentVersion, err := prb.Documents.PublishMinorVersion( + ctx, + input.DocumentID, + authn.IdentityFromContext(ctx).ID, + input.Changelog, + ) + if err != nil { + if errArchived, ok := errors.AsType[*probo.ErrDocumentArchived](err); ok { + return nil, gqlutils.Conflict(ctx, errArchived) + } + + if errNotDraft, ok := errors.AsType[*probo.ErrDocumentVersionNotDraft](err); ok { + return nil, gqlutils.Invalid(ctx, errNotDraft) + } + + if errPending, ok := errors.AsType[*probo.ErrDocumentVersionPendingApproval](err); ok { + return nil, gqlutils.Conflict(ctx, errPending) + } + + r.logger.ErrorCtx(ctx, "cannot publish minor document version", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.PublishDocumentVersionPayload{ + Document: types.NewDocument(document), + DocumentVersion: types.NewDocumentVersion(documentVersion), + }, nil +} + +// BulkPublishMajorDocumentVersions is the resolver for the bulkPublishMajorDocumentVersions field. +func (r *mutationResolver) BulkPublishMajorDocumentVersions(ctx context.Context, input types.BulkPublishDocumentVersionsInput) (*types.BulkPublishDocumentVersionsPayload, error) { + if len(input.DocumentIds) == 0 { + return &types.BulkPublishDocumentVersionsPayload{ + DocumentVersions: []*types.DocumentVersion{}, + Documents: []*types.Document{}, + }, nil + } + + for _, documentID := range input.DocumentIds { + if err := r.authorize(ctx, documentID, probo.ActionDocumentVersionPublish); err != nil { + return nil, err + } + } + + prb := r.ProboService(ctx, input.DocumentIds[0].TenantID()) + + versions, documents, err := prb.DocumentApprovals.BulkPublishMajorVersions(ctx, probo.BulkPublishVersionsRequest{ + DocumentIDs: input.DocumentIds, + Changelog: input.Changelog, + }) + if err != nil { + if errArchived, ok := errors.AsType[*probo.ErrDocumentArchived](err); ok { + return nil, gqlutils.Conflict(ctx, errArchived) + } + + if errNotDraft, ok := errors.AsType[*probo.ErrDocumentVersionNotDraft](err); ok { + return nil, gqlutils.Invalid(ctx, errNotDraft) + } + + r.logger.ErrorCtx(ctx, "cannot bulk publish major document versions", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + typesVersions := make([]*types.DocumentVersion, len(versions)) + for i, v := range versions { + typesVersions[i] = types.NewDocumentVersion(v) + } + + typesDocuments := make([]*types.Document, len(documents)) + for i, d := range documents { + typesDocuments[i] = types.NewDocument(d) + } + + return &types.BulkPublishDocumentVersionsPayload{ + DocumentVersions: typesVersions, + Documents: typesDocuments, + }, nil +} + +// BulkPublishMinorDocumentVersions is the resolver for the bulkPublishMinorDocumentVersions field. +func (r *mutationResolver) BulkPublishMinorDocumentVersions(ctx context.Context, input types.BulkPublishDocumentVersionsInput) (*types.BulkPublishDocumentVersionsPayload, error) { + if len(input.DocumentIds) == 0 { + return &types.BulkPublishDocumentVersionsPayload{ + DocumentVersions: []*types.DocumentVersion{}, + Documents: []*types.Document{}, + }, nil + } + + for _, documentID := range input.DocumentIds { + if err := r.authorize(ctx, documentID, probo.ActionDocumentVersionPublish); err != nil { + return nil, err + } + } + + prb := r.ProboService(ctx, input.DocumentIds[0].TenantID()) + + versions, documents, err := prb.Documents.BulkPublishMinorVersions(ctx, probo.BulkPublishVersionsRequest{ + DocumentIDs: input.DocumentIds, + Changelog: input.Changelog, + }) + if err != nil { + if errArchived, ok := errors.AsType[*probo.ErrDocumentArchived](err); ok { + return nil, gqlutils.Conflict(ctx, errArchived) + } + + if errNotDraft, ok := errors.AsType[*probo.ErrDocumentVersionNotDraft](err); ok { + return nil, gqlutils.Invalid(ctx, errNotDraft) + } + + r.logger.ErrorCtx(ctx, "cannot bulk publish minor document versions", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + typesVersions := make([]*types.DocumentVersion, len(versions)) + for i, v := range versions { + typesVersions[i] = types.NewDocumentVersion(v) + } + + typesDocuments := make([]*types.Document, len(documents)) + for i, d := range documents { + typesDocuments[i] = types.NewDocument(d) + } + + return &types.BulkPublishDocumentVersionsPayload{ + DocumentVersions: typesVersions, + Documents: typesDocuments, + }, nil +} + +// RequestDocumentVersionApproval is the resolver for the requestDocumentVersionApproval field. +func (r *mutationResolver) RequestDocumentVersionApproval(ctx context.Context, input types.RequestDocumentVersionApprovalInput) (*types.RequestDocumentVersionApprovalPayload, error) { + if err := r.authorize(ctx, input.DocumentID, probo.ActionDocumentVersionRequestApproval); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, input.DocumentID.TenantID()) + + quorum, err := prb.DocumentApprovals.RequestApproval(ctx, probo.RequestApprovalRequest{ + DocumentID: input.DocumentID, + ApproverIDs: input.ApproverIds, + Changelog: input.Changelog, + }) + if err != nil { + if errArchived, ok := errors.AsType[*probo.ErrDocumentArchived](err); ok { + return nil, gqlutils.Conflict(ctx, errArchived) + } + + if errNotDraft, ok := errors.AsType[*probo.ErrDocumentVersionNotDraft](err); ok { + return nil, gqlutils.Conflict(ctx, errNotDraft) + } + + if validationErrors, ok := errors.AsType[validator.ValidationErrors](err); ok { + return nil, gqlutils.InvalidValidationErrors(ctx, validationErrors) + } + + r.logger.ErrorCtx(ctx, "cannot request document version approval", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.RequestDocumentVersionApprovalPayload{ + ApprovalQuorum: types.NewDocumentVersionApprovalQuorum(quorum), + }, nil +} + +// VoidDocumentVersionApproval is the resolver for the voidDocumentVersionApproval field. +func (r *mutationResolver) VoidDocumentVersionApproval(ctx context.Context, input types.VoidDocumentVersionApprovalInput) (*types.VoidDocumentVersionApprovalPayload, error) { + if err := r.authorize(ctx, input.DocumentVersionID, probo.ActionDocumentVersionVoidApproval); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, input.DocumentVersionID.TenantID()) + + quorum, documentVersion, err := prb.DocumentApprovals.VoidApproval(ctx, input.DocumentVersionID) + if err != nil { + if errArchived, ok := errors.AsType[*probo.ErrDocumentArchived](err); ok { + return nil, gqlutils.Conflict(ctx, errArchived) + } + + if errNotPending, ok := errors.AsType[*probo.ErrDocumentVersionNotPendingApproval](err); ok { + return nil, gqlutils.Conflict(ctx, errNotPending) + } + + r.logger.ErrorCtx(ctx, "cannot void document version approval", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.VoidDocumentVersionApprovalPayload{ + ApprovalQuorum: types.NewDocumentVersionApprovalQuorum(quorum), + DocumentVersion: types.NewDocumentVersion(documentVersion), + }, nil +} + +// BulkDeleteDocuments is the resolver for the bulkDeleteDocuments field. +func (r *mutationResolver) BulkDeleteDocuments(ctx context.Context, input types.BulkDeleteDocumentsInput) (*types.BulkDeleteDocumentsPayload, error) { + if len(input.DocumentIds) == 0 { + return &types.BulkDeleteDocumentsPayload{ + DeletedDocumentIds: []gid.GID{}, + }, nil + } + + for _, documentID := range input.DocumentIds { + if err := r.authorize(ctx, documentID, probo.ActionDocumentDelete); err != nil { + return nil, err + } + } + + prb := r.ProboService(ctx, input.DocumentIds[0].TenantID()) + + err := prb.Documents.BulkSoftDelete(ctx, input.DocumentIds) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot bulk delete documents", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.BulkDeleteDocumentsPayload{ + DeletedDocumentIds: input.DocumentIds, + }, nil +} + +// BulkArchiveDocuments is the resolver for the bulkArchiveDocuments field. +func (r *mutationResolver) BulkArchiveDocuments(ctx context.Context, input types.BulkArchiveDocumentsInput) (*types.BulkArchiveDocumentsPayload, error) { + if len(input.DocumentIds) == 0 { + return &types.BulkArchiveDocumentsPayload{ + Documents: []*types.Document{}, + }, nil + } + + for _, documentID := range input.DocumentIds { + if err := r.authorize(ctx, documentID, probo.ActionDocumentArchive); err != nil { + return nil, err + } + } + + prb := r.ProboService(ctx, input.DocumentIds[0].TenantID()) + + if err := prb.Documents.BulkArchive(ctx, input.DocumentIds); err != nil { + r.logger.ErrorCtx(ctx, "cannot bulk archive documents", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.BulkArchiveDocumentsPayload{ + Documents: []*types.Document{}, + }, nil +} + +// BulkUnarchiveDocuments is the resolver for the bulkUnarchiveDocuments field. +func (r *mutationResolver) BulkUnarchiveDocuments(ctx context.Context, input types.BulkUnarchiveDocumentsInput) (*types.BulkUnarchiveDocumentsPayload, error) { + if len(input.DocumentIds) == 0 { + return &types.BulkUnarchiveDocumentsPayload{ + Documents: []*types.Document{}, + }, nil + } + + for _, documentID := range input.DocumentIds { + if err := r.authorize(ctx, documentID, probo.ActionDocumentUnarchive); err != nil { + return nil, err + } + } + + prb := r.ProboService(ctx, input.DocumentIds[0].TenantID()) + + if err := prb.Documents.BulkUnarchive(ctx, input.DocumentIds); err != nil { + r.logger.ErrorCtx(ctx, "cannot bulk unarchive documents", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.BulkUnarchiveDocumentsPayload{ + Documents: []*types.Document{}, + }, nil +} + +// BulkExportDocuments is the resolver for the bulkExportDocuments field. +func (r *mutationResolver) BulkExportDocuments(ctx context.Context, input types.BulkExportDocumentsInput) (*types.BulkExportDocumentsPayload, error) { + if len(input.DocumentIds) == 0 { + r.logger.ErrorCtx(ctx, "no document ids provided") + return nil, gqlutils.Internal(ctx) + } + + // TODO have a way to batch authorize for resources + for _, documentID := range input.DocumentIds { + if err := r.authorize(ctx, documentID, probo.ActionDocumentVersionExport); err != nil { + return nil, err + } + } + + prb := r.ProboService(ctx, input.DocumentIds[0].TenantID()) + + identity := authn.IdentityFromContext(ctx) + + options := probo.ExportPDFOptions{ + WithWatermark: input.WithWatermark, + WithSignatures: input.WithSignatures, + WatermarkEmail: input.WatermarkEmail, + } + + documentExport, exportErr := prb.Documents.RequestExport(ctx, input.DocumentIds, identity.EmailAddress, identity.FullName, options) + if exportErr != nil { + r.logger.ErrorCtx(ctx, "cannot request document export", log.Error(exportErr)) + return nil, gqlutils.Internal(ctx) + } + + return &types.BulkExportDocumentsPayload{ + ExportJobID: documentExport.ID, + }, nil +} + +// GenerateDocumentChangelog is the resolver for the generateDocumentChangelog field. +func (r *mutationResolver) GenerateDocumentChangelog(ctx context.Context, input types.GenerateDocumentChangelogInput) (*types.GenerateDocumentChangelogPayload, error) { + if err := r.authorize(ctx, input.DocumentID, probo.ActionDocumentChangelogGenerate); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, input.DocumentID.TenantID()) + + changelog, err := prb.Documents.GenerateChangelog(ctx, input.DocumentID) + if err != nil { + if errArchived, ok := errors.AsType[*probo.ErrDocumentArchived](err); ok { + return nil, gqlutils.Conflict(ctx, errArchived) + } + + r.logger.ErrorCtx(ctx, "cannot generate document changelog", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.GenerateDocumentChangelogPayload{ + Changelog: *changelog, + }, nil +} + +// RequestSignature is the resolver for the requestSignature field. +func (r *mutationResolver) RequestSignature(ctx context.Context, input types.RequestSignatureInput) (*types.RequestSignaturePayload, error) { + if err := r.authorize(ctx, input.DocumentVersionID, probo.ActionDocumentVersionSignatureRequest); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, input.DocumentVersionID.TenantID()) + + documentVersionSignature, err := prb.Documents.RequestSignature( + ctx, + probo.RequestSignatureRequest{ + DocumentVersionID: input.DocumentVersionID, + Signatory: input.SignatoryID, + }, + ) + if err != nil { + if errArchived, ok := errors.AsType[*probo.ErrDocumentArchived](err); ok { + return nil, gqlutils.Conflict(ctx, errArchived) + } + + r.logger.ErrorCtx(ctx, "cannot request signature", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.RequestSignaturePayload{ + DocumentVersionSignatureEdge: types.NewDocumentVersionSignatureEdge(documentVersionSignature, coredata.DocumentVersionSignatureOrderFieldCreatedAt), + }, nil +} + +// BulkRequestSignatures is the resolver for the bulkRequestSignatures field. +func (r *mutationResolver) BulkRequestSignatures(ctx context.Context, input types.BulkRequestSignaturesInput) (*types.BulkRequestSignaturesPayload, error) { + if len(input.DocumentIds) == 0 { + return &types.BulkRequestSignaturesPayload{ + DocumentVersionSignatureEdges: []*types.DocumentVersionSignatureEdge{}, + }, nil + } + + for _, documentID := range input.DocumentIds { + if err := r.authorize(ctx, documentID, probo.ActionDocumentVersionSignatureRequest); err != nil { + return nil, err + } + } + + prb := r.ProboService(ctx, input.DocumentIds[0].TenantID()) + + documentVersionSignatures, err := prb.Documents.BulkRequestSignatures( + ctx, + probo.BulkRequestSignaturesRequest{ + DocumentIDs: input.DocumentIds, + SignatoryIDs: input.SignatoryIds, + }, + ) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot bulk request signatures", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.BulkRequestSignaturesPayload{ + DocumentVersionSignatureEdges: types.NewDocumentVersionSignatureEdges(documentVersionSignatures, coredata.DocumentVersionSignatureOrderFieldCreatedAt), + }, nil +} + +// SendSigningNotifications is the resolver for the sendSigningNotifications field. +func (r *mutationResolver) SendSigningNotifications(ctx context.Context, input types.SendSigningNotificationsInput) (*types.SendSigningNotificationsPayload, error) { + if err := r.authorize(ctx, input.OrganizationID, probo.ActionDocumentSendSigningNotifications); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, input.OrganizationID.TenantID()) + + err := prb.Documents.SendSigningNotifications(ctx, input.OrganizationID) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot send signing notifications", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.SendSigningNotificationsPayload{ + Success: true, + }, nil +} + +// CancelSignatureRequest is the resolver for the cancelSignatureRequest field. +func (r *mutationResolver) CancelSignatureRequest(ctx context.Context, input types.CancelSignatureRequestInput) (*types.CancelSignatureRequestPayload, error) { + if err := r.authorize(ctx, input.DocumentVersionSignatureID, probo.ActionDocumentVersionCancelSignature); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, input.DocumentVersionSignatureID.TenantID()) + + err := prb.Documents.CancelSignatureRequest(ctx, input.DocumentVersionSignatureID) + if err != nil { + if errArchived, ok := errors.AsType[*probo.ErrDocumentArchived](err); ok { + return nil, gqlutils.Conflict(ctx, errArchived) + } + + r.logger.ErrorCtx(ctx, "cannot cancel signature request", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.CancelSignatureRequestPayload{ + DeletedDocumentVersionSignatureID: input.DocumentVersionSignatureID, + }, nil +} + +// SignDocument is the resolver for the signDocument field. +func (r *mutationResolver) SignDocument(ctx context.Context, input types.SignDocumentInput) (*types.SignDocumentPayload, error) { + if err := r.authorize(ctx, input.DocumentVersionID, probo.ActionDocumentVersionSign); err != nil { + return nil, err + } + + identity := authn.IdentityFromContext(ctx) + prb := r.ProboService(ctx, input.DocumentVersionID.TenantID()) + + documentVersionSignature, err := prb.Documents.SignDocumentVersionByIdentity(ctx, input.DocumentVersionID, identity.ID) + if err != nil { + if errors.Is(err, coredata.ErrResourceAlreadyExists) { + return nil, gqlutils.Conflict(ctx, err) + } + + r.logger.ErrorCtx(ctx, "cannot sign document", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.SignDocumentPayload{ + DocumentVersionSignature: types.NewDocumentVersionSignature(documentVersionSignature), + }, nil +} + +// ApproveDocumentVersion is the resolver for the approveDocumentVersion field. +func (r *mutationResolver) ApproveDocumentVersion(ctx context.Context, input types.ApproveDocumentVersionInput) (*types.ApproveDocumentVersionPayload, error) { + if err := r.authorize(ctx, input.DocumentVersionID, probo.ActionDocumentVersionApprove); err != nil { + return nil, err + } + + identity := authn.IdentityFromContext(ctx) + httpReq := gqlutils.HTTPRequestFromContext(ctx) + + signerIP, _, _ := net.SplitHostPort(httpReq.RemoteAddr) + if signerIP == "" { + signerIP = httpReq.RemoteAddr + } + + prb := r.ProboService(ctx, input.DocumentVersionID.TenantID()) + + decision, err := prb.DocumentApprovals.Approve(ctx, probo.ApproveDocumentVersionRequest{ + DocumentVersionID: input.DocumentVersionID, + IdentityID: identity.ID, + Comment: input.Comment, + SignerFullName: identity.FullName, + SignerEmail: identity.EmailAddress, + SignerIPAddr: signerIP, + SignerUA: httpReq.UserAgent(), + }) + if err != nil { + if errArchived, ok := errors.AsType[*probo.ErrDocumentArchived](err); ok { + return nil, gqlutils.Conflict(ctx, errArchived) + } + + if errNotPending, ok := errors.AsType[*probo.ErrDocumentVersionNotPendingApproval](err); ok { + return nil, gqlutils.Invalid(ctx, errNotPending) + } + + if errAlready, ok := errors.AsType[*probo.ErrApprovalDecisionAlreadyMade](err); ok { + return nil, gqlutils.Conflict(ctx, errAlready) + } + + if errors.Is(err, coredata.ErrResourceNotFound) { + return nil, gqlutils.NotFound(ctx, err) + } + + r.logger.ErrorCtx(ctx, "cannot approve document version", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.ApproveDocumentVersionPayload{ + ApprovalDecision: types.NewDocumentVersionApprovalDecision(decision), + }, nil +} + +// RejectDocumentVersion is the resolver for the rejectDocumentVersion field. +func (r *mutationResolver) RejectDocumentVersion(ctx context.Context, input types.RejectDocumentVersionInput) (*types.RejectDocumentVersionPayload, error) { + if err := r.authorize(ctx, input.DocumentVersionID, probo.ActionDocumentVersionReject); err != nil { + return nil, err + } + + identity := authn.IdentityFromContext(ctx) + + prb := r.ProboService(ctx, input.DocumentVersionID.TenantID()) + + decision, err := prb.DocumentApprovals.Reject(ctx, probo.RejectDocumentVersionRequest{ + DocumentVersionID: input.DocumentVersionID, + IdentityID: identity.ID, + Comment: input.Comment, + }) + if err != nil { + if errArchived, ok := errors.AsType[*probo.ErrDocumentArchived](err); ok { + return nil, gqlutils.Conflict(ctx, errArchived) + } + + if errNotPending, ok := errors.AsType[*probo.ErrDocumentVersionNotPendingApproval](err); ok { + return nil, gqlutils.Invalid(ctx, errNotPending) + } + + if errAlready, ok := errors.AsType[*probo.ErrApprovalDecisionAlreadyMade](err); ok { + return nil, gqlutils.Conflict(ctx, errAlready) + } + + if errors.Is(err, coredata.ErrResourceNotFound) { + return nil, gqlutils.NotFound(ctx, err) + } + + r.logger.ErrorCtx(ctx, "cannot reject document version", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.RejectDocumentVersionPayload{ + ApprovalDecision: types.NewDocumentVersionApprovalDecision(decision), + }, nil +} + +// ExportDocumentVersionPDF is the resolver for the exportDocumentVersionPDF field. +func (r *mutationResolver) ExportDocumentVersionPDF(ctx context.Context, input types.ExportDocumentVersionPDFInput) (*types.ExportDocumentVersionPDFPayload, error) { + if err := r.authorize(ctx, input.DocumentVersionID, probo.ActionDocumentVersionExportPDF); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, input.DocumentVersionID.TenantID()) + + watermarkEmail := input.WatermarkEmail + if input.WithWatermark && watermarkEmail == nil { + identity := authn.IdentityFromContext(ctx) + watermarkEmail = &identity.EmailAddress + } + + options := probo.ExportPDFOptions{ + WithSignatures: input.WithSignatures, + WithWatermark: input.WithWatermark, + WatermarkEmail: watermarkEmail, + } + + pdf, err := prb.Documents.ExportPDF(ctx, input.DocumentVersionID, options) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot export document version PDF", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.ExportDocumentVersionPDFPayload{ + Data: fmt.Sprintf("data:application/pdf;base64,%s", base64.StdEncoding.EncodeToString(pdf)), + }, nil +} + +// ExportEmployeeDocumentVersionPDF is the resolver for the exportEmployeeDocumentVersionPDF field. +func (r *mutationResolver) ExportEmployeeDocumentVersionPDF(ctx context.Context, input types.ExportEmployeeDocumentVersionPDFInput) (*types.ExportEmployeeDocumentVersionPDFPayload, error) { + if err := r.authorize(ctx, input.DocumentVersionID, probo.ActionEmployeeDocumentVersionExportPDF); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, input.DocumentVersionID.TenantID()) + + documentVersion, err := prb.Documents.GetVersion(ctx, input.DocumentVersionID) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot get document version", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + identity := authn.IdentityFromContext(ctx) + documentFilter := coredata.NewDocumentFilter(nil).WithEmployeeIdentityID( + &identity.ID, + coredata.EmployeeFilterModeSignature, + coredata.EmployeeFilterModeApproval, + ) + + _, err = prb.Documents.GetWithFilter(ctx, documentVersion.DocumentID, documentFilter) + if err != nil { + if errors.Is(err, coredata.ErrResourceNotFound) { + return nil, gqlutils.NotFound(ctx, err) + } + + r.logger.ErrorCtx(ctx, "cannot get employee document", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + options := probo.ExportPDFOptions{ + WithSignatures: false, + WithWatermark: true, + WatermarkEmail: &identity.EmailAddress, + } + + pdf, err := prb.Documents.ExportPDF(ctx, input.DocumentVersionID, options) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot export employee document PDF", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.ExportEmployeeDocumentVersionPDFPayload{ + Data: fmt.Sprintf("data:application/pdf;base64,%s", base64.StdEncoding.EncodeToString(pdf)), + }, nil +} + +// Documents is the resolver for the documents field. +func (r *organizationResolver) Documents(ctx context.Context, obj *types.Organization, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.DocumentOrderBy, filter *types.DocumentFilter) (*types.DocumentConnection, error) { + if err := r.authorize(ctx, obj.ID, probo.ActionDocumentList); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, obj.ID.TenantID()) + + pageOrderBy := page.OrderBy[coredata.DocumentOrderField]{ + Field: coredata.DocumentOrderFieldTitle, + Direction: page.OrderDirectionDesc, + } + if orderBy != nil { + pageOrderBy = page.OrderBy[coredata.DocumentOrderField]{ + Field: orderBy.Field, + Direction: orderBy.Direction, + } + } + + cursor := types.NewCursor(first, after, last, before, pageOrderBy) + + var documentFilter = coredata.NewDocumentFilter(nil) + if filter != nil { + documentFilter = coredata.NewDocumentFilter(filter.Query). + WithDocumentTypes(filter.DocumentTypes). + WithClassifications(filter.Classifications). + WithStatus(filter.Status) + } + + page, err := prb.Documents.ListByOrganizationID(ctx, obj.ID, cursor, documentFilter) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot list organization documents", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return types.NewDocumentConnection(page, r, obj.ID, documentFilter), nil +} + +// SignableDocuments is the resolver for the signableDocuments field. +func (r *viewerResolver) SignableDocuments(ctx context.Context, obj *types.Viewer, organizationID gid.GID, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.DocumentOrderBy) (*types.EmployeeDocumentConnection, error) { + if err := r.authorize(ctx, organizationID, probo.ActionEmployeeDocumentList); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, organizationID.TenantID()) + + pageOrderBy := page.OrderBy[coredata.DocumentOrderField]{ + Field: coredata.DocumentOrderFieldCreatedAt, + Direction: page.OrderDirectionDesc, + } + if orderBy != nil { + pageOrderBy = page.OrderBy[coredata.DocumentOrderField]{ + Field: orderBy.Field, + Direction: orderBy.Direction, + } + } + + cursor := types.NewCursor(first, after, last, before, pageOrderBy) + + identity := authn.IdentityFromContext(ctx) + + documentFilter := coredata.NewDocumentFilter(nil).WithEmployeeIdentityID(&identity.ID, coredata.EmployeeFilterModeSignature) + + documentsPage, err := prb.Documents.ListByOrganizationID(ctx, organizationID, cursor, documentFilter) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot list organization signable documents", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + employeeDocuments := make([]*types.EmployeeDocument, len(documentsPage.Data)) + for i, doc := range documentsPage.Data { + employeeDocuments[i] = &types.EmployeeDocument{ + ID: doc.ID, + Title: doc.Title, + DocumentType: doc.DocumentType, + CreatedAt: doc.CreatedAt, + UpdatedAt: doc.UpdatedAt, + FilterMode: types.EmployeeDocumentFilterModeSignature, + } + } + + page := page.NewPage(employeeDocuments, documentsPage.Cursor) + + return types.NewEmployeeDocumentConnection(page), nil +} + +// SignableDocument is the resolver for the signableDocument field. +func (r *viewerResolver) SignableDocument(ctx context.Context, obj *types.Viewer, id gid.GID) (*types.EmployeeDocument, error) { + if err := r.authorize(ctx, id, probo.ActionEmployeeDocumentGet); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, id.TenantID()) + + identity := authn.IdentityFromContext(ctx) + + documentFilter := coredata.NewDocumentFilter(nil).WithEmployeeIdentityID(&identity.ID, coredata.EmployeeFilterModeSignature) + document, err := prb.Documents.GetWithFilter(ctx, id, documentFilter) + if err != nil { + if errors.Is(err, coredata.ErrResourceNotFound) { + return nil, gqlutils.NotFound(ctx, err) + } + + r.logger.ErrorCtx(ctx, "cannot get signable document", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.EmployeeDocument{ + ID: document.ID, + Title: document.Title, + DocumentType: document.DocumentType, + CreatedAt: document.CreatedAt, + UpdatedAt: document.UpdatedAt, + FilterMode: types.EmployeeDocumentFilterModeSignature, + }, nil +} + +// ApprovableDocuments is the resolver for the approvableDocuments field. +func (r *viewerResolver) ApprovableDocuments(ctx context.Context, obj *types.Viewer, organizationID gid.GID, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.DocumentOrderBy) (*types.EmployeeDocumentConnection, error) { + if err := r.authorize(ctx, organizationID, probo.ActionEmployeeDocumentList); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, organizationID.TenantID()) + + pageOrderBy := page.OrderBy[coredata.DocumentOrderField]{ + Field: coredata.DocumentOrderFieldCreatedAt, + Direction: page.OrderDirectionDesc, + } + if orderBy != nil { + pageOrderBy = page.OrderBy[coredata.DocumentOrderField]{ + Field: orderBy.Field, + Direction: orderBy.Direction, + } + } + + cursor := types.NewCursor(first, after, last, before, pageOrderBy) + + identity := authn.IdentityFromContext(ctx) + + documentFilter := coredata.NewDocumentFilter(nil).WithEmployeeIdentityID(&identity.ID, coredata.EmployeeFilterModeApproval) + + documentsPage, err := prb.Documents.ListByOrganizationID(ctx, organizationID, cursor, documentFilter) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot list organization approvable documents", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + employeeDocuments := make([]*types.EmployeeDocument, len(documentsPage.Data)) + for i, doc := range documentsPage.Data { + employeeDocuments[i] = &types.EmployeeDocument{ + ID: doc.ID, + Title: doc.Title, + DocumentType: doc.DocumentType, + CreatedAt: doc.CreatedAt, + UpdatedAt: doc.UpdatedAt, + FilterMode: types.EmployeeDocumentFilterModeApproval, + } + } + + page := page.NewPage(employeeDocuments, documentsPage.Cursor) + + return types.NewEmployeeDocumentConnection(page), nil +} + +// ApprovableDocument is the resolver for the approvableDocument field. +func (r *viewerResolver) ApprovableDocument(ctx context.Context, obj *types.Viewer, id gid.GID) (*types.EmployeeDocument, error) { + if err := r.authorize(ctx, id, probo.ActionEmployeeDocumentGet); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, id.TenantID()) + + identity := authn.IdentityFromContext(ctx) + + documentFilter := coredata.NewDocumentFilter(nil).WithEmployeeIdentityID(&identity.ID, coredata.EmployeeFilterModeApproval) + document, err := prb.Documents.GetWithFilter(ctx, id, documentFilter) + if err != nil { + if errors.Is(err, coredata.ErrResourceNotFound) { + return nil, gqlutils.NotFound(ctx, err) + } + + r.logger.ErrorCtx(ctx, "cannot get approvable document", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.EmployeeDocument{ + ID: document.ID, + Title: document.Title, + DocumentType: document.DocumentType, + CreatedAt: document.CreatedAt, + UpdatedAt: document.UpdatedAt, + FilterMode: types.EmployeeDocumentFilterModeApproval, + }, nil +} + +// Document returns schema.DocumentResolver implementation. +func (r *Resolver) Document() schema.DocumentResolver { return &documentResolver{r} } + +// DocumentConnection returns schema.DocumentConnectionResolver implementation. +func (r *Resolver) DocumentConnection() schema.DocumentConnectionResolver { + return &documentConnectionResolver{r} +} + +// DocumentVersion returns schema.DocumentVersionResolver implementation. +func (r *Resolver) DocumentVersion() schema.DocumentVersionResolver { + return &documentVersionResolver{r} +} + +// DocumentVersionApprovalDecision returns schema.DocumentVersionApprovalDecisionResolver implementation. +func (r *Resolver) DocumentVersionApprovalDecision() schema.DocumentVersionApprovalDecisionResolver { + return &documentVersionApprovalDecisionResolver{r} +} + +// DocumentVersionApprovalDecisionConnection returns schema.DocumentVersionApprovalDecisionConnectionResolver implementation. +func (r *Resolver) DocumentVersionApprovalDecisionConnection() schema.DocumentVersionApprovalDecisionConnectionResolver { + return &documentVersionApprovalDecisionConnectionResolver{r} +} + +// DocumentVersionApprovalQuorum returns schema.DocumentVersionApprovalQuorumResolver implementation. +func (r *Resolver) DocumentVersionApprovalQuorum() schema.DocumentVersionApprovalQuorumResolver { + return &documentVersionApprovalQuorumResolver{r} +} + +// DocumentVersionApprovalQuorumConnection returns schema.DocumentVersionApprovalQuorumConnectionResolver implementation. +func (r *Resolver) DocumentVersionApprovalQuorumConnection() schema.DocumentVersionApprovalQuorumConnectionResolver { + return &documentVersionApprovalQuorumConnectionResolver{r} +} + +// DocumentVersionConnection returns schema.DocumentVersionConnectionResolver implementation. +func (r *Resolver) DocumentVersionConnection() schema.DocumentVersionConnectionResolver { + return &documentVersionConnectionResolver{r} +} + +// DocumentVersionSignature returns schema.DocumentVersionSignatureResolver implementation. +func (r *Resolver) DocumentVersionSignature() schema.DocumentVersionSignatureResolver { + return &documentVersionSignatureResolver{r} +} + +// DocumentVersionSignatureConnection returns schema.DocumentVersionSignatureConnectionResolver implementation. +func (r *Resolver) DocumentVersionSignatureConnection() schema.DocumentVersionSignatureConnectionResolver { + return &documentVersionSignatureConnectionResolver{r} +} + +// EmployeeDocument returns schema.EmployeeDocumentResolver implementation. +func (r *Resolver) EmployeeDocument() schema.EmployeeDocumentResolver { + return &employeeDocumentResolver{r} +} + +// EmployeeDocumentVersion returns schema.EmployeeDocumentVersionResolver implementation. +func (r *Resolver) EmployeeDocumentVersion() schema.EmployeeDocumentVersionResolver { + return &employeeDocumentVersionResolver{r} +} + +type documentResolver struct{ *Resolver } +type documentConnectionResolver struct{ *Resolver } +type documentVersionResolver struct{ *Resolver } +type documentVersionApprovalDecisionResolver struct{ *Resolver } +type documentVersionApprovalDecisionConnectionResolver struct{ *Resolver } +type documentVersionApprovalQuorumResolver struct{ *Resolver } +type documentVersionApprovalQuorumConnectionResolver struct{ *Resolver } +type documentVersionConnectionResolver struct{ *Resolver } +type documentVersionSignatureResolver struct{ *Resolver } +type documentVersionSignatureConnectionResolver struct{ *Resolver } +type employeeDocumentResolver struct{ *Resolver } +type employeeDocumentVersionResolver struct{ *Resolver } diff --git a/pkg/server/api/console/v1/electronic_signature.resolvers.go b/pkg/server/api/console/v1/electronic_signature.resolvers.go new file mode 100644 index 000000000..ad3caf165 --- /dev/null +++ b/pkg/server/api/console/v1/electronic_signature.resolvers.go @@ -0,0 +1,56 @@ +package console_v1 + +// This file will be automatically regenerated based on the schema, any resolver +// implementations +// will be copied through when generating and any unknown code will be moved to the end. +// Code generated by github.com/99designs/gqlgen version v0.17.87 + +import ( + "context" + "fmt" + "time" + + "go.probo.inc/probo/pkg/server/api/console/v1/schema" + "go.probo.inc/probo/pkg/server/api/console/v1/types" +) + +// CertificateFileURL is the resolver for the certificateFileUrl field. +func (r *electronicSignatureResolver) CertificateFileURL(ctx context.Context, obj *types.ElectronicSignature) (*string, error) { + signature, err := r.esign.GetSignatureByID(ctx, obj.ID) + if err != nil { + return nil, fmt.Errorf("cannot load signature: %w", err) + } + + if signature.CertificateFileID == nil { + return nil, nil + } + + url, err := r.esign.GenerateCertificateFileURL(ctx, *signature.CertificateFileID, 1*time.Hour) + if err != nil { + return nil, fmt.Errorf("cannot generate certificate file URL: %w", err) + } + + return &url, nil +} + +// Events is the resolver for the events field. +func (r *electronicSignatureResolver) Events(ctx context.Context, obj *types.ElectronicSignature) ([]*types.ElectronicSignatureEvent, error) { + events, err := r.esign.GetEventsBySignatureID(ctx, obj.ID) + if err != nil { + return nil, fmt.Errorf("cannot load signature events: %w", err) + } + + result := make([]*types.ElectronicSignatureEvent, len(events)) + for i := range events { + result[i] = types.NewElectronicSignatureEvent(events[i]) + } + + return result, nil +} + +// ElectronicSignature returns schema.ElectronicSignatureResolver implementation. +func (r *Resolver) ElectronicSignature() schema.ElectronicSignatureResolver { + return &electronicSignatureResolver{r} +} + +type electronicSignatureResolver struct{ *Resolver } diff --git a/pkg/server/api/console/v1/evidence.resolvers.go b/pkg/server/api/console/v1/evidence.resolvers.go new file mode 100644 index 000000000..ff9c198c9 --- /dev/null +++ b/pkg/server/api/console/v1/evidence.resolvers.go @@ -0,0 +1,197 @@ +package console_v1 + +// This file will be automatically regenerated based on the schema, any resolver +// implementations +// will be copied through when generating and any unknown code will be moved to the end. +// Code generated by github.com/99designs/gqlgen version v0.17.87 + +import ( + "context" + "errors" + "fmt" + + "github.com/vikstrous/dataloadgen" + "go.gearno.de/kit/log" + "go.probo.inc/probo/pkg/coredata" + "go.probo.inc/probo/pkg/page" + "go.probo.inc/probo/pkg/probo" + "go.probo.inc/probo/pkg/server/api/console/v1/dataloader" + "go.probo.inc/probo/pkg/server/api/console/v1/schema" + "go.probo.inc/probo/pkg/server/api/console/v1/types" + "go.probo.inc/probo/pkg/server/gqlutils" + "go.probo.inc/probo/pkg/validator" +) + +// File is the resolver for the file field. +func (r *evidenceResolver) File(ctx context.Context, obj *types.Evidence) (*types.File, error) { + if err := r.authorize(ctx, obj.ID, probo.ActionFileGet); err != nil { + return nil, err + } + + if obj.File == nil { + return nil, nil + } + + loaders := dataloader.FromContext(ctx) + + file, err := loaders.File.Load(ctx, obj.File.ID) + if err != nil { + if errors.Is(err, coredata.ErrResourceNotFound) || errors.Is(err, dataloadgen.ErrNotFound) { + return nil, gqlutils.NotFound(ctx, err) + } + + r.logger.ErrorCtx(ctx, "cannot load evidence file", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return types.NewFile(file), nil +} + +// Task is the resolver for the task field. +func (r *evidenceResolver) Task(ctx context.Context, obj *types.Evidence) (*types.Task, error) { + if err := r.authorize(ctx, obj.ID, probo.ActionTaskGet); err != nil { + return nil, err + } + + if obj.Task == nil { + r.logger.ErrorCtx(ctx, "evidence is not associated with a task") + return nil, gqlutils.Internal(ctx) + } + + loaders := dataloader.FromContext(ctx) + + task, err := loaders.Task.Load(ctx, obj.Task.ID) + if err != nil { + if errors.Is(err, coredata.ErrResourceNotFound) || errors.Is(err, dataloadgen.ErrNotFound) { + return nil, gqlutils.NotFound(ctx, err) + } + + r.logger.ErrorCtx(ctx, "cannot load task", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return types.NewTask(task), nil +} + +// Measure is the resolver for the measure field. +func (r *evidenceResolver) Measure(ctx context.Context, obj *types.Evidence) (*types.Measure, error) { + if err := r.authorize(ctx, obj.ID, probo.ActionMeasureGet); err != nil { + return nil, err + } + + loaders := dataloader.FromContext(ctx) + + measure, err := loaders.Measure.Load(ctx, obj.Measure.ID) + if err != nil { + if errors.Is(err, coredata.ErrResourceNotFound) || errors.Is(err, dataloadgen.ErrNotFound) { + return nil, gqlutils.NotFound(ctx, err) + } + + r.logger.ErrorCtx(ctx, "cannot load measure", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return types.NewMeasure(measure), nil +} + +// Permission is the resolver for the permission field. +func (r *evidenceResolver) Permission(ctx context.Context, obj *types.Evidence, action string) (bool, error) { + return r.Resolver.Permission(ctx, obj, action) +} + +// TotalCount is the resolver for the totalCount field. +func (r *evidenceConnectionResolver) TotalCount(ctx context.Context, obj *types.EvidenceConnection) (int, error) { + if err := r.authorize(ctx, obj.ParentID, probo.ActionEvidenceList); err != nil { + return 0, err + } + + prb := r.ProboService(ctx, obj.ParentID.TenantID()) + + switch obj.Resolver.(type) { + case *measureResolver: + count, err := prb.Evidences.CountForMeasureID(ctx, obj.ParentID) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot count measure evidence", log.Error(err)) + return 0, gqlutils.Internal(ctx) + } + return count, nil + case *taskResolver: + count, err := prb.Evidences.CountForTaskID(ctx, obj.ParentID) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot count task evidence", log.Error(err)) + return 0, gqlutils.Internal(ctx) + } + return count, nil + } + + r.logger.ErrorCtx(ctx, "unsupported resolver") + return 0, gqlutils.Internal(ctx) +} + +// DeleteEvidence is the resolver for the deleteEvidence field. +func (r *mutationResolver) DeleteEvidence(ctx context.Context, input types.DeleteEvidenceInput) (*types.DeleteEvidencePayload, error) { + if err := r.authorize(ctx, input.EvidenceID, probo.ActionEvidenceDelete); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, input.EvidenceID.TenantID()) + + err := prb.Evidences.Delete(ctx, input.EvidenceID) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot delete evidence", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.DeleteEvidencePayload{ + DeletedEvidenceID: input.EvidenceID, + }, nil +} + +// UploadMeasureEvidence is the resolver for the uploadMeasureEvidence field. +func (r *mutationResolver) UploadMeasureEvidence(ctx context.Context, input types.UploadMeasureEvidenceInput) (*types.UploadMeasureEvidencePayload, error) { + if err := r.authorize(ctx, input.MeasureID, probo.ActionMeasureEvidenceUpload); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, input.MeasureID.TenantID()) + + evidence, err := prb.Evidences.UploadMeasureEvidence( + ctx, + probo.UploadMeasureEvidenceRequest{ + MeasureID: input.MeasureID, + File: probo.FileUpload{ + Content: input.File.File, + Filename: input.File.Filename, + Size: input.File.Size, + ContentType: input.File.ContentType, + }, + }, + ) + if err != nil { + if validationErrors, ok := errors.AsType[validator.ValidationErrors](err); ok { + return nil, gqlutils.InvalidValidationErrors(ctx, validationErrors) + } + r.logger.ErrorCtx(ctx, "cannot upload measure evidence", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.UploadMeasureEvidencePayload{ + EvidenceEdge: types.NewEvidenceEdge(evidence, coredata.EvidenceOrderFieldCreatedAt), + }, nil +} + +// Evidences is the resolver for the evidences field. +func (r *organizationResolver) Evidences(ctx context.Context, obj *types.Organization, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.EvidenceOrderBy) (*types.EvidenceConnection, error) { + panic(fmt.Errorf("not implemented: Evidences - evidences")) +} + +// Evidence returns schema.EvidenceResolver implementation. +func (r *Resolver) Evidence() schema.EvidenceResolver { return &evidenceResolver{r} } + +// EvidenceConnection returns schema.EvidenceConnectionResolver implementation. +func (r *Resolver) EvidenceConnection() schema.EvidenceConnectionResolver { + return &evidenceConnectionResolver{r} +} + +type evidenceResolver struct{ *Resolver } +type evidenceConnectionResolver struct{ *Resolver } diff --git a/pkg/server/api/console/v1/framework.resolvers.go b/pkg/server/api/console/v1/framework.resolvers.go new file mode 100644 index 000000000..bd74601ef --- /dev/null +++ b/pkg/server/api/console/v1/framework.resolvers.go @@ -0,0 +1,301 @@ +package console_v1 + +// This file will be automatically regenerated based on the schema, any resolver +// implementations +// will be copied through when generating and any unknown code will be moved to the end. +// Code generated by github.com/99designs/gqlgen version v0.17.87 + +import ( + "context" + "encoding/json" + "errors" + "time" + + "github.com/vikstrous/dataloadgen" + "go.gearno.de/kit/log" + "go.probo.inc/probo/pkg/coredata" + "go.probo.inc/probo/pkg/page" + "go.probo.inc/probo/pkg/probo" + "go.probo.inc/probo/pkg/server/api/authn" + "go.probo.inc/probo/pkg/server/api/console/v1/dataloader" + "go.probo.inc/probo/pkg/server/api/console/v1/schema" + "go.probo.inc/probo/pkg/server/api/console/v1/types" + "go.probo.inc/probo/pkg/server/gqlutils" + "go.probo.inc/probo/pkg/validator" +) + +// Organization is the resolver for the organization field. +func (r *frameworkResolver) Organization(ctx context.Context, obj *types.Framework) (*types.Organization, error) { + if err := r.authorize(ctx, obj.ID, probo.ActionOrganizationGet); err != nil { + return nil, err + } + + loaders := dataloader.FromContext(ctx) + + organization, err := loaders.Organization.Load(ctx, obj.Organization.ID) + if err != nil { + if errors.Is(err, coredata.ErrResourceNotFound) || errors.Is(err, dataloadgen.ErrNotFound) { + return nil, gqlutils.NotFound(ctx, err) + } + + r.logger.ErrorCtx(ctx, "cannot load organization", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return types.NewOrganization(organization), nil +} + +// Controls is the resolver for the controls field. +func (r *frameworkResolver) Controls(ctx context.Context, obj *types.Framework, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.ControlOrderBy, filter *types.ControlFilter) (*types.ControlConnection, error) { + if err := r.authorize(ctx, obj.ID, probo.ActionControlList); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, obj.ID.TenantID()) + + pageOrderBy := page.OrderBy[coredata.ControlOrderField]{ + Field: coredata.ControlOrderFieldCreatedAt, + Direction: page.OrderDirectionDesc, + } + if orderBy != nil { + pageOrderBy = page.OrderBy[coredata.ControlOrderField]{ + Field: orderBy.Field, + Direction: orderBy.Direction, + } + } + + cursor := types.NewCursor(first, after, last, before, pageOrderBy) + + var controlFilter = coredata.NewControlFilter(nil) + if filter != nil { + controlFilter = coredata.NewControlFilter(filter.Query) + } + + page, err := prb.Controls.ListForFrameworkID(ctx, obj.ID, cursor, controlFilter) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot list controls", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return types.NewControlConnection(page, r, obj.ID, controlFilter), nil +} + +// LightLogoURL is the resolver for the lightLogoURL field. +func (r *frameworkResolver) LightLogoURL(ctx context.Context, obj *types.Framework) (*string, error) { + if err := r.authorize(ctx, obj.ID, probo.ActionFrameworkGet); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, obj.ID.TenantID()) + + return prb.Frameworks.GenerateLightLogoURL(ctx, obj.ID, 1*time.Hour) +} + +// DarkLogoURL is the resolver for the darkLogoURL field. +func (r *frameworkResolver) DarkLogoURL(ctx context.Context, obj *types.Framework) (*string, error) { + if err := r.authorize(ctx, obj.ID, probo.ActionFrameworkGet); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, obj.ID.TenantID()) + + return prb.Frameworks.GenerateDarkLogoURL(ctx, obj.ID, 1*time.Hour) +} + +// Permission is the resolver for the permission field. +func (r *frameworkResolver) Permission(ctx context.Context, obj *types.Framework, action string) (bool, error) { + return r.Resolver.Permission(ctx, obj, action) +} + +// TotalCount is the resolver for the totalCount field. +func (r *frameworkConnectionResolver) TotalCount(ctx context.Context, obj *types.FrameworkConnection) (int, error) { + if err := r.authorize(ctx, obj.ParentID, probo.ActionFrameworkList); err != nil { + return 0, err + } + + switch obj.Resolver.(type) { + case *organizationResolver: + prb := r.ProboService(ctx, obj.ParentID.TenantID()) + + count, err := prb.Frameworks.CountForOrganizationID(ctx, obj.ParentID) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot count frameworks", log.Error(err)) + return 0, gqlutils.Internal(ctx) + } + return count, nil + } + + r.logger.ErrorCtx(ctx, "unsupported resolver") + return 0, gqlutils.Internal(ctx) +} + +// CreateFramework is the resolver for the createFramework field. +func (r *mutationResolver) CreateFramework(ctx context.Context, input types.CreateFrameworkInput) (*types.CreateFrameworkPayload, error) { + if err := r.authorize(ctx, input.OrganizationID, probo.ActionFrameworkCreate); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, input.OrganizationID.TenantID()) + + framework, err := prb.Frameworks.Create( + ctx, + probo.CreateFrameworkRequest{ + OrganizationID: input.OrganizationID, + Name: input.Name, + }, + ) + if err != nil { + if validationErrors, ok := errors.AsType[validator.ValidationErrors](err); ok { + return nil, gqlutils.InvalidValidationErrors(ctx, validationErrors) + } + r.logger.ErrorCtx(ctx, "cannot create framework", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.CreateFrameworkPayload{ + FrameworkEdge: types.NewFrameworkEdge(framework, coredata.FrameworkOrderFieldCreatedAt), + }, nil +} + +// UpdateFramework is the resolver for the updateFramework field. +func (r *mutationResolver) UpdateFramework(ctx context.Context, input types.UpdateFrameworkInput) (*types.UpdateFrameworkPayload, error) { + if err := r.authorize(ctx, input.ID, probo.ActionFrameworkUpdate); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, input.ID.TenantID()) + + framework, err := prb.Frameworks.Update( + ctx, + probo.UpdateFrameworkRequest{ + ID: input.ID, + Name: input.Name, + Description: gqlutils.UnwrapOmittable(input.Description), + }, + ) + if err != nil { + if validationErrors, ok := errors.AsType[validator.ValidationErrors](err); ok { + return nil, gqlutils.InvalidValidationErrors(ctx, validationErrors) + } + r.logger.ErrorCtx(ctx, "cannot update framework", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.UpdateFrameworkPayload{ + Framework: types.NewFramework(framework), + }, nil +} + +// ImportFramework is the resolver for the importFramework field. +func (r *mutationResolver) ImportFramework(ctx context.Context, input types.ImportFrameworkInput) (*types.ImportFrameworkPayload, error) { + if err := r.authorize(ctx, input.OrganizationID, probo.ActionFrameworkImport); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, input.OrganizationID.TenantID()) + + req := probo.ImportFrameworkRequest{} + if err := json.NewDecoder(input.File.File).Decode(&req.Framework); err != nil { + r.logger.ErrorCtx(ctx, "cannot decode framework", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + framework, err := prb.Frameworks.Import(ctx, input.OrganizationID, req) + if err != nil { + if errors.Is(err, coredata.ErrResourceAlreadyExists) { + return nil, gqlutils.Conflict(ctx, err) + } + + r.logger.ErrorCtx(ctx, "cannot import framework", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.ImportFrameworkPayload{ + FrameworkEdge: types.NewFrameworkEdge(framework, coredata.FrameworkOrderFieldCreatedAt), + }, nil +} + +// DeleteFramework is the resolver for the deleteFramework field. +func (r *mutationResolver) DeleteFramework(ctx context.Context, input types.DeleteFrameworkInput) (*types.DeleteFrameworkPayload, error) { + if err := r.authorize(ctx, input.FrameworkID, probo.ActionFrameworkDelete); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, input.FrameworkID.TenantID()) + + err := prb.Frameworks.Delete(ctx, input.FrameworkID) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot delete framework", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.DeleteFrameworkPayload{ + DeletedFrameworkID: input.FrameworkID, + }, nil +} + +// ExportFramework is the resolver for the exportFramework field. +func (r *mutationResolver) ExportFramework(ctx context.Context, input types.ExportFrameworkInput) (*types.ExportFrameworkPayload, error) { + if err := r.authorize(ctx, input.FrameworkID, probo.ActionFrameworkExport); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, input.FrameworkID.TenantID()) + identity := authn.IdentityFromContext(ctx) + + exportJob, exportErr := prb.Frameworks.RequestExport( + ctx, + input.FrameworkID, + identity.EmailAddress, + identity.FullName, + ) + if exportErr != nil { + r.logger.ErrorCtx(ctx, "cannot export framework", log.Error(exportErr)) + return nil, gqlutils.Internal(ctx) + } + + return &types.ExportFrameworkPayload{ + ExportJobID: exportJob.ID, + }, nil +} + +// Frameworks is the resolver for the frameworks field. +func (r *organizationResolver) Frameworks(ctx context.Context, obj *types.Organization, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.FrameworkOrderBy) (*types.FrameworkConnection, error) { + if err := r.authorize(ctx, obj.ID, probo.ActionFrameworkList); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, obj.ID.TenantID()) + + pageOrderBy := page.OrderBy[coredata.FrameworkOrderField]{ + Field: coredata.FrameworkOrderFieldCreatedAt, + Direction: page.OrderDirectionDesc, + } + if orderBy != nil { + pageOrderBy = page.OrderBy[coredata.FrameworkOrderField]{ + Field: orderBy.Field, + Direction: orderBy.Direction, + } + } + + cursor := types.NewCursor(first, after, last, before, pageOrderBy) + + page, err := prb.Frameworks.ListForOrganizationID(ctx, obj.ID, cursor) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot list organization frameworks", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return types.NewFrameworkConnection(page, r, obj.ID), nil +} + +// Framework returns schema.FrameworkResolver implementation. +func (r *Resolver) Framework() schema.FrameworkResolver { return &frameworkResolver{r} } + +// FrameworkConnection returns schema.FrameworkConnectionResolver implementation. +func (r *Resolver) FrameworkConnection() schema.FrameworkConnectionResolver { + return &frameworkConnectionResolver{r} +} + +type frameworkResolver struct{ *Resolver } +type frameworkConnectionResolver struct{ *Resolver } diff --git a/pkg/server/api/console/v1/gqlgen.yaml b/pkg/server/api/console/v1/gqlgen.yaml index 3cd7aa628..8b26fc1a3 100644 --- a/pkg/server/api/console/v1/gqlgen.yaml +++ b/pkg/server/api/console/v1/gqlgen.yaml @@ -1,4 +1,5 @@ -schema: ["schema.graphql"] +schema: + - "graphql/*.graphql" exec: filename: "schema/schema.go" @@ -12,7 +13,7 @@ resolver: layout: "follow-schema" dir: "." package: "console_v1" - filename_template: "v1_resolver.go" + filename_template: "{name}.resolvers.go" autobind: [] call_argument_directives_with_null: true diff --git a/pkg/server/api/console/v1/graphql/access_review_campaign.graphql b/pkg/server/api/console/v1/graphql/access_review_campaign.graphql new file mode 100644 index 000000000..56a3a39a0 --- /dev/null +++ b/pkg/server/api/console/v1/graphql/access_review_campaign.graphql @@ -0,0 +1,709 @@ +extend type Organization { + accessSources( + first: Int + after: CursorKey + last: Int + before: CursorKey + orderBy: AccessSourceOrder + ): AccessSourceConnection! @goField(forceResolver: true) + + accessReviewCampaigns( + first: Int + after: CursorKey + last: Int + before: CursorKey + orderBy: AccessReviewCampaignOrder + ): AccessReviewCampaignConnection! @goField(forceResolver: true) +} + +enum AccessReviewCampaignStatus + @goModel( + model: "go.probo.inc/probo/pkg/coredata.AccessReviewCampaignStatus" + ) { + DRAFT + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.AccessReviewCampaignStatusDraft" + ) + IN_PROGRESS + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.AccessReviewCampaignStatusInProgress" + ) + PENDING_ACTIONS + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.AccessReviewCampaignStatusPendingActions" + ) + FAILED + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.AccessReviewCampaignStatusFailed" + ) + COMPLETED + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.AccessReviewCampaignStatusCompleted" + ) + CANCELLED + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.AccessReviewCampaignStatusCancelled" + ) +} + +enum AccessSourceCategory + @goModel( + model: "go.probo.inc/probo/pkg/coredata.AccessSourceCategory" + ) { + SAAS + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.AccessSourceCategorySaaS" + ) + CLOUD_INFRA + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.AccessSourceCategoryCloudInfra" + ) + SOURCE_CODE + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.AccessSourceCategorySourceCode" + ) + OTHER + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.AccessSourceCategoryOther" + ) +} + +enum AccessReviewCampaignSourceFetchStatus + @goModel( + model: "go.probo.inc/probo/pkg/coredata.AccessReviewCampaignSourceFetchStatus" + ) { + QUEUED + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.AccessReviewCampaignSourceFetchStatusQueued" + ) + FETCHING + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.AccessReviewCampaignSourceFetchStatusFetching" + ) + SUCCESS + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.AccessReviewCampaignSourceFetchStatusSuccess" + ) + FAILED + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.AccessReviewCampaignSourceFetchStatusFailed" + ) +} + +enum AccessEntryFlag + @goModel(model: "go.probo.inc/probo/pkg/coredata.AccessEntryFlag") { + NONE + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.AccessEntryFlagNone" + ) + ORPHANED + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.AccessEntryFlagOrphaned" + ) + INACTIVE + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.AccessEntryFlagInactive" + ) + EXCESSIVE + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.AccessEntryFlagExcessive" + ) + ROLE_MISMATCH + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.AccessEntryFlagRoleMismatch" + ) + NEW + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.AccessEntryFlagNew" + ) + DORMANT + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.AccessEntryFlagDormant" + ) + TERMINATED_USER + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.AccessEntryFlagTerminatedUser" + ) + CONTRACTOR_EXPIRED + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.AccessEntryFlagContractorExpired" + ) + SOD_CONFLICT + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.AccessEntryFlagSoDConflict" + ) + PRIVILEGED_ACCESS + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.AccessEntryFlagPrivilegedAccess" + ) + ROLE_CREEP + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.AccessEntryFlagRoleCreep" + ) + NO_BUSINESS_JUSTIFICATION + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.AccessEntryFlagNoBusinessJustification" + ) + OUT_OF_DEPARTMENT + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.AccessEntryFlagOutOfDepartment" + ) + SHARED_ACCOUNT + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.AccessEntryFlagSharedAccount" + ) +} + +enum AccessEntryDecision + @goModel( + model: "go.probo.inc/probo/pkg/coredata.AccessEntryDecision" + ) { + PENDING + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.AccessEntryDecisionPending" + ) + APPROVED + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.AccessEntryDecisionApproved" + ) + REVOKE + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.AccessEntryDecisionRevoke" + ) + DEFER + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.AccessEntryDecisionDefer" + ) + ESCALATE + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.AccessEntryDecisionEscalate" + ) +} + +enum AccessEntryIncrementalTag + @goModel(model: "go.probo.inc/probo/pkg/coredata.AccessEntryIncrementalTag") { + NEW + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.AccessEntryIncrementalTagNew" + ) + REMOVED + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.AccessEntryIncrementalTagRemoved" + ) + UNCHANGED + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.AccessEntryIncrementalTagUnchanged" + ) +} + +enum MfaStatus + @goModel(model: "go.probo.inc/probo/pkg/coredata.MFAStatus") { + ENABLED + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.MFAStatusEnabled" + ) + DISABLED + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.MFAStatusDisabled" + ) + UNKNOWN + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.MFAStatusUnknown" + ) +} + +enum AccessEntryAuthMethod + @goModel( + model: "go.probo.inc/probo/pkg/coredata.AccessEntryAuthMethod" + ) { + SSO + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.AccessEntryAuthMethodSSO" + ) + PASSWORD + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.AccessEntryAuthMethodPassword" + ) + API_KEY + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.AccessEntryAuthMethodAPIKey" + ) + SERVICE_ACCOUNT + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.AccessEntryAuthMethodServiceAccount" + ) + UNKNOWN + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.AccessEntryAuthMethodUnknown" + ) +} + +enum AccessEntryAccountType + @goModel( + model: "go.probo.inc/probo/pkg/coredata.AccessEntryAccountType" + ) { + USER + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.AccessEntryAccountTypeUser" + ) + SERVICE_ACCOUNT + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.AccessEntryAccountTypeServiceAccount" + ) +} + +enum AccessSourceOrderField + @goModel( + model: "go.probo.inc/probo/pkg/coredata.AccessSourceOrderField" + ) { + CREATED_AT +} + +enum AccessReviewCampaignOrderField + @goModel( + model: "go.probo.inc/probo/pkg/coredata.AccessReviewCampaignOrderField" + ) { + CREATED_AT +} + +enum AccessEntryOrderField + @goModel( + model: "go.probo.inc/probo/pkg/coredata.AccessEntryOrderField" + ) { + CREATED_AT +} + +enum AccessSourceConnectionStatus { + CONNECTED + DISCONNECTED + NOT_APPLICABLE +} + +input AccessSourceOrder { + direction: OrderDirection! + field: AccessSourceOrderField! +} + +input AccessReviewCampaignOrder { + direction: OrderDirection! + field: AccessReviewCampaignOrderField! +} + +input AccessEntryOrder { + direction: OrderDirection! + field: AccessEntryOrderField! +} + +input AccessEntryFilter + @goModel( + model: "go.probo.inc/probo/pkg/coredata.AccessEntryFilter" + ) { + decision: AccessEntryDecision + flag: AccessEntryFlag + incrementalTag: AccessEntryIncrementalTag + isAdmin: Boolean + authMethod: AccessEntryAuthMethod + accountType: AccessEntryAccountType +} + +type AccessReview implements Node { + id: ID! + organization: Organization! @goField(forceResolver: true) + identitySource: AccessSource @goField(forceResolver: true) + createdAt: Datetime! + updatedAt: Datetime! + + accessSources( + first: Int + after: CursorKey + last: Int + before: CursorKey + orderBy: AccessSourceOrder + ): AccessSourceConnection! @goField(forceResolver: true) + + campaigns( + first: Int + after: CursorKey + last: Int + before: CursorKey + orderBy: AccessReviewCampaignOrder + ): AccessReviewCampaignConnection! @goField(forceResolver: true) + + permission(action: String!): Boolean! @goField(forceResolver: true) +} + +type ProviderOrganization { + slug: String! + displayName: String! +} + +type AccessSource implements Node { + id: ID! + organization: Organization! @goField(forceResolver: true) + connectorId: ID + connector: Connector @goField(forceResolver: true) + name: String! + csvData: String + providerOrganizations: [ProviderOrganization!]! @goField(forceResolver: true) + needsConfiguration: Boolean! @goField(forceResolver: true) + connectionStatus: AccessSourceConnectionStatus! @goField(forceResolver: true) + selectedOrganization: String @goField(forceResolver: true) + createdAt: Datetime! + updatedAt: Datetime! + + permission(action: String!): Boolean! @goField(forceResolver: true) +} + +type AccessReviewCampaignScopeSource + @goModel( + model: "go.probo.inc/probo/pkg/server/api/console/v1/types.AccessReviewCampaignScopeSource" + ) { + id: ID! + source: AccessSource! + name: String! + fetchStatus: AccessReviewCampaignSourceFetchStatus! + fetchedAccountsCount: Int! + attemptCount: Int! + lastError: String + fetchStartedAt: Datetime + fetchCompletedAt: Datetime + + entries( + first: Int + after: CursorKey + last: Int + before: CursorKey + orderBy: AccessEntryOrder + filter: AccessEntryFilter + ): AccessEntryConnection! @goField(forceResolver: true) + + statistics: AccessReviewCampaignStatistics! @goField(forceResolver: true) +} + +type AccessReviewCampaign implements Node { + id: ID! + organization: Organization! @goField(forceResolver: true) + name: String! + description: String! + status: AccessReviewCampaignStatus! + startedAt: Datetime + completedAt: Datetime + frameworkControls: [String!] + createdAt: Datetime! + updatedAt: Datetime! + + scopeSources: [AccessReviewCampaignScopeSource!]! @goField(forceResolver: true) + + entries( + first: Int + after: CursorKey + last: Int + before: CursorKey + orderBy: AccessEntryOrder + accessSourceId: ID + filter: AccessEntryFilter + ): AccessEntryConnection! @goField(forceResolver: true) + + pendingEntryCount: Int! @goField(forceResolver: true) + + statistics: AccessReviewCampaignStatistics! @goField(forceResolver: true) + + permission(action: String!): Boolean! @goField(forceResolver: true) +} + +type AccessEntry implements Node { + id: ID! + campaign: AccessReviewCampaign! @goField(forceResolver: true) + accessSource: AccessSource! @goField(forceResolver: true) + email: String! + fullName: String! + role: String! + jobTitle: String! + isAdmin: Boolean! + mfaStatus: MfaStatus! + authMethod: AccessEntryAuthMethod! + accountType: AccessEntryAccountType! + lastLogin: Datetime + accountCreatedAt: Datetime + externalId: String! + incrementalTag: AccessEntryIncrementalTag! + flags: [AccessEntryFlag!]! + flagReasons: [String!]! + decision: AccessEntryDecision! + decisionNote: String + decidedBy: ID + decidedAt: Datetime + decisionHistory: [AccessEntryDecisionHistoryEntry!]! + @goField(forceResolver: true) + createdAt: Datetime! + updatedAt: Datetime! + + permission(action: String!): Boolean! @goField(forceResolver: true) +} + +type AccessEntryDecisionHistoryEntry { + id: ID! + decision: AccessEntryDecision! + decisionNote: String + decidedBy: ID + decidedAt: Datetime! + createdAt: Datetime! +} + +type AccessSourceConnection + @goModel( + model: "go.probo.inc/probo/pkg/server/api/console/v1/types.AccessSourceConnection" + ) { + totalCount: Int! @goField(forceResolver: true) + edges: [AccessSourceEdge!]! + pageInfo: PageInfo! +} + +type AccessSourceEdge { + cursor: CursorKey! + node: AccessSource! +} + +type AccessReviewCampaignConnection + @goModel( + model: "go.probo.inc/probo/pkg/server/api/console/v1/types.AccessReviewCampaignConnection" + ) { + totalCount: Int! @goField(forceResolver: true) + edges: [AccessReviewCampaignEdge!]! + pageInfo: PageInfo! +} + +type AccessReviewCampaignEdge { + cursor: CursorKey! + node: AccessReviewCampaign! +} + +type AccessEntryConnection + @goModel( + model: "go.probo.inc/probo/pkg/server/api/console/v1/types.AccessEntryConnection" + ) { + totalCount: Int! @goField(forceResolver: true) + edges: [AccessEntryEdge!]! + pageInfo: PageInfo! +} + +type AccessEntryEdge { + cursor: CursorKey! + node: AccessEntry! +} + +type AccessReviewCampaignStatistics { + totalCount: Int! + decisionCounts: [AccessEntryDecisionCount!]! + flagCounts: [AccessEntryFlagCount!]! + incrementalTagCounts: [AccessEntryIncrementalTagCount!]! +} + +type AccessEntryDecisionCount { + decision: AccessEntryDecision! + count: Int! +} + +type AccessEntryFlagCount { + flag: AccessEntryFlag! + count: Int! +} + +type AccessEntryIncrementalTagCount { + incrementalTag: AccessEntryIncrementalTag! + count: Int! +} + +extend type Mutation { + createAccessSource( + input: CreateAccessSourceInput! + ): CreateAccessSourcePayload! + updateAccessSource( + input: UpdateAccessSourceInput! + ): UpdateAccessSourcePayload! + deleteAccessSource( + input: DeleteAccessSourceInput! + ): DeleteAccessSourcePayload! + configureAccessSource( + input: ConfigureAccessSourceInput! + ): ConfigureAccessSourcePayload! + createAccessReviewCampaign( + input: CreateAccessReviewCampaignInput! + ): CreateAccessReviewCampaignPayload! + updateAccessReviewCampaign( + input: UpdateAccessReviewCampaignInput! + ): UpdateAccessReviewCampaignPayload! + deleteAccessReviewCampaign( + input: DeleteAccessReviewCampaignInput! + ): DeleteAccessReviewCampaignPayload! + startAccessReviewCampaign( + input: StartAccessReviewCampaignInput! + ): StartAccessReviewCampaignPayload! + closeAccessReviewCampaign( + input: CloseAccessReviewCampaignInput! + ): CloseAccessReviewCampaignPayload! + cancelAccessReviewCampaign( + input: CancelAccessReviewCampaignInput! + ): CancelAccessReviewCampaignPayload! + addAccessReviewCampaignScopeSource( + input: AddAccessReviewCampaignScopeSourceInput! + ): AddAccessReviewCampaignScopeSourcePayload! + removeAccessReviewCampaignScopeSource( + input: RemoveAccessReviewCampaignScopeSourceInput! + ): RemoveAccessReviewCampaignScopeSourcePayload! + recordAccessEntryDecision( + input: RecordAccessEntryDecisionInput! + ): RecordAccessEntryDecisionPayload! + recordAccessEntryDecisions( + input: RecordAccessEntryDecisionsInput! + ): RecordAccessEntryDecisionsPayload! + flagAccessEntry( + input: FlagAccessEntryInput! + ): FlagAccessEntryPayload! +} + +input CreateAccessSourceInput { + organizationId: ID! + connectorId: ID + name: String! + csvData: String +} + +type CreateAccessSourcePayload { + accessSourceEdge: AccessSourceEdge! +} + +input UpdateAccessSourceInput { + accessSourceId: ID! + name: String @goField(omittable: true) + connectorId: ID @goField(omittable: true) + csvData: String @goField(omittable: true) +} + +type UpdateAccessSourcePayload { + accessSource: AccessSource! +} + +input DeleteAccessSourceInput { + accessSourceId: ID! +} + +type DeleteAccessSourcePayload { + deletedAccessSourceId: ID! +} + +input ConfigureAccessSourceInput { + accessSourceId: ID! + organizationSlug: String! +} + +type ConfigureAccessSourcePayload { + accessSource: AccessSource! +} + +input CreateAccessReviewCampaignInput { + organizationId: ID! + name: String! + description: String + frameworkControls: [String!] + accessSourceIds: [ID!] +} + +type CreateAccessReviewCampaignPayload { + accessReviewCampaignEdge: AccessReviewCampaignEdge! +} + +input UpdateAccessReviewCampaignInput { + accessReviewCampaignId: ID! + name: String @goField(omittable: true) + description: String @goField(omittable: true) + frameworkControls: [String!] @goField(omittable: true) +} + +type UpdateAccessReviewCampaignPayload { + accessReviewCampaign: AccessReviewCampaign! +} + +input DeleteAccessReviewCampaignInput { + accessReviewCampaignId: ID! +} + +type DeleteAccessReviewCampaignPayload { + deletedAccessReviewCampaignId: ID! +} + +input StartAccessReviewCampaignInput { + accessReviewCampaignId: ID! +} + +type StartAccessReviewCampaignPayload { + accessReviewCampaign: AccessReviewCampaign! +} + +input CloseAccessReviewCampaignInput { + accessReviewCampaignId: ID! +} + +type CloseAccessReviewCampaignPayload { + accessReviewCampaign: AccessReviewCampaign! +} + +input CancelAccessReviewCampaignInput { + accessReviewCampaignId: ID! +} + +type CancelAccessReviewCampaignPayload { + accessReviewCampaign: AccessReviewCampaign! +} + +input AddAccessReviewCampaignScopeSourceInput { + accessReviewCampaignId: ID! + accessSourceId: ID! +} + +type AddAccessReviewCampaignScopeSourcePayload { + accessReviewCampaign: AccessReviewCampaign! +} + +input RemoveAccessReviewCampaignScopeSourceInput { + accessReviewCampaignId: ID! + accessSourceId: ID! +} + +type RemoveAccessReviewCampaignScopeSourcePayload { + accessReviewCampaign: AccessReviewCampaign! +} + +input RecordAccessEntryDecisionInput { + accessEntryId: ID! + decision: AccessEntryDecision! + decisionNote: String +} + +type RecordAccessEntryDecisionPayload { + accessEntry: AccessEntry! +} + +input RecordAccessEntryDecisionsInput { + decisions: [AccessEntryDecisionInput!]! +} + +input AccessEntryDecisionInput { + accessEntryId: ID! + decision: AccessEntryDecision! + decisionNote: String +} + +type RecordAccessEntryDecisionsPayload { + accessEntries: [AccessEntry!]! +} + +input FlagAccessEntryInput { + accessEntryId: ID! + flags: [AccessEntryFlag!]! + flagReasons: [String!] +} + +type FlagAccessEntryPayload { + accessEntry: AccessEntry! +} diff --git a/pkg/server/api/console/v1/graphql/asset.graphql b/pkg/server/api/console/v1/graphql/asset.graphql new file mode 100644 index 000000000..9fa2cf38f --- /dev/null +++ b/pkg/server/api/console/v1/graphql/asset.graphql @@ -0,0 +1,241 @@ +extend type Organization { + assets( + first: Int + after: CursorKey + last: Int + before: CursorKey + orderBy: AssetOrder + filter: AssetFilter = { snapshotId: null } + ): AssetConnection! @goField(forceResolver: true) + + data( + first: Int + after: CursorKey + last: Int + before: CursorKey + orderBy: DatumOrder + filter: DatumFilter = { snapshotId: null } + ): DatumConnection! @goField(forceResolver: true) +} + +enum AssetType @goModel(model: "go.probo.inc/probo/pkg/coredata.AssetType") { + PHYSICAL @goEnum(value: "go.probo.inc/probo/pkg/coredata.AssetTypePhysical") + VIRTUAL @goEnum(value: "go.probo.inc/probo/pkg/coredata.AssetTypeVirtual") +} + +enum AssetOrderField + @goModel(model: "go.probo.inc/probo/pkg/coredata.AssetOrderField") { + CREATED_AT + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.AssetOrderFieldCreatedAt" + ) + AMOUNT + @goEnum(value: "go.probo.inc/probo/pkg/coredata.AssetOrderFieldAmount") +} + +enum DatumOrderField + @goModel(model: "go.probo.inc/probo/pkg/coredata.DatumOrderField") { + CREATED_AT + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.DatumOrderFieldCreatedAt" + ) + NAME @goEnum(value: "go.probo.inc/probo/pkg/coredata.DatumOrderFieldName") + DATA_CLASSIFICATION + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.DatumOrderFieldDataClassification" + ) +} + +enum DataClassification + @goModel(model: "go.probo.inc/probo/pkg/coredata.DataClassification") { + PUBLIC + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.DataClassificationPublic" + ) + INTERNAL + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.DataClassificationInternal" + ) + CONFIDENTIAL + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.DataClassificationConfidential" + ) + SECRET + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.DataClassificationSecret" + ) +} + +input AssetOrder + @goModel( + model: "go.probo.inc/probo/pkg/server/api/console/v1/types.AssetOrderBy" + ) { + direction: OrderDirection! + field: AssetOrderField! +} + +input DatumOrder + @goModel( + model: "go.probo.inc/probo/pkg/server/api/console/v1/types.DatumOrderBy" + ) { + direction: OrderDirection! + field: DatumOrderField! +} + +input AssetFilter { + snapshotId: ID +} + +input DatumFilter { + snapshotId: ID +} + +type Asset implements Node { + id: ID! + snapshotId: ID + name: String! + amount: Int! + owner: Profile! @goField(forceResolver: true) + vendors( + first: Int + after: CursorKey + last: Int + before: CursorKey + orderBy: VendorOrder + ): VendorConnection! @goField(forceResolver: true) + assetType: AssetType! + dataTypesStored: String! + organization: Organization! @goField(forceResolver: true) + createdAt: Datetime! + updatedAt: Datetime! + + permission(action: String!): Boolean! @goField(forceResolver: true) +} + +type Datum implements Node + @goModel( + model: "go.probo.inc/probo/pkg/server/api/console/v1/types.Datum" + ) { + id: ID! + snapshotId: ID + name: String! + dataClassification: DataClassification! + owner: Profile! @goField(forceResolver: true) + vendors( + first: Int + after: CursorKey + last: Int + before: CursorKey + orderBy: VendorOrder + ): VendorConnection! @goField(forceResolver: true) + organization: Organization! @goField(forceResolver: true) + createdAt: Datetime! + updatedAt: Datetime! + + permission(action: String!): Boolean! @goField(forceResolver: true) +} + +type AssetConnection + @goModel( + model: "go.probo.inc/probo/pkg/server/api/console/v1/types.AssetConnection" + ) { + totalCount: Int! @goField(forceResolver: true) + edges: [AssetEdge!]! + pageInfo: PageInfo! +} + +type AssetEdge { + cursor: CursorKey! + node: Asset! +} + +type DatumConnection + @goModel( + model: "go.probo.inc/probo/pkg/server/api/console/v1/types.DatumConnection" + ) { + totalCount: Int! @goField(forceResolver: true) + edges: [DatumEdge!]! + pageInfo: PageInfo! +} + +type DatumEdge { + cursor: CursorKey! + node: Datum! +} + +extend type Mutation { + createAsset(input: CreateAssetInput!): CreateAssetPayload! + updateAsset(input: UpdateAssetInput!): UpdateAssetPayload! + deleteAsset(input: DeleteAssetInput!): DeleteAssetPayload! + createDatum(input: CreateDatumInput!): CreateDatumPayload! + updateDatum(input: UpdateDatumInput!): UpdateDatumPayload! + deleteDatum(input: DeleteDatumInput!): DeleteDatumPayload! +} + +input CreateAssetInput { + organizationId: ID! + name: String! + amount: Int! + ownerId: ID! + assetType: AssetType! + dataTypesStored: String! + vendorIds: [ID!] +} + +input UpdateAssetInput { + id: ID! + name: String + amount: Int + ownerId: ID + assetType: AssetType + dataTypesStored: String + vendorIds: [ID!] +} + +input DeleteAssetInput { + assetId: ID! +} + +input CreateDatumInput { + organizationId: ID! + name: String! + dataClassification: DataClassification! + ownerId: ID! + vendorIds: [ID!] +} + +input UpdateDatumInput { + id: ID! + name: String + dataClassification: DataClassification + ownerId: ID + vendorIds: [ID!] +} + +input DeleteDatumInput { + datumId: ID! +} + +type CreateAssetPayload { + assetEdge: AssetEdge! +} + +type UpdateAssetPayload { + asset: Asset! +} + +type DeleteAssetPayload { + deletedAssetId: ID! +} + +type CreateDatumPayload { + datumEdge: DatumEdge! +} + +type UpdateDatumPayload { + datum: Datum! +} + +type DeleteDatumPayload { + deletedDatumId: ID! +} diff --git a/pkg/server/api/console/v1/graphql/audit.graphql b/pkg/server/api/console/v1/graphql/audit.graphql new file mode 100644 index 000000000..7eac1ffc1 --- /dev/null +++ b/pkg/server/api/console/v1/graphql/audit.graphql @@ -0,0 +1,413 @@ +extend type Organization { + audits( + first: Int + after: CursorKey + last: Int + before: CursorKey + orderBy: AuditOrder + ): AuditConnection! @goField(forceResolver: true) + + findings( + first: Int + after: CursorKey + last: Int + before: CursorKey + orderBy: FindingOrder + filter: FindingFilter = { snapshotId: null } + ): FindingConnection @goField(forceResolver: true) +} + +enum AuditState @goModel(model: "go.probo.inc/probo/pkg/coredata.AuditState") { + NOT_STARTED + @goEnum(value: "go.probo.inc/probo/pkg/coredata.AuditStateNotStarted") + IN_PROGRESS + @goEnum(value: "go.probo.inc/probo/pkg/coredata.AuditStateInProgress") + COMPLETED + @goEnum(value: "go.probo.inc/probo/pkg/coredata.AuditStateCompleted") + REJECTED + @goEnum(value: "go.probo.inc/probo/pkg/coredata.AuditStateRejected") + OUTDATED + @goEnum(value: "go.probo.inc/probo/pkg/coredata.AuditStateOutdated") +} + +enum FindingKind + @goModel(model: "go.probo.inc/probo/pkg/coredata.FindingKind") { + MINOR_NONCONFORMITY + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.FindingKindMinorNonconformity" + ) + MAJOR_NONCONFORMITY + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.FindingKindMajorNonconformity" + ) + OBSERVATION + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.FindingKindObservation" + ) + EXCEPTION + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.FindingKindException" + ) +} + +enum FindingStatus + @goModel(model: "go.probo.inc/probo/pkg/coredata.FindingStatus") { + OPEN + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.FindingStatusOpen" + ) + IN_PROGRESS + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.FindingStatusInProgress" + ) + CLOSED + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.FindingStatusClosed" + ) + RISK_ACCEPTED + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.FindingStatusRiskAccepted" + ) + MITIGATED + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.FindingStatusMitigated" + ) + FALSE_POSITIVE + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.FindingStatusFalsePositive" + ) +} + +enum FindingPriority + @goModel(model: "go.probo.inc/probo/pkg/coredata.FindingPriority") { + LOW + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.FindingPriorityLow" + ) + MEDIUM + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.FindingPriorityMedium" + ) + HIGH + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.FindingPriorityHigh" + ) +} + +enum AuditOrderField + @goModel(model: "go.probo.inc/probo/pkg/coredata.AuditOrderField") { + CREATED_AT + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.AuditOrderFieldCreatedAt" + ) + VALID_FROM + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.AuditOrderFieldValidFrom" + ) + VALID_UNTIL + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.AuditOrderFieldValidUntil" + ) + STATE @goEnum(value: "go.probo.inc/probo/pkg/coredata.AuditOrderFieldState") +} + +enum FindingOrderField + @goModel(model: "go.probo.inc/probo/pkg/coredata.FindingOrderField") { + CREATED_AT + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.FindingOrderFieldCreatedAt" + ) + REFERENCE_ID + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.FindingOrderFieldReferenceId" + ) + IDENTIFIED_ON + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.FindingOrderFieldIdentifiedOn" + ) + DUE_DATE + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.FindingOrderFieldDueDate" + ) + STATUS + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.FindingOrderFieldStatus" + ) + PRIORITY + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.FindingOrderFieldPriority" + ) + KIND + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.FindingOrderFieldKind" + ) +} + +input AuditOrder + @goModel( + model: "go.probo.inc/probo/pkg/server/api/console/v1/types.AuditOrderBy" + ) { + direction: OrderDirection! + field: AuditOrderField! +} + +input FindingOrder { + field: FindingOrderField! + direction: OrderDirection! +} + +input FindingFilter { + snapshotId: ID + kind: FindingKind + status: FindingStatus + priority: FindingPriority + ownerId: ID +} + +type Audit implements Node { + id: ID! + name: String + organization: Organization @goField(forceResolver: true) + framework: Framework @goField(forceResolver: true) + validFrom: Datetime + validUntil: Datetime + report: Report @goField(forceResolver: true) + reportUrl: String @goField(forceResolver: true) + state: AuditState! + + controls( + first: Int + after: CursorKey + last: Int + before: CursorKey + orderBy: ControlOrder + filter: ControlFilter + ): ControlConnection @goField(forceResolver: true) + + findings( + first: Int + after: CursorKey + last: Int + before: CursorKey + orderBy: FindingOrder + filter: FindingFilter = { snapshotId: null } + ): FindingConnection @goField(forceResolver: true) + + trustCenterVisibility: TrustCenterVisibility! + createdAt: Datetime! + updatedAt: Datetime! + + permission(action: String!): Boolean! @goField(forceResolver: true) +} + +type Finding implements Node { + id: ID! + snapshotId: ID + organization: Organization @goField(forceResolver: true) + kind: FindingKind! + referenceId: String! + description: String + source: String + audits( + first: Int + after: CursorKey + last: Int + before: CursorKey + orderBy: AuditOrder + ): AuditConnection @goField(forceResolver: true) + identifiedOn: Datetime + rootCause: String + correctiveAction: String + owner: Profile @goField(forceResolver: true) + dueDate: Datetime + status: FindingStatus! + priority: FindingPriority! + risk: Risk @goField(forceResolver: true) + effectivenessCheck: String + createdAt: Datetime! + updatedAt: Datetime! + permission(action: String!): Boolean! @goField(forceResolver: true) +} + +type Report implements Node { + id: ID! + objectKey: String! + mimeType: String! + filename: String! + size: Int! + downloadUrl: String @goField(forceResolver: true) + createdAt: Datetime! + updatedAt: Datetime! + audit: Audit @goField(forceResolver: true) + + permission(action: String!): Boolean! @goField(forceResolver: true) +} + +type AuditConnection + @goModel( + model: "go.probo.inc/probo/pkg/server/api/console/v1/types.AuditConnection" + ) { + totalCount: Int! @goField(forceResolver: true) + edges: [AuditEdge!]! + pageInfo: PageInfo! +} + +type AuditEdge { + cursor: CursorKey! + node: Audit! +} + +type FindingConnection + @goModel( + model: "go.probo.inc/probo/pkg/server/api/console/v1/types.FindingConnection" + ) { + totalCount: Int! @goField(forceResolver: true) + edges: [FindingEdge!]! + pageInfo: PageInfo! +} + +type FindingEdge + @goModel( + model: "go.probo.inc/probo/pkg/server/api/console/v1/types.FindingEdge" + ) { + cursor: CursorKey! + node: Finding! +} + +extend type Mutation { + createAudit(input: CreateAuditInput!): CreateAuditPayload + updateAudit(input: UpdateAuditInput!): UpdateAuditPayload + deleteAudit(input: DeleteAuditInput!): DeleteAuditPayload + uploadAuditReport(input: UploadAuditReportInput!): UploadAuditReportPayload + deleteAuditReport(input: DeleteAuditReportInput!): DeleteAuditReportPayload + createFinding(input: CreateFindingInput!): CreateFindingPayload + updateFinding(input: UpdateFindingInput!): UpdateFindingPayload + deleteFinding(input: DeleteFindingInput!): DeleteFindingPayload + createFindingAuditMapping( + input: CreateFindingAuditMappingInput! + ): CreateFindingAuditMappingPayload + deleteFindingAuditMapping( + input: DeleteFindingAuditMappingInput! + ): DeleteFindingAuditMappingPayload +} + +input CreateAuditInput { + organizationId: ID! + frameworkId: ID! + name: String + validFrom: Datetime + validUntil: Datetime + state: AuditState + trustCenterVisibility: TrustCenterVisibility + file: Upload +} + +input UpdateAuditInput { + id: ID! + name: String @goField(omittable: true) + validFrom: Datetime + validUntil: Datetime + state: AuditState + trustCenterVisibility: TrustCenterVisibility +} + +input DeleteAuditInput { + auditId: ID! +} + +input UploadAuditReportInput { + auditId: ID! + file: Upload! +} + +input DeleteAuditReportInput { + auditId: ID! +} + +input CreateFindingInput { + organizationId: ID! + kind: FindingKind! + description: String + source: String + identifiedOn: Datetime + rootCause: String + correctiveAction: String + ownerId: ID + dueDate: Datetime + status: FindingStatus! + priority: FindingPriority! + riskId: ID + effectivenessCheck: String +} + +input UpdateFindingInput { + id: ID! + description: String @goField(omittable: true) + source: String @goField(omittable: true) + identifiedOn: Datetime @goField(omittable: true) + rootCause: String @goField(omittable: true) + correctiveAction: String @goField(omittable: true) + ownerId: ID + dueDate: Datetime @goField(omittable: true) + status: FindingStatus + priority: FindingPriority + riskId: ID @goField(omittable: true) + effectivenessCheck: String @goField(omittable: true) +} + +input DeleteFindingInput { + findingId: ID! +} + +input CreateFindingAuditMappingInput { + findingId: ID! + auditId: ID! + referenceId: String! +} + +input DeleteFindingAuditMappingInput { + findingId: ID! + auditId: ID! +} + +type CreateAuditPayload { + auditEdge: AuditEdge +} + +type UpdateAuditPayload { + audit: Audit +} + +type DeleteAuditPayload { + deletedAuditId: ID +} + +type UploadAuditReportPayload { + audit: Audit +} + +type DeleteAuditReportPayload { + audit: Audit +} + +type CreateFindingPayload { + findingEdge: FindingEdge +} + +type UpdateFindingPayload { + finding: Finding +} + +type DeleteFindingPayload { + deletedFindingId: ID +} + +type CreateFindingAuditMappingPayload { + findingEdge: FindingEdge + auditEdge: AuditEdge +} + +type DeleteFindingAuditMappingPayload { + deletedFindingId: ID + deletedAuditId: ID +} diff --git a/pkg/server/api/console/v1/graphql/audit_log.graphql b/pkg/server/api/console/v1/graphql/audit_log.graphql new file mode 100644 index 000000000..d2751a92c --- /dev/null +++ b/pkg/server/api/console/v1/graphql/audit_log.graphql @@ -0,0 +1,81 @@ +extend type Organization { + auditLogEntries( + first: Int + after: CursorKey + last: Int + before: CursorKey + orderBy: AuditLogEntryOrder + filter: AuditLogEntryFilter + ): AuditLogEntryConnection! @goField(forceResolver: true) +} + +enum AuditLogActorType + @goModel( + model: "go.probo.inc/probo/pkg/coredata.AuditLogActorType" + ) { + USER + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.AuditLogActorTypeUser" + ) + API_KEY + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.AuditLogActorTypeAPIKey" + ) + SYSTEM + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.AuditLogActorTypeSystem" + ) +} + +enum AuditLogEntryOrderField + @goModel( + model: "go.probo.inc/probo/pkg/coredata.AuditLogEntryOrderField" + ) { + CREATED_AT + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.AuditLogEntryOrderFieldCreatedAt" + ) +} + +input AuditLogEntryOrder + @goModel( + model: "go.probo.inc/probo/pkg/server/api/console/v1/types.AuditLogEntryOrderBy" + ) { + field: AuditLogEntryOrderField! + direction: OrderDirection! +} + +input AuditLogEntryFilter { + action: String + actorId: ID + resourceType: String + resourceId: ID +} + +type AuditLogEntry implements Node { + id: ID! + organization: Organization @goField(forceResolver: true) + actorId: ID! + actorType: AuditLogActorType! + action: String! + resourceType: String! + resourceId: ID! + metadata: String + createdAt: Datetime! + + permission(action: String!): Boolean! @goField(forceResolver: true) +} + +type AuditLogEntryConnection + @goModel( + model: "go.probo.inc/probo/pkg/server/api/console/v1/types.AuditLogEntryConnection" + ) { + edges: [AuditLogEntryEdge!]! + pageInfo: PageInfo! + totalCount: Int! @goField(forceResolver: true) +} + +type AuditLogEntryEdge { + cursor: CursorKey! + node: AuditLogEntry! +} diff --git a/pkg/server/api/console/v1/graphql/base.graphql b/pkg/server/api/console/v1/graphql/base.graphql new file mode 100644 index 000000000..dd6c8d75f --- /dev/null +++ b/pkg/server/api/console/v1/graphql/base.graphql @@ -0,0 +1,349 @@ +# Directives +directive @goField( + forceResolver: Boolean + name: String + omittable: Boolean +) on INPUT_FIELD_DEFINITION | FIELD_DEFINITION + +directive @goModel( + model: String + models: [String!] +) on OBJECT | INPUT_OBJECT | SCALAR | ENUM | INTERFACE | UNION + +directive @goEnum(value: String) on ENUM_VALUE + +# Scalars +scalar CursorKey +scalar Datetime +scalar Upload +scalar Duration +scalar BigInt +scalar EmailAddr + +# Interfaces +interface Node { + id: ID! +} + +# Pagination +type PageInfo { + hasNextPage: Boolean! + hasPreviousPage: Boolean! + startCursor: CursorKey + endCursor: CursorKey +} + +# Enums +enum OrderDirection + @goModel(model: "go.probo.inc/probo/pkg/page.OrderDirection") { + ASC @goEnum(value: "go.probo.inc/probo/pkg/page.OrderDirectionAsc") + DESC @goEnum(value: "go.probo.inc/probo/pkg/page.OrderDirectionDesc") +} + +enum CountryCode + @goModel(model: "go.probo.inc/probo/pkg/coredata.CountryCode") { + AD @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeAD") + AE @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeAE") + AF @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeAF") + AG @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeAG") + AI @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeAI") + AL @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeAL") + AM @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeAM") + AO @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeAO") + AQ @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeAQ") + AR @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeAR") + AS @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeAS") + AT @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeAT") + AU @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeAU") + AW @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeAW") + AX @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeAX") + AZ @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeAZ") + BA @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeBA") + BB @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeBB") + BD @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeBD") + BE @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeBE") + BF @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeBF") + BG @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeBG") + BH @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeBH") + BI @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeBI") + BJ @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeBJ") + BL @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeBL") + BM @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeBM") + BN @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeBN") + BO @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeBO") + BQ @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeBQ") + BR @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeBR") + BS @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeBS") + BT @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeBT") + BV @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeBV") + BW @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeBW") + BY @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeBY") + BZ @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeBZ") + CA @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeCA") + CC @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeCC") + CD @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeCD") + CF @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeCF") + CG @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeCG") + CH @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeCH") + CI @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeCI") + CK @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeCK") + CL @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeCL") + CM @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeCM") + CN @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeCN") + CO @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeCO") + CR @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeCR") + CU @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeCU") + CV @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeCV") + CW @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeCW") + CX @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeCX") + CY @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeCY") + CZ @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeCZ") + DE @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeDE") + DJ @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeDJ") + DK @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeDK") + DM @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeDM") + DO @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeDO") + DZ @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeDZ") + EC @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeEC") + EE @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeEE") + EG @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeEG") + EH @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeEH") + ER @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeER") + ES @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeES") + ET @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeET") + EU @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeEU") + FI @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeFI") + FJ @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeFJ") + FK @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeFK") + FM @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeFM") + FO @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeFO") + FR @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeFR") + GA @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeGA") + GB @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeGB") + GD @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeGD") + GE @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeGE") + GF @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeGF") + GG @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeGG") + GH @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeGH") + GI @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeGI") + GL @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeGL") + GM @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeGM") + GN @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeGN") + GP @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeGP") + GQ @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeGQ") + GR @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeGR") + GT @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeGT") + GU @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeGU") + GW @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeGW") + GY @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeGY") + HK @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeHK") + HM @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeHM") + HN @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeHN") + HR @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeHR") + HT @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeHT") + HU @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeHU") + ID @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeID") + IE @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeIE") + IL @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeIL") + IM @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeIM") + IN @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeIN") + IO @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeIO") + IQ @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeIQ") + IR @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeIR") + IS @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeIS") + IT @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeIT") + JE @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeJE") + JM @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeJM") + JO @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeJO") + JP @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeJP") + KE @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeKE") + KG @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeKG") + KH @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeKH") + KI @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeKI") + KM @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeKM") + KN @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeKN") + KP @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeKP") + KR @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeKR") + KW @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeKW") + KY @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeKY") + KZ @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeKZ") + LA @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeLA") + LB @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeLB") + LC @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeLC") + LI @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeLI") + LK @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeLK") + LR @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeLR") + LS @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeLS") + LT @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeLT") + LU @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeLU") + LV @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeLV") + LY @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeLY") + MA @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeMA") + MC @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeMC") + MD @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeMD") + ME @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeME") + MF @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeMF") + MG @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeMG") + MH @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeMH") + MK @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeMK") + ML @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeML") + MM @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeMM") + MN @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeMN") + MO @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeMO") + MP @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeMP") + MQ @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeMQ") + MR @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeMR") + MS @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeMS") + MT @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeMT") + MU @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeMU") + MV @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeMV") + MW @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeMW") + MX @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeMX") + MY @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeMY") + MZ @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeMZ") + NA @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeNA") + NC @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeNC") + NE @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeNE") + NF @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeNF") + NG @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeNG") + NI @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeNI") + NL @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeNL") + NO @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeNO") + NP @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeNP") + NR @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeNR") + NU @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeNU") + NZ @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeNZ") + OM @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeOM") + PA @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodePA") + PE @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodePE") + PF @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodePF") + PG @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodePG") + PH @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodePH") + PK @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodePK") + PL @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodePL") + PM @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodePM") + PN @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodePN") + PR @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodePR") + PS @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodePS") + PT @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodePT") + PW @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodePW") + PY @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodePY") + QA @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeQA") + RE @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeRE") + RO @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeRO") + RS @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeRS") + RU @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeRU") + RW @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeRW") + SA @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeSA") + SB @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeSB") + SC @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeSC") + SD @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeSD") + SE @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeSE") + SG @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeSG") + SH @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeSH") + SI @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeSI") + SJ @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeSJ") + SK @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeSK") + SL @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeSL") + SM @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeSM") + SN @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeSN") + SO @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeSO") + SR @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeSR") + SS @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeSS") + ST @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeST") + SV @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeSV") + SX @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeSX") + SY @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeSY") + SZ @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeSZ") + TC @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeTC") + TD @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeTD") + TF @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeTF") + TG @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeTG") + TH @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeTH") + TJ @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeTJ") + TK @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeTK") + TL @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeTL") + TM @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeTM") + TN @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeTN") + TO @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeTO") + TR @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeTR") + TT @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeTT") + TV @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeTV") + TW @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeTW") + TZ @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeTZ") + UA @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeUA") + UG @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeUG") + UM @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeUM") + US @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeUS") + UY @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeUY") + UZ @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeUZ") + VA @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeVA") + VC @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeVC") + VE @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeVE") + VG @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeVG") + VI @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeVI") + VN @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeVN") + VU @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeVU") + WF @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeWF") + WS @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeWS") + YE @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeYE") + YT @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeYT") + ZA @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeZA") + ZM @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeZM") + ZW @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeZW") +} + +type File { + id: ID! + mimeType: String! + fileName: String! + size: BigInt! + downloadUrl: String! @goField(forceResolver: true) + createdAt: Datetime! + updatedAt: Datetime! +} + +# Root Types +type Query { + node(id: ID!): Node! + viewer: Viewer! +} + +type Viewer { + id: ID! +} + +type Organization implements Node { + id: ID! + name: String! + logoUrl: String @goField(forceResolver: true) + horizontalLogoUrl: String @goField(forceResolver: true) + + description: String + websiteUrl: String + email: String + headquarterAddress: String + context: OrganizationContext @goField(forceResolver: true) + + profiles( + first: Int + after: CursorKey + last: Int + before: CursorKey + orderBy: ProfileOrder + filter: ProfileFilter + ): ProfileConnection! @goField(forceResolver: true) + + measureCategories: [String!]! @goField(forceResolver: true) + + createdAt: Datetime! + updatedAt: Datetime! + + permission(action: String!): Boolean! @goField(forceResolver: true) +} + +type Mutation { + updateOrganizationContext( + input: UpdateOrganizationContextInput! + ): UpdateOrganizationContextPayload! +} diff --git a/pkg/server/api/console/v1/graphql/connector.graphql b/pkg/server/api/console/v1/graphql/connector.graphql new file mode 100644 index 000000000..2c7731b5f --- /dev/null +++ b/pkg/server/api/console/v1/graphql/connector.graphql @@ -0,0 +1,148 @@ +extend type Organization { + slackConnections( + first: Int + after: CursorKey + last: Int + before: CursorKey + ): SlackConnectionConnection! @goField(forceResolver: true) + slackOAuth2Scopes: [String!]! @goField(forceResolver: true) + connectors(filter: ConnectorFilter): [Connector!]! @goField(forceResolver: true) + connectorProviderInfos: [ConnectorProviderInfo!]! @goField(forceResolver: true) +} + +enum ConnectorProvider + @goModel(model: "go.probo.inc/probo/pkg/coredata.ConnectorProvider") { + SLACK @goEnum(value: "go.probo.inc/probo/pkg/coredata.ConnectorProviderSlack") + GOOGLE_WORKSPACE + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.ConnectorProviderGoogleWorkspace" + ) + LINEAR @goEnum(value: "go.probo.inc/probo/pkg/coredata.ConnectorProviderLinear") + ONE_PASSWORD + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.ConnectorProviderOnePassword" + ) + HUBSPOT + @goEnum(value: "go.probo.inc/probo/pkg/coredata.ConnectorProviderHubSpot") + DOCUSIGN + @goEnum(value: "go.probo.inc/probo/pkg/coredata.ConnectorProviderDocuSign") + NOTION @goEnum(value: "go.probo.inc/probo/pkg/coredata.ConnectorProviderNotion") + BREX @goEnum(value: "go.probo.inc/probo/pkg/coredata.ConnectorProviderBrex") + TALLY @goEnum(value: "go.probo.inc/probo/pkg/coredata.ConnectorProviderTally") + CLOUDFLARE + @goEnum(value: "go.probo.inc/probo/pkg/coredata.ConnectorProviderCloudflare") + OPENAI @goEnum(value: "go.probo.inc/probo/pkg/coredata.ConnectorProviderOpenAI") + SENTRY @goEnum(value: "go.probo.inc/probo/pkg/coredata.ConnectorProviderSentry") + SUPABASE + @goEnum(value: "go.probo.inc/probo/pkg/coredata.ConnectorProviderSupabase") + GITHUB @goEnum(value: "go.probo.inc/probo/pkg/coredata.ConnectorProviderGitHub") + INTERCOM + @goEnum(value: "go.probo.inc/probo/pkg/coredata.ConnectorProviderIntercom") + RESEND @goEnum(value: "go.probo.inc/probo/pkg/coredata.ConnectorProviderResend") +} + +type ConnectorProviderInfo { + provider: ConnectorProvider! + displayName: String! + oauthConfigured: Boolean! + apiKeySupported: Boolean! + clientCredentialsSupported: Boolean! + oauth2Scopes: [String!]! + extraSettings: [ConnectorProviderSettingInfo!]! +} + +type ConnectorProviderSettingInfo { + key: String! + label: String! + required: Boolean! +} + +input ConnectorFilter { + providers: [ConnectorProvider!] +} + +type Connector { + id: ID! + provider: ConnectorProvider! + oauth2Scopes: [String!]! @goField(forceResolver: true) + createdAt: Datetime! +} + +type SlackConnection implements Node { + id: ID! + channel: String + channelId: String + createdAt: Datetime! + updatedAt: Datetime! + + permission(action: String!): Boolean! @goField(forceResolver: true) +} + +type SlackConnectionConnection { + edges: [SlackConnectionEdge!]! + pageInfo: PageInfo! +} + +type SlackConnectionEdge { + cursor: CursorKey! + node: SlackConnection! +} + +extend type Mutation { + createAPIKeyConnector( + input: CreateAPIKeyConnectorInput! + ): CreateAPIKeyConnectorPayload! + createClientCredentialsConnector( + input: CreateClientCredentialsConnectorInput! + ): CreateClientCredentialsConnectorPayload! + deleteConnector(input: DeleteConnectorInput!): DeleteConnectorPayload! + deleteSlackConnection( + input: DeleteSlackConnectionInput! + ): DeleteSlackConnectionPayload! +} + +input CreateAPIKeyConnectorInput { + organizationId: ID! + provider: ConnectorProvider! + apiKey: String! + tallyOrganizationId: String + sentryOrganizationSlug: String + supabaseOrganizationSlug: String + githubOrganization: String + onePasswordScimBridgeUrl: String +} + +type CreateAPIKeyConnectorPayload { + connector: Connector! +} + +input CreateClientCredentialsConnectorInput { + organizationId: ID! + provider: ConnectorProvider! + clientId: String! + clientSecret: String! + tokenUrl: String! + scope: String + onePasswordAccountId: String + onePasswordRegion: String +} + +type CreateClientCredentialsConnectorPayload { + connector: Connector +} + +input DeleteConnectorInput { + connectorId: ID! +} + +type DeleteConnectorPayload { + deletedConnectorId: ID! +} + +input DeleteSlackConnectionInput { + slackConnectionId: ID! +} + +type DeleteSlackConnectionPayload { + deletedSlackConnectionId: ID! +} diff --git a/pkg/server/api/console/v1/graphql/control.graphql b/pkg/server/api/console/v1/graphql/control.graphql new file mode 100644 index 000000000..f5b1ad5ec --- /dev/null +++ b/pkg/server/api/console/v1/graphql/control.graphql @@ -0,0 +1,506 @@ +extend type Organization { + controls( + first: Int + after: CursorKey + last: Int + before: CursorKey + orderBy: ControlOrder + filter: ControlFilter + ): ControlConnection! @goField(forceResolver: true) + + statementsOfApplicability( + first: Int + after: CursorKey + last: Int + before: CursorKey + orderBy: StatementOfApplicabilityOrder + filter: StatementOfApplicabilityFilter = { snapshotId: null } + ): StatementOfApplicabilityConnection! @goField(forceResolver: true) +} + +enum ControlImplementationState + @goModel( + model: "go.probo.inc/probo/pkg/coredata.ControlImplementationState" + ) { + IMPLEMENTED + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.ControlImplementationStateImplemented" + ) + NOT_IMPLEMENTED + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.ControlImplementationStateNotImplemented" + ) +} + +enum ControlOrderField + @goModel(model: "go.probo.inc/probo/pkg/coredata.ControlOrderField") { + CREATED_AT + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.ControlOrderFieldCreatedAt" + ) + SECTION_TITLE + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.ControlOrderFieldSectionTitle" + ) +} + +enum ApplicabilityStatementOrderField + @goModel(model: "go.probo.inc/probo/pkg/coredata.ApplicabilityStatementOrderField") { + CREATED_AT + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.ApplicabilityStatementOrderFieldCreatedAt" + ) + CONTROL_SECTION_TITLE + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.ApplicabilityStatementOrderFieldControlSectionTitle" + ) +} + +enum StatementOfApplicabilityOrderField + @goModel( + model: "go.probo.inc/probo/pkg/coredata.StatementOfApplicabilityOrderField" + ) { + NAME + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.StatementOfApplicabilityOrderFieldName" + ) + CREATED_AT + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.StatementOfApplicabilityOrderFieldCreatedAt" + ) +} + +input ControlOrder + @goModel( + model: "go.probo.inc/probo/pkg/server/api/console/v1/types.ControlOrderBy" + ) { + direction: OrderDirection! + field: ControlOrderField! +} + +input ApplicabilityStatementOrder + @goModel( + model: "go.probo.inc/probo/pkg/server/api/console/v1/types.ApplicabilityStatementOrderBy" + ) { + direction: OrderDirection! + field: ApplicabilityStatementOrderField! +} + +input StatementOfApplicabilityOrder + @goModel( + model: "go.probo.inc/probo/pkg/server/api/console/v1/types.StatementOfApplicabilityOrderBy" + ) { + direction: OrderDirection! + field: StatementOfApplicabilityOrderField! +} + +input ControlFilter { + query: String +} + +input StatementOfApplicabilityFilter { + snapshotId: ID +} + +type Control implements Node { + id: ID! + organization: Organization @goField(forceResolver: true) + sectionTitle: String! + name: String! + description: String + bestPractice: Boolean! + implemented: ControlImplementationState! + notImplementedJustification: String + regulatory: Boolean! @goField(forceResolver: true) + contractual: Boolean! @goField(forceResolver: true) + riskAssessment: Boolean! @goField(forceResolver: true) + + framework: Framework! @goField(forceResolver: true) + + measures( + first: Int + after: CursorKey + last: Int + before: CursorKey + orderBy: MeasureOrder + filter: MeasureFilter + ): MeasureConnection! @goField(forceResolver: true) + + documents( + first: Int + after: CursorKey + last: Int + before: CursorKey + orderBy: DocumentOrder + filter: DocumentFilter + ): DocumentConnection! @goField(forceResolver: true) + + audits( + first: Int + after: CursorKey + last: Int + before: CursorKey + orderBy: AuditOrder + ): AuditConnection! @goField(forceResolver: true) + + obligations( + first: Int + after: CursorKey + last: Int + before: CursorKey + orderBy: ObligationOrder + filter: ObligationFilter + ): ObligationConnection! @goField(forceResolver: true) + + snapshots( + first: Int + after: CursorKey + last: Int + before: CursorKey + orderBy: SnapshotOrder + ): SnapshotConnection! @goField(forceResolver: true) + + createdAt: Datetime! + updatedAt: Datetime! + + permission(action: String!): Boolean! @goField(forceResolver: true) +} + +type ControlConnection + @goModel( + model: "go.probo.inc/probo/pkg/server/api/console/v1/types.ControlConnection" + ) { + totalCount: Int! @goField(forceResolver: true) + edges: [ControlEdge!]! + pageInfo: PageInfo! +} + +type ControlEdge { + cursor: CursorKey! + node: Control! +} + +type StatementOfApplicability implements Node { + id: ID! + name: String! + sourceId: ID + snapshotId: ID + organization: Organization @goField(forceResolver: true) + owner: Profile! @goField(forceResolver: true) + createdAt: Datetime! + updatedAt: Datetime! + + applicabilityStatements( + first: Int + after: CursorKey + last: Int + before: CursorKey + orderBy: ApplicabilityStatementOrder + ): ApplicabilityStatementConnection! @goField(forceResolver: true) + + permission(action: String!): Boolean! @goField(forceResolver: true) +} + +type ApplicabilityStatementConnection + @goModel( + model: "go.probo.inc/probo/pkg/server/api/console/v1/types.ApplicabilityStatementConnection" + ) { + totalCount: Int! @goField(forceResolver: true) + edges: [ApplicabilityStatementEdge!]! + pageInfo: PageInfo! +} + +type ApplicabilityStatementEdge { + cursor: CursorKey! + node: ApplicabilityStatement! +} + +type ApplicabilityStatement implements Node { + id: ID! + statementOfApplicability: StatementOfApplicability! @goField(forceResolver: true) + control: Control! @goField(forceResolver: true) + applicability: Boolean! + justification: String! + createdAt: Datetime! + updatedAt: Datetime! + + permission(action: String!): Boolean! @goField(forceResolver: true) +} + +type StatementOfApplicabilityConnection + @goModel( + model: "go.probo.inc/probo/pkg/server/api/console/v1/types.StatementOfApplicabilityConnection" + ) { + totalCount: Int! @goField(forceResolver: true) + edges: [StatementOfApplicabilityEdge!]! + pageInfo: PageInfo! +} + +type StatementOfApplicabilityEdge { + cursor: CursorKey! + node: StatementOfApplicability! +} + +extend type Mutation { + createControl(input: CreateControlInput!): CreateControlPayload! + updateControl(input: UpdateControlInput!): UpdateControlPayload! + deleteControl(input: DeleteControlInput!): DeleteControlPayload! + createControlMeasureMapping( + input: CreateControlMeasureMappingInput! + ): CreateControlMeasureMappingPayload! + createControlDocumentMapping( + input: CreateControlDocumentMappingInput! + ): CreateControlDocumentMappingPayload! + deleteControlMeasureMapping( + input: DeleteControlMeasureMappingInput! + ): DeleteControlMeasureMappingPayload! + deleteControlDocumentMapping( + input: DeleteControlDocumentMappingInput! + ): DeleteControlDocumentMappingPayload! + createApplicabilityStatement( + input: CreateApplicabilityStatementInput! + ): CreateApplicabilityStatementPayload! + updateApplicabilityStatement( + input: UpdateApplicabilityStatementInput! + ): UpdateApplicabilityStatementPayload! + deleteApplicabilityStatement( + input: DeleteApplicabilityStatementInput! + ): DeleteApplicabilityStatementPayload! + createControlAuditMapping( + input: CreateControlAuditMappingInput! + ): CreateControlAuditMappingPayload + deleteControlAuditMapping( + input: DeleteControlAuditMappingInput! + ): DeleteControlAuditMappingPayload + createControlObligationMapping( + input: CreateControlObligationMappingInput! + ): CreateControlObligationMappingPayload! + deleteControlObligationMapping( + input: DeleteControlObligationMappingInput! + ): DeleteControlObligationMappingPayload! + createControlSnapshotMapping( + input: CreateControlSnapshotMappingInput! + ): CreateControlSnapshotMappingPayload! + deleteControlSnapshotMapping( + input: DeleteControlSnapshotMappingInput! + ): DeleteControlSnapshotMappingPayload! + createStatementOfApplicability( + input: CreateStatementOfApplicabilityInput! + ): CreateStatementOfApplicabilityPayload! + updateStatementOfApplicability( + input: UpdateStatementOfApplicabilityInput! + ): UpdateStatementOfApplicabilityPayload! + deleteStatementOfApplicability( + input: DeleteStatementOfApplicabilityInput! + ): DeleteStatementOfApplicabilityPayload! + exportStatementOfApplicabilityPDF( + input: ExportStatementOfApplicabilityPDFInput! + ): ExportStatementOfApplicabilityPDFPayload! +} + +input CreateControlInput { + frameworkId: ID! + sectionTitle: String! + name: String! + description: String + bestPractice: Boolean! + implemented: ControlImplementationState! + notImplementedJustification: String +} + +input UpdateControlInput { + id: ID! + sectionTitle: String + name: String + description: String @goField(omittable: true) + bestPractice: Boolean + implemented: ControlImplementationState + notImplementedJustification: String @goField(omittable: true) +} + +input DeleteControlInput { + controlId: ID! +} + +input CreateControlMeasureMappingInput { + controlId: ID! + measureId: ID! +} + +input CreateControlDocumentMappingInput { + controlId: ID! + documentId: ID! +} + +input DeleteControlMeasureMappingInput { + controlId: ID! + measureId: ID! +} + +input DeleteControlDocumentMappingInput { + controlId: ID! + documentId: ID! +} + +input CreateApplicabilityStatementInput { + statementOfApplicabilityId: ID! + controlId: ID! + applicability: Boolean! + justification: String +} + +input UpdateApplicabilityStatementInput { + applicabilityStatementId: ID! + applicability: Boolean! + justification: String +} + +input DeleteApplicabilityStatementInput { + applicabilityStatementId: ID! +} + +input CreateControlAuditMappingInput { + controlId: ID! + auditId: ID! +} + +input DeleteControlAuditMappingInput { + controlId: ID! + auditId: ID! +} + +input CreateControlObligationMappingInput { + controlId: ID! + obligationId: ID! +} + +input DeleteControlObligationMappingInput { + controlId: ID! + obligationId: ID! +} + +input CreateControlSnapshotMappingInput { + controlId: ID! + snapshotId: ID! +} + +input DeleteControlSnapshotMappingInput { + controlId: ID! + snapshotId: ID! +} + +input CreateStatementOfApplicabilityInput { + organizationId: ID! + name: String! + ownerId: ID! +} + +input UpdateStatementOfApplicabilityInput { + id: ID! + name: String + ownerId: ID +} + +input ApplicabilityStatementInput { + controlId: ID! + applicability: Boolean! + justification: String +} + +input DeleteStatementOfApplicabilityInput { + statementOfApplicabilityId: ID! +} + +input ExportStatementOfApplicabilityPDFInput { + statementOfApplicabilityId: ID! +} + +type CreateControlPayload { + controlEdge: ControlEdge! +} + +type UpdateControlPayload { + control: Control! +} + +type DeleteControlPayload { + deletedControlId: ID! +} + +type CreateControlMeasureMappingPayload { + controlEdge: ControlEdge! + measureEdge: MeasureEdge! +} + +type CreateControlDocumentMappingPayload { + controlEdge: ControlEdge! + documentEdge: DocumentEdge! +} + +type DeleteControlMeasureMappingPayload { + deletedControlId: ID! + deletedMeasureId: ID! +} + +type DeleteControlDocumentMappingPayload { + deletedControlId: ID! + deletedDocumentId: ID! +} + +type CreateApplicabilityStatementPayload { + applicabilityStatementEdge: ApplicabilityStatementEdge! +} + +type UpdateApplicabilityStatementPayload { + applicabilityStatement: ApplicabilityStatement! +} + +type DeleteApplicabilityStatementPayload { + deletedApplicabilityStatementId: ID! +} + +type CreateControlAuditMappingPayload { + controlEdge: ControlEdge + auditEdge: AuditEdge +} + +type DeleteControlAuditMappingPayload { + deletedControlId: ID + deletedAuditId: ID +} + +type CreateControlObligationMappingPayload { + controlEdge: ControlEdge! + obligationEdge: ObligationEdge! +} + +type DeleteControlObligationMappingPayload { + deletedControlId: ID! + deletedObligationId: ID! +} + +type CreateControlSnapshotMappingPayload { + controlEdge: ControlEdge! + snapshotEdge: SnapshotEdge! +} + +type DeleteControlSnapshotMappingPayload { + deletedControlId: ID! + deletedSnapshotId: ID! +} + +type CreateStatementOfApplicabilityPayload { + statementOfApplicabilityEdge: StatementOfApplicabilityEdge! +} + +type UpdateStatementOfApplicabilityPayload { + statementOfApplicability: StatementOfApplicability! +} + +type DeleteStatementOfApplicabilityPayload { + deletedStatementOfApplicabilityId: ID! +} + +type ExportStatementOfApplicabilityPDFPayload { + data: String! +} diff --git a/pkg/server/api/console/v1/graphql/data_protection_impact_assessment.graphql b/pkg/server/api/console/v1/graphql/data_protection_impact_assessment.graphql new file mode 100644 index 000000000..1ccd81f8f --- /dev/null +++ b/pkg/server/api/console/v1/graphql/data_protection_impact_assessment.graphql @@ -0,0 +1,252 @@ +extend type Organization { + dataProtectionImpactAssessments( + first: Int + after: CursorKey + last: Int + before: CursorKey + orderBy: DataProtectionImpactAssessmentOrder + filter: DataProtectionImpactAssessmentFilter = { snapshotId: null } + ): DataProtectionImpactAssessmentConnection! @goField(forceResolver: true) + + transferImpactAssessments( + first: Int + after: CursorKey + last: Int + before: CursorKey + orderBy: TransferImpactAssessmentOrder + filter: TransferImpactAssessmentFilter = { snapshotId: null } + ): TransferImpactAssessmentConnection! @goField(forceResolver: true) +} + +enum DataProtectionImpactAssessmentResidualRisk + @goModel( + model: "go.probo.inc/probo/pkg/coredata.DataProtectionImpactAssessmentResidualRisk" + ) { + LOW + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.DataProtectionImpactAssessmentResidualRiskLow" + ) + MEDIUM + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.DataProtectionImpactAssessmentResidualRiskMedium" + ) + HIGH + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.DataProtectionImpactAssessmentResidualRiskHigh" + ) +} + +enum DataProtectionImpactAssessmentOrderField + @goModel( + model: "go.probo.inc/probo/pkg/coredata.DataProtectionImpactAssessmentOrderField" + ) { + CREATED_AT + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.DataProtectionImpactAssessmentOrderFieldCreatedAt" + ) +} + +enum TransferImpactAssessmentOrderField + @goModel( + model: "go.probo.inc/probo/pkg/coredata.TransferImpactAssessmentOrderField" + ) { + CREATED_AT + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.TransferImpactAssessmentOrderFieldCreatedAt" + ) +} + +input DataProtectionImpactAssessmentOrder + @goModel( + model: "go.probo.inc/probo/pkg/server/api/console/v1/types.DataProtectionImpactAssessmentOrderBy" + ) { + direction: OrderDirection! + field: DataProtectionImpactAssessmentOrderField! +} + +input TransferImpactAssessmentOrder + @goModel( + model: "go.probo.inc/probo/pkg/server/api/console/v1/types.TransferImpactAssessmentOrderBy" + ) { + direction: OrderDirection! + field: TransferImpactAssessmentOrderField! +} + +input DataProtectionImpactAssessmentFilter { + snapshotId: ID +} + +input TransferImpactAssessmentFilter { + snapshotId: ID +} + +type DataProtectionImpactAssessment implements Node { + id: ID! + processingActivity: ProcessingActivity! @goField(forceResolver: true) + organization: Organization! @goField(forceResolver: true) + description: String + necessityAndProportionality: String + potentialRisk: String + mitigations: String + residualRisk: DataProtectionImpactAssessmentResidualRisk + createdAt: Datetime! + updatedAt: Datetime! + + permission(action: String!): Boolean! @goField(forceResolver: true) +} + +type TransferImpactAssessment implements Node { + id: ID! + processingActivity: ProcessingActivity! @goField(forceResolver: true) + organization: Organization! @goField(forceResolver: true) + dataSubjects: String + legalMechanism: String + transfer: String + localLawRisk: String + supplementaryMeasures: String + createdAt: Datetime! + updatedAt: Datetime! + + permission(action: String!): Boolean! @goField(forceResolver: true) +} + +type DataProtectionImpactAssessmentConnection + @goModel( + model: "go.probo.inc/probo/pkg/server/api/console/v1/types.DataProtectionImpactAssessmentConnection" + ) { + totalCount: Int! @goField(forceResolver: true) + edges: [DataProtectionImpactAssessmentEdge!]! + pageInfo: PageInfo! +} + +type DataProtectionImpactAssessmentEdge { + cursor: CursorKey! + node: DataProtectionImpactAssessment! +} + +type TransferImpactAssessmentConnection + @goModel( + model: "go.probo.inc/probo/pkg/server/api/console/v1/types.TransferImpactAssessmentConnection" + ) { + totalCount: Int! @goField(forceResolver: true) + edges: [TransferImpactAssessmentEdge!]! + pageInfo: PageInfo! +} + +type TransferImpactAssessmentEdge { + cursor: CursorKey! + node: TransferImpactAssessment! +} + +extend type Mutation { + createDataProtectionImpactAssessment( + input: CreateDataProtectionImpactAssessmentInput! + ): CreateDataProtectionImpactAssessmentPayload! + updateDataProtectionImpactAssessment( + input: UpdateDataProtectionImpactAssessmentInput! + ): UpdateDataProtectionImpactAssessmentPayload! + deleteDataProtectionImpactAssessment( + input: DeleteDataProtectionImpactAssessmentInput! + ): DeleteDataProtectionImpactAssessmentPayload! + createTransferImpactAssessment( + input: CreateTransferImpactAssessmentInput! + ): CreateTransferImpactAssessmentPayload! + updateTransferImpactAssessment( + input: UpdateTransferImpactAssessmentInput! + ): UpdateTransferImpactAssessmentPayload! + deleteTransferImpactAssessment( + input: DeleteTransferImpactAssessmentInput! + ): DeleteTransferImpactAssessmentPayload! + exportDataProtectionImpactAssessmentsPDF( + input: ExportDataProtectionImpactAssessmentsPDFInput! + ): ExportDataProtectionImpactAssessmentsPDFPayload! + exportTransferImpactAssessmentsPDF( + input: ExportTransferImpactAssessmentsPDFInput! + ): ExportTransferImpactAssessmentsPDFPayload! +} + +input CreateDataProtectionImpactAssessmentInput { + processingActivityId: ID! + description: String + necessityAndProportionality: String + potentialRisk: String + mitigations: String + residualRisk: DataProtectionImpactAssessmentResidualRisk +} + +input UpdateDataProtectionImpactAssessmentInput { + id: ID! + description: String @goField(omittable: true) + necessityAndProportionality: String @goField(omittable: true) + potentialRisk: String @goField(omittable: true) + mitigations: String @goField(omittable: true) + residualRisk: DataProtectionImpactAssessmentResidualRisk +} + +input DeleteDataProtectionImpactAssessmentInput { + dataProtectionImpactAssessmentId: ID! +} + +input CreateTransferImpactAssessmentInput { + processingActivityId: ID! + dataSubjects: String + legalMechanism: String + transfer: String + localLawRisk: String + supplementaryMeasures: String +} + +input UpdateTransferImpactAssessmentInput { + id: ID! + dataSubjects: String @goField(omittable: true) + legalMechanism: String @goField(omittable: true) + transfer: String @goField(omittable: true) + localLawRisk: String @goField(omittable: true) + supplementaryMeasures: String @goField(omittable: true) +} + +input DeleteTransferImpactAssessmentInput { + transferImpactAssessmentId: ID! +} + +input ExportDataProtectionImpactAssessmentsPDFInput { + organizationId: ID! + filter: DataProtectionImpactAssessmentFilter +} + +input ExportTransferImpactAssessmentsPDFInput { + organizationId: ID! + filter: TransferImpactAssessmentFilter +} + +type CreateDataProtectionImpactAssessmentPayload { + dataProtectionImpactAssessment: DataProtectionImpactAssessment! +} + +type UpdateDataProtectionImpactAssessmentPayload { + dataProtectionImpactAssessment: DataProtectionImpactAssessment! +} + +type DeleteDataProtectionImpactAssessmentPayload { + deletedDataProtectionImpactAssessmentId: ID! +} + +type CreateTransferImpactAssessmentPayload { + transferImpactAssessment: TransferImpactAssessment! +} + +type UpdateTransferImpactAssessmentPayload { + transferImpactAssessment: TransferImpactAssessment! +} + +type DeleteTransferImpactAssessmentPayload { + deletedTransferImpactAssessmentId: ID! +} + +type ExportDataProtectionImpactAssessmentsPDFPayload { + data: String! +} + +type ExportTransferImpactAssessmentsPDFPayload { + data: String! +} diff --git a/pkg/server/api/console/v1/graphql/document.graphql b/pkg/server/api/console/v1/graphql/document.graphql new file mode 100644 index 000000000..4339f2d86 --- /dev/null +++ b/pkg/server/api/console/v1/graphql/document.graphql @@ -0,0 +1,826 @@ +extend type Organization { + documents( + first: Int + after: CursorKey + last: Int + before: CursorKey + orderBy: DocumentOrder + filter: DocumentFilter + ): DocumentConnection! @goField(forceResolver: true) +} + +extend type Viewer { + signableDocuments( + organizationId: ID! + first: Int + after: CursorKey + last: Int + before: CursorKey + orderBy: DocumentOrder + ): EmployeeDocumentConnection! @goField(forceResolver: true) + + signableDocument(id: ID!): EmployeeDocument @goField(forceResolver: true) + + approvableDocuments( + organizationId: ID! + first: Int + after: CursorKey + last: Int + before: CursorKey + orderBy: DocumentOrder + ): EmployeeDocumentConnection! @goField(forceResolver: true) + + approvableDocument(id: ID!): EmployeeDocument @goField(forceResolver: true) +} + +enum DocumentVersionStatus + @goModel(model: "go.probo.inc/probo/pkg/coredata.DocumentVersionStatus") { + DRAFT + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.DocumentVersionStatusDraft" + ) + PENDING_APPROVAL + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.DocumentVersionStatusPendingApproval" + ) + PUBLISHED + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.DocumentVersionStatusPublished" + ) +} + +enum DocumentStatus + @goModel(model: "go.probo.inc/probo/pkg/coredata.DocumentStatus") { + ACTIVE + @goEnum(value: "go.probo.inc/probo/pkg/coredata.DocumentStatusActive") + ARCHIVED + @goEnum(value: "go.probo.inc/probo/pkg/coredata.DocumentStatusArchived") +} + +enum DocumentType + @goModel(model: "go.probo.inc/probo/pkg/coredata.DocumentType") { + OTHER @goEnum(value: "go.probo.inc/probo/pkg/coredata.DocumentTypeOther") + GOVERNANCE + @goEnum(value: "go.probo.inc/probo/pkg/coredata.DocumentTypeGovernance") + POLICY @goEnum(value: "go.probo.inc/probo/pkg/coredata.DocumentTypePolicy") + PROCEDURE + @goEnum(value: "go.probo.inc/probo/pkg/coredata.DocumentTypeProcedure") + PLAN @goEnum(value: "go.probo.inc/probo/pkg/coredata.DocumentTypePlan") + REGISTER + @goEnum(value: "go.probo.inc/probo/pkg/coredata.DocumentTypeRegister") + RECORD @goEnum(value: "go.probo.inc/probo/pkg/coredata.DocumentTypeRecord") + REPORT @goEnum(value: "go.probo.inc/probo/pkg/coredata.DocumentTypeReport") + TEMPLATE + @goEnum(value: "go.probo.inc/probo/pkg/coredata.DocumentTypeTemplate") +} + +enum DocumentClassification + @goModel(model: "go.probo.inc/probo/pkg/coredata.DocumentClassification") { + PUBLIC + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.DocumentClassificationPublic" + ) + INTERNAL + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.DocumentClassificationInternal" + ) + CONFIDENTIAL + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.DocumentClassificationConfidential" + ) + SECRET + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.DocumentClassificationSecret" + ) +} + +enum DocumentOrderField + @goModel(model: "go.probo.inc/probo/pkg/coredata.DocumentOrderField") { + TITLE + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.DocumentOrderFieldTitle" + ) + CREATED_AT + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.DocumentOrderFieldCreatedAt" + ) + UPDATED_AT + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.DocumentOrderFieldUpdatedAt" + ) + DOCUMENT_TYPE + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.DocumentOrderFieldDocumentType" + ) +} + +enum DocumentVersionOrderField + @goModel( + model: "go.probo.inc/probo/pkg/coredata.DocumentVersionOrderField" + ) { + CREATED_AT + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.DocumentVersionOrderFieldCreatedAt" + ) +} + +enum DocumentVersionSignatureState + @goModel( + model: "go.probo.inc/probo/pkg/coredata.DocumentVersionSignatureState" + ) { + REQUESTED + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.DocumentVersionSignatureStateRequested" + ) + SIGNED + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.DocumentVersionSignatureStateSigned" + ) +} + +enum DocumentVersionSignatureOrderField + @goModel( + model: "go.probo.inc/probo/pkg/coredata.DocumentVersionSignatureOrderField" + ) { + CREATED_AT + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.DocumentVersionSignatureOrderFieldCreatedAt" + ) + SIGNED_AT + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.DocumentVersionSignatureOrderFieldSignedAt" + ) +} + +enum DocumentVersionApprovalDecisionState + @goModel( + model: "go.probo.inc/probo/pkg/coredata.DocumentVersionApprovalDecisionState" + ) { + PENDING + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.DocumentVersionApprovalDecisionStatePending" + ) + APPROVED + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.DocumentVersionApprovalDecisionStateApproved" + ) + REJECTED + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.DocumentVersionApprovalDecisionStateRejected" + ) + VOIDED + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.DocumentVersionApprovalDecisionStateVoided" + ) +} + +enum DocumentVersionApprovalDecisionOrderField + @goModel( + model: "go.probo.inc/probo/pkg/coredata.DocumentVersionApprovalDecisionOrderField" + ) { + CREATED_AT + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.DocumentVersionApprovalDecisionOrderFieldCreatedAt" + ) +} + +enum DocumentVersionApprovalQuorumStatus + @goModel( + model: "go.probo.inc/probo/pkg/coredata.DocumentVersionApprovalQuorumStatus" + ) { + PENDING + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.DocumentVersionApprovalQuorumStatusPending" + ) + APPROVED + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.DocumentVersionApprovalQuorumStatusApproved" + ) + REJECTED + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.DocumentVersionApprovalQuorumStatusRejected" + ) + VOIDED + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.DocumentVersionApprovalQuorumStatusVoided" + ) +} + +enum DocumentVersionApprovalQuorumOrderField + @goModel( + model: "go.probo.inc/probo/pkg/coredata.DocumentVersionApprovalQuorumOrderField" + ) { + CREATED_AT + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.DocumentVersionApprovalQuorumOrderFieldCreatedAt" + ) +} + +input DocumentOrder + @goModel( + model: "go.probo.inc/probo/pkg/server/api/console/v1/types.DocumentOrderBy" + ) { + direction: OrderDirection! + field: DocumentOrderField! +} + +input DocumentVersionOrder + @goModel( + model: "go.probo.inc/probo/pkg/server/api/console/v1/types.DocumentVersionOrderBy" + ) { + direction: OrderDirection! + field: DocumentVersionOrderField! +} + +input DocumentFilter { + query: String + documentTypes: [DocumentType!] + classifications: [DocumentClassification!] + status: [DocumentStatus!] +} + +input DocumentVersionFilter { + statuses: [DocumentVersionStatus!] +} + +input DocumentVersionSignatureOrder { + field: DocumentVersionSignatureOrderField! + direction: OrderDirection! +} + +input DocumentVersionSignatureFilter { + states: [DocumentVersionSignatureState!] + activeContract: Boolean +} + +input DocumentVersionApprovalQuorumOrder { + field: DocumentVersionApprovalQuorumOrderField! + direction: OrderDirection! +} + +input DocumentVersionApprovalDecisionFilter { + states: [DocumentVersionApprovalDecisionState!] +} + +input DocumentVersionApprovalDecisionOrder { + field: DocumentVersionApprovalDecisionOrderField! + direction: OrderDirection! +} + +type Document implements Node { + id: ID! + currentPublishedMajor: Int + currentPublishedMinor: Int + trustCenterVisibility: TrustCenterVisibility! + organization: Organization! @goField(forceResolver: true) + + versions( + first: Int + after: CursorKey + last: Int + before: CursorKey + orderBy: DocumentVersionOrder + filter: DocumentVersionFilter + ): DocumentVersionConnection! @goField(forceResolver: true) + + controls( + first: Int + after: CursorKey + last: Int + before: CursorKey + orderBy: ControlOrder + filter: ControlFilter + ): ControlConnection! @goField(forceResolver: true) + + defaultApprovers: [Profile!]! @goField(forceResolver: true) + + status: DocumentStatus! + archivedAt: Datetime + + createdAt: Datetime! + updatedAt: Datetime! + + permission(action: String!): Boolean! @goField(forceResolver: true) +} + +type DocumentVersion implements Node { + id: ID! + document: Document! @goField(forceResolver: true) + status: DocumentVersionStatus! + major: Int! + minor: Int! + content: String! + changelog: String! + title: String! + classification: DocumentClassification! + documentType: DocumentType! + approvers( + first: Int + after: CursorKey + last: Int + before: CursorKey + orderBy: ProfileOrder + ): ProfileConnection! @goField(forceResolver: true) + + signatures( + first: Int + after: CursorKey + last: Int + before: CursorKey + orderBy: DocumentVersionSignatureOrder + filter: DocumentVersionSignatureFilter + ): DocumentVersionSignatureConnection! @goField(forceResolver: true) + + approvalQuorums( + first: Int + after: CursorKey + last: Int + before: CursorKey + orderBy: DocumentVersionApprovalQuorumOrder + ): DocumentVersionApprovalQuorumConnection! @goField(forceResolver: true) + + signed: Boolean! @goField(forceResolver: true) + + publishedAt: Datetime + createdAt: Datetime! + updatedAt: Datetime! + + permission(action: String!): Boolean! @goField(forceResolver: true) +} + +type DocumentVersionSignature implements Node { + id: ID! + documentVersion: DocumentVersion! @goField(forceResolver: true) + state: DocumentVersionSignatureState! + signedBy: Profile! @goField(forceResolver: true) + signedAt: Datetime + requestedAt: Datetime! + createdAt: Datetime! + updatedAt: Datetime! + + permission(action: String!): Boolean! @goField(forceResolver: true) +} + +type DocumentVersionApprovalDecision implements Node { + id: ID! + quorum: DocumentVersionApprovalQuorum! @goField(forceResolver: true) + documentVersion: DocumentVersion! @goField(forceResolver: true) + approver: Profile! @goField(forceResolver: true) + state: DocumentVersionApprovalDecisionState! + comment: String + decidedAt: Datetime + createdAt: Datetime! + updatedAt: Datetime! + permission(action: String!): Boolean! @goField(forceResolver: true) +} + +type DocumentVersionApprovalQuorum implements Node { + id: ID! + documentVersion: DocumentVersion! @goField(forceResolver: true) + status: DocumentVersionApprovalQuorumStatus! + decisions( + first: Int + after: CursorKey + last: Int + before: CursorKey + orderBy: DocumentVersionApprovalDecisionOrder + filter: DocumentVersionApprovalDecisionFilter + ): DocumentVersionApprovalDecisionConnection! @goField(forceResolver: true) + createdAt: Datetime! + updatedAt: Datetime! + permission(action: String!): Boolean! @goField(forceResolver: true) +} + +type EmployeeDocument + @goModel( + model: "go.probo.inc/probo/pkg/server/api/console/v1/types.EmployeeDocument" + ) { + id: ID! + title: String! + signed: Boolean @goField(forceResolver: true) + approvalState: DocumentVersionApprovalDecisionState @goField(forceResolver: true) + + versions( + first: Int + after: CursorKey + last: Int + before: CursorKey + orderBy: DocumentVersionOrder + ): EmployeeDocumentVersionConnection! @goField(forceResolver: true) + + createdAt: Datetime! + updatedAt: Datetime! +} + +type EmployeeDocumentVersion + @goModel( + model: "go.probo.inc/probo/pkg/server/api/console/v1/types.EmployeeDocumentVersion" + ) { + id: ID! + major: Int! + minor: Int! + status: DocumentVersionStatus! + classification: DocumentClassification! + documentType: DocumentType! + signed: Boolean! @goField(forceResolver: true) + approvalDecision: DocumentVersionApprovalDecision @goField(forceResolver: true) + publishedAt: Datetime + createdAt: Datetime! + updatedAt: Datetime! +} + +type DocumentConnection + @goModel( + model: "go.probo.inc/probo/pkg/server/api/console/v1/types.DocumentConnection" + ) { + totalCount: Int! @goField(forceResolver: true) + edges: [DocumentEdge!]! + pageInfo: PageInfo! +} + +type DocumentEdge { + cursor: CursorKey! + node: Document! +} + +type DocumentVersionConnection + @goModel( + model: "go.probo.inc/probo/pkg/server/api/console/v1/types.DocumentVersionConnection" + ) { + edges: [DocumentVersionEdge!]! + pageInfo: PageInfo! + totalCount: Int! @goField(forceResolver: true) +} + +type DocumentVersionEdge { + cursor: CursorKey! + node: DocumentVersion! +} + +type DocumentVersionSignatureConnection + @goModel( + model: "go.probo.inc/probo/pkg/server/api/console/v1/types.DocumentVersionSignatureConnection" + ) { + edges: [DocumentVersionSignatureEdge!]! + pageInfo: PageInfo! + totalCount: Int! @goField(forceResolver: true) +} + +type DocumentVersionSignatureEdge { + cursor: CursorKey! + node: DocumentVersionSignature! +} + +type DocumentVersionApprovalQuorumConnection + @goModel( + model: "go.probo.inc/probo/pkg/server/api/console/v1/types.DocumentVersionApprovalQuorumConnection" + ) { + edges: [DocumentVersionApprovalQuorumEdge!]! + pageInfo: PageInfo! + totalCount: Int! @goField(forceResolver: true) +} + +type DocumentVersionApprovalQuorumEdge { + cursor: CursorKey! + node: DocumentVersionApprovalQuorum! +} + +type DocumentVersionApprovalDecisionConnection + @goModel( + model: "go.probo.inc/probo/pkg/server/api/console/v1/types.DocumentVersionApprovalDecisionConnection" + ) { + edges: [DocumentVersionApprovalDecisionEdge!]! + pageInfo: PageInfo! + totalCount: Int! @goField(forceResolver: true) +} + +type DocumentVersionApprovalDecisionEdge { + cursor: CursorKey! + node: DocumentVersionApprovalDecision! +} + +type EmployeeDocumentConnection + @goModel( + model: "go.probo.inc/probo/pkg/server/api/console/v1/types.EmployeeDocumentConnection" + ) { + edges: [EmployeeDocumentEdge!]! + pageInfo: PageInfo! +} + +type EmployeeDocumentEdge + @goModel( + model: "go.probo.inc/probo/pkg/server/api/console/v1/types.EmployeeDocumentEdge" + ) { + cursor: CursorKey! + node: EmployeeDocument! +} + +type EmployeeDocumentVersionConnection + @goModel( + model: "go.probo.inc/probo/pkg/server/api/console/v1/types.EmployeeDocumentVersionConnection" + ) { + edges: [EmployeeDocumentVersionEdge!]! + pageInfo: PageInfo! +} + +type EmployeeDocumentVersionEdge + @goModel( + model: "go.probo.inc/probo/pkg/server/api/console/v1/types.EmployeeDocumentVersionEdge" + ) { + cursor: CursorKey! + node: EmployeeDocumentVersion! +} + +extend type Mutation { + createDocument(input: CreateDocumentInput!): CreateDocumentPayload! + updateDocument(input: UpdateDocumentInput!): UpdateDocumentPayload! + deleteDocumentDraft(input: DeleteDocumentDraftInput!): DeleteDocumentDraftPayload! + archiveDocument(input: ArchiveDocumentInput!): ArchiveDocumentPayload! + unarchiveDocument(input: UnarchiveDocumentInput!): UnarchiveDocumentPayload! + deleteDocument(input: DeleteDocumentInput!): DeleteDocumentPayload! + publishMajorDocumentVersion( + input: PublishMajorDocumentVersionInput! + ): PublishDocumentVersionPayload! + publishMinorDocumentVersion( + input: PublishMinorDocumentVersionInput! + ): PublishDocumentVersionPayload! + bulkPublishMajorDocumentVersions( + input: BulkPublishDocumentVersionsInput! + ): BulkPublishDocumentVersionsPayload! + bulkPublishMinorDocumentVersions( + input: BulkPublishDocumentVersionsInput! + ): BulkPublishDocumentVersionsPayload! + requestDocumentVersionApproval( + input: RequestDocumentVersionApprovalInput! + ): RequestDocumentVersionApprovalPayload! + voidDocumentVersionApproval( + input: VoidDocumentVersionApprovalInput! + ): VoidDocumentVersionApprovalPayload! + bulkDeleteDocuments( + input: BulkDeleteDocumentsInput! + ): BulkDeleteDocumentsPayload! + bulkArchiveDocuments( + input: BulkArchiveDocumentsInput! + ): BulkArchiveDocumentsPayload! + bulkUnarchiveDocuments( + input: BulkUnarchiveDocumentsInput! + ): BulkUnarchiveDocumentsPayload! + bulkExportDocuments( + input: BulkExportDocumentsInput! + ): BulkExportDocumentsPayload! + generateDocumentChangelog( + input: GenerateDocumentChangelogInput! + ): GenerateDocumentChangelogPayload! + requestSignature(input: RequestSignatureInput!): RequestSignaturePayload! + bulkRequestSignatures( + input: BulkRequestSignaturesInput! + ): BulkRequestSignaturesPayload! + sendSigningNotifications( + input: SendSigningNotificationsInput! + ): SendSigningNotificationsPayload! + cancelSignatureRequest( + input: CancelSignatureRequestInput! + ): CancelSignatureRequestPayload! + signDocument(input: SignDocumentInput!): SignDocumentPayload! + approveDocumentVersion( + input: ApproveDocumentVersionInput! + ): ApproveDocumentVersionPayload! + rejectDocumentVersion( + input: RejectDocumentVersionInput! + ): RejectDocumentVersionPayload! + exportDocumentVersionPDF( + input: ExportDocumentVersionPDFInput! + ): ExportDocumentVersionPDFPayload! + exportEmployeeDocumentVersionPDF( + input: ExportEmployeeDocumentVersionPDFInput! + ): ExportEmployeeDocumentVersionPDFPayload! +} + +input CreateDocumentInput { + organizationId: ID! + title: String! + content: String + documentType: DocumentType! + classification: DocumentClassification! + trustCenterVisibility: TrustCenterVisibility + defaultApproverIds: [ID!] +} + +input UpdateDocumentInput { + id: ID! + title: String + content: String + classification: DocumentClassification + documentType: DocumentType + trustCenterVisibility: TrustCenterVisibility + defaultApproverIds: [ID!] +} + +input DeleteDocumentDraftInput { + documentId: ID! +} + +input ArchiveDocumentInput { + documentId: ID! +} + +input UnarchiveDocumentInput { + documentId: ID! +} + +input DeleteDocumentInput { + documentId: ID! +} + +input ExportDocumentVersionPDFInput { + documentVersionId: ID! + withWatermark: Boolean! + watermarkEmail: EmailAddr + withSignatures: Boolean! +} + +input ExportEmployeeDocumentVersionPDFInput { + documentVersionId: ID! +} + +input PublishMajorDocumentVersionInput { + documentId: ID! + changelog: String +} + +input PublishMinorDocumentVersionInput { + documentId: ID! + changelog: String +} + +input BulkPublishDocumentVersionsInput { + documentIds: [ID!]! + changelog: String! +} + +input RequestDocumentVersionApprovalInput { + documentId: ID! + approverIds: [ID!]! + changelog: String +} + +input VoidDocumentVersionApprovalInput { + documentVersionId: ID! +} + +input BulkDeleteDocumentsInput { + documentIds: [ID!]! +} + +input BulkArchiveDocumentsInput { + documentIds: [ID!]! +} + +input BulkUnarchiveDocumentsInput { + documentIds: [ID!]! +} + +input BulkExportDocumentsInput { + documentIds: [ID!]! + withWatermark: Boolean! + watermarkEmail: EmailAddr + withSignatures: Boolean! +} + +input GenerateDocumentChangelogInput { + documentId: ID! +} + +input RequestSignatureInput { + documentVersionId: ID! + signatoryId: ID! +} + +input BulkRequestSignaturesInput { + documentIds: [ID!]! + signatoryIds: [ID!]! +} + +input SendSigningNotificationsInput { + organizationId: ID! +} + +input CancelSignatureRequestInput { + documentVersionSignatureId: ID! +} + +input SignDocumentInput { + documentVersionId: ID! +} + +input ApproveDocumentVersionInput { + documentVersionId: ID! + comment: String +} + +input RejectDocumentVersionInput { + documentVersionId: ID! + comment: String +} + +type CreateDocumentPayload { + documentEdge: DocumentEdge! + documentVersionEdge: DocumentVersionEdge! +} + +type UpdateDocumentPayload { + document: Document! + documentVersion: DocumentVersion + documentVersionEdge: DocumentVersionEdge +} + +type DeleteDocumentDraftPayload { + document: Document! +} + +type ArchiveDocumentPayload { + document: Document! +} + +type UnarchiveDocumentPayload { + document: Document! +} + +type DeleteDocumentPayload { + deletedDocumentId: ID! +} + +type ExportDocumentVersionPDFPayload { + data: String! +} + +type ExportEmployeeDocumentVersionPDFPayload { + data: String! +} + +type PublishDocumentVersionPayload { + document: Document! + documentVersion: DocumentVersion! +} + +type BulkPublishDocumentVersionsPayload { + documentVersions: [DocumentVersion!]! + documents: [Document!]! +} + +type RequestDocumentVersionApprovalPayload { + approvalQuorum: DocumentVersionApprovalQuorum! +} + +type VoidDocumentVersionApprovalPayload { + approvalQuorum: DocumentVersionApprovalQuorum! + documentVersion: DocumentVersion! +} + +type BulkDeleteDocumentsPayload { + deletedDocumentIds: [ID!]! +} + +type BulkArchiveDocumentsPayload { + documents: [Document!]! +} + +type BulkUnarchiveDocumentsPayload { + documents: [Document!]! +} + +type BulkExportDocumentsPayload { + exportJobId: ID! +} + +type RequestSignaturePayload { + documentVersionSignatureEdge: DocumentVersionSignatureEdge! +} + +type BulkRequestSignaturesPayload { + documentVersionSignatureEdges: [DocumentVersionSignatureEdge!]! +} + +type SendSigningNotificationsPayload { + success: Boolean! +} + +type CancelSignatureRequestPayload { + deletedDocumentVersionSignatureId: ID! +} + +type SignDocumentPayload { + documentVersionSignature: DocumentVersionSignature! +} + +type ApproveDocumentVersionPayload { + approvalDecision: DocumentVersionApprovalDecision! +} + +type RejectDocumentVersionPayload { + approvalDecision: DocumentVersionApprovalDecision! +} + +type GenerateDocumentChangelogPayload { + changelog: String! +} diff --git a/pkg/server/api/console/v1/graphql/electronic_signature.graphql b/pkg/server/api/console/v1/graphql/electronic_signature.graphql new file mode 100644 index 000000000..fa781e5bf --- /dev/null +++ b/pkg/server/api/console/v1/graphql/electronic_signature.graphql @@ -0,0 +1,143 @@ +enum ElectronicSignatureStatus + @goModel( + model: "go.probo.inc/probo/pkg/coredata.ElectronicSignatureStatus" + ) { + PENDING + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.ElectronicSignatureStatusPending" + ) + ACCEPTED + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.ElectronicSignatureStatusAccepted" + ) + PROCESSING + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.ElectronicSignatureStatusProcessing" + ) + COMPLETED + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.ElectronicSignatureStatusCompleted" + ) + FAILED + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.ElectronicSignatureStatusFailed" + ) +} + +enum ElectronicSignatureDocumentType + @goModel( + model: "go.probo.inc/probo/pkg/coredata.ElectronicSignatureDocumentType" + ) { + NDA + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.ElectronicSignatureDocumentTypeNDA" + ) + DPA + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.ElectronicSignatureDocumentTypeDPA" + ) + MSA + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.ElectronicSignatureDocumentTypeMSA" + ) + SOW + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.ElectronicSignatureDocumentTypeSOW" + ) + SLA + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.ElectronicSignatureDocumentTypeSLA" + ) + TOS + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.ElectronicSignatureDocumentTypeTOS" + ) + PRIVACY_POLICY + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.ElectronicSignatureDocumentTypePrivacyPolicy" + ) + OTHER + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.ElectronicSignatureDocumentTypeOther" + ) +} + +enum ElectronicSignatureEventType + @goModel( + model: "go.probo.inc/probo/pkg/coredata.ElectronicSignatureEventType" + ) { + DOCUMENT_VIEWED + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.ElectronicSignatureEventTypeDocumentViewed" + ) + CONSENT_GIVEN + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.ElectronicSignatureEventTypeConsentGiven" + ) + FULL_NAME_TYPED + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.ElectronicSignatureEventTypeFullNameTyped" + ) + SIGNATURE_ACCEPTED + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.ElectronicSignatureEventTypeSignatureAccepted" + ) + SIGNATURE_COMPLETED + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.ElectronicSignatureEventTypeSignatureCompleted" + ) + SEAL_COMPUTED + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.ElectronicSignatureEventTypeSealComputed" + ) + TIMESTAMP_REQUESTED + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.ElectronicSignatureEventTypeTimestampRequested" + ) + CERTIFICATE_GENERATED + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.ElectronicSignatureEventTypeCertificateGenerated" + ) + PROCESSING_ERROR + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.ElectronicSignatureEventTypeProcessingError" + ) +} + +enum ElectronicSignatureEventSource + @goModel( + model: "go.probo.inc/probo/pkg/coredata.ElectronicSignatureEventSource" + ) { + CLIENT + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.ElectronicSignatureEventSourceClient" + ) + SERVER + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.ElectronicSignatureEventSourceServer" + ) +} + +type ElectronicSignature implements Node { + id: ID! + status: ElectronicSignatureStatus! + documentType: ElectronicSignatureDocumentType! + consentText: String! + lastError: String + signedAt: Datetime + certificateFileUrl: String @goField(forceResolver: true) + events: [ElectronicSignatureEvent!]! @goField(forceResolver: true) + createdAt: Datetime! + updatedAt: Datetime! +} + +type ElectronicSignatureEvent { + id: ID! + eventType: ElectronicSignatureEventType! + eventSource: ElectronicSignatureEventSource! + actorEmail: String! + actorIpAddress: String! + actorUserAgent: String! + occurredAt: Datetime! + createdAt: Datetime! +} diff --git a/pkg/server/api/console/v1/graphql/evidence.graphql b/pkg/server/api/console/v1/graphql/evidence.graphql new file mode 100644 index 000000000..b5fc2c505 --- /dev/null +++ b/pkg/server/api/console/v1/graphql/evidence.graphql @@ -0,0 +1,92 @@ +extend type Organization { + evidences( + first: Int + after: CursorKey + last: Int + before: CursorKey + orderBy: EvidenceOrder + ): EvidenceConnection! @goField(forceResolver: true) +} + +enum EvidenceState + @goModel(model: "go.probo.inc/probo/pkg/coredata.EvidenceState") { + FULFILLED + @goEnum(value: "go.probo.inc/probo/pkg/coredata.EvidenceStateFulfilled") + REQUESTED + @goEnum(value: "go.probo.inc/probo/pkg/coredata.EvidenceStateRequested") +} + +enum EvidenceType + @goModel(model: "go.probo.inc/probo/pkg/coredata.EvidenceType") { + FILE @goEnum(value: "go.probo.inc/probo/pkg/coredata.EvidenceTypeFile") + LINK @goEnum(value: "go.probo.inc/probo/pkg/coredata.EvidenceTypeLink") +} + +enum EvidenceOrderField + @goModel(model: "go.probo.inc/probo/pkg/coredata.EvidenceOrderField") { + CREATED_AT +} + +input EvidenceOrder + @goModel( + model: "go.probo.inc/probo/pkg/server/api/console/v1/types.EvidenceOrderBy" + ) { + direction: OrderDirection! + field: EvidenceOrderField! +} + +type Evidence implements Node { + id: ID! + size: Int! + state: EvidenceState! + type: EvidenceType! + file: File @goField(forceResolver: true) + url: String + description: String + + task: Task @goField(forceResolver: true) + measure: Measure! @goField(forceResolver: true) + + createdAt: Datetime! + updatedAt: Datetime! + + permission(action: String!): Boolean! @goField(forceResolver: true) +} + +type EvidenceConnection + @goModel( + model: "go.probo.inc/probo/pkg/server/api/console/v1/types.EvidenceConnection" + ) { + totalCount: Int! @goField(forceResolver: true) + edges: [EvidenceEdge!]! + pageInfo: PageInfo! +} + +type EvidenceEdge { + cursor: CursorKey! + node: Evidence! +} + +extend type Mutation { + deleteEvidence(input: DeleteEvidenceInput!): DeleteEvidencePayload! + uploadMeasureEvidence( + input: UploadMeasureEvidenceInput! + ): UploadMeasureEvidencePayload! +} + +input DeleteEvidenceInput { + evidenceId: ID! +} + +input UploadMeasureEvidenceInput { + measureId: ID! + file: Upload! +} + +type DeleteEvidencePayload { + deletedEvidenceId: ID! +} + +type UploadMeasureEvidencePayload { + evidenceEdge: EvidenceEdge! +} diff --git a/pkg/server/api/console/v1/graphql/framework.graphql b/pkg/server/api/console/v1/graphql/framework.graphql new file mode 100644 index 000000000..f07382e7f --- /dev/null +++ b/pkg/server/api/console/v1/graphql/framework.graphql @@ -0,0 +1,116 @@ +extend type Organization { + frameworks( + first: Int + after: CursorKey + last: Int + before: CursorKey + orderBy: FrameworkOrder + ): FrameworkConnection! @goField(forceResolver: true) +} + +enum FrameworkOrderField + @goModel(model: "go.probo.inc/probo/pkg/coredata.FrameworkOrderField") { + CREATED_AT + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.FrameworkOrderFieldCreatedAt" + ) +} + +input FrameworkOrder + @goModel( + model: "go.probo.inc/probo/pkg/server/api/console/v1/types.FrameworkOrderBy" + ) { + direction: OrderDirection! + field: FrameworkOrderField! +} + +type Framework implements Node { + id: ID! + name: String! + description: String + + organization: Organization! @goField(forceResolver: true) + + controls( + first: Int + after: CursorKey + last: Int + before: CursorKey + orderBy: ControlOrder + filter: ControlFilter + ): ControlConnection! @goField(forceResolver: true) + lightLogoURL: String @goField(forceResolver: true) + darkLogoURL: String @goField(forceResolver: true) + + createdAt: Datetime! + updatedAt: Datetime! + + permission(action: String!): Boolean! @goField(forceResolver: true) +} + +type FrameworkConnection + @goModel( + model: "go.probo.inc/probo/pkg/server/api/console/v1/types.FrameworkConnection" + ) { + totalCount: Int! @goField(forceResolver: true) + edges: [FrameworkEdge!]! + pageInfo: PageInfo! +} + +type FrameworkEdge { + cursor: CursorKey! + node: Framework! +} + +extend type Mutation { + createFramework(input: CreateFrameworkInput!): CreateFrameworkPayload! + updateFramework(input: UpdateFrameworkInput!): UpdateFrameworkPayload! + importFramework(input: ImportFrameworkInput!): ImportFrameworkPayload! + deleteFramework(input: DeleteFrameworkInput!): DeleteFrameworkPayload! + exportFramework(input: ExportFrameworkInput!): ExportFrameworkPayload! +} + +input CreateFrameworkInput { + organizationId: ID! + name: String! + description: String +} + +input UpdateFrameworkInput { + id: ID! + name: String + description: String @goField(omittable: true) +} + +input ImportFrameworkInput { + organizationId: ID! + file: Upload! +} + +input DeleteFrameworkInput { + frameworkId: ID! +} + +input ExportFrameworkInput { + frameworkId: ID! +} + +type CreateFrameworkPayload { + frameworkEdge: FrameworkEdge! +} + +type UpdateFrameworkPayload { + framework: Framework! +} + +type ImportFrameworkPayload { + frameworkEdge: FrameworkEdge! +} + +type DeleteFrameworkPayload { + deletedFrameworkId: ID! +} + +type ExportFrameworkPayload { + exportJobId: ID! +} diff --git a/pkg/server/api/console/v1/graphql/mailing_list.graphql b/pkg/server/api/console/v1/graphql/mailing_list.graphql new file mode 100644 index 000000000..e1a93d0e0 --- /dev/null +++ b/pkg/server/api/console/v1/graphql/mailing_list.graphql @@ -0,0 +1,188 @@ +enum MailingListSubscriberStatus + @goModel( + model: "go.probo.inc/probo/pkg/coredata.MailingListSubscriberStatus" + ) { + PENDING + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.MailingListSubscriberStatusPending" + ) + CONFIRMED + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.MailingListSubscriberStatusConfirmed" + ) +} + +enum MailingListUpdateStatus + @goModel( + model: "go.probo.inc/probo/pkg/coredata.MailingListUpdateStatus" + ) { + DRAFT + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.MailingListUpdateStatusDraft" + ) + ENQUEUED + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.MailingListUpdateStatusEnqueued" + ) + PROCESSING + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.MailingListUpdateStatusProcessing" + ) + SENT + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.MailingListUpdateStatusSent" + ) +} + +type MailingList implements Node { + id: ID! + replyTo: EmailAddr + + subscribers( + first: Int + after: CursorKey + last: Int + before: CursorKey + ): MailingListSubscriberConnection! @goField(forceResolver: true) + + updates( + first: Int + after: CursorKey + last: Int + before: CursorKey + ): MailingListUpdateConnection! @goField(forceResolver: true) +} + +type MailingListSubscriber implements Node { + id: ID! + fullName: String! + email: EmailAddr! + status: MailingListSubscriberStatus! + createdAt: Datetime! + updatedAt: Datetime! +} + +type MailingListSubscriberConnection + @goModel( + model: "go.probo.inc/probo/pkg/server/api/console/v1/types.MailingListSubscriberConnection" + ) { + edges: [MailingListSubscriberEdge!]! + pageInfo: PageInfo! + totalCount: Int! @goField(forceResolver: true) +} + +type MailingListSubscriberEdge { + cursor: CursorKey! + node: MailingListSubscriber! +} + +type MailingListUpdate implements Node { + id: ID! + title: String! + body: String! + status: MailingListUpdateStatus! + createdAt: Datetime! + updatedAt: Datetime! +} + +type MailingListUpdateConnection + @goModel( + model: "go.probo.inc/probo/pkg/server/api/console/v1/types.MailingListUpdateConnection" + ) { + edges: [MailingListUpdateEdge!]! + pageInfo: PageInfo! + totalCount: Int! @goField(forceResolver: true) +} + +type MailingListUpdateEdge { + cursor: CursorKey! + node: MailingListUpdate! +} + +extend type Mutation { + createMailingListUpdate( + input: CreateMailingListUpdateInput! + ): CreateMailingListUpdatePayload! + updateMailingListUpdate( + input: UpdateMailingListUpdateInput! + ): UpdateMailingListUpdatePayload! + sendMailingListUpdate( + input: SendMailingListUpdateInput! + ): SendMailingListUpdatePayload! + deleteMailingListUpdate( + input: DeleteMailingListUpdateInput! + ): DeleteMailingListUpdatePayload! + updateMailingList( + input: UpdateMailingListInput! + ): UpdateMailingListPayload! + createMailingListSubscriber( + input: CreateMailingListSubscriberInput! + ): CreateMailingListSubscriberPayload! + deleteMailingListSubscriber( + input: DeleteMailingListSubscriberInput! + ): DeleteMailingListSubscriberPayload! +} + +input UpdateMailingListInput { + id: ID! + replyTo: EmailAddr +} + +type UpdateMailingListPayload { + mailingList: MailingList! +} + +input CreateMailingListUpdateInput { + mailingListId: ID! + title: String! + body: String! +} + +input UpdateMailingListUpdateInput { + id: ID! + title: String + body: String +} + +input SendMailingListUpdateInput { + id: ID! +} + +input DeleteMailingListUpdateInput { + id: ID! +} + +input CreateMailingListSubscriberInput { + mailingListId: ID! + fullName: String! + email: EmailAddr! + confirmed: Boolean +} + +input DeleteMailingListSubscriberInput { + id: ID! +} + +type CreateMailingListUpdatePayload { + mailingListUpdate: MailingListUpdate! +} + +type UpdateMailingListUpdatePayload { + mailingListUpdate: MailingListUpdate! +} + +type SendMailingListUpdatePayload { + mailingListUpdate: MailingListUpdate! +} + +type DeleteMailingListUpdatePayload { + deletedMailingListUpdateId: ID! +} + +type CreateMailingListSubscriberPayload { + mailingListSubscriberEdge: MailingListSubscriberEdge! +} + +type DeleteMailingListSubscriberPayload { + deletedMailingListSubscriberId: ID! +} diff --git a/pkg/server/api/console/v1/graphql/measure.graphql b/pkg/server/api/console/v1/graphql/measure.graphql new file mode 100644 index 000000000..71072ac33 --- /dev/null +++ b/pkg/server/api/console/v1/graphql/measure.graphql @@ -0,0 +1,200 @@ +extend type Organization { + measures( + first: Int + after: CursorKey + last: Int + before: CursorKey + orderBy: MeasureOrder + filter: MeasureFilter + ): MeasureConnection! @goField(forceResolver: true) +} + +enum MeasureState + @goModel(model: "go.probo.inc/probo/pkg/coredata.MeasureState") { + NOT_STARTED + @goEnum(value: "go.probo.inc/probo/pkg/coredata.MeasureStateNotStarted") + IN_PROGRESS + @goEnum(value: "go.probo.inc/probo/pkg/coredata.MeasureStateInProgress") + NOT_APPLICABLE + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.MeasureStateNotApplicable" + ) + IMPLEMENTED + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.MeasureStateImplemented" + ) + UNKNOWN + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.MeasureStateUnknown" + ) + NOT_IMPLEMENTED + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.MeasureStateNotImplemented" + ) +} + +enum MeasureOrderField + @goModel(model: "go.probo.inc/probo/pkg/coredata.MeasureOrderField") { + CREATED_AT + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.MeasureOrderFieldCreatedAt" + ) + NAME @goEnum(value: "go.probo.inc/probo/pkg/coredata.MeasureOrderFieldName") +} + +input MeasureOrder + @goModel( + model: "go.probo.inc/probo/pkg/server/api/console/v1/types.MeasureOrderBy" + ) { + direction: OrderDirection! + field: MeasureOrderField! +} + +input MeasureFilter { + query: String + state: MeasureState + category: String +} + +type Measure implements Node { + id: ID! + category: String! + name: String! + description: String + state: MeasureState! + + evidences( + first: Int + after: CursorKey + last: Int + before: CursorKey + orderBy: EvidenceOrder + ): EvidenceConnection! @goField(forceResolver: true) + + tasks( + first: Int + after: CursorKey + last: Int + before: CursorKey + orderBy: TaskOrder + ): TaskConnection! @goField(forceResolver: true) + + risks( + first: Int + after: CursorKey + last: Int + before: CursorKey + orderBy: RiskOrder + filter: RiskFilter + ): RiskConnection! @goField(forceResolver: true) + + controls( + first: Int + after: CursorKey + last: Int + before: CursorKey + orderBy: ControlOrder + filter: ControlFilter + ): ControlConnection! @goField(forceResolver: true) + + documents( + first: Int + after: CursorKey + last: Int + before: CursorKey + orderBy: DocumentOrder + filter: DocumentFilter + ): DocumentConnection! @goField(forceResolver: true) + + createdAt: Datetime! + updatedAt: Datetime! + + permission(action: String!): Boolean! @goField(forceResolver: true) +} + +type MeasureConnection + @goModel( + model: "go.probo.inc/probo/pkg/server/api/console/v1/types.MeasureConnection" + ) { + totalCount: Int! @goField(forceResolver: true) + edges: [MeasureEdge!]! + pageInfo: PageInfo! +} + +type MeasureEdge { + cursor: CursorKey! + node: Measure! +} + +extend type Mutation { + createMeasure(input: CreateMeasureInput!): CreateMeasurePayload! + updateMeasure(input: UpdateMeasureInput!): UpdateMeasurePayload! + importMeasure(input: ImportMeasureInput!): ImportMeasurePayload! + deleteMeasure(input: DeleteMeasureInput!): DeleteMeasurePayload! + createMeasureDocumentMapping( + input: CreateMeasureDocumentMappingInput! + ): CreateMeasureDocumentMappingPayload! + deleteMeasureDocumentMapping( + input: DeleteMeasureDocumentMappingInput! + ): DeleteMeasureDocumentMappingPayload! +} + +input CreateMeasureInput { + organizationId: ID! + name: String! + description: String + category: String! +} + +input UpdateMeasureInput { + id: ID! + name: String + description: String @goField(omittable: true) + category: String + state: MeasureState +} + +input ImportMeasureInput { + organizationId: ID! + file: Upload! +} + +input DeleteMeasureInput { + measureId: ID! +} + +input CreateMeasureDocumentMappingInput { + measureId: ID! + documentId: ID! +} + +input DeleteMeasureDocumentMappingInput { + measureId: ID! + documentId: ID! +} + +type CreateMeasurePayload { + measureEdge: MeasureEdge! +} + +type UpdateMeasurePayload { + measure: Measure! +} + +type ImportMeasurePayload { + measureEdges: [MeasureEdge!]! +} + +type DeleteMeasurePayload { + deletedMeasureId: ID! +} + +type CreateMeasureDocumentMappingPayload { + measureEdge: MeasureEdge! + documentEdge: DocumentEdge! +} + +type DeleteMeasureDocumentMappingPayload { + deletedMeasureId: ID! + deletedDocumentId: ID! +} diff --git a/pkg/server/api/console/v1/graphql/meeting.graphql b/pkg/server/api/console/v1/graphql/meeting.graphql new file mode 100644 index 000000000..417f2113a --- /dev/null +++ b/pkg/server/api/console/v1/graphql/meeting.graphql @@ -0,0 +1,92 @@ +extend type Organization { + meetings( + first: Int + after: CursorKey + last: Int + before: CursorKey + orderBy: MeetingOrder + ): MeetingConnection! @goField(forceResolver: true) +} + +enum MeetingOrderField + @goModel(model: "go.probo.inc/probo/pkg/coredata.MeetingOrderField") { + DATE @goEnum(value: "go.probo.inc/probo/pkg/coredata.MeetingOrderFieldDate") + NAME @goEnum(value: "go.probo.inc/probo/pkg/coredata.MeetingOrderFieldName") + CREATED_AT + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.MeetingOrderFieldCreatedAt" + ) +} + +input MeetingOrder + @goModel( + model: "go.probo.inc/probo/pkg/server/api/console/v1/types.MeetingOrderBy" + ) { + direction: OrderDirection! + field: MeetingOrderField! +} + +type Meeting implements Node { + id: ID! + name: String! + date: Datetime! + minutes: String + attendees: [Profile!]! @goField(forceResolver: true) + organization: Organization! @goField(forceResolver: true) + createdAt: Datetime! + updatedAt: Datetime! + + permission(action: String!): Boolean! @goField(forceResolver: true) +} + +type MeetingConnection + @goModel( + model: "go.probo.inc/probo/pkg/server/api/console/v1/types.MeetingConnection" + ) { + totalCount: Int! @goField(forceResolver: true) + edges: [MeetingEdge!]! + pageInfo: PageInfo! +} + +type MeetingEdge { + cursor: CursorKey! + node: Meeting! +} + +extend type Mutation { + createMeeting(input: CreateMeetingInput!): CreateMeetingPayload! + updateMeeting(input: UpdateMeetingInput!): UpdateMeetingPayload! + deleteMeeting(input: DeleteMeetingInput!): DeleteMeetingPayload! +} + +input CreateMeetingInput { + organizationId: ID! + name: String! + date: Datetime! + attendeeIds: [ID!] + minutes: String +} + +input UpdateMeetingInput { + meetingId: ID! + name: String + date: Datetime + attendeeIds: [ID!] + minutes: String @goField(omittable: true) +} + +input DeleteMeetingInput { + meetingId: ID! +} + +type CreateMeetingPayload { + meetingEdge: MeetingEdge! +} + +type UpdateMeetingPayload { + meeting: Meeting! +} + +type DeleteMeetingPayload { + deletedMeetingId: ID! +} diff --git a/pkg/server/api/console/v1/graphql/obligation.graphql b/pkg/server/api/console/v1/graphql/obligation.graphql new file mode 100644 index 000000000..1a42052b6 --- /dev/null +++ b/pkg/server/api/console/v1/graphql/obligation.graphql @@ -0,0 +1,152 @@ +extend type Organization { + obligations( + first: Int + after: CursorKey + last: Int + before: CursorKey + orderBy: ObligationOrder + filter: ObligationFilter = { snapshotId: null } + ): ObligationConnection! @goField(forceResolver: true) +} + +enum ObligationStatus + @goModel(model: "go.probo.inc/probo/pkg/coredata.ObligationStatus") { + NON_COMPLIANT + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.ObligationStatusNonCompliant" + ) + PARTIALLY_COMPLIANT + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.ObligationStatusPartiallyCompliant" + ) + COMPLIANT + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.ObligationStatusCompliant" + ) +} + +enum ObligationType + @goModel(model: "go.probo.inc/probo/pkg/coredata.ObligationType") { + LEGAL @goEnum(value: "go.probo.inc/probo/pkg/coredata.ObligationTypeLegal") + CONTRACTUAL + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.ObligationTypeContractual" + ) +} + +enum ObligationOrderField + @goModel(model: "go.probo.inc/probo/pkg/coredata.ObligationOrderField") { + CREATED_AT + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.ObligationOrderFieldCreatedAt" + ) + LAST_REVIEW_DATE + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.ObligationOrderFieldLastReviewDate" + ) + DUE_DATE + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.ObligationOrderFieldDueDate" + ) + STATUS + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.ObligationOrderFieldStatus" + ) +} + +input ObligationOrder + @goModel( + model: "go.probo.inc/probo/pkg/server/api/console/v1/types.ObligationOrderBy" + ) { + direction: OrderDirection! + field: ObligationOrderField! +} + +input ObligationFilter { + snapshotId: ID +} + +type Obligation implements Node { + id: ID! + snapshotId: ID + sourceId: ID + organization: Organization! @goField(forceResolver: true) + area: String + source: String + requirement: String + actionsToBeImplemented: String + regulator: String + owner: Profile! @goField(forceResolver: true) + lastReviewDate: Datetime + dueDate: Datetime + status: ObligationStatus! + type: ObligationType! + createdAt: Datetime! + updatedAt: Datetime! + + permission(action: String!): Boolean! @goField(forceResolver: true) +} + +type ObligationConnection + @goModel( + model: "go.probo.inc/probo/pkg/server/api/console/v1/types.ObligationConnection" + ) { + totalCount: Int! @goField(forceResolver: true) + edges: [ObligationEdge!]! + pageInfo: PageInfo! +} + +type ObligationEdge { + cursor: CursorKey! + node: Obligation! +} + +extend type Mutation { + createObligation(input: CreateObligationInput!): CreateObligationPayload! + updateObligation(input: UpdateObligationInput!): UpdateObligationPayload! + deleteObligation(input: DeleteObligationInput!): DeleteObligationPayload! +} + +input CreateObligationInput { + organizationId: ID! + area: String + source: String + requirement: String + actionsToBeImplemented: String + regulator: String + ownerId: ID! + lastReviewDate: Datetime + dueDate: Datetime + status: ObligationStatus! + type: ObligationType! +} + +input UpdateObligationInput { + id: ID! + area: String @goField(omittable: true) + source: String @goField(omittable: true) + requirement: String @goField(omittable: true) + actionsToBeImplemented: String @goField(omittable: true) + regulator: String @goField(omittable: true) + ownerId: ID + lastReviewDate: Datetime @goField(omittable: true) + dueDate: Datetime @goField(omittable: true) + status: ObligationStatus + type: ObligationType +} + +input DeleteObligationInput { + obligationId: ID! +} + +type CreateObligationPayload { + obligationEdge: ObligationEdge! +} + +type UpdateObligationPayload { + obligation: Obligation! +} + +type DeleteObligationPayload { + deletedObligationId: ID! +} diff --git a/pkg/server/api/console/v1/graphql/organization.graphql b/pkg/server/api/console/v1/graphql/organization.graphql new file mode 100644 index 000000000..bd0e345cb --- /dev/null +++ b/pkg/server/api/console/v1/graphql/organization.graphql @@ -0,0 +1,83 @@ +type OrganizationContext { + organizationId: ID! + product: String + architecture: String + team: String + processes: String + customers: String +} + +enum ProfileState + @goModel(model: "go.probo.inc/probo/pkg/coredata.ProfileState") { + ACTIVE @goEnum(value: "go.probo.inc/probo/pkg/coredata.ProfileStateActive") + INACTIVE + @goEnum(value: "go.probo.inc/probo/pkg/coredata.ProfileStateInactive") +} + +type Profile implements Node { + id: ID! + fullName: String! + emailAddress: EmailAddr! + state: ProfileState! + additionalEmailAddresses: [EmailAddr!]! + kind: String + position: String + contractStartDate: Datetime + contractEndDate: Datetime + createdAt: Datetime! + updatedAt: Datetime! + + permission(action: String!): Boolean! @goField(forceResolver: true) +} + +enum ProfileOrderField + @goModel(model: "go.probo.inc/probo/pkg/coredata.MembershipProfileOrderField") { + FULL_NAME + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.MembershipProfileOrderFieldFullName" + ) + CREATED_AT + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.MembershipProfileOrderFieldCreatedAt" + ) + KIND @goEnum(value: "go.probo.inc/probo/pkg/coredata.MembershipProfileOrderFieldKind") +} + +input ProfileOrder + @goModel( + model: "go.probo.inc/probo/pkg/server/api/console/v1/types.ProfileOrderBy" + ) { + direction: OrderDirection! + field: ProfileOrderField! +} + +input ProfileFilter { + excludeContractEnded: Boolean +} + +type ProfileConnection + @goModel( + model: "go.probo.inc/probo/pkg/server/api/console/v1/types.ProfileConnection" + ) { + totalCount: Int! @goField(forceResolver: true) + edges: [ProfileEdge!]! + pageInfo: PageInfo! +} + +type ProfileEdge { + cursor: CursorKey! + node: Profile! +} + +input UpdateOrganizationContextInput { + organizationId: ID! + product: String @goField(omittable: true) + architecture: String @goField(omittable: true) + team: String @goField(omittable: true) + processes: String @goField(omittable: true) + customers: String @goField(omittable: true) +} + +type UpdateOrganizationContextPayload { + context: OrganizationContext! +} diff --git a/pkg/server/api/console/v1/graphql/processing_activity.graphql b/pkg/server/api/console/v1/graphql/processing_activity.graphql new file mode 100644 index 000000000..efbfdd799 --- /dev/null +++ b/pkg/server/api/console/v1/graphql/processing_activity.graphql @@ -0,0 +1,298 @@ +extend type Organization { + processingActivities( + first: Int + after: CursorKey + last: Int + before: CursorKey + orderBy: ProcessingActivityOrder + filter: ProcessingActivityFilter = { snapshotId: null } + ): ProcessingActivityConnection! @goField(forceResolver: true) +} + +enum ProcessingActivitySpecialOrCriminalDatum + @goModel( + model: "go.probo.inc/probo/pkg/coredata.ProcessingActivitySpecialOrCriminalDatum" + ) { + YES + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.ProcessingActivitySpecialOrCriminalDatumYes" + ) + NO + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.ProcessingActivitySpecialOrCriminalDatumNo" + ) + POSSIBLE + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.ProcessingActivitySpecialOrCriminalDatumPossible" + ) +} + +enum ProcessingActivityLawfulBasis + @goModel( + model: "go.probo.inc/probo/pkg/coredata.ProcessingActivityLawfulBasis" + ) { + LEGITIMATE_INTEREST + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.ProcessingActivityLawfulBasisLegitimateInterest" + ) + CONSENT + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.ProcessingActivityLawfulBasisConsent" + ) + CONTRACTUAL_NECESSITY + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.ProcessingActivityLawfulBasisContractualNecessity" + ) + LEGAL_OBLIGATION + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.ProcessingActivityLawfulBasisLegalObligation" + ) + VITAL_INTERESTS + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.ProcessingActivityLawfulBasisVitalInterests" + ) + PUBLIC_TASK + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.ProcessingActivityLawfulBasisPublicTask" + ) +} + +enum ProcessingActivityTransferSafeguard + @goModel( + model: "go.probo.inc/probo/pkg/coredata.ProcessingActivityTransferSafeguard" + ) { + STANDARD_CONTRACTUAL_CLAUSES + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.ProcessingActivityTransferSafeguardStandardContractualClauses" + ) + BINDING_CORPORATE_RULES + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.ProcessingActivityTransferSafeguardBindingCorporateRules" + ) + ADEQUACY_DECISION + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.ProcessingActivityTransferSafeguardAdequacyDecision" + ) + DEROGATIONS + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.ProcessingActivityTransferSafeguardDerogations" + ) + CODES_OF_CONDUCT + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.ProcessingActivityTransferSafeguardCodesOfConduct" + ) + CERTIFICATION_MECHANISMS + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.ProcessingActivityTransferSafeguardCertificationMechanisms" + ) +} + +enum ProcessingActivityDataProtectionImpactAssessment + @goModel( + model: "go.probo.inc/probo/pkg/coredata.ProcessingActivityDataProtectionImpactAssessment" + ) { + NEEDED + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.ProcessingActivityDataProtectionImpactAssessmentNeeded" + ) + NOT_NEEDED + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.ProcessingActivityDataProtectionImpactAssessmentNotNeeded" + ) +} + +enum ProcessingActivityTransferImpactAssessment + @goModel( + model: "go.probo.inc/probo/pkg/coredata.ProcessingActivityTransferImpactAssessment" + ) { + NEEDED + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.ProcessingActivityTransferImpactAssessmentNeeded" + ) + NOT_NEEDED + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.ProcessingActivityTransferImpactAssessmentNotNeeded" + ) +} + +enum ProcessingActivityRole + @goModel(model: "go.probo.inc/probo/pkg/coredata.ProcessingActivityRole") { + CONTROLLER + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.ProcessingActivityRoleController" + ) + PROCESSOR + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.ProcessingActivityRoleProcessor" + ) +} + +enum ProcessingActivityOrderField + @goModel( + model: "go.probo.inc/probo/pkg/coredata.ProcessingActivityOrderField" + ) { + CREATED_AT + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.ProcessingActivityOrderFieldCreatedAt" + ) + NAME + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.ProcessingActivityOrderFieldName" + ) +} + +input ProcessingActivityOrder + @goModel( + model: "go.probo.inc/probo/pkg/server/api/console/v1/types.ProcessingActivityOrderBy" + ) { + direction: OrderDirection! + field: ProcessingActivityOrderField! +} + +input ProcessingActivityFilter { + snapshotId: ID +} + +type ProcessingActivity implements Node { + id: ID! + snapshotId: ID + sourceId: ID + organization: Organization! @goField(forceResolver: true) + name: String! + purpose: String + dataSubjectCategory: String + personalDataCategory: String + specialOrCriminalData: ProcessingActivitySpecialOrCriminalDatum! + consentEvidenceLink: String + lawfulBasis: ProcessingActivityLawfulBasis! + recipients: String + location: String + internationalTransfers: Boolean! + transferSafeguards: ProcessingActivityTransferSafeguard + retentionPeriod: String + securityMeasures: String + dataProtectionImpactAssessmentNeeded: ProcessingActivityDataProtectionImpactAssessment! + transferImpactAssessmentNeeded: ProcessingActivityTransferImpactAssessment! + lastReviewDate: Datetime + nextReviewDate: Datetime + role: ProcessingActivityRole! + dataProtectionOfficer: Profile @goField(forceResolver: true) + vendors( + first: Int + after: CursorKey + last: Int + before: CursorKey + orderBy: VendorOrder + ): VendorConnection! @goField(forceResolver: true) + dataProtectionImpactAssessment: DataProtectionImpactAssessment + @goField(forceResolver: true) + transferImpactAssessment: TransferImpactAssessment + @goField(forceResolver: true) + createdAt: Datetime! + updatedAt: Datetime! + + permission(action: String!): Boolean! @goField(forceResolver: true) +} + +type ProcessingActivityConnection + @goModel( + model: "go.probo.inc/probo/pkg/server/api/console/v1/types.ProcessingActivityConnection" + ) { + totalCount: Int! @goField(forceResolver: true) + edges: [ProcessingActivityEdge!]! + pageInfo: PageInfo! +} + +type ProcessingActivityEdge { + cursor: CursorKey! + node: ProcessingActivity! +} + +extend type Mutation { + createProcessingActivity( + input: CreateProcessingActivityInput! + ): CreateProcessingActivityPayload! + updateProcessingActivity( + input: UpdateProcessingActivityInput! + ): UpdateProcessingActivityPayload! + deleteProcessingActivity( + input: DeleteProcessingActivityInput! + ): DeleteProcessingActivityPayload! + exportProcessingActivitiesPDF( + input: ExportProcessingActivitiesPDFInput! + ): ExportProcessingActivitiesPDFPayload! +} + +input CreateProcessingActivityInput { + organizationId: ID! + name: String! + purpose: String + dataSubjectCategory: String + personalDataCategory: String + specialOrCriminalData: ProcessingActivitySpecialOrCriminalDatum! + consentEvidenceLink: String + lawfulBasis: ProcessingActivityLawfulBasis! + recipients: String + location: String + internationalTransfers: Boolean! + transferSafeguards: ProcessingActivityTransferSafeguard + retentionPeriod: String + securityMeasures: String + dataProtectionImpactAssessmentNeeded: ProcessingActivityDataProtectionImpactAssessment! + transferImpactAssessmentNeeded: ProcessingActivityTransferImpactAssessment! + lastReviewDate: Datetime + nextReviewDate: Datetime + role: ProcessingActivityRole! + dataProtectionOfficerId: ID + vendorIds: [ID!] +} + +input UpdateProcessingActivityInput { + id: ID! + name: String + purpose: String @goField(omittable: true) + dataSubjectCategory: String @goField(omittable: true) + personalDataCategory: String @goField(omittable: true) + specialOrCriminalData: ProcessingActivitySpecialOrCriminalDatum + consentEvidenceLink: String + lawfulBasis: ProcessingActivityLawfulBasis + recipients: String @goField(omittable: true) + location: String @goField(omittable: true) + internationalTransfers: Boolean + transferSafeguards: ProcessingActivityTransferSafeguard + @goField(omittable: true) + retentionPeriod: String @goField(omittable: true) + securityMeasures: String @goField(omittable: true) + dataProtectionImpactAssessmentNeeded: ProcessingActivityDataProtectionImpactAssessment + transferImpactAssessmentNeeded: ProcessingActivityTransferImpactAssessment + lastReviewDate: Datetime @goField(omittable: true) + nextReviewDate: Datetime @goField(omittable: true) + role: ProcessingActivityRole + dataProtectionOfficerId: ID @goField(omittable: true) + vendorIds: [ID!] +} + +input DeleteProcessingActivityInput { + processingActivityId: ID! +} + +input ExportProcessingActivitiesPDFInput { + organizationId: ID! + filter: ProcessingActivityFilter +} + +type CreateProcessingActivityPayload { + processingActivityEdge: ProcessingActivityEdge! +} + +type UpdateProcessingActivityPayload { + processingActivity: ProcessingActivity! +} + +type DeleteProcessingActivityPayload { + deletedProcessingActivityId: ID! +} + +type ExportProcessingActivitiesPDFPayload { + data: String! +} diff --git a/pkg/server/api/console/v1/graphql/rights_request.graphql b/pkg/server/api/console/v1/graphql/rights_request.graphql new file mode 100644 index 000000000..0e3ba7eca --- /dev/null +++ b/pkg/server/api/console/v1/graphql/rights_request.graphql @@ -0,0 +1,145 @@ +extend type Organization { + rightsRequests( + first: Int + after: CursorKey + last: Int + before: CursorKey + orderBy: RightsRequestOrder + ): RightsRequestConnection! @goField(forceResolver: true) +} + +enum RightsRequestType + @goModel(model: "go.probo.inc/probo/pkg/coredata.RightsRequestType") { + ACCESS + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.RightsRequestTypeAccess" + ) + DELETION + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.RightsRequestTypeDeletion" + ) + PORTABILITY + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.RightsRequestTypePortability" + ) +} + +enum RightsRequestState + @goModel(model: "go.probo.inc/probo/pkg/coredata.RightsRequestState") { + TODO + @goEnum(value: "go.probo.inc/probo/pkg/coredata.RightsRequestStateTodo") + IN_PROGRESS + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.RightsRequestStateInProgress" + ) + DONE + @goEnum(value: "go.probo.inc/probo/pkg/coredata.RightsRequestStateDone") +} + +enum RightsRequestOrderField + @goModel(model: "go.probo.inc/probo/pkg/coredata.RightsRequestOrderField") { + CREATED_AT + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.RightsRequestOrderFieldCreatedAt" + ) + DEADLINE + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.RightsRequestOrderFieldDeadline" + ) + STATE + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.RightsRequestOrderFieldState" + ) + TYPE + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.RightsRequestOrderFieldType" + ) +} + +input RightsRequestOrder + @goModel( + model: "go.probo.inc/probo/pkg/server/api/console/v1/types.RightsRequestOrderBy" + ) { + direction: OrderDirection! + field: RightsRequestOrderField! +} + +type RightsRequest implements Node { + id: ID! + organization: Organization! @goField(forceResolver: true) + requestType: RightsRequestType! + requestState: RightsRequestState! + dataSubject: String + contact: String + details: String + deadline: Datetime + actionTaken: String + createdAt: Datetime! + updatedAt: Datetime! + + permission(action: String!): Boolean! @goField(forceResolver: true) +} + +type RightsRequestConnection + @goModel( + model: "go.probo.inc/probo/pkg/server/api/console/v1/types.RightsRequestConnection" + ) { + totalCount: Int! @goField(forceResolver: true) + edges: [RightsRequestEdge!]! + pageInfo: PageInfo! +} + +type RightsRequestEdge { + cursor: CursorKey! + node: RightsRequest! +} + +extend type Mutation { + createRightsRequest( + input: CreateRightsRequestInput! + ): CreateRightsRequestPayload! + updateRightsRequest( + input: UpdateRightsRequestInput! + ): UpdateRightsRequestPayload! + deleteRightsRequest( + input: DeleteRightsRequestInput! + ): DeleteRightsRequestPayload! +} + +input CreateRightsRequestInput { + organizationId: ID! + requestType: RightsRequestType! + requestState: RightsRequestState! + dataSubject: String + contact: String + details: String + deadline: Datetime + actionTaken: String +} + +input UpdateRightsRequestInput { + id: ID! + requestType: RightsRequestType + requestState: RightsRequestState + dataSubject: String @goField(omittable: true) + contact: String @goField(omittable: true) + details: String @goField(omittable: true) + deadline: Datetime @goField(omittable: true) + actionTaken: String @goField(omittable: true) +} + +input DeleteRightsRequestInput { + rightsRequestId: ID! +} + +type CreateRightsRequestPayload { + rightsRequestEdge: RightsRequestEdge! +} + +type UpdateRightsRequestPayload { + rightsRequest: RightsRequest! +} + +type DeleteRightsRequestPayload { + deletedRightsRequestId: ID! +} diff --git a/pkg/server/api/console/v1/graphql/risk.graphql b/pkg/server/api/console/v1/graphql/risk.graphql new file mode 100644 index 000000000..4a04ff6fc --- /dev/null +++ b/pkg/server/api/console/v1/graphql/risk.graphql @@ -0,0 +1,270 @@ +extend type Organization { + risks( + first: Int + after: CursorKey + last: Int + before: CursorKey + orderBy: RiskOrder + filter: RiskFilter = { snapshotId: null } + ): RiskConnection! @goField(forceResolver: true) +} + +enum RiskTreatment + @goModel(model: "go.probo.inc/probo/pkg/coredata.RiskTreatment") { + MITIGATED + @goEnum(value: "go.probo.inc/probo/pkg/coredata.RiskTreatmentMitigated") + ACCEPTED + @goEnum(value: "go.probo.inc/probo/pkg/coredata.RiskTreatmentAccepted") + AVOIDED + @goEnum(value: "go.probo.inc/probo/pkg/coredata.RiskTreatmentAvoided") + TRANSFERRED + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.RiskTreatmentTransferred" + ) +} + +enum RiskOrderField + @goModel(model: "go.probo.inc/probo/pkg/coredata.RiskOrderField") { + CREATED_AT + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.RiskOrderFieldCreatedAt" + ) + UPDATED_AT + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.RiskOrderFieldUpdatedAt" + ) + NAME @goEnum(value: "go.probo.inc/probo/pkg/coredata.RiskOrderFieldName") + CATEGORY + @goEnum(value: "go.probo.inc/probo/pkg/coredata.RiskOrderFieldCategory") + TREATMENT + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.RiskOrderFieldTreatment" + ) + INHERENT_RISK_SCORE + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.RiskOrderFieldInherentRiskScore" + ) + RESIDUAL_RISK_SCORE + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.RiskOrderFieldResidualRiskScore" + ) + OWNER_FULL_NAME + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.RiskOrderFieldOwnerFullName" + ) +} + +input RiskOrder + @goModel( + model: "go.probo.inc/probo/pkg/server/api/console/v1/types.RiskOrderBy" + ) { + direction: OrderDirection! + field: RiskOrderField! +} + +input RiskFilter { + query: String + snapshotId: ID +} + +type Risk implements Node { + id: ID! + snapshotId: ID + name: String! + description: String + category: String! + treatment: RiskTreatment! + inherentLikelihood: Int! + inherentImpact: Int! + inherentRiskScore: Int! + residualLikelihood: Int! + residualImpact: Int! + residualRiskScore: Int! + note: String! + + owner: Profile @goField(forceResolver: true) + organization: Organization! @goField(forceResolver: true) + + measures( + first: Int + after: CursorKey + last: Int + before: CursorKey + orderBy: MeasureOrder + filter: MeasureFilter + ): MeasureConnection! @goField(forceResolver: true) + + documents( + first: Int + after: CursorKey + last: Int + before: CursorKey + orderBy: DocumentOrder + filter: DocumentFilter + ): DocumentConnection! @goField(forceResolver: true) + + controls( + first: Int + after: CursorKey + last: Int + before: CursorKey + orderBy: ControlOrder + filter: ControlFilter + ): ControlConnection! @goField(forceResolver: true) + + obligations( + first: Int + after: CursorKey + last: Int + before: CursorKey + orderBy: ObligationOrder + filter: ObligationFilter + ): ObligationConnection! @goField(forceResolver: true) + + createdAt: Datetime! + updatedAt: Datetime! + + permission(action: String!): Boolean! @goField(forceResolver: true) +} + +type RiskConnection + @goModel( + model: "go.probo.inc/probo/pkg/server/api/console/v1/types.RiskConnection" + ) { + totalCount: Int! @goField(forceResolver: true) + edges: [RiskEdge!]! + pageInfo: PageInfo! +} + +type RiskEdge { + cursor: CursorKey! + node: Risk! +} + +extend type Mutation { + createRisk(input: CreateRiskInput!): CreateRiskPayload! + updateRisk(input: UpdateRiskInput!): UpdateRiskPayload! + deleteRisk(input: DeleteRiskInput!): DeleteRiskPayload! + createRiskMeasureMapping( + input: CreateRiskMeasureMappingInput! + ): CreateRiskMeasureMappingPayload! + deleteRiskMeasureMapping( + input: DeleteRiskMeasureMappingInput! + ): DeleteRiskMeasureMappingPayload! + createRiskDocumentMapping( + input: CreateRiskDocumentMappingInput! + ): CreateRiskDocumentMappingPayload! + deleteRiskDocumentMapping( + input: DeleteRiskDocumentMappingInput! + ): DeleteRiskDocumentMappingPayload! + createRiskObligationMapping( + input: CreateRiskObligationMappingInput! + ): CreateRiskObligationMappingPayload! + deleteRiskObligationMapping( + input: DeleteRiskObligationMappingInput! + ): DeleteRiskObligationMappingPayload! +} + +input CreateRiskInput { + organizationId: ID! + name: String! + description: String + category: String! + ownerId: ID + treatment: RiskTreatment! + inherentLikelihood: Int! + inherentImpact: Int! + residualLikelihood: Int + residualImpact: Int + note: String +} + +input UpdateRiskInput { + id: ID! + name: String + description: String @goField(omittable: true) + category: String + ownerId: ID @goField(omittable: true) + treatment: RiskTreatment + inherentLikelihood: Int + inherentImpact: Int + residualLikelihood: Int + residualImpact: Int + note: String +} + +input DeleteRiskInput { + riskId: ID! +} + +input CreateRiskMeasureMappingInput { + riskId: ID! + measureId: ID! +} + +input DeleteRiskMeasureMappingInput { + riskId: ID! + measureId: ID! +} + +input CreateRiskDocumentMappingInput { + riskId: ID! + documentId: ID! +} + +input DeleteRiskDocumentMappingInput { + riskId: ID! + documentId: ID! +} + +input CreateRiskObligationMappingInput { + riskId: ID! + obligationId: ID! +} + +input DeleteRiskObligationMappingInput { + riskId: ID! + obligationId: ID! +} + +type CreateRiskPayload { + riskEdge: RiskEdge! +} + +type UpdateRiskPayload { + risk: Risk! +} + +type DeleteRiskPayload { + deletedRiskId: ID! +} + +type CreateRiskMeasureMappingPayload { + riskEdge: RiskEdge! + measureEdge: MeasureEdge! +} + +type DeleteRiskMeasureMappingPayload { + deletedMeasureId: ID! + deletedRiskId: ID! +} + +type CreateRiskDocumentMappingPayload { + riskEdge: RiskEdge! + documentEdge: DocumentEdge! +} + +type DeleteRiskDocumentMappingPayload { + deletedRiskId: ID! + deletedDocumentId: ID! +} + +type CreateRiskObligationMappingPayload { + riskEdge: RiskEdge! + obligationEdge: ObligationEdge! +} + +type DeleteRiskObligationMappingPayload { + deletedRiskId: ID! + deletedObligationId: ID! +} diff --git a/pkg/server/api/console/v1/graphql/snapshot.graphql b/pkg/server/api/console/v1/graphql/snapshot.graphql new file mode 100644 index 000000000..b7122f813 --- /dev/null +++ b/pkg/server/api/console/v1/graphql/snapshot.graphql @@ -0,0 +1,113 @@ +extend type Organization { + snapshots( + first: Int + after: CursorKey + last: Int + before: CursorKey + orderBy: SnapshotOrder + ): SnapshotConnection! @goField(forceResolver: true) +} + +enum SnapshotsType + @goModel(model: "go.probo.inc/probo/pkg/coredata.SnapshotsType") { + RISKS @goEnum(value: "go.probo.inc/probo/pkg/coredata.SnapshotsTypeRisks") + VENDORS + @goEnum(value: "go.probo.inc/probo/pkg/coredata.SnapshotsTypeVendors") + ASSETS @goEnum(value: "go.probo.inc/probo/pkg/coredata.SnapshotsTypeAssets") + DATA @goEnum(value: "go.probo.inc/probo/pkg/coredata.SnapshotsTypeData") + FINDINGS + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.SnapshotsTypeFindings" + ) + OBLIGATIONS + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.SnapshotsTypeObligations" + ) + PROCESSING_ACTIVITIES + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.SnapshotsTypeProcessingActivities" + ) + STATEMENTS_OF_APPLICABILITY + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.SnapshotsTypeStatementsOfApplicability" + ) +} + +enum SnapshotOrderField + @goModel(model: "go.probo.inc/probo/pkg/coredata.SnapshotOrderField") { + CREATED_AT + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.SnapshotOrderFieldCreatedAt" + ) + NAME + @goEnum(value: "go.probo.inc/probo/pkg/coredata.SnapshotOrderFieldName") + TYPE + @goEnum(value: "go.probo.inc/probo/pkg/coredata.SnapshotOrderFieldType") +} + +input SnapshotOrder + @goModel( + model: "go.probo.inc/probo/pkg/server/api/console/v1/types.SnapshotOrderBy" + ) { + direction: OrderDirection! + field: SnapshotOrderField! +} + +type Snapshot implements Node { + id: ID! + organization: Organization! @goField(forceResolver: true) + name: String! + description: String + type: SnapshotsType! + + controls( + first: Int + after: CursorKey + last: Int + before: CursorKey + orderBy: ControlOrder + filter: ControlFilter + ): ControlConnection! @goField(forceResolver: true) + + createdAt: Datetime! + + permission(action: String!): Boolean! @goField(forceResolver: true) +} + +type SnapshotConnection + @goModel( + model: "go.probo.inc/probo/pkg/server/api/console/v1/types.SnapshotConnection" + ) { + totalCount: Int! @goField(forceResolver: true) + edges: [SnapshotEdge!]! + pageInfo: PageInfo! +} + +type SnapshotEdge { + cursor: CursorKey! + node: Snapshot! +} + +extend type Mutation { + createSnapshot(input: CreateSnapshotInput!): CreateSnapshotPayload! + deleteSnapshot(input: DeleteSnapshotInput!): DeleteSnapshotPayload! +} + +input CreateSnapshotInput { + organizationId: ID! + name: String! + description: String + type: SnapshotsType! +} + +input DeleteSnapshotInput { + snapshotId: ID! +} + +type CreateSnapshotPayload { + snapshotEdge: SnapshotEdge! +} + +type DeleteSnapshotPayload { + deletedSnapshotId: ID! +} diff --git a/pkg/server/api/console/v1/graphql/task.graphql b/pkg/server/api/console/v1/graphql/task.graphql new file mode 100644 index 000000000..71d2ff40e --- /dev/null +++ b/pkg/server/api/console/v1/graphql/task.graphql @@ -0,0 +1,137 @@ +extend type Organization { + tasks( + first: Int + after: CursorKey + last: Int + before: CursorKey + orderBy: TaskOrder + ): TaskConnection! @goField(forceResolver: true) +} + +enum TaskState @goModel(model: "go.probo.inc/probo/pkg/coredata.TaskState") { + TODO @goEnum(value: "go.probo.inc/probo/pkg/coredata.TaskStateTodo") + IN_PROGRESS @goEnum(value: "go.probo.inc/probo/pkg/coredata.TaskStateInProgress") + DONE @goEnum(value: "go.probo.inc/probo/pkg/coredata.TaskStateDone") +} + +enum TaskPriority + @goModel(model: "go.probo.inc/probo/pkg/coredata.TaskPriority") { + URGENT + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.TaskPriorityUrgent" + ) + HIGH + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.TaskPriorityHigh" + ) + MEDIUM + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.TaskPriorityMedium" + ) + LOW + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.TaskPriorityLow" + ) +} + +enum TaskOrderField + @goModel(model: "go.probo.inc/probo/pkg/coredata.TaskOrderField") { + PRIORITY_RANK + CREATED_AT +} + +input TaskOrder + @goModel( + model: "go.probo.inc/probo/pkg/server/api/console/v1/types.TaskOrderBy" + ) { + direction: OrderDirection! + field: TaskOrderField! +} + +type Task implements Node { + id: ID! + name: String! + description: String + state: TaskState! + priority: TaskPriority! + rank: Int! + timeEstimate: Duration + deadline: Datetime + assignedTo: Profile @goField(forceResolver: true) + + organization: Organization! @goField(forceResolver: true) + measure: Measure @goField(forceResolver: true) + + evidences( + first: Int + after: CursorKey + last: Int + before: CursorKey + orderBy: EvidenceOrder + ): EvidenceConnection! @goField(forceResolver: true) + + createdAt: Datetime! + updatedAt: Datetime! + + permission(action: String!): Boolean! @goField(forceResolver: true) +} + +type TaskConnection + @goModel( + model: "go.probo.inc/probo/pkg/server/api/console/v1/types.TaskConnection" + ) { + totalCount: Int! @goField(forceResolver: true) + edges: [TaskEdge!]! + pageInfo: PageInfo! +} + +type TaskEdge { + cursor: CursorKey! + node: Task! +} + +extend type Mutation { + createTask(input: CreateTaskInput!): CreateTaskPayload! + updateTask(input: UpdateTaskInput!): UpdateTaskPayload! + deleteTask(input: DeleteTaskInput!): DeleteTaskPayload! +} + +input CreateTaskInput { + organizationId: ID! + measureId: ID + name: String! + description: String + priority: TaskPriority! + timeEstimate: Duration + assignedToId: ID + deadline: Datetime +} + +input UpdateTaskInput { + taskId: ID! + name: String + description: String @goField(omittable: true) + state: TaskState + priority: TaskPriority + rank: Int + timeEstimate: Duration @goField(omittable: true) + deadline: Datetime @goField(omittable: true) + assignedToId: ID @goField(omittable: true) + measureId: ID @goField(omittable: true) +} + +input DeleteTaskInput { + taskId: ID! +} + +type CreateTaskPayload { + taskEdge: TaskEdge! +} + +type UpdateTaskPayload { + task: Task! +} + +type DeleteTaskPayload { + deletedTaskId: ID! +} diff --git a/pkg/server/api/console/v1/graphql/trust_center.graphql b/pkg/server/api/console/v1/graphql/trust_center.graphql new file mode 100644 index 000000000..f03dc3565 --- /dev/null +++ b/pkg/server/api/console/v1/graphql/trust_center.graphql @@ -0,0 +1,771 @@ +extend type Organization { + trustCenter: TrustCenter @goField(forceResolver: true) + customDomain: CustomDomain @goField(forceResolver: true) + trustCenterFiles( + first: Int + after: CursorKey + last: Int + before: CursorKey + orderBy: TrustCenterFileOrder + ): TrustCenterFileConnection! @goField(forceResolver: true) +} + +enum TrustCenterVisibility + @goModel(model: "go.probo.inc/probo/pkg/coredata.TrustCenterVisibility") { + NONE + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.TrustCenterVisibilityNone" + ) + PRIVATE + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.TrustCenterVisibilityPrivate" + ) + PUBLIC + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.TrustCenterVisibilityPublic" + ) +} + +enum SearchEngineIndexing + @goModel( + model: "go.probo.inc/probo/pkg/coredata.SearchEngineIndexing" + ) { + INDEXABLE + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.SearchEngineIndexingIndexable" + ) + NOT_INDEXABLE + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.SearchEngineIndexingNotIndexable" + ) +} + +enum TrustCenterDocumentAccessStatus + @goModel( + model: "go.probo.inc/probo/pkg/coredata.TrustCenterDocumentAccessStatus" + ) { + REQUESTED + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.TrustCenterDocumentAccessStatusRequested" + ) + GRANTED + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.TrustCenterDocumentAccessStatusGranted" + ) + REJECTED + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.TrustCenterDocumentAccessStatusRejected" + ) + REVOKED + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.TrustCenterDocumentAccessStatusRevoked" + ) +} + +enum TrustCenterAccessOrderField + @goModel( + model: "go.probo.inc/probo/pkg/coredata.TrustCenterAccessOrderField" + ) { + CREATED_AT + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.TrustCenterAccessOrderFieldCreatedAt" + ) +} + +enum TrustCenterAccessState + @goModel( + model: "go.probo.inc/probo/pkg/coredata.TrustCenterAccessState" + ) { + ACTIVE + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.TrustCenterAccessStateActive" + ) + INACTIVE + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.TrustCenterAccessStateInactive" + ) +} + +enum TrustCenterDocumentAccessOrderField + @goModel( + model: "go.probo.inc/probo/pkg/coredata.TrustCenterDocumentAccessOrderField" + ) { + CREATED_AT + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.TrustCenterDocumentAccessOrderFieldCreatedAt" + ) +} + +enum TrustCenterReferenceOrderField + @goModel( + model: "go.probo.inc/probo/pkg/coredata.TrustCenterReferenceOrderField" + ) { + RANK + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.TrustCenterReferenceOrderFieldRank" + ) + NAME + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.TrustCenterReferenceOrderFieldName" + ) + CREATED_AT + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.TrustCenterReferenceOrderFieldCreatedAt" + ) + UPDATED_AT + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.TrustCenterReferenceOrderFieldUpdatedAt" + ) +} + +enum ComplianceExternalURLOrderField + @goModel( + model: "go.probo.inc/probo/pkg/coredata.ComplianceExternalURLOrderField" + ) { + CREATED_AT + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.ComplianceExternalURLOrderFieldCreatedAt" + ) + RANK + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.ComplianceExternalURLOrderFieldRank" + ) +} + +enum ComplianceFrameworkOrderField + @goModel( + model: "go.probo.inc/probo/pkg/coredata.ComplianceFrameworkOrderField" + ) { + CREATED_AT + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.ComplianceFrameworkOrderFieldCreatedAt" + ) + RANK + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.ComplianceFrameworkOrderFieldRank" + ) +} + +enum ComplianceFrameworkVisibility + @goModel( + model: "go.probo.inc/probo/pkg/coredata.ComplianceFrameworkVisibility" + ) { + NONE + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.ComplianceFrameworkVisibilityNone" + ) + PUBLIC + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.ComplianceFrameworkVisibilityPublic" + ) +} + +enum TrustCenterFileOrderField + @goModel( + model: "go.probo.inc/probo/pkg/coredata.TrustCenterFileOrderField" + ) { + NAME + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.TrustCenterFileOrderFieldName" + ) + CREATED_AT + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.TrustCenterFileOrderFieldCreatedAt" + ) + UPDATED_AT + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.TrustCenterFileOrderFieldUpdatedAt" + ) +} + +enum SSLStatus + @goModel(model: "go.probo.inc/probo/pkg/coredata.CustomDomainSSLStatus") { + PENDING + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.CustomDomainSSLStatusPending" + ) + PROVISIONING + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.CustomDomainSSLStatusProvisioning" + ) + ACTIVE + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.CustomDomainSSLStatusActive" + ) + RENEWING + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.CustomDomainSSLStatusRenewing" + ) + EXPIRED + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.CustomDomainSSLStatusExpired" + ) + FAILED + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.CustomDomainSSLStatusFailed" + ) +} + +input TrustCenterAccessOrder + @goModel( + model: "go.probo.inc/probo/pkg/server/api/console/v1/types.TrustCenterAccessOrderBy" + ) { + direction: OrderDirection! + field: TrustCenterAccessOrderField! +} + +input TrustCenterDocumentAccessOrder + @goModel( + model: "go.probo.inc/probo/pkg/server/api/console/v1/types.TrustCenterDocumentAccessOrderBy" + ) { + direction: OrderDirection! + field: TrustCenterDocumentAccessOrderField! +} + +input TrustCenterReferenceOrder + @goModel( + model: "go.probo.inc/probo/pkg/server/api/console/v1/types.TrustCenterReferenceOrderBy" + ) { + direction: OrderDirection! + field: TrustCenterReferenceOrderField! +} + +input TrustCenterFileOrder + @goModel( + model: "go.probo.inc/probo/pkg/server/api/console/v1/types.TrustCenterFileOrderBy" + ) { + direction: OrderDirection! + field: TrustCenterFileOrderField! +} + +input ComplianceExternalURLOrder + @goModel( + model: "go.probo.inc/probo/pkg/server/api/console/v1/types.ComplianceExternalURLOrderBy" + ) { + direction: OrderDirection! + field: ComplianceExternalURLOrderField! +} + +input ComplianceFrameworkOrder + @goModel( + model: "go.probo.inc/probo/pkg/server/api/console/v1/types.ComplianceFrameworkOrderBy" + ) { + direction: OrderDirection! + field: ComplianceFrameworkOrderField! +} + +type TrustCenter implements Node + @goModel( + model: "go.probo.inc/probo/pkg/server/api/console/v1/types.TrustCenter" + ) { + id: ID! + active: Boolean! + searchEngineIndexing: SearchEngineIndexing! + logoFileUrl: String @goField(forceResolver: true) + darkLogoFileUrl: String @goField(forceResolver: true) + ndaFileName: String + ndaFileUrl: String @goField(forceResolver: true) + createdAt: Datetime! + updatedAt: Datetime! + organization: Organization! @goField(forceResolver: true) + + accesses( + first: Int + after: CursorKey + last: Int + before: CursorKey + orderBy: TrustCenterAccessOrder + ): TrustCenterAccessConnection! @goField(forceResolver: true) + + references( + first: Int + after: CursorKey + last: Int + before: CursorKey + orderBy: TrustCenterReferenceOrder + ): TrustCenterReferenceConnection! @goField(forceResolver: true) + + complianceFrameworks( + first: Int + after: CursorKey + last: Int + before: CursorKey + orderBy: ComplianceFrameworkOrder + ): ComplianceFrameworkConnection! @goField(forceResolver: true) + + externalUrls( + first: Int + after: CursorKey + last: Int + before: CursorKey + orderBy: ComplianceExternalURLOrder + ): ComplianceExternalURLConnection! @goField(forceResolver: true) + + mailingList: MailingList @goField(forceResolver: true) + + permission(action: String!): Boolean! @goField(forceResolver: true) +} + +type TrustCenterConnection { + edges: [TrustCenterEdge!]! + pageInfo: PageInfo! +} + +type TrustCenterEdge { + cursor: CursorKey! + node: TrustCenter! +} + +type TrustCenterAccess implements Node { + id: ID! + ndaSignature: ElectronicSignature @goField(forceResolver: true) + createdAt: Datetime! + updatedAt: Datetime! + pendingRequestCount: Int! @goField(forceResolver: true) + activeCount: Int! @goField(forceResolver: true) + organizationId: ID! + identityID: ID! + + profile: Profile! @goField(forceResolver: true) + + availableDocumentAccesses( + first: Int + after: CursorKey + last: Int + before: CursorKey + orderBy: TrustCenterDocumentAccessOrder + ): TrustCenterDocumentAccessConnection! @goField(forceResolver: true) + + permission(action: String!): Boolean! @goField(forceResolver: true) +} + +type TrustCenterDocumentAccess + @goModel( + model: "go.probo.inc/probo/pkg/server/api/console/v1/types.TrustCenterDocumentAccess" + ) { + id: ID! + status: TrustCenterDocumentAccessStatus! + document: Document @goField(forceResolver: true) + report: Report @goField(forceResolver: true) + trustCenterFile: TrustCenterFile @goField(forceResolver: true) +} + +type TrustCenterDocumentAccessConnection + @goModel( + model: "go.probo.inc/probo/pkg/server/api/console/v1/types.TrustCenterDocumentAccessConnection" + ) { + totalCount: Int! @goField(forceResolver: true) + edges: [TrustCenterDocumentAccessEdge!]! + pageInfo: PageInfo! +} + +type TrustCenterDocumentAccessEdge { + cursor: CursorKey! + node: TrustCenterDocumentAccess! +} + +type TrustCenterAccessConnection { + edges: [TrustCenterAccessEdge!]! + pageInfo: PageInfo! +} + +type TrustCenterAccessEdge { + cursor: CursorKey! + node: TrustCenterAccess! +} + +type TrustCenterReference implements Node { + id: ID! + name: String! + description: String + websiteUrl: String! + logoUrl: String! @goField(forceResolver: true) + rank: Int! + createdAt: Datetime! + updatedAt: Datetime! + + permission(action: String!): Boolean! @goField(forceResolver: true) +} + +type TrustCenterReferenceConnection + @goModel( + model: "go.probo.inc/probo/pkg/server/api/console/v1/types.TrustCenterReferenceConnection" + ) { + totalCount: Int! @goField(forceResolver: true) + edges: [TrustCenterReferenceEdge!]! + pageInfo: PageInfo! +} + +type TrustCenterReferenceEdge { + cursor: CursorKey! + node: TrustCenterReference! +} + +type ComplianceFramework implements Node + @goModel( + model: "go.probo.inc/probo/pkg/server/api/console/v1/types.ComplianceFramework" + ) { + id: ID! + framework: Framework! @goField(forceResolver: true) + rank: Int! + visibility: ComplianceFrameworkVisibility! + createdAt: Datetime! + updatedAt: Datetime! +} + +type ComplianceFrameworkConnection + @goModel( + model: "go.probo.inc/probo/pkg/server/api/console/v1/types.ComplianceFrameworkConnection" + ) { + edges: [ComplianceFrameworkEdge!]! + pageInfo: PageInfo! +} + +type ComplianceFrameworkEdge { + cursor: CursorKey! + node: ComplianceFramework! +} + +type ComplianceExternalURL implements Node { + id: ID! + name: String! + url: String! + rank: Int! + createdAt: Datetime! + updatedAt: Datetime! + + permission(action: String!): Boolean! @goField(forceResolver: true) +} + +type ComplianceExternalURLConnection + @goModel( + model: "go.probo.inc/probo/pkg/server/api/console/v1/types.ComplianceExternalURLConnection" + ) { + edges: [ComplianceExternalURLEdge!]! + pageInfo: PageInfo! +} + +type ComplianceExternalURLEdge { + cursor: CursorKey! + node: ComplianceExternalURL! +} + +type TrustCenterFile implements Node { + id: ID! + name: String! + category: String! + fileUrl: String! @goField(forceResolver: true) + trustCenterVisibility: TrustCenterVisibility! + createdAt: Datetime! + updatedAt: Datetime! + organization: Organization! @goField(forceResolver: true) + + permission(action: String!): Boolean! @goField(forceResolver: true) +} + +type TrustCenterFileConnection + @goModel( + model: "go.probo.inc/probo/pkg/server/api/console/v1/types.TrustCenterFileConnection" + ) { + totalCount: Int! @goField(forceResolver: true) + edges: [TrustCenterFileEdge!]! + pageInfo: PageInfo! +} + +type TrustCenterFileEdge { + cursor: CursorKey! + node: TrustCenterFile! +} + +type CustomDomain implements Node { + id: ID! + organization: Organization! + domain: String! + sslStatus: SSLStatus! + sslExpiresAt: Datetime + dnsRecords: [DNSRecordInstruction!]! + createdAt: Datetime! + updatedAt: Datetime! + + permission(action: String!): Boolean! @goField(forceResolver: true) +} + +type DNSRecordInstruction { + type: String! + name: String! + value: String! + ttl: Int! + purpose: String! +} + +extend type Mutation { + updateTrustCenter(input: UpdateTrustCenterInput!): UpdateTrustCenterPayload! + uploadTrustCenterNDA( + input: UploadTrustCenterNDAInput! + ): UploadTrustCenterNDAPayload! + deleteTrustCenterNDA( + input: DeleteTrustCenterNDAInput! + ): DeleteTrustCenterNDAPayload! + updateTrustCenterBrand( + input: UpdateTrustCenterBrandInput! + ): UpdateTrustCenterBrandPayload! + updateTrustCenterAccess( + input: UpdateTrustCenterAccessInput! + ): UpdateTrustCenterAccessPayload! + deleteTrustCenterAccess( + input: DeleteTrustCenterAccessInput! + ): DeleteTrustCenterAccessPayload! + createTrustCenterReference( + input: CreateTrustCenterReferenceInput! + ): CreateTrustCenterReferencePayload! + updateTrustCenterReference( + input: UpdateTrustCenterReferenceInput! + ): UpdateTrustCenterReferencePayload! + deleteTrustCenterReference( + input: DeleteTrustCenterReferenceInput! + ): DeleteTrustCenterReferencePayload! + createComplianceFramework( + input: CreateComplianceFrameworkInput! + ): CreateComplianceFrameworkPayload! + updateComplianceFramework( + input: UpdateComplianceFrameworkInput! + ): UpdateComplianceFrameworkPayload! + deleteComplianceFramework( + input: DeleteComplianceFrameworkInput! + ): DeleteComplianceFrameworkPayload! + createComplianceExternalURL( + input: CreateComplianceExternalURLInput! + ): CreateComplianceExternalURLPayload! + updateComplianceExternalURL( + input: UpdateComplianceExternalURLInput! + ): UpdateComplianceExternalURLPayload! + deleteComplianceExternalURL( + input: DeleteComplianceExternalURLInput! + ): DeleteComplianceExternalURLPayload! + createTrustCenterFile( + input: CreateTrustCenterFileInput! + ): CreateTrustCenterFilePayload! + updateTrustCenterFile( + input: UpdateTrustCenterFileInput! + ): UpdateTrustCenterFilePayload! + getTrustCenterFile( + input: GetTrustCenterFileInput! + ): GetTrustCenterFilePayload! + deleteTrustCenterFile( + input: DeleteTrustCenterFileInput! + ): DeleteTrustCenterFilePayload! + createCustomDomain( + input: CreateCustomDomainInput! + ): CreateCustomDomainPayload! + deleteCustomDomain( + input: DeleteCustomDomainInput! + ): DeleteCustomDomainPayload! +} + +input UpdateTrustCenterInput { + trustCenterId: ID! + active: Boolean + searchEngineIndexing: SearchEngineIndexing +} + +input UploadTrustCenterNDAInput { + trustCenterId: ID! + fileName: String! + file: Upload! +} + +input DeleteTrustCenterNDAInput { + trustCenterId: ID! +} + +input UpdateTrustCenterBrandInput { + trustCenterId: ID! + logoFile: Upload @goField(omittable: true) + darkLogoFile: Upload @goField(omittable: true) +} + +input TrustCenterDocumentAccessInput { + id: ID! + status: TrustCenterDocumentAccessStatus! +} + +input UpdateTrustCenterAccessInput { + id: ID! + name: String + state: TrustCenterAccessState + documents: [TrustCenterDocumentAccessInput!] + reports: [TrustCenterDocumentAccessInput!] + trustCenterFiles: [TrustCenterDocumentAccessInput!] +} + +input DeleteTrustCenterAccessInput { + id: ID! +} + +input CreateTrustCenterReferenceInput { + trustCenterId: ID! + name: String! + description: String + websiteUrl: String! + logoFile: Upload! +} + +input UpdateTrustCenterReferenceInput { + id: ID! + name: String + description: String @goField(omittable: true) + websiteUrl: String + logoFile: Upload + rank: Int +} + +input DeleteTrustCenterReferenceInput { + id: ID! +} + +input CreateComplianceFrameworkInput { + trustCenterId: ID! + frameworkId: ID! +} + +input UpdateComplianceFrameworkInput { + id: ID! + rank: Int! +} + +input DeleteComplianceFrameworkInput { + id: ID! +} + +input CreateComplianceExternalURLInput { + trustCenterId: ID! + name: String! + url: String! +} + +input UpdateComplianceExternalURLInput { + id: ID! + name: String! + url: String! + rank: Int +} + +input DeleteComplianceExternalURLInput { + id: ID! +} + +input CreateTrustCenterFileInput { + organizationId: ID! + name: String! + category: String! + file: Upload! + trustCenterVisibility: TrustCenterVisibility! +} + +input UpdateTrustCenterFileInput { + id: ID! + name: String + category: String + trustCenterVisibility: TrustCenterVisibility +} + +input GetTrustCenterFileInput { + id: ID! +} + +input DeleteTrustCenterFileInput { + id: ID! +} + +input CreateCustomDomainInput { + organizationId: ID! + domain: String! +} + +input DeleteCustomDomainInput { + organizationId: ID! +} + +type UpdateTrustCenterPayload { + trustCenter: TrustCenter! +} + +type UploadTrustCenterNDAPayload { + trustCenter: TrustCenter! +} + +type DeleteTrustCenterNDAPayload { + trustCenter: TrustCenter! +} + +type UpdateTrustCenterBrandPayload { + trustCenter: TrustCenter! +} + +type UpdateTrustCenterAccessPayload { + trustCenterAccess: TrustCenterAccess! +} + +type DeleteTrustCenterAccessPayload { + deletedTrustCenterAccessId: ID! +} + +type CreateTrustCenterReferencePayload { + trustCenterReferenceEdge: TrustCenterReferenceEdge! +} + +type UpdateTrustCenterReferencePayload { + trustCenterReference: TrustCenterReference! +} + +type DeleteTrustCenterReferencePayload { + deletedTrustCenterReferenceId: ID! +} + +type CreateComplianceFrameworkPayload { + complianceFrameworkEdge: ComplianceFrameworkEdge! +} + +type UpdateComplianceFrameworkPayload { + complianceFramework: ComplianceFramework! +} + +type DeleteComplianceFrameworkPayload { + deletedComplianceFrameworkId: ID! +} + +type CreateComplianceExternalURLPayload { + complianceExternalUrlEdge: ComplianceExternalURLEdge! +} + +type UpdateComplianceExternalURLPayload { + complianceExternalUrl: ComplianceExternalURL! +} + +type DeleteComplianceExternalURLPayload { + deletedComplianceExternalUrlId: ID! +} + +type CreateTrustCenterFilePayload { + trustCenterFileEdge: TrustCenterFileEdge! +} + +type UpdateTrustCenterFilePayload { + trustCenterFile: TrustCenterFile! +} + +type GetTrustCenterFilePayload { + trustCenterFile: TrustCenterFile! +} + +type DeleteTrustCenterFilePayload { + deletedTrustCenterFileId: ID! +} + +type CreateCustomDomainPayload { + customDomain: CustomDomain! +} + +type DeleteCustomDomainPayload { + deletedCustomDomainId: ID! +} diff --git a/pkg/server/api/console/v1/graphql/vendor.graphql b/pkg/server/api/console/v1/graphql/vendor.graphql new file mode 100644 index 000000000..064d10987 --- /dev/null +++ b/pkg/server/api/console/v1/graphql/vendor.graphql @@ -0,0 +1,704 @@ +extend type Organization { + vendors( + first: Int + after: CursorKey + last: Int + before: CursorKey + orderBy: VendorOrder + filter: VendorFilter = { snapshotId: null } + ): VendorConnection! @goField(forceResolver: true) +} + +enum VendorCategory + @goModel(model: "go.probo.inc/probo/pkg/coredata.VendorCategory") { + ANALYTICS + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.VendorCategoryAnalytics" + ) + CLOUD_MONITORING + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.VendorCategoryCloudMonitoring" + ) + CLOUD_PROVIDER + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.VendorCategoryCloudProvider" + ) + COLLABORATION + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.VendorCategoryCollaboration" + ) + CUSTOMER_SUPPORT + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.VendorCategoryCustomerSupport" + ) + DATA_STORAGE_AND_PROCESSING + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.VendorCategoryDataStorageAndProcessing" + ) + DOCUMENT_MANAGEMENT + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.VendorCategoryDocumentManagement" + ) + EMPLOYEE_MANAGEMENT + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.VendorCategoryEmployeeManagement" + ) + ENGINEERING + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.VendorCategoryEngineering" + ) + FINANCE + @goEnum(value: "go.probo.inc/probo/pkg/coredata.VendorCategoryFinance") + IDENTITY_PROVIDER + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.VendorCategoryIdentityProvider" + ) + IT @goEnum(value: "go.probo.inc/probo/pkg/coredata.VendorCategoryIT") + MARKETING + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.VendorCategoryMarketing" + ) + OFFICE_OPERATIONS + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.VendorCategoryOfficeOperations" + ) + OTHER @goEnum(value: "go.probo.inc/probo/pkg/coredata.VendorCategoryOther") + PASSWORD_MANAGEMENT + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.VendorCategoryPasswordManagement" + ) + PRODUCT_AND_DESIGN + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.VendorCategoryProductAndDesign" + ) + PROFESSIONAL_SERVICES + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.VendorCategoryProfessionalServices" + ) + RECRUITING + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.VendorCategoryRecruiting" + ) + SALES @goEnum(value: "go.probo.inc/probo/pkg/coredata.VendorCategorySales") + SECURITY + @goEnum(value: "go.probo.inc/probo/pkg/coredata.VendorCategorySecurity") + VERSION_CONTROL + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.VendorCategoryVersionControl" + ) +} + +enum DataSensitivity + @goModel(model: "go.probo.inc/probo/pkg/coredata.DataSensitivity") { + NONE @goEnum(value: "go.probo.inc/probo/pkg/coredata.DataSensitivityNone") + LOW @goEnum(value: "go.probo.inc/probo/pkg/coredata.DataSensitivityLow") + MEDIUM + @goEnum(value: "go.probo.inc/probo/pkg/coredata.DataSensitivityMedium") + HIGH @goEnum(value: "go.probo.inc/probo/pkg/coredata.DataSensitivityHigh") + CRITICAL + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.DataSensitivityCritical" + ) +} + +enum BusinessImpact + @goModel(model: "go.probo.inc/probo/pkg/coredata.BusinessImpact") { + LOW @goEnum(value: "go.probo.inc/probo/pkg/coredata.BusinessImpactLow") + MEDIUM + @goEnum(value: "go.probo.inc/probo/pkg/coredata.BusinessImpactMedium") + HIGH @goEnum(value: "go.probo.inc/probo/pkg/coredata.BusinessImpactHigh") + CRITICAL + @goEnum(value: "go.probo.inc/probo/pkg/coredata.BusinessImpactCritical") +} + +enum VendorOrderField + @goModel(model: "go.probo.inc/probo/pkg/coredata.VendorOrderField") { + NAME @goEnum(value: "go.probo.inc/probo/pkg/coredata.VendorOrderFieldName") + CREATED_AT + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.VendorOrderFieldCreatedAt" + ) + UPDATED_AT + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.VendorOrderFieldUpdatedAt" + ) +} + +enum VendorComplianceReportOrderField + @goModel( + model: "go.probo.inc/probo/pkg/coredata.VendorComplianceReportOrderField" + ) { + REPORT_DATE + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.VendorComplianceReportOrderFieldReportDate" + ) + CREATED_AT + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.VendorComplianceReportOrderFieldCreatedAt" + ) +} + +enum VendorContactOrderField + @goModel(model: "go.probo.inc/probo/pkg/coredata.VendorContactOrderField") { + CREATED_AT + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.VendorContactOrderFieldCreatedAt" + ) + FULL_NAME + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.VendorContactOrderFieldFullName" + ) + EMAIL + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.VendorContactOrderFieldEmail" + ) +} + +enum VendorServiceOrderField + @goModel(model: "go.probo.inc/probo/pkg/coredata.VendorServiceOrderField") { + CREATED_AT + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.VendorServiceOrderFieldCreatedAt" + ) + NAME + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.VendorServiceOrderFieldName" + ) +} + +enum VendorRiskAssessmentOrderField + @goModel( + model: "go.probo.inc/probo/pkg/coredata.VendorRiskAssessmentOrderField" + ) { + CREATED_AT + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.VendorRiskAssessmentOrderFieldCreatedAt" + ) + EXPIRES_AT + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.VendorRiskAssessmentOrderFieldExpiresAt" + ) +} + +input VendorOrder + @goModel( + model: "go.probo.inc/probo/pkg/server/api/console/v1/types.VendorOrderBy" + ) { + direction: OrderDirection! + field: VendorOrderField! +} + +input VendorComplianceReportOrder + @goModel( + model: "go.probo.inc/probo/pkg/server/api/console/v1/types.VendorComplianceReportOrderBy" + ) { + direction: OrderDirection! + field: VendorComplianceReportOrderField! +} + +input VendorContactOrder + @goModel( + model: "go.probo.inc/probo/pkg/server/api/console/v1/types.VendorContactOrderBy" + ) { + direction: OrderDirection! + field: VendorContactOrderField! +} + +input VendorServiceOrder + @goModel( + model: "go.probo.inc/probo/pkg/server/api/console/v1/types.VendorServiceOrderBy" + ) { + direction: OrderDirection! + field: VendorServiceOrderField! +} + +input VendorRiskAssessmentOrder { + field: VendorRiskAssessmentOrderField! + direction: OrderDirection! +} + +input VendorFilter { + snapshotId: ID +} + +type Vendor implements Node { + id: ID! + snapshotId: ID + name: String! + category: VendorCategory! + description: String + + organization: Organization! @goField(forceResolver: true) + + complianceReports( + first: Int + after: CursorKey + last: Int + before: CursorKey + orderBy: VendorComplianceReportOrder + ): VendorComplianceReportConnection! @goField(forceResolver: true) + + businessAssociateAgreement: VendorBusinessAssociateAgreement + @goField(forceResolver: true) + dataPrivacyAgreement: VendorDataPrivacyAgreement + @goField(forceResolver: true) + + contacts( + first: Int + after: CursorKey + last: Int + before: CursorKey + orderBy: VendorContactOrder + ): VendorContactConnection! @goField(forceResolver: true) + + services( + first: Int + after: CursorKey + last: Int + before: CursorKey + orderBy: VendorServiceOrder + ): VendorServiceConnection! @goField(forceResolver: true) + + riskAssessments( + first: Int + after: CursorKey + last: Int + before: CursorKey + orderBy: VendorRiskAssessmentOrder + ): VendorRiskAssessmentConnection! @goField(forceResolver: true) + + businessOwner: Profile @goField(forceResolver: true) + securityOwner: Profile @goField(forceResolver: true) + + statusPageUrl: String + termsOfServiceUrl: String + privacyPolicyUrl: String + serviceLevelAgreementUrl: String + dataProcessingAgreementUrl: String + businessAssociateAgreementUrl: String + subprocessorsListUrl: String + certifications: [String!]! + countries: [CountryCode!]! + securityPageUrl: String + trustPageUrl: String + headquarterAddress: String + legalName: String + websiteUrl: String + showOnTrustCenter: Boolean! + createdAt: Datetime! + updatedAt: Datetime! + + permission(action: String!): Boolean! @goField(forceResolver: true) +} + +type VendorComplianceReport implements Node { + id: ID! + vendor: Vendor! @goField(forceResolver: true) + reportDate: Datetime! + validUntil: Datetime + reportName: String! + file: File @goField(forceResolver: true) + createdAt: Datetime! + updatedAt: Datetime! + + permission(action: String!): Boolean! @goField(forceResolver: true) +} + +type VendorBusinessAssociateAgreement implements Node { + id: ID! + vendor: Vendor! @goField(forceResolver: true) + validFrom: Datetime + validUntil: Datetime + fileName: String! + fileUrl: String! @goField(forceResolver: true) + fileSize: BigInt! + createdAt: Datetime! + updatedAt: Datetime! + + permission(action: String!): Boolean! @goField(forceResolver: true) +} + +type VendorContact implements Node { + id: ID! + vendor: Vendor! @goField(forceResolver: true) + fullName: String + email: EmailAddr + phone: String + role: String + createdAt: Datetime! + updatedAt: Datetime! + + permission(action: String!): Boolean! @goField(forceResolver: true) +} + +type VendorService implements Node { + id: ID! + vendor: Vendor! @goField(forceResolver: true) + name: String! + description: String + createdAt: Datetime! + updatedAt: Datetime! + + permission(action: String!): Boolean! @goField(forceResolver: true) +} + +type VendorDataPrivacyAgreement implements Node { + id: ID! + vendor: Vendor! @goField(forceResolver: true) + validFrom: Datetime + validUntil: Datetime + fileName: String! + fileUrl: String! @goField(forceResolver: true) + fileSize: BigInt! + createdAt: Datetime! + updatedAt: Datetime! + + permission(action: String!): Boolean! @goField(forceResolver: true) +} + +type VendorRiskAssessment implements Node { + id: ID! + vendor: Vendor! @goField(forceResolver: true) + expiresAt: Datetime! + dataSensitivity: DataSensitivity! + businessImpact: BusinessImpact! + notes: String + createdAt: Datetime! + updatedAt: Datetime! + + permission(action: String!): Boolean! @goField(forceResolver: true) +} + +type VendorConnection + @goModel( + model: "go.probo.inc/probo/pkg/server/api/console/v1/types.VendorConnection" + ) { + totalCount: Int! @goField(forceResolver: true) + edges: [VendorEdge!]! + pageInfo: PageInfo! +} + +type VendorEdge { + cursor: CursorKey! + node: Vendor! +} + +type VendorComplianceReportConnection { + edges: [VendorComplianceReportEdge!]! + pageInfo: PageInfo! +} + +type VendorComplianceReportEdge { + cursor: CursorKey! + node: VendorComplianceReport! +} + +type VendorContactConnection { + edges: [VendorContactEdge!]! + pageInfo: PageInfo! +} + +type VendorContactEdge { + cursor: CursorKey! + node: VendorContact! +} + +type VendorServiceConnection { + edges: [VendorServiceEdge!]! + pageInfo: PageInfo! +} + +type VendorServiceEdge { + cursor: CursorKey! + node: VendorService! +} + +type VendorRiskAssessmentConnection { + edges: [VendorRiskAssessmentEdge!]! + pageInfo: PageInfo! +} + +type VendorRiskAssessmentEdge { + cursor: CursorKey! + node: VendorRiskAssessment! +} + +extend type Mutation { + createVendor(input: CreateVendorInput!): CreateVendorPayload! + updateVendor(input: UpdateVendorInput!): UpdateVendorPayload! + deleteVendor(input: DeleteVendorInput!): DeleteVendorPayload! + createVendorContact( + input: CreateVendorContactInput! + ): CreateVendorContactPayload! + updateVendorContact( + input: UpdateVendorContactInput! + ): UpdateVendorContactPayload! + deleteVendorContact( + input: DeleteVendorContactInput! + ): DeleteVendorContactPayload! + createVendorService( + input: CreateVendorServiceInput! + ): CreateVendorServicePayload! + updateVendorService( + input: UpdateVendorServiceInput! + ): UpdateVendorServicePayload! + deleteVendorService( + input: DeleteVendorServiceInput! + ): DeleteVendorServicePayload! + uploadVendorComplianceReport( + input: UploadVendorComplianceReportInput! + ): UploadVendorComplianceReportPayload! + deleteVendorComplianceReport( + input: DeleteVendorComplianceReportInput! + ): DeleteVendorComplianceReportPayload! + uploadVendorBusinessAssociateAgreement( + input: UploadVendorBusinessAssociateAgreementInput! + ): UploadVendorBusinessAssociateAgreementPayload! + updateVendorBusinessAssociateAgreement( + input: UpdateVendorBusinessAssociateAgreementInput! + ): UpdateVendorBusinessAssociateAgreementPayload! + deleteVendorBusinessAssociateAgreement( + input: DeleteVendorBusinessAssociateAgreementInput! + ): DeleteVendorBusinessAssociateAgreementPayload! + uploadVendorDataPrivacyAgreement( + input: UploadVendorDataPrivacyAgreementInput! + ): UploadVendorDataPrivacyAgreementPayload! + updateVendorDataPrivacyAgreement( + input: UpdateVendorDataPrivacyAgreementInput! + ): UpdateVendorDataPrivacyAgreementPayload! + deleteVendorDataPrivacyAgreement( + input: DeleteVendorDataPrivacyAgreementInput! + ): DeleteVendorDataPrivacyAgreementPayload! + createVendorRiskAssessment( + input: CreateVendorRiskAssessmentInput! + ): CreateVendorRiskAssessmentPayload! + assessVendor(input: AssessVendorInput!): AssessVendorPayload! +} + +input CreateVendorInput { + organizationId: ID! + name: String! + description: String + headquarterAddress: String + legalName: String + websiteUrl: String + privacyPolicyUrl: String + category: VendorCategory + serviceLevelAgreementUrl: String + dataProcessingAgreementUrl: String + businessAssociateAgreementUrl: String + subprocessorsListUrl: String + certifications: [String!] + countries: [CountryCode!] + securityPageUrl: String + trustPageUrl: String + statusPageUrl: String + termsOfServiceUrl: String + businessOwnerId: ID + securityOwnerId: ID +} + +input UpdateVendorInput { + id: ID! + name: String + description: String @goField(omittable: true) + statusPageUrl: String @goField(omittable: true) + termsOfServiceUrl: String @goField(omittable: true) + privacyPolicyUrl: String @goField(omittable: true) + serviceLevelAgreementUrl: String @goField(omittable: true) + dataProcessingAgreementUrl: String @goField(omittable: true) + businessAssociateAgreementUrl: String @goField(omittable: true) + subprocessorsListUrl: String @goField(omittable: true) + websiteUrl: String @goField(omittable: true) + legalName: String @goField(omittable: true) + headquarterAddress: String @goField(omittable: true) + category: VendorCategory + certifications: [String!] + countries: [CountryCode!] + securityPageUrl: String @goField(omittable: true) + trustPageUrl: String @goField(omittable: true) + businessOwnerId: ID @goField(omittable: true) + securityOwnerId: ID @goField(omittable: true) + showOnTrustCenter: Boolean +} + +input DeleteVendorInput { + vendorId: ID! +} + +input CreateVendorContactInput { + vendorId: ID! + fullName: String + email: EmailAddr + phone: String + role: String +} + +input UpdateVendorContactInput { + id: ID! + fullName: String @goField(omittable: true) + email: EmailAddr @goField(omittable: true) + phone: String @goField(omittable: true) + role: String @goField(omittable: true) +} + +input DeleteVendorContactInput { + vendorContactId: ID! +} + +input CreateVendorServiceInput { + vendorId: ID! + name: String! + description: String + url: String + type: String +} + +input UpdateVendorServiceInput { + id: ID! + name: String + description: String @goField(omittable: true) + url: String + type: String +} + +input DeleteVendorServiceInput { + vendorServiceId: ID! +} + +input UploadVendorComplianceReportInput { + vendorId: ID! + reportDate: Datetime! + validUntil: Datetime + reportName: String! + file: Upload! +} + +input DeleteVendorComplianceReportInput { + reportId: ID! +} + +input UploadVendorBusinessAssociateAgreementInput { + vendorId: ID! + validFrom: Datetime + validUntil: Datetime + fileName: String! + file: Upload! +} + +input UpdateVendorBusinessAssociateAgreementInput { + vendorId: ID! + validFrom: Datetime @goField(omittable: true) + validUntil: Datetime @goField(omittable: true) +} + +input DeleteVendorBusinessAssociateAgreementInput { + vendorId: ID! +} + +input UploadVendorDataPrivacyAgreementInput { + vendorId: ID! + validFrom: Datetime + validUntil: Datetime + fileName: String! + file: Upload! +} + +input UpdateVendorDataPrivacyAgreementInput { + vendorId: ID! + validFrom: Datetime @goField(omittable: true) + validUntil: Datetime @goField(omittable: true) +} + +input DeleteVendorDataPrivacyAgreementInput { + vendorId: ID! +} + +input CreateVendorRiskAssessmentInput { + vendorId: ID! + expiresAt: Datetime! + dataSensitivity: DataSensitivity! + businessImpact: BusinessImpact! + notes: String +} + +input AssessVendorInput { + id: ID! + websiteUrl: String! +} + +type CreateVendorPayload { + vendorEdge: VendorEdge! +} + +type UpdateVendorPayload { + vendor: Vendor! +} + +type DeleteVendorPayload { + deletedVendorId: ID! +} + +type CreateVendorContactPayload { + vendorContactEdge: VendorContactEdge! +} + +type UpdateVendorContactPayload { + vendorContact: VendorContact! +} + +type DeleteVendorContactPayload { + deletedVendorContactId: ID! +} + +type CreateVendorServicePayload { + vendorServiceEdge: VendorServiceEdge! +} + +type UpdateVendorServicePayload { + vendorService: VendorService! +} + +type DeleteVendorServicePayload { + deletedVendorServiceId: ID! +} + +type UploadVendorComplianceReportPayload { + vendorComplianceReportEdge: VendorComplianceReportEdge! +} + +type DeleteVendorComplianceReportPayload { + deletedVendorComplianceReportId: ID! +} + +type UploadVendorBusinessAssociateAgreementPayload { + vendorBusinessAssociateAgreement: VendorBusinessAssociateAgreement! +} + +type UpdateVendorBusinessAssociateAgreementPayload { + vendorBusinessAssociateAgreement: VendorBusinessAssociateAgreement! +} + +type DeleteVendorBusinessAssociateAgreementPayload { + deletedVendorId: ID! +} + +type UploadVendorDataPrivacyAgreementPayload { + vendorDataPrivacyAgreement: VendorDataPrivacyAgreement! +} + +type UpdateVendorDataPrivacyAgreementPayload { + vendorDataPrivacyAgreement: VendorDataPrivacyAgreement! +} + +type DeleteVendorDataPrivacyAgreementPayload { + deletedVendorId: ID! +} + +type CreateVendorRiskAssessmentPayload { + vendorRiskAssessmentEdge: VendorRiskAssessmentEdge! +} + +type AssessVendorPayload { + vendor: Vendor! +} diff --git a/pkg/server/api/console/v1/graphql/webhook.graphql b/pkg/server/api/console/v1/graphql/webhook.graphql new file mode 100644 index 000000000..045c70529 --- /dev/null +++ b/pkg/server/api/console/v1/graphql/webhook.graphql @@ -0,0 +1,179 @@ +extend type Organization { + webhookSubscriptions( + first: Int + after: CursorKey + last: Int + before: CursorKey + orderBy: WebhookSubscriptionOrder + ): WebhookSubscriptionConnection! @goField(forceResolver: true) +} + +enum WebhookEventType + @goModel(model: "go.probo.inc/probo/pkg/coredata.WebhookEventType") { + MEETING_CREATED + @goEnum(value: "go.probo.inc/probo/pkg/coredata.WebhookEventTypeMeetingCreated") + MEETING_UPDATED + @goEnum(value: "go.probo.inc/probo/pkg/coredata.WebhookEventTypeMeetingUpdated") + MEETING_DELETED + @goEnum(value: "go.probo.inc/probo/pkg/coredata.WebhookEventTypeMeetingDeleted") + VENDOR_CREATED + @goEnum(value: "go.probo.inc/probo/pkg/coredata.WebhookEventTypeVendorCreated") + VENDOR_UPDATED + @goEnum(value: "go.probo.inc/probo/pkg/coredata.WebhookEventTypeVendorUpdated") + VENDOR_DELETED + @goEnum(value: "go.probo.inc/probo/pkg/coredata.WebhookEventTypeVendorDeleted") + USER_CREATED + @goEnum(value: "go.probo.inc/probo/pkg/coredata.WebhookEventTypeUserCreated") + USER_UPDATED + @goEnum(value: "go.probo.inc/probo/pkg/coredata.WebhookEventTypeUserUpdated") + USER_DELETED + @goEnum(value: "go.probo.inc/probo/pkg/coredata.WebhookEventTypeUserDeleted") + OBLIGATION_CREATED + @goEnum(value: "go.probo.inc/probo/pkg/coredata.WebhookEventTypeObligationCreated") + OBLIGATION_UPDATED + @goEnum(value: "go.probo.inc/probo/pkg/coredata.WebhookEventTypeObligationUpdated") + OBLIGATION_DELETED + @goEnum(value: "go.probo.inc/probo/pkg/coredata.WebhookEventTypeObligationDeleted") +} + +enum WebhookEventStatus + @goModel(model: "go.probo.inc/probo/pkg/coredata.WebhookEventStatus") { + PENDING + @goEnum(value: "go.probo.inc/probo/pkg/coredata.WebhookEventStatusPending") + SUCCEEDED + @goEnum(value: "go.probo.inc/probo/pkg/coredata.WebhookEventStatusSucceeded") + FAILED + @goEnum(value: "go.probo.inc/probo/pkg/coredata.WebhookEventStatusFailed") +} + +enum WebhookSubscriptionOrderField + @goModel( + model: "go.probo.inc/probo/pkg/coredata.WebhookSubscriptionOrderField" + ) { + CREATED_AT + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.WebhookSubscriptionOrderFieldCreatedAt" + ) +} + +enum WebhookEventOrderField + @goModel( + model: "go.probo.inc/probo/pkg/coredata.WebhookEventOrderField" + ) { + CREATED_AT + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.WebhookEventOrderFieldCreatedAt" + ) +} + +input WebhookSubscriptionOrder + @goModel( + model: "go.probo.inc/probo/pkg/server/api/console/v1/types.WebhookSubscriptionOrderBy" + ) { + direction: OrderDirection! + field: WebhookSubscriptionOrderField! +} + +input WebhookEventOrder + @goModel( + model: "go.probo.inc/probo/pkg/server/api/console/v1/types.WebhookEventOrderBy" + ) { + field: WebhookEventOrderField! + direction: OrderDirection! +} + +type WebhookSubscription implements Node { + id: ID! + organization: Organization @goField(forceResolver: true) + endpointUrl: String! + signingSecret: String! @goField(forceResolver: true) + selectedEvents: [WebhookEventType!]! + createdAt: Datetime! + updatedAt: Datetime! + + events( + first: Int + after: CursorKey + last: Int + before: CursorKey + orderBy: WebhookEventOrder + ): WebhookEventConnection! @goField(forceResolver: true) + + permission(action: String!): Boolean! @goField(forceResolver: true) +} + +type WebhookEvent implements Node { + id: ID! + webhookSubscriptionId: ID! + status: WebhookEventStatus! + response: String + createdAt: Datetime! +} + +type WebhookSubscriptionConnection + @goModel( + model: "go.probo.inc/probo/pkg/server/api/console/v1/types.WebhookSubscriptionConnection" + ) { + edges: [WebhookSubscriptionEdge!]! + pageInfo: PageInfo! + totalCount: Int! @goField(forceResolver: true) +} + +type WebhookSubscriptionEdge { + cursor: CursorKey! + node: WebhookSubscription! +} + +type WebhookEventConnection + @goModel( + model: "go.probo.inc/probo/pkg/server/api/console/v1/types.WebhookEventConnection" + ) { + edges: [WebhookEventEdge!]! + pageInfo: PageInfo! + totalCount: Int! @goField(forceResolver: true) +} + +type WebhookEventEdge { + cursor: CursorKey! + node: WebhookEvent! +} + +extend type Mutation { + createWebhookSubscription( + input: CreateWebhookSubscriptionInput! + ): CreateWebhookSubscriptionPayload! + updateWebhookSubscription( + input: UpdateWebhookSubscriptionInput! + ): UpdateWebhookSubscriptionPayload! + deleteWebhookSubscription( + input: DeleteWebhookSubscriptionInput! + ): DeleteWebhookSubscriptionPayload! +} + +input CreateWebhookSubscriptionInput { + organizationId: ID! + endpointUrl: String! + selectedEvents: [WebhookEventType!]! +} + +input UpdateWebhookSubscriptionInput { + id: ID! + endpointUrl: String + selectedEvents: [WebhookEventType!] +} + +input DeleteWebhookSubscriptionInput { + webhookSubscriptionId: ID! +} + +type CreateWebhookSubscriptionPayload { + webhookSubscriptionEdge: WebhookSubscriptionEdge! +} + +type UpdateWebhookSubscriptionPayload { + webhookSubscription: WebhookSubscription! +} + +type DeleteWebhookSubscriptionPayload { + deletedWebhookSubscriptionId: ID! +} diff --git a/pkg/server/api/console/v1/mailing_list.resolvers.go b/pkg/server/api/console/v1/mailing_list.resolvers.go new file mode 100644 index 000000000..b60d5c5d8 --- /dev/null +++ b/pkg/server/api/console/v1/mailing_list.resolvers.go @@ -0,0 +1,287 @@ +package console_v1 + +// This file will be automatically regenerated based on the schema, any resolver +// implementations +// will be copied through when generating and any unknown code will be moved to the end. +// Code generated by github.com/99designs/gqlgen version v0.17.87 + +import ( + "context" + "errors" + "fmt" + + "go.gearno.de/kit/log" + "go.probo.inc/probo/pkg/coredata" + "go.probo.inc/probo/pkg/mailman" + "go.probo.inc/probo/pkg/page" + "go.probo.inc/probo/pkg/probo" + "go.probo.inc/probo/pkg/server/api/console/v1/schema" + "go.probo.inc/probo/pkg/server/api/console/v1/types" + "go.probo.inc/probo/pkg/server/gqlutils" + "go.probo.inc/probo/pkg/validator" +) + +// Subscribers is the resolver for the subscribers field on MailingList. +func (r *mailingListResolver) Subscribers(ctx context.Context, obj *types.MailingList, first *int, after *page.CursorKey, last *int, before *page.CursorKey) (*types.MailingListSubscriberConnection, error) { + if err := r.authorize(ctx, obj.ID, probo.ActionMailingListSubscriberList); err != nil { + return nil, err + } + + pageOrderBy := page.OrderBy[coredata.MailingListSubscriberOrderField]{ + Field: coredata.MailingListSubscriberOrderFieldCreatedAt, + Direction: page.OrderDirectionDesc, + } + + cursor := types.NewCursor(first, after, last, before, pageOrderBy) + + result, err := r.mailman.ListSubscribers(ctx, obj.ID, cursor) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot list mailing list subscribers", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return types.NewMailingListSubscriberConnection(result, r, obj.ID), nil +} + +// Updates is the resolver for the updates field on MailingList. +func (r *mailingListResolver) Updates(ctx context.Context, obj *types.MailingList, first *int, after *page.CursorKey, last *int, before *page.CursorKey) (*types.MailingListUpdateConnection, error) { + if err := r.authorize(ctx, obj.ID, probo.ActionMailingListUpdateList); err != nil { + return nil, err + } + + pageOrderBy := page.OrderBy[coredata.MailingListUpdateOrderField]{ + Field: coredata.MailingListUpdateOrderFieldUpdatedAt, + Direction: page.OrderDirectionDesc, + } + + cursor := types.NewCursor(first, after, last, before, pageOrderBy) + + result, err := r.mailman.ListMailingListUpdates(ctx, obj.ID, cursor) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot list mailing list updates", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return types.NewMailingListUpdateConnection(result, r, obj.ID), nil +} + +// TotalCount is the resolver for the totalCount field. +func (r *mailingListSubscriberConnectionResolver) TotalCount(ctx context.Context, obj *types.MailingListSubscriberConnection) (int, error) { + if err := r.authorize(ctx, obj.ParentID, probo.ActionMailingListSubscriberList); err != nil { + return 0, err + } + + switch obj.Resolver.(type) { + case *mailingListResolver: + count, err := r.mailman.CountSubscribers(ctx, obj.ParentID) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot count mailing list subscribers", log.Error(err)) + return 0, gqlutils.Internal(ctx) + } + return count, nil + } + + r.logger.ErrorCtx(ctx, "unsupported resolver for mailing list subscriber connection", log.String("resolver", fmt.Sprintf("%T", obj.Resolver))) + return 0, gqlutils.Internal(ctx) +} + +// TotalCount is the resolver for the totalCount field on MailingListUpdateConnection. +func (r *mailingListUpdateConnectionResolver) TotalCount(ctx context.Context, obj *types.MailingListUpdateConnection) (int, error) { + if err := r.authorize(ctx, obj.ParentID, probo.ActionMailingListUpdateList); err != nil { + return 0, err + } + + count, err := r.mailman.CountMailingListUpdates(ctx, obj.ParentID) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot count mailing list updates", log.Error(err)) + return 0, gqlutils.Internal(ctx) + } + + return count, nil +} + +// CreateMailingListUpdate is the resolver for the createMailingListUpdate field. +func (r *mutationResolver) CreateMailingListUpdate(ctx context.Context, input types.CreateMailingListUpdateInput) (*types.CreateMailingListUpdatePayload, error) { + if err := r.authorize(ctx, input.MailingListID, probo.ActionMailingListUpdateCreate); err != nil { + return nil, err + } + + mlu, err := r.mailman.CreateMailingListUpdate( + ctx, + &mailman.CreateMailingListUpdateRequest{ + MailingListID: input.MailingListID, + Title: input.Title, + Body: input.Body, + }, + ) + if err != nil { + if validationErrors, ok := errors.AsType[validator.ValidationErrors](err); ok { + return nil, gqlutils.InvalidValidationErrors(ctx, validationErrors) + } + r.logger.ErrorCtx(ctx, "cannot create mailing list update", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.CreateMailingListUpdatePayload{ + MailingListUpdate: types.NewMailingListUpdate(mlu), + }, nil +} + +// UpdateMailingListUpdate is the resolver for the updateMailingListUpdate field. +func (r *mutationResolver) UpdateMailingListUpdate(ctx context.Context, input types.UpdateMailingListUpdateInput) (*types.UpdateMailingListUpdatePayload, error) { + if err := r.authorize(ctx, input.ID, probo.ActionMailingListUpdateUpdate); err != nil { + return nil, err + } + + mlu, err := r.mailman.UpdateMailingListUpdate( + ctx, + &mailman.UpdateMailingListUpdateRequest{ + ID: input.ID, + Title: input.Title, + Body: input.Body, + }, + ) + if err != nil { + if validationErrors, ok := errors.AsType[validator.ValidationErrors](err); ok { + return nil, gqlutils.InvalidValidationErrors(ctx, validationErrors) + } + if errors.Is(err, mailman.ErrMailingListUpdateAlreadySent) { + return nil, gqlutils.Conflictf(ctx, "mailing list update can only be edited when in draft") + } + if errors.Is(err, mailman.ErrMailingListUpdateNotFound) { + return nil, gqlutils.NotFoundf(ctx, "mailing list update not found") + } + r.logger.ErrorCtx(ctx, "cannot update mailing list update", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.UpdateMailingListUpdatePayload{ + MailingListUpdate: types.NewMailingListUpdate(mlu), + }, nil +} + +// SendMailingListUpdate is the resolver for the sendMailingListUpdate field. +func (r *mutationResolver) SendMailingListUpdate(ctx context.Context, input types.SendMailingListUpdateInput) (*types.SendMailingListUpdatePayload, error) { + if err := r.authorize(ctx, input.ID, probo.ActionMailingListUpdateUpdate); err != nil { + return nil, err + } + + mlu, err := r.mailman.SendMailingListUpdate(ctx, input.ID) + if err != nil { + if errors.Is(err, mailman.ErrMailingListUpdateAlreadySent) { + return nil, gqlutils.Conflictf(ctx, "mailing list update has already been queued for sending") + } + if errors.Is(err, mailman.ErrMailingListUpdateNotFound) { + return nil, gqlutils.NotFoundf(ctx, "mailing list update not found") + } + r.logger.ErrorCtx(ctx, "cannot queue mailing list update for sending", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.SendMailingListUpdatePayload{ + MailingListUpdate: types.NewMailingListUpdate(mlu), + }, nil +} + +// DeleteMailingListUpdate is the resolver for the deleteMailingListUpdate field. +func (r *mutationResolver) DeleteMailingListUpdate(ctx context.Context, input types.DeleteMailingListUpdateInput) (*types.DeleteMailingListUpdatePayload, error) { + if err := r.authorize(ctx, input.ID, probo.ActionMailingListUpdateDelete); err != nil { + return nil, err + } + + if err := r.mailman.DeleteMailingListUpdate(ctx, input.ID); err != nil { + if errors.Is(err, mailman.ErrMailingListUpdateNotFound) { + return nil, gqlutils.NotFoundf(ctx, "mailing list update not found") + } + r.logger.ErrorCtx(ctx, "cannot delete mailing list update", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.DeleteMailingListUpdatePayload{ + DeletedMailingListUpdateID: input.ID, + }, nil +} + +// UpdateMailingList is the resolver for the updateMailingList field. +func (r *mutationResolver) UpdateMailingList(ctx context.Context, input types.UpdateMailingListInput) (*types.UpdateMailingListPayload, error) { + if err := r.authorize(ctx, input.ID, probo.ActionMailingListUpdate); err != nil { + return nil, err + } + + ml, err := r.mailman.UpdateMailingList(ctx, input.ID, input.ReplyTo) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot update mailing list", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.UpdateMailingListPayload{ + MailingList: types.NewMailingList(ml), + }, nil +} + +// CreateMailingListSubscriber is the resolver for the createMailingListSubscriber field. +func (r *mutationResolver) CreateMailingListSubscriber(ctx context.Context, input types.CreateMailingListSubscriberInput) (*types.CreateMailingListSubscriberPayload, error) { + if err := r.authorize(ctx, input.MailingListID, probo.ActionMailingListSubscriberCreate); err != nil { + return nil, err + } + + subscriber, err := r.mailman.CreateSubscriber( + ctx, + &mailman.CreateSubscriberRequest{ + MailingListID: input.MailingListID, + Email: input.Email, + FullName: input.FullName, + Confirmed: input.Confirmed != nil && *input.Confirmed, + }, + ) + if err != nil { + if validationErrors, ok := errors.AsType[validator.ValidationErrors](err); ok { + return nil, gqlutils.InvalidValidationErrors(ctx, validationErrors) + } + if errors.Is(err, mailman.ErrSubscriberAlreadyExist) { + return nil, gqlutils.Conflictf(ctx, "subscriber already exists in this mailing list") + } + r.logger.ErrorCtx(ctx, "cannot create mailing list subscriber", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.CreateMailingListSubscriberPayload{ + MailingListSubscriberEdge: types.NewMailingListSubscriberEdge(subscriber, coredata.MailingListSubscriberOrderFieldCreatedAt), + }, nil +} + +// DeleteMailingListSubscriber is the resolver for the deleteMailingListSubscriber field. +func (r *mutationResolver) DeleteMailingListSubscriber(ctx context.Context, input types.DeleteMailingListSubscriberInput) (*types.DeleteMailingListSubscriberPayload, error) { + if err := r.authorize(ctx, input.ID, probo.ActionMailingListSubscriberDelete); err != nil { + return nil, err + } + + if err := r.mailman.DeleteSubscriber(ctx, input.ID); err != nil { + if errors.Is(err, mailman.ErrSubscriberNotFound) { + return nil, gqlutils.NotFoundf(ctx, "mailing list subscriber not found") + } + r.logger.ErrorCtx(ctx, "cannot delete mailing list subscriber", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.DeleteMailingListSubscriberPayload{ + DeletedMailingListSubscriberID: input.ID, + }, nil +} + +// MailingList returns schema.MailingListResolver implementation. +func (r *Resolver) MailingList() schema.MailingListResolver { return &mailingListResolver{r} } + +// MailingListSubscriberConnection returns schema.MailingListSubscriberConnectionResolver implementation. +func (r *Resolver) MailingListSubscriberConnection() schema.MailingListSubscriberConnectionResolver { + return &mailingListSubscriberConnectionResolver{r} +} + +// MailingListUpdateConnection returns schema.MailingListUpdateConnectionResolver implementation. +func (r *Resolver) MailingListUpdateConnection() schema.MailingListUpdateConnectionResolver { + return &mailingListUpdateConnectionResolver{r} +} + +type mailingListResolver struct{ *Resolver } +type mailingListSubscriberConnectionResolver struct{ *Resolver } +type mailingListUpdateConnectionResolver struct{ *Resolver } diff --git a/pkg/server/api/console/v1/measure.resolvers.go b/pkg/server/api/console/v1/measure.resolvers.go new file mode 100644 index 000000000..d85e3062e --- /dev/null +++ b/pkg/server/api/console/v1/measure.resolvers.go @@ -0,0 +1,433 @@ +package console_v1 + +// This file will be automatically regenerated based on the schema, any resolver +// implementations +// will be copied through when generating and any unknown code will be moved to the end. +// Code generated by github.com/99designs/gqlgen version v0.17.87 + +import ( + "context" + "encoding/json" + "errors" + + "go.gearno.de/kit/log" + "go.probo.inc/probo/pkg/coredata" + "go.probo.inc/probo/pkg/page" + "go.probo.inc/probo/pkg/probo" + "go.probo.inc/probo/pkg/server/api/console/v1/schema" + "go.probo.inc/probo/pkg/server/api/console/v1/types" + "go.probo.inc/probo/pkg/server/gqlutils" + "go.probo.inc/probo/pkg/validator" +) + +// Evidences is the resolver for the evidences field. +func (r *measureResolver) Evidences(ctx context.Context, obj *types.Measure, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.EvidenceOrderBy) (*types.EvidenceConnection, error) { + if err := r.authorize(ctx, obj.ID, probo.ActionEvidenceList); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, obj.ID.TenantID()) + + pageOrderBy := page.OrderBy[coredata.EvidenceOrderField]{ + Field: coredata.EvidenceOrderFieldCreatedAt, + Direction: page.OrderDirectionDesc, + } + if orderBy != nil { + pageOrderBy = page.OrderBy[coredata.EvidenceOrderField]{ + Field: orderBy.Field, + Direction: orderBy.Direction, + } + } + + cursor := types.NewCursor(first, after, last, before, pageOrderBy) + + page, err := prb.Evidences.ListForMeasureID(ctx, obj.ID, cursor) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot list measure evidences", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return types.NewEvidenceConnection(page, r, obj.ID), nil +} + +// Tasks is the resolver for the tasks field. +func (r *measureResolver) Tasks(ctx context.Context, obj *types.Measure, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.TaskOrderBy) (*types.TaskConnection, error) { + if err := r.authorize(ctx, obj.ID, probo.ActionTaskList); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, obj.ID.TenantID()) + + pageOrderBy := page.OrderBy[coredata.TaskOrderField]{ + Field: coredata.TaskOrderFieldCreatedAt, + Direction: page.OrderDirectionDesc, + } + if orderBy != nil { + pageOrderBy = page.OrderBy[coredata.TaskOrderField]{ + Field: orderBy.Field, + Direction: orderBy.Direction, + } + } + + cursor := types.NewCursor(first, after, last, before, pageOrderBy) + + page, err := prb.Tasks.ListForMeasureID(ctx, obj.ID, cursor) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot list measure tasks", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return types.NewTaskConnection(page, r, obj.ID), nil +} + +// Risks is the resolver for the risks field. +func (r *measureResolver) Risks(ctx context.Context, obj *types.Measure, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.RiskOrderBy, filter *types.RiskFilter) (*types.RiskConnection, error) { + if err := r.authorize(ctx, obj.ID, probo.ActionRiskList); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, obj.ID.TenantID()) + + pageOrderBy := page.OrderBy[coredata.RiskOrderField]{ + Field: coredata.RiskOrderFieldCreatedAt, + Direction: page.OrderDirectionDesc, + } + if orderBy != nil { + pageOrderBy = page.OrderBy[coredata.RiskOrderField]{ + Field: orderBy.Field, + Direction: orderBy.Direction, + } + } + + cursor := types.NewCursor(first, after, last, before, pageOrderBy) + + var riskFilter = coredata.NewRiskFilter(nil, nil) + if filter != nil { + riskFilter = coredata.NewRiskFilter(filter.Query, &filter.SnapshotID) + } + + page, err := prb.Risks.ListForMeasureID(ctx, obj.ID, cursor, riskFilter) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot list measure risks", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return types.NewRiskConnection(page, r, obj.ID, riskFilter), nil +} + +// Controls is the resolver for the controls field. +func (r *measureResolver) Controls(ctx context.Context, obj *types.Measure, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.ControlOrderBy, filter *types.ControlFilter) (*types.ControlConnection, error) { + if err := r.authorize(ctx, obj.ID, probo.ActionControlList); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, obj.ID.TenantID()) + + pageOrderBy := page.OrderBy[coredata.ControlOrderField]{ + Field: coredata.ControlOrderFieldCreatedAt, + Direction: page.OrderDirectionDesc, + } + if orderBy != nil { + pageOrderBy = page.OrderBy[coredata.ControlOrderField]{ + Field: orderBy.Field, + Direction: orderBy.Direction, + } + } + + cursor := types.NewCursor(first, after, last, before, pageOrderBy) + + var controlFilter = coredata.NewControlFilter(nil) + if filter != nil { + controlFilter = coredata.NewControlFilter(filter.Query) + } + + page, err := prb.Controls.ListForMeasureID(ctx, obj.ID, cursor, controlFilter) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot list measure controls", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return types.NewControlConnection(page, r, obj.ID, controlFilter), nil +} + +// Documents is the resolver for the documents field. +func (r *measureResolver) Documents(ctx context.Context, obj *types.Measure, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.DocumentOrderBy, filter *types.DocumentFilter) (*types.DocumentConnection, error) { + if err := r.authorize(ctx, obj.ID, probo.ActionDocumentList); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, obj.ID.TenantID()) + + pageOrderBy := page.OrderBy[coredata.DocumentOrderField]{ + Field: coredata.DocumentOrderFieldCreatedAt, + Direction: page.OrderDirectionDesc, + } + if orderBy != nil { + pageOrderBy = page.OrderBy[coredata.DocumentOrderField]{ + Field: orderBy.Field, + Direction: orderBy.Direction, + } + } + + cursor := types.NewCursor(first, after, last, before, pageOrderBy) + + var documentFilter = coredata.NewDocumentFilter(nil) + if filter != nil { + documentFilter = coredata.NewDocumentFilter(filter.Query). + WithDocumentTypes(filter.DocumentTypes). + WithClassifications(filter.Classifications) + } + + pg, err := prb.Documents.ListForMeasureID(ctx, obj.ID, cursor, documentFilter) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot list documents", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return types.NewDocumentConnection(pg, r, obj.ID, documentFilter), nil +} + +// Permission is the resolver for the permission field. +func (r *measureResolver) Permission(ctx context.Context, obj *types.Measure, action string) (bool, error) { + return r.Resolver.Permission(ctx, obj, action) +} + +// TotalCount is the resolver for the totalCount field. +func (r *measureConnectionResolver) TotalCount(ctx context.Context, obj *types.MeasureConnection) (int, error) { + if err := r.authorize(ctx, obj.ParentID, probo.ActionMeasureList); err != nil { + return 0, err + } + + prb := r.ProboService(ctx, obj.ParentID.TenantID()) + + switch obj.Resolver.(type) { + case *organizationResolver: + count, err := prb.Measures.CountForOrganizationID(ctx, obj.ParentID, obj.Filters) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot count measures", log.Error(err)) + return 0, gqlutils.Internal(ctx) + } + return count, nil + case *controlResolver: + count, err := prb.Measures.CountForControlID(ctx, obj.ParentID, obj.Filters) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot count measures", log.Error(err)) + return 0, gqlutils.Internal(ctx) + } + return count, nil + case *riskResolver: + count, err := prb.Measures.CountForRiskID(ctx, obj.ParentID, obj.Filters) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot count measures", log.Error(err)) + return 0, gqlutils.Internal(ctx) + } + return count, nil + } + + r.logger.ErrorCtx(ctx, "unsupported resolver") + return 0, gqlutils.Internal(ctx) +} + +// // CreateMeasure is the resolver for the createMeasure field. +func (r *mutationResolver) CreateMeasure(ctx context.Context, input types.CreateMeasureInput) (*types.CreateMeasurePayload, error) { + if err := r.authorize(ctx, input.OrganizationID, probo.ActionMeasureCreate); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, input.OrganizationID.TenantID()) + + measure, err := prb.Measures.Create( + ctx, + probo.CreateMeasureRequest{ + OrganizationID: input.OrganizationID, + Name: input.Name, + Description: input.Description, + Category: input.Category, + }, + ) + if err != nil { + if errors.Is(err, coredata.ErrResourceAlreadyExists) { + return nil, gqlutils.Conflict(ctx, err) + } + + if validationErrors, ok := errors.AsType[validator.ValidationErrors](err); ok { + return nil, gqlutils.InvalidValidationErrors(ctx, validationErrors) + } + r.logger.ErrorCtx(ctx, "cannot create measure", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.CreateMeasurePayload{ + MeasureEdge: types.NewMeasureEdge(measure, coredata.MeasureOrderFieldCreatedAt), + }, nil +} + +// UpdateMeasure is the resolver for the updateMeasure field. +func (r *mutationResolver) UpdateMeasure(ctx context.Context, input types.UpdateMeasureInput) (*types.UpdateMeasurePayload, error) { + if err := r.authorize(ctx, input.ID, probo.ActionMeasureUpdate); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, input.ID.TenantID()) + + measure, err := prb.Measures.Update( + ctx, + probo.UpdateMeasureRequest{ + ID: input.ID, + Name: input.Name, + Description: gqlutils.UnwrapOmittable(input.Description), + Category: input.Category, + State: input.State, + }, + ) + if err != nil { + if validationErrors, ok := errors.AsType[validator.ValidationErrors](err); ok { + return nil, gqlutils.InvalidValidationErrors(ctx, validationErrors) + } + r.logger.ErrorCtx(ctx, "cannot update measure", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.UpdateMeasurePayload{ + Measure: types.NewMeasure(measure), + }, nil +} + +// ImportMeasure is the resolver for the importMeasure field. +func (r *mutationResolver) ImportMeasure(ctx context.Context, input types.ImportMeasureInput) (*types.ImportMeasurePayload, error) { + if err := r.authorize(ctx, input.OrganizationID, probo.ActionMeasureImport); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, input.OrganizationID.TenantID()) + + var req probo.ImportMeasureRequest + if err := json.NewDecoder(input.File.File).Decode(&req.Measures); err != nil { + r.logger.ErrorCtx(ctx, "cannot unmarshal measure", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + measures, err := prb.Measures.Import(ctx, input.OrganizationID, req) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot import measure", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + measureEdges := make([]*types.MeasureEdge, len(measures.Data)) + for i, measure := range measures.Data { + measureEdges[i] = types.NewMeasureEdge(measure, coredata.MeasureOrderFieldCreatedAt) + } + + return &types.ImportMeasurePayload{ + MeasureEdges: measureEdges, + }, nil +} + +// DeleteMeasure is the resolver for the deleteMeasure field. +func (r *mutationResolver) DeleteMeasure(ctx context.Context, input types.DeleteMeasureInput) (*types.DeleteMeasurePayload, error) { + if err := r.authorize(ctx, input.MeasureID, probo.ActionMeasureDelete); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, input.MeasureID.TenantID()) + + err := prb.Measures.Delete(ctx, input.MeasureID) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot delete measure", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.DeleteMeasurePayload{ + DeletedMeasureID: input.MeasureID, + }, nil +} + +// CreateMeasureDocumentMapping is the resolver for the createMeasureDocumentMapping field. +func (r *mutationResolver) CreateMeasureDocumentMapping(ctx context.Context, input types.CreateMeasureDocumentMappingInput) (*types.CreateMeasureDocumentMappingPayload, error) { + if err := r.authorize(ctx, input.MeasureID, probo.ActionMeasureDocumentMappingCreate); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, input.MeasureID.TenantID()) + + measure, document, err := prb.Measures.CreateDocumentMapping(ctx, input.MeasureID, input.DocumentID) + if err != nil { + if errors.Is(err, coredata.ErrResourceAlreadyExists) { + return nil, gqlutils.Conflict(ctx, err) + } + + r.logger.ErrorCtx(ctx, "cannot create measure document mapping", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.CreateMeasureDocumentMappingPayload{ + MeasureEdge: types.NewMeasureEdge(measure, coredata.MeasureOrderFieldCreatedAt), + DocumentEdge: types.NewDocumentEdge(document, coredata.DocumentOrderFieldTitle), + }, nil +} + +// DeleteMeasureDocumentMapping is the resolver for the deleteMeasureDocumentMapping field. +func (r *mutationResolver) DeleteMeasureDocumentMapping(ctx context.Context, input types.DeleteMeasureDocumentMappingInput) (*types.DeleteMeasureDocumentMappingPayload, error) { + if err := r.authorize(ctx, input.MeasureID, probo.ActionMeasureDocumentMappingDelete); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, input.MeasureID.TenantID()) + + measure, document, err := prb.Measures.DeleteDocumentMapping(ctx, input.MeasureID, input.DocumentID) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot delete measure document mapping", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.DeleteMeasureDocumentMappingPayload{ + DeletedMeasureID: measure.ID, + DeletedDocumentID: document.ID, + }, nil +} + +// Measures is the resolver for the measures field. +func (r *organizationResolver) Measures(ctx context.Context, obj *types.Organization, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.MeasureOrderBy, filter *types.MeasureFilter) (*types.MeasureConnection, error) { + if err := r.authorize(ctx, obj.ID, probo.ActionMeasureList); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, obj.ID.TenantID()) + + pageOrderBy := page.OrderBy[coredata.MeasureOrderField]{ + Field: coredata.MeasureOrderFieldCreatedAt, + Direction: page.OrderDirectionDesc, + } + if orderBy != nil { + pageOrderBy = page.OrderBy[coredata.MeasureOrderField]{ + Field: orderBy.Field, + Direction: orderBy.Direction, + } + } + + cursor := types.NewCursor(first, after, last, before, pageOrderBy) + + var measureFilter = coredata.NewMeasureFilter(nil, nil, nil) + if filter != nil { + measureFilter = coredata.NewMeasureFilter(filter.Query, filter.State, filter.Category) + } + + page, err := prb.Measures.ListForOrganizationID(ctx, obj.ID, cursor, measureFilter) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot list organization measures", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return types.NewMeasureConnection(page, r, obj.ID, measureFilter), nil +} + +// Measure returns schema.MeasureResolver implementation. +func (r *Resolver) Measure() schema.MeasureResolver { return &measureResolver{r} } + +// MeasureConnection returns schema.MeasureConnectionResolver implementation. +func (r *Resolver) MeasureConnection() schema.MeasureConnectionResolver { + return &measureConnectionResolver{r} +} + +type measureResolver struct{ *Resolver } +type measureConnectionResolver struct{ *Resolver } diff --git a/pkg/server/api/console/v1/meeting.resolvers.go b/pkg/server/api/console/v1/meeting.resolvers.go new file mode 100644 index 000000000..6de2f789d --- /dev/null +++ b/pkg/server/api/console/v1/meeting.resolvers.go @@ -0,0 +1,227 @@ +package console_v1 + +// This file will be automatically regenerated based on the schema, any resolver +// implementations +// will be copied through when generating and any unknown code will be moved to the end. +// Code generated by github.com/99designs/gqlgen version v0.17.87 + +import ( + "context" + "errors" + + "github.com/vikstrous/dataloadgen" + "go.gearno.de/kit/log" + "go.probo.inc/probo/pkg/coredata" + "go.probo.inc/probo/pkg/gid" + "go.probo.inc/probo/pkg/iam" + "go.probo.inc/probo/pkg/page" + "go.probo.inc/probo/pkg/probo" + "go.probo.inc/probo/pkg/server/api/console/v1/dataloader" + "go.probo.inc/probo/pkg/server/api/console/v1/schema" + "go.probo.inc/probo/pkg/server/api/console/v1/types" + "go.probo.inc/probo/pkg/server/gqlutils" + "go.probo.inc/probo/pkg/validator" +) + +// Attendees is the resolver for the attendees field. +func (r *meetingResolver) Attendees(ctx context.Context, obj *types.Meeting) ([]*types.Profile, error) { + // TODO bug must be paginated + + if err := r.authorize(ctx, obj.ID, iam.ActionMembershipProfileList); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, obj.ID.TenantID()) + + attendees, err := prb.Meetings.GetAttendees(ctx, obj.ID) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot load meeting attendees", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + if len(attendees) == 0 { + return []*types.Profile{}, nil + } + + people := make([]*types.Profile, len(attendees)) + for i, attendee := range attendees { + people[i] = types.NewProfile(attendee) + } + + return people, nil +} + +// Organization is the resolver for the organization field. +func (r *meetingResolver) Organization(ctx context.Context, obj *types.Meeting) (*types.Organization, error) { + if err := r.authorize(ctx, obj.ID, probo.ActionOrganizationGet); err != nil { + return nil, err + } + + loaders := dataloader.FromContext(ctx) + + organization, err := loaders.Organization.Load(ctx, obj.Organization.ID) + if err != nil { + if errors.Is(err, coredata.ErrResourceNotFound) || errors.Is(err, dataloadgen.ErrNotFound) { + return nil, gqlutils.NotFound(ctx, err) + } + + r.logger.ErrorCtx(ctx, "cannot load organization", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return types.NewOrganization(organization), nil +} + +// Permission is the resolver for the permission field. +func (r *meetingResolver) Permission(ctx context.Context, obj *types.Meeting, action string) (bool, error) { + return r.Resolver.Permission(ctx, obj, action) +} + +// TotalCount is the resolver for the totalCount field. +func (r *meetingConnectionResolver) TotalCount(ctx context.Context, obj *types.MeetingConnection) (int, error) { + if err := r.authorize(ctx, obj.ParentID, probo.ActionMeetingList); err != nil { + return 0, err + } + + prb := r.ProboService(ctx, obj.ParentID.TenantID()) + + switch obj.Resolver.(type) { + case *organizationResolver: + count, err := prb.Meetings.CountForOrganizationID(ctx, obj.ParentID) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot count meetings", log.Error(err)) + return 0, gqlutils.Internal(ctx) + } + return count, nil + } + + r.logger.ErrorCtx(ctx, "unsupported resolver") + return 0, gqlutils.Internal(ctx) +} + +// CreateMeeting is the resolver for the createMeeting field. +func (r *mutationResolver) CreateMeeting(ctx context.Context, input types.CreateMeetingInput) (*types.CreateMeetingPayload, error) { + if err := r.authorize(ctx, input.OrganizationID, probo.ActionMeetingCreate); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, input.OrganizationID.TenantID()) + + meeting, err := prb.Meetings.Create( + ctx, + probo.CreateMeetingRequest{ + OrganizationID: input.OrganizationID, + Name: input.Name, + Date: input.Date, + AttendeeIDs: input.AttendeeIds, + Minutes: input.Minutes, + }, + ) + if err != nil { + if validationErrors, ok := errors.AsType[validator.ValidationErrors](err); ok { + return nil, gqlutils.InvalidValidationErrors(ctx, validationErrors) + } + r.logger.ErrorCtx(ctx, "cannot create meeting", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.CreateMeetingPayload{ + MeetingEdge: types.NewMeetingEdge(meeting, coredata.MeetingOrderFieldCreatedAt), + }, nil +} + +// UpdateMeeting is the resolver for the updateMeeting field. +func (r *mutationResolver) UpdateMeeting(ctx context.Context, input types.UpdateMeetingInput) (*types.UpdateMeetingPayload, error) { + if err := r.authorize(ctx, input.MeetingID, probo.ActionMeetingUpdate); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, input.MeetingID.TenantID()) + + var attendeeIDs []gid.GID + if input.AttendeeIds != nil { + attendeeIDs = input.AttendeeIds + } + + meeting, err := prb.Meetings.Update( + ctx, + probo.UpdateMeetingRequest{ + MeetingID: input.MeetingID, + Name: input.Name, + Date: input.Date, + AttendeeIDs: attendeeIDs, + Minutes: gqlutils.UnwrapOmittable(input.Minutes), + }, + ) + if err != nil { + if validationErrors, ok := errors.AsType[validator.ValidationErrors](err); ok { + return nil, gqlutils.InvalidValidationErrors(ctx, validationErrors) + } + r.logger.ErrorCtx(ctx, "cannot update meeting", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.UpdateMeetingPayload{ + Meeting: types.NewMeeting(meeting), + }, nil +} + +// DeleteMeeting is the resolver for the deleteMeeting field. +func (r *mutationResolver) DeleteMeeting(ctx context.Context, input types.DeleteMeetingInput) (*types.DeleteMeetingPayload, error) { + if err := r.authorize(ctx, input.MeetingID, probo.ActionMeetingDelete); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, input.MeetingID.TenantID()) + + err := prb.Meetings.Delete(ctx, input.MeetingID) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot delete meeting", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.DeleteMeetingPayload{ + DeletedMeetingID: input.MeetingID, + }, nil +} + +// Meetings is the resolver for the meetings field. +func (r *organizationResolver) Meetings(ctx context.Context, obj *types.Organization, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.MeetingOrderBy) (*types.MeetingConnection, error) { + if err := r.authorize(ctx, obj.ID, probo.ActionMeetingList); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, obj.ID.TenantID()) + + pageOrderBy := page.OrderBy[coredata.MeetingOrderField]{ + Field: coredata.MeetingOrderFieldCreatedAt, + Direction: page.OrderDirectionDesc, + } + if orderBy != nil { + pageOrderBy = page.OrderBy[coredata.MeetingOrderField]{ + Field: orderBy.Field, + Direction: orderBy.Direction, + } + } + + cursor := types.NewCursor(first, after, last, before, pageOrderBy) + + page, err := prb.Meetings.ListForOrganizationID(ctx, obj.ID, cursor) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot list organization meetings", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return types.NewMeetingConnection(page, r, obj.ID), nil +} + +// Meeting returns schema.MeetingResolver implementation. +func (r *Resolver) Meeting() schema.MeetingResolver { return &meetingResolver{r} } + +// MeetingConnection returns schema.MeetingConnectionResolver implementation. +func (r *Resolver) MeetingConnection() schema.MeetingConnectionResolver { + return &meetingConnectionResolver{r} +} + +type meetingResolver struct{ *Resolver } +type meetingConnectionResolver struct{ *Resolver } diff --git a/pkg/server/api/console/v1/obligation.resolvers.go b/pkg/server/api/console/v1/obligation.resolvers.go new file mode 100644 index 000000000..2f6614103 --- /dev/null +++ b/pkg/server/api/console/v1/obligation.resolvers.go @@ -0,0 +1,247 @@ +package console_v1 + +// This file will be automatically regenerated based on the schema, any resolver +// implementations +// will be copied through when generating and any unknown code will be moved to the end. +// Code generated by github.com/99designs/gqlgen version v0.17.87 + +import ( + "context" + "errors" + + "github.com/vikstrous/dataloadgen" + "go.gearno.de/kit/log" + "go.probo.inc/probo/pkg/coredata" + "go.probo.inc/probo/pkg/iam" + "go.probo.inc/probo/pkg/page" + "go.probo.inc/probo/pkg/probo" + "go.probo.inc/probo/pkg/server/api/console/v1/dataloader" + "go.probo.inc/probo/pkg/server/api/console/v1/schema" + "go.probo.inc/probo/pkg/server/api/console/v1/types" + "go.probo.inc/probo/pkg/server/gqlutils" + "go.probo.inc/probo/pkg/validator" +) + +// CreateObligation is the resolver for the createObligation field. +func (r *mutationResolver) CreateObligation(ctx context.Context, input types.CreateObligationInput) (*types.CreateObligationPayload, error) { + if err := r.authorize(ctx, input.OrganizationID, probo.ActionObligationCreate); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, input.OrganizationID.TenantID()) + + req := probo.CreateObligationRequest{ + OrganizationID: input.OrganizationID, + Area: input.Area, + Source: input.Source, + Requirement: input.Requirement, + ActionsToBeImplemented: input.ActionsToBeImplemented, + Regulator: input.Regulator, + OwnerID: input.OwnerID, + LastReviewDate: input.LastReviewDate, + DueDate: input.DueDate, + Status: input.Status, + Type: input.Type, + } + + obligation, err := prb.Obligations.Create(ctx, &req) + if err != nil { + if validationErrors, ok := errors.AsType[validator.ValidationErrors](err); ok { + return nil, gqlutils.InvalidValidationErrors(ctx, validationErrors) + } + r.logger.ErrorCtx(ctx, "cannot create obligation", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.CreateObligationPayload{ + ObligationEdge: types.NewObligationEdge(obligation, coredata.ObligationOrderFieldCreatedAt), + }, nil +} + +// UpdateObligation is the resolver for the updateObligation field. +func (r *mutationResolver) UpdateObligation(ctx context.Context, input types.UpdateObligationInput) (*types.UpdateObligationPayload, error) { + if err := r.authorize(ctx, input.ID, probo.ActionObligationUpdate); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, input.ID.TenantID()) + + req := probo.UpdateObligationRequest{ + ID: input.ID, + Area: gqlutils.UnwrapOmittable(input.Area), + Source: gqlutils.UnwrapOmittable(input.Source), + Requirement: gqlutils.UnwrapOmittable(input.Requirement), + ActionsToBeImplemented: gqlutils.UnwrapOmittable(input.ActionsToBeImplemented), + Regulator: gqlutils.UnwrapOmittable(input.Regulator), + OwnerID: input.OwnerID, + LastReviewDate: gqlutils.UnwrapOmittable(input.LastReviewDate), + DueDate: gqlutils.UnwrapOmittable(input.DueDate), + Status: input.Status, + Type: input.Type, + } + + obligation, err := prb.Obligations.Update(ctx, &req) + if err != nil { + if validationErrors, ok := errors.AsType[validator.ValidationErrors](err); ok { + return nil, gqlutils.InvalidValidationErrors(ctx, validationErrors) + } + r.logger.ErrorCtx(ctx, "cannot update obligation", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.UpdateObligationPayload{ + Obligation: types.NewObligation(obligation), + }, nil +} + +// DeleteObligation is the resolver for the deleteObligation field. +func (r *mutationResolver) DeleteObligation(ctx context.Context, input types.DeleteObligationInput) (*types.DeleteObligationPayload, error) { + if err := r.authorize(ctx, input.ObligationID, probo.ActionObligationDelete); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, input.ObligationID.TenantID()) + + err := prb.Obligations.Delete(ctx, input.ObligationID) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot delete obligation", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.DeleteObligationPayload{ + DeletedObligationID: input.ObligationID, + }, nil +} + +// Organization is the resolver for the organization field. +func (r *obligationResolver) Organization(ctx context.Context, obj *types.Obligation) (*types.Organization, error) { + if err := r.authorize(ctx, obj.ID, probo.ActionOrganizationGet); err != nil { + return nil, err + } + + loaders := dataloader.FromContext(ctx) + + organization, err := loaders.Organization.Load(ctx, obj.Organization.ID) + if err != nil { + if errors.Is(err, coredata.ErrResourceNotFound) || errors.Is(err, dataloadgen.ErrNotFound) { + return nil, gqlutils.NotFound(ctx, err) + } + + r.logger.ErrorCtx(ctx, "cannot get obligation organization", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return types.NewOrganization(organization), nil +} + +// Owner is the resolver for the owner field. +func (r *obligationResolver) Owner(ctx context.Context, obj *types.Obligation) (*types.Profile, error) { + if err := r.authorize(ctx, obj.ID, iam.ActionMembershipProfileGet); err != nil { + return nil, err + } + + loaders := dataloader.FromContext(ctx) + + owner, err := loaders.Profile.Load(ctx, obj.Owner.ID) + if err != nil { + if errors.Is(err, coredata.ErrResourceNotFound) || errors.Is(err, dataloadgen.ErrNotFound) { + return nil, gqlutils.NotFound(ctx, err) + } + + r.logger.ErrorCtx(ctx, "cannot get obligation owner", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return types.NewProfile(owner), nil +} + +// Permission is the resolver for the permission field. +func (r *obligationResolver) Permission(ctx context.Context, obj *types.Obligation, action string) (bool, error) { + return r.Resolver.Permission(ctx, obj, action) +} + +// TotalCount is the resolver for the totalCount field. +func (r *obligationConnectionResolver) TotalCount(ctx context.Context, obj *types.ObligationConnection) (int, error) { + if err := r.authorize(ctx, obj.ParentID, probo.ActionObligationList); err != nil { + return 0, err + } + + prb := r.ProboService(ctx, obj.ParentID.TenantID()) + + switch obj.Resolver.(type) { + case *organizationResolver: + obligationFilter := coredata.NewObligationFilter(nil) + if obj.Filter != nil { + obligationFilter = coredata.NewObligationFilter(&obj.Filter.SnapshotID) + } + + count, err := prb.Obligations.CountForOrganizationID(ctx, obj.ParentID, obligationFilter) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot count obligations", log.Error(err)) + return 0, gqlutils.Internal(ctx) + } + return count, nil + case *riskResolver: + obligationFilter := coredata.NewObligationFilter(nil) + if obj.Filter != nil { + obligationFilter = coredata.NewObligationFilter(&obj.Filter.SnapshotID) + } + + count, err := prb.Obligations.CountForRiskID(ctx, obj.ParentID, obligationFilter) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot count risk obligations", log.Error(err)) + return 0, gqlutils.Internal(ctx) + } + return count, nil + } + + r.logger.ErrorCtx(ctx, "unsupported resolver") + return 0, gqlutils.Internal(ctx) +} + +// Obligations is the resolver for the obligations field. +func (r *organizationResolver) Obligations(ctx context.Context, obj *types.Organization, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.ObligationOrderBy, filter *types.ObligationFilter) (*types.ObligationConnection, error) { + if err := r.authorize(ctx, obj.ID, probo.ActionObligationList); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, obj.ID.TenantID()) + + pageOrderBy := page.OrderBy[coredata.ObligationOrderField]{ + Field: coredata.ObligationOrderFieldCreatedAt, + Direction: page.OrderDirectionDesc, + } + + if orderBy != nil { + pageOrderBy = page.OrderBy[coredata.ObligationOrderField]{ + Field: orderBy.Field, + Direction: orderBy.Direction, + } + } + + cursor := types.NewCursor(first, after, last, before, pageOrderBy) + + obligationFilter := coredata.NewObligationFilter(nil) + if filter != nil { + obligationFilter = coredata.NewObligationFilter(&filter.SnapshotID) + } + + page, err := prb.Obligations.ListForOrganizationID(ctx, obj.ID, cursor, obligationFilter) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot list organization obligations", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return types.NewObligationConnection(page, r, obj.ID, filter), nil +} + +// Obligation returns schema.ObligationResolver implementation. +func (r *Resolver) Obligation() schema.ObligationResolver { return &obligationResolver{r} } + +// ObligationConnection returns schema.ObligationConnectionResolver implementation. +func (r *Resolver) ObligationConnection() schema.ObligationConnectionResolver { + return &obligationConnectionResolver{r} +} + +type obligationResolver struct{ *Resolver } +type obligationConnectionResolver struct{ *Resolver } diff --git a/pkg/server/api/console/v1/organization.resolvers.go b/pkg/server/api/console/v1/organization.resolvers.go new file mode 100644 index 000000000..99f35105f --- /dev/null +++ b/pkg/server/api/console/v1/organization.resolvers.go @@ -0,0 +1,61 @@ +package console_v1 + +// This file will be automatically regenerated based on the schema, any resolver +// implementations +// will be copied through when generating and any unknown code will be moved to the end. +// Code generated by github.com/99designs/gqlgen version v0.17.87 + +import ( + "context" + "fmt" + + "go.gearno.de/kit/log" + "go.probo.inc/probo/pkg/iam" + "go.probo.inc/probo/pkg/server/api/console/v1/schema" + "go.probo.inc/probo/pkg/server/api/console/v1/types" + "go.probo.inc/probo/pkg/server/gqlutils" +) + +// Permission is the resolver for the permission field. +func (r *profileResolver) Permission(ctx context.Context, obj *types.Profile, action string) (bool, error) { + return r.Resolver.Permission(ctx, obj, action) +} + +// TotalCount is the resolver for the totalCount field. +func (r *profileConnectionResolver) TotalCount(ctx context.Context, obj *types.ProfileConnection) (int, error) { + if err := r.authorize(ctx, obj.ParentID, iam.ActionMembershipProfileList); err != nil { + return 0, err + } + + switch obj.Resolver.(type) { + case *organizationResolver: + count, err := r.iam.OrganizationService.CountProfiles(ctx, obj.ParentID, obj.Filters) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot count profiles", log.Error(err)) + return 0, gqlutils.Internal(ctx) + } + return count, nil + case *documentVersionResolver: + prb := r.ProboService(ctx, obj.ParentID.TenantID()) + count, err := prb.Documents.CountVersionApprovers(ctx, obj.ParentID) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot count document version approvers", log.Error(err)) + return 0, gqlutils.Internal(ctx) + } + return count, nil + } + + r.logger.ErrorCtx(ctx, "unsupported resolver for profile connection", log.String("resolver", fmt.Sprintf("%T", obj.Resolver))) + return 0, gqlutils.Internal(ctx) +} + +// Profile returns schema.ProfileResolver implementation. +func (r *Resolver) Profile() schema.ProfileResolver { return &profileResolver{r} } + +// ProfileConnection returns schema.ProfileConnectionResolver implementation. +func (r *Resolver) ProfileConnection() schema.ProfileConnectionResolver { + return &profileConnectionResolver{r} +} + +type profileResolver struct{ *Resolver } +type profileConnectionResolver struct{ *Resolver } diff --git a/pkg/server/api/console/v1/processing_activity.resolvers.go b/pkg/server/api/console/v1/processing_activity.resolvers.go new file mode 100644 index 000000000..fc81c40fd --- /dev/null +++ b/pkg/server/api/console/v1/processing_activity.resolvers.go @@ -0,0 +1,353 @@ +package console_v1 + +// This file will be automatically regenerated based on the schema, any resolver +// implementations +// will be copied through when generating and any unknown code will be moved to the end. +// Code generated by github.com/99designs/gqlgen version v0.17.87 + +import ( + "context" + "encoding/base64" + "errors" + "fmt" + + "github.com/vikstrous/dataloadgen" + "go.gearno.de/kit/log" + "go.probo.inc/probo/pkg/coredata" + "go.probo.inc/probo/pkg/gid" + "go.probo.inc/probo/pkg/iam" + "go.probo.inc/probo/pkg/page" + "go.probo.inc/probo/pkg/probo" + "go.probo.inc/probo/pkg/server/api/console/v1/dataloader" + "go.probo.inc/probo/pkg/server/api/console/v1/schema" + "go.probo.inc/probo/pkg/server/api/console/v1/types" + "go.probo.inc/probo/pkg/server/gqlutils" +) + +// CreateProcessingActivity is the resolver for the createProcessingActivity field. +func (r *mutationResolver) CreateProcessingActivity(ctx context.Context, input types.CreateProcessingActivityInput) (*types.CreateProcessingActivityPayload, error) { + if err := r.authorize(ctx, input.OrganizationID, probo.ActionProcessingActivityCreate); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, input.OrganizationID.TenantID()) + + req := probo.CreateProcessingActivityRequest{ + OrganizationID: input.OrganizationID, + Name: input.Name, + Purpose: input.Purpose, + DataSubjectCategory: input.DataSubjectCategory, + PersonalDataCategory: input.PersonalDataCategory, + SpecialOrCriminalData: input.SpecialOrCriminalData, + LawfulBasis: input.LawfulBasis, + Recipients: input.Recipients, + Location: input.Location, + InternationalTransfers: input.InternationalTransfers, + TransferSafeguard: input.TransferSafeguards, + RetentionPeriod: input.RetentionPeriod, + SecurityMeasures: input.SecurityMeasures, + DataProtectionImpactAssessmentNeeded: input.DataProtectionImpactAssessmentNeeded, + TransferImpactAssessmentNeeded: input.TransferImpactAssessmentNeeded, + LastReviewDate: input.LastReviewDate, + NextReviewDate: input.NextReviewDate, + Role: input.Role, + DataProtectionOfficerID: input.DataProtectionOfficerID, + VendorIDs: input.VendorIds, + } + + activity, err := prb.ProcessingActivities.Create(ctx, &req) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot create processing activity", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.CreateProcessingActivityPayload{ + ProcessingActivityEdge: types.NewProcessingActivityEdge(activity, coredata.ProcessingActivityOrderFieldCreatedAt), + }, nil +} + +// UpdateProcessingActivity is the resolver for the updateProcessingActivity field. +func (r *mutationResolver) UpdateProcessingActivity(ctx context.Context, input types.UpdateProcessingActivityInput) (*types.UpdateProcessingActivityPayload, error) { + if err := r.authorize(ctx, input.ID, probo.ActionProcessingActivityUpdate); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, input.ID.TenantID()) + + req := probo.UpdateProcessingActivityRequest{ + ID: input.ID, + Name: input.Name, + Purpose: gqlutils.UnwrapOmittable(input.Purpose), + DataSubjectCategory: gqlutils.UnwrapOmittable(input.DataSubjectCategory), + PersonalDataCategory: gqlutils.UnwrapOmittable(input.PersonalDataCategory), + SpecialOrCriminalData: input.SpecialOrCriminalData, + LawfulBasis: input.LawfulBasis, + Recipients: gqlutils.UnwrapOmittable(input.Recipients), + Location: gqlutils.UnwrapOmittable(input.Location), + InternationalTransfers: input.InternationalTransfers, + TransferSafeguard: gqlutils.UnwrapOmittable(input.TransferSafeguards), + RetentionPeriod: gqlutils.UnwrapOmittable(input.RetentionPeriod), + SecurityMeasures: gqlutils.UnwrapOmittable(input.SecurityMeasures), + DataProtectionImpactAssessmentNeeded: input.DataProtectionImpactAssessmentNeeded, + TransferImpactAssessmentNeeded: input.TransferImpactAssessmentNeeded, + LastReviewDate: gqlutils.UnwrapOmittable(input.LastReviewDate), + NextReviewDate: gqlutils.UnwrapOmittable(input.NextReviewDate), + Role: input.Role, + DataProtectionOfficerID: gqlutils.UnwrapOmittable(input.DataProtectionOfficerID), + VendorIDs: &input.VendorIds, + } + + activity, err := prb.ProcessingActivities.Update(ctx, &req) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot update processing activity", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.UpdateProcessingActivityPayload{ + ProcessingActivity: types.NewProcessingActivity(activity), + }, nil +} + +// DeleteProcessingActivity is the resolver for the deleteProcessingActivity field. +func (r *mutationResolver) DeleteProcessingActivity(ctx context.Context, input types.DeleteProcessingActivityInput) (*types.DeleteProcessingActivityPayload, error) { + if err := r.authorize(ctx, input.ProcessingActivityID, probo.ActionProcessingActivityDelete); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, input.ProcessingActivityID.TenantID()) + + err := prb.ProcessingActivities.Delete(ctx, input.ProcessingActivityID) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot delete processing activity", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.DeleteProcessingActivityPayload{ + DeletedProcessingActivityID: input.ProcessingActivityID, + }, nil +} + +// ExportProcessingActivitiesPDF is the resolver for the exportProcessingActivitiesPDF field. +func (r *mutationResolver) ExportProcessingActivitiesPDF(ctx context.Context, input types.ExportProcessingActivitiesPDFInput) (*types.ExportProcessingActivitiesPDFPayload, error) { + if err := r.authorize(ctx, input.OrganizationID, probo.ActionProcessingActivityExport); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, input.OrganizationID.TenantID()) + + var snapshotIDPtr *gid.GID + if input.Filter != nil { + snapshotIDPtr = input.Filter.SnapshotID + } + processingActivityFilter := coredata.NewProcessingActivityFilter(&snapshotIDPtr) + + pdf, err := prb.ProcessingActivities.ExportPDF(ctx, input.OrganizationID, processingActivityFilter) + if err != nil { + if errors.Is(err, coredata.ErrResourceNotFound) { + return nil, gqlutils.NotFound(ctx, err) + } + r.logger.ErrorCtx(ctx, "cannot export processing activities PDF", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.ExportProcessingActivitiesPDFPayload{ + Data: fmt.Sprintf("data:application/pdf;base64,%s", base64.StdEncoding.EncodeToString(pdf)), + }, nil +} + +// ProcessingActivities is the resolver for the processingActivities field. +func (r *organizationResolver) ProcessingActivities(ctx context.Context, obj *types.Organization, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.ProcessingActivityOrderBy, filter *types.ProcessingActivityFilter) (*types.ProcessingActivityConnection, error) { + if err := r.authorize(ctx, obj.ID, probo.ActionProcessingActivityList); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, obj.ID.TenantID()) + + pageOrderBy := page.OrderBy[coredata.ProcessingActivityOrderField]{ + Field: coredata.ProcessingActivityOrderFieldCreatedAt, + Direction: page.OrderDirectionDesc, + } + + if orderBy != nil { + pageOrderBy = page.OrderBy[coredata.ProcessingActivityOrderField]{ + Field: orderBy.Field, + Direction: orderBy.Direction, + } + } + + cursor := types.NewCursor(first, after, last, before, pageOrderBy) + + processingActivityFilter := coredata.NewProcessingActivityFilter(nil) + if filter != nil { + processingActivityFilter = coredata.NewProcessingActivityFilter(&filter.SnapshotID) + } + + page, err := prb.ProcessingActivities.ListForOrganizationID(ctx, obj.ID, cursor, processingActivityFilter) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot list organization processing activities", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return types.NewProcessingActivityConnection(page, r, obj.ID, filter), nil +} + +// Organization is the resolver for the organization field. +func (r *processingActivityResolver) Organization(ctx context.Context, obj *types.ProcessingActivity) (*types.Organization, error) { + if err := r.authorize(ctx, obj.ID, probo.ActionOrganizationGet); err != nil { + return nil, err + } + + loaders := dataloader.FromContext(ctx) + + organization, err := loaders.Organization.Load(ctx, obj.Organization.ID) + if err != nil { + if errors.Is(err, coredata.ErrResourceNotFound) || errors.Is(err, dataloadgen.ErrNotFound) { + return nil, gqlutils.NotFound(ctx, err) + } + + r.logger.ErrorCtx(ctx, "cannot get organization", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return types.NewOrganization(organization), nil +} + +// DataProtectionOfficer is the resolver for the dataProtectionOfficer field. +func (r *processingActivityResolver) DataProtectionOfficer(ctx context.Context, obj *types.ProcessingActivity) (*types.Profile, error) { + if err := r.authorize(ctx, obj.ID, iam.ActionMembershipProfileGet); err != nil { + return nil, err + } + + if obj.DataProtectionOfficer == nil { + return nil, nil + } + + loaders := dataloader.FromContext(ctx) + + dpo, err := loaders.Profile.Load(ctx, obj.DataProtectionOfficer.ID) + if err != nil { + if errors.Is(err, dataloadgen.ErrNotFound) { + return nil, gqlutils.NotFound(ctx, err) + } + + r.logger.ErrorCtx(ctx, "cannot get data protection officer", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return types.NewProfile(dpo), nil +} + +// Vendors is the resolver for the vendors field. +func (r *processingActivityResolver) Vendors(ctx context.Context, obj *types.ProcessingActivity, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.VendorOrderBy) (*types.VendorConnection, error) { + if err := r.authorize(ctx, obj.ID, probo.ActionVendorList); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, obj.ID.TenantID()) + + pageOrderBy := page.OrderBy[coredata.VendorOrderField]{ + Field: coredata.VendorOrderFieldCreatedAt, + Direction: page.OrderDirectionDesc, + } + if orderBy != nil { + pageOrderBy = page.OrderBy[coredata.VendorOrderField]{ + Field: orderBy.Field, + Direction: orderBy.Direction, + } + } + + cursor := types.NewCursor(first, after, last, before, pageOrderBy) + + page, err := prb.Vendors.ListForProcessingActivityID(ctx, obj.ID, cursor) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot list processing activity vendors", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return types.NewVendorConnection(page, r, obj.ID), nil +} + +// DataProtectionImpactAssessment is the resolver for the dataProtectionImpactAssessment field. +func (r *processingActivityResolver) DataProtectionImpactAssessment(ctx context.Context, obj *types.ProcessingActivity) (*types.DataProtectionImpactAssessment, error) { + if err := r.authorize(ctx, obj.ID, probo.ActionDataProtectionImpactAssessmentGet); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, obj.ID.TenantID()) + + dpia, err := prb.DataProtectionImpactAssessments.GetByProcessingActivityID(ctx, obj.ID) + if err != nil { + if errors.Is(err, coredata.ErrResourceNotFound) { + return nil, nil + } + r.logger.ErrorCtx(ctx, "cannot get processing activity dpia", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return types.NewDataProtectionImpactAssessment(dpia), nil +} + +// TransferImpactAssessment is the resolver for the transferImpactAssessment field. +func (r *processingActivityResolver) TransferImpactAssessment(ctx context.Context, obj *types.ProcessingActivity) (*types.TransferImpactAssessment, error) { + if err := r.authorize(ctx, obj.ID, probo.ActionTransferImpactAssessmentGet); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, obj.ID.TenantID()) + + tia, err := prb.TransferImpactAssessments.GetByProcessingActivityID(ctx, obj.ID) + if err != nil { + if errors.Is(err, coredata.ErrResourceNotFound) { + return nil, nil + } + r.logger.ErrorCtx(ctx, "cannot get processing activity tia", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return types.NewTransferImpactAssessment(tia), nil +} + +// Permission is the resolver for the permission field. +func (r *processingActivityResolver) Permission(ctx context.Context, obj *types.ProcessingActivity, action string) (bool, error) { + return r.Resolver.Permission(ctx, obj, action) +} + +// TotalCount is the resolver for the totalCount field. +func (r *processingActivityConnectionResolver) TotalCount(ctx context.Context, obj *types.ProcessingActivityConnection) (int, error) { + if err := r.authorize(ctx, obj.ParentID, probo.ActionProcessingActivityList); err != nil { + return 0, err + } + + prb := r.ProboService(ctx, obj.ParentID.TenantID()) + + switch obj.Resolver.(type) { + case *organizationResolver: + processingActivityFilter := coredata.NewProcessingActivityFilter(nil) + if obj.Filter != nil { + processingActivityFilter = coredata.NewProcessingActivityFilter(&obj.Filter.SnapshotID) + } + + count, err := prb.ProcessingActivities.CountForOrganizationID(ctx, obj.ParentID, processingActivityFilter) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot count organization processing activities", log.Error(err)) + return 0, gqlutils.Internal(ctx) + } + return count, nil + } + + r.logger.ErrorCtx(ctx, "unsupported resolver") + return 0, gqlutils.Internal(ctx) +} + +// ProcessingActivity returns schema.ProcessingActivityResolver implementation. +func (r *Resolver) ProcessingActivity() schema.ProcessingActivityResolver { + return &processingActivityResolver{r} +} + +// ProcessingActivityConnection returns schema.ProcessingActivityConnectionResolver implementation. +func (r *Resolver) ProcessingActivityConnection() schema.ProcessingActivityConnectionResolver { + return &processingActivityConnectionResolver{r} +} + +type processingActivityResolver struct{ *Resolver } +type processingActivityConnectionResolver struct{ *Resolver } diff --git a/pkg/server/api/console/v1/rights_request.resolvers.go b/pkg/server/api/console/v1/rights_request.resolvers.go new file mode 100644 index 000000000..119d73f34 --- /dev/null +++ b/pkg/server/api/console/v1/rights_request.resolvers.go @@ -0,0 +1,202 @@ +package console_v1 + +// This file will be automatically regenerated based on the schema, any resolver +// implementations +// will be copied through when generating and any unknown code will be moved to the end. +// Code generated by github.com/99designs/gqlgen version v0.17.87 + +import ( + "context" + "errors" + + "go.gearno.de/kit/log" + "go.probo.inc/probo/pkg/coredata" + "go.probo.inc/probo/pkg/iam" + "go.probo.inc/probo/pkg/page" + "go.probo.inc/probo/pkg/probo" + "go.probo.inc/probo/pkg/server/api/console/v1/schema" + "go.probo.inc/probo/pkg/server/api/console/v1/types" + "go.probo.inc/probo/pkg/server/gqlutils" + "go.probo.inc/probo/pkg/validator" +) + +// CreateRightsRequest is the resolver for the createRightsRequest field. +func (r *mutationResolver) CreateRightsRequest(ctx context.Context, input types.CreateRightsRequestInput) (*types.CreateRightsRequestPayload, error) { + if err := r.authorize(ctx, input.OrganizationID, probo.ActionRightsRequestCreate); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, input.OrganizationID.TenantID()) + + req := probo.CreateRightsRequestRequest{ + OrganizationID: input.OrganizationID, + RequestType: &input.RequestType, + RequestState: &input.RequestState, + DataSubject: input.DataSubject, + Contact: input.Contact, + Details: input.Details, + Deadline: input.Deadline, + ActionTaken: input.ActionTaken, + } + + rightsRequest, err := prb.RightsRequests.Create(ctx, &req) + if err != nil { + if validationErrors, ok := errors.AsType[validator.ValidationErrors](err); ok { + return nil, gqlutils.InvalidValidationErrors(ctx, validationErrors) + } + r.logger.ErrorCtx(ctx, "cannot create rights request", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.CreateRightsRequestPayload{ + RightsRequestEdge: types.NewRightsRequestEdge(rightsRequest, coredata.RightsRequestOrderFieldCreatedAt), + }, nil +} + +// UpdateRightsRequest is the resolver for the updateRightsRequest field. +func (r *mutationResolver) UpdateRightsRequest(ctx context.Context, input types.UpdateRightsRequestInput) (*types.UpdateRightsRequestPayload, error) { + if err := r.authorize(ctx, input.ID, probo.ActionRightsRequestUpdate); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, input.ID.TenantID()) + + req := probo.UpdateRightsRequestRequest{ + ID: input.ID, + RequestType: input.RequestType, + RequestState: input.RequestState, + DataSubject: gqlutils.UnwrapOmittable(input.DataSubject), + Contact: gqlutils.UnwrapOmittable(input.Contact), + Details: gqlutils.UnwrapOmittable(input.Details), + Deadline: gqlutils.UnwrapOmittable(input.Deadline), + ActionTaken: gqlutils.UnwrapOmittable(input.ActionTaken), + } + + rightsRequest, err := prb.RightsRequests.Update(ctx, &req) + if err != nil { + if validationErrors, ok := errors.AsType[validator.ValidationErrors](err); ok { + return nil, gqlutils.InvalidValidationErrors(ctx, validationErrors) + } + r.logger.ErrorCtx(ctx, "cannot update rights request", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.UpdateRightsRequestPayload{ + RightsRequest: types.NewRightsRequest(rightsRequest), + }, nil +} + +// DeleteRightsRequest is the resolver for the deleteRightsRequest field. +func (r *mutationResolver) DeleteRightsRequest(ctx context.Context, input types.DeleteRightsRequestInput) (*types.DeleteRightsRequestPayload, error) { + if err := r.authorize(ctx, input.RightsRequestID, probo.ActionRightsRequestDelete); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, input.RightsRequestID.TenantID()) + + err := prb.RightsRequests.Delete(ctx, input.RightsRequestID) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot delete rights request", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.DeleteRightsRequestPayload{ + DeletedRightsRequestID: input.RightsRequestID, + }, nil +} + +// RightsRequests is the resolver for the rightsRequests field. +func (r *organizationResolver) RightsRequests(ctx context.Context, obj *types.Organization, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.RightsRequestOrderBy) (*types.RightsRequestConnection, error) { + if err := r.authorize(ctx, obj.ID, probo.ActionRightsRequestList); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, obj.ID.TenantID()) + + pageOrderBy := page.OrderBy[coredata.RightsRequestOrderField]{ + Field: coredata.RightsRequestOrderFieldCreatedAt, + Direction: page.OrderDirectionDesc, + } + + if orderBy != nil { + pageOrderBy = page.OrderBy[coredata.RightsRequestOrderField]{ + Field: orderBy.Field, + Direction: orderBy.Direction, + } + } + + cursor := types.NewCursor(first, after, last, before, pageOrderBy) + + page, err := prb.RightsRequests.ListForOrganizationID(ctx, obj.ID, cursor) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot list organization rights requests", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return types.NewRightsRequestConnection(page, r, obj.ID), nil +} + +// Organization is the resolver for the organization field. +func (r *rightsRequestResolver) Organization(ctx context.Context, obj *types.RightsRequest) (*types.Organization, error) { + if err := r.authorize(ctx, obj.ID, iam.ActionOrganizationGet); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, obj.ID.TenantID()) + + rightsRequest, err := prb.RightsRequests.Get(ctx, obj.ID) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot get rights request", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + organization, err := prb.Organizations.Get(ctx, rightsRequest.OrganizationID) + if err != nil { + if errors.Is(err, coredata.ErrResourceNotFound) { + return nil, gqlutils.NotFound(ctx, err) + } + r.logger.ErrorCtx(ctx, "cannot get organization", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return types.NewOrganization(organization), nil +} + +// Permission is the resolver for the permission field. +func (r *rightsRequestResolver) Permission(ctx context.Context, obj *types.RightsRequest, action string) (bool, error) { + return r.Resolver.Permission(ctx, obj, action) +} + +// TotalCount is the resolver for the totalCount field. +func (r *rightsRequestConnectionResolver) TotalCount(ctx context.Context, obj *types.RightsRequestConnection) (int, error) { + if err := r.authorize(ctx, obj.ParentID, probo.ActionRightsRequestList); err != nil { + return 0, err + } + + prb := r.ProboService(ctx, obj.ParentID.TenantID()) + + switch obj.Resolver.(type) { + case *organizationResolver: + count, err := prb.RightsRequests.CountByOrganizationID(ctx, obj.ParentID) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot count rights requests", log.Error(err)) + return 0, gqlutils.Internal(ctx) + } + + return count, nil + default: + r.logger.ErrorCtx(ctx, "unsupported resolver type for RightsRequestConnection") + return 0, gqlutils.Internal(ctx) + } +} + +// RightsRequest returns schema.RightsRequestResolver implementation. +func (r *Resolver) RightsRequest() schema.RightsRequestResolver { return &rightsRequestResolver{r} } + +// RightsRequestConnection returns schema.RightsRequestConnectionResolver implementation. +func (r *Resolver) RightsRequestConnection() schema.RightsRequestConnectionResolver { + return &rightsRequestConnectionResolver{r} +} + +type rightsRequestResolver struct{ *Resolver } +type rightsRequestConnectionResolver struct{ *Resolver } diff --git a/pkg/server/api/console/v1/risk.resolvers.go b/pkg/server/api/console/v1/risk.resolvers.go new file mode 100644 index 000000000..d8675407d --- /dev/null +++ b/pkg/server/api/console/v1/risk.resolvers.go @@ -0,0 +1,505 @@ +package console_v1 + +// This file will be automatically regenerated based on the schema, any resolver +// implementations +// will be copied through when generating and any unknown code will be moved to the end. +// Code generated by github.com/99designs/gqlgen version v0.17.87 + +import ( + "context" + "errors" + + "github.com/vikstrous/dataloadgen" + "go.gearno.de/kit/log" + "go.probo.inc/probo/pkg/coredata" + "go.probo.inc/probo/pkg/iam" + "go.probo.inc/probo/pkg/page" + "go.probo.inc/probo/pkg/probo" + "go.probo.inc/probo/pkg/server/api/console/v1/dataloader" + "go.probo.inc/probo/pkg/server/api/console/v1/schema" + "go.probo.inc/probo/pkg/server/api/console/v1/types" + "go.probo.inc/probo/pkg/server/gqlutils" + "go.probo.inc/probo/pkg/validator" +) + +// CreateRisk is the resolver for the createRisk field. +func (r *mutationResolver) CreateRisk(ctx context.Context, input types.CreateRiskInput) (*types.CreateRiskPayload, error) { + if err := r.authorize(ctx, input.OrganizationID, probo.ActionRiskCreate); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, input.OrganizationID.TenantID()) + + risk, err := prb.Risks.Create( + ctx, + probo.CreateRiskRequest{ + OrganizationID: input.OrganizationID, + Name: input.Name, + Description: input.Description, + Category: input.Category, + Treatment: input.Treatment, + OwnerID: input.OwnerID, + InherentLikelihood: input.InherentLikelihood, + InherentImpact: input.InherentImpact, + ResidualLikelihood: input.ResidualLikelihood, + ResidualImpact: input.ResidualImpact, + Note: input.Note, + }, + ) + if err != nil { + if errors.Is(err, coredata.ErrResourceAlreadyExists) { + return nil, gqlutils.Conflict(ctx, err) + } + + if validationErrors, ok := errors.AsType[validator.ValidationErrors](err); ok { + return nil, gqlutils.InvalidValidationErrors(ctx, validationErrors) + } + r.logger.ErrorCtx(ctx, "cannot create risk", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.CreateRiskPayload{ + RiskEdge: types.NewRiskEdge(risk, coredata.RiskOrderFieldCreatedAt), + }, nil +} + +// UpdateRisk is the resolver for the updateRisk field. +func (r *mutationResolver) UpdateRisk(ctx context.Context, input types.UpdateRiskInput) (*types.UpdateRiskPayload, error) { + if err := r.authorize(ctx, input.ID, probo.ActionRiskUpdate); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, input.ID.TenantID()) + + risk, err := prb.Risks.Update( + ctx, + probo.UpdateRiskRequest{ + ID: input.ID, + Name: input.Name, + Description: gqlutils.UnwrapOmittable(input.Description), + Category: input.Category, + Treatment: input.Treatment, + OwnerID: gqlutils.UnwrapOmittable(input.OwnerID), + InherentLikelihood: input.InherentLikelihood, + InherentImpact: input.InherentImpact, + ResidualLikelihood: input.ResidualLikelihood, + ResidualImpact: input.ResidualImpact, + Note: input.Note, + }, + ) + if err != nil { + if validationErrors, ok := errors.AsType[validator.ValidationErrors](err); ok { + return nil, gqlutils.InvalidValidationErrors(ctx, validationErrors) + } + r.logger.ErrorCtx(ctx, "cannot update risk", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.UpdateRiskPayload{ + Risk: types.NewRisk(risk), + }, nil +} + +// DeleteRisk is the resolver for the deleteRisk field. +func (r *mutationResolver) DeleteRisk(ctx context.Context, input types.DeleteRiskInput) (*types.DeleteRiskPayload, error) { + if err := r.authorize(ctx, input.RiskID, probo.ActionRiskDelete); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, input.RiskID.TenantID()) + + err := prb.Risks.Delete(ctx, input.RiskID) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot delete risk", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.DeleteRiskPayload{ + DeletedRiskID: input.RiskID, + }, nil +} + +// CreateRiskMeasureMapping is the resolver for the createRiskMeasureMapping field. +func (r *mutationResolver) CreateRiskMeasureMapping(ctx context.Context, input types.CreateRiskMeasureMappingInput) (*types.CreateRiskMeasureMappingPayload, error) { + if err := r.authorize(ctx, input.RiskID, probo.ActionRiskMeasureMappingCreate); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, input.RiskID.TenantID()) + + risk, measure, err := prb.Risks.CreateMeasureMapping(ctx, input.RiskID, input.MeasureID) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot create risk measure mapping", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.CreateRiskMeasureMappingPayload{ + RiskEdge: types.NewRiskEdge(risk, coredata.RiskOrderFieldCreatedAt), + MeasureEdge: types.NewMeasureEdge(measure, coredata.MeasureOrderFieldCreatedAt), + }, nil +} + +// DeleteRiskMeasureMapping is the resolver for the deleteRiskMeasureMapping field. +func (r *mutationResolver) DeleteRiskMeasureMapping(ctx context.Context, input types.DeleteRiskMeasureMappingInput) (*types.DeleteRiskMeasureMappingPayload, error) { + if err := r.authorize(ctx, input.RiskID, probo.ActionRiskMeasureMappingDelete); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, input.RiskID.TenantID()) + + risk, measure, err := prb.Risks.DeleteMeasureMapping(ctx, input.RiskID, input.MeasureID) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot delete risk measure mapping", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.DeleteRiskMeasureMappingPayload{ + DeletedRiskID: risk.ID, + DeletedMeasureID: measure.ID, + }, nil +} + +// CreateRiskDocumentMapping is the resolver for the createRiskDocumentMapping field. +func (r *mutationResolver) CreateRiskDocumentMapping(ctx context.Context, input types.CreateRiskDocumentMappingInput) (*types.CreateRiskDocumentMappingPayload, error) { + if err := r.authorize(ctx, input.RiskID, probo.ActionRiskDocumentMappingCreate); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, input.RiskID.TenantID()) + + risk, document, err := prb.Risks.CreateDocumentMapping(ctx, input.RiskID, input.DocumentID) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot create risk document mapping", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.CreateRiskDocumentMappingPayload{ + RiskEdge: types.NewRiskEdge(risk, coredata.RiskOrderFieldCreatedAt), + DocumentEdge: types.NewDocumentEdge(document, coredata.DocumentOrderFieldTitle), + }, nil +} + +// DeleteRiskDocumentMapping is the resolver for the deleteRiskDocumentMapping field. +func (r *mutationResolver) DeleteRiskDocumentMapping(ctx context.Context, input types.DeleteRiskDocumentMappingInput) (*types.DeleteRiskDocumentMappingPayload, error) { + if err := r.authorize(ctx, input.RiskID, probo.ActionRiskDocumentMappingDelete); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, input.RiskID.TenantID()) + + risk, document, err := prb.Risks.DeleteDocumentMapping(ctx, input.RiskID, input.DocumentID) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot delete risk document mapping", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.DeleteRiskDocumentMappingPayload{ + DeletedRiskID: risk.ID, + DeletedDocumentID: document.ID, + }, nil +} + +// CreateRiskObligationMapping is the resolver for the createRiskObligationMapping field. +func (r *mutationResolver) CreateRiskObligationMapping(ctx context.Context, input types.CreateRiskObligationMappingInput) (*types.CreateRiskObligationMappingPayload, error) { + if err := r.authorize(ctx, input.RiskID, probo.ActionRiskObligationMappingCreate); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, input.RiskID.TenantID()) + + risk, obligation, err := prb.Risks.CreateObligationMapping(ctx, input.RiskID, input.ObligationID) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot create risk obligation mapping", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.CreateRiskObligationMappingPayload{ + RiskEdge: types.NewRiskEdge(risk, coredata.RiskOrderFieldCreatedAt), + ObligationEdge: types.NewObligationEdge(obligation, coredata.ObligationOrderFieldCreatedAt), + }, nil +} + +// DeleteRiskObligationMapping is the resolver for the deleteRiskObligationMapping field. +func (r *mutationResolver) DeleteRiskObligationMapping(ctx context.Context, input types.DeleteRiskObligationMappingInput) (*types.DeleteRiskObligationMappingPayload, error) { + if err := r.authorize(ctx, input.RiskID, probo.ActionRiskObligationMappingDelete); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, input.RiskID.TenantID()) + + risk, obligation, err := prb.Risks.DeleteObligationMapping(ctx, input.RiskID, input.ObligationID) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot delete risk obligation mapping", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.DeleteRiskObligationMappingPayload{ + DeletedRiskID: risk.ID, + DeletedObligationID: obligation.ID, + }, nil +} + +// Risks is the resolver for the risks field. +func (r *organizationResolver) Risks(ctx context.Context, obj *types.Organization, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.RiskOrderBy, filter *types.RiskFilter) (*types.RiskConnection, error) { + if err := r.authorize(ctx, obj.ID, probo.ActionRiskList); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, obj.ID.TenantID()) + + pageOrderBy := page.OrderBy[coredata.RiskOrderField]{ + Field: coredata.RiskOrderFieldCreatedAt, + Direction: page.OrderDirectionDesc, + } + if orderBy != nil { + pageOrderBy = page.OrderBy[coredata.RiskOrderField]{ + Field: orderBy.Field, + Direction: orderBy.Direction, + } + } + + cursor := types.NewCursor(first, after, last, before, pageOrderBy) + + var riskFilter = coredata.NewRiskFilter(nil, nil) + if filter != nil { + riskFilter = coredata.NewRiskFilter(filter.Query, &filter.SnapshotID) + } + + page, err := prb.Risks.ListForOrganizationID(ctx, obj.ID, cursor, riskFilter) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot list organization risks", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return types.NewRiskConnection(page, r, obj.ID, riskFilter), nil +} + +// Owner is the resolver for the owner field. +func (r *riskResolver) Owner(ctx context.Context, obj *types.Risk) (*types.Profile, error) { + if err := r.authorize(ctx, obj.ID, iam.ActionMembershipProfileGet); err != nil { + return nil, err + } + + if obj.Owner == nil { + return nil, nil + } + + loaders := dataloader.FromContext(ctx) + + owner, err := loaders.Profile.Load(ctx, obj.Owner.ID) + if err != nil { + if errors.Is(err, coredata.ErrResourceNotFound) || errors.Is(err, dataloadgen.ErrNotFound) { + return nil, gqlutils.NotFound(ctx, err) + } + + r.logger.ErrorCtx(ctx, "cannot get owner", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return types.NewProfile(owner), nil +} + +// Organization is the resolver for the organization field. +func (r *riskResolver) Organization(ctx context.Context, obj *types.Risk) (*types.Organization, error) { + if err := r.authorize(ctx, obj.ID, probo.ActionOrganizationGet); err != nil { + return nil, err + } + + loaders := dataloader.FromContext(ctx) + + organization, err := loaders.Organization.Load(ctx, obj.Organization.ID) + if err != nil { + if errors.Is(err, coredata.ErrResourceNotFound) || errors.Is(err, dataloadgen.ErrNotFound) { + return nil, gqlutils.NotFound(ctx, err) + } + + r.logger.ErrorCtx(ctx, "cannot get organization", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return types.NewOrganization(organization), nil +} + +// Measures is the resolver for the measures field. +func (r *riskResolver) Measures(ctx context.Context, obj *types.Risk, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.MeasureOrderBy, filter *types.MeasureFilter) (*types.MeasureConnection, error) { + if err := r.authorize(ctx, obj.ID, probo.ActionMeasureList); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, obj.ID.TenantID()) + + pageOrderBy := page.OrderBy[coredata.MeasureOrderField]{ + Field: coredata.MeasureOrderFieldCreatedAt, + Direction: page.OrderDirectionDesc, + } + if orderBy != nil { + pageOrderBy = page.OrderBy[coredata.MeasureOrderField]{ + Field: orderBy.Field, + Direction: orderBy.Direction, + } + } + + cursor := types.NewCursor(first, after, last, before, pageOrderBy) + + var measureFilter = coredata.NewMeasureFilter(nil, nil, nil) + if filter != nil { + measureFilter = coredata.NewMeasureFilter(filter.Query, filter.State, filter.Category) + } + + page, err := prb.Measures.ListForRiskID(ctx, obj.ID, cursor, measureFilter) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot list risk measures", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return types.NewMeasureConnection(page, r, obj.ID, measureFilter), nil +} + +// Documents is the resolver for the documents field. +func (r *riskResolver) Documents(ctx context.Context, obj *types.Risk, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.DocumentOrderBy, filter *types.DocumentFilter) (*types.DocumentConnection, error) { + if err := r.authorize(ctx, obj.ID, probo.ActionDocumentList); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, obj.ID.TenantID()) + + pageOrderBy := page.OrderBy[coredata.DocumentOrderField]{ + Field: coredata.DocumentOrderFieldCreatedAt, + Direction: page.OrderDirectionDesc, + } + if orderBy != nil { + pageOrderBy = page.OrderBy[coredata.DocumentOrderField]{ + Field: orderBy.Field, + Direction: orderBy.Direction, + } + } + + cursor := types.NewCursor(first, after, last, before, pageOrderBy) + + var documentFilter = coredata.NewDocumentFilter(nil) + if filter != nil { + documentFilter = coredata.NewDocumentFilter(filter.Query). + WithDocumentTypes(filter.DocumentTypes). + WithClassifications(filter.Classifications) + } + + page, err := prb.Documents.ListForRiskID(ctx, obj.ID, cursor, documentFilter) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot list risk documents", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return types.NewDocumentConnection(page, r, obj.ID, documentFilter), nil +} + +// Controls is the resolver for the controls field. +func (r *riskResolver) Controls(ctx context.Context, obj *types.Risk, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.ControlOrderBy, filter *types.ControlFilter) (*types.ControlConnection, error) { + if err := r.authorize(ctx, obj.ID, probo.ActionControlList); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, obj.ID.TenantID()) + + pageOrderBy := page.OrderBy[coredata.ControlOrderField]{ + Field: coredata.ControlOrderFieldCreatedAt, + Direction: page.OrderDirectionDesc, + } + if orderBy != nil { + pageOrderBy = page.OrderBy[coredata.ControlOrderField]{ + Field: orderBy.Field, + Direction: orderBy.Direction, + } + } + + cursor := types.NewCursor(first, after, last, before, pageOrderBy) + var filters = coredata.NewControlFilter(nil) + if filter != nil { + filters = coredata.NewControlFilter(filter.Query) + } + + page, err := prb.Controls.ListForRiskID(ctx, obj.ID, cursor, filters) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot list risk controls", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return types.NewControlConnection(page, r, obj.ID, filters), nil +} + +// Obligations is the resolver for the obligations field. +func (r *riskResolver) Obligations(ctx context.Context, obj *types.Risk, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.ObligationOrderBy, filter *types.ObligationFilter) (*types.ObligationConnection, error) { + if err := r.authorize(ctx, obj.ID, probo.ActionObligationList); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, obj.ID.TenantID()) + + pageOrderBy := page.OrderBy[coredata.ObligationOrderField]{ + Field: coredata.ObligationOrderFieldCreatedAt, + Direction: page.OrderDirectionDesc, + } + if orderBy != nil { + pageOrderBy = page.OrderBy[coredata.ObligationOrderField]{ + Field: orderBy.Field, + Direction: orderBy.Direction, + } + } + + cursor := types.NewCursor(first, after, last, before, pageOrderBy) + + var obligationFilter = coredata.NewObligationFilter(nil) + if filter != nil { + obligationFilter = coredata.NewObligationFilter(&filter.SnapshotID) + } + + page, err := prb.Obligations.ListForRiskID(ctx, obj.ID, cursor, obligationFilter) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot list risk obligations", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return types.NewObligationConnection(page, r, obj.ID, filter), nil +} + +// Permission is the resolver for the permission field. +func (r *riskResolver) Permission(ctx context.Context, obj *types.Risk, action string) (bool, error) { + return r.Resolver.Permission(ctx, obj, action) +} + +// TotalCount is the resolver for the totalCount field. +func (r *riskConnectionResolver) TotalCount(ctx context.Context, obj *types.RiskConnection) (int, error) { + if err := r.authorize(ctx, obj.ParentID, probo.ActionRiskList); err != nil { + return 0, err + } + + prb := r.ProboService(ctx, obj.ParentID.TenantID()) + + switch obj.Resolver.(type) { + case *measureResolver: + count, err := prb.Risks.CountForMeasureID(ctx, obj.ParentID, obj.Filters) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot count risks", log.Error(err)) + return 0, gqlutils.Internal(ctx) + } + return count, nil + case *organizationResolver: + count, err := prb.Risks.CountForOrganizationID(ctx, obj.ParentID, obj.Filters) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot count risks", log.Error(err)) + return 0, gqlutils.Internal(ctx) + } + return count, nil + } + + r.logger.ErrorCtx(ctx, "unsupported resolver") + return 0, gqlutils.Internal(ctx) +} + +// Risk returns schema.RiskResolver implementation. +func (r *Resolver) Risk() schema.RiskResolver { return &riskResolver{r} } + +// RiskConnection returns schema.RiskConnectionResolver implementation. +func (r *Resolver) RiskConnection() schema.RiskConnectionResolver { return &riskConnectionResolver{r} } + +type riskResolver struct{ *Resolver } +type riskConnectionResolver struct{ *Resolver } diff --git a/pkg/server/api/console/v1/schema.graphql b/pkg/server/api/console/v1/schema.graphql deleted file mode 100644 index 162fd4756..000000000 --- a/pkg/server/api/console/v1/schema.graphql +++ /dev/null @@ -1,7185 +0,0 @@ -# Directives -directive @goField( - forceResolver: Boolean - name: String - omittable: Boolean -) on INPUT_FIELD_DEFINITION | FIELD_DEFINITION - -directive @goModel( - model: String - models: [String!] -) on OBJECT | INPUT_OBJECT | SCALAR | ENUM | INTERFACE | UNION - -directive @goEnum(value: String) on ENUM_VALUE - -# Scalars -scalar CursorKey -scalar Datetime -scalar Upload -scalar Duration -scalar BigInt -scalar EmailAddr - -# Interfaces -interface Node { - id: ID! -} - -# Pagination -type PageInfo { - hasNextPage: Boolean! - hasPreviousPage: Boolean! - startCursor: CursorKey - endCursor: CursorKey -} - -# Enums -enum OrderDirection - @goModel(model: "go.probo.inc/probo/pkg/page.OrderDirection") { - ASC @goEnum(value: "go.probo.inc/probo/pkg/page.OrderDirectionAsc") - DESC @goEnum(value: "go.probo.inc/probo/pkg/page.OrderDirectionDesc") -} - -enum MeasureState - @goModel(model: "go.probo.inc/probo/pkg/coredata.MeasureState") { - NOT_STARTED - @goEnum(value: "go.probo.inc/probo/pkg/coredata.MeasureStateNotStarted") - IN_PROGRESS - @goEnum(value: "go.probo.inc/probo/pkg/coredata.MeasureStateInProgress") - NOT_APPLICABLE - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.MeasureStateNotApplicable" - ) - IMPLEMENTED - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.MeasureStateImplemented" - ) - UNKNOWN - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.MeasureStateUnknown" - ) - NOT_IMPLEMENTED - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.MeasureStateNotImplemented" - ) -} - -enum TaskState @goModel(model: "go.probo.inc/probo/pkg/coredata.TaskState") { - TODO @goEnum(value: "go.probo.inc/probo/pkg/coredata.TaskStateTodo") - IN_PROGRESS @goEnum(value: "go.probo.inc/probo/pkg/coredata.TaskStateInProgress") - DONE @goEnum(value: "go.probo.inc/probo/pkg/coredata.TaskStateDone") -} - -enum TaskPriority - @goModel(model: "go.probo.inc/probo/pkg/coredata.TaskPriority") { - URGENT - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.TaskPriorityUrgent" - ) - HIGH - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.TaskPriorityHigh" - ) - MEDIUM - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.TaskPriorityMedium" - ) - LOW - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.TaskPriorityLow" - ) -} - -enum EvidenceState - @goModel(model: "go.probo.inc/probo/pkg/coredata.EvidenceState") { - FULFILLED - @goEnum(value: "go.probo.inc/probo/pkg/coredata.EvidenceStateFulfilled") - REQUESTED - @goEnum(value: "go.probo.inc/probo/pkg/coredata.EvidenceStateRequested") -} - -enum DocumentVersionStatus - @goModel(model: "go.probo.inc/probo/pkg/coredata.DocumentVersionStatus") { - DRAFT - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.DocumentVersionStatusDraft" - ) - PENDING_APPROVAL - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.DocumentVersionStatusPendingApproval" - ) - PUBLISHED - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.DocumentVersionStatusPublished" - ) -} - -enum DocumentStatus - @goModel(model: "go.probo.inc/probo/pkg/coredata.DocumentStatus") { - ACTIVE - @goEnum(value: "go.probo.inc/probo/pkg/coredata.DocumentStatusActive") - ARCHIVED - @goEnum(value: "go.probo.inc/probo/pkg/coredata.DocumentStatusArchived") -} - -enum EvidenceType - @goModel(model: "go.probo.inc/probo/pkg/coredata.EvidenceType") { - FILE @goEnum(value: "go.probo.inc/probo/pkg/coredata.EvidenceTypeFile") - LINK @goEnum(value: "go.probo.inc/probo/pkg/coredata.EvidenceTypeLink") -} - -enum RiskTreatment - @goModel(model: "go.probo.inc/probo/pkg/coredata.RiskTreatment") { - MITIGATED - @goEnum(value: "go.probo.inc/probo/pkg/coredata.RiskTreatmentMitigated") - ACCEPTED - @goEnum(value: "go.probo.inc/probo/pkg/coredata.RiskTreatmentAccepted") - AVOIDED - @goEnum(value: "go.probo.inc/probo/pkg/coredata.RiskTreatmentAvoided") - TRANSFERRED - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.RiskTreatmentTransferred" - ) -} - -enum AuditState @goModel(model: "go.probo.inc/probo/pkg/coredata.AuditState") { - NOT_STARTED - @goEnum(value: "go.probo.inc/probo/pkg/coredata.AuditStateNotStarted") - IN_PROGRESS - @goEnum(value: "go.probo.inc/probo/pkg/coredata.AuditStateInProgress") - COMPLETED - @goEnum(value: "go.probo.inc/probo/pkg/coredata.AuditStateCompleted") - REJECTED - @goEnum(value: "go.probo.inc/probo/pkg/coredata.AuditStateRejected") - OUTDATED - @goEnum(value: "go.probo.inc/probo/pkg/coredata.AuditStateOutdated") -} - -enum TrustCenterVisibility - @goModel(model: "go.probo.inc/probo/pkg/coredata.TrustCenterVisibility") { - NONE - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.TrustCenterVisibilityNone" - ) - PRIVATE - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.TrustCenterVisibilityPrivate" - ) - PUBLIC - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.TrustCenterVisibilityPublic" - ) -} - -enum SearchEngineIndexing - @goModel( - model: "go.probo.inc/probo/pkg/coredata.SearchEngineIndexing" - ) { - INDEXABLE - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.SearchEngineIndexingIndexable" - ) - NOT_INDEXABLE - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.SearchEngineIndexingNotIndexable" - ) -} - -enum ControlImplementationState - @goModel( - model: "go.probo.inc/probo/pkg/coredata.ControlImplementationState" - ) { - IMPLEMENTED - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.ControlImplementationStateImplemented" - ) - NOT_IMPLEMENTED - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.ControlImplementationStateNotImplemented" - ) -} - -enum TrustCenterDocumentAccessStatus - @goModel( - model: "go.probo.inc/probo/pkg/coredata.TrustCenterDocumentAccessStatus" - ) { - REQUESTED - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.TrustCenterDocumentAccessStatusRequested" - ) - GRANTED - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.TrustCenterDocumentAccessStatusGranted" - ) - REJECTED - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.TrustCenterDocumentAccessStatusRejected" - ) - REVOKED - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.TrustCenterDocumentAccessStatusRevoked" - ) -} - -enum FindingKind - @goModel(model: "go.probo.inc/probo/pkg/coredata.FindingKind") { - MINOR_NONCONFORMITY - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.FindingKindMinorNonconformity" - ) - MAJOR_NONCONFORMITY - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.FindingKindMajorNonconformity" - ) - OBSERVATION - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.FindingKindObservation" - ) - EXCEPTION - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.FindingKindException" - ) -} - -enum FindingStatus - @goModel(model: "go.probo.inc/probo/pkg/coredata.FindingStatus") { - OPEN - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.FindingStatusOpen" - ) - IN_PROGRESS - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.FindingStatusInProgress" - ) - CLOSED - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.FindingStatusClosed" - ) - RISK_ACCEPTED - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.FindingStatusRiskAccepted" - ) - MITIGATED - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.FindingStatusMitigated" - ) - FALSE_POSITIVE - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.FindingStatusFalsePositive" - ) -} - -enum FindingPriority - @goModel(model: "go.probo.inc/probo/pkg/coredata.FindingPriority") { - LOW - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.FindingPriorityLow" - ) - MEDIUM - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.FindingPriorityMedium" - ) - HIGH - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.FindingPriorityHigh" - ) -} - -enum ObligationStatus - @goModel(model: "go.probo.inc/probo/pkg/coredata.ObligationStatus") { - NON_COMPLIANT - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.ObligationStatusNonCompliant" - ) - PARTIALLY_COMPLIANT - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.ObligationStatusPartiallyCompliant" - ) - COMPLIANT - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.ObligationStatusCompliant" - ) -} - -enum ObligationType - @goModel(model: "go.probo.inc/probo/pkg/coredata.ObligationType") { - LEGAL @goEnum(value: "go.probo.inc/probo/pkg/coredata.ObligationTypeLegal") - CONTRACTUAL - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.ObligationTypeContractual" - ) -} - -enum RightsRequestType - @goModel(model: "go.probo.inc/probo/pkg/coredata.RightsRequestType") { - ACCESS - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.RightsRequestTypeAccess" - ) - DELETION - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.RightsRequestTypeDeletion" - ) - PORTABILITY - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.RightsRequestTypePortability" - ) -} - -enum RightsRequestState - @goModel(model: "go.probo.inc/probo/pkg/coredata.RightsRequestState") { - TODO - @goEnum(value: "go.probo.inc/probo/pkg/coredata.RightsRequestStateTodo") - IN_PROGRESS - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.RightsRequestStateInProgress" - ) - DONE - @goEnum(value: "go.probo.inc/probo/pkg/coredata.RightsRequestStateDone") -} - -enum ProcessingActivitySpecialOrCriminalDatum - @goModel( - model: "go.probo.inc/probo/pkg/coredata.ProcessingActivitySpecialOrCriminalDatum" - ) { - YES - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.ProcessingActivitySpecialOrCriminalDatumYes" - ) - NO - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.ProcessingActivitySpecialOrCriminalDatumNo" - ) - POSSIBLE - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.ProcessingActivitySpecialOrCriminalDatumPossible" - ) -} - -enum ProcessingActivityLawfulBasis - @goModel( - model: "go.probo.inc/probo/pkg/coredata.ProcessingActivityLawfulBasis" - ) { - LEGITIMATE_INTEREST - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.ProcessingActivityLawfulBasisLegitimateInterest" - ) - CONSENT - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.ProcessingActivityLawfulBasisConsent" - ) - CONTRACTUAL_NECESSITY - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.ProcessingActivityLawfulBasisContractualNecessity" - ) - LEGAL_OBLIGATION - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.ProcessingActivityLawfulBasisLegalObligation" - ) - VITAL_INTERESTS - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.ProcessingActivityLawfulBasisVitalInterests" - ) - PUBLIC_TASK - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.ProcessingActivityLawfulBasisPublicTask" - ) -} - -enum ProcessingActivityTransferSafeguard - @goModel( - model: "go.probo.inc/probo/pkg/coredata.ProcessingActivityTransferSafeguard" - ) { - STANDARD_CONTRACTUAL_CLAUSES - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.ProcessingActivityTransferSafeguardStandardContractualClauses" - ) - BINDING_CORPORATE_RULES - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.ProcessingActivityTransferSafeguardBindingCorporateRules" - ) - ADEQUACY_DECISION - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.ProcessingActivityTransferSafeguardAdequacyDecision" - ) - DEROGATIONS - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.ProcessingActivityTransferSafeguardDerogations" - ) - CODES_OF_CONDUCT - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.ProcessingActivityTransferSafeguardCodesOfConduct" - ) - CERTIFICATION_MECHANISMS - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.ProcessingActivityTransferSafeguardCertificationMechanisms" - ) -} - -enum ProcessingActivityDataProtectionImpactAssessment - @goModel( - model: "go.probo.inc/probo/pkg/coredata.ProcessingActivityDataProtectionImpactAssessment" - ) { - NEEDED - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.ProcessingActivityDataProtectionImpactAssessmentNeeded" - ) - NOT_NEEDED - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.ProcessingActivityDataProtectionImpactAssessmentNotNeeded" - ) -} - -enum ProcessingActivityTransferImpactAssessment - @goModel( - model: "go.probo.inc/probo/pkg/coredata.ProcessingActivityTransferImpactAssessment" - ) { - NEEDED - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.ProcessingActivityTransferImpactAssessmentNeeded" - ) - NOT_NEEDED - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.ProcessingActivityTransferImpactAssessmentNotNeeded" - ) -} - -enum DataProtectionImpactAssessmentResidualRisk - @goModel( - model: "go.probo.inc/probo/pkg/coredata.DataProtectionImpactAssessmentResidualRisk" - ) { - LOW - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.DataProtectionImpactAssessmentResidualRiskLow" - ) - MEDIUM - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.DataProtectionImpactAssessmentResidualRiskMedium" - ) - HIGH - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.DataProtectionImpactAssessmentResidualRiskHigh" - ) -} - -enum ProcessingActivityRole - @goModel(model: "go.probo.inc/probo/pkg/coredata.ProcessingActivityRole") { - CONTROLLER - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.ProcessingActivityRoleController" - ) - PROCESSOR - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.ProcessingActivityRoleProcessor" - ) -} - -# Order Field Enums -enum ProfileOrderField - @goModel(model: "go.probo.inc/probo/pkg/coredata.MembershipProfileOrderField") { - FULL_NAME - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.MembershipProfileOrderFieldFullName" - ) - CREATED_AT - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.MembershipProfileOrderFieldCreatedAt" - ) - KIND @goEnum(value: "go.probo.inc/probo/pkg/coredata.MembershipProfileOrderFieldKind") -} - -enum VendorOrderField - @goModel(model: "go.probo.inc/probo/pkg/coredata.VendorOrderField") { - NAME @goEnum(value: "go.probo.inc/probo/pkg/coredata.VendorOrderFieldName") - CREATED_AT - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.VendorOrderFieldCreatedAt" - ) - UPDATED_AT - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.VendorOrderFieldUpdatedAt" - ) -} - -enum FrameworkOrderField - @goModel(model: "go.probo.inc/probo/pkg/coredata.FrameworkOrderField") { - CREATED_AT - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.FrameworkOrderFieldCreatedAt" - ) -} - -enum ControlOrderField - @goModel(model: "go.probo.inc/probo/pkg/coredata.ControlOrderField") { - CREATED_AT - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.ControlOrderFieldCreatedAt" - ) - SECTION_TITLE - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.ControlOrderFieldSectionTitle" - ) -} - -enum MeasureOrderField - @goModel(model: "go.probo.inc/probo/pkg/coredata.MeasureOrderField") { - CREATED_AT - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.MeasureOrderFieldCreatedAt" - ) - NAME @goEnum(value: "go.probo.inc/probo/pkg/coredata.MeasureOrderFieldName") -} - -enum TaskOrderField - @goModel(model: "go.probo.inc/probo/pkg/coredata.TaskOrderField") { - PRIORITY_RANK - CREATED_AT -} - -enum DocumentOrderField - @goModel(model: "go.probo.inc/probo/pkg/coredata.DocumentOrderField") { - TITLE - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.DocumentOrderFieldTitle" - ) - CREATED_AT - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.DocumentOrderFieldCreatedAt" - ) - UPDATED_AT - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.DocumentOrderFieldUpdatedAt" - ) - DOCUMENT_TYPE - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.DocumentOrderFieldDocumentType" - ) -} - -enum MeetingOrderField - @goModel(model: "go.probo.inc/probo/pkg/coredata.MeetingOrderField") { - DATE @goEnum(value: "go.probo.inc/probo/pkg/coredata.MeetingOrderFieldDate") - NAME @goEnum(value: "go.probo.inc/probo/pkg/coredata.MeetingOrderFieldName") - CREATED_AT - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.MeetingOrderFieldCreatedAt" - ) -} - -enum WebhookSubscriptionOrderField - @goModel( - model: "go.probo.inc/probo/pkg/coredata.WebhookSubscriptionOrderField" - ) { - CREATED_AT - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.WebhookSubscriptionOrderFieldCreatedAt" - ) -} - -enum AuditLogActorType - @goModel( - model: "go.probo.inc/probo/pkg/coredata.AuditLogActorType" - ) { - USER - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.AuditLogActorTypeUser" - ) - API_KEY - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.AuditLogActorTypeAPIKey" - ) - SYSTEM - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.AuditLogActorTypeSystem" - ) -} - -enum AuditLogEntryOrderField - @goModel( - model: "go.probo.inc/probo/pkg/coredata.AuditLogEntryOrderField" - ) { - CREATED_AT - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.AuditLogEntryOrderFieldCreatedAt" - ) -} - -enum RiskOrderField - @goModel(model: "go.probo.inc/probo/pkg/coredata.RiskOrderField") { - CREATED_AT - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.RiskOrderFieldCreatedAt" - ) - UPDATED_AT - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.RiskOrderFieldUpdatedAt" - ) - NAME @goEnum(value: "go.probo.inc/probo/pkg/coredata.RiskOrderFieldName") - CATEGORY - @goEnum(value: "go.probo.inc/probo/pkg/coredata.RiskOrderFieldCategory") - TREATMENT - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.RiskOrderFieldTreatment" - ) - INHERENT_RISK_SCORE - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.RiskOrderFieldInherentRiskScore" - ) - RESIDUAL_RISK_SCORE - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.RiskOrderFieldResidualRiskScore" - ) - OWNER_FULL_NAME - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.RiskOrderFieldOwnerFullName" - ) -} - -enum EvidenceOrderField - @goModel(model: "go.probo.inc/probo/pkg/coredata.EvidenceOrderField") { - CREATED_AT -} - -enum VendorComplianceReportOrderField - @goModel( - model: "go.probo.inc/probo/pkg/coredata.VendorComplianceReportOrderField" - ) { - REPORT_DATE - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.VendorComplianceReportOrderFieldReportDate" - ) - CREATED_AT - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.VendorComplianceReportOrderFieldCreatedAt" - ) -} - -enum VendorContactOrderField - @goModel(model: "go.probo.inc/probo/pkg/coredata.VendorContactOrderField") { - CREATED_AT - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.VendorContactOrderFieldCreatedAt" - ) - FULL_NAME - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.VendorContactOrderFieldFullName" - ) - EMAIL - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.VendorContactOrderFieldEmail" - ) -} - -enum VendorServiceOrderField - @goModel(model: "go.probo.inc/probo/pkg/coredata.VendorServiceOrderField") { - CREATED_AT - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.VendorServiceOrderFieldCreatedAt" - ) - NAME - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.VendorServiceOrderFieldName" - ) -} - -enum DataSensitivity - @goModel(model: "go.probo.inc/probo/pkg/coredata.DataSensitivity") { - NONE @goEnum(value: "go.probo.inc/probo/pkg/coredata.DataSensitivityNone") - LOW @goEnum(value: "go.probo.inc/probo/pkg/coredata.DataSensitivityLow") - MEDIUM - @goEnum(value: "go.probo.inc/probo/pkg/coredata.DataSensitivityMedium") - HIGH @goEnum(value: "go.probo.inc/probo/pkg/coredata.DataSensitivityHigh") - CRITICAL - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.DataSensitivityCritical" - ) -} - -enum BusinessImpact - @goModel(model: "go.probo.inc/probo/pkg/coredata.BusinessImpact") { - LOW @goEnum(value: "go.probo.inc/probo/pkg/coredata.BusinessImpactLow") - MEDIUM - @goEnum(value: "go.probo.inc/probo/pkg/coredata.BusinessImpactMedium") - HIGH @goEnum(value: "go.probo.inc/probo/pkg/coredata.BusinessImpactHigh") - CRITICAL - @goEnum(value: "go.probo.inc/probo/pkg/coredata.BusinessImpactCritical") -} - -enum DocumentVersionOrderField - @goModel( - model: "go.probo.inc/probo/pkg/coredata.DocumentVersionOrderField" - ) { - CREATED_AT - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.DocumentVersionOrderFieldCreatedAt" - ) -} - -enum ApplicabilityStatementOrderField - @goModel(model: "go.probo.inc/probo/pkg/coredata.ApplicabilityStatementOrderField") { - CREATED_AT - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.ApplicabilityStatementOrderFieldCreatedAt" - ) - CONTROL_SECTION_TITLE - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.ApplicabilityStatementOrderFieldControlSectionTitle" - ) -} - -enum CountryCode - @goModel(model: "go.probo.inc/probo/pkg/coredata.CountryCode") { - AD @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeAD") - AE @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeAE") - AF @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeAF") - AG @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeAG") - AI @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeAI") - AL @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeAL") - AM @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeAM") - AO @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeAO") - AQ @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeAQ") - AR @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeAR") - AS @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeAS") - AT @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeAT") - AU @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeAU") - AW @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeAW") - AX @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeAX") - AZ @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeAZ") - BA @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeBA") - BB @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeBB") - BD @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeBD") - BE @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeBE") - BF @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeBF") - BG @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeBG") - BH @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeBH") - BI @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeBI") - BJ @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeBJ") - BL @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeBL") - BM @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeBM") - BN @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeBN") - BO @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeBO") - BQ @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeBQ") - BR @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeBR") - BS @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeBS") - BT @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeBT") - BV @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeBV") - BW @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeBW") - BY @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeBY") - BZ @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeBZ") - CA @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeCA") - CC @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeCC") - CD @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeCD") - CF @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeCF") - CG @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeCG") - CH @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeCH") - CI @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeCI") - CK @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeCK") - CL @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeCL") - CM @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeCM") - CN @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeCN") - CO @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeCO") - CR @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeCR") - CU @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeCU") - CV @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeCV") - CW @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeCW") - CX @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeCX") - CY @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeCY") - CZ @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeCZ") - DE @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeDE") - DJ @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeDJ") - DK @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeDK") - DM @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeDM") - DO @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeDO") - DZ @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeDZ") - EC @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeEC") - EE @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeEE") - EG @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeEG") - EH @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeEH") - ER @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeER") - ES @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeES") - ET @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeET") - EU @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeEU") - FI @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeFI") - FJ @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeFJ") - FK @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeFK") - FM @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeFM") - FO @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeFO") - FR @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeFR") - GA @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeGA") - GB @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeGB") - GD @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeGD") - GE @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeGE") - GF @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeGF") - GG @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeGG") - GH @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeGH") - GI @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeGI") - GL @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeGL") - GM @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeGM") - GN @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeGN") - GP @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeGP") - GQ @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeGQ") - GR @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeGR") - GT @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeGT") - GU @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeGU") - GW @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeGW") - GY @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeGY") - HK @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeHK") - HM @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeHM") - HN @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeHN") - HR @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeHR") - HT @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeHT") - HU @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeHU") - ID @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeID") - IE @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeIE") - IL @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeIL") - IM @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeIM") - IN @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeIN") - IO @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeIO") - IQ @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeIQ") - IR @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeIR") - IS @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeIS") - IT @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeIT") - JE @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeJE") - JM @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeJM") - JO @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeJO") - JP @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeJP") - KE @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeKE") - KG @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeKG") - KH @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeKH") - KI @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeKI") - KM @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeKM") - KN @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeKN") - KP @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeKP") - KR @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeKR") - KW @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeKW") - KY @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeKY") - KZ @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeKZ") - LA @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeLA") - LB @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeLB") - LC @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeLC") - LI @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeLI") - LK @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeLK") - LR @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeLR") - LS @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeLS") - LT @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeLT") - LU @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeLU") - LV @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeLV") - LY @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeLY") - MA @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeMA") - MC @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeMC") - MD @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeMD") - ME @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeME") - MF @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeMF") - MG @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeMG") - MH @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeMH") - MK @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeMK") - ML @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeML") - MM @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeMM") - MN @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeMN") - MO @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeMO") - MP @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeMP") - MQ @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeMQ") - MR @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeMR") - MS @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeMS") - MT @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeMT") - MU @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeMU") - MV @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeMV") - MW @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeMW") - MX @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeMX") - MY @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeMY") - MZ @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeMZ") - NA @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeNA") - NC @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeNC") - NE @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeNE") - NF @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeNF") - NG @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeNG") - NI @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeNI") - NL @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeNL") - NO @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeNO") - NP @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeNP") - NR @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeNR") - NU @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeNU") - NZ @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeNZ") - OM @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeOM") - PA @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodePA") - PE @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodePE") - PF @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodePF") - PG @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodePG") - PH @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodePH") - PK @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodePK") - PL @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodePL") - PM @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodePM") - PN @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodePN") - PR @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodePR") - PS @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodePS") - PT @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodePT") - PW @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodePW") - PY @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodePY") - QA @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeQA") - RE @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeRE") - RO @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeRO") - RS @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeRS") - RU @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeRU") - RW @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeRW") - SA @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeSA") - SB @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeSB") - SC @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeSC") - SD @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeSD") - SE @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeSE") - SG @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeSG") - SH @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeSH") - SI @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeSI") - SJ @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeSJ") - SK @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeSK") - SL @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeSL") - SM @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeSM") - SN @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeSN") - SO @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeSO") - SR @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeSR") - SS @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeSS") - ST @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeST") - SV @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeSV") - SX @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeSX") - SY @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeSY") - SZ @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeSZ") - TC @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeTC") - TD @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeTD") - TF @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeTF") - TG @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeTG") - TH @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeTH") - TJ @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeTJ") - TK @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeTK") - TL @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeTL") - TM @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeTM") - TN @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeTN") - TO @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeTO") - TR @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeTR") - TT @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeTT") - TV @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeTV") - TW @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeTW") - TZ @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeTZ") - UA @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeUA") - UG @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeUG") - UM @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeUM") - US @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeUS") - UY @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeUY") - UZ @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeUZ") - VA @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeVA") - VC @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeVC") - VE @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeVE") - VG @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeVG") - VI @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeVI") - VN @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeVN") - VU @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeVU") - WF @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeWF") - WS @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeWS") - YE @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeYE") - YT @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeYT") - ZA @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeZA") - ZM @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeZM") - ZW @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeZW") -} - -enum VendorCategory - @goModel(model: "go.probo.inc/probo/pkg/coredata.VendorCategory") { - ANALYTICS - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.VendorCategoryAnalytics" - ) - CLOUD_MONITORING - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.VendorCategoryCloudMonitoring" - ) - CLOUD_PROVIDER - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.VendorCategoryCloudProvider" - ) - COLLABORATION - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.VendorCategoryCollaboration" - ) - CUSTOMER_SUPPORT - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.VendorCategoryCustomerSupport" - ) - DATA_STORAGE_AND_PROCESSING - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.VendorCategoryDataStorageAndProcessing" - ) - DOCUMENT_MANAGEMENT - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.VendorCategoryDocumentManagement" - ) - EMPLOYEE_MANAGEMENT - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.VendorCategoryEmployeeManagement" - ) - ENGINEERING - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.VendorCategoryEngineering" - ) - FINANCE - @goEnum(value: "go.probo.inc/probo/pkg/coredata.VendorCategoryFinance") - IDENTITY_PROVIDER - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.VendorCategoryIdentityProvider" - ) - IT @goEnum(value: "go.probo.inc/probo/pkg/coredata.VendorCategoryIT") - MARKETING - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.VendorCategoryMarketing" - ) - OFFICE_OPERATIONS - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.VendorCategoryOfficeOperations" - ) - OTHER @goEnum(value: "go.probo.inc/probo/pkg/coredata.VendorCategoryOther") - PASSWORD_MANAGEMENT - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.VendorCategoryPasswordManagement" - ) - PRODUCT_AND_DESIGN - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.VendorCategoryProductAndDesign" - ) - PROFESSIONAL_SERVICES - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.VendorCategoryProfessionalServices" - ) - RECRUITING - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.VendorCategoryRecruiting" - ) - SALES @goEnum(value: "go.probo.inc/probo/pkg/coredata.VendorCategorySales") - SECURITY - @goEnum(value: "go.probo.inc/probo/pkg/coredata.VendorCategorySecurity") - VERSION_CONTROL - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.VendorCategoryVersionControl" - ) -} - -enum DocumentType - @goModel(model: "go.probo.inc/probo/pkg/coredata.DocumentType") { - OTHER @goEnum(value: "go.probo.inc/probo/pkg/coredata.DocumentTypeOther") - GOVERNANCE - @goEnum(value: "go.probo.inc/probo/pkg/coredata.DocumentTypeGovernance") - POLICY @goEnum(value: "go.probo.inc/probo/pkg/coredata.DocumentTypePolicy") - PROCEDURE - @goEnum(value: "go.probo.inc/probo/pkg/coredata.DocumentTypeProcedure") - PLAN @goEnum(value: "go.probo.inc/probo/pkg/coredata.DocumentTypePlan") - REGISTER - @goEnum(value: "go.probo.inc/probo/pkg/coredata.DocumentTypeRegister") - RECORD @goEnum(value: "go.probo.inc/probo/pkg/coredata.DocumentTypeRecord") - REPORT @goEnum(value: "go.probo.inc/probo/pkg/coredata.DocumentTypeReport") - TEMPLATE - @goEnum(value: "go.probo.inc/probo/pkg/coredata.DocumentTypeTemplate") -} - -enum DocumentClassification - @goModel(model: "go.probo.inc/probo/pkg/coredata.DocumentClassification") { - PUBLIC - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.DocumentClassificationPublic" - ) - INTERNAL - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.DocumentClassificationInternal" - ) - CONFIDENTIAL - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.DocumentClassificationConfidential" - ) - SECRET - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.DocumentClassificationSecret" - ) -} - -enum AssetType @goModel(model: "go.probo.inc/probo/pkg/coredata.AssetType") { - PHYSICAL @goEnum(value: "go.probo.inc/probo/pkg/coredata.AssetTypePhysical") - VIRTUAL @goEnum(value: "go.probo.inc/probo/pkg/coredata.AssetTypeVirtual") -} - -enum AssetOrderField - @goModel(model: "go.probo.inc/probo/pkg/coredata.AssetOrderField") { - CREATED_AT - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.AssetOrderFieldCreatedAt" - ) - AMOUNT - @goEnum(value: "go.probo.inc/probo/pkg/coredata.AssetOrderFieldAmount") -} - -enum DatumOrderField - @goModel(model: "go.probo.inc/probo/pkg/coredata.DatumOrderField") { - CREATED_AT - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.DatumOrderFieldCreatedAt" - ) - NAME @goEnum(value: "go.probo.inc/probo/pkg/coredata.DatumOrderFieldName") - DATA_CLASSIFICATION - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.DatumOrderFieldDataClassification" - ) -} - -enum DataClassification - @goModel(model: "go.probo.inc/probo/pkg/coredata.DataClassification") { - PUBLIC - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.DataClassificationPublic" - ) - INTERNAL - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.DataClassificationInternal" - ) - CONFIDENTIAL - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.DataClassificationConfidential" - ) - SECRET - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.DataClassificationSecret" - ) -} - -enum AuditOrderField - @goModel(model: "go.probo.inc/probo/pkg/coredata.AuditOrderField") { - CREATED_AT - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.AuditOrderFieldCreatedAt" - ) - VALID_FROM - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.AuditOrderFieldValidFrom" - ) - VALID_UNTIL - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.AuditOrderFieldValidUntil" - ) - STATE @goEnum(value: "go.probo.inc/probo/pkg/coredata.AuditOrderFieldState") -} - -enum FindingOrderField - @goModel(model: "go.probo.inc/probo/pkg/coredata.FindingOrderField") { - CREATED_AT - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.FindingOrderFieldCreatedAt" - ) - REFERENCE_ID - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.FindingOrderFieldReferenceId" - ) - IDENTIFIED_ON - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.FindingOrderFieldIdentifiedOn" - ) - DUE_DATE - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.FindingOrderFieldDueDate" - ) - STATUS - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.FindingOrderFieldStatus" - ) - PRIORITY - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.FindingOrderFieldPriority" - ) - KIND - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.FindingOrderFieldKind" - ) -} - -enum ObligationOrderField - @goModel(model: "go.probo.inc/probo/pkg/coredata.ObligationOrderField") { - CREATED_AT - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.ObligationOrderFieldCreatedAt" - ) - LAST_REVIEW_DATE - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.ObligationOrderFieldLastReviewDate" - ) - DUE_DATE - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.ObligationOrderFieldDueDate" - ) - STATUS - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.ObligationOrderFieldStatus" - ) -} - -enum RightsRequestOrderField - @goModel(model: "go.probo.inc/probo/pkg/coredata.RightsRequestOrderField") { - CREATED_AT - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.RightsRequestOrderFieldCreatedAt" - ) - DEADLINE - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.RightsRequestOrderFieldDeadline" - ) - STATE - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.RightsRequestOrderFieldState" - ) - TYPE - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.RightsRequestOrderFieldType" - ) -} - -enum ProcessingActivityOrderField - @goModel( - model: "go.probo.inc/probo/pkg/coredata.ProcessingActivityOrderField" - ) { - CREATED_AT - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.ProcessingActivityOrderFieldCreatedAt" - ) - NAME - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.ProcessingActivityOrderFieldName" - ) -} - -enum DataProtectionImpactAssessmentOrderField - @goModel( - model: "go.probo.inc/probo/pkg/coredata.DataProtectionImpactAssessmentOrderField" - ) { - CREATED_AT - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.DataProtectionImpactAssessmentOrderFieldCreatedAt" - ) -} - -enum TransferImpactAssessmentOrderField - @goModel( - model: "go.probo.inc/probo/pkg/coredata.TransferImpactAssessmentOrderField" - ) { - CREATED_AT - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.TransferImpactAssessmentOrderFieldCreatedAt" - ) -} - -enum TrustCenterAccessOrderField - @goModel( - model: "go.probo.inc/probo/pkg/coredata.TrustCenterAccessOrderField" - ) { - CREATED_AT - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.TrustCenterAccessOrderFieldCreatedAt" - ) -} - -enum TrustCenterAccessState - @goModel( - model: "go.probo.inc/probo/pkg/coredata.TrustCenterAccessState" - ) { - ACTIVE - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.TrustCenterAccessStateActive" - ) - INACTIVE - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.TrustCenterAccessStateInactive" - ) -} - -enum TrustCenterDocumentAccessOrderField - @goModel( - model: "go.probo.inc/probo/pkg/coredata.TrustCenterDocumentAccessOrderField" - ) { - CREATED_AT - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.TrustCenterDocumentAccessOrderFieldCreatedAt" - ) -} - -enum TrustCenterReferenceOrderField - @goModel( - model: "go.probo.inc/probo/pkg/coredata.TrustCenterReferenceOrderField" - ) { - RANK - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.TrustCenterReferenceOrderFieldRank" - ) - NAME - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.TrustCenterReferenceOrderFieldName" - ) - CREATED_AT - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.TrustCenterReferenceOrderFieldCreatedAt" - ) - UPDATED_AT - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.TrustCenterReferenceOrderFieldUpdatedAt" - ) -} - -enum ComplianceExternalURLOrderField - @goModel( - model: "go.probo.inc/probo/pkg/coredata.ComplianceExternalURLOrderField" - ) { - CREATED_AT - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.ComplianceExternalURLOrderFieldCreatedAt" - ) - RANK - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.ComplianceExternalURLOrderFieldRank" - ) -} - -enum ComplianceFrameworkOrderField - @goModel( - model: "go.probo.inc/probo/pkg/coredata.ComplianceFrameworkOrderField" - ) { - CREATED_AT - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.ComplianceFrameworkOrderFieldCreatedAt" - ) - RANK - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.ComplianceFrameworkOrderFieldRank" - ) -} - -enum ComplianceFrameworkVisibility - @goModel( - model: "go.probo.inc/probo/pkg/coredata.ComplianceFrameworkVisibility" - ) { - NONE - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.ComplianceFrameworkVisibilityNone" - ) - PUBLIC - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.ComplianceFrameworkVisibilityPublic" - ) -} - -enum TrustCenterFileOrderField - @goModel( - model: "go.probo.inc/probo/pkg/coredata.TrustCenterFileOrderField" - ) { - NAME - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.TrustCenterFileOrderFieldName" - ) - CREATED_AT - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.TrustCenterFileOrderFieldCreatedAt" - ) - UPDATED_AT - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.TrustCenterFileOrderFieldUpdatedAt" - ) -} - -enum SnapshotsType - @goModel(model: "go.probo.inc/probo/pkg/coredata.SnapshotsType") { - RISKS @goEnum(value: "go.probo.inc/probo/pkg/coredata.SnapshotsTypeRisks") - VENDORS - @goEnum(value: "go.probo.inc/probo/pkg/coredata.SnapshotsTypeVendors") - ASSETS @goEnum(value: "go.probo.inc/probo/pkg/coredata.SnapshotsTypeAssets") - DATA @goEnum(value: "go.probo.inc/probo/pkg/coredata.SnapshotsTypeData") - FINDINGS - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.SnapshotsTypeFindings" - ) - OBLIGATIONS - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.SnapshotsTypeObligations" - ) - PROCESSING_ACTIVITIES - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.SnapshotsTypeProcessingActivities" - ) - STATEMENTS_OF_APPLICABILITY - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.SnapshotsTypeStatementsOfApplicability" - ) -} - -enum SnapshotOrderField - @goModel(model: "go.probo.inc/probo/pkg/coredata.SnapshotOrderField") { - CREATED_AT - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.SnapshotOrderFieldCreatedAt" - ) - NAME - @goEnum(value: "go.probo.inc/probo/pkg/coredata.SnapshotOrderFieldName") - TYPE - @goEnum(value: "go.probo.inc/probo/pkg/coredata.SnapshotOrderFieldType") -} - -# Input Types -input ProfileOrder - @goModel( - model: "go.probo.inc/probo/pkg/server/api/console/v1/types.ProfileOrderBy" - ) { - direction: OrderDirection! - field: ProfileOrderField! -} - -input VendorOrder - @goModel( - model: "go.probo.inc/probo/pkg/server/api/console/v1/types.VendorOrderBy" - ) { - direction: OrderDirection! - field: VendorOrderField! -} - -input FrameworkOrder - @goModel( - model: "go.probo.inc/probo/pkg/server/api/console/v1/types.FrameworkOrderBy" - ) { - direction: OrderDirection! - field: FrameworkOrderField! -} - -input ControlOrder - @goModel( - model: "go.probo.inc/probo/pkg/server/api/console/v1/types.ControlOrderBy" - ) { - direction: OrderDirection! - field: ControlOrderField! -} - -input MeasureOrder - @goModel( - model: "go.probo.inc/probo/pkg/server/api/console/v1/types.MeasureOrderBy" - ) { - direction: OrderDirection! - field: MeasureOrderField! -} - -input TaskOrder - @goModel( - model: "go.probo.inc/probo/pkg/server/api/console/v1/types.TaskOrderBy" - ) { - direction: OrderDirection! - field: TaskOrderField! -} - -input DocumentOrder - @goModel( - model: "go.probo.inc/probo/pkg/server/api/console/v1/types.DocumentOrderBy" - ) { - direction: OrderDirection! - field: DocumentOrderField! -} - -input MeetingOrder - @goModel( - model: "go.probo.inc/probo/pkg/server/api/console/v1/types.MeetingOrderBy" - ) { - direction: OrderDirection! - field: MeetingOrderField! -} - -input WebhookSubscriptionOrder - @goModel( - model: "go.probo.inc/probo/pkg/server/api/console/v1/types.WebhookSubscriptionOrderBy" - ) { - direction: OrderDirection! - field: WebhookSubscriptionOrderField! -} - -input RiskOrder - @goModel( - model: "go.probo.inc/probo/pkg/server/api/console/v1/types.RiskOrderBy" - ) { - direction: OrderDirection! - field: RiskOrderField! -} - -input AuditOrder - @goModel( - model: "go.probo.inc/probo/pkg/server/api/console/v1/types.AuditOrderBy" - ) { - direction: OrderDirection! - field: AuditOrderField! -} - -input FindingOrder { - field: FindingOrderField! - direction: OrderDirection! -} - -input ObligationOrder - @goModel( - model: "go.probo.inc/probo/pkg/server/api/console/v1/types.ObligationOrderBy" - ) { - direction: OrderDirection! - field: ObligationOrderField! -} - - -input RightsRequestOrder - @goModel( - model: "go.probo.inc/probo/pkg/server/api/console/v1/types.RightsRequestOrderBy" - ) { - direction: OrderDirection! - field: RightsRequestOrderField! -} - -input ProcessingActivityOrder - @goModel( - model: "go.probo.inc/probo/pkg/server/api/console/v1/types.ProcessingActivityOrderBy" - ) { - direction: OrderDirection! - field: ProcessingActivityOrderField! -} - -input DataProtectionImpactAssessmentOrder - @goModel( - model: "go.probo.inc/probo/pkg/server/api/console/v1/types.DataProtectionImpactAssessmentOrderBy" - ) { - direction: OrderDirection! - field: DataProtectionImpactAssessmentOrderField! -} - -input TransferImpactAssessmentOrder - @goModel( - model: "go.probo.inc/probo/pkg/server/api/console/v1/types.TransferImpactAssessmentOrderBy" - ) { - direction: OrderDirection! - field: TransferImpactAssessmentOrderField! -} - -input TrustCenterAccessOrder - @goModel( - model: "go.probo.inc/probo/pkg/server/api/console/v1/types.TrustCenterAccessOrderBy" - ) { - direction: OrderDirection! - field: TrustCenterAccessOrderField! -} - -input TrustCenterDocumentAccessOrder - @goModel( - model: "go.probo.inc/probo/pkg/server/api/console/v1/types.TrustCenterDocumentAccessOrderBy" - ) { - direction: OrderDirection! - field: TrustCenterDocumentAccessOrderField! -} - -input TrustCenterReferenceOrder - @goModel( - model: "go.probo.inc/probo/pkg/server/api/console/v1/types.TrustCenterReferenceOrderBy" - ) { - direction: OrderDirection! - field: TrustCenterReferenceOrderField! -} - -input TrustCenterFileOrder - @goModel( - model: "go.probo.inc/probo/pkg/server/api/console/v1/types.TrustCenterFileOrderBy" - ) { - direction: OrderDirection! - field: TrustCenterFileOrderField! -} - -input ComplianceExternalURLOrder - @goModel( - model: "go.probo.inc/probo/pkg/server/api/console/v1/types.ComplianceExternalURLOrderBy" - ) { - direction: OrderDirection! - field: ComplianceExternalURLOrderField! -} - -input ComplianceFrameworkOrder - @goModel( - model: "go.probo.inc/probo/pkg/server/api/console/v1/types.ComplianceFrameworkOrderBy" - ) { - direction: OrderDirection! - field: ComplianceFrameworkOrderField! -} - -input EvidenceOrder - @goModel( - model: "go.probo.inc/probo/pkg/server/api/console/v1/types.EvidenceOrderBy" - ) { - direction: OrderDirection! - field: EvidenceOrderField! -} - -input VendorComplianceReportOrder - @goModel( - model: "go.probo.inc/probo/pkg/server/api/console/v1/types.VendorComplianceReportOrderBy" - ) { - direction: OrderDirection! - field: VendorComplianceReportOrderField! -} - -input VendorContactOrder - @goModel( - model: "go.probo.inc/probo/pkg/server/api/console/v1/types.VendorContactOrderBy" - ) { - direction: OrderDirection! - field: VendorContactOrderField! -} - -input VendorServiceOrder - @goModel( - model: "go.probo.inc/probo/pkg/server/api/console/v1/types.VendorServiceOrderBy" - ) { - direction: OrderDirection! - field: VendorServiceOrderField! -} - -input DocumentVersionOrder - @goModel( - model: "go.probo.inc/probo/pkg/server/api/console/v1/types.DocumentVersionOrderBy" - ) { - direction: OrderDirection! - field: DocumentVersionOrderField! -} - -input SnapshotOrder - @goModel( - model: "go.probo.inc/probo/pkg/server/api/console/v1/types.SnapshotOrderBy" - ) { - direction: OrderDirection! - field: SnapshotOrderField! -} - -input ApplicabilityStatementOrder - @goModel( - model: "go.probo.inc/probo/pkg/server/api/console/v1/types.ApplicabilityStatementOrderBy" - ) { - direction: OrderDirection! - field: ApplicabilityStatementOrderField! -} - -input DocumentVersionFilter { - statuses: [DocumentVersionStatus!] -} - -# Input Types for Filtering -input ControlFilter { - query: String -} - -input DocumentFilter { - query: String - documentTypes: [DocumentType!] - classifications: [DocumentClassification!] - status: [DocumentStatus!] -} - -input MeasureFilter { - query: String - state: MeasureState - category: String -} - -input RiskFilter { - query: String - snapshotId: ID -} - -input ProfileFilter { - excludeContractEnded: Boolean -} - -input DatumFilter { - snapshotId: ID -} - -input StatementOfApplicabilityFilter { - snapshotId: ID -} - -input FindingFilter { - snapshotId: ID - kind: FindingKind - status: FindingStatus - priority: FindingPriority - ownerId: ID -} - -input ObligationFilter { - snapshotId: ID -} - - -input ProcessingActivityFilter { - snapshotId: ID -} - -input DataProtectionImpactAssessmentFilter { - snapshotId: ID -} - -input TransferImpactAssessmentFilter { - snapshotId: ID -} - -input AssetFilter { - snapshotId: ID -} - -input VendorFilter { - snapshotId: ID -} - -# Core Types -type TrustCenter implements Node - @goModel( - model: "go.probo.inc/probo/pkg/server/api/console/v1/types.TrustCenter" - ) { - id: ID! - active: Boolean! - searchEngineIndexing: SearchEngineIndexing! - logoFileUrl: String @goField(forceResolver: true) - darkLogoFileUrl: String @goField(forceResolver: true) - ndaFileName: String - ndaFileUrl: String @goField(forceResolver: true) - createdAt: Datetime! - updatedAt: Datetime! - organization: Organization! @goField(forceResolver: true) - - accesses( - first: Int - after: CursorKey - last: Int - before: CursorKey - orderBy: TrustCenterAccessOrder - ): TrustCenterAccessConnection! @goField(forceResolver: true) - - references( - first: Int - after: CursorKey - last: Int - before: CursorKey - orderBy: TrustCenterReferenceOrder - ): TrustCenterReferenceConnection! @goField(forceResolver: true) - - complianceFrameworks( - first: Int - after: CursorKey - last: Int - before: CursorKey - orderBy: ComplianceFrameworkOrder - ): ComplianceFrameworkConnection! @goField(forceResolver: true) - - externalUrls( - first: Int - after: CursorKey - last: Int - before: CursorKey - orderBy: ComplianceExternalURLOrder - ): ComplianceExternalURLConnection! @goField(forceResolver: true) - - mailingList: MailingList @goField(forceResolver: true) - - permission(action: String!): Boolean! @goField(forceResolver: true) -} - -type MailingList implements Node { - id: ID! - replyTo: EmailAddr - - subscribers( - first: Int - after: CursorKey - last: Int - before: CursorKey - ): MailingListSubscriberConnection! @goField(forceResolver: true) - - updates( - first: Int - after: CursorKey - last: Int - before: CursorKey - ): MailingListUpdateConnection! @goField(forceResolver: true) -} - -enum MailingListSubscriberStatus - @goModel( - model: "go.probo.inc/probo/pkg/coredata.MailingListSubscriberStatus" - ) { - PENDING - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.MailingListSubscriberStatusPending" - ) - CONFIRMED - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.MailingListSubscriberStatusConfirmed" - ) -} - -type MailingListSubscriber implements Node { - id: ID! - fullName: String! - email: EmailAddr! - status: MailingListSubscriberStatus! - createdAt: Datetime! - updatedAt: Datetime! -} - -type MailingListSubscriberConnection - @goModel( - model: "go.probo.inc/probo/pkg/server/api/console/v1/types.MailingListSubscriberConnection" - ) { - edges: [MailingListSubscriberEdge!]! - pageInfo: PageInfo! - totalCount: Int! @goField(forceResolver: true) -} - -type MailingListSubscriberEdge { - cursor: CursorKey! - node: MailingListSubscriber! -} - -enum MailingListUpdateStatus - @goModel( - model: "go.probo.inc/probo/pkg/coredata.MailingListUpdateStatus" - ) { - DRAFT - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.MailingListUpdateStatusDraft" - ) - ENQUEUED - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.MailingListUpdateStatusEnqueued" - ) - PROCESSING - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.MailingListUpdateStatusProcessing" - ) - SENT - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.MailingListUpdateStatusSent" - ) -} - -type MailingListUpdate implements Node { - id: ID! - title: String! - body: String! - status: MailingListUpdateStatus! - createdAt: Datetime! - updatedAt: Datetime! -} - -type MailingListUpdateConnection - @goModel( - model: "go.probo.inc/probo/pkg/server/api/console/v1/types.MailingListUpdateConnection" - ) { - edges: [MailingListUpdateEdge!]! - pageInfo: PageInfo! - totalCount: Int! @goField(forceResolver: true) -} - -type MailingListUpdateEdge { - cursor: CursorKey! - node: MailingListUpdate! -} - -type Organization implements Node { - id: ID! - name: String! - logoUrl: String @goField(forceResolver: true) - horizontalLogoUrl: String @goField(forceResolver: true) - - description: String - websiteUrl: String - email: String - headquarterAddress: String - context: OrganizationContext @goField(forceResolver: true) - - profiles( - first: Int - after: CursorKey - last: Int - before: CursorKey - orderBy: ProfileOrder - filter: ProfileFilter - ): ProfileConnection! @goField(forceResolver: true) - - slackConnections( - first: Int - after: CursorKey - last: Int - before: CursorKey - ): SlackConnectionConnection! @goField(forceResolver: true) - slackOAuth2Scopes: [String!]! @goField(forceResolver: true) - connectors(filter: ConnectorFilter): [Connector!]! @goField(forceResolver: true) - connectorProviderInfos: [ConnectorProviderInfo!]! @goField(forceResolver: true) - - frameworks( - first: Int - after: CursorKey - last: Int - before: CursorKey - orderBy: FrameworkOrder - ): FrameworkConnection! @goField(forceResolver: true) - - controls( - first: Int - after: CursorKey - last: Int - before: CursorKey - orderBy: ControlOrder - filter: ControlFilter - ): ControlConnection! @goField(forceResolver: true) - - vendors( - first: Int - after: CursorKey - last: Int - before: CursorKey - orderBy: VendorOrder - filter: VendorFilter = { snapshotId: null } - ): VendorConnection! @goField(forceResolver: true) - - documents( - first: Int - after: CursorKey - last: Int - before: CursorKey - orderBy: DocumentOrder - filter: DocumentFilter - ): DocumentConnection! @goField(forceResolver: true) - - meetings( - first: Int - after: CursorKey - last: Int - before: CursorKey - orderBy: MeetingOrder - ): MeetingConnection! @goField(forceResolver: true) - - statementsOfApplicability( - first: Int - after: CursorKey - last: Int - before: CursorKey - orderBy: StatementOfApplicabilityOrder - filter: StatementOfApplicabilityFilter = { snapshotId: null } - ): StatementOfApplicabilityConnection! @goField(forceResolver: true) - - measureCategories: [String!]! @goField(forceResolver: true) - - measures( - first: Int - after: CursorKey - last: Int - before: CursorKey - orderBy: MeasureOrder - filter: MeasureFilter - ): MeasureConnection! @goField(forceResolver: true) - - risks( - first: Int - after: CursorKey - last: Int - before: CursorKey - orderBy: RiskOrder - filter: RiskFilter = { snapshotId: null } - ): RiskConnection! @goField(forceResolver: true) - - tasks( - first: Int - after: CursorKey - last: Int - before: CursorKey - orderBy: TaskOrder - ): TaskConnection! @goField(forceResolver: true) - - assets( - first: Int - after: CursorKey - last: Int - before: CursorKey - orderBy: AssetOrder - filter: AssetFilter = { snapshotId: null } - ): AssetConnection! @goField(forceResolver: true) - - data( - first: Int - after: CursorKey - last: Int - before: CursorKey - orderBy: DatumOrder - filter: DatumFilter = { snapshotId: null } - ): DatumConnection! @goField(forceResolver: true) - - audits( - first: Int - after: CursorKey - last: Int - before: CursorKey - orderBy: AuditOrder - ): AuditConnection! @goField(forceResolver: true) - - findings( - first: Int - after: CursorKey - last: Int - before: CursorKey - orderBy: FindingOrder - filter: FindingFilter = { snapshotId: null } - ): FindingConnection @goField(forceResolver: true) - - obligations( - first: Int - after: CursorKey - last: Int - before: CursorKey - orderBy: ObligationOrder - filter: ObligationFilter = { snapshotId: null } - ): ObligationConnection! @goField(forceResolver: true) - - rightsRequests( - first: Int - after: CursorKey - last: Int - before: CursorKey - orderBy: RightsRequestOrder - ): RightsRequestConnection! @goField(forceResolver: true) - - processingActivities( - first: Int - after: CursorKey - last: Int - before: CursorKey - orderBy: ProcessingActivityOrder - filter: ProcessingActivityFilter = { snapshotId: null } - ): ProcessingActivityConnection! @goField(forceResolver: true) - - dataProtectionImpactAssessments( - first: Int - after: CursorKey - last: Int - before: CursorKey - orderBy: DataProtectionImpactAssessmentOrder - filter: DataProtectionImpactAssessmentFilter = { snapshotId: null } - ): DataProtectionImpactAssessmentConnection! @goField(forceResolver: true) - - transferImpactAssessments( - first: Int - after: CursorKey - last: Int - before: CursorKey - orderBy: TransferImpactAssessmentOrder - filter: TransferImpactAssessmentFilter = { snapshotId: null } - ): TransferImpactAssessmentConnection! @goField(forceResolver: true) - - snapshots( - first: Int - after: CursorKey - last: Int - before: CursorKey - orderBy: SnapshotOrder - ): SnapshotConnection! @goField(forceResolver: true) - - trustCenterFiles( - first: Int - after: CursorKey - last: Int - before: CursorKey - orderBy: TrustCenterFileOrder - ): TrustCenterFileConnection! @goField(forceResolver: true) - - trustCenter: TrustCenter @goField(forceResolver: true) - - customDomain: CustomDomain @goField(forceResolver: true) - - webhookSubscriptions( - first: Int - after: CursorKey - last: Int - before: CursorKey - orderBy: WebhookSubscriptionOrder - ): WebhookSubscriptionConnection! @goField(forceResolver: true) - - auditLogEntries( - first: Int - after: CursorKey - last: Int - before: CursorKey - orderBy: AuditLogEntryOrder - filter: AuditLogEntryFilter - ): AuditLogEntryConnection! @goField(forceResolver: true) - - accessSources( - first: Int - after: CursorKey - last: Int - before: CursorKey - orderBy: AccessSourceOrder - ): AccessSourceConnection! @goField(forceResolver: true) - - accessReviewCampaigns( - first: Int - after: CursorKey - last: Int - before: CursorKey - orderBy: AccessReviewCampaignOrder - ): AccessReviewCampaignConnection! @goField(forceResolver: true) - - createdAt: Datetime! - updatedAt: Datetime! - - permission(action: String!): Boolean! @goField(forceResolver: true) -} - -enum ConnectorProvider - @goModel(model: "go.probo.inc/probo/pkg/coredata.ConnectorProvider") { - SLACK @goEnum(value: "go.probo.inc/probo/pkg/coredata.ConnectorProviderSlack") - GOOGLE_WORKSPACE - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.ConnectorProviderGoogleWorkspace" - ) - LINEAR @goEnum(value: "go.probo.inc/probo/pkg/coredata.ConnectorProviderLinear") - ONE_PASSWORD - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.ConnectorProviderOnePassword" - ) - HUBSPOT - @goEnum(value: "go.probo.inc/probo/pkg/coredata.ConnectorProviderHubSpot") - DOCUSIGN - @goEnum(value: "go.probo.inc/probo/pkg/coredata.ConnectorProviderDocuSign") - NOTION @goEnum(value: "go.probo.inc/probo/pkg/coredata.ConnectorProviderNotion") - BREX @goEnum(value: "go.probo.inc/probo/pkg/coredata.ConnectorProviderBrex") - TALLY @goEnum(value: "go.probo.inc/probo/pkg/coredata.ConnectorProviderTally") - CLOUDFLARE - @goEnum(value: "go.probo.inc/probo/pkg/coredata.ConnectorProviderCloudflare") - OPENAI @goEnum(value: "go.probo.inc/probo/pkg/coredata.ConnectorProviderOpenAI") - SENTRY @goEnum(value: "go.probo.inc/probo/pkg/coredata.ConnectorProviderSentry") - SUPABASE - @goEnum(value: "go.probo.inc/probo/pkg/coredata.ConnectorProviderSupabase") - GITHUB @goEnum(value: "go.probo.inc/probo/pkg/coredata.ConnectorProviderGitHub") - INTERCOM - @goEnum(value: "go.probo.inc/probo/pkg/coredata.ConnectorProviderIntercom") - RESEND @goEnum(value: "go.probo.inc/probo/pkg/coredata.ConnectorProviderResend") -} - -type ConnectorProviderInfo { - provider: ConnectorProvider! - displayName: String! - oauthConfigured: Boolean! - apiKeySupported: Boolean! - clientCredentialsSupported: Boolean! - oauth2Scopes: [String!]! - extraSettings: [ConnectorProviderSettingInfo!]! -} - -type ConnectorProviderSettingInfo { - key: String! - label: String! - required: Boolean! -} - -input ConnectorFilter { - providers: [ConnectorProvider!] -} - -type Connector { - id: ID! - provider: ConnectorProvider! - oauth2Scopes: [String!]! @goField(forceResolver: true) - createdAt: Datetime! -} - -type SlackConnection implements Node { - id: ID! - channel: String - channelId: String - createdAt: Datetime! - updatedAt: Datetime! - - permission(action: String!): Boolean! @goField(forceResolver: true) -} - -type SlackConnectionConnection { - edges: [SlackConnectionEdge!]! - pageInfo: PageInfo! -} - -type SlackConnectionEdge { - cursor: CursorKey! - node: SlackConnection! -} - -type Profile implements Node { - id: ID! - fullName: String! - emailAddress: EmailAddr! - state: ProfileState! - additionalEmailAddresses: [EmailAddr!]! - kind: String - position: String - contractStartDate: Datetime - contractEndDate: Datetime - createdAt: Datetime! - updatedAt: Datetime! - - permission(action: String!): Boolean! @goField(forceResolver: true) -} - -enum ProfileState - @goModel(model: "go.probo.inc/probo/pkg/coredata.ProfileState") { - ACTIVE @goEnum(value: "go.probo.inc/probo/pkg/coredata.ProfileStateActive") - INACTIVE - @goEnum(value: "go.probo.inc/probo/pkg/coredata.ProfileStateInactive") -} - -type Vendor implements Node { - id: ID! - snapshotId: ID - name: String! - category: VendorCategory! - description: String - - organization: Organization! @goField(forceResolver: true) - - complianceReports( - first: Int - after: CursorKey - last: Int - before: CursorKey - orderBy: VendorComplianceReportOrder - ): VendorComplianceReportConnection! @goField(forceResolver: true) - - businessAssociateAgreement: VendorBusinessAssociateAgreement - @goField(forceResolver: true) - dataPrivacyAgreement: VendorDataPrivacyAgreement - @goField(forceResolver: true) - - contacts( - first: Int - after: CursorKey - last: Int - before: CursorKey - orderBy: VendorContactOrder - ): VendorContactConnection! @goField(forceResolver: true) - - services( - first: Int - after: CursorKey - last: Int - before: CursorKey - orderBy: VendorServiceOrder - ): VendorServiceConnection! @goField(forceResolver: true) - - riskAssessments( - first: Int - after: CursorKey - last: Int - before: CursorKey - orderBy: VendorRiskAssessmentOrder - ): VendorRiskAssessmentConnection! @goField(forceResolver: true) - - businessOwner: Profile @goField(forceResolver: true) - securityOwner: Profile @goField(forceResolver: true) - - statusPageUrl: String - termsOfServiceUrl: String - privacyPolicyUrl: String - serviceLevelAgreementUrl: String - dataProcessingAgreementUrl: String - businessAssociateAgreementUrl: String - subprocessorsListUrl: String - certifications: [String!]! - countries: [CountryCode!]! - securityPageUrl: String - trustPageUrl: String - headquarterAddress: String - legalName: String - websiteUrl: String - showOnTrustCenter: Boolean! - createdAt: Datetime! - updatedAt: Datetime! - - permission(action: String!): Boolean! @goField(forceResolver: true) -} - -type VendorComplianceReport implements Node { - id: ID! - vendor: Vendor! @goField(forceResolver: true) - reportDate: Datetime! - validUntil: Datetime - reportName: String! - file: File @goField(forceResolver: true) - createdAt: Datetime! - updatedAt: Datetime! - - permission(action: String!): Boolean! @goField(forceResolver: true) -} - -type VendorBusinessAssociateAgreement implements Node { - id: ID! - vendor: Vendor! @goField(forceResolver: true) - validFrom: Datetime - validUntil: Datetime - fileName: String! - fileUrl: String! @goField(forceResolver: true) - fileSize: BigInt! - createdAt: Datetime! - updatedAt: Datetime! - - permission(action: String!): Boolean! @goField(forceResolver: true) -} - -type VendorContact implements Node { - id: ID! - vendor: Vendor! @goField(forceResolver: true) - fullName: String - email: EmailAddr - phone: String - role: String - createdAt: Datetime! - updatedAt: Datetime! - - permission(action: String!): Boolean! @goField(forceResolver: true) -} - -type VendorService implements Node { - id: ID! - vendor: Vendor! @goField(forceResolver: true) - name: String! - description: String - createdAt: Datetime! - updatedAt: Datetime! - - permission(action: String!): Boolean! @goField(forceResolver: true) -} - -type VendorDataPrivacyAgreement implements Node { - id: ID! - vendor: Vendor! @goField(forceResolver: true) - validFrom: Datetime - validUntil: Datetime - fileName: String! - fileUrl: String! @goField(forceResolver: true) - fileSize: BigInt! - createdAt: Datetime! - updatedAt: Datetime! - - permission(action: String!): Boolean! @goField(forceResolver: true) -} - -type Framework implements Node { - id: ID! - name: String! - description: String - - organization: Organization! @goField(forceResolver: true) - - controls( - first: Int - after: CursorKey - last: Int - before: CursorKey - orderBy: ControlOrder - filter: ControlFilter - ): ControlConnection! @goField(forceResolver: true) - lightLogoURL: String @goField(forceResolver: true) - darkLogoURL: String @goField(forceResolver: true) - - createdAt: Datetime! - updatedAt: Datetime! - - permission(action: String!): Boolean! @goField(forceResolver: true) -} - -type Control implements Node { - id: ID! - organization: Organization @goField(forceResolver: true) - sectionTitle: String! - name: String! - description: String - bestPractice: Boolean! - implemented: ControlImplementationState! - notImplementedJustification: String - regulatory: Boolean! @goField(forceResolver: true) - contractual: Boolean! @goField(forceResolver: true) - riskAssessment: Boolean! @goField(forceResolver: true) - - framework: Framework! @goField(forceResolver: true) - - measures( - first: Int - after: CursorKey - last: Int - before: CursorKey - orderBy: MeasureOrder - filter: MeasureFilter - ): MeasureConnection! @goField(forceResolver: true) - - documents( - first: Int - after: CursorKey - last: Int - before: CursorKey - orderBy: DocumentOrder - filter: DocumentFilter - ): DocumentConnection! @goField(forceResolver: true) - - audits( - first: Int - after: CursorKey - last: Int - before: CursorKey - orderBy: AuditOrder - ): AuditConnection! @goField(forceResolver: true) - - obligations( - first: Int - after: CursorKey - last: Int - before: CursorKey - orderBy: ObligationOrder - filter: ObligationFilter - ): ObligationConnection! @goField(forceResolver: true) - - snapshots( - first: Int - after: CursorKey - last: Int - before: CursorKey - orderBy: SnapshotOrder - ): SnapshotConnection! @goField(forceResolver: true) - - createdAt: Datetime! - updatedAt: Datetime! - - permission(action: String!): Boolean! @goField(forceResolver: true) -} - -type Measure implements Node { - id: ID! - category: String! - name: String! - description: String - state: MeasureState! - - evidences( - first: Int - after: CursorKey - last: Int - before: CursorKey - orderBy: EvidenceOrder - ): EvidenceConnection! @goField(forceResolver: true) - - tasks( - first: Int - after: CursorKey - last: Int - before: CursorKey - orderBy: TaskOrder - ): TaskConnection! @goField(forceResolver: true) - - risks( - first: Int - after: CursorKey - last: Int - before: CursorKey - orderBy: RiskOrder - filter: RiskFilter - ): RiskConnection! @goField(forceResolver: true) - - controls( - first: Int - after: CursorKey - last: Int - before: CursorKey - orderBy: ControlOrder - filter: ControlFilter - ): ControlConnection! @goField(forceResolver: true) - - documents( - first: Int - after: CursorKey - last: Int - before: CursorKey - orderBy: DocumentOrder - filter: DocumentFilter - ): DocumentConnection! @goField(forceResolver: true) - - createdAt: Datetime! - updatedAt: Datetime! - - permission(action: String!): Boolean! @goField(forceResolver: true) -} - -type Task implements Node { - id: ID! - name: String! - description: String - state: TaskState! - priority: TaskPriority! - rank: Int! - timeEstimate: Duration - deadline: Datetime - assignedTo: Profile @goField(forceResolver: true) - - organization: Organization! @goField(forceResolver: true) - measure: Measure @goField(forceResolver: true) - - evidences( - first: Int - after: CursorKey - last: Int - before: CursorKey - orderBy: EvidenceOrder - ): EvidenceConnection! @goField(forceResolver: true) - - createdAt: Datetime! - updatedAt: Datetime! - - permission(action: String!): Boolean! @goField(forceResolver: true) -} - -type Evidence implements Node { - id: ID! - size: Int! - state: EvidenceState! - type: EvidenceType! - file: File @goField(forceResolver: true) - url: String - description: String - - task: Task @goField(forceResolver: true) - measure: Measure! @goField(forceResolver: true) - - createdAt: Datetime! - updatedAt: Datetime! - - permission(action: String!): Boolean! @goField(forceResolver: true) -} - -type Document implements Node { - id: ID! - currentPublishedMajor: Int - currentPublishedMinor: Int - trustCenterVisibility: TrustCenterVisibility! - organization: Organization! @goField(forceResolver: true) - - versions( - first: Int - after: CursorKey - last: Int - before: CursorKey - orderBy: DocumentVersionOrder - filter: DocumentVersionFilter - ): DocumentVersionConnection! @goField(forceResolver: true) - - controls( - first: Int - after: CursorKey - last: Int - before: CursorKey - orderBy: ControlOrder - filter: ControlFilter - ): ControlConnection! @goField(forceResolver: true) - - defaultApprovers: [Profile!]! @goField(forceResolver: true) - - status: DocumentStatus! - archivedAt: Datetime - - createdAt: Datetime! - updatedAt: Datetime! - - permission(action: String!): Boolean! @goField(forceResolver: true) -} - -type EmployeeDocument - @goModel( - model: "go.probo.inc/probo/pkg/server/api/console/v1/types.EmployeeDocument" - ) { - id: ID! - title: String! - signed: Boolean @goField(forceResolver: true) - approvalState: DocumentVersionApprovalDecisionState @goField(forceResolver: true) - - versions( - first: Int - after: CursorKey - last: Int - before: CursorKey - orderBy: DocumentVersionOrder - ): EmployeeDocumentVersionConnection! @goField(forceResolver: true) - - createdAt: Datetime! - updatedAt: Datetime! -} - -type EmployeeDocumentVersion - @goModel( - model: "go.probo.inc/probo/pkg/server/api/console/v1/types.EmployeeDocumentVersion" - ) { - id: ID! - major: Int! - minor: Int! - status: DocumentVersionStatus! - classification: DocumentClassification! - documentType: DocumentType! - signed: Boolean! @goField(forceResolver: true) - approvalDecision: DocumentVersionApprovalDecision @goField(forceResolver: true) - publishedAt: Datetime - createdAt: Datetime! - updatedAt: Datetime! -} - -type Meeting implements Node { - id: ID! - name: String! - date: Datetime! - minutes: String - attendees: [Profile!]! @goField(forceResolver: true) - organization: Organization! @goField(forceResolver: true) - createdAt: Datetime! - updatedAt: Datetime! - - permission(action: String!): Boolean! @goField(forceResolver: true) -} - -enum WebhookEventType - @goModel(model: "go.probo.inc/probo/pkg/coredata.WebhookEventType") { - MEETING_CREATED - @goEnum(value: "go.probo.inc/probo/pkg/coredata.WebhookEventTypeMeetingCreated") - MEETING_UPDATED - @goEnum(value: "go.probo.inc/probo/pkg/coredata.WebhookEventTypeMeetingUpdated") - MEETING_DELETED - @goEnum(value: "go.probo.inc/probo/pkg/coredata.WebhookEventTypeMeetingDeleted") - VENDOR_CREATED - @goEnum(value: "go.probo.inc/probo/pkg/coredata.WebhookEventTypeVendorCreated") - VENDOR_UPDATED - @goEnum(value: "go.probo.inc/probo/pkg/coredata.WebhookEventTypeVendorUpdated") - VENDOR_DELETED - @goEnum(value: "go.probo.inc/probo/pkg/coredata.WebhookEventTypeVendorDeleted") - USER_CREATED - @goEnum(value: "go.probo.inc/probo/pkg/coredata.WebhookEventTypeUserCreated") - USER_UPDATED - @goEnum(value: "go.probo.inc/probo/pkg/coredata.WebhookEventTypeUserUpdated") - USER_DELETED - @goEnum(value: "go.probo.inc/probo/pkg/coredata.WebhookEventTypeUserDeleted") - OBLIGATION_CREATED - @goEnum(value: "go.probo.inc/probo/pkg/coredata.WebhookEventTypeObligationCreated") - OBLIGATION_UPDATED - @goEnum(value: "go.probo.inc/probo/pkg/coredata.WebhookEventTypeObligationUpdated") - OBLIGATION_DELETED - @goEnum(value: "go.probo.inc/probo/pkg/coredata.WebhookEventTypeObligationDeleted") -} - -type WebhookSubscription implements Node { - id: ID! - organization: Organization @goField(forceResolver: true) - endpointUrl: String! - signingSecret: String! @goField(forceResolver: true) - selectedEvents: [WebhookEventType!]! - createdAt: Datetime! - updatedAt: Datetime! - - events( - first: Int - after: CursorKey - last: Int - before: CursorKey - orderBy: WebhookEventOrder - ): WebhookEventConnection! @goField(forceResolver: true) - - permission(action: String!): Boolean! @goField(forceResolver: true) -} - -type WebhookSubscriptionConnection - @goModel( - model: "go.probo.inc/probo/pkg/server/api/console/v1/types.WebhookSubscriptionConnection" - ) { - edges: [WebhookSubscriptionEdge!]! - pageInfo: PageInfo! - totalCount: Int! @goField(forceResolver: true) -} - -type WebhookSubscriptionEdge { - cursor: CursorKey! - node: WebhookSubscription! -} - -enum WebhookEventStatus - @goModel(model: "go.probo.inc/probo/pkg/coredata.WebhookEventStatus") { - PENDING - @goEnum(value: "go.probo.inc/probo/pkg/coredata.WebhookEventStatusPending") - SUCCEEDED - @goEnum(value: "go.probo.inc/probo/pkg/coredata.WebhookEventStatusSucceeded") - FAILED - @goEnum(value: "go.probo.inc/probo/pkg/coredata.WebhookEventStatusFailed") -} - -enum WebhookEventOrderField - @goModel( - model: "go.probo.inc/probo/pkg/coredata.WebhookEventOrderField" - ) { - CREATED_AT - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.WebhookEventOrderFieldCreatedAt" - ) -} - -input WebhookEventOrder - @goModel( - model: "go.probo.inc/probo/pkg/server/api/console/v1/types.WebhookEventOrderBy" - ) { - field: WebhookEventOrderField! - direction: OrderDirection! -} - -type WebhookEvent implements Node { - id: ID! - webhookSubscriptionId: ID! - status: WebhookEventStatus! - response: String - createdAt: Datetime! -} - -type WebhookEventConnection - @goModel( - model: "go.probo.inc/probo/pkg/server/api/console/v1/types.WebhookEventConnection" - ) { - edges: [WebhookEventEdge!]! - pageInfo: PageInfo! - totalCount: Int! @goField(forceResolver: true) -} - -type WebhookEventEdge { - cursor: CursorKey! - node: WebhookEvent! -} - -type StatementOfApplicability implements Node { - id: ID! - name: String! - sourceId: ID - snapshotId: ID - organization: Organization @goField(forceResolver: true) - owner: Profile! @goField(forceResolver: true) - createdAt: Datetime! - updatedAt: Datetime! - - applicabilityStatements( - first: Int - after: CursorKey - last: Int - before: CursorKey - orderBy: ApplicabilityStatementOrder - ): ApplicabilityStatementConnection! @goField(forceResolver: true) - - permission(action: String!): Boolean! @goField(forceResolver: true) -} - -type ApplicabilityStatementConnection - @goModel( - model: "go.probo.inc/probo/pkg/server/api/console/v1/types.ApplicabilityStatementConnection" - ) { - totalCount: Int! @goField(forceResolver: true) - edges: [ApplicabilityStatementEdge!]! - pageInfo: PageInfo! -} - -type ApplicabilityStatementEdge { - cursor: CursorKey! - node: ApplicabilityStatement! -} - -type ApplicabilityStatement implements Node { - id: ID! - statementOfApplicability: StatementOfApplicability! @goField(forceResolver: true) - control: Control! @goField(forceResolver: true) - applicability: Boolean! - justification: String! - createdAt: Datetime! - updatedAt: Datetime! - - permission(action: String!): Boolean! @goField(forceResolver: true) -} - -type Risk implements Node { - id: ID! - snapshotId: ID - name: String! - description: String - category: String! - treatment: RiskTreatment! - inherentLikelihood: Int! - inherentImpact: Int! - inherentRiskScore: Int! - residualLikelihood: Int! - residualImpact: Int! - residualRiskScore: Int! - note: String! - - owner: Profile @goField(forceResolver: true) - organization: Organization! @goField(forceResolver: true) - - measures( - first: Int - after: CursorKey - last: Int - before: CursorKey - orderBy: MeasureOrder - filter: MeasureFilter - ): MeasureConnection! @goField(forceResolver: true) - - documents( - first: Int - after: CursorKey - last: Int - before: CursorKey - orderBy: DocumentOrder - filter: DocumentFilter - ): DocumentConnection! @goField(forceResolver: true) - - controls( - first: Int - after: CursorKey - last: Int - before: CursorKey - orderBy: ControlOrder - filter: ControlFilter - ): ControlConnection! @goField(forceResolver: true) - - obligations( - first: Int - after: CursorKey - last: Int - before: CursorKey - orderBy: ObligationOrder - filter: ObligationFilter - ): ObligationConnection! @goField(forceResolver: true) - - createdAt: Datetime! - updatedAt: Datetime! - - permission(action: String!): Boolean! @goField(forceResolver: true) -} - -type Audit implements Node { - id: ID! - name: String - organization: Organization @goField(forceResolver: true) - framework: Framework @goField(forceResolver: true) - validFrom: Datetime - validUntil: Datetime - report: Report @goField(forceResolver: true) - reportUrl: String @goField(forceResolver: true) - state: AuditState! - - controls( - first: Int - after: CursorKey - last: Int - before: CursorKey - orderBy: ControlOrder - filter: ControlFilter - ): ControlConnection @goField(forceResolver: true) - - findings( - first: Int - after: CursorKey - last: Int - before: CursorKey - orderBy: FindingOrder - filter: FindingFilter = { snapshotId: null } - ): FindingConnection @goField(forceResolver: true) - - trustCenterVisibility: TrustCenterVisibility! - createdAt: Datetime! - updatedAt: Datetime! - - permission(action: String!): Boolean! @goField(forceResolver: true) -} - -type Finding implements Node { - id: ID! - snapshotId: ID - organization: Organization @goField(forceResolver: true) - kind: FindingKind! - referenceId: String! - description: String - source: String - audits( - first: Int - after: CursorKey - last: Int - before: CursorKey - orderBy: AuditOrder - ): AuditConnection @goField(forceResolver: true) - identifiedOn: Datetime - rootCause: String - correctiveAction: String - owner: Profile @goField(forceResolver: true) - dueDate: Datetime - status: FindingStatus! - priority: FindingPriority! - risk: Risk @goField(forceResolver: true) - effectivenessCheck: String - createdAt: Datetime! - updatedAt: Datetime! - permission(action: String!): Boolean! @goField(forceResolver: true) -} - -type Obligation implements Node { - id: ID! - snapshotId: ID - sourceId: ID - organization: Organization! @goField(forceResolver: true) - area: String - source: String - requirement: String - actionsToBeImplemented: String - regulator: String - owner: Profile! @goField(forceResolver: true) - lastReviewDate: Datetime - dueDate: Datetime - status: ObligationStatus! - type: ObligationType! - createdAt: Datetime! - updatedAt: Datetime! - - permission(action: String!): Boolean! @goField(forceResolver: true) -} - - -type RightsRequest implements Node { - id: ID! - organization: Organization! @goField(forceResolver: true) - requestType: RightsRequestType! - requestState: RightsRequestState! - dataSubject: String - contact: String - details: String - deadline: Datetime - actionTaken: String - createdAt: Datetime! - updatedAt: Datetime! - - permission(action: String!): Boolean! @goField(forceResolver: true) -} - -type ProcessingActivity implements Node { - id: ID! - snapshotId: ID - sourceId: ID - organization: Organization! @goField(forceResolver: true) - name: String! - purpose: String - dataSubjectCategory: String - personalDataCategory: String - specialOrCriminalData: ProcessingActivitySpecialOrCriminalDatum! - consentEvidenceLink: String - lawfulBasis: ProcessingActivityLawfulBasis! - recipients: String - location: String - internationalTransfers: Boolean! - transferSafeguards: ProcessingActivityTransferSafeguard - retentionPeriod: String - securityMeasures: String - dataProtectionImpactAssessmentNeeded: ProcessingActivityDataProtectionImpactAssessment! - transferImpactAssessmentNeeded: ProcessingActivityTransferImpactAssessment! - lastReviewDate: Datetime - nextReviewDate: Datetime - role: ProcessingActivityRole! - dataProtectionOfficer: Profile @goField(forceResolver: true) - vendors( - first: Int - after: CursorKey - last: Int - before: CursorKey - orderBy: VendorOrder - ): VendorConnection! @goField(forceResolver: true) - dataProtectionImpactAssessment: DataProtectionImpactAssessment - @goField(forceResolver: true) - transferImpactAssessment: TransferImpactAssessment - @goField(forceResolver: true) - createdAt: Datetime! - updatedAt: Datetime! - - permission(action: String!): Boolean! @goField(forceResolver: true) -} - -type DataProtectionImpactAssessment implements Node { - id: ID! - processingActivity: ProcessingActivity! @goField(forceResolver: true) - organization: Organization! @goField(forceResolver: true) - description: String - necessityAndProportionality: String - potentialRisk: String - mitigations: String - residualRisk: DataProtectionImpactAssessmentResidualRisk - createdAt: Datetime! - updatedAt: Datetime! - - permission(action: String!): Boolean! @goField(forceResolver: true) -} - -type TransferImpactAssessment implements Node { - id: ID! - processingActivity: ProcessingActivity! @goField(forceResolver: true) - organization: Organization! @goField(forceResolver: true) - dataSubjects: String - legalMechanism: String - transfer: String - localLawRisk: String - supplementaryMeasures: String - createdAt: Datetime! - updatedAt: Datetime! - - permission(action: String!): Boolean! @goField(forceResolver: true) -} - -type Snapshot implements Node { - id: ID! - organization: Organization! @goField(forceResolver: true) - name: String! - description: String - type: SnapshotsType! - - controls( - first: Int - after: CursorKey - last: Int - before: CursorKey - orderBy: ControlOrder - filter: ControlFilter - ): ControlConnection! @goField(forceResolver: true) - - createdAt: Datetime! - - permission(action: String!): Boolean! @goField(forceResolver: true) -} - -type Report implements Node { - id: ID! - objectKey: String! - mimeType: String! - filename: String! - size: Int! - downloadUrl: String @goField(forceResolver: true) - createdAt: Datetime! - updatedAt: Datetime! - audit: Audit @goField(forceResolver: true) - - permission(action: String!): Boolean! @goField(forceResolver: true) -} - -type Viewer { - id: ID! - - signableDocuments( - organizationId: ID! - first: Int - after: CursorKey - last: Int - before: CursorKey - orderBy: DocumentOrder - ): EmployeeDocumentConnection! @goField(forceResolver: true) - - signableDocument(id: ID!): EmployeeDocument @goField(forceResolver: true) - - approvableDocuments( - organizationId: ID! - first: Int - after: CursorKey - last: Int - before: CursorKey - orderBy: DocumentOrder - ): EmployeeDocumentConnection! @goField(forceResolver: true) - - approvableDocument(id: ID!): EmployeeDocument @goField(forceResolver: true) -} - -type TrustCenterConnection { - edges: [TrustCenterEdge!]! - pageInfo: PageInfo! -} - -type TrustCenterEdge { - cursor: CursorKey! - node: TrustCenter! -} - -type TrustCenterAccess implements Node { - id: ID! - ndaSignature: ElectronicSignature @goField(forceResolver: true) - createdAt: Datetime! - updatedAt: Datetime! - pendingRequestCount: Int! @goField(forceResolver: true) - activeCount: Int! @goField(forceResolver: true) - organizationId: ID! - identityID: ID! - - profile: Profile! @goField(forceResolver: true) - - availableDocumentAccesses( - first: Int - after: CursorKey - last: Int - before: CursorKey - orderBy: TrustCenterDocumentAccessOrder - ): TrustCenterDocumentAccessConnection! @goField(forceResolver: true) - - permission(action: String!): Boolean! @goField(forceResolver: true) -} - -type TrustCenterDocumentAccess - @goModel( - model: "go.probo.inc/probo/pkg/server/api/console/v1/types.TrustCenterDocumentAccess" - ) { - id: ID! - status: TrustCenterDocumentAccessStatus! - document: Document @goField(forceResolver: true) - report: Report @goField(forceResolver: true) - trustCenterFile: TrustCenterFile @goField(forceResolver: true) -} - -type TrustCenterDocumentAccessConnection - @goModel( - model: "go.probo.inc/probo/pkg/server/api/console/v1/types.TrustCenterDocumentAccessConnection" - ) { - totalCount: Int! @goField(forceResolver: true) - edges: [TrustCenterDocumentAccessEdge!]! - pageInfo: PageInfo! -} - -type TrustCenterDocumentAccessEdge { - cursor: CursorKey! - node: TrustCenterDocumentAccess! -} - -type TrustCenterAccessConnection { - edges: [TrustCenterAccessEdge!]! - pageInfo: PageInfo! -} - -type TrustCenterAccessEdge { - cursor: CursorKey! - node: TrustCenterAccess! -} - -type TrustCenterReference implements Node { - id: ID! - name: String! - description: String - websiteUrl: String! - logoUrl: String! @goField(forceResolver: true) - rank: Int! - createdAt: Datetime! - updatedAt: Datetime! - - permission(action: String!): Boolean! @goField(forceResolver: true) -} - -type TrustCenterReferenceConnection - @goModel( - model: "go.probo.inc/probo/pkg/server/api/console/v1/types.TrustCenterReferenceConnection" - ) { - totalCount: Int! @goField(forceResolver: true) - edges: [TrustCenterReferenceEdge!]! - pageInfo: PageInfo! -} - -type TrustCenterReferenceEdge { - cursor: CursorKey! - node: TrustCenterReference! -} - -type ComplianceFramework implements Node - @goModel( - model: "go.probo.inc/probo/pkg/server/api/console/v1/types.ComplianceFramework" - ) { - id: ID! - framework: Framework! @goField(forceResolver: true) - rank: Int! - visibility: ComplianceFrameworkVisibility! - createdAt: Datetime! - updatedAt: Datetime! -} - -type ComplianceFrameworkConnection - @goModel( - model: "go.probo.inc/probo/pkg/server/api/console/v1/types.ComplianceFrameworkConnection" - ) { - edges: [ComplianceFrameworkEdge!]! - pageInfo: PageInfo! -} - -type ComplianceFrameworkEdge { - cursor: CursorKey! - node: ComplianceFramework! -} - -type ComplianceExternalURL implements Node { - id: ID! - name: String! - url: String! - rank: Int! - createdAt: Datetime! - updatedAt: Datetime! - - permission(action: String!): Boolean! @goField(forceResolver: true) -} - -type ComplianceExternalURLConnection - @goModel( - model: "go.probo.inc/probo/pkg/server/api/console/v1/types.ComplianceExternalURLConnection" - ) { - edges: [ComplianceExternalURLEdge!]! - pageInfo: PageInfo! -} - -type ComplianceExternalURLEdge { - cursor: CursorKey! - node: ComplianceExternalURL! -} - -type TrustCenterFile implements Node { - id: ID! - name: String! - category: String! - fileUrl: String! @goField(forceResolver: true) - trustCenterVisibility: TrustCenterVisibility! - createdAt: Datetime! - updatedAt: Datetime! - organization: Organization! @goField(forceResolver: true) - - permission(action: String!): Boolean! @goField(forceResolver: true) -} - -type TrustCenterFileConnection - @goModel( - model: "go.probo.inc/probo/pkg/server/api/console/v1/types.TrustCenterFileConnection" - ) { - totalCount: Int! @goField(forceResolver: true) - edges: [TrustCenterFileEdge!]! - pageInfo: PageInfo! -} - -type TrustCenterFileEdge { - cursor: CursorKey! - node: TrustCenterFile! -} - -type ProfileConnection - @goModel( - model: "go.probo.inc/probo/pkg/server/api/console/v1/types.ProfileConnection" - ) { - totalCount: Int! @goField(forceResolver: true) - edges: [ProfileEdge!]! - pageInfo: PageInfo! -} - -type ProfileEdge { - cursor: CursorKey! - node: Profile! -} - -type VendorConnection - @goModel( - model: "go.probo.inc/probo/pkg/server/api/console/v1/types.VendorConnection" - ) { - totalCount: Int! @goField(forceResolver: true) - edges: [VendorEdge!]! - pageInfo: PageInfo! -} - -type VendorEdge { - cursor: CursorKey! - node: Vendor! -} - -type FrameworkConnection - @goModel( - model: "go.probo.inc/probo/pkg/server/api/console/v1/types.FrameworkConnection" - ) { - totalCount: Int! @goField(forceResolver: true) - edges: [FrameworkEdge!]! - pageInfo: PageInfo! -} - -type FrameworkEdge { - cursor: CursorKey! - node: Framework! -} - -type ControlConnection - @goModel( - model: "go.probo.inc/probo/pkg/server/api/console/v1/types.ControlConnection" - ) { - totalCount: Int! @goField(forceResolver: true) - edges: [ControlEdge!]! - pageInfo: PageInfo! -} - -type ControlEdge { - cursor: CursorKey! - node: Control! -} - -type MeasureConnection - @goModel( - model: "go.probo.inc/probo/pkg/server/api/console/v1/types.MeasureConnection" - ) { - totalCount: Int! @goField(forceResolver: true) - edges: [MeasureEdge!]! - pageInfo: PageInfo! -} - -type MeasureEdge { - cursor: CursorKey! - node: Measure! -} - -type TaskConnection - @goModel( - model: "go.probo.inc/probo/pkg/server/api/console/v1/types.TaskConnection" - ) { - totalCount: Int! @goField(forceResolver: true) - edges: [TaskEdge!]! - pageInfo: PageInfo! -} - -type TaskEdge { - cursor: CursorKey! - node: Task! -} - -type EvidenceConnection - @goModel( - model: "go.probo.inc/probo/pkg/server/api/console/v1/types.EvidenceConnection" - ) { - totalCount: Int! @goField(forceResolver: true) - edges: [EvidenceEdge!]! - pageInfo: PageInfo! -} - -type EvidenceEdge { - cursor: CursorKey! - node: Evidence! -} - -type EmployeeDocumentConnection - @goModel( - model: "go.probo.inc/probo/pkg/server/api/console/v1/types.EmployeeDocumentConnection" - ) { - edges: [EmployeeDocumentEdge!]! - pageInfo: PageInfo! -} - -type EmployeeDocumentEdge - @goModel( - model: "go.probo.inc/probo/pkg/server/api/console/v1/types.EmployeeDocumentEdge" - ) { - cursor: CursorKey! - node: EmployeeDocument! -} - -type EmployeeDocumentVersionConnection - @goModel( - model: "go.probo.inc/probo/pkg/server/api/console/v1/types.EmployeeDocumentVersionConnection" - ) { - edges: [EmployeeDocumentVersionEdge!]! - pageInfo: PageInfo! -} - -type EmployeeDocumentVersionEdge - @goModel( - model: "go.probo.inc/probo/pkg/server/api/console/v1/types.EmployeeDocumentVersionEdge" - ) { - cursor: CursorKey! - node: EmployeeDocumentVersion! -} - -type DocumentConnection - @goModel( - model: "go.probo.inc/probo/pkg/server/api/console/v1/types.DocumentConnection" - ) { - totalCount: Int! @goField(forceResolver: true) - edges: [DocumentEdge!]! - pageInfo: PageInfo! -} - -type DocumentEdge { - cursor: CursorKey! - node: Document! -} - -type MeetingConnection - @goModel( - model: "go.probo.inc/probo/pkg/server/api/console/v1/types.MeetingConnection" - ) { - totalCount: Int! @goField(forceResolver: true) - edges: [MeetingEdge!]! - pageInfo: PageInfo! -} - -type MeetingEdge { - cursor: CursorKey! - node: Meeting! -} - -type StatementOfApplicabilityConnection - @goModel( - model: "go.probo.inc/probo/pkg/server/api/console/v1/types.StatementOfApplicabilityConnection" - ) { - totalCount: Int! @goField(forceResolver: true) - edges: [StatementOfApplicabilityEdge!]! - pageInfo: PageInfo! -} - -type StatementOfApplicabilityEdge { - cursor: CursorKey! - node: StatementOfApplicability! -} - - -type RiskConnection - @goModel( - model: "go.probo.inc/probo/pkg/server/api/console/v1/types.RiskConnection" - ) { - totalCount: Int! @goField(forceResolver: true) - edges: [RiskEdge!]! - pageInfo: PageInfo! -} - -type RiskEdge { - cursor: CursorKey! - node: Risk! -} - -type VendorComplianceReportConnection { - edges: [VendorComplianceReportEdge!]! - pageInfo: PageInfo! -} - -type VendorComplianceReportEdge { - cursor: CursorKey! - node: VendorComplianceReport! -} - -type VendorContactConnection { - edges: [VendorContactEdge!]! - pageInfo: PageInfo! -} - -type VendorContactEdge { - cursor: CursorKey! - node: VendorContact! -} - -type VendorServiceConnection { - edges: [VendorServiceEdge!]! - pageInfo: PageInfo! -} - -type VendorServiceEdge { - cursor: CursorKey! - node: VendorService! -} - -type VendorRiskAssessmentConnection { - edges: [VendorRiskAssessmentEdge!]! - pageInfo: PageInfo! -} - -type VendorRiskAssessmentEdge { - cursor: CursorKey! - node: VendorRiskAssessment! -} - -type DocumentVersionConnection - @goModel( - model: "go.probo.inc/probo/pkg/server/api/console/v1/types.DocumentVersionConnection" - ) { - edges: [DocumentVersionEdge!]! - pageInfo: PageInfo! - totalCount: Int! @goField(forceResolver: true) -} - -type DocumentVersionEdge { - cursor: CursorKey! - node: DocumentVersion! -} - -type DatumConnection - @goModel( - model: "go.probo.inc/probo/pkg/server/api/console/v1/types.DatumConnection" - ) { - totalCount: Int! @goField(forceResolver: true) - edges: [DatumEdge!]! - pageInfo: PageInfo! -} - -type DatumEdge { - cursor: CursorKey! - node: Datum! -} - -type AuditConnection - @goModel( - model: "go.probo.inc/probo/pkg/server/api/console/v1/types.AuditConnection" - ) { - totalCount: Int! @goField(forceResolver: true) - edges: [AuditEdge!]! - pageInfo: PageInfo! -} - -type AuditEdge { - cursor: CursorKey! - node: Audit! -} - -type FindingConnection - @goModel( - model: "go.probo.inc/probo/pkg/server/api/console/v1/types.FindingConnection" - ) { - totalCount: Int! @goField(forceResolver: true) - edges: [FindingEdge!]! - pageInfo: PageInfo! -} - -type FindingEdge - @goModel( - model: "go.probo.inc/probo/pkg/server/api/console/v1/types.FindingEdge" - ) { - cursor: CursorKey! - node: Finding! -} -type ObligationConnection - @goModel( - model: "go.probo.inc/probo/pkg/server/api/console/v1/types.ObligationConnection" - ) { - totalCount: Int! @goField(forceResolver: true) - edges: [ObligationEdge!]! - pageInfo: PageInfo! -} - -type ObligationEdge { - cursor: CursorKey! - node: Obligation! -} - - -type RightsRequestConnection - @goModel( - model: "go.probo.inc/probo/pkg/server/api/console/v1/types.RightsRequestConnection" - ) { - totalCount: Int! @goField(forceResolver: true) - edges: [RightsRequestEdge!]! - pageInfo: PageInfo! -} - -type RightsRequestEdge { - cursor: CursorKey! - node: RightsRequest! -} - -type ProcessingActivityConnection - @goModel( - model: "go.probo.inc/probo/pkg/server/api/console/v1/types.ProcessingActivityConnection" - ) { - totalCount: Int! @goField(forceResolver: true) - edges: [ProcessingActivityEdge!]! - pageInfo: PageInfo! -} - -type ProcessingActivityEdge { - cursor: CursorKey! - node: ProcessingActivity! -} - -type DataProtectionImpactAssessmentConnection - @goModel( - model: "go.probo.inc/probo/pkg/server/api/console/v1/types.DataProtectionImpactAssessmentConnection" - ) { - totalCount: Int! @goField(forceResolver: true) - edges: [DataProtectionImpactAssessmentEdge!]! - pageInfo: PageInfo! -} - -type DataProtectionImpactAssessmentEdge { - cursor: CursorKey! - node: DataProtectionImpactAssessment! -} - -type TransferImpactAssessmentConnection - @goModel( - model: "go.probo.inc/probo/pkg/server/api/console/v1/types.TransferImpactAssessmentConnection" - ) { - totalCount: Int! @goField(forceResolver: true) - edges: [TransferImpactAssessmentEdge!]! - pageInfo: PageInfo! -} - -type TransferImpactAssessmentEdge { - cursor: CursorKey! - node: TransferImpactAssessment! -} - -type SnapshotConnection - @goModel( - model: "go.probo.inc/probo/pkg/server/api/console/v1/types.SnapshotConnection" - ) { - totalCount: Int! @goField(forceResolver: true) - edges: [SnapshotEdge!]! - pageInfo: PageInfo! -} - -type SnapshotEdge { - cursor: CursorKey! - node: Snapshot! -} - -type File { - id: ID! - mimeType: String! - fileName: String! - size: BigInt! - downloadUrl: String! @goField(forceResolver: true) - createdAt: Datetime! - updatedAt: Datetime! -} - -# Root Types -type Query { - node(id: ID!): Node! - viewer: Viewer! -} - -type Mutation { - updateOrganizationContext( - input: UpdateOrganizationContextInput! - ): UpdateOrganizationContextPayload! - updateTrustCenter(input: UpdateTrustCenterInput!): UpdateTrustCenterPayload! - uploadTrustCenterNDA( - input: UploadTrustCenterNDAInput! - ): UploadTrustCenterNDAPayload! - deleteTrustCenterNDA( - input: DeleteTrustCenterNDAInput! - ): DeleteTrustCenterNDAPayload! - updateTrustCenterBrand( - input: UpdateTrustCenterBrandInput! - ): UpdateTrustCenterBrandPayload! - # Trust Center Access CRUD mutations - updateTrustCenterAccess( - input: UpdateTrustCenterAccessInput! - ): UpdateTrustCenterAccessPayload! - deleteTrustCenterAccess( - input: DeleteTrustCenterAccessInput! - ): DeleteTrustCenterAccessPayload! - # Compliance News mutations - createMailingListUpdate( - input: CreateMailingListUpdateInput! - ): CreateMailingListUpdatePayload! - updateMailingListUpdate( - input: UpdateMailingListUpdateInput! - ): UpdateMailingListUpdatePayload! - sendMailingListUpdate( - input: SendMailingListUpdateInput! - ): SendMailingListUpdatePayload! - deleteMailingListUpdate( - input: DeleteMailingListUpdateInput! - ): DeleteMailingListUpdatePayload! - # Mailing List mutations - updateMailingList( - input: UpdateMailingListInput! - ): UpdateMailingListPayload! - # Mailing List Subscriber mutations - createMailingListSubscriber( - input: CreateMailingListSubscriberInput! - ): CreateMailingListSubscriberPayload! - deleteMailingListSubscriber( - input: DeleteMailingListSubscriberInput! - ): DeleteMailingListSubscriberPayload! - # Trust Center Reference mutations - createTrustCenterReference( - input: CreateTrustCenterReferenceInput! - ): CreateTrustCenterReferencePayload! - updateTrustCenterReference( - input: UpdateTrustCenterReferenceInput! - ): UpdateTrustCenterReferencePayload! - deleteTrustCenterReference( - input: DeleteTrustCenterReferenceInput! - ): DeleteTrustCenterReferencePayload! - # Compliance Framework mutations - createComplianceFramework( - input: CreateComplianceFrameworkInput! - ): CreateComplianceFrameworkPayload! - updateComplianceFramework( - input: UpdateComplianceFrameworkInput! - ): UpdateComplianceFrameworkPayload! - deleteComplianceFramework( - input: DeleteComplianceFrameworkInput! - ): DeleteComplianceFrameworkPayload! - createComplianceExternalURL( - input: CreateComplianceExternalURLInput! - ): CreateComplianceExternalURLPayload! - updateComplianceExternalURL( - input: UpdateComplianceExternalURLInput! - ): UpdateComplianceExternalURLPayload! - deleteComplianceExternalURL( - input: DeleteComplianceExternalURLInput! - ): DeleteComplianceExternalURLPayload! - # Trust Center File mutations - createTrustCenterFile( - input: CreateTrustCenterFileInput! - ): CreateTrustCenterFilePayload! - updateTrustCenterFile( - input: UpdateTrustCenterFileInput! - ): UpdateTrustCenterFilePayload! - getTrustCenterFile( - input: GetTrustCenterFileInput! - ): GetTrustCenterFilePayload! - deleteTrustCenterFile( - input: DeleteTrustCenterFileInput! - ): DeleteTrustCenterFilePayload! - - # Vendor mutations - createVendor(input: CreateVendorInput!): CreateVendorPayload! - updateVendor(input: UpdateVendorInput!): UpdateVendorPayload! - deleteVendor(input: DeleteVendorInput!): DeleteVendorPayload! - # Vendor Contact mutations - createVendorContact( - input: CreateVendorContactInput! - ): CreateVendorContactPayload! - updateVendorContact( - input: UpdateVendorContactInput! - ): UpdateVendorContactPayload! - deleteVendorContact( - input: DeleteVendorContactInput! - ): DeleteVendorContactPayload! - # Vendor Service mutations - createVendorService( - input: CreateVendorServiceInput! - ): CreateVendorServicePayload! - updateVendorService( - input: UpdateVendorServiceInput! - ): UpdateVendorServicePayload! - deleteVendorService( - input: DeleteVendorServiceInput! - ): DeleteVendorServicePayload! - # Framework mutations - createFramework(input: CreateFrameworkInput!): CreateFrameworkPayload! - updateFramework(input: UpdateFrameworkInput!): UpdateFrameworkPayload! - importFramework(input: ImportFrameworkInput!): ImportFrameworkPayload! - deleteFramework(input: DeleteFrameworkInput!): DeleteFrameworkPayload! - exportFramework(input: ExportFrameworkInput!): ExportFrameworkPayload! - # Control mutations - createControl(input: CreateControlInput!): CreateControlPayload! - updateControl(input: UpdateControlInput!): UpdateControlPayload! - deleteControl(input: DeleteControlInput!): DeleteControlPayload! - # Measure mutations - createMeasure(input: CreateMeasureInput!): CreateMeasurePayload! - updateMeasure(input: UpdateMeasureInput!): UpdateMeasurePayload! - importMeasure(input: ImportMeasureInput!): ImportMeasurePayload! - deleteMeasure(input: DeleteMeasureInput!): DeleteMeasurePayload! - # Control mutations - createControlMeasureMapping( - input: CreateControlMeasureMappingInput! - ): CreateControlMeasureMappingPayload! - createControlDocumentMapping( - input: CreateControlDocumentMappingInput! - ): CreateControlDocumentMappingPayload! - deleteControlMeasureMapping( - input: DeleteControlMeasureMappingInput! - ): DeleteControlMeasureMappingPayload! - deleteControlDocumentMapping( - input: DeleteControlDocumentMappingInput! - ): DeleteControlDocumentMappingPayload! - createApplicabilityStatement( - input: CreateApplicabilityStatementInput! - ): CreateApplicabilityStatementPayload! - updateApplicabilityStatement( - input: UpdateApplicabilityStatementInput! - ): UpdateApplicabilityStatementPayload! - deleteApplicabilityStatement( - input: DeleteApplicabilityStatementInput! - ): DeleteApplicabilityStatementPayload! - createControlAuditMapping( - input: CreateControlAuditMappingInput! - ): CreateControlAuditMappingPayload - deleteControlAuditMapping( - input: DeleteControlAuditMappingInput! - ): DeleteControlAuditMappingPayload - createControlObligationMapping( - input: CreateControlObligationMappingInput! - ): CreateControlObligationMappingPayload! - deleteControlObligationMapping( - input: DeleteControlObligationMappingInput! - ): DeleteControlObligationMappingPayload! - createControlSnapshotMapping( - input: CreateControlSnapshotMappingInput! - ): CreateControlSnapshotMappingPayload! - deleteControlSnapshotMapping( - input: DeleteControlSnapshotMappingInput! - ): DeleteControlSnapshotMappingPayload! - # Task mutations - createTask(input: CreateTaskInput!): CreateTaskPayload! - updateTask(input: UpdateTaskInput!): UpdateTaskPayload! - deleteTask(input: DeleteTaskInput!): DeleteTaskPayload! - # Risk mutations - createRisk(input: CreateRiskInput!): CreateRiskPayload! - updateRisk(input: UpdateRiskInput!): UpdateRiskPayload! - deleteRisk(input: DeleteRiskInput!): DeleteRiskPayload! - createRiskMeasureMapping( - input: CreateRiskMeasureMappingInput! - ): CreateRiskMeasureMappingPayload! - deleteRiskMeasureMapping( - input: DeleteRiskMeasureMappingInput! - ): DeleteRiskMeasureMappingPayload! - createRiskDocumentMapping( - input: CreateRiskDocumentMappingInput! - ): CreateRiskDocumentMappingPayload! - deleteRiskDocumentMapping( - input: DeleteRiskDocumentMappingInput! - ): DeleteRiskDocumentMappingPayload! - createMeasureDocumentMapping( - input: CreateMeasureDocumentMappingInput! - ): CreateMeasureDocumentMappingPayload! - deleteMeasureDocumentMapping( - input: DeleteMeasureDocumentMappingInput! - ): DeleteMeasureDocumentMappingPayload! - createRiskObligationMapping( - input: CreateRiskObligationMappingInput! - ): CreateRiskObligationMappingPayload! - deleteRiskObligationMapping( - input: DeleteRiskObligationMappingInput! - ): DeleteRiskObligationMappingPayload! - # Evidence mutations - deleteEvidence(input: DeleteEvidenceInput!): DeleteEvidencePayload! - uploadMeasureEvidence( - input: UploadMeasureEvidenceInput! - ): UploadMeasureEvidencePayload! - # Vendor Compliance Report mutations - uploadVendorComplianceReport( - input: UploadVendorComplianceReportInput! - ): UploadVendorComplianceReportPayload! - deleteVendorComplianceReport( - input: DeleteVendorComplianceReportInput! - ): DeleteVendorComplianceReportPayload! - # Vendor Business Associate Agreement mutations - uploadVendorBusinessAssociateAgreement( - input: UploadVendorBusinessAssociateAgreementInput! - ): UploadVendorBusinessAssociateAgreementPayload! - updateVendorBusinessAssociateAgreement( - input: UpdateVendorBusinessAssociateAgreementInput! - ): UpdateVendorBusinessAssociateAgreementPayload! - deleteVendorBusinessAssociateAgreement( - input: DeleteVendorBusinessAssociateAgreementInput! - ): DeleteVendorBusinessAssociateAgreementPayload! - # Vendor Data Privacy Agreement mutations - uploadVendorDataPrivacyAgreement( - input: UploadVendorDataPrivacyAgreementInput! - ): UploadVendorDataPrivacyAgreementPayload! - updateVendorDataPrivacyAgreement( - input: UpdateVendorDataPrivacyAgreementInput! - ): UpdateVendorDataPrivacyAgreementPayload! - deleteVendorDataPrivacyAgreement( - input: DeleteVendorDataPrivacyAgreementInput! - ): DeleteVendorDataPrivacyAgreementPayload! - # Document mutations - createDocument(input: CreateDocumentInput!): CreateDocumentPayload! - updateDocument(input: UpdateDocumentInput!): UpdateDocumentPayload! - deleteDocumentDraft(input: DeleteDocumentDraftInput!): DeleteDocumentDraftPayload! - archiveDocument(input: ArchiveDocumentInput!): ArchiveDocumentPayload! - unarchiveDocument(input: UnarchiveDocumentInput!): UnarchiveDocumentPayload! - deleteDocument(input: DeleteDocumentInput!): DeleteDocumentPayload! - # Meeting mutations - createMeeting(input: CreateMeetingInput!): CreateMeetingPayload! - updateMeeting(input: UpdateMeetingInput!): UpdateMeetingPayload! - deleteMeeting(input: DeleteMeetingInput!): DeleteMeetingPayload! - # WebhookSubscription mutations - createWebhookSubscription( - input: CreateWebhookSubscriptionInput! - ): CreateWebhookSubscriptionPayload! - updateWebhookSubscription( - input: UpdateWebhookSubscriptionInput! - ): UpdateWebhookSubscriptionPayload! - deleteWebhookSubscription( - input: DeleteWebhookSubscriptionInput! - ): DeleteWebhookSubscriptionPayload! - # StatementOfApplicability mutations - createStatementOfApplicability( - input: CreateStatementOfApplicabilityInput! - ): CreateStatementOfApplicabilityPayload! - updateStatementOfApplicability( - input: UpdateStatementOfApplicabilityInput! - ): UpdateStatementOfApplicabilityPayload! - deleteStatementOfApplicability( - input: DeleteStatementOfApplicabilityInput! - ): DeleteStatementOfApplicabilityPayload! - exportStatementOfApplicabilityPDF( - input: ExportStatementOfApplicabilityPDFInput! - ): ExportStatementOfApplicabilityPDFPayload! - publishMajorDocumentVersion( - input: PublishMajorDocumentVersionInput! - ): PublishDocumentVersionPayload! - publishMinorDocumentVersion( - input: PublishMinorDocumentVersionInput! - ): PublishDocumentVersionPayload! - bulkPublishMajorDocumentVersions( - input: BulkPublishDocumentVersionsInput! - ): BulkPublishDocumentVersionsPayload! - bulkPublishMinorDocumentVersions( - input: BulkPublishDocumentVersionsInput! - ): BulkPublishDocumentVersionsPayload! - requestDocumentVersionApproval( - input: RequestDocumentVersionApprovalInput! - ): RequestDocumentVersionApprovalPayload! - voidDocumentVersionApproval( - input: VoidDocumentVersionApprovalInput! - ): VoidDocumentVersionApprovalPayload! - bulkDeleteDocuments( - input: BulkDeleteDocumentsInput! - ): BulkDeleteDocumentsPayload! - bulkArchiveDocuments( - input: BulkArchiveDocumentsInput! - ): BulkArchiveDocumentsPayload! - bulkUnarchiveDocuments( - input: BulkUnarchiveDocumentsInput! - ): BulkUnarchiveDocumentsPayload! - bulkExportDocuments( - input: BulkExportDocumentsInput! - ): BulkExportDocumentsPayload! - generateDocumentChangelog( - input: GenerateDocumentChangelogInput! - ): GenerateDocumentChangelogPayload! - requestSignature(input: RequestSignatureInput!): RequestSignaturePayload! - bulkRequestSignatures( - input: BulkRequestSignaturesInput! - ): BulkRequestSignaturesPayload! - sendSigningNotifications( - input: SendSigningNotificationsInput! - ): SendSigningNotificationsPayload! - cancelSignatureRequest( - input: CancelSignatureRequestInput! - ): CancelSignatureRequestPayload! - signDocument(input: SignDocumentInput!): SignDocumentPayload! - approveDocumentVersion( - input: ApproveDocumentVersionInput! - ): ApproveDocumentVersionPayload! - rejectDocumentVersion( - input: RejectDocumentVersionInput! - ): RejectDocumentVersionPayload! - - exportDocumentVersionPDF( - input: ExportDocumentVersionPDFInput! - ): ExportDocumentVersionPDFPayload! - exportEmployeeDocumentVersionPDF( - input: ExportEmployeeDocumentVersionPDFInput! - ): ExportEmployeeDocumentVersionPDFPayload! - exportProcessingActivitiesPDF( - input: ExportProcessingActivitiesPDFInput! - ): ExportProcessingActivitiesPDFPayload! - exportDataProtectionImpactAssessmentsPDF( - input: ExportDataProtectionImpactAssessmentsPDFInput! - ): ExportDataProtectionImpactAssessmentsPDFPayload! - exportTransferImpactAssessmentsPDF( - input: ExportTransferImpactAssessmentsPDFInput! - ): ExportTransferImpactAssessmentsPDFPayload! - createVendorRiskAssessment( - input: CreateVendorRiskAssessmentInput! - ): CreateVendorRiskAssessmentPayload! - assessVendor(input: AssessVendorInput!): AssessVendorPayload! - createAsset(input: CreateAssetInput!): CreateAssetPayload! - updateAsset(input: UpdateAssetInput!): UpdateAssetPayload! - deleteAsset(input: DeleteAssetInput!): DeleteAssetPayload! - createDatum(input: CreateDatumInput!): CreateDatumPayload! - updateDatum(input: UpdateDatumInput!): UpdateDatumPayload! - deleteDatum(input: DeleteDatumInput!): DeleteDatumPayload! - createAudit(input: CreateAuditInput!): CreateAuditPayload - updateAudit(input: UpdateAuditInput!): UpdateAuditPayload - deleteAudit(input: DeleteAuditInput!): DeleteAuditPayload - uploadAuditReport(input: UploadAuditReportInput!): UploadAuditReportPayload - deleteAuditReport(input: DeleteAuditReportInput!): DeleteAuditReportPayload - # Finding mutations - createFinding(input: CreateFindingInput!): CreateFindingPayload - updateFinding(input: UpdateFindingInput!): UpdateFindingPayload - deleteFinding(input: DeleteFindingInput!): DeleteFindingPayload - createFindingAuditMapping( - input: CreateFindingAuditMappingInput! - ): CreateFindingAuditMappingPayload - deleteFindingAuditMapping( - input: DeleteFindingAuditMappingInput! - ): DeleteFindingAuditMappingPayload - # Obligation mutations - createObligation(input: CreateObligationInput!): CreateObligationPayload! - updateObligation(input: UpdateObligationInput!): UpdateObligationPayload! - deleteObligation(input: DeleteObligationInput!): DeleteObligationPayload! - # Rights Request mutations - createRightsRequest( - input: CreateRightsRequestInput! - ): CreateRightsRequestPayload! - - updateRightsRequest( - input: UpdateRightsRequestInput! - ): UpdateRightsRequestPayload! - - deleteRightsRequest( - input: DeleteRightsRequestInput! - ): DeleteRightsRequestPayload! - - # Processing Activity mutations - createProcessingActivity( - input: CreateProcessingActivityInput! - ): CreateProcessingActivityPayload! - updateProcessingActivity( - input: UpdateProcessingActivityInput! - ): UpdateProcessingActivityPayload! - deleteProcessingActivity( - input: DeleteProcessingActivityInput! - ): DeleteProcessingActivityPayload! - # Data Protection Impact Assessment mutations - createDataProtectionImpactAssessment( - input: CreateDataProtectionImpactAssessmentInput! - ): CreateDataProtectionImpactAssessmentPayload! - updateDataProtectionImpactAssessment( - input: UpdateDataProtectionImpactAssessmentInput! - ): UpdateDataProtectionImpactAssessmentPayload! - deleteDataProtectionImpactAssessment( - input: DeleteDataProtectionImpactAssessmentInput! - ): DeleteDataProtectionImpactAssessmentPayload! - # Transfer Impact Assessment mutations - createTransferImpactAssessment( - input: CreateTransferImpactAssessmentInput! - ): CreateTransferImpactAssessmentPayload! - updateTransferImpactAssessment( - input: UpdateTransferImpactAssessmentInput! - ): UpdateTransferImpactAssessmentPayload! - deleteTransferImpactAssessment( - input: DeleteTransferImpactAssessmentInput! - ): DeleteTransferImpactAssessmentPayload! - # Snapshot mutations - createSnapshot(input: CreateSnapshotInput!): CreateSnapshotPayload! - deleteSnapshot(input: DeleteSnapshotInput!): DeleteSnapshotPayload! - # Custom Domain mutations - createCustomDomain( - input: CreateCustomDomainInput! - ): CreateCustomDomainPayload! - deleteCustomDomain( - input: DeleteCustomDomainInput! - ): DeleteCustomDomainPayload! - # Access Source mutations - createAccessSource( - input: CreateAccessSourceInput! - ): CreateAccessSourcePayload! - updateAccessSource( - input: UpdateAccessSourceInput! - ): UpdateAccessSourcePayload! - deleteAccessSource( - input: DeleteAccessSourceInput! - ): DeleteAccessSourcePayload! - # Access Review Campaign mutations - createAccessReviewCampaign( - input: CreateAccessReviewCampaignInput! - ): CreateAccessReviewCampaignPayload! - updateAccessReviewCampaign( - input: UpdateAccessReviewCampaignInput! - ): UpdateAccessReviewCampaignPayload! - deleteAccessReviewCampaign( - input: DeleteAccessReviewCampaignInput! - ): DeleteAccessReviewCampaignPayload! - startAccessReviewCampaign( - input: StartAccessReviewCampaignInput! - ): StartAccessReviewCampaignPayload! - closeAccessReviewCampaign( - input: CloseAccessReviewCampaignInput! - ): CloseAccessReviewCampaignPayload! - cancelAccessReviewCampaign( - input: CancelAccessReviewCampaignInput! - ): CancelAccessReviewCampaignPayload! - addAccessReviewCampaignScopeSource( - input: AddAccessReviewCampaignScopeSourceInput! - ): AddAccessReviewCampaignScopeSourcePayload! - removeAccessReviewCampaignScopeSource( - input: RemoveAccessReviewCampaignScopeSourceInput! - ): RemoveAccessReviewCampaignScopeSourcePayload! - # Access Entry mutations - recordAccessEntryDecision( - input: RecordAccessEntryDecisionInput! - ): RecordAccessEntryDecisionPayload! - recordAccessEntryDecisions( - input: RecordAccessEntryDecisionsInput! - ): RecordAccessEntryDecisionsPayload! - flagAccessEntry( - input: FlagAccessEntryInput! - ): FlagAccessEntryPayload! - # Connector mutations - createAPIKeyConnector( - input: CreateAPIKeyConnectorInput! - ): CreateAPIKeyConnectorPayload! - createClientCredentialsConnector( - input: CreateClientCredentialsConnectorInput! - ): CreateClientCredentialsConnectorPayload! - deleteConnector(input: DeleteConnectorInput!): DeleteConnectorPayload! - configureAccessSource( - input: ConfigureAccessSourceInput! - ): ConfigureAccessSourcePayload! - # Slack Connection mutations - deleteSlackConnection( - input: DeleteSlackConnectionInput! - ): DeleteSlackConnectionPayload! -} - -# Input Types -input UpdateOrganizationContextInput { - organizationId: ID! - product: String @goField(omittable: true) - architecture: String @goField(omittable: true) - team: String @goField(omittable: true) - processes: String @goField(omittable: true) - customers: String @goField(omittable: true) -} - -input UpdateTrustCenterInput { - trustCenterId: ID! - active: Boolean - searchEngineIndexing: SearchEngineIndexing -} - -input UploadTrustCenterNDAInput { - trustCenterId: ID! - fileName: String! - file: Upload! -} - -input DeleteTrustCenterNDAInput { - trustCenterId: ID! -} - -input UpdateTrustCenterBrandInput { - trustCenterId: ID! - logoFile: Upload @goField(omittable: true) - darkLogoFile: Upload @goField(omittable: true) -} - -input TrustCenterDocumentAccessInput { - id: ID! - status: TrustCenterDocumentAccessStatus! -} - -input UpdateTrustCenterAccessInput { - id: ID! - name: String - state: TrustCenterAccessState - documents: [TrustCenterDocumentAccessInput!] - reports: [TrustCenterDocumentAccessInput!] - trustCenterFiles: [TrustCenterDocumentAccessInput!] -} - -input DeleteTrustCenterAccessInput { - id: ID! -} - -input UpdateMailingListInput { - id: ID! - replyTo: EmailAddr -} - -type UpdateMailingListPayload { - mailingList: MailingList! -} - -input CreateMailingListUpdateInput { - mailingListId: ID! - title: String! - body: String! -} - -input UpdateMailingListUpdateInput { - id: ID! - title: String - body: String -} - -input SendMailingListUpdateInput { - id: ID! -} - -input DeleteMailingListUpdateInput { - id: ID! -} - -input CreateMailingListSubscriberInput { - mailingListId: ID! - fullName: String! - email: EmailAddr! - confirmed: Boolean -} - -input DeleteMailingListSubscriberInput { - id: ID! -} - -input CreateTrustCenterReferenceInput { - trustCenterId: ID! - name: String! - description: String - websiteUrl: String! - logoFile: Upload! -} - -input UpdateTrustCenterReferenceInput { - id: ID! - name: String - description: String @goField(omittable: true) - websiteUrl: String - logoFile: Upload - rank: Int -} - -input DeleteTrustCenterReferenceInput { - id: ID! -} - -input CreateComplianceFrameworkInput { - trustCenterId: ID! - frameworkId: ID! -} - -input UpdateComplianceFrameworkInput { - id: ID! - rank: Int! -} - -input DeleteComplianceFrameworkInput { - id: ID! -} - -input CreateComplianceExternalURLInput { - trustCenterId: ID! - name: String! - url: String! -} - -input UpdateComplianceExternalURLInput { - id: ID! - name: String! - url: String! - rank: Int -} - -input DeleteComplianceExternalURLInput { - id: ID! -} - -input CreateTrustCenterFileInput { - organizationId: ID! - name: String! - category: String! - file: Upload! - trustCenterVisibility: TrustCenterVisibility! -} - -input UpdateTrustCenterFileInput { - id: ID! - name: String - category: String - trustCenterVisibility: TrustCenterVisibility -} - -input GetTrustCenterFileInput { - id: ID! -} - -input DeleteTrustCenterFileInput { - id: ID! -} - -input CreateVendorInput { - organizationId: ID! - name: String! - description: String - headquarterAddress: String - legalName: String - websiteUrl: String - privacyPolicyUrl: String - category: VendorCategory - serviceLevelAgreementUrl: String - dataProcessingAgreementUrl: String - businessAssociateAgreementUrl: String - subprocessorsListUrl: String - certifications: [String!] - countries: [CountryCode!] - securityPageUrl: String - trustPageUrl: String - statusPageUrl: String - termsOfServiceUrl: String - businessOwnerId: ID - securityOwnerId: ID -} - -input UpdateVendorInput { - id: ID! - name: String - description: String @goField(omittable: true) - statusPageUrl: String @goField(omittable: true) - termsOfServiceUrl: String @goField(omittable: true) - privacyPolicyUrl: String @goField(omittable: true) - serviceLevelAgreementUrl: String @goField(omittable: true) - dataProcessingAgreementUrl: String @goField(omittable: true) - businessAssociateAgreementUrl: String @goField(omittable: true) - subprocessorsListUrl: String @goField(omittable: true) - websiteUrl: String @goField(omittable: true) - legalName: String @goField(omittable: true) - headquarterAddress: String @goField(omittable: true) - category: VendorCategory - certifications: [String!] - countries: [CountryCode!] - securityPageUrl: String @goField(omittable: true) - trustPageUrl: String @goField(omittable: true) - businessOwnerId: ID @goField(omittable: true) - securityOwnerId: ID @goField(omittable: true) - showOnTrustCenter: Boolean -} - -input DeleteVendorInput { - vendorId: ID! -} - -input CreateVendorContactInput { - vendorId: ID! - fullName: String - email: EmailAddr - phone: String - role: String -} - -input UpdateVendorContactInput { - id: ID! - fullName: String @goField(omittable: true) - email: EmailAddr @goField(omittable: true) - phone: String @goField(omittable: true) - role: String @goField(omittable: true) -} - -input DeleteVendorContactInput { - vendorContactId: ID! -} - -input CreateVendorServiceInput { - vendorId: ID! - name: String! - description: String - url: String - type: String -} - -input UpdateVendorServiceInput { - id: ID! - name: String - description: String @goField(omittable: true) - url: String - type: String -} - -input DeleteVendorServiceInput { - vendorServiceId: ID! -} - -input CreateFrameworkInput { - organizationId: ID! - name: String! - description: String -} - -input UpdateFrameworkInput { - id: ID! - name: String - description: String @goField(omittable: true) -} - -input ImportFrameworkInput { - organizationId: ID! - file: Upload! -} - -input DeleteFrameworkInput { - frameworkId: ID! -} - -input ExportFrameworkInput { - frameworkId: ID! -} - -input CreateMeasureInput { - organizationId: ID! - name: String! - description: String - category: String! -} - -input UpdateMeasureInput { - id: ID! - name: String - description: String @goField(omittable: true) - category: String - state: MeasureState -} - -input ImportMeasureInput { - organizationId: ID! - file: Upload! -} - -input CreateTaskInput { - organizationId: ID! - measureId: ID - name: String! - description: String - priority: TaskPriority! - timeEstimate: Duration - assignedToId: ID - deadline: Datetime -} - -input UpdateTaskInput { - taskId: ID! - name: String - description: String @goField(omittable: true) - state: TaskState - priority: TaskPriority - rank: Int - timeEstimate: Duration @goField(omittable: true) - deadline: Datetime @goField(omittable: true) - assignedToId: ID @goField(omittable: true) - measureId: ID @goField(omittable: true) -} - -input DeleteTaskInput { - taskId: ID! -} - -input CreateControlMeasureMappingInput { - controlId: ID! - measureId: ID! -} - -input CreateControlDocumentMappingInput { - controlId: ID! - documentId: ID! -} - -input DeleteControlMeasureMappingInput { - controlId: ID! - measureId: ID! -} - -input DeleteControlDocumentMappingInput { - controlId: ID! - documentId: ID! -} - -input CreateApplicabilityStatementInput { - statementOfApplicabilityId: ID! - controlId: ID! - applicability: Boolean! - justification: String -} - -input UpdateApplicabilityStatementInput { - applicabilityStatementId: ID! - applicability: Boolean! - justification: String -} - -input DeleteApplicabilityStatementInput { - applicabilityStatementId: ID! -} - -input CreateControlAuditMappingInput { - controlId: ID! - auditId: ID! -} - -input DeleteControlAuditMappingInput { - controlId: ID! - auditId: ID! -} - -input CreateControlObligationMappingInput { - controlId: ID! - obligationId: ID! -} - -input DeleteControlObligationMappingInput { - controlId: ID! - obligationId: ID! -} - -input CreateControlSnapshotMappingInput { - controlId: ID! - snapshotId: ID! -} - -input DeleteControlSnapshotMappingInput { - controlId: ID! - snapshotId: ID! -} - -input CreateRiskInput { - organizationId: ID! - name: String! - description: String - category: String! - ownerId: ID - treatment: RiskTreatment! - inherentLikelihood: Int! - inherentImpact: Int! - residualLikelihood: Int - residualImpact: Int - note: String -} - -input UpdateRiskInput { - id: ID! - name: String - description: String @goField(omittable: true) - category: String - ownerId: ID @goField(omittable: true) - treatment: RiskTreatment - inherentLikelihood: Int - inherentImpact: Int - residualLikelihood: Int - residualImpact: Int - note: String -} - -input DeleteRiskInput { - riskId: ID! -} - -input CreateRiskMeasureMappingInput { - riskId: ID! - measureId: ID! -} - -input DeleteRiskMeasureMappingInput { - riskId: ID! - measureId: ID! -} - -input CreateRiskDocumentMappingInput { - riskId: ID! - documentId: ID! -} - -input DeleteRiskDocumentMappingInput { - riskId: ID! - documentId: ID! -} - -input CreateMeasureDocumentMappingInput { - measureId: ID! - documentId: ID! -} - -input DeleteMeasureDocumentMappingInput { - measureId: ID! - documentId: ID! -} - -input CreateRiskObligationMappingInput { - riskId: ID! - obligationId: ID! -} - -input DeleteRiskObligationMappingInput { - riskId: ID! - obligationId: ID! -} - -input DeleteEvidenceInput { - evidenceId: ID! -} - -input UploadVendorComplianceReportInput { - vendorId: ID! - reportDate: Datetime! - validUntil: Datetime - reportName: String! - file: Upload! -} - -input DeleteVendorComplianceReportInput { - reportId: ID! -} - -input UploadVendorBusinessAssociateAgreementInput { - vendorId: ID! - validFrom: Datetime - validUntil: Datetime - fileName: String! - file: Upload! -} - -input UpdateVendorBusinessAssociateAgreementInput { - vendorId: ID! - validFrom: Datetime @goField(omittable: true) - validUntil: Datetime @goField(omittable: true) -} - -input DeleteVendorBusinessAssociateAgreementInput { - vendorId: ID! -} - -input UploadVendorDataPrivacyAgreementInput { - vendorId: ID! - validFrom: Datetime - validUntil: Datetime - fileName: String! - file: Upload! -} - -input UpdateVendorDataPrivacyAgreementInput { - vendorId: ID! - validFrom: Datetime @goField(omittable: true) - validUntil: Datetime @goField(omittable: true) -} - -input DeleteVendorDataPrivacyAgreementInput { - vendorId: ID! -} - -input CreateDocumentInput { - organizationId: ID! - title: String! - content: String - documentType: DocumentType! - classification: DocumentClassification! - trustCenterVisibility: TrustCenterVisibility - defaultApproverIds: [ID!] -} - -input UpdateDocumentInput { - id: ID! - title: String - content: String - classification: DocumentClassification - documentType: DocumentType - trustCenterVisibility: TrustCenterVisibility - defaultApproverIds: [ID!] -} - -input ExportDocumentVersionPDFInput { - documentVersionId: ID! - withWatermark: Boolean! - watermarkEmail: EmailAddr - withSignatures: Boolean! -} - -input ExportEmployeeDocumentVersionPDFInput { - documentVersionId: ID! -} - -input ExportProcessingActivitiesPDFInput { - organizationId: ID! - filter: ProcessingActivityFilter -} - -input ExportDataProtectionImpactAssessmentsPDFInput { - organizationId: ID! - filter: DataProtectionImpactAssessmentFilter -} - -input ExportTransferImpactAssessmentsPDFInput { - organizationId: ID! - filter: TransferImpactAssessmentFilter -} - -input DeleteDocumentDraftInput { - documentId: ID! -} - -input ArchiveDocumentInput { - documentId: ID! -} - -input UnarchiveDocumentInput { - documentId: ID! -} - -input DeleteDocumentInput { - documentId: ID! -} - -input CreateMeetingInput { - organizationId: ID! - name: String! - date: Datetime! - attendeeIds: [ID!] - minutes: String -} - -input UpdateMeetingInput { - meetingId: ID! - name: String - date: Datetime - attendeeIds: [ID!] - minutes: String @goField(omittable: true) -} - -input DeleteMeetingInput { - meetingId: ID! -} - -input CreateStatementOfApplicabilityInput { - organizationId: ID! - name: String! - ownerId: ID! -} - -input UpdateStatementOfApplicabilityInput { - id: ID! - name: String - ownerId: ID -} - -input ApplicabilityStatementInput { - controlId: ID! - applicability: Boolean! - justification: String -} - -input DeleteStatementOfApplicabilityInput { - statementOfApplicabilityId: ID! -} - -input ExportStatementOfApplicabilityPDFInput { - statementOfApplicabilityId: ID! -} - -type ExportStatementOfApplicabilityPDFPayload { - data: String! -} - -input StatementOfApplicabilityOrder - @goModel( - model: "go.probo.inc/probo/pkg/server/api/console/v1/types.StatementOfApplicabilityOrderBy" - ) { - direction: OrderDirection! - field: StatementOfApplicabilityOrderField! -} - -enum StatementOfApplicabilityOrderField - @goModel( - model: "go.probo.inc/probo/pkg/coredata.StatementOfApplicabilityOrderField" - ) { - NAME - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.StatementOfApplicabilityOrderFieldName" - ) - CREATED_AT - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.StatementOfApplicabilityOrderFieldCreatedAt" - ) -} -input CreateControlInput { - frameworkId: ID! - sectionTitle: String! - name: String! - description: String - bestPractice: Boolean! - implemented: ControlImplementationState! - notImplementedJustification: String -} - -input UpdateControlInput { - id: ID! - sectionTitle: String - name: String - description: String @goField(omittable: true) - bestPractice: Boolean - implemented: ControlImplementationState - notImplementedJustification: String @goField(omittable: true) -} - -input DeleteControlInput { - controlId: ID! -} - -# Audit input types -input CreateAuditInput { - organizationId: ID! - frameworkId: ID! - name: String - validFrom: Datetime - validUntil: Datetime - state: AuditState - trustCenterVisibility: TrustCenterVisibility - file: Upload -} - -input UpdateAuditInput { - id: ID! - name: String @goField(omittable: true) - validFrom: Datetime - validUntil: Datetime - state: AuditState - trustCenterVisibility: TrustCenterVisibility -} - -input DeleteAuditInput { - auditId: ID! -} - -input UploadAuditReportInput { - auditId: ID! - file: Upload! -} - -input DeleteAuditReportInput { - auditId: ID! -} - -# Finding input types -input CreateFindingInput { - organizationId: ID! - kind: FindingKind! - description: String - source: String - identifiedOn: Datetime - rootCause: String - correctiveAction: String - ownerId: ID - dueDate: Datetime - status: FindingStatus! - priority: FindingPriority! - riskId: ID - effectivenessCheck: String -} - -input UpdateFindingInput { - id: ID! - description: String @goField(omittable: true) - source: String @goField(omittable: true) - identifiedOn: Datetime @goField(omittable: true) - rootCause: String @goField(omittable: true) - correctiveAction: String @goField(omittable: true) - ownerId: ID - dueDate: Datetime @goField(omittable: true) - status: FindingStatus - priority: FindingPriority - riskId: ID @goField(omittable: true) - effectivenessCheck: String @goField(omittable: true) -} - -input DeleteFindingInput { - findingId: ID! -} - -input CreateFindingAuditMappingInput { - findingId: ID! - auditId: ID! - referenceId: String! -} - -input DeleteFindingAuditMappingInput { - findingId: ID! - auditId: ID! -} - -input CreateObligationInput { - organizationId: ID! - area: String - source: String - requirement: String - actionsToBeImplemented: String - regulator: String - ownerId: ID! - lastReviewDate: Datetime - dueDate: Datetime - status: ObligationStatus! - type: ObligationType! -} - -input UpdateObligationInput { - id: ID! - area: String @goField(omittable: true) - source: String @goField(omittable: true) - requirement: String @goField(omittable: true) - actionsToBeImplemented: String @goField(omittable: true) - regulator: String @goField(omittable: true) - ownerId: ID - lastReviewDate: Datetime @goField(omittable: true) - dueDate: Datetime @goField(omittable: true) - status: ObligationStatus - type: ObligationType -} - -input DeleteObligationInput { - obligationId: ID! -} - - -input CreateRightsRequestInput { - organizationId: ID! - requestType: RightsRequestType! - requestState: RightsRequestState! - dataSubject: String - contact: String - details: String - deadline: Datetime - actionTaken: String -} - -input UpdateRightsRequestInput { - id: ID! - requestType: RightsRequestType - requestState: RightsRequestState - dataSubject: String @goField(omittable: true) - contact: String @goField(omittable: true) - details: String @goField(omittable: true) - deadline: Datetime @goField(omittable: true) - actionTaken: String @goField(omittable: true) -} - -input DeleteRightsRequestInput { - rightsRequestId: ID! -} - -input CreateProcessingActivityInput { - organizationId: ID! - name: String! - purpose: String - dataSubjectCategory: String - personalDataCategory: String - specialOrCriminalData: ProcessingActivitySpecialOrCriminalDatum! - consentEvidenceLink: String - lawfulBasis: ProcessingActivityLawfulBasis! - recipients: String - location: String - internationalTransfers: Boolean! - transferSafeguards: ProcessingActivityTransferSafeguard - retentionPeriod: String - securityMeasures: String - dataProtectionImpactAssessmentNeeded: ProcessingActivityDataProtectionImpactAssessment! - transferImpactAssessmentNeeded: ProcessingActivityTransferImpactAssessment! - lastReviewDate: Datetime - nextReviewDate: Datetime - role: ProcessingActivityRole! - dataProtectionOfficerId: ID - vendorIds: [ID!] -} - -input UpdateProcessingActivityInput { - id: ID! - name: String - purpose: String @goField(omittable: true) - dataSubjectCategory: String @goField(omittable: true) - personalDataCategory: String @goField(omittable: true) - specialOrCriminalData: ProcessingActivitySpecialOrCriminalDatum - consentEvidenceLink: String - lawfulBasis: ProcessingActivityLawfulBasis - recipients: String @goField(omittable: true) - location: String @goField(omittable: true) - internationalTransfers: Boolean - transferSafeguards: ProcessingActivityTransferSafeguard - @goField(omittable: true) - retentionPeriod: String @goField(omittable: true) - securityMeasures: String @goField(omittable: true) - dataProtectionImpactAssessmentNeeded: ProcessingActivityDataProtectionImpactAssessment - transferImpactAssessmentNeeded: ProcessingActivityTransferImpactAssessment - lastReviewDate: Datetime @goField(omittable: true) - nextReviewDate: Datetime @goField(omittable: true) - role: ProcessingActivityRole - dataProtectionOfficerId: ID @goField(omittable: true) - vendorIds: [ID!] -} - -input DeleteProcessingActivityInput { - processingActivityId: ID! -} - -input CreateDataProtectionImpactAssessmentInput { - processingActivityId: ID! - description: String - necessityAndProportionality: String - potentialRisk: String - mitigations: String - residualRisk: DataProtectionImpactAssessmentResidualRisk -} - -input UpdateDataProtectionImpactAssessmentInput { - id: ID! - description: String @goField(omittable: true) - necessityAndProportionality: String @goField(omittable: true) - potentialRisk: String @goField(omittable: true) - mitigations: String @goField(omittable: true) - residualRisk: DataProtectionImpactAssessmentResidualRisk -} - -input DeleteDataProtectionImpactAssessmentInput { - dataProtectionImpactAssessmentId: ID! -} - -input CreateTransferImpactAssessmentInput { - processingActivityId: ID! - dataSubjects: String - legalMechanism: String - transfer: String - localLawRisk: String - supplementaryMeasures: String -} - -input UpdateTransferImpactAssessmentInput { - id: ID! - dataSubjects: String @goField(omittable: true) - legalMechanism: String @goField(omittable: true) - transfer: String @goField(omittable: true) - localLawRisk: String @goField(omittable: true) - supplementaryMeasures: String @goField(omittable: true) -} - -input DeleteTransferImpactAssessmentInput { - transferImpactAssessmentId: ID! -} - -input CreateSnapshotInput { - organizationId: ID! - name: String! - description: String - type: SnapshotsType! -} - -input DeleteSnapshotInput { - snapshotId: ID! -} - -# Payload Types - -type UpdateOrganizationContextPayload { - context: OrganizationContext! -} - -type OrganizationContext { - organizationId: ID! - product: String - architecture: String - team: String - processes: String - customers: String -} - -type UpdateTrustCenterPayload { - trustCenter: TrustCenter! -} - -type UploadTrustCenterNDAPayload { - trustCenter: TrustCenter! -} - -type DeleteTrustCenterNDAPayload { - trustCenter: TrustCenter! -} - -type UpdateTrustCenterBrandPayload { - trustCenter: TrustCenter! -} - -type UpdateTrustCenterAccessPayload { - trustCenterAccess: TrustCenterAccess! -} - -type DeleteTrustCenterAccessPayload { - deletedTrustCenterAccessId: ID! -} - -type CreateMailingListUpdatePayload { - mailingListUpdate: MailingListUpdate! -} - -type UpdateMailingListUpdatePayload { - mailingListUpdate: MailingListUpdate! -} - -type SendMailingListUpdatePayload { - mailingListUpdate: MailingListUpdate! -} - -type DeleteMailingListUpdatePayload { - deletedMailingListUpdateId: ID! -} - -type CreateMailingListSubscriberPayload { - mailingListSubscriberEdge: MailingListSubscriberEdge! -} - -type DeleteMailingListSubscriberPayload { - deletedMailingListSubscriberId: ID! -} - -type CreateTrustCenterReferencePayload { - trustCenterReferenceEdge: TrustCenterReferenceEdge! -} - -type UpdateTrustCenterReferencePayload { - trustCenterReference: TrustCenterReference! -} - -type DeleteTrustCenterReferencePayload { - deletedTrustCenterReferenceId: ID! -} - -type CreateComplianceFrameworkPayload { - complianceFrameworkEdge: ComplianceFrameworkEdge! -} - -type UpdateComplianceFrameworkPayload { - complianceFramework: ComplianceFramework! -} - -type DeleteComplianceFrameworkPayload { - deletedComplianceFrameworkId: ID! -} - -type CreateComplianceExternalURLPayload { - complianceExternalUrlEdge: ComplianceExternalURLEdge! -} - -type UpdateComplianceExternalURLPayload { - complianceExternalUrl: ComplianceExternalURL! -} - -type DeleteComplianceExternalURLPayload { - deletedComplianceExternalUrlId: ID! -} - -type CreateTrustCenterFilePayload { - trustCenterFileEdge: TrustCenterFileEdge! -} - -type UpdateTrustCenterFilePayload { - trustCenterFile: TrustCenterFile! -} - -type GetTrustCenterFilePayload { - trustCenterFile: TrustCenterFile! -} - -type DeleteTrustCenterFilePayload { - deletedTrustCenterFileId: ID! -} - -type CreateControlPayload { - controlEdge: ControlEdge! -} - -type UpdateControlPayload { - control: Control! -} - -type DeleteControlPayload { - deletedControlId: ID! -} - -type CreateVendorPayload { - vendorEdge: VendorEdge! -} - -type UpdateVendorPayload { - vendor: Vendor! -} - -type DeleteVendorPayload { - deletedVendorId: ID! -} - -type CreateVendorContactPayload { - vendorContactEdge: VendorContactEdge! -} - -type UpdateVendorContactPayload { - vendorContact: VendorContact! -} - -type DeleteVendorContactPayload { - deletedVendorContactId: ID! -} - -type CreateVendorServicePayload { - vendorServiceEdge: VendorServiceEdge! -} - -type UpdateVendorServicePayload { - vendorService: VendorService! -} - -type DeleteVendorServicePayload { - deletedVendorServiceId: ID! -} - -type CreateFrameworkPayload { - frameworkEdge: FrameworkEdge! -} - -type UpdateFrameworkPayload { - framework: Framework! -} - -type ImportFrameworkPayload { - frameworkEdge: FrameworkEdge! -} - -type DeleteFrameworkPayload { - deletedFrameworkId: ID! -} - -type ExportFrameworkPayload { - exportJobId: ID! -} - -type CreateMeasurePayload { - measureEdge: MeasureEdge! -} - -type UpdateMeasurePayload { - measure: Measure! -} - -type ImportMeasurePayload { - measureEdges: [MeasureEdge!]! -} - -type CreateTaskPayload { - taskEdge: TaskEdge! -} - -type UpdateTaskPayload { - task: Task! -} - -type DeleteTaskPayload { - deletedTaskId: ID! -} - -type CreateControlMeasureMappingPayload { - controlEdge: ControlEdge! - measureEdge: MeasureEdge! -} - -type CreateControlDocumentMappingPayload { - controlEdge: ControlEdge! - documentEdge: DocumentEdge! -} - -type DeleteControlMeasureMappingPayload { - deletedControlId: ID! - deletedMeasureId: ID! -} - -type DeleteControlDocumentMappingPayload { - deletedControlId: ID! - deletedDocumentId: ID! -} - -type CreateApplicabilityStatementPayload { - applicabilityStatementEdge: ApplicabilityStatementEdge! -} - -type UpdateApplicabilityStatementPayload { - applicabilityStatement: ApplicabilityStatement! -} - -type DeleteApplicabilityStatementPayload { - deletedApplicabilityStatementId: ID! -} - -type CreateControlAuditMappingPayload { - controlEdge: ControlEdge - auditEdge: AuditEdge -} - -type DeleteControlAuditMappingPayload { - deletedControlId: ID - deletedAuditId: ID -} - -type CreateControlObligationMappingPayload { - controlEdge: ControlEdge! - obligationEdge: ObligationEdge! -} - -type DeleteControlObligationMappingPayload { - deletedControlId: ID! - deletedObligationId: ID! -} - -type CreateControlSnapshotMappingPayload { - controlEdge: ControlEdge! - snapshotEdge: SnapshotEdge! -} - -type DeleteControlSnapshotMappingPayload { - deletedControlId: ID! - deletedSnapshotId: ID! -} - -type CreateRiskPayload { - riskEdge: RiskEdge! -} - -type UpdateRiskPayload { - risk: Risk! -} - -type DeleteRiskPayload { - deletedRiskId: ID! -} - -type CreateRiskMeasureMappingPayload { - riskEdge: RiskEdge! - measureEdge: MeasureEdge! -} - -type DeleteRiskMeasureMappingPayload { - deletedMeasureId: ID! - deletedRiskId: ID! -} - -type CreateRiskDocumentMappingPayload { - riskEdge: RiskEdge! - documentEdge: DocumentEdge! -} - -type DeleteRiskDocumentMappingPayload { - deletedRiskId: ID! - deletedDocumentId: ID! -} - -type CreateMeasureDocumentMappingPayload { - measureEdge: MeasureEdge! - documentEdge: DocumentEdge! -} - -type DeleteMeasureDocumentMappingPayload { - deletedMeasureId: ID! - deletedDocumentId: ID! -} - -type CreateRiskObligationMappingPayload { - riskEdge: RiskEdge! - obligationEdge: ObligationEdge! -} - -type DeleteRiskObligationMappingPayload { - deletedRiskId: ID! - deletedObligationId: ID! -} - -type DeleteEvidencePayload { - deletedEvidenceId: ID! -} - -type UploadVendorComplianceReportPayload { - vendorComplianceReportEdge: VendorComplianceReportEdge! -} - -type DeleteVendorComplianceReportPayload { - deletedVendorComplianceReportId: ID! -} - -type UploadVendorBusinessAssociateAgreementPayload { - vendorBusinessAssociateAgreement: VendorBusinessAssociateAgreement! -} - -type UpdateVendorBusinessAssociateAgreementPayload { - vendorBusinessAssociateAgreement: VendorBusinessAssociateAgreement! -} - -type DeleteVendorBusinessAssociateAgreementPayload { - deletedVendorId: ID! -} - -type UploadVendorDataPrivacyAgreementPayload { - vendorDataPrivacyAgreement: VendorDataPrivacyAgreement! -} - -type UpdateVendorDataPrivacyAgreementPayload { - vendorDataPrivacyAgreement: VendorDataPrivacyAgreement! -} - -type DeleteVendorDataPrivacyAgreementPayload { - deletedVendorId: ID! -} - -type CreateDocumentPayload { - documentEdge: DocumentEdge! - documentVersionEdge: DocumentVersionEdge! -} - -type ExportDocumentVersionPDFPayload { - data: String! -} - -type ExportEmployeeDocumentVersionPDFPayload { - data: String! -} - -type ExportProcessingActivitiesPDFPayload { - data: String! -} - -type ExportDataProtectionImpactAssessmentsPDFPayload { - data: String! -} - -type ExportTransferImpactAssessmentsPDFPayload { - data: String! -} - -type UpdateDocumentPayload { - document: Document! - documentVersion: DocumentVersion - documentVersionEdge: DocumentVersionEdge -} - -type DeleteDocumentDraftPayload { - document: Document! -} - -type ArchiveDocumentPayload { - document: Document! -} - -type UnarchiveDocumentPayload { - document: Document! -} - -type DeleteDocumentPayload { - deletedDocumentId: ID! -} - -type CreateMeetingPayload { - meetingEdge: MeetingEdge! -} - -type UpdateMeetingPayload { - meeting: Meeting! -} - -type DeleteMeetingPayload { - deletedMeetingId: ID! -} - -input CreateWebhookSubscriptionInput { - organizationId: ID! - endpointUrl: String! - selectedEvents: [WebhookEventType!]! -} - -input UpdateWebhookSubscriptionInput { - id: ID! - endpointUrl: String - selectedEvents: [WebhookEventType!] -} - -input DeleteWebhookSubscriptionInput { - webhookSubscriptionId: ID! -} - -type CreateWebhookSubscriptionPayload { - webhookSubscriptionEdge: WebhookSubscriptionEdge! -} - -type UpdateWebhookSubscriptionPayload { - webhookSubscription: WebhookSubscription! -} - -type DeleteWebhookSubscriptionPayload { - deletedWebhookSubscriptionId: ID! -} - -type CreateStatementOfApplicabilityPayload { - statementOfApplicabilityEdge: StatementOfApplicabilityEdge! -} - -type UpdateStatementOfApplicabilityPayload { - statementOfApplicability: StatementOfApplicability! -} - -type DeleteStatementOfApplicabilityPayload { - deletedStatementOfApplicabilityId: ID! -} - -input VendorRiskAssessmentOrder { - field: VendorRiskAssessmentOrderField! - direction: OrderDirection! -} - -type VendorRiskAssessment implements Node { - id: ID! - vendor: Vendor! @goField(forceResolver: true) - expiresAt: Datetime! - dataSensitivity: DataSensitivity! - businessImpact: BusinessImpact! - notes: String - createdAt: Datetime! - updatedAt: Datetime! - - permission(action: String!): Boolean! @goField(forceResolver: true) -} - -enum VendorRiskAssessmentOrderField - @goModel( - model: "go.probo.inc/probo/pkg/coredata.VendorRiskAssessmentOrderField" - ) { - CREATED_AT - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.VendorRiskAssessmentOrderFieldCreatedAt" - ) - EXPIRES_AT - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.VendorRiskAssessmentOrderFieldExpiresAt" - ) -} - -input CreateVendorRiskAssessmentInput { - vendorId: ID! - expiresAt: Datetime! - dataSensitivity: DataSensitivity! - businessImpact: BusinessImpact! - notes: String -} - -type CreateVendorRiskAssessmentPayload { - vendorRiskAssessmentEdge: VendorRiskAssessmentEdge! -} - -input DeleteMeasureInput { - measureId: ID! -} - -type DeleteMeasurePayload { - deletedMeasureId: ID! -} - -type DocumentVersion implements Node { - id: ID! - document: Document! @goField(forceResolver: true) - status: DocumentVersionStatus! - major: Int! - minor: Int! - content: String! - changelog: String! - title: String! - classification: DocumentClassification! - documentType: DocumentType! - approvers( - first: Int - after: CursorKey - last: Int - before: CursorKey - orderBy: ProfileOrder - ): ProfileConnection! @goField(forceResolver: true) - - signatures( - first: Int - after: CursorKey - last: Int - before: CursorKey - orderBy: DocumentVersionSignatureOrder - filter: DocumentVersionSignatureFilter - ): DocumentVersionSignatureConnection! @goField(forceResolver: true) - - approvalQuorums( - first: Int - after: CursorKey - last: Int - before: CursorKey - orderBy: DocumentVersionApprovalQuorumOrder - ): DocumentVersionApprovalQuorumConnection! @goField(forceResolver: true) - - signed: Boolean! @goField(forceResolver: true) - - publishedAt: Datetime - createdAt: Datetime! - updatedAt: Datetime! - - permission(action: String!): Boolean! @goField(forceResolver: true) -} - -type DocumentVersionSignatureConnection - @goModel( - model: "go.probo.inc/probo/pkg/server/api/console/v1/types.DocumentVersionSignatureConnection" - ) { - edges: [DocumentVersionSignatureEdge!]! - pageInfo: PageInfo! - totalCount: Int! @goField(forceResolver: true) -} - -type DocumentVersionSignatureEdge { - cursor: CursorKey! - node: DocumentVersionSignature! -} - -input DocumentVersionSignatureOrder { - field: DocumentVersionSignatureOrderField! - direction: OrderDirection! -} - -input DocumentVersionSignatureFilter { - states: [DocumentVersionSignatureState!] - activeContract: Boolean -} - -enum DocumentVersionSignatureState - @goModel( - model: "go.probo.inc/probo/pkg/coredata.DocumentVersionSignatureState" - ) { - REQUESTED - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.DocumentVersionSignatureStateRequested" - ) - SIGNED - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.DocumentVersionSignatureStateSigned" - ) -} - -enum DocumentVersionSignatureOrderField - @goModel( - model: "go.probo.inc/probo/pkg/coredata.DocumentVersionSignatureOrderField" - ) { - CREATED_AT - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.DocumentVersionSignatureOrderFieldCreatedAt" - ) - SIGNED_AT - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.DocumentVersionSignatureOrderFieldSignedAt" - ) -} - -type DocumentVersionSignature implements Node { - id: ID! - documentVersion: DocumentVersion! @goField(forceResolver: true) - state: DocumentVersionSignatureState! - signedBy: Profile! @goField(forceResolver: true) - signedAt: Datetime - requestedAt: Datetime! - createdAt: Datetime! - updatedAt: Datetime! - - permission(action: String!): Boolean! @goField(forceResolver: true) -} - -enum DocumentVersionApprovalDecisionState - @goModel( - model: "go.probo.inc/probo/pkg/coredata.DocumentVersionApprovalDecisionState" - ) { - PENDING - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.DocumentVersionApprovalDecisionStatePending" - ) - APPROVED - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.DocumentVersionApprovalDecisionStateApproved" - ) - REJECTED - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.DocumentVersionApprovalDecisionStateRejected" - ) - VOIDED - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.DocumentVersionApprovalDecisionStateVoided" - ) -} - -enum DocumentVersionApprovalDecisionOrderField - @goModel( - model: "go.probo.inc/probo/pkg/coredata.DocumentVersionApprovalDecisionOrderField" - ) { - CREATED_AT - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.DocumentVersionApprovalDecisionOrderFieldCreatedAt" - ) -} - -enum DocumentVersionApprovalQuorumStatus - @goModel( - model: "go.probo.inc/probo/pkg/coredata.DocumentVersionApprovalQuorumStatus" - ) { - PENDING - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.DocumentVersionApprovalQuorumStatusPending" - ) - APPROVED - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.DocumentVersionApprovalQuorumStatusApproved" - ) - REJECTED - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.DocumentVersionApprovalQuorumStatusRejected" - ) - VOIDED - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.DocumentVersionApprovalQuorumStatusVoided" - ) -} - -enum DocumentVersionApprovalQuorumOrderField - @goModel( - model: "go.probo.inc/probo/pkg/coredata.DocumentVersionApprovalQuorumOrderField" - ) { - CREATED_AT - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.DocumentVersionApprovalQuorumOrderFieldCreatedAt" - ) -} - -input DocumentVersionApprovalQuorumOrder { - field: DocumentVersionApprovalQuorumOrderField! - direction: OrderDirection! -} - -type DocumentVersionApprovalQuorumConnection - @goModel( - model: "go.probo.inc/probo/pkg/server/api/console/v1/types.DocumentVersionApprovalQuorumConnection" - ) { - edges: [DocumentVersionApprovalQuorumEdge!]! - pageInfo: PageInfo! - totalCount: Int! @goField(forceResolver: true) -} - -type DocumentVersionApprovalQuorumEdge { - cursor: CursorKey! - node: DocumentVersionApprovalQuorum! -} - -type DocumentVersionApprovalQuorum implements Node { - id: ID! - documentVersion: DocumentVersion! @goField(forceResolver: true) - status: DocumentVersionApprovalQuorumStatus! - decisions( - first: Int - after: CursorKey - last: Int - before: CursorKey - orderBy: DocumentVersionApprovalDecisionOrder - filter: DocumentVersionApprovalDecisionFilter - ): DocumentVersionApprovalDecisionConnection! @goField(forceResolver: true) - createdAt: Datetime! - updatedAt: Datetime! - permission(action: String!): Boolean! @goField(forceResolver: true) -} - -input DocumentVersionApprovalDecisionFilter { - states: [DocumentVersionApprovalDecisionState!] -} - -input DocumentVersionApprovalDecisionOrder { - field: DocumentVersionApprovalDecisionOrderField! - direction: OrderDirection! -} - -type DocumentVersionApprovalDecisionConnection - @goModel( - model: "go.probo.inc/probo/pkg/server/api/console/v1/types.DocumentVersionApprovalDecisionConnection" - ) { - edges: [DocumentVersionApprovalDecisionEdge!]! - pageInfo: PageInfo! - totalCount: Int! @goField(forceResolver: true) -} - -type DocumentVersionApprovalDecisionEdge { - cursor: CursorKey! - node: DocumentVersionApprovalDecision! -} - -type DocumentVersionApprovalDecision implements Node { - id: ID! - quorum: DocumentVersionApprovalQuorum! @goField(forceResolver: true) - documentVersion: DocumentVersion! @goField(forceResolver: true) - approver: Profile! @goField(forceResolver: true) - state: DocumentVersionApprovalDecisionState! - comment: String - decidedAt: Datetime - createdAt: Datetime! - updatedAt: Datetime! - permission(action: String!): Boolean! @goField(forceResolver: true) -} - -input ApproveDocumentVersionInput { - documentVersionId: ID! - comment: String -} - -type ApproveDocumentVersionPayload { - approvalDecision: DocumentVersionApprovalDecision! -} - -input RejectDocumentVersionInput { - documentVersionId: ID! - comment: String -} - -type RejectDocumentVersionPayload { - approvalDecision: DocumentVersionApprovalDecision! -} - - -input RequestSignatureInput { - documentVersionId: ID! - signatoryId: ID! -} - -input BulkRequestSignaturesInput { - documentIds: [ID!]! - signatoryIds: [ID!]! -} - -type RequestSignaturePayload { - documentVersionSignatureEdge: DocumentVersionSignatureEdge! -} - -type BulkRequestSignaturesPayload { - documentVersionSignatureEdges: [DocumentVersionSignatureEdge!]! -} - -input BulkDeleteDocumentsInput { - documentIds: [ID!]! -} - -input BulkArchiveDocumentsInput { - documentIds: [ID!]! -} - -type BulkArchiveDocumentsPayload { - documents: [Document!]! -} - -input BulkUnarchiveDocumentsInput { - documentIds: [ID!]! -} - -type BulkUnarchiveDocumentsPayload { - documents: [Document!]! -} - -input BulkExportDocumentsInput { - documentIds: [ID!]! - withWatermark: Boolean! - watermarkEmail: EmailAddr - withSignatures: Boolean! -} - -type BulkDeleteDocumentsPayload { - deletedDocumentIds: [ID!]! -} - -type BulkExportDocumentsPayload { - exportJobId: ID! -} - -input RequestDocumentVersionApprovalInput { - documentId: ID! - approverIds: [ID!]! - changelog: String -} - -type RequestDocumentVersionApprovalPayload { - approvalQuorum: DocumentVersionApprovalQuorum! -} - -input VoidDocumentVersionApprovalInput { - documentVersionId: ID! -} - -type VoidDocumentVersionApprovalPayload { - approvalQuorum: DocumentVersionApprovalQuorum! - documentVersion: DocumentVersion! -} - -input PublishMajorDocumentVersionInput { - documentId: ID! - changelog: String -} - -input PublishMinorDocumentVersionInput { - documentId: ID! - changelog: String -} - -type PublishDocumentVersionPayload { - document: Document! - documentVersion: DocumentVersion! -} - -input BulkPublishDocumentVersionsInput { - documentIds: [ID!]! - changelog: String! -} - -type BulkPublishDocumentVersionsPayload { - documentVersions: [DocumentVersion!]! - documents: [Document!]! -} - -input CancelSignatureRequestInput { - documentVersionSignatureId: ID! -} - -input SendSigningNotificationsInput { - organizationId: ID! -} - -type SendSigningNotificationsPayload { - success: Boolean! -} - -type CancelSignatureRequestPayload { - deletedDocumentVersionSignatureId: ID! -} - -input SignDocumentInput { - documentVersionId: ID! -} - -type SignDocumentPayload { - documentVersionSignature: DocumentVersionSignature! -} - -type UploadMeasureEvidencePayload { - evidenceEdge: EvidenceEdge! -} - -input UploadMeasureEvidenceInput { - measureId: ID! - file: Upload! -} - -input GenerateDocumentChangelogInput { - documentId: ID! -} - -type GenerateDocumentChangelogPayload { - changelog: String! -} - -input AssessVendorInput { - id: ID! - websiteUrl: String! -} - -type AssessVendorPayload { - vendor: Vendor! -} - -type Asset implements Node { - id: ID! - snapshotId: ID - name: String! - amount: Int! - owner: Profile! @goField(forceResolver: true) - vendors( - first: Int - after: CursorKey - last: Int - before: CursorKey - orderBy: VendorOrder - ): VendorConnection! @goField(forceResolver: true) - assetType: AssetType! - dataTypesStored: String! - organization: Organization! @goField(forceResolver: true) - createdAt: Datetime! - updatedAt: Datetime! - - permission(action: String!): Boolean! @goField(forceResolver: true) -} - -type AssetConnection - @goModel( - model: "go.probo.inc/probo/pkg/server/api/console/v1/types.AssetConnection" - ) { - totalCount: Int! @goField(forceResolver: true) - edges: [AssetEdge!]! - pageInfo: PageInfo! -} - -type AssetEdge { - cursor: CursorKey! - node: Asset! -} - -input AssetOrder - @goModel( - model: "go.probo.inc/probo/pkg/server/api/console/v1/types.AssetOrderBy" - ) { - direction: OrderDirection! - field: AssetOrderField! -} - -input CreateAssetInput { - organizationId: ID! - name: String! - amount: Int! - ownerId: ID! - assetType: AssetType! - dataTypesStored: String! - vendorIds: [ID!] -} - -input UpdateAssetInput { - id: ID! - name: String - amount: Int - ownerId: ID - assetType: AssetType - dataTypesStored: String - vendorIds: [ID!] -} - -input DeleteAssetInput { - assetId: ID! -} - -type CreateAssetPayload { - assetEdge: AssetEdge! -} - -type UpdateAssetPayload { - asset: Asset! -} - -type DeleteAssetPayload { - deletedAssetId: ID! -} - -type Datum implements Node - @goModel( - model: "go.probo.inc/probo/pkg/server/api/console/v1/types.Datum" - ) { - id: ID! - snapshotId: ID - name: String! - dataClassification: DataClassification! - owner: Profile! @goField(forceResolver: true) - vendors( - first: Int - after: CursorKey - last: Int - before: CursorKey - orderBy: VendorOrder - ): VendorConnection! @goField(forceResolver: true) - organization: Organization! @goField(forceResolver: true) - createdAt: Datetime! - updatedAt: Datetime! - - permission(action: String!): Boolean! @goField(forceResolver: true) -} - -input DatumOrder - @goModel( - model: "go.probo.inc/probo/pkg/server/api/console/v1/types.DatumOrderBy" - ) { - direction: OrderDirection! - field: DatumOrderField! -} - -input CreateDatumInput { - organizationId: ID! - name: String! - dataClassification: DataClassification! - ownerId: ID! - vendorIds: [ID!] -} - -input UpdateDatumInput { - id: ID! - name: String - dataClassification: DataClassification - ownerId: ID - vendorIds: [ID!] -} - -input DeleteDatumInput { - datumId: ID! -} - -type CreateDatumPayload { - datumEdge: DatumEdge! -} - -type UpdateDatumPayload { - datum: Datum! -} - -type DeleteDatumPayload { - deletedDatumId: ID! -} - -type CreateAuditPayload { - auditEdge: AuditEdge -} - -type UpdateAuditPayload { - audit: Audit -} - -type DeleteAuditPayload { - deletedAuditId: ID -} - -type UploadAuditReportPayload { - audit: Audit -} - -type DeleteAuditReportPayload { - audit: Audit -} - -# Finding payload types -type CreateFindingPayload { - findingEdge: FindingEdge -} - -type UpdateFindingPayload { - finding: Finding -} - -type DeleteFindingPayload { - deletedFindingId: ID -} - -type CreateFindingAuditMappingPayload { - findingEdge: FindingEdge - auditEdge: AuditEdge -} - -type DeleteFindingAuditMappingPayload { - deletedFindingId: ID - deletedAuditId: ID -} - -type CreateObligationPayload { - obligationEdge: ObligationEdge! -} - -type UpdateObligationPayload { - obligation: Obligation! -} - -type DeleteObligationPayload { - deletedObligationId: ID! -} - - -type CreateRightsRequestPayload { - rightsRequestEdge: RightsRequestEdge! -} - -type UpdateRightsRequestPayload { - rightsRequest: RightsRequest! -} - -type DeleteRightsRequestPayload { - deletedRightsRequestId: ID! -} - -type CreateProcessingActivityPayload { - processingActivityEdge: ProcessingActivityEdge! -} - -type UpdateProcessingActivityPayload { - processingActivity: ProcessingActivity! -} - -type DeleteProcessingActivityPayload { - deletedProcessingActivityId: ID! -} - -type CreateDataProtectionImpactAssessmentPayload { - dataProtectionImpactAssessment: DataProtectionImpactAssessment! -} - -type UpdateDataProtectionImpactAssessmentPayload { - dataProtectionImpactAssessment: DataProtectionImpactAssessment! -} - -type DeleteDataProtectionImpactAssessmentPayload { - deletedDataProtectionImpactAssessmentId: ID! -} - -type CreateTransferImpactAssessmentPayload { - transferImpactAssessment: TransferImpactAssessment! -} - -type UpdateTransferImpactAssessmentPayload { - transferImpactAssessment: TransferImpactAssessment! -} - -type DeleteTransferImpactAssessmentPayload { - deletedTransferImpactAssessmentId: ID! -} - -type CreateSnapshotPayload { - snapshotEdge: SnapshotEdge! -} - -type DeleteSnapshotPayload { - deletedSnapshotId: ID! -} - -enum SSLStatus - @goModel(model: "go.probo.inc/probo/pkg/coredata.CustomDomainSSLStatus") { - PENDING - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.CustomDomainSSLStatusPending" - ) - PROVISIONING - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.CustomDomainSSLStatusProvisioning" - ) - ACTIVE - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.CustomDomainSSLStatusActive" - ) - RENEWING - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.CustomDomainSSLStatusRenewing" - ) - EXPIRED - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.CustomDomainSSLStatusExpired" - ) - FAILED - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.CustomDomainSSLStatusFailed" - ) -} - -type CustomDomain implements Node { - id: ID! - organization: Organization! - domain: String! - sslStatus: SSLStatus! - sslExpiresAt: Datetime - dnsRecords: [DNSRecordInstruction!]! - createdAt: Datetime! - updatedAt: Datetime! - - permission(action: String!): Boolean! @goField(forceResolver: true) -} - -type DNSRecordInstruction { - type: String! - name: String! - value: String! - ttl: Int! - purpose: String! -} - -input CreateCustomDomainInput { - organizationId: ID! - domain: String! -} - -input DeleteCustomDomainInput { - organizationId: ID! -} - -type CreateCustomDomainPayload { - customDomain: CustomDomain! -} - -type DeleteCustomDomainPayload { - deletedCustomDomainId: ID! -} - -input DeleteSlackConnectionInput { - slackConnectionId: ID! -} - -type DeleteSlackConnectionPayload { - deletedSlackConnectionId: ID! -} - -# Electronic Signature - -enum ElectronicSignatureStatus - @goModel( - model: "go.probo.inc/probo/pkg/coredata.ElectronicSignatureStatus" - ) { - PENDING - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.ElectronicSignatureStatusPending" - ) - ACCEPTED - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.ElectronicSignatureStatusAccepted" - ) - PROCESSING - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.ElectronicSignatureStatusProcessing" - ) - COMPLETED - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.ElectronicSignatureStatusCompleted" - ) - FAILED - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.ElectronicSignatureStatusFailed" - ) -} - -enum ElectronicSignatureDocumentType - @goModel( - model: "go.probo.inc/probo/pkg/coredata.ElectronicSignatureDocumentType" - ) { - NDA - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.ElectronicSignatureDocumentTypeNDA" - ) - DPA - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.ElectronicSignatureDocumentTypeDPA" - ) - MSA - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.ElectronicSignatureDocumentTypeMSA" - ) - SOW - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.ElectronicSignatureDocumentTypeSOW" - ) - SLA - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.ElectronicSignatureDocumentTypeSLA" - ) - TOS - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.ElectronicSignatureDocumentTypeTOS" - ) - PRIVACY_POLICY - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.ElectronicSignatureDocumentTypePrivacyPolicy" - ) - OTHER - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.ElectronicSignatureDocumentTypeOther" - ) -} - -enum ElectronicSignatureEventType - @goModel( - model: "go.probo.inc/probo/pkg/coredata.ElectronicSignatureEventType" - ) { - DOCUMENT_VIEWED - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.ElectronicSignatureEventTypeDocumentViewed" - ) - CONSENT_GIVEN - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.ElectronicSignatureEventTypeConsentGiven" - ) - FULL_NAME_TYPED - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.ElectronicSignatureEventTypeFullNameTyped" - ) - SIGNATURE_ACCEPTED - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.ElectronicSignatureEventTypeSignatureAccepted" - ) - SIGNATURE_COMPLETED - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.ElectronicSignatureEventTypeSignatureCompleted" - ) - SEAL_COMPUTED - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.ElectronicSignatureEventTypeSealComputed" - ) - TIMESTAMP_REQUESTED - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.ElectronicSignatureEventTypeTimestampRequested" - ) - CERTIFICATE_GENERATED - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.ElectronicSignatureEventTypeCertificateGenerated" - ) - PROCESSING_ERROR - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.ElectronicSignatureEventTypeProcessingError" - ) -} - -enum ElectronicSignatureEventSource - @goModel( - model: "go.probo.inc/probo/pkg/coredata.ElectronicSignatureEventSource" - ) { - CLIENT - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.ElectronicSignatureEventSourceClient" - ) - SERVER - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.ElectronicSignatureEventSourceServer" - ) -} - -type ElectronicSignature implements Node { - id: ID! - status: ElectronicSignatureStatus! - documentType: ElectronicSignatureDocumentType! - consentText: String! - lastError: String - signedAt: Datetime - certificateFileUrl: String @goField(forceResolver: true) - events: [ElectronicSignatureEvent!]! @goField(forceResolver: true) - createdAt: Datetime! - updatedAt: Datetime! -} - -type ElectronicSignatureEvent { - id: ID! - eventType: ElectronicSignatureEventType! - eventSource: ElectronicSignatureEventSource! - actorEmail: String! - actorIpAddress: String! - actorUserAgent: String! - occurredAt: Datetime! - createdAt: Datetime! -} - -# Audit Log - -input AuditLogEntryOrder - @goModel( - model: "go.probo.inc/probo/pkg/server/api/console/v1/types.AuditLogEntryOrderBy" - ) { - field: AuditLogEntryOrderField! - direction: OrderDirection! -} - -input AuditLogEntryFilter { - action: String - actorId: ID - resourceType: String - resourceId: ID -} - -type AuditLogEntry implements Node { - id: ID! - organization: Organization @goField(forceResolver: true) - actorId: ID! - actorType: AuditLogActorType! - action: String! - resourceType: String! - resourceId: ID! - metadata: String - createdAt: Datetime! - - permission(action: String!): Boolean! @goField(forceResolver: true) -} - -# ===== Access Review Types ===== - -enum AccessReviewCampaignStatus - @goModel( - model: "go.probo.inc/probo/pkg/coredata.AccessReviewCampaignStatus" - ) { - DRAFT - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.AccessReviewCampaignStatusDraft" - ) - IN_PROGRESS - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.AccessReviewCampaignStatusInProgress" - ) - PENDING_ACTIONS - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.AccessReviewCampaignStatusPendingActions" - ) - FAILED - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.AccessReviewCampaignStatusFailed" - ) - COMPLETED - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.AccessReviewCampaignStatusCompleted" - ) - CANCELLED - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.AccessReviewCampaignStatusCancelled" - ) -} - -enum AccessSourceCategory - @goModel( - model: "go.probo.inc/probo/pkg/coredata.AccessSourceCategory" - ) { - SAAS - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.AccessSourceCategorySaaS" - ) - CLOUD_INFRA - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.AccessSourceCategoryCloudInfra" - ) - SOURCE_CODE - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.AccessSourceCategorySourceCode" - ) - OTHER - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.AccessSourceCategoryOther" - ) -} - -enum AccessReviewCampaignSourceFetchStatus - @goModel( - model: "go.probo.inc/probo/pkg/coredata.AccessReviewCampaignSourceFetchStatus" - ) { - QUEUED - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.AccessReviewCampaignSourceFetchStatusQueued" - ) - FETCHING - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.AccessReviewCampaignSourceFetchStatusFetching" - ) - SUCCESS - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.AccessReviewCampaignSourceFetchStatusSuccess" - ) - FAILED - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.AccessReviewCampaignSourceFetchStatusFailed" - ) -} - -enum AccessEntryFlag - @goModel(model: "go.probo.inc/probo/pkg/coredata.AccessEntryFlag") { - NONE - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.AccessEntryFlagNone" - ) - ORPHANED - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.AccessEntryFlagOrphaned" - ) - INACTIVE - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.AccessEntryFlagInactive" - ) - EXCESSIVE - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.AccessEntryFlagExcessive" - ) - ROLE_MISMATCH - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.AccessEntryFlagRoleMismatch" - ) - NEW - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.AccessEntryFlagNew" - ) - DORMANT - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.AccessEntryFlagDormant" - ) - TERMINATED_USER - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.AccessEntryFlagTerminatedUser" - ) - CONTRACTOR_EXPIRED - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.AccessEntryFlagContractorExpired" - ) - SOD_CONFLICT - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.AccessEntryFlagSoDConflict" - ) - PRIVILEGED_ACCESS - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.AccessEntryFlagPrivilegedAccess" - ) - ROLE_CREEP - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.AccessEntryFlagRoleCreep" - ) - NO_BUSINESS_JUSTIFICATION - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.AccessEntryFlagNoBusinessJustification" - ) - OUT_OF_DEPARTMENT - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.AccessEntryFlagOutOfDepartment" - ) - SHARED_ACCOUNT - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.AccessEntryFlagSharedAccount" - ) -} - -enum AccessEntryDecision - @goModel( - model: "go.probo.inc/probo/pkg/coredata.AccessEntryDecision" - ) { - PENDING - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.AccessEntryDecisionPending" - ) - APPROVED - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.AccessEntryDecisionApproved" - ) - REVOKE - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.AccessEntryDecisionRevoke" - ) - DEFER - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.AccessEntryDecisionDefer" - ) - ESCALATE - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.AccessEntryDecisionEscalate" - ) -} - -enum AccessEntryIncrementalTag - @goModel(model: "go.probo.inc/probo/pkg/coredata.AccessEntryIncrementalTag") { - NEW - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.AccessEntryIncrementalTagNew" - ) - REMOVED - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.AccessEntryIncrementalTagRemoved" - ) - UNCHANGED - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.AccessEntryIncrementalTagUnchanged" - ) -} - -enum MfaStatus - @goModel(model: "go.probo.inc/probo/pkg/coredata.MFAStatus") { - ENABLED - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.MFAStatusEnabled" - ) - DISABLED - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.MFAStatusDisabled" - ) - UNKNOWN - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.MFAStatusUnknown" - ) -} - -enum AccessEntryAuthMethod - @goModel( - model: "go.probo.inc/probo/pkg/coredata.AccessEntryAuthMethod" - ) { - SSO - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.AccessEntryAuthMethodSSO" - ) - PASSWORD - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.AccessEntryAuthMethodPassword" - ) - API_KEY - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.AccessEntryAuthMethodAPIKey" - ) - SERVICE_ACCOUNT - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.AccessEntryAuthMethodServiceAccount" - ) - UNKNOWN - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.AccessEntryAuthMethodUnknown" - ) -} - -type AccessReview implements Node { - id: ID! - organization: Organization! @goField(forceResolver: true) - identitySource: AccessSource @goField(forceResolver: true) - createdAt: Datetime! - updatedAt: Datetime! - - accessSources( - first: Int - after: CursorKey - last: Int - before: CursorKey - orderBy: AccessSourceOrder - ): AccessSourceConnection! @goField(forceResolver: true) - - campaigns( - first: Int - after: CursorKey - last: Int - before: CursorKey - orderBy: AccessReviewCampaignOrder - ): AccessReviewCampaignConnection! @goField(forceResolver: true) - - permission(action: String!): Boolean! @goField(forceResolver: true) -} - -type AuditLogEntryConnection - @goModel( - model: "go.probo.inc/probo/pkg/server/api/console/v1/types.AuditLogEntryConnection" - ) { - edges: [AuditLogEntryEdge!]! - pageInfo: PageInfo! - totalCount: Int! @goField(forceResolver: true) -} - -type AuditLogEntryEdge { - cursor: CursorKey! - node: AuditLogEntry! -} - -enum AccessEntryAccountType - @goModel( - model: "go.probo.inc/probo/pkg/coredata.AccessEntryAccountType" - ) { - USER - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.AccessEntryAccountTypeUser" - ) - SERVICE_ACCOUNT - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.AccessEntryAccountTypeServiceAccount" - ) -} - -type ProviderOrganization { - slug: String! - displayName: String! -} - -enum AccessSourceConnectionStatus { - CONNECTED - DISCONNECTED - NOT_APPLICABLE -} - -type AccessSource implements Node { - id: ID! - organization: Organization! @goField(forceResolver: true) - connectorId: ID - connector: Connector @goField(forceResolver: true) - name: String! - csvData: String - providerOrganizations: [ProviderOrganization!]! @goField(forceResolver: true) - needsConfiguration: Boolean! @goField(forceResolver: true) - connectionStatus: AccessSourceConnectionStatus! @goField(forceResolver: true) - selectedOrganization: String @goField(forceResolver: true) - createdAt: Datetime! - updatedAt: Datetime! - - permission(action: String!): Boolean! @goField(forceResolver: true) -} - -type AccessReviewCampaignScopeSource - @goModel( - model: "go.probo.inc/probo/pkg/server/api/console/v1/types.AccessReviewCampaignScopeSource" - ) { - id: ID! - source: AccessSource! - name: String! - fetchStatus: AccessReviewCampaignSourceFetchStatus! - fetchedAccountsCount: Int! - attemptCount: Int! - lastError: String - fetchStartedAt: Datetime - fetchCompletedAt: Datetime - - entries( - first: Int - after: CursorKey - last: Int - before: CursorKey - orderBy: AccessEntryOrder - filter: AccessEntryFilter - ): AccessEntryConnection! @goField(forceResolver: true) - - statistics: AccessReviewCampaignStatistics! @goField(forceResolver: true) -} - -type AccessSourceConnection - @goModel( - model: "go.probo.inc/probo/pkg/server/api/console/v1/types.AccessSourceConnection" - ) { - totalCount: Int! @goField(forceResolver: true) - edges: [AccessSourceEdge!]! - pageInfo: PageInfo! -} - -type AccessSourceEdge { - cursor: CursorKey! - node: AccessSource! -} - -input AccessSourceOrder { - direction: OrderDirection! - field: AccessSourceOrderField! -} - -enum AccessSourceOrderField - @goModel( - model: "go.probo.inc/probo/pkg/coredata.AccessSourceOrderField" - ) { - CREATED_AT -} - -type AccessReviewCampaign implements Node { - id: ID! - organization: Organization! @goField(forceResolver: true) - name: String! - description: String! - status: AccessReviewCampaignStatus! - startedAt: Datetime - completedAt: Datetime - frameworkControls: [String!] - createdAt: Datetime! - updatedAt: Datetime! - - scopeSources: [AccessReviewCampaignScopeSource!]! @goField(forceResolver: true) - - entries( - first: Int - after: CursorKey - last: Int - before: CursorKey - orderBy: AccessEntryOrder - accessSourceId: ID - filter: AccessEntryFilter - ): AccessEntryConnection! @goField(forceResolver: true) - - pendingEntryCount: Int! @goField(forceResolver: true) - - statistics: AccessReviewCampaignStatistics! @goField(forceResolver: true) - - permission(action: String!): Boolean! @goField(forceResolver: true) -} - -type AccessReviewCampaignConnection - @goModel( - model: "go.probo.inc/probo/pkg/server/api/console/v1/types.AccessReviewCampaignConnection" - ) { - totalCount: Int! @goField(forceResolver: true) - edges: [AccessReviewCampaignEdge!]! - pageInfo: PageInfo! -} - -type AccessReviewCampaignEdge { - cursor: CursorKey! - node: AccessReviewCampaign! -} - -input AccessReviewCampaignOrder { - direction: OrderDirection! - field: AccessReviewCampaignOrderField! -} - -enum AccessReviewCampaignOrderField - @goModel( - model: "go.probo.inc/probo/pkg/coredata.AccessReviewCampaignOrderField" - ) { - CREATED_AT -} - -type AccessEntry implements Node { - id: ID! - campaign: AccessReviewCampaign! @goField(forceResolver: true) - accessSource: AccessSource! @goField(forceResolver: true) - email: String! - fullName: String! - role: String! - jobTitle: String! - isAdmin: Boolean! - mfaStatus: MfaStatus! - authMethod: AccessEntryAuthMethod! - accountType: AccessEntryAccountType! - lastLogin: Datetime - accountCreatedAt: Datetime - externalId: String! - incrementalTag: AccessEntryIncrementalTag! - flags: [AccessEntryFlag!]! - flagReasons: [String!]! - decision: AccessEntryDecision! - decisionNote: String - decidedBy: ID - decidedAt: Datetime - decisionHistory: [AccessEntryDecisionHistoryEntry!]! - @goField(forceResolver: true) - createdAt: Datetime! - updatedAt: Datetime! - - permission(action: String!): Boolean! @goField(forceResolver: true) -} - -type AccessEntryDecisionHistoryEntry { - id: ID! - decision: AccessEntryDecision! - decisionNote: String - decidedBy: ID - decidedAt: Datetime! - createdAt: Datetime! -} - -type AccessEntryConnection - @goModel( - model: "go.probo.inc/probo/pkg/server/api/console/v1/types.AccessEntryConnection" - ) { - totalCount: Int! @goField(forceResolver: true) - edges: [AccessEntryEdge!]! - pageInfo: PageInfo! -} - -type AccessEntryEdge { - cursor: CursorKey! - node: AccessEntry! -} - -input AccessEntryOrder { - direction: OrderDirection! - field: AccessEntryOrderField! -} - -enum AccessEntryOrderField - @goModel( - model: "go.probo.inc/probo/pkg/coredata.AccessEntryOrderField" - ) { - CREATED_AT -} - -input AccessEntryFilter - @goModel( - model: "go.probo.inc/probo/pkg/coredata.AccessEntryFilter" - ) { - decision: AccessEntryDecision - flag: AccessEntryFlag - incrementalTag: AccessEntryIncrementalTag - isAdmin: Boolean - authMethod: AccessEntryAuthMethod - accountType: AccessEntryAccountType -} - -type AccessReviewCampaignStatistics { - totalCount: Int! - decisionCounts: [AccessEntryDecisionCount!]! - flagCounts: [AccessEntryFlagCount!]! - incrementalTagCounts: [AccessEntryIncrementalTagCount!]! -} - -type AccessEntryDecisionCount { - decision: AccessEntryDecision! - count: Int! -} - -type AccessEntryFlagCount { - flag: AccessEntryFlag! - count: Int! -} - -type AccessEntryIncrementalTagCount { - incrementalTag: AccessEntryIncrementalTag! - count: Int! -} - -# Access Review Inputs & Payloads - -input CreateAccessSourceInput { - organizationId: ID! - connectorId: ID - name: String! - csvData: String -} - -type CreateAccessSourcePayload { - accessSourceEdge: AccessSourceEdge! -} - -input UpdateAccessSourceInput { - accessSourceId: ID! - name: String @goField(omittable: true) - connectorId: ID @goField(omittable: true) - csvData: String @goField(omittable: true) -} - -type UpdateAccessSourcePayload { - accessSource: AccessSource! -} - -input DeleteAccessSourceInput { - accessSourceId: ID! -} - -type DeleteAccessSourcePayload { - deletedAccessSourceId: ID! -} - -input ConfigureAccessSourceInput { - accessSourceId: ID! - organizationSlug: String! -} - -type ConfigureAccessSourcePayload { - accessSource: AccessSource! -} - -input CreateAccessReviewCampaignInput { - organizationId: ID! - name: String! - description: String - frameworkControls: [String!] - accessSourceIds: [ID!] -} - -type CreateAccessReviewCampaignPayload { - accessReviewCampaignEdge: AccessReviewCampaignEdge! -} - -input UpdateAccessReviewCampaignInput { - accessReviewCampaignId: ID! - name: String @goField(omittable: true) - description: String @goField(omittable: true) - frameworkControls: [String!] @goField(omittable: true) -} - -type UpdateAccessReviewCampaignPayload { - accessReviewCampaign: AccessReviewCampaign! -} - -input DeleteAccessReviewCampaignInput { - accessReviewCampaignId: ID! -} - -type DeleteAccessReviewCampaignPayload { - deletedAccessReviewCampaignId: ID! -} - -input StartAccessReviewCampaignInput { - accessReviewCampaignId: ID! -} - -input AddAccessReviewCampaignScopeSourceInput { - accessReviewCampaignId: ID! - accessSourceId: ID! -} - -type AddAccessReviewCampaignScopeSourcePayload { - accessReviewCampaign: AccessReviewCampaign! -} - -input RemoveAccessReviewCampaignScopeSourceInput { - accessReviewCampaignId: ID! - accessSourceId: ID! -} - -type RemoveAccessReviewCampaignScopeSourcePayload { - accessReviewCampaign: AccessReviewCampaign! -} - -type StartAccessReviewCampaignPayload { - accessReviewCampaign: AccessReviewCampaign! -} - -input CloseAccessReviewCampaignInput { - accessReviewCampaignId: ID! -} - -type CloseAccessReviewCampaignPayload { - accessReviewCampaign: AccessReviewCampaign! -} - -input CancelAccessReviewCampaignInput { - accessReviewCampaignId: ID! -} - -type CancelAccessReviewCampaignPayload { - accessReviewCampaign: AccessReviewCampaign! -} - -input RecordAccessEntryDecisionInput { - accessEntryId: ID! - decision: AccessEntryDecision! - decisionNote: String -} - -type RecordAccessEntryDecisionPayload { - accessEntry: AccessEntry! -} - -input RecordAccessEntryDecisionsInput { - decisions: [AccessEntryDecisionInput!]! -} - -input AccessEntryDecisionInput { - accessEntryId: ID! - decision: AccessEntryDecision! - decisionNote: String -} - -type RecordAccessEntryDecisionsPayload { - accessEntries: [AccessEntry!]! -} - -input FlagAccessEntryInput { - accessEntryId: ID! - flags: [AccessEntryFlag!]! - flagReasons: [String!] -} - -type FlagAccessEntryPayload { - accessEntry: AccessEntry! -} - -input CreateAPIKeyConnectorInput { - organizationId: ID! - provider: ConnectorProvider! - apiKey: String! - tallyOrganizationId: String - sentryOrganizationSlug: String - supabaseOrganizationSlug: String - githubOrganization: String - onePasswordScimBridgeUrl: String -} - -type CreateAPIKeyConnectorPayload { - connector: Connector! -} - -input CreateClientCredentialsConnectorInput { - organizationId: ID! - provider: ConnectorProvider! - clientId: String! - clientSecret: String! - tokenUrl: String! - scope: String - onePasswordAccountId: String - onePasswordRegion: String -} - -type CreateClientCredentialsConnectorPayload { - connector: Connector -} - -input DeleteConnectorInput { - connectorId: ID! -} - -type DeleteConnectorPayload { - deletedConnectorId: ID! -} diff --git a/pkg/server/api/console/v1/snapshot.resolvers.go b/pkg/server/api/console/v1/snapshot.resolvers.go new file mode 100644 index 000000000..1115a12e7 --- /dev/null +++ b/pkg/server/api/console/v1/snapshot.resolvers.go @@ -0,0 +1,195 @@ +package console_v1 + +// This file will be automatically regenerated based on the schema, any resolver +// implementations +// will be copied through when generating and any unknown code will be moved to the end. +// Code generated by github.com/99designs/gqlgen version v0.17.87 + +import ( + "context" + "errors" + + "go.gearno.de/kit/log" + "go.probo.inc/probo/pkg/coredata" + "go.probo.inc/probo/pkg/page" + "go.probo.inc/probo/pkg/probo" + "go.probo.inc/probo/pkg/server/api/console/v1/schema" + "go.probo.inc/probo/pkg/server/api/console/v1/types" + "go.probo.inc/probo/pkg/server/gqlutils" +) + +// CreateSnapshot is the resolver for the createSnapshot field. +func (r *mutationResolver) CreateSnapshot(ctx context.Context, input types.CreateSnapshotInput) (*types.CreateSnapshotPayload, error) { + if err := r.authorize(ctx, input.OrganizationID, probo.ActionSnapshotCreate); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, input.OrganizationID.TenantID()) + + snapshot, err := prb.Snapshots.Create( + ctx, + &probo.CreateSnapshotRequest{ + OrganizationID: input.OrganizationID, + Name: input.Name, + Description: input.Description, + Type: input.Type, + }, + ) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot create snapshot", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.CreateSnapshotPayload{ + SnapshotEdge: types.NewSnapshotEdge(snapshot, coredata.SnapshotOrderFieldCreatedAt), + }, nil +} + +// DeleteSnapshot is the resolver for the deleteSnapshot field. +func (r *mutationResolver) DeleteSnapshot(ctx context.Context, input types.DeleteSnapshotInput) (*types.DeleteSnapshotPayload, error) { + if err := r.authorize(ctx, input.SnapshotID, probo.ActionSnapshotDelete); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, input.SnapshotID.TenantID()) + + err := prb.Snapshots.Delete(ctx, input.SnapshotID) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot delete snapshot", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.DeleteSnapshotPayload{ + DeletedSnapshotID: input.SnapshotID, + }, nil +} + +// Snapshots is the resolver for the snapshots field. +func (r *organizationResolver) Snapshots(ctx context.Context, obj *types.Organization, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.SnapshotOrderBy) (*types.SnapshotConnection, error) { + if err := r.authorize(ctx, obj.ID, probo.ActionSnapshotList); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, obj.ID.TenantID()) + + pageOrderBy := page.OrderBy[coredata.SnapshotOrderField]{ + Field: coredata.SnapshotOrderFieldCreatedAt, + Direction: page.OrderDirectionDesc, + } + if orderBy != nil { + pageOrderBy = page.OrderBy[coredata.SnapshotOrderField]{ + Field: orderBy.Field, + Direction: orderBy.Direction, + } + } + + cursor := types.NewCursor(first, after, last, before, pageOrderBy) + + page, err := prb.Snapshots.ListForOrganizationID(ctx, obj.ID, cursor) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot list organization snapshots", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return types.NewSnapshotConnection(page, r, obj.ID), nil +} + +// Organization is the resolver for the organization field. +func (r *snapshotResolver) Organization(ctx context.Context, obj *types.Snapshot) (*types.Organization, error) { + if err := r.authorize(ctx, obj.ID, probo.ActionOrganizationGet); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, obj.ID.TenantID()) + + snapshot, err := prb.Snapshots.Get(ctx, obj.ID) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot get snapshot", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + organization, err := prb.Organizations.Get(ctx, snapshot.OrganizationID) + if err != nil { + if errors.Is(err, coredata.ErrResourceNotFound) { + return nil, gqlutils.NotFound(ctx, err) + } + + r.logger.ErrorCtx(ctx, "cannot get organization", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return types.NewOrganization(organization), nil +} + +// Controls is the resolver for the controls field. +func (r *snapshotResolver) Controls(ctx context.Context, obj *types.Snapshot, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.ControlOrderBy, filter *types.ControlFilter) (*types.ControlConnection, error) { + if err := r.authorize(ctx, obj.ID, probo.ActionControlList); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, obj.ID.TenantID()) + + pageOrderBy := page.OrderBy[coredata.ControlOrderField]{ + Field: coredata.ControlOrderFieldCreatedAt, + Direction: page.OrderDirectionDesc, + } + if orderBy != nil { + pageOrderBy = page.OrderBy[coredata.ControlOrderField]{ + Field: orderBy.Field, + Direction: orderBy.Direction, + } + } + + cursor := types.NewCursor(first, after, last, before, pageOrderBy) + + var controlFilter = coredata.NewControlFilter(nil) + if filter != nil { + controlFilter = coredata.NewControlFilter(filter.Query) + } + + page, err := prb.Controls.ListForSnapshotID(ctx, obj.ID, cursor, controlFilter) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot list snapshot controls", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return types.NewControlConnection(page, r, obj.ID, controlFilter), nil +} + +// Permission is the resolver for the permission field. +func (r *snapshotResolver) Permission(ctx context.Context, obj *types.Snapshot, action string) (bool, error) { + return r.Resolver.Permission(ctx, obj, action) +} + +// TotalCount is the resolver for the totalCount field. +func (r *snapshotConnectionResolver) TotalCount(ctx context.Context, obj *types.SnapshotConnection) (int, error) { + if err := r.authorize(ctx, obj.ParentID, probo.ActionSnapshotList); err != nil { + return 0, err + } + + prb := r.ProboService(ctx, obj.ParentID.TenantID()) + + switch obj.Resolver.(type) { + case *organizationResolver: + count, err := prb.Snapshots.CountForOrganizationID(ctx, obj.ParentID) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot count snapshots", log.Error(err)) + return 0, gqlutils.Internal(ctx) + } + return count, nil + } + + r.logger.ErrorCtx(ctx, "unsupported resolver") + return 0, gqlutils.Internal(ctx) +} + +// Snapshot returns schema.SnapshotResolver implementation. +func (r *Resolver) Snapshot() schema.SnapshotResolver { return &snapshotResolver{r} } + +// SnapshotConnection returns schema.SnapshotConnectionResolver implementation. +func (r *Resolver) SnapshotConnection() schema.SnapshotConnectionResolver { + return &snapshotConnectionResolver{r} +} + +type snapshotResolver struct{ *Resolver } +type snapshotConnectionResolver struct{ *Resolver } diff --git a/pkg/server/api/console/v1/task.resolvers.go b/pkg/server/api/console/v1/task.resolvers.go new file mode 100644 index 000000000..c9f59c53e --- /dev/null +++ b/pkg/server/api/console/v1/task.resolvers.go @@ -0,0 +1,289 @@ +package console_v1 + +// This file will be automatically regenerated based on the schema, any resolver +// implementations +// will be copied through when generating and any unknown code will be moved to the end. +// Code generated by github.com/99designs/gqlgen version v0.17.87 + +import ( + "context" + "errors" + + "github.com/vikstrous/dataloadgen" + "go.gearno.de/kit/log" + "go.probo.inc/probo/pkg/coredata" + "go.probo.inc/probo/pkg/iam" + "go.probo.inc/probo/pkg/page" + "go.probo.inc/probo/pkg/probo" + "go.probo.inc/probo/pkg/server/api/console/v1/dataloader" + "go.probo.inc/probo/pkg/server/api/console/v1/schema" + "go.probo.inc/probo/pkg/server/api/console/v1/types" + "go.probo.inc/probo/pkg/server/gqlutils" + "go.probo.inc/probo/pkg/validator" +) + +// CreateTask is the resolver for the createTask field. +func (r *mutationResolver) CreateTask(ctx context.Context, input types.CreateTaskInput) (*types.CreateTaskPayload, error) { + if err := r.authorize(ctx, input.OrganizationID, probo.ActionTaskCreate); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, input.OrganizationID.TenantID()) + + task, err := prb.Tasks.Create( + ctx, + probo.CreateTaskRequest{ + MeasureID: input.MeasureID, + OrganizationID: input.OrganizationID, + Name: input.Name, + Description: input.Description, + Priority: input.Priority, + TimeEstimate: input.TimeEstimate, + AssignedToID: input.AssignedToID, + Deadline: input.Deadline, + }, + ) + if err != nil { + if errors.Is(err, coredata.ErrResourceAlreadyExists) { + return nil, gqlutils.Conflict(ctx, err) + } + + if validationErrors, ok := errors.AsType[validator.ValidationErrors](err); ok { + return nil, gqlutils.InvalidValidationErrors(ctx, validationErrors) + } + r.logger.ErrorCtx(ctx, "cannot create task", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.CreateTaskPayload{ + TaskEdge: types.NewTaskEdge(task, coredata.TaskOrderFieldCreatedAt), + }, nil +} + +// UpdateTask is the resolver for the updateTask field. +func (r *mutationResolver) UpdateTask(ctx context.Context, input types.UpdateTaskInput) (*types.UpdateTaskPayload, error) { + if err := r.authorize(ctx, input.TaskID, probo.ActionTaskUpdate); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, input.TaskID.TenantID()) + + task, err := prb.Tasks.Update( + ctx, + probo.UpdateTaskRequest{ + TaskID: input.TaskID, + Name: input.Name, + Description: gqlutils.UnwrapOmittable(input.Description), + State: input.State, + Priority: input.Priority, + Rank: input.Rank, + TimeEstimate: gqlutils.UnwrapOmittable(input.TimeEstimate), + Deadline: gqlutils.UnwrapOmittable(input.Deadline), + AssignedToID: gqlutils.UnwrapOmittable(input.AssignedToID), + MeasureID: gqlutils.UnwrapOmittable(input.MeasureID), + }, + ) + if err != nil { + if validationErrors, ok := errors.AsType[validator.ValidationErrors](err); ok { + return nil, gqlutils.InvalidValidationErrors(ctx, validationErrors) + } + r.logger.ErrorCtx(ctx, "cannot update task", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.UpdateTaskPayload{ + Task: types.NewTask(task), + }, nil +} + +// DeleteTask is the resolver for the deleteTask field. +func (r *mutationResolver) DeleteTask(ctx context.Context, input types.DeleteTaskInput) (*types.DeleteTaskPayload, error) { + if err := r.authorize(ctx, input.TaskID, probo.ActionTaskDelete); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, input.TaskID.TenantID()) + + err := prb.Tasks.Delete(ctx, input.TaskID) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot delete task", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.DeleteTaskPayload{ + DeletedTaskID: input.TaskID, + }, nil +} + +// Tasks is the resolver for the tasks field. +func (r *organizationResolver) Tasks(ctx context.Context, obj *types.Organization, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.TaskOrderBy) (*types.TaskConnection, error) { + if err := r.authorize(ctx, obj.ID, probo.ActionTaskList); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, obj.ID.TenantID()) + + pageOrderBy := page.OrderBy[coredata.TaskOrderField]{ + Field: coredata.TaskOrderFieldCreatedAt, + Direction: page.OrderDirectionDesc, + } + if orderBy != nil { + pageOrderBy = page.OrderBy[coredata.TaskOrderField]{ + Field: orderBy.Field, + Direction: orderBy.Direction, + } + } + + cursor := types.NewCursor(first, after, last, before, pageOrderBy) + + page, err := prb.Tasks.ListForOrganizationID(ctx, obj.ID, cursor) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot list organization tasks", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return types.NewTaskConnection(page, r, obj.ID), nil +} + +// AssignedTo is the resolver for the assignedTo field. +func (r *taskResolver) AssignedTo(ctx context.Context, obj *types.Task) (*types.Profile, error) { + if err := r.authorize(ctx, obj.ID, iam.ActionMembershipProfileGet); err != nil { + return nil, err + } + + if obj.AssignedTo == nil { + return nil, nil + } + + loaders := dataloader.FromContext(ctx) + + assignee, err := loaders.Profile.Load(ctx, obj.AssignedTo.ID) + if err != nil { + if errors.Is(err, coredata.ErrResourceNotFound) || errors.Is(err, dataloadgen.ErrNotFound) { + return nil, gqlutils.NotFound(ctx, err) + } + + r.logger.ErrorCtx(ctx, "cannot get assigned to", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return types.NewProfile(assignee), nil +} + +// Organization is the resolver for the organization field. +func (r *taskResolver) Organization(ctx context.Context, obj *types.Task) (*types.Organization, error) { + if err := r.authorize(ctx, obj.ID, probo.ActionOrganizationGet); err != nil { + return nil, err + } + + loaders := dataloader.FromContext(ctx) + + organization, err := loaders.Organization.Load(ctx, obj.Organization.ID) + if err != nil { + if errors.Is(err, coredata.ErrResourceNotFound) || errors.Is(err, dataloadgen.ErrNotFound) { + return nil, gqlutils.NotFound(ctx, err) + } + + r.logger.ErrorCtx(ctx, "cannot get organization", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return types.NewOrganization(organization), nil +} + +// Measure is the resolver for the measure field. +func (r *taskResolver) Measure(ctx context.Context, obj *types.Task) (*types.Measure, error) { + if err := r.authorize(ctx, obj.ID, probo.ActionMeasureGet); err != nil { + return nil, err + } + + if obj.Measure == nil { + return nil, nil + } + + loaders := dataloader.FromContext(ctx) + + measure, err := loaders.Measure.Load(ctx, obj.Measure.ID) + if err != nil { + if errors.Is(err, coredata.ErrResourceNotFound) || errors.Is(err, dataloadgen.ErrNotFound) { + return nil, gqlutils.NotFound(ctx, err) + } + + r.logger.ErrorCtx(ctx, "cannot get measure", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return types.NewMeasure(measure), nil +} + +// Evidences is the resolver for the evidences field. +func (r *taskResolver) Evidences(ctx context.Context, obj *types.Task, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.EvidenceOrderBy) (*types.EvidenceConnection, error) { + if err := r.authorize(ctx, obj.ID, probo.ActionEvidenceList); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, obj.ID.TenantID()) + + pageOrderBy := page.OrderBy[coredata.EvidenceOrderField]{ + Field: coredata.EvidenceOrderFieldCreatedAt, + Direction: page.OrderDirectionDesc, + } + if orderBy != nil { + pageOrderBy = page.OrderBy[coredata.EvidenceOrderField]{ + Field: orderBy.Field, + Direction: orderBy.Direction, + } + } + + cursor := types.NewCursor(first, after, last, before, pageOrderBy) + page, err := prb.Evidences.ListForTaskID(ctx, obj.ID, cursor) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot list task evidences", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return types.NewEvidenceConnection(page, r, obj.ID), nil +} + +// Permission is the resolver for the permission field. +func (r *taskResolver) Permission(ctx context.Context, obj *types.Task, action string) (bool, error) { + return r.Resolver.Permission(ctx, obj, action) +} + +// TotalCount is the resolver for the totalCount field. +func (r *taskConnectionResolver) TotalCount(ctx context.Context, obj *types.TaskConnection) (int, error) { + if err := r.authorize(ctx, obj.ParentID, probo.ActionTaskList); err != nil { + return 0, err + } + + prb := r.ProboService(ctx, obj.ParentID.TenantID()) + + switch obj.Resolver.(type) { + case *measureResolver: + count, err := prb.Tasks.CountForMeasureID(ctx, obj.ParentID) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot count tasks", log.Error(err)) + return 0, gqlutils.Internal(ctx) + } + return count, nil + case *organizationResolver: + count, err := prb.Tasks.CountForOrganizationID(ctx, obj.ParentID) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot count tasks", log.Error(err)) + return 0, gqlutils.Internal(ctx) + } + return count, nil + } + + r.logger.ErrorCtx(ctx, "unsupported resolver") + return 0, gqlutils.Internal(ctx) +} + +// Task returns schema.TaskResolver implementation. +func (r *Resolver) Task() schema.TaskResolver { return &taskResolver{r} } + +// TaskConnection returns schema.TaskConnectionResolver implementation. +func (r *Resolver) TaskConnection() schema.TaskConnectionResolver { return &taskConnectionResolver{r} } + +type taskResolver struct{ *Resolver } +type taskConnectionResolver struct{ *Resolver } diff --git a/pkg/server/api/console/v1/trust_center.resolvers.go b/pkg/server/api/console/v1/trust_center.resolvers.go new file mode 100644 index 000000000..4eb3ec8dc --- /dev/null +++ b/pkg/server/api/console/v1/trust_center.resolvers.go @@ -0,0 +1,1350 @@ +package console_v1 + +// This file will be automatically regenerated based on the schema, any resolver +// implementations +// will be copied through when generating and any unknown code will be moved to the end. +// Code generated by github.com/99designs/gqlgen version v0.17.87 + +import ( + "context" + "errors" + "fmt" + "time" + + "github.com/vikstrous/dataloadgen" + "go.gearno.de/kit/log" + "go.probo.inc/probo/pkg/coredata" + "go.probo.inc/probo/pkg/iam" + "go.probo.inc/probo/pkg/page" + "go.probo.inc/probo/pkg/probo" + "go.probo.inc/probo/pkg/server/api/console/v1/dataloader" + "go.probo.inc/probo/pkg/server/api/console/v1/schema" + "go.probo.inc/probo/pkg/server/api/console/v1/types" + "go.probo.inc/probo/pkg/server/gqlutils" + "go.probo.inc/probo/pkg/validator" +) + +// Permission is the resolver for the permission field. +func (r *complianceExternalURLResolver) Permission(ctx context.Context, obj *types.ComplianceExternalURL, action string) (bool, error) { + return r.Resolver.Permission(ctx, obj, action) +} + +// Framework is the resolver for the framework field. +func (r *complianceFrameworkResolver) Framework(ctx context.Context, obj *types.ComplianceFramework) (*types.Framework, error) { + if err := r.authorize(ctx, obj.FrameworkID, probo.ActionFrameworkGet); err != nil { + return nil, err + } + + loaders := dataloader.FromContext(ctx) + + framework, err := loaders.Framework.Load(ctx, obj.FrameworkID) + if err != nil { + if errors.Is(err, dataloadgen.ErrNotFound) { + return nil, gqlutils.NotFound(ctx, err) + } + + r.logger.ErrorCtx(ctx, "cannot load framework", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return types.NewFramework(framework), nil +} + +// Permission is the resolver for the permission field. +func (r *customDomainResolver) Permission(ctx context.Context, obj *types.CustomDomain, action string) (bool, error) { + return r.Resolver.Permission(ctx, obj, action) +} + +// UpdateTrustCenter is the resolver for the updateTrustCenter field. +func (r *mutationResolver) UpdateTrustCenter(ctx context.Context, input types.UpdateTrustCenterInput) (*types.UpdateTrustCenterPayload, error) { + if err := r.authorize(ctx, input.TrustCenterID, probo.ActionTrustCenterUpdate); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, input.TrustCenterID.TenantID()) + + trustCenter, file, err := prb.TrustCenters.Update( + ctx, + &probo.UpdateTrustCenterRequest{ + ID: input.TrustCenterID, + Active: input.Active, + SearchEngineIndexing: input.SearchEngineIndexing, + }, + ) + if err != nil { + if validationErrors, ok := errors.AsType[validator.ValidationErrors](err); ok { + return nil, gqlutils.InvalidValidationErrors(ctx, validationErrors) + } + r.logger.ErrorCtx(ctx, "cannot update trust center", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.UpdateTrustCenterPayload{ + TrustCenter: types.NewTrustCenter(trustCenter, file), + }, nil +} + +// UploadTrustCenterNda is the resolver for the uploadTrustCenterNDA field. +func (r *mutationResolver) UploadTrustCenterNda(ctx context.Context, input types.UploadTrustCenterNDAInput) (*types.UploadTrustCenterNDAPayload, error) { + if err := r.authorize(ctx, input.TrustCenterID, probo.ActionTrustCenterNonDisclosureAgreementUpload); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, input.TrustCenterID.TenantID()) + + trustCenter, file, err := prb.TrustCenters.UploadNDA( + ctx, + &probo.UploadTrustCenterNDARequest{ + TrustCenterID: input.TrustCenterID, + File: input.File.File, + FileName: input.FileName, + }, + ) + if err != nil { + if validationErrors, ok := errors.AsType[validator.ValidationErrors](err); ok { + return nil, gqlutils.InvalidValidationErrors(ctx, validationErrors) + } + r.logger.ErrorCtx(ctx, "cannot upload trust center NDA", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.UploadTrustCenterNDAPayload{ + TrustCenter: types.NewTrustCenter(trustCenter, file), + }, nil +} + +// DeleteTrustCenterNda is the resolver for the deleteTrustCenterNDA field. +func (r *mutationResolver) DeleteTrustCenterNda(ctx context.Context, input types.DeleteTrustCenterNDAInput) (*types.DeleteTrustCenterNDAPayload, error) { + if err := r.authorize(ctx, input.TrustCenterID, probo.ActionTrustCenterNonDisclosureAgreementDelete); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, input.TrustCenterID.TenantID()) + + trustCenter, file, err := prb.TrustCenters.DeleteNDA(ctx, input.TrustCenterID) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot delete trust center NDA", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.DeleteTrustCenterNDAPayload{ + TrustCenter: types.NewTrustCenter(trustCenter, file), + }, nil +} + +// UpdateTrustCenterBrand is the resolver for the updateTrustCenterBrand field. +func (r *mutationResolver) UpdateTrustCenterBrand(ctx context.Context, input types.UpdateTrustCenterBrandInput) (*types.UpdateTrustCenterBrandPayload, error) { + if err := r.authorize(ctx, input.TrustCenterID, probo.ActionTrustCenterUpdate); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, input.TrustCenterID.TenantID()) + + req := &probo.UpdateTrustCenterBrandRequest{ + TrustCenterID: input.TrustCenterID, + } + + if input.LogoFile.IsSet() { + logoFile := input.LogoFile.Value() + if logoFile == nil { + var nilFile *probo.FileUpload + req.LogoFile = &nilFile + } else { + fileUpload := &probo.FileUpload{ + Content: logoFile.File, + Filename: logoFile.Filename, + Size: logoFile.Size, + ContentType: logoFile.ContentType, + } + req.LogoFile = &fileUpload + } + } + + if input.DarkLogoFile.IsSet() { + darkLogoFile := input.DarkLogoFile.Value() + if darkLogoFile == nil { + var nilFile *probo.FileUpload + req.DarkLogoFile = &nilFile + } else { + fileUpload := &probo.FileUpload{ + Content: darkLogoFile.File, + Filename: darkLogoFile.Filename, + Size: darkLogoFile.Size, + ContentType: darkLogoFile.ContentType, + } + req.DarkLogoFile = &fileUpload + } + } + + trustCenter, file, err := prb.TrustCenters.UpdateTrustCenterBrand(ctx, req) + if err != nil { + if validationErrors, ok := errors.AsType[validator.ValidationErrors](err); ok { + return nil, gqlutils.InvalidValidationErrors(ctx, validationErrors) + } + r.logger.ErrorCtx(ctx, "cannot update trust center brand", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.UpdateTrustCenterBrandPayload{ + TrustCenter: types.NewTrustCenter(trustCenter, file), + }, nil +} + +// UpdateTrustCenterAccess is the resolver for the updateTrustCenterAccess field. +func (r *mutationResolver) UpdateTrustCenterAccess(ctx context.Context, input types.UpdateTrustCenterAccessInput) (*types.UpdateTrustCenterAccessPayload, error) { + if err := r.authorize(ctx, input.ID, probo.ActionTrustCenterAccessUpdate); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, input.ID.TenantID()) + + var documentAccesses []probo.UpdateTrustCenterDocumentAccessRequest + var reportAccesses []probo.UpdateTrustCenterDocumentAccessRequest + var fileAccesses []probo.UpdateTrustCenterDocumentAccessRequest + for _, documentAccess := range input.Documents { + documentAccesses = append(documentAccesses, probo.UpdateTrustCenterDocumentAccessRequest{ + ID: documentAccess.ID, + Status: documentAccess.Status, + }) + } + for _, reportAccess := range input.Reports { + reportAccesses = append(reportAccesses, probo.UpdateTrustCenterDocumentAccessRequest{ + ID: reportAccess.ID, + Status: reportAccess.Status, + }) + } + for _, fileAccess := range input.TrustCenterFiles { + fileAccesses = append(fileAccesses, probo.UpdateTrustCenterDocumentAccessRequest{ + ID: fileAccess.ID, + Status: fileAccess.Status, + }) + } + access, err := prb.TrustCenterAccesses.Update( + ctx, + &probo.UpdateTrustCenterAccessRequest{ + ID: input.ID, + DocumentAccesses: documentAccesses, + ReportAccesses: reportAccesses, + TrustCenterFileAccesses: fileAccesses, + }, + ) + if err != nil { + if validationErrors, ok := errors.AsType[validator.ValidationErrors](err); ok { + return nil, gqlutils.InvalidValidationErrors(ctx, validationErrors) + } + r.logger.ErrorCtx(ctx, "cannot update trust center access", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.UpdateTrustCenterAccessPayload{ + TrustCenterAccess: types.NewTrustCenterAccess(access), + }, nil +} + +// DeleteTrustCenterAccess is the resolver for the deleteTrustCenterAccess field. +func (r *mutationResolver) DeleteTrustCenterAccess(ctx context.Context, input types.DeleteTrustCenterAccessInput) (*types.DeleteTrustCenterAccessPayload, error) { + if err := r.authorize(ctx, input.ID, probo.ActionTrustCenterAccessDelete); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, input.ID.TenantID()) + + err := prb.TrustCenterAccesses.Delete(ctx, input.ID) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot delete trust center access", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.DeleteTrustCenterAccessPayload{ + DeletedTrustCenterAccessID: input.ID, + }, nil +} + +// CreateTrustCenterReference is the resolver for the createTrustCenterReference field. +func (r *mutationResolver) CreateTrustCenterReference(ctx context.Context, input types.CreateTrustCenterReferenceInput) (*types.CreateTrustCenterReferencePayload, error) { + if err := r.authorize(ctx, input.TrustCenterID, probo.ActionTrustCenterReferenceCreate); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, input.TrustCenterID.TenantID()) + + reference, err := prb.TrustCenterReferences.Create( + ctx, + &probo.CreateTrustCenterReferenceRequest{ + TrustCenterID: input.TrustCenterID, + Name: input.Name, + Description: input.Description, + WebsiteURL: input.WebsiteURL, + LogoFile: probo.File{ + Content: input.LogoFile.File, + Filename: input.LogoFile.Filename, + Size: input.LogoFile.Size, + ContentType: input.LogoFile.ContentType, + }, + }, + ) + if err != nil { + if validationErrors, ok := errors.AsType[validator.ValidationErrors](err); ok { + return nil, gqlutils.InvalidValidationErrors(ctx, validationErrors) + } + r.logger.ErrorCtx(ctx, "cannot create trust center reference", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.CreateTrustCenterReferencePayload{ + TrustCenterReferenceEdge: types.NewTrustCenterReferenceEdge(reference, coredata.TrustCenterReferenceOrderFieldRank), + }, nil +} + +// UpdateTrustCenterReference is the resolver for the updateTrustCenterReference field. +func (r *mutationResolver) UpdateTrustCenterReference(ctx context.Context, input types.UpdateTrustCenterReferenceInput) (*types.UpdateTrustCenterReferencePayload, error) { + if err := r.authorize(ctx, input.ID, probo.ActionTrustCenterReferenceUpdate); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, input.ID.TenantID()) + + req := &probo.UpdateTrustCenterReferenceRequest{ + ID: input.ID, + Name: input.Name, + Description: gqlutils.UnwrapOmittable(input.Description), + WebsiteURL: input.WebsiteURL, + Rank: input.Rank, + } + + if input.LogoFile != nil { + req.LogoFile = &probo.File{ + Content: input.LogoFile.File, + Filename: input.LogoFile.Filename, + Size: input.LogoFile.Size, + ContentType: input.LogoFile.ContentType, + } + } + + reference, err := prb.TrustCenterReferences.Update(ctx, req) + if err != nil { + if validationErrors, ok := errors.AsType[validator.ValidationErrors](err); ok { + return nil, gqlutils.InvalidValidationErrors(ctx, validationErrors) + } + r.logger.ErrorCtx(ctx, "cannot update trust center reference", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.UpdateTrustCenterReferencePayload{ + TrustCenterReference: types.NewTrustCenterReference(reference), + }, nil +} + +// DeleteTrustCenterReference is the resolver for the deleteTrustCenterReference field. +func (r *mutationResolver) DeleteTrustCenterReference(ctx context.Context, input types.DeleteTrustCenterReferenceInput) (*types.DeleteTrustCenterReferencePayload, error) { + if err := r.authorize(ctx, input.ID, probo.ActionTrustCenterReferenceDelete); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, input.ID.TenantID()) + + err := prb.TrustCenterReferences.Delete(ctx, input.ID) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot delete trust center reference", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.DeleteTrustCenterReferencePayload{ + DeletedTrustCenterReferenceID: input.ID, + }, nil +} + +// CreateComplianceFramework is the resolver for the createComplianceFramework field. +func (r *mutationResolver) CreateComplianceFramework(ctx context.Context, input types.CreateComplianceFrameworkInput) (*types.CreateComplianceFrameworkPayload, error) { + if err := r.authorize(ctx, input.TrustCenterID, probo.ActionComplianceFrameworkCreate); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, input.TrustCenterID.TenantID()) + + cf, err := prb.ComplianceFrameworks.Create( + ctx, + &probo.CreateComplianceFrameworkRequest{ + TrustCenterID: input.TrustCenterID, + FrameworkID: input.FrameworkID, + }, + ) + if err != nil { + if validationErrors, ok := errors.AsType[validator.ValidationErrors](err); ok { + return nil, gqlutils.InvalidValidationErrors(ctx, validationErrors) + } + r.logger.ErrorCtx(ctx, "cannot create compliance framework", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.CreateComplianceFrameworkPayload{ + ComplianceFrameworkEdge: types.NewComplianceFrameworkEdge(cf, coredata.ComplianceFrameworkOrderFieldRank), + }, nil +} + +// UpdateComplianceFramework is the resolver for the updateComplianceFramework field. +func (r *mutationResolver) UpdateComplianceFramework(ctx context.Context, input types.UpdateComplianceFrameworkInput) (*types.UpdateComplianceFrameworkPayload, error) { + if err := r.authorize(ctx, input.ID, probo.ActionComplianceFrameworkUpdateRank); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, input.ID.TenantID()) + + cf, err := prb.ComplianceFrameworks.Update(ctx, &probo.UpdateComplianceFrameworkRequest{ + ID: input.ID, + Rank: input.Rank, + }) + if err != nil { + if validationErrors, ok := errors.AsType[validator.ValidationErrors](err); ok { + return nil, gqlutils.InvalidValidationErrors(ctx, validationErrors) + } + r.logger.ErrorCtx(ctx, "cannot update compliance framework", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.UpdateComplianceFrameworkPayload{ + ComplianceFramework: types.NewComplianceFramework(cf), + }, nil +} + +// DeleteComplianceFramework is the resolver for the deleteComplianceFramework field. +func (r *mutationResolver) DeleteComplianceFramework(ctx context.Context, input types.DeleteComplianceFrameworkInput) (*types.DeleteComplianceFrameworkPayload, error) { + if err := r.authorize(ctx, input.ID, probo.ActionComplianceFrameworkDelete); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, input.ID.TenantID()) + + err := prb.ComplianceFrameworks.Delete( + ctx, + &probo.DeleteComplianceFrameworkRequest{ + ID: input.ID, + }, + ) + if err != nil { + if validationErrors, ok := errors.AsType[validator.ValidationErrors](err); ok { + return nil, gqlutils.InvalidValidationErrors(ctx, validationErrors) + } + r.logger.ErrorCtx(ctx, "cannot delete compliance framework", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.DeleteComplianceFrameworkPayload{ + DeletedComplianceFrameworkID: input.ID, + }, nil +} + +// CreateComplianceExternalURL is the resolver for the createComplianceExternalURL field. +func (r *mutationResolver) CreateComplianceExternalURL(ctx context.Context, input types.CreateComplianceExternalURLInput) (*types.CreateComplianceExternalURLPayload, error) { + if err := r.authorize(ctx, input.TrustCenterID, probo.ActionComplianceExternalURLCreate); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, input.TrustCenterID.TenantID()) + + item, err := prb.ComplianceExternalURLs.Create( + ctx, + &probo.CreateComplianceExternalURLRequest{ + TrustCenterID: input.TrustCenterID, + Name: input.Name, + URL: input.URL, + }, + ) + if err != nil { + if validationErrors, ok := errors.AsType[validator.ValidationErrors](err); ok { + return nil, gqlutils.InvalidValidationErrors(ctx, validationErrors) + } + r.logger.ErrorCtx(ctx, "cannot create compliance external URL", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.CreateComplianceExternalURLPayload{ + ComplianceExternalURLEdge: types.NewComplianceExternalURLEdge(item, coredata.ComplianceExternalURLOrderFieldRank), + }, nil +} + +// UpdateComplianceExternalURL is the resolver for the updateComplianceExternalURL field. +func (r *mutationResolver) UpdateComplianceExternalURL(ctx context.Context, input types.UpdateComplianceExternalURLInput) (*types.UpdateComplianceExternalURLPayload, error) { + if err := r.authorize(ctx, input.ID, probo.ActionComplianceExternalURLUpdate); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, input.ID.TenantID()) + + item, err := prb.ComplianceExternalURLs.Update(ctx, &probo.UpdateComplianceExternalURLRequest{ + ID: input.ID, + Name: input.Name, + URL: input.URL, + Rank: input.Rank, + }) + if err != nil { + if validationErrors, ok := errors.AsType[validator.ValidationErrors](err); ok { + return nil, gqlutils.InvalidValidationErrors(ctx, validationErrors) + } + r.logger.ErrorCtx(ctx, "cannot update compliance external URL", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.UpdateComplianceExternalURLPayload{ + ComplianceExternalURL: types.NewComplianceExternalURL(item), + }, nil +} + +// DeleteComplianceExternalURL is the resolver for the deleteComplianceExternalURL field. +func (r *mutationResolver) DeleteComplianceExternalURL(ctx context.Context, input types.DeleteComplianceExternalURLInput) (*types.DeleteComplianceExternalURLPayload, error) { + if err := r.authorize(ctx, input.ID, probo.ActionComplianceExternalURLDelete); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, input.ID.TenantID()) + + if err := prb.ComplianceExternalURLs.Delete(ctx, &probo.DeleteComplianceExternalURLRequest{ID: input.ID}); err != nil { + if validationErrors, ok := errors.AsType[validator.ValidationErrors](err); ok { + return nil, gqlutils.InvalidValidationErrors(ctx, validationErrors) + } + r.logger.ErrorCtx(ctx, "cannot delete compliance external URL", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.DeleteComplianceExternalURLPayload{ + DeletedComplianceExternalURLID: input.ID, + }, nil +} + +// CreateTrustCenterFile is the resolver for the createTrustCenterFile field. +func (r *mutationResolver) CreateTrustCenterFile(ctx context.Context, input types.CreateTrustCenterFileInput) (*types.CreateTrustCenterFilePayload, error) { + if err := r.authorize(ctx, input.OrganizationID, probo.ActionTrustCenterFileCreate); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, input.OrganizationID.TenantID()) + + file, err := prb.TrustCenterFiles.Create( + ctx, + &probo.CreateTrustCenterFileRequest{ + OrganizationID: input.OrganizationID, + Name: input.Name, + Category: input.Category, + File: probo.File{ + Content: input.File.File, + Filename: input.File.Filename, + Size: input.File.Size, + ContentType: input.File.ContentType, + }, + TrustCenterVisibility: input.TrustCenterVisibility, + }, + ) + if err != nil { + if validationErrors, ok := errors.AsType[validator.ValidationErrors](err); ok { + return nil, gqlutils.InvalidValidationErrors(ctx, validationErrors) + } + r.logger.ErrorCtx(ctx, "cannot create trust center file", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.CreateTrustCenterFilePayload{ + TrustCenterFileEdge: types.NewTrustCenterFileEdge(file, coredata.TrustCenterFileOrderFieldCreatedAt), + }, nil +} + +// UpdateTrustCenterFile is the resolver for the updateTrustCenterFile field. +func (r *mutationResolver) UpdateTrustCenterFile(ctx context.Context, input types.UpdateTrustCenterFileInput) (*types.UpdateTrustCenterFilePayload, error) { + if err := r.authorize(ctx, input.ID, probo.ActionTrustCenterFileUpdate); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, input.ID.TenantID()) + + file, err := prb.TrustCenterFiles.Update( + ctx, + &probo.UpdateTrustCenterFileRequest{ + ID: input.ID, + Name: input.Name, + Category: input.Category, + TrustCenterVisibility: input.TrustCenterVisibility, + }, + ) + if err != nil { + if validationErrors, ok := errors.AsType[validator.ValidationErrors](err); ok { + return nil, gqlutils.InvalidValidationErrors(ctx, validationErrors) + } + r.logger.ErrorCtx(ctx, "cannot update trust center file", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.UpdateTrustCenterFilePayload{ + TrustCenterFile: types.NewTrustCenterFile(file), + }, nil +} + +// GetTrustCenterFile is the resolver for the getTrustCenterFile field. +func (r *mutationResolver) GetTrustCenterFile(ctx context.Context, input types.GetTrustCenterFileInput) (*types.GetTrustCenterFilePayload, error) { + if err := r.authorize(ctx, input.ID, probo.ActionTrustCenterFileGet); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, input.ID.TenantID()) + + file, err := prb.TrustCenterFiles.Get(ctx, input.ID) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot get trust center file", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.GetTrustCenterFilePayload{ + TrustCenterFile: types.NewTrustCenterFile(file), + }, nil +} + +// DeleteTrustCenterFile is the resolver for the deleteTrustCenterFile field. +func (r *mutationResolver) DeleteTrustCenterFile(ctx context.Context, input types.DeleteTrustCenterFileInput) (*types.DeleteTrustCenterFilePayload, error) { + if err := r.authorize(ctx, input.ID, probo.ActionTrustCenterFileDelete); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, input.ID.TenantID()) + + err := prb.TrustCenterFiles.Delete(ctx, input.ID) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot delete trust center file", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.DeleteTrustCenterFilePayload{ + DeletedTrustCenterFileID: input.ID, + }, nil +} + +// CreateCustomDomain is the resolver for the createCustomDomain field. +func (r *mutationResolver) CreateCustomDomain(ctx context.Context, input types.CreateCustomDomainInput) (*types.CreateCustomDomainPayload, error) { + if err := r.authorize(ctx, input.OrganizationID, probo.ActionCustomDomainCreate); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, input.OrganizationID.TenantID()) + + domain, err := prb.CustomDomains.CreateCustomDomain( + ctx, + probo.CreateCustomDomainRequest{ + OrganizationID: input.OrganizationID, + Domain: input.Domain, + }, + ) + if err != nil { + if validationErrors, ok := errors.AsType[validator.ValidationErrors](err); ok { + return nil, gqlutils.InvalidValidationErrors(ctx, validationErrors) + } + r.logger.ErrorCtx(ctx, "cannot create custom domain", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.CreateCustomDomainPayload{ + CustomDomain: types.NewCustomDomain(domain, r.customDomainCname), + }, nil +} + +// DeleteCustomDomain is the resolver for the deleteCustomDomain field. +func (r *mutationResolver) DeleteCustomDomain(ctx context.Context, input types.DeleteCustomDomainInput) (*types.DeleteCustomDomainPayload, error) { + if err := r.authorize(ctx, input.OrganizationID, probo.ActionCustomDomainDelete); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, input.OrganizationID.TenantID()) + + // TODO Drop this wierd logic + // Get the current custom domain ID before deleting + domain, err := prb.CustomDomains.GetOrganizationCustomDomain(ctx, input.OrganizationID) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot get custom domain", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + if domain == nil { + return nil, fmt.Errorf("organization has no custom domain") + } + + deletedDomainID := domain.ID + + if err := prb.CustomDomains.DeleteCustomDomain(ctx, input.OrganizationID); err != nil { + r.logger.ErrorCtx(ctx, "cannot delete custom domain", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.DeleteCustomDomainPayload{ + DeletedCustomDomainID: deletedDomainID, + }, nil +} + +// TrustCenter is the resolver for the trustCenter field. +func (r *organizationResolver) TrustCenter(ctx context.Context, obj *types.Organization) (*types.TrustCenter, error) { + if err := r.authorize(ctx, obj.ID, probo.ActionTrustCenterGet); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, obj.ID.TenantID()) + + trustCenter, err := prb.TrustCenters.GetByOrganizationID(ctx, obj.ID) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot get trust center", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + var file *coredata.File + if trustCenter.NonDisclosureAgreementFileID != nil { + file, err = prb.Files.Get(ctx, *trustCenter.NonDisclosureAgreementFileID) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot get NDA file", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + } + + return types.NewTrustCenter(trustCenter, file), nil +} + +// CustomDomain is the resolver for the customDomain field. +func (r *organizationResolver) CustomDomain(ctx context.Context, obj *types.Organization) (*types.CustomDomain, error) { + if err := r.authorize(ctx, obj.ID, probo.ActionCustomDomainGet); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, obj.ID.TenantID()) + + domain, err := prb.CustomDomains.GetOrganizationCustomDomain(ctx, obj.ID) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot get custom domain", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + if domain == nil { + return nil, nil + } + + return types.NewCustomDomain(domain, r.customDomainCname), nil +} + +// TrustCenterFiles is the resolver for the trustCenterFiles field. +func (r *organizationResolver) TrustCenterFiles(ctx context.Context, obj *types.Organization, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.OrderBy[coredata.TrustCenterFileOrderField]) (*types.TrustCenterFileConnection, error) { + if err := r.authorize(ctx, obj.ID, probo.ActionTrustCenterFileList); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, obj.ID.TenantID()) + + pageOrderBy := page.OrderBy[coredata.TrustCenterFileOrderField]{ + Field: coredata.TrustCenterFileOrderFieldCreatedAt, + Direction: page.OrderDirectionDesc, + } + if orderBy != nil { + pageOrderBy = page.OrderBy[coredata.TrustCenterFileOrderField]{ + Field: orderBy.Field, + Direction: orderBy.Direction, + } + } + + cursor := types.NewCursor(first, after, last, before, pageOrderBy) + + pageResult, err := prb.TrustCenterFiles.ListForOrganizationID(ctx, obj.ID, cursor, &coredata.TrustCenterFileFilter{}) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot list organization trust center files", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return types.NewTrustCenterFileConnection(pageResult, obj.ID), nil +} + +// LogoFileURL is the resolver for the logoFileUrl field. +func (r *trustCenterResolver) LogoFileURL(ctx context.Context, obj *types.TrustCenter) (*string, error) { + if err := r.authorize(ctx, obj.ID, probo.ActionTrustCenterGet); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, obj.ID.TenantID()) + + logoURL, err := prb.TrustCenters.GenerateLogoURL(ctx, obj.ID, 1*time.Hour) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot generate logo url", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return logoURL, nil +} + +// DarkLogoFileURL is the resolver for the darkLogoFileUrl field. +func (r *trustCenterResolver) DarkLogoFileURL(ctx context.Context, obj *types.TrustCenter) (*string, error) { + if err := r.authorize(ctx, obj.ID, probo.ActionTrustCenterGet); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, obj.ID.TenantID()) + + logoURL, err := prb.TrustCenters.GenerateDarkLogoURL(ctx, obj.ID, 1*time.Hour) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot generate logo url", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return logoURL, nil +} + +// NdaFileURL is the resolver for the ndaFileUrl field. +func (r *trustCenterResolver) NdaFileURL(ctx context.Context, obj *types.TrustCenter) (*string, error) { + hasPermission, err := r.Resolver.Permission(ctx, obj, probo.ActionTrustCenterGetNda) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot authorize", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + if !hasPermission { + return nil, nil + } + + prb := r.ProboService(ctx, obj.ID.TenantID()) + + fileURL, err := prb.TrustCenters.GenerateNDAFileURL(ctx, obj.ID, 15*time.Minute) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot generate NDA file URL", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return fileURL, nil +} + +// Organization is the resolver for the organization field. +func (r *trustCenterResolver) Organization(ctx context.Context, obj *types.TrustCenter) (*types.Organization, error) { + if err := r.authorize(ctx, obj.ID, probo.ActionOrganizationGet); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, obj.ID.TenantID()) + + trustCenter, err := prb.TrustCenters.Get(ctx, obj.ID) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot get trust center", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + organization, err := prb.Organizations.Get(ctx, trustCenter.OrganizationID) + if err != nil { + if errors.Is(err, coredata.ErrResourceNotFound) { + return nil, gqlutils.NotFound(ctx, err) + } + + r.logger.ErrorCtx(ctx, "cannot get organization", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return types.NewOrganization(organization), nil +} + +// Accesses is the resolver for the accesses field. +func (r *trustCenterResolver) Accesses(ctx context.Context, obj *types.TrustCenter, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.OrderBy[coredata.TrustCenterAccessOrderField]) (*types.TrustCenterAccessConnection, error) { + if err := r.authorize(ctx, obj.ID, probo.ActionTrustCenterAccessList); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, obj.ID.TenantID()) + + pageOrderBy := page.OrderBy[coredata.TrustCenterAccessOrderField]{ + Field: coredata.TrustCenterAccessOrderFieldCreatedAt, + Direction: page.OrderDirectionDesc, + } + if orderBy != nil { + pageOrderBy = page.OrderBy[coredata.TrustCenterAccessOrderField]{ + Field: orderBy.Field, + Direction: orderBy.Direction, + } + } + + cursor := types.NewCursor(first, after, last, before, pageOrderBy) + + result, err := prb.TrustCenterAccesses.ListForTrustCenterID(ctx, obj.ID, cursor) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot list trust center accesses", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return types.NewTrustCenterAccessConnection(result), nil +} + +// References is the resolver for the references field. +func (r *trustCenterResolver) References(ctx context.Context, obj *types.TrustCenter, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.OrderBy[coredata.TrustCenterReferenceOrderField]) (*types.TrustCenterReferenceConnection, error) { + if err := r.authorize(ctx, obj.ID, probo.ActionTrustCenterReferenceList); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, obj.ID.TenantID()) + + pageOrderBy := page.OrderBy[coredata.TrustCenterReferenceOrderField]{ + Field: coredata.TrustCenterReferenceOrderFieldRank, + Direction: page.OrderDirectionAsc, + } + if orderBy != nil { + pageOrderBy = page.OrderBy[coredata.TrustCenterReferenceOrderField]{ + Field: orderBy.Field, + Direction: orderBy.Direction, + } + } + + cursor := types.NewCursor(first, after, last, before, pageOrderBy) + + result, err := prb.TrustCenterReferences.ListForTrustCenterID(ctx, obj.ID, cursor) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot list trust center references", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return types.NewTrustCenterReferenceConnection(result, obj.ID), nil +} + +// ComplianceFrameworks is the resolver for the complianceFrameworks field. +func (r *trustCenterResolver) ComplianceFrameworks(ctx context.Context, obj *types.TrustCenter, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.OrderBy[coredata.ComplianceFrameworkOrderField]) (*types.ComplianceFrameworkConnection, error) { + if err := r.authorize(ctx, obj.ID, probo.ActionComplianceFrameworkList); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, obj.ID.TenantID()) + + pageOrderBy := page.OrderBy[coredata.ComplianceFrameworkOrderField]{ + Field: coredata.ComplianceFrameworkOrderFieldRank, + Direction: page.OrderDirectionAsc, + } + if orderBy != nil { + pageOrderBy = page.OrderBy[coredata.ComplianceFrameworkOrderField]{ + Field: orderBy.Field, + Direction: orderBy.Direction, + } + } + + cursor := types.NewCursor(first, after, last, before, pageOrderBy) + + result, err := prb.ComplianceFrameworks.ListWithHiddenForTrustCenterID(ctx, obj.ID, cursor) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot list compliance frameworks", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return types.NewComplianceFrameworkConnection(result), nil +} + +// ExternalUrls is the resolver for the externalUrls field. +func (r *trustCenterResolver) ExternalUrls(ctx context.Context, obj *types.TrustCenter, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.OrderBy[coredata.ComplianceExternalURLOrderField]) (*types.ComplianceExternalURLConnection, error) { + if err := r.authorize(ctx, obj.ID, probo.ActionComplianceExternalURLList); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, obj.ID.TenantID()) + + pageOrderBy := page.OrderBy[coredata.ComplianceExternalURLOrderField]{ + Field: coredata.ComplianceExternalURLOrderFieldRank, + Direction: page.OrderDirectionAsc, + } + if orderBy != nil { + pageOrderBy = page.OrderBy[coredata.ComplianceExternalURLOrderField]{ + Field: orderBy.Field, + Direction: orderBy.Direction, + } + } + + cursor := types.NewCursor(first, after, last, before, pageOrderBy) + + result, err := prb.ComplianceExternalURLs.List(ctx, obj.ID, cursor) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot list compliance external URLs", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return types.NewComplianceExternalURLConnection(result), nil +} + +// MailingList is the resolver for the mailingList field. +func (r *trustCenterResolver) MailingList(ctx context.Context, obj *types.TrustCenter) (*types.MailingList, error) { + if err := r.authorize(ctx, obj.ID, probo.ActionMailingListSubscriberList); err != nil { + return nil, err + } + + if obj.MailingList != nil { + return obj.MailingList, nil + } + + prb := r.ProboService(ctx, obj.ID.TenantID()) + + ml, err := prb.TrustCenters.GetMailingList(ctx, obj.ID) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot get mailing list for trust center", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + if ml == nil { + return nil, nil + } + + return types.NewMailingList(ml), nil +} + +// Permission is the resolver for the permission field. +func (r *trustCenterResolver) Permission(ctx context.Context, obj *types.TrustCenter, action string) (bool, error) { + return r.Resolver.Permission(ctx, obj, action) +} + +// NdaSignature is the resolver for the ndaSignature field. +func (r *trustCenterAccessResolver) NdaSignature(ctx context.Context, obj *types.TrustCenterAccess) (*types.ElectronicSignature, error) { + if err := r.authorize(ctx, obj.ID, probo.ActionTrustCenterAccessGet); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, obj.ID.TenantID()) + + access, err := prb.TrustCenterAccesses.Get(ctx, obj.ID) + if err != nil { + return nil, fmt.Errorf("cannot load trust center access: %w", err) + } + + if access.ElectronicSignatureID == nil { + return nil, nil + } + + sig, err := r.esign.GetSignatureByID(ctx, *access.ElectronicSignatureID) + if err != nil { + return nil, nil + } + + return types.NewElectronicSignature(sig), nil +} + +// PendingRequestCount is the resolver for the pendingRequestCount field. +func (r *trustCenterAccessResolver) PendingRequestCount(ctx context.Context, obj *types.TrustCenterAccess) (int, error) { + if err := r.authorize(ctx, obj.ID, probo.ActionTrustCenterAccessGet); err != nil { + return 0, err + } + + prb := r.ProboService(ctx, obj.ID.TenantID()) + + count, err := prb.TrustCenterAccesses.CountPendingRequestDocumentAccesses(ctx, obj.ID) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot count pending request document accesses", log.Error(err)) + return 0, gqlutils.Internal(ctx) + } + + return count, nil +} + +// ActiveCount is the resolver for the activeCount field. +func (r *trustCenterAccessResolver) ActiveCount(ctx context.Context, obj *types.TrustCenterAccess) (int, error) { + if err := r.authorize(ctx, obj.ID, probo.ActionTrustCenterAccessGet); err != nil { + return 0, err + } + prb := r.ProboService(ctx, obj.ID.TenantID()) + + count, err := prb.TrustCenterAccesses.CountActiveDocumentAccesses(ctx, obj.ID) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot count active document accesses", log.Error(err)) + return 0, gqlutils.Internal(ctx) + } + + return count, nil +} + +// Profile is the resolver for the profile field. +func (r *trustCenterAccessResolver) Profile(ctx context.Context, obj *types.TrustCenterAccess) (*types.Profile, error) { + if err := r.authorize(ctx, obj.ID, iam.ActionMembershipProfileGet); err != nil { + return nil, err + } + + profile, err := r.iam.OrganizationService.GetProfileForIdentityAndOrganization(ctx, obj.IdentityID, obj.OrganizationID) + if err != nil { + if _, ok := errors.AsType[*iam.ErrProfileNotFound](err); ok { + return nil, gqlutils.NotFound(ctx, err) + } + + r.logger.ErrorCtx(ctx, "cannot get profile", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return types.NewProfile(profile), nil +} + +// AvailableDocumentAccesses is the resolver for the availableDocumentAccesses field. +func (r *trustCenterAccessResolver) AvailableDocumentAccesses(ctx context.Context, obj *types.TrustCenterAccess, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.OrderBy[coredata.TrustCenterDocumentAccessOrderField]) (*types.TrustCenterDocumentAccessConnection, error) { + if err := r.authorize(ctx, obj.ID, probo.ActionTrustCenterAccessGet); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, obj.ID.TenantID()) + + pageOrderBy := page.OrderBy[coredata.TrustCenterDocumentAccessOrderField]{ + Field: coredata.TrustCenterDocumentAccessOrderFieldCreatedAt, + Direction: page.OrderDirectionDesc, + } + if orderBy != nil { + pageOrderBy = page.OrderBy[coredata.TrustCenterDocumentAccessOrderField]{ + Field: orderBy.Field, + Direction: orderBy.Direction, + } + } + + cursor := types.NewCursor(first, after, last, before, pageOrderBy) + + result, err := prb.TrustCenterAccesses.ListAvailableDocumentAccesses(ctx, obj.ID, cursor) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot list trust center document accesses", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return types.NewTrustCenterDocumentAccessConnection(result, obj, obj.ID), nil +} + +// Permission is the resolver for the permission field. +func (r *trustCenterAccessResolver) Permission(ctx context.Context, obj *types.TrustCenterAccess, action string) (bool, error) { + return r.Resolver.Permission(ctx, obj, action) +} + +// Document is the resolver for the document field. +func (r *trustCenterDocumentAccessResolver) Document(ctx context.Context, obj *types.TrustCenterDocumentAccess) (*types.Document, error) { + if err := r.authorize(ctx, obj.ID, probo.ActionDocumentGet); err != nil { + return nil, err + } + + if obj.DocumentID == nil { + return nil, nil + } + + prb := r.ProboService(ctx, obj.TrustCenterAccessID.TenantID()) + + document, err := prb.Documents.Get(ctx, *obj.DocumentID) + if err != nil { + if errors.Is(err, coredata.ErrResourceNotFound) { + return nil, gqlutils.NotFound(ctx, err) + } + + r.logger.ErrorCtx(ctx, "cannot load document", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return types.NewDocument(document), nil +} + +// Report is the resolver for the report field. +func (r *trustCenterDocumentAccessResolver) Report(ctx context.Context, obj *types.TrustCenterDocumentAccess) (*types.Report, error) { + if err := r.authorize(ctx, obj.TrustCenterAccessID, probo.ActionReportGet); err != nil { + return nil, err + } + + if obj.ReportID == nil { + return nil, nil + } + + prb := r.ProboService(ctx, obj.TrustCenterAccessID.TenantID()) + + report, err := prb.Reports.Get(ctx, *obj.ReportID) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot load report", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return types.NewReport(report), nil +} + +// TrustCenterFile is the resolver for the trustCenterFile field. +func (r *trustCenterDocumentAccessResolver) TrustCenterFile(ctx context.Context, obj *types.TrustCenterDocumentAccess) (*types.TrustCenterFile, error) { + if err := r.authorize(ctx, obj.TrustCenterAccessID, probo.ActionTrustCenterFileGet); err != nil { + return nil, err + } + + if obj.TrustCenterFileID == nil { + return nil, nil + } + + prb := r.ProboService(ctx, obj.TrustCenterAccessID.TenantID()) + + trustCenterFile, err := prb.TrustCenterFiles.Get(ctx, *obj.TrustCenterFileID) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot load trust center file", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return types.NewTrustCenterFile(trustCenterFile), nil +} + +// TotalCount is the resolver for the totalCount field. +func (r *trustCenterDocumentAccessConnectionResolver) TotalCount(ctx context.Context, obj *types.TrustCenterDocumentAccessConnection) (int, error) { + if err := r.authorize(ctx, obj.ParentID, probo.ActionTrustCenterDocumentAccessList); err != nil { + return 0, err + } + + prb := r.ProboService(ctx, obj.ParentID.TenantID()) + + count, err := prb.TrustCenterAccesses.CountDocumentAccesses(ctx, obj.ParentID) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot count trust center document accesses", log.Error(err)) + return 0, gqlutils.Internal(ctx) + } + + return count, nil +} + +// FileURL is the resolver for the fileUrl field. +func (r *trustCenterFileResolver) FileURL(ctx context.Context, obj *types.TrustCenterFile) (string, error) { + if err := r.authorize(ctx, obj.ID, probo.ActionTrustCenterFileGetFileUrl); err != nil { + return "", err + } + + prb := r.ProboService(ctx, obj.ID.TenantID()) + + fileURL, err := prb.TrustCenterFiles.GenerateFileURL(ctx, obj.ID, 1*time.Hour) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot generate file URL", log.Error(err)) + return "", gqlutils.Internal(ctx) + } + + return fileURL, nil +} + +// Organization is the resolver for the organization field. +func (r *trustCenterFileResolver) Organization(ctx context.Context, obj *types.TrustCenterFile) (*types.Organization, error) { + if err := r.authorize(ctx, obj.ID, probo.ActionOrganizationGet); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, obj.ID.TenantID()) + + trustCenterFile, err := prb.TrustCenterFiles.Get(ctx, obj.ID) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot get trust center file", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + organization, err := prb.Organizations.Get(ctx, trustCenterFile.OrganizationID) + if err != nil { + if errors.Is(err, coredata.ErrResourceNotFound) { + return nil, gqlutils.NotFound(ctx, err) + } + + r.logger.ErrorCtx(ctx, "cannot get organization", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return types.NewOrganization(organization), nil +} + +// Permission is the resolver for the permission field. +func (r *trustCenterFileResolver) Permission(ctx context.Context, obj *types.TrustCenterFile, action string) (bool, error) { + return r.Resolver.Permission(ctx, obj, action) +} + +// TotalCount is the resolver for the totalCount field. +func (r *trustCenterFileConnectionResolver) TotalCount(ctx context.Context, obj *types.TrustCenterFileConnection) (int, error) { + if err := r.authorize(ctx, obj.ParentID, probo.ActionTrustCenterFileList); err != nil { + return 0, err + } + + prb := r.ProboService(ctx, obj.ParentID.TenantID()) + + count, err := prb.TrustCenterFiles.CountForOrganizationID(ctx, obj.ParentID) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot count trust center files", log.Error(err)) + return 0, gqlutils.Internal(ctx) + } + return count, nil +} + +// LogoURL is the resolver for the logoUrl field. +func (r *trustCenterReferenceResolver) LogoURL(ctx context.Context, obj *types.TrustCenterReference) (string, error) { + if err := r.authorize(ctx, obj.ID, probo.ActionTrustCenterReferenceGetLogoUrl); err != nil { + return "", err + } + + prb := r.ProboService(ctx, obj.ID.TenantID()) + + fileURL, err := prb.TrustCenterReferences.GenerateLogoURL(ctx, obj.ID, 1*time.Hour) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot generate logo URL", log.Error(err)) + return "", gqlutils.Internal(ctx) + } + + return fileURL, nil +} + +// Permission is the resolver for the permission field. +func (r *trustCenterReferenceResolver) Permission(ctx context.Context, obj *types.TrustCenterReference, action string) (bool, error) { + return r.Resolver.Permission(ctx, obj, action) +} + +// TotalCount is the resolver for the totalCount field. +func (r *trustCenterReferenceConnectionResolver) TotalCount(ctx context.Context, obj *types.TrustCenterReferenceConnection) (int, error) { + if err := r.authorize(ctx, obj.ParentID, probo.ActionTrustCenterReferenceList); err != nil { + return 0, err + } + + prb := r.ProboService(ctx, obj.ParentID.TenantID()) + + count, err := prb.TrustCenterReferences.CountForTrustCenterID(ctx, obj.ParentID) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot count trust center references", log.Error(err)) + return 0, gqlutils.Internal(ctx) + } + + return count, nil +} + +// ComplianceExternalURL returns schema.ComplianceExternalURLResolver implementation. +func (r *Resolver) ComplianceExternalURL() schema.ComplianceExternalURLResolver { + return &complianceExternalURLResolver{r} +} + +// ComplianceFramework returns schema.ComplianceFrameworkResolver implementation. +func (r *Resolver) ComplianceFramework() schema.ComplianceFrameworkResolver { + return &complianceFrameworkResolver{r} +} + +// CustomDomain returns schema.CustomDomainResolver implementation. +func (r *Resolver) CustomDomain() schema.CustomDomainResolver { return &customDomainResolver{r} } + +// TrustCenter returns schema.TrustCenterResolver implementation. +func (r *Resolver) TrustCenter() schema.TrustCenterResolver { return &trustCenterResolver{r} } + +// TrustCenterAccess returns schema.TrustCenterAccessResolver implementation. +func (r *Resolver) TrustCenterAccess() schema.TrustCenterAccessResolver { + return &trustCenterAccessResolver{r} +} + +// TrustCenterDocumentAccess returns schema.TrustCenterDocumentAccessResolver implementation. +func (r *Resolver) TrustCenterDocumentAccess() schema.TrustCenterDocumentAccessResolver { + return &trustCenterDocumentAccessResolver{r} +} + +// TrustCenterDocumentAccessConnection returns schema.TrustCenterDocumentAccessConnectionResolver implementation. +func (r *Resolver) TrustCenterDocumentAccessConnection() schema.TrustCenterDocumentAccessConnectionResolver { + return &trustCenterDocumentAccessConnectionResolver{r} +} + +// TrustCenterFile returns schema.TrustCenterFileResolver implementation. +func (r *Resolver) TrustCenterFile() schema.TrustCenterFileResolver { + return &trustCenterFileResolver{r} +} + +// TrustCenterFileConnection returns schema.TrustCenterFileConnectionResolver implementation. +func (r *Resolver) TrustCenterFileConnection() schema.TrustCenterFileConnectionResolver { + return &trustCenterFileConnectionResolver{r} +} + +// TrustCenterReference returns schema.TrustCenterReferenceResolver implementation. +func (r *Resolver) TrustCenterReference() schema.TrustCenterReferenceResolver { + return &trustCenterReferenceResolver{r} +} + +// TrustCenterReferenceConnection returns schema.TrustCenterReferenceConnectionResolver implementation. +func (r *Resolver) TrustCenterReferenceConnection() schema.TrustCenterReferenceConnectionResolver { + return &trustCenterReferenceConnectionResolver{r} +} + +type complianceExternalURLResolver struct{ *Resolver } +type complianceFrameworkResolver struct{ *Resolver } +type customDomainResolver struct{ *Resolver } +type trustCenterResolver struct{ *Resolver } +type trustCenterAccessResolver struct{ *Resolver } +type trustCenterDocumentAccessResolver struct{ *Resolver } +type trustCenterDocumentAccessConnectionResolver struct{ *Resolver } +type trustCenterFileResolver struct{ *Resolver } +type trustCenterFileConnectionResolver struct{ *Resolver } +type trustCenterReferenceResolver struct{ *Resolver } +type trustCenterReferenceConnectionResolver struct{ *Resolver } diff --git a/pkg/server/api/console/v1/v1_resolver.go b/pkg/server/api/console/v1/v1_resolver.go deleted file mode 100644 index d4b5a2f56..000000000 --- a/pkg/server/api/console/v1/v1_resolver.go +++ /dev/null @@ -1,12305 +0,0 @@ -package console_v1 - -// This file will be automatically regenerated based on the schema, any resolver -// implementations -// will be copied through when generating and any unknown code will be moved to the end. -// Code generated by github.com/99designs/gqlgen version v0.17.87 - -import ( - "context" - "encoding/base64" - "encoding/json" - "errors" - "fmt" - "net" - "time" - - pgx "github.com/jackc/pgx/v5" - "github.com/vikstrous/dataloadgen" - "go.gearno.de/kit/log" - "go.probo.inc/probo/pkg/accessreview" - "go.probo.inc/probo/pkg/accessreview/drivers" - "go.probo.inc/probo/pkg/connector" - "go.probo.inc/probo/pkg/coredata" - "go.probo.inc/probo/pkg/gid" - "go.probo.inc/probo/pkg/iam" - "go.probo.inc/probo/pkg/mailman" - "go.probo.inc/probo/pkg/page" - "go.probo.inc/probo/pkg/probo" - "go.probo.inc/probo/pkg/server/api/authn" - "go.probo.inc/probo/pkg/server/api/console/v1/dataloader" - "go.probo.inc/probo/pkg/server/api/console/v1/schema" - "go.probo.inc/probo/pkg/server/api/console/v1/types" - "go.probo.inc/probo/pkg/server/gqlutils" - "go.probo.inc/probo/pkg/server/gqlutils/types/cursor" - "go.probo.inc/probo/pkg/slack" - "go.probo.inc/probo/pkg/validator" -) - -// Campaign is the resolver for the campaign field. -func (r *accessEntryResolver) Campaign(ctx context.Context, obj *types.AccessEntry) (*types.AccessReviewCampaign, error) { - if err := r.authorize(ctx, obj.Campaign.ID, probo.ActionAccessReviewCampaignGet); err != nil { - return nil, err - } - - scope := coredata.NewScopeFromObjectID(obj.Campaign.ID) - - campaign, err := r.accessReview.Campaigns(scope).Get(ctx, obj.Campaign.ID) - if err != nil { - if errors.Is(err, coredata.ErrResourceNotFound) { - return nil, gqlutils.NotFound(ctx, err) - } - panic(fmt.Errorf("cannot get access review campaign: %w", err)) - } - - return types.NewAccessReviewCampaign(campaign), nil -} - -// AccessSource is the resolver for the accessSource field. -func (r *accessEntryResolver) AccessSource(ctx context.Context, obj *types.AccessEntry) (*types.AccessSource, error) { - if err := r.authorize(ctx, obj.AccessSource.ID, probo.ActionAccessSourceGet); err != nil { - return nil, err - } - - scope := coredata.NewScopeFromObjectID(obj.AccessSource.ID) - - source, err := r.accessReview.Sources(scope).Get(ctx, obj.AccessSource.ID) - if err != nil { - if errors.Is(err, coredata.ErrResourceNotFound) { - return nil, gqlutils.NotFound(ctx, err) - } - panic(fmt.Errorf("cannot get access source: %w", err)) - } - - return types.NewAccessSource(source), nil -} - -// DecisionHistory is the resolver for the decisionHistory field. -func (r *accessEntryResolver) DecisionHistory(ctx context.Context, obj *types.AccessEntry) ([]*types.AccessEntryDecisionHistoryEntry, error) { - if err := r.authorize(ctx, obj.ID, probo.ActionAccessEntryGet); err != nil { - return nil, err - } - - scope := coredata.NewScopeFromObjectID(obj.ID) - - histories, err := r.accessReview.Entries(scope).DecisionHistory(ctx, obj.ID) - if err != nil { - panic(fmt.Errorf("cannot get decision history: %w", err)) - } - - result := make([]*types.AccessEntryDecisionHistoryEntry, len(histories)) - for i, h := range histories { - result[i] = types.NewAccessEntryDecisionHistoryEntry(h) - } - - return result, nil -} - -// Permission is the resolver for the permission field. -func (r *accessEntryResolver) Permission(ctx context.Context, obj *types.AccessEntry, action string) (bool, error) { - return r.Resolver.Permission(ctx, obj, action) -} - -// TotalCount is the resolver for the totalCount field. -func (r *accessEntryConnectionResolver) TotalCount(ctx context.Context, obj *types.AccessEntryConnection) (int, error) { - scope := coredata.NewScopeFromObjectID(obj.ParentID) - - switch obj.Resolver.(type) { - case *accessReviewCampaignResolver: - if obj.SourceID != nil { - count, err := r.accessReview.Entries(scope).CountForCampaignIDAndSourceID(ctx, obj.ParentID, *obj.SourceID, obj.Filter) - if err != nil { - panic(fmt.Errorf("cannot count access entries: %w", err)) - } - return count, nil - } - count, err := r.accessReview.Entries(scope).CountForCampaignID(ctx, obj.ParentID, obj.Filter) - if err != nil { - panic(fmt.Errorf("cannot count access entries: %w", err)) - } - return count, nil - } - - panic(fmt.Errorf("unsupported resolver: %T", obj.Resolver)) -} - -// Organization is the resolver for the organization field. -func (r *accessReviewResolver) Organization(ctx context.Context, obj *types.AccessReview) (*types.Organization, error) { - return obj.Organization, nil -} - -// IdentitySource is the resolver for the identitySource field. -func (r *accessReviewResolver) IdentitySource(ctx context.Context, obj *types.AccessReview) (*types.AccessSource, error) { - return obj.IdentitySource, nil -} - -// AccessSources is the resolver for the accessSources field. -func (r *accessReviewResolver) AccessSources(ctx context.Context, obj *types.AccessReview, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.AccessSourceOrder) (*types.AccessSourceConnection, error) { - if err := r.authorize(ctx, obj.Organization.ID, probo.ActionAccessSourceList); err != nil { - return nil, err - } - - scope := coredata.NewScopeFromObjectID(obj.Organization.ID) - - pageOrderBy := page.OrderBy[coredata.AccessSourceOrderField]{ - Field: coredata.AccessSourceOrderFieldCreatedAt, - Direction: page.OrderDirectionDesc, - } - if orderBy != nil { - pageOrderBy = page.OrderBy[coredata.AccessSourceOrderField]{ - Field: orderBy.Field, - Direction: orderBy.Direction, - } - } - - cursor := types.NewCursor(first, after, last, before, pageOrderBy) - - p, err := r.accessReview.Sources(scope).ListForOrganizationID(ctx, obj.Organization.ID, cursor) - if err != nil { - panic(fmt.Errorf("cannot list access sources: %w", err)) - } - - return types.NewAccessSourceConnection(p, r, obj.Organization.ID), nil -} - -// Campaigns is the resolver for the campaigns field. -func (r *accessReviewResolver) Campaigns(ctx context.Context, obj *types.AccessReview, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.AccessReviewCampaignOrder) (*types.AccessReviewCampaignConnection, error) { - if err := r.authorize(ctx, obj.Organization.ID, probo.ActionAccessReviewCampaignList); err != nil { - return nil, err - } - - scope := coredata.NewScopeFromObjectID(obj.Organization.ID) - - pageOrderBy := page.OrderBy[coredata.AccessReviewCampaignOrderField]{ - Field: coredata.AccessReviewCampaignOrderFieldCreatedAt, - Direction: page.OrderDirectionDesc, - } - if orderBy != nil { - pageOrderBy = page.OrderBy[coredata.AccessReviewCampaignOrderField]{ - Field: orderBy.Field, - Direction: orderBy.Direction, - } - } - - cursor := types.NewCursor(first, after, last, before, pageOrderBy) - - p, err := r.accessReview.Campaigns(scope).ListForOrganizationID(ctx, obj.Organization.ID, cursor) - if err != nil { - panic(fmt.Errorf("cannot list access review campaigns: %w", err)) - } - - return types.NewAccessReviewCampaignConnection(p, r, obj.Organization.ID), nil -} - -// Permission is the resolver for the permission field. -func (r *accessReviewResolver) Permission(ctx context.Context, obj *types.AccessReview, action string) (bool, error) { - return r.Resolver.Permission(ctx, obj, action) -} - -// Organization is the resolver for the organization field. -func (r *accessReviewCampaignResolver) Organization(ctx context.Context, obj *types.AccessReviewCampaign) (*types.Organization, error) { - return obj.Organization, nil -} - -// ScopeSources is the resolver for the scopeSources field. -func (r *accessReviewCampaignResolver) ScopeSources(ctx context.Context, obj *types.AccessReviewCampaign) ([]*types.AccessReviewCampaignScopeSource, error) { - if err := r.authorize(ctx, obj.ID, probo.ActionAccessSourceList); err != nil { - return nil, err - } - - scope := coredata.NewScopeFromObjectID(obj.ID) - - sources, err := r.accessReview.Sources(scope).ListScopeSourcesForCampaignID(ctx, obj.ID) - if err != nil { - panic(fmt.Errorf("cannot list scope sources: %w", err)) - } - - fetches, err := r.accessReview.Campaigns(scope).ListSourceFetches(ctx, obj.ID) - if err != nil { - panic(fmt.Errorf("cannot list source fetch states: %w", err)) - } - - fetchBySourceID := make(map[gid.GID]*coredata.AccessReviewCampaignSourceFetch, len(fetches)) - for _, fetch := range fetches { - fetchBySourceID[fetch.AccessSourceID] = fetch - } - - result := make([]*types.AccessReviewCampaignScopeSource, len(sources)) - for i, s := range sources { - result[i] = types.NewAccessReviewCampaignScopeSource(obj.ID, s, fetchBySourceID[s.ID]) - } - - return result, nil -} - -// Entries is the resolver for the entries field. -func (r *accessReviewCampaignResolver) Entries(ctx context.Context, obj *types.AccessReviewCampaign, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.AccessEntryOrder, accessSourceID *gid.GID, filter *coredata.AccessEntryFilter) (*types.AccessEntryConnection, error) { - if err := r.authorize(ctx, obj.ID, probo.ActionAccessEntryList); err != nil { - return nil, err - } - - scope := coredata.NewScopeFromObjectID(obj.ID) - - pageOrderBy := page.OrderBy[coredata.AccessEntryOrderField]{ - Field: coredata.AccessEntryOrderFieldCreatedAt, - Direction: page.OrderDirectionDesc, - } - if orderBy != nil { - pageOrderBy = page.OrderBy[coredata.AccessEntryOrderField]{ - Field: orderBy.Field, - Direction: orderBy.Direction, - } - } - - cursor := types.NewCursor(first, after, last, before, pageOrderBy) - - var ( - p *page.Page[*coredata.AccessEntry, coredata.AccessEntryOrderField] - err error - ) - - if accessSourceID != nil { - p, err = r.accessReview.Entries(scope).ListForCampaignIDAndSourceID(ctx, obj.ID, *accessSourceID, cursor, filter) - } else { - p, err = r.accessReview.Entries(scope).ListForCampaignID(ctx, obj.ID, cursor, filter) - } - if err != nil { - panic(fmt.Errorf("cannot list access entries: %w", err)) - } - - return types.NewAccessEntryConnection(p, r, obj.ID, accessSourceID, filter), nil -} - -// PendingEntryCount is the resolver for the pendingEntryCount field. -func (r *accessReviewCampaignResolver) PendingEntryCount(ctx context.Context, obj *types.AccessReviewCampaign) (int, error) { - if err := r.authorize(ctx, obj.ID, probo.ActionAccessEntryList); err != nil { - return 0, err - } - - scope := coredata.NewScopeFromObjectID(obj.ID) - - count, err := r.accessReview.Entries(scope).CountPendingForCampaignID(ctx, obj.ID) - if err != nil { - panic(fmt.Errorf("cannot count pending access entries: %w", err)) - } - - return count, nil -} - -// Statistics is the resolver for the statistics field. -func (r *accessReviewCampaignResolver) Statistics(ctx context.Context, obj *types.AccessReviewCampaign) (*types.AccessReviewCampaignStatistics, error) { - if err := r.authorize(ctx, obj.ID, probo.ActionAccessEntryList); err != nil { - return nil, err - } - - scope := coredata.NewScopeFromObjectID(obj.ID) - - stats, err := r.accessReview.Entries(scope).Statistics(ctx, obj.ID) - if err != nil { - panic(fmt.Errorf("cannot get campaign statistics: %w", err)) - } - - return types.NewAccessReviewCampaignStatistics(stats), nil -} - -// Permission is the resolver for the permission field. -func (r *accessReviewCampaignResolver) Permission(ctx context.Context, obj *types.AccessReviewCampaign, action string) (bool, error) { - return r.Resolver.Permission(ctx, obj, action) -} - -// TotalCount is the resolver for the totalCount field. -func (r *accessReviewCampaignConnectionResolver) TotalCount(ctx context.Context, obj *types.AccessReviewCampaignConnection) (int, error) { - scope := coredata.NewScopeFromObjectID(obj.ParentID) - - switch obj.Resolver.(type) { - case *organizationResolver: - count, err := r.accessReview.Campaigns(scope).CountForOrganizationID(ctx, obj.ParentID) - if err != nil { - panic(fmt.Errorf("cannot count access review campaigns: %w", err)) - } - return count, nil - } - - panic(fmt.Errorf("unsupported resolver: %T", obj.Resolver)) -} - -// Entries is the resolver for the entries field. -func (r *accessReviewCampaignScopeSourceResolver) Entries(ctx context.Context, obj *types.AccessReviewCampaignScopeSource, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.AccessEntryOrder, filter *coredata.AccessEntryFilter) (*types.AccessEntryConnection, error) { - if err := r.authorize(ctx, obj.CampaignID, probo.ActionAccessEntryList); err != nil { - return nil, err - } - - scope := coredata.NewScopeFromObjectID(obj.CampaignID) - - pageOrderBy := page.OrderBy[coredata.AccessEntryOrderField]{ - Field: coredata.AccessEntryOrderFieldCreatedAt, - Direction: page.OrderDirectionDesc, - } - if orderBy != nil { - pageOrderBy = page.OrderBy[coredata.AccessEntryOrderField]{ - Field: orderBy.Field, - Direction: orderBy.Direction, - } - } - - cursor := types.NewCursor(first, after, last, before, pageOrderBy) - - p, err := r.accessReview.Entries(scope).ListForCampaignIDAndSourceID(ctx, obj.CampaignID, obj.ID, cursor, filter) - if err != nil { - panic(fmt.Errorf("cannot list access entries: %w", err)) - } - - sourceID := obj.ID - return types.NewAccessEntryConnection(p, r, obj.CampaignID, &sourceID, filter), nil -} - -// Statistics is the resolver for the statistics field. -func (r *accessReviewCampaignScopeSourceResolver) Statistics(ctx context.Context, obj *types.AccessReviewCampaignScopeSource) (*types.AccessReviewCampaignStatistics, error) { - if err := r.authorize(ctx, obj.CampaignID, probo.ActionAccessEntryList); err != nil { - return nil, err - } - - scope := coredata.NewScopeFromObjectID(obj.CampaignID) - - stats, err := r.accessReview.Entries(scope).StatisticsForSource(ctx, obj.CampaignID, obj.ID) - if err != nil { - panic(fmt.Errorf("cannot get source statistics: %w", err)) - } - - return types.NewAccessReviewCampaignStatistics(stats), nil -} - -// Organization is the resolver for the organization field. -func (r *accessSourceResolver) Organization(ctx context.Context, obj *types.AccessSource) (*types.Organization, error) { - return obj.Organization, nil -} - -// Connector is the resolver for the connector field. -func (r *accessSourceResolver) Connector(ctx context.Context, obj *types.AccessSource) (*types.Connector, error) { - if obj.ConnectorID == nil { - return nil, nil - } - - prb := r.ProboService(ctx, obj.ID.TenantID()) - - connector, err := prb.Connectors.Get(ctx, *obj.ConnectorID) - if err != nil { - if errors.Is(err, coredata.ErrResourceNotFound) { - return nil, nil - } - panic(fmt.Errorf("cannot get connector: %w", err)) - } - - return types.NewConnector(connector), nil -} - -// ProviderOrganizations is the resolver for the providerOrganizations field. -func (r *accessSourceResolver) ProviderOrganizations(ctx context.Context, obj *types.AccessSource) ([]*types.ProviderOrganization, error) { - if err := r.authorize(ctx, obj.ID, probo.ActionAccessSourceGet); err != nil { - return nil, err - } - - if obj.ConnectorID == nil { - return []*types.ProviderOrganization{}, nil - } - - scope := coredata.NewScopeFromObjectID(obj.ID) - - httpClient, dbConnector, err := r.accessReview.Sources(scope).ConnectorHTTPClient(ctx, *obj.ConnectorID) - if err != nil { - if errors.Is(err, coredata.ErrResourceNotFound) { - return []*types.ProviderOrganization{}, nil - } - return nil, fmt.Errorf("cannot get connector HTTP client: %w", err) - } - - switch dbConnector.Provider { - case coredata.ConnectorProviderGitHub: - orgs, err := fetchGitHubOrganizations(ctx, httpClient) - if err != nil { - return nil, fmt.Errorf("cannot fetch github organizations: %w", err) - } - return orgs, nil - case coredata.ConnectorProviderSentry: - orgs, err := fetchSentryOrganizations(ctx, httpClient) - if err != nil { - return nil, fmt.Errorf("cannot fetch sentry organizations: %w", err) - } - return orgs, nil - default: - return []*types.ProviderOrganization{}, nil - } -} - -// NeedsConfiguration is the resolver for the needsConfiguration field. -func (r *accessSourceResolver) NeedsConfiguration(ctx context.Context, obj *types.AccessSource) (bool, error) { - if obj.ConnectorID == nil { - return false, nil - } - - prb := r.ProboService(ctx, obj.ID.TenantID()) - - dbConnector, err := prb.Connectors.Get(ctx, *obj.ConnectorID) - if err != nil { - if errors.Is(err, coredata.ErrResourceNotFound) { - return false, nil - } - panic(fmt.Errorf("cannot get connector: %w", err)) - } - - switch dbConnector.Provider { - case coredata.ConnectorProviderGitHub: - settings, _ := dbConnector.GitHubSettings() - return settings.Organization == "", nil - case coredata.ConnectorProviderSentry: - settings, _ := dbConnector.SentrySettings() - return settings.OrganizationSlug == "", nil - default: - return false, nil - } -} - -// ConnectionStatus is the resolver for the connectionStatus field. -func (r *accessSourceResolver) ConnectionStatus(ctx context.Context, obj *types.AccessSource) (types.AccessSourceConnectionStatus, error) { - if obj.ConnectorID == nil { - return types.AccessSourceConnectionStatusNotApplicable, nil - } - - scope := coredata.NewScopeFromObjectID(obj.ID) - - httpClient, dbConnector, err := r.accessReview.Sources(scope).ConnectorHTTPClient(ctx, *obj.ConnectorID) - if err != nil { - if errors.Is(err, coredata.ErrResourceNotFound) { - return types.AccessSourceConnectionStatusNotApplicable, nil - } - return types.AccessSourceConnectionStatusDisconnected, nil - } - - if dbConnector.Protocol != coredata.ConnectorProtocolOAuth2 { - return types.AccessSourceConnectionStatusConnected, nil - } - - // Creating an HTTP client may succeed even with an expired token - // (e.g. no refresh token available). Make a lightweight probe - // request to verify the token is actually valid. - probeURL := r.connectorRegistry.GetProbeURL(string(dbConnector.Provider)) - if err := probeConnection(ctx, httpClient, probeURL); err != nil { - return types.AccessSourceConnectionStatusDisconnected, nil - } - - return types.AccessSourceConnectionStatusConnected, nil -} - -// SelectedOrganization is the resolver for the selectedOrganization field. -func (r *accessSourceResolver) SelectedOrganization(ctx context.Context, obj *types.AccessSource) (*string, error) { - if obj.ConnectorID == nil { - return nil, nil - } - - prb := r.ProboService(ctx, obj.ID.TenantID()) - - dbConnector, err := prb.Connectors.Get(ctx, *obj.ConnectorID) - if err != nil { - if errors.Is(err, coredata.ErrResourceNotFound) { - return nil, nil - } - panic(fmt.Errorf("cannot get connector: %w", err)) - } - - switch dbConnector.Provider { - case coredata.ConnectorProviderGitHub: - settings, _ := dbConnector.GitHubSettings() - if settings.Organization != "" { - return &settings.Organization, nil - } - case coredata.ConnectorProviderSentry: - settings, _ := dbConnector.SentrySettings() - if settings.OrganizationSlug != "" { - return &settings.OrganizationSlug, nil - } - } - - return nil, nil -} - -// Permission is the resolver for the permission field. -func (r *accessSourceResolver) Permission(ctx context.Context, obj *types.AccessSource, action string) (bool, error) { - return r.Resolver.Permission(ctx, obj, action) -} - -// TotalCount is the resolver for the totalCount field. -func (r *accessSourceConnectionResolver) TotalCount(ctx context.Context, obj *types.AccessSourceConnection) (int, error) { - scope := coredata.NewScopeFromObjectID(obj.ParentID) - - switch obj.Resolver.(type) { - case *organizationResolver: - count, err := r.accessReview.Sources(scope).CountForOrganizationID(ctx, obj.ParentID) - if err != nil { - panic(fmt.Errorf("cannot count access sources: %w", err)) - } - return count, nil - } - - panic(fmt.Errorf("unsupported resolver: %T", obj.Resolver)) -} - -// StatementOfApplicability is the resolver for the statementOfApplicability field. -func (r *applicabilityStatementResolver) StatementOfApplicability(ctx context.Context, obj *types.ApplicabilityStatement) (*types.StatementOfApplicability, error) { - if err := r.authorize(ctx, obj.StatementOfApplicability.ID, probo.ActionStatementOfApplicabilityGet); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, obj.StatementOfApplicability.ID.TenantID()) - - soa, err := prb.StatementsOfApplicability.Get(ctx, obj.StatementOfApplicability.ID) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot get statement of applicability", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return types.NewStatementOfApplicability(soa), nil -} - -// Control is the resolver for the control field. -func (r *applicabilityStatementResolver) Control(ctx context.Context, obj *types.ApplicabilityStatement) (*types.Control, error) { - if err := r.authorize(ctx, obj.ID, probo.ActionControlGet); err != nil { - return nil, err - } - - loaders := dataloader.FromContext(ctx) - - control, err := loaders.Control.Load(ctx, obj.Control.ID) - if err != nil { - if errors.Is(err, dataloadgen.ErrNotFound) { - return nil, gqlutils.NotFound(ctx, err) - } - - r.logger.ErrorCtx(ctx, "cannot get control", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return types.NewControl(control), nil -} - -// Permission is the resolver for the permission field. -func (r *applicabilityStatementResolver) Permission(ctx context.Context, obj *types.ApplicabilityStatement, action string) (bool, error) { - return r.Resolver.Permission(ctx, obj, action) -} - -// TotalCount is the resolver for the totalCount field. -func (r *applicabilityStatementConnectionResolver) TotalCount(ctx context.Context, obj *types.ApplicabilityStatementConnection) (int, error) { - if err := r.authorize(ctx, obj.ParentID, probo.ActionApplicabilityStatementList); err != nil { - return 0, err - } - - prb := r.ProboService(ctx, obj.ParentID.TenantID()) - - switch obj.Resolver.(type) { - case *statementOfApplicabilityResolver: - count, err := prb.StatementsOfApplicability.CountApplicabilityStatements(ctx, obj.ParentID) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot count applicability statements", log.Error(err)) - return 0, gqlutils.Internal(ctx) - } - return count, nil - } - - r.logger.ErrorCtx(ctx, "unsupported resolver for applicability statement connection", log.String("resolver", fmt.Sprintf("%T", obj.Resolver))) - return 0, gqlutils.Internal(ctx) -} - -// Owner is the resolver for the owner field. -func (r *assetResolver) Owner(ctx context.Context, obj *types.Asset) (*types.Profile, error) { - if err := r.authorize(ctx, obj.ID, iam.ActionMembershipProfileGet); err != nil { - return nil, err - } - - loaders := dataloader.FromContext(ctx) - - owner, err := loaders.Profile.Load(ctx, obj.Owner.ID) - if err != nil { - if errors.Is(err, coredata.ErrResourceNotFound) || errors.Is(err, dataloadgen.ErrNotFound) { - return nil, gqlutils.NotFound(ctx, err) - } - - r.logger.ErrorCtx(ctx, "cannot get owner", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return types.NewProfile(owner), nil -} - -// Vendors is the resolver for the vendors field. -func (r *assetResolver) Vendors(ctx context.Context, obj *types.Asset, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.VendorOrderBy) (*types.VendorConnection, error) { - if err := r.authorize(ctx, obj.ID, probo.ActionVendorList); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, obj.ID.TenantID()) - - pageOrderBy := page.OrderBy[coredata.VendorOrderField]{ - Field: coredata.VendorOrderFieldCreatedAt, - Direction: page.OrderDirectionDesc, - } - if orderBy != nil { - pageOrderBy = page.OrderBy[coredata.VendorOrderField]{ - Field: orderBy.Field, - Direction: orderBy.Direction, - } - } - - cursor := types.NewCursor(first, after, last, before, pageOrderBy) - - page, err := prb.Vendors.ListForAssetID(ctx, obj.ID, cursor) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot list asset vendors", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return types.NewVendorConnection(page, r, obj.ID), nil -} - -// Organization is the resolver for the organization field. -func (r *assetResolver) Organization(ctx context.Context, obj *types.Asset) (*types.Organization, error) { - if err := r.authorize(ctx, obj.ID, probo.ActionOrganizationGet); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, obj.ID.TenantID()) - - asset, err := prb.Assets.Get(ctx, obj.ID) - if err != nil { - - r.logger.ErrorCtx(ctx, "cannot load audit", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - org, err := prb.Organizations.Get(ctx, asset.OrganizationID) - if err != nil { - if errors.Is(err, coredata.ErrResourceNotFound) { - return nil, gqlutils.NotFound(ctx, err) - } - - r.logger.ErrorCtx(ctx, "cannot get organization", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return types.NewOrganization(org), nil -} - -// Permission is the resolver for the permission field. -func (r *assetResolver) Permission(ctx context.Context, obj *types.Asset, action string) (bool, error) { - return r.Resolver.Permission(ctx, obj, action) -} - -// TotalCount is the resolver for the totalCount field. -func (r *assetConnectionResolver) TotalCount(ctx context.Context, obj *types.AssetConnection) (int, error) { - if err := r.authorize(ctx, obj.ParentID, probo.ActionAssetList); err != nil { - return 0, err - } - - prb := r.ProboService(ctx, obj.ParentID.TenantID()) - - switch obj.Resolver.(type) { - case *organizationResolver: - assetFilter := coredata.NewAssetFilter(nil) - if obj.Filter != nil { - assetFilter = coredata.NewAssetFilter(&obj.Filter.SnapshotID) - } - - count, err := prb.Assets.CountForOrganizationID(ctx, obj.ParentID, assetFilter) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot count assets", log.Error(err)) - return 0, gqlutils.Internal(ctx) - } - return count, nil - } - - r.logger.ErrorCtx(ctx, "unsupported resolver") - return 0, gqlutils.Internal(ctx) -} - -// Organization is the resolver for the organization field. -func (r *auditResolver) Organization(ctx context.Context, obj *types.Audit) (*types.Organization, error) { - if err := r.authorize(ctx, obj.ID, probo.ActionOrganizationGet); err != nil { - return nil, err - } - - loaders := dataloader.FromContext(ctx) - - organization, err := loaders.Organization.Load(ctx, obj.Organization.ID) - if err != nil { - if errors.Is(err, coredata.ErrResourceNotFound) || errors.Is(err, dataloadgen.ErrNotFound) { - return nil, gqlutils.NotFound(ctx, err) - } - - r.logger.ErrorCtx(ctx, "cannot load organization", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return types.NewOrganization(organization), nil -} - -// Framework is the resolver for the framework field. -func (r *auditResolver) Framework(ctx context.Context, obj *types.Audit) (*types.Framework, error) { - if err := r.authorize(ctx, obj.ID, probo.ActionFrameworkGet); err != nil { - return nil, err - } - - loaders := dataloader.FromContext(ctx) - - framework, err := loaders.Framework.Load(ctx, obj.Framework.ID) - if err != nil { - if errors.Is(err, coredata.ErrResourceNotFound) || errors.Is(err, dataloadgen.ErrNotFound) { - return nil, gqlutils.NotFound(ctx, err) - } - - r.logger.ErrorCtx(ctx, "cannot load framework", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return types.NewFramework(framework), nil -} - -// Report is the resolver for the report field. -func (r *auditResolver) Report(ctx context.Context, obj *types.Audit) (*types.Report, error) { - if err := r.authorize(ctx, obj.ID, probo.ActionReportGet); err != nil { - return nil, err - } - - if obj.Report == nil { - return nil, nil - } - - loaders := dataloader.FromContext(ctx) - - report, err := loaders.Report.Load(ctx, obj.Report.ID) - if err != nil { - if errors.Is(err, dataloadgen.ErrNotFound) { - return nil, gqlutils.NotFound(ctx, err) - } - - r.logger.ErrorCtx(ctx, "cannot load report", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return types.NewReport(report), nil -} - -// ReportURL is the resolver for the reportUrl field. -func (r *auditResolver) ReportURL(ctx context.Context, obj *types.Audit) (*string, error) { - if err := r.authorize(ctx, obj.ID, probo.ActionReportGetReportUrl); err != nil { - return nil, err - } - - if obj.Report == nil { - return nil, nil - } - - prb := r.ProboService(ctx, obj.ID.TenantID()) - - url, err := prb.Audits.GenerateReportURL(ctx, obj.ID, 15*time.Minute) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot generate report URL", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return url, nil -} - -// Controls is the resolver for the controls field. -func (r *auditResolver) Controls(ctx context.Context, obj *types.Audit, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.ControlOrderBy, filter *types.ControlFilter) (*types.ControlConnection, error) { - if err := r.authorize(ctx, obj.ID, probo.ActionControlList); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, obj.ID.TenantID()) - - pageOrderBy := page.OrderBy[coredata.ControlOrderField]{ - Field: coredata.ControlOrderFieldCreatedAt, - Direction: page.OrderDirectionDesc, - } - if orderBy != nil { - pageOrderBy = page.OrderBy[coredata.ControlOrderField]{ - Field: orderBy.Field, - Direction: orderBy.Direction, - } - } - - cursor := types.NewCursor(first, after, last, before, pageOrderBy) - - var controlFilter = coredata.NewControlFilter(nil) - if filter != nil { - controlFilter = coredata.NewControlFilter(filter.Query) - } - - page, err := prb.Controls.ListForAuditID(ctx, obj.ID, cursor, controlFilter) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot list audit controls", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return types.NewControlConnection(page, r, obj.ID, controlFilter), nil -} - -// Findings is the resolver for the findings field. -func (r *auditResolver) Findings(ctx context.Context, obj *types.Audit, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.FindingOrder, filter *types.FindingFilter) (*types.FindingConnection, error) { - if err := r.authorize(ctx, obj.ID, probo.ActionFindingList); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, obj.ID.TenantID()) - - pageOrderBy := page.OrderBy[coredata.FindingOrderField]{ - Field: coredata.FindingOrderFieldCreatedAt, - Direction: page.OrderDirectionDesc, - } - if orderBy != nil { - pageOrderBy = page.OrderBy[coredata.FindingOrderField]{ - Field: orderBy.Field, - Direction: orderBy.Direction, - } - } - - cursor := types.NewCursor(first, after, last, before, pageOrderBy) - - var ( - kind *coredata.FindingKind - status *coredata.FindingStatus - priority *coredata.FindingPriority - ownerID *gid.GID - ) - if filter != nil { - kind = filter.Kind - status = filter.Status - priority = filter.Priority - ownerID = filter.OwnerID - } - - findingFilter := coredata.NewFindingFilter(nil, kind, status, priority, ownerID) - if filter != nil { - findingFilter = coredata.NewFindingFilter(&filter.SnapshotID, kind, status, priority, ownerID) - } - - p, err := prb.Findings.ListForAuditID(ctx, obj.ID, cursor, findingFilter) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot list audit findings", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return types.NewFindingConnection(p, r, obj.ID, filter), nil -} - -// Permission is the resolver for the permission field. -func (r *auditResolver) Permission(ctx context.Context, obj *types.Audit, action string) (bool, error) { - return r.Resolver.Permission(ctx, obj, action) -} - -// TotalCount is the resolver for the totalCount field. -func (r *auditConnectionResolver) TotalCount(ctx context.Context, obj *types.AuditConnection) (int, error) { - if err := r.authorize(ctx, obj.ParentID, probo.ActionAuditList); err != nil { - return 0, err - } - - prb := r.ProboService(ctx, obj.ParentID.TenantID()) - - switch obj.Resolver.(type) { - case *organizationResolver: - count, err := prb.Audits.CountForOrganizationID(ctx, obj.ParentID) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot count audits", log.Error(err)) - return 0, gqlutils.Internal(ctx) - } - return count, nil - case *findingResolver: - count, err := prb.Audits.CountForFindingID(ctx, obj.ParentID) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot count audits", log.Error(err)) - return 0, gqlutils.Internal(ctx) - } - return count, nil - case *controlResolver: - count, err := prb.Audits.CountForControlID(ctx, obj.ParentID) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot count audits", log.Error(err)) - return 0, gqlutils.Internal(ctx) - } - return count, nil - default: - r.logger.ErrorCtx(ctx, "unsupported resolver", log.Any("resolver", obj.Resolver)) - return 0, gqlutils.Internal(ctx) - } -} - -// Organization is the resolver for the organization field. -func (r *auditLogEntryResolver) Organization(ctx context.Context, obj *types.AuditLogEntry) (*types.Organization, error) { - return obj.Organization, nil -} - -// Permission is the resolver for the permission field. -func (r *auditLogEntryResolver) Permission(ctx context.Context, obj *types.AuditLogEntry, action string) (bool, error) { - return r.Resolver.Permission(ctx, obj, action) -} - -// TotalCount is the resolver for the totalCount field. -func (r *auditLogEntryConnectionResolver) TotalCount(ctx context.Context, obj *types.AuditLogEntryConnection) (int, error) { - filter := coredata.NewAuditLogEntryFilter() - if obj.Filter != nil { - filter = obj.Filter - } - - count, err := r.iam.OrganizationService.CountAuditLogEntries(ctx, obj.ParentID, filter) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot count audit log entries", log.Error(err)) - return 0, gqlutils.Internal(ctx) - } - - return count, nil -} - -// Permission is the resolver for the permission field. -func (r *complianceExternalURLResolver) Permission(ctx context.Context, obj *types.ComplianceExternalURL, action string) (bool, error) { - return r.Resolver.Permission(ctx, obj, action) -} - -// Framework is the resolver for the framework field. -func (r *complianceFrameworkResolver) Framework(ctx context.Context, obj *types.ComplianceFramework) (*types.Framework, error) { - if err := r.authorize(ctx, obj.FrameworkID, probo.ActionFrameworkGet); err != nil { - return nil, err - } - - loaders := dataloader.FromContext(ctx) - - framework, err := loaders.Framework.Load(ctx, obj.FrameworkID) - if err != nil { - if errors.Is(err, dataloadgen.ErrNotFound) { - return nil, gqlutils.NotFound(ctx, err) - } - - r.logger.ErrorCtx(ctx, "cannot load framework", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return types.NewFramework(framework), nil -} - -// Oauth2Scopes is the resolver for the oauth2Scopes field. -func (r *connectorResolver) Oauth2Scopes(ctx context.Context, obj *types.Connector) ([]string, error) { - scopes := drivers.ProviderOAuth2Scopes(obj.Provider) - if scopes == nil { - return []string{}, nil - } - return scopes, nil -} - -// Organization is the resolver for the organization field. -func (r *controlResolver) Organization(ctx context.Context, obj *types.Control) (*types.Organization, error) { - if err := r.authorize(ctx, obj.ID, probo.ActionOrganizationGet); err != nil { - return nil, err - } - - loaders := dataloader.FromContext(ctx) - - organization, err := loaders.Organization.Load(ctx, obj.Organization.ID) - if err != nil { - if errors.Is(err, coredata.ErrResourceNotFound) || errors.Is(err, dataloadgen.ErrNotFound) { - return nil, gqlutils.NotFound(ctx, err) - } - - r.logger.ErrorCtx(ctx, "cannot get organization", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - return types.NewOrganization(organization), nil -} - -// Regulatory is the resolver for the regulatory field. -func (r *controlResolver) Regulatory(ctx context.Context, obj *types.Control) (bool, error) { - prb := r.ProboService(ctx, obj.ID.TenantID()) - - hasRegulatory, err := prb.Controls.HasRegulatoryObligation(ctx, obj.ID) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot check regulatory obligation", log.Error(err)) - return false, gqlutils.Internal(ctx) - } - - return hasRegulatory, nil -} - -// Contractual is the resolver for the contractual field. -func (r *controlResolver) Contractual(ctx context.Context, obj *types.Control) (bool, error) { - prb := r.ProboService(ctx, obj.ID.TenantID()) - - hasContractual, err := prb.Controls.HasContractualObligation(ctx, obj.ID) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot check contractual obligation", log.Error(err)) - return false, gqlutils.Internal(ctx) - } - - return hasContractual, nil -} - -// RiskAssessment is the resolver for the riskAssessment field. -func (r *controlResolver) RiskAssessment(ctx context.Context, obj *types.Control) (bool, error) { - prb := r.ProboService(ctx, obj.ID.TenantID()) - - hasRisk, err := prb.Controls.HasRiskAssessment(ctx, obj.ID) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot check risk assessment", log.Error(err)) - return false, gqlutils.Internal(ctx) - } - - return hasRisk, nil -} - -// Framework is the resolver for the framework field. -func (r *controlResolver) Framework(ctx context.Context, obj *types.Control) (*types.Framework, error) { - if err := r.authorize(ctx, obj.ID, probo.ActionFrameworkGet); err != nil { - return nil, err - } - - loaders := dataloader.FromContext(ctx) - - framework, err := loaders.Framework.Load(ctx, obj.Framework.ID) - if err != nil { - if errors.Is(err, coredata.ErrResourceNotFound) || errors.Is(err, dataloadgen.ErrNotFound) { - return nil, gqlutils.NotFound(ctx, err) - } - - r.logger.ErrorCtx(ctx, "cannot get framework", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return types.NewFramework(framework), nil -} - -// Measures is the resolver for the measures field. -func (r *controlResolver) Measures(ctx context.Context, obj *types.Control, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.MeasureOrderBy, filter *types.MeasureFilter) (*types.MeasureConnection, error) { - if err := r.authorize(ctx, obj.ID, probo.ActionMeasureList); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, obj.ID.TenantID()) - - pageOrderBy := page.OrderBy[coredata.MeasureOrderField]{ - Field: coredata.MeasureOrderFieldCreatedAt, - Direction: page.OrderDirectionDesc, - } - if orderBy != nil { - pageOrderBy = page.OrderBy[coredata.MeasureOrderField]{ - Field: orderBy.Field, - Direction: orderBy.Direction, - } - } - - cursor := types.NewCursor(first, after, last, before, pageOrderBy) - - var measureFilter = coredata.NewMeasureFilter(nil, nil, nil) - if filter != nil { - measureFilter = coredata.NewMeasureFilter(filter.Query, filter.State, filter.Category) - } - - page, err := prb.Measures.ListForControlID(ctx, obj.ID, cursor, measureFilter) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot list measures", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return types.NewMeasureConnection(page, r, obj.ID, measureFilter), nil -} - -// Documents is the resolver for the documents field. -func (r *controlResolver) Documents(ctx context.Context, obj *types.Control, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.DocumentOrderBy, filter *types.DocumentFilter) (*types.DocumentConnection, error) { - if err := r.authorize(ctx, obj.ID, probo.ActionDocumentList); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, obj.ID.TenantID()) - - pageOrderBy := page.OrderBy[coredata.DocumentOrderField]{ - Field: coredata.DocumentOrderFieldCreatedAt, - Direction: page.OrderDirectionDesc, - } - if orderBy != nil { - pageOrderBy = page.OrderBy[coredata.DocumentOrderField]{ - Field: orderBy.Field, - Direction: orderBy.Direction, - } - } - - cursor := types.NewCursor(first, after, last, before, pageOrderBy) - - var documentFilter = coredata.NewDocumentFilter(nil) - if filter != nil { - documentFilter = coredata.NewDocumentFilter(filter.Query). - WithDocumentTypes(filter.DocumentTypes). - WithClassifications(filter.Classifications) - } - - page, err := prb.Documents.ListForControlID(ctx, obj.ID, cursor, documentFilter) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot list documents", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return types.NewDocumentConnection(page, r, obj.ID, documentFilter), nil -} - -// Audits is the resolver for the audits field. -func (r *controlResolver) Audits(ctx context.Context, obj *types.Control, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.AuditOrderBy) (*types.AuditConnection, error) { - if err := r.authorize(ctx, obj.ID, probo.ActionAuditList); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, obj.ID.TenantID()) - - pageOrderBy := page.OrderBy[coredata.AuditOrderField]{ - Field: coredata.AuditOrderFieldCreatedAt, - Direction: page.OrderDirectionDesc, - } - if orderBy != nil { - pageOrderBy = page.OrderBy[coredata.AuditOrderField]{ - Field: orderBy.Field, - Direction: orderBy.Direction, - } - } - - cursor := types.NewCursor(first, after, last, before, pageOrderBy) - - page, err := prb.Audits.ListForControlID(ctx, obj.ID, cursor) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot list control audits", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return types.NewAuditConnection(page, r, obj.ID), nil -} - -// Obligations is the resolver for the obligations field. -func (r *controlResolver) Obligations(ctx context.Context, obj *types.Control, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.ObligationOrderBy, filter *types.ObligationFilter) (*types.ObligationConnection, error) { - if err := r.authorize(ctx, obj.ID, probo.ActionObligationList); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, obj.ID.TenantID()) - - pageOrderBy := page.OrderBy[coredata.ObligationOrderField]{ - Field: coredata.ObligationOrderFieldCreatedAt, - Direction: page.OrderDirectionDesc, - } - if orderBy != nil { - pageOrderBy = page.OrderBy[coredata.ObligationOrderField]{ - Field: orderBy.Field, - Direction: orderBy.Direction, - } - } - - cursor := types.NewCursor(first, after, last, before, pageOrderBy) - - var snapshotID **gid.GID - if filter != nil { - snapshotID = &filter.SnapshotID - } - obligationFilter := coredata.NewObligationFilter(snapshotID) - page, err := prb.Obligations.ListForControlID(ctx, obj.ID, cursor, obligationFilter) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot list control obligations", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return types.NewObligationConnection(page, r, obj.ID, filter), nil -} - -// Snapshots is the resolver for the snapshots field. -func (r *controlResolver) Snapshots(ctx context.Context, obj *types.Control, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.SnapshotOrderBy) (*types.SnapshotConnection, error) { - if err := r.authorize(ctx, obj.ID, probo.ActionSnapshotList); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, obj.ID.TenantID()) - - pageOrderBy := page.OrderBy[coredata.SnapshotOrderField]{ - Field: coredata.SnapshotOrderFieldCreatedAt, - Direction: page.OrderDirectionDesc, - } - - if orderBy != nil { - pageOrderBy = page.OrderBy[coredata.SnapshotOrderField]{ - Field: orderBy.Field, - Direction: orderBy.Direction, - } - } - - cursor := types.NewCursor(first, after, last, before, pageOrderBy) - - page, err := prb.Snapshots.ListForControlID(ctx, obj.ID, cursor) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot list control snapshots", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return types.NewSnapshotConnection(page, r, obj.ID), nil -} - -// Permission is the resolver for the permission field. -func (r *controlResolver) Permission(ctx context.Context, obj *types.Control, action string) (bool, error) { - return r.Resolver.Permission(ctx, obj, action) -} - -// TotalCount is the resolver for the totalCount field. -func (r *controlConnectionResolver) TotalCount(ctx context.Context, obj *types.ControlConnection) (int, error) { - if err := r.authorize(ctx, obj.ParentID, probo.ActionControlList); err != nil { - return 0, err - } - - prb := r.ProboService(ctx, obj.ParentID.TenantID()) - - switch obj.Resolver.(type) { - case *organizationResolver: - count, err := prb.Controls.CountForOrganizationID(ctx, obj.ParentID, obj.Filters) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot count controls", log.Error(err)) - return 0, gqlutils.Internal(ctx) - } - return count, nil - case *frameworkResolver: - count, err := prb.Controls.CountForFrameworkID(ctx, obj.ParentID, obj.Filters) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot count controls", log.Error(err)) - return 0, gqlutils.Internal(ctx) - } - return count, nil - case *documentResolver: - count, err := prb.Controls.CountForDocumentID(ctx, obj.ParentID, obj.Filters) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot count controls", log.Error(err)) - return 0, gqlutils.Internal(ctx) - } - return count, nil - case *measureResolver: - count, err := prb.Controls.CountForMeasureID(ctx, obj.ParentID, obj.Filters) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot count controls", log.Error(err)) - return 0, gqlutils.Internal(ctx) - } - return count, nil - case *riskResolver: - count, err := prb.Controls.CountForRiskID(ctx, obj.ParentID, obj.Filters) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot count controls", log.Error(err)) - return 0, gqlutils.Internal(ctx) - } - return count, nil - case *statementOfApplicabilityResolver: - count, err := prb.Controls.CountForStatementOfApplicabilityID(ctx, obj.ParentID, obj.Filters) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot count controls", log.Error(err)) - return 0, gqlutils.Internal(ctx) - } - return count, nil - } - - r.logger.ErrorCtx(ctx, "unsupported resolver") - return 0, gqlutils.Internal(ctx) -} - -// Permission is the resolver for the permission field. -func (r *customDomainResolver) Permission(ctx context.Context, obj *types.CustomDomain, action string) (bool, error) { - return r.Resolver.Permission(ctx, obj, action) -} - -// ProcessingActivity is the resolver for the processingActivity field. -func (r *dataProtectionImpactAssessmentResolver) ProcessingActivity(ctx context.Context, obj *types.DataProtectionImpactAssessment) (*types.ProcessingActivity, error) { - if err := r.authorize(ctx, obj.ID, probo.ActionProcessingActivityList); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, obj.ID.TenantID()) - - dpia, err := prb.DataProtectionImpactAssessments.Get(ctx, obj.ID) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot get processing activity dpia", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - processingActivity, err := prb.ProcessingActivities.Get(ctx, dpia.ProcessingActivityID) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot get processing activity", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return types.NewProcessingActivity(processingActivity), nil -} - -// Organization is the resolver for the organization field. -func (r *dataProtectionImpactAssessmentResolver) Organization(ctx context.Context, obj *types.DataProtectionImpactAssessment) (*types.Organization, error) { - if err := r.authorize(ctx, obj.ID, probo.ActionOrganizationGet); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, obj.ID.TenantID()) - - dpia, err := prb.DataProtectionImpactAssessments.Get(ctx, obj.ID) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot get processing activity dpia", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - organization, err := prb.Organizations.Get(ctx, dpia.OrganizationID) - if err != nil { - if errors.Is(err, coredata.ErrResourceNotFound) { - return nil, gqlutils.NotFound(ctx, err) - } - r.logger.ErrorCtx(ctx, "cannot get organization", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return types.NewOrganization(organization), nil -} - -// Permission is the resolver for the permission field. -func (r *dataProtectionImpactAssessmentResolver) Permission(ctx context.Context, obj *types.DataProtectionImpactAssessment, action string) (bool, error) { - return r.Resolver.Permission(ctx, obj, action) -} - -// TotalCount is the resolver for the totalCount field. -func (r *dataProtectionImpactAssessmentConnectionResolver) TotalCount(ctx context.Context, obj *types.DataProtectionImpactAssessmentConnection) (int, error) { - if err := r.authorize(ctx, obj.ParentID, probo.ActionDataProtectionImpactAssessmentList); err != nil { - return 0, err - } - - prb := r.ProboService(ctx, obj.ParentID.TenantID()) - - switch obj.Resolver.(type) { - case *organizationResolver: - count, err := prb.DataProtectionImpactAssessments.CountForOrganizationID(ctx, obj.ParentID, obj.Filter) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot count organization data protection impact assessments", log.Error(err)) - return 0, gqlutils.Internal(ctx) - } - return count, nil - } - - r.logger.ErrorCtx(ctx, "unsupported resolver") - return 0, gqlutils.Internal(ctx) -} - -// Owner is the resolver for the owner field. -func (r *datumResolver) Owner(ctx context.Context, obj *types.Datum) (*types.Profile, error) { - if err := r.authorize(ctx, obj.ID, iam.ActionMembershipProfileGet); err != nil { - return nil, err - } - - loaders := dataloader.FromContext(ctx) - - owner, err := loaders.Profile.Load(ctx, obj.Owner.ID) - if err != nil { - if errors.Is(err, coredata.ErrResourceNotFound) || errors.Is(err, dataloadgen.ErrNotFound) { - return nil, gqlutils.NotFound(ctx, err) - } - - return nil, fmt.Errorf("cannot get owner: %w", err) - } - - return types.NewProfile(owner), nil -} - -// Vendors is the resolver for the vendors field. -func (r *datumResolver) Vendors(ctx context.Context, obj *types.Datum, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.VendorOrderBy) (*types.VendorConnection, error) { - if err := r.authorize(ctx, obj.ID, probo.ActionVendorList); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, obj.ID.TenantID()) - - pageOrderBy := page.OrderBy[coredata.VendorOrderField]{ - Field: coredata.VendorOrderFieldCreatedAt, - Direction: page.OrderDirectionDesc, - } - if orderBy != nil { - pageOrderBy = page.OrderBy[coredata.VendorOrderField]{ - Field: orderBy.Field, - Direction: orderBy.Direction, - } - } - - cursor := types.NewCursor(first, after, last, before, pageOrderBy) - - page, err := prb.Data.ListVendors(ctx, obj.ID, cursor) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot list data vendors", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return types.NewVendorConnection(page, r, obj.ID), nil -} - -// Organization is the resolver for the organization field. -func (r *datumResolver) Organization(ctx context.Context, obj *types.Datum) (*types.Organization, error) { - if err := r.authorize(ctx, obj.ID, probo.ActionOrganizationGet); err != nil { - return nil, err - } - - loaders := dataloader.FromContext(ctx) - - org, err := loaders.Organization.Load(ctx, obj.OrganizationID) - if err != nil { - if errors.Is(err, coredata.ErrResourceNotFound) || errors.Is(err, dataloadgen.ErrNotFound) { - return nil, gqlutils.NotFound(ctx, err) - } - - r.logger.ErrorCtx(ctx, "cannot get organization", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return types.NewOrganization(org), nil -} - -// Permission is the resolver for the permission field. -func (r *datumResolver) Permission(ctx context.Context, obj *types.Datum, action string) (bool, error) { - return r.Resolver.Permission(ctx, obj, action) -} - -// TotalCount is the resolver for the totalCount field. -func (r *datumConnectionResolver) TotalCount(ctx context.Context, obj *types.DatumConnection) (int, error) { - if err := r.authorize(ctx, obj.ParentID, probo.ActionDatumList); err != nil { - return 0, err - } - - prb := r.ProboService(ctx, obj.ParentID.TenantID()) - - switch obj.Resolver.(type) { - case *organizationResolver: - datumFilter := coredata.NewDatumFilter(nil) - if obj.Filter != nil { - datumFilter = coredata.NewDatumFilter(&obj.Filter.SnapshotID) - } - - count, err := prb.Data.CountForOrganizationID(ctx, obj.ParentID, datumFilter) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot count data", log.Error(err)) - return 0, gqlutils.Internal(ctx) - } - return count, nil - } - - r.logger.ErrorCtx(ctx, "unsupported resolver") - return 0, gqlutils.Internal(ctx) -} - -// Organization is the resolver for the organization field. -func (r *documentResolver) Organization(ctx context.Context, obj *types.Document) (*types.Organization, error) { - if err := r.authorize(ctx, obj.ID, probo.ActionOrganizationGet); err != nil { - return nil, err - } - - loaders := dataloader.FromContext(ctx) - - organization, err := loaders.Organization.Load(ctx, obj.Organization.ID) - if err != nil { - if errors.Is(err, coredata.ErrResourceNotFound) || errors.Is(err, dataloadgen.ErrNotFound) { - return nil, gqlutils.NotFound(ctx, err) - } - - r.logger.ErrorCtx(ctx, "cannot get organization", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return types.NewOrganization(organization), nil -} - -// Versions is the resolver for the versions field. -func (r *documentResolver) Versions(ctx context.Context, obj *types.Document, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.DocumentVersionOrderBy, filter *types.DocumentVersionFilter) (*types.DocumentVersionConnection, error) { - if err := r.authorize(ctx, obj.ID, probo.ActionDocumentVersionList); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, obj.ID.TenantID()) - - pageOrderBy := page.OrderBy[coredata.DocumentVersionOrderField]{ - Field: coredata.DocumentVersionOrderFieldCreatedAt, - Direction: page.OrderDirectionDesc, - } - if orderBy != nil { - pageOrderBy = page.OrderBy[coredata.DocumentVersionOrderField]{ - Field: orderBy.Field, - Direction: orderBy.Direction, - } - } - - cursor := types.NewCursor(first, after, last, before, pageOrderBy) - - versionFilter := coredata.NewDocumentVersionFilter() - if filter != nil && len(filter.Statuses) > 0 { - versionFilter = versionFilter.WithStatuses(filter.Statuses...) - } - - page, err := prb.Documents.ListVersions(ctx, obj.ID, cursor, versionFilter) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot list document versions", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return types.NewDocumentVersionConnection(page, r, obj.ID), nil -} - -// Controls is the resolver for the controls field. -func (r *documentResolver) Controls(ctx context.Context, obj *types.Document, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.ControlOrderBy, filter *types.ControlFilter) (*types.ControlConnection, error) { - if err := r.authorize(ctx, obj.ID, probo.ActionControlList); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, obj.ID.TenantID()) - - pageOrderBy := page.OrderBy[coredata.ControlOrderField]{ - Field: coredata.ControlOrderFieldCreatedAt, - Direction: page.OrderDirectionDesc, - } - if orderBy != nil { - pageOrderBy = page.OrderBy[coredata.ControlOrderField]{ - Field: orderBy.Field, - Direction: orderBy.Direction, - } - } - - cursor := types.NewCursor(first, after, last, before, pageOrderBy) - - var controlFilter = coredata.NewControlFilter(nil) - if filter != nil { - controlFilter = coredata.NewControlFilter(filter.Query) - } - - page, err := prb.Controls.ListForDocumentID(ctx, obj.ID, cursor, controlFilter) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot list document controls", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return types.NewControlConnection(page, r, obj.ID, controlFilter), nil -} - -// DefaultApprovers is the resolver for the defaultApprovers field. -func (r *documentResolver) DefaultApprovers(ctx context.Context, obj *types.Document) ([]*types.Profile, error) { - if err := r.authorize(ctx, obj.ID, probo.ActionDocumentGet); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, obj.ID.TenantID()) - - profiles, err := prb.Documents.GetDefaultApprovers(ctx, obj.ID) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot get default approvers", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - result := make([]*types.Profile, len(profiles)) - for i, p := range profiles { - result[i] = types.NewProfile(p) - } - - return result, nil -} - -// Permission is the resolver for the permission field. -func (r *documentResolver) Permission(ctx context.Context, obj *types.Document, action string) (bool, error) { - return r.Resolver.Permission(ctx, obj, action) -} - -// TotalCount is the resolver for the totalCount field. -func (r *documentConnectionResolver) TotalCount(ctx context.Context, obj *types.DocumentConnection) (int, error) { - if err := r.authorize(ctx, obj.ParentID, probo.ActionDocumentList); err != nil { - return 0, err - } - - prb := r.ProboService(ctx, obj.ParentID.TenantID()) - - switch obj.Resolver.(type) { - case *controlResolver: - count, err := prb.Documents.CountForControlID(ctx, obj.ParentID, obj.Filters) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot count controls", log.Error(err)) - return 0, gqlutils.Internal(ctx) - } - return count, nil - case *organizationResolver: - count, err := prb.Documents.CountForOrganizationID(ctx, obj.ParentID, obj.Filters) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot count documents", log.Error(err)) - return 0, gqlutils.Internal(ctx) - } - return count, nil - case *riskResolver: - count, err := prb.Documents.CountForRiskID(ctx, obj.ParentID, obj.Filters) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot count risks", log.Error(err)) - return 0, gqlutils.Internal(ctx) - } - return count, nil - case *measureResolver: - count, err := prb.Documents.CountForMeasureID(ctx, obj.ParentID, obj.Filters) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot count documents", log.Error(err)) - return 0, gqlutils.Internal(ctx) - } - return count, nil - } - - r.logger.ErrorCtx(ctx, "unsupported resolver") - return 0, gqlutils.Internal(ctx) -} - -// Document is the resolver for the document field. -func (r *documentVersionResolver) Document(ctx context.Context, obj *types.DocumentVersion) (*types.Document, error) { - if err := r.authorize(ctx, obj.ID, probo.ActionDocumentGet); err != nil { - return nil, err - } - - loaders := dataloader.FromContext(ctx) - - document, err := loaders.Document.Load(ctx, obj.Document.ID) - if err != nil { - if errors.Is(err, coredata.ErrResourceNotFound) || errors.Is(err, dataloadgen.ErrNotFound) { - return nil, gqlutils.NotFound(ctx, err) - } - - r.logger.ErrorCtx(ctx, "cannot get document", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return types.NewDocument(document), nil -} - -// Approvers is the resolver for the approvers field. -func (r *documentVersionResolver) Approvers(ctx context.Context, obj *types.DocumentVersion, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.ProfileOrderBy) (*types.ProfileConnection, error) { - if err := r.authorize(ctx, obj.ID, iam.ActionMembershipProfileList); err != nil { - return nil, err - } - - if gqlutils.OnlyTotalCountSelected(ctx) { - return &types.ProfileConnection{ - Resolver: r, - ParentID: obj.ID, - }, nil - } - - prb := r.ProboService(ctx, obj.ID.TenantID()) - - pageOrderBy := page.OrderBy[coredata.MembershipProfileOrderField]{ - Field: coredata.MembershipProfileOrderFieldCreatedAt, - Direction: page.OrderDirectionDesc, - } - if orderBy != nil { - pageOrderBy.Field = coredata.MembershipProfileOrderField(orderBy.Field) - pageOrderBy.Direction = page.OrderDirection(orderBy.Direction) - } - - c := types.NewCursor(first, after, last, before, pageOrderBy) - - p, err := prb.Documents.ListVersionApprovers(ctx, obj.ID, c) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot list document version approvers", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return types.NewProfileConnection(p, r, obj.ID, nil), nil -} - -// Signatures is the resolver for the signatures field. -func (r *documentVersionResolver) Signatures(ctx context.Context, obj *types.DocumentVersion, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.DocumentVersionSignatureOrder, filter *types.DocumentVersionSignatureFilter) (*types.DocumentVersionSignatureConnection, error) { - if err := r.authorize(ctx, obj.ID, probo.ActionDocumentVersionSignatureList); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, obj.ID.TenantID()) - - pageOrderBy := page.OrderBy[coredata.DocumentVersionSignatureOrderField]{ - Field: coredata.DocumentVersionSignatureOrderFieldCreatedAt, - Direction: page.OrderDirectionDesc, - } - if orderBy != nil { - pageOrderBy = page.OrderBy[coredata.DocumentVersionSignatureOrderField]{ - Field: orderBy.Field, - Direction: orderBy.Direction, - } - } - - var signatureStates []coredata.DocumentVersionSignatureState - var activeContract *bool - if filter != nil { - if filter.States != nil { - signatureStates = filter.States - } - if filter.ActiveContract != nil { - activeContract = filter.ActiveContract - } - } - signatureFilter := coredata.NewDocumentVersionSignatureFilter(signatureStates, activeContract) - - cursor := types.NewCursor(first, after, last, before, pageOrderBy) - - page, err := prb.Documents.ListSignatures(ctx, obj.ID, cursor, signatureFilter) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot list document version signatures", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return types.NewDocumentVersionSignatureConnection(page, r, obj.ID, signatureFilter), nil -} - -// ApprovalQuorums is the resolver for the approvalQuorums field. -func (r *documentVersionResolver) ApprovalQuorums(ctx context.Context, obj *types.DocumentVersion, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.DocumentVersionApprovalQuorumOrder) (*types.DocumentVersionApprovalQuorumConnection, error) { - if err := r.authorize(ctx, obj.ID, probo.ActionDocumentVersionApprovalList); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, obj.ID.TenantID()) - - pageOrderBy := page.OrderBy[coredata.DocumentVersionApprovalQuorumOrderField]{ - Field: coredata.DocumentVersionApprovalQuorumOrderFieldCreatedAt, - Direction: page.OrderDirectionDesc, - } - if orderBy != nil { - pageOrderBy = page.OrderBy[coredata.DocumentVersionApprovalQuorumOrderField]{ - Field: orderBy.Field, - Direction: orderBy.Direction, - } - } - - cursor := types.NewCursor(first, after, last, before, pageOrderBy) - - p, err := prb.DocumentApprovals.ListQuorums(ctx, obj.ID, cursor) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot list approval quorums", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return types.NewDocumentVersionApprovalQuorumConnection(p, r, obj.ID), nil -} - -// Signed is the resolver for the signed field. -func (r *documentVersionResolver) Signed(ctx context.Context, obj *types.DocumentVersion) (bool, error) { - if err := r.authorize(ctx, obj.ID, probo.ActionDocumentVersionGet); err != nil { - return false, err - } - - identity := authn.IdentityFromContext(ctx) - - prb := r.ProboService(ctx, obj.ID.TenantID()) - - signed, err := prb.Documents.IsVersionSignedByUserEmail(ctx, obj.ID, identity.EmailAddress) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot check if document version is signed", log.Error(err)) - return false, gqlutils.Internal(ctx) - } - - return signed, nil -} - -// Permission is the resolver for the permission field. -func (r *documentVersionResolver) Permission(ctx context.Context, obj *types.DocumentVersion, action string) (bool, error) { - return r.Resolver.Permission(ctx, obj, action) -} - -// Quorum is the resolver for the quorum field. -func (r *documentVersionApprovalDecisionResolver) Quorum(ctx context.Context, obj *types.DocumentVersionApprovalDecision) (*types.DocumentVersionApprovalQuorum, error) { - if err := r.authorize(ctx, obj.ID, probo.ActionDocumentVersionApprovalList); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, obj.ID.TenantID()) - - quorum, err := prb.DocumentApprovals.GetQuorum(ctx, obj.Quorum.ID) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot get approval quorum", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return types.NewDocumentVersionApprovalQuorum(quorum), nil -} - -// DocumentVersion is the resolver for the documentVersion field. -func (r *documentVersionApprovalDecisionResolver) DocumentVersion(ctx context.Context, obj *types.DocumentVersionApprovalDecision) (*types.DocumentVersion, error) { - if err := r.authorize(ctx, obj.ID, probo.ActionDocumentVersionGet); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, obj.ID.TenantID()) - - quorum, err := prb.DocumentApprovals.GetQuorum(ctx, obj.Quorum.ID) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot get approval quorum", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - documentVersion, err := prb.Documents.GetVersion(ctx, quorum.VersionID) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot get document version", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return types.NewDocumentVersion(documentVersion), nil -} - -// Approver is the resolver for the approver field. -func (r *documentVersionApprovalDecisionResolver) Approver(ctx context.Context, obj *types.DocumentVersionApprovalDecision) (*types.Profile, error) { - if err := r.authorize(ctx, obj.ID, iam.ActionMembershipProfileGet); err != nil { - return nil, err - } - - profile, err := r.iam.OrganizationService.GetProfile(ctx, obj.Approver.ID) - if err != nil { - if errors.Is(err, coredata.ErrResourceNotFound) { - return nil, gqlutils.NotFound(ctx, err) - } - - r.logger.ErrorCtx(ctx, "cannot get approver profile", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return types.NewProfile(profile), nil -} - -// Permission is the resolver for the permission field. -func (r *documentVersionApprovalDecisionResolver) Permission(ctx context.Context, obj *types.DocumentVersionApprovalDecision, action string) (bool, error) { - // Approve and reject actions are only allowed for the viewer's own decision. - if action == probo.ActionDocumentVersionApprove || action == probo.ActionDocumentVersionReject { - identity := authn.IdentityFromContext(ctx) - - profile, err := r.iam.OrganizationService.GetProfile(ctx, obj.Approver.ID) - if err != nil { - if errors.Is(err, coredata.ErrResourceNotFound) { - return false, nil - } - - return false, gqlutils.Internal(ctx) - } - - if profile.IdentityID != identity.ID { - return false, nil - } - } - - return r.Resolver.Permission(ctx, obj, action) -} - -// TotalCount is the resolver for the totalCount field. -func (r *documentVersionApprovalDecisionConnectionResolver) TotalCount(ctx context.Context, obj *types.DocumentVersionApprovalDecisionConnection) (int, error) { - if obj.ParentID.EntityType() != coredata.DocumentVersionApprovalQuorumEntityType { - return 0, nil - } - - if err := r.authorize(ctx, obj.ParentID, probo.ActionDocumentVersionApprovalList); err != nil { - return 0, err - } - - prb := r.ProboService(ctx, obj.ParentID.TenantID()) - - filter := coredata.NewDocumentVersionApprovalDecisionFilter(nil) - if obj.Filters != nil { - filter = obj.Filters - } - - count, err := prb.DocumentApprovals.CountDecisions(ctx, obj.ParentID, filter) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot count approval decisions", log.Error(err)) - return 0, gqlutils.Internal(ctx) - } - - return count, nil -} - -// DocumentVersion is the resolver for the documentVersion field. -func (r *documentVersionApprovalQuorumResolver) DocumentVersion(ctx context.Context, obj *types.DocumentVersionApprovalQuorum) (*types.DocumentVersion, error) { - if err := r.authorize(ctx, obj.ID, probo.ActionDocumentVersionGet); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, obj.ID.TenantID()) - - documentVersion, err := prb.Documents.GetVersion(ctx, obj.DocumentVersion.ID) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot get document version", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return types.NewDocumentVersion(documentVersion), nil -} - -// Decisions is the resolver for the decisions field. -func (r *documentVersionApprovalQuorumResolver) Decisions(ctx context.Context, obj *types.DocumentVersionApprovalQuorum, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.DocumentVersionApprovalDecisionOrder, filter *types.DocumentVersionApprovalDecisionFilter) (*types.DocumentVersionApprovalDecisionConnection, error) { - if err := r.authorize(ctx, obj.ID, probo.ActionDocumentVersionApprovalList); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, obj.ID.TenantID()) - - pageOrderBy := page.OrderBy[coredata.DocumentVersionApprovalDecisionOrderField]{ - Field: coredata.DocumentVersionApprovalDecisionOrderFieldCreatedAt, - Direction: page.OrderDirectionDesc, - } - if orderBy != nil { - pageOrderBy = page.OrderBy[coredata.DocumentVersionApprovalDecisionOrderField]{ - Field: orderBy.Field, - Direction: orderBy.Direction, - } - } - - var approvalStates []coredata.DocumentVersionApprovalDecisionState - if filter != nil && filter.States != nil { - approvalStates = filter.States - } - approvalFilter := coredata.NewDocumentVersionApprovalDecisionFilter(approvalStates) - - cursor := types.NewCursor(first, after, last, before, pageOrderBy) - - p, err := prb.DocumentApprovals.ListDecisions(ctx, obj.ID, cursor, approvalFilter) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot list approval decisions", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return types.NewDocumentVersionApprovalDecisionConnection(p, r, obj.ID, approvalFilter), nil -} - -// Permission is the resolver for the permission field. -func (r *documentVersionApprovalQuorumResolver) Permission(ctx context.Context, obj *types.DocumentVersionApprovalQuorum, action string) (bool, error) { - return r.Resolver.Permission(ctx, obj, action) -} - -// TotalCount is the resolver for the totalCount field. -func (r *documentVersionApprovalQuorumConnectionResolver) TotalCount(ctx context.Context, obj *types.DocumentVersionApprovalQuorumConnection) (int, error) { - if err := r.authorize(ctx, obj.ParentID, probo.ActionDocumentVersionApprovalList); err != nil { - return 0, err - } - - prb := r.ProboService(ctx, obj.ParentID.TenantID()) - - count, err := prb.DocumentApprovals.CountQuorums(ctx, obj.ParentID) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot count approval quorums", log.Error(err)) - return 0, gqlutils.Internal(ctx) - } - - return count, nil -} - -// TotalCount is the resolver for the totalCount field. -func (r *documentVersionConnectionResolver) TotalCount(ctx context.Context, obj *types.DocumentVersionConnection) (int, error) { - if err := r.authorize(ctx, obj.ParentID, probo.ActionDocumentVersionList); err != nil { - return 0, err - } - - prb := r.ProboService(ctx, obj.ParentID.TenantID()) - - switch obj.Resolver.(type) { - case *documentResolver: - filter := &coredata.DocumentVersionFilter{} - if obj.Filters != nil { - filter = obj.Filters - } - count, err := prb.Documents.CountVersionsForDocumentID(ctx, obj.ParentID, filter) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot count document versions", log.Error(err)) - return 0, gqlutils.Internal(ctx) - } - return count, nil - } - - r.logger.ErrorCtx(ctx, "unsupported resolver") - return 0, gqlutils.Internal(ctx) -} - -// DocumentVersion is the resolver for the documentVersion field. -func (r *documentVersionSignatureResolver) DocumentVersion(ctx context.Context, obj *types.DocumentVersionSignature) (*types.DocumentVersion, error) { - if err := r.authorize(ctx, obj.ID, probo.ActionDocumentVersionGet); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, obj.ID.TenantID()) - - documentVersion, err := prb.Documents.GetVersion(ctx, obj.DocumentVersion.ID) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot get document version", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return types.NewDocumentVersion(documentVersion), nil -} - -// SignedBy is the resolver for the signedBy field. -func (r *documentVersionSignatureResolver) SignedBy(ctx context.Context, obj *types.DocumentVersionSignature) (*types.Profile, error) { - if err := r.authorize(ctx, obj.ID, iam.ActionMembershipProfileGet); err != nil { - return nil, err - } - - loaders := dataloader.FromContext(ctx) - - signatory, err := loaders.Profile.Load(ctx, obj.SignedBy.ID) - if err != nil { - if errors.Is(err, coredata.ErrResourceNotFound) || errors.Is(err, dataloadgen.ErrNotFound) { - return nil, gqlutils.NotFound(ctx, err) - } - - r.logger.ErrorCtx(ctx, "cannot get people", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return types.NewProfile(signatory), nil -} - -// Permission is the resolver for the permission field. -func (r *documentVersionSignatureResolver) Permission(ctx context.Context, obj *types.DocumentVersionSignature, action string) (bool, error) { - return r.Resolver.Permission(ctx, obj, action) -} - -// TotalCount is the resolver for the totalCount field. -func (r *documentVersionSignatureConnectionResolver) TotalCount(ctx context.Context, obj *types.DocumentVersionSignatureConnection) (int, error) { - if err := r.authorize(ctx, obj.ParentID, probo.ActionDocumentVersionSignatureList); err != nil { - return 0, err - } - - prb := r.ProboService(ctx, obj.ParentID.TenantID()) - - switch obj.Resolver.(type) { - case *documentVersionResolver: - filter := &coredata.DocumentVersionSignatureFilter{} - if obj.Filters != nil { - filter = obj.Filters - } - count, err := prb.Documents.CountSignaturesForVersionID(ctx, obj.ParentID, filter) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot count signatures", log.Error(err)) - return 0, gqlutils.Internal(ctx) - } - return count, nil - } - - r.logger.ErrorCtx(ctx, "unsupported resolver") - return 0, gqlutils.Internal(ctx) -} - -// CertificateFileURL is the resolver for the certificateFileUrl field. -func (r *electronicSignatureResolver) CertificateFileURL(ctx context.Context, obj *types.ElectronicSignature) (*string, error) { - signature, err := r.esign.GetSignatureByID(ctx, obj.ID) - if err != nil { - return nil, fmt.Errorf("cannot load signature: %w", err) - } - - if signature.CertificateFileID == nil { - return nil, nil - } - - url, err := r.esign.GenerateCertificateFileURL(ctx, *signature.CertificateFileID, 1*time.Hour) - if err != nil { - return nil, fmt.Errorf("cannot generate certificate file URL: %w", err) - } - - return &url, nil -} - -// Events is the resolver for the events field. -func (r *electronicSignatureResolver) Events(ctx context.Context, obj *types.ElectronicSignature) ([]*types.ElectronicSignatureEvent, error) { - events, err := r.esign.GetEventsBySignatureID(ctx, obj.ID) - if err != nil { - return nil, fmt.Errorf("cannot load signature events: %w", err) - } - - result := make([]*types.ElectronicSignatureEvent, len(events)) - for i := range events { - result[i] = types.NewElectronicSignatureEvent(events[i]) - } - - return result, nil -} - -// Signed is the resolver for the signed field. -func (r *employeeDocumentResolver) Signed(ctx context.Context, obj *types.EmployeeDocument) (*bool, error) { - if err := r.authorize(ctx, obj.ID, probo.ActionEmployeeDocumentGet); err != nil { - return nil, err - } - - identity := authn.IdentityFromContext(ctx) - - prb := r.ProboService(ctx, obj.ID.TenantID()) - - signed, err := prb.Documents.IsSigned(ctx, obj.ID, identity.EmailAddress) - if err != nil { - if errors.Is(err, coredata.ErrResourceNotFound) { - return nil, nil - } - r.logger.ErrorCtx(ctx, "cannot check if document is signed", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &signed, nil -} - -// ApprovalState is the resolver for the approvalState field. -func (r *employeeDocumentResolver) ApprovalState(ctx context.Context, obj *types.EmployeeDocument) (*coredata.DocumentVersionApprovalDecisionState, error) { - if err := r.authorize(ctx, obj.ID, probo.ActionEmployeeDocumentGet); err != nil { - return nil, err - } - - identity := authn.IdentityFromContext(ctx) - - prb := r.ProboService(ctx, obj.ID.TenantID()) - - state, err := prb.Documents.GetViewerApprovalState(ctx, obj.ID, identity.ID) - if err != nil { - if errors.Is(err, coredata.ErrResourceNotFound) { - return nil, nil - } - r.logger.ErrorCtx(ctx, "cannot get viewer approval state", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &state, nil -} - -// Versions is the resolver for the versions field. -func (r *employeeDocumentResolver) Versions(ctx context.Context, obj *types.EmployeeDocument, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.DocumentVersionOrderBy) (*types.EmployeeDocumentVersionConnection, error) { - if err := r.authorize(ctx, obj.ID, probo.ActionEmployeeDocumentGet); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, obj.ID.TenantID()) - - pageOrderBy := page.OrderBy[coredata.DocumentVersionOrderField]{ - Field: coredata.DocumentVersionOrderFieldCreatedAt, - Direction: page.OrderDirectionDesc, - } - if orderBy != nil { - pageOrderBy = page.OrderBy[coredata.DocumentVersionOrderField]{ - Field: orderBy.Field, - Direction: orderBy.Direction, - } - } - - cursor := types.NewCursor(first, after, last, before, pageOrderBy) - - identity := authn.IdentityFromContext(ctx) - - var filterMode coredata.EmployeeFilterMode - switch obj.FilterMode { - case types.EmployeeDocumentFilterModeSignature: - filterMode = coredata.EmployeeFilterModeSignature - case types.EmployeeDocumentFilterModeApproval: - filterMode = coredata.EmployeeFilterModeApproval - default: - r.logger.ErrorCtx(ctx, "unsupported employee document filter mode", log.String("filter_mode", string(obj.FilterMode))) - return nil, gqlutils.Internal(ctx) - } - - versionFilter := coredata.NewDocumentVersionFilter(). - WithEmployeeIdentityID(&identity.ID, filterMode) - - versionsPage, err := prb.Documents.ListVersions(ctx, obj.ID, cursor, versionFilter) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot list employee document versions", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - employeeVersions := make([]*types.EmployeeDocumentVersion, len(versionsPage.Data)) - for i, v := range versionsPage.Data { - employeeVersions[i] = &types.EmployeeDocumentVersion{ - ID: v.ID, - DocumentID: obj.ID, - OrganizationID: v.OrganizationID, - Major: v.Major, - Minor: v.Minor, - Status: v.Status, - Classification: v.Classification, - DocumentType: v.DocumentType, - PublishedAt: v.PublishedAt, - CreatedAt: v.CreatedAt, - UpdatedAt: v.UpdatedAt, - } - } - - p := page.NewPage(employeeVersions, versionsPage.Cursor) - - return types.NewEmployeeDocumentVersionConnection(p), nil -} - -// Signed is the resolver for the signed field. -func (r *employeeDocumentVersionResolver) Signed(ctx context.Context, obj *types.EmployeeDocumentVersion) (bool, error) { - if err := r.authorize(ctx, obj.DocumentID, probo.ActionEmployeeDocumentGet); err != nil { - return false, err - } - - identity := authn.IdentityFromContext(ctx) - - prb := r.ProboService(ctx, obj.ID.TenantID()) - - signed, err := prb.Documents.IsVersionSignedByUserEmail(ctx, obj.ID, identity.EmailAddress) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot check if version is signed", log.Error(err)) - return false, gqlutils.Internal(ctx) - } - - return signed, nil -} - -// ApprovalDecision is the resolver for the approvalDecision field. -func (r *employeeDocumentVersionResolver) ApprovalDecision(ctx context.Context, obj *types.EmployeeDocumentVersion) (*types.DocumentVersionApprovalDecision, error) { - if err := r.authorize(ctx, obj.DocumentID, probo.ActionEmployeeDocumentGet); err != nil { - return nil, err - } - - identity := authn.IdentityFromContext(ctx) - prb := r.ProboService(ctx, obj.ID.TenantID()) - - decision, err := prb.DocumentApprovals.GetViewerDecision(ctx, obj.ID, identity.ID) - if err != nil { - if errors.Is(err, coredata.ErrResourceNotFound) { - return nil, nil - } - - r.logger.ErrorCtx(ctx, "cannot get viewer approval decision", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return types.NewDocumentVersionApprovalDecision(decision), nil -} - -// File is the resolver for the file field. -func (r *evidenceResolver) File(ctx context.Context, obj *types.Evidence) (*types.File, error) { - if err := r.authorize(ctx, obj.ID, probo.ActionFileGet); err != nil { - return nil, err - } - - if obj.File == nil { - return nil, nil - } - - loaders := dataloader.FromContext(ctx) - - file, err := loaders.File.Load(ctx, obj.File.ID) - if err != nil { - if errors.Is(err, coredata.ErrResourceNotFound) || errors.Is(err, dataloadgen.ErrNotFound) { - return nil, gqlutils.NotFound(ctx, err) - } - - r.logger.ErrorCtx(ctx, "cannot load evidence file", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return types.NewFile(file), nil -} - -// Task is the resolver for the task field. -func (r *evidenceResolver) Task(ctx context.Context, obj *types.Evidence) (*types.Task, error) { - if err := r.authorize(ctx, obj.ID, probo.ActionTaskGet); err != nil { - return nil, err - } - - if obj.Task == nil { - r.logger.ErrorCtx(ctx, "evidence is not associated with a task") - return nil, gqlutils.Internal(ctx) - } - - loaders := dataloader.FromContext(ctx) - - task, err := loaders.Task.Load(ctx, obj.Task.ID) - if err != nil { - if errors.Is(err, coredata.ErrResourceNotFound) || errors.Is(err, dataloadgen.ErrNotFound) { - return nil, gqlutils.NotFound(ctx, err) - } - - r.logger.ErrorCtx(ctx, "cannot load task", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return types.NewTask(task), nil -} - -// Measure is the resolver for the measure field. -func (r *evidenceResolver) Measure(ctx context.Context, obj *types.Evidence) (*types.Measure, error) { - if err := r.authorize(ctx, obj.ID, probo.ActionMeasureGet); err != nil { - return nil, err - } - - loaders := dataloader.FromContext(ctx) - - measure, err := loaders.Measure.Load(ctx, obj.Measure.ID) - if err != nil { - if errors.Is(err, coredata.ErrResourceNotFound) || errors.Is(err, dataloadgen.ErrNotFound) { - return nil, gqlutils.NotFound(ctx, err) - } - - r.logger.ErrorCtx(ctx, "cannot load measure", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return types.NewMeasure(measure), nil -} - -// Permission is the resolver for the permission field. -func (r *evidenceResolver) Permission(ctx context.Context, obj *types.Evidence, action string) (bool, error) { - return r.Resolver.Permission(ctx, obj, action) -} - -// TotalCount is the resolver for the totalCount field. -func (r *evidenceConnectionResolver) TotalCount(ctx context.Context, obj *types.EvidenceConnection) (int, error) { - if err := r.authorize(ctx, obj.ParentID, probo.ActionEvidenceList); err != nil { - return 0, err - } - - prb := r.ProboService(ctx, obj.ParentID.TenantID()) - - switch obj.Resolver.(type) { - case *measureResolver: - count, err := prb.Evidences.CountForMeasureID(ctx, obj.ParentID) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot count measure evidence", log.Error(err)) - return 0, gqlutils.Internal(ctx) - } - return count, nil - case *taskResolver: - count, err := prb.Evidences.CountForTaskID(ctx, obj.ParentID) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot count task evidence", log.Error(err)) - return 0, gqlutils.Internal(ctx) - } - return count, nil - } - - r.logger.ErrorCtx(ctx, "unsupported resolver") - return 0, gqlutils.Internal(ctx) -} - -// DownloadURL is the resolver for the downloadUrl field. -func (r *fileResolver) DownloadURL(ctx context.Context, obj *types.File) (string, error) { - if err := r.authorize(ctx, obj.ID, probo.ActionFileDownloadUrl); err != nil { - return "", err - } - - prb := r.ProboService(ctx, obj.ID.TenantID()) - - downloadUrl, err := prb.Files.GenerateFileTempURL(ctx, obj.ID, 60*time.Second) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot generate download URL", log.Error(err)) - return "", gqlutils.Internal(ctx) - } - - return downloadUrl, nil -} - -// Organization is the resolver for the organization field. -func (r *findingResolver) Organization(ctx context.Context, obj *types.Finding) (*types.Organization, error) { - if err := r.authorize(ctx, obj.ID, probo.ActionOrganizationGet); err != nil { - return nil, err - } - - loaders := dataloader.FromContext(ctx) - - organization, err := loaders.Organization.Load(ctx, obj.Organization.ID) - if err != nil { - if errors.Is(err, coredata.ErrResourceNotFound) || errors.Is(err, dataloadgen.ErrNotFound) { - return nil, gqlutils.NotFound(ctx, err) - } - - r.logger.ErrorCtx(ctx, "cannot get finding organization", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return types.NewOrganization(organization), nil -} - -// Audits is the resolver for the audits field. -func (r *findingResolver) Audits(ctx context.Context, obj *types.Finding, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.AuditOrderBy) (*types.AuditConnection, error) { - if err := r.authorize(ctx, obj.ID, probo.ActionAuditList); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, obj.ID.TenantID()) - - pageOrderBy := page.OrderBy[coredata.AuditOrderField]{ - Field: coredata.AuditOrderFieldCreatedAt, - Direction: page.OrderDirectionDesc, - } - if orderBy != nil { - pageOrderBy = page.OrderBy[coredata.AuditOrderField]{ - Field: orderBy.Field, - Direction: orderBy.Direction, - } - } - - cursor := types.NewCursor(first, after, last, before, pageOrderBy) - - p, err := prb.Audits.ListForFindingID(ctx, obj.ID, cursor) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot list finding audits", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return types.NewAuditConnection(p, r, obj.ID), nil -} - -// Owner is the resolver for the owner field. -func (r *findingResolver) Owner(ctx context.Context, obj *types.Finding) (*types.Profile, error) { - if obj.Owner == nil { - return nil, nil - } - - if err := r.authorize(ctx, obj.ID, iam.ActionMembershipProfileGet); err != nil { - return nil, err - } - - loaders := dataloader.FromContext(ctx) - - owner, err := loaders.Profile.Load(ctx, obj.Owner.ID) - if err != nil { - if errors.Is(err, coredata.ErrResourceNotFound) || errors.Is(err, dataloadgen.ErrNotFound) { - return nil, gqlutils.NotFound(ctx, err) - } - - r.logger.ErrorCtx(ctx, "cannot get finding owner", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return types.NewProfile(owner), nil -} - -// Risk is the resolver for the risk field. -func (r *findingResolver) Risk(ctx context.Context, obj *types.Finding) (*types.Risk, error) { - if obj.Risk == nil { - return nil, nil - } - - if err := r.authorize(ctx, obj.ID, probo.ActionRiskGet); err != nil { - return nil, err - } - - loaders := dataloader.FromContext(ctx) - - risk, err := loaders.Risk.Load(ctx, obj.Risk.ID) - if err != nil { - if errors.Is(err, coredata.ErrResourceNotFound) || errors.Is(err, dataloadgen.ErrNotFound) { - return nil, gqlutils.NotFound(ctx, err) - } - - r.logger.ErrorCtx(ctx, "cannot get finding risk", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return types.NewRisk(risk), nil -} - -// Permission is the resolver for the permission field. -func (r *findingResolver) Permission(ctx context.Context, obj *types.Finding, action string) (bool, error) { - return r.Resolver.Permission(ctx, obj, action) -} - -// TotalCount is the resolver for the totalCount field. -func (r *findingConnectionResolver) TotalCount(ctx context.Context, obj *types.FindingConnection) (int, error) { - if err := r.authorize(ctx, obj.ParentID, probo.ActionFindingList); err != nil { - return 0, err - } - - prb := r.ProboService(ctx, obj.ParentID.TenantID()) - - var ( - kind *coredata.FindingKind - status *coredata.FindingStatus - priority *coredata.FindingPriority - ownerID *gid.GID - ) - if obj.Filter != nil { - kind = obj.Filter.Kind - status = obj.Filter.Status - priority = obj.Filter.Priority - ownerID = obj.Filter.OwnerID - } - - findingFilter := coredata.NewFindingFilter(nil, kind, status, priority, ownerID) - if obj.Filter != nil { - findingFilter = coredata.NewFindingFilter(&obj.Filter.SnapshotID, kind, status, priority, ownerID) - } - - switch obj.Resolver.(type) { - case *organizationResolver: - count, err := prb.Findings.CountForOrganizationID(ctx, obj.ParentID, findingFilter) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot count findings", log.Error(err)) - return 0, gqlutils.Internal(ctx) - } - return count, nil - case *auditResolver: - count, err := prb.Findings.CountForAuditID(ctx, obj.ParentID, findingFilter) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot count findings", log.Error(err)) - return 0, gqlutils.Internal(ctx) - } - return count, nil - } - - r.logger.ErrorCtx(ctx, "unsupported resolver", log.Any("resolver", obj.Resolver)) - return 0, gqlutils.Internal(ctx) -} - -// Organization is the resolver for the organization field. -func (r *frameworkResolver) Organization(ctx context.Context, obj *types.Framework) (*types.Organization, error) { - if err := r.authorize(ctx, obj.ID, probo.ActionOrganizationGet); err != nil { - return nil, err - } - - loaders := dataloader.FromContext(ctx) - - organization, err := loaders.Organization.Load(ctx, obj.Organization.ID) - if err != nil { - if errors.Is(err, coredata.ErrResourceNotFound) || errors.Is(err, dataloadgen.ErrNotFound) { - return nil, gqlutils.NotFound(ctx, err) - } - - r.logger.ErrorCtx(ctx, "cannot load organization", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return types.NewOrganization(organization), nil -} - -// Controls is the resolver for the controls field. -func (r *frameworkResolver) Controls(ctx context.Context, obj *types.Framework, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.ControlOrderBy, filter *types.ControlFilter) (*types.ControlConnection, error) { - if err := r.authorize(ctx, obj.ID, probo.ActionControlList); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, obj.ID.TenantID()) - - pageOrderBy := page.OrderBy[coredata.ControlOrderField]{ - Field: coredata.ControlOrderFieldCreatedAt, - Direction: page.OrderDirectionDesc, - } - if orderBy != nil { - pageOrderBy = page.OrderBy[coredata.ControlOrderField]{ - Field: orderBy.Field, - Direction: orderBy.Direction, - } - } - - cursor := types.NewCursor(first, after, last, before, pageOrderBy) - - var controlFilter = coredata.NewControlFilter(nil) - if filter != nil { - controlFilter = coredata.NewControlFilter(filter.Query) - } - - page, err := prb.Controls.ListForFrameworkID(ctx, obj.ID, cursor, controlFilter) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot list controls", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return types.NewControlConnection(page, r, obj.ID, controlFilter), nil -} - -// LightLogoURL is the resolver for the lightLogoURL field. -func (r *frameworkResolver) LightLogoURL(ctx context.Context, obj *types.Framework) (*string, error) { - if err := r.authorize(ctx, obj.ID, probo.ActionFrameworkGet); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, obj.ID.TenantID()) - - return prb.Frameworks.GenerateLightLogoURL(ctx, obj.ID, 1*time.Hour) -} - -// DarkLogoURL is the resolver for the darkLogoURL field. -func (r *frameworkResolver) DarkLogoURL(ctx context.Context, obj *types.Framework) (*string, error) { - if err := r.authorize(ctx, obj.ID, probo.ActionFrameworkGet); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, obj.ID.TenantID()) - - return prb.Frameworks.GenerateDarkLogoURL(ctx, obj.ID, 1*time.Hour) -} - -// Permission is the resolver for the permission field. -func (r *frameworkResolver) Permission(ctx context.Context, obj *types.Framework, action string) (bool, error) { - return r.Resolver.Permission(ctx, obj, action) -} - -// TotalCount is the resolver for the totalCount field. -func (r *frameworkConnectionResolver) TotalCount(ctx context.Context, obj *types.FrameworkConnection) (int, error) { - if err := r.authorize(ctx, obj.ParentID, probo.ActionFrameworkList); err != nil { - return 0, err - } - - switch obj.Resolver.(type) { - case *organizationResolver: - prb := r.ProboService(ctx, obj.ParentID.TenantID()) - - count, err := prb.Frameworks.CountForOrganizationID(ctx, obj.ParentID) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot count frameworks", log.Error(err)) - return 0, gqlutils.Internal(ctx) - } - return count, nil - } - - r.logger.ErrorCtx(ctx, "unsupported resolver") - return 0, gqlutils.Internal(ctx) -} - -// Subscribers is the resolver for the subscribers field on MailingList. -func (r *mailingListResolver) Subscribers(ctx context.Context, obj *types.MailingList, first *int, after *page.CursorKey, last *int, before *page.CursorKey) (*types.MailingListSubscriberConnection, error) { - if err := r.authorize(ctx, obj.ID, probo.ActionMailingListSubscriberList); err != nil { - return nil, err - } - - pageOrderBy := page.OrderBy[coredata.MailingListSubscriberOrderField]{ - Field: coredata.MailingListSubscriberOrderFieldCreatedAt, - Direction: page.OrderDirectionDesc, - } - - cursor := types.NewCursor(first, after, last, before, pageOrderBy) - - result, err := r.mailman.ListSubscribers(ctx, obj.ID, cursor) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot list mailing list subscribers", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return types.NewMailingListSubscriberConnection(result, r, obj.ID), nil -} - -// Updates is the resolver for the updates field on MailingList. -func (r *mailingListResolver) Updates(ctx context.Context, obj *types.MailingList, first *int, after *page.CursorKey, last *int, before *page.CursorKey) (*types.MailingListUpdateConnection, error) { - if err := r.authorize(ctx, obj.ID, probo.ActionMailingListUpdateList); err != nil { - return nil, err - } - - pageOrderBy := page.OrderBy[coredata.MailingListUpdateOrderField]{ - Field: coredata.MailingListUpdateOrderFieldUpdatedAt, - Direction: page.OrderDirectionDesc, - } - - cursor := types.NewCursor(first, after, last, before, pageOrderBy) - - result, err := r.mailman.ListMailingListUpdates(ctx, obj.ID, cursor) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot list mailing list updates", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return types.NewMailingListUpdateConnection(result, r, obj.ID), nil -} - -// TotalCount is the resolver for the totalCount field. -func (r *mailingListSubscriberConnectionResolver) TotalCount(ctx context.Context, obj *types.MailingListSubscriberConnection) (int, error) { - if err := r.authorize(ctx, obj.ParentID, probo.ActionMailingListSubscriberList); err != nil { - return 0, err - } - - switch obj.Resolver.(type) { - case *mailingListResolver: - count, err := r.mailman.CountSubscribers(ctx, obj.ParentID) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot count mailing list subscribers", log.Error(err)) - return 0, gqlutils.Internal(ctx) - } - return count, nil - } - - r.logger.ErrorCtx(ctx, "unsupported resolver for mailing list subscriber connection", log.String("resolver", fmt.Sprintf("%T", obj.Resolver))) - return 0, gqlutils.Internal(ctx) -} - -// TotalCount is the resolver for the totalCount field on MailingListUpdateConnection. -func (r *mailingListUpdateConnectionResolver) TotalCount(ctx context.Context, obj *types.MailingListUpdateConnection) (int, error) { - if err := r.authorize(ctx, obj.ParentID, probo.ActionMailingListUpdateList); err != nil { - return 0, err - } - - count, err := r.mailman.CountMailingListUpdates(ctx, obj.ParentID) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot count mailing list updates", log.Error(err)) - return 0, gqlutils.Internal(ctx) - } - - return count, nil -} - -// Evidences is the resolver for the evidences field. -func (r *measureResolver) Evidences(ctx context.Context, obj *types.Measure, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.EvidenceOrderBy) (*types.EvidenceConnection, error) { - if err := r.authorize(ctx, obj.ID, probo.ActionEvidenceList); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, obj.ID.TenantID()) - - pageOrderBy := page.OrderBy[coredata.EvidenceOrderField]{ - Field: coredata.EvidenceOrderFieldCreatedAt, - Direction: page.OrderDirectionDesc, - } - if orderBy != nil { - pageOrderBy = page.OrderBy[coredata.EvidenceOrderField]{ - Field: orderBy.Field, - Direction: orderBy.Direction, - } - } - - cursor := types.NewCursor(first, after, last, before, pageOrderBy) - - page, err := prb.Evidences.ListForMeasureID(ctx, obj.ID, cursor) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot list measure evidences", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return types.NewEvidenceConnection(page, r, obj.ID), nil -} - -// Tasks is the resolver for the tasks field. -func (r *measureResolver) Tasks(ctx context.Context, obj *types.Measure, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.TaskOrderBy) (*types.TaskConnection, error) { - if err := r.authorize(ctx, obj.ID, probo.ActionTaskList); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, obj.ID.TenantID()) - - pageOrderBy := page.OrderBy[coredata.TaskOrderField]{ - Field: coredata.TaskOrderFieldCreatedAt, - Direction: page.OrderDirectionDesc, - } - if orderBy != nil { - pageOrderBy = page.OrderBy[coredata.TaskOrderField]{ - Field: orderBy.Field, - Direction: orderBy.Direction, - } - } - - cursor := types.NewCursor(first, after, last, before, pageOrderBy) - - page, err := prb.Tasks.ListForMeasureID(ctx, obj.ID, cursor) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot list measure tasks", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return types.NewTaskConnection(page, r, obj.ID), nil -} - -// Risks is the resolver for the risks field. -func (r *measureResolver) Risks(ctx context.Context, obj *types.Measure, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.RiskOrderBy, filter *types.RiskFilter) (*types.RiskConnection, error) { - if err := r.authorize(ctx, obj.ID, probo.ActionRiskList); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, obj.ID.TenantID()) - - pageOrderBy := page.OrderBy[coredata.RiskOrderField]{ - Field: coredata.RiskOrderFieldCreatedAt, - Direction: page.OrderDirectionDesc, - } - if orderBy != nil { - pageOrderBy = page.OrderBy[coredata.RiskOrderField]{ - Field: orderBy.Field, - Direction: orderBy.Direction, - } - } - - cursor := types.NewCursor(first, after, last, before, pageOrderBy) - - var riskFilter = coredata.NewRiskFilter(nil, nil) - if filter != nil { - riskFilter = coredata.NewRiskFilter(filter.Query, &filter.SnapshotID) - } - - page, err := prb.Risks.ListForMeasureID(ctx, obj.ID, cursor, riskFilter) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot list measure risks", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return types.NewRiskConnection(page, r, obj.ID, riskFilter), nil -} - -// Controls is the resolver for the controls field. -func (r *measureResolver) Controls(ctx context.Context, obj *types.Measure, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.ControlOrderBy, filter *types.ControlFilter) (*types.ControlConnection, error) { - if err := r.authorize(ctx, obj.ID, probo.ActionControlList); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, obj.ID.TenantID()) - - pageOrderBy := page.OrderBy[coredata.ControlOrderField]{ - Field: coredata.ControlOrderFieldCreatedAt, - Direction: page.OrderDirectionDesc, - } - if orderBy != nil { - pageOrderBy = page.OrderBy[coredata.ControlOrderField]{ - Field: orderBy.Field, - Direction: orderBy.Direction, - } - } - - cursor := types.NewCursor(first, after, last, before, pageOrderBy) - - var controlFilter = coredata.NewControlFilter(nil) - if filter != nil { - controlFilter = coredata.NewControlFilter(filter.Query) - } - - page, err := prb.Controls.ListForMeasureID(ctx, obj.ID, cursor, controlFilter) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot list measure controls", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return types.NewControlConnection(page, r, obj.ID, controlFilter), nil -} - -// Documents is the resolver for the documents field. -func (r *measureResolver) Documents(ctx context.Context, obj *types.Measure, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.DocumentOrderBy, filter *types.DocumentFilter) (*types.DocumentConnection, error) { - if err := r.authorize(ctx, obj.ID, probo.ActionDocumentList); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, obj.ID.TenantID()) - - pageOrderBy := page.OrderBy[coredata.DocumentOrderField]{ - Field: coredata.DocumentOrderFieldCreatedAt, - Direction: page.OrderDirectionDesc, - } - if orderBy != nil { - pageOrderBy = page.OrderBy[coredata.DocumentOrderField]{ - Field: orderBy.Field, - Direction: orderBy.Direction, - } - } - - cursor := types.NewCursor(first, after, last, before, pageOrderBy) - - var documentFilter = coredata.NewDocumentFilter(nil) - if filter != nil { - documentFilter = coredata.NewDocumentFilter(filter.Query). - WithDocumentTypes(filter.DocumentTypes). - WithClassifications(filter.Classifications) - } - - pg, err := prb.Documents.ListForMeasureID(ctx, obj.ID, cursor, documentFilter) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot list documents", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return types.NewDocumentConnection(pg, r, obj.ID, documentFilter), nil -} - -// Permission is the resolver for the permission field. -func (r *measureResolver) Permission(ctx context.Context, obj *types.Measure, action string) (bool, error) { - return r.Resolver.Permission(ctx, obj, action) -} - -// TotalCount is the resolver for the totalCount field. -func (r *measureConnectionResolver) TotalCount(ctx context.Context, obj *types.MeasureConnection) (int, error) { - if err := r.authorize(ctx, obj.ParentID, probo.ActionMeasureList); err != nil { - return 0, err - } - - prb := r.ProboService(ctx, obj.ParentID.TenantID()) - - switch obj.Resolver.(type) { - case *organizationResolver: - count, err := prb.Measures.CountForOrganizationID(ctx, obj.ParentID, obj.Filters) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot count measures", log.Error(err)) - return 0, gqlutils.Internal(ctx) - } - return count, nil - case *controlResolver: - count, err := prb.Measures.CountForControlID(ctx, obj.ParentID, obj.Filters) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot count measures", log.Error(err)) - return 0, gqlutils.Internal(ctx) - } - return count, nil - case *riskResolver: - count, err := prb.Measures.CountForRiskID(ctx, obj.ParentID, obj.Filters) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot count measures", log.Error(err)) - return 0, gqlutils.Internal(ctx) - } - return count, nil - } - - r.logger.ErrorCtx(ctx, "unsupported resolver") - return 0, gqlutils.Internal(ctx) -} - -// Attendees is the resolver for the attendees field. -func (r *meetingResolver) Attendees(ctx context.Context, obj *types.Meeting) ([]*types.Profile, error) { - // TODO bug must be paginated - - if err := r.authorize(ctx, obj.ID, iam.ActionMembershipProfileList); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, obj.ID.TenantID()) - - attendees, err := prb.Meetings.GetAttendees(ctx, obj.ID) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot load meeting attendees", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - if len(attendees) == 0 { - return []*types.Profile{}, nil - } - - people := make([]*types.Profile, len(attendees)) - for i, attendee := range attendees { - people[i] = types.NewProfile(attendee) - } - - return people, nil -} - -// Organization is the resolver for the organization field. -func (r *meetingResolver) Organization(ctx context.Context, obj *types.Meeting) (*types.Organization, error) { - if err := r.authorize(ctx, obj.ID, probo.ActionOrganizationGet); err != nil { - return nil, err - } - - loaders := dataloader.FromContext(ctx) - - organization, err := loaders.Organization.Load(ctx, obj.Organization.ID) - if err != nil { - if errors.Is(err, coredata.ErrResourceNotFound) || errors.Is(err, dataloadgen.ErrNotFound) { - return nil, gqlutils.NotFound(ctx, err) - } - - r.logger.ErrorCtx(ctx, "cannot load organization", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return types.NewOrganization(organization), nil -} - -// Permission is the resolver for the permission field. -func (r *meetingResolver) Permission(ctx context.Context, obj *types.Meeting, action string) (bool, error) { - return r.Resolver.Permission(ctx, obj, action) -} - -// TotalCount is the resolver for the totalCount field. -func (r *meetingConnectionResolver) TotalCount(ctx context.Context, obj *types.MeetingConnection) (int, error) { - if err := r.authorize(ctx, obj.ParentID, probo.ActionMeetingList); err != nil { - return 0, err - } - - prb := r.ProboService(ctx, obj.ParentID.TenantID()) - - switch obj.Resolver.(type) { - case *organizationResolver: - count, err := prb.Meetings.CountForOrganizationID(ctx, obj.ParentID) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot count meetings", log.Error(err)) - return 0, gqlutils.Internal(ctx) - } - return count, nil - } - - r.logger.ErrorCtx(ctx, "unsupported resolver") - return 0, gqlutils.Internal(ctx) -} - -// UpdateOrganizationContext is the resolver for the updateOrganizationContext field. -func (r *mutationResolver) UpdateOrganizationContext(ctx context.Context, input types.UpdateOrganizationContextInput) (*types.UpdateOrganizationContextPayload, error) { - if err := r.authorize(ctx, input.OrganizationID, probo.ActionOrganizationContextUpdate); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, input.OrganizationID.TenantID()) - - req := probo.UpdateOrganizationContextRequest{ - OrganizationID: input.OrganizationID, - Product: gqlutils.UnwrapOmittable(input.Product), - Architecture: gqlutils.UnwrapOmittable(input.Architecture), - Team: gqlutils.UnwrapOmittable(input.Team), - Processes: gqlutils.UnwrapOmittable(input.Processes), - Customers: gqlutils.UnwrapOmittable(input.Customers), - } - - organizationContext, err := prb.Organizations.UpdateContext(ctx, req) - if err != nil { - if validationErrors, ok := errors.AsType[validator.ValidationErrors](err); ok { - return nil, gqlutils.InvalidValidationErrors(ctx, validationErrors) - } - r.logger.ErrorCtx(ctx, "cannot update organization context", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.UpdateOrganizationContextPayload{ - Context: types.NewOrganizationContext(organizationContext), - }, nil -} - -// UpdateTrustCenter is the resolver for the updateTrustCenter field. -func (r *mutationResolver) UpdateTrustCenter(ctx context.Context, input types.UpdateTrustCenterInput) (*types.UpdateTrustCenterPayload, error) { - if err := r.authorize(ctx, input.TrustCenterID, probo.ActionTrustCenterUpdate); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, input.TrustCenterID.TenantID()) - - trustCenter, file, err := prb.TrustCenters.Update( - ctx, - &probo.UpdateTrustCenterRequest{ - ID: input.TrustCenterID, - Active: input.Active, - SearchEngineIndexing: input.SearchEngineIndexing, - }, - ) - if err != nil { - if validationErrors, ok := errors.AsType[validator.ValidationErrors](err); ok { - return nil, gqlutils.InvalidValidationErrors(ctx, validationErrors) - } - r.logger.ErrorCtx(ctx, "cannot update trust center", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.UpdateTrustCenterPayload{ - TrustCenter: types.NewTrustCenter(trustCenter, file), - }, nil -} - -// UploadTrustCenterNda is the resolver for the uploadTrustCenterNDA field. -func (r *mutationResolver) UploadTrustCenterNda(ctx context.Context, input types.UploadTrustCenterNDAInput) (*types.UploadTrustCenterNDAPayload, error) { - if err := r.authorize(ctx, input.TrustCenterID, probo.ActionTrustCenterNonDisclosureAgreementUpload); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, input.TrustCenterID.TenantID()) - - trustCenter, file, err := prb.TrustCenters.UploadNDA( - ctx, - &probo.UploadTrustCenterNDARequest{ - TrustCenterID: input.TrustCenterID, - File: input.File.File, - FileName: input.FileName, - }, - ) - if err != nil { - if validationErrors, ok := errors.AsType[validator.ValidationErrors](err); ok { - return nil, gqlutils.InvalidValidationErrors(ctx, validationErrors) - } - r.logger.ErrorCtx(ctx, "cannot upload trust center NDA", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.UploadTrustCenterNDAPayload{ - TrustCenter: types.NewTrustCenter(trustCenter, file), - }, nil -} - -// DeleteTrustCenterNda is the resolver for the deleteTrustCenterNDA field. -func (r *mutationResolver) DeleteTrustCenterNda(ctx context.Context, input types.DeleteTrustCenterNDAInput) (*types.DeleteTrustCenterNDAPayload, error) { - if err := r.authorize(ctx, input.TrustCenterID, probo.ActionTrustCenterNonDisclosureAgreementDelete); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, input.TrustCenterID.TenantID()) - - trustCenter, file, err := prb.TrustCenters.DeleteNDA(ctx, input.TrustCenterID) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot delete trust center NDA", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.DeleteTrustCenterNDAPayload{ - TrustCenter: types.NewTrustCenter(trustCenter, file), - }, nil -} - -// UpdateTrustCenterBrand is the resolver for the updateTrustCenterBrand field. -func (r *mutationResolver) UpdateTrustCenterBrand(ctx context.Context, input types.UpdateTrustCenterBrandInput) (*types.UpdateTrustCenterBrandPayload, error) { - if err := r.authorize(ctx, input.TrustCenterID, probo.ActionTrustCenterUpdate); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, input.TrustCenterID.TenantID()) - - req := &probo.UpdateTrustCenterBrandRequest{ - TrustCenterID: input.TrustCenterID, - } - - if input.LogoFile.IsSet() { - logoFile := input.LogoFile.Value() - if logoFile == nil { - var nilFile *probo.FileUpload - req.LogoFile = &nilFile - } else { - fileUpload := &probo.FileUpload{ - Content: logoFile.File, - Filename: logoFile.Filename, - Size: logoFile.Size, - ContentType: logoFile.ContentType, - } - req.LogoFile = &fileUpload - } - } - - if input.DarkLogoFile.IsSet() { - darkLogoFile := input.DarkLogoFile.Value() - if darkLogoFile == nil { - var nilFile *probo.FileUpload - req.DarkLogoFile = &nilFile - } else { - fileUpload := &probo.FileUpload{ - Content: darkLogoFile.File, - Filename: darkLogoFile.Filename, - Size: darkLogoFile.Size, - ContentType: darkLogoFile.ContentType, - } - req.DarkLogoFile = &fileUpload - } - } - - trustCenter, file, err := prb.TrustCenters.UpdateTrustCenterBrand(ctx, req) - if err != nil { - if validationErrors, ok := errors.AsType[validator.ValidationErrors](err); ok { - return nil, gqlutils.InvalidValidationErrors(ctx, validationErrors) - } - r.logger.ErrorCtx(ctx, "cannot update trust center brand", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.UpdateTrustCenterBrandPayload{ - TrustCenter: types.NewTrustCenter(trustCenter, file), - }, nil -} - -// UpdateTrustCenterAccess is the resolver for the updateTrustCenterAccess field. -func (r *mutationResolver) UpdateTrustCenterAccess(ctx context.Context, input types.UpdateTrustCenterAccessInput) (*types.UpdateTrustCenterAccessPayload, error) { - if err := r.authorize(ctx, input.ID, probo.ActionTrustCenterAccessUpdate); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, input.ID.TenantID()) - - var documentAccesses []probo.UpdateTrustCenterDocumentAccessRequest - var reportAccesses []probo.UpdateTrustCenterDocumentAccessRequest - var fileAccesses []probo.UpdateTrustCenterDocumentAccessRequest - for _, documentAccess := range input.Documents { - documentAccesses = append(documentAccesses, probo.UpdateTrustCenterDocumentAccessRequest{ - ID: documentAccess.ID, - Status: documentAccess.Status, - }) - } - for _, reportAccess := range input.Reports { - reportAccesses = append(reportAccesses, probo.UpdateTrustCenterDocumentAccessRequest{ - ID: reportAccess.ID, - Status: reportAccess.Status, - }) - } - for _, fileAccess := range input.TrustCenterFiles { - fileAccesses = append(fileAccesses, probo.UpdateTrustCenterDocumentAccessRequest{ - ID: fileAccess.ID, - Status: fileAccess.Status, - }) - } - access, err := prb.TrustCenterAccesses.Update( - ctx, - &probo.UpdateTrustCenterAccessRequest{ - ID: input.ID, - DocumentAccesses: documentAccesses, - ReportAccesses: reportAccesses, - TrustCenterFileAccesses: fileAccesses, - }, - ) - if err != nil { - if validationErrors, ok := errors.AsType[validator.ValidationErrors](err); ok { - return nil, gqlutils.InvalidValidationErrors(ctx, validationErrors) - } - r.logger.ErrorCtx(ctx, "cannot update trust center access", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.UpdateTrustCenterAccessPayload{ - TrustCenterAccess: types.NewTrustCenterAccess(access), - }, nil -} - -// DeleteTrustCenterAccess is the resolver for the deleteTrustCenterAccess field. -func (r *mutationResolver) DeleteTrustCenterAccess(ctx context.Context, input types.DeleteTrustCenterAccessInput) (*types.DeleteTrustCenterAccessPayload, error) { - if err := r.authorize(ctx, input.ID, probo.ActionTrustCenterAccessDelete); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, input.ID.TenantID()) - - err := prb.TrustCenterAccesses.Delete(ctx, input.ID) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot delete trust center access", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.DeleteTrustCenterAccessPayload{ - DeletedTrustCenterAccessID: input.ID, - }, nil -} - -// CreateMailingListUpdate is the resolver for the createMailingListUpdate field. -func (r *mutationResolver) CreateMailingListUpdate(ctx context.Context, input types.CreateMailingListUpdateInput) (*types.CreateMailingListUpdatePayload, error) { - if err := r.authorize(ctx, input.MailingListID, probo.ActionMailingListUpdateCreate); err != nil { - return nil, err - } - - mlu, err := r.mailman.CreateMailingListUpdate( - ctx, - &mailman.CreateMailingListUpdateRequest{ - MailingListID: input.MailingListID, - Title: input.Title, - Body: input.Body, - }, - ) - if err != nil { - if validationErrors, ok := errors.AsType[validator.ValidationErrors](err); ok { - return nil, gqlutils.InvalidValidationErrors(ctx, validationErrors) - } - r.logger.ErrorCtx(ctx, "cannot create mailing list update", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.CreateMailingListUpdatePayload{ - MailingListUpdate: types.NewMailingListUpdate(mlu), - }, nil -} - -// UpdateMailingListUpdate is the resolver for the updateMailingListUpdate field. -func (r *mutationResolver) UpdateMailingListUpdate(ctx context.Context, input types.UpdateMailingListUpdateInput) (*types.UpdateMailingListUpdatePayload, error) { - if err := r.authorize(ctx, input.ID, probo.ActionMailingListUpdateUpdate); err != nil { - return nil, err - } - - mlu, err := r.mailman.UpdateMailingListUpdate( - ctx, - &mailman.UpdateMailingListUpdateRequest{ - ID: input.ID, - Title: input.Title, - Body: input.Body, - }, - ) - if err != nil { - if validationErrors, ok := errors.AsType[validator.ValidationErrors](err); ok { - return nil, gqlutils.InvalidValidationErrors(ctx, validationErrors) - } - if errors.Is(err, mailman.ErrMailingListUpdateAlreadySent) { - return nil, gqlutils.Conflictf(ctx, "mailing list update can only be edited when in draft") - } - if errors.Is(err, mailman.ErrMailingListUpdateNotFound) { - return nil, gqlutils.NotFoundf(ctx, "mailing list update not found") - } - r.logger.ErrorCtx(ctx, "cannot update mailing list update", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.UpdateMailingListUpdatePayload{ - MailingListUpdate: types.NewMailingListUpdate(mlu), - }, nil -} - -// SendMailingListUpdate is the resolver for the sendMailingListUpdate field. -func (r *mutationResolver) SendMailingListUpdate(ctx context.Context, input types.SendMailingListUpdateInput) (*types.SendMailingListUpdatePayload, error) { - if err := r.authorize(ctx, input.ID, probo.ActionMailingListUpdateUpdate); err != nil { - return nil, err - } - - mlu, err := r.mailman.SendMailingListUpdate(ctx, input.ID) - if err != nil { - if errors.Is(err, mailman.ErrMailingListUpdateAlreadySent) { - return nil, gqlutils.Conflictf(ctx, "mailing list update has already been queued for sending") - } - if errors.Is(err, mailman.ErrMailingListUpdateNotFound) { - return nil, gqlutils.NotFoundf(ctx, "mailing list update not found") - } - r.logger.ErrorCtx(ctx, "cannot queue mailing list update for sending", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.SendMailingListUpdatePayload{ - MailingListUpdate: types.NewMailingListUpdate(mlu), - }, nil -} - -// DeleteMailingListUpdate is the resolver for the deleteMailingListUpdate field. -func (r *mutationResolver) DeleteMailingListUpdate(ctx context.Context, input types.DeleteMailingListUpdateInput) (*types.DeleteMailingListUpdatePayload, error) { - if err := r.authorize(ctx, input.ID, probo.ActionMailingListUpdateDelete); err != nil { - return nil, err - } - - if err := r.mailman.DeleteMailingListUpdate(ctx, input.ID); err != nil { - if errors.Is(err, mailman.ErrMailingListUpdateNotFound) { - return nil, gqlutils.NotFoundf(ctx, "mailing list update not found") - } - r.logger.ErrorCtx(ctx, "cannot delete mailing list update", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.DeleteMailingListUpdatePayload{ - DeletedMailingListUpdateID: input.ID, - }, nil -} - -// UpdateMailingList is the resolver for the updateMailingList field. -func (r *mutationResolver) UpdateMailingList(ctx context.Context, input types.UpdateMailingListInput) (*types.UpdateMailingListPayload, error) { - if err := r.authorize(ctx, input.ID, probo.ActionMailingListUpdate); err != nil { - return nil, err - } - - ml, err := r.mailman.UpdateMailingList(ctx, input.ID, input.ReplyTo) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot update mailing list", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.UpdateMailingListPayload{ - MailingList: types.NewMailingList(ml), - }, nil -} - -// CreateMailingListSubscriber is the resolver for the createMailingListSubscriber field. -func (r *mutationResolver) CreateMailingListSubscriber(ctx context.Context, input types.CreateMailingListSubscriberInput) (*types.CreateMailingListSubscriberPayload, error) { - if err := r.authorize(ctx, input.MailingListID, probo.ActionMailingListSubscriberCreate); err != nil { - return nil, err - } - - subscriber, err := r.mailman.CreateSubscriber( - ctx, - &mailman.CreateSubscriberRequest{ - MailingListID: input.MailingListID, - Email: input.Email, - FullName: input.FullName, - Confirmed: input.Confirmed != nil && *input.Confirmed, - }, - ) - if err != nil { - if validationErrors, ok := errors.AsType[validator.ValidationErrors](err); ok { - return nil, gqlutils.InvalidValidationErrors(ctx, validationErrors) - } - if errors.Is(err, mailman.ErrSubscriberAlreadyExist) { - return nil, gqlutils.Conflictf(ctx, "subscriber already exists in this mailing list") - } - r.logger.ErrorCtx(ctx, "cannot create mailing list subscriber", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.CreateMailingListSubscriberPayload{ - MailingListSubscriberEdge: types.NewMailingListSubscriberEdge(subscriber, coredata.MailingListSubscriberOrderFieldCreatedAt), - }, nil -} - -// DeleteMailingListSubscriber is the resolver for the deleteMailingListSubscriber field. -func (r *mutationResolver) DeleteMailingListSubscriber(ctx context.Context, input types.DeleteMailingListSubscriberInput) (*types.DeleteMailingListSubscriberPayload, error) { - if err := r.authorize(ctx, input.ID, probo.ActionMailingListSubscriberDelete); err != nil { - return nil, err - } - - if err := r.mailman.DeleteSubscriber(ctx, input.ID); err != nil { - if errors.Is(err, mailman.ErrSubscriberNotFound) { - return nil, gqlutils.NotFoundf(ctx, "mailing list subscriber not found") - } - r.logger.ErrorCtx(ctx, "cannot delete mailing list subscriber", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.DeleteMailingListSubscriberPayload{ - DeletedMailingListSubscriberID: input.ID, - }, nil -} - -// CreateTrustCenterReference is the resolver for the createTrustCenterReference field. -func (r *mutationResolver) CreateTrustCenterReference(ctx context.Context, input types.CreateTrustCenterReferenceInput) (*types.CreateTrustCenterReferencePayload, error) { - if err := r.authorize(ctx, input.TrustCenterID, probo.ActionTrustCenterReferenceCreate); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, input.TrustCenterID.TenantID()) - - reference, err := prb.TrustCenterReferences.Create( - ctx, - &probo.CreateTrustCenterReferenceRequest{ - TrustCenterID: input.TrustCenterID, - Name: input.Name, - Description: input.Description, - WebsiteURL: input.WebsiteURL, - LogoFile: probo.File{ - Content: input.LogoFile.File, - Filename: input.LogoFile.Filename, - Size: input.LogoFile.Size, - ContentType: input.LogoFile.ContentType, - }, - }, - ) - if err != nil { - if validationErrors, ok := errors.AsType[validator.ValidationErrors](err); ok { - return nil, gqlutils.InvalidValidationErrors(ctx, validationErrors) - } - r.logger.ErrorCtx(ctx, "cannot create trust center reference", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.CreateTrustCenterReferencePayload{ - TrustCenterReferenceEdge: types.NewTrustCenterReferenceEdge(reference, coredata.TrustCenterReferenceOrderFieldRank), - }, nil -} - -// UpdateTrustCenterReference is the resolver for the updateTrustCenterReference field. -func (r *mutationResolver) UpdateTrustCenterReference(ctx context.Context, input types.UpdateTrustCenterReferenceInput) (*types.UpdateTrustCenterReferencePayload, error) { - if err := r.authorize(ctx, input.ID, probo.ActionTrustCenterReferenceUpdate); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, input.ID.TenantID()) - - req := &probo.UpdateTrustCenterReferenceRequest{ - ID: input.ID, - Name: input.Name, - Description: gqlutils.UnwrapOmittable(input.Description), - WebsiteURL: input.WebsiteURL, - Rank: input.Rank, - } - - if input.LogoFile != nil { - req.LogoFile = &probo.File{ - Content: input.LogoFile.File, - Filename: input.LogoFile.Filename, - Size: input.LogoFile.Size, - ContentType: input.LogoFile.ContentType, - } - } - - reference, err := prb.TrustCenterReferences.Update(ctx, req) - if err != nil { - if validationErrors, ok := errors.AsType[validator.ValidationErrors](err); ok { - return nil, gqlutils.InvalidValidationErrors(ctx, validationErrors) - } - r.logger.ErrorCtx(ctx, "cannot update trust center reference", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.UpdateTrustCenterReferencePayload{ - TrustCenterReference: types.NewTrustCenterReference(reference), - }, nil -} - -// DeleteTrustCenterReference is the resolver for the deleteTrustCenterReference field. -func (r *mutationResolver) DeleteTrustCenterReference(ctx context.Context, input types.DeleteTrustCenterReferenceInput) (*types.DeleteTrustCenterReferencePayload, error) { - if err := r.authorize(ctx, input.ID, probo.ActionTrustCenterReferenceDelete); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, input.ID.TenantID()) - - err := prb.TrustCenterReferences.Delete(ctx, input.ID) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot delete trust center reference", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.DeleteTrustCenterReferencePayload{ - DeletedTrustCenterReferenceID: input.ID, - }, nil -} - -// CreateComplianceFramework is the resolver for the createComplianceFramework field. -func (r *mutationResolver) CreateComplianceFramework(ctx context.Context, input types.CreateComplianceFrameworkInput) (*types.CreateComplianceFrameworkPayload, error) { - if err := r.authorize(ctx, input.TrustCenterID, probo.ActionComplianceFrameworkCreate); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, input.TrustCenterID.TenantID()) - - cf, err := prb.ComplianceFrameworks.Create( - ctx, - &probo.CreateComplianceFrameworkRequest{ - TrustCenterID: input.TrustCenterID, - FrameworkID: input.FrameworkID, - }, - ) - if err != nil { - if validationErrors, ok := errors.AsType[validator.ValidationErrors](err); ok { - return nil, gqlutils.InvalidValidationErrors(ctx, validationErrors) - } - r.logger.ErrorCtx(ctx, "cannot create compliance framework", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.CreateComplianceFrameworkPayload{ - ComplianceFrameworkEdge: types.NewComplianceFrameworkEdge(cf, coredata.ComplianceFrameworkOrderFieldRank), - }, nil -} - -// UpdateComplianceFramework is the resolver for the updateComplianceFramework field. -func (r *mutationResolver) UpdateComplianceFramework(ctx context.Context, input types.UpdateComplianceFrameworkInput) (*types.UpdateComplianceFrameworkPayload, error) { - if err := r.authorize(ctx, input.ID, probo.ActionComplianceFrameworkUpdateRank); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, input.ID.TenantID()) - - cf, err := prb.ComplianceFrameworks.Update(ctx, &probo.UpdateComplianceFrameworkRequest{ - ID: input.ID, - Rank: input.Rank, - }) - if err != nil { - if validationErrors, ok := errors.AsType[validator.ValidationErrors](err); ok { - return nil, gqlutils.InvalidValidationErrors(ctx, validationErrors) - } - r.logger.ErrorCtx(ctx, "cannot update compliance framework", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.UpdateComplianceFrameworkPayload{ - ComplianceFramework: types.NewComplianceFramework(cf), - }, nil -} - -// DeleteComplianceFramework is the resolver for the deleteComplianceFramework field. -func (r *mutationResolver) DeleteComplianceFramework(ctx context.Context, input types.DeleteComplianceFrameworkInput) (*types.DeleteComplianceFrameworkPayload, error) { - if err := r.authorize(ctx, input.ID, probo.ActionComplianceFrameworkDelete); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, input.ID.TenantID()) - - err := prb.ComplianceFrameworks.Delete( - ctx, - &probo.DeleteComplianceFrameworkRequest{ - ID: input.ID, - }, - ) - if err != nil { - if validationErrors, ok := errors.AsType[validator.ValidationErrors](err); ok { - return nil, gqlutils.InvalidValidationErrors(ctx, validationErrors) - } - r.logger.ErrorCtx(ctx, "cannot delete compliance framework", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.DeleteComplianceFrameworkPayload{ - DeletedComplianceFrameworkID: input.ID, - }, nil -} - -// CreateComplianceExternalURL is the resolver for the createComplianceExternalURL field. -func (r *mutationResolver) CreateComplianceExternalURL(ctx context.Context, input types.CreateComplianceExternalURLInput) (*types.CreateComplianceExternalURLPayload, error) { - if err := r.authorize(ctx, input.TrustCenterID, probo.ActionComplianceExternalURLCreate); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, input.TrustCenterID.TenantID()) - - item, err := prb.ComplianceExternalURLs.Create( - ctx, - &probo.CreateComplianceExternalURLRequest{ - TrustCenterID: input.TrustCenterID, - Name: input.Name, - URL: input.URL, - }, - ) - if err != nil { - if validationErrors, ok := errors.AsType[validator.ValidationErrors](err); ok { - return nil, gqlutils.InvalidValidationErrors(ctx, validationErrors) - } - r.logger.ErrorCtx(ctx, "cannot create compliance external URL", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.CreateComplianceExternalURLPayload{ - ComplianceExternalURLEdge: types.NewComplianceExternalURLEdge(item, coredata.ComplianceExternalURLOrderFieldRank), - }, nil -} - -// UpdateComplianceExternalURL is the resolver for the updateComplianceExternalURL field. -func (r *mutationResolver) UpdateComplianceExternalURL(ctx context.Context, input types.UpdateComplianceExternalURLInput) (*types.UpdateComplianceExternalURLPayload, error) { - if err := r.authorize(ctx, input.ID, probo.ActionComplianceExternalURLUpdate); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, input.ID.TenantID()) - - item, err := prb.ComplianceExternalURLs.Update(ctx, &probo.UpdateComplianceExternalURLRequest{ - ID: input.ID, - Name: input.Name, - URL: input.URL, - Rank: input.Rank, - }) - if err != nil { - if validationErrors, ok := errors.AsType[validator.ValidationErrors](err); ok { - return nil, gqlutils.InvalidValidationErrors(ctx, validationErrors) - } - r.logger.ErrorCtx(ctx, "cannot update compliance external URL", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.UpdateComplianceExternalURLPayload{ - ComplianceExternalURL: types.NewComplianceExternalURL(item), - }, nil -} - -// DeleteComplianceExternalURL is the resolver for the deleteComplianceExternalURL field. -func (r *mutationResolver) DeleteComplianceExternalURL(ctx context.Context, input types.DeleteComplianceExternalURLInput) (*types.DeleteComplianceExternalURLPayload, error) { - if err := r.authorize(ctx, input.ID, probo.ActionComplianceExternalURLDelete); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, input.ID.TenantID()) - - if err := prb.ComplianceExternalURLs.Delete(ctx, &probo.DeleteComplianceExternalURLRequest{ID: input.ID}); err != nil { - if validationErrors, ok := errors.AsType[validator.ValidationErrors](err); ok { - return nil, gqlutils.InvalidValidationErrors(ctx, validationErrors) - } - r.logger.ErrorCtx(ctx, "cannot delete compliance external URL", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.DeleteComplianceExternalURLPayload{ - DeletedComplianceExternalURLID: input.ID, - }, nil -} - -// CreateTrustCenterFile is the resolver for the createTrustCenterFile field. -func (r *mutationResolver) CreateTrustCenterFile(ctx context.Context, input types.CreateTrustCenterFileInput) (*types.CreateTrustCenterFilePayload, error) { - if err := r.authorize(ctx, input.OrganizationID, probo.ActionTrustCenterFileCreate); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, input.OrganizationID.TenantID()) - - file, err := prb.TrustCenterFiles.Create( - ctx, - &probo.CreateTrustCenterFileRequest{ - OrganizationID: input.OrganizationID, - Name: input.Name, - Category: input.Category, - File: probo.File{ - Content: input.File.File, - Filename: input.File.Filename, - Size: input.File.Size, - ContentType: input.File.ContentType, - }, - TrustCenterVisibility: input.TrustCenterVisibility, - }, - ) - if err != nil { - if validationErrors, ok := errors.AsType[validator.ValidationErrors](err); ok { - return nil, gqlutils.InvalidValidationErrors(ctx, validationErrors) - } - r.logger.ErrorCtx(ctx, "cannot create trust center file", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.CreateTrustCenterFilePayload{ - TrustCenterFileEdge: types.NewTrustCenterFileEdge(file, coredata.TrustCenterFileOrderFieldCreatedAt), - }, nil -} - -// UpdateTrustCenterFile is the resolver for the updateTrustCenterFile field. -func (r *mutationResolver) UpdateTrustCenterFile(ctx context.Context, input types.UpdateTrustCenterFileInput) (*types.UpdateTrustCenterFilePayload, error) { - if err := r.authorize(ctx, input.ID, probo.ActionTrustCenterFileUpdate); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, input.ID.TenantID()) - - file, err := prb.TrustCenterFiles.Update( - ctx, - &probo.UpdateTrustCenterFileRequest{ - ID: input.ID, - Name: input.Name, - Category: input.Category, - TrustCenterVisibility: input.TrustCenterVisibility, - }, - ) - if err != nil { - if validationErrors, ok := errors.AsType[validator.ValidationErrors](err); ok { - return nil, gqlutils.InvalidValidationErrors(ctx, validationErrors) - } - r.logger.ErrorCtx(ctx, "cannot update trust center file", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.UpdateTrustCenterFilePayload{ - TrustCenterFile: types.NewTrustCenterFile(file), - }, nil -} - -// GetTrustCenterFile is the resolver for the getTrustCenterFile field. -func (r *mutationResolver) GetTrustCenterFile(ctx context.Context, input types.GetTrustCenterFileInput) (*types.GetTrustCenterFilePayload, error) { - if err := r.authorize(ctx, input.ID, probo.ActionTrustCenterFileGet); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, input.ID.TenantID()) - - file, err := prb.TrustCenterFiles.Get(ctx, input.ID) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot get trust center file", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.GetTrustCenterFilePayload{ - TrustCenterFile: types.NewTrustCenterFile(file), - }, nil -} - -// DeleteTrustCenterFile is the resolver for the deleteTrustCenterFile field. -func (r *mutationResolver) DeleteTrustCenterFile(ctx context.Context, input types.DeleteTrustCenterFileInput) (*types.DeleteTrustCenterFilePayload, error) { - if err := r.authorize(ctx, input.ID, probo.ActionTrustCenterFileDelete); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, input.ID.TenantID()) - - err := prb.TrustCenterFiles.Delete(ctx, input.ID) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot delete trust center file", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.DeleteTrustCenterFilePayload{ - DeletedTrustCenterFileID: input.ID, - }, nil -} - -// CreateVendor is the resolver for the createVendor field. -func (r *mutationResolver) CreateVendor(ctx context.Context, input types.CreateVendorInput) (*types.CreateVendorPayload, error) { - if err := r.authorize(ctx, input.OrganizationID, probo.ActionVendorCreate); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, input.OrganizationID.TenantID()) - - vendor, err := prb.Vendors.Create( - ctx, - probo.CreateVendorRequest{ - OrganizationID: input.OrganizationID, - Name: input.Name, - Description: input.Description, - StatusPageURL: input.StatusPageURL, - TermsOfServiceURL: input.TermsOfServiceURL, - PrivacyPolicyURL: input.PrivacyPolicyURL, - ServiceLevelAgreementURL: input.ServiceLevelAgreementURL, - LegalName: input.LegalName, - HeadquarterAddress: input.HeadquarterAddress, - WebsiteURL: input.WebsiteURL, - Category: input.Category, - DataProcessingAgreementURL: input.DataProcessingAgreementURL, - BusinessAssociateAgreementURL: input.BusinessAssociateAgreementURL, - SubprocessorsListURL: input.SubprocessorsListURL, - Certifications: input.Certifications, - SecurityPageURL: input.SecurityPageURL, - TrustPageURL: input.TrustPageURL, - BusinessOwnerID: input.BusinessOwnerID, - SecurityOwnerID: input.SecurityOwnerID, - Countries: input.Countries, - }, - ) - if err != nil { - if errors.Is(err, coredata.ErrResourceAlreadyExists) { - return nil, gqlutils.Conflict(ctx, err) - } - - if validationErrors, ok := errors.AsType[validator.ValidationErrors](err); ok { - return nil, gqlutils.InvalidValidationErrors(ctx, validationErrors) - } - r.logger.ErrorCtx(ctx, "cannot create vendor", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - return &types.CreateVendorPayload{ - VendorEdge: types.NewVendorEdge(vendor, coredata.VendorOrderFieldName), - }, nil -} - -// UpdateVendor is the resolver for the updateVendor field. -func (r *mutationResolver) UpdateVendor(ctx context.Context, input types.UpdateVendorInput) (*types.UpdateVendorPayload, error) { - if err := r.authorize(ctx, input.ID, probo.ActionVendorUpdate); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, input.ID.TenantID()) - - vendor, err := prb.Vendors.Update( - ctx, - probo.UpdateVendorRequest{ - ID: input.ID, - Name: input.Name, - Description: gqlutils.UnwrapOmittable(input.Description), - StatusPageURL: gqlutils.UnwrapOmittable(input.StatusPageURL), - TermsOfServiceURL: gqlutils.UnwrapOmittable(input.TermsOfServiceURL), - PrivacyPolicyURL: gqlutils.UnwrapOmittable(input.PrivacyPolicyURL), - ServiceLevelAgreementURL: gqlutils.UnwrapOmittable(input.ServiceLevelAgreementURL), - DataProcessingAgreementURL: gqlutils.UnwrapOmittable(input.DataProcessingAgreementURL), - BusinessAssociateAgreementURL: gqlutils.UnwrapOmittable(input.BusinessAssociateAgreementURL), - SubprocessorsListURL: gqlutils.UnwrapOmittable(input.SubprocessorsListURL), - SecurityPageURL: gqlutils.UnwrapOmittable(input.SecurityPageURL), - TrustPageURL: gqlutils.UnwrapOmittable(input.TrustPageURL), - HeadquarterAddress: gqlutils.UnwrapOmittable(input.HeadquarterAddress), - LegalName: gqlutils.UnwrapOmittable(input.LegalName), - WebsiteURL: gqlutils.UnwrapOmittable(input.WebsiteURL), - Category: input.Category, - Certifications: input.Certifications, - BusinessOwnerID: gqlutils.UnwrapOmittable(input.BusinessOwnerID), - SecurityOwnerID: gqlutils.UnwrapOmittable(input.SecurityOwnerID), - ShowOnTrustCenter: input.ShowOnTrustCenter, - Countries: input.Countries, - }, - ) - if err != nil { - if validationErrors, ok := errors.AsType[validator.ValidationErrors](err); ok { - return nil, gqlutils.InvalidValidationErrors(ctx, validationErrors) - } - r.logger.ErrorCtx(ctx, "cannot update vendor", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.UpdateVendorPayload{ - Vendor: types.NewVendor(vendor), - }, nil -} - -// DeleteVendor is the resolver for the deleteVendor field. -func (r *mutationResolver) DeleteVendor(ctx context.Context, input types.DeleteVendorInput) (*types.DeleteVendorPayload, error) { - if err := r.authorize(ctx, input.VendorID, probo.ActionVendorDelete); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, input.VendorID.TenantID()) - - err := prb.Vendors.Delete(ctx, input.VendorID) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot delete vendor", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.DeleteVendorPayload{ - DeletedVendorID: input.VendorID, - }, nil -} - -// CreateVendorContact is the resolver for the createVendorContact field. -func (r *mutationResolver) CreateVendorContact(ctx context.Context, input types.CreateVendorContactInput) (*types.CreateVendorContactPayload, error) { - if err := r.authorize(ctx, input.VendorID, probo.ActionVendorContactCreate); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, input.VendorID.TenantID()) - - req := probo.CreateVendorContactRequest{ - VendorID: input.VendorID, - FullName: input.FullName, - Email: input.Email, - Phone: input.Phone, - Role: input.Role, - } - - vendorContact, err := prb.VendorContacts.Create(ctx, req) - if err != nil { - if validationErrors, ok := errors.AsType[validator.ValidationErrors](err); ok { - return nil, gqlutils.InvalidValidationErrors(ctx, validationErrors) - } - r.logger.ErrorCtx(ctx, "cannot create vendor contact", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.CreateVendorContactPayload{ - VendorContactEdge: types.NewVendorContactEdge(vendorContact, coredata.VendorContactOrderFieldCreatedAt), - }, nil -} - -// UpdateVendorContact is the resolver for the updateVendorContact field. -func (r *mutationResolver) UpdateVendorContact(ctx context.Context, input types.UpdateVendorContactInput) (*types.UpdateVendorContactPayload, error) { - if err := r.authorize(ctx, input.ID, probo.ActionVendorContactUpdate); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, input.ID.TenantID()) - - req := probo.UpdateVendorContactRequest{ - ID: input.ID, - FullName: gqlutils.UnwrapOmittable(input.FullName), - Email: gqlutils.UnwrapOmittable(input.Email), - Phone: gqlutils.UnwrapOmittable(input.Phone), - Role: gqlutils.UnwrapOmittable(input.Role), - } - - vendorContact, err := prb.VendorContacts.Update(ctx, req) - if err != nil { - if validationErrors, ok := errors.AsType[validator.ValidationErrors](err); ok { - return nil, gqlutils.InvalidValidationErrors(ctx, validationErrors) - } - r.logger.ErrorCtx(ctx, "cannot update vendor contact", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.UpdateVendorContactPayload{ - VendorContact: types.NewVendorContact(vendorContact), - }, nil -} - -// DeleteVendorContact is the resolver for the deleteVendorContact field. -func (r *mutationResolver) DeleteVendorContact(ctx context.Context, input types.DeleteVendorContactInput) (*types.DeleteVendorContactPayload, error) { - if err := r.authorize(ctx, input.VendorContactID, probo.ActionVendorContactDelete); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, input.VendorContactID.TenantID()) - - err := prb.VendorContacts.Delete(ctx, input.VendorContactID) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot delete vendor contact", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.DeleteVendorContactPayload{ - DeletedVendorContactID: input.VendorContactID, - }, nil -} - -// CreateVendorService is the resolver for the createVendorService field. -func (r *mutationResolver) CreateVendorService(ctx context.Context, input types.CreateVendorServiceInput) (*types.CreateVendorServicePayload, error) { - if err := r.authorize(ctx, input.VendorID, probo.ActionVendorServiceCreate); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, input.VendorID.TenantID()) - - req := probo.CreateVendorServiceRequest{ - VendorID: input.VendorID, - Name: input.Name, - Description: input.Description, - } - - vendorService, err := prb.VendorServices.Create(ctx, req) - if err != nil { - if validationErrors, ok := errors.AsType[validator.ValidationErrors](err); ok { - return nil, gqlutils.InvalidValidationErrors(ctx, validationErrors) - } - r.logger.ErrorCtx(ctx, "cannot create vendor service", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.CreateVendorServicePayload{ - VendorServiceEdge: types.NewVendorServiceEdge(vendorService, coredata.VendorServiceOrderFieldCreatedAt), - }, nil -} - -// UpdateVendorService is the resolver for the updateVendorService field. -func (r *mutationResolver) UpdateVendorService(ctx context.Context, input types.UpdateVendorServiceInput) (*types.UpdateVendorServicePayload, error) { - if err := r.authorize(ctx, input.ID, probo.ActionVendorServiceUpdate); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, input.ID.TenantID()) - - req := probo.UpdateVendorServiceRequest{ - ID: input.ID, - Name: input.Name, - Description: gqlutils.UnwrapOmittable(input.Description), - } - - vendorService, err := prb.VendorServices.Update(ctx, req) - if err != nil { - if validationErrors, ok := errors.AsType[validator.ValidationErrors](err); ok { - return nil, gqlutils.InvalidValidationErrors(ctx, validationErrors) - } - r.logger.ErrorCtx(ctx, "cannot update vendor service", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.UpdateVendorServicePayload{ - VendorService: types.NewVendorService(vendorService), - }, nil -} - -// DeleteVendorService is the resolver for the deleteVendorService field. -func (r *mutationResolver) DeleteVendorService(ctx context.Context, input types.DeleteVendorServiceInput) (*types.DeleteVendorServicePayload, error) { - if err := r.authorize(ctx, input.VendorServiceID, probo.ActionVendorServiceDelete); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, input.VendorServiceID.TenantID()) - - err := prb.VendorServices.Delete(ctx, input.VendorServiceID) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot delete vendor service", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.DeleteVendorServicePayload{ - DeletedVendorServiceID: input.VendorServiceID, - }, nil -} - -// CreateFramework is the resolver for the createFramework field. -func (r *mutationResolver) CreateFramework(ctx context.Context, input types.CreateFrameworkInput) (*types.CreateFrameworkPayload, error) { - if err := r.authorize(ctx, input.OrganizationID, probo.ActionFrameworkCreate); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, input.OrganizationID.TenantID()) - - framework, err := prb.Frameworks.Create( - ctx, - probo.CreateFrameworkRequest{ - OrganizationID: input.OrganizationID, - Name: input.Name, - }, - ) - if err != nil { - if validationErrors, ok := errors.AsType[validator.ValidationErrors](err); ok { - return nil, gqlutils.InvalidValidationErrors(ctx, validationErrors) - } - r.logger.ErrorCtx(ctx, "cannot create framework", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.CreateFrameworkPayload{ - FrameworkEdge: types.NewFrameworkEdge(framework, coredata.FrameworkOrderFieldCreatedAt), - }, nil -} - -// UpdateFramework is the resolver for the updateFramework field. -func (r *mutationResolver) UpdateFramework(ctx context.Context, input types.UpdateFrameworkInput) (*types.UpdateFrameworkPayload, error) { - if err := r.authorize(ctx, input.ID, probo.ActionFrameworkUpdate); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, input.ID.TenantID()) - - framework, err := prb.Frameworks.Update( - ctx, - probo.UpdateFrameworkRequest{ - ID: input.ID, - Name: input.Name, - Description: gqlutils.UnwrapOmittable(input.Description), - }, - ) - if err != nil { - if validationErrors, ok := errors.AsType[validator.ValidationErrors](err); ok { - return nil, gqlutils.InvalidValidationErrors(ctx, validationErrors) - } - r.logger.ErrorCtx(ctx, "cannot update framework", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.UpdateFrameworkPayload{ - Framework: types.NewFramework(framework), - }, nil -} - -// ImportFramework is the resolver for the importFramework field. -func (r *mutationResolver) ImportFramework(ctx context.Context, input types.ImportFrameworkInput) (*types.ImportFrameworkPayload, error) { - if err := r.authorize(ctx, input.OrganizationID, probo.ActionFrameworkImport); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, input.OrganizationID.TenantID()) - - req := probo.ImportFrameworkRequest{} - if err := json.NewDecoder(input.File.File).Decode(&req.Framework); err != nil { - r.logger.ErrorCtx(ctx, "cannot decode framework", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - framework, err := prb.Frameworks.Import(ctx, input.OrganizationID, req) - if err != nil { - if errors.Is(err, coredata.ErrResourceAlreadyExists) { - return nil, gqlutils.Conflict(ctx, err) - } - - r.logger.ErrorCtx(ctx, "cannot import framework", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.ImportFrameworkPayload{ - FrameworkEdge: types.NewFrameworkEdge(framework, coredata.FrameworkOrderFieldCreatedAt), - }, nil -} - -// DeleteFramework is the resolver for the deleteFramework field. -func (r *mutationResolver) DeleteFramework(ctx context.Context, input types.DeleteFrameworkInput) (*types.DeleteFrameworkPayload, error) { - if err := r.authorize(ctx, input.FrameworkID, probo.ActionFrameworkDelete); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, input.FrameworkID.TenantID()) - - err := prb.Frameworks.Delete(ctx, input.FrameworkID) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot delete framework", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.DeleteFrameworkPayload{ - DeletedFrameworkID: input.FrameworkID, - }, nil -} - -// ExportFramework is the resolver for the exportFramework field. -func (r *mutationResolver) ExportFramework(ctx context.Context, input types.ExportFrameworkInput) (*types.ExportFrameworkPayload, error) { - if err := r.authorize(ctx, input.FrameworkID, probo.ActionFrameworkExport); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, input.FrameworkID.TenantID()) - identity := authn.IdentityFromContext(ctx) - - exportJob, exportErr := prb.Frameworks.RequestExport( - ctx, - input.FrameworkID, - identity.EmailAddress, - identity.FullName, - ) - if exportErr != nil { - r.logger.ErrorCtx(ctx, "cannot export framework", log.Error(exportErr)) - return nil, gqlutils.Internal(ctx) - } - - return &types.ExportFrameworkPayload{ - ExportJobID: exportJob.ID, - }, nil -} - -// CreateControl is the resolver for the createControl field. -func (r *mutationResolver) CreateControl(ctx context.Context, input types.CreateControlInput) (*types.CreateControlPayload, error) { - if err := r.authorize(ctx, input.FrameworkID, probo.ActionControlCreate); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, input.FrameworkID.TenantID()) - - control, err := prb.Controls.Create( - ctx, - probo.CreateControlRequest{ - FrameworkID: input.FrameworkID, - Name: input.Name, - Description: input.Description, - SectionTitle: input.SectionTitle, - BestPractice: input.BestPractice, - Implemented: input.Implemented, - NotImplementedJustification: input.NotImplementedJustification, - }, - ) - if err != nil { - if errors.Is(err, coredata.ErrResourceAlreadyExists) { - return nil, gqlutils.Conflict(ctx, err) - } - - if validationErrors, ok := errors.AsType[validator.ValidationErrors](err); ok { - return nil, gqlutils.InvalidValidationErrors(ctx, validationErrors) - } - r.logger.ErrorCtx(ctx, "cannot create control", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.CreateControlPayload{ - ControlEdge: types.NewControlEdge(control, coredata.ControlOrderFieldCreatedAt), - }, nil -} - -// UpdateControl is the resolver for the updateControl field. -func (r *mutationResolver) UpdateControl(ctx context.Context, input types.UpdateControlInput) (*types.UpdateControlPayload, error) { - if err := r.authorize(ctx, input.ID, probo.ActionControlUpdate); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, input.ID.TenantID()) - - control, err := prb.Controls.Update( - ctx, - probo.UpdateControlRequest{ - ID: input.ID, - Name: input.Name, - Description: gqlutils.UnwrapOmittable(input.Description), - SectionTitle: input.SectionTitle, - BestPractice: input.BestPractice, - Implemented: input.Implemented, - NotImplementedJustification: gqlutils.UnwrapOmittable(input.NotImplementedJustification), - }, - ) - - if err != nil { - if errors.Is(err, coredata.ErrResourceAlreadyExists) { - return nil, gqlutils.Conflict(ctx, err) - } - - if validationErrors, ok := errors.AsType[validator.ValidationErrors](err); ok { - return nil, gqlutils.InvalidValidationErrors(ctx, validationErrors) - } - r.logger.ErrorCtx(ctx, "cannot update control", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.UpdateControlPayload{ - Control: types.NewControl(control), - }, nil -} - -// DeleteControl is the resolver for the deleteControl field. -func (r *mutationResolver) DeleteControl(ctx context.Context, input types.DeleteControlInput) (*types.DeleteControlPayload, error) { - if err := r.authorize(ctx, input.ControlID, probo.ActionControlDelete); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, input.ControlID.TenantID()) - - err := prb.Controls.Delete(ctx, input.ControlID) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot delete control", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.DeleteControlPayload{ - DeletedControlID: input.ControlID, - }, nil -} - -// // CreateMeasure is the resolver for the createMeasure field. -func (r *mutationResolver) CreateMeasure(ctx context.Context, input types.CreateMeasureInput) (*types.CreateMeasurePayload, error) { - if err := r.authorize(ctx, input.OrganizationID, probo.ActionMeasureCreate); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, input.OrganizationID.TenantID()) - - measure, err := prb.Measures.Create( - ctx, - probo.CreateMeasureRequest{ - OrganizationID: input.OrganizationID, - Name: input.Name, - Description: input.Description, - Category: input.Category, - }, - ) - if err != nil { - if errors.Is(err, coredata.ErrResourceAlreadyExists) { - return nil, gqlutils.Conflict(ctx, err) - } - - if validationErrors, ok := errors.AsType[validator.ValidationErrors](err); ok { - return nil, gqlutils.InvalidValidationErrors(ctx, validationErrors) - } - r.logger.ErrorCtx(ctx, "cannot create measure", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.CreateMeasurePayload{ - MeasureEdge: types.NewMeasureEdge(measure, coredata.MeasureOrderFieldCreatedAt), - }, nil -} - -// UpdateMeasure is the resolver for the updateMeasure field. -func (r *mutationResolver) UpdateMeasure(ctx context.Context, input types.UpdateMeasureInput) (*types.UpdateMeasurePayload, error) { - if err := r.authorize(ctx, input.ID, probo.ActionMeasureUpdate); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, input.ID.TenantID()) - - measure, err := prb.Measures.Update( - ctx, - probo.UpdateMeasureRequest{ - ID: input.ID, - Name: input.Name, - Description: gqlutils.UnwrapOmittable(input.Description), - Category: input.Category, - State: input.State, - }, - ) - if err != nil { - if validationErrors, ok := errors.AsType[validator.ValidationErrors](err); ok { - return nil, gqlutils.InvalidValidationErrors(ctx, validationErrors) - } - r.logger.ErrorCtx(ctx, "cannot update measure", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.UpdateMeasurePayload{ - Measure: types.NewMeasure(measure), - }, nil -} - -// ImportMeasure is the resolver for the importMeasure field. -func (r *mutationResolver) ImportMeasure(ctx context.Context, input types.ImportMeasureInput) (*types.ImportMeasurePayload, error) { - if err := r.authorize(ctx, input.OrganizationID, probo.ActionMeasureImport); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, input.OrganizationID.TenantID()) - - var req probo.ImportMeasureRequest - if err := json.NewDecoder(input.File.File).Decode(&req.Measures); err != nil { - r.logger.ErrorCtx(ctx, "cannot unmarshal measure", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - measures, err := prb.Measures.Import(ctx, input.OrganizationID, req) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot import measure", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - measureEdges := make([]*types.MeasureEdge, len(measures.Data)) - for i, measure := range measures.Data { - measureEdges[i] = types.NewMeasureEdge(measure, coredata.MeasureOrderFieldCreatedAt) - } - - return &types.ImportMeasurePayload{ - MeasureEdges: measureEdges, - }, nil -} - -// DeleteMeasure is the resolver for the deleteMeasure field. -func (r *mutationResolver) DeleteMeasure(ctx context.Context, input types.DeleteMeasureInput) (*types.DeleteMeasurePayload, error) { - if err := r.authorize(ctx, input.MeasureID, probo.ActionMeasureDelete); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, input.MeasureID.TenantID()) - - err := prb.Measures.Delete(ctx, input.MeasureID) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot delete measure", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.DeleteMeasurePayload{ - DeletedMeasureID: input.MeasureID, - }, nil -} - -// CreateControlMeasureMapping is the resolver for the createControlMeasureMapping field. -func (r *mutationResolver) CreateControlMeasureMapping(ctx context.Context, input types.CreateControlMeasureMappingInput) (*types.CreateControlMeasureMappingPayload, error) { - if err := r.authorize(ctx, input.ControlID, probo.ActionControlMeasureMappingCreate); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, input.MeasureID.TenantID()) - - control, measure, err := prb.Controls.CreateMeasureMapping(ctx, input.ControlID, input.MeasureID) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot create control measure mapping", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.CreateControlMeasureMappingPayload{ - ControlEdge: types.NewControlEdge(control, coredata.ControlOrderFieldCreatedAt), - MeasureEdge: types.NewMeasureEdge(measure, coredata.MeasureOrderFieldCreatedAt), - }, nil -} - -// CreateControlDocumentMapping is the resolver for the createControlDocumentMapping field. -func (r *mutationResolver) CreateControlDocumentMapping(ctx context.Context, input types.CreateControlDocumentMappingInput) (*types.CreateControlDocumentMappingPayload, error) { - if err := r.authorize(ctx, input.ControlID, probo.ActionControlDocumentMappingCreate); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, input.DocumentID.TenantID()) - - control, document, err := prb.Controls.CreateDocumentMapping(ctx, input.ControlID, input.DocumentID) - if err != nil { - if errors.Is(err, coredata.ErrResourceAlreadyExists) { - return nil, gqlutils.Conflict(ctx, err) - } - - r.logger.ErrorCtx(ctx, "cannot create control document mapping", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.CreateControlDocumentMappingPayload{ - ControlEdge: types.NewControlEdge(control, coredata.ControlOrderFieldCreatedAt), - DocumentEdge: types.NewDocumentEdge(document, coredata.DocumentOrderFieldTitle), - }, nil -} - -// DeleteControlMeasureMapping is the resolver for the deleteControlMeasureMapping field. -func (r *mutationResolver) DeleteControlMeasureMapping(ctx context.Context, input types.DeleteControlMeasureMappingInput) (*types.DeleteControlMeasureMappingPayload, error) { - if err := r.authorize(ctx, input.ControlID, probo.ActionControlMeasureMappingDelete); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, input.MeasureID.TenantID()) - - control, measure, err := prb.Controls.DeleteMeasureMapping(ctx, input.ControlID, input.MeasureID) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot delete control measure mapping", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.DeleteControlMeasureMappingPayload{ - DeletedControlID: control.ID, - DeletedMeasureID: measure.ID, - }, nil -} - -// DeleteControlDocumentMapping is the resolver for the deleteControlDocumentMapping field. -func (r *mutationResolver) DeleteControlDocumentMapping(ctx context.Context, input types.DeleteControlDocumentMappingInput) (*types.DeleteControlDocumentMappingPayload, error) { - if err := r.authorize(ctx, input.ControlID, probo.ActionControlDocumentMappingDelete); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, input.DocumentID.TenantID()) - - control, document, err := prb.Controls.DeleteDocumentMapping(ctx, input.ControlID, input.DocumentID) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot delete control document mapping", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.DeleteControlDocumentMappingPayload{ - DeletedControlID: control.ID, - DeletedDocumentID: document.ID, - }, nil -} - -// CreateApplicabilityStatement is the resolver for the createApplicabilityStatement field. -func (r *mutationResolver) CreateApplicabilityStatement(ctx context.Context, input types.CreateApplicabilityStatementInput) (*types.CreateApplicabilityStatementPayload, error) { - if err := r.authorize(ctx, input.ControlID, probo.ActionApplicabilityStatementCreate); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, input.StatementOfApplicabilityID.TenantID()) - - applicabilityStatement, err := prb.StatementsOfApplicability.CreateApplicabilityStatement(ctx, input.StatementOfApplicabilityID, input.ControlID, input.Applicability, input.Justification) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot create applicability statement", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.CreateApplicabilityStatementPayload{ - ApplicabilityStatementEdge: types.NewApplicabilityStatementEdge(applicabilityStatement, coredata.ApplicabilityStatementOrderFieldCreatedAt), - }, nil -} - -// UpdateApplicabilityStatement is the resolver for the updateApplicabilityStatement field. -func (r *mutationResolver) UpdateApplicabilityStatement(ctx context.Context, input types.UpdateApplicabilityStatementInput) (*types.UpdateApplicabilityStatementPayload, error) { - if err := r.authorize(ctx, input.ApplicabilityStatementID, probo.ActionApplicabilityStatementUpdate); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, input.ApplicabilityStatementID.TenantID()) - - applicabilityStatement, err := prb.StatementsOfApplicability.UpdateApplicabilityStatement(ctx, input.ApplicabilityStatementID, input.Applicability, input.Justification) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot update applicability statement", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.UpdateApplicabilityStatementPayload{ - ApplicabilityStatement: types.NewApplicabilityStatement(applicabilityStatement), - }, nil -} - -// DeleteApplicabilityStatement is the resolver for the deleteApplicabilityStatement field. -func (r *mutationResolver) DeleteApplicabilityStatement(ctx context.Context, input types.DeleteApplicabilityStatementInput) (*types.DeleteApplicabilityStatementPayload, error) { - if err := r.authorize(ctx, input.ApplicabilityStatementID, probo.ActionApplicabilityStatementDelete); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, input.ApplicabilityStatementID.TenantID()) - - err := prb.StatementsOfApplicability.DeleteApplicabilityStatement(ctx, input.ApplicabilityStatementID) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot delete applicability statement", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.DeleteApplicabilityStatementPayload{ - DeletedApplicabilityStatementID: input.ApplicabilityStatementID, - }, nil -} - -// CreateControlAuditMapping is the resolver for the createControlAuditMapping field. -func (r *mutationResolver) CreateControlAuditMapping(ctx context.Context, input types.CreateControlAuditMappingInput) (*types.CreateControlAuditMappingPayload, error) { - if err := r.authorize(ctx, input.ControlID, probo.ActionControlAuditMappingCreate); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, input.AuditID.TenantID()) - - control, audit, err := prb.Controls.CreateAuditMapping(ctx, input.ControlID, input.AuditID) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot create control audit mapping", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.CreateControlAuditMappingPayload{ - ControlEdge: types.NewControlEdge(control, coredata.ControlOrderFieldCreatedAt), - AuditEdge: types.NewAuditEdge(audit, coredata.AuditOrderFieldCreatedAt), - }, nil -} - -// DeleteControlAuditMapping is the resolver for the deleteControlAuditMapping field. -func (r *mutationResolver) DeleteControlAuditMapping(ctx context.Context, input types.DeleteControlAuditMappingInput) (*types.DeleteControlAuditMappingPayload, error) { - if err := r.authorize(ctx, input.ControlID, probo.ActionControlAuditMappingDelete); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, input.AuditID.TenantID()) - - control, audit, err := prb.Controls.DeleteAuditMapping(ctx, input.ControlID, input.AuditID) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot delete control audit mapping", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.DeleteControlAuditMappingPayload{ - DeletedControlID: &control.ID, - DeletedAuditID: &audit.ID, - }, nil -} - -// CreateControlObligationMapping is the resolver for the createControlObligationMapping field. -func (r *mutationResolver) CreateControlObligationMapping(ctx context.Context, input types.CreateControlObligationMappingInput) (*types.CreateControlObligationMappingPayload, error) { - if err := r.authorize(ctx, input.ControlID, probo.ActionControlObligationMappingCreate); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, input.ObligationID.TenantID()) - - control, obligation, err := prb.Controls.CreateObligationMapping(ctx, input.ControlID, input.ObligationID) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot create control obligation mapping", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.CreateControlObligationMappingPayload{ - ControlEdge: types.NewControlEdge(control, coredata.ControlOrderFieldCreatedAt), - ObligationEdge: types.NewObligationEdge(obligation, coredata.ObligationOrderFieldCreatedAt), - }, nil -} - -// DeleteControlObligationMapping is the resolver for the deleteControlObligationMapping field. -func (r *mutationResolver) DeleteControlObligationMapping(ctx context.Context, input types.DeleteControlObligationMappingInput) (*types.DeleteControlObligationMappingPayload, error) { - if err := r.authorize(ctx, input.ControlID, probo.ActionControlObligationMappingDelete); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, input.ObligationID.TenantID()) - - control, obligation, err := prb.Controls.DeleteObligationMapping(ctx, input.ControlID, input.ObligationID) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot delete control obligation mapping", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.DeleteControlObligationMappingPayload{ - DeletedControlID: control.ID, - DeletedObligationID: obligation.ID, - }, nil -} - -// CreateControlSnapshotMapping is the resolver for the createControlSnapshotMapping field. -func (r *mutationResolver) CreateControlSnapshotMapping(ctx context.Context, input types.CreateControlSnapshotMappingInput) (*types.CreateControlSnapshotMappingPayload, error) { - if err := r.authorize(ctx, input.ControlID, probo.ActionControlSnapshotMappingCreate); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, input.SnapshotID.TenantID()) - - control, snapshot, err := prb.Controls.CreateSnapshotMapping(ctx, input.ControlID, input.SnapshotID) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot create control snapshot mapping", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.CreateControlSnapshotMappingPayload{ - ControlEdge: types.NewControlEdge(control, coredata.ControlOrderFieldCreatedAt), - SnapshotEdge: types.NewSnapshotEdge(snapshot, coredata.SnapshotOrderFieldCreatedAt), - }, nil -} - -// DeleteControlSnapshotMapping is the resolver for the deleteControlSnapshotMapping field. -func (r *mutationResolver) DeleteControlSnapshotMapping(ctx context.Context, input types.DeleteControlSnapshotMappingInput) (*types.DeleteControlSnapshotMappingPayload, error) { - if err := r.authorize(ctx, input.ControlID, probo.ActionControlSnapshotMappingDelete); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, input.SnapshotID.TenantID()) - - control, snapshot, err := prb.Controls.DeleteSnapshotMapping(ctx, input.ControlID, input.SnapshotID) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot delete control snapshot mapping", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.DeleteControlSnapshotMappingPayload{ - DeletedControlID: control.ID, - DeletedSnapshotID: snapshot.ID, - }, nil -} - -// CreateTask is the resolver for the createTask field. -func (r *mutationResolver) CreateTask(ctx context.Context, input types.CreateTaskInput) (*types.CreateTaskPayload, error) { - if err := r.authorize(ctx, input.OrganizationID, probo.ActionTaskCreate); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, input.OrganizationID.TenantID()) - - task, err := prb.Tasks.Create( - ctx, - probo.CreateTaskRequest{ - MeasureID: input.MeasureID, - OrganizationID: input.OrganizationID, - Name: input.Name, - Description: input.Description, - Priority: input.Priority, - TimeEstimate: input.TimeEstimate, - AssignedToID: input.AssignedToID, - Deadline: input.Deadline, - }, - ) - if err != nil { - if errors.Is(err, coredata.ErrResourceAlreadyExists) { - return nil, gqlutils.Conflict(ctx, err) - } - - if validationErrors, ok := errors.AsType[validator.ValidationErrors](err); ok { - return nil, gqlutils.InvalidValidationErrors(ctx, validationErrors) - } - r.logger.ErrorCtx(ctx, "cannot create task", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.CreateTaskPayload{ - TaskEdge: types.NewTaskEdge(task, coredata.TaskOrderFieldCreatedAt), - }, nil -} - -// UpdateTask is the resolver for the updateTask field. -func (r *mutationResolver) UpdateTask(ctx context.Context, input types.UpdateTaskInput) (*types.UpdateTaskPayload, error) { - if err := r.authorize(ctx, input.TaskID, probo.ActionTaskUpdate); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, input.TaskID.TenantID()) - - task, err := prb.Tasks.Update( - ctx, - probo.UpdateTaskRequest{ - TaskID: input.TaskID, - Name: input.Name, - Description: gqlutils.UnwrapOmittable(input.Description), - State: input.State, - Priority: input.Priority, - Rank: input.Rank, - TimeEstimate: gqlutils.UnwrapOmittable(input.TimeEstimate), - Deadline: gqlutils.UnwrapOmittable(input.Deadline), - AssignedToID: gqlutils.UnwrapOmittable(input.AssignedToID), - MeasureID: gqlutils.UnwrapOmittable(input.MeasureID), - }, - ) - if err != nil { - if validationErrors, ok := errors.AsType[validator.ValidationErrors](err); ok { - return nil, gqlutils.InvalidValidationErrors(ctx, validationErrors) - } - r.logger.ErrorCtx(ctx, "cannot update task", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.UpdateTaskPayload{ - Task: types.NewTask(task), - }, nil -} - -// DeleteTask is the resolver for the deleteTask field. -func (r *mutationResolver) DeleteTask(ctx context.Context, input types.DeleteTaskInput) (*types.DeleteTaskPayload, error) { - if err := r.authorize(ctx, input.TaskID, probo.ActionTaskDelete); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, input.TaskID.TenantID()) - - err := prb.Tasks.Delete(ctx, input.TaskID) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot delete task", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.DeleteTaskPayload{ - DeletedTaskID: input.TaskID, - }, nil -} - -// CreateRisk is the resolver for the createRisk field. -func (r *mutationResolver) CreateRisk(ctx context.Context, input types.CreateRiskInput) (*types.CreateRiskPayload, error) { - if err := r.authorize(ctx, input.OrganizationID, probo.ActionRiskCreate); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, input.OrganizationID.TenantID()) - - risk, err := prb.Risks.Create( - ctx, - probo.CreateRiskRequest{ - OrganizationID: input.OrganizationID, - Name: input.Name, - Description: input.Description, - Category: input.Category, - Treatment: input.Treatment, - OwnerID: input.OwnerID, - InherentLikelihood: input.InherentLikelihood, - InherentImpact: input.InherentImpact, - ResidualLikelihood: input.ResidualLikelihood, - ResidualImpact: input.ResidualImpact, - Note: input.Note, - }, - ) - if err != nil { - if errors.Is(err, coredata.ErrResourceAlreadyExists) { - return nil, gqlutils.Conflict(ctx, err) - } - - if validationErrors, ok := errors.AsType[validator.ValidationErrors](err); ok { - return nil, gqlutils.InvalidValidationErrors(ctx, validationErrors) - } - r.logger.ErrorCtx(ctx, "cannot create risk", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.CreateRiskPayload{ - RiskEdge: types.NewRiskEdge(risk, coredata.RiskOrderFieldCreatedAt), - }, nil -} - -// UpdateRisk is the resolver for the updateRisk field. -func (r *mutationResolver) UpdateRisk(ctx context.Context, input types.UpdateRiskInput) (*types.UpdateRiskPayload, error) { - if err := r.authorize(ctx, input.ID, probo.ActionRiskUpdate); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, input.ID.TenantID()) - - risk, err := prb.Risks.Update( - ctx, - probo.UpdateRiskRequest{ - ID: input.ID, - Name: input.Name, - Description: gqlutils.UnwrapOmittable(input.Description), - Category: input.Category, - Treatment: input.Treatment, - OwnerID: gqlutils.UnwrapOmittable(input.OwnerID), - InherentLikelihood: input.InherentLikelihood, - InherentImpact: input.InherentImpact, - ResidualLikelihood: input.ResidualLikelihood, - ResidualImpact: input.ResidualImpact, - Note: input.Note, - }, - ) - if err != nil { - if validationErrors, ok := errors.AsType[validator.ValidationErrors](err); ok { - return nil, gqlutils.InvalidValidationErrors(ctx, validationErrors) - } - r.logger.ErrorCtx(ctx, "cannot update risk", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.UpdateRiskPayload{ - Risk: types.NewRisk(risk), - }, nil -} - -// DeleteRisk is the resolver for the deleteRisk field. -func (r *mutationResolver) DeleteRisk(ctx context.Context, input types.DeleteRiskInput) (*types.DeleteRiskPayload, error) { - if err := r.authorize(ctx, input.RiskID, probo.ActionRiskDelete); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, input.RiskID.TenantID()) - - err := prb.Risks.Delete(ctx, input.RiskID) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot delete risk", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.DeleteRiskPayload{ - DeletedRiskID: input.RiskID, - }, nil -} - -// CreateRiskMeasureMapping is the resolver for the createRiskMeasureMapping field. -func (r *mutationResolver) CreateRiskMeasureMapping(ctx context.Context, input types.CreateRiskMeasureMappingInput) (*types.CreateRiskMeasureMappingPayload, error) { - if err := r.authorize(ctx, input.RiskID, probo.ActionRiskMeasureMappingCreate); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, input.RiskID.TenantID()) - - risk, measure, err := prb.Risks.CreateMeasureMapping(ctx, input.RiskID, input.MeasureID) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot create risk measure mapping", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.CreateRiskMeasureMappingPayload{ - RiskEdge: types.NewRiskEdge(risk, coredata.RiskOrderFieldCreatedAt), - MeasureEdge: types.NewMeasureEdge(measure, coredata.MeasureOrderFieldCreatedAt), - }, nil -} - -// DeleteRiskMeasureMapping is the resolver for the deleteRiskMeasureMapping field. -func (r *mutationResolver) DeleteRiskMeasureMapping(ctx context.Context, input types.DeleteRiskMeasureMappingInput) (*types.DeleteRiskMeasureMappingPayload, error) { - if err := r.authorize(ctx, input.RiskID, probo.ActionRiskMeasureMappingDelete); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, input.RiskID.TenantID()) - - risk, measure, err := prb.Risks.DeleteMeasureMapping(ctx, input.RiskID, input.MeasureID) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot delete risk measure mapping", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.DeleteRiskMeasureMappingPayload{ - DeletedRiskID: risk.ID, - DeletedMeasureID: measure.ID, - }, nil -} - -// CreateRiskDocumentMapping is the resolver for the createRiskDocumentMapping field. -func (r *mutationResolver) CreateRiskDocumentMapping(ctx context.Context, input types.CreateRiskDocumentMappingInput) (*types.CreateRiskDocumentMappingPayload, error) { - if err := r.authorize(ctx, input.RiskID, probo.ActionRiskDocumentMappingCreate); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, input.RiskID.TenantID()) - - risk, document, err := prb.Risks.CreateDocumentMapping(ctx, input.RiskID, input.DocumentID) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot create risk document mapping", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.CreateRiskDocumentMappingPayload{ - RiskEdge: types.NewRiskEdge(risk, coredata.RiskOrderFieldCreatedAt), - DocumentEdge: types.NewDocumentEdge(document, coredata.DocumentOrderFieldTitle), - }, nil -} - -// DeleteRiskDocumentMapping is the resolver for the deleteRiskDocumentMapping field. -func (r *mutationResolver) DeleteRiskDocumentMapping(ctx context.Context, input types.DeleteRiskDocumentMappingInput) (*types.DeleteRiskDocumentMappingPayload, error) { - if err := r.authorize(ctx, input.RiskID, probo.ActionRiskDocumentMappingDelete); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, input.RiskID.TenantID()) - - risk, document, err := prb.Risks.DeleteDocumentMapping(ctx, input.RiskID, input.DocumentID) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot delete risk document mapping", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.DeleteRiskDocumentMappingPayload{ - DeletedRiskID: risk.ID, - DeletedDocumentID: document.ID, - }, nil -} - -// CreateMeasureDocumentMapping is the resolver for the createMeasureDocumentMapping field. -func (r *mutationResolver) CreateMeasureDocumentMapping(ctx context.Context, input types.CreateMeasureDocumentMappingInput) (*types.CreateMeasureDocumentMappingPayload, error) { - if err := r.authorize(ctx, input.MeasureID, probo.ActionMeasureDocumentMappingCreate); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, input.MeasureID.TenantID()) - - measure, document, err := prb.Measures.CreateDocumentMapping(ctx, input.MeasureID, input.DocumentID) - if err != nil { - if errors.Is(err, coredata.ErrResourceAlreadyExists) { - return nil, gqlutils.Conflict(ctx, err) - } - - r.logger.ErrorCtx(ctx, "cannot create measure document mapping", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.CreateMeasureDocumentMappingPayload{ - MeasureEdge: types.NewMeasureEdge(measure, coredata.MeasureOrderFieldCreatedAt), - DocumentEdge: types.NewDocumentEdge(document, coredata.DocumentOrderFieldTitle), - }, nil -} - -// DeleteMeasureDocumentMapping is the resolver for the deleteMeasureDocumentMapping field. -func (r *mutationResolver) DeleteMeasureDocumentMapping(ctx context.Context, input types.DeleteMeasureDocumentMappingInput) (*types.DeleteMeasureDocumentMappingPayload, error) { - if err := r.authorize(ctx, input.MeasureID, probo.ActionMeasureDocumentMappingDelete); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, input.MeasureID.TenantID()) - - measure, document, err := prb.Measures.DeleteDocumentMapping(ctx, input.MeasureID, input.DocumentID) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot delete measure document mapping", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.DeleteMeasureDocumentMappingPayload{ - DeletedMeasureID: measure.ID, - DeletedDocumentID: document.ID, - }, nil -} - -// CreateRiskObligationMapping is the resolver for the createRiskObligationMapping field. -func (r *mutationResolver) CreateRiskObligationMapping(ctx context.Context, input types.CreateRiskObligationMappingInput) (*types.CreateRiskObligationMappingPayload, error) { - if err := r.authorize(ctx, input.RiskID, probo.ActionRiskObligationMappingCreate); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, input.RiskID.TenantID()) - - risk, obligation, err := prb.Risks.CreateObligationMapping(ctx, input.RiskID, input.ObligationID) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot create risk obligation mapping", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.CreateRiskObligationMappingPayload{ - RiskEdge: types.NewRiskEdge(risk, coredata.RiskOrderFieldCreatedAt), - ObligationEdge: types.NewObligationEdge(obligation, coredata.ObligationOrderFieldCreatedAt), - }, nil -} - -// DeleteRiskObligationMapping is the resolver for the deleteRiskObligationMapping field. -func (r *mutationResolver) DeleteRiskObligationMapping(ctx context.Context, input types.DeleteRiskObligationMappingInput) (*types.DeleteRiskObligationMappingPayload, error) { - if err := r.authorize(ctx, input.RiskID, probo.ActionRiskObligationMappingDelete); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, input.RiskID.TenantID()) - - risk, obligation, err := prb.Risks.DeleteObligationMapping(ctx, input.RiskID, input.ObligationID) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot delete risk obligation mapping", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.DeleteRiskObligationMappingPayload{ - DeletedRiskID: risk.ID, - DeletedObligationID: obligation.ID, - }, nil -} - -// DeleteEvidence is the resolver for the deleteEvidence field. -func (r *mutationResolver) DeleteEvidence(ctx context.Context, input types.DeleteEvidenceInput) (*types.DeleteEvidencePayload, error) { - if err := r.authorize(ctx, input.EvidenceID, probo.ActionEvidenceDelete); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, input.EvidenceID.TenantID()) - - err := prb.Evidences.Delete(ctx, input.EvidenceID) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot delete evidence", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.DeleteEvidencePayload{ - DeletedEvidenceID: input.EvidenceID, - }, nil -} - -// UploadMeasureEvidence is the resolver for the uploadMeasureEvidence field. -func (r *mutationResolver) UploadMeasureEvidence(ctx context.Context, input types.UploadMeasureEvidenceInput) (*types.UploadMeasureEvidencePayload, error) { - if err := r.authorize(ctx, input.MeasureID, probo.ActionMeasureEvidenceUpload); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, input.MeasureID.TenantID()) - - evidence, err := prb.Evidences.UploadMeasureEvidence( - ctx, - probo.UploadMeasureEvidenceRequest{ - MeasureID: input.MeasureID, - File: probo.FileUpload{ - Content: input.File.File, - Filename: input.File.Filename, - Size: input.File.Size, - ContentType: input.File.ContentType, - }, - }, - ) - if err != nil { - if validationErrors, ok := errors.AsType[validator.ValidationErrors](err); ok { - return nil, gqlutils.InvalidValidationErrors(ctx, validationErrors) - } - r.logger.ErrorCtx(ctx, "cannot upload measure evidence", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.UploadMeasureEvidencePayload{ - EvidenceEdge: types.NewEvidenceEdge(evidence, coredata.EvidenceOrderFieldCreatedAt), - }, nil -} - -// UploadVendorComplianceReport is the resolver for the uploadVendorComplianceReport field. -func (r *mutationResolver) UploadVendorComplianceReport(ctx context.Context, input types.UploadVendorComplianceReportInput) (*types.UploadVendorComplianceReportPayload, error) { - if err := r.authorize(ctx, input.VendorID, probo.ActionVendorComplianceReportUpload); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, input.VendorID.TenantID()) - - vendorComplianceReport, err := prb.VendorComplianceReports.Upload( - ctx, - input.VendorID, - &probo.VendorComplianceReportCreateRequest{ - File: probo.FileUpload{Filename: input.File.Filename, Size: input.File.Size, Content: input.File.File, ContentType: input.File.ContentType}, - ReportDate: input.ReportDate, - ValidUntil: input.ValidUntil, - ReportName: input.ReportName, - }, - ) - if err != nil { - if validationErrors, ok := errors.AsType[validator.ValidationErrors](err); ok { - return nil, gqlutils.InvalidValidationErrors(ctx, validationErrors) - } - r.logger.ErrorCtx(ctx, "cannot upload vendor compliance report", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.UploadVendorComplianceReportPayload{ - VendorComplianceReportEdge: types.NewVendorComplianceReportEdge(vendorComplianceReport, coredata.VendorComplianceReportOrderFieldCreatedAt), - }, nil -} - -// DeleteVendorComplianceReport is the resolver for the deleteVendorComplianceReport field. -func (r *mutationResolver) DeleteVendorComplianceReport(ctx context.Context, input types.DeleteVendorComplianceReportInput) (*types.DeleteVendorComplianceReportPayload, error) { - if err := r.authorize(ctx, input.ReportID, probo.ActionVendorComplianceReportDelete); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, input.ReportID.TenantID()) - - err := prb.VendorComplianceReports.Delete(ctx, input.ReportID) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot delete vendor compliance report", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.DeleteVendorComplianceReportPayload{ - DeletedVendorComplianceReportID: input.ReportID, - }, nil -} - -// UploadVendorBusinessAssociateAgreement is the resolver for the uploadVendorBusinessAssociateAgreement field. -func (r *mutationResolver) UploadVendorBusinessAssociateAgreement(ctx context.Context, input types.UploadVendorBusinessAssociateAgreementInput) (*types.UploadVendorBusinessAssociateAgreementPayload, error) { - if err := r.authorize(ctx, input.VendorID, probo.ActionVendorBusinessAssociateAgreementUpload); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, input.VendorID.TenantID()) - - vendorBusinessAssociateAgreement, file, err := prb.VendorBusinessAssociateAgreements.Upload( - ctx, - input.VendorID, - &probo.VendorBusinessAssociateAgreementCreateRequest{ - File: input.File.File, - ValidFrom: input.ValidFrom, - ValidUntil: input.ValidUntil, - FileName: input.FileName, - }, - ) - if err != nil { - if validationErrors, ok := errors.AsType[validator.ValidationErrors](err); ok { - return nil, gqlutils.InvalidValidationErrors(ctx, validationErrors) - } - r.logger.ErrorCtx(ctx, "cannot upload vendor business associate agreement", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.UploadVendorBusinessAssociateAgreementPayload{ - VendorBusinessAssociateAgreement: types.NewVendorBusinessAssociateAgreement(vendorBusinessAssociateAgreement, file), - }, nil -} - -// UpdateVendorBusinessAssociateAgreement is the resolver for the updateVendorBusinessAssociateAgreement field. -func (r *mutationResolver) UpdateVendorBusinessAssociateAgreement(ctx context.Context, input types.UpdateVendorBusinessAssociateAgreementInput) (*types.UpdateVendorBusinessAssociateAgreementPayload, error) { - if err := r.authorize(ctx, input.VendorID, probo.ActionVendorBusinessAssociateAgreementUpdate); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, input.VendorID.TenantID()) - - vendorBusinessAssociateAgreement, file, err := prb.VendorBusinessAssociateAgreements.Update( - ctx, - input.VendorID, - &probo.VendorBusinessAssociateAgreementUpdateRequest{ - ValidFrom: gqlutils.UnwrapOmittable(input.ValidFrom), - ValidUntil: gqlutils.UnwrapOmittable(input.ValidUntil), - }, - ) - if err != nil { - if validationErrors, ok := errors.AsType[validator.ValidationErrors](err); ok { - return nil, gqlutils.InvalidValidationErrors(ctx, validationErrors) - } - r.logger.ErrorCtx(ctx, "cannot update vendor business associate agreement", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.UpdateVendorBusinessAssociateAgreementPayload{ - VendorBusinessAssociateAgreement: types.NewVendorBusinessAssociateAgreement(vendorBusinessAssociateAgreement, file), - }, nil -} - -// DeleteVendorBusinessAssociateAgreement is the resolver for the deleteVendorBusinessAssociateAgreement field. -func (r *mutationResolver) DeleteVendorBusinessAssociateAgreement(ctx context.Context, input types.DeleteVendorBusinessAssociateAgreementInput) (*types.DeleteVendorBusinessAssociateAgreementPayload, error) { - if err := r.authorize(ctx, input.VendorID, probo.ActionVendorBusinessAssociateAgreementDelete); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, input.VendorID.TenantID()) - - err := prb.VendorBusinessAssociateAgreements.DeleteByVendorID(ctx, input.VendorID) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot delete vendor business associate agreement", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.DeleteVendorBusinessAssociateAgreementPayload{ - DeletedVendorID: input.VendorID, - }, nil -} - -// UploadVendorDataPrivacyAgreement is the resolver for the uploadVendorDataPrivacyAgreement field. -func (r *mutationResolver) UploadVendorDataPrivacyAgreement(ctx context.Context, input types.UploadVendorDataPrivacyAgreementInput) (*types.UploadVendorDataPrivacyAgreementPayload, error) { - if err := r.authorize(ctx, input.VendorID, probo.ActionVendorDataPrivacyAgreementUpload); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, input.VendorID.TenantID()) - - vendorDataPrivacyAgreement, file, err := prb.VendorDataPrivacyAgreements.Upload( - ctx, - input.VendorID, - &probo.VendorDataPrivacyAgreementCreateRequest{ - File: input.File.File, - ValidFrom: input.ValidFrom, - ValidUntil: input.ValidUntil, - FileName: input.FileName, - }, - ) - if err != nil { - if validationErrors, ok := errors.AsType[validator.ValidationErrors](err); ok { - return nil, gqlutils.InvalidValidationErrors(ctx, validationErrors) - } - r.logger.ErrorCtx(ctx, "cannot upload vendor data privacy agreement", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.UploadVendorDataPrivacyAgreementPayload{ - VendorDataPrivacyAgreement: types.NewVendorDataPrivacyAgreement(vendorDataPrivacyAgreement, file), - }, nil -} - -// UpdateVendorDataPrivacyAgreement is the resolver for the updateVendorDataPrivacyAgreement field. -func (r *mutationResolver) UpdateVendorDataPrivacyAgreement(ctx context.Context, input types.UpdateVendorDataPrivacyAgreementInput) (*types.UpdateVendorDataPrivacyAgreementPayload, error) { - if err := r.authorize(ctx, input.VendorID, probo.ActionVendorDataPrivacyAgreementUpdate); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, input.VendorID.TenantID()) - - vendorDataPrivacyAgreement, file, err := prb.VendorDataPrivacyAgreements.Update( - ctx, - input.VendorID, - &probo.VendorDataPrivacyAgreementUpdateRequest{ - ValidFrom: gqlutils.UnwrapOmittable(input.ValidFrom), - ValidUntil: gqlutils.UnwrapOmittable(input.ValidUntil), - }, - ) - if err != nil { - if validationErrors, ok := errors.AsType[validator.ValidationErrors](err); ok { - return nil, gqlutils.InvalidValidationErrors(ctx, validationErrors) - } - r.logger.ErrorCtx(ctx, "cannot update vendor data privacy agreement", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.UpdateVendorDataPrivacyAgreementPayload{ - VendorDataPrivacyAgreement: types.NewVendorDataPrivacyAgreement(vendorDataPrivacyAgreement, file), - }, nil -} - -// DeleteVendorDataPrivacyAgreement is the resolver for the deleteVendorDataPrivacyAgreement field. -func (r *mutationResolver) DeleteVendorDataPrivacyAgreement(ctx context.Context, input types.DeleteVendorDataPrivacyAgreementInput) (*types.DeleteVendorDataPrivacyAgreementPayload, error) { - if err := r.authorize(ctx, input.VendorID, probo.ActionVendorDataPrivacyAgreementDelete); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, input.VendorID.TenantID()) - - err := prb.VendorDataPrivacyAgreements.DeleteByVendorID(ctx, input.VendorID) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot delete vendor data privacy agreement", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.DeleteVendorDataPrivacyAgreementPayload{ - DeletedVendorID: input.VendorID, - }, nil -} - -// CreateDocument is the resolver for the createDocument field. -func (r *mutationResolver) CreateDocument(ctx context.Context, input types.CreateDocumentInput) (*types.CreateDocumentPayload, error) { - if err := r.authorize(ctx, input.OrganizationID, probo.ActionDocumentCreate); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, input.OrganizationID.TenantID()) - - var content string - if input.Content != nil { - content = *input.Content - } - - document, documentVersion, err := prb.Documents.Create( - ctx, - probo.CreateDocumentRequest{ - OrganizationID: input.OrganizationID, - Title: input.Title, - Content: content, - Classification: input.Classification, - DocumentType: input.DocumentType, - TrustCenterVisibility: input.TrustCenterVisibility, - DefaultApproverIDs: input.DefaultApproverIds, - }, - ) - if err != nil { - if errors.Is(err, coredata.ErrResourceAlreadyExists) { - return nil, gqlutils.Conflict(ctx, err) - } - - if validationErrors, ok := errors.AsType[validator.ValidationErrors](err); ok { - return nil, gqlutils.InvalidValidationErrors(ctx, validationErrors) - } - r.logger.ErrorCtx(ctx, "cannot create document", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.CreateDocumentPayload{ - DocumentEdge: types.NewDocumentEdge(document, coredata.DocumentOrderFieldTitle), - DocumentVersionEdge: types.NewDocumentVersionEdge(documentVersion, coredata.DocumentVersionOrderFieldCreatedAt), - }, nil -} - -// UpdateDocument is the resolver for the updateDocument field. -func (r *mutationResolver) UpdateDocument(ctx context.Context, input types.UpdateDocumentInput) (*types.UpdateDocumentPayload, error) { - if err := r.authorize(ctx, input.ID, probo.ActionDocumentUpdate); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, input.ID.TenantID()) - - var defaultApproverIDs *[]gid.GID - if input.DefaultApproverIds != nil { - defaultApproverIDs = &input.DefaultApproverIds - } - - document, documentVersion, draftCreated, err := prb.Documents.Update( - ctx, - probo.UpdateDocumentRequest{ - DocumentID: input.ID, - Title: input.Title, - Content: input.Content, - Classification: input.Classification, - DocumentType: input.DocumentType, - TrustCenterVisibility: input.TrustCenterVisibility, - DefaultApproverIDs: defaultApproverIDs, - }, - ) - - if err != nil { - if errors.Is(err, coredata.ErrResourceNotFound) { - return nil, gqlutils.NotFound(ctx, err) - } - if errArchived, ok := errors.AsType[*probo.ErrDocumentArchived](err); ok { - return nil, gqlutils.Conflict(ctx, errArchived) - } - if validationErrors, ok := errors.AsType[validator.ValidationErrors](err); ok { - return nil, gqlutils.InvalidValidationErrors(ctx, validationErrors) - } - r.logger.ErrorCtx(ctx, "cannot update document", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - payload := &types.UpdateDocumentPayload{ - Document: types.NewDocument(document), - } - - if documentVersion != nil { - payload.DocumentVersion = types.NewDocumentVersion(documentVersion) - } - - if draftCreated { - payload.DocumentVersionEdge = types.NewDocumentVersionEdge( - documentVersion, - coredata.DocumentVersionOrderFieldCreatedAt, - ) - } - - return payload, nil -} - -// DeleteDocumentDraft is the resolver for the deleteDocumentDraft field. -func (r *mutationResolver) DeleteDocumentDraft(ctx context.Context, input types.DeleteDocumentDraftInput) (*types.DeleteDocumentDraftPayload, error) { - if err := r.authorize(ctx, input.DocumentID, probo.ActionDocumentDeleteDraft); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, input.DocumentID.TenantID()) - - document, err := prb.Documents.DeleteDraft(ctx, input.DocumentID) - if err != nil { - if errors.Is(err, coredata.ErrResourceNotFound) { - return nil, gqlutils.NotFound(ctx, err) - } - if errNotDeletable, ok := errors.AsType[*probo.ErrDocumentDraftNotDeletable](err); ok { - return nil, gqlutils.Conflict(ctx, errNotDeletable) - } - if errArchived, ok := errors.AsType[*probo.ErrDocumentArchived](err); ok { - return nil, gqlutils.Conflict(ctx, errArchived) - } - r.logger.ErrorCtx(ctx, "cannot delete document draft", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.DeleteDocumentDraftPayload{ - Document: types.NewDocument(document), - }, nil -} - -// ArchiveDocument is the resolver for the archiveDocument field. -func (r *mutationResolver) ArchiveDocument(ctx context.Context, input types.ArchiveDocumentInput) (*types.ArchiveDocumentPayload, error) { - if err := r.authorize(ctx, input.DocumentID, probo.ActionDocumentArchive); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, input.DocumentID.TenantID()) - - document, err := prb.Documents.Archive(ctx, input.DocumentID) - if err != nil { - if errArchived, ok := errors.AsType[*probo.ErrDocumentArchived](err); ok { - return nil, gqlutils.Conflict(ctx, errArchived) - } - r.logger.ErrorCtx(ctx, "cannot archive document", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.ArchiveDocumentPayload{ - Document: types.NewDocument(document), - }, nil -} - -// UnarchiveDocument is the resolver for the unarchiveDocument field. -func (r *mutationResolver) UnarchiveDocument(ctx context.Context, input types.UnarchiveDocumentInput) (*types.UnarchiveDocumentPayload, error) { - if err := r.authorize(ctx, input.DocumentID, probo.ActionDocumentUnarchive); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, input.DocumentID.TenantID()) - - document, err := prb.Documents.Unarchive(ctx, input.DocumentID) - if err != nil { - if errNotArchived, ok := errors.AsType[*probo.ErrDocumentNotArchived](err); ok { - return nil, gqlutils.Conflict(ctx, errNotArchived) - } - r.logger.ErrorCtx(ctx, "cannot unarchive document", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.UnarchiveDocumentPayload{ - Document: types.NewDocument(document), - }, nil -} - -// DeleteDocument is the resolver for the deleteDocument field. -func (r *mutationResolver) DeleteDocument(ctx context.Context, input types.DeleteDocumentInput) (*types.DeleteDocumentPayload, error) { - if err := r.authorize(ctx, input.DocumentID, probo.ActionDocumentDelete); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, input.DocumentID.TenantID()) - - err := prb.Documents.SoftDelete(ctx, input.DocumentID) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot soft delete document", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.DeleteDocumentPayload{ - DeletedDocumentID: input.DocumentID, - }, nil -} - -// CreateMeeting is the resolver for the createMeeting field. -func (r *mutationResolver) CreateMeeting(ctx context.Context, input types.CreateMeetingInput) (*types.CreateMeetingPayload, error) { - if err := r.authorize(ctx, input.OrganizationID, probo.ActionMeetingCreate); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, input.OrganizationID.TenantID()) - - meeting, err := prb.Meetings.Create( - ctx, - probo.CreateMeetingRequest{ - OrganizationID: input.OrganizationID, - Name: input.Name, - Date: input.Date, - AttendeeIDs: input.AttendeeIds, - Minutes: input.Minutes, - }, - ) - if err != nil { - if validationErrors, ok := errors.AsType[validator.ValidationErrors](err); ok { - return nil, gqlutils.InvalidValidationErrors(ctx, validationErrors) - } - r.logger.ErrorCtx(ctx, "cannot create meeting", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.CreateMeetingPayload{ - MeetingEdge: types.NewMeetingEdge(meeting, coredata.MeetingOrderFieldCreatedAt), - }, nil -} - -// UpdateMeeting is the resolver for the updateMeeting field. -func (r *mutationResolver) UpdateMeeting(ctx context.Context, input types.UpdateMeetingInput) (*types.UpdateMeetingPayload, error) { - if err := r.authorize(ctx, input.MeetingID, probo.ActionMeetingUpdate); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, input.MeetingID.TenantID()) - - var attendeeIDs []gid.GID - if input.AttendeeIds != nil { - attendeeIDs = input.AttendeeIds - } - - meeting, err := prb.Meetings.Update( - ctx, - probo.UpdateMeetingRequest{ - MeetingID: input.MeetingID, - Name: input.Name, - Date: input.Date, - AttendeeIDs: attendeeIDs, - Minutes: gqlutils.UnwrapOmittable(input.Minutes), - }, - ) - if err != nil { - if validationErrors, ok := errors.AsType[validator.ValidationErrors](err); ok { - return nil, gqlutils.InvalidValidationErrors(ctx, validationErrors) - } - r.logger.ErrorCtx(ctx, "cannot update meeting", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.UpdateMeetingPayload{ - Meeting: types.NewMeeting(meeting), - }, nil -} - -// DeleteMeeting is the resolver for the deleteMeeting field. -func (r *mutationResolver) DeleteMeeting(ctx context.Context, input types.DeleteMeetingInput) (*types.DeleteMeetingPayload, error) { - if err := r.authorize(ctx, input.MeetingID, probo.ActionMeetingDelete); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, input.MeetingID.TenantID()) - - err := prb.Meetings.Delete(ctx, input.MeetingID) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot delete meeting", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.DeleteMeetingPayload{ - DeletedMeetingID: input.MeetingID, - }, nil -} - -// CreateWebhookSubscription is the resolver for the createWebhookSubscription field. -func (r *mutationResolver) CreateWebhookSubscription(ctx context.Context, input types.CreateWebhookSubscriptionInput) (*types.CreateWebhookSubscriptionPayload, error) { - if err := r.authorize(ctx, input.OrganizationID, probo.ActionWebhookSubscriptionCreate); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, input.OrganizationID.TenantID()) - - wc, err := prb.WebhookSubscriptions.Create( - ctx, - probo.CreateWebhookSubscriptionRequest{ - OrganizationID: input.OrganizationID, - EndpointURL: input.EndpointURL, - SelectedEvents: input.SelectedEvents, - }, - ) - if err != nil { - if validationErrors, ok := errors.AsType[validator.ValidationErrors](err); ok { - return nil, gqlutils.InvalidValidationErrors(ctx, validationErrors) - } - r.logger.ErrorCtx(ctx, "cannot create webhook subscription", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.CreateWebhookSubscriptionPayload{ - WebhookSubscriptionEdge: types.NewWebhookSubscriptionEdge(wc, coredata.WebhookSubscriptionOrderFieldCreatedAt), - }, nil -} - -// UpdateWebhookSubscription is the resolver for the updateWebhookSubscription field. -func (r *mutationResolver) UpdateWebhookSubscription(ctx context.Context, input types.UpdateWebhookSubscriptionInput) (*types.UpdateWebhookSubscriptionPayload, error) { - if err := r.authorize(ctx, input.ID, probo.ActionWebhookSubscriptionUpdate); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, input.ID.TenantID()) - - wc, err := prb.WebhookSubscriptions.Update( - ctx, - probo.UpdateWebhookSubscriptionRequest{ - WebhookSubscriptionID: input.ID, - EndpointURL: input.EndpointURL, - SelectedEvents: input.SelectedEvents, - }, - ) - if err != nil { - if validationErrors, ok := errors.AsType[validator.ValidationErrors](err); ok { - return nil, gqlutils.InvalidValidationErrors(ctx, validationErrors) - } - r.logger.ErrorCtx(ctx, "cannot update webhook subscription", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.UpdateWebhookSubscriptionPayload{ - WebhookSubscription: types.NewWebhookSubscription(wc), - }, nil -} - -// DeleteWebhookSubscription is the resolver for the deleteWebhookSubscription field. -func (r *mutationResolver) DeleteWebhookSubscription(ctx context.Context, input types.DeleteWebhookSubscriptionInput) (*types.DeleteWebhookSubscriptionPayload, error) { - if err := r.authorize(ctx, input.WebhookSubscriptionID, probo.ActionWebhookSubscriptionDelete); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, input.WebhookSubscriptionID.TenantID()) - - err := prb.WebhookSubscriptions.Delete(ctx, input.WebhookSubscriptionID) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot delete webhook subscription", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.DeleteWebhookSubscriptionPayload{ - DeletedWebhookSubscriptionID: input.WebhookSubscriptionID, - }, nil -} - -// CreateStatementOfApplicability is the resolver for the createStatementOfApplicability field. -func (r *mutationResolver) CreateStatementOfApplicability(ctx context.Context, input types.CreateStatementOfApplicabilityInput) (*types.CreateStatementOfApplicabilityPayload, error) { - if err := r.authorize(ctx, input.OrganizationID, probo.ActionStatementOfApplicabilityCreate); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, input.OrganizationID.TenantID()) - - statementOfApplicability, err := prb.StatementsOfApplicability.Create( - ctx, - probo.CreateStatementOfApplicabilityRequest{ - OrganizationID: input.OrganizationID, - Name: input.Name, - OwnerID: input.OwnerID, - }, - ) - if err != nil { - if errors.Is(err, coredata.ErrResourceAlreadyExists) { - return nil, gqlutils.Conflict(ctx, err) - } - if validationErrors, ok := errors.AsType[validator.ValidationErrors](err); ok { - return nil, gqlutils.InvalidValidationErrors(ctx, validationErrors) - } - r.logger.ErrorCtx(ctx, "cannot create statement_of_applicability", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.CreateStatementOfApplicabilityPayload{ - StatementOfApplicabilityEdge: types.NewStatementOfApplicabilityEdge(statementOfApplicability, coredata.StatementOfApplicabilityOrderFieldCreatedAt), - }, nil -} - -// UpdateStatementOfApplicability is the resolver for the updateStatementOfApplicability field. -func (r *mutationResolver) UpdateStatementOfApplicability(ctx context.Context, input types.UpdateStatementOfApplicabilityInput) (*types.UpdateStatementOfApplicabilityPayload, error) { - if err := r.authorize(ctx, input.ID, probo.ActionStatementOfApplicabilityUpdate); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, input.ID.TenantID()) - - var name *string - if input.Name != nil { - name = input.Name - } - - statementOfApplicability, err := prb.StatementsOfApplicability.Update( - ctx, - probo.UpdateStatementOfApplicabilityRequest{ - StatementOfApplicabilityID: input.ID, - Name: name, - OwnerID: input.OwnerID, - }, - ) - if err != nil { - if errors.Is(err, coredata.ErrResourceAlreadyExists) { - return nil, gqlutils.Conflict(ctx, err) - } - if validationErrors, ok := errors.AsType[validator.ValidationErrors](err); ok { - return nil, gqlutils.InvalidValidationErrors(ctx, validationErrors) - } - r.logger.ErrorCtx(ctx, "cannot update statement_of_applicability", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.UpdateStatementOfApplicabilityPayload{ - StatementOfApplicability: types.NewStatementOfApplicability(statementOfApplicability), - }, nil -} - -// DeleteStatementOfApplicability is the resolver for the deleteStatementOfApplicability field. -func (r *mutationResolver) DeleteStatementOfApplicability(ctx context.Context, input types.DeleteStatementOfApplicabilityInput) (*types.DeleteStatementOfApplicabilityPayload, error) { - if err := r.authorize(ctx, input.StatementOfApplicabilityID, probo.ActionStatementOfApplicabilityDelete); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, input.StatementOfApplicabilityID.TenantID()) - - err := prb.StatementsOfApplicability.Delete(ctx, input.StatementOfApplicabilityID) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot delete statement_of_applicability", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.DeleteStatementOfApplicabilityPayload{ - DeletedStatementOfApplicabilityID: input.StatementOfApplicabilityID, - }, nil -} - -// ExportStatementOfApplicabilityPDF is the resolver for the exportStatementOfApplicabilityPDF field. -func (r *mutationResolver) ExportStatementOfApplicabilityPDF(ctx context.Context, input types.ExportStatementOfApplicabilityPDFInput) (*types.ExportStatementOfApplicabilityPDFPayload, error) { - if err := r.authorize(ctx, input.StatementOfApplicabilityID, probo.ActionStatementOfApplicabilityExport); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, input.StatementOfApplicabilityID.TenantID()) - - pdfData, err := prb.StatementsOfApplicability.ExportPDF(ctx, input.StatementOfApplicabilityID) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot export statement of applicability PDF", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - base64Data := base64.StdEncoding.EncodeToString(pdfData) - dataURI := fmt.Sprintf("data:application/pdf;base64,%s", base64Data) - - return &types.ExportStatementOfApplicabilityPDFPayload{ - Data: dataURI, - }, nil -} - -// PublishMajorDocumentVersion is the resolver for the publishMajorDocumentVersion field. -func (r *mutationResolver) PublishMajorDocumentVersion(ctx context.Context, input types.PublishMajorDocumentVersionInput) (*types.PublishDocumentVersionPayload, error) { - if err := r.authorize(ctx, input.DocumentID, probo.ActionDocumentVersionPublish); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, input.DocumentID.TenantID()) - - document, documentVersion, err := prb.Documents.PublishMajorVersion( - ctx, - input.DocumentID, - authn.IdentityFromContext(ctx).ID, - input.Changelog, - ) - if err != nil { - if errArchived, ok := errors.AsType[*probo.ErrDocumentArchived](err); ok { - return nil, gqlutils.Conflict(ctx, errArchived) - } - - if errNotDraft, ok := errors.AsType[*probo.ErrDocumentVersionNotDraft](err); ok { - return nil, gqlutils.Invalid(ctx, errNotDraft) - } - - if errPending, ok := errors.AsType[*probo.ErrDocumentVersionPendingApproval](err); ok { - return nil, gqlutils.Conflict(ctx, errPending) - } - - r.logger.ErrorCtx(ctx, "cannot publish major document version", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.PublishDocumentVersionPayload{ - Document: types.NewDocument(document), - DocumentVersion: types.NewDocumentVersion(documentVersion), - }, nil -} - -// PublishMinorDocumentVersion is the resolver for the publishMinorDocumentVersion field. -func (r *mutationResolver) PublishMinorDocumentVersion(ctx context.Context, input types.PublishMinorDocumentVersionInput) (*types.PublishDocumentVersionPayload, error) { - if err := r.authorize(ctx, input.DocumentID, probo.ActionDocumentVersionPublish); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, input.DocumentID.TenantID()) - - document, documentVersion, err := prb.Documents.PublishMinorVersion( - ctx, - input.DocumentID, - authn.IdentityFromContext(ctx).ID, - input.Changelog, - ) - if err != nil { - if errArchived, ok := errors.AsType[*probo.ErrDocumentArchived](err); ok { - return nil, gqlutils.Conflict(ctx, errArchived) - } - - if errNotDraft, ok := errors.AsType[*probo.ErrDocumentVersionNotDraft](err); ok { - return nil, gqlutils.Invalid(ctx, errNotDraft) - } - - if errPending, ok := errors.AsType[*probo.ErrDocumentVersionPendingApproval](err); ok { - return nil, gqlutils.Conflict(ctx, errPending) - } - - r.logger.ErrorCtx(ctx, "cannot publish minor document version", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.PublishDocumentVersionPayload{ - Document: types.NewDocument(document), - DocumentVersion: types.NewDocumentVersion(documentVersion), - }, nil -} - -// BulkPublishMajorDocumentVersions is the resolver for the bulkPublishMajorDocumentVersions field. -func (r *mutationResolver) BulkPublishMajorDocumentVersions(ctx context.Context, input types.BulkPublishDocumentVersionsInput) (*types.BulkPublishDocumentVersionsPayload, error) { - if len(input.DocumentIds) == 0 { - return &types.BulkPublishDocumentVersionsPayload{ - DocumentVersions: []*types.DocumentVersion{}, - Documents: []*types.Document{}, - }, nil - } - - for _, documentID := range input.DocumentIds { - if err := r.authorize(ctx, documentID, probo.ActionDocumentVersionPublish); err != nil { - return nil, err - } - } - - prb := r.ProboService(ctx, input.DocumentIds[0].TenantID()) - - versions, documents, err := prb.DocumentApprovals.BulkPublishMajorVersions(ctx, probo.BulkPublishVersionsRequest{ - DocumentIDs: input.DocumentIds, - Changelog: input.Changelog, - }) - if err != nil { - if errArchived, ok := errors.AsType[*probo.ErrDocumentArchived](err); ok { - return nil, gqlutils.Conflict(ctx, errArchived) - } - - if errNotDraft, ok := errors.AsType[*probo.ErrDocumentVersionNotDraft](err); ok { - return nil, gqlutils.Invalid(ctx, errNotDraft) - } - - r.logger.ErrorCtx(ctx, "cannot bulk publish major document versions", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - typesVersions := make([]*types.DocumentVersion, len(versions)) - for i, v := range versions { - typesVersions[i] = types.NewDocumentVersion(v) - } - - typesDocuments := make([]*types.Document, len(documents)) - for i, d := range documents { - typesDocuments[i] = types.NewDocument(d) - } - - return &types.BulkPublishDocumentVersionsPayload{ - DocumentVersions: typesVersions, - Documents: typesDocuments, - }, nil -} - -// BulkPublishMinorDocumentVersions is the resolver for the bulkPublishMinorDocumentVersions field. -func (r *mutationResolver) BulkPublishMinorDocumentVersions(ctx context.Context, input types.BulkPublishDocumentVersionsInput) (*types.BulkPublishDocumentVersionsPayload, error) { - if len(input.DocumentIds) == 0 { - return &types.BulkPublishDocumentVersionsPayload{ - DocumentVersions: []*types.DocumentVersion{}, - Documents: []*types.Document{}, - }, nil - } - - for _, documentID := range input.DocumentIds { - if err := r.authorize(ctx, documentID, probo.ActionDocumentVersionPublish); err != nil { - return nil, err - } - } - - prb := r.ProboService(ctx, input.DocumentIds[0].TenantID()) - - versions, documents, err := prb.Documents.BulkPublishMinorVersions(ctx, probo.BulkPublishVersionsRequest{ - DocumentIDs: input.DocumentIds, - Changelog: input.Changelog, - }) - if err != nil { - if errArchived, ok := errors.AsType[*probo.ErrDocumentArchived](err); ok { - return nil, gqlutils.Conflict(ctx, errArchived) - } - - if errNotDraft, ok := errors.AsType[*probo.ErrDocumentVersionNotDraft](err); ok { - return nil, gqlutils.Invalid(ctx, errNotDraft) - } - - r.logger.ErrorCtx(ctx, "cannot bulk publish minor document versions", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - typesVersions := make([]*types.DocumentVersion, len(versions)) - for i, v := range versions { - typesVersions[i] = types.NewDocumentVersion(v) - } - - typesDocuments := make([]*types.Document, len(documents)) - for i, d := range documents { - typesDocuments[i] = types.NewDocument(d) - } - - return &types.BulkPublishDocumentVersionsPayload{ - DocumentVersions: typesVersions, - Documents: typesDocuments, - }, nil -} - -// RequestDocumentVersionApproval is the resolver for the requestDocumentVersionApproval field. -func (r *mutationResolver) RequestDocumentVersionApproval(ctx context.Context, input types.RequestDocumentVersionApprovalInput) (*types.RequestDocumentVersionApprovalPayload, error) { - if err := r.authorize(ctx, input.DocumentID, probo.ActionDocumentVersionRequestApproval); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, input.DocumentID.TenantID()) - - quorum, err := prb.DocumentApprovals.RequestApproval(ctx, probo.RequestApprovalRequest{ - DocumentID: input.DocumentID, - ApproverIDs: input.ApproverIds, - Changelog: input.Changelog, - }) - if err != nil { - if errArchived, ok := errors.AsType[*probo.ErrDocumentArchived](err); ok { - return nil, gqlutils.Conflict(ctx, errArchived) - } - - if errNotDraft, ok := errors.AsType[*probo.ErrDocumentVersionNotDraft](err); ok { - return nil, gqlutils.Conflict(ctx, errNotDraft) - } - - if validationErrors, ok := errors.AsType[validator.ValidationErrors](err); ok { - return nil, gqlutils.InvalidValidationErrors(ctx, validationErrors) - } - - r.logger.ErrorCtx(ctx, "cannot request document version approval", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.RequestDocumentVersionApprovalPayload{ - ApprovalQuorum: types.NewDocumentVersionApprovalQuorum(quorum), - }, nil -} - -// VoidDocumentVersionApproval is the resolver for the voidDocumentVersionApproval field. -func (r *mutationResolver) VoidDocumentVersionApproval(ctx context.Context, input types.VoidDocumentVersionApprovalInput) (*types.VoidDocumentVersionApprovalPayload, error) { - if err := r.authorize(ctx, input.DocumentVersionID, probo.ActionDocumentVersionVoidApproval); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, input.DocumentVersionID.TenantID()) - - quorum, documentVersion, err := prb.DocumentApprovals.VoidApproval(ctx, input.DocumentVersionID) - if err != nil { - if errArchived, ok := errors.AsType[*probo.ErrDocumentArchived](err); ok { - return nil, gqlutils.Conflict(ctx, errArchived) - } - - if errNotPending, ok := errors.AsType[*probo.ErrDocumentVersionNotPendingApproval](err); ok { - return nil, gqlutils.Conflict(ctx, errNotPending) - } - - r.logger.ErrorCtx(ctx, "cannot void document version approval", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.VoidDocumentVersionApprovalPayload{ - ApprovalQuorum: types.NewDocumentVersionApprovalQuorum(quorum), - DocumentVersion: types.NewDocumentVersion(documentVersion), - }, nil -} - -// BulkDeleteDocuments is the resolver for the bulkDeleteDocuments field. -func (r *mutationResolver) BulkDeleteDocuments(ctx context.Context, input types.BulkDeleteDocumentsInput) (*types.BulkDeleteDocumentsPayload, error) { - if len(input.DocumentIds) == 0 { - return &types.BulkDeleteDocumentsPayload{ - DeletedDocumentIds: []gid.GID{}, - }, nil - } - - for _, documentID := range input.DocumentIds { - if err := r.authorize(ctx, documentID, probo.ActionDocumentDelete); err != nil { - return nil, err - } - } - - prb := r.ProboService(ctx, input.DocumentIds[0].TenantID()) - - err := prb.Documents.BulkSoftDelete(ctx, input.DocumentIds) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot bulk delete documents", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.BulkDeleteDocumentsPayload{ - DeletedDocumentIds: input.DocumentIds, - }, nil -} - -// BulkArchiveDocuments is the resolver for the bulkArchiveDocuments field. -func (r *mutationResolver) BulkArchiveDocuments(ctx context.Context, input types.BulkArchiveDocumentsInput) (*types.BulkArchiveDocumentsPayload, error) { - if len(input.DocumentIds) == 0 { - return &types.BulkArchiveDocumentsPayload{ - Documents: []*types.Document{}, - }, nil - } - - for _, documentID := range input.DocumentIds { - if err := r.authorize(ctx, documentID, probo.ActionDocumentArchive); err != nil { - return nil, err - } - } - - prb := r.ProboService(ctx, input.DocumentIds[0].TenantID()) - - if err := prb.Documents.BulkArchive(ctx, input.DocumentIds); err != nil { - r.logger.ErrorCtx(ctx, "cannot bulk archive documents", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.BulkArchiveDocumentsPayload{ - Documents: []*types.Document{}, - }, nil -} - -// BulkUnarchiveDocuments is the resolver for the bulkUnarchiveDocuments field. -func (r *mutationResolver) BulkUnarchiveDocuments(ctx context.Context, input types.BulkUnarchiveDocumentsInput) (*types.BulkUnarchiveDocumentsPayload, error) { - if len(input.DocumentIds) == 0 { - return &types.BulkUnarchiveDocumentsPayload{ - Documents: []*types.Document{}, - }, nil - } - - for _, documentID := range input.DocumentIds { - if err := r.authorize(ctx, documentID, probo.ActionDocumentUnarchive); err != nil { - return nil, err - } - } - - prb := r.ProboService(ctx, input.DocumentIds[0].TenantID()) - - if err := prb.Documents.BulkUnarchive(ctx, input.DocumentIds); err != nil { - r.logger.ErrorCtx(ctx, "cannot bulk unarchive documents", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.BulkUnarchiveDocumentsPayload{ - Documents: []*types.Document{}, - }, nil -} - -// BulkExportDocuments is the resolver for the bulkExportDocuments field. -func (r *mutationResolver) BulkExportDocuments(ctx context.Context, input types.BulkExportDocumentsInput) (*types.BulkExportDocumentsPayload, error) { - if len(input.DocumentIds) == 0 { - r.logger.ErrorCtx(ctx, "no document ids provided") - return nil, gqlutils.Internal(ctx) - } - - // TODO have a way to batch authorize for resources - for _, documentID := range input.DocumentIds { - if err := r.authorize(ctx, documentID, probo.ActionDocumentVersionExport); err != nil { - return nil, err - } - } - - prb := r.ProboService(ctx, input.DocumentIds[0].TenantID()) - - identity := authn.IdentityFromContext(ctx) - - options := probo.ExportPDFOptions{ - WithWatermark: input.WithWatermark, - WithSignatures: input.WithSignatures, - WatermarkEmail: input.WatermarkEmail, - } - - documentExport, exportErr := prb.Documents.RequestExport(ctx, input.DocumentIds, identity.EmailAddress, identity.FullName, options) - if exportErr != nil { - r.logger.ErrorCtx(ctx, "cannot request document export", log.Error(exportErr)) - return nil, gqlutils.Internal(ctx) - } - - return &types.BulkExportDocumentsPayload{ - ExportJobID: documentExport.ID, - }, nil -} - -// GenerateDocumentChangelog is the resolver for the generateDocumentChangelog field. -func (r *mutationResolver) GenerateDocumentChangelog(ctx context.Context, input types.GenerateDocumentChangelogInput) (*types.GenerateDocumentChangelogPayload, error) { - if err := r.authorize(ctx, input.DocumentID, probo.ActionDocumentChangelogGenerate); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, input.DocumentID.TenantID()) - - changelog, err := prb.Documents.GenerateChangelog(ctx, input.DocumentID) - if err != nil { - if errArchived, ok := errors.AsType[*probo.ErrDocumentArchived](err); ok { - return nil, gqlutils.Conflict(ctx, errArchived) - } - - r.logger.ErrorCtx(ctx, "cannot generate document changelog", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.GenerateDocumentChangelogPayload{ - Changelog: *changelog, - }, nil -} - -// RequestSignature is the resolver for the requestSignature field. -func (r *mutationResolver) RequestSignature(ctx context.Context, input types.RequestSignatureInput) (*types.RequestSignaturePayload, error) { - if err := r.authorize(ctx, input.DocumentVersionID, probo.ActionDocumentVersionSignatureRequest); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, input.DocumentVersionID.TenantID()) - - documentVersionSignature, err := prb.Documents.RequestSignature( - ctx, - probo.RequestSignatureRequest{ - DocumentVersionID: input.DocumentVersionID, - Signatory: input.SignatoryID, - }, - ) - if err != nil { - if errArchived, ok := errors.AsType[*probo.ErrDocumentArchived](err); ok { - return nil, gqlutils.Conflict(ctx, errArchived) - } - - r.logger.ErrorCtx(ctx, "cannot request signature", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.RequestSignaturePayload{ - DocumentVersionSignatureEdge: types.NewDocumentVersionSignatureEdge(documentVersionSignature, coredata.DocumentVersionSignatureOrderFieldCreatedAt), - }, nil -} - -// BulkRequestSignatures is the resolver for the bulkRequestSignatures field. -func (r *mutationResolver) BulkRequestSignatures(ctx context.Context, input types.BulkRequestSignaturesInput) (*types.BulkRequestSignaturesPayload, error) { - if len(input.DocumentIds) == 0 { - return &types.BulkRequestSignaturesPayload{ - DocumentVersionSignatureEdges: []*types.DocumentVersionSignatureEdge{}, - }, nil - } - - for _, documentID := range input.DocumentIds { - if err := r.authorize(ctx, documentID, probo.ActionDocumentVersionSignatureRequest); err != nil { - return nil, err - } - } - - prb := r.ProboService(ctx, input.DocumentIds[0].TenantID()) - - documentVersionSignatures, err := prb.Documents.BulkRequestSignatures( - ctx, - probo.BulkRequestSignaturesRequest{ - DocumentIDs: input.DocumentIds, - SignatoryIDs: input.SignatoryIds, - }, - ) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot bulk request signatures", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.BulkRequestSignaturesPayload{ - DocumentVersionSignatureEdges: types.NewDocumentVersionSignatureEdges(documentVersionSignatures, coredata.DocumentVersionSignatureOrderFieldCreatedAt), - }, nil -} - -// SendSigningNotifications is the resolver for the sendSigningNotifications field. -func (r *mutationResolver) SendSigningNotifications(ctx context.Context, input types.SendSigningNotificationsInput) (*types.SendSigningNotificationsPayload, error) { - if err := r.authorize(ctx, input.OrganizationID, probo.ActionDocumentSendSigningNotifications); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, input.OrganizationID.TenantID()) - - err := prb.Documents.SendSigningNotifications(ctx, input.OrganizationID) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot send signing notifications", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.SendSigningNotificationsPayload{ - Success: true, - }, nil -} - -// CancelSignatureRequest is the resolver for the cancelSignatureRequest field. -func (r *mutationResolver) CancelSignatureRequest(ctx context.Context, input types.CancelSignatureRequestInput) (*types.CancelSignatureRequestPayload, error) { - if err := r.authorize(ctx, input.DocumentVersionSignatureID, probo.ActionDocumentVersionCancelSignature); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, input.DocumentVersionSignatureID.TenantID()) - - err := prb.Documents.CancelSignatureRequest(ctx, input.DocumentVersionSignatureID) - if err != nil { - if errArchived, ok := errors.AsType[*probo.ErrDocumentArchived](err); ok { - return nil, gqlutils.Conflict(ctx, errArchived) - } - - r.logger.ErrorCtx(ctx, "cannot cancel signature request", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.CancelSignatureRequestPayload{ - DeletedDocumentVersionSignatureID: input.DocumentVersionSignatureID, - }, nil -} - -// SignDocument is the resolver for the signDocument field. -func (r *mutationResolver) SignDocument(ctx context.Context, input types.SignDocumentInput) (*types.SignDocumentPayload, error) { - if err := r.authorize(ctx, input.DocumentVersionID, probo.ActionDocumentVersionSign); err != nil { - return nil, err - } - - identity := authn.IdentityFromContext(ctx) - prb := r.ProboService(ctx, input.DocumentVersionID.TenantID()) - - documentVersionSignature, err := prb.Documents.SignDocumentVersionByIdentity(ctx, input.DocumentVersionID, identity.ID) - if err != nil { - if errors.Is(err, coredata.ErrResourceAlreadyExists) { - return nil, gqlutils.Conflict(ctx, err) - } - - r.logger.ErrorCtx(ctx, "cannot sign document", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.SignDocumentPayload{ - DocumentVersionSignature: types.NewDocumentVersionSignature(documentVersionSignature), - }, nil -} - -// ApproveDocumentVersion is the resolver for the approveDocumentVersion field. -func (r *mutationResolver) ApproveDocumentVersion(ctx context.Context, input types.ApproveDocumentVersionInput) (*types.ApproveDocumentVersionPayload, error) { - if err := r.authorize(ctx, input.DocumentVersionID, probo.ActionDocumentVersionApprove); err != nil { - return nil, err - } - - identity := authn.IdentityFromContext(ctx) - httpReq := gqlutils.HTTPRequestFromContext(ctx) - - signerIP, _, _ := net.SplitHostPort(httpReq.RemoteAddr) - if signerIP == "" { - signerIP = httpReq.RemoteAddr - } - - prb := r.ProboService(ctx, input.DocumentVersionID.TenantID()) - - decision, err := prb.DocumentApprovals.Approve(ctx, probo.ApproveDocumentVersionRequest{ - DocumentVersionID: input.DocumentVersionID, - IdentityID: identity.ID, - Comment: input.Comment, - SignerFullName: identity.FullName, - SignerEmail: identity.EmailAddress, - SignerIPAddr: signerIP, - SignerUA: httpReq.UserAgent(), - }) - if err != nil { - if errArchived, ok := errors.AsType[*probo.ErrDocumentArchived](err); ok { - return nil, gqlutils.Conflict(ctx, errArchived) - } - - if errNotPending, ok := errors.AsType[*probo.ErrDocumentVersionNotPendingApproval](err); ok { - return nil, gqlutils.Invalid(ctx, errNotPending) - } - - if errAlready, ok := errors.AsType[*probo.ErrApprovalDecisionAlreadyMade](err); ok { - return nil, gqlutils.Conflict(ctx, errAlready) - } - - if errors.Is(err, coredata.ErrResourceNotFound) { - return nil, gqlutils.NotFound(ctx, err) - } - - r.logger.ErrorCtx(ctx, "cannot approve document version", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.ApproveDocumentVersionPayload{ - ApprovalDecision: types.NewDocumentVersionApprovalDecision(decision), - }, nil -} - -// RejectDocumentVersion is the resolver for the rejectDocumentVersion field. -func (r *mutationResolver) RejectDocumentVersion(ctx context.Context, input types.RejectDocumentVersionInput) (*types.RejectDocumentVersionPayload, error) { - if err := r.authorize(ctx, input.DocumentVersionID, probo.ActionDocumentVersionReject); err != nil { - return nil, err - } - - identity := authn.IdentityFromContext(ctx) - - prb := r.ProboService(ctx, input.DocumentVersionID.TenantID()) - - decision, err := prb.DocumentApprovals.Reject(ctx, probo.RejectDocumentVersionRequest{ - DocumentVersionID: input.DocumentVersionID, - IdentityID: identity.ID, - Comment: input.Comment, - }) - if err != nil { - if errArchived, ok := errors.AsType[*probo.ErrDocumentArchived](err); ok { - return nil, gqlutils.Conflict(ctx, errArchived) - } - - if errNotPending, ok := errors.AsType[*probo.ErrDocumentVersionNotPendingApproval](err); ok { - return nil, gqlutils.Invalid(ctx, errNotPending) - } - - if errAlready, ok := errors.AsType[*probo.ErrApprovalDecisionAlreadyMade](err); ok { - return nil, gqlutils.Conflict(ctx, errAlready) - } - - if errors.Is(err, coredata.ErrResourceNotFound) { - return nil, gqlutils.NotFound(ctx, err) - } - - r.logger.ErrorCtx(ctx, "cannot reject document version", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.RejectDocumentVersionPayload{ - ApprovalDecision: types.NewDocumentVersionApprovalDecision(decision), - }, nil -} - -// ExportDocumentVersionPDF is the resolver for the exportDocumentVersionPDF field. -func (r *mutationResolver) ExportDocumentVersionPDF(ctx context.Context, input types.ExportDocumentVersionPDFInput) (*types.ExportDocumentVersionPDFPayload, error) { - if err := r.authorize(ctx, input.DocumentVersionID, probo.ActionDocumentVersionExportPDF); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, input.DocumentVersionID.TenantID()) - - watermarkEmail := input.WatermarkEmail - if input.WithWatermark && watermarkEmail == nil { - identity := authn.IdentityFromContext(ctx) - watermarkEmail = &identity.EmailAddress - } - - options := probo.ExportPDFOptions{ - WithSignatures: input.WithSignatures, - WithWatermark: input.WithWatermark, - WatermarkEmail: watermarkEmail, - } - - pdf, err := prb.Documents.ExportPDF(ctx, input.DocumentVersionID, options) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot export document version PDF", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.ExportDocumentVersionPDFPayload{ - Data: fmt.Sprintf("data:application/pdf;base64,%s", base64.StdEncoding.EncodeToString(pdf)), - }, nil -} - -// ExportEmployeeDocumentVersionPDF is the resolver for the exportEmployeeDocumentVersionPDF field. -func (r *mutationResolver) ExportEmployeeDocumentVersionPDF(ctx context.Context, input types.ExportEmployeeDocumentVersionPDFInput) (*types.ExportEmployeeDocumentVersionPDFPayload, error) { - if err := r.authorize(ctx, input.DocumentVersionID, probo.ActionEmployeeDocumentVersionExportPDF); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, input.DocumentVersionID.TenantID()) - - documentVersion, err := prb.Documents.GetVersion(ctx, input.DocumentVersionID) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot get document version", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - identity := authn.IdentityFromContext(ctx) - documentFilter := coredata.NewDocumentFilter(nil).WithEmployeeIdentityID( - &identity.ID, - coredata.EmployeeFilterModeSignature, - coredata.EmployeeFilterModeApproval, - ) - - _, err = prb.Documents.GetWithFilter(ctx, documentVersion.DocumentID, documentFilter) - if err != nil { - if errors.Is(err, coredata.ErrResourceNotFound) { - return nil, gqlutils.NotFound(ctx, err) - } - - r.logger.ErrorCtx(ctx, "cannot get employee document", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - options := probo.ExportPDFOptions{ - WithSignatures: false, - WithWatermark: true, - WatermarkEmail: &identity.EmailAddress, - } - - pdf, err := prb.Documents.ExportPDF(ctx, input.DocumentVersionID, options) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot export employee document PDF", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.ExportEmployeeDocumentVersionPDFPayload{ - Data: fmt.Sprintf("data:application/pdf;base64,%s", base64.StdEncoding.EncodeToString(pdf)), - }, nil -} - -// ExportProcessingActivitiesPDF is the resolver for the exportProcessingActivitiesPDF field. -func (r *mutationResolver) ExportProcessingActivitiesPDF(ctx context.Context, input types.ExportProcessingActivitiesPDFInput) (*types.ExportProcessingActivitiesPDFPayload, error) { - if err := r.authorize(ctx, input.OrganizationID, probo.ActionProcessingActivityExport); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, input.OrganizationID.TenantID()) - - var snapshotIDPtr *gid.GID - if input.Filter != nil { - snapshotIDPtr = input.Filter.SnapshotID - } - processingActivityFilter := coredata.NewProcessingActivityFilter(&snapshotIDPtr) - - pdf, err := prb.ProcessingActivities.ExportPDF(ctx, input.OrganizationID, processingActivityFilter) - if err != nil { - if errors.Is(err, coredata.ErrResourceNotFound) { - return nil, gqlutils.NotFound(ctx, err) - } - r.logger.ErrorCtx(ctx, "cannot export processing activities PDF", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.ExportProcessingActivitiesPDFPayload{ - Data: fmt.Sprintf("data:application/pdf;base64,%s", base64.StdEncoding.EncodeToString(pdf)), - }, nil -} - -// ExportDataProtectionImpactAssessmentsPDF is the resolver for the exportDataProtectionImpactAssessmentsPDF field. -func (r *mutationResolver) ExportDataProtectionImpactAssessmentsPDF(ctx context.Context, input types.ExportDataProtectionImpactAssessmentsPDFInput) (*types.ExportDataProtectionImpactAssessmentsPDFPayload, error) { - if err := r.authorize(ctx, input.OrganizationID, probo.ActionDataProtectionImpactAssessmentExport); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, input.OrganizationID.TenantID()) - - var snapshotIDPtr *gid.GID - if input.Filter != nil { - snapshotIDPtr = input.Filter.SnapshotID - } - dpiaFilter := coredata.NewDataProtectionImpactAssessmentFilter(&snapshotIDPtr) - - pdf, err := prb.DataProtectionImpactAssessments.ExportPDF(ctx, input.OrganizationID, dpiaFilter) - if err != nil { - if errors.Is(err, coredata.ErrResourceNotFound) { - return nil, gqlutils.NotFound(ctx, err) - } - r.logger.ErrorCtx(ctx, "cannot export data protection impact assessments PDF", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.ExportDataProtectionImpactAssessmentsPDFPayload{ - Data: fmt.Sprintf("data:application/pdf;base64,%s", base64.StdEncoding.EncodeToString(pdf)), - }, nil -} - -// ExportTransferImpactAssessmentsPDF is the resolver for the exportTransferImpactAssessmentsPDF field. -func (r *mutationResolver) ExportTransferImpactAssessmentsPDF(ctx context.Context, input types.ExportTransferImpactAssessmentsPDFInput) (*types.ExportTransferImpactAssessmentsPDFPayload, error) { - if err := r.authorize(ctx, input.OrganizationID, probo.ActionTransferImpactAssessmentExport); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, input.OrganizationID.TenantID()) - - var snapshotIDPtr *gid.GID - if input.Filter != nil { - snapshotIDPtr = input.Filter.SnapshotID - } - tiaFilter := coredata.NewTransferImpactAssessmentFilter(&snapshotIDPtr) - - pdf, err := prb.TransferImpactAssessments.ExportPDF(ctx, input.OrganizationID, tiaFilter) - if err != nil { - if errors.Is(err, coredata.ErrResourceNotFound) { - return nil, gqlutils.NotFound(ctx, err) - } - r.logger.ErrorCtx(ctx, "cannot export transfer impact assessments PDF", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.ExportTransferImpactAssessmentsPDFPayload{ - Data: fmt.Sprintf("data:application/pdf;base64,%s", base64.StdEncoding.EncodeToString(pdf)), - }, nil -} - -// CreateVendorRiskAssessment is the resolver for the createVendorRiskAssessment field. -func (r *mutationResolver) CreateVendorRiskAssessment(ctx context.Context, input types.CreateVendorRiskAssessmentInput) (*types.CreateVendorRiskAssessmentPayload, error) { - if err := r.authorize(ctx, input.VendorID, probo.ActionVendorRiskAssessmentCreate); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, input.VendorID.TenantID()) - - vendorRiskAssessment, err := prb.Vendors.CreateRiskAssessment( - ctx, - probo.CreateVendorRiskAssessmentRequest{ - VendorID: input.VendorID, - ExpiresAt: input.ExpiresAt, - DataSensitivity: input.DataSensitivity, - BusinessImpact: input.BusinessImpact, - Notes: input.Notes, - }, - ) - if err != nil { - if validationErrors, ok := errors.AsType[validator.ValidationErrors](err); ok { - return nil, gqlutils.InvalidValidationErrors(ctx, validationErrors) - } - r.logger.ErrorCtx(ctx, "cannot create vendor risk assessment", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.CreateVendorRiskAssessmentPayload{ - VendorRiskAssessmentEdge: types.NewVendorRiskAssessmentEdge(vendorRiskAssessment, coredata.VendorRiskAssessmentOrderFieldCreatedAt), - }, nil -} - -// AssessVendor is the resolver for the assessVendor field. -func (r *mutationResolver) AssessVendor(ctx context.Context, input types.AssessVendorInput) (*types.AssessVendorPayload, error) { - if err := r.authorize(ctx, input.ID, probo.ActionVendorAssess); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, input.ID.TenantID()) - - vendor, err := prb.Vendors.Assess( - ctx, - probo.AssessVendorRequest{ - ID: input.ID, - WebsiteURL: input.WebsiteURL, - }, - ) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot assess vendor", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.AssessVendorPayload{ - Vendor: types.NewVendor(vendor), - }, nil -} - -// CreateAsset is the resolver for the createAsset field. -func (r *mutationResolver) CreateAsset(ctx context.Context, input types.CreateAssetInput) (*types.CreateAssetPayload, error) { - if err := r.authorize(ctx, input.OrganizationID, probo.ActionAssetCreate); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, input.OrganizationID.TenantID()) - - asset, err := prb.Assets.Create( - ctx, - probo.CreateAssetRequest{ - OrganizationID: input.OrganizationID, - Name: input.Name, - Amount: input.Amount, - OwnerID: input.OwnerID, - AssetType: input.AssetType, - DataTypesStored: input.DataTypesStored, - VendorIDs: input.VendorIds, - }, - ) - - if err != nil { - if validationErrors, ok := errors.AsType[validator.ValidationErrors](err); ok { - return nil, gqlutils.InvalidValidationErrors(ctx, validationErrors) - } - r.logger.ErrorCtx(ctx, "cannot create asset", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.CreateAssetPayload{ - AssetEdge: types.NewAssetEdge(asset, coredata.AssetOrderFieldCreatedAt), - }, nil -} - -// UpdateAsset is the resolver for the updateAsset field. -func (r *mutationResolver) UpdateAsset(ctx context.Context, input types.UpdateAssetInput) (*types.UpdateAssetPayload, error) { - if err := r.authorize(ctx, input.ID, probo.ActionAssetUpdate); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, input.ID.TenantID()) - - asset, err := prb.Assets.Update( - ctx, - probo.UpdateAssetRequest{ - ID: input.ID, - Name: input.Name, - Amount: input.Amount, - OwnerID: input.OwnerID, - AssetType: input.AssetType, - DataTypesStored: input.DataTypesStored, - VendorIDs: input.VendorIds, - }, - ) - if err != nil { - if validationErrors, ok := errors.AsType[validator.ValidationErrors](err); ok { - return nil, gqlutils.InvalidValidationErrors(ctx, validationErrors) - } - r.logger.ErrorCtx(ctx, "cannot update asset", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.UpdateAssetPayload{ - Asset: types.NewAsset(asset), - }, nil -} - -// DeleteAsset is the resolver for the deleteAsset field. -func (r *mutationResolver) DeleteAsset(ctx context.Context, input types.DeleteAssetInput) (*types.DeleteAssetPayload, error) { - if err := r.authorize(ctx, input.AssetID, probo.ActionAssetDelete); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, input.AssetID.TenantID()) - - err := prb.Assets.Delete(ctx, input.AssetID) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot delete asset", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.DeleteAssetPayload{ - DeletedAssetID: input.AssetID, - }, nil -} - -// CreateDatum is the resolver for the createDatum field. -func (r *mutationResolver) CreateDatum(ctx context.Context, input types.CreateDatumInput) (*types.CreateDatumPayload, error) { - if err := r.authorize(ctx, input.OrganizationID, probo.ActionDatumCreate); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, input.OrganizationID.TenantID()) - - data, err := prb.Data.Create( - ctx, - probo.CreateDatumRequest{ - OrganizationID: input.OrganizationID, - Name: input.Name, - DataClassification: input.DataClassification, - OwnerID: input.OwnerID, - VendorIDs: input.VendorIds, - }, - ) - - if err != nil { - if validationErrors, ok := errors.AsType[validator.ValidationErrors](err); ok { - return nil, gqlutils.InvalidValidationErrors(ctx, validationErrors) - } - r.logger.ErrorCtx(ctx, "cannot create datum", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.CreateDatumPayload{ - DatumEdge: types.NewDatumEdge(data, coredata.DatumOrderFieldCreatedAt), - }, nil -} - -// UpdateDatum is the resolver for the updateDatum field. -func (r *mutationResolver) UpdateDatum(ctx context.Context, input types.UpdateDatumInput) (*types.UpdateDatumPayload, error) { - if err := r.authorize(ctx, input.ID, probo.ActionDatumUpdate); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, input.ID.TenantID()) - - datum, err := prb.Data.Update( - ctx, - probo.UpdateDatumRequest{ - ID: input.ID, - Name: input.Name, - DataClassification: input.DataClassification, - OwnerID: input.OwnerID, - VendorIDs: input.VendorIds, - }, - ) - - if err != nil { - if validationErrors, ok := errors.AsType[validator.ValidationErrors](err); ok { - return nil, gqlutils.InvalidValidationErrors(ctx, validationErrors) - } - r.logger.ErrorCtx(ctx, "cannot update datum", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.UpdateDatumPayload{ - Datum: types.NewDatum(datum), - }, nil -} - -// DeleteDatum is the resolver for the deleteDatum field. -func (r *mutationResolver) DeleteDatum(ctx context.Context, input types.DeleteDatumInput) (*types.DeleteDatumPayload, error) { - if err := r.authorize(ctx, input.DatumID, probo.ActionDatumDelete); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, input.DatumID.TenantID()) - - if err := prb.Data.Delete(ctx, input.DatumID); err != nil { - r.logger.ErrorCtx(ctx, "cannot delete datum", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.DeleteDatumPayload{ - DeletedDatumID: input.DatumID, - }, nil -} - -// CreateAudit is the resolver for the createAudit field. -func (r *mutationResolver) CreateAudit(ctx context.Context, input types.CreateAuditInput) (*types.CreateAuditPayload, error) { - if err := r.authorize(ctx, input.OrganizationID, probo.ActionAuditCreate); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, input.OrganizationID.TenantID()) - - req := probo.CreateAuditRequest{ - OrganizationID: input.OrganizationID, - FrameworkID: input.FrameworkID, - Name: input.Name, - ValidFrom: input.ValidFrom, - ValidUntil: input.ValidUntil, - State: input.State, - TrustCenterVisibility: input.TrustCenterVisibility, - } - - audit, err := prb.Audits.Create(ctx, &req) - if err != nil { - if validationErrors, ok := errors.AsType[validator.ValidationErrors](err); ok { - return nil, gqlutils.InvalidValidationErrors(ctx, validationErrors) - } - r.logger.ErrorCtx(ctx, "cannot create audit", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - if input.File != nil { - uploadReq := probo.UploadAuditReportRequest{ - AuditID: audit.ID, - File: probo.File{ - Content: input.File.File, - Filename: input.File.Filename, - Size: input.File.Size, - ContentType: input.File.ContentType, - }, - } - - audit, err = prb.Audits.UploadReport(ctx, uploadReq) - if err != nil { - if validationErrors, ok := errors.AsType[validator.ValidationErrors](err); ok { - return nil, gqlutils.InvalidValidationErrors(ctx, validationErrors) - } - r.logger.ErrorCtx(ctx, "cannot upload audit report", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - } - - return &types.CreateAuditPayload{ - AuditEdge: types.NewAuditEdge(audit, coredata.AuditOrderFieldCreatedAt), - }, nil -} - -// UpdateAudit is the resolver for the updateAudit field. -func (r *mutationResolver) UpdateAudit(ctx context.Context, input types.UpdateAuditInput) (*types.UpdateAuditPayload, error) { - if err := r.authorize(ctx, input.ID, probo.ActionAuditUpdate); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, input.ID.TenantID()) - - req := probo.UpdateAuditRequest{ - ID: input.ID, - Name: gqlutils.UnwrapOmittable(input.Name), - ValidFrom: input.ValidFrom, - ValidUntil: input.ValidUntil, - State: input.State, - TrustCenterVisibility: input.TrustCenterVisibility, - } - - audit, err := prb.Audits.Update(ctx, &req) - if err != nil { - if validationErrors, ok := errors.AsType[validator.ValidationErrors](err); ok { - return nil, gqlutils.InvalidValidationErrors(ctx, validationErrors) - } - r.logger.ErrorCtx(ctx, "cannot update audit", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.UpdateAuditPayload{ - Audit: types.NewAudit(audit), - }, nil -} - -// DeleteAudit is the resolver for the deleteAudit field. -func (r *mutationResolver) DeleteAudit(ctx context.Context, input types.DeleteAuditInput) (*types.DeleteAuditPayload, error) { - if err := r.authorize(ctx, input.AuditID, probo.ActionAuditDelete); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, input.AuditID.TenantID()) - - err := prb.Audits.Delete(ctx, input.AuditID) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot delete audit", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.DeleteAuditPayload{ - DeletedAuditID: &input.AuditID, - }, nil -} - -// UploadAuditReport is the resolver for the uploadAuditReport field. -func (r *mutationResolver) UploadAuditReport(ctx context.Context, input types.UploadAuditReportInput) (*types.UploadAuditReportPayload, error) { - if err := r.authorize(ctx, input.AuditID, probo.ActionAuditReportUpload); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, input.AuditID.TenantID()) - - req := probo.UploadAuditReportRequest{ - AuditID: input.AuditID, - File: probo.File{ - Content: input.File.File, - Filename: input.File.Filename, - Size: input.File.Size, - ContentType: input.File.ContentType, - }, - } - - audit, err := prb.Audits.UploadReport(ctx, req) - if err != nil { - if validationErrors, ok := errors.AsType[validator.ValidationErrors](err); ok { - return nil, gqlutils.InvalidValidationErrors(ctx, validationErrors) - } - r.logger.ErrorCtx(ctx, "cannot upload audit report", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.UploadAuditReportPayload{ - Audit: types.NewAudit(audit), - }, nil -} - -// DeleteAuditReport is the resolver for the deleteAuditReport field. -func (r *mutationResolver) DeleteAuditReport(ctx context.Context, input types.DeleteAuditReportInput) (*types.DeleteAuditReportPayload, error) { - if err := r.authorize(ctx, input.AuditID, probo.ActionAuditReportDelete); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, input.AuditID.TenantID()) - - audit, err := prb.Audits.DeleteReport(ctx, input.AuditID) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot delete audit report", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.DeleteAuditReportPayload{ - Audit: types.NewAudit(audit), - }, nil -} - -// CreateFinding is the resolver for the createFinding field. -func (r *mutationResolver) CreateFinding(ctx context.Context, input types.CreateFindingInput) (*types.CreateFindingPayload, error) { - if err := r.authorize(ctx, input.OrganizationID, probo.ActionFindingCreate); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, input.OrganizationID.TenantID()) - - req := probo.CreateFindingRequest{ - OrganizationID: input.OrganizationID, - Kind: input.Kind, - Description: input.Description, - Source: input.Source, - IdentifiedOn: input.IdentifiedOn, - RootCause: input.RootCause, - CorrectiveAction: input.CorrectiveAction, - OwnerID: input.OwnerID, - DueDate: input.DueDate, - Status: &input.Status, - Priority: &input.Priority, - RiskID: input.RiskID, - EffectivenessCheck: input.EffectivenessCheck, - } - - finding, err := prb.Findings.Create(ctx, &req) - if err != nil { - if validationErrors, ok := errors.AsType[validator.ValidationErrors](err); ok { - return nil, gqlutils.InvalidValidationErrors(ctx, validationErrors) - } - r.logger.ErrorCtx(ctx, "cannot create finding", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.CreateFindingPayload{ - FindingEdge: types.NewFindingEdge(finding, coredata.FindingOrderFieldCreatedAt), - }, nil -} - -// UpdateFinding is the resolver for the updateFinding field. -func (r *mutationResolver) UpdateFinding(ctx context.Context, input types.UpdateFindingInput) (*types.UpdateFindingPayload, error) { - if err := r.authorize(ctx, input.ID, probo.ActionFindingUpdate); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, input.ID.TenantID()) - - req := probo.UpdateFindingRequest{ - ID: input.ID, - Description: gqlutils.UnwrapOmittable(input.Description), - Source: gqlutils.UnwrapOmittable(input.Source), - IdentifiedOn: gqlutils.UnwrapOmittable(input.IdentifiedOn), - RootCause: gqlutils.UnwrapOmittable(input.RootCause), - CorrectiveAction: gqlutils.UnwrapOmittable(input.CorrectiveAction), - OwnerID: input.OwnerID, - DueDate: gqlutils.UnwrapOmittable(input.DueDate), - Status: input.Status, - Priority: input.Priority, - RiskID: gqlutils.UnwrapOmittable(input.RiskID), - EffectivenessCheck: gqlutils.UnwrapOmittable(input.EffectivenessCheck), - } - - finding, err := prb.Findings.Update(ctx, &req) - if err != nil { - if validationErrors, ok := errors.AsType[validator.ValidationErrors](err); ok { - return nil, gqlutils.InvalidValidationErrors(ctx, validationErrors) - } - r.logger.ErrorCtx(ctx, "cannot update finding", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.UpdateFindingPayload{ - Finding: types.NewFinding(finding), - }, nil -} - -// DeleteFinding is the resolver for the deleteFinding field. -func (r *mutationResolver) DeleteFinding(ctx context.Context, input types.DeleteFindingInput) (*types.DeleteFindingPayload, error) { - if err := r.authorize(ctx, input.FindingID, probo.ActionFindingDelete); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, input.FindingID.TenantID()) - - err := prb.Findings.Delete(ctx, input.FindingID) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot delete finding", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.DeleteFindingPayload{ - DeletedFindingID: &input.FindingID, - }, nil -} - -// CreateFindingAuditMapping is the resolver for the createFindingAuditMapping field. -func (r *mutationResolver) CreateFindingAuditMapping(ctx context.Context, input types.CreateFindingAuditMappingInput) (*types.CreateFindingAuditMappingPayload, error) { - if err := r.authorize(ctx, input.FindingID, probo.ActionFindingAuditMappingCreate); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, input.FindingID.TenantID()) - - finding, audit, err := prb.Findings.CreateAuditMapping(ctx, input.FindingID, input.AuditID, input.ReferenceID) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot create finding audit mapping", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.CreateFindingAuditMappingPayload{ - FindingEdge: types.NewFindingEdge(finding, coredata.FindingOrderFieldCreatedAt), - AuditEdge: types.NewAuditEdge(audit, coredata.AuditOrderFieldCreatedAt), - }, nil -} - -// DeleteFindingAuditMapping is the resolver for the deleteFindingAuditMapping field. -func (r *mutationResolver) DeleteFindingAuditMapping(ctx context.Context, input types.DeleteFindingAuditMappingInput) (*types.DeleteFindingAuditMappingPayload, error) { - if err := r.authorize(ctx, input.FindingID, probo.ActionFindingAuditMappingDelete); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, input.FindingID.TenantID()) - - finding, audit, err := prb.Findings.DeleteAuditMapping(ctx, input.FindingID, input.AuditID) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot delete finding audit mapping", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.DeleteFindingAuditMappingPayload{ - DeletedFindingID: &finding.ID, - DeletedAuditID: &audit.ID, - }, nil -} - -// CreateObligation is the resolver for the createObligation field. -func (r *mutationResolver) CreateObligation(ctx context.Context, input types.CreateObligationInput) (*types.CreateObligationPayload, error) { - if err := r.authorize(ctx, input.OrganizationID, probo.ActionObligationCreate); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, input.OrganizationID.TenantID()) - - req := probo.CreateObligationRequest{ - OrganizationID: input.OrganizationID, - Area: input.Area, - Source: input.Source, - Requirement: input.Requirement, - ActionsToBeImplemented: input.ActionsToBeImplemented, - Regulator: input.Regulator, - OwnerID: input.OwnerID, - LastReviewDate: input.LastReviewDate, - DueDate: input.DueDate, - Status: input.Status, - Type: input.Type, - } - - obligation, err := prb.Obligations.Create(ctx, &req) - if err != nil { - if validationErrors, ok := errors.AsType[validator.ValidationErrors](err); ok { - return nil, gqlutils.InvalidValidationErrors(ctx, validationErrors) - } - r.logger.ErrorCtx(ctx, "cannot create obligation", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.CreateObligationPayload{ - ObligationEdge: types.NewObligationEdge(obligation, coredata.ObligationOrderFieldCreatedAt), - }, nil -} - -// UpdateObligation is the resolver for the updateObligation field. -func (r *mutationResolver) UpdateObligation(ctx context.Context, input types.UpdateObligationInput) (*types.UpdateObligationPayload, error) { - if err := r.authorize(ctx, input.ID, probo.ActionObligationUpdate); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, input.ID.TenantID()) - - req := probo.UpdateObligationRequest{ - ID: input.ID, - Area: gqlutils.UnwrapOmittable(input.Area), - Source: gqlutils.UnwrapOmittable(input.Source), - Requirement: gqlutils.UnwrapOmittable(input.Requirement), - ActionsToBeImplemented: gqlutils.UnwrapOmittable(input.ActionsToBeImplemented), - Regulator: gqlutils.UnwrapOmittable(input.Regulator), - OwnerID: input.OwnerID, - LastReviewDate: gqlutils.UnwrapOmittable(input.LastReviewDate), - DueDate: gqlutils.UnwrapOmittable(input.DueDate), - Status: input.Status, - Type: input.Type, - } - - obligation, err := prb.Obligations.Update(ctx, &req) - if err != nil { - if validationErrors, ok := errors.AsType[validator.ValidationErrors](err); ok { - return nil, gqlutils.InvalidValidationErrors(ctx, validationErrors) - } - r.logger.ErrorCtx(ctx, "cannot update obligation", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.UpdateObligationPayload{ - Obligation: types.NewObligation(obligation), - }, nil -} - -// DeleteObligation is the resolver for the deleteObligation field. -func (r *mutationResolver) DeleteObligation(ctx context.Context, input types.DeleteObligationInput) (*types.DeleteObligationPayload, error) { - if err := r.authorize(ctx, input.ObligationID, probo.ActionObligationDelete); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, input.ObligationID.TenantID()) - - err := prb.Obligations.Delete(ctx, input.ObligationID) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot delete obligation", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.DeleteObligationPayload{ - DeletedObligationID: input.ObligationID, - }, nil -} - -// CreateRightsRequest is the resolver for the createRightsRequest field. -func (r *mutationResolver) CreateRightsRequest(ctx context.Context, input types.CreateRightsRequestInput) (*types.CreateRightsRequestPayload, error) { - if err := r.authorize(ctx, input.OrganizationID, probo.ActionRightsRequestCreate); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, input.OrganizationID.TenantID()) - - req := probo.CreateRightsRequestRequest{ - OrganizationID: input.OrganizationID, - RequestType: &input.RequestType, - RequestState: &input.RequestState, - DataSubject: input.DataSubject, - Contact: input.Contact, - Details: input.Details, - Deadline: input.Deadline, - ActionTaken: input.ActionTaken, - } - - rightsRequest, err := prb.RightsRequests.Create(ctx, &req) - if err != nil { - if validationErrors, ok := errors.AsType[validator.ValidationErrors](err); ok { - return nil, gqlutils.InvalidValidationErrors(ctx, validationErrors) - } - r.logger.ErrorCtx(ctx, "cannot create rights request", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.CreateRightsRequestPayload{ - RightsRequestEdge: types.NewRightsRequestEdge(rightsRequest, coredata.RightsRequestOrderFieldCreatedAt), - }, nil -} - -// UpdateRightsRequest is the resolver for the updateRightsRequest field. -func (r *mutationResolver) UpdateRightsRequest(ctx context.Context, input types.UpdateRightsRequestInput) (*types.UpdateRightsRequestPayload, error) { - if err := r.authorize(ctx, input.ID, probo.ActionRightsRequestUpdate); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, input.ID.TenantID()) - - req := probo.UpdateRightsRequestRequest{ - ID: input.ID, - RequestType: input.RequestType, - RequestState: input.RequestState, - DataSubject: gqlutils.UnwrapOmittable(input.DataSubject), - Contact: gqlutils.UnwrapOmittable(input.Contact), - Details: gqlutils.UnwrapOmittable(input.Details), - Deadline: gqlutils.UnwrapOmittable(input.Deadline), - ActionTaken: gqlutils.UnwrapOmittable(input.ActionTaken), - } - - rightsRequest, err := prb.RightsRequests.Update(ctx, &req) - if err != nil { - if validationErrors, ok := errors.AsType[validator.ValidationErrors](err); ok { - return nil, gqlutils.InvalidValidationErrors(ctx, validationErrors) - } - r.logger.ErrorCtx(ctx, "cannot update rights request", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.UpdateRightsRequestPayload{ - RightsRequest: types.NewRightsRequest(rightsRequest), - }, nil -} - -// DeleteRightsRequest is the resolver for the deleteRightsRequest field. -func (r *mutationResolver) DeleteRightsRequest(ctx context.Context, input types.DeleteRightsRequestInput) (*types.DeleteRightsRequestPayload, error) { - if err := r.authorize(ctx, input.RightsRequestID, probo.ActionRightsRequestDelete); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, input.RightsRequestID.TenantID()) - - err := prb.RightsRequests.Delete(ctx, input.RightsRequestID) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot delete rights request", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.DeleteRightsRequestPayload{ - DeletedRightsRequestID: input.RightsRequestID, - }, nil -} - -// CreateProcessingActivity is the resolver for the createProcessingActivity field. -func (r *mutationResolver) CreateProcessingActivity(ctx context.Context, input types.CreateProcessingActivityInput) (*types.CreateProcessingActivityPayload, error) { - if err := r.authorize(ctx, input.OrganizationID, probo.ActionProcessingActivityCreate); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, input.OrganizationID.TenantID()) - - req := probo.CreateProcessingActivityRequest{ - OrganizationID: input.OrganizationID, - Name: input.Name, - Purpose: input.Purpose, - DataSubjectCategory: input.DataSubjectCategory, - PersonalDataCategory: input.PersonalDataCategory, - SpecialOrCriminalData: input.SpecialOrCriminalData, - LawfulBasis: input.LawfulBasis, - Recipients: input.Recipients, - Location: input.Location, - InternationalTransfers: input.InternationalTransfers, - TransferSafeguard: input.TransferSafeguards, - RetentionPeriod: input.RetentionPeriod, - SecurityMeasures: input.SecurityMeasures, - DataProtectionImpactAssessmentNeeded: input.DataProtectionImpactAssessmentNeeded, - TransferImpactAssessmentNeeded: input.TransferImpactAssessmentNeeded, - LastReviewDate: input.LastReviewDate, - NextReviewDate: input.NextReviewDate, - Role: input.Role, - DataProtectionOfficerID: input.DataProtectionOfficerID, - VendorIDs: input.VendorIds, - } - - activity, err := prb.ProcessingActivities.Create(ctx, &req) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot create processing activity", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.CreateProcessingActivityPayload{ - ProcessingActivityEdge: types.NewProcessingActivityEdge(activity, coredata.ProcessingActivityOrderFieldCreatedAt), - }, nil -} - -// UpdateProcessingActivity is the resolver for the updateProcessingActivity field. -func (r *mutationResolver) UpdateProcessingActivity(ctx context.Context, input types.UpdateProcessingActivityInput) (*types.UpdateProcessingActivityPayload, error) { - if err := r.authorize(ctx, input.ID, probo.ActionProcessingActivityUpdate); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, input.ID.TenantID()) - - req := probo.UpdateProcessingActivityRequest{ - ID: input.ID, - Name: input.Name, - Purpose: gqlutils.UnwrapOmittable(input.Purpose), - DataSubjectCategory: gqlutils.UnwrapOmittable(input.DataSubjectCategory), - PersonalDataCategory: gqlutils.UnwrapOmittable(input.PersonalDataCategory), - SpecialOrCriminalData: input.SpecialOrCriminalData, - LawfulBasis: input.LawfulBasis, - Recipients: gqlutils.UnwrapOmittable(input.Recipients), - Location: gqlutils.UnwrapOmittable(input.Location), - InternationalTransfers: input.InternationalTransfers, - TransferSafeguard: gqlutils.UnwrapOmittable(input.TransferSafeguards), - RetentionPeriod: gqlutils.UnwrapOmittable(input.RetentionPeriod), - SecurityMeasures: gqlutils.UnwrapOmittable(input.SecurityMeasures), - DataProtectionImpactAssessmentNeeded: input.DataProtectionImpactAssessmentNeeded, - TransferImpactAssessmentNeeded: input.TransferImpactAssessmentNeeded, - LastReviewDate: gqlutils.UnwrapOmittable(input.LastReviewDate), - NextReviewDate: gqlutils.UnwrapOmittable(input.NextReviewDate), - Role: input.Role, - DataProtectionOfficerID: gqlutils.UnwrapOmittable(input.DataProtectionOfficerID), - VendorIDs: &input.VendorIds, - } - - activity, err := prb.ProcessingActivities.Update(ctx, &req) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot update processing activity", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.UpdateProcessingActivityPayload{ - ProcessingActivity: types.NewProcessingActivity(activity), - }, nil -} - -// DeleteProcessingActivity is the resolver for the deleteProcessingActivity field. -func (r *mutationResolver) DeleteProcessingActivity(ctx context.Context, input types.DeleteProcessingActivityInput) (*types.DeleteProcessingActivityPayload, error) { - if err := r.authorize(ctx, input.ProcessingActivityID, probo.ActionProcessingActivityDelete); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, input.ProcessingActivityID.TenantID()) - - err := prb.ProcessingActivities.Delete(ctx, input.ProcessingActivityID) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot delete processing activity", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.DeleteProcessingActivityPayload{ - DeletedProcessingActivityID: input.ProcessingActivityID, - }, nil -} - -// CreateDataProtectionImpactAssessment is the resolver for the createDataProtectionImpactAssessment field. -func (r *mutationResolver) CreateDataProtectionImpactAssessment(ctx context.Context, input types.CreateDataProtectionImpactAssessmentInput) (*types.CreateDataProtectionImpactAssessmentPayload, error) { - if err := r.authorize(ctx, input.ProcessingActivityID, probo.ActionDataProtectionImpactAssessmentCreate); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, input.ProcessingActivityID.TenantID()) - - req := probo.CreateDataProtectionImpactAssessmentRequest{ - ProcessingActivityID: input.ProcessingActivityID, - Description: input.Description, - NecessityAndProportionality: input.NecessityAndProportionality, - PotentialRisk: input.PotentialRisk, - Mitigations: input.Mitigations, - ResidualRisk: input.ResidualRisk, - } - - dpia, err := prb.DataProtectionImpactAssessments.Create(ctx, &req) - if err != nil { - if errors.Is(err, coredata.ErrResourceAlreadyExists) { - return nil, gqlutils.Conflict(ctx, err) - } - - if validationErrors, ok := errors.AsType[validator.ValidationErrors](err); ok { - return nil, gqlutils.InvalidValidationErrors(ctx, validationErrors) - } - r.logger.ErrorCtx(ctx, "cannot create data protection impact assessment", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.CreateDataProtectionImpactAssessmentPayload{ - DataProtectionImpactAssessment: types.NewDataProtectionImpactAssessment(dpia), - }, nil -} - -// UpdateDataProtectionImpactAssessment is the resolver for the updateDataProtectionImpactAssessment field. -func (r *mutationResolver) UpdateDataProtectionImpactAssessment(ctx context.Context, input types.UpdateDataProtectionImpactAssessmentInput) (*types.UpdateDataProtectionImpactAssessmentPayload, error) { - if err := r.authorize(ctx, input.ID, probo.ActionDataProtectionImpactAssessmentUpdate); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, input.ID.TenantID()) - - req := probo.UpdateDataProtectionImpactAssessmentRequest{ - ID: input.ID, - Description: gqlutils.UnwrapOmittable(input.Description), - NecessityAndProportionality: gqlutils.UnwrapOmittable(input.NecessityAndProportionality), - PotentialRisk: gqlutils.UnwrapOmittable(input.PotentialRisk), - Mitigations: gqlutils.UnwrapOmittable(input.Mitigations), - ResidualRisk: input.ResidualRisk, - } - - dpia, err := prb.DataProtectionImpactAssessments.Update(ctx, &req) - if err != nil { - if validationErrors, ok := errors.AsType[validator.ValidationErrors](err); ok { - return nil, gqlutils.InvalidValidationErrors(ctx, validationErrors) - } - r.logger.ErrorCtx(ctx, "cannot update data protection impact assessment", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.UpdateDataProtectionImpactAssessmentPayload{ - DataProtectionImpactAssessment: types.NewDataProtectionImpactAssessment(dpia), - }, nil -} - -// DeleteDataProtectionImpactAssessment is the resolver for the deleteDataProtectionImpactAssessment field. -func (r *mutationResolver) DeleteDataProtectionImpactAssessment(ctx context.Context, input types.DeleteDataProtectionImpactAssessmentInput) (*types.DeleteDataProtectionImpactAssessmentPayload, error) { - if err := r.authorize(ctx, input.DataProtectionImpactAssessmentID, probo.ActionDataProtectionImpactAssessmentDelete); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, input.DataProtectionImpactAssessmentID.TenantID()) - - err := prb.DataProtectionImpactAssessments.Delete(ctx, input.DataProtectionImpactAssessmentID) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot delete data protection impact assessment", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.DeleteDataProtectionImpactAssessmentPayload{ - DeletedDataProtectionImpactAssessmentID: input.DataProtectionImpactAssessmentID, - }, nil -} - -// CreateTransferImpactAssessment is the resolver for the createTransferImpactAssessment field. -func (r *mutationResolver) CreateTransferImpactAssessment(ctx context.Context, input types.CreateTransferImpactAssessmentInput) (*types.CreateTransferImpactAssessmentPayload, error) { - if err := r.authorize(ctx, input.ProcessingActivityID, probo.ActionTransferImpactAssessmentCreate); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, input.ProcessingActivityID.TenantID()) - - req := probo.CreateTransferImpactAssessmentRequest{ - ProcessingActivityID: input.ProcessingActivityID, - DataSubjects: input.DataSubjects, - LegalMechanism: input.LegalMechanism, - Transfer: input.Transfer, - LocalLawRisk: input.LocalLawRisk, - SupplementaryMeasures: input.SupplementaryMeasures, - } - - tia, err := prb.TransferImpactAssessments.Create(ctx, &req) - if err != nil { - if errors.Is(err, coredata.ErrResourceAlreadyExists) { - return nil, gqlutils.Conflict(ctx, err) - } - - if validationErrors, ok := errors.AsType[validator.ValidationErrors](err); ok { - return nil, gqlutils.InvalidValidationErrors(ctx, validationErrors) - } - r.logger.ErrorCtx(ctx, "cannot create transfer impact assessment", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.CreateTransferImpactAssessmentPayload{ - TransferImpactAssessment: types.NewTransferImpactAssessment(tia), - }, nil -} - -// UpdateTransferImpactAssessment is the resolver for the updateTransferImpactAssessment field. -func (r *mutationResolver) UpdateTransferImpactAssessment(ctx context.Context, input types.UpdateTransferImpactAssessmentInput) (*types.UpdateTransferImpactAssessmentPayload, error) { - if err := r.authorize(ctx, input.ID, probo.ActionTransferImpactAssessmentUpdate); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, input.ID.TenantID()) - - req := probo.UpdateTransferImpactAssessmentRequest{ - ID: input.ID, - DataSubjects: gqlutils.UnwrapOmittable(input.DataSubjects), - LegalMechanism: gqlutils.UnwrapOmittable(input.LegalMechanism), - Transfer: gqlutils.UnwrapOmittable(input.Transfer), - LocalLawRisk: gqlutils.UnwrapOmittable(input.LocalLawRisk), - SupplementaryMeasures: gqlutils.UnwrapOmittable(input.SupplementaryMeasures), - } - - tia, err := prb.TransferImpactAssessments.Update(ctx, &req) - if err != nil { - if validationErrors, ok := errors.AsType[validator.ValidationErrors](err); ok { - return nil, gqlutils.InvalidValidationErrors(ctx, validationErrors) - } - r.logger.ErrorCtx(ctx, "cannot update transfer impact assessment", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.UpdateTransferImpactAssessmentPayload{ - TransferImpactAssessment: types.NewTransferImpactAssessment(tia), - }, nil -} - -// DeleteTransferImpactAssessment is the resolver for the deleteTransferImpactAssessment field. -func (r *mutationResolver) DeleteTransferImpactAssessment(ctx context.Context, input types.DeleteTransferImpactAssessmentInput) (*types.DeleteTransferImpactAssessmentPayload, error) { - if err := r.authorize(ctx, input.TransferImpactAssessmentID, probo.ActionTransferImpactAssessmentDelete); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, input.TransferImpactAssessmentID.TenantID()) - - err := prb.TransferImpactAssessments.Delete(ctx, input.TransferImpactAssessmentID) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot delete transfer impact assessment", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.DeleteTransferImpactAssessmentPayload{ - DeletedTransferImpactAssessmentID: input.TransferImpactAssessmentID, - }, nil -} - -// CreateSnapshot is the resolver for the createSnapshot field. -func (r *mutationResolver) CreateSnapshot(ctx context.Context, input types.CreateSnapshotInput) (*types.CreateSnapshotPayload, error) { - if err := r.authorize(ctx, input.OrganizationID, probo.ActionSnapshotCreate); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, input.OrganizationID.TenantID()) - - snapshot, err := prb.Snapshots.Create( - ctx, - &probo.CreateSnapshotRequest{ - OrganizationID: input.OrganizationID, - Name: input.Name, - Description: input.Description, - Type: input.Type, - }, - ) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot create snapshot", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.CreateSnapshotPayload{ - SnapshotEdge: types.NewSnapshotEdge(snapshot, coredata.SnapshotOrderFieldCreatedAt), - }, nil -} - -// DeleteSnapshot is the resolver for the deleteSnapshot field. -func (r *mutationResolver) DeleteSnapshot(ctx context.Context, input types.DeleteSnapshotInput) (*types.DeleteSnapshotPayload, error) { - if err := r.authorize(ctx, input.SnapshotID, probo.ActionSnapshotDelete); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, input.SnapshotID.TenantID()) - - err := prb.Snapshots.Delete(ctx, input.SnapshotID) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot delete snapshot", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.DeleteSnapshotPayload{ - DeletedSnapshotID: input.SnapshotID, - }, nil -} - -// CreateCustomDomain is the resolver for the createCustomDomain field. -func (r *mutationResolver) CreateCustomDomain(ctx context.Context, input types.CreateCustomDomainInput) (*types.CreateCustomDomainPayload, error) { - if err := r.authorize(ctx, input.OrganizationID, probo.ActionCustomDomainCreate); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, input.OrganizationID.TenantID()) - - domain, err := prb.CustomDomains.CreateCustomDomain( - ctx, - probo.CreateCustomDomainRequest{ - OrganizationID: input.OrganizationID, - Domain: input.Domain, - }, - ) - if err != nil { - if validationErrors, ok := errors.AsType[validator.ValidationErrors](err); ok { - return nil, gqlutils.InvalidValidationErrors(ctx, validationErrors) - } - r.logger.ErrorCtx(ctx, "cannot create custom domain", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.CreateCustomDomainPayload{ - CustomDomain: types.NewCustomDomain(domain, r.customDomainCname), - }, nil -} - -// DeleteCustomDomain is the resolver for the deleteCustomDomain field. -func (r *mutationResolver) DeleteCustomDomain(ctx context.Context, input types.DeleteCustomDomainInput) (*types.DeleteCustomDomainPayload, error) { - if err := r.authorize(ctx, input.OrganizationID, probo.ActionCustomDomainDelete); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, input.OrganizationID.TenantID()) - - // TODO Drop this wierd logic - // Get the current custom domain ID before deleting - domain, err := prb.CustomDomains.GetOrganizationCustomDomain(ctx, input.OrganizationID) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot get custom domain", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - if domain == nil { - return nil, fmt.Errorf("organization has no custom domain") - } - - deletedDomainID := domain.ID - - if err := prb.CustomDomains.DeleteCustomDomain(ctx, input.OrganizationID); err != nil { - r.logger.ErrorCtx(ctx, "cannot delete custom domain", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.DeleteCustomDomainPayload{ - DeletedCustomDomainID: deletedDomainID, - }, nil -} - -// CreateAccessSource is the resolver for the createAccessSource field. -func (r *mutationResolver) CreateAccessSource(ctx context.Context, input types.CreateAccessSourceInput) (*types.CreateAccessSourcePayload, error) { - if err := r.authorize(ctx, input.OrganizationID, probo.ActionAccessSourceCreate); err != nil { - return nil, err - } - - scope := coredata.NewScopeFromObjectID(input.OrganizationID) - - source, err := r.accessReview.Sources(scope).Create(ctx, accessreview.CreateAccessSourceRequest{ - OrganizationID: input.OrganizationID, - ConnectorID: input.ConnectorID, - Name: input.Name, - Category: coredata.AccessSourceCategorySaaS, - CsvData: input.CSVData, - }) - if err != nil { - panic(fmt.Errorf("cannot create access source: %w", err)) - } - - return &types.CreateAccessSourcePayload{ - AccessSourceEdge: types.NewAccessSourceEdge(source, coredata.AccessSourceOrderFieldCreatedAt), - }, nil -} - -// UpdateAccessSource is the resolver for the updateAccessSource field. -func (r *mutationResolver) UpdateAccessSource(ctx context.Context, input types.UpdateAccessSourceInput) (*types.UpdateAccessSourcePayload, error) { - if err := r.authorize(ctx, input.AccessSourceID, probo.ActionAccessSourceUpdate); err != nil { - return nil, err - } - - scope := coredata.NewScopeFromObjectID(input.AccessSourceID) - - req := accessreview.UpdateAccessSourceRequest{ - AccessSourceID: input.AccessSourceID, - } - if input.Name.IsSet() { - req.Name = input.Name.Value() - } - if input.ConnectorID.IsSet() { - req.ConnectorID = gqlutils.UnwrapOmittable(input.ConnectorID) - } - if input.CSVData.IsSet() { - req.CsvData = gqlutils.UnwrapOmittable(input.CSVData) - } - - source, err := r.accessReview.Sources(scope).Update(ctx, req) - if err != nil { - if errors.Is(err, coredata.ErrResourceNotFound) { - return nil, gqlutils.NotFound(ctx, err) - } - panic(fmt.Errorf("cannot update access source: %w", err)) - } - - return &types.UpdateAccessSourcePayload{ - AccessSource: types.NewAccessSource(source), - }, nil -} - -// DeleteAccessSource is the resolver for the deleteAccessSource field. -func (r *mutationResolver) DeleteAccessSource(ctx context.Context, input types.DeleteAccessSourceInput) (*types.DeleteAccessSourcePayload, error) { - if err := r.authorize(ctx, input.AccessSourceID, probo.ActionAccessSourceDelete); err != nil { - return nil, err - } - - scope := coredata.NewScopeFromObjectID(input.AccessSourceID) - - if err := r.accessReview.Sources(scope).Delete(ctx, input.AccessSourceID); err != nil { - if errors.Is(err, coredata.ErrResourceNotFound) { - return nil, gqlutils.NotFound(ctx, err) - } - panic(fmt.Errorf("cannot delete access source: %w", err)) - } - - return &types.DeleteAccessSourcePayload{ - DeletedAccessSourceID: input.AccessSourceID, - }, nil -} - -// CreateAccessReviewCampaign is the resolver for the createAccessReviewCampaign field. -func (r *mutationResolver) CreateAccessReviewCampaign(ctx context.Context, input types.CreateAccessReviewCampaignInput) (*types.CreateAccessReviewCampaignPayload, error) { - if err := r.authorize(ctx, input.OrganizationID, probo.ActionAccessReviewCampaignCreate); err != nil { - return nil, err - } - - scope := coredata.NewScopeFromObjectID(input.OrganizationID) - - var description string - if input.Description != nil { - description = *input.Description - } - - campaign, err := r.accessReview.Campaigns(scope).Create(ctx, accessreview.CreateAccessReviewCampaignRequest{ - OrganizationID: input.OrganizationID, - Name: input.Name, - Description: description, - FrameworkControls: input.FrameworkControls, - AccessSourceIDs: input.AccessSourceIds, - }) - if err != nil { - panic(fmt.Errorf("cannot create access review campaign: %w", err)) - } - - return &types.CreateAccessReviewCampaignPayload{ - AccessReviewCampaignEdge: types.NewAccessReviewCampaignEdge(campaign, coredata.AccessReviewCampaignOrderFieldCreatedAt), - }, nil -} - -// UpdateAccessReviewCampaign is the resolver for the updateAccessReviewCampaign field. -func (r *mutationResolver) UpdateAccessReviewCampaign(ctx context.Context, input types.UpdateAccessReviewCampaignInput) (*types.UpdateAccessReviewCampaignPayload, error) { - if err := r.authorize(ctx, input.AccessReviewCampaignID, probo.ActionAccessReviewCampaignUpdate); err != nil { - return nil, err - } - - scope := coredata.NewScopeFromObjectID(input.AccessReviewCampaignID) - - req := accessreview.UpdateAccessReviewCampaignRequest{ - CampaignID: input.AccessReviewCampaignID, - } - if input.Name.IsSet() { - req.Name = input.Name.Value() - } - if input.Description.IsSet() { - req.Description = input.Description.Value() - } - if input.FrameworkControls.IsSet() { - controls := input.FrameworkControls.Value() - req.FrameworkControls = &controls - } - - campaign, err := r.accessReview.Campaigns(scope).Update(ctx, req) - if err != nil { - if errors.Is(err, coredata.ErrResourceNotFound) { - return nil, gqlutils.NotFound(ctx, err) - } - panic(fmt.Errorf("cannot update access review campaign: %w", err)) - } - - return &types.UpdateAccessReviewCampaignPayload{ - AccessReviewCampaign: types.NewAccessReviewCampaign(campaign), - }, nil -} - -// DeleteAccessReviewCampaign is the resolver for the deleteAccessReviewCampaign field. -func (r *mutationResolver) DeleteAccessReviewCampaign(ctx context.Context, input types.DeleteAccessReviewCampaignInput) (*types.DeleteAccessReviewCampaignPayload, error) { - if err := r.authorize(ctx, input.AccessReviewCampaignID, probo.ActionAccessReviewCampaignDelete); err != nil { - return nil, err - } - - scope := coredata.NewScopeFromObjectID(input.AccessReviewCampaignID) - - if err := r.accessReview.Campaigns(scope).Delete(ctx, input.AccessReviewCampaignID); err != nil { - if errors.Is(err, coredata.ErrResourceNotFound) { - return nil, gqlutils.NotFound(ctx, err) - } - panic(fmt.Errorf("cannot delete access review campaign: %w", err)) - } - - return &types.DeleteAccessReviewCampaignPayload{ - DeletedAccessReviewCampaignID: input.AccessReviewCampaignID, - }, nil -} - -// StartAccessReviewCampaign is the resolver for the startAccessReviewCampaign field. -func (r *mutationResolver) StartAccessReviewCampaign(ctx context.Context, input types.StartAccessReviewCampaignInput) (*types.StartAccessReviewCampaignPayload, error) { - if err := r.authorize(ctx, input.AccessReviewCampaignID, probo.ActionAccessReviewCampaignStart); err != nil { - return nil, err - } - - scope := coredata.NewScopeFromObjectID(input.AccessReviewCampaignID) - - campaign, err := r.accessReview.Campaigns(scope).Start(ctx, input.AccessReviewCampaignID) - if err != nil { - panic(fmt.Errorf("cannot start access review campaign: %w", err)) - } - - return &types.StartAccessReviewCampaignPayload{ - AccessReviewCampaign: types.NewAccessReviewCampaign(campaign), - }, nil -} - -// CloseAccessReviewCampaign is the resolver for the closeAccessReviewCampaign field. -func (r *mutationResolver) CloseAccessReviewCampaign(ctx context.Context, input types.CloseAccessReviewCampaignInput) (*types.CloseAccessReviewCampaignPayload, error) { - if err := r.authorize(ctx, input.AccessReviewCampaignID, probo.ActionAccessReviewCampaignClose); err != nil { - return nil, err - } - - scope := coredata.NewScopeFromObjectID(input.AccessReviewCampaignID) - - campaign, err := r.accessReview.Campaigns(scope).Close(ctx, input.AccessReviewCampaignID) - if err != nil { - panic(fmt.Errorf("cannot close access review campaign: %w", err)) - } - - return &types.CloseAccessReviewCampaignPayload{ - AccessReviewCampaign: types.NewAccessReviewCampaign(campaign), - }, nil -} - -// CancelAccessReviewCampaign is the resolver for the cancelAccessReviewCampaign field. -func (r *mutationResolver) CancelAccessReviewCampaign(ctx context.Context, input types.CancelAccessReviewCampaignInput) (*types.CancelAccessReviewCampaignPayload, error) { - if err := r.authorize(ctx, input.AccessReviewCampaignID, probo.ActionAccessReviewCampaignCancel); err != nil { - return nil, err - } - - scope := coredata.NewScopeFromObjectID(input.AccessReviewCampaignID) - - campaign, err := r.accessReview.Campaigns(scope).Cancel(ctx, input.AccessReviewCampaignID) - if err != nil { - panic(fmt.Errorf("cannot cancel access review campaign: %w", err)) - } - - return &types.CancelAccessReviewCampaignPayload{ - AccessReviewCampaign: types.NewAccessReviewCampaign(campaign), - }, nil -} - -// AddAccessReviewCampaignScopeSource is the resolver for the addAccessReviewCampaignScopeSource field. -func (r *mutationResolver) AddAccessReviewCampaignScopeSource(ctx context.Context, input types.AddAccessReviewCampaignScopeSourceInput) (*types.AddAccessReviewCampaignScopeSourcePayload, error) { - if err := r.authorize(ctx, input.AccessReviewCampaignID, probo.ActionAccessReviewCampaignAddScopeSource); err != nil { - return nil, err - } - - scope := coredata.NewScopeFromObjectID(input.AccessReviewCampaignID) - - campaign, err := r.accessReview.Campaigns(scope).AddScopeSource(ctx, accessreview.AddCampaignScopeSourceRequest{ - CampaignID: input.AccessReviewCampaignID, - AccessSourceID: input.AccessSourceID, - }) - if err != nil { - panic(fmt.Errorf("cannot add scope source to access review campaign: %w", err)) - } - - return &types.AddAccessReviewCampaignScopeSourcePayload{ - AccessReviewCampaign: types.NewAccessReviewCampaign(campaign), - }, nil -} - -// RemoveAccessReviewCampaignScopeSource is the resolver for the removeAccessReviewCampaignScopeSource field. -func (r *mutationResolver) RemoveAccessReviewCampaignScopeSource(ctx context.Context, input types.RemoveAccessReviewCampaignScopeSourceInput) (*types.RemoveAccessReviewCampaignScopeSourcePayload, error) { - if err := r.authorize(ctx, input.AccessReviewCampaignID, probo.ActionAccessReviewCampaignRemoveScopeSource); err != nil { - return nil, err - } - - scope := coredata.NewScopeFromObjectID(input.AccessReviewCampaignID) - - campaign, err := r.accessReview.Campaigns(scope).RemoveScopeSource(ctx, accessreview.RemoveCampaignScopeSourceRequest{ - CampaignID: input.AccessReviewCampaignID, - AccessSourceID: input.AccessSourceID, - }) - if err != nil { - panic(fmt.Errorf("cannot remove scope source from access review campaign: %w", err)) - } - - return &types.RemoveAccessReviewCampaignScopeSourcePayload{ - AccessReviewCampaign: types.NewAccessReviewCampaign(campaign), - }, nil -} - -// RecordAccessEntryDecision is the resolver for the recordAccessEntryDecision field. -func (r *mutationResolver) RecordAccessEntryDecision(ctx context.Context, input types.RecordAccessEntryDecisionInput) (*types.RecordAccessEntryDecisionPayload, error) { - if err := r.authorize(ctx, input.AccessEntryID, probo.ActionAccessEntryDecide); err != nil { - return nil, err - } - - scope := coredata.NewScopeFromObjectID(input.AccessEntryID) - - // Resolve the profile ID from the session's identity. - // The profile may not exist for every identity, in which - // case decided_by will be left nil. - identity := authn.IdentityFromContext(ctx) - if identity == nil { - return nil, fmt.Errorf("no identity in context") - } - - req := accessreview.RecordAccessEntryDecisionRequest{ - EntryID: input.AccessEntryID, - Decision: input.Decision, - DecisionNote: input.DecisionNote, - } - - organizationID, err := r.accessReview.ResolveEntryOrganizationID(ctx, input.AccessEntryID) - if err == nil { - profile, err := r.iam.OrganizationService.GetProfileForIdentityAndOrganization(ctx, identity.ID, organizationID) - if err == nil { - req.DecidedByID = &profile.ID - } - } - - entry, err := r.accessReview.Entries(scope).RecordDecision(ctx, req) - if err != nil { - if errors.Is(err, coredata.ErrResourceNotFound) { - return nil, gqlutils.NotFound(ctx, err) - } - panic(fmt.Errorf("cannot record access entry decision: %w", err)) - } - - return &types.RecordAccessEntryDecisionPayload{ - AccessEntry: types.NewAccessEntry(entry), - }, nil -} - -// RecordAccessEntryDecisions is the resolver for the recordAccessEntryDecisions field. -func (r *mutationResolver) RecordAccessEntryDecisions(ctx context.Context, input types.RecordAccessEntryDecisionsInput) (*types.RecordAccessEntryDecisionsPayload, error) { - if len(input.Decisions) == 0 { - return &types.RecordAccessEntryDecisionsPayload{ - AccessEntries: []*types.AccessEntry{}, - }, nil - } - - const maxBatchSize = 100 - if len(input.Decisions) > maxBatchSize { - return nil, fmt.Errorf("cannot record decisions: batch size %d exceeds maximum of %d", len(input.Decisions), maxBatchSize) - } - - // Authorize each entry individually to prevent cross-org bypass. - for _, d := range input.Decisions { - if err := r.authorize(ctx, d.AccessEntryID, probo.ActionAccessEntryDecide); err != nil { - return nil, err - } - } - - identity := authn.IdentityFromContext(ctx) - if identity == nil { - return nil, fmt.Errorf("no identity in context") - } - - tenantID := input.Decisions[0].AccessEntryID.TenantID() - scope := coredata.NewScope(tenantID) - - // Cache profile lookups per organization so we resolve the correct - // decidedByID for each entry even when a batch spans multiple orgs. - profileCache := make(map[gid.GID]*gid.GID) - - decisions := make([]accessreview.RecordAccessEntryDecisionRequest, len(input.Decisions)) - for i, d := range input.Decisions { - var decidedByID *gid.GID - organizationID, err := r.accessReview.ResolveEntryOrganizationID(ctx, d.AccessEntryID) - if err == nil { - if cached, ok := profileCache[organizationID]; ok { - decidedByID = cached - } else { - profile, err := r.iam.OrganizationService.GetProfileForIdentityAndOrganization(ctx, identity.ID, organizationID) - if err == nil { - decidedByID = &profile.ID - } - profileCache[organizationID] = decidedByID - } - } - - decisions[i] = accessreview.RecordAccessEntryDecisionRequest{ - EntryID: d.AccessEntryID, - Decision: d.Decision, - DecisionNote: d.DecisionNote, - DecidedByID: decidedByID, - } - } - - entries, err := r.accessReview.Entries(scope).RecordDecisions(ctx, decisions) - if err != nil { - if errors.Is(err, coredata.ErrResourceNotFound) { - return nil, gqlutils.NotFound(ctx, err) - } - panic(fmt.Errorf("cannot record access entry decisions: %w", err)) - } - - accessEntries := make([]*types.AccessEntry, len(entries)) - for i, e := range entries { - accessEntries[i] = types.NewAccessEntry(e) - } - - return &types.RecordAccessEntryDecisionsPayload{ - AccessEntries: accessEntries, - }, nil -} - -// FlagAccessEntry is the resolver for the flagAccessEntry field. -func (r *mutationResolver) FlagAccessEntry(ctx context.Context, input types.FlagAccessEntryInput) (*types.FlagAccessEntryPayload, error) { - if err := r.authorize(ctx, input.AccessEntryID, probo.ActionAccessEntryFlag); err != nil { - return nil, err - } - - scope := coredata.NewScopeFromObjectID(input.AccessEntryID) - - entry, err := r.accessReview.Entries(scope).FlagEntry(ctx, accessreview.FlagAccessEntryRequest{ - EntryID: input.AccessEntryID, - Flags: input.Flags, - FlagReasons: input.FlagReasons, - }) - if err != nil { - if errors.Is(err, coredata.ErrResourceNotFound) { - return nil, gqlutils.NotFound(ctx, err) - } - panic(fmt.Errorf("cannot flag access entry: %w", err)) - } - - return &types.FlagAccessEntryPayload{ - AccessEntry: types.NewAccessEntry(entry), - }, nil -} - -// CreateAPIKeyConnector is the resolver for the createAPIKeyConnector field. -func (r *mutationResolver) CreateAPIKeyConnector(ctx context.Context, input types.CreateAPIKeyConnectorInput) (*types.CreateAPIKeyConnectorPayload, error) { - if err := r.authorize(ctx, input.OrganizationID, probo.ActionConnectorCreate); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, input.OrganizationID.TenantID()) - - req := probo.CreateConnectorRequest{ - OrganizationID: input.OrganizationID, - Provider: input.Provider, - Protocol: coredata.ConnectorProtocolAPIKey, - Connection: &connector.APIKeyConnection{APIKey: input.APIKey}, - } - - if input.TallyOrganizationID != nil { - req.TallySettings = &coredata.TallyConnectorSettings{ - OrganizationID: *input.TallyOrganizationID, - } - } - if input.SentryOrganizationSlug != nil { - req.SentrySettings = &coredata.SentryConnectorSettings{ - OrganizationSlug: *input.SentryOrganizationSlug, - } - } - if input.SupabaseOrganizationSlug != nil { - req.SupabaseSettings = &coredata.SupabaseConnectorSettings{ - OrganizationSlug: *input.SupabaseOrganizationSlug, - } - } - if input.GithubOrganization != nil { - req.GitHubSettings = &coredata.GitHubConnectorSettings{ - Organization: *input.GithubOrganization, - } - } - if input.OnePasswordScimBridgeURL != nil { - req.OnePasswordSettings = &coredata.OnePasswordConnectorSettings{ - SCIMBridgeURL: *input.OnePasswordScimBridgeURL, - } - } - cnnctr, err := prb.Connectors.Create(ctx, req) - if err != nil { - if errors.Is(err, coredata.ErrResourceAlreadyExists) { - return nil, gqlutils.Conflict(ctx, err) - } - - panic(fmt.Errorf("cannot create API key connector: %w", err)) - } - - return &types.CreateAPIKeyConnectorPayload{ - Connector: types.NewConnector(cnnctr), - }, nil -} - -// CreateClientCredentialsConnector is the resolver for the createClientCredentialsConnector field. -func (r *mutationResolver) CreateClientCredentialsConnector(ctx context.Context, input types.CreateClientCredentialsConnectorInput) (*types.CreateClientCredentialsConnectorPayload, error) { - if err := r.authorize(ctx, input.OrganizationID, probo.ActionConnectorCreate); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, input.OrganizationID.TenantID()) - - oauth2Conn := &connector.OAuth2Connection{ - GrantType: connector.OAuth2GrantTypeClientCredentials, - ClientID: input.ClientID, - ClientSecret: input.ClientSecret, - TokenURL: input.TokenURL, - } - if input.Scope != nil { - oauth2Conn.Scope = *input.Scope - } - - req := probo.CreateConnectorRequest{ - OrganizationID: input.OrganizationID, - Provider: input.Provider, - Protocol: coredata.ConnectorProtocolOAuth2, - Connection: oauth2Conn, - } - - if input.OnePasswordAccountID != nil && input.OnePasswordRegion != nil { - req.OnePasswordUsersAPISettings = &coredata.OnePasswordUsersAPISettings{ - AccountID: *input.OnePasswordAccountID, - Region: *input.OnePasswordRegion, - } - } - - cnnctr, err := prb.Connectors.Create(ctx, req) - if err != nil { - if errors.Is(err, coredata.ErrResourceAlreadyExists) { - return nil, gqlutils.Conflict(ctx, err) - } - - panic(fmt.Errorf("cannot create client credentials connector: %w", err)) - } - - return &types.CreateClientCredentialsConnectorPayload{ - Connector: types.NewConnector(cnnctr), - }, nil -} - -// DeleteConnector is the resolver for the deleteConnector field. -func (r *mutationResolver) DeleteConnector(ctx context.Context, input types.DeleteConnectorInput) (*types.DeleteConnectorPayload, error) { - if err := r.authorize(ctx, input.ConnectorID, probo.ActionConnectorDelete); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, input.ConnectorID.TenantID()) - - if err := prb.Connectors.Delete(ctx, input.ConnectorID); err != nil { - panic(fmt.Errorf("cannot delete connector: %w", err)) - } - - return &types.DeleteConnectorPayload{ - DeletedConnectorID: input.ConnectorID, - }, nil -} - -// ConfigureAccessSource is the resolver for the configureAccessSource field. -func (r *mutationResolver) ConfigureAccessSource(ctx context.Context, input types.ConfigureAccessSourceInput) (*types.ConfigureAccessSourcePayload, error) { - if err := r.authorize(ctx, input.AccessSourceID, probo.ActionAccessSourceUpdate); err != nil { - return nil, err - } - - scope := coredata.NewScopeFromObjectID(input.AccessSourceID) - - source, err := r.accessReview.Sources(scope).ConfigureAccessSource( - ctx, - accessreview.ConfigureAccessSourceRequest{ - AccessSourceID: input.AccessSourceID, - OrganizationSlug: input.OrganizationSlug, - }, - ) - if err != nil { - if errors.Is(err, coredata.ErrResourceNotFound) { - return nil, gqlutils.NotFound(ctx, err) - } - panic(fmt.Errorf("cannot configure access source: %w", err)) - } - - return &types.ConfigureAccessSourcePayload{ - AccessSource: types.NewAccessSource(source), - }, nil -} - -// DeleteSlackConnection is the resolver for the deleteSlackConnection field. -func (r *mutationResolver) DeleteSlackConnection(ctx context.Context, input types.DeleteSlackConnectionInput) (*types.DeleteSlackConnectionPayload, error) { - if err := r.authorize(ctx, input.SlackConnectionID, probo.ActionConnectorDelete); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, input.SlackConnectionID.TenantID()) - - err := prb.Connectors.Delete(ctx, input.SlackConnectionID) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot delete slack connection", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.DeleteSlackConnectionPayload{ - DeletedSlackConnectionID: input.SlackConnectionID, - }, nil -} - -// Organization is the resolver for the organization field. -func (r *obligationResolver) Organization(ctx context.Context, obj *types.Obligation) (*types.Organization, error) { - if err := r.authorize(ctx, obj.ID, probo.ActionOrganizationGet); err != nil { - return nil, err - } - - loaders := dataloader.FromContext(ctx) - - organization, err := loaders.Organization.Load(ctx, obj.Organization.ID) - if err != nil { - if errors.Is(err, coredata.ErrResourceNotFound) || errors.Is(err, dataloadgen.ErrNotFound) { - return nil, gqlutils.NotFound(ctx, err) - } - - r.logger.ErrorCtx(ctx, "cannot get obligation organization", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return types.NewOrganization(organization), nil -} - -// Owner is the resolver for the owner field. -func (r *obligationResolver) Owner(ctx context.Context, obj *types.Obligation) (*types.Profile, error) { - if err := r.authorize(ctx, obj.ID, iam.ActionMembershipProfileGet); err != nil { - return nil, err - } - - loaders := dataloader.FromContext(ctx) - - owner, err := loaders.Profile.Load(ctx, obj.Owner.ID) - if err != nil { - if errors.Is(err, coredata.ErrResourceNotFound) || errors.Is(err, dataloadgen.ErrNotFound) { - return nil, gqlutils.NotFound(ctx, err) - } - - r.logger.ErrorCtx(ctx, "cannot get obligation owner", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return types.NewProfile(owner), nil -} - -// Permission is the resolver for the permission field. -func (r *obligationResolver) Permission(ctx context.Context, obj *types.Obligation, action string) (bool, error) { - return r.Resolver.Permission(ctx, obj, action) -} - -// TotalCount is the resolver for the totalCount field. -func (r *obligationConnectionResolver) TotalCount(ctx context.Context, obj *types.ObligationConnection) (int, error) { - if err := r.authorize(ctx, obj.ParentID, probo.ActionObligationList); err != nil { - return 0, err - } - - prb := r.ProboService(ctx, obj.ParentID.TenantID()) - - switch obj.Resolver.(type) { - case *organizationResolver: - obligationFilter := coredata.NewObligationFilter(nil) - if obj.Filter != nil { - obligationFilter = coredata.NewObligationFilter(&obj.Filter.SnapshotID) - } - - count, err := prb.Obligations.CountForOrganizationID(ctx, obj.ParentID, obligationFilter) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot count obligations", log.Error(err)) - return 0, gqlutils.Internal(ctx) - } - return count, nil - case *riskResolver: - obligationFilter := coredata.NewObligationFilter(nil) - if obj.Filter != nil { - obligationFilter = coredata.NewObligationFilter(&obj.Filter.SnapshotID) - } - - count, err := prb.Obligations.CountForRiskID(ctx, obj.ParentID, obligationFilter) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot count risk obligations", log.Error(err)) - return 0, gqlutils.Internal(ctx) - } - return count, nil - } - - r.logger.ErrorCtx(ctx, "unsupported resolver") - return 0, gqlutils.Internal(ctx) -} - -// LogoURL is the resolver for the logoUrl field. -func (r *organizationResolver) LogoURL(ctx context.Context, obj *types.Organization) (*string, error) { - if err := r.authorize(ctx, obj.ID, probo.ActionOrganizationGetLogoUrl); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, obj.ID.TenantID()) - - logoURL, err := prb.Organizations.GenerateLogoURL(ctx, obj.ID, 1*time.Hour) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot generate logo url", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return logoURL, nil -} - -// HorizontalLogoURL is the resolver for the horizontalLogoUrl field. -func (r *organizationResolver) HorizontalLogoURL(ctx context.Context, obj *types.Organization) (*string, error) { - if err := r.authorize(ctx, obj.ID, probo.ActionOrganizationGetHorizontalLogoUrl); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, obj.ID.TenantID()) - - horizontalLogoURL, err := prb.Organizations.GenerateHorizontalLogoURL(ctx, obj.ID, 1*time.Hour) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot generate horizontal logo url", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return horizontalLogoURL, nil -} - -// Context is the resolver for the context field. -func (r *organizationResolver) Context(ctx context.Context, obj *types.Organization) (*types.OrganizationContext, error) { - if err := r.authorize(ctx, obj.ID, probo.ActionOrganizationContextGet); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, obj.ID.TenantID()) - - orgContext, err := prb.Organizations.GetContext(ctx, obj.ID) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot load organization context", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return types.NewOrganizationContext(orgContext), nil -} - -// Profiles is the resolver for the profiles field. -func (r *organizationResolver) Profiles(ctx context.Context, obj *types.Organization, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.ProfileOrderBy, filter *types.ProfileFilter) (*types.ProfileConnection, error) { - if err := r.authorize(ctx, obj.ID, iam.ActionMembershipProfileList); err != nil { - return nil, err - } - - if gqlutils.OnlyTotalCountSelected(ctx) { - return &types.ProfileConnection{ - Resolver: r, - ParentID: obj.ID, - }, nil - } - - filters := coredata.NewMembershipProfileFilter(nil).WithMembership() - if filter != nil { - filters = coredata.NewMembershipProfileFilter(filter.ExcludeContractEnded).WithMembership() - } - - pageOrderBy := page.OrderBy[coredata.MembershipProfileOrderField]{ - Field: coredata.MembershipProfileOrderFieldCreatedAt, - Direction: page.OrderDirectionDesc, - } - if orderBy != nil { - pageOrderBy.Field = coredata.MembershipProfileOrderField(orderBy.Field) - pageOrderBy.Direction = page.OrderDirection(orderBy.Direction) - } - - cursor := cursor.NewCursor(first, after, last, before, pageOrderBy) - - page, err := r.iam.OrganizationService.ListProfiles(ctx, obj.ID, cursor, filters) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot list profiles", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return types.NewProfileConnection(page, r, obj.ID, filters), nil -} - -// SlackConnections is the resolver for the slackConnections field. -func (r *organizationResolver) SlackConnections(ctx context.Context, obj *types.Organization, first *int, after *page.CursorKey, last *int, before *page.CursorKey) (*types.SlackConnectionConnection, error) { - if err := r.authorize(ctx, obj.ID, probo.ActionSlackConnectionList); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, obj.ID.TenantID()) - - slackProvider := coredata.ConnectorProviderSlack - filter := coredata.NewConnectorProviderFilter(&slackProvider) - - pageOrderBy := page.OrderBy[coredata.ConnectorOrderField]{ - Field: coredata.ConnectorOrderFieldCreatedAt, - Direction: page.OrderDirectionDesc, - } - - cursor := types.NewCursor(first, after, last, before, pageOrderBy) - - page, err := prb.Connectors.ListForOrganizationID(ctx, obj.ID, cursor, filter) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot list organization slack connections", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return types.NewSlackConnectionConnection(page), nil -} - -// SlackOAuth2Scopes is the resolver for the slackOAuth2Scopes field. -func (r *organizationResolver) SlackOAuth2Scopes(ctx context.Context, obj *types.Organization) ([]string, error) { - return slack.OAuth2Scopes, nil -} - -// Connectors is the resolver for the connectors field. -func (r *organizationResolver) Connectors(ctx context.Context, obj *types.Organization, filter *types.ConnectorFilter) ([]*types.Connector, error) { - if err := r.authorize(ctx, obj.ID, probo.ActionConnectorList); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, obj.ID.TenantID()) - - connectors, err := prb.Connectors.ListAllForOrganizationID(ctx, obj.ID) - if err != nil { - panic(fmt.Errorf("cannot list organization connectors: %w", err)) - } - - if filter != nil && len(filter.Providers) > 0 { - allowed := make(map[coredata.ConnectorProvider]struct{}, len(filter.Providers)) - for _, provider := range filter.Providers { - allowed[provider] = struct{}{} - } - - filtered := make(coredata.Connectors, 0, len(connectors)) - for _, cnnctr := range connectors { - if _, ok := allowed[cnnctr.Provider]; ok { - filtered = append(filtered, cnnctr) - } - } - connectors = filtered - } - - return types.NewConnectors(connectors), nil -} - -// ConnectorProviderInfos is the resolver for the connectorProviderInfos field. -func (r *organizationResolver) ConnectorProviderInfos(ctx context.Context, obj *types.Organization) ([]*types.ConnectorProviderInfo, error) { - if err := r.authorize(ctx, obj.ID, probo.ActionConnectorList); err != nil { - return nil, err - } - - var infos []*types.ConnectorProviderInfo - for _, provider := range coredata.ConnectorProviders() { - _, oauthErr := r.connectorRegistry.Get(string(provider)) - scopes := drivers.ProviderOAuth2Scopes(provider) - if scopes == nil { - scopes = []string{} - } - info := &types.ConnectorProviderInfo{ - Provider: provider, - DisplayName: providerDisplayName(provider), - OauthConfigured: oauthErr == nil, - APIKeySupported: providerSupportsAPIKey(provider), - ClientCredentialsSupported: providerSupportsClientCredentials(provider), - Oauth2Scopes: scopes, - ExtraSettings: providerExtraSettings(provider), - } - infos = append(infos, info) - } - return infos, nil -} - -// Frameworks is the resolver for the frameworks field. -func (r *organizationResolver) Frameworks(ctx context.Context, obj *types.Organization, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.FrameworkOrderBy) (*types.FrameworkConnection, error) { - if err := r.authorize(ctx, obj.ID, probo.ActionFrameworkList); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, obj.ID.TenantID()) - - pageOrderBy := page.OrderBy[coredata.FrameworkOrderField]{ - Field: coredata.FrameworkOrderFieldCreatedAt, - Direction: page.OrderDirectionDesc, - } - if orderBy != nil { - pageOrderBy = page.OrderBy[coredata.FrameworkOrderField]{ - Field: orderBy.Field, - Direction: orderBy.Direction, - } - } - - cursor := types.NewCursor(first, after, last, before, pageOrderBy) - - page, err := prb.Frameworks.ListForOrganizationID(ctx, obj.ID, cursor) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot list organization frameworks", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return types.NewFrameworkConnection(page, r, obj.ID), nil -} - -// Controls is the resolver for the controls field. -func (r *organizationResolver) Controls(ctx context.Context, obj *types.Organization, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.ControlOrderBy, filter *types.ControlFilter) (*types.ControlConnection, error) { - if err := r.authorize(ctx, obj.ID, probo.ActionControlList); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, obj.ID.TenantID()) - - pageOrderBy := page.OrderBy[coredata.ControlOrderField]{ - Field: coredata.ControlOrderFieldCreatedAt, - Direction: page.OrderDirectionDesc, - } - if orderBy != nil { - pageOrderBy = page.OrderBy[coredata.ControlOrderField]{ - Field: orderBy.Field, - Direction: orderBy.Direction, - } - } - - cursor := types.NewCursor(first, after, last, before, pageOrderBy) - - var controlFilter = coredata.NewControlFilter(nil) - if filter != nil { - controlFilter = coredata.NewControlFilter(filter.Query) - } - - page, err := prb.Controls.ListForOrganizationID(ctx, obj.ID, cursor, controlFilter) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot list controls", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return types.NewControlConnection(page, r, obj.ID, controlFilter), nil -} - -// Vendors is the resolver for the vendors field. -func (r *organizationResolver) Vendors(ctx context.Context, obj *types.Organization, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.VendorOrderBy, filter *types.VendorFilter) (*types.VendorConnection, error) { - if err := r.authorize(ctx, obj.ID, probo.ActionVendorList); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, obj.ID.TenantID()) - - pageOrderBy := page.OrderBy[coredata.VendorOrderField]{ - Field: coredata.VendorOrderFieldCreatedAt, - Direction: page.OrderDirectionDesc, - } - if orderBy != nil { - pageOrderBy = page.OrderBy[coredata.VendorOrderField]{ - Field: orderBy.Field, - Direction: orderBy.Direction, - } - } - - cursor := types.NewCursor(first, after, last, before, pageOrderBy) - - var vendorFilter = coredata.NewVendorFilter(nil, nil) - if filter != nil { - vendorFilter = coredata.NewVendorFilter(&filter.SnapshotID, nil) - } - - page, err := prb.Vendors.ListForOrganizationID(ctx, obj.ID, cursor, vendorFilter) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot list organization vendors", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return types.NewVendorConnection(page, r, obj.ID), nil -} - -// Documents is the resolver for the documents field. -func (r *organizationResolver) Documents(ctx context.Context, obj *types.Organization, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.DocumentOrderBy, filter *types.DocumentFilter) (*types.DocumentConnection, error) { - if err := r.authorize(ctx, obj.ID, probo.ActionDocumentList); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, obj.ID.TenantID()) - - pageOrderBy := page.OrderBy[coredata.DocumentOrderField]{ - Field: coredata.DocumentOrderFieldTitle, - Direction: page.OrderDirectionDesc, - } - if orderBy != nil { - pageOrderBy = page.OrderBy[coredata.DocumentOrderField]{ - Field: orderBy.Field, - Direction: orderBy.Direction, - } - } - - cursor := types.NewCursor(first, after, last, before, pageOrderBy) - - var documentFilter = coredata.NewDocumentFilter(nil) - if filter != nil { - documentFilter = coredata.NewDocumentFilter(filter.Query). - WithDocumentTypes(filter.DocumentTypes). - WithClassifications(filter.Classifications). - WithStatus(filter.Status) - } - - page, err := prb.Documents.ListByOrganizationID(ctx, obj.ID, cursor, documentFilter) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot list organization documents", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return types.NewDocumentConnection(page, r, obj.ID, documentFilter), nil -} - -// Meetings is the resolver for the meetings field. -func (r *organizationResolver) Meetings(ctx context.Context, obj *types.Organization, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.MeetingOrderBy) (*types.MeetingConnection, error) { - if err := r.authorize(ctx, obj.ID, probo.ActionMeetingList); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, obj.ID.TenantID()) - - pageOrderBy := page.OrderBy[coredata.MeetingOrderField]{ - Field: coredata.MeetingOrderFieldCreatedAt, - Direction: page.OrderDirectionDesc, - } - if orderBy != nil { - pageOrderBy = page.OrderBy[coredata.MeetingOrderField]{ - Field: orderBy.Field, - Direction: orderBy.Direction, - } - } - - cursor := types.NewCursor(first, after, last, before, pageOrderBy) - - page, err := prb.Meetings.ListForOrganizationID(ctx, obj.ID, cursor) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot list organization meetings", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return types.NewMeetingConnection(page, r, obj.ID), nil -} - -// StatementsOfApplicability is the resolver for the statementsOfApplicability field. -func (r *organizationResolver) StatementsOfApplicability(ctx context.Context, obj *types.Organization, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.StatementOfApplicabilityOrderBy, filter *types.StatementOfApplicabilityFilter) (*types.StatementOfApplicabilityConnection, error) { - if err := r.authorize(ctx, obj.ID, probo.ActionStatementOfApplicabilityList); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, obj.ID.TenantID()) - - pageOrderBy := page.OrderBy[coredata.StatementOfApplicabilityOrderField]{ - Field: coredata.StatementOfApplicabilityOrderFieldCreatedAt, - Direction: page.OrderDirectionDesc, - } - if orderBy != nil { - pageOrderBy = page.OrderBy[coredata.StatementOfApplicabilityOrderField]{ - Field: orderBy.Field, - Direction: orderBy.Direction, - } - } - - cursor := types.NewCursor(first, after, last, before, pageOrderBy) - - var statementOfApplicabilityFilter = coredata.NewStatementOfApplicabilityFilter(nil) - if filter != nil { - statementOfApplicabilityFilter = coredata.NewStatementOfApplicabilityFilter(&filter.SnapshotID) - } - - page, err := prb.StatementsOfApplicability.ListForOrganizationID(ctx, obj.ID, cursor, statementOfApplicabilityFilter) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot list organization statements_of_applicability", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return types.NewStatementOfApplicabilityConnection(page, r, obj.ID, statementOfApplicabilityFilter), nil -} - -// MeasureCategories is the resolver for the measureCategories field. -func (r *organizationResolver) MeasureCategories(ctx context.Context, obj *types.Organization) ([]string, error) { - if err := r.authorize(ctx, obj.ID, probo.ActionMeasureList); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, obj.ID.TenantID()) - - categories, err := prb.Measures.ListDistinctCategoriesForOrganizationID(ctx, obj.ID) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot list measure categories", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return categories, nil -} - -// Measures is the resolver for the measures field. -func (r *organizationResolver) Measures(ctx context.Context, obj *types.Organization, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.MeasureOrderBy, filter *types.MeasureFilter) (*types.MeasureConnection, error) { - if err := r.authorize(ctx, obj.ID, probo.ActionMeasureList); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, obj.ID.TenantID()) - - pageOrderBy := page.OrderBy[coredata.MeasureOrderField]{ - Field: coredata.MeasureOrderFieldCreatedAt, - Direction: page.OrderDirectionDesc, - } - if orderBy != nil { - pageOrderBy = page.OrderBy[coredata.MeasureOrderField]{ - Field: orderBy.Field, - Direction: orderBy.Direction, - } - } - - cursor := types.NewCursor(first, after, last, before, pageOrderBy) - - var measureFilter = coredata.NewMeasureFilter(nil, nil, nil) - if filter != nil { - measureFilter = coredata.NewMeasureFilter(filter.Query, filter.State, filter.Category) - } - - page, err := prb.Measures.ListForOrganizationID(ctx, obj.ID, cursor, measureFilter) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot list organization measures", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return types.NewMeasureConnection(page, r, obj.ID, measureFilter), nil -} - -// Risks is the resolver for the risks field. -func (r *organizationResolver) Risks(ctx context.Context, obj *types.Organization, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.RiskOrderBy, filter *types.RiskFilter) (*types.RiskConnection, error) { - if err := r.authorize(ctx, obj.ID, probo.ActionRiskList); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, obj.ID.TenantID()) - - pageOrderBy := page.OrderBy[coredata.RiskOrderField]{ - Field: coredata.RiskOrderFieldCreatedAt, - Direction: page.OrderDirectionDesc, - } - if orderBy != nil { - pageOrderBy = page.OrderBy[coredata.RiskOrderField]{ - Field: orderBy.Field, - Direction: orderBy.Direction, - } - } - - cursor := types.NewCursor(first, after, last, before, pageOrderBy) - - var riskFilter = coredata.NewRiskFilter(nil, nil) - if filter != nil { - riskFilter = coredata.NewRiskFilter(filter.Query, &filter.SnapshotID) - } - - page, err := prb.Risks.ListForOrganizationID(ctx, obj.ID, cursor, riskFilter) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot list organization risks", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return types.NewRiskConnection(page, r, obj.ID, riskFilter), nil -} - -// Tasks is the resolver for the tasks field. -func (r *organizationResolver) Tasks(ctx context.Context, obj *types.Organization, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.TaskOrderBy) (*types.TaskConnection, error) { - if err := r.authorize(ctx, obj.ID, probo.ActionTaskList); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, obj.ID.TenantID()) - - pageOrderBy := page.OrderBy[coredata.TaskOrderField]{ - Field: coredata.TaskOrderFieldCreatedAt, - Direction: page.OrderDirectionDesc, - } - if orderBy != nil { - pageOrderBy = page.OrderBy[coredata.TaskOrderField]{ - Field: orderBy.Field, - Direction: orderBy.Direction, - } - } - - cursor := types.NewCursor(first, after, last, before, pageOrderBy) - - page, err := prb.Tasks.ListForOrganizationID(ctx, obj.ID, cursor) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot list organization tasks", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return types.NewTaskConnection(page, r, obj.ID), nil -} - -// Assets is the resolver for the assets field. -func (r *organizationResolver) Assets(ctx context.Context, obj *types.Organization, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.AssetOrderBy, filter *types.AssetFilter) (*types.AssetConnection, error) { - if err := r.authorize(ctx, obj.ID, probo.ActionAssetList); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, obj.ID.TenantID()) - - pageOrderBy := page.OrderBy[coredata.AssetOrderField]{ - Field: coredata.AssetOrderFieldCreatedAt, - Direction: page.OrderDirectionDesc, - } - if orderBy != nil { - pageOrderBy = page.OrderBy[coredata.AssetOrderField]{ - Field: orderBy.Field, - Direction: orderBy.Direction, - } - } - - cursor := types.NewCursor(first, after, last, before, pageOrderBy) - - assetFilter := coredata.NewAssetFilter(nil) - if filter != nil { - assetFilter = coredata.NewAssetFilter(&filter.SnapshotID) - } - - page, err := prb.Assets.ListForOrganizationID(ctx, obj.ID, cursor, assetFilter) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot list organization assets", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return types.NewAssetConnection(page, r, obj.ID, filter), nil -} - -// Assets is the resolver for the assets field. -func (r *organizationResolver) Data(ctx context.Context, obj *types.Organization, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.DatumOrderBy, filter *types.DatumFilter) (*types.DatumConnection, error) { - if err := r.authorize(ctx, obj.ID, probo.ActionDatumList); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, obj.ID.TenantID()) - - pageOrderBy := page.OrderBy[coredata.DatumOrderField]{ - Field: coredata.DatumOrderFieldCreatedAt, - Direction: page.OrderDirectionDesc, - } - if orderBy != nil { - pageOrderBy = page.OrderBy[coredata.DatumOrderField]{ - Field: orderBy.Field, - Direction: orderBy.Direction, - } - } - - cursor := types.NewCursor(first, after, last, before, pageOrderBy) - - datumFilter := coredata.NewDatumFilter(nil) - if filter != nil { - datumFilter = coredata.NewDatumFilter(&filter.SnapshotID) - } - - page, err := prb.Data.ListForOrganizationID(ctx, obj.ID, cursor, datumFilter) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot list organization data", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return types.NewDataConnection(page, r, obj.ID, filter), nil -} - -// Audits is the resolver for the audits field. -func (r *organizationResolver) Audits(ctx context.Context, obj *types.Organization, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.AuditOrderBy) (*types.AuditConnection, error) { - if err := r.authorize(ctx, obj.ID, probo.ActionAuditList); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, obj.ID.TenantID()) - - pageOrderBy := page.OrderBy[coredata.AuditOrderField]{ - Field: coredata.AuditOrderFieldCreatedAt, - Direction: page.OrderDirectionDesc, - } - if orderBy != nil { - pageOrderBy = page.OrderBy[coredata.AuditOrderField]{ - Field: orderBy.Field, - Direction: orderBy.Direction, - } - } - - cursor := types.NewCursor(first, after, last, before, pageOrderBy) - - page, err := prb.Audits.ListForOrganizationID(ctx, obj.ID, cursor) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot list organization audits", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return types.NewAuditConnection(page, r, obj.ID), nil -} - -// Findings is the resolver for the findings field. -func (r *organizationResolver) Findings(ctx context.Context, obj *types.Organization, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.FindingOrder, filter *types.FindingFilter) (*types.FindingConnection, error) { - if err := r.authorize(ctx, obj.ID, probo.ActionFindingList); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, obj.ID.TenantID()) - - pageOrderBy := page.OrderBy[coredata.FindingOrderField]{ - Field: coredata.FindingOrderFieldCreatedAt, - Direction: page.OrderDirectionDesc, - } - - if orderBy != nil { - pageOrderBy = page.OrderBy[coredata.FindingOrderField]{ - Field: orderBy.Field, - Direction: orderBy.Direction, - } - } - - cursor := types.NewCursor(first, after, last, before, pageOrderBy) - - var ( - kind *coredata.FindingKind - status *coredata.FindingStatus - priority *coredata.FindingPriority - ownerID *gid.GID - ) - if filter != nil { - kind = filter.Kind - status = filter.Status - priority = filter.Priority - ownerID = filter.OwnerID - } - - findingFilter := coredata.NewFindingFilter(nil, kind, status, priority, ownerID) - if filter != nil { - findingFilter = coredata.NewFindingFilter(&filter.SnapshotID, kind, status, priority, ownerID) - } - - page, err := prb.Findings.ListForOrganizationID(ctx, obj.ID, cursor, findingFilter) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot list organization findings", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return types.NewFindingConnection(page, r, obj.ID, filter), nil -} - -// Obligations is the resolver for the obligations field. -func (r *organizationResolver) Obligations(ctx context.Context, obj *types.Organization, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.ObligationOrderBy, filter *types.ObligationFilter) (*types.ObligationConnection, error) { - if err := r.authorize(ctx, obj.ID, probo.ActionObligationList); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, obj.ID.TenantID()) - - pageOrderBy := page.OrderBy[coredata.ObligationOrderField]{ - Field: coredata.ObligationOrderFieldCreatedAt, - Direction: page.OrderDirectionDesc, - } - - if orderBy != nil { - pageOrderBy = page.OrderBy[coredata.ObligationOrderField]{ - Field: orderBy.Field, - Direction: orderBy.Direction, - } - } - - cursor := types.NewCursor(first, after, last, before, pageOrderBy) - - obligationFilter := coredata.NewObligationFilter(nil) - if filter != nil { - obligationFilter = coredata.NewObligationFilter(&filter.SnapshotID) - } - - page, err := prb.Obligations.ListForOrganizationID(ctx, obj.ID, cursor, obligationFilter) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot list organization obligations", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return types.NewObligationConnection(page, r, obj.ID, filter), nil -} - -// RightsRequests is the resolver for the rightsRequests field. -func (r *organizationResolver) RightsRequests(ctx context.Context, obj *types.Organization, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.RightsRequestOrderBy) (*types.RightsRequestConnection, error) { - if err := r.authorize(ctx, obj.ID, probo.ActionRightsRequestList); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, obj.ID.TenantID()) - - pageOrderBy := page.OrderBy[coredata.RightsRequestOrderField]{ - Field: coredata.RightsRequestOrderFieldCreatedAt, - Direction: page.OrderDirectionDesc, - } - - if orderBy != nil { - pageOrderBy = page.OrderBy[coredata.RightsRequestOrderField]{ - Field: orderBy.Field, - Direction: orderBy.Direction, - } - } - - cursor := types.NewCursor(first, after, last, before, pageOrderBy) - - page, err := prb.RightsRequests.ListForOrganizationID(ctx, obj.ID, cursor) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot list organization rights requests", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return types.NewRightsRequestConnection(page, r, obj.ID), nil -} - -// ProcessingActivities is the resolver for the processingActivities field. -func (r *organizationResolver) ProcessingActivities(ctx context.Context, obj *types.Organization, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.ProcessingActivityOrderBy, filter *types.ProcessingActivityFilter) (*types.ProcessingActivityConnection, error) { - if err := r.authorize(ctx, obj.ID, probo.ActionProcessingActivityList); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, obj.ID.TenantID()) - - pageOrderBy := page.OrderBy[coredata.ProcessingActivityOrderField]{ - Field: coredata.ProcessingActivityOrderFieldCreatedAt, - Direction: page.OrderDirectionDesc, - } - - if orderBy != nil { - pageOrderBy = page.OrderBy[coredata.ProcessingActivityOrderField]{ - Field: orderBy.Field, - Direction: orderBy.Direction, - } - } - - cursor := types.NewCursor(first, after, last, before, pageOrderBy) - - processingActivityFilter := coredata.NewProcessingActivityFilter(nil) - if filter != nil { - processingActivityFilter = coredata.NewProcessingActivityFilter(&filter.SnapshotID) - } - - page, err := prb.ProcessingActivities.ListForOrganizationID(ctx, obj.ID, cursor, processingActivityFilter) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot list organization processing activities", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return types.NewProcessingActivityConnection(page, r, obj.ID, filter), nil -} - -// DataProtectionImpactAssessments is the resolver for the dataProtectionImpactAssessments field. -func (r *organizationResolver) DataProtectionImpactAssessments(ctx context.Context, obj *types.Organization, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.DataProtectionImpactAssessmentOrderBy, filter *types.DataProtectionImpactAssessmentFilter) (*types.DataProtectionImpactAssessmentConnection, error) { - if err := r.authorize(ctx, obj.ID, probo.ActionDataProtectionImpactAssessmentList); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, obj.ID.TenantID()) - - pageOrderBy := page.OrderBy[coredata.DataProtectionImpactAssessmentOrderField]{ - Field: coredata.DataProtectionImpactAssessmentOrderFieldCreatedAt, - Direction: page.OrderDirectionDesc, - } - - if orderBy != nil { - pageOrderBy = page.OrderBy[coredata.DataProtectionImpactAssessmentOrderField]{ - Field: orderBy.Field, - Direction: orderBy.Direction, - } - } - - cursor := types.NewCursor(first, after, last, before, pageOrderBy) - - dpiaFilter := coredata.NewDataProtectionImpactAssessmentFilter(nil) - if filter != nil { - dpiaFilter = coredata.NewDataProtectionImpactAssessmentFilter(&filter.SnapshotID) - } - - page, err := prb.DataProtectionImpactAssessments.ListForOrganizationID(ctx, obj.ID, cursor, dpiaFilter) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot list organization data protection impact assessments", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return types.NewDataProtectionImpactAssessmentConnection(page, r, obj.ID, dpiaFilter), nil -} - -// TransferImpactAssessments is the resolver for the transferImpactAssessments field. -func (r *organizationResolver) TransferImpactAssessments(ctx context.Context, obj *types.Organization, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.TransferImpactAssessmentOrderBy, filter *types.TransferImpactAssessmentFilter) (*types.TransferImpactAssessmentConnection, error) { - if err := r.authorize(ctx, obj.ID, probo.ActionTransferImpactAssessmentList); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, obj.ID.TenantID()) - - pageOrderBy := page.OrderBy[coredata.TransferImpactAssessmentOrderField]{ - Field: coredata.TransferImpactAssessmentOrderFieldCreatedAt, - Direction: page.OrderDirectionDesc, - } - - if orderBy != nil { - pageOrderBy = page.OrderBy[coredata.TransferImpactAssessmentOrderField]{ - Field: orderBy.Field, - Direction: orderBy.Direction, - } - } - - cursor := types.NewCursor(first, after, last, before, pageOrderBy) - - tiaFilter := coredata.NewTransferImpactAssessmentFilter(nil) - if filter != nil { - tiaFilter = coredata.NewTransferImpactAssessmentFilter(&filter.SnapshotID) - } - - page, err := prb.TransferImpactAssessments.ListForOrganizationID(ctx, obj.ID, cursor, tiaFilter) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot list organization transfer impact assessments", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return types.NewTransferImpactAssessmentConnection(page, r, obj.ID, tiaFilter), nil -} - -// Snapshots is the resolver for the snapshots field. -func (r *organizationResolver) Snapshots(ctx context.Context, obj *types.Organization, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.SnapshotOrderBy) (*types.SnapshotConnection, error) { - if err := r.authorize(ctx, obj.ID, probo.ActionSnapshotList); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, obj.ID.TenantID()) - - pageOrderBy := page.OrderBy[coredata.SnapshotOrderField]{ - Field: coredata.SnapshotOrderFieldCreatedAt, - Direction: page.OrderDirectionDesc, - } - if orderBy != nil { - pageOrderBy = page.OrderBy[coredata.SnapshotOrderField]{ - Field: orderBy.Field, - Direction: orderBy.Direction, - } - } - - cursor := types.NewCursor(first, after, last, before, pageOrderBy) - - page, err := prb.Snapshots.ListForOrganizationID(ctx, obj.ID, cursor) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot list organization snapshots", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return types.NewSnapshotConnection(page, r, obj.ID), nil -} - -// TrustCenterFiles is the resolver for the trustCenterFiles field. -func (r *organizationResolver) TrustCenterFiles(ctx context.Context, obj *types.Organization, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.OrderBy[coredata.TrustCenterFileOrderField]) (*types.TrustCenterFileConnection, error) { - if err := r.authorize(ctx, obj.ID, probo.ActionTrustCenterFileList); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, obj.ID.TenantID()) - - pageOrderBy := page.OrderBy[coredata.TrustCenterFileOrderField]{ - Field: coredata.TrustCenterFileOrderFieldCreatedAt, - Direction: page.OrderDirectionDesc, - } - if orderBy != nil { - pageOrderBy = page.OrderBy[coredata.TrustCenterFileOrderField]{ - Field: orderBy.Field, - Direction: orderBy.Direction, - } - } - - cursor := types.NewCursor(first, after, last, before, pageOrderBy) - - pageResult, err := prb.TrustCenterFiles.ListForOrganizationID(ctx, obj.ID, cursor, &coredata.TrustCenterFileFilter{}) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot list organization trust center files", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return types.NewTrustCenterFileConnection(pageResult, obj.ID), nil -} - -// TrustCenter is the resolver for the trustCenter field. -func (r *organizationResolver) TrustCenter(ctx context.Context, obj *types.Organization) (*types.TrustCenter, error) { - if err := r.authorize(ctx, obj.ID, probo.ActionTrustCenterGet); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, obj.ID.TenantID()) - - trustCenter, err := prb.TrustCenters.GetByOrganizationID(ctx, obj.ID) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot get trust center", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - var file *coredata.File - if trustCenter.NonDisclosureAgreementFileID != nil { - file, err = prb.Files.Get(ctx, *trustCenter.NonDisclosureAgreementFileID) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot get NDA file", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - } - - return types.NewTrustCenter(trustCenter, file), nil -} - -// CustomDomain is the resolver for the customDomain field. -func (r *organizationResolver) CustomDomain(ctx context.Context, obj *types.Organization) (*types.CustomDomain, error) { - if err := r.authorize(ctx, obj.ID, probo.ActionCustomDomainGet); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, obj.ID.TenantID()) - - domain, err := prb.CustomDomains.GetOrganizationCustomDomain(ctx, obj.ID) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot get custom domain", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - if domain == nil { - return nil, nil - } - - return types.NewCustomDomain(domain, r.customDomainCname), nil -} - -// WebhookSubscriptions is the resolver for the webhookSubscriptions field. -func (r *organizationResolver) WebhookSubscriptions(ctx context.Context, obj *types.Organization, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.WebhookSubscriptionOrderBy) (*types.WebhookSubscriptionConnection, error) { - if err := r.authorize(ctx, obj.ID, probo.ActionWebhookSubscriptionList); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, obj.ID.TenantID()) - - pageOrderBy := page.OrderBy[coredata.WebhookSubscriptionOrderField]{ - Field: coredata.WebhookSubscriptionOrderFieldCreatedAt, - Direction: page.OrderDirectionDesc, - } - if orderBy != nil { - pageOrderBy = page.OrderBy[coredata.WebhookSubscriptionOrderField]{ - Field: orderBy.Field, - Direction: orderBy.Direction, - } - } - - cursor := types.NewCursor(first, after, last, before, pageOrderBy) - - page, err := prb.WebhookSubscriptions.ListForOrganizationID(ctx, obj.ID, cursor) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot list organization webhook subscriptions", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return types.NewWebhookSubscriptionConnection(page, r, obj.ID), nil -} - -// AuditLogEntries is the resolver for the auditLogEntries field. -func (r *organizationResolver) AuditLogEntries(ctx context.Context, obj *types.Organization, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.AuditLogEntryOrderBy, filter *types.AuditLogEntryFilter) (*types.AuditLogEntryConnection, error) { - if err := r.authorize(ctx, obj.ID, iam.ActionAuditLogEntryList); err != nil { - return nil, err - } - - pageOrderBy := page.OrderBy[coredata.AuditLogEntryOrderField]{ - Field: coredata.AuditLogEntryOrderFieldCreatedAt, - Direction: page.OrderDirectionDesc, - } - if orderBy != nil { - pageOrderBy = page.OrderBy[coredata.AuditLogEntryOrderField]{ - Field: orderBy.Field, - Direction: orderBy.Direction, - } - } - - cursor := types.NewCursor(first, after, last, before, pageOrderBy) - - coredataFilter := coredata.NewAuditLogEntryFilter() - if filter != nil { - if filter.Action != nil { - coredataFilter.WithAction(*filter.Action) - } - if filter.ActorID != nil { - coredataFilter.WithActorID(*filter.ActorID) - } - if filter.ResourceType != nil { - coredataFilter.WithResourceType(*filter.ResourceType) - } - if filter.ResourceID != nil { - coredataFilter.WithResourceID(*filter.ResourceID) - } - } - - p, err := r.iam.OrganizationService.ListAuditLogEntries(ctx, obj.ID, cursor, coredataFilter) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot list audit log entries", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return types.NewAuditLogEntryConnection(p, r, obj.ID, coredataFilter), nil -} - -// AccessSources is the resolver for the accessSources field. -func (r *organizationResolver) AccessSources(ctx context.Context, obj *types.Organization, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.AccessSourceOrder) (*types.AccessSourceConnection, error) { - if err := r.authorize(ctx, obj.ID, probo.ActionAccessSourceList); err != nil { - return nil, err - } - - scope := coredata.NewScopeFromObjectID(obj.ID) - - pageOrderBy := page.OrderBy[coredata.AccessSourceOrderField]{ - Field: coredata.AccessSourceOrderFieldCreatedAt, - Direction: page.OrderDirectionDesc, - } - if orderBy != nil { - pageOrderBy = page.OrderBy[coredata.AccessSourceOrderField]{ - Field: orderBy.Field, - Direction: orderBy.Direction, - } - } - - cursor := types.NewCursor(first, after, last, before, pageOrderBy) - - p, err := r.accessReview.Sources(scope).ListForOrganizationID(ctx, obj.ID, cursor) - if err != nil { - panic(fmt.Errorf("cannot list access sources: %w", err)) - } - - return types.NewAccessSourceConnection(p, r, obj.ID), nil -} - -// AccessReviewCampaigns is the resolver for the accessReviewCampaigns field. -func (r *organizationResolver) AccessReviewCampaigns(ctx context.Context, obj *types.Organization, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.AccessReviewCampaignOrder) (*types.AccessReviewCampaignConnection, error) { - if err := r.authorize(ctx, obj.ID, probo.ActionAccessReviewCampaignList); err != nil { - return nil, err - } - - scope := coredata.NewScopeFromObjectID(obj.ID) - - pageOrderBy := page.OrderBy[coredata.AccessReviewCampaignOrderField]{ - Field: coredata.AccessReviewCampaignOrderFieldCreatedAt, - Direction: page.OrderDirectionDesc, - } - if orderBy != nil { - pageOrderBy = page.OrderBy[coredata.AccessReviewCampaignOrderField]{ - Field: orderBy.Field, - Direction: orderBy.Direction, - } - } - - cursor := types.NewCursor(first, after, last, before, pageOrderBy) - - p, err := r.accessReview.Campaigns(scope).ListForOrganizationID(ctx, obj.ID, cursor) - if err != nil { - panic(fmt.Errorf("cannot list access review campaigns: %w", err)) - } - - return types.NewAccessReviewCampaignConnection(p, r, obj.ID), nil -} - -// Permission is the resolver for the permission field. -func (r *organizationResolver) Permission(ctx context.Context, obj *types.Organization, action string) (bool, error) { - return r.Resolver.Permission(ctx, obj, action) -} - -// Organization is the resolver for the organization field. -func (r *processingActivityResolver) Organization(ctx context.Context, obj *types.ProcessingActivity) (*types.Organization, error) { - if err := r.authorize(ctx, obj.ID, probo.ActionOrganizationGet); err != nil { - return nil, err - } - - loaders := dataloader.FromContext(ctx) - - organization, err := loaders.Organization.Load(ctx, obj.Organization.ID) - if err != nil { - if errors.Is(err, coredata.ErrResourceNotFound) || errors.Is(err, dataloadgen.ErrNotFound) { - return nil, gqlutils.NotFound(ctx, err) - } - - r.logger.ErrorCtx(ctx, "cannot get organization", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return types.NewOrganization(organization), nil -} - -// DataProtectionOfficer is the resolver for the dataProtectionOfficer field. -func (r *processingActivityResolver) DataProtectionOfficer(ctx context.Context, obj *types.ProcessingActivity) (*types.Profile, error) { - if err := r.authorize(ctx, obj.ID, iam.ActionMembershipProfileGet); err != nil { - return nil, err - } - - if obj.DataProtectionOfficer == nil { - return nil, nil - } - - loaders := dataloader.FromContext(ctx) - - dpo, err := loaders.Profile.Load(ctx, obj.DataProtectionOfficer.ID) - if err != nil { - if errors.Is(err, dataloadgen.ErrNotFound) { - return nil, gqlutils.NotFound(ctx, err) - } - - r.logger.ErrorCtx(ctx, "cannot get data protection officer", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return types.NewProfile(dpo), nil -} - -// Vendors is the resolver for the vendors field. -func (r *processingActivityResolver) Vendors(ctx context.Context, obj *types.ProcessingActivity, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.VendorOrderBy) (*types.VendorConnection, error) { - if err := r.authorize(ctx, obj.ID, probo.ActionVendorList); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, obj.ID.TenantID()) - - pageOrderBy := page.OrderBy[coredata.VendorOrderField]{ - Field: coredata.VendorOrderFieldCreatedAt, - Direction: page.OrderDirectionDesc, - } - if orderBy != nil { - pageOrderBy = page.OrderBy[coredata.VendorOrderField]{ - Field: orderBy.Field, - Direction: orderBy.Direction, - } - } - - cursor := types.NewCursor(first, after, last, before, pageOrderBy) - - page, err := prb.Vendors.ListForProcessingActivityID(ctx, obj.ID, cursor) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot list processing activity vendors", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return types.NewVendorConnection(page, r, obj.ID), nil -} - -// DataProtectionImpactAssessment is the resolver for the dataProtectionImpactAssessment field. -func (r *processingActivityResolver) DataProtectionImpactAssessment(ctx context.Context, obj *types.ProcessingActivity) (*types.DataProtectionImpactAssessment, error) { - if err := r.authorize(ctx, obj.ID, probo.ActionDataProtectionImpactAssessmentGet); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, obj.ID.TenantID()) - - dpia, err := prb.DataProtectionImpactAssessments.GetByProcessingActivityID(ctx, obj.ID) - if err != nil { - if errors.Is(err, coredata.ErrResourceNotFound) { - return nil, nil - } - r.logger.ErrorCtx(ctx, "cannot get processing activity dpia", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return types.NewDataProtectionImpactAssessment(dpia), nil -} - -// TransferImpactAssessment is the resolver for the transferImpactAssessment field. -func (r *processingActivityResolver) TransferImpactAssessment(ctx context.Context, obj *types.ProcessingActivity) (*types.TransferImpactAssessment, error) { - if err := r.authorize(ctx, obj.ID, probo.ActionTransferImpactAssessmentGet); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, obj.ID.TenantID()) - - tia, err := prb.TransferImpactAssessments.GetByProcessingActivityID(ctx, obj.ID) - if err != nil { - if errors.Is(err, coredata.ErrResourceNotFound) { - return nil, nil - } - r.logger.ErrorCtx(ctx, "cannot get processing activity tia", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return types.NewTransferImpactAssessment(tia), nil -} - -// Permission is the resolver for the permission field. -func (r *processingActivityResolver) Permission(ctx context.Context, obj *types.ProcessingActivity, action string) (bool, error) { - return r.Resolver.Permission(ctx, obj, action) -} - -// TotalCount is the resolver for the totalCount field. -func (r *processingActivityConnectionResolver) TotalCount(ctx context.Context, obj *types.ProcessingActivityConnection) (int, error) { - if err := r.authorize(ctx, obj.ParentID, probo.ActionProcessingActivityList); err != nil { - return 0, err - } - - prb := r.ProboService(ctx, obj.ParentID.TenantID()) - - switch obj.Resolver.(type) { - case *organizationResolver: - processingActivityFilter := coredata.NewProcessingActivityFilter(nil) - if obj.Filter != nil { - processingActivityFilter = coredata.NewProcessingActivityFilter(&obj.Filter.SnapshotID) - } - - count, err := prb.ProcessingActivities.CountForOrganizationID(ctx, obj.ParentID, processingActivityFilter) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot count organization processing activities", log.Error(err)) - return 0, gqlutils.Internal(ctx) - } - return count, nil - } - - r.logger.ErrorCtx(ctx, "unsupported resolver") - return 0, gqlutils.Internal(ctx) -} - -// Permission is the resolver for the permission field. -func (r *profileResolver) Permission(ctx context.Context, obj *types.Profile, action string) (bool, error) { - return r.Resolver.Permission(ctx, obj, action) -} - -// TotalCount is the resolver for the totalCount field. -func (r *profileConnectionResolver) TotalCount(ctx context.Context, obj *types.ProfileConnection) (int, error) { - if err := r.authorize(ctx, obj.ParentID, iam.ActionMembershipProfileList); err != nil { - return 0, err - } - - switch obj.Resolver.(type) { - case *organizationResolver: - count, err := r.iam.OrganizationService.CountProfiles(ctx, obj.ParentID, obj.Filters) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot count profiles", log.Error(err)) - return 0, gqlutils.Internal(ctx) - } - return count, nil - case *documentVersionResolver: - prb := r.ProboService(ctx, obj.ParentID.TenantID()) - count, err := prb.Documents.CountVersionApprovers(ctx, obj.ParentID) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot count document version approvers", log.Error(err)) - return 0, gqlutils.Internal(ctx) - } - return count, nil - } - - r.logger.ErrorCtx(ctx, "unsupported resolver for profile connection", log.String("resolver", fmt.Sprintf("%T", obj.Resolver))) - return 0, gqlutils.Internal(ctx) -} - -// Node is the resolver for the node field. -func (r *queryResolver) Node(ctx context.Context, id gid.GID) (types.Node, error) { - var ( - loadNode func(ctx context.Context, id gid.GID) (types.Node, error) - action string - prb = r.ProboService(ctx, id.TenantID()) - ) - - switch id.EntityType() { - case coredata.OrganizationEntityType: - action = iam.ActionOrganizationGet - loadNode = func(ctx context.Context, id gid.GID) (types.Node, error) { - organization, err := prb.Organizations.Get(ctx, id) - if err != nil { - return nil, err - } - return types.NewOrganization(organization), nil - } - case coredata.VendorEntityType: - action = probo.ActionVendorGet - loadNode = func(ctx context.Context, id gid.GID) (types.Node, error) { - vendor, err := prb.Vendors.Get(ctx, id) - if err != nil { - return nil, err - } - return types.NewVendor(vendor), nil - } - case coredata.FrameworkEntityType: - action = probo.ActionFrameworkGet - loadNode = func(ctx context.Context, id gid.GID) (types.Node, error) { - framework, err := prb.Frameworks.Get(ctx, id) - if err != nil { - return nil, err - } - return types.NewFramework(framework), nil - } - case coredata.MeasureEntityType: - action = probo.ActionMeasureGet - loadNode = func(ctx context.Context, id gid.GID) (types.Node, error) { - measure, err := prb.Measures.Get(ctx, id) - if err != nil { - return nil, err - } - return types.NewMeasure(measure), nil - } - case coredata.TaskEntityType: - action = probo.ActionTaskGet - loadNode = func(ctx context.Context, id gid.GID) (types.Node, error) { - task, err := prb.Tasks.Get(ctx, id) - if err != nil { - return nil, err - } - return types.NewTask(task), nil - } - case coredata.EvidenceEntityType: - action = probo.ActionEvidenceList - loadNode = func(ctx context.Context, id gid.GID) (types.Node, error) { - evidence, err := prb.Evidences.Get(ctx, id) - if err != nil { - return nil, err - } - return types.NewEvidence(evidence), nil - } - case coredata.DocumentEntityType: - action = probo.ActionDocumentGet - loadNode = func(ctx context.Context, id gid.GID) (types.Node, error) { - document, err := prb.Documents.Get(ctx, id) - if err != nil { - return nil, err - } - return types.NewDocument(document), nil - } - case coredata.ControlEntityType: - action = probo.ActionControlList - loadNode = func(ctx context.Context, id gid.GID) (types.Node, error) { - control, err := prb.Controls.Get(ctx, id) - if err != nil { - return nil, err - } - return types.NewControl(control), nil - } - case coredata.RiskEntityType: - action = probo.ActionRiskGet - loadNode = func(ctx context.Context, id gid.GID) (types.Node, error) { - risk, err := prb.Risks.Get(ctx, id) - if err != nil { - return nil, err - } - return types.NewRisk(risk), nil - } - case coredata.VendorComplianceReportEntityType: - action = probo.ActionVendorComplianceReportGet - loadNode = func(ctx context.Context, id gid.GID) (types.Node, error) { - vendorComplianceReport, err := prb.VendorComplianceReports.Get(ctx, id) - if err != nil { - return nil, err - } - return types.NewVendorComplianceReport(vendorComplianceReport), nil - } - case coredata.VendorContactEntityType: - action = probo.ActionVendorContactGet - loadNode = func(ctx context.Context, id gid.GID) (types.Node, error) { - vendorContact, err := prb.VendorContacts.Get(ctx, id) - if err != nil { - return nil, err - } - return types.NewVendorContact(vendorContact), nil - } - case coredata.VendorServiceEntityType: - action = probo.ActionVendorServiceGet - loadNode = func(ctx context.Context, id gid.GID) (types.Node, error) { - vendorService, err := prb.VendorServices.Get(ctx, id) - if err != nil { - return nil, err - } - return types.NewVendorService(vendorService), nil - } - case coredata.DocumentVersionEntityType: - action = probo.ActionDocumentVersionList - loadNode = func(ctx context.Context, id gid.GID) (types.Node, error) { - documentVersion, err := prb.Documents.GetVersion(ctx, id) - if err != nil { - return nil, err - } - return types.NewDocumentVersion(documentVersion), nil - } - case coredata.DocumentVersionSignatureEntityType: - action = probo.ActionDocumentVersionSignatureList - loadNode = func(ctx context.Context, id gid.GID) (types.Node, error) { - documentVersionSignature, err := prb.Documents.GetVersionSignature(ctx, id) - if err != nil { - return nil, err - } - return types.NewDocumentVersionSignature(documentVersionSignature), nil - } - case coredata.AssetEntityType: - action = probo.ActionAssetList - loadNode = func(ctx context.Context, id gid.GID) (types.Node, error) { - asset, err := prb.Assets.Get(ctx, id) - if err != nil { - return nil, err - } - return types.NewAsset(asset), nil - } - case coredata.DatumEntityType: - action = probo.ActionDatumList - loadNode = func(ctx context.Context, id gid.GID) (types.Node, error) { - datum, err := prb.Data.Get(ctx, id) - if err != nil { - return nil, err - } - return types.NewDatum(datum), nil - } - case coredata.AuditEntityType: - action = probo.ActionAuditList - loadNode = func(ctx context.Context, id gid.GID) (types.Node, error) { - audit, err := prb.Audits.Get(ctx, id) - if err != nil { - return nil, err - } - return types.NewAudit(audit), nil - } - case coredata.FindingEntityType: - action = probo.ActionFindingList - loadNode = func(ctx context.Context, id gid.GID) (types.Node, error) { - finding, err := prb.Findings.Get(ctx, id) - if err != nil { - return nil, err - } - return types.NewFinding(finding), nil - } - case coredata.ObligationEntityType: - action = probo.ActionObligationList - loadNode = func(ctx context.Context, id gid.GID) (types.Node, error) { - obligation, err := prb.Obligations.Get(ctx, id) - if err != nil { - return nil, err - } - return types.NewObligation(obligation), nil - } - case coredata.ReportEntityType: - action = probo.ActionReportGet - loadNode = func(ctx context.Context, id gid.GID) (types.Node, error) { - report, err := prb.Reports.Get(ctx, id) - if err != nil { - return nil, err - } - return types.NewReport(report), nil - } - case coredata.ProcessingActivityEntityType: - action = probo.ActionProcessingActivityList - loadNode = func(ctx context.Context, id gid.GID) (types.Node, error) { - processingActivity, err := prb.ProcessingActivities.Get(ctx, id) - if err != nil { - return nil, err - } - return types.NewProcessingActivity(processingActivity), nil - } - case coredata.DataProtectionImpactAssessmentEntityType: - // TODO: add action - // action = probo.ActionDataProtectionImpactAssessmentGet - loadNode = func(ctx context.Context, id gid.GID) (types.Node, error) { - dpia, err := prb.DataProtectionImpactAssessments.Get(ctx, id) - if err != nil { - return nil, err - } - return types.NewDataProtectionImpactAssessment(dpia), nil - } - case coredata.TransferImpactAssessmentEntityType: - // TODO: add action - //action = probo.ActionTransferImpactAssessmentGet - loadNode = func(ctx context.Context, id gid.GID) (types.Node, error) { - tia, err := prb.TransferImpactAssessments.Get(ctx, id) - if err != nil { - return nil, err - } - return types.NewTransferImpactAssessment(tia), nil - } - case coredata.SnapshotEntityType: - action = probo.ActionSnapshotList - loadNode = func(ctx context.Context, id gid.GID) (types.Node, error) { - snapshot, err := prb.Snapshots.Get(ctx, id) - if err != nil { - return nil, err - } - return types.NewSnapshot(snapshot), nil - } - case coredata.TrustCenterEntityType: - action = probo.ActionTrustCenterGet - loadNode = func(ctx context.Context, id gid.GID) (types.Node, error) { - trustCenter, err := prb.TrustCenters.Get(ctx, id) - if err != nil { - return nil, err - } - - var file *coredata.File - if trustCenter.NonDisclosureAgreementFileID != nil { - file, err = prb.Files.Get(ctx, *trustCenter.NonDisclosureAgreementFileID) - if err != nil { - return nil, fmt.Errorf("cannot get NDA file: %w", err) - } - } - - return types.NewTrustCenter(trustCenter, file), nil - } - case coredata.TrustCenterAccessEntityType: - action = probo.ActionTrustCenterAccessGet - loadNode = func(ctx context.Context, id gid.GID) (types.Node, error) { - trustCenterAccess, err := prb.TrustCenterAccesses.Get(ctx, id) - if err != nil { - return nil, err - } - return types.NewTrustCenterAccess(trustCenterAccess), nil - } - case coredata.MeetingEntityType: - action = probo.ActionMeetingGet - loadNode = func(ctx context.Context, id gid.GID) (types.Node, error) { - meeting, err := prb.Meetings.Get(ctx, id) - if err != nil { - return nil, err - } - return types.NewMeeting(meeting), nil - } - case coredata.RightsRequestEntityType: - action = probo.ActionRightsRequestGet - loadNode = func(ctx context.Context, id gid.GID) (types.Node, error) { - rightsRequest, err := prb.RightsRequests.Get(ctx, id) - if err != nil { - return nil, err - } - return types.NewRightsRequest(rightsRequest), nil - } - case coredata.StatementOfApplicabilityEntityType: - action = probo.ActionStatementOfApplicabilityGet - loadNode = func(ctx context.Context, id gid.GID) (types.Node, error) { - statementOfApplicability, err := prb.StatementsOfApplicability.Get(ctx, id) - if err != nil { - return nil, err - } - return types.NewStatementOfApplicability(statementOfApplicability), nil - } - case coredata.WebhookSubscriptionEntityType: - action = probo.ActionWebhookSubscriptionGet - loadNode = func(ctx context.Context, id gid.GID) (types.Node, error) { - wc, err := prb.WebhookSubscriptions.Get(ctx, id) - if err != nil { - return nil, err - } - return types.NewWebhookSubscription(wc), nil - } - case coredata.AccessReviewCampaignEntityType: - action = probo.ActionAccessReviewCampaignGet - loadNode = func(ctx context.Context, id gid.GID) (types.Node, error) { - scope := coredata.NewScopeFromObjectID(id) - campaign, err := r.accessReview.Campaigns(scope).Get(ctx, id) - if err != nil { - return nil, err - } - return types.NewAccessReviewCampaign(campaign), nil - } - case coredata.AccessSourceEntityType: - action = probo.ActionAccessSourceGet - loadNode = func(ctx context.Context, id gid.GID) (types.Node, error) { - scope := coredata.NewScopeFromObjectID(id) - source, err := r.accessReview.Sources(scope).Get(ctx, id) - if err != nil { - return nil, err - } - return types.NewAccessSource(source), nil - } - case coredata.AccessEntryEntityType: - action = probo.ActionAccessEntryGet - loadNode = func(ctx context.Context, id gid.GID) (types.Node, error) { - scope := coredata.NewScopeFromObjectID(id) - entry, err := r.accessReview.Entries(scope).Get(ctx, id) - if err != nil { - return nil, err - } - return types.NewAccessEntry(entry), nil - } - default: - } - - if err := r.authorize(ctx, id, action); err != nil { - return nil, err - } - - node, err := loadNode(ctx, id) - if err != nil { - if errors.Is(err, coredata.ErrResourceNotFound) { - return nil, gqlutils.NotFound(ctx, err) - } - - r.logger.ErrorCtx(ctx, "cannot load node", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return node, nil -} - -// Viewer is the resolver for the viewer field. -func (r *queryResolver) Viewer(ctx context.Context) (*types.Viewer, error) { - identity := authn.IdentityFromContext(ctx) - - session := authn.SessionFromContext(ctx) - apiKey := authn.APIKeyFromContext(ctx) - - var viewerID gid.GID - if session != nil { - viewerID = session.ID - } else if apiKey != nil { - viewerID = apiKey.ID - } else { - viewerID = identity.ID - } - - return &types.Viewer{ID: viewerID}, nil -} - -// DownloadURL is the resolver for the downloadUrl field. -func (r *reportResolver) DownloadURL(ctx context.Context, obj *types.Report) (*string, error) { - if err := r.authorize(ctx, obj.ID, probo.ActionReportDownloadUrlGet); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, obj.ID.TenantID()) - - url, err := prb.Reports.GenerateDownloadURL(ctx, obj.ID, 15*time.Minute) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot generate download URL", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return url, nil -} - -// Audit is the resolver for the audit field. -func (r *reportResolver) Audit(ctx context.Context, obj *types.Report) (*types.Audit, error) { - if err := r.authorize(ctx, obj.ID, probo.ActionAuditGet); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, obj.ID.TenantID()) - - audit, err := prb.Audits.GetByReportID(ctx, obj.ID) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot load audit for report", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return types.NewAudit(audit), nil -} - -// Permission is the resolver for the permission field. -func (r *reportResolver) Permission(ctx context.Context, obj *types.Report, action string) (bool, error) { - return r.Resolver.Permission(ctx, obj, action) -} - -// Organization is the resolver for the organization field. -func (r *rightsRequestResolver) Organization(ctx context.Context, obj *types.RightsRequest) (*types.Organization, error) { - if err := r.authorize(ctx, obj.ID, iam.ActionOrganizationGet); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, obj.ID.TenantID()) - - rightsRequest, err := prb.RightsRequests.Get(ctx, obj.ID) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot get rights request", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - organization, err := prb.Organizations.Get(ctx, rightsRequest.OrganizationID) - if err != nil { - if errors.Is(err, coredata.ErrResourceNotFound) { - return nil, gqlutils.NotFound(ctx, err) - } - r.logger.ErrorCtx(ctx, "cannot get organization", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return types.NewOrganization(organization), nil -} - -// Permission is the resolver for the permission field. -func (r *rightsRequestResolver) Permission(ctx context.Context, obj *types.RightsRequest, action string) (bool, error) { - return r.Resolver.Permission(ctx, obj, action) -} - -// TotalCount is the resolver for the totalCount field. -func (r *rightsRequestConnectionResolver) TotalCount(ctx context.Context, obj *types.RightsRequestConnection) (int, error) { - if err := r.authorize(ctx, obj.ParentID, probo.ActionRightsRequestList); err != nil { - return 0, err - } - - prb := r.ProboService(ctx, obj.ParentID.TenantID()) - - switch obj.Resolver.(type) { - case *organizationResolver: - count, err := prb.RightsRequests.CountByOrganizationID(ctx, obj.ParentID) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot count rights requests", log.Error(err)) - return 0, gqlutils.Internal(ctx) - } - - return count, nil - default: - r.logger.ErrorCtx(ctx, "unsupported resolver type for RightsRequestConnection") - return 0, gqlutils.Internal(ctx) - } -} - -// Owner is the resolver for the owner field. -func (r *riskResolver) Owner(ctx context.Context, obj *types.Risk) (*types.Profile, error) { - if err := r.authorize(ctx, obj.ID, iam.ActionMembershipProfileGet); err != nil { - return nil, err - } - - if obj.Owner == nil { - return nil, nil - } - - loaders := dataloader.FromContext(ctx) - - owner, err := loaders.Profile.Load(ctx, obj.Owner.ID) - if err != nil { - if errors.Is(err, coredata.ErrResourceNotFound) || errors.Is(err, dataloadgen.ErrNotFound) { - return nil, gqlutils.NotFound(ctx, err) - } - - r.logger.ErrorCtx(ctx, "cannot get owner", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return types.NewProfile(owner), nil -} - -// Organization is the resolver for the organization field. -func (r *riskResolver) Organization(ctx context.Context, obj *types.Risk) (*types.Organization, error) { - if err := r.authorize(ctx, obj.ID, probo.ActionOrganizationGet); err != nil { - return nil, err - } - - loaders := dataloader.FromContext(ctx) - - organization, err := loaders.Organization.Load(ctx, obj.Organization.ID) - if err != nil { - if errors.Is(err, coredata.ErrResourceNotFound) || errors.Is(err, dataloadgen.ErrNotFound) { - return nil, gqlutils.NotFound(ctx, err) - } - - r.logger.ErrorCtx(ctx, "cannot get organization", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return types.NewOrganization(organization), nil -} - -// Measures is the resolver for the measures field. -func (r *riskResolver) Measures(ctx context.Context, obj *types.Risk, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.MeasureOrderBy, filter *types.MeasureFilter) (*types.MeasureConnection, error) { - if err := r.authorize(ctx, obj.ID, probo.ActionMeasureList); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, obj.ID.TenantID()) - - pageOrderBy := page.OrderBy[coredata.MeasureOrderField]{ - Field: coredata.MeasureOrderFieldCreatedAt, - Direction: page.OrderDirectionDesc, - } - if orderBy != nil { - pageOrderBy = page.OrderBy[coredata.MeasureOrderField]{ - Field: orderBy.Field, - Direction: orderBy.Direction, - } - } - - cursor := types.NewCursor(first, after, last, before, pageOrderBy) - - var measureFilter = coredata.NewMeasureFilter(nil, nil, nil) - if filter != nil { - measureFilter = coredata.NewMeasureFilter(filter.Query, filter.State, filter.Category) - } - - page, err := prb.Measures.ListForRiskID(ctx, obj.ID, cursor, measureFilter) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot list risk measures", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return types.NewMeasureConnection(page, r, obj.ID, measureFilter), nil -} - -// Documents is the resolver for the documents field. -func (r *riskResolver) Documents(ctx context.Context, obj *types.Risk, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.DocumentOrderBy, filter *types.DocumentFilter) (*types.DocumentConnection, error) { - if err := r.authorize(ctx, obj.ID, probo.ActionDocumentList); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, obj.ID.TenantID()) - - pageOrderBy := page.OrderBy[coredata.DocumentOrderField]{ - Field: coredata.DocumentOrderFieldCreatedAt, - Direction: page.OrderDirectionDesc, - } - if orderBy != nil { - pageOrderBy = page.OrderBy[coredata.DocumentOrderField]{ - Field: orderBy.Field, - Direction: orderBy.Direction, - } - } - - cursor := types.NewCursor(first, after, last, before, pageOrderBy) - - var documentFilter = coredata.NewDocumentFilter(nil) - if filter != nil { - documentFilter = coredata.NewDocumentFilter(filter.Query). - WithDocumentTypes(filter.DocumentTypes). - WithClassifications(filter.Classifications) - } - - page, err := prb.Documents.ListForRiskID(ctx, obj.ID, cursor, documentFilter) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot list risk documents", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return types.NewDocumentConnection(page, r, obj.ID, documentFilter), nil -} - -// Controls is the resolver for the controls field. -func (r *riskResolver) Controls(ctx context.Context, obj *types.Risk, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.ControlOrderBy, filter *types.ControlFilter) (*types.ControlConnection, error) { - if err := r.authorize(ctx, obj.ID, probo.ActionControlList); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, obj.ID.TenantID()) - - pageOrderBy := page.OrderBy[coredata.ControlOrderField]{ - Field: coredata.ControlOrderFieldCreatedAt, - Direction: page.OrderDirectionDesc, - } - if orderBy != nil { - pageOrderBy = page.OrderBy[coredata.ControlOrderField]{ - Field: orderBy.Field, - Direction: orderBy.Direction, - } - } - - cursor := types.NewCursor(first, after, last, before, pageOrderBy) - var filters = coredata.NewControlFilter(nil) - if filter != nil { - filters = coredata.NewControlFilter(filter.Query) - } - - page, err := prb.Controls.ListForRiskID(ctx, obj.ID, cursor, filters) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot list risk controls", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return types.NewControlConnection(page, r, obj.ID, filters), nil -} - -// Obligations is the resolver for the obligations field. -func (r *riskResolver) Obligations(ctx context.Context, obj *types.Risk, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.ObligationOrderBy, filter *types.ObligationFilter) (*types.ObligationConnection, error) { - if err := r.authorize(ctx, obj.ID, probo.ActionObligationList); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, obj.ID.TenantID()) - - pageOrderBy := page.OrderBy[coredata.ObligationOrderField]{ - Field: coredata.ObligationOrderFieldCreatedAt, - Direction: page.OrderDirectionDesc, - } - if orderBy != nil { - pageOrderBy = page.OrderBy[coredata.ObligationOrderField]{ - Field: orderBy.Field, - Direction: orderBy.Direction, - } - } - - cursor := types.NewCursor(first, after, last, before, pageOrderBy) - - var obligationFilter = coredata.NewObligationFilter(nil) - if filter != nil { - obligationFilter = coredata.NewObligationFilter(&filter.SnapshotID) - } - - page, err := prb.Obligations.ListForRiskID(ctx, obj.ID, cursor, obligationFilter) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot list risk obligations", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return types.NewObligationConnection(page, r, obj.ID, filter), nil -} - -// Permission is the resolver for the permission field. -func (r *riskResolver) Permission(ctx context.Context, obj *types.Risk, action string) (bool, error) { - return r.Resolver.Permission(ctx, obj, action) -} - -// TotalCount is the resolver for the totalCount field. -func (r *riskConnectionResolver) TotalCount(ctx context.Context, obj *types.RiskConnection) (int, error) { - if err := r.authorize(ctx, obj.ParentID, probo.ActionRiskList); err != nil { - return 0, err - } - - prb := r.ProboService(ctx, obj.ParentID.TenantID()) - - switch obj.Resolver.(type) { - case *measureResolver: - count, err := prb.Risks.CountForMeasureID(ctx, obj.ParentID, obj.Filters) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot count risks", log.Error(err)) - return 0, gqlutils.Internal(ctx) - } - return count, nil - case *organizationResolver: - count, err := prb.Risks.CountForOrganizationID(ctx, obj.ParentID, obj.Filters) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot count risks", log.Error(err)) - return 0, gqlutils.Internal(ctx) - } - return count, nil - } - - r.logger.ErrorCtx(ctx, "unsupported resolver") - return 0, gqlutils.Internal(ctx) -} - -// Permission is the resolver for the permission field. -func (r *slackConnectionResolver) Permission(ctx context.Context, obj *types.SlackConnection, action string) (bool, error) { - return r.Resolver.Permission(ctx, obj, action) -} - -// Organization is the resolver for the organization field. -func (r *snapshotResolver) Organization(ctx context.Context, obj *types.Snapshot) (*types.Organization, error) { - if err := r.authorize(ctx, obj.ID, probo.ActionOrganizationGet); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, obj.ID.TenantID()) - - snapshot, err := prb.Snapshots.Get(ctx, obj.ID) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot get snapshot", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - organization, err := prb.Organizations.Get(ctx, snapshot.OrganizationID) - if err != nil { - if errors.Is(err, coredata.ErrResourceNotFound) { - return nil, gqlutils.NotFound(ctx, err) - } - - r.logger.ErrorCtx(ctx, "cannot get organization", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return types.NewOrganization(organization), nil -} - -// Controls is the resolver for the controls field. -func (r *snapshotResolver) Controls(ctx context.Context, obj *types.Snapshot, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.ControlOrderBy, filter *types.ControlFilter) (*types.ControlConnection, error) { - if err := r.authorize(ctx, obj.ID, probo.ActionControlList); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, obj.ID.TenantID()) - - pageOrderBy := page.OrderBy[coredata.ControlOrderField]{ - Field: coredata.ControlOrderFieldCreatedAt, - Direction: page.OrderDirectionDesc, - } - if orderBy != nil { - pageOrderBy = page.OrderBy[coredata.ControlOrderField]{ - Field: orderBy.Field, - Direction: orderBy.Direction, - } - } - - cursor := types.NewCursor(first, after, last, before, pageOrderBy) - - var controlFilter = coredata.NewControlFilter(nil) - if filter != nil { - controlFilter = coredata.NewControlFilter(filter.Query) - } - - page, err := prb.Controls.ListForSnapshotID(ctx, obj.ID, cursor, controlFilter) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot list snapshot controls", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return types.NewControlConnection(page, r, obj.ID, controlFilter), nil -} - -// Permission is the resolver for the permission field. -func (r *snapshotResolver) Permission(ctx context.Context, obj *types.Snapshot, action string) (bool, error) { - return r.Resolver.Permission(ctx, obj, action) -} - -// TotalCount is the resolver for the totalCount field. -func (r *snapshotConnectionResolver) TotalCount(ctx context.Context, obj *types.SnapshotConnection) (int, error) { - if err := r.authorize(ctx, obj.ParentID, probo.ActionSnapshotList); err != nil { - return 0, err - } - - prb := r.ProboService(ctx, obj.ParentID.TenantID()) - - switch obj.Resolver.(type) { - case *organizationResolver: - count, err := prb.Snapshots.CountForOrganizationID(ctx, obj.ParentID) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot count snapshots", log.Error(err)) - return 0, gqlutils.Internal(ctx) - } - return count, nil - } - - r.logger.ErrorCtx(ctx, "unsupported resolver") - return 0, gqlutils.Internal(ctx) -} - -// Organization is the resolver for the organization field. -func (r *statementOfApplicabilityResolver) Organization(ctx context.Context, obj *types.StatementOfApplicability) (*types.Organization, error) { - if err := r.authorize(ctx, obj.ID, probo.ActionOrganizationGet); err != nil { - return nil, err - } - - loaders := dataloader.FromContext(ctx) - - organization, err := loaders.Organization.Load(ctx, obj.Organization.ID) - if err != nil { - if errors.Is(err, coredata.ErrResourceNotFound) || errors.Is(err, dataloadgen.ErrNotFound) { - return nil, gqlutils.NotFound(ctx, err) - } - r.logger.ErrorCtx(ctx, "cannot load organization", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return types.NewOrganization(organization), nil -} - -// Owner is the resolver for the owner field. -func (r *statementOfApplicabilityResolver) Owner(ctx context.Context, obj *types.StatementOfApplicability) (*types.Profile, error) { - if err := r.authorize(ctx, obj.ID, iam.ActionMembershipProfileGet); err != nil { - return nil, err - } - - loaders := dataloader.FromContext(ctx) - - owner, err := loaders.Profile.Load(ctx, obj.Owner.ID) - if err != nil { - if errors.Is(err, coredata.ErrResourceNotFound) || errors.Is(err, dataloadgen.ErrNotFound) { - return nil, gqlutils.NotFound(ctx, err) - } - r.logger.ErrorCtx(ctx, "cannot load owner", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return types.NewProfile(owner), nil -} - -// ApplicabilityStatements is the resolver for the applicabilityStatements field. -func (r *statementOfApplicabilityResolver) ApplicabilityStatements(ctx context.Context, obj *types.StatementOfApplicability, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.ApplicabilityStatementOrderBy) (*types.ApplicabilityStatementConnection, error) { - if err := r.authorize(ctx, obj.ID, probo.ActionApplicabilityStatementList); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, obj.ID.TenantID()) - - pageOrderBy := page.OrderBy[coredata.ApplicabilityStatementOrderField]{ - Field: coredata.ApplicabilityStatementOrderFieldCreatedAt, - Direction: page.OrderDirectionAsc, - } - if orderBy != nil { - pageOrderBy = page.OrderBy[coredata.ApplicabilityStatementOrderField]{ - Field: coredata.ApplicabilityStatementOrderField(orderBy.Field), - Direction: orderBy.Direction, - } - } - - cursor := types.NewCursor(first, after, last, before, pageOrderBy) - - p, err := prb.StatementsOfApplicability.ListApplicabilityStatements(ctx, obj.ID, cursor) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot list applicability statements", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return types.NewApplicabilityStatementConnection(p, r, obj.ID), nil -} - -// Permission is the resolver for the permission field. -func (r *statementOfApplicabilityResolver) Permission(ctx context.Context, obj *types.StatementOfApplicability, action string) (bool, error) { - return r.Resolver.Permission(ctx, obj, action) -} - -// TotalCount is the resolver for the totalCount field. -func (r *statementOfApplicabilityConnectionResolver) TotalCount(ctx context.Context, obj *types.StatementOfApplicabilityConnection) (int, error) { - prb := r.ProboService(ctx, obj.ParentID.TenantID()) - - switch obj.Resolver.(type) { - case *organizationResolver: - count, err := prb.StatementsOfApplicability.CountForOrganizationID(ctx, obj.ParentID, obj.Filters) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot count statements_of_applicability", log.Error(err)) - return 0, gqlutils.Internal(ctx) - } - return count, nil - } - - r.logger.ErrorCtx(ctx, "unsupported resolver") - return 0, gqlutils.Internal(ctx) -} - -// AssignedTo is the resolver for the assignedTo field. -func (r *taskResolver) AssignedTo(ctx context.Context, obj *types.Task) (*types.Profile, error) { - if err := r.authorize(ctx, obj.ID, iam.ActionMembershipProfileGet); err != nil { - return nil, err - } - - if obj.AssignedTo == nil { - return nil, nil - } - - loaders := dataloader.FromContext(ctx) - - assignee, err := loaders.Profile.Load(ctx, obj.AssignedTo.ID) - if err != nil { - if errors.Is(err, coredata.ErrResourceNotFound) || errors.Is(err, dataloadgen.ErrNotFound) { - return nil, gqlutils.NotFound(ctx, err) - } - - r.logger.ErrorCtx(ctx, "cannot get assigned to", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return types.NewProfile(assignee), nil -} - -// Organization is the resolver for the organization field. -func (r *taskResolver) Organization(ctx context.Context, obj *types.Task) (*types.Organization, error) { - if err := r.authorize(ctx, obj.ID, probo.ActionOrganizationGet); err != nil { - return nil, err - } - - loaders := dataloader.FromContext(ctx) - - organization, err := loaders.Organization.Load(ctx, obj.Organization.ID) - if err != nil { - if errors.Is(err, coredata.ErrResourceNotFound) || errors.Is(err, dataloadgen.ErrNotFound) { - return nil, gqlutils.NotFound(ctx, err) - } - - r.logger.ErrorCtx(ctx, "cannot get organization", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return types.NewOrganization(organization), nil -} - -// Measure is the resolver for the measure field. -func (r *taskResolver) Measure(ctx context.Context, obj *types.Task) (*types.Measure, error) { - if err := r.authorize(ctx, obj.ID, probo.ActionMeasureGet); err != nil { - return nil, err - } - - if obj.Measure == nil { - return nil, nil - } - - loaders := dataloader.FromContext(ctx) - - measure, err := loaders.Measure.Load(ctx, obj.Measure.ID) - if err != nil { - if errors.Is(err, coredata.ErrResourceNotFound) || errors.Is(err, dataloadgen.ErrNotFound) { - return nil, gqlutils.NotFound(ctx, err) - } - - r.logger.ErrorCtx(ctx, "cannot get measure", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return types.NewMeasure(measure), nil -} - -// Evidences is the resolver for the evidences field. -func (r *taskResolver) Evidences(ctx context.Context, obj *types.Task, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.EvidenceOrderBy) (*types.EvidenceConnection, error) { - if err := r.authorize(ctx, obj.ID, probo.ActionEvidenceList); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, obj.ID.TenantID()) - - pageOrderBy := page.OrderBy[coredata.EvidenceOrderField]{ - Field: coredata.EvidenceOrderFieldCreatedAt, - Direction: page.OrderDirectionDesc, - } - if orderBy != nil { - pageOrderBy = page.OrderBy[coredata.EvidenceOrderField]{ - Field: orderBy.Field, - Direction: orderBy.Direction, - } - } - - cursor := types.NewCursor(first, after, last, before, pageOrderBy) - page, err := prb.Evidences.ListForTaskID(ctx, obj.ID, cursor) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot list task evidences", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return types.NewEvidenceConnection(page, r, obj.ID), nil -} - -// Permission is the resolver for the permission field. -func (r *taskResolver) Permission(ctx context.Context, obj *types.Task, action string) (bool, error) { - return r.Resolver.Permission(ctx, obj, action) -} - -// TotalCount is the resolver for the totalCount field. -func (r *taskConnectionResolver) TotalCount(ctx context.Context, obj *types.TaskConnection) (int, error) { - if err := r.authorize(ctx, obj.ParentID, probo.ActionTaskList); err != nil { - return 0, err - } - - prb := r.ProboService(ctx, obj.ParentID.TenantID()) - - switch obj.Resolver.(type) { - case *measureResolver: - count, err := prb.Tasks.CountForMeasureID(ctx, obj.ParentID) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot count tasks", log.Error(err)) - return 0, gqlutils.Internal(ctx) - } - return count, nil - case *organizationResolver: - count, err := prb.Tasks.CountForOrganizationID(ctx, obj.ParentID) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot count tasks", log.Error(err)) - return 0, gqlutils.Internal(ctx) - } - return count, nil - } - - r.logger.ErrorCtx(ctx, "unsupported resolver") - return 0, gqlutils.Internal(ctx) -} - -// ProcessingActivity is the resolver for the processingActivity field. -func (r *transferImpactAssessmentResolver) ProcessingActivity(ctx context.Context, obj *types.TransferImpactAssessment) (*types.ProcessingActivity, error) { - if err := r.authorize(ctx, obj.ID, probo.ActionProcessingActivityGet); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, obj.ID.TenantID()) - - processingActivity, err := prb.ProcessingActivities.Get(ctx, obj.ProcessingActivity.ID) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot get processing activity", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return types.NewProcessingActivity(processingActivity), nil -} - -// Organization is the resolver for the organization field. -func (r *transferImpactAssessmentResolver) Organization(ctx context.Context, obj *types.TransferImpactAssessment) (*types.Organization, error) { - if err := r.authorize(ctx, obj.ID, probo.ActionOrganizationGet); err != nil { - return nil, err - } - - loaders := dataloader.FromContext(ctx) - - organization, err := loaders.Organization.Load(ctx, obj.Organization.ID) - if err != nil { - if errors.Is(err, coredata.ErrResourceNotFound) || errors.Is(err, dataloadgen.ErrNotFound) { - return nil, gqlutils.NotFound(ctx, err) - } - - r.logger.ErrorCtx(ctx, "cannot get organization", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return types.NewOrganization(organization), nil -} - -// Permission is the resolver for the permission field. -func (r *transferImpactAssessmentResolver) Permission(ctx context.Context, obj *types.TransferImpactAssessment, action string) (bool, error) { - return r.Resolver.Permission(ctx, obj, action) -} - -// TotalCount is the resolver for the totalCount field. -func (r *transferImpactAssessmentConnectionResolver) TotalCount(ctx context.Context, obj *types.TransferImpactAssessmentConnection) (int, error) { - if err := r.authorize(ctx, obj.ParentID, probo.ActionTransferImpactAssessmentList); err != nil { - return 0, err - } - - prb := r.ProboService(ctx, obj.ParentID.TenantID()) - - switch obj.Resolver.(type) { - case *organizationResolver: - count, err := prb.TransferImpactAssessments.CountForOrganizationID(ctx, obj.ParentID, obj.Filter) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot count organization transfer impact assessments", log.Error(err)) - return 0, gqlutils.Internal(ctx) - } - return count, nil - } - - r.logger.ErrorCtx(ctx, "unsupported resolver") - return 0, gqlutils.Internal(ctx) -} - -// LogoFileURL is the resolver for the logoFileUrl field. -func (r *trustCenterResolver) LogoFileURL(ctx context.Context, obj *types.TrustCenter) (*string, error) { - if err := r.authorize(ctx, obj.ID, probo.ActionTrustCenterGet); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, obj.ID.TenantID()) - - logoURL, err := prb.TrustCenters.GenerateLogoURL(ctx, obj.ID, 1*time.Hour) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot generate logo url", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return logoURL, nil -} - -// DarkLogoFileURL is the resolver for the darkLogoFileUrl field. -func (r *trustCenterResolver) DarkLogoFileURL(ctx context.Context, obj *types.TrustCenter) (*string, error) { - if err := r.authorize(ctx, obj.ID, probo.ActionTrustCenterGet); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, obj.ID.TenantID()) - - logoURL, err := prb.TrustCenters.GenerateDarkLogoURL(ctx, obj.ID, 1*time.Hour) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot generate logo url", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return logoURL, nil -} - -// NdaFileURL is the resolver for the ndaFileUrl field. -func (r *trustCenterResolver) NdaFileURL(ctx context.Context, obj *types.TrustCenter) (*string, error) { - hasPermission, err := r.Resolver.Permission(ctx, obj, probo.ActionTrustCenterGetNda) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot authorize", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - if !hasPermission { - return nil, nil - } - - prb := r.ProboService(ctx, obj.ID.TenantID()) - - fileURL, err := prb.TrustCenters.GenerateNDAFileURL(ctx, obj.ID, 15*time.Minute) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot generate NDA file URL", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return fileURL, nil -} - -// Organization is the resolver for the organization field. -func (r *trustCenterResolver) Organization(ctx context.Context, obj *types.TrustCenter) (*types.Organization, error) { - if err := r.authorize(ctx, obj.ID, probo.ActionOrganizationGet); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, obj.ID.TenantID()) - - trustCenter, err := prb.TrustCenters.Get(ctx, obj.ID) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot get trust center", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - organization, err := prb.Organizations.Get(ctx, trustCenter.OrganizationID) - if err != nil { - if errors.Is(err, coredata.ErrResourceNotFound) { - return nil, gqlutils.NotFound(ctx, err) - } - - r.logger.ErrorCtx(ctx, "cannot get organization", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return types.NewOrganization(organization), nil -} - -// Accesses is the resolver for the accesses field. -func (r *trustCenterResolver) Accesses(ctx context.Context, obj *types.TrustCenter, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.OrderBy[coredata.TrustCenterAccessOrderField]) (*types.TrustCenterAccessConnection, error) { - if err := r.authorize(ctx, obj.ID, probo.ActionTrustCenterAccessList); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, obj.ID.TenantID()) - - pageOrderBy := page.OrderBy[coredata.TrustCenterAccessOrderField]{ - Field: coredata.TrustCenterAccessOrderFieldCreatedAt, - Direction: page.OrderDirectionDesc, - } - if orderBy != nil { - pageOrderBy = page.OrderBy[coredata.TrustCenterAccessOrderField]{ - Field: orderBy.Field, - Direction: orderBy.Direction, - } - } - - cursor := types.NewCursor(first, after, last, before, pageOrderBy) - - result, err := prb.TrustCenterAccesses.ListForTrustCenterID(ctx, obj.ID, cursor) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot list trust center accesses", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return types.NewTrustCenterAccessConnection(result), nil -} - -// References is the resolver for the references field. -func (r *trustCenterResolver) References(ctx context.Context, obj *types.TrustCenter, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.OrderBy[coredata.TrustCenterReferenceOrderField]) (*types.TrustCenterReferenceConnection, error) { - if err := r.authorize(ctx, obj.ID, probo.ActionTrustCenterReferenceList); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, obj.ID.TenantID()) - - pageOrderBy := page.OrderBy[coredata.TrustCenterReferenceOrderField]{ - Field: coredata.TrustCenterReferenceOrderFieldRank, - Direction: page.OrderDirectionAsc, - } - if orderBy != nil { - pageOrderBy = page.OrderBy[coredata.TrustCenterReferenceOrderField]{ - Field: orderBy.Field, - Direction: orderBy.Direction, - } - } - - cursor := types.NewCursor(first, after, last, before, pageOrderBy) - - result, err := prb.TrustCenterReferences.ListForTrustCenterID(ctx, obj.ID, cursor) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot list trust center references", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return types.NewTrustCenterReferenceConnection(result, obj.ID), nil -} - -// ComplianceFrameworks is the resolver for the complianceFrameworks field. -func (r *trustCenterResolver) ComplianceFrameworks(ctx context.Context, obj *types.TrustCenter, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.OrderBy[coredata.ComplianceFrameworkOrderField]) (*types.ComplianceFrameworkConnection, error) { - if err := r.authorize(ctx, obj.ID, probo.ActionComplianceFrameworkList); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, obj.ID.TenantID()) - - pageOrderBy := page.OrderBy[coredata.ComplianceFrameworkOrderField]{ - Field: coredata.ComplianceFrameworkOrderFieldRank, - Direction: page.OrderDirectionAsc, - } - if orderBy != nil { - pageOrderBy = page.OrderBy[coredata.ComplianceFrameworkOrderField]{ - Field: orderBy.Field, - Direction: orderBy.Direction, - } - } - - cursor := types.NewCursor(first, after, last, before, pageOrderBy) - - result, err := prb.ComplianceFrameworks.ListWithHiddenForTrustCenterID(ctx, obj.ID, cursor) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot list compliance frameworks", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return types.NewComplianceFrameworkConnection(result), nil -} - -// ExternalUrls is the resolver for the externalUrls field. -func (r *trustCenterResolver) ExternalUrls(ctx context.Context, obj *types.TrustCenter, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.OrderBy[coredata.ComplianceExternalURLOrderField]) (*types.ComplianceExternalURLConnection, error) { - if err := r.authorize(ctx, obj.ID, probo.ActionComplianceExternalURLList); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, obj.ID.TenantID()) - - pageOrderBy := page.OrderBy[coredata.ComplianceExternalURLOrderField]{ - Field: coredata.ComplianceExternalURLOrderFieldRank, - Direction: page.OrderDirectionAsc, - } - if orderBy != nil { - pageOrderBy = page.OrderBy[coredata.ComplianceExternalURLOrderField]{ - Field: orderBy.Field, - Direction: orderBy.Direction, - } - } - - cursor := types.NewCursor(first, after, last, before, pageOrderBy) - - result, err := prb.ComplianceExternalURLs.List(ctx, obj.ID, cursor) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot list compliance external URLs", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return types.NewComplianceExternalURLConnection(result), nil -} - -// MailingList is the resolver for the mailingList field. -func (r *trustCenterResolver) MailingList(ctx context.Context, obj *types.TrustCenter) (*types.MailingList, error) { - if err := r.authorize(ctx, obj.ID, probo.ActionMailingListSubscriberList); err != nil { - return nil, err - } - - if obj.MailingList != nil { - return obj.MailingList, nil - } - - prb := r.ProboService(ctx, obj.ID.TenantID()) - - ml, err := prb.TrustCenters.GetMailingList(ctx, obj.ID) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot get mailing list for trust center", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - if ml == nil { - return nil, nil - } - - return types.NewMailingList(ml), nil -} - -// Permission is the resolver for the permission field. -func (r *trustCenterResolver) Permission(ctx context.Context, obj *types.TrustCenter, action string) (bool, error) { - return r.Resolver.Permission(ctx, obj, action) -} - -// NdaSignature is the resolver for the ndaSignature field. -func (r *trustCenterAccessResolver) NdaSignature(ctx context.Context, obj *types.TrustCenterAccess) (*types.ElectronicSignature, error) { - if err := r.authorize(ctx, obj.ID, probo.ActionTrustCenterAccessGet); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, obj.ID.TenantID()) - - access, err := prb.TrustCenterAccesses.Get(ctx, obj.ID) - if err != nil { - return nil, fmt.Errorf("cannot load trust center access: %w", err) - } - - if access.ElectronicSignatureID == nil { - return nil, nil - } - - sig, err := r.esign.GetSignatureByID(ctx, *access.ElectronicSignatureID) - if err != nil { - return nil, nil - } - - return types.NewElectronicSignature(sig), nil -} - -// PendingRequestCount is the resolver for the pendingRequestCount field. -func (r *trustCenterAccessResolver) PendingRequestCount(ctx context.Context, obj *types.TrustCenterAccess) (int, error) { - if err := r.authorize(ctx, obj.ID, probo.ActionTrustCenterAccessGet); err != nil { - return 0, err - } - - prb := r.ProboService(ctx, obj.ID.TenantID()) - - count, err := prb.TrustCenterAccesses.CountPendingRequestDocumentAccesses(ctx, obj.ID) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot count pending request document accesses", log.Error(err)) - return 0, gqlutils.Internal(ctx) - } - - return count, nil -} - -// ActiveCount is the resolver for the activeCount field. -func (r *trustCenterAccessResolver) ActiveCount(ctx context.Context, obj *types.TrustCenterAccess) (int, error) { - if err := r.authorize(ctx, obj.ID, probo.ActionTrustCenterAccessGet); err != nil { - return 0, err - } - prb := r.ProboService(ctx, obj.ID.TenantID()) - - count, err := prb.TrustCenterAccesses.CountActiveDocumentAccesses(ctx, obj.ID) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot count active document accesses", log.Error(err)) - return 0, gqlutils.Internal(ctx) - } - - return count, nil -} - -// Profile is the resolver for the profile field. -func (r *trustCenterAccessResolver) Profile(ctx context.Context, obj *types.TrustCenterAccess) (*types.Profile, error) { - if err := r.authorize(ctx, obj.ID, iam.ActionMembershipProfileGet); err != nil { - return nil, err - } - - profile, err := r.iam.OrganizationService.GetProfileForIdentityAndOrganization(ctx, obj.IdentityID, obj.OrganizationID) - if err != nil { - if _, ok := errors.AsType[*iam.ErrProfileNotFound](err); ok { - return nil, gqlutils.NotFound(ctx, err) - } - - r.logger.ErrorCtx(ctx, "cannot get profile", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return types.NewProfile(profile), nil -} - -// AvailableDocumentAccesses is the resolver for the availableDocumentAccesses field. -func (r *trustCenterAccessResolver) AvailableDocumentAccesses(ctx context.Context, obj *types.TrustCenterAccess, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.OrderBy[coredata.TrustCenterDocumentAccessOrderField]) (*types.TrustCenterDocumentAccessConnection, error) { - if err := r.authorize(ctx, obj.ID, probo.ActionTrustCenterAccessGet); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, obj.ID.TenantID()) - - pageOrderBy := page.OrderBy[coredata.TrustCenterDocumentAccessOrderField]{ - Field: coredata.TrustCenterDocumentAccessOrderFieldCreatedAt, - Direction: page.OrderDirectionDesc, - } - if orderBy != nil { - pageOrderBy = page.OrderBy[coredata.TrustCenterDocumentAccessOrderField]{ - Field: orderBy.Field, - Direction: orderBy.Direction, - } - } - - cursor := types.NewCursor(first, after, last, before, pageOrderBy) - - result, err := prb.TrustCenterAccesses.ListAvailableDocumentAccesses(ctx, obj.ID, cursor) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot list trust center document accesses", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return types.NewTrustCenterDocumentAccessConnection(result, obj, obj.ID), nil -} - -// Permission is the resolver for the permission field. -func (r *trustCenterAccessResolver) Permission(ctx context.Context, obj *types.TrustCenterAccess, action string) (bool, error) { - return r.Resolver.Permission(ctx, obj, action) -} - -// Document is the resolver for the document field. -func (r *trustCenterDocumentAccessResolver) Document(ctx context.Context, obj *types.TrustCenterDocumentAccess) (*types.Document, error) { - if err := r.authorize(ctx, obj.ID, probo.ActionDocumentGet); err != nil { - return nil, err - } - - if obj.DocumentID == nil { - return nil, nil - } - - prb := r.ProboService(ctx, obj.TrustCenterAccessID.TenantID()) - - document, err := prb.Documents.Get(ctx, *obj.DocumentID) - if err != nil { - if errors.Is(err, coredata.ErrResourceNotFound) { - return nil, gqlutils.NotFound(ctx, err) - } - - r.logger.ErrorCtx(ctx, "cannot load document", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return types.NewDocument(document), nil -} - -// Report is the resolver for the report field. -func (r *trustCenterDocumentAccessResolver) Report(ctx context.Context, obj *types.TrustCenterDocumentAccess) (*types.Report, error) { - if err := r.authorize(ctx, obj.TrustCenterAccessID, probo.ActionReportGet); err != nil { - return nil, err - } - - if obj.ReportID == nil { - return nil, nil - } - - prb := r.ProboService(ctx, obj.TrustCenterAccessID.TenantID()) - - report, err := prb.Reports.Get(ctx, *obj.ReportID) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot load report", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return types.NewReport(report), nil -} - -// TrustCenterFile is the resolver for the trustCenterFile field. -func (r *trustCenterDocumentAccessResolver) TrustCenterFile(ctx context.Context, obj *types.TrustCenterDocumentAccess) (*types.TrustCenterFile, error) { - if err := r.authorize(ctx, obj.TrustCenterAccessID, probo.ActionTrustCenterFileGet); err != nil { - return nil, err - } - - if obj.TrustCenterFileID == nil { - return nil, nil - } - - prb := r.ProboService(ctx, obj.TrustCenterAccessID.TenantID()) - - trustCenterFile, err := prb.TrustCenterFiles.Get(ctx, *obj.TrustCenterFileID) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot load trust center file", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return types.NewTrustCenterFile(trustCenterFile), nil -} - -// TotalCount is the resolver for the totalCount field. -func (r *trustCenterDocumentAccessConnectionResolver) TotalCount(ctx context.Context, obj *types.TrustCenterDocumentAccessConnection) (int, error) { - if err := r.authorize(ctx, obj.ParentID, probo.ActionTrustCenterDocumentAccessList); err != nil { - return 0, err - } - - prb := r.ProboService(ctx, obj.ParentID.TenantID()) - - count, err := prb.TrustCenterAccesses.CountDocumentAccesses(ctx, obj.ParentID) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot count trust center document accesses", log.Error(err)) - return 0, gqlutils.Internal(ctx) - } - - return count, nil -} - -// FileURL is the resolver for the fileUrl field. -func (r *trustCenterFileResolver) FileURL(ctx context.Context, obj *types.TrustCenterFile) (string, error) { - if err := r.authorize(ctx, obj.ID, probo.ActionTrustCenterFileGetFileUrl); err != nil { - return "", err - } - - prb := r.ProboService(ctx, obj.ID.TenantID()) - - fileURL, err := prb.TrustCenterFiles.GenerateFileURL(ctx, obj.ID, 1*time.Hour) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot generate file URL", log.Error(err)) - return "", gqlutils.Internal(ctx) - } - - return fileURL, nil -} - -// Organization is the resolver for the organization field. -func (r *trustCenterFileResolver) Organization(ctx context.Context, obj *types.TrustCenterFile) (*types.Organization, error) { - if err := r.authorize(ctx, obj.ID, probo.ActionOrganizationGet); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, obj.ID.TenantID()) - - trustCenterFile, err := prb.TrustCenterFiles.Get(ctx, obj.ID) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot get trust center file", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - organization, err := prb.Organizations.Get(ctx, trustCenterFile.OrganizationID) - if err != nil { - if errors.Is(err, coredata.ErrResourceNotFound) { - return nil, gqlutils.NotFound(ctx, err) - } - - r.logger.ErrorCtx(ctx, "cannot get organization", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return types.NewOrganization(organization), nil -} - -// Permission is the resolver for the permission field. -func (r *trustCenterFileResolver) Permission(ctx context.Context, obj *types.TrustCenterFile, action string) (bool, error) { - return r.Resolver.Permission(ctx, obj, action) -} - -// TotalCount is the resolver for the totalCount field. -func (r *trustCenterFileConnectionResolver) TotalCount(ctx context.Context, obj *types.TrustCenterFileConnection) (int, error) { - if err := r.authorize(ctx, obj.ParentID, probo.ActionTrustCenterFileList); err != nil { - return 0, err - } - - prb := r.ProboService(ctx, obj.ParentID.TenantID()) - - count, err := prb.TrustCenterFiles.CountForOrganizationID(ctx, obj.ParentID) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot count trust center files", log.Error(err)) - return 0, gqlutils.Internal(ctx) - } - return count, nil -} - -// LogoURL is the resolver for the logoUrl field. -func (r *trustCenterReferenceResolver) LogoURL(ctx context.Context, obj *types.TrustCenterReference) (string, error) { - if err := r.authorize(ctx, obj.ID, probo.ActionTrustCenterReferenceGetLogoUrl); err != nil { - return "", err - } - - prb := r.ProboService(ctx, obj.ID.TenantID()) - - fileURL, err := prb.TrustCenterReferences.GenerateLogoURL(ctx, obj.ID, 1*time.Hour) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot generate logo URL", log.Error(err)) - return "", gqlutils.Internal(ctx) - } - - return fileURL, nil -} - -// Permission is the resolver for the permission field. -func (r *trustCenterReferenceResolver) Permission(ctx context.Context, obj *types.TrustCenterReference, action string) (bool, error) { - return r.Resolver.Permission(ctx, obj, action) -} - -// TotalCount is the resolver for the totalCount field. -func (r *trustCenterReferenceConnectionResolver) TotalCount(ctx context.Context, obj *types.TrustCenterReferenceConnection) (int, error) { - if err := r.authorize(ctx, obj.ParentID, probo.ActionTrustCenterReferenceList); err != nil { - return 0, err - } - - prb := r.ProboService(ctx, obj.ParentID.TenantID()) - - count, err := prb.TrustCenterReferences.CountForTrustCenterID(ctx, obj.ParentID) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot count trust center references", log.Error(err)) - return 0, gqlutils.Internal(ctx) - } - - return count, nil -} - -// Organization is the resolver for the organization field. -func (r *vendorResolver) Organization(ctx context.Context, obj *types.Vendor) (*types.Organization, error) { - if err := r.authorize(ctx, obj.ID, probo.ActionOrganizationGet); err != nil { - return nil, err - } - - loaders := dataloader.FromContext(ctx) - - organization, err := loaders.Organization.Load(ctx, obj.Organization.ID) - if err != nil { - if errors.Is(err, coredata.ErrResourceNotFound) || errors.Is(err, dataloadgen.ErrNotFound) { - return nil, gqlutils.NotFound(ctx, err) - } - - r.logger.ErrorCtx(ctx, "cannot get organization", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return types.NewOrganization(organization), nil -} - -// ComplianceReports is the resolver for the complianceReports field. -func (r *vendorResolver) ComplianceReports(ctx context.Context, obj *types.Vendor, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.VendorComplianceReportOrderBy) (*types.VendorComplianceReportConnection, error) { - if err := r.authorize(ctx, obj.ID, probo.ActionVendorComplianceReportList); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, obj.ID.TenantID()) - - pageOrderBy := page.OrderBy[coredata.VendorComplianceReportOrderField]{ - Field: coredata.VendorComplianceReportOrderFieldReportDate, - Direction: page.OrderDirectionDesc, - } - if orderBy != nil { - pageOrderBy = page.OrderBy[coredata.VendorComplianceReportOrderField]{ - Field: orderBy.Field, - Direction: orderBy.Direction, - } - } - - cursor := types.NewCursor(first, after, last, before, pageOrderBy) - - page, err := prb.VendorComplianceReports.ListForVendorID(ctx, obj.ID, cursor) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot list vendor compliance reports", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return types.NewVendorComplianceReportConnection(page), nil -} - -// BusinessAssociateAgreement is the resolver for the businessAssociateAgreement field. -func (r *vendorResolver) BusinessAssociateAgreement(ctx context.Context, obj *types.Vendor) (*types.VendorBusinessAssociateAgreement, error) { - if err := r.authorize(ctx, obj.ID, probo.ActionVendorBusinessAssociateAgreementGet); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, obj.ID.TenantID()) - - vendorBusinessAssociateAgreement, file, err := prb.VendorBusinessAssociateAgreements.GetByVendorID(ctx, obj.ID) - if err != nil { - if errors.Is(err, pgx.ErrNoRows) { - return nil, nil - } - - r.logger.ErrorCtx(ctx, "cannot get vendor business associate agreement", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return types.NewVendorBusinessAssociateAgreement(vendorBusinessAssociateAgreement, file), nil -} - -// DataPrivacyAgreement is the resolver for the dataPrivacyAgreement field. -func (r *vendorResolver) DataPrivacyAgreement(ctx context.Context, obj *types.Vendor) (*types.VendorDataPrivacyAgreement, error) { - if err := r.authorize(ctx, obj.ID, probo.ActionVendorDataPrivacyAgreementGet); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, obj.ID.TenantID()) - - vendorDataPrivacyAgreement, file, err := prb.VendorDataPrivacyAgreements.GetByVendorID(ctx, obj.ID) - if err != nil { - if errors.Is(err, pgx.ErrNoRows) { - return nil, nil - } - - r.logger.ErrorCtx(ctx, "cannot get vendor data privacy agreement", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return types.NewVendorDataPrivacyAgreement(vendorDataPrivacyAgreement, file), nil -} - -// Contacts is the resolver for the contacts field. -func (r *vendorResolver) Contacts(ctx context.Context, obj *types.Vendor, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.VendorContactOrderBy) (*types.VendorContactConnection, error) { - if err := r.authorize(ctx, obj.ID, probo.ActionVendorContactList); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, obj.ID.TenantID()) - - pageOrderBy := page.OrderBy[coredata.VendorContactOrderField]{ - Field: coredata.VendorContactOrderFieldCreatedAt, - Direction: page.OrderDirectionDesc, - } - if orderBy != nil { - pageOrderBy = page.OrderBy[coredata.VendorContactOrderField]{ - Field: orderBy.Field, - Direction: orderBy.Direction, - } - } - - cursor := types.NewCursor(first, after, last, before, pageOrderBy) - - page, err := prb.VendorContacts.List(ctx, obj.ID, cursor) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot list vendor contacts", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return types.NewVendorContactConnection(page), nil -} - -// Services is the resolver for the services field. -func (r *vendorResolver) Services(ctx context.Context, obj *types.Vendor, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.VendorServiceOrderBy) (*types.VendorServiceConnection, error) { - if err := r.authorize(ctx, obj.ID, probo.ActionVendorServiceList); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, obj.ID.TenantID()) - - pageOrderBy := page.OrderBy[coredata.VendorServiceOrderField]{ - Field: coredata.VendorServiceOrderFieldCreatedAt, - Direction: page.OrderDirectionDesc, - } - if orderBy != nil { - pageOrderBy = page.OrderBy[coredata.VendorServiceOrderField]{ - Field: orderBy.Field, - Direction: orderBy.Direction, - } - } - - cursor := types.NewCursor(first, after, last, before, pageOrderBy) - - page, err := prb.VendorServices.List(ctx, obj.ID, cursor) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot list vendor services", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return types.NewVendorServiceConnection(page), nil -} - -// RiskAssessments is the resolver for the riskAssessments field. -func (r *vendorResolver) RiskAssessments(ctx context.Context, obj *types.Vendor, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.VendorRiskAssessmentOrder) (*types.VendorRiskAssessmentConnection, error) { - if err := r.authorize(ctx, obj.ID, probo.ActionVendorRiskAssessmentList); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, obj.ID.TenantID()) - - pageOrderBy := page.OrderBy[coredata.VendorRiskAssessmentOrderField]{ - Field: coredata.VendorRiskAssessmentOrderFieldCreatedAt, - Direction: page.OrderDirectionDesc, - } - if orderBy != nil { - pageOrderBy = page.OrderBy[coredata.VendorRiskAssessmentOrderField]{ - Field: orderBy.Field, - Direction: orderBy.Direction, - } - } - - cursor := types.NewCursor(first, after, last, before, pageOrderBy) - - page, err := prb.Vendors.ListRiskAssessments(ctx, obj.ID, cursor) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot list vendor risk assessments", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return types.NewVendorRiskAssessmentConnection(page), nil -} - -// BusinessOwner is the resolver for the businessOwner field. -func (r *vendorResolver) BusinessOwner(ctx context.Context, obj *types.Vendor) (*types.Profile, error) { - if err := r.authorize(ctx, obj.ID, iam.ActionMembershipProfileGet); err != nil { - return nil, err - } - - if obj.BusinessOwner == nil { - return nil, nil - } - - loaders := dataloader.FromContext(ctx) - - businessOwner, err := loaders.Profile.Load(ctx, obj.BusinessOwner.ID) - if err != nil { - if errors.Is(err, coredata.ErrResourceNotFound) || errors.Is(err, dataloadgen.ErrNotFound) { - return nil, gqlutils.NotFound(ctx, err) - } - - r.logger.ErrorCtx(ctx, "cannot get business owner", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return types.NewProfile(businessOwner), nil -} - -// SecurityOwner is the resolver for the securityOwner field. -func (r *vendorResolver) SecurityOwner(ctx context.Context, obj *types.Vendor) (*types.Profile, error) { - if err := r.authorize(ctx, obj.ID, iam.ActionMembershipProfileGet); err != nil { - return nil, err - } - - if obj.SecurityOwner == nil { - return nil, nil - } - - loaders := dataloader.FromContext(ctx) - - securityOwner, err := loaders.Profile.Load(ctx, obj.SecurityOwner.ID) - if err != nil { - if errors.Is(err, coredata.ErrResourceNotFound) || errors.Is(err, dataloadgen.ErrNotFound) { - return nil, gqlutils.NotFound(ctx, err) - } - - r.logger.ErrorCtx(ctx, "cannot get security owner", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return types.NewProfile(securityOwner), nil -} - -// Permission is the resolver for the permission field. -func (r *vendorResolver) Permission(ctx context.Context, obj *types.Vendor, action string) (bool, error) { - return r.Resolver.Permission(ctx, obj, action) -} - -// Vendor is the resolver for the vendor field. -func (r *vendorBusinessAssociateAgreementResolver) Vendor(ctx context.Context, obj *types.VendorBusinessAssociateAgreement) (*types.Vendor, error) { - if err := r.authorize(ctx, obj.ID, probo.ActionVendorGet); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, obj.ID.TenantID()) - - vendor, err := prb.Vendors.Get(ctx, obj.ID) - if err != nil { - if errors.Is(err, coredata.ErrResourceNotFound) { - return nil, gqlutils.NotFound(ctx, err) - } - - return nil, fmt.Errorf("cannot get vendor: %w", err) - } - - return types.NewVendor(vendor), nil -} - -// FileURL is the resolver for the fileUrl field. -func (r *vendorBusinessAssociateAgreementResolver) FileURL(ctx context.Context, obj *types.VendorBusinessAssociateAgreement) (string, error) { - if err := r.authorize(ctx, obj.ID, probo.ActionFileDownloadUrl); err != nil { - return "", err - } - - prb := r.ProboService(ctx, obj.ID.TenantID()) - - fileURL, err := prb.VendorBusinessAssociateAgreements.GenerateFileURL(ctx, obj.ID, 1*time.Hour) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot generate file URL", log.Error(err)) - return "", gqlutils.Internal(ctx) - } - - return fileURL, nil -} - -// Permission is the resolver for the permission field. -func (r *vendorBusinessAssociateAgreementResolver) Permission(ctx context.Context, obj *types.VendorBusinessAssociateAgreement, action string) (bool, error) { - return r.Resolver.Permission(ctx, obj, action) -} - -// Vendor is the resolver for the vendor field. -func (r *vendorComplianceReportResolver) Vendor(ctx context.Context, obj *types.VendorComplianceReport) (*types.Vendor, error) { - if err := r.authorize(ctx, obj.ID, probo.ActionVendorGet); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, obj.ID.TenantID()) - - vendor, err := prb.Vendors.Get(ctx, obj.ID) - if err != nil { - if errors.Is(err, coredata.ErrResourceNotFound) { - return nil, gqlutils.NotFound(ctx, err) - } - - r.logger.ErrorCtx(ctx, "cannot get vendor", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return types.NewVendor(vendor), nil -} - -// File is the resolver for the file field. -func (r *vendorComplianceReportResolver) File(ctx context.Context, obj *types.VendorComplianceReport) (*types.File, error) { - if err := r.authorize(ctx, obj.ID, probo.ActionFileGet); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, obj.ID.TenantID()) - - evidence, err := prb.VendorComplianceReports.Get(ctx, obj.ID) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot load evidence", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - if evidence.ReportFileId == nil { - return nil, nil - } - - file, err := prb.Files.Get(ctx, *evidence.ReportFileId) - if err != nil { - if errors.Is(err, coredata.ErrResourceNotFound) { - return nil, gqlutils.NotFound(ctx, err) - } - - r.logger.ErrorCtx(ctx, "cannot load evidence file", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return types.NewFile(file), nil -} - -// Permission is the resolver for the permission field. -func (r *vendorComplianceReportResolver) Permission(ctx context.Context, obj *types.VendorComplianceReport, action string) (bool, error) { - return r.Resolver.Permission(ctx, obj, action) -} - -// TotalCount is the resolver for the totalCount field. -func (r *vendorConnectionResolver) TotalCount(ctx context.Context, obj *types.VendorConnection) (int, error) { - if err := r.authorize(ctx, obj.ParentID, probo.ActionVendorList); err != nil { - return 0, err - } - - prb := r.ProboService(ctx, obj.ParentID.TenantID()) - - switch obj.Resolver.(type) { - case *organizationResolver: - count, err := prb.Vendors.CountForOrganizationID(ctx, obj.ParentID) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot count vendors", log.Error(err)) - return 0, gqlutils.Internal(ctx) - } - return count, nil - case *assetResolver: - count, err := prb.Vendors.CountForAssetID(ctx, obj.ParentID) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot count vendors", log.Error(err)) - return 0, gqlutils.Internal(ctx) - } - return count, nil - case *datumResolver: - count, err := prb.Vendors.CountForDatumID(ctx, obj.ParentID) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot count vendors", log.Error(err)) - return 0, gqlutils.Internal(ctx) - } - return count, nil - } - - r.logger.ErrorCtx(ctx, "unsupported resolver") - return 0, gqlutils.Internal(ctx) -} - -// Vendor is the resolver for the vendor field. -func (r *vendorContactResolver) Vendor(ctx context.Context, obj *types.VendorContact) (*types.Vendor, error) { - if err := r.authorize(ctx, obj.ID, probo.ActionVendorGet); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, obj.ID.TenantID()) - - // Get the vendor contact to access the VendorID - vendorContact, err := prb.VendorContacts.Get(ctx, obj.ID) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot get vendor contact", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - vendor, err := prb.Vendors.Get(ctx, vendorContact.VendorID) - if err != nil { - if errors.Is(err, coredata.ErrResourceNotFound) { - return nil, gqlutils.NotFound(ctx, err) - } - - r.logger.ErrorCtx(ctx, "cannot get vendor", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return types.NewVendor(vendor), nil -} - -// Permission is the resolver for the permission field. -func (r *vendorContactResolver) Permission(ctx context.Context, obj *types.VendorContact, action string) (bool, error) { - return r.Resolver.Permission(ctx, obj, action) -} - -// Vendor is the resolver for the vendor field. -func (r *vendorDataPrivacyAgreementResolver) Vendor(ctx context.Context, obj *types.VendorDataPrivacyAgreement) (*types.Vendor, error) { - if err := r.authorize(ctx, obj.ID, probo.ActionVendorGet); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, obj.ID.TenantID()) - - vendor, err := prb.Vendors.Get(ctx, obj.ID) - if err != nil { - if errors.Is(err, coredata.ErrResourceNotFound) { - return nil, gqlutils.NotFound(ctx, err) - } - - r.logger.ErrorCtx(ctx, "cannot get vendor", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return types.NewVendor(vendor), nil -} - -// FileURL is the resolver for the fileUrl field. -func (r *vendorDataPrivacyAgreementResolver) FileURL(ctx context.Context, obj *types.VendorDataPrivacyAgreement) (string, error) { - if err := r.authorize(ctx, obj.ID, probo.ActionFileDownloadUrl); err != nil { - return "", err - } - - prb := r.ProboService(ctx, obj.ID.TenantID()) - - fileURL, err := prb.VendorDataPrivacyAgreements.GenerateFileURL(ctx, obj.ID, 1*time.Hour) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot generate file URL", log.Error(err)) - return "", gqlutils.Internal(ctx) - } - - return fileURL, nil -} - -// Permission is the resolver for the permission field. -func (r *vendorDataPrivacyAgreementResolver) Permission(ctx context.Context, obj *types.VendorDataPrivacyAgreement, action string) (bool, error) { - return r.Resolver.Permission(ctx, obj, action) -} - -// Vendor is the resolver for the vendor field. -func (r *vendorRiskAssessmentResolver) Vendor(ctx context.Context, obj *types.VendorRiskAssessment) (*types.Vendor, error) { - if err := r.authorize(ctx, obj.ID, probo.ActionVendorGet); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, obj.ID.TenantID()) - - vendor, err := prb.Vendors.GetByRiskAssessmentID(ctx, obj.ID) - if err != nil { - if errors.Is(err, coredata.ErrResourceNotFound) { - return nil, gqlutils.NotFound(ctx, err) - } - - r.logger.ErrorCtx(ctx, "cannot get vendor", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return types.NewVendor(vendor), nil -} - -// Permission is the resolver for the permission field. -func (r *vendorRiskAssessmentResolver) Permission(ctx context.Context, obj *types.VendorRiskAssessment, action string) (bool, error) { - return r.Resolver.Permission(ctx, obj, action) -} - -// Vendor is the resolver for the vendor field. -func (r *vendorServiceResolver) Vendor(ctx context.Context, obj *types.VendorService) (*types.Vendor, error) { - if err := r.authorize(ctx, obj.ID, probo.ActionVendorGet); err != nil { - return nil, err - } - - loaders := dataloader.FromContext(ctx) - - vendor, err := loaders.Vendor.Load(ctx, obj.Vendor.ID) - if err != nil { - if errors.Is(err, coredata.ErrResourceNotFound) || errors.Is(err, dataloadgen.ErrNotFound) { - return nil, gqlutils.NotFound(ctx, err) - } - - r.logger.ErrorCtx(ctx, "cannot get vendor", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return types.NewVendor(vendor), nil -} - -// Permission is the resolver for the permission field. -func (r *vendorServiceResolver) Permission(ctx context.Context, obj *types.VendorService, action string) (bool, error) { - return r.Resolver.Permission(ctx, obj, action) -} - -// SignableDocuments is the resolver for the signableDocuments field. -func (r *viewerResolver) SignableDocuments(ctx context.Context, obj *types.Viewer, organizationID gid.GID, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.DocumentOrderBy) (*types.EmployeeDocumentConnection, error) { - if err := r.authorize(ctx, organizationID, probo.ActionEmployeeDocumentList); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, organizationID.TenantID()) - - pageOrderBy := page.OrderBy[coredata.DocumentOrderField]{ - Field: coredata.DocumentOrderFieldCreatedAt, - Direction: page.OrderDirectionDesc, - } - if orderBy != nil { - pageOrderBy = page.OrderBy[coredata.DocumentOrderField]{ - Field: orderBy.Field, - Direction: orderBy.Direction, - } - } - - cursor := types.NewCursor(first, after, last, before, pageOrderBy) - - identity := authn.IdentityFromContext(ctx) - - documentFilter := coredata.NewDocumentFilter(nil).WithEmployeeIdentityID(&identity.ID, coredata.EmployeeFilterModeSignature) - - documentsPage, err := prb.Documents.ListByOrganizationID(ctx, organizationID, cursor, documentFilter) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot list organization signable documents", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - employeeDocuments := make([]*types.EmployeeDocument, len(documentsPage.Data)) - for i, doc := range documentsPage.Data { - employeeDocuments[i] = &types.EmployeeDocument{ - ID: doc.ID, - Title: doc.Title, - DocumentType: doc.DocumentType, - CreatedAt: doc.CreatedAt, - UpdatedAt: doc.UpdatedAt, - FilterMode: types.EmployeeDocumentFilterModeSignature, - } - } - - page := page.NewPage(employeeDocuments, documentsPage.Cursor) - - return types.NewEmployeeDocumentConnection(page), nil -} - -// SignableDocument is the resolver for the signableDocument field. -func (r *viewerResolver) SignableDocument(ctx context.Context, obj *types.Viewer, id gid.GID) (*types.EmployeeDocument, error) { - if err := r.authorize(ctx, id, probo.ActionEmployeeDocumentGet); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, id.TenantID()) - - identity := authn.IdentityFromContext(ctx) - - documentFilter := coredata.NewDocumentFilter(nil).WithEmployeeIdentityID(&identity.ID, coredata.EmployeeFilterModeSignature) - document, err := prb.Documents.GetWithFilter(ctx, id, documentFilter) - if err != nil { - if errors.Is(err, coredata.ErrResourceNotFound) { - return nil, gqlutils.NotFound(ctx, err) - } - - r.logger.ErrorCtx(ctx, "cannot get signable document", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.EmployeeDocument{ - ID: document.ID, - Title: document.Title, - DocumentType: document.DocumentType, - CreatedAt: document.CreatedAt, - UpdatedAt: document.UpdatedAt, - FilterMode: types.EmployeeDocumentFilterModeSignature, - }, nil -} - -// ApprovableDocuments is the resolver for the approvableDocuments field. -func (r *viewerResolver) ApprovableDocuments(ctx context.Context, obj *types.Viewer, organizationID gid.GID, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.DocumentOrderBy) (*types.EmployeeDocumentConnection, error) { - if err := r.authorize(ctx, organizationID, probo.ActionEmployeeDocumentList); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, organizationID.TenantID()) - - pageOrderBy := page.OrderBy[coredata.DocumentOrderField]{ - Field: coredata.DocumentOrderFieldCreatedAt, - Direction: page.OrderDirectionDesc, - } - if orderBy != nil { - pageOrderBy = page.OrderBy[coredata.DocumentOrderField]{ - Field: orderBy.Field, - Direction: orderBy.Direction, - } - } - - cursor := types.NewCursor(first, after, last, before, pageOrderBy) - - identity := authn.IdentityFromContext(ctx) - - documentFilter := coredata.NewDocumentFilter(nil).WithEmployeeIdentityID(&identity.ID, coredata.EmployeeFilterModeApproval) - - documentsPage, err := prb.Documents.ListByOrganizationID(ctx, organizationID, cursor, documentFilter) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot list organization approvable documents", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - employeeDocuments := make([]*types.EmployeeDocument, len(documentsPage.Data)) - for i, doc := range documentsPage.Data { - employeeDocuments[i] = &types.EmployeeDocument{ - ID: doc.ID, - Title: doc.Title, - DocumentType: doc.DocumentType, - CreatedAt: doc.CreatedAt, - UpdatedAt: doc.UpdatedAt, - FilterMode: types.EmployeeDocumentFilterModeApproval, - } - } - - page := page.NewPage(employeeDocuments, documentsPage.Cursor) - - return types.NewEmployeeDocumentConnection(page), nil -} - -// ApprovableDocument is the resolver for the approvableDocument field. -func (r *viewerResolver) ApprovableDocument(ctx context.Context, obj *types.Viewer, id gid.GID) (*types.EmployeeDocument, error) { - if err := r.authorize(ctx, id, probo.ActionEmployeeDocumentGet); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, id.TenantID()) - - identity := authn.IdentityFromContext(ctx) - - documentFilter := coredata.NewDocumentFilter(nil).WithEmployeeIdentityID(&identity.ID, coredata.EmployeeFilterModeApproval) - document, err := prb.Documents.GetWithFilter(ctx, id, documentFilter) - if err != nil { - if errors.Is(err, coredata.ErrResourceNotFound) { - return nil, gqlutils.NotFound(ctx, err) - } - - r.logger.ErrorCtx(ctx, "cannot get approvable document", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.EmployeeDocument{ - ID: document.ID, - Title: document.Title, - DocumentType: document.DocumentType, - CreatedAt: document.CreatedAt, - UpdatedAt: document.UpdatedAt, - FilterMode: types.EmployeeDocumentFilterModeApproval, - }, nil -} - -// TotalCount is the resolver for the totalCount field. -func (r *webhookEventConnectionResolver) TotalCount(ctx context.Context, obj *types.WebhookEventConnection) (int, error) { - if err := r.authorize(ctx, obj.ParentID, probo.ActionWebhookSubscriptionGet); err != nil { - return 0, err - } - - prb := r.ProboService(ctx, obj.ParentID.TenantID()) - - count, err := prb.WebhookSubscriptions.CountEventsForSubscriptionID(ctx, obj.ParentID) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot count webhook events", log.Error(err)) - return 0, gqlutils.Internal(ctx) - } - - return count, nil -} - -// Organization is the resolver for the organization field. -func (r *webhookSubscriptionResolver) Organization(ctx context.Context, obj *types.WebhookSubscription) (*types.Organization, error) { - if err := r.authorize(ctx, obj.ID, probo.ActionOrganizationGet); err != nil { - return nil, err - } - - loaders := dataloader.FromContext(ctx) - - organization, err := loaders.Organization.Load(ctx, obj.Organization.ID) - if err != nil { - if errors.Is(err, coredata.ErrResourceNotFound) || errors.Is(err, dataloadgen.ErrNotFound) { - return nil, gqlutils.NotFound(ctx, err) - } - - r.logger.ErrorCtx(ctx, "cannot load organization", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return types.NewOrganization(organization), nil -} - -// SigningSecret is the resolver for the signingSecret field. -func (r *webhookSubscriptionResolver) SigningSecret(ctx context.Context, obj *types.WebhookSubscription) (string, error) { - if err := r.authorize(ctx, obj.ID, probo.ActionWebhookSubscriptionUpdate); err != nil { - return "", err - } - - prb := r.ProboService(ctx, obj.ID.TenantID()) - - signingSecret, err := prb.WebhookSubscriptions.GetSigningSecret(ctx, obj.ID) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot get signing secret", log.Error(err)) - return "", gqlutils.Internal(ctx) - } - - return signingSecret, nil -} - -// Events is the resolver for the events field. -func (r *webhookSubscriptionResolver) Events(ctx context.Context, obj *types.WebhookSubscription, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.WebhookEventOrderBy) (*types.WebhookEventConnection, error) { - if err := r.authorize(ctx, obj.ID, probo.ActionWebhookSubscriptionGet); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, obj.ID.TenantID()) - - pageOrderBy := page.OrderBy[coredata.WebhookEventOrderField]{ - Field: coredata.WebhookEventOrderFieldCreatedAt, - Direction: page.OrderDirectionDesc, - } - if orderBy != nil { - pageOrderBy = page.OrderBy[coredata.WebhookEventOrderField]{ - Field: orderBy.Field, - Direction: orderBy.Direction, - } - } - - cursor := types.NewCursor(first, after, last, before, pageOrderBy) - - page, err := prb.WebhookSubscriptions.ListEventsForSubscriptionID(ctx, obj.ID, cursor) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot list webhook events", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return types.NewWebhookEventConnection(page, r, obj.ID), nil -} - -// Permission is the resolver for the permission field. -func (r *webhookSubscriptionResolver) Permission(ctx context.Context, obj *types.WebhookSubscription, action string) (bool, error) { - return r.Resolver.Permission(ctx, obj, action) -} - -// TotalCount is the resolver for the totalCount field. -func (r *webhookSubscriptionConnectionResolver) TotalCount(ctx context.Context, obj *types.WebhookSubscriptionConnection) (int, error) { - if err := r.authorize(ctx, obj.ParentID, probo.ActionWebhookSubscriptionList); err != nil { - return 0, err - } - - prb := r.ProboService(ctx, obj.ParentID.TenantID()) - - switch obj.Resolver.(type) { - case *organizationResolver: - count, err := prb.WebhookSubscriptions.CountForOrganizationID(ctx, obj.ParentID) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot count webhook subscriptions", log.Error(err)) - return 0, gqlutils.Internal(ctx) - } - return count, nil - } - - r.logger.ErrorCtx(ctx, "unsupported resolver for webhook subscription connection", log.String("resolver", fmt.Sprintf("%T", obj.Resolver))) - return 0, gqlutils.Internal(ctx) -} - -// AccessEntry returns schema.AccessEntryResolver implementation. -func (r *Resolver) AccessEntry() schema.AccessEntryResolver { return &accessEntryResolver{r} } - -// AccessEntryConnection returns schema.AccessEntryConnectionResolver implementation. -func (r *Resolver) AccessEntryConnection() schema.AccessEntryConnectionResolver { - return &accessEntryConnectionResolver{r} -} - -// AccessReview returns schema.AccessReviewResolver implementation. -func (r *Resolver) AccessReview() schema.AccessReviewResolver { return &accessReviewResolver{r} } - -// AccessReviewCampaign returns schema.AccessReviewCampaignResolver implementation. -func (r *Resolver) AccessReviewCampaign() schema.AccessReviewCampaignResolver { - return &accessReviewCampaignResolver{r} -} - -// AccessReviewCampaignConnection returns schema.AccessReviewCampaignConnectionResolver implementation. -func (r *Resolver) AccessReviewCampaignConnection() schema.AccessReviewCampaignConnectionResolver { - return &accessReviewCampaignConnectionResolver{r} -} - -// AccessReviewCampaignScopeSource returns schema.AccessReviewCampaignScopeSourceResolver implementation. -func (r *Resolver) AccessReviewCampaignScopeSource() schema.AccessReviewCampaignScopeSourceResolver { - return &accessReviewCampaignScopeSourceResolver{r} -} - -// AccessSource returns schema.AccessSourceResolver implementation. -func (r *Resolver) AccessSource() schema.AccessSourceResolver { return &accessSourceResolver{r} } - -// AccessSourceConnection returns schema.AccessSourceConnectionResolver implementation. -func (r *Resolver) AccessSourceConnection() schema.AccessSourceConnectionResolver { - return &accessSourceConnectionResolver{r} -} - -// ApplicabilityStatement returns schema.ApplicabilityStatementResolver implementation. -func (r *Resolver) ApplicabilityStatement() schema.ApplicabilityStatementResolver { - return &applicabilityStatementResolver{r} -} - -// ApplicabilityStatementConnection returns schema.ApplicabilityStatementConnectionResolver implementation. -func (r *Resolver) ApplicabilityStatementConnection() schema.ApplicabilityStatementConnectionResolver { - return &applicabilityStatementConnectionResolver{r} -} - -// Asset returns schema.AssetResolver implementation. -func (r *Resolver) Asset() schema.AssetResolver { return &assetResolver{r} } - -// AssetConnection returns schema.AssetConnectionResolver implementation. -func (r *Resolver) AssetConnection() schema.AssetConnectionResolver { - return &assetConnectionResolver{r} -} - -// Audit returns schema.AuditResolver implementation. -func (r *Resolver) Audit() schema.AuditResolver { return &auditResolver{r} } - -// AuditConnection returns schema.AuditConnectionResolver implementation. -func (r *Resolver) AuditConnection() schema.AuditConnectionResolver { - return &auditConnectionResolver{r} -} - -// AuditLogEntry returns schema.AuditLogEntryResolver implementation. -func (r *Resolver) AuditLogEntry() schema.AuditLogEntryResolver { return &auditLogEntryResolver{r} } - -// AuditLogEntryConnection returns schema.AuditLogEntryConnectionResolver implementation. -func (r *Resolver) AuditLogEntryConnection() schema.AuditLogEntryConnectionResolver { - return &auditLogEntryConnectionResolver{r} -} - -// ComplianceExternalURL returns schema.ComplianceExternalURLResolver implementation. -func (r *Resolver) ComplianceExternalURL() schema.ComplianceExternalURLResolver { - return &complianceExternalURLResolver{r} -} - -// ComplianceFramework returns schema.ComplianceFrameworkResolver implementation. -func (r *Resolver) ComplianceFramework() schema.ComplianceFrameworkResolver { - return &complianceFrameworkResolver{r} -} - -// Connector returns schema.ConnectorResolver implementation. -func (r *Resolver) Connector() schema.ConnectorResolver { return &connectorResolver{r} } - -// Control returns schema.ControlResolver implementation. -func (r *Resolver) Control() schema.ControlResolver { return &controlResolver{r} } - -// ControlConnection returns schema.ControlConnectionResolver implementation. -func (r *Resolver) ControlConnection() schema.ControlConnectionResolver { - return &controlConnectionResolver{r} -} - -// CustomDomain returns schema.CustomDomainResolver implementation. -func (r *Resolver) CustomDomain() schema.CustomDomainResolver { return &customDomainResolver{r} } - -// DataProtectionImpactAssessment returns schema.DataProtectionImpactAssessmentResolver implementation. -func (r *Resolver) DataProtectionImpactAssessment() schema.DataProtectionImpactAssessmentResolver { - return &dataProtectionImpactAssessmentResolver{r} -} - -// DataProtectionImpactAssessmentConnection returns schema.DataProtectionImpactAssessmentConnectionResolver implementation. -func (r *Resolver) DataProtectionImpactAssessmentConnection() schema.DataProtectionImpactAssessmentConnectionResolver { - return &dataProtectionImpactAssessmentConnectionResolver{r} -} - -// Datum returns schema.DatumResolver implementation. -func (r *Resolver) Datum() schema.DatumResolver { return &datumResolver{r} } - -// DatumConnection returns schema.DatumConnectionResolver implementation. -func (r *Resolver) DatumConnection() schema.DatumConnectionResolver { - return &datumConnectionResolver{r} -} - -// Document returns schema.DocumentResolver implementation. -func (r *Resolver) Document() schema.DocumentResolver { return &documentResolver{r} } - -// DocumentConnection returns schema.DocumentConnectionResolver implementation. -func (r *Resolver) DocumentConnection() schema.DocumentConnectionResolver { - return &documentConnectionResolver{r} -} - -// DocumentVersion returns schema.DocumentVersionResolver implementation. -func (r *Resolver) DocumentVersion() schema.DocumentVersionResolver { - return &documentVersionResolver{r} -} - -// DocumentVersionApprovalDecision returns schema.DocumentVersionApprovalDecisionResolver implementation. -func (r *Resolver) DocumentVersionApprovalDecision() schema.DocumentVersionApprovalDecisionResolver { - return &documentVersionApprovalDecisionResolver{r} -} - -// DocumentVersionApprovalDecisionConnection returns schema.DocumentVersionApprovalDecisionConnectionResolver implementation. -func (r *Resolver) DocumentVersionApprovalDecisionConnection() schema.DocumentVersionApprovalDecisionConnectionResolver { - return &documentVersionApprovalDecisionConnectionResolver{r} -} - -// DocumentVersionApprovalQuorum returns schema.DocumentVersionApprovalQuorumResolver implementation. -func (r *Resolver) DocumentVersionApprovalQuorum() schema.DocumentVersionApprovalQuorumResolver { - return &documentVersionApprovalQuorumResolver{r} -} - -// DocumentVersionApprovalQuorumConnection returns schema.DocumentVersionApprovalQuorumConnectionResolver implementation. -func (r *Resolver) DocumentVersionApprovalQuorumConnection() schema.DocumentVersionApprovalQuorumConnectionResolver { - return &documentVersionApprovalQuorumConnectionResolver{r} -} - -// DocumentVersionConnection returns schema.DocumentVersionConnectionResolver implementation. -func (r *Resolver) DocumentVersionConnection() schema.DocumentVersionConnectionResolver { - return &documentVersionConnectionResolver{r} -} - -// DocumentVersionSignature returns schema.DocumentVersionSignatureResolver implementation. -func (r *Resolver) DocumentVersionSignature() schema.DocumentVersionSignatureResolver { - return &documentVersionSignatureResolver{r} -} - -// DocumentVersionSignatureConnection returns schema.DocumentVersionSignatureConnectionResolver implementation. -func (r *Resolver) DocumentVersionSignatureConnection() schema.DocumentVersionSignatureConnectionResolver { - return &documentVersionSignatureConnectionResolver{r} -} - -// ElectronicSignature returns schema.ElectronicSignatureResolver implementation. -func (r *Resolver) ElectronicSignature() schema.ElectronicSignatureResolver { - return &electronicSignatureResolver{r} -} - -// EmployeeDocument returns schema.EmployeeDocumentResolver implementation. -func (r *Resolver) EmployeeDocument() schema.EmployeeDocumentResolver { - return &employeeDocumentResolver{r} -} - -// EmployeeDocumentVersion returns schema.EmployeeDocumentVersionResolver implementation. -func (r *Resolver) EmployeeDocumentVersion() schema.EmployeeDocumentVersionResolver { - return &employeeDocumentVersionResolver{r} -} - -// Evidence returns schema.EvidenceResolver implementation. -func (r *Resolver) Evidence() schema.EvidenceResolver { return &evidenceResolver{r} } - -// EvidenceConnection returns schema.EvidenceConnectionResolver implementation. -func (r *Resolver) EvidenceConnection() schema.EvidenceConnectionResolver { - return &evidenceConnectionResolver{r} -} - -// File returns schema.FileResolver implementation. -func (r *Resolver) File() schema.FileResolver { return &fileResolver{r} } - -// Finding returns schema.FindingResolver implementation. -func (r *Resolver) Finding() schema.FindingResolver { return &findingResolver{r} } - -// FindingConnection returns schema.FindingConnectionResolver implementation. -func (r *Resolver) FindingConnection() schema.FindingConnectionResolver { - return &findingConnectionResolver{r} -} - -// Framework returns schema.FrameworkResolver implementation. -func (r *Resolver) Framework() schema.FrameworkResolver { return &frameworkResolver{r} } - -// FrameworkConnection returns schema.FrameworkConnectionResolver implementation. -func (r *Resolver) FrameworkConnection() schema.FrameworkConnectionResolver { - return &frameworkConnectionResolver{r} -} - -// MailingList returns schema.MailingListResolver implementation. -func (r *Resolver) MailingList() schema.MailingListResolver { return &mailingListResolver{r} } - -// MailingListSubscriberConnection returns schema.MailingListSubscriberConnectionResolver implementation. -func (r *Resolver) MailingListSubscriberConnection() schema.MailingListSubscriberConnectionResolver { - return &mailingListSubscriberConnectionResolver{r} -} - -// MailingListUpdateConnection returns schema.MailingListUpdateConnectionResolver implementation. -func (r *Resolver) MailingListUpdateConnection() schema.MailingListUpdateConnectionResolver { - return &mailingListUpdateConnectionResolver{r} -} - -// Measure returns schema.MeasureResolver implementation. -func (r *Resolver) Measure() schema.MeasureResolver { return &measureResolver{r} } - -// MeasureConnection returns schema.MeasureConnectionResolver implementation. -func (r *Resolver) MeasureConnection() schema.MeasureConnectionResolver { - return &measureConnectionResolver{r} -} - -// Meeting returns schema.MeetingResolver implementation. -func (r *Resolver) Meeting() schema.MeetingResolver { return &meetingResolver{r} } - -// MeetingConnection returns schema.MeetingConnectionResolver implementation. -func (r *Resolver) MeetingConnection() schema.MeetingConnectionResolver { - return &meetingConnectionResolver{r} -} - -// Mutation returns schema.MutationResolver implementation. -func (r *Resolver) Mutation() schema.MutationResolver { return &mutationResolver{r} } - -// Obligation returns schema.ObligationResolver implementation. -func (r *Resolver) Obligation() schema.ObligationResolver { return &obligationResolver{r} } - -// ObligationConnection returns schema.ObligationConnectionResolver implementation. -func (r *Resolver) ObligationConnection() schema.ObligationConnectionResolver { - return &obligationConnectionResolver{r} -} - -// Organization returns schema.OrganizationResolver implementation. -func (r *Resolver) Organization() schema.OrganizationResolver { return &organizationResolver{r} } - -// ProcessingActivity returns schema.ProcessingActivityResolver implementation. -func (r *Resolver) ProcessingActivity() schema.ProcessingActivityResolver { - return &processingActivityResolver{r} -} - -// ProcessingActivityConnection returns schema.ProcessingActivityConnectionResolver implementation. -func (r *Resolver) ProcessingActivityConnection() schema.ProcessingActivityConnectionResolver { - return &processingActivityConnectionResolver{r} -} - -// Profile returns schema.ProfileResolver implementation. -func (r *Resolver) Profile() schema.ProfileResolver { return &profileResolver{r} } - -// ProfileConnection returns schema.ProfileConnectionResolver implementation. -func (r *Resolver) ProfileConnection() schema.ProfileConnectionResolver { - return &profileConnectionResolver{r} -} - -// Query returns schema.QueryResolver implementation. -func (r *Resolver) Query() schema.QueryResolver { return &queryResolver{r} } - -// Report returns schema.ReportResolver implementation. -func (r *Resolver) Report() schema.ReportResolver { return &reportResolver{r} } - -// RightsRequest returns schema.RightsRequestResolver implementation. -func (r *Resolver) RightsRequest() schema.RightsRequestResolver { return &rightsRequestResolver{r} } - -// RightsRequestConnection returns schema.RightsRequestConnectionResolver implementation. -func (r *Resolver) RightsRequestConnection() schema.RightsRequestConnectionResolver { - return &rightsRequestConnectionResolver{r} -} - -// Risk returns schema.RiskResolver implementation. -func (r *Resolver) Risk() schema.RiskResolver { return &riskResolver{r} } - -// RiskConnection returns schema.RiskConnectionResolver implementation. -func (r *Resolver) RiskConnection() schema.RiskConnectionResolver { return &riskConnectionResolver{r} } - -// SlackConnection returns schema.SlackConnectionResolver implementation. -func (r *Resolver) SlackConnection() schema.SlackConnectionResolver { - return &slackConnectionResolver{r} -} - -// Snapshot returns schema.SnapshotResolver implementation. -func (r *Resolver) Snapshot() schema.SnapshotResolver { return &snapshotResolver{r} } - -// SnapshotConnection returns schema.SnapshotConnectionResolver implementation. -func (r *Resolver) SnapshotConnection() schema.SnapshotConnectionResolver { - return &snapshotConnectionResolver{r} -} - -// StatementOfApplicability returns schema.StatementOfApplicabilityResolver implementation. -func (r *Resolver) StatementOfApplicability() schema.StatementOfApplicabilityResolver { - return &statementOfApplicabilityResolver{r} -} - -// StatementOfApplicabilityConnection returns schema.StatementOfApplicabilityConnectionResolver implementation. -func (r *Resolver) StatementOfApplicabilityConnection() schema.StatementOfApplicabilityConnectionResolver { - return &statementOfApplicabilityConnectionResolver{r} -} - -// Task returns schema.TaskResolver implementation. -func (r *Resolver) Task() schema.TaskResolver { return &taskResolver{r} } - -// TaskConnection returns schema.TaskConnectionResolver implementation. -func (r *Resolver) TaskConnection() schema.TaskConnectionResolver { return &taskConnectionResolver{r} } - -// TransferImpactAssessment returns schema.TransferImpactAssessmentResolver implementation. -func (r *Resolver) TransferImpactAssessment() schema.TransferImpactAssessmentResolver { - return &transferImpactAssessmentResolver{r} -} - -// TransferImpactAssessmentConnection returns schema.TransferImpactAssessmentConnectionResolver implementation. -func (r *Resolver) TransferImpactAssessmentConnection() schema.TransferImpactAssessmentConnectionResolver { - return &transferImpactAssessmentConnectionResolver{r} -} - -// TrustCenter returns schema.TrustCenterResolver implementation. -func (r *Resolver) TrustCenter() schema.TrustCenterResolver { return &trustCenterResolver{r} } - -// TrustCenterAccess returns schema.TrustCenterAccessResolver implementation. -func (r *Resolver) TrustCenterAccess() schema.TrustCenterAccessResolver { - return &trustCenterAccessResolver{r} -} - -// TrustCenterDocumentAccess returns schema.TrustCenterDocumentAccessResolver implementation. -func (r *Resolver) TrustCenterDocumentAccess() schema.TrustCenterDocumentAccessResolver { - return &trustCenterDocumentAccessResolver{r} -} - -// TrustCenterDocumentAccessConnection returns schema.TrustCenterDocumentAccessConnectionResolver implementation. -func (r *Resolver) TrustCenterDocumentAccessConnection() schema.TrustCenterDocumentAccessConnectionResolver { - return &trustCenterDocumentAccessConnectionResolver{r} -} - -// TrustCenterFile returns schema.TrustCenterFileResolver implementation. -func (r *Resolver) TrustCenterFile() schema.TrustCenterFileResolver { - return &trustCenterFileResolver{r} -} - -// TrustCenterFileConnection returns schema.TrustCenterFileConnectionResolver implementation. -func (r *Resolver) TrustCenterFileConnection() schema.TrustCenterFileConnectionResolver { - return &trustCenterFileConnectionResolver{r} -} - -// TrustCenterReference returns schema.TrustCenterReferenceResolver implementation. -func (r *Resolver) TrustCenterReference() schema.TrustCenterReferenceResolver { - return &trustCenterReferenceResolver{r} -} - -// TrustCenterReferenceConnection returns schema.TrustCenterReferenceConnectionResolver implementation. -func (r *Resolver) TrustCenterReferenceConnection() schema.TrustCenterReferenceConnectionResolver { - return &trustCenterReferenceConnectionResolver{r} -} - -// Vendor returns schema.VendorResolver implementation. -func (r *Resolver) Vendor() schema.VendorResolver { return &vendorResolver{r} } - -// VendorBusinessAssociateAgreement returns schema.VendorBusinessAssociateAgreementResolver implementation. -func (r *Resolver) VendorBusinessAssociateAgreement() schema.VendorBusinessAssociateAgreementResolver { - return &vendorBusinessAssociateAgreementResolver{r} -} - -// VendorComplianceReport returns schema.VendorComplianceReportResolver implementation. -func (r *Resolver) VendorComplianceReport() schema.VendorComplianceReportResolver { - return &vendorComplianceReportResolver{r} -} - -// VendorConnection returns schema.VendorConnectionResolver implementation. -func (r *Resolver) VendorConnection() schema.VendorConnectionResolver { - return &vendorConnectionResolver{r} -} - -// VendorContact returns schema.VendorContactResolver implementation. -func (r *Resolver) VendorContact() schema.VendorContactResolver { return &vendorContactResolver{r} } - -// VendorDataPrivacyAgreement returns schema.VendorDataPrivacyAgreementResolver implementation. -func (r *Resolver) VendorDataPrivacyAgreement() schema.VendorDataPrivacyAgreementResolver { - return &vendorDataPrivacyAgreementResolver{r} -} - -// VendorRiskAssessment returns schema.VendorRiskAssessmentResolver implementation. -func (r *Resolver) VendorRiskAssessment() schema.VendorRiskAssessmentResolver { - return &vendorRiskAssessmentResolver{r} -} - -// VendorService returns schema.VendorServiceResolver implementation. -func (r *Resolver) VendorService() schema.VendorServiceResolver { return &vendorServiceResolver{r} } - -// Viewer returns schema.ViewerResolver implementation. -func (r *Resolver) Viewer() schema.ViewerResolver { return &viewerResolver{r} } - -// WebhookEventConnection returns schema.WebhookEventConnectionResolver implementation. -func (r *Resolver) WebhookEventConnection() schema.WebhookEventConnectionResolver { - return &webhookEventConnectionResolver{r} -} - -// WebhookSubscription returns schema.WebhookSubscriptionResolver implementation. -func (r *Resolver) WebhookSubscription() schema.WebhookSubscriptionResolver { - return &webhookSubscriptionResolver{r} -} - -// WebhookSubscriptionConnection returns schema.WebhookSubscriptionConnectionResolver implementation. -func (r *Resolver) WebhookSubscriptionConnection() schema.WebhookSubscriptionConnectionResolver { - return &webhookSubscriptionConnectionResolver{r} -} - -type accessEntryResolver struct{ *Resolver } -type accessEntryConnectionResolver struct{ *Resolver } -type accessReviewResolver struct{ *Resolver } -type accessReviewCampaignResolver struct{ *Resolver } -type accessReviewCampaignConnectionResolver struct{ *Resolver } -type accessReviewCampaignScopeSourceResolver struct{ *Resolver } -type accessSourceResolver struct{ *Resolver } -type accessSourceConnectionResolver struct{ *Resolver } -type applicabilityStatementResolver struct{ *Resolver } -type applicabilityStatementConnectionResolver struct{ *Resolver } -type assetResolver struct{ *Resolver } -type assetConnectionResolver struct{ *Resolver } -type auditResolver struct{ *Resolver } -type auditConnectionResolver struct{ *Resolver } -type auditLogEntryResolver struct{ *Resolver } -type auditLogEntryConnectionResolver struct{ *Resolver } -type complianceExternalURLResolver struct{ *Resolver } -type complianceFrameworkResolver struct{ *Resolver } -type connectorResolver struct{ *Resolver } -type controlResolver struct{ *Resolver } -type controlConnectionResolver struct{ *Resolver } -type customDomainResolver struct{ *Resolver } -type dataProtectionImpactAssessmentResolver struct{ *Resolver } -type dataProtectionImpactAssessmentConnectionResolver struct{ *Resolver } -type datumResolver struct{ *Resolver } -type datumConnectionResolver struct{ *Resolver } -type documentResolver struct{ *Resolver } -type documentConnectionResolver struct{ *Resolver } -type documentVersionResolver struct{ *Resolver } -type documentVersionApprovalDecisionResolver struct{ *Resolver } -type documentVersionApprovalDecisionConnectionResolver struct{ *Resolver } -type documentVersionApprovalQuorumResolver struct{ *Resolver } -type documentVersionApprovalQuorumConnectionResolver struct{ *Resolver } -type documentVersionConnectionResolver struct{ *Resolver } -type documentVersionSignatureResolver struct{ *Resolver } -type documentVersionSignatureConnectionResolver struct{ *Resolver } -type electronicSignatureResolver struct{ *Resolver } -type employeeDocumentResolver struct{ *Resolver } -type employeeDocumentVersionResolver struct{ *Resolver } -type evidenceResolver struct{ *Resolver } -type evidenceConnectionResolver struct{ *Resolver } -type fileResolver struct{ *Resolver } -type findingResolver struct{ *Resolver } -type findingConnectionResolver struct{ *Resolver } -type frameworkResolver struct{ *Resolver } -type frameworkConnectionResolver struct{ *Resolver } -type mailingListResolver struct{ *Resolver } -type mailingListSubscriberConnectionResolver struct{ *Resolver } -type mailingListUpdateConnectionResolver struct{ *Resolver } -type measureResolver struct{ *Resolver } -type measureConnectionResolver struct{ *Resolver } -type meetingResolver struct{ *Resolver } -type meetingConnectionResolver struct{ *Resolver } -type mutationResolver struct{ *Resolver } -type obligationResolver struct{ *Resolver } -type obligationConnectionResolver struct{ *Resolver } -type organizationResolver struct{ *Resolver } -type processingActivityResolver struct{ *Resolver } -type processingActivityConnectionResolver struct{ *Resolver } -type profileResolver struct{ *Resolver } -type profileConnectionResolver struct{ *Resolver } -type queryResolver struct{ *Resolver } -type reportResolver struct{ *Resolver } -type rightsRequestResolver struct{ *Resolver } -type rightsRequestConnectionResolver struct{ *Resolver } -type riskResolver struct{ *Resolver } -type riskConnectionResolver struct{ *Resolver } -type slackConnectionResolver struct{ *Resolver } -type snapshotResolver struct{ *Resolver } -type snapshotConnectionResolver struct{ *Resolver } -type statementOfApplicabilityResolver struct{ *Resolver } -type statementOfApplicabilityConnectionResolver struct{ *Resolver } -type taskResolver struct{ *Resolver } -type taskConnectionResolver struct{ *Resolver } -type transferImpactAssessmentResolver struct{ *Resolver } -type transferImpactAssessmentConnectionResolver struct{ *Resolver } -type trustCenterResolver struct{ *Resolver } -type trustCenterAccessResolver struct{ *Resolver } -type trustCenterDocumentAccessResolver struct{ *Resolver } -type trustCenterDocumentAccessConnectionResolver struct{ *Resolver } -type trustCenterFileResolver struct{ *Resolver } -type trustCenterFileConnectionResolver struct{ *Resolver } -type trustCenterReferenceResolver struct{ *Resolver } -type trustCenterReferenceConnectionResolver struct{ *Resolver } -type vendorResolver struct{ *Resolver } -type vendorBusinessAssociateAgreementResolver struct{ *Resolver } -type vendorComplianceReportResolver struct{ *Resolver } -type vendorConnectionResolver struct{ *Resolver } -type vendorContactResolver struct{ *Resolver } -type vendorDataPrivacyAgreementResolver struct{ *Resolver } -type vendorRiskAssessmentResolver struct{ *Resolver } -type vendorServiceResolver struct{ *Resolver } -type viewerResolver struct{ *Resolver } -type webhookEventConnectionResolver struct{ *Resolver } -type webhookSubscriptionResolver struct{ *Resolver } -type webhookSubscriptionConnectionResolver struct{ *Resolver } diff --git a/pkg/server/api/console/v1/vendor.resolvers.go b/pkg/server/api/console/v1/vendor.resolvers.go new file mode 100644 index 000000000..ae60f5fc0 --- /dev/null +++ b/pkg/server/api/console/v1/vendor.resolvers.go @@ -0,0 +1,1138 @@ +package console_v1 + +// This file will be automatically regenerated based on the schema, any resolver +// implementations +// will be copied through when generating and any unknown code will be moved to the end. +// Code generated by github.com/99designs/gqlgen version v0.17.87 + +import ( + "context" + "errors" + "fmt" + "time" + + pgx "github.com/jackc/pgx/v5" + "github.com/vikstrous/dataloadgen" + "go.gearno.de/kit/log" + "go.probo.inc/probo/pkg/coredata" + "go.probo.inc/probo/pkg/iam" + "go.probo.inc/probo/pkg/page" + "go.probo.inc/probo/pkg/probo" + "go.probo.inc/probo/pkg/server/api/console/v1/dataloader" + "go.probo.inc/probo/pkg/server/api/console/v1/schema" + "go.probo.inc/probo/pkg/server/api/console/v1/types" + "go.probo.inc/probo/pkg/server/gqlutils" + "go.probo.inc/probo/pkg/validator" +) + +// CreateVendor is the resolver for the createVendor field. +func (r *mutationResolver) CreateVendor(ctx context.Context, input types.CreateVendorInput) (*types.CreateVendorPayload, error) { + if err := r.authorize(ctx, input.OrganizationID, probo.ActionVendorCreate); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, input.OrganizationID.TenantID()) + + vendor, err := prb.Vendors.Create( + ctx, + probo.CreateVendorRequest{ + OrganizationID: input.OrganizationID, + Name: input.Name, + Description: input.Description, + StatusPageURL: input.StatusPageURL, + TermsOfServiceURL: input.TermsOfServiceURL, + PrivacyPolicyURL: input.PrivacyPolicyURL, + ServiceLevelAgreementURL: input.ServiceLevelAgreementURL, + LegalName: input.LegalName, + HeadquarterAddress: input.HeadquarterAddress, + WebsiteURL: input.WebsiteURL, + Category: input.Category, + DataProcessingAgreementURL: input.DataProcessingAgreementURL, + BusinessAssociateAgreementURL: input.BusinessAssociateAgreementURL, + SubprocessorsListURL: input.SubprocessorsListURL, + Certifications: input.Certifications, + SecurityPageURL: input.SecurityPageURL, + TrustPageURL: input.TrustPageURL, + BusinessOwnerID: input.BusinessOwnerID, + SecurityOwnerID: input.SecurityOwnerID, + Countries: input.Countries, + }, + ) + if err != nil { + if errors.Is(err, coredata.ErrResourceAlreadyExists) { + return nil, gqlutils.Conflict(ctx, err) + } + + if validationErrors, ok := errors.AsType[validator.ValidationErrors](err); ok { + return nil, gqlutils.InvalidValidationErrors(ctx, validationErrors) + } + r.logger.ErrorCtx(ctx, "cannot create vendor", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + return &types.CreateVendorPayload{ + VendorEdge: types.NewVendorEdge(vendor, coredata.VendorOrderFieldName), + }, nil +} + +// UpdateVendor is the resolver for the updateVendor field. +func (r *mutationResolver) UpdateVendor(ctx context.Context, input types.UpdateVendorInput) (*types.UpdateVendorPayload, error) { + if err := r.authorize(ctx, input.ID, probo.ActionVendorUpdate); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, input.ID.TenantID()) + + vendor, err := prb.Vendors.Update( + ctx, + probo.UpdateVendorRequest{ + ID: input.ID, + Name: input.Name, + Description: gqlutils.UnwrapOmittable(input.Description), + StatusPageURL: gqlutils.UnwrapOmittable(input.StatusPageURL), + TermsOfServiceURL: gqlutils.UnwrapOmittable(input.TermsOfServiceURL), + PrivacyPolicyURL: gqlutils.UnwrapOmittable(input.PrivacyPolicyURL), + ServiceLevelAgreementURL: gqlutils.UnwrapOmittable(input.ServiceLevelAgreementURL), + DataProcessingAgreementURL: gqlutils.UnwrapOmittable(input.DataProcessingAgreementURL), + BusinessAssociateAgreementURL: gqlutils.UnwrapOmittable(input.BusinessAssociateAgreementURL), + SubprocessorsListURL: gqlutils.UnwrapOmittable(input.SubprocessorsListURL), + SecurityPageURL: gqlutils.UnwrapOmittable(input.SecurityPageURL), + TrustPageURL: gqlutils.UnwrapOmittable(input.TrustPageURL), + HeadquarterAddress: gqlutils.UnwrapOmittable(input.HeadquarterAddress), + LegalName: gqlutils.UnwrapOmittable(input.LegalName), + WebsiteURL: gqlutils.UnwrapOmittable(input.WebsiteURL), + Category: input.Category, + Certifications: input.Certifications, + BusinessOwnerID: gqlutils.UnwrapOmittable(input.BusinessOwnerID), + SecurityOwnerID: gqlutils.UnwrapOmittable(input.SecurityOwnerID), + ShowOnTrustCenter: input.ShowOnTrustCenter, + Countries: input.Countries, + }, + ) + if err != nil { + if validationErrors, ok := errors.AsType[validator.ValidationErrors](err); ok { + return nil, gqlutils.InvalidValidationErrors(ctx, validationErrors) + } + r.logger.ErrorCtx(ctx, "cannot update vendor", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.UpdateVendorPayload{ + Vendor: types.NewVendor(vendor), + }, nil +} + +// DeleteVendor is the resolver for the deleteVendor field. +func (r *mutationResolver) DeleteVendor(ctx context.Context, input types.DeleteVendorInput) (*types.DeleteVendorPayload, error) { + if err := r.authorize(ctx, input.VendorID, probo.ActionVendorDelete); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, input.VendorID.TenantID()) + + err := prb.Vendors.Delete(ctx, input.VendorID) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot delete vendor", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.DeleteVendorPayload{ + DeletedVendorID: input.VendorID, + }, nil +} + +// CreateVendorContact is the resolver for the createVendorContact field. +func (r *mutationResolver) CreateVendorContact(ctx context.Context, input types.CreateVendorContactInput) (*types.CreateVendorContactPayload, error) { + if err := r.authorize(ctx, input.VendorID, probo.ActionVendorContactCreate); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, input.VendorID.TenantID()) + + req := probo.CreateVendorContactRequest{ + VendorID: input.VendorID, + FullName: input.FullName, + Email: input.Email, + Phone: input.Phone, + Role: input.Role, + } + + vendorContact, err := prb.VendorContacts.Create(ctx, req) + if err != nil { + if validationErrors, ok := errors.AsType[validator.ValidationErrors](err); ok { + return nil, gqlutils.InvalidValidationErrors(ctx, validationErrors) + } + r.logger.ErrorCtx(ctx, "cannot create vendor contact", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.CreateVendorContactPayload{ + VendorContactEdge: types.NewVendorContactEdge(vendorContact, coredata.VendorContactOrderFieldCreatedAt), + }, nil +} + +// UpdateVendorContact is the resolver for the updateVendorContact field. +func (r *mutationResolver) UpdateVendorContact(ctx context.Context, input types.UpdateVendorContactInput) (*types.UpdateVendorContactPayload, error) { + if err := r.authorize(ctx, input.ID, probo.ActionVendorContactUpdate); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, input.ID.TenantID()) + + req := probo.UpdateVendorContactRequest{ + ID: input.ID, + FullName: gqlutils.UnwrapOmittable(input.FullName), + Email: gqlutils.UnwrapOmittable(input.Email), + Phone: gqlutils.UnwrapOmittable(input.Phone), + Role: gqlutils.UnwrapOmittable(input.Role), + } + + vendorContact, err := prb.VendorContacts.Update(ctx, req) + if err != nil { + if validationErrors, ok := errors.AsType[validator.ValidationErrors](err); ok { + return nil, gqlutils.InvalidValidationErrors(ctx, validationErrors) + } + r.logger.ErrorCtx(ctx, "cannot update vendor contact", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.UpdateVendorContactPayload{ + VendorContact: types.NewVendorContact(vendorContact), + }, nil +} + +// DeleteVendorContact is the resolver for the deleteVendorContact field. +func (r *mutationResolver) DeleteVendorContact(ctx context.Context, input types.DeleteVendorContactInput) (*types.DeleteVendorContactPayload, error) { + if err := r.authorize(ctx, input.VendorContactID, probo.ActionVendorContactDelete); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, input.VendorContactID.TenantID()) + + err := prb.VendorContacts.Delete(ctx, input.VendorContactID) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot delete vendor contact", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.DeleteVendorContactPayload{ + DeletedVendorContactID: input.VendorContactID, + }, nil +} + +// CreateVendorService is the resolver for the createVendorService field. +func (r *mutationResolver) CreateVendorService(ctx context.Context, input types.CreateVendorServiceInput) (*types.CreateVendorServicePayload, error) { + if err := r.authorize(ctx, input.VendorID, probo.ActionVendorServiceCreate); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, input.VendorID.TenantID()) + + req := probo.CreateVendorServiceRequest{ + VendorID: input.VendorID, + Name: input.Name, + Description: input.Description, + } + + vendorService, err := prb.VendorServices.Create(ctx, req) + if err != nil { + if validationErrors, ok := errors.AsType[validator.ValidationErrors](err); ok { + return nil, gqlutils.InvalidValidationErrors(ctx, validationErrors) + } + r.logger.ErrorCtx(ctx, "cannot create vendor service", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.CreateVendorServicePayload{ + VendorServiceEdge: types.NewVendorServiceEdge(vendorService, coredata.VendorServiceOrderFieldCreatedAt), + }, nil +} + +// UpdateVendorService is the resolver for the updateVendorService field. +func (r *mutationResolver) UpdateVendorService(ctx context.Context, input types.UpdateVendorServiceInput) (*types.UpdateVendorServicePayload, error) { + if err := r.authorize(ctx, input.ID, probo.ActionVendorServiceUpdate); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, input.ID.TenantID()) + + req := probo.UpdateVendorServiceRequest{ + ID: input.ID, + Name: input.Name, + Description: gqlutils.UnwrapOmittable(input.Description), + } + + vendorService, err := prb.VendorServices.Update(ctx, req) + if err != nil { + if validationErrors, ok := errors.AsType[validator.ValidationErrors](err); ok { + return nil, gqlutils.InvalidValidationErrors(ctx, validationErrors) + } + r.logger.ErrorCtx(ctx, "cannot update vendor service", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.UpdateVendorServicePayload{ + VendorService: types.NewVendorService(vendorService), + }, nil +} + +// DeleteVendorService is the resolver for the deleteVendorService field. +func (r *mutationResolver) DeleteVendorService(ctx context.Context, input types.DeleteVendorServiceInput) (*types.DeleteVendorServicePayload, error) { + if err := r.authorize(ctx, input.VendorServiceID, probo.ActionVendorServiceDelete); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, input.VendorServiceID.TenantID()) + + err := prb.VendorServices.Delete(ctx, input.VendorServiceID) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot delete vendor service", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.DeleteVendorServicePayload{ + DeletedVendorServiceID: input.VendorServiceID, + }, nil +} + +// UploadVendorComplianceReport is the resolver for the uploadVendorComplianceReport field. +func (r *mutationResolver) UploadVendorComplianceReport(ctx context.Context, input types.UploadVendorComplianceReportInput) (*types.UploadVendorComplianceReportPayload, error) { + if err := r.authorize(ctx, input.VendorID, probo.ActionVendorComplianceReportUpload); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, input.VendorID.TenantID()) + + vendorComplianceReport, err := prb.VendorComplianceReports.Upload( + ctx, + input.VendorID, + &probo.VendorComplianceReportCreateRequest{ + File: probo.FileUpload{Filename: input.File.Filename, Size: input.File.Size, Content: input.File.File, ContentType: input.File.ContentType}, + ReportDate: input.ReportDate, + ValidUntil: input.ValidUntil, + ReportName: input.ReportName, + }, + ) + if err != nil { + if validationErrors, ok := errors.AsType[validator.ValidationErrors](err); ok { + return nil, gqlutils.InvalidValidationErrors(ctx, validationErrors) + } + r.logger.ErrorCtx(ctx, "cannot upload vendor compliance report", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.UploadVendorComplianceReportPayload{ + VendorComplianceReportEdge: types.NewVendorComplianceReportEdge(vendorComplianceReport, coredata.VendorComplianceReportOrderFieldCreatedAt), + }, nil +} + +// DeleteVendorComplianceReport is the resolver for the deleteVendorComplianceReport field. +func (r *mutationResolver) DeleteVendorComplianceReport(ctx context.Context, input types.DeleteVendorComplianceReportInput) (*types.DeleteVendorComplianceReportPayload, error) { + if err := r.authorize(ctx, input.ReportID, probo.ActionVendorComplianceReportDelete); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, input.ReportID.TenantID()) + + err := prb.VendorComplianceReports.Delete(ctx, input.ReportID) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot delete vendor compliance report", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.DeleteVendorComplianceReportPayload{ + DeletedVendorComplianceReportID: input.ReportID, + }, nil +} + +// UploadVendorBusinessAssociateAgreement is the resolver for the uploadVendorBusinessAssociateAgreement field. +func (r *mutationResolver) UploadVendorBusinessAssociateAgreement(ctx context.Context, input types.UploadVendorBusinessAssociateAgreementInput) (*types.UploadVendorBusinessAssociateAgreementPayload, error) { + if err := r.authorize(ctx, input.VendorID, probo.ActionVendorBusinessAssociateAgreementUpload); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, input.VendorID.TenantID()) + + vendorBusinessAssociateAgreement, file, err := prb.VendorBusinessAssociateAgreements.Upload( + ctx, + input.VendorID, + &probo.VendorBusinessAssociateAgreementCreateRequest{ + File: input.File.File, + ValidFrom: input.ValidFrom, + ValidUntil: input.ValidUntil, + FileName: input.FileName, + }, + ) + if err != nil { + if validationErrors, ok := errors.AsType[validator.ValidationErrors](err); ok { + return nil, gqlutils.InvalidValidationErrors(ctx, validationErrors) + } + r.logger.ErrorCtx(ctx, "cannot upload vendor business associate agreement", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.UploadVendorBusinessAssociateAgreementPayload{ + VendorBusinessAssociateAgreement: types.NewVendorBusinessAssociateAgreement(vendorBusinessAssociateAgreement, file), + }, nil +} + +// UpdateVendorBusinessAssociateAgreement is the resolver for the updateVendorBusinessAssociateAgreement field. +func (r *mutationResolver) UpdateVendorBusinessAssociateAgreement(ctx context.Context, input types.UpdateVendorBusinessAssociateAgreementInput) (*types.UpdateVendorBusinessAssociateAgreementPayload, error) { + if err := r.authorize(ctx, input.VendorID, probo.ActionVendorBusinessAssociateAgreementUpdate); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, input.VendorID.TenantID()) + + vendorBusinessAssociateAgreement, file, err := prb.VendorBusinessAssociateAgreements.Update( + ctx, + input.VendorID, + &probo.VendorBusinessAssociateAgreementUpdateRequest{ + ValidFrom: gqlutils.UnwrapOmittable(input.ValidFrom), + ValidUntil: gqlutils.UnwrapOmittable(input.ValidUntil), + }, + ) + if err != nil { + if validationErrors, ok := errors.AsType[validator.ValidationErrors](err); ok { + return nil, gqlutils.InvalidValidationErrors(ctx, validationErrors) + } + r.logger.ErrorCtx(ctx, "cannot update vendor business associate agreement", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.UpdateVendorBusinessAssociateAgreementPayload{ + VendorBusinessAssociateAgreement: types.NewVendorBusinessAssociateAgreement(vendorBusinessAssociateAgreement, file), + }, nil +} + +// DeleteVendorBusinessAssociateAgreement is the resolver for the deleteVendorBusinessAssociateAgreement field. +func (r *mutationResolver) DeleteVendorBusinessAssociateAgreement(ctx context.Context, input types.DeleteVendorBusinessAssociateAgreementInput) (*types.DeleteVendorBusinessAssociateAgreementPayload, error) { + if err := r.authorize(ctx, input.VendorID, probo.ActionVendorBusinessAssociateAgreementDelete); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, input.VendorID.TenantID()) + + err := prb.VendorBusinessAssociateAgreements.DeleteByVendorID(ctx, input.VendorID) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot delete vendor business associate agreement", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.DeleteVendorBusinessAssociateAgreementPayload{ + DeletedVendorID: input.VendorID, + }, nil +} + +// UploadVendorDataPrivacyAgreement is the resolver for the uploadVendorDataPrivacyAgreement field. +func (r *mutationResolver) UploadVendorDataPrivacyAgreement(ctx context.Context, input types.UploadVendorDataPrivacyAgreementInput) (*types.UploadVendorDataPrivacyAgreementPayload, error) { + if err := r.authorize(ctx, input.VendorID, probo.ActionVendorDataPrivacyAgreementUpload); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, input.VendorID.TenantID()) + + vendorDataPrivacyAgreement, file, err := prb.VendorDataPrivacyAgreements.Upload( + ctx, + input.VendorID, + &probo.VendorDataPrivacyAgreementCreateRequest{ + File: input.File.File, + ValidFrom: input.ValidFrom, + ValidUntil: input.ValidUntil, + FileName: input.FileName, + }, + ) + if err != nil { + if validationErrors, ok := errors.AsType[validator.ValidationErrors](err); ok { + return nil, gqlutils.InvalidValidationErrors(ctx, validationErrors) + } + r.logger.ErrorCtx(ctx, "cannot upload vendor data privacy agreement", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.UploadVendorDataPrivacyAgreementPayload{ + VendorDataPrivacyAgreement: types.NewVendorDataPrivacyAgreement(vendorDataPrivacyAgreement, file), + }, nil +} + +// UpdateVendorDataPrivacyAgreement is the resolver for the updateVendorDataPrivacyAgreement field. +func (r *mutationResolver) UpdateVendorDataPrivacyAgreement(ctx context.Context, input types.UpdateVendorDataPrivacyAgreementInput) (*types.UpdateVendorDataPrivacyAgreementPayload, error) { + if err := r.authorize(ctx, input.VendorID, probo.ActionVendorDataPrivacyAgreementUpdate); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, input.VendorID.TenantID()) + + vendorDataPrivacyAgreement, file, err := prb.VendorDataPrivacyAgreements.Update( + ctx, + input.VendorID, + &probo.VendorDataPrivacyAgreementUpdateRequest{ + ValidFrom: gqlutils.UnwrapOmittable(input.ValidFrom), + ValidUntil: gqlutils.UnwrapOmittable(input.ValidUntil), + }, + ) + if err != nil { + if validationErrors, ok := errors.AsType[validator.ValidationErrors](err); ok { + return nil, gqlutils.InvalidValidationErrors(ctx, validationErrors) + } + r.logger.ErrorCtx(ctx, "cannot update vendor data privacy agreement", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.UpdateVendorDataPrivacyAgreementPayload{ + VendorDataPrivacyAgreement: types.NewVendorDataPrivacyAgreement(vendorDataPrivacyAgreement, file), + }, nil +} + +// DeleteVendorDataPrivacyAgreement is the resolver for the deleteVendorDataPrivacyAgreement field. +func (r *mutationResolver) DeleteVendorDataPrivacyAgreement(ctx context.Context, input types.DeleteVendorDataPrivacyAgreementInput) (*types.DeleteVendorDataPrivacyAgreementPayload, error) { + if err := r.authorize(ctx, input.VendorID, probo.ActionVendorDataPrivacyAgreementDelete); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, input.VendorID.TenantID()) + + err := prb.VendorDataPrivacyAgreements.DeleteByVendorID(ctx, input.VendorID) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot delete vendor data privacy agreement", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.DeleteVendorDataPrivacyAgreementPayload{ + DeletedVendorID: input.VendorID, + }, nil +} + +// CreateVendorRiskAssessment is the resolver for the createVendorRiskAssessment field. +func (r *mutationResolver) CreateVendorRiskAssessment(ctx context.Context, input types.CreateVendorRiskAssessmentInput) (*types.CreateVendorRiskAssessmentPayload, error) { + if err := r.authorize(ctx, input.VendorID, probo.ActionVendorRiskAssessmentCreate); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, input.VendorID.TenantID()) + + vendorRiskAssessment, err := prb.Vendors.CreateRiskAssessment( + ctx, + probo.CreateVendorRiskAssessmentRequest{ + VendorID: input.VendorID, + ExpiresAt: input.ExpiresAt, + DataSensitivity: input.DataSensitivity, + BusinessImpact: input.BusinessImpact, + Notes: input.Notes, + }, + ) + if err != nil { + if validationErrors, ok := errors.AsType[validator.ValidationErrors](err); ok { + return nil, gqlutils.InvalidValidationErrors(ctx, validationErrors) + } + r.logger.ErrorCtx(ctx, "cannot create vendor risk assessment", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.CreateVendorRiskAssessmentPayload{ + VendorRiskAssessmentEdge: types.NewVendorRiskAssessmentEdge(vendorRiskAssessment, coredata.VendorRiskAssessmentOrderFieldCreatedAt), + }, nil +} + +// AssessVendor is the resolver for the assessVendor field. +func (r *mutationResolver) AssessVendor(ctx context.Context, input types.AssessVendorInput) (*types.AssessVendorPayload, error) { + if err := r.authorize(ctx, input.ID, probo.ActionVendorAssess); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, input.ID.TenantID()) + + vendor, err := prb.Vendors.Assess( + ctx, + probo.AssessVendorRequest{ + ID: input.ID, + WebsiteURL: input.WebsiteURL, + }, + ) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot assess vendor", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.AssessVendorPayload{ + Vendor: types.NewVendor(vendor), + }, nil +} + +// Vendors is the resolver for the vendors field. +func (r *organizationResolver) Vendors(ctx context.Context, obj *types.Organization, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.VendorOrderBy, filter *types.VendorFilter) (*types.VendorConnection, error) { + if err := r.authorize(ctx, obj.ID, probo.ActionVendorList); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, obj.ID.TenantID()) + + pageOrderBy := page.OrderBy[coredata.VendorOrderField]{ + Field: coredata.VendorOrderFieldCreatedAt, + Direction: page.OrderDirectionDesc, + } + if orderBy != nil { + pageOrderBy = page.OrderBy[coredata.VendorOrderField]{ + Field: orderBy.Field, + Direction: orderBy.Direction, + } + } + + cursor := types.NewCursor(first, after, last, before, pageOrderBy) + + var vendorFilter = coredata.NewVendorFilter(nil, nil) + if filter != nil { + vendorFilter = coredata.NewVendorFilter(&filter.SnapshotID, nil) + } + + page, err := prb.Vendors.ListForOrganizationID(ctx, obj.ID, cursor, vendorFilter) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot list organization vendors", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return types.NewVendorConnection(page, r, obj.ID), nil +} + +// Organization is the resolver for the organization field. +func (r *vendorResolver) Organization(ctx context.Context, obj *types.Vendor) (*types.Organization, error) { + if err := r.authorize(ctx, obj.ID, probo.ActionOrganizationGet); err != nil { + return nil, err + } + + loaders := dataloader.FromContext(ctx) + + organization, err := loaders.Organization.Load(ctx, obj.Organization.ID) + if err != nil { + if errors.Is(err, coredata.ErrResourceNotFound) || errors.Is(err, dataloadgen.ErrNotFound) { + return nil, gqlutils.NotFound(ctx, err) + } + + r.logger.ErrorCtx(ctx, "cannot get organization", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return types.NewOrganization(organization), nil +} + +// ComplianceReports is the resolver for the complianceReports field. +func (r *vendorResolver) ComplianceReports(ctx context.Context, obj *types.Vendor, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.VendorComplianceReportOrderBy) (*types.VendorComplianceReportConnection, error) { + if err := r.authorize(ctx, obj.ID, probo.ActionVendorComplianceReportList); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, obj.ID.TenantID()) + + pageOrderBy := page.OrderBy[coredata.VendorComplianceReportOrderField]{ + Field: coredata.VendorComplianceReportOrderFieldReportDate, + Direction: page.OrderDirectionDesc, + } + if orderBy != nil { + pageOrderBy = page.OrderBy[coredata.VendorComplianceReportOrderField]{ + Field: orderBy.Field, + Direction: orderBy.Direction, + } + } + + cursor := types.NewCursor(first, after, last, before, pageOrderBy) + + page, err := prb.VendorComplianceReports.ListForVendorID(ctx, obj.ID, cursor) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot list vendor compliance reports", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return types.NewVendorComplianceReportConnection(page), nil +} + +// BusinessAssociateAgreement is the resolver for the businessAssociateAgreement field. +func (r *vendorResolver) BusinessAssociateAgreement(ctx context.Context, obj *types.Vendor) (*types.VendorBusinessAssociateAgreement, error) { + if err := r.authorize(ctx, obj.ID, probo.ActionVendorBusinessAssociateAgreementGet); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, obj.ID.TenantID()) + + vendorBusinessAssociateAgreement, file, err := prb.VendorBusinessAssociateAgreements.GetByVendorID(ctx, obj.ID) + if err != nil { + if errors.Is(err, pgx.ErrNoRows) { + return nil, nil + } + + r.logger.ErrorCtx(ctx, "cannot get vendor business associate agreement", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return types.NewVendorBusinessAssociateAgreement(vendorBusinessAssociateAgreement, file), nil +} + +// DataPrivacyAgreement is the resolver for the dataPrivacyAgreement field. +func (r *vendorResolver) DataPrivacyAgreement(ctx context.Context, obj *types.Vendor) (*types.VendorDataPrivacyAgreement, error) { + if err := r.authorize(ctx, obj.ID, probo.ActionVendorDataPrivacyAgreementGet); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, obj.ID.TenantID()) + + vendorDataPrivacyAgreement, file, err := prb.VendorDataPrivacyAgreements.GetByVendorID(ctx, obj.ID) + if err != nil { + if errors.Is(err, pgx.ErrNoRows) { + return nil, nil + } + + r.logger.ErrorCtx(ctx, "cannot get vendor data privacy agreement", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return types.NewVendorDataPrivacyAgreement(vendorDataPrivacyAgreement, file), nil +} + +// Contacts is the resolver for the contacts field. +func (r *vendorResolver) Contacts(ctx context.Context, obj *types.Vendor, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.VendorContactOrderBy) (*types.VendorContactConnection, error) { + if err := r.authorize(ctx, obj.ID, probo.ActionVendorContactList); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, obj.ID.TenantID()) + + pageOrderBy := page.OrderBy[coredata.VendorContactOrderField]{ + Field: coredata.VendorContactOrderFieldCreatedAt, + Direction: page.OrderDirectionDesc, + } + if orderBy != nil { + pageOrderBy = page.OrderBy[coredata.VendorContactOrderField]{ + Field: orderBy.Field, + Direction: orderBy.Direction, + } + } + + cursor := types.NewCursor(first, after, last, before, pageOrderBy) + + page, err := prb.VendorContacts.List(ctx, obj.ID, cursor) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot list vendor contacts", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return types.NewVendorContactConnection(page), nil +} + +// Services is the resolver for the services field. +func (r *vendorResolver) Services(ctx context.Context, obj *types.Vendor, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.VendorServiceOrderBy) (*types.VendorServiceConnection, error) { + if err := r.authorize(ctx, obj.ID, probo.ActionVendorServiceList); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, obj.ID.TenantID()) + + pageOrderBy := page.OrderBy[coredata.VendorServiceOrderField]{ + Field: coredata.VendorServiceOrderFieldCreatedAt, + Direction: page.OrderDirectionDesc, + } + if orderBy != nil { + pageOrderBy = page.OrderBy[coredata.VendorServiceOrderField]{ + Field: orderBy.Field, + Direction: orderBy.Direction, + } + } + + cursor := types.NewCursor(first, after, last, before, pageOrderBy) + + page, err := prb.VendorServices.List(ctx, obj.ID, cursor) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot list vendor services", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return types.NewVendorServiceConnection(page), nil +} + +// RiskAssessments is the resolver for the riskAssessments field. +func (r *vendorResolver) RiskAssessments(ctx context.Context, obj *types.Vendor, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.VendorRiskAssessmentOrder) (*types.VendorRiskAssessmentConnection, error) { + if err := r.authorize(ctx, obj.ID, probo.ActionVendorRiskAssessmentList); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, obj.ID.TenantID()) + + pageOrderBy := page.OrderBy[coredata.VendorRiskAssessmentOrderField]{ + Field: coredata.VendorRiskAssessmentOrderFieldCreatedAt, + Direction: page.OrderDirectionDesc, + } + if orderBy != nil { + pageOrderBy = page.OrderBy[coredata.VendorRiskAssessmentOrderField]{ + Field: orderBy.Field, + Direction: orderBy.Direction, + } + } + + cursor := types.NewCursor(first, after, last, before, pageOrderBy) + + page, err := prb.Vendors.ListRiskAssessments(ctx, obj.ID, cursor) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot list vendor risk assessments", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return types.NewVendorRiskAssessmentConnection(page), nil +} + +// BusinessOwner is the resolver for the businessOwner field. +func (r *vendorResolver) BusinessOwner(ctx context.Context, obj *types.Vendor) (*types.Profile, error) { + if err := r.authorize(ctx, obj.ID, iam.ActionMembershipProfileGet); err != nil { + return nil, err + } + + if obj.BusinessOwner == nil { + return nil, nil + } + + loaders := dataloader.FromContext(ctx) + + businessOwner, err := loaders.Profile.Load(ctx, obj.BusinessOwner.ID) + if err != nil { + if errors.Is(err, coredata.ErrResourceNotFound) || errors.Is(err, dataloadgen.ErrNotFound) { + return nil, gqlutils.NotFound(ctx, err) + } + + r.logger.ErrorCtx(ctx, "cannot get business owner", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return types.NewProfile(businessOwner), nil +} + +// SecurityOwner is the resolver for the securityOwner field. +func (r *vendorResolver) SecurityOwner(ctx context.Context, obj *types.Vendor) (*types.Profile, error) { + if err := r.authorize(ctx, obj.ID, iam.ActionMembershipProfileGet); err != nil { + return nil, err + } + + if obj.SecurityOwner == nil { + return nil, nil + } + + loaders := dataloader.FromContext(ctx) + + securityOwner, err := loaders.Profile.Load(ctx, obj.SecurityOwner.ID) + if err != nil { + if errors.Is(err, coredata.ErrResourceNotFound) || errors.Is(err, dataloadgen.ErrNotFound) { + return nil, gqlutils.NotFound(ctx, err) + } + + r.logger.ErrorCtx(ctx, "cannot get security owner", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return types.NewProfile(securityOwner), nil +} + +// Permission is the resolver for the permission field. +func (r *vendorResolver) Permission(ctx context.Context, obj *types.Vendor, action string) (bool, error) { + return r.Resolver.Permission(ctx, obj, action) +} + +// Vendor is the resolver for the vendor field. +func (r *vendorBusinessAssociateAgreementResolver) Vendor(ctx context.Context, obj *types.VendorBusinessAssociateAgreement) (*types.Vendor, error) { + if err := r.authorize(ctx, obj.ID, probo.ActionVendorGet); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, obj.ID.TenantID()) + + vendor, err := prb.Vendors.Get(ctx, obj.ID) + if err != nil { + if errors.Is(err, coredata.ErrResourceNotFound) { + return nil, gqlutils.NotFound(ctx, err) + } + + return nil, fmt.Errorf("cannot get vendor: %w", err) + } + + return types.NewVendor(vendor), nil +} + +// FileURL is the resolver for the fileUrl field. +func (r *vendorBusinessAssociateAgreementResolver) FileURL(ctx context.Context, obj *types.VendorBusinessAssociateAgreement) (string, error) { + if err := r.authorize(ctx, obj.ID, probo.ActionFileDownloadUrl); err != nil { + return "", err + } + + prb := r.ProboService(ctx, obj.ID.TenantID()) + + fileURL, err := prb.VendorBusinessAssociateAgreements.GenerateFileURL(ctx, obj.ID, 1*time.Hour) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot generate file URL", log.Error(err)) + return "", gqlutils.Internal(ctx) + } + + return fileURL, nil +} + +// Permission is the resolver for the permission field. +func (r *vendorBusinessAssociateAgreementResolver) Permission(ctx context.Context, obj *types.VendorBusinessAssociateAgreement, action string) (bool, error) { + return r.Resolver.Permission(ctx, obj, action) +} + +// Vendor is the resolver for the vendor field. +func (r *vendorComplianceReportResolver) Vendor(ctx context.Context, obj *types.VendorComplianceReport) (*types.Vendor, error) { + if err := r.authorize(ctx, obj.ID, probo.ActionVendorGet); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, obj.ID.TenantID()) + + vendor, err := prb.Vendors.Get(ctx, obj.ID) + if err != nil { + if errors.Is(err, coredata.ErrResourceNotFound) { + return nil, gqlutils.NotFound(ctx, err) + } + + r.logger.ErrorCtx(ctx, "cannot get vendor", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return types.NewVendor(vendor), nil +} + +// File is the resolver for the file field. +func (r *vendorComplianceReportResolver) File(ctx context.Context, obj *types.VendorComplianceReport) (*types.File, error) { + if err := r.authorize(ctx, obj.ID, probo.ActionFileGet); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, obj.ID.TenantID()) + + evidence, err := prb.VendorComplianceReports.Get(ctx, obj.ID) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot load evidence", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + if evidence.ReportFileId == nil { + return nil, nil + } + + file, err := prb.Files.Get(ctx, *evidence.ReportFileId) + if err != nil { + if errors.Is(err, coredata.ErrResourceNotFound) { + return nil, gqlutils.NotFound(ctx, err) + } + + r.logger.ErrorCtx(ctx, "cannot load evidence file", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return types.NewFile(file), nil +} + +// Permission is the resolver for the permission field. +func (r *vendorComplianceReportResolver) Permission(ctx context.Context, obj *types.VendorComplianceReport, action string) (bool, error) { + return r.Resolver.Permission(ctx, obj, action) +} + +// TotalCount is the resolver for the totalCount field. +func (r *vendorConnectionResolver) TotalCount(ctx context.Context, obj *types.VendorConnection) (int, error) { + if err := r.authorize(ctx, obj.ParentID, probo.ActionVendorList); err != nil { + return 0, err + } + + prb := r.ProboService(ctx, obj.ParentID.TenantID()) + + switch obj.Resolver.(type) { + case *organizationResolver: + count, err := prb.Vendors.CountForOrganizationID(ctx, obj.ParentID) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot count vendors", log.Error(err)) + return 0, gqlutils.Internal(ctx) + } + return count, nil + case *assetResolver: + count, err := prb.Vendors.CountForAssetID(ctx, obj.ParentID) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot count vendors", log.Error(err)) + return 0, gqlutils.Internal(ctx) + } + return count, nil + case *datumResolver: + count, err := prb.Vendors.CountForDatumID(ctx, obj.ParentID) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot count vendors", log.Error(err)) + return 0, gqlutils.Internal(ctx) + } + return count, nil + } + + r.logger.ErrorCtx(ctx, "unsupported resolver") + return 0, gqlutils.Internal(ctx) +} + +// Vendor is the resolver for the vendor field. +func (r *vendorContactResolver) Vendor(ctx context.Context, obj *types.VendorContact) (*types.Vendor, error) { + if err := r.authorize(ctx, obj.ID, probo.ActionVendorGet); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, obj.ID.TenantID()) + + // Get the vendor contact to access the VendorID + vendorContact, err := prb.VendorContacts.Get(ctx, obj.ID) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot get vendor contact", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + vendor, err := prb.Vendors.Get(ctx, vendorContact.VendorID) + if err != nil { + if errors.Is(err, coredata.ErrResourceNotFound) { + return nil, gqlutils.NotFound(ctx, err) + } + + r.logger.ErrorCtx(ctx, "cannot get vendor", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return types.NewVendor(vendor), nil +} + +// Permission is the resolver for the permission field. +func (r *vendorContactResolver) Permission(ctx context.Context, obj *types.VendorContact, action string) (bool, error) { + return r.Resolver.Permission(ctx, obj, action) +} + +// Vendor is the resolver for the vendor field. +func (r *vendorDataPrivacyAgreementResolver) Vendor(ctx context.Context, obj *types.VendorDataPrivacyAgreement) (*types.Vendor, error) { + if err := r.authorize(ctx, obj.ID, probo.ActionVendorGet); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, obj.ID.TenantID()) + + vendor, err := prb.Vendors.Get(ctx, obj.ID) + if err != nil { + if errors.Is(err, coredata.ErrResourceNotFound) { + return nil, gqlutils.NotFound(ctx, err) + } + + r.logger.ErrorCtx(ctx, "cannot get vendor", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return types.NewVendor(vendor), nil +} + +// FileURL is the resolver for the fileUrl field. +func (r *vendorDataPrivacyAgreementResolver) FileURL(ctx context.Context, obj *types.VendorDataPrivacyAgreement) (string, error) { + if err := r.authorize(ctx, obj.ID, probo.ActionFileDownloadUrl); err != nil { + return "", err + } + + prb := r.ProboService(ctx, obj.ID.TenantID()) + + fileURL, err := prb.VendorDataPrivacyAgreements.GenerateFileURL(ctx, obj.ID, 1*time.Hour) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot generate file URL", log.Error(err)) + return "", gqlutils.Internal(ctx) + } + + return fileURL, nil +} + +// Permission is the resolver for the permission field. +func (r *vendorDataPrivacyAgreementResolver) Permission(ctx context.Context, obj *types.VendorDataPrivacyAgreement, action string) (bool, error) { + return r.Resolver.Permission(ctx, obj, action) +} + +// Vendor is the resolver for the vendor field. +func (r *vendorRiskAssessmentResolver) Vendor(ctx context.Context, obj *types.VendorRiskAssessment) (*types.Vendor, error) { + if err := r.authorize(ctx, obj.ID, probo.ActionVendorGet); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, obj.ID.TenantID()) + + vendor, err := prb.Vendors.GetByRiskAssessmentID(ctx, obj.ID) + if err != nil { + if errors.Is(err, coredata.ErrResourceNotFound) { + return nil, gqlutils.NotFound(ctx, err) + } + + r.logger.ErrorCtx(ctx, "cannot get vendor", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return types.NewVendor(vendor), nil +} + +// Permission is the resolver for the permission field. +func (r *vendorRiskAssessmentResolver) Permission(ctx context.Context, obj *types.VendorRiskAssessment, action string) (bool, error) { + return r.Resolver.Permission(ctx, obj, action) +} + +// Vendor is the resolver for the vendor field. +func (r *vendorServiceResolver) Vendor(ctx context.Context, obj *types.VendorService) (*types.Vendor, error) { + if err := r.authorize(ctx, obj.ID, probo.ActionVendorGet); err != nil { + return nil, err + } + + loaders := dataloader.FromContext(ctx) + + vendor, err := loaders.Vendor.Load(ctx, obj.Vendor.ID) + if err != nil { + if errors.Is(err, coredata.ErrResourceNotFound) || errors.Is(err, dataloadgen.ErrNotFound) { + return nil, gqlutils.NotFound(ctx, err) + } + + r.logger.ErrorCtx(ctx, "cannot get vendor", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return types.NewVendor(vendor), nil +} + +// Permission is the resolver for the permission field. +func (r *vendorServiceResolver) Permission(ctx context.Context, obj *types.VendorService, action string) (bool, error) { + return r.Resolver.Permission(ctx, obj, action) +} + +// Vendor returns schema.VendorResolver implementation. +func (r *Resolver) Vendor() schema.VendorResolver { return &vendorResolver{r} } + +// VendorBusinessAssociateAgreement returns schema.VendorBusinessAssociateAgreementResolver implementation. +func (r *Resolver) VendorBusinessAssociateAgreement() schema.VendorBusinessAssociateAgreementResolver { + return &vendorBusinessAssociateAgreementResolver{r} +} + +// VendorComplianceReport returns schema.VendorComplianceReportResolver implementation. +func (r *Resolver) VendorComplianceReport() schema.VendorComplianceReportResolver { + return &vendorComplianceReportResolver{r} +} + +// VendorConnection returns schema.VendorConnectionResolver implementation. +func (r *Resolver) VendorConnection() schema.VendorConnectionResolver { + return &vendorConnectionResolver{r} +} + +// VendorContact returns schema.VendorContactResolver implementation. +func (r *Resolver) VendorContact() schema.VendorContactResolver { return &vendorContactResolver{r} } + +// VendorDataPrivacyAgreement returns schema.VendorDataPrivacyAgreementResolver implementation. +func (r *Resolver) VendorDataPrivacyAgreement() schema.VendorDataPrivacyAgreementResolver { + return &vendorDataPrivacyAgreementResolver{r} +} + +// VendorRiskAssessment returns schema.VendorRiskAssessmentResolver implementation. +func (r *Resolver) VendorRiskAssessment() schema.VendorRiskAssessmentResolver { + return &vendorRiskAssessmentResolver{r} +} + +// VendorService returns schema.VendorServiceResolver implementation. +func (r *Resolver) VendorService() schema.VendorServiceResolver { return &vendorServiceResolver{r} } + +type vendorResolver struct{ *Resolver } +type vendorBusinessAssociateAgreementResolver struct{ *Resolver } +type vendorComplianceReportResolver struct{ *Resolver } +type vendorConnectionResolver struct{ *Resolver } +type vendorContactResolver struct{ *Resolver } +type vendorDataPrivacyAgreementResolver struct{ *Resolver } +type vendorRiskAssessmentResolver struct{ *Resolver } +type vendorServiceResolver struct{ *Resolver } diff --git a/pkg/server/api/console/v1/webhook.resolvers.go b/pkg/server/api/console/v1/webhook.resolvers.go new file mode 100644 index 000000000..93349687e --- /dev/null +++ b/pkg/server/api/console/v1/webhook.resolvers.go @@ -0,0 +1,261 @@ +package console_v1 + +// This file will be automatically regenerated based on the schema, any resolver +// implementations +// will be copied through when generating and any unknown code will be moved to the end. +// Code generated by github.com/99designs/gqlgen version v0.17.87 + +import ( + "context" + "errors" + "fmt" + + "github.com/vikstrous/dataloadgen" + "go.gearno.de/kit/log" + "go.probo.inc/probo/pkg/coredata" + "go.probo.inc/probo/pkg/page" + "go.probo.inc/probo/pkg/probo" + "go.probo.inc/probo/pkg/server/api/console/v1/dataloader" + "go.probo.inc/probo/pkg/server/api/console/v1/schema" + "go.probo.inc/probo/pkg/server/api/console/v1/types" + "go.probo.inc/probo/pkg/server/gqlutils" + "go.probo.inc/probo/pkg/validator" +) + +// CreateWebhookSubscription is the resolver for the createWebhookSubscription field. +func (r *mutationResolver) CreateWebhookSubscription(ctx context.Context, input types.CreateWebhookSubscriptionInput) (*types.CreateWebhookSubscriptionPayload, error) { + if err := r.authorize(ctx, input.OrganizationID, probo.ActionWebhookSubscriptionCreate); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, input.OrganizationID.TenantID()) + + wc, err := prb.WebhookSubscriptions.Create( + ctx, + probo.CreateWebhookSubscriptionRequest{ + OrganizationID: input.OrganizationID, + EndpointURL: input.EndpointURL, + SelectedEvents: input.SelectedEvents, + }, + ) + if err != nil { + if validationErrors, ok := errors.AsType[validator.ValidationErrors](err); ok { + return nil, gqlutils.InvalidValidationErrors(ctx, validationErrors) + } + r.logger.ErrorCtx(ctx, "cannot create webhook subscription", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.CreateWebhookSubscriptionPayload{ + WebhookSubscriptionEdge: types.NewWebhookSubscriptionEdge(wc, coredata.WebhookSubscriptionOrderFieldCreatedAt), + }, nil +} + +// UpdateWebhookSubscription is the resolver for the updateWebhookSubscription field. +func (r *mutationResolver) UpdateWebhookSubscription(ctx context.Context, input types.UpdateWebhookSubscriptionInput) (*types.UpdateWebhookSubscriptionPayload, error) { + if err := r.authorize(ctx, input.ID, probo.ActionWebhookSubscriptionUpdate); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, input.ID.TenantID()) + + wc, err := prb.WebhookSubscriptions.Update( + ctx, + probo.UpdateWebhookSubscriptionRequest{ + WebhookSubscriptionID: input.ID, + EndpointURL: input.EndpointURL, + SelectedEvents: input.SelectedEvents, + }, + ) + if err != nil { + if validationErrors, ok := errors.AsType[validator.ValidationErrors](err); ok { + return nil, gqlutils.InvalidValidationErrors(ctx, validationErrors) + } + r.logger.ErrorCtx(ctx, "cannot update webhook subscription", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.UpdateWebhookSubscriptionPayload{ + WebhookSubscription: types.NewWebhookSubscription(wc), + }, nil +} + +// DeleteWebhookSubscription is the resolver for the deleteWebhookSubscription field. +func (r *mutationResolver) DeleteWebhookSubscription(ctx context.Context, input types.DeleteWebhookSubscriptionInput) (*types.DeleteWebhookSubscriptionPayload, error) { + if err := r.authorize(ctx, input.WebhookSubscriptionID, probo.ActionWebhookSubscriptionDelete); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, input.WebhookSubscriptionID.TenantID()) + + err := prb.WebhookSubscriptions.Delete(ctx, input.WebhookSubscriptionID) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot delete webhook subscription", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.DeleteWebhookSubscriptionPayload{ + DeletedWebhookSubscriptionID: input.WebhookSubscriptionID, + }, nil +} + +// WebhookSubscriptions is the resolver for the webhookSubscriptions field. +func (r *organizationResolver) WebhookSubscriptions(ctx context.Context, obj *types.Organization, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.WebhookSubscriptionOrderBy) (*types.WebhookSubscriptionConnection, error) { + if err := r.authorize(ctx, obj.ID, probo.ActionWebhookSubscriptionList); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, obj.ID.TenantID()) + + pageOrderBy := page.OrderBy[coredata.WebhookSubscriptionOrderField]{ + Field: coredata.WebhookSubscriptionOrderFieldCreatedAt, + Direction: page.OrderDirectionDesc, + } + if orderBy != nil { + pageOrderBy = page.OrderBy[coredata.WebhookSubscriptionOrderField]{ + Field: orderBy.Field, + Direction: orderBy.Direction, + } + } + + cursor := types.NewCursor(first, after, last, before, pageOrderBy) + + page, err := prb.WebhookSubscriptions.ListForOrganizationID(ctx, obj.ID, cursor) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot list organization webhook subscriptions", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return types.NewWebhookSubscriptionConnection(page, r, obj.ID), nil +} + +// TotalCount is the resolver for the totalCount field. +func (r *webhookEventConnectionResolver) TotalCount(ctx context.Context, obj *types.WebhookEventConnection) (int, error) { + if err := r.authorize(ctx, obj.ParentID, probo.ActionWebhookSubscriptionGet); err != nil { + return 0, err + } + + prb := r.ProboService(ctx, obj.ParentID.TenantID()) + + count, err := prb.WebhookSubscriptions.CountEventsForSubscriptionID(ctx, obj.ParentID) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot count webhook events", log.Error(err)) + return 0, gqlutils.Internal(ctx) + } + + return count, nil +} + +// Organization is the resolver for the organization field. +func (r *webhookSubscriptionResolver) Organization(ctx context.Context, obj *types.WebhookSubscription) (*types.Organization, error) { + if err := r.authorize(ctx, obj.ID, probo.ActionOrganizationGet); err != nil { + return nil, err + } + + loaders := dataloader.FromContext(ctx) + + organization, err := loaders.Organization.Load(ctx, obj.Organization.ID) + if err != nil { + if errors.Is(err, coredata.ErrResourceNotFound) || errors.Is(err, dataloadgen.ErrNotFound) { + return nil, gqlutils.NotFound(ctx, err) + } + + r.logger.ErrorCtx(ctx, "cannot load organization", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return types.NewOrganization(organization), nil +} + +// SigningSecret is the resolver for the signingSecret field. +func (r *webhookSubscriptionResolver) SigningSecret(ctx context.Context, obj *types.WebhookSubscription) (string, error) { + if err := r.authorize(ctx, obj.ID, probo.ActionWebhookSubscriptionUpdate); err != nil { + return "", err + } + + prb := r.ProboService(ctx, obj.ID.TenantID()) + + signingSecret, err := prb.WebhookSubscriptions.GetSigningSecret(ctx, obj.ID) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot get signing secret", log.Error(err)) + return "", gqlutils.Internal(ctx) + } + + return signingSecret, nil +} + +// Events is the resolver for the events field. +func (r *webhookSubscriptionResolver) Events(ctx context.Context, obj *types.WebhookSubscription, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.WebhookEventOrderBy) (*types.WebhookEventConnection, error) { + if err := r.authorize(ctx, obj.ID, probo.ActionWebhookSubscriptionGet); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, obj.ID.TenantID()) + + pageOrderBy := page.OrderBy[coredata.WebhookEventOrderField]{ + Field: coredata.WebhookEventOrderFieldCreatedAt, + Direction: page.OrderDirectionDesc, + } + if orderBy != nil { + pageOrderBy = page.OrderBy[coredata.WebhookEventOrderField]{ + Field: orderBy.Field, + Direction: orderBy.Direction, + } + } + + cursor := types.NewCursor(first, after, last, before, pageOrderBy) + + page, err := prb.WebhookSubscriptions.ListEventsForSubscriptionID(ctx, obj.ID, cursor) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot list webhook events", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return types.NewWebhookEventConnection(page, r, obj.ID), nil +} + +// Permission is the resolver for the permission field. +func (r *webhookSubscriptionResolver) Permission(ctx context.Context, obj *types.WebhookSubscription, action string) (bool, error) { + return r.Resolver.Permission(ctx, obj, action) +} + +// TotalCount is the resolver for the totalCount field. +func (r *webhookSubscriptionConnectionResolver) TotalCount(ctx context.Context, obj *types.WebhookSubscriptionConnection) (int, error) { + if err := r.authorize(ctx, obj.ParentID, probo.ActionWebhookSubscriptionList); err != nil { + return 0, err + } + + prb := r.ProboService(ctx, obj.ParentID.TenantID()) + + switch obj.Resolver.(type) { + case *organizationResolver: + count, err := prb.WebhookSubscriptions.CountForOrganizationID(ctx, obj.ParentID) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot count webhook subscriptions", log.Error(err)) + return 0, gqlutils.Internal(ctx) + } + return count, nil + } + + r.logger.ErrorCtx(ctx, "unsupported resolver for webhook subscription connection", log.String("resolver", fmt.Sprintf("%T", obj.Resolver))) + return 0, gqlutils.Internal(ctx) +} + +// WebhookEventConnection returns schema.WebhookEventConnectionResolver implementation. +func (r *Resolver) WebhookEventConnection() schema.WebhookEventConnectionResolver { + return &webhookEventConnectionResolver{r} +} + +// WebhookSubscription returns schema.WebhookSubscriptionResolver implementation. +func (r *Resolver) WebhookSubscription() schema.WebhookSubscriptionResolver { + return &webhookSubscriptionResolver{r} +} + +// WebhookSubscriptionConnection returns schema.WebhookSubscriptionConnectionResolver implementation. +func (r *Resolver) WebhookSubscriptionConnection() schema.WebhookSubscriptionConnectionResolver { + return &webhookSubscriptionConnectionResolver{r} +} + +type webhookEventConnectionResolver struct{ *Resolver } +type webhookSubscriptionResolver struct{ *Resolver } +type webhookSubscriptionConnectionResolver struct{ *Resolver } diff --git a/pkg/server/api/trust/v1/base.resolvers.go b/pkg/server/api/trust/v1/base.resolvers.go new file mode 100644 index 000000000..329cc0d33 --- /dev/null +++ b/pkg/server/api/trust/v1/base.resolvers.go @@ -0,0 +1,526 @@ +package trust_v1 + +// This file will be automatically regenerated based on the schema, any resolver +// implementations +// will be copied through when generating and any unknown code will be moved to the end. +// Code generated by github.com/99designs/gqlgen version v0.17.87 + +import ( + "context" + "errors" + "strings" + "time" + + "go.gearno.de/kit/log" + "go.probo.inc/probo/pkg/baseurl" + "go.probo.inc/probo/pkg/coredata" + "go.probo.inc/probo/pkg/gid" + "go.probo.inc/probo/pkg/iam" + "go.probo.inc/probo/pkg/page" + "go.probo.inc/probo/pkg/saferedirect" + "go.probo.inc/probo/pkg/server/api/authn" + "go.probo.inc/probo/pkg/server/api/compliancepage" + "go.probo.inc/probo/pkg/server/api/trust/v1/schema" + "go.probo.inc/probo/pkg/server/api/trust/v1/types" + "go.probo.inc/probo/pkg/server/gqlutils" + "go.probo.inc/probo/pkg/trust" +) + +// SendMagicLink is the resolver for the sendMagicLink field. +func (r *mutationResolver) SendMagicLink(ctx context.Context, input types.SendMagicLinkInput) (*types.SendMagicLinkPayload, error) { + trustCenter := compliancepage.CompliancePageFromContext(ctx) + + baseURL := compliancepage.CompliancePageBaseURLFromContext(ctx) + + safeRedirect := saferedirect.New(saferedirect.StaticHosts(baseurl.MustParse(*baseURL).Host())) + + if input.Continue != nil { + _, ok := safeRedirect.Validate(ctx, *input.Continue) + if !ok { + return nil, gqlutils.Invalidf(ctx, "invalid continue URL") + } + } + + req := &iam.SendMagicLinkRequest{ + Email: input.Email, + CompliancePageID: &trustCenter.ID, + OrganizationID: trustCenter.OrganizationID, + URLPath: "verify-magic-link", + Continue: input.Continue, + } + + if err := r.iam.AuthService.SendMagicLink(ctx, req); err != nil { + r.logger.ErrorCtx(ctx, "cannot send magic link", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return nil, nil +} + +// VerifyMagicLink is the resolver for the verifyMagicLink field. +func (r *mutationResolver) VerifyMagicLink(ctx context.Context, input types.VerifyMagicLinkInput) (*types.VerifyMagicLinkPayload, error) { + session := authn.SessionFromContext(ctx) + identity := authn.IdentityFromContext(ctx) + + email, err := r.iam.AuthService.GetMagicLinkEmail(ctx, input.Token) + if err != nil { + var errExpiredToken *iam.ErrExpiredToken + if errors.As(err, &errExpiredToken) { + return nil, gqlutils.Invalid(ctx, err) + } + + var errInvalidToken *iam.ErrInvalidToken + if errors.As(err, &errInvalidToken) { + return nil, gqlutils.Invalid(ctx, err) + } + + r.logger.ErrorCtx(ctx, "cannot get magic link email", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + var continueURL *string + + switch { + case session == nil: + var err error + identity, session, continueURL, err = r.iam.AuthService.OpenSessionWithMagicLink(ctx, input.Token) + if err != nil { + var errExpiredToken *iam.ErrExpiredToken + if errors.As(err, &errExpiredToken) { + return nil, gqlutils.Invalid(ctx, err) + } + + var errInvalidToken *iam.ErrInvalidToken + if errors.As(err, &errInvalidToken) { + return nil, gqlutils.Invalid(ctx, err) + } + + r.logger.ErrorCtx(ctx, "cannot open session with magic link", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + case identity.EmailAddress != email: + if err := r.iam.SessionService.CloseSession(ctx, session.ID); err != nil { + r.logger.ErrorCtx(ctx, "cannot close session", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + var err error + identity, session, continueURL, err = r.iam.AuthService.OpenSessionWithMagicLink(ctx, input.Token) + if err != nil { + var errExpiredToken *iam.ErrExpiredToken + if errors.As(err, &errExpiredToken) { + return nil, gqlutils.Invalid(ctx, err) + } + + var errInvalidToken *iam.ErrInvalidToken + if errors.As(err, &errInvalidToken) { + return nil, gqlutils.Invalid(ctx, err) + } + + r.logger.ErrorCtx(ctx, "cannot open session with magic link", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + } + + trustCenter := compliancepage.CompliancePageFromContext(ctx) + + if _, err := r.trust.ProvisionMember(ctx, trustCenter.ID, identity.ID); err != nil { + r.logger.ErrorCtx(ctx, "cannot provision member", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + w := gqlutils.HTTPResponseWriterFromContext(ctx) + r.sessionCookie.Set(w, session) + + return &types.VerifyMagicLinkPayload{ + Continue: continueURL, + }, nil +} + +// UpdateFullName is the resolver for the updateFullName field. +func (r *mutationResolver) UpdateFullName(ctx context.Context, input types.UpdateFullNameInput) (*types.UpdateFullNamePayload, error) { + identity := authn.IdentityFromContext(ctx) + if identity == nil { + return nil, gqlutils.Unauthenticatedf(ctx, "authentication is required to request access") + } + + identity, err := r.iam.AccountService.UpdateIdentity( + ctx, + identity.ID, + &iam.UpdateIdentityRequest{ + FullName: input.FullName, + }, + ) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot update identity", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + compliancePage := compliancepage.CompliancePageFromContext(ctx) + + profile, err := r.iam.OrganizationService.GetProfileForIdentityAndOrganization(ctx, identity.ID, compliancePage.OrganizationID) + if err != nil { + if _, ok := errors.AsType[*iam.ErrProfileNotFound](err); !ok { + r.logger.ErrorCtx(ctx, "cannot get profile", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + } + + if profile.Source == coredata.ProfileSourceManual { + if _, err := r.iam.OrganizationService.UpdateUser(ctx, &iam.UpdateUserRequest{ + ID: profile.ID, + FullName: identity.FullName, + AdditionalEmailAddresses: profile.AdditionalEmailAddresses, + Kind: profile.Kind, + Position: profile.Position, + ContractStartDate: &profile.ContractStartDate, + ContractEndDate: &profile.ContractEndDate, + }); err != nil { + r.logger.ErrorCtx(ctx, "cannot update profile", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + } + + return &types.UpdateFullNamePayload{Success: true}, nil +} + +// LogoURL is the resolver for the logoUrl field. +func (r *organizationResolver) LogoURL(ctx context.Context, obj *types.Organization) (*string, error) { + trustService := r.TrustService(ctx, obj.ID.TenantID()) + + return trustService.Organizations.GenerateLogoURL(ctx, obj.ID, 1*time.Hour) +} + +// Viewer is the resolver for the viewer field. +func (r *queryResolver) Viewer(ctx context.Context) (*types.Identity, error) { + identity := authn.IdentityFromContext(ctx) + + if identity == nil { + return nil, nil + } + + return &types.Identity{ + ID: identity.ID, + Email: identity.EmailAddress, + FullName: identity.FullName, + EmailVerified: identity.EmailAddressVerified, + CreatedAt: identity.CreatedAt, + UpdatedAt: identity.UpdatedAt, + }, nil +} + +// Node is the resolver for the node field. +func (r *queryResolver) Node(ctx context.Context, id gid.GID) (types.Node, error) { + trustService := r.TrustService(ctx, id.TenantID()) + + switch id.EntityType() { + case coredata.OrganizationEntityType: + organization, err := trustService.Organizations.Get(ctx, id) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot get organization", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + return types.NewOrganization(organization), nil + + case coredata.DocumentEntityType: + trustCenter := compliancepage.CompliancePageFromContext(ctx) + + document, err := trustService.Documents.Get(ctx, trustCenter.OrganizationID, id) + if err != nil { + if errors.Is(err, trust.ErrDocumentNotFound) || errors.Is(err, trust.ErrDocumentNotVisible) || errors.Is(err, coredata.ErrResourceNotFound) { + return nil, gqlutils.NotFoundf(ctx, "node %q not found", id) + } + if _, ok := errors.AsType[*trust.ErrDocumentArchived](err); ok { + return nil, gqlutils.NotFoundf(ctx, "node %q not found", id) + } + r.logger.ErrorCtx(ctx, "cannot get document", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + return types.NewDocument(document), nil + + case coredata.FrameworkEntityType: + framework, err := trustService.Frameworks.Get(ctx, id) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot get framework", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + return types.NewFramework(framework), nil + + case coredata.ReportEntityType: + trustCenter := compliancepage.CompliancePageFromContext(ctx) + + report, err := trustService.Reports.Get(ctx, trustCenter.OrganizationID, id) + if err != nil { + if errors.Is(err, trust.ErrReportNotFound) || errors.Is(err, coredata.ErrResourceNotFound) { + return nil, gqlutils.NotFoundf(ctx, "node %q not found", id) + } + r.logger.ErrorCtx(ctx, "cannot get report", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + return types.NewReport(report), nil + + case coredata.AuditEntityType: + audit, err := trustService.Audits.Get(ctx, id) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot get audit", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + return types.NewAudit(audit), nil + + case coredata.VendorEntityType: + vendor, err := trustService.Vendors.Get(ctx, id) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot get vendor", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + return types.NewSubprocessor(vendor), nil + + case coredata.TrustCenterEntityType: + trustCenter, err := trustService.TrustCenters.Get(ctx, id) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot get trust center", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + return types.NewTrustCenter(trustCenter), nil + + case coredata.TrustCenterReferenceEntityType: + reference, err := trustService.TrustCenterReferences.Get(ctx, id) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot get trust center reference", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + return types.NewTrustCenterReference(reference), nil + + case coredata.TrustCenterFileEntityType: + trustCenter := compliancepage.CompliancePageFromContext(ctx) + + trustCenterFile, err := trustService.TrustCenterFiles.Get(ctx, trustCenter.OrganizationID, id) + if err != nil { + if errors.Is(err, trust.ErrTrustCenterFileNotFound) || errors.Is(err, trust.ErrTrustCenterFileNotVisible) { + return nil, gqlutils.NotFoundf(ctx, "node %q not found", id) + } + r.logger.ErrorCtx(ctx, "cannot get trust center file", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return types.NewTrustCenterFile(trustCenterFile), nil + + default: + return nil, gqlutils.NotFoundf(ctx, "node %q not found", id) + } +} + +// CurrentTrustCenter is the resolver for the currentTrustCenter field. +func (r *queryResolver) CurrentTrustCenter(ctx context.Context) (*types.TrustCenter, error) { + trustCenter := compliancepage.CompliancePageFromContext(ctx) + + trustService := r.TrustService(ctx, trustCenter.ID.TenantID()) + + org, err := trustService.Organizations.Get(ctx, trustCenter.OrganizationID) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot get organization", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + trustCenter, err = trustService.TrustCenters.Get(ctx, trustCenter.ID) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot get trust center", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + response := types.NewTrustCenter(trustCenter) + response.Organization = types.NewOrganization(org) + + return response, nil +} + +// OidcProviders is the resolver for the oidcProviders field. +func (r *queryResolver) OidcProviders(ctx context.Context) ([]*types.OIDCProviderInfo, error) { + providers := r.iam.OIDCService.EnabledProviders() + result := make([]*types.OIDCProviderInfo, 0, len(providers)) + + for _, p := range providers { + name := strings.ToLower(p.String()) + result = append(result, &types.OIDCProviderInfo{ + Name: name, + LoginURL: r.baseURL.WithPath("/api/connect/v1/oidc/" + name + "/login").MustString(), + }) + } + + return result, nil +} + +// LogoFileURL is the resolver for the logoFileUrl field. +func (r *trustCenterResolver) LogoFileURL(ctx context.Context, obj *types.TrustCenter) (*string, error) { + trustService := r.TrustService(ctx, obj.ID.TenantID()) + + return trustService.TrustCenters.GenerateLogoURL(ctx, obj.ID, 1*time.Hour) +} + +// DarkLogoFileURL is the resolver for the darkLogoFileUrl field. +func (r *trustCenterResolver) DarkLogoFileURL(ctx context.Context, obj *types.TrustCenter) (*string, error) { + trustService := r.TrustService(ctx, obj.ID.TenantID()) + + return trustService.TrustCenters.GenerateDarkLogoURL(ctx, obj.ID, 1*time.Hour) +} + +// Organization is the resolver for the organization field. +func (r *trustCenterResolver) Organization(ctx context.Context, obj *types.TrustCenter) (*types.Organization, error) { + return obj.Organization, nil +} + +// Documents is the resolver for the documents field. +func (r *trustCenterResolver) Documents(ctx context.Context, obj *types.TrustCenter, first *int, after *page.CursorKey, last *int, before *page.CursorKey) (*types.DocumentConnection, error) { + trustService := r.TrustService(ctx, obj.ID.TenantID()) + + pageOrderBy := page.OrderBy[coredata.DocumentOrderField]{ + Field: coredata.DocumentOrderFieldTitle, + Direction: page.OrderDirectionAsc, + } + cursor := types.NewCursor(first, after, last, before, pageOrderBy) + + documentPage, err := trustService.Documents.ListForOrganizationId(ctx, obj.Organization.ID, cursor) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot list public documents", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return types.NewDocumentConnection(documentPage), nil +} + +// Audits is the resolver for the audits field. +func (r *trustCenterResolver) Audits(ctx context.Context, obj *types.TrustCenter, first *int, after *page.CursorKey, last *int, before *page.CursorKey) (*types.AuditConnection, error) { + trustService := r.TrustService(ctx, obj.ID.TenantID()) + + pageOrderBy := page.OrderBy[coredata.AuditOrderField]{ + Field: coredata.AuditOrderFieldValidFrom, + Direction: page.OrderDirectionDesc, + } + cursor := types.NewCursor(first, after, last, before, pageOrderBy) + + auditPage, err := trustService.Audits.ListForOrganizationId(ctx, obj.Organization.ID, cursor) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot list public audits", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return types.NewAuditConnection(auditPage), nil +} + +// Subprocessors is the resolver for the subprocessors field. +func (r *trustCenterResolver) Subprocessors(ctx context.Context, obj *types.TrustCenter, first *int, after *page.CursorKey, last *int, before *page.CursorKey) (*types.SubprocessorConnection, error) { + trustService := r.TrustService(ctx, obj.ID.TenantID()) + + pageOrderBy := page.OrderBy[coredata.VendorOrderField]{ + Field: coredata.VendorOrderFieldName, + Direction: page.OrderDirectionAsc, + } + cursor := types.NewCursor(first, after, last, before, pageOrderBy) + + vendorPage, err := trustService.Vendors.ListForOrganizationId(ctx, obj.Organization.ID, cursor) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot list subprocessors", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return types.NewSubprocessorConnection(vendorPage, r, obj.ID), nil +} + +// References is the resolver for the references field. +func (r *trustCenterResolver) References(ctx context.Context, obj *types.TrustCenter, first *int, after *page.CursorKey, last *int, before *page.CursorKey) (*types.TrustCenterReferenceConnection, error) { + trustService := r.TrustService(ctx, obj.ID.TenantID()) + + pageOrderBy := page.OrderBy[coredata.TrustCenterReferenceOrderField]{ + Field: coredata.TrustCenterReferenceOrderFieldRank, + Direction: page.OrderDirectionAsc, + } + cursor := types.NewCursor(first, after, last, before, pageOrderBy) + + referencePage, err := trustService.TrustCenterReferences.ListForTrustCenterID(ctx, obj.ID, cursor) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot list public trust center references", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return types.NewTrustCenterReferenceConnection(referencePage), nil +} + +// TrustCenterFiles is the resolver for the trustCenterFiles field. +func (r *trustCenterResolver) TrustCenterFiles(ctx context.Context, obj *types.TrustCenter, first *int, after *page.CursorKey, last *int, before *page.CursorKey) (*types.TrustCenterFileConnection, error) { + trustService := r.TrustService(ctx, obj.ID.TenantID()) + + pageOrderBy := page.OrderBy[coredata.TrustCenterFileOrderField]{ + Field: coredata.TrustCenterFileOrderFieldName, + Direction: page.OrderDirectionAsc, + } + cursor := types.NewCursor(first, after, last, before, pageOrderBy) + + filter := coredata.NewTrustCenterFileFilter( + coredata.WithTrustCenterFileVisibilities( + coredata.TrustCenterVisibilityPublic, + coredata.TrustCenterVisibilityPrivate, + ), + ) + trustCenterFilePage, err := trustService.TrustCenterFiles.ListForOrganizationId(ctx, obj.Organization.ID, cursor, filter) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot list public trust center files", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return types.NewTrustCenterFileConnection(trustCenterFilePage), nil +} + +// ComplianceFrameworks is the resolver for the complianceFrameworks field. +func (r *trustCenterResolver) ComplianceFrameworks(ctx context.Context, obj *types.TrustCenter, first *int, after *page.CursorKey, last *int, before *page.CursorKey) (*types.ComplianceFrameworkConnection, error) { + trustService := r.TrustService(ctx, obj.ID.TenantID()) + + pageOrderBy := page.OrderBy[coredata.ComplianceFrameworkOrderField]{ + Field: coredata.ComplianceFrameworkOrderFieldRank, + Direction: page.OrderDirectionAsc, + } + cursor := types.NewCursor(first, after, last, before, pageOrderBy) + + cfPage, err := trustService.ComplianceFrameworks.ListByTrustCenterID(ctx, obj.ID, cursor) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot list compliance frameworks", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return types.NewComplianceFrameworkConnection(cfPage), nil +} + +// ExternalUrls is the resolver for the externalUrls field. +func (r *trustCenterResolver) ExternalUrls(ctx context.Context, obj *types.TrustCenter, first *int, after *page.CursorKey, last *int, before *page.CursorKey) (*types.ComplianceExternalURLConnection, error) { + trustService := r.TrustService(ctx, obj.ID.TenantID()) + + pageOrderBy := page.OrderBy[coredata.ComplianceExternalURLOrderField]{ + Field: coredata.ComplianceExternalURLOrderFieldRank, + Direction: page.OrderDirectionAsc, + } + cursor := types.NewCursor(first, after, last, before, pageOrderBy) + + result, err := trustService.ComplianceExternalURLs.ListForTrustCenterID(ctx, obj.ID, cursor) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot list compliance external URLs", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return types.NewComplianceExternalURLConnection(result), nil +} + +// Mutation returns schema.MutationResolver implementation. +func (r *Resolver) Mutation() schema.MutationResolver { return &mutationResolver{r} } + +// Organization returns schema.OrganizationResolver implementation. +func (r *Resolver) Organization() schema.OrganizationResolver { return &organizationResolver{r} } + +// Query returns schema.QueryResolver implementation. +func (r *Resolver) Query() schema.QueryResolver { return &queryResolver{r} } + +// TrustCenter returns schema.TrustCenterResolver implementation. +func (r *Resolver) TrustCenter() schema.TrustCenterResolver { return &trustCenterResolver{r} } + +type mutationResolver struct{ *Resolver } +type organizationResolver struct{ *Resolver } +type queryResolver struct{ *Resolver } +type trustCenterResolver struct{ *Resolver } diff --git a/pkg/server/api/trust/v1/gqlgen.yaml b/pkg/server/api/trust/v1/gqlgen.yaml index be9e7aca8..f8764c0a1 100644 --- a/pkg/server/api/trust/v1/gqlgen.yaml +++ b/pkg/server/api/trust/v1/gqlgen.yaml @@ -1,5 +1,5 @@ schema: - - "schema.graphql" + - "graphql/*.graphql" - "../../../gqlutils/directives/session/schema.graphql" exec: @@ -14,7 +14,7 @@ resolver: layout: "follow-schema" dir: "." package: "trust_v1" - filename_template: "v1_resolver.go" + filename_template: "{name}.resolvers.go" autobind: [] call_argument_directives_with_null: true diff --git a/pkg/server/api/trust/v1/graphql/auth.graphql b/pkg/server/api/trust/v1/graphql/auth.graphql new file mode 100644 index 000000000..d0f1d2f9f --- /dev/null +++ b/pkg/server/api/trust/v1/graphql/auth.graphql @@ -0,0 +1,24 @@ +input SendMagicLinkInput { + email: EmailAddr! + continue: String +} + +type SendMagicLinkPayload { + success: Boolean! +} + +input VerifyMagicLinkInput { + token: String! +} + +type VerifyMagicLinkPayload { + continue: String +} + +input UpdateFullNameInput { + fullName: String! +} + +type UpdateFullNamePayload { + success: Boolean! +} diff --git a/pkg/server/api/trust/v1/schema.graphql b/pkg/server/api/trust/v1/graphql/base.graphql similarity index 57% rename from pkg/server/api/trust/v1/schema.graphql rename to pkg/server/api/trust/v1/graphql/base.graphql index 16cb2365a..3568df9ca 100644 --- a/pkg/server/api/trust/v1/schema.graphql +++ b/pkg/server/api/trust/v1/graphql/base.graphql @@ -1,4 +1,3 @@ -# Directives directive @goField( forceResolver: Boolean name: String @@ -49,111 +48,9 @@ type Organization implements Node { headquarterAddress: String } -enum DocumentType - @goModel(model: "go.probo.inc/probo/pkg/coredata.DocumentType") { - OTHER @goEnum(value: "go.probo.inc/probo/pkg/coredata.DocumentTypeOther") - GOVERNANCE - @goEnum(value: "go.probo.inc/probo/pkg/coredata.DocumentTypeGovernance") - POLICY @goEnum(value: "go.probo.inc/probo/pkg/coredata.DocumentTypePolicy") - PROCEDURE - @goEnum(value: "go.probo.inc/probo/pkg/coredata.DocumentTypeProcedure") - PLAN @goEnum(value: "go.probo.inc/probo/pkg/coredata.DocumentTypePlan") - REGISTER - @goEnum(value: "go.probo.inc/probo/pkg/coredata.DocumentTypeRegister") - RECORD @goEnum(value: "go.probo.inc/probo/pkg/coredata.DocumentTypeRecord") - REPORT @goEnum(value: "go.probo.inc/probo/pkg/coredata.DocumentTypeReport") - TEMPLATE - @goEnum(value: "go.probo.inc/probo/pkg/coredata.DocumentTypeTemplate") -} - -type Document implements Node @nda { - id: ID! - title: String! - documentType: DocumentType! - isUserAuthorized: Boolean! @goField(forceResolver: true) - access: DocumentAccess @goField(forceResolver: true) -} - -type DocumentConnection @nda { - edges: [DocumentEdge!]! - pageInfo: PageInfo! -} - -type DocumentEdge @nda { - cursor: CursorKey! - node: Document! -} - -type Framework implements Node @nda { - id: ID! +type OIDCProviderInfo { name: String! - lightLogoURL: String @goField(forceResolver: true) - darkLogoURL: String @goField(forceResolver: true) -} - -type Report implements Node @nda { - id: ID! - filename: String! - isUserAuthorized: Boolean! @goField(forceResolver: true) - access: DocumentAccess @goField(forceResolver: true) -} - -type Audit implements Node @nda { - id: ID! - name: String - framework: Framework! @goField(forceResolver: true) - report: Report @goField(forceResolver: true) -} - -type AuditConnection @nda { - edges: [AuditEdge!]! - pageInfo: PageInfo! -} - -type AuditEdge @nda { - cursor: CursorKey! - node: Audit! -} - -type ComplianceFramework implements Node - @goModel( - model: "go.probo.inc/probo/pkg/server/api/trust/v1/types.ComplianceFramework" - ) { - id: ID! - framework: Framework! @goField(forceResolver: true) -} - -type ComplianceFrameworkConnection - @goModel( - model: "go.probo.inc/probo/pkg/server/api/trust/v1/types.ComplianceFrameworkConnection" - ) { - edges: [ComplianceFrameworkEdge!]! - pageInfo: PageInfo! -} - -type ComplianceFrameworkEdge - @goModel( - model: "go.probo.inc/probo/pkg/server/api/trust/v1/types.ComplianceFrameworkEdge" - ) { - cursor: CursorKey! - node: ComplianceFramework! -} - -type MailingListUpdate implements Node { - id: ID! - title: String! - body: String! - updatedAt: Datetime! -} - -type MailingListUpdateConnection { - edges: [MailingListUpdateEdge!]! - pageInfo: PageInfo! -} - -type MailingListUpdateEdge { - cursor: CursorKey! - node: MailingListUpdate! + loginURL: String! } enum CountryCode @@ -409,141 +306,13 @@ enum CountryCode ZW @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeZW") } -enum SubprocessorCategory - @goModel(model: "go.probo.inc/probo/pkg/coredata.VendorCategory") { - ANALYTICS - @goEnum(value: "go.probo.inc/probo/pkg/coredata.VendorCategoryAnalytics") - CLOUD_MONITORING - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.VendorCategoryCloudMonitoring" - ) - CLOUD_PROVIDER - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.VendorCategoryCloudProvider" - ) - COLLABORATION - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.VendorCategoryCollaboration" - ) - CUSTOMER_SUPPORT - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.VendorCategoryCustomerSupport" - ) - DATA_STORAGE_AND_PROCESSING - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.VendorCategoryDataStorageAndProcessing" - ) - DOCUMENT_MANAGEMENT - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.VendorCategoryDocumentManagement" - ) - EMPLOYEE_MANAGEMENT - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.VendorCategoryEmployeeManagement" - ) - ENGINEERING - @goEnum(value: "go.probo.inc/probo/pkg/coredata.VendorCategoryEngineering") - FINANCE - @goEnum(value: "go.probo.inc/probo/pkg/coredata.VendorCategoryFinance") - IDENTITY_PROVIDER - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.VendorCategoryIdentityProvider" - ) - IT @goEnum(value: "go.probo.inc/probo/pkg/coredata.VendorCategoryIT") - MARKETING - @goEnum(value: "go.probo.inc/probo/pkg/coredata.VendorCategoryMarketing") - OFFICE_OPERATIONS - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.VendorCategoryOfficeOperations" - ) - OTHER @goEnum(value: "go.probo.inc/probo/pkg/coredata.VendorCategoryOther") - PASSWORD_MANAGEMENT - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.VendorCategoryPasswordManagement" - ) - PRODUCT_AND_DESIGN - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.VendorCategoryProductAndDesign" - ) - PROFESSIONAL_SERVICES - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.VendorCategoryProfessionalServices" - ) - RECRUITING - @goEnum(value: "go.probo.inc/probo/pkg/coredata.VendorCategoryRecruiting") - SALES @goEnum(value: "go.probo.inc/probo/pkg/coredata.VendorCategorySales") - SECURITY - @goEnum(value: "go.probo.inc/probo/pkg/coredata.VendorCategorySecurity") - VERSION_CONTROL - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.VendorCategoryVersionControl" - ) -} - -type Subprocessor implements Node @nda { - id: ID! - name: String! - description: String - category: SubprocessorCategory! - websiteUrl: String - privacyPolicyUrl: String - countries: [CountryCode!]! -} - -type SubprocessorConnection - @goModel( - model: "go.probo.inc/probo/pkg/server/api/trust/v1/types.SubprocessorConnection" - ) @nda { - edges: [SubprocessorEdge!]! - pageInfo: PageInfo! - totalCount: Int! @goField(forceResolver: true) -} - -type SubprocessorEdge @nda { - cursor: CursorKey! - node: Subprocessor! -} - -type TrustCenterReference implements Node @nda { - id: ID! - name: String! - description: String - websiteUrl: String! - logoUrl: String! @goField(forceResolver: true) -} - -type TrustCenterReferenceConnection @nda { - edges: [TrustCenterReferenceEdge!]! - pageInfo: PageInfo! -} - -type TrustCenterReferenceEdge @nda { - cursor: CursorKey! - node: TrustCenterReference! -} - -type TrustCenterFile implements Node @nda { - id: ID! - name: String! - category: String! - isUserAuthorized: Boolean! @goField(forceResolver: true) - access: DocumentAccess @goField(forceResolver: true) -} - -type TrustCenterFileConnection @nda { - edges: [TrustCenterFileEdge!]! - pageInfo: PageInfo! -} - -type TrustCenterFileEdge @nda { - cursor: CursorKey! - node: TrustCenterFile! -} - -type NonDisclosureAgreement { - fileName: String! - fileUrl: String! @goField(forceResolver: true) - viewerSignature: ElectronicSignature @goField(forceResolver: true) +type Query { + viewer: Identity + node(id: ID!): Node + currentTrustCenter: TrustCenter + oidcProviders: [OIDCProviderInfo!]! + @goField(forceResolver: true) + @session(required: OPTIONAL) } type TrustCenter implements Node { @@ -553,9 +322,6 @@ type TrustCenter implements Node { logoFileUrl: String @goField(forceResolver: true) darkLogoFileUrl: String @goField(forceResolver: true) - nonDisclosureAgreement: NonDisclosureAgreement @goField(forceResolver: true) - - viewerSubscription: MailingListSubscriber @goField(forceResolver: true) organization: Organization! @goField(forceResolver: true) documents( @@ -606,292 +372,6 @@ type TrustCenter implements Node { last: Int before: CursorKey ): ComplianceExternalURLConnection! @goField(forceResolver: true) - - updates( - first: Int - after: CursorKey - last: Int - before: CursorKey - ): MailingListUpdateConnection! @goField(forceResolver: true) -} - -type ComplianceExternalURL implements Node { - id: ID! - name: String! - url: String! - rank: Int! -} - -type ComplianceExternalURLConnection { - edges: [ComplianceExternalURLEdge!]! - pageInfo: PageInfo! -} - -type ComplianceExternalURLEdge { - cursor: CursorKey! - node: ComplianceExternalURL! -} - -type TrustCenterAccess implements Node { - id: ID! - email: EmailAddr! - name: String! - createdAt: Datetime! - updatedAt: Datetime! -} - -enum DocumentAccessStatus - @goModel( - model: "go.probo.inc/probo/pkg/coredata.TrustCenterDocumentAccessStatus" - ) { - REQUESTED - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.TrustCenterDocumentAccessStatusRequested" - ) - GRANTED - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.TrustCenterDocumentAccessStatusGranted" - ) - REJECTED - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.TrustCenterDocumentAccessStatusRejected" - ) - REVOKED - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.TrustCenterDocumentAccessStatusRevoked" - ) -} - -type DocumentAccess implements Node { - id: ID! - status: DocumentAccessStatus! -} - -input SendMagicLinkInput { - email: EmailAddr! - continue: String -} - -type SendMagicLinkPayload { - success: Boolean! -} - -input VerifyMagicLinkInput { - token: String! -} - -type VerifyMagicLinkPayload { - continue: String -} - -input UpdateFullNameInput { - fullName: String! -} - -type UpdateFullNamePayload { - success: Boolean! -} - -type RequestDocumentAccessPayload { - document: Document -} - -type RequestReportAccessPayload { - audit: Audit -} - -type RequestFileAccessPayload { - file: TrustCenterFile -} - -type RequestAccessesPayload { - trustCenterAccess: TrustCenterAccess! -} - -input ExportDocumentPDFInput { - documentId: ID! -} - -input ExportReportPDFInput { - reportId: ID! -} - -input RequestDocumentAccessInput { - documentId: ID! -} - -input RequestReportAccessInput { - reportId: ID! -} - -input RequestTrustCenterFileAccessInput { - trustCenterFileId: ID! -} - -input ExportTrustCenterFileInput { - trustCenterFileId: ID! -} - -type ExportDocumentPDFPayload { - data: String! -} - -type ExportReportPDFPayload { - data: String! -} - -type ExportTrustCenterFilePayload { - data: String! -} - -# Electronic Signature - -enum ElectronicSignatureStatus - @goModel( - model: "go.probo.inc/probo/pkg/coredata.ElectronicSignatureStatus" - ) { - PENDING - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.ElectronicSignatureStatusPending" - ) - ACCEPTED - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.ElectronicSignatureStatusAccepted" - ) - PROCESSING - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.ElectronicSignatureStatusProcessing" - ) - COMPLETED - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.ElectronicSignatureStatusCompleted" - ) - FAILED - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.ElectronicSignatureStatusFailed" - ) -} - -enum ElectronicSignatureDocumentType - @goModel( - model: "go.probo.inc/probo/pkg/coredata.ElectronicSignatureDocumentType" - ) { - NDA - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.ElectronicSignatureDocumentTypeNDA" - ) - DPA - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.ElectronicSignatureDocumentTypeDPA" - ) - MSA - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.ElectronicSignatureDocumentTypeMSA" - ) - SOW - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.ElectronicSignatureDocumentTypeSOW" - ) - SLA - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.ElectronicSignatureDocumentTypeSLA" - ) - TOS - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.ElectronicSignatureDocumentTypeTOS" - ) - PRIVACY_POLICY - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.ElectronicSignatureDocumentTypePrivacyPolicy" - ) - OTHER - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.ElectronicSignatureDocumentTypeOther" - ) -} - -enum ElectronicSignatureEventType - @goModel( - model: "go.probo.inc/probo/pkg/coredata.ElectronicSignatureEventType" - ) { - DOCUMENT_VIEWED - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.ElectronicSignatureEventTypeDocumentViewed" - ) - CONSENT_GIVEN - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.ElectronicSignatureEventTypeConsentGiven" - ) - FULL_NAME_TYPED - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.ElectronicSignatureEventTypeFullNameTyped" - ) - SIGNATURE_ACCEPTED - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.ElectronicSignatureEventTypeSignatureAccepted" - ) - SIGNATURE_COMPLETED - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.ElectronicSignatureEventTypeSignatureCompleted" - ) - SEAL_COMPUTED - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.ElectronicSignatureEventTypeSealComputed" - ) - TIMESTAMP_REQUESTED - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.ElectronicSignatureEventTypeTimestampRequested" - ) - CERTIFICATE_GENERATED - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.ElectronicSignatureEventTypeCertificateGenerated" - ) - PROCESSING_ERROR - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.ElectronicSignatureEventTypeProcessingError" - ) -} - -type ElectronicSignature implements Node { - id: ID! - status: ElectronicSignatureStatus! - documentType: ElectronicSignatureDocumentType! - consentText: String! - lastError: String - signedAt: Datetime - createdAt: Datetime! - updatedAt: Datetime! -} - -input AcceptElectronicSignatureInput { - signatureId: ID! -} - -type AcceptElectronicSignaturePayload { - signature: ElectronicSignature! -} - -input RecordSigningEventInput { - signatureId: ID! - eventType: ElectronicSignatureEventType! -} - -type RecordSigningEventPayload { - success: Boolean! -} - -type OIDCProviderInfo { - name: String! - loginURL: String! -} - -type Query { - viewer: Identity - node(id: ID!): Node - currentTrustCenter: TrustCenter - oidcProviders: [OIDCProviderInfo!]! - @goField(forceResolver: true) - @session(required: OPTIONAL) } type Mutation { @@ -901,74 +381,4 @@ type Mutation { @session(required: OPTIONAL) updateFullName(input: UpdateFullNameInput!): UpdateFullNamePayload @session(required: PRESENT) - - requestAllAccesses: RequestAccessesPayload! @session(required: PRESENT) @nda - - exportDocumentPDF(input: ExportDocumentPDFInput!): ExportDocumentPDFPayload! - @session(required: OPTIONAL) @nda - - exportReportPDF(input: ExportReportPDFInput!): ExportReportPDFPayload! - @session(required: OPTIONAL) @nda - - exportTrustCenterFile( - input: ExportTrustCenterFileInput! - ): ExportTrustCenterFilePayload! @session(required: OPTIONAL) @nda - - requestDocumentAccess( - input: RequestDocumentAccessInput! - ): RequestDocumentAccessPayload! @session(required: PRESENT) @nda - - requestReportAccess( - input: RequestReportAccessInput! - ): RequestReportAccessPayload! @session(required: PRESENT) @nda - - requestTrustCenterFileAccess( - input: RequestTrustCenterFileAccessInput! - ): RequestFileAccessPayload! @session(required: PRESENT) @nda - - acceptElectronicSignature( - input: AcceptElectronicSignatureInput! - ): AcceptElectronicSignaturePayload @session(required: PRESENT) - - recordSigningEvent( - input: RecordSigningEventInput! - ): RecordSigningEventPayload @session(required: PRESENT) - - subscribeToMailingList: SubscribeToMailingListPayload! @session(required: PRESENT) - - unsubscribeFromMailingList: UnsubscribeFromMailingListPayload! @session(required: PRESENT) -} - -type SubscribeToMailingListPayload { - subscription: MailingListSubscriber! -} - -type UnsubscribeFromMailingListPayload { - deletedMailingListSubscriberId: ID -} - -enum MailingListSubscriberStatus - @goModel( - model: "go.probo.inc/probo/pkg/coredata.MailingListSubscriberStatus" - ) { - PENDING - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.MailingListSubscriberStatusPending" - ) - CONFIRMED - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.MailingListSubscriberStatusConfirmed" - ) -} - -type MailingListSubscriber implements Node - @goModel( - model: "go.probo.inc/probo/pkg/server/api/trust/v1/types.MailingListSubscriber" - ) { - id: ID! - fullName: String! - email: EmailAddr! - status: MailingListSubscriberStatus! - createdAt: Datetime! - updatedAt: Datetime! } diff --git a/pkg/server/api/trust/v1/graphql/mailing_list.graphql b/pkg/server/api/trust/v1/graphql/mailing_list.graphql new file mode 100644 index 000000000..ed98b0288 --- /dev/null +++ b/pkg/server/api/trust/v1/graphql/mailing_list.graphql @@ -0,0 +1,67 @@ +extend type TrustCenter { + viewerSubscription: MailingListSubscriber @goField(forceResolver: true) + + updates( + first: Int + after: CursorKey + last: Int + before: CursorKey + ): MailingListUpdateConnection! @goField(forceResolver: true) +} + +type MailingListUpdate implements Node { + id: ID! + title: String! + body: String! + updatedAt: Datetime! +} + +type MailingListUpdateConnection { + edges: [MailingListUpdateEdge!]! + pageInfo: PageInfo! +} + +type MailingListUpdateEdge { + cursor: CursorKey! + node: MailingListUpdate! +} + +enum MailingListSubscriberStatus + @goModel( + model: "go.probo.inc/probo/pkg/coredata.MailingListSubscriberStatus" + ) { + PENDING + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.MailingListSubscriberStatusPending" + ) + CONFIRMED + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.MailingListSubscriberStatusConfirmed" + ) +} + +type MailingListSubscriber implements Node + @goModel( + model: "go.probo.inc/probo/pkg/server/api/trust/v1/types.MailingListSubscriber" + ) { + id: ID! + fullName: String! + email: EmailAddr! + status: MailingListSubscriberStatus! + createdAt: Datetime! + updatedAt: Datetime! +} + +extend type Mutation { + subscribeToMailingList: SubscribeToMailingListPayload! @session(required: PRESENT) + + unsubscribeFromMailingList: UnsubscribeFromMailingListPayload! @session(required: PRESENT) +} + +type SubscribeToMailingListPayload { + subscription: MailingListSubscriber! +} + +type UnsubscribeFromMailingListPayload { + deletedMailingListSubscriberId: ID +} diff --git a/pkg/server/api/trust/v1/graphql/nda.graphql b/pkg/server/api/trust/v1/graphql/nda.graphql new file mode 100644 index 000000000..064615569 --- /dev/null +++ b/pkg/server/api/trust/v1/graphql/nda.graphql @@ -0,0 +1,153 @@ +extend type TrustCenter { + nonDisclosureAgreement: NonDisclosureAgreement @goField(forceResolver: true) +} + +type NonDisclosureAgreement { + fileName: String! + fileUrl: String! @goField(forceResolver: true) + viewerSignature: ElectronicSignature @goField(forceResolver: true) +} + +enum ElectronicSignatureStatus + @goModel( + model: "go.probo.inc/probo/pkg/coredata.ElectronicSignatureStatus" + ) { + PENDING + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.ElectronicSignatureStatusPending" + ) + ACCEPTED + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.ElectronicSignatureStatusAccepted" + ) + PROCESSING + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.ElectronicSignatureStatusProcessing" + ) + COMPLETED + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.ElectronicSignatureStatusCompleted" + ) + FAILED + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.ElectronicSignatureStatusFailed" + ) +} + +enum ElectronicSignatureDocumentType + @goModel( + model: "go.probo.inc/probo/pkg/coredata.ElectronicSignatureDocumentType" + ) { + NDA + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.ElectronicSignatureDocumentTypeNDA" + ) + DPA + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.ElectronicSignatureDocumentTypeDPA" + ) + MSA + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.ElectronicSignatureDocumentTypeMSA" + ) + SOW + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.ElectronicSignatureDocumentTypeSOW" + ) + SLA + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.ElectronicSignatureDocumentTypeSLA" + ) + TOS + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.ElectronicSignatureDocumentTypeTOS" + ) + PRIVACY_POLICY + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.ElectronicSignatureDocumentTypePrivacyPolicy" + ) + OTHER + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.ElectronicSignatureDocumentTypeOther" + ) +} + +enum ElectronicSignatureEventType + @goModel( + model: "go.probo.inc/probo/pkg/coredata.ElectronicSignatureEventType" + ) { + DOCUMENT_VIEWED + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.ElectronicSignatureEventTypeDocumentViewed" + ) + CONSENT_GIVEN + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.ElectronicSignatureEventTypeConsentGiven" + ) + FULL_NAME_TYPED + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.ElectronicSignatureEventTypeFullNameTyped" + ) + SIGNATURE_ACCEPTED + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.ElectronicSignatureEventTypeSignatureAccepted" + ) + SIGNATURE_COMPLETED + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.ElectronicSignatureEventTypeSignatureCompleted" + ) + SEAL_COMPUTED + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.ElectronicSignatureEventTypeSealComputed" + ) + TIMESTAMP_REQUESTED + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.ElectronicSignatureEventTypeTimestampRequested" + ) + CERTIFICATE_GENERATED + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.ElectronicSignatureEventTypeCertificateGenerated" + ) + PROCESSING_ERROR + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.ElectronicSignatureEventTypeProcessingError" + ) +} + +type ElectronicSignature implements Node { + id: ID! + status: ElectronicSignatureStatus! + documentType: ElectronicSignatureDocumentType! + consentText: String! + lastError: String + signedAt: Datetime + createdAt: Datetime! + updatedAt: Datetime! +} + +extend type Mutation { + acceptElectronicSignature( + input: AcceptElectronicSignatureInput! + ): AcceptElectronicSignaturePayload @session(required: PRESENT) + + recordSigningEvent( + input: RecordSigningEventInput! + ): RecordSigningEventPayload @session(required: PRESENT) +} + +input AcceptElectronicSignatureInput { + signatureId: ID! +} + +type AcceptElectronicSignaturePayload { + signature: ElectronicSignature! +} + +input RecordSigningEventInput { + signatureId: ID! + eventType: ElectronicSignatureEventType! +} + +type RecordSigningEventPayload { + success: Boolean! +} diff --git a/pkg/server/api/trust/v1/graphql/trust_center.graphql b/pkg/server/api/trust/v1/graphql/trust_center.graphql new file mode 100644 index 000000000..d68bb21dd --- /dev/null +++ b/pkg/server/api/trust/v1/graphql/trust_center.graphql @@ -0,0 +1,350 @@ +enum DocumentType + @goModel(model: "go.probo.inc/probo/pkg/coredata.DocumentType") { + OTHER @goEnum(value: "go.probo.inc/probo/pkg/coredata.DocumentTypeOther") + GOVERNANCE + @goEnum(value: "go.probo.inc/probo/pkg/coredata.DocumentTypeGovernance") + POLICY @goEnum(value: "go.probo.inc/probo/pkg/coredata.DocumentTypePolicy") + PROCEDURE + @goEnum(value: "go.probo.inc/probo/pkg/coredata.DocumentTypeProcedure") + PLAN @goEnum(value: "go.probo.inc/probo/pkg/coredata.DocumentTypePlan") + REGISTER + @goEnum(value: "go.probo.inc/probo/pkg/coredata.DocumentTypeRegister") + RECORD @goEnum(value: "go.probo.inc/probo/pkg/coredata.DocumentTypeRecord") + REPORT @goEnum(value: "go.probo.inc/probo/pkg/coredata.DocumentTypeReport") + TEMPLATE + @goEnum(value: "go.probo.inc/probo/pkg/coredata.DocumentTypeTemplate") +} + +type Document implements Node @nda { + id: ID! + title: String! + documentType: DocumentType! + isUserAuthorized: Boolean! @goField(forceResolver: true) + access: DocumentAccess @goField(forceResolver: true) +} + +type DocumentConnection @nda { + edges: [DocumentEdge!]! + pageInfo: PageInfo! +} + +type DocumentEdge @nda { + cursor: CursorKey! + node: Document! +} + +type Framework implements Node @nda { + id: ID! + name: String! + lightLogoURL: String @goField(forceResolver: true) + darkLogoURL: String @goField(forceResolver: true) +} + +type Report implements Node @nda { + id: ID! + filename: String! + isUserAuthorized: Boolean! @goField(forceResolver: true) + access: DocumentAccess @goField(forceResolver: true) +} + +type Audit implements Node @nda { + id: ID! + name: String + framework: Framework! @goField(forceResolver: true) + report: Report @goField(forceResolver: true) +} + +type AuditConnection @nda { + edges: [AuditEdge!]! + pageInfo: PageInfo! +} + +type AuditEdge @nda { + cursor: CursorKey! + node: Audit! +} + +type ComplianceFramework implements Node + @goModel( + model: "go.probo.inc/probo/pkg/server/api/trust/v1/types.ComplianceFramework" + ) { + id: ID! + framework: Framework! @goField(forceResolver: true) +} + +type ComplianceFrameworkConnection + @goModel( + model: "go.probo.inc/probo/pkg/server/api/trust/v1/types.ComplianceFrameworkConnection" + ) { + edges: [ComplianceFrameworkEdge!]! + pageInfo: PageInfo! +} + +type ComplianceFrameworkEdge + @goModel( + model: "go.probo.inc/probo/pkg/server/api/trust/v1/types.ComplianceFrameworkEdge" + ) { + cursor: CursorKey! + node: ComplianceFramework! +} + +enum SubprocessorCategory + @goModel(model: "go.probo.inc/probo/pkg/coredata.VendorCategory") { + ANALYTICS + @goEnum(value: "go.probo.inc/probo/pkg/coredata.VendorCategoryAnalytics") + CLOUD_MONITORING + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.VendorCategoryCloudMonitoring" + ) + CLOUD_PROVIDER + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.VendorCategoryCloudProvider" + ) + COLLABORATION + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.VendorCategoryCollaboration" + ) + CUSTOMER_SUPPORT + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.VendorCategoryCustomerSupport" + ) + DATA_STORAGE_AND_PROCESSING + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.VendorCategoryDataStorageAndProcessing" + ) + DOCUMENT_MANAGEMENT + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.VendorCategoryDocumentManagement" + ) + EMPLOYEE_MANAGEMENT + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.VendorCategoryEmployeeManagement" + ) + ENGINEERING + @goEnum(value: "go.probo.inc/probo/pkg/coredata.VendorCategoryEngineering") + FINANCE + @goEnum(value: "go.probo.inc/probo/pkg/coredata.VendorCategoryFinance") + IDENTITY_PROVIDER + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.VendorCategoryIdentityProvider" + ) + IT @goEnum(value: "go.probo.inc/probo/pkg/coredata.VendorCategoryIT") + MARKETING + @goEnum(value: "go.probo.inc/probo/pkg/coredata.VendorCategoryMarketing") + OFFICE_OPERATIONS + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.VendorCategoryOfficeOperations" + ) + OTHER @goEnum(value: "go.probo.inc/probo/pkg/coredata.VendorCategoryOther") + PASSWORD_MANAGEMENT + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.VendorCategoryPasswordManagement" + ) + PRODUCT_AND_DESIGN + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.VendorCategoryProductAndDesign" + ) + PROFESSIONAL_SERVICES + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.VendorCategoryProfessionalServices" + ) + RECRUITING + @goEnum(value: "go.probo.inc/probo/pkg/coredata.VendorCategoryRecruiting") + SALES @goEnum(value: "go.probo.inc/probo/pkg/coredata.VendorCategorySales") + SECURITY + @goEnum(value: "go.probo.inc/probo/pkg/coredata.VendorCategorySecurity") + VERSION_CONTROL + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.VendorCategoryVersionControl" + ) +} + +type Subprocessor implements Node @nda { + id: ID! + name: String! + description: String + category: SubprocessorCategory! + websiteUrl: String + privacyPolicyUrl: String + countries: [CountryCode!]! +} + +type SubprocessorConnection + @goModel( + model: "go.probo.inc/probo/pkg/server/api/trust/v1/types.SubprocessorConnection" + ) @nda { + edges: [SubprocessorEdge!]! + pageInfo: PageInfo! + totalCount: Int! @goField(forceResolver: true) +} + +type SubprocessorEdge @nda { + cursor: CursorKey! + node: Subprocessor! +} + +type TrustCenterReference implements Node @nda { + id: ID! + name: String! + description: String + websiteUrl: String! + logoUrl: String! @goField(forceResolver: true) +} + +type TrustCenterReferenceConnection @nda { + edges: [TrustCenterReferenceEdge!]! + pageInfo: PageInfo! +} + +type TrustCenterReferenceEdge @nda { + cursor: CursorKey! + node: TrustCenterReference! +} + +type TrustCenterFile implements Node @nda { + id: ID! + name: String! + category: String! + isUserAuthorized: Boolean! @goField(forceResolver: true) + access: DocumentAccess @goField(forceResolver: true) +} + +type TrustCenterFileConnection @nda { + edges: [TrustCenterFileEdge!]! + pageInfo: PageInfo! +} + +type TrustCenterFileEdge @nda { + cursor: CursorKey! + node: TrustCenterFile! +} + +type ComplianceExternalURL implements Node { + id: ID! + name: String! + url: String! + rank: Int! +} + +type ComplianceExternalURLConnection { + edges: [ComplianceExternalURLEdge!]! + pageInfo: PageInfo! +} + +type ComplianceExternalURLEdge { + cursor: CursorKey! + node: ComplianceExternalURL! +} + +type TrustCenterAccess implements Node { + id: ID! + email: EmailAddr! + name: String! + createdAt: Datetime! + updatedAt: Datetime! +} + +enum DocumentAccessStatus + @goModel( + model: "go.probo.inc/probo/pkg/coredata.TrustCenterDocumentAccessStatus" + ) { + REQUESTED + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.TrustCenterDocumentAccessStatusRequested" + ) + GRANTED + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.TrustCenterDocumentAccessStatusGranted" + ) + REJECTED + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.TrustCenterDocumentAccessStatusRejected" + ) + REVOKED + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.TrustCenterDocumentAccessStatusRevoked" + ) +} + +type DocumentAccess implements Node { + id: ID! + status: DocumentAccessStatus! +} + +extend type Mutation { + requestAllAccesses: RequestAccessesPayload! @session(required: PRESENT) @nda + + exportDocumentPDF(input: ExportDocumentPDFInput!): ExportDocumentPDFPayload! + @session(required: OPTIONAL) @nda + + exportReportPDF(input: ExportReportPDFInput!): ExportReportPDFPayload! + @session(required: OPTIONAL) @nda + + exportTrustCenterFile( + input: ExportTrustCenterFileInput! + ): ExportTrustCenterFilePayload! @session(required: OPTIONAL) @nda + + requestDocumentAccess( + input: RequestDocumentAccessInput! + ): RequestDocumentAccessPayload! @session(required: PRESENT) @nda + + requestReportAccess( + input: RequestReportAccessInput! + ): RequestReportAccessPayload! @session(required: PRESENT) @nda + + requestTrustCenterFileAccess( + input: RequestTrustCenterFileAccessInput! + ): RequestFileAccessPayload! @session(required: PRESENT) @nda +} + +type RequestDocumentAccessPayload { + document: Document +} + +type RequestReportAccessPayload { + audit: Audit +} + +type RequestFileAccessPayload { + file: TrustCenterFile +} + +type RequestAccessesPayload { + trustCenterAccess: TrustCenterAccess! +} + +input ExportDocumentPDFInput { + documentId: ID! +} + +input ExportReportPDFInput { + reportId: ID! +} + +input RequestDocumentAccessInput { + documentId: ID! +} + +input RequestReportAccessInput { + reportId: ID! +} + +input RequestTrustCenterFileAccessInput { + trustCenterFileId: ID! +} + +input ExportTrustCenterFileInput { + trustCenterFileId: ID! +} + +type ExportDocumentPDFPayload { + data: String! +} + +type ExportReportPDFPayload { + data: String! +} + +type ExportTrustCenterFilePayload { + data: String! +} diff --git a/pkg/server/api/trust/v1/mailing_list.resolvers.go b/pkg/server/api/trust/v1/mailing_list.resolvers.go new file mode 100644 index 000000000..c566873e3 --- /dev/null +++ b/pkg/server/api/trust/v1/mailing_list.resolvers.go @@ -0,0 +1,137 @@ +package trust_v1 + +// This file will be automatically regenerated based on the schema, any resolver +// implementations +// will be copied through when generating and any unknown code will be moved to the end. +// Code generated by github.com/99designs/gqlgen version v0.17.87 + +import ( + "context" + "errors" + + "go.gearno.de/kit/log" + "go.probo.inc/probo/pkg/coredata" + "go.probo.inc/probo/pkg/mailman" + "go.probo.inc/probo/pkg/page" + "go.probo.inc/probo/pkg/server/api/authn" + "go.probo.inc/probo/pkg/server/api/compliancepage" + "go.probo.inc/probo/pkg/server/api/trust/v1/types" + "go.probo.inc/probo/pkg/server/gqlutils" + "go.probo.inc/probo/pkg/validator" +) + +// SubscribeToMailingList is the resolver for the subscribeToMailingList field. +func (r *mutationResolver) SubscribeToMailingList(ctx context.Context) (*types.SubscribeToMailingListPayload, error) { + trustCenter := compliancepage.CompliancePageFromContext(ctx) + if trustCenter.MailingListID == nil { + return nil, gqlutils.NotFoundf(ctx, "mailing list not found") + } + + identity := authn.IdentityFromContext(ctx) + + subscriber, err := r.mailman.CreateSubscriber( + ctx, + &mailman.CreateSubscriberRequest{ + MailingListID: *trustCenter.MailingListID, + Email: identity.EmailAddress, + FullName: identity.FullName, + }, + ) + if err != nil { + if validationErrors, ok := errors.AsType[validator.ValidationErrors](err); ok { + return nil, gqlutils.InvalidValidationErrors(ctx, validationErrors) + } + if errors.Is(err, mailman.ErrSubscriberAlreadyExist) { + return nil, gqlutils.Conflictf(ctx, "already subscribed to this mailing list") + } + r.logger.ErrorCtx(ctx, "cannot subscribe to mailing list", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.SubscribeToMailingListPayload{ + Subscription: types.NewMailingListSubscriber(subscriber), + }, nil +} + +// UnsubscribeFromMailingList is the resolver for the unsubscribeFromMailingList field. +func (r *mutationResolver) UnsubscribeFromMailingList(ctx context.Context) (*types.UnsubscribeFromMailingListPayload, error) { + trustCenter := compliancepage.CompliancePageFromContext(ctx) + if trustCenter.MailingListID == nil { + return nil, gqlutils.NotFoundf(ctx, "mailing list not found") + } + + identity := authn.IdentityFromContext(ctx) + + subscriber, err := r.mailman.GetSubscriber(ctx, *trustCenter.MailingListID, identity.EmailAddress) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot get mailing list subscription", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + if subscriber == nil { + return nil, gqlutils.NotFoundf(ctx, "not subscribed to this mailing list") + } + + if err := r.mailman.DeleteSubscriber(ctx, subscriber.ID); err != nil { + if errors.Is(err, mailman.ErrSubscriberNotFound) { + return nil, gqlutils.NotFoundf(ctx, "not subscribed to this mailing list") + } + r.logger.ErrorCtx(ctx, "cannot unsubscribe from mailing list", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.UnsubscribeFromMailingListPayload{DeletedMailingListSubscriberID: &subscriber.ID}, nil +} + +// ViewerSubscription is the resolver for the viewerSubscription field. +func (r *trustCenterResolver) ViewerSubscription(ctx context.Context, obj *types.TrustCenter) (*types.MailingListSubscriber, error) { + trustCenter := compliancepage.CompliancePageFromContext(ctx) + if trustCenter.MailingListID == nil { + return nil, nil + } + + identity := authn.IdentityFromContext(ctx) + if identity == nil { + return nil, nil + } + + subscriber, err := r.mailman.GetSubscriber(ctx, *trustCenter.MailingListID, identity.EmailAddress) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot get mailing list subscription", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + if subscriber == nil { + return nil, nil + } + + return types.NewMailingListSubscriber(subscriber), nil +} + +// Updates is the resolver for the updates field. +func (r *trustCenterResolver) Updates(ctx context.Context, obj *types.TrustCenter, first *int, after *page.CursorKey, last *int, before *page.CursorKey) (*types.MailingListUpdateConnection, error) { + trustService := r.TrustService(ctx, obj.ID.TenantID()) + + tc, err := trustService.TrustCenters.Get(ctx, obj.ID) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot load trust center", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + if tc.MailingListID == nil { + return &types.MailingListUpdateConnection{Edges: []*types.MailingListUpdateEdge{}, PageInfo: &types.PageInfo{}}, nil + } + + pageOrderBy := page.OrderBy[coredata.MailingListUpdateOrderField]{ + Field: coredata.MailingListUpdateOrderFieldUpdatedAt, + Direction: page.OrderDirectionDesc, + } + cursor := types.NewCursor(first, after, last, before, pageOrderBy) + + result, err := r.mailman.ListSentMailingListUpdates(ctx, *tc.MailingListID, cursor) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot list mailing list updates", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return types.NewMailingListUpdateConnection(result), nil +} diff --git a/pkg/server/api/trust/v1/nda.resolvers.go b/pkg/server/api/trust/v1/nda.resolvers.go new file mode 100644 index 000000000..d61863dc4 --- /dev/null +++ b/pkg/server/api/trust/v1/nda.resolvers.go @@ -0,0 +1,166 @@ +package trust_v1 + +// This file will be automatically regenerated based on the schema, any resolver +// implementations +// will be copied through when generating and any unknown code will be moved to the end. +// Code generated by github.com/99designs/gqlgen version v0.17.87 + +import ( + "context" + "net" + "time" + + "go.gearno.de/kit/log" + "go.probo.inc/probo/pkg/coredata" + "go.probo.inc/probo/pkg/esign" + "go.probo.inc/probo/pkg/server/api/authn" + "go.probo.inc/probo/pkg/server/api/compliancepage" + "go.probo.inc/probo/pkg/server/api/trust/v1/schema" + "go.probo.inc/probo/pkg/server/api/trust/v1/types" + "go.probo.inc/probo/pkg/server/gqlutils" +) + +// AcceptElectronicSignature is the resolver for the acceptElectronicSignature field. +func (r *mutationResolver) AcceptElectronicSignature(ctx context.Context, input types.AcceptElectronicSignatureInput) (*types.AcceptElectronicSignaturePayload, error) { + var ( + identity = authn.IdentityFromContext(ctx) + httpReq = gqlutils.HTTPRequestFromContext(ctx) + ) + + signerIP, _, _ := net.SplitHostPort(httpReq.RemoteAddr) + if signerIP == "" { + signerIP = httpReq.RemoteAddr + } + + signature, err := r.esign.AcceptSignature( + ctx, + &esign.AcceptSignatureRequest{ + SignatureID: input.SignatureID, + SignerFullName: identity.FullName, + SignerEmail: identity.EmailAddress, + SignerIPAddr: signerIP, + SignerUA: httpReq.UserAgent(), + }, + ) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot accept electronic signature", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.AcceptElectronicSignaturePayload{ + Signature: types.NewElectronicSignature(signature), + }, nil +} + +// RecordSigningEvent is the resolver for the recordSigningEvent field. +func (r *mutationResolver) RecordSigningEvent(ctx context.Context, input types.RecordSigningEventInput) (*types.RecordSigningEventPayload, error) { + var ( + identity = authn.IdentityFromContext(ctx) + httpReq = gqlutils.HTTPRequestFromContext(ctx) + ) + + actorIP, _, _ := net.SplitHostPort(httpReq.RemoteAddr) + if actorIP == "" { + actorIP = httpReq.RemoteAddr + } + + if err := r.esign.RecordEvent( + ctx, + &esign.RecordEventRequest{ + SignatureID: input.SignatureID, + EventType: input.EventType, + EventSource: coredata.ElectronicSignatureEventSourceClient, + ActorEmail: identity.EmailAddress, + ActorIPAddr: actorIP, + ActorUA: httpReq.UserAgent(), + }, + ); err != nil { + r.logger.ErrorCtx(ctx, "cannot record signing event", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.RecordSigningEventPayload{Success: true}, nil +} + +// FileURL is the resolver for the fileUrl field. +func (r *nonDisclosureAgreementResolver) FileURL(ctx context.Context, obj *types.NonDisclosureAgreement) (string, error) { + trustCenter := compliancepage.CompliancePageFromContext(ctx) + + if identity := authn.IdentityFromContext(ctx); identity != nil && r.esign != nil { + trustService := r.TrustService(ctx, trustCenter.ID.TenantID()) + + access, err := trustService.TrustCenterAccesses.GetAccess(ctx, trustCenter.ID, identity.ID) + if err == nil && access.ElectronicSignatureID != nil { + fileURL, err := r.esign.GenerateSignatureFileURL(ctx, *access.ElectronicSignatureID, 15*time.Minute) + if err == nil { + return fileURL, nil + } + + r.logger.ErrorCtx(ctx, "cannot generate signature file URL, falling back to original NDA", log.Error(err)) + } + } + + trustService := r.TrustService(ctx, trustCenter.ID.TenantID()) + + fileURL, err := trustService.TrustCenters.GenerateNDAFileURL(ctx, trustCenter.ID, 15*time.Minute) + if err != nil { + return "", gqlutils.Internal(ctx) + } + + return fileURL, nil +} + +// ViewerSignature is the resolver for the viewerSignature field. +func (r *nonDisclosureAgreementResolver) ViewerSignature(ctx context.Context, obj *types.NonDisclosureAgreement) (*types.ElectronicSignature, error) { + identity := authn.IdentityFromContext(ctx) + if identity == nil { + return nil, nil + } + + trustCenter := compliancepage.CompliancePageFromContext(ctx) + trustService := r.TrustService(ctx, trustCenter.ID.TenantID()) + + access, err := trustService.TrustCenterAccesses.GetAccess(ctx, trustCenter.ID, identity.ID) + if err != nil { + return nil, nil + } + + if access.ElectronicSignatureID == nil { + return nil, nil + } + + sig, err := r.esign.GetSignatureByID(ctx, *access.ElectronicSignatureID) + if err != nil { + return nil, nil + } + + return types.NewElectronicSignature(sig), nil +} + +// NonDisclosureAgreement is the resolver for the nonDisclosureAgreement field. +func (r *trustCenterResolver) NonDisclosureAgreement(ctx context.Context, obj *types.TrustCenter) (*types.NonDisclosureAgreement, error) { + trustCenter := compliancepage.CompliancePageFromContext(ctx) + if trustCenter.NonDisclosureAgreementFileID == nil { + return nil, nil + } + + trustService := r.TrustService(ctx, obj.ID.TenantID()) + + file, err := trustService.TrustCenters.GetNDAFile(ctx, obj.ID) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot load NDA file", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + if file == nil { + return nil, nil + } + + return types.NewNonDisclosureAgreement(file), nil +} + +// NonDisclosureAgreement returns schema.NonDisclosureAgreementResolver implementation. +func (r *Resolver) NonDisclosureAgreement() schema.NonDisclosureAgreementResolver { + return &nonDisclosureAgreementResolver{r} +} + +type nonDisclosureAgreementResolver struct{ *Resolver } diff --git a/pkg/server/api/trust/v1/trust_center.resolvers.go b/pkg/server/api/trust/v1/trust_center.resolvers.go new file mode 100644 index 000000000..455685e36 --- /dev/null +++ b/pkg/server/api/trust/v1/trust_center.resolvers.go @@ -0,0 +1,745 @@ +package trust_v1 + +// This file will be automatically regenerated based on the schema, any resolver +// implementations +// will be copied through when generating and any unknown code will be moved to the end. +// Code generated by github.com/99designs/gqlgen version v0.17.87 + +import ( + "context" + "encoding/base64" + "errors" + "fmt" + "time" + + "go.gearno.de/kit/log" + "go.probo.inc/probo/pkg/coredata" + "go.probo.inc/probo/pkg/gid" + "go.probo.inc/probo/pkg/server/api/authn" + "go.probo.inc/probo/pkg/server/api/compliancepage" + "go.probo.inc/probo/pkg/server/api/trust/v1/schema" + "go.probo.inc/probo/pkg/server/api/trust/v1/types" + "go.probo.inc/probo/pkg/server/gqlutils" + "go.probo.inc/probo/pkg/trust" +) + +// Framework is the resolver for the framework field. +func (r *auditResolver) Framework(ctx context.Context, obj *types.Audit) (*types.Framework, error) { + trustService := r.TrustService(ctx, obj.ID.TenantID()) + + audit, err := trustService.Audits.Get(ctx, obj.ID) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot load audit", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + framework, err := trustService.Frameworks.Get(ctx, audit.FrameworkID) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot load framework", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return types.NewFramework(framework), nil +} + +// Report is the resolver for the report field. +func (r *auditResolver) Report(ctx context.Context, obj *types.Audit) (*types.Report, error) { + trustService := r.TrustService(ctx, obj.ID.TenantID()) + + audit, err := trustService.Audits.Get(ctx, obj.ID) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot load audit", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + if audit.ReportID == nil { + return nil, nil + } + + trustCenter := compliancepage.CompliancePageFromContext(ctx) + + report, err := trustService.Reports.Get(ctx, trustCenter.OrganizationID, *audit.ReportID) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot load report", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return types.NewReport(report), nil +} + +// Framework is the resolver for the framework field on ComplianceFramework. +func (r *complianceFrameworkResolver) Framework(ctx context.Context, obj *types.ComplianceFramework) (*types.Framework, error) { + trustService := r.TrustService(ctx, obj.ID.TenantID()) + + framework, err := trustService.Frameworks.Get(ctx, obj.FrameworkID) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot load framework", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return types.NewFramework(framework), nil +} + +// IsUserAuthorized is the resolver for the isUserAuthorized field. +func (r *documentResolver) IsUserAuthorized(ctx context.Context, obj *types.Document) (bool, error) { + trustService := r.TrustService(ctx, obj.ID.TenantID()) + trustCenter := compliancepage.CompliancePageFromContext(ctx) + + document, err := trustService.Documents.Get(ctx, trustCenter.OrganizationID, obj.ID) + if err != nil { + if errors.Is(err, trust.ErrDocumentNotFound) || errors.Is(err, trust.ErrDocumentNotVisible) || errors.Is(err, coredata.ErrResourceNotFound) { + return false, gqlutils.NotFoundf(ctx, "document %q not found", obj.ID) + } + if _, ok := errors.AsType[*trust.ErrDocumentArchived](err); ok { + return false, gqlutils.NotFoundf(ctx, "document %q not found", obj.ID) + } + r.logger.ErrorCtx(ctx, "cannot load document", log.Error(err)) + return false, gqlutils.Internal(ctx) + } + + if document.TrustCenterVisibility == coredata.TrustCenterVisibilityPublic { + return true, nil + } + + identity := authn.IdentityFromContext(ctx) + if identity == nil { + return false, nil + } + + documentAccess, err := trustService.TrustCenterAccesses.GetDocumentAccess( + ctx, + trustCenter.ID, + identity.ID, + obj.ID, + ) + if err != nil { + if errors.Is(err, trust.ErrMembershipNotFound) || + errors.Is(err, trust.ErrUserNotFound) || + errors.Is(err, trust.ErrUserInactive) || + errors.Is(err, trust.ErrDocumentAccessNotFound) { + return false, nil + } + + r.logger.ErrorCtx(ctx, "cannot check document access", log.Error(err)) + return false, gqlutils.Internal(ctx) + } + + return documentAccess.Status == coredata.TrustCenterDocumentAccessStatusGranted, nil +} + +// Access is the resolver for the access field. +func (r *documentResolver) Access(ctx context.Context, obj *types.Document) (*types.DocumentAccess, error) { + trustService := r.TrustService(ctx, obj.ID.TenantID()) + trustCenter := compliancepage.CompliancePageFromContext(ctx) + + identity := authn.IdentityFromContext(ctx) + if identity == nil { + return nil, nil // User is not authenticated, so no access requested + } + + access, err := trustService.TrustCenterAccesses.GetDocumentAccess( + ctx, + trustCenter.ID, + identity.ID, + obj.ID, + ) + if err != nil { + if errors.Is(err, trust.ErrMembershipNotFound) || + errors.Is(err, trust.ErrUserNotFound) || + errors.Is(err, trust.ErrDocumentAccessNotFound) { + return nil, nil + } + + if errors.Is(err, trust.ErrUserInactive) { + return nil, gqlutils.Forbidden(ctx, err) + } + + r.logger.ErrorCtx(ctx, "cannot get document access", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.DocumentAccess{ + ID: access.ID, + Status: access.Status, + }, nil +} + +// LightLogoURL is the resolver for the lightLogoURL field. +func (r *frameworkResolver) LightLogoURL(ctx context.Context, obj *types.Framework) (*string, error) { + trustService := r.TrustService(ctx, obj.ID.TenantID()) + + return trustService.Frameworks.GenerateLightLogoURL(ctx, obj.ID, 1*time.Hour) +} + +// DarkLogoURL is the resolver for the darkLogoURL field. +func (r *frameworkResolver) DarkLogoURL(ctx context.Context, obj *types.Framework) (*string, error) { + trustService := r.TrustService(ctx, obj.ID.TenantID()) + + return trustService.Frameworks.GenerateDarkLogoURL(ctx, obj.ID, 1*time.Hour) +} + +// RequestAllAccesses is the resolver for the requestAllAccesses field. +func (r *mutationResolver) RequestAllAccesses(ctx context.Context) (*types.RequestAccessesPayload, error) { + trustCenter := compliancepage.CompliancePageFromContext(ctx) + trustService := r.TrustService(ctx, trustCenter.ID.TenantID()) + + identity := authn.IdentityFromContext(ctx) + if identity == nil { + return nil, gqlutils.Unauthenticatedf(ctx, "authentication is required to request access") + } + + access, err := trustService.TrustCenterAccesses.Request( + ctx, + &trust.TrustCenterAccessRequest{ + TrustCenterID: trustCenter.ID, + IdentityID: identity.ID, + DocumentIDs: nil, + ReportIDs: nil, + }, + ) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot create trust center access", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.RequestAccessesPayload{ + TrustCenterAccess: &types.TrustCenterAccess{ + ID: access.ID, + CreatedAt: access.CreatedAt, + UpdatedAt: access.UpdatedAt, + }, + }, nil +} + +// ExportDocumentPDF is the resolver for the exportDocumentPDF field. +func (r *mutationResolver) ExportDocumentPDF(ctx context.Context, input types.ExportDocumentPDFInput) (*types.ExportDocumentPDFPayload, error) { + trustService := r.TrustService(ctx, input.DocumentID.TenantID()) + trustCenter := compliancepage.CompliancePageFromContext(ctx) + + document, err := trustService.Documents.Get(ctx, trustCenter.OrganizationID, input.DocumentID) + if err != nil { + if errors.Is(err, trust.ErrDocumentNotFound) || errors.Is(err, trust.ErrDocumentNotVisible) || errors.Is(err, coredata.ErrResourceNotFound) { + return nil, gqlutils.NotFoundf(ctx, "document %q not found", input.DocumentID) + } + if _, ok := errors.AsType[*trust.ErrDocumentArchived](err); ok { + return nil, gqlutils.NotFoundf(ctx, "document %q not found", input.DocumentID) + } + r.logger.ErrorCtx(ctx, "cannot load document", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + if document.TrustCenterVisibility == coredata.TrustCenterVisibilityPublic { + pdf, err := trustService.Documents.ExportPDFWithoutWatermark(ctx, input.DocumentID) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot export document PDF", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.ExportDocumentPDFPayload{ + Data: fmt.Sprintf("data:application/pdf;base64,%s", base64.StdEncoding.EncodeToString(pdf)), + }, nil + } + + identity := authn.IdentityFromContext(ctx) + if identity == nil { + return nil, gqlutils.Unauthenticated(ctx, errors.New("unauthenticated")) + } + + documentAccess, err := trustService.TrustCenterAccesses.GetDocumentAccess( + ctx, + trustCenter.ID, + identity.ID, + input.DocumentID, + ) + if err != nil { + return nil, nil + } + + if documentAccess.Status != coredata.TrustCenterDocumentAccessStatusGranted { + return nil, gqlutils.Forbiddenf(ctx, "access denied: no permission to access this document") + } + + pdf, err := trustService.Documents.ExportPDF(ctx, input.DocumentID, identity.EmailAddress) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot export document PDF", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.ExportDocumentPDFPayload{ + Data: fmt.Sprintf("data:application/pdf;base64,%s", base64.StdEncoding.EncodeToString(pdf)), + }, nil +} + +// ExportReportPDF is the resolver for the exportReportPDF field. +func (r *mutationResolver) ExportReportPDF(ctx context.Context, input types.ExportReportPDFInput) (*types.ExportReportPDFPayload, error) { + trustService := r.TrustService(ctx, input.ReportID.TenantID()) + + trustCenter := compliancepage.CompliancePageFromContext(ctx) + + audit, err := trustService.Audits.GetByReportID(ctx, input.ReportID) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot load audit", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + if audit.TrustCenterVisibility == coredata.TrustCenterVisibilityPublic { + pdf, err := trustService.Reports.ExportPDFWithoutWatermark(ctx, input.ReportID) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot export report PDF", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.ExportReportPDFPayload{ + Data: fmt.Sprintf("data:application/pdf;base64,%s", base64.StdEncoding.EncodeToString(pdf)), + }, nil + } + + identity := authn.IdentityFromContext(ctx) + if identity == nil { + return nil, gqlutils.Unauthenticatedf(ctx, "unauthenticated") + } + + reportAccess, err := trustService.TrustCenterAccesses.GetReportAccess( + ctx, + trustCenter.ID, + identity.ID, + input.ReportID, + ) + if err != nil { + return nil, nil + } + + if reportAccess.Status != coredata.TrustCenterDocumentAccessStatusGranted { + return nil, gqlutils.Forbiddenf(ctx, "access denied: no permission to access this report") + } + + pdf, err := trustService.Reports.ExportPDF(ctx, input.ReportID, identity.EmailAddress) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot export report PDF", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.ExportReportPDFPayload{ + Data: fmt.Sprintf("data:application/pdf;base64,%s", base64.StdEncoding.EncodeToString(pdf)), + }, nil +} + +// ExportTrustCenterFile is the resolver for the exportTrustCenterFile field. +func (r *mutationResolver) ExportTrustCenterFile(ctx context.Context, input types.ExportTrustCenterFileInput) (*types.ExportTrustCenterFilePayload, error) { + trustCenter := compliancepage.CompliancePageFromContext(ctx) + trustService := r.TrustService(ctx, trustCenter.ID.TenantID()) + + trustCenterFile, err := trustService.TrustCenterFiles.Get(ctx, trustCenter.OrganizationID, input.TrustCenterFileID) + if err != nil { + if errors.Is(err, trust.ErrTrustCenterFileNotFound) || errors.Is(err, trust.ErrTrustCenterFileNotVisible) { + return nil, gqlutils.NotFoundf(ctx, "trust center file %q not found", input.TrustCenterFileID) + } + r.logger.ErrorCtx(ctx, "cannot load trust center file", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + if trustCenterFile.TrustCenterVisibility == coredata.TrustCenterVisibilityPublic { + fileData, mimeType, err := trustService.TrustCenterFiles.ExportFileWithoutWatermark(ctx, input.TrustCenterFileID) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot export trust center file", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.ExportTrustCenterFilePayload{ + Data: fmt.Sprintf("data:%s;base64,%s", mimeType, base64.StdEncoding.EncodeToString(fileData)), + }, nil + } + + identity := authn.IdentityFromContext(ctx) + if identity == nil { + return nil, gqlutils.Unauthenticatedf(ctx, "unauthenticated") + } + + fileAccess, err := trustService.TrustCenterAccesses.GetTrustCenterFileAccess(ctx, + trustCenter.ID, + identity.ID, + input.TrustCenterFileID, + ) + if err != nil { + return nil, nil + } + + if fileAccess.Status != coredata.TrustCenterDocumentAccessStatusGranted { + return nil, gqlutils.Forbiddenf(ctx, "access denied: no permission to access this file") + } + + fileData, mimeType, err := trustService.TrustCenterFiles.ExportFile(ctx, input.TrustCenterFileID, identity.EmailAddress) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot export trust center file", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.ExportTrustCenterFilePayload{ + Data: fmt.Sprintf("data:%s;base64,%s", mimeType, base64.StdEncoding.EncodeToString(fileData)), + }, nil +} + +// RequestDocumentAccess is the resolver for the requestDocumentAccess field. +func (r *mutationResolver) RequestDocumentAccess(ctx context.Context, input types.RequestDocumentAccessInput) (*types.RequestDocumentAccessPayload, error) { + trustCenter := compliancepage.CompliancePageFromContext(ctx) + trustService := r.TrustService(ctx, trustCenter.ID.TenantID()) + + document, err := trustService.Documents.Get(ctx, trustCenter.OrganizationID, input.DocumentID) + if err != nil { + if errors.Is(err, trust.ErrDocumentNotFound) || errors.Is(err, trust.ErrDocumentNotVisible) || errors.Is(err, coredata.ErrResourceNotFound) { + return nil, gqlutils.NotFoundf(ctx, "document %q not found", input.DocumentID) + } + if _, ok := errors.AsType[*trust.ErrDocumentArchived](err); ok { + return nil, gqlutils.NotFoundf(ctx, "document %q not found", input.DocumentID) + } + r.logger.ErrorCtx(ctx, "cannot load document", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + if document.TrustCenterVisibility == coredata.TrustCenterVisibilityPublic { + return nil, gqlutils.Invalidf( + ctx, + "document is publicly available and does not require access request", + ) + } + + identity := authn.IdentityFromContext(ctx) + if identity == nil { + return nil, gqlutils.Unauthenticatedf(ctx, "authentication is required to request access") + } + + if _, err := trustService.TrustCenterAccesses.Request( + ctx, + &trust.TrustCenterAccessRequest{ + TrustCenterID: trustCenter.ID, + IdentityID: identity.ID, + DocumentIDs: []gid.GID{input.DocumentID}, + ReportIDs: []gid.GID{}, + TrustCenterFileIDs: []gid.GID{}, + }, + ); err != nil { + r.logger.ErrorCtx(ctx, "cannot request document access", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.RequestDocumentAccessPayload{ + Document: types.NewDocument(document), + }, nil +} + +// RequestReportAccess is the resolver for the requestReportAccess field. +func (r *mutationResolver) RequestReportAccess(ctx context.Context, input types.RequestReportAccessInput) (*types.RequestReportAccessPayload, error) { + trustCenter := compliancepage.CompliancePageFromContext(ctx) + trustService := r.TrustService(ctx, trustCenter.ID.TenantID()) + + audit, err := trustService.Audits.GetByReportID(ctx, input.ReportID) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot load audit", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + if audit.TrustCenterVisibility == coredata.TrustCenterVisibilityPublic { + return nil, gqlutils.Invalidf( + ctx, + "report is publicly available and does not require access request", + ) + } + + identity := authn.IdentityFromContext(ctx) + if identity == nil { + return nil, gqlutils.Unauthenticatedf(ctx, "authentication is required to request access") + } + + if _, err := trustService.TrustCenterAccesses.Request( + ctx, + &trust.TrustCenterAccessRequest{ + TrustCenterID: trustCenter.ID, + IdentityID: identity.ID, + DocumentIDs: []gid.GID{}, + ReportIDs: []gid.GID{input.ReportID}, + TrustCenterFileIDs: []gid.GID{}, + }, + ); err != nil { + r.logger.ErrorCtx(ctx, "cannot request report access", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.RequestReportAccessPayload{ + Audit: types.NewAudit(audit), + }, nil +} + +// RequestTrustCenterFileAccess is the resolver for the requestTrustCenterFileAccess field. +func (r *mutationResolver) RequestTrustCenterFileAccess(ctx context.Context, input types.RequestTrustCenterFileAccessInput) (*types.RequestFileAccessPayload, error) { + trustCenter := compliancepage.CompliancePageFromContext(ctx) + trustService := r.TrustService(ctx, trustCenter.ID.TenantID()) + + trustCenterFile, err := trustService.TrustCenterFiles.Get(ctx, trustCenter.OrganizationID, input.TrustCenterFileID) + if err != nil { + if errors.Is(err, trust.ErrTrustCenterFileNotFound) || errors.Is(err, trust.ErrTrustCenterFileNotVisible) { + return nil, gqlutils.NotFoundf(ctx, "trust center file %q not found", input.TrustCenterFileID) + } + r.logger.ErrorCtx(ctx, "cannot load trust center file", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + if trustCenterFile.TrustCenterVisibility == coredata.TrustCenterVisibilityPublic { + return nil, gqlutils.Invalidf( + ctx, + "trust center file is publicly available and does not require access request", + ) + } + + identity := authn.IdentityFromContext(ctx) + if identity == nil { + return nil, gqlutils.Unauthenticatedf(ctx, "authentication is required to request access") + } + + if _, err := trustService.TrustCenterAccesses.Request( + ctx, + &trust.TrustCenterAccessRequest{ + TrustCenterID: trustCenter.ID, + IdentityID: identity.ID, + DocumentIDs: []gid.GID{}, + ReportIDs: []gid.GID{}, + TrustCenterFileIDs: []gid.GID{input.TrustCenterFileID}, + }, + ); err != nil { + r.logger.ErrorCtx(ctx, "cannot request trust center file access", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.RequestFileAccessPayload{ + File: types.NewTrustCenterFile(trustCenterFile), + }, nil +} + +// IsUserAuthorized is the resolver for the isUserAuthorized field. +func (r *reportResolver) IsUserAuthorized(ctx context.Context, obj *types.Report) (bool, error) { + trustService := r.TrustService(ctx, obj.ID.TenantID()) + + trustCenter := compliancepage.CompliancePageFromContext(ctx) + + audit, err := trustService.Audits.GetByReportID(ctx, obj.ID) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot load document", log.Error(err)) + return false, gqlutils.Internal(ctx) + } + + if audit.TrustCenterVisibility == coredata.TrustCenterVisibilityPublic { + return true, nil + } + + identity := authn.IdentityFromContext(ctx) + if identity == nil { + return false, nil + } + + reportAccess, err := trustService.TrustCenterAccesses.GetReportAccess(ctx, + trustCenter.ID, + identity.ID, + obj.ID, + ) + if err != nil { + if errors.Is(err, trust.ErrMembershipNotFound) || + errors.Is(err, trust.ErrUserNotFound) || + errors.Is(err, trust.ErrUserInactive) || + errors.Is(err, trust.ErrDocumentAccessNotFound) { + return false, nil + } + + r.logger.ErrorCtx(ctx, "cannot check report access", log.Error(err)) + return false, gqlutils.Internal(ctx) + } + + return reportAccess.Status == coredata.TrustCenterDocumentAccessStatusGranted, nil +} + +// Access is the resolver for the access field. +func (r *reportResolver) Access(ctx context.Context, obj *types.Report) (*types.DocumentAccess, error) { + trustService := r.TrustService(ctx, obj.ID.TenantID()) + trustCenter := compliancepage.CompliancePageFromContext(ctx) + + identity := authn.IdentityFromContext(ctx) + if identity == nil { + return nil, nil // User is not authenticated, so no access requested + } + + access, err := trustService.TrustCenterAccesses.GetReportAccess( + ctx, + trustCenter.ID, + identity.ID, + obj.ID, + ) + if err != nil { + if errors.Is(err, trust.ErrMembershipNotFound) || + errors.Is(err, trust.ErrUserNotFound) || + errors.Is(err, trust.ErrDocumentAccessNotFound) { + return nil, nil + } + + if errors.Is(err, trust.ErrUserInactive) { + return nil, gqlutils.Forbidden(ctx, err) + } + + r.logger.ErrorCtx(ctx, "cannot get audit report access", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.DocumentAccess{ + ID: access.ID, + Status: access.Status, + }, nil +} + +// TotalCount is the resolver for the totalCount field. +func (r *subprocessorConnectionResolver) TotalCount(ctx context.Context, obj *types.SubprocessorConnection) (int, error) { + trustService := r.TrustService(ctx, obj.ParentID.TenantID()) + + switch obj.Resolver.(type) { + case *trustCenterResolver: + count, err := trustService.Vendors.CountForTrustCenterId(ctx, obj.ParentID) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot count subprocessors", log.Error(err)) + return 0, gqlutils.Internal(ctx) + } + return count, nil + } + + r.logger.ErrorCtx(ctx, "not implemented: TotalCount for parent type") + return 0, gqlutils.Internal(ctx) +} + +// IsUserAuthorized is the resolver for the isUserAuthorized field. +func (r *trustCenterFileResolver) IsUserAuthorized(ctx context.Context, obj *types.TrustCenterFile) (bool, error) { + trustService := r.TrustService(ctx, obj.ID.TenantID()) + + trustCenter := compliancepage.CompliancePageFromContext(ctx) + + trustCenterFile, err := trustService.TrustCenterFiles.Get(ctx, trustCenter.OrganizationID, obj.ID) + if err != nil { + if errors.Is(err, trust.ErrTrustCenterFileNotFound) || errors.Is(err, trust.ErrTrustCenterFileNotVisible) { + return false, gqlutils.NotFoundf(ctx, "trust center file %q not found", obj.ID) + } + r.logger.ErrorCtx(ctx, "cannot load trust center file", log.Error(err)) + return false, gqlutils.Internal(ctx) + } + + if trustCenterFile.TrustCenterVisibility == coredata.TrustCenterVisibilityPublic { + return true, nil + } + + identity := authn.IdentityFromContext(ctx) + if identity == nil { + return false, nil + } + + fileAccess, err := trustService.TrustCenterAccesses.GetTrustCenterFileAccess(ctx, + trustCenter.ID, + identity.ID, + obj.ID, + ) + if err != nil { + if errors.Is(err, trust.ErrMembershipNotFound) || + errors.Is(err, trust.ErrUserNotFound) || + errors.Is(err, trust.ErrUserInactive) || + errors.Is(err, trust.ErrDocumentAccessNotFound) { + return false, nil + } + + r.logger.ErrorCtx(ctx, "cannot check trust center file access", log.Error(err)) + return false, gqlutils.Internal(ctx) + } + + return fileAccess.Status == coredata.TrustCenterDocumentAccessStatusGranted, nil +} + +// Access is the resolver for the access field. +func (r *trustCenterFileResolver) Access(ctx context.Context, obj *types.TrustCenterFile) (*types.DocumentAccess, error) { + trustService := r.TrustService(ctx, obj.ID.TenantID()) + trustCenter := compliancepage.CompliancePageFromContext(ctx) + + identity := authn.IdentityFromContext(ctx) + if identity == nil { + return nil, nil // User is not authenticated, so no access requested + } + + access, err := trustService.TrustCenterAccesses.GetTrustCenterFileAccess( + ctx, + trustCenter.ID, + identity.ID, + obj.ID, + ) + if err != nil { + if errors.Is(err, trust.ErrMembershipNotFound) || + errors.Is(err, trust.ErrUserNotFound) || + errors.Is(err, trust.ErrDocumentAccessNotFound) { + return nil, nil + } + + if errors.Is(err, trust.ErrUserInactive) { + return nil, gqlutils.Forbidden(ctx, err) + } + + r.logger.ErrorCtx(ctx, "cannot get file access", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.DocumentAccess{ + ID: access.ID, + Status: access.Status, + }, nil +} + +// LogoURL is the resolver for the logoUrl field. +func (r *trustCenterReferenceResolver) LogoURL(ctx context.Context, obj *types.TrustCenterReference) (string, error) { + trustService := r.TrustService(ctx, obj.ID.TenantID()) + + logoURL, err := trustService.TrustCenterReferences.GenerateLogoURL(ctx, obj.ID, 1*time.Hour) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot generate logo URL", log.Error(err)) + return "", gqlutils.Internal(ctx) + } + + return logoURL, nil +} + +// Audit returns schema.AuditResolver implementation. +func (r *Resolver) Audit() schema.AuditResolver { return &auditResolver{r} } + +// ComplianceFramework returns schema.ComplianceFrameworkResolver implementation. +func (r *Resolver) ComplianceFramework() schema.ComplianceFrameworkResolver { + return &complianceFrameworkResolver{r} +} + +// Document returns schema.DocumentResolver implementation. +func (r *Resolver) Document() schema.DocumentResolver { return &documentResolver{r} } + +// Framework returns schema.FrameworkResolver implementation. +func (r *Resolver) Framework() schema.FrameworkResolver { return &frameworkResolver{r} } + +// Report returns schema.ReportResolver implementation. +func (r *Resolver) Report() schema.ReportResolver { return &reportResolver{r} } + +// SubprocessorConnection returns schema.SubprocessorConnectionResolver implementation. +func (r *Resolver) SubprocessorConnection() schema.SubprocessorConnectionResolver { + return &subprocessorConnectionResolver{r} +} + +// TrustCenterFile returns schema.TrustCenterFileResolver implementation. +func (r *Resolver) TrustCenterFile() schema.TrustCenterFileResolver { + return &trustCenterFileResolver{r} +} + +// TrustCenterReference returns schema.TrustCenterReferenceResolver implementation. +func (r *Resolver) TrustCenterReference() schema.TrustCenterReferenceResolver { + return &trustCenterReferenceResolver{r} +} + +type auditResolver struct{ *Resolver } +type complianceFrameworkResolver struct{ *Resolver } +type documentResolver struct{ *Resolver } +type frameworkResolver struct{ *Resolver } +type reportResolver struct{ *Resolver } +type subprocessorConnectionResolver struct{ *Resolver } +type trustCenterFileResolver struct{ *Resolver } +type trustCenterReferenceResolver struct{ *Resolver } diff --git a/pkg/server/api/trust/v1/v1_resolver.go b/pkg/server/api/trust/v1/v1_resolver.go deleted file mode 100644 index 7593974ae..000000000 --- a/pkg/server/api/trust/v1/v1_resolver.go +++ /dev/null @@ -1,1512 +0,0 @@ -package trust_v1 - -// This file will be automatically regenerated based on the schema, any resolver -// implementations -// will be copied through when generating and any unknown code will be moved to the end. -// Code generated by github.com/99designs/gqlgen version v0.17.87 - -import ( - "context" - "encoding/base64" - "errors" - "fmt" - "net" - "strings" - "time" - - "go.gearno.de/kit/log" - "go.probo.inc/probo/pkg/baseurl" - "go.probo.inc/probo/pkg/coredata" - "go.probo.inc/probo/pkg/esign" - "go.probo.inc/probo/pkg/gid" - "go.probo.inc/probo/pkg/iam" - "go.probo.inc/probo/pkg/mailman" - "go.probo.inc/probo/pkg/page" - "go.probo.inc/probo/pkg/saferedirect" - "go.probo.inc/probo/pkg/server/api/authn" - "go.probo.inc/probo/pkg/server/api/compliancepage" - "go.probo.inc/probo/pkg/server/api/trust/v1/schema" - "go.probo.inc/probo/pkg/server/api/trust/v1/types" - "go.probo.inc/probo/pkg/server/gqlutils" - "go.probo.inc/probo/pkg/trust" - "go.probo.inc/probo/pkg/validator" -) - -// Framework is the resolver for the framework field. -func (r *auditResolver) Framework(ctx context.Context, obj *types.Audit) (*types.Framework, error) { - trustService := r.TrustService(ctx, obj.ID.TenantID()) - - audit, err := trustService.Audits.Get(ctx, obj.ID) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot load audit", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - framework, err := trustService.Frameworks.Get(ctx, audit.FrameworkID) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot load framework", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return types.NewFramework(framework), nil -} - -// Report is the resolver for the report field. -func (r *auditResolver) Report(ctx context.Context, obj *types.Audit) (*types.Report, error) { - trustService := r.TrustService(ctx, obj.ID.TenantID()) - - audit, err := trustService.Audits.Get(ctx, obj.ID) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot load audit", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - if audit.ReportID == nil { - return nil, nil - } - - trustCenter := compliancepage.CompliancePageFromContext(ctx) - - report, err := trustService.Reports.Get(ctx, trustCenter.OrganizationID, *audit.ReportID) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot load report", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return types.NewReport(report), nil -} - -// Framework is the resolver for the framework field on ComplianceFramework. -func (r *complianceFrameworkResolver) Framework(ctx context.Context, obj *types.ComplianceFramework) (*types.Framework, error) { - trustService := r.TrustService(ctx, obj.ID.TenantID()) - - framework, err := trustService.Frameworks.Get(ctx, obj.FrameworkID) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot load framework", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return types.NewFramework(framework), nil -} - -// IsUserAuthorized is the resolver for the isUserAuthorized field. -func (r *documentResolver) IsUserAuthorized(ctx context.Context, obj *types.Document) (bool, error) { - trustService := r.TrustService(ctx, obj.ID.TenantID()) - trustCenter := compliancepage.CompliancePageFromContext(ctx) - - document, err := trustService.Documents.Get(ctx, trustCenter.OrganizationID, obj.ID) - if err != nil { - if errors.Is(err, trust.ErrDocumentNotFound) || errors.Is(err, trust.ErrDocumentNotVisible) || errors.Is(err, coredata.ErrResourceNotFound) { - return false, gqlutils.NotFoundf(ctx, "document %q not found", obj.ID) - } - if _, ok := errors.AsType[*trust.ErrDocumentArchived](err); ok { - return false, gqlutils.NotFoundf(ctx, "document %q not found", obj.ID) - } - r.logger.ErrorCtx(ctx, "cannot load document", log.Error(err)) - return false, gqlutils.Internal(ctx) - } - - if document.TrustCenterVisibility == coredata.TrustCenterVisibilityPublic { - return true, nil - } - - identity := authn.IdentityFromContext(ctx) - if identity == nil { - return false, nil - } - - documentAccess, err := trustService.TrustCenterAccesses.GetDocumentAccess( - ctx, - trustCenter.ID, - identity.ID, - obj.ID, - ) - if err != nil { - if errors.Is(err, trust.ErrMembershipNotFound) || - errors.Is(err, trust.ErrUserNotFound) || - errors.Is(err, trust.ErrUserInactive) || - errors.Is(err, trust.ErrDocumentAccessNotFound) { - return false, nil - } - - r.logger.ErrorCtx(ctx, "cannot check document access", log.Error(err)) - return false, gqlutils.Internal(ctx) - } - - return documentAccess.Status == coredata.TrustCenterDocumentAccessStatusGranted, nil -} - -// Access is the resolver for the access field. -func (r *documentResolver) Access(ctx context.Context, obj *types.Document) (*types.DocumentAccess, error) { - trustService := r.TrustService(ctx, obj.ID.TenantID()) - trustCenter := compliancepage.CompliancePageFromContext(ctx) - - identity := authn.IdentityFromContext(ctx) - if identity == nil { - return nil, nil // User is not authenticated, so no access requested - } - - access, err := trustService.TrustCenterAccesses.GetDocumentAccess( - ctx, - trustCenter.ID, - identity.ID, - obj.ID, - ) - if err != nil { - if errors.Is(err, trust.ErrMembershipNotFound) || - errors.Is(err, trust.ErrUserNotFound) || - errors.Is(err, trust.ErrDocumentAccessNotFound) { - return nil, nil - } - - if errors.Is(err, trust.ErrUserInactive) { - return nil, gqlutils.Forbidden(ctx, err) - } - - r.logger.ErrorCtx(ctx, "cannot get document access", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.DocumentAccess{ - ID: access.ID, - Status: access.Status, - }, nil -} - -// LightLogoURL is the resolver for the lightLogoURL field. -func (r *frameworkResolver) LightLogoURL(ctx context.Context, obj *types.Framework) (*string, error) { - trustService := r.TrustService(ctx, obj.ID.TenantID()) - - return trustService.Frameworks.GenerateLightLogoURL(ctx, obj.ID, 1*time.Hour) -} - -// DarkLogoURL is the resolver for the darkLogoURL field. -func (r *frameworkResolver) DarkLogoURL(ctx context.Context, obj *types.Framework) (*string, error) { - trustService := r.TrustService(ctx, obj.ID.TenantID()) - - return trustService.Frameworks.GenerateDarkLogoURL(ctx, obj.ID, 1*time.Hour) -} - -// SendMagicLink is the resolver for the sendMagicLink field. -func (r *mutationResolver) SendMagicLink(ctx context.Context, input types.SendMagicLinkInput) (*types.SendMagicLinkPayload, error) { - trustCenter := compliancepage.CompliancePageFromContext(ctx) - - baseURL := compliancepage.CompliancePageBaseURLFromContext(ctx) - - safeRedirect := saferedirect.New(saferedirect.StaticHosts(baseurl.MustParse(*baseURL).Host())) - - if input.Continue != nil { - _, ok := safeRedirect.Validate(ctx, *input.Continue) - if !ok { - return nil, gqlutils.Invalidf(ctx, "invalid continue URL") - } - } - - req := &iam.SendMagicLinkRequest{ - Email: input.Email, - CompliancePageID: &trustCenter.ID, - OrganizationID: trustCenter.OrganizationID, - URLPath: "verify-magic-link", - Continue: input.Continue, - } - - if err := r.iam.AuthService.SendMagicLink(ctx, req); err != nil { - r.logger.ErrorCtx(ctx, "cannot send magic link", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return nil, nil -} - -// VerifyMagicLink is the resolver for the verifyMagicLink field. -func (r *mutationResolver) VerifyMagicLink(ctx context.Context, input types.VerifyMagicLinkInput) (*types.VerifyMagicLinkPayload, error) { - session := authn.SessionFromContext(ctx) - identity := authn.IdentityFromContext(ctx) - - email, err := r.iam.AuthService.GetMagicLinkEmail(ctx, input.Token) - if err != nil { - var errExpiredToken *iam.ErrExpiredToken - if errors.As(err, &errExpiredToken) { - return nil, gqlutils.Invalid(ctx, err) - } - - var errInvalidToken *iam.ErrInvalidToken - if errors.As(err, &errInvalidToken) { - return nil, gqlutils.Invalid(ctx, err) - } - - r.logger.ErrorCtx(ctx, "cannot get magic link email", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - var continueURL *string - - switch { - case session == nil: - var err error - identity, session, continueURL, err = r.iam.AuthService.OpenSessionWithMagicLink(ctx, input.Token) - if err != nil { - var errExpiredToken *iam.ErrExpiredToken - if errors.As(err, &errExpiredToken) { - return nil, gqlutils.Invalid(ctx, err) - } - - var errInvalidToken *iam.ErrInvalidToken - if errors.As(err, &errInvalidToken) { - return nil, gqlutils.Invalid(ctx, err) - } - - r.logger.ErrorCtx(ctx, "cannot open session with magic link", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - case identity.EmailAddress != email: - if err := r.iam.SessionService.CloseSession(ctx, session.ID); err != nil { - r.logger.ErrorCtx(ctx, "cannot close session", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - var err error - identity, session, continueURL, err = r.iam.AuthService.OpenSessionWithMagicLink(ctx, input.Token) - if err != nil { - var errExpiredToken *iam.ErrExpiredToken - if errors.As(err, &errExpiredToken) { - return nil, gqlutils.Invalid(ctx, err) - } - - var errInvalidToken *iam.ErrInvalidToken - if errors.As(err, &errInvalidToken) { - return nil, gqlutils.Invalid(ctx, err) - } - - r.logger.ErrorCtx(ctx, "cannot open session with magic link", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - } - - trustCenter := compliancepage.CompliancePageFromContext(ctx) - - if _, err := r.trust.ProvisionMember(ctx, trustCenter.ID, identity.ID); err != nil { - r.logger.ErrorCtx(ctx, "cannot provision member", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - w := gqlutils.HTTPResponseWriterFromContext(ctx) - r.sessionCookie.Set(w, session) - - return &types.VerifyMagicLinkPayload{ - Continue: continueURL, - }, nil -} - -// UpdateFullName is the resolver for the updateFullName field. -func (r *mutationResolver) UpdateFullName(ctx context.Context, input types.UpdateFullNameInput) (*types.UpdateFullNamePayload, error) { - identity := authn.IdentityFromContext(ctx) - if identity == nil { - return nil, gqlutils.Unauthenticatedf(ctx, "authentication is required to request access") - } - - identity, err := r.iam.AccountService.UpdateIdentity( - ctx, - identity.ID, - &iam.UpdateIdentityRequest{ - FullName: input.FullName, - }, - ) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot update identity", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - compliancePage := compliancepage.CompliancePageFromContext(ctx) - - profile, err := r.iam.OrganizationService.GetProfileForIdentityAndOrganization(ctx, identity.ID, compliancePage.OrganizationID) - if err != nil { - if _, ok := errors.AsType[*iam.ErrProfileNotFound](err); !ok { - r.logger.ErrorCtx(ctx, "cannot get profile", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - } - - if profile.Source == coredata.ProfileSourceManual { - if _, err := r.iam.OrganizationService.UpdateUser(ctx, &iam.UpdateUserRequest{ - ID: profile.ID, - FullName: identity.FullName, - AdditionalEmailAddresses: profile.AdditionalEmailAddresses, - Kind: profile.Kind, - Position: profile.Position, - ContractStartDate: &profile.ContractStartDate, - ContractEndDate: &profile.ContractEndDate, - }); err != nil { - r.logger.ErrorCtx(ctx, "cannot update profile", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - } - - return &types.UpdateFullNamePayload{Success: true}, nil -} - -// RequestAllAccesses is the resolver for the requestAllAccesses field. -func (r *mutationResolver) RequestAllAccesses(ctx context.Context) (*types.RequestAccessesPayload, error) { - trustCenter := compliancepage.CompliancePageFromContext(ctx) - trustService := r.TrustService(ctx, trustCenter.ID.TenantID()) - - identity := authn.IdentityFromContext(ctx) - if identity == nil { - return nil, gqlutils.Unauthenticatedf(ctx, "authentication is required to request access") - } - - access, err := trustService.TrustCenterAccesses.Request( - ctx, - &trust.TrustCenterAccessRequest{ - TrustCenterID: trustCenter.ID, - IdentityID: identity.ID, - DocumentIDs: nil, - ReportIDs: nil, - }, - ) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot create trust center access", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.RequestAccessesPayload{ - TrustCenterAccess: &types.TrustCenterAccess{ - ID: access.ID, - CreatedAt: access.CreatedAt, - UpdatedAt: access.UpdatedAt, - }, - }, nil -} - -// ExportDocumentPDF is the resolver for the exportDocumentPDF field. -func (r *mutationResolver) ExportDocumentPDF(ctx context.Context, input types.ExportDocumentPDFInput) (*types.ExportDocumentPDFPayload, error) { - trustService := r.TrustService(ctx, input.DocumentID.TenantID()) - trustCenter := compliancepage.CompliancePageFromContext(ctx) - - document, err := trustService.Documents.Get(ctx, trustCenter.OrganizationID, input.DocumentID) - if err != nil { - if errors.Is(err, trust.ErrDocumentNotFound) || errors.Is(err, trust.ErrDocumentNotVisible) || errors.Is(err, coredata.ErrResourceNotFound) { - return nil, gqlutils.NotFoundf(ctx, "document %q not found", input.DocumentID) - } - if _, ok := errors.AsType[*trust.ErrDocumentArchived](err); ok { - return nil, gqlutils.NotFoundf(ctx, "document %q not found", input.DocumentID) - } - r.logger.ErrorCtx(ctx, "cannot load document", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - if document.TrustCenterVisibility == coredata.TrustCenterVisibilityPublic { - pdf, err := trustService.Documents.ExportPDFWithoutWatermark(ctx, input.DocumentID) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot export document PDF", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.ExportDocumentPDFPayload{ - Data: fmt.Sprintf("data:application/pdf;base64,%s", base64.StdEncoding.EncodeToString(pdf)), - }, nil - } - - identity := authn.IdentityFromContext(ctx) - if identity == nil { - return nil, gqlutils.Unauthenticated(ctx, errors.New("unauthenticated")) - } - - documentAccess, err := trustService.TrustCenterAccesses.GetDocumentAccess( - ctx, - trustCenter.ID, - identity.ID, - input.DocumentID, - ) - if err != nil { - return nil, nil - } - - if documentAccess.Status != coredata.TrustCenterDocumentAccessStatusGranted { - return nil, gqlutils.Forbiddenf(ctx, "access denied: no permission to access this document") - } - - pdf, err := trustService.Documents.ExportPDF(ctx, input.DocumentID, identity.EmailAddress) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot export document PDF", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.ExportDocumentPDFPayload{ - Data: fmt.Sprintf("data:application/pdf;base64,%s", base64.StdEncoding.EncodeToString(pdf)), - }, nil -} - -// ExportReportPDF is the resolver for the exportReportPDF field. -func (r *mutationResolver) ExportReportPDF(ctx context.Context, input types.ExportReportPDFInput) (*types.ExportReportPDFPayload, error) { - trustService := r.TrustService(ctx, input.ReportID.TenantID()) - - trustCenter := compliancepage.CompliancePageFromContext(ctx) - - audit, err := trustService.Audits.GetByReportID(ctx, input.ReportID) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot load audit", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - if audit.TrustCenterVisibility == coredata.TrustCenterVisibilityPublic { - pdf, err := trustService.Reports.ExportPDFWithoutWatermark(ctx, input.ReportID) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot export report PDF", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.ExportReportPDFPayload{ - Data: fmt.Sprintf("data:application/pdf;base64,%s", base64.StdEncoding.EncodeToString(pdf)), - }, nil - } - - identity := authn.IdentityFromContext(ctx) - if identity == nil { - return nil, gqlutils.Unauthenticatedf(ctx, "unauthenticated") - } - - reportAccess, err := trustService.TrustCenterAccesses.GetReportAccess( - ctx, - trustCenter.ID, - identity.ID, - input.ReportID, - ) - if err != nil { - return nil, nil - } - - if reportAccess.Status != coredata.TrustCenterDocumentAccessStatusGranted { - return nil, gqlutils.Forbiddenf(ctx, "access denied: no permission to access this report") - } - - pdf, err := trustService.Reports.ExportPDF(ctx, input.ReportID, identity.EmailAddress) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot export report PDF", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.ExportReportPDFPayload{ - Data: fmt.Sprintf("data:application/pdf;base64,%s", base64.StdEncoding.EncodeToString(pdf)), - }, nil -} - -// ExportTrustCenterFile is the resolver for the exportTrustCenterFile field. -func (r *mutationResolver) ExportTrustCenterFile(ctx context.Context, input types.ExportTrustCenterFileInput) (*types.ExportTrustCenterFilePayload, error) { - trustCenter := compliancepage.CompliancePageFromContext(ctx) - trustService := r.TrustService(ctx, trustCenter.ID.TenantID()) - - trustCenterFile, err := trustService.TrustCenterFiles.Get(ctx, trustCenter.OrganizationID, input.TrustCenterFileID) - if err != nil { - if errors.Is(err, trust.ErrTrustCenterFileNotFound) || errors.Is(err, trust.ErrTrustCenterFileNotVisible) { - return nil, gqlutils.NotFoundf(ctx, "trust center file %q not found", input.TrustCenterFileID) - } - r.logger.ErrorCtx(ctx, "cannot load trust center file", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - if trustCenterFile.TrustCenterVisibility == coredata.TrustCenterVisibilityPublic { - fileData, mimeType, err := trustService.TrustCenterFiles.ExportFileWithoutWatermark(ctx, input.TrustCenterFileID) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot export trust center file", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.ExportTrustCenterFilePayload{ - Data: fmt.Sprintf("data:%s;base64,%s", mimeType, base64.StdEncoding.EncodeToString(fileData)), - }, nil - } - - identity := authn.IdentityFromContext(ctx) - if identity == nil { - return nil, gqlutils.Unauthenticatedf(ctx, "unauthenticated") - } - - fileAccess, err := trustService.TrustCenterAccesses.GetTrustCenterFileAccess(ctx, - trustCenter.ID, - identity.ID, - input.TrustCenterFileID, - ) - if err != nil { - return nil, nil - } - - if fileAccess.Status != coredata.TrustCenterDocumentAccessStatusGranted { - return nil, gqlutils.Forbiddenf(ctx, "access denied: no permission to access this file") - } - - fileData, mimeType, err := trustService.TrustCenterFiles.ExportFile(ctx, input.TrustCenterFileID, identity.EmailAddress) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot export trust center file", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.ExportTrustCenterFilePayload{ - Data: fmt.Sprintf("data:%s;base64,%s", mimeType, base64.StdEncoding.EncodeToString(fileData)), - }, nil -} - -// RequestDocumentAccess is the resolver for the requestDocumentAccess field. -func (r *mutationResolver) RequestDocumentAccess(ctx context.Context, input types.RequestDocumentAccessInput) (*types.RequestDocumentAccessPayload, error) { - trustCenter := compliancepage.CompliancePageFromContext(ctx) - trustService := r.TrustService(ctx, trustCenter.ID.TenantID()) - - document, err := trustService.Documents.Get(ctx, trustCenter.OrganizationID, input.DocumentID) - if err != nil { - if errors.Is(err, trust.ErrDocumentNotFound) || errors.Is(err, trust.ErrDocumentNotVisible) || errors.Is(err, coredata.ErrResourceNotFound) { - return nil, gqlutils.NotFoundf(ctx, "document %q not found", input.DocumentID) - } - if _, ok := errors.AsType[*trust.ErrDocumentArchived](err); ok { - return nil, gqlutils.NotFoundf(ctx, "document %q not found", input.DocumentID) - } - r.logger.ErrorCtx(ctx, "cannot load document", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - if document.TrustCenterVisibility == coredata.TrustCenterVisibilityPublic { - return nil, gqlutils.Invalidf( - ctx, - "document is publicly available and does not require access request", - ) - } - - identity := authn.IdentityFromContext(ctx) - if identity == nil { - return nil, gqlutils.Unauthenticatedf(ctx, "authentication is required to request access") - } - - if _, err := trustService.TrustCenterAccesses.Request( - ctx, - &trust.TrustCenterAccessRequest{ - TrustCenterID: trustCenter.ID, - IdentityID: identity.ID, - DocumentIDs: []gid.GID{input.DocumentID}, - ReportIDs: []gid.GID{}, - TrustCenterFileIDs: []gid.GID{}, - }, - ); err != nil { - r.logger.ErrorCtx(ctx, "cannot request document access", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.RequestDocumentAccessPayload{ - Document: types.NewDocument(document), - }, nil -} - -// RequestReportAccess is the resolver for the requestReportAccess field. -func (r *mutationResolver) RequestReportAccess(ctx context.Context, input types.RequestReportAccessInput) (*types.RequestReportAccessPayload, error) { - trustCenter := compliancepage.CompliancePageFromContext(ctx) - trustService := r.TrustService(ctx, trustCenter.ID.TenantID()) - - audit, err := trustService.Audits.GetByReportID(ctx, input.ReportID) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot load audit", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - if audit.TrustCenterVisibility == coredata.TrustCenterVisibilityPublic { - return nil, gqlutils.Invalidf( - ctx, - "report is publicly available and does not require access request", - ) - } - - identity := authn.IdentityFromContext(ctx) - if identity == nil { - return nil, gqlutils.Unauthenticatedf(ctx, "authentication is required to request access") - } - - if _, err := trustService.TrustCenterAccesses.Request( - ctx, - &trust.TrustCenterAccessRequest{ - TrustCenterID: trustCenter.ID, - IdentityID: identity.ID, - DocumentIDs: []gid.GID{}, - ReportIDs: []gid.GID{input.ReportID}, - TrustCenterFileIDs: []gid.GID{}, - }, - ); err != nil { - r.logger.ErrorCtx(ctx, "cannot request report access", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.RequestReportAccessPayload{ - Audit: types.NewAudit(audit), - }, nil -} - -// RequestTrustCenterFileAccess is the resolver for the requestTrustCenterFileAccess field. -func (r *mutationResolver) RequestTrustCenterFileAccess(ctx context.Context, input types.RequestTrustCenterFileAccessInput) (*types.RequestFileAccessPayload, error) { - trustCenter := compliancepage.CompliancePageFromContext(ctx) - trustService := r.TrustService(ctx, trustCenter.ID.TenantID()) - - trustCenterFile, err := trustService.TrustCenterFiles.Get(ctx, trustCenter.OrganizationID, input.TrustCenterFileID) - if err != nil { - if errors.Is(err, trust.ErrTrustCenterFileNotFound) || errors.Is(err, trust.ErrTrustCenterFileNotVisible) { - return nil, gqlutils.NotFoundf(ctx, "trust center file %q not found", input.TrustCenterFileID) - } - r.logger.ErrorCtx(ctx, "cannot load trust center file", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - if trustCenterFile.TrustCenterVisibility == coredata.TrustCenterVisibilityPublic { - return nil, gqlutils.Invalidf( - ctx, - "trust center file is publicly available and does not require access request", - ) - } - - identity := authn.IdentityFromContext(ctx) - if identity == nil { - return nil, gqlutils.Unauthenticatedf(ctx, "authentication is required to request access") - } - - if _, err := trustService.TrustCenterAccesses.Request( - ctx, - &trust.TrustCenterAccessRequest{ - TrustCenterID: trustCenter.ID, - IdentityID: identity.ID, - DocumentIDs: []gid.GID{}, - ReportIDs: []gid.GID{}, - TrustCenterFileIDs: []gid.GID{input.TrustCenterFileID}, - }, - ); err != nil { - r.logger.ErrorCtx(ctx, "cannot request trust center file access", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.RequestFileAccessPayload{ - File: types.NewTrustCenterFile(trustCenterFile), - }, nil -} - -// AcceptElectronicSignature is the resolver for the acceptElectronicSignature field. -func (r *mutationResolver) AcceptElectronicSignature(ctx context.Context, input types.AcceptElectronicSignatureInput) (*types.AcceptElectronicSignaturePayload, error) { - var ( - identity = authn.IdentityFromContext(ctx) - httpReq = gqlutils.HTTPRequestFromContext(ctx) - ) - - signerIP, _, _ := net.SplitHostPort(httpReq.RemoteAddr) - if signerIP == "" { - signerIP = httpReq.RemoteAddr - } - - signature, err := r.esign.AcceptSignature( - ctx, - &esign.AcceptSignatureRequest{ - SignatureID: input.SignatureID, - SignerFullName: identity.FullName, - SignerEmail: identity.EmailAddress, - SignerIPAddr: signerIP, - SignerUA: httpReq.UserAgent(), - }, - ) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot accept electronic signature", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.AcceptElectronicSignaturePayload{ - Signature: types.NewElectronicSignature(signature), - }, nil -} - -// RecordSigningEvent is the resolver for the recordSigningEvent field. -func (r *mutationResolver) RecordSigningEvent(ctx context.Context, input types.RecordSigningEventInput) (*types.RecordSigningEventPayload, error) { - var ( - identity = authn.IdentityFromContext(ctx) - httpReq = gqlutils.HTTPRequestFromContext(ctx) - ) - - actorIP, _, _ := net.SplitHostPort(httpReq.RemoteAddr) - if actorIP == "" { - actorIP = httpReq.RemoteAddr - } - - if err := r.esign.RecordEvent( - ctx, - &esign.RecordEventRequest{ - SignatureID: input.SignatureID, - EventType: input.EventType, - EventSource: coredata.ElectronicSignatureEventSourceClient, - ActorEmail: identity.EmailAddress, - ActorIPAddr: actorIP, - ActorUA: httpReq.UserAgent(), - }, - ); err != nil { - r.logger.ErrorCtx(ctx, "cannot record signing event", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.RecordSigningEventPayload{Success: true}, nil -} - -// SubscribeToMailingList is the resolver for the subscribeToMailingList field. -func (r *mutationResolver) SubscribeToMailingList(ctx context.Context) (*types.SubscribeToMailingListPayload, error) { - trustCenter := compliancepage.CompliancePageFromContext(ctx) - if trustCenter.MailingListID == nil { - return nil, gqlutils.NotFoundf(ctx, "mailing list not found") - } - - identity := authn.IdentityFromContext(ctx) - - subscriber, err := r.mailman.CreateSubscriber( - ctx, - &mailman.CreateSubscriberRequest{ - MailingListID: *trustCenter.MailingListID, - Email: identity.EmailAddress, - FullName: identity.FullName, - }, - ) - if err != nil { - if validationErrors, ok := errors.AsType[validator.ValidationErrors](err); ok { - return nil, gqlutils.InvalidValidationErrors(ctx, validationErrors) - } - if errors.Is(err, mailman.ErrSubscriberAlreadyExist) { - return nil, gqlutils.Conflictf(ctx, "already subscribed to this mailing list") - } - r.logger.ErrorCtx(ctx, "cannot subscribe to mailing list", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.SubscribeToMailingListPayload{ - Subscription: types.NewMailingListSubscriber(subscriber), - }, nil -} - -// UnsubscribeFromMailingList is the resolver for the unsubscribeFromMailingList field. -func (r *mutationResolver) UnsubscribeFromMailingList(ctx context.Context) (*types.UnsubscribeFromMailingListPayload, error) { - trustCenter := compliancepage.CompliancePageFromContext(ctx) - if trustCenter.MailingListID == nil { - return nil, gqlutils.NotFoundf(ctx, "mailing list not found") - } - - identity := authn.IdentityFromContext(ctx) - - subscriber, err := r.mailman.GetSubscriber(ctx, *trustCenter.MailingListID, identity.EmailAddress) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot get mailing list subscription", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - if subscriber == nil { - return nil, gqlutils.NotFoundf(ctx, "not subscribed to this mailing list") - } - - if err := r.mailman.DeleteSubscriber(ctx, subscriber.ID); err != nil { - if errors.Is(err, mailman.ErrSubscriberNotFound) { - return nil, gqlutils.NotFoundf(ctx, "not subscribed to this mailing list") - } - r.logger.ErrorCtx(ctx, "cannot unsubscribe from mailing list", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.UnsubscribeFromMailingListPayload{DeletedMailingListSubscriberID: &subscriber.ID}, nil -} - -// FileURL is the resolver for the fileUrl field. -func (r *nonDisclosureAgreementResolver) FileURL(ctx context.Context, obj *types.NonDisclosureAgreement) (string, error) { - trustCenter := compliancepage.CompliancePageFromContext(ctx) - - if identity := authn.IdentityFromContext(ctx); identity != nil && r.esign != nil { - trustService := r.TrustService(ctx, trustCenter.ID.TenantID()) - - access, err := trustService.TrustCenterAccesses.GetAccess(ctx, trustCenter.ID, identity.ID) - if err == nil && access.ElectronicSignatureID != nil { - fileURL, err := r.esign.GenerateSignatureFileURL(ctx, *access.ElectronicSignatureID, 15*time.Minute) - if err == nil { - return fileURL, nil - } - - r.logger.ErrorCtx(ctx, "cannot generate signature file URL, falling back to original NDA", log.Error(err)) - } - } - - trustService := r.TrustService(ctx, trustCenter.ID.TenantID()) - - fileURL, err := trustService.TrustCenters.GenerateNDAFileURL(ctx, trustCenter.ID, 15*time.Minute) - if err != nil { - return "", gqlutils.Internal(ctx) - } - - return fileURL, nil -} - -// ViewerSignature is the resolver for the viewerSignature field. -func (r *nonDisclosureAgreementResolver) ViewerSignature(ctx context.Context, obj *types.NonDisclosureAgreement) (*types.ElectronicSignature, error) { - identity := authn.IdentityFromContext(ctx) - if identity == nil { - return nil, nil - } - - trustCenter := compliancepage.CompliancePageFromContext(ctx) - trustService := r.TrustService(ctx, trustCenter.ID.TenantID()) - - access, err := trustService.TrustCenterAccesses.GetAccess(ctx, trustCenter.ID, identity.ID) - if err != nil { - return nil, nil - } - - if access.ElectronicSignatureID == nil { - return nil, nil - } - - sig, err := r.esign.GetSignatureByID(ctx, *access.ElectronicSignatureID) - if err != nil { - return nil, nil - } - - return types.NewElectronicSignature(sig), nil -} - -// LogoURL is the resolver for the logoUrl field. -func (r *organizationResolver) LogoURL(ctx context.Context, obj *types.Organization) (*string, error) { - trustService := r.TrustService(ctx, obj.ID.TenantID()) - - return trustService.Organizations.GenerateLogoURL(ctx, obj.ID, 1*time.Hour) -} - -// Viewer is the resolver for the viewer field. -func (r *queryResolver) Viewer(ctx context.Context) (*types.Identity, error) { - identity := authn.IdentityFromContext(ctx) - - if identity == nil { - return nil, nil - } - - return &types.Identity{ - ID: identity.ID, - Email: identity.EmailAddress, - FullName: identity.FullName, - EmailVerified: identity.EmailAddressVerified, - CreatedAt: identity.CreatedAt, - UpdatedAt: identity.UpdatedAt, - }, nil -} - -// Node is the resolver for the node field. -func (r *queryResolver) Node(ctx context.Context, id gid.GID) (types.Node, error) { - trustService := r.TrustService(ctx, id.TenantID()) - - switch id.EntityType() { - case coredata.OrganizationEntityType: - organization, err := trustService.Organizations.Get(ctx, id) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot get organization", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - return types.NewOrganization(organization), nil - - case coredata.DocumentEntityType: - trustCenter := compliancepage.CompliancePageFromContext(ctx) - - document, err := trustService.Documents.Get(ctx, trustCenter.OrganizationID, id) - if err != nil { - if errors.Is(err, trust.ErrDocumentNotFound) || errors.Is(err, trust.ErrDocumentNotVisible) || errors.Is(err, coredata.ErrResourceNotFound) { - return nil, gqlutils.NotFoundf(ctx, "node %q not found", id) - } - if _, ok := errors.AsType[*trust.ErrDocumentArchived](err); ok { - return nil, gqlutils.NotFoundf(ctx, "node %q not found", id) - } - r.logger.ErrorCtx(ctx, "cannot get document", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - return types.NewDocument(document), nil - - case coredata.FrameworkEntityType: - framework, err := trustService.Frameworks.Get(ctx, id) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot get framework", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - return types.NewFramework(framework), nil - - case coredata.ReportEntityType: - trustCenter := compliancepage.CompliancePageFromContext(ctx) - - report, err := trustService.Reports.Get(ctx, trustCenter.OrganizationID, id) - if err != nil { - if errors.Is(err, trust.ErrReportNotFound) || errors.Is(err, coredata.ErrResourceNotFound) { - return nil, gqlutils.NotFoundf(ctx, "node %q not found", id) - } - r.logger.ErrorCtx(ctx, "cannot get report", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - return types.NewReport(report), nil - - case coredata.AuditEntityType: - audit, err := trustService.Audits.Get(ctx, id) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot get audit", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - return types.NewAudit(audit), nil - - case coredata.VendorEntityType: - vendor, err := trustService.Vendors.Get(ctx, id) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot get vendor", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - return types.NewSubprocessor(vendor), nil - - case coredata.TrustCenterEntityType: - trustCenter, err := trustService.TrustCenters.Get(ctx, id) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot get trust center", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - return types.NewTrustCenter(trustCenter), nil - - case coredata.TrustCenterReferenceEntityType: - reference, err := trustService.TrustCenterReferences.Get(ctx, id) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot get trust center reference", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - return types.NewTrustCenterReference(reference), nil - - case coredata.TrustCenterFileEntityType: - trustCenter := compliancepage.CompliancePageFromContext(ctx) - - trustCenterFile, err := trustService.TrustCenterFiles.Get(ctx, trustCenter.OrganizationID, id) - if err != nil { - if errors.Is(err, trust.ErrTrustCenterFileNotFound) || errors.Is(err, trust.ErrTrustCenterFileNotVisible) { - return nil, gqlutils.NotFoundf(ctx, "node %q not found", id) - } - r.logger.ErrorCtx(ctx, "cannot get trust center file", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return types.NewTrustCenterFile(trustCenterFile), nil - - default: - return nil, gqlutils.NotFoundf(ctx, "node %q not found", id) - } -} - -// CurrentTrustCenter is the resolver for the currentTrustCenter field. -func (r *queryResolver) CurrentTrustCenter(ctx context.Context) (*types.TrustCenter, error) { - trustCenter := compliancepage.CompliancePageFromContext(ctx) - - trustService := r.TrustService(ctx, trustCenter.ID.TenantID()) - - org, err := trustService.Organizations.Get(ctx, trustCenter.OrganizationID) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot get organization", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - trustCenter, err = trustService.TrustCenters.Get(ctx, trustCenter.ID) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot get trust center", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - response := types.NewTrustCenter(trustCenter) - response.Organization = types.NewOrganization(org) - - return response, nil -} - -// OidcProviders is the resolver for the oidcProviders field. -func (r *queryResolver) OidcProviders(ctx context.Context) ([]*types.OIDCProviderInfo, error) { - providers := r.iam.OIDCService.EnabledProviders() - result := make([]*types.OIDCProviderInfo, 0, len(providers)) - - for _, p := range providers { - name := strings.ToLower(p.String()) - result = append(result, &types.OIDCProviderInfo{ - Name: name, - LoginURL: r.baseURL.WithPath("/api/connect/v1/oidc/" + name + "/login").MustString(), - }) - } - - return result, nil -} - -// IsUserAuthorized is the resolver for the isUserAuthorized field. -func (r *reportResolver) IsUserAuthorized(ctx context.Context, obj *types.Report) (bool, error) { - trustService := r.TrustService(ctx, obj.ID.TenantID()) - - trustCenter := compliancepage.CompliancePageFromContext(ctx) - - audit, err := trustService.Audits.GetByReportID(ctx, obj.ID) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot load document", log.Error(err)) - return false, gqlutils.Internal(ctx) - } - - if audit.TrustCenterVisibility == coredata.TrustCenterVisibilityPublic { - return true, nil - } - - identity := authn.IdentityFromContext(ctx) - if identity == nil { - return false, nil - } - - reportAccess, err := trustService.TrustCenterAccesses.GetReportAccess(ctx, - trustCenter.ID, - identity.ID, - obj.ID, - ) - if err != nil { - if errors.Is(err, trust.ErrMembershipNotFound) || - errors.Is(err, trust.ErrUserNotFound) || - errors.Is(err, trust.ErrUserInactive) || - errors.Is(err, trust.ErrDocumentAccessNotFound) { - return false, nil - } - - r.logger.ErrorCtx(ctx, "cannot check report access", log.Error(err)) - return false, gqlutils.Internal(ctx) - } - - return reportAccess.Status == coredata.TrustCenterDocumentAccessStatusGranted, nil -} - -// Access is the resolver for the access field. -func (r *reportResolver) Access(ctx context.Context, obj *types.Report) (*types.DocumentAccess, error) { - trustService := r.TrustService(ctx, obj.ID.TenantID()) - trustCenter := compliancepage.CompliancePageFromContext(ctx) - - identity := authn.IdentityFromContext(ctx) - if identity == nil { - return nil, nil // User is not authenticated, so no access requested - } - - access, err := trustService.TrustCenterAccesses.GetReportAccess( - ctx, - trustCenter.ID, - identity.ID, - obj.ID, - ) - if err != nil { - if errors.Is(err, trust.ErrMembershipNotFound) || - errors.Is(err, trust.ErrUserNotFound) || - errors.Is(err, trust.ErrDocumentAccessNotFound) { - return nil, nil - } - - if errors.Is(err, trust.ErrUserInactive) { - return nil, gqlutils.Forbidden(ctx, err) - } - - r.logger.ErrorCtx(ctx, "cannot get audit report access", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.DocumentAccess{ - ID: access.ID, - Status: access.Status, - }, nil -} - -// TotalCount is the resolver for the totalCount field. -func (r *subprocessorConnectionResolver) TotalCount(ctx context.Context, obj *types.SubprocessorConnection) (int, error) { - trustService := r.TrustService(ctx, obj.ParentID.TenantID()) - - switch obj.Resolver.(type) { - case *trustCenterResolver: - count, err := trustService.Vendors.CountForTrustCenterId(ctx, obj.ParentID) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot count subprocessors", log.Error(err)) - return 0, gqlutils.Internal(ctx) - } - return count, nil - } - - r.logger.ErrorCtx(ctx, "not implemented: TotalCount for parent type") - return 0, gqlutils.Internal(ctx) -} - -// LogoFileURL is the resolver for the logoFileUrl field. -func (r *trustCenterResolver) LogoFileURL(ctx context.Context, obj *types.TrustCenter) (*string, error) { - trustService := r.TrustService(ctx, obj.ID.TenantID()) - - return trustService.TrustCenters.GenerateLogoURL(ctx, obj.ID, 1*time.Hour) -} - -// DarkLogoFileURL is the resolver for the darkLogoFileUrl field. -func (r *trustCenterResolver) DarkLogoFileURL(ctx context.Context, obj *types.TrustCenter) (*string, error) { - trustService := r.TrustService(ctx, obj.ID.TenantID()) - - return trustService.TrustCenters.GenerateDarkLogoURL(ctx, obj.ID, 1*time.Hour) -} - -// NonDisclosureAgreement is the resolver for the nonDisclosureAgreement field. -func (r *trustCenterResolver) NonDisclosureAgreement(ctx context.Context, obj *types.TrustCenter) (*types.NonDisclosureAgreement, error) { - trustCenter := compliancepage.CompliancePageFromContext(ctx) - if trustCenter.NonDisclosureAgreementFileID == nil { - return nil, nil - } - - trustService := r.TrustService(ctx, obj.ID.TenantID()) - - file, err := trustService.TrustCenters.GetNDAFile(ctx, obj.ID) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot load NDA file", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - if file == nil { - return nil, nil - } - - return types.NewNonDisclosureAgreement(file), nil -} - -// ViewerSubscription is the resolver for the viewerSubscription field. -func (r *trustCenterResolver) ViewerSubscription(ctx context.Context, obj *types.TrustCenter) (*types.MailingListSubscriber, error) { - trustCenter := compliancepage.CompliancePageFromContext(ctx) - if trustCenter.MailingListID == nil { - return nil, nil - } - - identity := authn.IdentityFromContext(ctx) - if identity == nil { - return nil, nil - } - - subscriber, err := r.mailman.GetSubscriber(ctx, *trustCenter.MailingListID, identity.EmailAddress) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot get mailing list subscription", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - if subscriber == nil { - return nil, nil - } - - return types.NewMailingListSubscriber(subscriber), nil -} - -// Organization is the resolver for the organization field. -func (r *trustCenterResolver) Organization(ctx context.Context, obj *types.TrustCenter) (*types.Organization, error) { - return obj.Organization, nil -} - -// Documents is the resolver for the documents field. -func (r *trustCenterResolver) Documents(ctx context.Context, obj *types.TrustCenter, first *int, after *page.CursorKey, last *int, before *page.CursorKey) (*types.DocumentConnection, error) { - trustService := r.TrustService(ctx, obj.ID.TenantID()) - - pageOrderBy := page.OrderBy[coredata.DocumentOrderField]{ - Field: coredata.DocumentOrderFieldTitle, - Direction: page.OrderDirectionAsc, - } - cursor := types.NewCursor(first, after, last, before, pageOrderBy) - - documentPage, err := trustService.Documents.ListForOrganizationId(ctx, obj.Organization.ID, cursor) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot list public documents", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return types.NewDocumentConnection(documentPage), nil -} - -// Audits is the resolver for the audits field. -func (r *trustCenterResolver) Audits(ctx context.Context, obj *types.TrustCenter, first *int, after *page.CursorKey, last *int, before *page.CursorKey) (*types.AuditConnection, error) { - trustService := r.TrustService(ctx, obj.ID.TenantID()) - - pageOrderBy := page.OrderBy[coredata.AuditOrderField]{ - Field: coredata.AuditOrderFieldValidFrom, - Direction: page.OrderDirectionDesc, - } - cursor := types.NewCursor(first, after, last, before, pageOrderBy) - - auditPage, err := trustService.Audits.ListForOrganizationId(ctx, obj.Organization.ID, cursor) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot list public audits", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return types.NewAuditConnection(auditPage), nil -} - -// Subprocessors is the resolver for the subprocessors field. -func (r *trustCenterResolver) Subprocessors(ctx context.Context, obj *types.TrustCenter, first *int, after *page.CursorKey, last *int, before *page.CursorKey) (*types.SubprocessorConnection, error) { - trustService := r.TrustService(ctx, obj.ID.TenantID()) - - pageOrderBy := page.OrderBy[coredata.VendorOrderField]{ - Field: coredata.VendorOrderFieldName, - Direction: page.OrderDirectionAsc, - } - cursor := types.NewCursor(first, after, last, before, pageOrderBy) - - vendorPage, err := trustService.Vendors.ListForOrganizationId(ctx, obj.Organization.ID, cursor) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot list subprocessors", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return types.NewSubprocessorConnection(vendorPage, r, obj.ID), nil -} - -// References is the resolver for the references field. -func (r *trustCenterResolver) References(ctx context.Context, obj *types.TrustCenter, first *int, after *page.CursorKey, last *int, before *page.CursorKey) (*types.TrustCenterReferenceConnection, error) { - trustService := r.TrustService(ctx, obj.ID.TenantID()) - - pageOrderBy := page.OrderBy[coredata.TrustCenterReferenceOrderField]{ - Field: coredata.TrustCenterReferenceOrderFieldRank, - Direction: page.OrderDirectionAsc, - } - cursor := types.NewCursor(first, after, last, before, pageOrderBy) - - referencePage, err := trustService.TrustCenterReferences.ListForTrustCenterID(ctx, obj.ID, cursor) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot list public trust center references", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return types.NewTrustCenterReferenceConnection(referencePage), nil -} - -// TrustCenterFiles is the resolver for the trustCenterFiles field. -func (r *trustCenterResolver) TrustCenterFiles(ctx context.Context, obj *types.TrustCenter, first *int, after *page.CursorKey, last *int, before *page.CursorKey) (*types.TrustCenterFileConnection, error) { - trustService := r.TrustService(ctx, obj.ID.TenantID()) - - pageOrderBy := page.OrderBy[coredata.TrustCenterFileOrderField]{ - Field: coredata.TrustCenterFileOrderFieldName, - Direction: page.OrderDirectionAsc, - } - cursor := types.NewCursor(first, after, last, before, pageOrderBy) - - filter := coredata.NewTrustCenterFileFilter( - coredata.WithTrustCenterFileVisibilities( - coredata.TrustCenterVisibilityPublic, - coredata.TrustCenterVisibilityPrivate, - ), - ) - trustCenterFilePage, err := trustService.TrustCenterFiles.ListForOrganizationId(ctx, obj.Organization.ID, cursor, filter) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot list public trust center files", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return types.NewTrustCenterFileConnection(trustCenterFilePage), nil -} - -// ComplianceFrameworks is the resolver for the complianceFrameworks field. -func (r *trustCenterResolver) ComplianceFrameworks(ctx context.Context, obj *types.TrustCenter, first *int, after *page.CursorKey, last *int, before *page.CursorKey) (*types.ComplianceFrameworkConnection, error) { - trustService := r.TrustService(ctx, obj.ID.TenantID()) - - pageOrderBy := page.OrderBy[coredata.ComplianceFrameworkOrderField]{ - Field: coredata.ComplianceFrameworkOrderFieldRank, - Direction: page.OrderDirectionAsc, - } - cursor := types.NewCursor(first, after, last, before, pageOrderBy) - - cfPage, err := trustService.ComplianceFrameworks.ListByTrustCenterID(ctx, obj.ID, cursor) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot list compliance frameworks", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return types.NewComplianceFrameworkConnection(cfPage), nil -} - -// ExternalUrls is the resolver for the externalUrls field. -func (r *trustCenterResolver) ExternalUrls(ctx context.Context, obj *types.TrustCenter, first *int, after *page.CursorKey, last *int, before *page.CursorKey) (*types.ComplianceExternalURLConnection, error) { - trustService := r.TrustService(ctx, obj.ID.TenantID()) - - pageOrderBy := page.OrderBy[coredata.ComplianceExternalURLOrderField]{ - Field: coredata.ComplianceExternalURLOrderFieldRank, - Direction: page.OrderDirectionAsc, - } - cursor := types.NewCursor(first, after, last, before, pageOrderBy) - - result, err := trustService.ComplianceExternalURLs.ListForTrustCenterID(ctx, obj.ID, cursor) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot list compliance external URLs", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return types.NewComplianceExternalURLConnection(result), nil -} - -// Updates is the resolver for the updates field. -func (r *trustCenterResolver) Updates(ctx context.Context, obj *types.TrustCenter, first *int, after *page.CursorKey, last *int, before *page.CursorKey) (*types.MailingListUpdateConnection, error) { - trustService := r.TrustService(ctx, obj.ID.TenantID()) - - tc, err := trustService.TrustCenters.Get(ctx, obj.ID) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot load trust center", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - if tc.MailingListID == nil { - return &types.MailingListUpdateConnection{Edges: []*types.MailingListUpdateEdge{}, PageInfo: &types.PageInfo{}}, nil - } - - pageOrderBy := page.OrderBy[coredata.MailingListUpdateOrderField]{ - Field: coredata.MailingListUpdateOrderFieldUpdatedAt, - Direction: page.OrderDirectionDesc, - } - cursor := types.NewCursor(first, after, last, before, pageOrderBy) - - result, err := r.mailman.ListSentMailingListUpdates(ctx, *tc.MailingListID, cursor) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot list mailing list updates", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return types.NewMailingListUpdateConnection(result), nil -} - -// IsUserAuthorized is the resolver for the isUserAuthorized field. -func (r *trustCenterFileResolver) IsUserAuthorized(ctx context.Context, obj *types.TrustCenterFile) (bool, error) { - trustService := r.TrustService(ctx, obj.ID.TenantID()) - - trustCenter := compliancepage.CompliancePageFromContext(ctx) - - trustCenterFile, err := trustService.TrustCenterFiles.Get(ctx, trustCenter.OrganizationID, obj.ID) - if err != nil { - if errors.Is(err, trust.ErrTrustCenterFileNotFound) || errors.Is(err, trust.ErrTrustCenterFileNotVisible) { - return false, gqlutils.NotFoundf(ctx, "trust center file %q not found", obj.ID) - } - r.logger.ErrorCtx(ctx, "cannot load trust center file", log.Error(err)) - return false, gqlutils.Internal(ctx) - } - - if trustCenterFile.TrustCenterVisibility == coredata.TrustCenterVisibilityPublic { - return true, nil - } - - identity := authn.IdentityFromContext(ctx) - if identity == nil { - return false, nil - } - - fileAccess, err := trustService.TrustCenterAccesses.GetTrustCenterFileAccess(ctx, - trustCenter.ID, - identity.ID, - obj.ID, - ) - if err != nil { - if errors.Is(err, trust.ErrMembershipNotFound) || - errors.Is(err, trust.ErrUserNotFound) || - errors.Is(err, trust.ErrUserInactive) || - errors.Is(err, trust.ErrDocumentAccessNotFound) { - return false, nil - } - - r.logger.ErrorCtx(ctx, "cannot check trust center file access", log.Error(err)) - return false, gqlutils.Internal(ctx) - } - - return fileAccess.Status == coredata.TrustCenterDocumentAccessStatusGranted, nil -} - -// Access is the resolver for the access field. -func (r *trustCenterFileResolver) Access(ctx context.Context, obj *types.TrustCenterFile) (*types.DocumentAccess, error) { - trustService := r.TrustService(ctx, obj.ID.TenantID()) - trustCenter := compliancepage.CompliancePageFromContext(ctx) - - identity := authn.IdentityFromContext(ctx) - if identity == nil { - return nil, nil // User is not authenticated, so no access requested - } - - access, err := trustService.TrustCenterAccesses.GetTrustCenterFileAccess( - ctx, - trustCenter.ID, - identity.ID, - obj.ID, - ) - if err != nil { - if errors.Is(err, trust.ErrMembershipNotFound) || - errors.Is(err, trust.ErrUserNotFound) || - errors.Is(err, trust.ErrDocumentAccessNotFound) { - return nil, nil - } - - if errors.Is(err, trust.ErrUserInactive) { - return nil, gqlutils.Forbidden(ctx, err) - } - - r.logger.ErrorCtx(ctx, "cannot get file access", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.DocumentAccess{ - ID: access.ID, - Status: access.Status, - }, nil -} - -// LogoURL is the resolver for the logoUrl field. -func (r *trustCenterReferenceResolver) LogoURL(ctx context.Context, obj *types.TrustCenterReference) (string, error) { - trustService := r.TrustService(ctx, obj.ID.TenantID()) - - logoURL, err := trustService.TrustCenterReferences.GenerateLogoURL(ctx, obj.ID, 1*time.Hour) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot generate logo URL", log.Error(err)) - return "", gqlutils.Internal(ctx) - } - - return logoURL, nil -} - -// Audit returns schema.AuditResolver implementation. -func (r *Resolver) Audit() schema.AuditResolver { return &auditResolver{r} } - -// ComplianceFramework returns schema.ComplianceFrameworkResolver implementation. -func (r *Resolver) ComplianceFramework() schema.ComplianceFrameworkResolver { - return &complianceFrameworkResolver{r} -} - -// Document returns schema.DocumentResolver implementation. -func (r *Resolver) Document() schema.DocumentResolver { return &documentResolver{r} } - -// Framework returns schema.FrameworkResolver implementation. -func (r *Resolver) Framework() schema.FrameworkResolver { return &frameworkResolver{r} } - -// Mutation returns schema.MutationResolver implementation. -func (r *Resolver) Mutation() schema.MutationResolver { return &mutationResolver{r} } - -// NonDisclosureAgreement returns schema.NonDisclosureAgreementResolver implementation. -func (r *Resolver) NonDisclosureAgreement() schema.NonDisclosureAgreementResolver { - return &nonDisclosureAgreementResolver{r} -} - -// Organization returns schema.OrganizationResolver implementation. -func (r *Resolver) Organization() schema.OrganizationResolver { return &organizationResolver{r} } - -// Query returns schema.QueryResolver implementation. -func (r *Resolver) Query() schema.QueryResolver { return &queryResolver{r} } - -// Report returns schema.ReportResolver implementation. -func (r *Resolver) Report() schema.ReportResolver { return &reportResolver{r} } - -// SubprocessorConnection returns schema.SubprocessorConnectionResolver implementation. -func (r *Resolver) SubprocessorConnection() schema.SubprocessorConnectionResolver { - return &subprocessorConnectionResolver{r} -} - -// TrustCenter returns schema.TrustCenterResolver implementation. -func (r *Resolver) TrustCenter() schema.TrustCenterResolver { return &trustCenterResolver{r} } - -// TrustCenterFile returns schema.TrustCenterFileResolver implementation. -func (r *Resolver) TrustCenterFile() schema.TrustCenterFileResolver { - return &trustCenterFileResolver{r} -} - -// TrustCenterReference returns schema.TrustCenterReferenceResolver implementation. -func (r *Resolver) TrustCenterReference() schema.TrustCenterReferenceResolver { - return &trustCenterReferenceResolver{r} -} - -type auditResolver struct{ *Resolver } -type complianceFrameworkResolver struct{ *Resolver } -type documentResolver struct{ *Resolver } -type frameworkResolver struct{ *Resolver } -type mutationResolver struct{ *Resolver } -type nonDisclosureAgreementResolver struct{ *Resolver } -type organizationResolver struct{ *Resolver } -type queryResolver struct{ *Resolver } -type reportResolver struct{ *Resolver } -type subprocessorConnectionResolver struct{ *Resolver } -type trustCenterResolver struct{ *Resolver } -type trustCenterFileResolver struct{ *Resolver } -type trustCenterReferenceResolver struct{ *Resolver } diff --git a/relay.config.json b/relay.config.json index d88213d6e..0d25a5f91 100644 --- a/relay.config.json +++ b/relay.config.json @@ -7,7 +7,8 @@ }, "projects": { "core": { - "schema": "pkg/server/api/console/v1/schema.graphql", + "schema": "pkg/server/api/console/v1/graphql/base.graphql", + "schemaExtensions": ["pkg/server/api/console/v1/graphql"], "language": "typescript", "noFutureProofEnums": true, "output": "apps/console/src/__generated__/core", @@ -22,7 +23,8 @@ } }, "iam": { - "schema": "pkg/server/api/connect/v1/schema.graphql", + "schema": "pkg/server/api/connect/v1/graphql/base.graphql", + "schemaExtensions": ["pkg/server/api/connect/v1/graphql"], "language": "typescript", "noFutureProofEnums": true, "output": "apps/console/src/__generated__/iam", @@ -37,7 +39,8 @@ } }, "trust": { - "schema": "pkg/server/api/trust/v1/schema.graphql", + "schema": "pkg/server/api/trust/v1/graphql/base.graphql", + "schemaExtensions": ["pkg/server/api/trust/v1/graphql"], "language": "typescript", "noFutureProofEnums": true, "customScalarTypes": {