Replace the immediate per-document approval email and the manual "send signing notifications" action with a single debounced worker that batches pending requests per recipient and organization. The worker (go.gearno.de/kit/worker) polls on an interval (default 5m) and claims one (organization, recipient) group at a time, sending one consolidated signing email and/or one approval email per recipient/org that lists every document awaiting their signature or approval. The claim is a conditional UPDATE that doubles as concurrency-safe dedup, so several workers never email the same group twice. Each request is notified once it has been pending past the debounce delay (default 15m), then reminded at 1x, 2x and 3x the reminder interval (default 1 day) after the previous email, after which it stops. New last_notified_at and notification_count columns on signatures and approval decisions drive the debounce, the widening reminder cadence and the four-email cap. Email copy lists each document with its title, type and a deep link to the employee page. Removed the inline approval-on-publish email, the SendSigningNotifications service method/mutation/MCP tool, its IAM action, and the related console UI and n8n operation. Signed-off-by: Sacha Al Himdani <sacha@probo.com>
1705 lines
58 KiB
Go
1705 lines
58 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"
|
|
"encoding/base64"
|
|
"errors"
|
|
"fmt"
|
|
"net"
|
|
|
|
"github.com/vikstrous/dataloadgen"
|
|
"go.gearno.de/kit/log"
|
|
"go.probo.inc/probo/pkg/coredata"
|
|
"go.probo.inc/probo/pkg/gid"
|
|
"go.probo.inc/probo/pkg/iam"
|
|
"go.probo.inc/probo/pkg/page"
|
|
"go.probo.inc/probo/pkg/probo"
|
|
"go.probo.inc/probo/pkg/server/api/authn"
|
|
"go.probo.inc/probo/pkg/server/api/console/v1/dataloader"
|
|
"go.probo.inc/probo/pkg/server/api/console/v1/schema"
|
|
"go.probo.inc/probo/pkg/server/api/console/v1/types"
|
|
"go.probo.inc/probo/pkg/server/gqlutils"
|
|
"go.probo.inc/probo/pkg/validator"
|
|
)
|
|
|
|
// Organization is the resolver for the organization field.
|
|
func (r *documentResolver) Organization(ctx context.Context, obj *types.Document) (*types.Organization, error) {
|
|
if _, err := r.authorize(ctx, obj.ID, probo.ActionOrganizationGet); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
loaders := dataloader.FromContext(ctx)
|
|
|
|
organization, err := loaders.Organization.Load(ctx, obj.Organization.ID)
|
|
if err != nil {
|
|
if errors.Is(err, coredata.ErrResourceNotFound) || errors.Is(err, dataloadgen.ErrNotFound) {
|
|
return nil, gqlutils.NotFound(ctx, err)
|
|
}
|
|
|
|
r.logger.ErrorCtx(ctx, "cannot get organization", log.Error(err))
|
|
|
|
return nil, gqlutils.Internal(ctx)
|
|
}
|
|
|
|
return types.NewOrganization(organization), nil
|
|
}
|
|
|
|
// Versions is the resolver for the versions field.
|
|
func (r *documentResolver) Versions(ctx context.Context, obj *types.Document, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.DocumentVersionOrderBy, filter *types.DocumentVersionFilter) (*types.DocumentVersionConnection, error) {
|
|
scope, err := r.authorize(ctx, obj.ID, probo.ActionDocumentVersionList)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
pageOrderBy := page.OrderBy[coredata.DocumentVersionOrderField]{
|
|
Field: coredata.DocumentVersionOrderFieldCreatedAt,
|
|
Direction: page.OrderDirectionDesc,
|
|
}
|
|
|
|
if orderBy != nil {
|
|
pageOrderBy = page.OrderBy[coredata.DocumentVersionOrderField]{
|
|
Field: orderBy.Field,
|
|
Direction: orderBy.Direction,
|
|
}
|
|
}
|
|
|
|
cursor := types.NewCursor(first, after, last, before, pageOrderBy)
|
|
|
|
versionFilter := coredata.NewDocumentVersionFilter()
|
|
if filter != nil && len(filter.Statuses) > 0 {
|
|
versionFilter = versionFilter.WithStatuses(filter.Statuses...)
|
|
}
|
|
|
|
page, err := r.probo.Documents.ListVersions(ctx, scope, obj.ID, cursor, versionFilter)
|
|
if err != nil {
|
|
r.logger.ErrorCtx(ctx, "cannot list document versions", log.Error(err))
|
|
return nil, gqlutils.Internal(ctx)
|
|
}
|
|
|
|
return types.NewDocumentVersionConnection(page, r, obj.ID), nil
|
|
}
|
|
|
|
// Controls is the resolver for the controls field.
|
|
func (r *documentResolver) Controls(ctx context.Context, obj *types.Document, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.ControlOrderBy, filter *types.ControlFilter) (*types.ControlConnection, error) {
|
|
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.ListForDocumentID(ctx, scope, obj.ID, cursor, controlFilter)
|
|
if err != nil {
|
|
r.logger.ErrorCtx(ctx, "cannot list document controls", log.Error(err))
|
|
return nil, gqlutils.Internal(ctx)
|
|
}
|
|
|
|
return types.NewControlConnection(page, r, obj.ID, controlFilter), nil
|
|
}
|
|
|
|
// DefaultApprovers is the resolver for the defaultApprovers field.
|
|
func (r *documentResolver) DefaultApprovers(ctx context.Context, obj *types.Document) ([]*types.Profile, error) {
|
|
scope, err := r.authorize(ctx, obj.ID, probo.ActionDocumentGet)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
profiles, err := r.probo.Documents.GetDefaultApprovers(ctx, scope, obj.ID)
|
|
if err != nil {
|
|
r.logger.ErrorCtx(ctx, "cannot get default approvers", log.Error(err))
|
|
return nil, gqlutils.Internal(ctx)
|
|
}
|
|
|
|
result := make([]*types.Profile, len(profiles))
|
|
for i, p := range profiles {
|
|
result[i] = types.NewProfile(p)
|
|
}
|
|
|
|
return result, nil
|
|
}
|
|
|
|
// Permission is the resolver for the permission field.
|
|
func (r *documentResolver) Permission(ctx context.Context, obj *types.Document, action string) (bool, error) {
|
|
return r.Resolver.Permission(ctx, obj, action)
|
|
}
|
|
|
|
// TotalCount is the resolver for the totalCount field.
|
|
func (r *documentConnectionResolver) TotalCount(ctx context.Context, obj *types.DocumentConnection) (int, error) {
|
|
scope, err := r.authorize(ctx, obj.ParentID, probo.ActionDocumentList)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
switch obj.Resolver.(type) {
|
|
case *controlResolver:
|
|
count, err := r.probo.Documents.CountForControlID(ctx, scope, obj.ParentID, obj.Filters)
|
|
if err != nil {
|
|
r.logger.ErrorCtx(ctx, "cannot count controls", log.Error(err))
|
|
return 0, gqlutils.Internal(ctx)
|
|
}
|
|
|
|
return count, nil
|
|
case *organizationResolver:
|
|
count, err := r.probo.Documents.CountForOrganizationID(ctx, scope, obj.ParentID, obj.Filters)
|
|
if err != nil {
|
|
r.logger.ErrorCtx(ctx, "cannot count documents", log.Error(err))
|
|
return 0, gqlutils.Internal(ctx)
|
|
}
|
|
|
|
return count, nil
|
|
case *riskResolver:
|
|
count, err := r.probo.Documents.CountForRiskID(ctx, scope, obj.ParentID, obj.Filters)
|
|
if err != nil {
|
|
r.logger.ErrorCtx(ctx, "cannot count risks", log.Error(err))
|
|
return 0, gqlutils.Internal(ctx)
|
|
}
|
|
|
|
return count, nil
|
|
case *measureResolver:
|
|
count, err := r.probo.Documents.CountForMeasureID(ctx, scope, obj.ParentID, obj.Filters)
|
|
if err != nil {
|
|
r.logger.ErrorCtx(ctx, "cannot count documents", log.Error(err))
|
|
return 0, gqlutils.Internal(ctx)
|
|
}
|
|
|
|
return count, nil
|
|
}
|
|
|
|
r.logger.ErrorCtx(ctx, "unsupported resolver")
|
|
|
|
return 0, gqlutils.Internal(ctx)
|
|
}
|
|
|
|
// Document is the resolver for the document field.
|
|
func (r *documentVersionResolver) Document(ctx context.Context, obj *types.DocumentVersion) (*types.Document, error) {
|
|
if _, err := r.authorize(ctx, obj.ID, probo.ActionDocumentGet); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
loaders := dataloader.FromContext(ctx)
|
|
|
|
document, err := loaders.Document.Load(ctx, obj.Document.ID)
|
|
if err != nil {
|
|
if errors.Is(err, coredata.ErrResourceNotFound) || errors.Is(err, dataloadgen.ErrNotFound) {
|
|
return nil, gqlutils.NotFound(ctx, err)
|
|
}
|
|
|
|
r.logger.ErrorCtx(ctx, "cannot get document", log.Error(err))
|
|
|
|
return nil, gqlutils.Internal(ctx)
|
|
}
|
|
|
|
return types.NewDocument(document), nil
|
|
}
|
|
|
|
// Approvers is the resolver for the approvers field.
|
|
func (r *documentVersionResolver) Approvers(ctx context.Context, obj *types.DocumentVersion, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.ProfileOrderBy) (*types.ProfileConnection, error) {
|
|
scope, err := r.authorize(ctx, obj.ID, iam.ActionMembershipProfileList)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if gqlutils.OnlyTotalCountSelected(ctx) {
|
|
return &types.ProfileConnection{
|
|
Resolver: r,
|
|
ParentID: obj.ID,
|
|
}, nil
|
|
}
|
|
|
|
pageOrderBy := page.OrderBy[coredata.MembershipProfileOrderField]{
|
|
Field: coredata.MembershipProfileOrderFieldCreatedAt,
|
|
Direction: page.OrderDirectionDesc,
|
|
}
|
|
|
|
if orderBy != nil {
|
|
pageOrderBy.Field = coredata.MembershipProfileOrderField(orderBy.Field)
|
|
pageOrderBy.Direction = page.OrderDirection(orderBy.Direction)
|
|
}
|
|
|
|
c := types.NewCursor(first, after, last, before, pageOrderBy)
|
|
|
|
p, err := r.probo.Documents.ListVersionApprovers(ctx, scope, obj.ID, c)
|
|
if err != nil {
|
|
r.logger.ErrorCtx(ctx, "cannot list document version approvers", log.Error(err))
|
|
return nil, gqlutils.Internal(ctx)
|
|
}
|
|
|
|
return types.NewProfileConnection(p, r, obj.ID, nil), nil
|
|
}
|
|
|
|
// Signatures is the resolver for the signatures field.
|
|
func (r *documentVersionResolver) Signatures(ctx context.Context, obj *types.DocumentVersion, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.DocumentVersionSignatureOrder, filter *types.DocumentVersionSignatureFilter) (*types.DocumentVersionSignatureConnection, error) {
|
|
scope, err := r.authorize(ctx, obj.ID, probo.ActionDocumentVersionSignatureList)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
pageOrderBy := page.OrderBy[coredata.DocumentVersionSignatureOrderField]{
|
|
Field: coredata.DocumentVersionSignatureOrderFieldCreatedAt,
|
|
Direction: page.OrderDirectionDesc,
|
|
}
|
|
|
|
if orderBy != nil {
|
|
pageOrderBy = page.OrderBy[coredata.DocumentVersionSignatureOrderField]{
|
|
Field: orderBy.Field,
|
|
Direction: orderBy.Direction,
|
|
}
|
|
}
|
|
|
|
var (
|
|
signatureStates []coredata.DocumentVersionSignatureState
|
|
activeContract *bool
|
|
profileState *coredata.ProfileState
|
|
)
|
|
|
|
if filter != nil {
|
|
if filter.States != nil {
|
|
signatureStates = filter.States
|
|
}
|
|
|
|
if filter.ActiveContract != nil {
|
|
activeContract = filter.ActiveContract
|
|
}
|
|
|
|
if filter.State != nil {
|
|
profileState = filter.State
|
|
}
|
|
}
|
|
|
|
signatureFilter := coredata.NewDocumentVersionSignatureFilter(signatureStates, activeContract, profileState)
|
|
|
|
cursor := types.NewCursor(first, after, last, before, pageOrderBy)
|
|
|
|
page, err := r.probo.Documents.ListSignatures(ctx, scope, obj.ID, cursor, signatureFilter)
|
|
if err != nil {
|
|
r.logger.ErrorCtx(ctx, "cannot list document version signatures", log.Error(err))
|
|
return nil, gqlutils.Internal(ctx)
|
|
}
|
|
|
|
return types.NewDocumentVersionSignatureConnection(page, r, obj.ID, signatureFilter), nil
|
|
}
|
|
|
|
// ApprovalQuorums is the resolver for the approvalQuorums field.
|
|
func (r *documentVersionResolver) ApprovalQuorums(ctx context.Context, obj *types.DocumentVersion, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.DocumentVersionApprovalQuorumOrder) (*types.DocumentVersionApprovalQuorumConnection, error) {
|
|
scope, err := r.authorize(ctx, obj.ID, probo.ActionDocumentVersionApprovalList)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
pageOrderBy := page.OrderBy[coredata.DocumentVersionApprovalQuorumOrderField]{
|
|
Field: coredata.DocumentVersionApprovalQuorumOrderFieldCreatedAt,
|
|
Direction: page.OrderDirectionDesc,
|
|
}
|
|
|
|
if orderBy != nil {
|
|
pageOrderBy = page.OrderBy[coredata.DocumentVersionApprovalQuorumOrderField]{
|
|
Field: orderBy.Field,
|
|
Direction: orderBy.Direction,
|
|
}
|
|
}
|
|
|
|
cursor := types.NewCursor(first, after, last, before, pageOrderBy)
|
|
|
|
p, err := r.probo.DocumentApprovals.ListQuorums(ctx, scope, obj.ID, cursor)
|
|
if err != nil {
|
|
r.logger.ErrorCtx(ctx, "cannot list approval quorums", log.Error(err))
|
|
return nil, gqlutils.Internal(ctx)
|
|
}
|
|
|
|
return types.NewDocumentVersionApprovalQuorumConnection(p, r, obj.ID), nil
|
|
}
|
|
|
|
// Signed is the resolver for the signed field.
|
|
func (r *documentVersionResolver) Signed(ctx context.Context, obj *types.DocumentVersion) (bool, error) {
|
|
scope, err := r.authorize(ctx, obj.ID, probo.ActionDocumentVersionGet)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
|
|
identity := authn.IdentityFromContext(ctx)
|
|
|
|
signed, err := r.probo.Documents.IsVersionSignedByUserEmail(ctx, scope, obj.ID, identity.EmailAddress)
|
|
if err != nil {
|
|
r.logger.ErrorCtx(ctx, "cannot check if document version is signed", log.Error(err))
|
|
return false, gqlutils.Internal(ctx)
|
|
}
|
|
|
|
return signed, nil
|
|
}
|
|
|
|
// Permission is the resolver for the permission field.
|
|
func (r *documentVersionResolver) Permission(ctx context.Context, obj *types.DocumentVersion, action string) (bool, error) {
|
|
return r.Resolver.Permission(ctx, obj, action)
|
|
}
|
|
|
|
// Quorum is the resolver for the quorum field.
|
|
func (r *documentVersionApprovalDecisionResolver) Quorum(ctx context.Context, obj *types.DocumentVersionApprovalDecision) (*types.DocumentVersionApprovalQuorum, error) {
|
|
scope, err := r.authorize(ctx, obj.ID, probo.ActionDocumentVersionApprovalList)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
quorum, err := r.probo.DocumentApprovals.GetQuorum(ctx, scope, obj.Quorum.ID)
|
|
if err != nil {
|
|
if errors.Is(err, coredata.ErrResourceNotFound) {
|
|
return nil, gqlutils.NotFound(ctx, err)
|
|
}
|
|
|
|
r.logger.ErrorCtx(ctx, "cannot get approval quorum", log.Error(err))
|
|
|
|
return nil, gqlutils.Internal(ctx)
|
|
}
|
|
|
|
return types.NewDocumentVersionApprovalQuorum(quorum), nil
|
|
}
|
|
|
|
// DocumentVersion is the resolver for the documentVersion field.
|
|
func (r *documentVersionApprovalDecisionResolver) DocumentVersion(ctx context.Context, obj *types.DocumentVersionApprovalDecision) (*types.DocumentVersion, error) {
|
|
scope, err := r.authorize(ctx, obj.ID, probo.ActionDocumentVersionGet)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
quorum, err := r.probo.DocumentApprovals.GetQuorum(ctx, scope, obj.Quorum.ID)
|
|
if err != nil {
|
|
if errors.Is(err, coredata.ErrResourceNotFound) {
|
|
return nil, gqlutils.NotFound(ctx, err)
|
|
}
|
|
|
|
r.logger.ErrorCtx(ctx, "cannot get approval quorum", log.Error(err))
|
|
|
|
return nil, gqlutils.Internal(ctx)
|
|
}
|
|
|
|
documentVersion, err := r.probo.Documents.GetVersion(ctx, scope, quorum.VersionID)
|
|
if err != nil {
|
|
if errors.Is(err, coredata.ErrResourceNotFound) {
|
|
return nil, gqlutils.NotFound(ctx, err)
|
|
}
|
|
|
|
r.logger.ErrorCtx(ctx, "cannot get document version", log.Error(err))
|
|
|
|
return nil, gqlutils.Internal(ctx)
|
|
}
|
|
|
|
return types.NewDocumentVersion(documentVersion), nil
|
|
}
|
|
|
|
// Approver is the resolver for the approver field.
|
|
func (r *documentVersionApprovalDecisionResolver) Approver(ctx context.Context, obj *types.DocumentVersionApprovalDecision) (*types.Profile, error) {
|
|
if _, err := r.authorize(ctx, obj.ID, iam.ActionMembershipProfileGet); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
profile, err := r.iam.OrganizationService.GetProfile(ctx, obj.Approver.ID)
|
|
if err != nil {
|
|
if errors.Is(err, coredata.ErrResourceNotFound) {
|
|
return nil, gqlutils.NotFound(ctx, err)
|
|
}
|
|
|
|
r.logger.ErrorCtx(ctx, "cannot get approver profile", log.Error(err))
|
|
|
|
return nil, gqlutils.Internal(ctx)
|
|
}
|
|
|
|
return types.NewProfile(profile), nil
|
|
}
|
|
|
|
// ConsentText is the resolver for the consentText field.
|
|
func (r *documentVersionApprovalDecisionResolver) ConsentText(ctx context.Context, obj *types.DocumentVersionApprovalDecision) (string, error) {
|
|
return probo.DocumentApprovalConsentText, nil
|
|
}
|
|
|
|
// Permission is the resolver for the permission field.
|
|
func (r *documentVersionApprovalDecisionResolver) Permission(ctx context.Context, obj *types.DocumentVersionApprovalDecision, action string) (bool, error) {
|
|
// Approve and reject actions are only allowed for the viewer's own decision.
|
|
if action == probo.ActionDocumentVersionApprove || action == probo.ActionDocumentVersionReject {
|
|
identity := authn.IdentityFromContext(ctx)
|
|
|
|
profile, err := r.iam.OrganizationService.GetProfile(ctx, obj.Approver.ID)
|
|
if err != nil {
|
|
if errors.Is(err, coredata.ErrResourceNotFound) {
|
|
return false, nil
|
|
}
|
|
|
|
return false, gqlutils.Internal(ctx)
|
|
}
|
|
|
|
if profile.IdentityID != identity.ID {
|
|
return false, nil
|
|
}
|
|
}
|
|
|
|
return r.Resolver.Permission(ctx, obj, action)
|
|
}
|
|
|
|
// TotalCount is the resolver for the totalCount field.
|
|
func (r *documentVersionApprovalDecisionConnectionResolver) TotalCount(ctx context.Context, obj *types.DocumentVersionApprovalDecisionConnection) (int, error) {
|
|
if obj.ParentID.EntityType() != coredata.DocumentVersionApprovalQuorumEntityType {
|
|
return 0, nil
|
|
}
|
|
|
|
scope, err := r.authorize(ctx, obj.ParentID, probo.ActionDocumentVersionApprovalList)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
filter := coredata.NewDocumentVersionApprovalDecisionFilter(nil)
|
|
if obj.Filters != nil {
|
|
filter = obj.Filters
|
|
}
|
|
|
|
count, err := r.probo.DocumentApprovals.CountDecisions(ctx, scope, obj.ParentID, filter)
|
|
if err != nil {
|
|
r.logger.ErrorCtx(ctx, "cannot count approval decisions", log.Error(err))
|
|
return 0, gqlutils.Internal(ctx)
|
|
}
|
|
|
|
return count, nil
|
|
}
|
|
|
|
// DocumentVersion is the resolver for the documentVersion field.
|
|
func (r *documentVersionApprovalQuorumResolver) DocumentVersion(ctx context.Context, obj *types.DocumentVersionApprovalQuorum) (*types.DocumentVersion, error) {
|
|
scope, err := r.authorize(ctx, obj.ID, probo.ActionDocumentVersionGet)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
documentVersion, err := r.probo.Documents.GetVersion(ctx, scope, obj.DocumentVersion.ID)
|
|
if err != nil {
|
|
if errors.Is(err, coredata.ErrResourceNotFound) {
|
|
return nil, gqlutils.NotFound(ctx, err)
|
|
}
|
|
|
|
r.logger.ErrorCtx(ctx, "cannot get document version", log.Error(err))
|
|
|
|
return nil, gqlutils.Internal(ctx)
|
|
}
|
|
|
|
return types.NewDocumentVersion(documentVersion), nil
|
|
}
|
|
|
|
// Decisions is the resolver for the decisions field.
|
|
func (r *documentVersionApprovalQuorumResolver) Decisions(ctx context.Context, obj *types.DocumentVersionApprovalQuorum, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.DocumentVersionApprovalDecisionOrder, filter *types.DocumentVersionApprovalDecisionFilter) (*types.DocumentVersionApprovalDecisionConnection, error) {
|
|
scope, err := r.authorize(ctx, obj.ID, probo.ActionDocumentVersionApprovalList)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
pageOrderBy := page.OrderBy[coredata.DocumentVersionApprovalDecisionOrderField]{
|
|
Field: coredata.DocumentVersionApprovalDecisionOrderFieldCreatedAt,
|
|
Direction: page.OrderDirectionDesc,
|
|
}
|
|
|
|
if orderBy != nil {
|
|
pageOrderBy = page.OrderBy[coredata.DocumentVersionApprovalDecisionOrderField]{
|
|
Field: orderBy.Field,
|
|
Direction: orderBy.Direction,
|
|
}
|
|
}
|
|
|
|
var approvalStates []coredata.DocumentVersionApprovalDecisionState
|
|
if filter != nil && filter.States != nil {
|
|
approvalStates = filter.States
|
|
}
|
|
|
|
approvalFilter := coredata.NewDocumentVersionApprovalDecisionFilter(approvalStates)
|
|
|
|
cursor := types.NewCursor(first, after, last, before, pageOrderBy)
|
|
|
|
p, err := r.probo.DocumentApprovals.ListDecisions(ctx, scope, obj.ID, cursor, approvalFilter)
|
|
if err != nil {
|
|
r.logger.ErrorCtx(ctx, "cannot list approval decisions", log.Error(err))
|
|
return nil, gqlutils.Internal(ctx)
|
|
}
|
|
|
|
return types.NewDocumentVersionApprovalDecisionConnection(p, r, obj.ID, approvalFilter), nil
|
|
}
|
|
|
|
// Permission is the resolver for the permission field.
|
|
func (r *documentVersionApprovalQuorumResolver) Permission(ctx context.Context, obj *types.DocumentVersionApprovalQuorum, action string) (bool, error) {
|
|
return r.Resolver.Permission(ctx, obj, action)
|
|
}
|
|
|
|
// TotalCount is the resolver for the totalCount field.
|
|
func (r *documentVersionApprovalQuorumConnectionResolver) TotalCount(ctx context.Context, obj *types.DocumentVersionApprovalQuorumConnection) (int, error) {
|
|
scope, err := r.authorize(ctx, obj.ParentID, probo.ActionDocumentVersionApprovalList)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
count, err := r.probo.DocumentApprovals.CountQuorums(ctx, scope, obj.ParentID)
|
|
if err != nil {
|
|
r.logger.ErrorCtx(ctx, "cannot count approval quorums", log.Error(err))
|
|
return 0, gqlutils.Internal(ctx)
|
|
}
|
|
|
|
return count, nil
|
|
}
|
|
|
|
// TotalCount is the resolver for the totalCount field.
|
|
func (r *documentVersionConnectionResolver) TotalCount(ctx context.Context, obj *types.DocumentVersionConnection) (int, error) {
|
|
scope, err := r.authorize(ctx, obj.ParentID, probo.ActionDocumentVersionList)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
switch obj.Resolver.(type) {
|
|
case *documentResolver:
|
|
filter := &coredata.DocumentVersionFilter{}
|
|
if obj.Filters != nil {
|
|
filter = obj.Filters
|
|
}
|
|
|
|
count, err := r.probo.Documents.CountVersionsForDocumentID(ctx, scope, obj.ParentID, filter)
|
|
if err != nil {
|
|
r.logger.ErrorCtx(ctx, "cannot count document versions", log.Error(err))
|
|
return 0, gqlutils.Internal(ctx)
|
|
}
|
|
|
|
return count, nil
|
|
}
|
|
|
|
r.logger.ErrorCtx(ctx, "unsupported resolver")
|
|
|
|
return 0, gqlutils.Internal(ctx)
|
|
}
|
|
|
|
// DocumentVersion is the resolver for the documentVersion field.
|
|
func (r *documentVersionSignatureResolver) DocumentVersion(ctx context.Context, obj *types.DocumentVersionSignature) (*types.DocumentVersion, error) {
|
|
scope, err := r.authorize(ctx, obj.ID, probo.ActionDocumentVersionGet)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
documentVersion, err := r.probo.Documents.GetVersion(ctx, scope, obj.DocumentVersion.ID)
|
|
if err != nil {
|
|
if errors.Is(err, coredata.ErrResourceNotFound) {
|
|
return nil, gqlutils.NotFound(ctx, err)
|
|
}
|
|
|
|
r.logger.ErrorCtx(ctx, "cannot get document version", log.Error(err))
|
|
|
|
return nil, gqlutils.Internal(ctx)
|
|
}
|
|
|
|
return types.NewDocumentVersion(documentVersion), nil
|
|
}
|
|
|
|
// SignedBy is the resolver for the signedBy field.
|
|
func (r *documentVersionSignatureResolver) SignedBy(ctx context.Context, obj *types.DocumentVersionSignature) (*types.Profile, error) {
|
|
if _, err := r.authorize(ctx, obj.ID, iam.ActionMembershipProfileGet); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
loaders := dataloader.FromContext(ctx)
|
|
|
|
signatory, err := loaders.Profile.Load(ctx, obj.SignedBy.ID)
|
|
if err != nil {
|
|
if errors.Is(err, coredata.ErrResourceNotFound) || errors.Is(err, dataloadgen.ErrNotFound) {
|
|
return nil, gqlutils.NotFound(ctx, err)
|
|
}
|
|
|
|
r.logger.ErrorCtx(ctx, "cannot get people", log.Error(err))
|
|
|
|
return nil, gqlutils.Internal(ctx)
|
|
}
|
|
|
|
return types.NewProfile(signatory), nil
|
|
}
|
|
|
|
// Permission is the resolver for the permission field.
|
|
func (r *documentVersionSignatureResolver) Permission(ctx context.Context, obj *types.DocumentVersionSignature, action string) (bool, error) {
|
|
return r.Resolver.Permission(ctx, obj, action)
|
|
}
|
|
|
|
// TotalCount is the resolver for the totalCount field.
|
|
func (r *documentVersionSignatureConnectionResolver) TotalCount(ctx context.Context, obj *types.DocumentVersionSignatureConnection) (int, error) {
|
|
scope, err := r.authorize(ctx, obj.ParentID, probo.ActionDocumentVersionSignatureList)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
switch obj.Resolver.(type) {
|
|
case *documentVersionResolver:
|
|
filter := &coredata.DocumentVersionSignatureFilter{}
|
|
if obj.Filters != nil {
|
|
filter = obj.Filters
|
|
}
|
|
|
|
count, err := r.probo.Documents.CountSignaturesForVersionID(ctx, scope, obj.ParentID, filter)
|
|
if err != nil {
|
|
r.logger.ErrorCtx(ctx, "cannot count signatures", log.Error(err))
|
|
return 0, gqlutils.Internal(ctx)
|
|
}
|
|
|
|
return count, nil
|
|
}
|
|
|
|
r.logger.ErrorCtx(ctx, "unsupported resolver")
|
|
|
|
return 0, gqlutils.Internal(ctx)
|
|
}
|
|
|
|
// Signed is the resolver for the signed field.
|
|
func (r *employeeDocumentResolver) Signed(ctx context.Context, obj *types.EmployeeDocument) (*bool, error) {
|
|
scope, err := r.authorize(ctx, obj.ID, probo.ActionEmployeeDocumentGet)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
identity := authn.IdentityFromContext(ctx)
|
|
|
|
signed, err := r.probo.Documents.IsSigned(ctx, scope, obj.ID, identity.EmailAddress)
|
|
if err != nil {
|
|
if errors.Is(err, coredata.ErrResourceNotFound) {
|
|
return nil, nil
|
|
}
|
|
|
|
r.logger.ErrorCtx(ctx, "cannot check if document is signed", log.Error(err))
|
|
|
|
return nil, gqlutils.Internal(ctx)
|
|
}
|
|
|
|
return &signed, nil
|
|
}
|
|
|
|
// ApprovalState is the resolver for the approvalState field.
|
|
func (r *employeeDocumentResolver) ApprovalState(ctx context.Context, obj *types.EmployeeDocument) (*coredata.DocumentVersionApprovalDecisionState, error) {
|
|
scope, err := r.authorize(ctx, obj.ID, probo.ActionEmployeeDocumentGet)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
identity := authn.IdentityFromContext(ctx)
|
|
|
|
state, err := r.probo.Documents.GetViewerApprovalState(ctx, scope, obj.ID, identity.ID)
|
|
if err != nil {
|
|
if errors.Is(err, coredata.ErrResourceNotFound) {
|
|
return nil, nil
|
|
}
|
|
|
|
r.logger.ErrorCtx(ctx, "cannot get viewer approval state", log.Error(err))
|
|
|
|
return nil, gqlutils.Internal(ctx)
|
|
}
|
|
|
|
return &state, nil
|
|
}
|
|
|
|
// Versions is the resolver for the versions field.
|
|
func (r *employeeDocumentResolver) Versions(ctx context.Context, obj *types.EmployeeDocument, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.DocumentVersionOrderBy) (*types.EmployeeDocumentVersionConnection, error) {
|
|
scope, err := r.authorize(ctx, obj.ID, probo.ActionEmployeeDocumentGet)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
pageOrderBy := page.OrderBy[coredata.DocumentVersionOrderField]{
|
|
Field: coredata.DocumentVersionOrderFieldCreatedAt,
|
|
Direction: page.OrderDirectionDesc,
|
|
}
|
|
|
|
if orderBy != nil {
|
|
pageOrderBy = page.OrderBy[coredata.DocumentVersionOrderField]{
|
|
Field: orderBy.Field,
|
|
Direction: orderBy.Direction,
|
|
}
|
|
}
|
|
|
|
cursor := types.NewCursor(first, after, last, before, pageOrderBy)
|
|
|
|
identity := authn.IdentityFromContext(ctx)
|
|
|
|
var filterMode coredata.EmployeeFilterMode
|
|
|
|
switch obj.FilterMode {
|
|
case types.EmployeeDocumentFilterModeSignature:
|
|
filterMode = coredata.EmployeeFilterModeSignature
|
|
case types.EmployeeDocumentFilterModeApproval:
|
|
filterMode = coredata.EmployeeFilterModeApproval
|
|
default:
|
|
r.logger.ErrorCtx(ctx, "unsupported employee document filter mode", log.String("filter_mode", string(obj.FilterMode)))
|
|
return nil, gqlutils.Internal(ctx)
|
|
}
|
|
|
|
versionFilter := coredata.NewDocumentVersionFilter().
|
|
WithEmployeeIdentityID(&identity.ID, filterMode)
|
|
|
|
versionsPage, err := r.probo.Documents.ListVersions(ctx, scope, obj.ID, cursor, versionFilter)
|
|
if err != nil {
|
|
r.logger.ErrorCtx(ctx, "cannot list employee document versions", log.Error(err))
|
|
return nil, gqlutils.Internal(ctx)
|
|
}
|
|
|
|
employeeVersions := make([]*types.EmployeeDocumentVersion, len(versionsPage.Data))
|
|
for i, v := range versionsPage.Data {
|
|
employeeVersions[i] = &types.EmployeeDocumentVersion{
|
|
ID: v.ID,
|
|
DocumentID: obj.ID,
|
|
OrganizationID: v.OrganizationID,
|
|
Major: v.Major,
|
|
Minor: v.Minor,
|
|
Status: v.Status,
|
|
Classification: v.Classification,
|
|
DocumentType: v.DocumentType,
|
|
PublishedAt: v.PublishedAt,
|
|
CreatedAt: v.CreatedAt,
|
|
UpdatedAt: v.UpdatedAt,
|
|
}
|
|
}
|
|
|
|
p := page.NewPage(employeeVersions, versionsPage.Cursor)
|
|
|
|
return types.NewEmployeeDocumentVersionConnection(p), nil
|
|
}
|
|
|
|
// Signed is the resolver for the signed field.
|
|
func (r *employeeDocumentVersionResolver) Signed(ctx context.Context, obj *types.EmployeeDocumentVersion) (bool, error) {
|
|
scope, err := r.authorize(ctx, obj.DocumentID, probo.ActionEmployeeDocumentGet)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
|
|
identity := authn.IdentityFromContext(ctx)
|
|
|
|
signed, err := r.probo.Documents.IsVersionSignedByUserEmail(ctx, scope, obj.ID, identity.EmailAddress)
|
|
if err != nil {
|
|
r.logger.ErrorCtx(ctx, "cannot check if version is signed", log.Error(err))
|
|
return false, gqlutils.Internal(ctx)
|
|
}
|
|
|
|
return signed, nil
|
|
}
|
|
|
|
// ConsentText is the resolver for the consentText field.
|
|
func (r *employeeDocumentVersionResolver) ConsentText(ctx context.Context, obj *types.EmployeeDocumentVersion) (string, error) {
|
|
return probo.DocumentSignatureConsentText, nil
|
|
}
|
|
|
|
// ApprovalDecision is the resolver for the approvalDecision field.
|
|
func (r *employeeDocumentVersionResolver) ApprovalDecision(ctx context.Context, obj *types.EmployeeDocumentVersion) (*types.DocumentVersionApprovalDecision, error) {
|
|
scope, err := r.authorize(ctx, obj.DocumentID, probo.ActionEmployeeDocumentGet)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
identity := authn.IdentityFromContext(ctx)
|
|
|
|
decision, err := r.probo.DocumentApprovals.GetViewerDecision(ctx, scope, obj.ID, identity.ID)
|
|
if err != nil {
|
|
if errors.Is(err, coredata.ErrResourceNotFound) {
|
|
return nil, nil
|
|
}
|
|
|
|
r.logger.ErrorCtx(ctx, "cannot get viewer approval decision", log.Error(err))
|
|
|
|
return nil, gqlutils.Internal(ctx)
|
|
}
|
|
|
|
return types.NewDocumentVersionApprovalDecision(decision), nil
|
|
}
|
|
|
|
// CreateDocument is the resolver for the createDocument field.
|
|
func (r *mutationResolver) CreateDocument(ctx context.Context, input types.CreateDocumentInput) (*types.CreateDocumentPayload, error) {
|
|
scope, err := r.authorize(ctx, input.OrganizationID, probo.ActionDocumentCreate)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var content string
|
|
if input.Content != nil {
|
|
content = *input.Content
|
|
}
|
|
|
|
document, documentVersion, err := r.probo.Documents.Create(
|
|
ctx, scope,
|
|
probo.CreateDocumentRequest{
|
|
OrganizationID: input.OrganizationID,
|
|
Title: input.Title,
|
|
Content: content,
|
|
Classification: input.Classification,
|
|
DocumentType: input.DocumentType,
|
|
TrustCenterVisibility: input.TrustCenterVisibility,
|
|
DefaultApproverIDs: input.DefaultApproverIds,
|
|
},
|
|
)
|
|
if err != nil {
|
|
if errors.Is(err, coredata.ErrResourceAlreadyExists) {
|
|
return nil, gqlutils.Conflict(ctx, err)
|
|
}
|
|
|
|
if validationErrors, ok := errors.AsType[validator.ValidationErrors](err); ok {
|
|
return nil, gqlutils.InvalidValidationErrors(ctx, validationErrors)
|
|
}
|
|
|
|
r.logger.ErrorCtx(ctx, "cannot create document", log.Error(err))
|
|
|
|
return nil, gqlutils.Internal(ctx)
|
|
}
|
|
|
|
return &types.CreateDocumentPayload{
|
|
DocumentEdge: types.NewDocumentEdge(document, coredata.DocumentOrderFieldTitle),
|
|
DocumentVersionEdge: types.NewDocumentVersionEdge(documentVersion, coredata.DocumentVersionOrderFieldCreatedAt),
|
|
}, nil
|
|
}
|
|
|
|
// UpdateDocument is the resolver for the updateDocument field.
|
|
func (r *mutationResolver) UpdateDocument(ctx context.Context, input types.UpdateDocumentInput) (*types.UpdateDocumentPayload, error) {
|
|
scope, err := r.authorize(ctx, input.ID, probo.ActionDocumentUpdate)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var defaultApproverIDs *[]gid.GID
|
|
if input.DefaultApproverIds != nil {
|
|
defaultApproverIDs = &input.DefaultApproverIds
|
|
}
|
|
|
|
document, documentVersion, draftCreated, err := r.probo.Documents.Update(
|
|
ctx, scope,
|
|
probo.UpdateDocumentRequest{
|
|
DocumentID: input.ID,
|
|
Title: input.Title,
|
|
Content: input.Content,
|
|
Classification: input.Classification,
|
|
DocumentType: input.DocumentType,
|
|
TrustCenterVisibility: input.TrustCenterVisibility,
|
|
DefaultApproverIDs: defaultApproverIDs,
|
|
},
|
|
)
|
|
if err != nil {
|
|
if errors.Is(err, coredata.ErrResourceNotFound) {
|
|
return nil, gqlutils.NotFound(ctx, err)
|
|
}
|
|
|
|
if errArchived, ok := errors.AsType[*probo.ErrDocumentArchived](err); ok {
|
|
return nil, gqlutils.Conflict(ctx, errArchived)
|
|
}
|
|
|
|
if errGenerated, ok := errors.AsType[*probo.ErrDocumentVersionGenerated](err); ok {
|
|
return nil, gqlutils.Conflict(ctx, errGenerated)
|
|
}
|
|
|
|
if validationErrors, ok := errors.AsType[validator.ValidationErrors](err); ok {
|
|
return nil, gqlutils.InvalidValidationErrors(ctx, validationErrors)
|
|
}
|
|
|
|
r.logger.ErrorCtx(ctx, "cannot update document", log.Error(err))
|
|
|
|
return nil, gqlutils.Internal(ctx)
|
|
}
|
|
|
|
payload := &types.UpdateDocumentPayload{
|
|
Document: types.NewDocument(document),
|
|
}
|
|
|
|
if documentVersion != nil {
|
|
payload.DocumentVersion = types.NewDocumentVersion(documentVersion)
|
|
}
|
|
|
|
if draftCreated {
|
|
payload.DocumentVersionEdge = types.NewDocumentVersionEdge(
|
|
documentVersion,
|
|
coredata.DocumentVersionOrderFieldCreatedAt,
|
|
)
|
|
}
|
|
|
|
return payload, nil
|
|
}
|
|
|
|
// DeleteDocumentDraft is the resolver for the deleteDocumentDraft field.
|
|
func (r *mutationResolver) DeleteDocumentDraft(ctx context.Context, input types.DeleteDocumentDraftInput) (*types.DeleteDocumentDraftPayload, error) {
|
|
scope, err := r.authorize(ctx, input.DocumentID, probo.ActionDocumentDeleteDraft)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
document, err := r.probo.Documents.DeleteDraft(ctx, scope, input.DocumentID)
|
|
if err != nil {
|
|
if errors.Is(err, coredata.ErrResourceNotFound) {
|
|
return nil, gqlutils.NotFound(ctx, err)
|
|
}
|
|
|
|
if errNotDeletable, ok := errors.AsType[*probo.ErrDocumentDraftNotDeletable](err); ok {
|
|
return nil, gqlutils.Conflict(ctx, errNotDeletable)
|
|
}
|
|
|
|
if errArchived, ok := errors.AsType[*probo.ErrDocumentArchived](err); ok {
|
|
return nil, gqlutils.Conflict(ctx, errArchived)
|
|
}
|
|
|
|
r.logger.ErrorCtx(ctx, "cannot delete document draft", log.Error(err))
|
|
|
|
return nil, gqlutils.Internal(ctx)
|
|
}
|
|
|
|
return &types.DeleteDocumentDraftPayload{
|
|
Document: types.NewDocument(document),
|
|
}, nil
|
|
}
|
|
|
|
// ArchiveDocument is the resolver for the archiveDocument field.
|
|
func (r *mutationResolver) ArchiveDocument(ctx context.Context, input types.ArchiveDocumentInput) (*types.ArchiveDocumentPayload, error) {
|
|
scope, err := r.authorize(ctx, input.DocumentID, probo.ActionDocumentArchive)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
document, err := r.probo.Documents.Archive(ctx, scope, input.DocumentID)
|
|
if err != nil {
|
|
if errArchived, ok := errors.AsType[*probo.ErrDocumentArchived](err); ok {
|
|
return nil, gqlutils.Conflict(ctx, errArchived)
|
|
}
|
|
|
|
r.logger.ErrorCtx(ctx, "cannot archive document", log.Error(err))
|
|
|
|
return nil, gqlutils.Internal(ctx)
|
|
}
|
|
|
|
return &types.ArchiveDocumentPayload{
|
|
Document: types.NewDocument(document),
|
|
}, nil
|
|
}
|
|
|
|
// UnarchiveDocument is the resolver for the unarchiveDocument field.
|
|
func (r *mutationResolver) UnarchiveDocument(ctx context.Context, input types.UnarchiveDocumentInput) (*types.UnarchiveDocumentPayload, error) {
|
|
scope, err := r.authorize(ctx, input.DocumentID, probo.ActionDocumentUnarchive)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
document, err := r.probo.Documents.Unarchive(ctx, scope, input.DocumentID)
|
|
if err != nil {
|
|
if errNotArchived, ok := errors.AsType[*probo.ErrDocumentNotArchived](err); ok {
|
|
return nil, gqlutils.Conflict(ctx, errNotArchived)
|
|
}
|
|
|
|
r.logger.ErrorCtx(ctx, "cannot unarchive document", log.Error(err))
|
|
|
|
return nil, gqlutils.Internal(ctx)
|
|
}
|
|
|
|
return &types.UnarchiveDocumentPayload{
|
|
Document: types.NewDocument(document),
|
|
}, nil
|
|
}
|
|
|
|
// DeleteDocument is the resolver for the deleteDocument field.
|
|
func (r *mutationResolver) DeleteDocument(ctx context.Context, input types.DeleteDocumentInput) (*types.DeleteDocumentPayload, error) {
|
|
scope, err := r.authorize(ctx, input.DocumentID, probo.ActionDocumentDelete)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if err := r.probo.Documents.SoftDelete(ctx, scope, input.DocumentID); err != nil {
|
|
r.logger.ErrorCtx(ctx, "cannot soft delete document", log.Error(err))
|
|
return nil, gqlutils.Internal(ctx)
|
|
}
|
|
|
|
return &types.DeleteDocumentPayload{
|
|
DeletedDocumentID: input.DocumentID,
|
|
}, nil
|
|
}
|
|
|
|
// PublishDocument is the resolver for the publishDocument field.
|
|
func (r *mutationResolver) PublishDocument(ctx context.Context, input types.PublishDocumentInput) (*types.PublishDocumentPayload, error) {
|
|
action := probo.ActionDocumentVersionPublish
|
|
if !input.Minor && len(input.ApproverIds) > 0 {
|
|
action = probo.ActionDocumentVersionRequestApproval
|
|
}
|
|
|
|
scope, err := r.authorize(ctx, input.DocumentID, action)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
result, err := r.probo.Documents.PublishVersion(ctx, scope, probo.PublishDocumentRequest{
|
|
DocumentID: input.DocumentID,
|
|
Minor: input.Minor,
|
|
ApproverIDs: input.ApproverIds,
|
|
Changelog: input.Changelog,
|
|
})
|
|
if err != nil {
|
|
if errArchived, ok := errors.AsType[*probo.ErrDocumentArchived](err); ok {
|
|
return nil, gqlutils.Conflict(ctx, errArchived)
|
|
}
|
|
|
|
if errNotDraft, ok := errors.AsType[*probo.ErrDocumentVersionNotDraft](err); ok {
|
|
return nil, gqlutils.Invalid(ctx, errNotDraft)
|
|
}
|
|
|
|
if errPending, ok := errors.AsType[*probo.ErrDocumentVersionPendingApproval](err); ok {
|
|
return nil, gqlutils.Conflict(ctx, errPending)
|
|
}
|
|
|
|
if errContractEnded, ok := errors.AsType[*probo.ErrProfileContractEnded](err); ok {
|
|
return nil, gqlutils.Conflict(ctx, errContractEnded)
|
|
}
|
|
|
|
if validationErrors, ok := errors.AsType[validator.ValidationErrors](err); ok {
|
|
return nil, gqlutils.InvalidValidationErrors(ctx, validationErrors)
|
|
}
|
|
|
|
r.logger.ErrorCtx(ctx, "cannot publish document", log.Error(err))
|
|
|
|
return nil, gqlutils.Internal(ctx)
|
|
}
|
|
|
|
payload := &types.PublishDocumentPayload{
|
|
Document: types.NewDocument(result.Document),
|
|
DocumentVersion: types.NewDocumentVersion(result.Version),
|
|
}
|
|
|
|
if result.Quorum != nil {
|
|
payload.ApprovalQuorum = types.NewDocumentVersionApprovalQuorum(result.Quorum)
|
|
}
|
|
|
|
return payload, nil
|
|
}
|
|
|
|
// BulkPublishDocuments is the resolver for the bulkPublishDocuments field.
|
|
func (r *mutationResolver) BulkPublishDocuments(ctx context.Context, input types.BulkPublishDocumentsInput) (*types.BulkPublishDocumentsPayload, error) {
|
|
if len(input.DocumentIds) == 0 {
|
|
return &types.BulkPublishDocumentsPayload{
|
|
DocumentVersions: []*types.DocumentVersion{},
|
|
Documents: []*types.Document{},
|
|
}, nil
|
|
}
|
|
|
|
for _, documentID := range input.DocumentIds {
|
|
if _, err := r.authorize(ctx, documentID, probo.ActionDocumentVersionPublish); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
scope := coredata.NewScopeFromObjectID(input.DocumentIds[0])
|
|
|
|
versions, documents, err := r.probo.DocumentApprovals.BulkPublishVersions(ctx, scope, probo.BulkPublishVersionsRequest{
|
|
DocumentIDs: input.DocumentIds,
|
|
Minor: input.Minor,
|
|
Changelog: input.Changelog,
|
|
})
|
|
if err != nil {
|
|
if errors.Is(err, coredata.ErrResourceNotFound) {
|
|
return nil, gqlutils.NotFound(ctx, err)
|
|
}
|
|
|
|
if errArchived, ok := errors.AsType[*probo.ErrDocumentArchived](err); ok {
|
|
return nil, gqlutils.Conflict(ctx, errArchived)
|
|
}
|
|
|
|
if errNotDraft, ok := errors.AsType[*probo.ErrDocumentVersionNotDraft](err); ok {
|
|
return nil, gqlutils.Invalid(ctx, errNotDraft)
|
|
}
|
|
|
|
r.logger.ErrorCtx(ctx, "cannot bulk publish documents", log.Error(err))
|
|
|
|
return nil, gqlutils.Internal(ctx)
|
|
}
|
|
|
|
typesVersions := make([]*types.DocumentVersion, len(versions))
|
|
for i, v := range versions {
|
|
typesVersions[i] = types.NewDocumentVersion(v)
|
|
}
|
|
|
|
typesDocuments := make([]*types.Document, len(documents))
|
|
for i, d := range documents {
|
|
typesDocuments[i] = types.NewDocument(d)
|
|
}
|
|
|
|
return &types.BulkPublishDocumentsPayload{
|
|
DocumentVersions: typesVersions,
|
|
Documents: typesDocuments,
|
|
}, nil
|
|
}
|
|
|
|
// VoidDocumentVersionApproval is the resolver for the voidDocumentVersionApproval field.
|
|
func (r *mutationResolver) VoidDocumentVersionApproval(ctx context.Context, input types.VoidDocumentVersionApprovalInput) (*types.VoidDocumentVersionApprovalPayload, error) {
|
|
scope, err := r.authorize(ctx, input.DocumentVersionID, probo.ActionDocumentVersionVoidApproval)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
quorum, documentVersion, err := r.probo.DocumentApprovals.VoidApproval(ctx, scope, input.DocumentVersionID)
|
|
if err != nil {
|
|
if errors.Is(err, coredata.ErrResourceNotFound) {
|
|
return nil, gqlutils.NotFound(ctx, err)
|
|
}
|
|
|
|
if errArchived, ok := errors.AsType[*probo.ErrDocumentArchived](err); ok {
|
|
return nil, gqlutils.Conflict(ctx, errArchived)
|
|
}
|
|
|
|
if errNotPending, ok := errors.AsType[*probo.ErrDocumentVersionNotPendingApproval](err); ok {
|
|
return nil, gqlutils.Conflict(ctx, errNotPending)
|
|
}
|
|
|
|
r.logger.ErrorCtx(ctx, "cannot void document version approval", log.Error(err))
|
|
|
|
return nil, gqlutils.Internal(ctx)
|
|
}
|
|
|
|
return &types.VoidDocumentVersionApprovalPayload{
|
|
ApprovalQuorum: types.NewDocumentVersionApprovalQuorum(quorum),
|
|
DocumentVersion: types.NewDocumentVersion(documentVersion),
|
|
}, nil
|
|
}
|
|
|
|
// BulkDeleteDocuments is the resolver for the bulkDeleteDocuments field.
|
|
func (r *mutationResolver) BulkDeleteDocuments(ctx context.Context, input types.BulkDeleteDocumentsInput) (*types.BulkDeleteDocumentsPayload, error) {
|
|
if len(input.DocumentIds) == 0 {
|
|
return &types.BulkDeleteDocumentsPayload{
|
|
DeletedDocumentIds: []gid.GID{},
|
|
}, nil
|
|
}
|
|
|
|
for _, documentID := range input.DocumentIds {
|
|
if _, err := r.authorize(ctx, documentID, probo.ActionDocumentDelete); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
scope := coredata.NewScopeFromObjectID(input.DocumentIds[0])
|
|
if err := r.probo.Documents.BulkSoftDelete(ctx, scope, input.DocumentIds); err != nil {
|
|
r.logger.ErrorCtx(ctx, "cannot bulk delete documents", log.Error(err))
|
|
return nil, gqlutils.Internal(ctx)
|
|
}
|
|
|
|
return &types.BulkDeleteDocumentsPayload{
|
|
DeletedDocumentIds: input.DocumentIds,
|
|
}, nil
|
|
}
|
|
|
|
// BulkArchiveDocuments is the resolver for the bulkArchiveDocuments field.
|
|
func (r *mutationResolver) BulkArchiveDocuments(ctx context.Context, input types.BulkArchiveDocumentsInput) (*types.BulkArchiveDocumentsPayload, error) {
|
|
if len(input.DocumentIds) == 0 {
|
|
return &types.BulkArchiveDocumentsPayload{
|
|
Documents: []*types.Document{},
|
|
}, nil
|
|
}
|
|
|
|
for _, documentID := range input.DocumentIds {
|
|
if _, err := r.authorize(ctx, documentID, probo.ActionDocumentArchive); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
scope := coredata.NewScopeFromObjectID(input.DocumentIds[0])
|
|
if err := r.probo.Documents.BulkArchive(ctx, scope, input.DocumentIds); err != nil {
|
|
r.logger.ErrorCtx(ctx, "cannot bulk archive documents", log.Error(err))
|
|
return nil, gqlutils.Internal(ctx)
|
|
}
|
|
|
|
return &types.BulkArchiveDocumentsPayload{
|
|
Documents: []*types.Document{},
|
|
}, nil
|
|
}
|
|
|
|
// BulkUnarchiveDocuments is the resolver for the bulkUnarchiveDocuments field.
|
|
func (r *mutationResolver) BulkUnarchiveDocuments(ctx context.Context, input types.BulkUnarchiveDocumentsInput) (*types.BulkUnarchiveDocumentsPayload, error) {
|
|
if len(input.DocumentIds) == 0 {
|
|
return &types.BulkUnarchiveDocumentsPayload{
|
|
Documents: []*types.Document{},
|
|
}, nil
|
|
}
|
|
|
|
for _, documentID := range input.DocumentIds {
|
|
if _, err := r.authorize(ctx, documentID, probo.ActionDocumentUnarchive); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
scope := coredata.NewScopeFromObjectID(input.DocumentIds[0])
|
|
if err := r.probo.Documents.BulkUnarchive(ctx, scope, input.DocumentIds); err != nil {
|
|
r.logger.ErrorCtx(ctx, "cannot bulk unarchive documents", log.Error(err))
|
|
return nil, gqlutils.Internal(ctx)
|
|
}
|
|
|
|
return &types.BulkUnarchiveDocumentsPayload{
|
|
Documents: []*types.Document{},
|
|
}, nil
|
|
}
|
|
|
|
// BulkExportDocuments is the resolver for the bulkExportDocuments field.
|
|
func (r *mutationResolver) BulkExportDocuments(ctx context.Context, input types.BulkExportDocumentsInput) (*types.BulkExportDocumentsPayload, error) {
|
|
if len(input.DocumentIds) == 0 {
|
|
r.logger.ErrorCtx(ctx, "no document ids provided")
|
|
return nil, gqlutils.Internal(ctx)
|
|
}
|
|
|
|
// TODO have a way to batch authorize for resources
|
|
for _, documentID := range input.DocumentIds {
|
|
if _, err := r.authorize(ctx, documentID, probo.ActionDocumentVersionExport); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
scope := coredata.NewScopeFromObjectID(input.DocumentIds[0])
|
|
identity := authn.IdentityFromContext(ctx)
|
|
|
|
options := probo.ExportPDFOptions{
|
|
WithWatermark: input.WithWatermark,
|
|
WithSignatures: input.WithSignatures,
|
|
WatermarkEmail: input.WatermarkEmail,
|
|
}
|
|
|
|
documentExport, exportErr := r.probo.Documents.RequestExport(ctx, scope, input.DocumentIds, identity.EmailAddress, identity.FullName, options)
|
|
if exportErr != nil {
|
|
r.logger.ErrorCtx(ctx, "cannot request document export", log.Error(exportErr))
|
|
return nil, gqlutils.Internal(ctx)
|
|
}
|
|
|
|
return &types.BulkExportDocumentsPayload{
|
|
ExportJobID: documentExport.ID,
|
|
}, nil
|
|
}
|
|
|
|
// GenerateDocumentChangelog is the resolver for the generateDocumentChangelog field.
|
|
func (r *mutationResolver) GenerateDocumentChangelog(ctx context.Context, input types.GenerateDocumentChangelogInput) (*types.GenerateDocumentChangelogPayload, error) {
|
|
scope, err := r.authorize(ctx, input.DocumentID, probo.ActionDocumentChangelogGenerate)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
changelog, err := r.probo.Documents.GenerateChangelog(ctx, scope, input.DocumentID)
|
|
if err != nil {
|
|
if errArchived, ok := errors.AsType[*probo.ErrDocumentArchived](err); ok {
|
|
return nil, gqlutils.Conflict(ctx, errArchived)
|
|
}
|
|
|
|
r.logger.ErrorCtx(ctx, "cannot generate document changelog", log.Error(err))
|
|
|
|
return nil, gqlutils.Internal(ctx)
|
|
}
|
|
|
|
return &types.GenerateDocumentChangelogPayload{
|
|
Changelog: *changelog,
|
|
}, nil
|
|
}
|
|
|
|
// RequestSignature is the resolver for the requestSignature field.
|
|
func (r *mutationResolver) RequestSignature(ctx context.Context, input types.RequestSignatureInput) (*types.RequestSignaturePayload, error) {
|
|
scope, err := r.authorize(ctx, input.DocumentVersionID, probo.ActionDocumentVersionSignatureRequest)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
documentVersionSignature, err := r.probo.Documents.RequestSignature(
|
|
ctx, scope,
|
|
probo.RequestSignatureRequest{
|
|
DocumentVersionID: input.DocumentVersionID,
|
|
Signatory: input.SignatoryID,
|
|
},
|
|
)
|
|
if err != nil {
|
|
if errors.Is(err, coredata.ErrResourceNotFound) {
|
|
return nil, gqlutils.NotFound(ctx, err)
|
|
}
|
|
|
|
if errArchived, ok := errors.AsType[*probo.ErrDocumentArchived](err); ok {
|
|
return nil, gqlutils.Conflict(ctx, errArchived)
|
|
}
|
|
|
|
if errNotPublished, ok := errors.AsType[*probo.ErrDocumentVersionNotPublished](err); ok {
|
|
return nil, gqlutils.Conflict(ctx, errNotPublished)
|
|
}
|
|
|
|
if errContractEnded, ok := errors.AsType[*probo.ErrProfileContractEnded](err); ok {
|
|
return nil, gqlutils.Conflict(ctx, errContractEnded)
|
|
}
|
|
|
|
r.logger.ErrorCtx(ctx, "cannot request signature", log.Error(err))
|
|
|
|
return nil, gqlutils.Internal(ctx)
|
|
}
|
|
|
|
return &types.RequestSignaturePayload{
|
|
DocumentVersionSignatureEdge: types.NewDocumentVersionSignatureEdge(documentVersionSignature, coredata.DocumentVersionSignatureOrderFieldCreatedAt),
|
|
}, nil
|
|
}
|
|
|
|
// BulkRequestSignatures is the resolver for the bulkRequestSignatures field.
|
|
func (r *mutationResolver) BulkRequestSignatures(ctx context.Context, input types.BulkRequestSignaturesInput) (*types.BulkRequestSignaturesPayload, error) {
|
|
if len(input.DocumentIds) == 0 {
|
|
return &types.BulkRequestSignaturesPayload{
|
|
DocumentVersionSignatureEdges: []*types.DocumentVersionSignatureEdge{},
|
|
}, nil
|
|
}
|
|
|
|
for _, documentID := range input.DocumentIds {
|
|
if _, err := r.authorize(ctx, documentID, probo.ActionDocumentVersionSignatureRequest); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
scope := coredata.NewScopeFromObjectID(input.DocumentIds[0])
|
|
|
|
documentVersionSignatures, err := r.probo.Documents.BulkRequestSignatures(
|
|
ctx, scope,
|
|
probo.BulkRequestSignaturesRequest{
|
|
DocumentIDs: input.DocumentIds,
|
|
SignatoryIDs: input.SignatoryIds,
|
|
},
|
|
)
|
|
if err != nil {
|
|
if errors.Is(err, coredata.ErrResourceNotFound) {
|
|
return nil, gqlutils.NotFound(ctx, err)
|
|
}
|
|
|
|
if errNotPublished, ok := errors.AsType[*probo.ErrDocumentVersionNotPublished](err); ok {
|
|
return nil, gqlutils.Conflict(ctx, errNotPublished)
|
|
}
|
|
|
|
if errContractEnded, ok := errors.AsType[*probo.ErrProfileContractEnded](err); ok {
|
|
return nil, gqlutils.Conflict(ctx, errContractEnded)
|
|
}
|
|
|
|
r.logger.ErrorCtx(ctx, "cannot bulk request signatures", log.Error(err))
|
|
|
|
return nil, gqlutils.Internal(ctx)
|
|
}
|
|
|
|
return &types.BulkRequestSignaturesPayload{
|
|
DocumentVersionSignatureEdges: types.NewDocumentVersionSignatureEdges(documentVersionSignatures, coredata.DocumentVersionSignatureOrderFieldCreatedAt),
|
|
}, nil
|
|
}
|
|
|
|
// CancelSignatureRequest is the resolver for the cancelSignatureRequest field.
|
|
func (r *mutationResolver) CancelSignatureRequest(ctx context.Context, input types.CancelSignatureRequestInput) (*types.CancelSignatureRequestPayload, error) {
|
|
scope, err := r.authorize(ctx, input.DocumentVersionSignatureID, probo.ActionDocumentVersionCancelSignature)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if err := r.probo.Documents.CancelSignatureRequest(ctx, scope, input.DocumentVersionSignatureID); err != nil {
|
|
if errArchived, ok := errors.AsType[*probo.ErrDocumentArchived](err); ok {
|
|
return nil, gqlutils.Conflict(ctx, errArchived)
|
|
}
|
|
|
|
r.logger.ErrorCtx(ctx, "cannot cancel signature request", log.Error(err))
|
|
|
|
return nil, gqlutils.Internal(ctx)
|
|
}
|
|
|
|
return &types.CancelSignatureRequestPayload{
|
|
DeletedDocumentVersionSignatureID: input.DocumentVersionSignatureID,
|
|
}, nil
|
|
}
|
|
|
|
// SignDocument is the resolver for the signDocument field.
|
|
func (r *mutationResolver) SignDocument(ctx context.Context, input types.SignDocumentInput) (*types.SignDocumentPayload, error) {
|
|
scope, err := r.authorize(ctx, input.DocumentVersionID, probo.ActionDocumentVersionSign)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
identity := authn.IdentityFromContext(ctx)
|
|
httpReq := gqlutils.HTTPRequestFromContext(ctx)
|
|
|
|
signerIP, _, _ := net.SplitHostPort(httpReq.RemoteAddr)
|
|
if signerIP == "" {
|
|
signerIP = httpReq.RemoteAddr
|
|
}
|
|
|
|
documentVersionSignature, err := r.probo.Documents.SignDocumentVersionByIdentity(
|
|
ctx,
|
|
scope,
|
|
probo.SignDocumentVersionRequest{
|
|
DocumentVersionID: input.DocumentVersionID,
|
|
IdentityID: identity.ID,
|
|
SignerFullName: identity.FullName,
|
|
SignerEmail: identity.EmailAddress,
|
|
SignerIPAddr: signerIP,
|
|
SignerUA: httpReq.UserAgent(),
|
|
},
|
|
)
|
|
if err != nil {
|
|
if errArchived, ok := errors.AsType[*probo.ErrDocumentArchived](err); ok {
|
|
return nil, gqlutils.Conflict(ctx, errArchived)
|
|
}
|
|
|
|
if errNotPublished, ok := errors.AsType[*probo.ErrDocumentVersionNotPublished](err); ok {
|
|
return nil, gqlutils.Invalid(ctx, errNotPublished)
|
|
}
|
|
|
|
if errAlreadySigned, ok := errors.AsType[*probo.ErrDocumentVersionSignatureAlreadySigned](err); ok {
|
|
return nil, gqlutils.Conflict(ctx, errAlreadySigned)
|
|
}
|
|
|
|
if errors.Is(err, coredata.ErrResourceAlreadyExists) {
|
|
return nil, gqlutils.Conflict(ctx, err)
|
|
}
|
|
|
|
r.logger.ErrorCtx(ctx, "cannot sign document", log.Error(err))
|
|
|
|
return nil, gqlutils.Internal(ctx)
|
|
}
|
|
|
|
return &types.SignDocumentPayload{
|
|
DocumentVersionSignature: types.NewDocumentVersionSignature(documentVersionSignature),
|
|
}, nil
|
|
}
|
|
|
|
// ApproveDocumentVersion is the resolver for the approveDocumentVersion field.
|
|
func (r *mutationResolver) ApproveDocumentVersion(ctx context.Context, input types.ApproveDocumentVersionInput) (*types.ApproveDocumentVersionPayload, error) {
|
|
scope, err := r.authorize(ctx, input.DocumentVersionID, probo.ActionDocumentVersionApprove)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
identity := authn.IdentityFromContext(ctx)
|
|
httpReq := gqlutils.HTTPRequestFromContext(ctx)
|
|
|
|
signerIP, _, _ := net.SplitHostPort(httpReq.RemoteAddr)
|
|
if signerIP == "" {
|
|
signerIP = httpReq.RemoteAddr
|
|
}
|
|
|
|
decision, err := r.probo.DocumentApprovals.Approve(ctx, scope, probo.ApproveDocumentVersionRequest{
|
|
DocumentVersionID: input.DocumentVersionID,
|
|
IdentityID: identity.ID,
|
|
Comment: input.Comment,
|
|
SignerFullName: identity.FullName,
|
|
SignerEmail: identity.EmailAddress,
|
|
SignerIPAddr: signerIP,
|
|
SignerUA: httpReq.UserAgent(),
|
|
})
|
|
if err != nil {
|
|
if errArchived, ok := errors.AsType[*probo.ErrDocumentArchived](err); ok {
|
|
return nil, gqlutils.Conflict(ctx, errArchived)
|
|
}
|
|
|
|
if errNotPending, ok := errors.AsType[*probo.ErrDocumentVersionNotPendingApproval](err); ok {
|
|
return nil, gqlutils.Invalid(ctx, errNotPending)
|
|
}
|
|
|
|
if errAlready, ok := errors.AsType[*probo.ErrApprovalDecisionAlreadyMade](err); ok {
|
|
return nil, gqlutils.Conflict(ctx, errAlready)
|
|
}
|
|
|
|
if errors.Is(err, coredata.ErrResourceNotFound) {
|
|
return nil, gqlutils.NotFound(ctx, err)
|
|
}
|
|
|
|
r.logger.ErrorCtx(ctx, "cannot approve document version", log.Error(err))
|
|
|
|
return nil, gqlutils.Internal(ctx)
|
|
}
|
|
|
|
return &types.ApproveDocumentVersionPayload{
|
|
ApprovalDecision: types.NewDocumentVersionApprovalDecision(decision),
|
|
}, nil
|
|
}
|
|
|
|
// RejectDocumentVersion is the resolver for the rejectDocumentVersion field.
|
|
func (r *mutationResolver) RejectDocumentVersion(ctx context.Context, input types.RejectDocumentVersionInput) (*types.RejectDocumentVersionPayload, error) {
|
|
scope, err := r.authorize(ctx, input.DocumentVersionID, probo.ActionDocumentVersionReject)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
identity := authn.IdentityFromContext(ctx)
|
|
|
|
decision, err := r.probo.DocumentApprovals.Reject(ctx, scope, probo.RejectDocumentVersionRequest{
|
|
DocumentVersionID: input.DocumentVersionID,
|
|
IdentityID: identity.ID,
|
|
Comment: input.Comment,
|
|
})
|
|
if err != nil {
|
|
if errArchived, ok := errors.AsType[*probo.ErrDocumentArchived](err); ok {
|
|
return nil, gqlutils.Conflict(ctx, errArchived)
|
|
}
|
|
|
|
if errNotPending, ok := errors.AsType[*probo.ErrDocumentVersionNotPendingApproval](err); ok {
|
|
return nil, gqlutils.Invalid(ctx, errNotPending)
|
|
}
|
|
|
|
if errAlready, ok := errors.AsType[*probo.ErrApprovalDecisionAlreadyMade](err); ok {
|
|
return nil, gqlutils.Conflict(ctx, errAlready)
|
|
}
|
|
|
|
if errors.Is(err, coredata.ErrResourceNotFound) {
|
|
return nil, gqlutils.NotFound(ctx, err)
|
|
}
|
|
|
|
r.logger.ErrorCtx(ctx, "cannot reject document version", log.Error(err))
|
|
|
|
return nil, gqlutils.Internal(ctx)
|
|
}
|
|
|
|
return &types.RejectDocumentVersionPayload{
|
|
ApprovalDecision: types.NewDocumentVersionApprovalDecision(decision),
|
|
}, nil
|
|
}
|
|
|
|
// ExportDocumentVersionPDF is the resolver for the exportDocumentVersionPDF field.
|
|
func (r *mutationResolver) ExportDocumentVersionPDF(ctx context.Context, input types.ExportDocumentVersionPDFInput) (*types.ExportDocumentVersionPDFPayload, error) {
|
|
scope, err := r.authorize(ctx, input.DocumentVersionID, probo.ActionDocumentVersionExportPDF)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
watermarkEmail := input.WatermarkEmail
|
|
if input.WithWatermark && watermarkEmail == nil {
|
|
identity := authn.IdentityFromContext(ctx)
|
|
watermarkEmail = &identity.EmailAddress
|
|
}
|
|
|
|
options := probo.ExportPDFOptions{
|
|
WithSignatures: input.WithSignatures,
|
|
WithWatermark: input.WithWatermark,
|
|
WatermarkEmail: watermarkEmail,
|
|
}
|
|
|
|
pdf, err := r.probo.Documents.ExportPDF(ctx, scope, input.DocumentVersionID, options)
|
|
if err != nil {
|
|
r.logger.ErrorCtx(ctx, "cannot export document version PDF", log.Error(err))
|
|
return nil, gqlutils.Internal(ctx)
|
|
}
|
|
|
|
return &types.ExportDocumentVersionPDFPayload{
|
|
Data: fmt.Sprintf("data:application/pdf;base64,%s", base64.StdEncoding.EncodeToString(pdf)),
|
|
}, nil
|
|
}
|
|
|
|
// ExportEmployeeDocumentVersionPDF is the resolver for the exportEmployeeDocumentVersionPDF field.
|
|
func (r *mutationResolver) ExportEmployeeDocumentVersionPDF(ctx context.Context, input types.ExportEmployeeDocumentVersionPDFInput) (*types.ExportEmployeeDocumentVersionPDFPayload, error) {
|
|
scope, err := r.authorize(ctx, input.DocumentVersionID, probo.ActionEmployeeDocumentVersionExportPDF)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
documentVersion, err := r.probo.Documents.GetVersion(ctx, scope, input.DocumentVersionID)
|
|
if err != nil {
|
|
r.logger.ErrorCtx(ctx, "cannot get document version", log.Error(err))
|
|
return nil, gqlutils.Internal(ctx)
|
|
}
|
|
|
|
identity := authn.IdentityFromContext(ctx)
|
|
documentFilter := coredata.NewDocumentFilter(nil).WithEmployeeIdentityID(
|
|
&identity.ID,
|
|
coredata.EmployeeFilterModeSignature,
|
|
coredata.EmployeeFilterModeApproval,
|
|
)
|
|
|
|
_, err = r.probo.Documents.GetWithFilter(ctx, scope, documentVersion.DocumentID, documentFilter)
|
|
if err != nil {
|
|
if errors.Is(err, coredata.ErrResourceNotFound) {
|
|
return nil, gqlutils.NotFound(ctx, err)
|
|
}
|
|
|
|
r.logger.ErrorCtx(ctx, "cannot get employee document", log.Error(err))
|
|
|
|
return nil, gqlutils.Internal(ctx)
|
|
}
|
|
|
|
options := probo.ExportPDFOptions{
|
|
WithSignatures: false,
|
|
WithWatermark: true,
|
|
WatermarkEmail: &identity.EmailAddress,
|
|
}
|
|
|
|
pdf, err := r.probo.Documents.ExportPDF(ctx, scope, input.DocumentVersionID, options)
|
|
if err != nil {
|
|
r.logger.ErrorCtx(ctx, "cannot export employee document PDF", log.Error(err))
|
|
return nil, gqlutils.Internal(ctx)
|
|
}
|
|
|
|
return &types.ExportEmployeeDocumentVersionPDFPayload{
|
|
Data: fmt.Sprintf("data:application/pdf;base64,%s", base64.StdEncoding.EncodeToString(pdf)),
|
|
}, nil
|
|
}
|
|
|
|
// Document returns schema.DocumentResolver implementation.
|
|
func (r *Resolver) Document() schema.DocumentResolver { return &documentResolver{r} }
|
|
|
|
// DocumentConnection returns schema.DocumentConnectionResolver implementation.
|
|
func (r *Resolver) DocumentConnection() schema.DocumentConnectionResolver {
|
|
return &documentConnectionResolver{r}
|
|
}
|
|
|
|
// DocumentVersion returns schema.DocumentVersionResolver implementation.
|
|
func (r *Resolver) DocumentVersion() schema.DocumentVersionResolver {
|
|
return &documentVersionResolver{r}
|
|
}
|
|
|
|
// DocumentVersionApprovalDecision returns schema.DocumentVersionApprovalDecisionResolver implementation.
|
|
func (r *Resolver) DocumentVersionApprovalDecision() schema.DocumentVersionApprovalDecisionResolver {
|
|
return &documentVersionApprovalDecisionResolver{r}
|
|
}
|
|
|
|
// DocumentVersionApprovalDecisionConnection returns schema.DocumentVersionApprovalDecisionConnectionResolver implementation.
|
|
func (r *Resolver) DocumentVersionApprovalDecisionConnection() schema.DocumentVersionApprovalDecisionConnectionResolver {
|
|
return &documentVersionApprovalDecisionConnectionResolver{r}
|
|
}
|
|
|
|
// DocumentVersionApprovalQuorum returns schema.DocumentVersionApprovalQuorumResolver implementation.
|
|
func (r *Resolver) DocumentVersionApprovalQuorum() schema.DocumentVersionApprovalQuorumResolver {
|
|
return &documentVersionApprovalQuorumResolver{r}
|
|
}
|
|
|
|
// DocumentVersionApprovalQuorumConnection returns schema.DocumentVersionApprovalQuorumConnectionResolver implementation.
|
|
func (r *Resolver) DocumentVersionApprovalQuorumConnection() schema.DocumentVersionApprovalQuorumConnectionResolver {
|
|
return &documentVersionApprovalQuorumConnectionResolver{r}
|
|
}
|
|
|
|
// DocumentVersionConnection returns schema.DocumentVersionConnectionResolver implementation.
|
|
func (r *Resolver) DocumentVersionConnection() schema.DocumentVersionConnectionResolver {
|
|
return &documentVersionConnectionResolver{r}
|
|
}
|
|
|
|
// DocumentVersionSignature returns schema.DocumentVersionSignatureResolver implementation.
|
|
func (r *Resolver) DocumentVersionSignature() schema.DocumentVersionSignatureResolver {
|
|
return &documentVersionSignatureResolver{r}
|
|
}
|
|
|
|
// DocumentVersionSignatureConnection returns schema.DocumentVersionSignatureConnectionResolver implementation.
|
|
func (r *Resolver) DocumentVersionSignatureConnection() schema.DocumentVersionSignatureConnectionResolver {
|
|
return &documentVersionSignatureConnectionResolver{r}
|
|
}
|
|
|
|
// EmployeeDocument returns schema.EmployeeDocumentResolver implementation.
|
|
func (r *Resolver) EmployeeDocument() schema.EmployeeDocumentResolver {
|
|
return &employeeDocumentResolver{r}
|
|
}
|
|
|
|
// EmployeeDocumentVersion returns schema.EmployeeDocumentVersionResolver implementation.
|
|
func (r *Resolver) EmployeeDocumentVersion() schema.EmployeeDocumentVersionResolver {
|
|
return &employeeDocumentVersionResolver{r}
|
|
}
|
|
|
|
type documentResolver struct{ *Resolver }
|
|
type documentConnectionResolver struct{ *Resolver }
|
|
type documentVersionResolver struct{ *Resolver }
|
|
type documentVersionApprovalDecisionResolver struct{ *Resolver }
|
|
type documentVersionApprovalDecisionConnectionResolver struct{ *Resolver }
|
|
type documentVersionApprovalQuorumResolver struct{ *Resolver }
|
|
type documentVersionApprovalQuorumConnectionResolver struct{ *Resolver }
|
|
type documentVersionConnectionResolver struct{ *Resolver }
|
|
type documentVersionSignatureResolver struct{ *Resolver }
|
|
type documentVersionSignatureConnectionResolver struct{ *Resolver }
|
|
type employeeDocumentResolver struct{ *Resolver }
|
|
type employeeDocumentVersionResolver struct{ *Resolver }
|