Consolidate document draft management into updateDocument

Replace the three separate draft mutations (createDraftDocumentVersion,
updateDocumentVersion, deleteDraftDocumentVersion) with automatic draft
lifecycle management inside updateDocument. The backend now auto-creates
a draft when a published document is edited, updates the existing draft
on subsequent edits, and auto-deletes the draft when content reverts to
match the published version.

A new deleteDocumentDraft mutation provides explicit draft deletion.

Backend:
- Merge version-level fields (content, title, classification,
  documentType) into UpdateDocumentRequest
- Convert CreateDraft, UpdateVersion, DeleteDraft into private
  transaction helpers called from Update
- Update returns (*Document, *DocumentVersion, error) with the version
  present only when a draft exists

Frontend:
- Remove all create/update/delete draft mutations from components
- Auto-save via updateDocument with layout refetch on draft status
  transitions while preserving editor cursor (data-generation key)
- Title, type, and classification editable on published versions
  (backend auto-creates draft)
- Forms use react-hook-form values option to stay synced with Relay
  fragment data across draft/publish transitions

API surface (GraphQL, MCP, CLI, n8n) updated consistently:
- Removed: createDraftDocumentVersion, updateDocumentVersion,
  deleteDraftDocumentVersion
- Added: deleteDocumentDraft (document-level)
- Updated: updateDocument accepts content, classification, documentType

Signed-off-by: Sacha Al Himdani <sacha@getprobo.com>
This commit is contained in:
Sacha Al Himdani
2026-04-14 14:37:24 +02:00
parent 74d7d3ff25
commit 03708d45c3
21 changed files with 1054 additions and 1087 deletions

View File

