Implement debounced auto save

Signed-off-by: Émile Ré <emile@getprobo.com>
This commit is contained in:
Émile Ré
2026-03-17 16:55:02 +04:00
parent a9f4ecacfa
commit 3a054c70ba
8 changed files with 458 additions and 61 deletions

View File

@@ -77,6 +77,7 @@ type (
OrganizationID gid.GID
Title string
Content string
ApproverIDs []gid.GID
Classification coredata.DocumentClassification
DocumentType coredata.DocumentType
TrustCenterVisibility *coredata.TrustCenterVisibility
@@ -121,6 +122,10 @@ func (cdr *CreateDocumentRequest) Validate() error {
v.Check(cdr.OrganizationID, "organization_id", validator.Required(), validator.GID(coredata.OrganizationEntityType))
v.Check(cdr.Title, "title", validator.Required(), validator.SafeTextNoNewLine(TitleMaxLength))
v.Check(cdr.Content, "content", validator.Required(), validator.NotEmpty(), validator.MaxLen(documentMaxLength))
v.Check(cdr.ApproverIDs, "approver_ids", validator.Required(), validator.NotEmpty())
for _, id := range cdr.ApproverIDs {
v.Check(id, "approver_ids", validator.Required(), validator.GID(coredata.MembershipProfileEntityType))
}
v.Check(cdr.Classification, "classification", validator.Required(), validator.OneOfSlice(coredata.DocumentClassifications()))
v.Check(cdr.DocumentType, "document_type", validator.Required(), validator.OneOfSlice(coredata.DocumentTypes()))
v.Check(cdr.TrustCenterVisibility, "trust_center_visibility", validator.OneOfSlice(coredata.TrustCenterVisibilities()))
@@ -1576,6 +1581,45 @@ func (s *DocumentService) Update(
return document, nil
}
func (s *DocumentService) UpdateDocumentVersionContent(
ctx context.Context,
req UpdateDocumentVersionRequest,
) (string, error) {
documentVersion := &coredata.DocumentVersion{}
if err := req.Validate(); err != nil {
return "", err
}
err := s.svc.pg.WithTx(
ctx,
func(conn pg.Conn) error {
if err := documentVersion.LoadByID(ctx, conn, s.svc.scope, req.ID); err != nil {
return fmt.Errorf("cannot load document version %q: %w", req.ID, err)
}
if documentVersion.Status != coredata.DocumentVersionStatusDraft {
return &ErrDocumentVersionNotDraft{}
}
documentVersion.Content = req.Content
documentVersion.UpdatedAt = time.Now()
if err := documentVersion.Update(ctx, conn, s.svc.scope); err != nil {
return fmt.Errorf("cannot update document version: %w", err)
}
return nil
},
)
if err != nil {
return "", err
}
return documentVersion.Content, nil
}
func (s *DocumentService) Archive(
ctx context.Context,
documentID gid.GID,

View File

@@ -3766,6 +3766,7 @@ type Mutation {
updateDocument(input: UpdateDocumentInput!): UpdateDocumentPayload!
archiveDocument(input: ArchiveDocumentInput!): ArchiveDocumentPayload!
unarchiveDocument(input: UnarchiveDocumentInput!): UnarchiveDocumentPayload!
updateDocumentVersionContent(input: UpdateDocumentVersionContentInput!): UpdateDocumentVersionContentPayload!
deleteDocument(input: DeleteDocumentInput!): DeleteDocumentPayload!
# Meeting mutations
createMeeting(input: CreateMeetingInput!): CreateMeetingPayload!
@@ -4472,6 +4473,7 @@ input CreateDocumentInput {
organizationId: ID!
title: String!
content: String!
approverIds: [ID!]!
documentType: DocumentType!
classification: DocumentClassification!
trustCenterVisibility: TrustCenterVisibility
@@ -4485,6 +4487,11 @@ input UpdateDocumentInput {
trustCenterVisibility: TrustCenterVisibility
}
input UpdateDocumentVersionContentInput {
id: ID!
content: String!
}
input ExportDocumentVersionPDFInput {
documentVersionId: ID!
withWatermark: Boolean!
@@ -5250,6 +5257,10 @@ type UnarchiveDocumentPayload {
document: Document!
}
type UpdateDocumentVersionContentPayload {
content: String!
}
type DeleteDocumentPayload {
deletedDocumentId: ID!
}

View File

@@ -4629,6 +4629,7 @@ func (r *mutationResolver) CreateDocument(ctx context.Context, input types.Creat
DocumentType: input.DocumentType,
Title: input.Title,
Content: input.Content,
ApproverIDs: input.ApproverIds,
Classification: input.Classification,
TrustCenterVisibility: input.TrustCenterVisibility,
},
@@ -4729,6 +4730,36 @@ func (r *mutationResolver) UnarchiveDocument(ctx context.Context, input types.Un
}, nil
}
// UpdateDocumentVersionContent is the resolver for the updateDocumentVersionContent field.
func (r *mutationResolver) UpdateDocumentVersionContent(ctx context.Context, input types.UpdateDocumentVersionContentInput) (*types.UpdateDocumentVersionContentPayload, error) {
if err := r.authorize(ctx, input.ID, probo.ActionDocumentUpdate); err != nil {
return nil, err
}
prb := r.ProboService(ctx, input.ID.TenantID())
content, err := prb.Documents.UpdateDocumentVersionContent(
ctx,
probo.UpdateDocumentVersionRequest{
ID: input.ID,
Content: input.Content,
},
)
if err != nil {
if _, ok := errors.AsType[*probo.ErrDocumentVersionNotDraft](err); ok {
return nil, gqlutils.Conflict(ctx, err)
}
r.logger.ErrorCtx(ctx, "cannot update document version content", log.Error(err))
return nil, gqlutils.Internal(ctx)
}
return &types.UpdateDocumentVersionContentPayload{
Content: content,
}, 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 {