Statements of Applicability are no longer exported as one-off PDFs. Instead, each SOA owns a persistent document that accumulates versions over time, following the same publish/approve lifecycle as authored documents. Publishing without approvers publishes immediately; publishing with approvers creates a draft pending approval via the existing quorum system. SOAs can also store default approvers that are pre-populated in the publish dialog. The SOA is removed from the snapshot system — applicability statements are now queried directly (snapshot_id IS NULL) rather than through snapshot copies. A standalone migration script (cmd/migrate-soa-snapshots-to-documents) converts existing SOA snapshots into documents with proper ProseMirror content, preserving version history and approval decisions. Signed-off-by: Sacha Al Himdani <sacha@getprobo.com>
400 lines
14 KiB
Go
400 lines
14 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.87
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
|
|
"go.gearno.de/kit/log"
|
|
"go.probo.inc/probo/pkg/coredata"
|
|
"go.probo.inc/probo/pkg/page"
|
|
"go.probo.inc/probo/pkg/probo"
|
|
"go.probo.inc/probo/pkg/server/api/console/v1/schema"
|
|
"go.probo.inc/probo/pkg/server/api/console/v1/types"
|
|
"go.probo.inc/probo/pkg/server/gqlutils"
|
|
"go.probo.inc/probo/pkg/validator"
|
|
)
|
|
|
|
// Evidences is the resolver for the evidences field.
|
|
func (r *measureResolver) Evidences(ctx context.Context, obj *types.Measure, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.EvidenceOrderBy) (*types.EvidenceConnection, error) {
|
|
if err := r.authorize(ctx, obj.ID, probo.ActionEvidenceList); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
prb := r.ProboService(ctx, obj.ID.TenantID())
|
|
|
|
pageOrderBy := page.OrderBy[coredata.EvidenceOrderField]{
|
|
Field: coredata.EvidenceOrderFieldCreatedAt,
|
|
Direction: page.OrderDirectionDesc,
|
|
}
|
|
if orderBy != nil {
|
|
pageOrderBy = page.OrderBy[coredata.EvidenceOrderField]{
|
|
Field: orderBy.Field,
|
|
Direction: orderBy.Direction,
|
|
}
|
|
}
|
|
|
|
cursor := types.NewCursor(first, after, last, before, pageOrderBy)
|
|
|
|
page, err := prb.Evidences.ListForMeasureID(ctx, obj.ID, cursor)
|
|
if err != nil {
|
|
r.logger.ErrorCtx(ctx, "cannot list measure evidences", log.Error(err))
|
|
return nil, gqlutils.Internal(ctx)
|
|
}
|
|
|
|
return types.NewEvidenceConnection(page, r, obj.ID), nil
|
|
}
|
|
|
|
// Tasks is the resolver for the tasks field.
|
|
func (r *measureResolver) Tasks(ctx context.Context, obj *types.Measure, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.TaskOrderBy) (*types.TaskConnection, error) {
|
|
if err := r.authorize(ctx, obj.ID, probo.ActionTaskList); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
prb := r.ProboService(ctx, obj.ID.TenantID())
|
|
|
|
pageOrderBy := page.OrderBy[coredata.TaskOrderField]{
|
|
Field: coredata.TaskOrderFieldCreatedAt,
|
|
Direction: page.OrderDirectionDesc,
|
|
}
|
|
if orderBy != nil {
|
|
pageOrderBy = page.OrderBy[coredata.TaskOrderField]{
|
|
Field: orderBy.Field,
|
|
Direction: orderBy.Direction,
|
|
}
|
|
}
|
|
|
|
cursor := types.NewCursor(first, after, last, before, pageOrderBy)
|
|
|
|
page, err := prb.Tasks.ListForMeasureID(ctx, obj.ID, cursor)
|
|
if err != nil {
|
|
r.logger.ErrorCtx(ctx, "cannot list measure tasks", log.Error(err))
|
|
return nil, gqlutils.Internal(ctx)
|
|
}
|
|
|
|
return types.NewTaskConnection(page, r, obj.ID), nil
|
|
}
|
|
|
|
// Risks is the resolver for the risks field.
|
|
func (r *measureResolver) Risks(ctx context.Context, obj *types.Measure, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.RiskOrderBy, filter *types.RiskFilter) (*types.RiskConnection, error) {
|
|
if err := r.authorize(ctx, obj.ID, probo.ActionRiskList); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
prb := r.ProboService(ctx, obj.ID.TenantID())
|
|
|
|
pageOrderBy := page.OrderBy[coredata.RiskOrderField]{
|
|
Field: coredata.RiskOrderFieldCreatedAt,
|
|
Direction: page.OrderDirectionDesc,
|
|
}
|
|
if orderBy != nil {
|
|
pageOrderBy = page.OrderBy[coredata.RiskOrderField]{
|
|
Field: orderBy.Field,
|
|
Direction: orderBy.Direction,
|
|
}
|
|
}
|
|
|
|
cursor := types.NewCursor(first, after, last, before, pageOrderBy)
|
|
|
|
var riskFilter = coredata.NewRiskFilter(nil, nil)
|
|
if filter != nil {
|
|
riskFilter = coredata.NewRiskFilter(filter.Query, &filter.SnapshotID)
|
|
}
|
|
|
|
page, err := prb.Risks.ListForMeasureID(ctx, obj.ID, cursor, riskFilter)
|
|
if err != nil {
|
|
r.logger.ErrorCtx(ctx, "cannot list measure risks", log.Error(err))
|
|
return nil, gqlutils.Internal(ctx)
|
|
}
|
|
|
|
return types.NewRiskConnection(page, r, obj.ID, riskFilter), nil
|
|
}
|
|
|
|
// Controls is the resolver for the controls field.
|
|
func (r *measureResolver) Controls(ctx context.Context, obj *types.Measure, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.ControlOrderBy, filter *types.ControlFilter) (*types.ControlConnection, error) {
|
|
if err := r.authorize(ctx, obj.ID, probo.ActionControlList); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
prb := r.ProboService(ctx, obj.ID.TenantID())
|
|
|
|
pageOrderBy := page.OrderBy[coredata.ControlOrderField]{
|
|
Field: coredata.ControlOrderFieldCreatedAt,
|
|
Direction: page.OrderDirectionDesc,
|
|
}
|
|
if orderBy != nil {
|
|
pageOrderBy = page.OrderBy[coredata.ControlOrderField]{
|
|
Field: orderBy.Field,
|
|
Direction: orderBy.Direction,
|
|
}
|
|
}
|
|
|
|
cursor := types.NewCursor(first, after, last, before, pageOrderBy)
|
|
|
|
var controlFilter = coredata.NewControlFilter(nil)
|
|
if filter != nil {
|
|
controlFilter = coredata.NewControlFilter(filter.Query)
|
|
}
|
|
|
|
page, err := prb.Controls.ListForMeasureID(ctx, obj.ID, cursor, controlFilter)
|
|
if err != nil {
|
|
r.logger.ErrorCtx(ctx, "cannot list measure controls", log.Error(err))
|
|
return nil, gqlutils.Internal(ctx)
|
|
}
|
|
|
|
return types.NewControlConnection(page, r, obj.ID, controlFilter), nil
|
|
}
|
|
|
|
// Documents is the resolver for the documents field.
|
|
func (r *measureResolver) Documents(ctx context.Context, obj *types.Measure, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.DocumentOrderBy, filter *types.DocumentFilter) (*types.DocumentConnection, error) {
|
|
if err := r.authorize(ctx, obj.ID, probo.ActionDocumentList); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
prb := r.ProboService(ctx, obj.ID.TenantID())
|
|
|
|
pageOrderBy := page.OrderBy[coredata.DocumentOrderField]{
|
|
Field: coredata.DocumentOrderFieldCreatedAt,
|
|
Direction: page.OrderDirectionDesc,
|
|
}
|
|
if orderBy != nil {
|
|
pageOrderBy = page.OrderBy[coredata.DocumentOrderField]{
|
|
Field: orderBy.Field,
|
|
Direction: orderBy.Direction,
|
|
}
|
|
}
|
|
|
|
cursor := types.NewCursor(first, after, last, before, pageOrderBy)
|
|
|
|
var documentFilter = coredata.NewDocumentFilter(nil)
|
|
if filter != nil {
|
|
documentFilter = coredata.NewDocumentFilter(filter.Query).
|
|
WithWriteModes(filter.WriteModes).
|
|
WithDocumentTypes(filter.DocumentTypes).
|
|
WithClassifications(filter.Classifications)
|
|
}
|
|
|
|
pg, err := prb.Documents.ListForMeasureID(ctx, obj.ID, cursor, documentFilter)
|
|
if err != nil {
|
|
r.logger.ErrorCtx(ctx, "cannot list documents", log.Error(err))
|
|
return nil, gqlutils.Internal(ctx)
|
|
}
|
|
|
|
return types.NewDocumentConnection(pg, r, obj.ID, documentFilter), nil
|
|
}
|
|
|
|
// Permission is the resolver for the permission field.
|
|
func (r *measureResolver) Permission(ctx context.Context, obj *types.Measure, action string) (bool, error) {
|
|
return r.Resolver.Permission(ctx, obj, action)
|
|
}
|
|
|
|
// TotalCount is the resolver for the totalCount field.
|
|
func (r *measureConnectionResolver) TotalCount(ctx context.Context, obj *types.MeasureConnection) (int, error) {
|
|
if err := r.authorize(ctx, obj.ParentID, probo.ActionMeasureList); err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
prb := r.ProboService(ctx, obj.ParentID.TenantID())
|
|
|
|
switch obj.Resolver.(type) {
|
|
case *organizationResolver:
|
|
count, err := prb.Measures.CountForOrganizationID(ctx, obj.ParentID, obj.Filters)
|
|
if err != nil {
|
|
r.logger.ErrorCtx(ctx, "cannot count measures", log.Error(err))
|
|
return 0, gqlutils.Internal(ctx)
|
|
}
|
|
return count, nil
|
|
case *controlResolver:
|
|
count, err := prb.Measures.CountForControlID(ctx, obj.ParentID, obj.Filters)
|
|
if err != nil {
|
|
r.logger.ErrorCtx(ctx, "cannot count measures", log.Error(err))
|
|
return 0, gqlutils.Internal(ctx)
|
|
}
|
|
return count, nil
|
|
case *riskResolver:
|
|
count, err := prb.Measures.CountForRiskID(ctx, obj.ParentID, obj.Filters)
|
|
if err != nil {
|
|
r.logger.ErrorCtx(ctx, "cannot count measures", log.Error(err))
|
|
return 0, gqlutils.Internal(ctx)
|
|
}
|
|
return count, nil
|
|
}
|
|
|
|
r.logger.ErrorCtx(ctx, "unsupported resolver")
|
|
return 0, gqlutils.Internal(ctx)
|
|
}
|
|
|
|
// // CreateMeasure is the resolver for the createMeasure field.
|
|
func (r *mutationResolver) CreateMeasure(ctx context.Context, input types.CreateMeasureInput) (*types.CreateMeasurePayload, error) {
|
|
if err := r.authorize(ctx, input.OrganizationID, probo.ActionMeasureCreate); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
prb := r.ProboService(ctx, input.OrganizationID.TenantID())
|
|
|
|
measure, err := prb.Measures.Create(
|
|
ctx,
|
|
probo.CreateMeasureRequest{
|
|
OrganizationID: input.OrganizationID,
|
|
Name: input.Name,
|
|
Description: input.Description,
|
|
Category: input.Category,
|
|
},
|
|
)
|
|
if err != nil {
|
|
if errors.Is(err, coredata.ErrResourceAlreadyExists) {
|
|
return nil, gqlutils.Conflict(ctx, err)
|
|
}
|
|
|
|
if validationErrors, ok := errors.AsType[validator.ValidationErrors](err); ok {
|
|
return nil, gqlutils.InvalidValidationErrors(ctx, validationErrors)
|
|
}
|
|
r.logger.ErrorCtx(ctx, "cannot create measure", log.Error(err))
|
|
return nil, gqlutils.Internal(ctx)
|
|
}
|
|
|
|
return &types.CreateMeasurePayload{
|
|
MeasureEdge: types.NewMeasureEdge(measure, coredata.MeasureOrderFieldCreatedAt),
|
|
}, nil
|
|
}
|
|
|
|
// UpdateMeasure is the resolver for the updateMeasure field.
|
|
func (r *mutationResolver) UpdateMeasure(ctx context.Context, input types.UpdateMeasureInput) (*types.UpdateMeasurePayload, error) {
|
|
if err := r.authorize(ctx, input.ID, probo.ActionMeasureUpdate); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
prb := r.ProboService(ctx, input.ID.TenantID())
|
|
|
|
measure, err := prb.Measures.Update(
|
|
ctx,
|
|
probo.UpdateMeasureRequest{
|
|
ID: input.ID,
|
|
Name: input.Name,
|
|
Description: gqlutils.UnwrapOmittable(input.Description),
|
|
Category: input.Category,
|
|
State: input.State,
|
|
},
|
|
)
|
|
if err != nil {
|
|
if validationErrors, ok := errors.AsType[validator.ValidationErrors](err); ok {
|
|
return nil, gqlutils.InvalidValidationErrors(ctx, validationErrors)
|
|
}
|
|
r.logger.ErrorCtx(ctx, "cannot update measure", log.Error(err))
|
|
return nil, gqlutils.Internal(ctx)
|
|
}
|
|
|
|
return &types.UpdateMeasurePayload{
|
|
Measure: types.NewMeasure(measure),
|
|
}, nil
|
|
}
|
|
|
|
// ImportMeasure is the resolver for the importMeasure field.
|
|
func (r *mutationResolver) ImportMeasure(ctx context.Context, input types.ImportMeasureInput) (*types.ImportMeasurePayload, error) {
|
|
if err := r.authorize(ctx, input.OrganizationID, probo.ActionMeasureImport); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
prb := r.ProboService(ctx, input.OrganizationID.TenantID())
|
|
|
|
var req probo.ImportMeasureRequest
|
|
if err := json.NewDecoder(input.File.File).Decode(&req.Measures); err != nil {
|
|
r.logger.ErrorCtx(ctx, "cannot unmarshal measure", log.Error(err))
|
|
return nil, gqlutils.Internal(ctx)
|
|
}
|
|
|
|
measures, err := prb.Measures.Import(ctx, input.OrganizationID, req)
|
|
if err != nil {
|
|
r.logger.ErrorCtx(ctx, "cannot import measure", log.Error(err))
|
|
return nil, gqlutils.Internal(ctx)
|
|
}
|
|
|
|
measureEdges := make([]*types.MeasureEdge, len(measures.Data))
|
|
for i, measure := range measures.Data {
|
|
measureEdges[i] = types.NewMeasureEdge(measure, coredata.MeasureOrderFieldCreatedAt)
|
|
}
|
|
|
|
return &types.ImportMeasurePayload{
|
|
MeasureEdges: measureEdges,
|
|
}, nil
|
|
}
|
|
|
|
// DeleteMeasure is the resolver for the deleteMeasure field.
|
|
func (r *mutationResolver) DeleteMeasure(ctx context.Context, input types.DeleteMeasureInput) (*types.DeleteMeasurePayload, error) {
|
|
if err := r.authorize(ctx, input.MeasureID, probo.ActionMeasureDelete); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
prb := r.ProboService(ctx, input.MeasureID.TenantID())
|
|
|
|
err := prb.Measures.Delete(ctx, input.MeasureID)
|
|
if err != nil {
|
|
r.logger.ErrorCtx(ctx, "cannot delete measure", log.Error(err))
|
|
return nil, gqlutils.Internal(ctx)
|
|
}
|
|
|
|
return &types.DeleteMeasurePayload{
|
|
DeletedMeasureID: input.MeasureID,
|
|
}, nil
|
|
}
|
|
|
|
// CreateMeasureDocumentMapping is the resolver for the createMeasureDocumentMapping field.
|
|
func (r *mutationResolver) CreateMeasureDocumentMapping(ctx context.Context, input types.CreateMeasureDocumentMappingInput) (*types.CreateMeasureDocumentMappingPayload, error) {
|
|
if err := r.authorize(ctx, input.MeasureID, probo.ActionMeasureDocumentMappingCreate); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
prb := r.ProboService(ctx, input.MeasureID.TenantID())
|
|
|
|
measure, document, err := prb.Measures.CreateDocumentMapping(ctx, input.MeasureID, input.DocumentID)
|
|
if err != nil {
|
|
if errors.Is(err, coredata.ErrResourceAlreadyExists) {
|
|
return nil, gqlutils.Conflict(ctx, err)
|
|
}
|
|
|
|
r.logger.ErrorCtx(ctx, "cannot create measure document mapping", log.Error(err))
|
|
return nil, gqlutils.Internal(ctx)
|
|
}
|
|
|
|
return &types.CreateMeasureDocumentMappingPayload{
|
|
MeasureEdge: types.NewMeasureEdge(measure, coredata.MeasureOrderFieldCreatedAt),
|
|
DocumentEdge: types.NewDocumentEdge(document, coredata.DocumentOrderFieldTitle),
|
|
}, nil
|
|
}
|
|
|
|
// DeleteMeasureDocumentMapping is the resolver for the deleteMeasureDocumentMapping field.
|
|
func (r *mutationResolver) DeleteMeasureDocumentMapping(ctx context.Context, input types.DeleteMeasureDocumentMappingInput) (*types.DeleteMeasureDocumentMappingPayload, error) {
|
|
if err := r.authorize(ctx, input.MeasureID, probo.ActionMeasureDocumentMappingDelete); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
prb := r.ProboService(ctx, input.MeasureID.TenantID())
|
|
|
|
measure, document, err := prb.Measures.DeleteDocumentMapping(ctx, input.MeasureID, input.DocumentID)
|
|
if err != nil {
|
|
r.logger.ErrorCtx(ctx, "cannot delete measure document mapping", log.Error(err))
|
|
return nil, gqlutils.Internal(ctx)
|
|
}
|
|
|
|
return &types.DeleteMeasureDocumentMappingPayload{
|
|
DeletedMeasureID: measure.ID,
|
|
DeletedDocumentID: document.ID,
|
|
}, nil
|
|
}
|
|
|
|
// Measure returns schema.MeasureResolver implementation.
|
|
func (r *Resolver) Measure() schema.MeasureResolver { return &measureResolver{r} }
|
|
|
|
// MeasureConnection returns schema.MeasureConnectionResolver implementation.
|
|
func (r *Resolver) MeasureConnection() schema.MeasureConnectionResolver {
|
|
return &measureConnectionResolver{r}
|
|
}
|
|
|
|
type measureResolver struct{ *Resolver }
|
|
type measureConnectionResolver struct{ *Resolver }
|