Trust GraphQL and MCP still exposed presigned URL strings for trust-center logos while console and connect already serve stable File.downloadUrl paths. Phase 1 migrates the seven public logo fields on trust GraphQL and the trust-center file references on MCP to the shared File type; trust GraphQL NDA stays on fileUrl for a follow-up. Trust resolvers load public files through filemanager and map them with types.NewFile. The trust app Relay queries and components now read logo.downloadUrl. MCP specification, resolvers, and helpers are updated in sync, including NDA on MCP where callers already have file access. filemanager is split into focused files and its URL surface is narrowed to GenerateFileURL(file) for stable app URLs and GeneratePresignedURL for S3 redirects. GetPublicFile remains the DB entry point when only a file ID is known. Add trust and MCP e2e coverage for public logo download URLs. Signed-off-by: Ludovic Vielle <ludovic@probo.com>
706 lines
23 KiB
Go
706 lines
23 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"
|
|
|
|
"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
|
|
}
|
|
|
|
// ReportFile is the resolver for the reportFile field.
|
|
func (r *auditResolver) ReportFile(ctx context.Context, obj *types.Audit) (*types.File, error) {
|
|
if _, err := r.authorize(ctx, obj.ID, probo.ActionReportGet); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if obj.ReportFile == nil {
|
|
return nil, nil
|
|
}
|
|
|
|
loaders := dataloader.FromContext(ctx)
|
|
|
|
file, err := loaders.File.Load(ctx, obj.ReportFile.ID)
|
|
if err != nil {
|
|
if errors.Is(err, dataloadgen.ErrNotFound) {
|
|
return nil, gqlutils.NotFound(ctx, err)
|
|
}
|
|
|
|
r.logger.ErrorCtx(ctx, "cannot load report file", log.Error(err))
|
|
|
|
return nil, gqlutils.Internal(ctx)
|
|
}
|
|
|
|
return types.NewFile(file, r.fileManager), 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) {
|
|
scope, err := r.authorize(ctx, obj.ID, probo.ActionControlList)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
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 := r.probo.Controls.ListForAuditID(ctx, scope, 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) {
|
|
scope, err := r.authorize(ctx, obj.ID, probo.ActionFindingList)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
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 := r.probo.Findings.ListForAuditID(ctx, scope, 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) {
|
|
scope, err := r.authorize(ctx, obj.ParentID, probo.ActionAuditList)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
switch obj.Resolver.(type) {
|
|
case *organizationResolver:
|
|
count, err := r.probo.Audits.CountForOrganizationID(ctx, scope, 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 := r.probo.Audits.CountForFindingID(ctx, scope, 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 := r.probo.Audits.CountForControlID(ctx, scope, 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) {
|
|
scope, err := r.authorize(ctx, obj.ID, probo.ActionAuditList)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
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 := r.probo.Audits.ListForFindingID(ctx, scope, 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) {
|
|
scope, err := r.authorize(ctx, obj.ParentID, probo.ActionFindingList)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
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 := r.probo.Findings.CountForOrganizationID(ctx, scope, 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 := r.probo.Findings.CountForAuditID(ctx, scope, 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) {
|
|
scope, err := r.authorize(ctx, input.OrganizationID, probo.ActionAuditCreate)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
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 := r.probo.Audits.Create(ctx, scope, &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 = r.probo.Audits.UploadReport(ctx, scope, &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) {
|
|
scope, err := r.authorize(ctx, input.ID, probo.ActionAuditUpdate)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
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 := r.probo.Audits.Update(ctx, scope, &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) {
|
|
scope, err := r.authorize(ctx, input.AuditID, probo.ActionAuditDelete)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if err := r.probo.Audits.Delete(ctx, scope, input.AuditID); 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) {
|
|
scope, err := r.authorize(ctx, input.AuditID, probo.ActionAuditReportUpload)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
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 := r.probo.Audits.UploadReport(ctx, scope, &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) {
|
|
scope, err := r.authorize(ctx, input.AuditID, probo.ActionAuditReportDelete)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
audit, err := r.probo.Audits.DeleteReport(ctx, scope, 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) {
|
|
scope, err := r.authorize(ctx, input.OrganizationID, probo.ActionFindingCreate)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
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 := r.probo.Findings.Create(ctx, scope, &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) {
|
|
scope, err := r.authorize(ctx, input.ID, probo.ActionFindingUpdate)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
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 := r.probo.Findings.Update(ctx, scope, &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) {
|
|
scope, err := r.authorize(ctx, input.FindingID, probo.ActionFindingDelete)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if err := r.probo.Findings.Delete(ctx, scope, input.FindingID); 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) {
|
|
scope, err := r.authorize(ctx, input.FindingID, probo.ActionFindingAuditMappingCreate)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
finding, audit, err := r.probo.Findings.CreateAuditMapping(ctx, scope, 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) {
|
|
scope, err := r.authorize(ctx, input.FindingID, probo.ActionFindingAuditMappingDelete)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
finding, audit, err := r.probo.Findings.DeleteAuditMapping(ctx, scope, 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) {
|
|
scope, err := r.authorize(ctx, input.OrganizationID, probo.ActionFindingPublish)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
document, documentVersion, err := r.probo.GeneratedDocuments.PublishFindingList(ctx, scope, input.OrganizationID, input.ApproverIds, input.Minor)
|
|
if err != nil {
|
|
if errors.Is(err, coredata.ErrResourceAlreadyExists) {
|
|
return nil, gqlutils.Conflict(ctx, err)
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
// 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}
|
|
}
|
|
|
|
type auditResolver struct{ *Resolver }
|
|
type auditConnectionResolver struct{ *Resolver }
|
|
type findingResolver struct{ *Resolver }
|
|
type findingConnectionResolver struct{ *Resolver }
|