Add document archiving
Documents can be archived and unarchived. Archived documents are read-only, excluded from the trust center, and moved to a dedicated Archived tab in the document list. - Add archived_at timestamp and status (ACTIVE/ARCHIVED) PG enum column - Rename DocumentStatus → DocumentVersionStatus, introduce DocumentStatus - Archive/unarchive mutations in GraphQL, MCP, and CLI - Bulk archive/unarchive mutations with Active/Archived tabs in the list - ABAC policies: write actions denied on archived docs, unarchive denied on active docs - Remove control/risk mappings and reset trust center visibility on archive - Exclude archived documents from mapping dialogs and trust center tab Signed-off-by: Sacha Al Himdani <sacha@getprobo.com>
This commit is contained in:
@@ -4286,6 +4286,9 @@ func (r *mutationResolver) UpdateDocument(ctx context.Context, input types.Updat
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
if errArchived, ok := errors.AsType[*probo.ErrDocumentArchived](err); ok {
|
||||
return nil, gqlutils.Conflict(ctx, errArchived)
|
||||
}
|
||||
if validationErrors, ok := errors.AsType[validator.ValidationErrors](err); ok {
|
||||
return nil, gqlutils.InvalidValidationErrors(ctx, validationErrors)
|
||||
}
|
||||
@@ -4298,6 +4301,50 @@ func (r *mutationResolver) UpdateDocument(ctx context.Context, input types.Updat
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ArchiveDocument is the resolver for the archiveDocument field.
|
||||
func (r *mutationResolver) ArchiveDocument(ctx context.Context, input types.ArchiveDocumentInput) (*types.ArchiveDocumentPayload, error) {
|
||||
if err := r.authorize(ctx, input.DocumentID, probo.ActionDocumentArchive); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
prb := r.ProboService(ctx, input.DocumentID.TenantID())
|
||||
|
||||
document, err := prb.Documents.Archive(ctx, 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) {
|
||||
if err := r.authorize(ctx, input.DocumentID, probo.ActionDocumentUnarchive); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
prb := r.ProboService(ctx, input.DocumentID.TenantID())
|
||||
|
||||
document, err := prb.Documents.Unarchive(ctx, 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) {
|
||||
if err := r.authorize(ctx, input.DocumentID, probo.ActionDocumentDelete); err != nil {
|
||||
@@ -4607,6 +4654,10 @@ func (r *mutationResolver) PublishDocumentVersion(ctx context.Context, input typ
|
||||
return nil, gqlutils.Invalid(ctx, errNoChanges)
|
||||
}
|
||||
|
||||
if errArchived, ok := errors.AsType[*probo.ErrDocumentArchived](err); ok {
|
||||
return nil, gqlutils.Conflict(ctx, errArchived)
|
||||
}
|
||||
|
||||
r.logger.ErrorCtx(ctx, "cannot publish document version", log.Error(err))
|
||||
return nil, gqlutils.Internal(ctx)
|
||||
}
|
||||
@@ -4687,6 +4738,58 @@ func (r *mutationResolver) BulkDeleteDocuments(ctx context.Context, input types.
|
||||
}, 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
|
||||
}
|
||||
}
|
||||
|
||||
prb := r.ProboService(ctx, input.DocumentIds[0].TenantID())
|
||||
|
||||
if err := prb.Documents.BulkArchive(ctx, 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
|
||||
}
|
||||
}
|
||||
|
||||
prb := r.ProboService(ctx, input.DocumentIds[0].TenantID())
|
||||
|
||||
if err := prb.Documents.BulkUnarchive(ctx, 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 {
|
||||
@@ -4803,6 +4906,9 @@ func (r *mutationResolver) UpdateDocumentVersion(ctx context.Context, input type
|
||||
return nil, gqlutils.Conflict(ctx, errNotDraft)
|
||||
}
|
||||
|
||||
if errArchived, ok := errors.AsType[*probo.ErrDocumentArchived](err); ok {
|
||||
return nil, gqlutils.Conflict(ctx, errArchived)
|
||||
}
|
||||
if validationErrors, ok := errors.AsType[validator.ValidationErrors](err); ok {
|
||||
return nil, gqlutils.InvalidValidationErrors(ctx, validationErrors)
|
||||
}
|
||||
@@ -6479,7 +6585,8 @@ func (r *organizationResolver) Documents(ctx context.Context, obj *types.Organiz
|
||||
var documentFilter = coredata.NewDocumentFilter(nil)
|
||||
if filter != nil {
|
||||
documentFilter = coredata.NewDocumentFilter(filter.Query).
|
||||
WithDocumentTypes(filter.DocumentTypes)
|
||||
WithDocumentTypes(filter.DocumentTypes).
|
||||
WithStatus(filter.Status)
|
||||
}
|
||||
|
||||
page, err := prb.Documents.ListByOrganizationID(ctx, obj.ID, cursor, documentFilter)
|
||||
|
||||
Reference in New Issue
Block a user