@@ -2115,10 +2115,23 @@ func (r *Resolver) UpdateDocumentTool(ctx context.Context, req *mcp.CallToolRequ
defaultApproverIDs = &input.DefaultApproverIds
}
document, err := svc.Documents.Update(
var content *string
if input.Content != nil {
c, err := markdownToProseMirrorJSON(*input.Content)
if err != nil {
panic(fmt.Errorf("cannot convert markdown to prosemirror: %w", err))
}
content = &c
}
document, documentVersion, _, err := svc.Documents.Update(
ctx,
probo.UpdateDocumentRequest{
DocumentID: input.ID,
Title: input.Title,
Content: content,
Classification: input.Classification,
DocumentType: input.DocumentType,
TrustCenterVisibility: input.TrustCenterVisibility,
DefaultApproverIDs: defaultApproverIDs,
},
@@ -2127,9 +2140,15 @@ func (r *Resolver) UpdateDocumentTool(ctx context.Context, req *mcp.CallToolRequ
panic(fmt.Errorf("cannot update document: %w", err))
}
return nil, types.UpdateDocumentOutput{
output := types.UpdateDocumentOutput{
Document: types.NewDocument(document),
}, nil
}
if documentVersion != nil {
output.DocumentVersion = types.NewDocumentVersion(documentVersion)
}
return nil, output, nil
}
func (r *Resolver) ListDocumentVersionsTool(ctx context.Context, req *mcp.CallToolRequest, input *types.ListDocumentVersionsInput) (*mcp.CallToolResult, types.ListDocumentVersionsOutput, error) {
@@ -2172,72 +2191,6 @@ func (r *Resolver) GetDocumentVersionTool(ctx context.Context, req *mcp.CallTool
}, nil
}
func (r *Resolver) CreateDraftDocumentVersionTool(ctx context.Context, req *mcp.CallToolRequest, input *types.CreateDraftDocumentVersionInput) (*mcp.CallToolResult, types.CreateDraftDocumentVersionOutput, error) {
r.MustAuthorize(ctx, input.DocumentID, probo.ActionDocumentDraftVersionCreate)
svc := r.ProboService(ctx, input.DocumentID)
draftVersion, err := svc.Documents.CreateDraft(ctx, input.DocumentID)
if err != nil {
panic(fmt.Errorf("cannot create draft document version: %w", err))
}
if input.Content != nil {
content, err := markdownToProseMirrorJSON(*input.Content)
if err != nil {
panic(fmt.Errorf("cannot convert markdown to prosemirror: %w", err))
}
draftVersion, err = svc.Documents.UpdateVersion(
ctx,
probo.UpdateDocumentVersionRequest{
ID: draftVersion.ID,
Content: &content,
},
)
if err != nil {
panic(fmt.Errorf("cannot update draft document version content: %w", err))
}
}
return nil, types.CreateDraftDocumentVersionOutput{
DocumentVersion: types.NewDocumentVersion(draftVersion),
}, nil
}
func (r *Resolver) UpdateDocumentVersionTool(ctx context.Context, req *mcp.CallToolRequest, input *types.UpdateDocumentVersionInput) (*mcp.CallToolResult, types.UpdateDocumentVersionOutput, error) {
r.MustAuthorize(ctx, input.DocumentVersionID, probo.ActionDocumentVersionUpdate)
svc := r.ProboService(ctx, input.DocumentVersionID)
var content *string
if input.Content != nil {
c, err := markdownToProseMirrorJSON(*input.Content)
if err != nil {
panic(fmt.Errorf("cannot convert markdown to prosemirror: %w", err))
}
content = &c
}
documentVersion, err := svc.Documents.UpdateVersion(
ctx,
probo.UpdateDocumentVersionRequest{
ID: input.DocumentVersionID,
Title: input.Title,
Content: content,
Classification: input.Classification,
DocumentType: input.DocumentType,
},
)
if err != nil {
panic(fmt.Errorf("cannot update document version: %w", err))
}
return nil, types.UpdateDocumentVersionOutput{
DocumentVersion: types.NewDocumentVersion(documentVersion),
}, nil
}
func (r *Resolver) ListDocumentVersionSignaturesTool(ctx context.Context, req *mcp.CallToolRequest, input *types.ListDocumentVersionSignaturesInput) (*mcp.CallToolResult, types.ListDocumentVersionSignaturesOutput, error) {
r.MustAuthorize(ctx, input.DocumentVersionID, probo.ActionDocumentVersionSignatureList)
@@ -2312,21 +2265,6 @@ func (r *Resolver) RequestDocumentVersionSignatureTool(ctx context.Context, req
}, nil
}
func (r *Resolver) DeleteDraftDocumentVersionTool(ctx context.Context, req *mcp.CallToolRequest, input *types.DeleteDraftDocumentVersionInput) (*mcp.CallToolResult, types.DeleteDraftDocumentVersionOutput, error) {
r.MustAuthorize(ctx, input.DocumentVersionID, probo.ActionDocumentVersionDeleteDraft)
svc := r.ProboService(ctx, input.DocumentVersionID)
err := svc.Documents.DeleteDraft(ctx, input.DocumentVersionID)
if err != nil {
panic(fmt.Errorf("cannot delete draft document version: %w", err))
}
return nil, types.DeleteDraftDocumentVersionOutput{
DeletedDocumentVersionID: input.DocumentVersionID,
}, nil
}
func (r *Resolver) DeleteDocumentTool(ctx context.Context, req *mcp.CallToolRequest, input *types.DeleteDocumentInput) (*mcp.CallToolResult, types.DeleteDocumentOutput, error) {
r.MustAuthorize(ctx, input.DocumentID, probo.ActionDocumentDelete)
@@ -3971,3 +3909,18 @@ func (r *Resolver) SendSigningNotificationsTool(ctx context.Context, req *mcp.Ca
Success: true,
}, nil
}
func (r *Resolver) DeleteDocumentDraftTool(ctx context.Context, req *mcp.CallToolRequest, input *types.DeleteDocumentDraftInput) (*mcp.CallToolResult, types.DeleteDocumentDraftOutput, error) {
r.MustAuthorize(ctx, input.ID, probo.ActionDocumentDeleteDraft)
svc := r.ProboService(ctx, input.ID)
document, err := svc.Documents.DeleteDraft(ctx, input.ID)
if err != nil {
return nil, types.DeleteDocumentDraftOutput{}, fmt.Errorf("cannot delete document draft: %w", err)
}
return nil, types.DeleteDocumentDraftOutput{
Document: types.NewDocument(document),
}, nil
}