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>
763 lines
25 KiB
Go
763 lines
25 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"
|
|
"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(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(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
|
|
}
|
|
|
|
// PublishFindingList is the resolver for the publishFindingList field.
|
|
func (r *mutationResolver) PublishFindingList(ctx context.Context, input types.PublishFindingListInput) (*types.PublishFindingListPayload, error) {
|
|
if err := r.authorize(ctx, input.OrganizationID, probo.ActionFindingPublish); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
prb := r.ProboService(ctx, input.OrganizationID.TenantID())
|
|
|
|
document, documentVersion, err := prb.GeneratedDocuments.PublishFindingList(ctx, input.OrganizationID, 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 finding list", log.Error(err))
|
|
return nil, gqlutils.Internal(ctx)
|
|
}
|
|
|
|
return &types.PublishFindingListPayload{
|
|
DocumentEdge: types.NewDocumentEdge(document, coredata.DocumentOrderFieldCreatedAt),
|
|
DocumentVersionEdge: types.NewDocumentVersionEdge(documentVersion, coredata.DocumentVersionOrderFieldCreatedAt),
|
|
}, 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 }
|