Generated documents (asset list, risk register, SoA, ...) previously
only ever produced a new major version. Every regeneration of an
auto-built register consumed a major number, even when the change was
trivial. They now accept a minor flag and publish as
currentMajor.currentMinor+1 when set, bypassing the approval flow.
To carry the flag through cleanly, the document publish API was
refactored. The three split mutations (publishMajor, publishMinor,
requestDocumentVersionApproval) and the two bulk variants collapse
into a single publishDocument / bulkPublishDocuments, both taking the
new minor: Boolean! and a now-required changelog: String!. The same
shape flows through the CLI ("prb document publish --minor"), the MCP
tool, the n8n operations, and the Relay dialogs, where each
generated-doc dialog gains a "Publish as minor" button. Publishing
minor without an existing major is rejected with
ErrCannotPublishMinorWithoutMajor.
This is a deliberate breaking change for callers of the prior
mutations.
Signed-off-by: Sacha Al Himdani <sacha@getprobo.com>
923 lines
35 KiB
Go
923 lines
35 KiB
Go
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.90
|
|
|
|
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"
|
|
)
|
|
|
|
// 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).
|
|
WithWriteModes(filter.WriteModes).
|
|
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) (*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)
|
|
|
|
page, err := prb.Obligations.ListForControlID(ctx, obj.ID, cursor)
|
|
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), 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,
|
|
MaturityLevel: input.MaturityLevel,
|
|
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,
|
|
MaturityLevel: input.MaturityLevel,
|
|
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
|
|
}
|
|
|
|
// 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,
|
|
},
|
|
)
|
|
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,
|
|
},
|
|
)
|
|
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
|
|
}
|
|
|
|
// PublishStatementOfApplicability is the resolver for the publishStatementOfApplicability field.
|
|
func (r *mutationResolver) PublishStatementOfApplicability(ctx context.Context, input types.PublishStatementOfApplicabilityInput) (*types.PublishStatementOfApplicabilityPayload, error) {
|
|
if err := r.authorize(ctx, input.StatementOfApplicabilityID, probo.ActionStatementOfApplicabilityPublish); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
prb := r.ProboService(ctx, input.StatementOfApplicabilityID.TenantID())
|
|
|
|
document, documentVersion, err := prb.GeneratedDocuments.PublishStatementOfApplicability(ctx, input.StatementOfApplicabilityID, input.ApproverIds, input.Minor)
|
|
if err != nil {
|
|
if errors.Is(err, coredata.ErrResourceAlreadyExists) {
|
|
return nil, gqlutils.Conflict(ctx, err)
|
|
}
|
|
if errMinor, ok := errors.AsType[*probo.ErrCannotPublishMinorWithoutMajor](err); ok {
|
|
return nil, gqlutils.Invalid(ctx, errMinor)
|
|
}
|
|
r.logger.ErrorCtx(ctx, "cannot publish statement of applicability", log.Error(err))
|
|
return nil, gqlutils.Internal(ctx)
|
|
}
|
|
|
|
return &types.PublishStatementOfApplicabilityPayload{
|
|
DocumentEdge: types.NewDocumentEdge(document, coredata.DocumentOrderFieldCreatedAt),
|
|
DocumentVersionEdge: types.NewDocumentVersionEdge(documentVersion, coredata.DocumentVersionOrderFieldCreatedAt),
|
|
}, nil
|
|
}
|
|
|
|
// Document is the resolver for the document field.
|
|
func (r *statementOfApplicabilityResolver) Document(ctx context.Context, obj *types.StatementOfApplicability) (*types.Document, error) {
|
|
if obj.Document == nil {
|
|
return nil, nil
|
|
}
|
|
|
|
if err := r.authorize(ctx, obj.Document.ID, probo.ActionDocumentGet); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
prb := r.ProboService(ctx, obj.Document.ID.TenantID())
|
|
|
|
document, err := prb.Documents.Get(ctx, obj.Document.ID)
|
|
if err != nil {
|
|
if errors.Is(err, coredata.ErrResourceNotFound) {
|
|
return nil, nil
|
|
}
|
|
r.logger.ErrorCtx(ctx, "cannot load document", log.Error(err))
|
|
return nil, gqlutils.Internal(ctx)
|
|
}
|
|
|
|
return types.NewDocument(document), 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
|
|
}
|
|
|
|
// 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)
|
|
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 }
|