Give each document command one postgres transaction and only let table owners unwrap the scope, so cross-owner writes can roll back together. Co-authored-by: Cursor <cursoragent@cursor.com>
107 lines
3.2 KiB
Go
107 lines
3.2 KiB
Go
package docgov
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"time"
|
|
|
|
"chat-agent-system/internal/app/uow"
|
|
)
|
|
|
|
// Repository is the document_records writer bound to a UnitOfWork scope.
|
|
type Repository struct{}
|
|
|
|
func (Repository) InsertRoot(ctx context.Context, scope uow.TxScope, rec DocumentRecord) error {
|
|
tx, err := uow.AsPG(scope)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if rec.CreatedAt.IsZero() {
|
|
rec.CreatedAt = time.Now().UTC()
|
|
}
|
|
if rec.UpdatedAt.IsZero() {
|
|
rec.UpdatedAt = rec.CreatedAt
|
|
}
|
|
if rec.Stage == "" {
|
|
rec.Stage = "V0.5_feasibility"
|
|
}
|
|
if rec.CurrentState == "" {
|
|
rec.CurrentState = StateDrafting
|
|
}
|
|
if rec.Title == "" {
|
|
rec.Title = rec.DocID
|
|
}
|
|
if rec.TaskKey == "" {
|
|
rec.TaskKey = "intake"
|
|
}
|
|
_, err = tx.Exec(ctx, `
|
|
INSERT INTO document_records (
|
|
doc_id, project_id, task_key, title, stage, version,
|
|
current_state, is_locked, is_template_only, content_sha256, work_dir,
|
|
author_name, reviewer_name, approver_name, created_at, updated_at
|
|
) VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16)`,
|
|
rec.DocID, rec.ProjectID, rec.TaskKey, rec.Title, rec.Stage, rec.Version,
|
|
string(rec.CurrentState), rec.IsLocked, rec.IsTemplateOnly, rec.ContentSHA256, rec.WorkDir,
|
|
rec.AuthorName, rec.ReviewerName, rec.ApproverName, rec.CreatedAt, rec.UpdatedAt)
|
|
if err != nil {
|
|
return fmt.Errorf("docgov: insert root: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (Repository) GetForUpdate(ctx context.Context, scope uow.TxScope, docID string) (*DocumentRecord, error) {
|
|
tx, err := uow.AsPG(scope)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var rec DocumentRecord
|
|
var state string
|
|
err = tx.QueryRow(ctx, `
|
|
SELECT doc_id, project_id, task_key, title, stage, version, current_state,
|
|
is_locked, is_template_only, content_sha256, work_dir
|
|
FROM document_records WHERE doc_id=$1 FOR UPDATE`, docID).Scan(
|
|
&rec.DocID, &rec.ProjectID, &rec.TaskKey, &rec.Title, &rec.Stage, &rec.Version, &state,
|
|
&rec.IsLocked, &rec.IsTemplateOnly, &rec.ContentSHA256, &rec.WorkDir)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("docgov: lock document: %w", err)
|
|
}
|
|
rec.CurrentState = DocumentState(state)
|
|
return &rec, nil
|
|
}
|
|
|
|
func (Repository) UpdateState(ctx context.Context, scope uow.TxScope, rec DocumentRecord) error {
|
|
tx, err := uow.AsPG(scope)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
_, err = tx.Exec(ctx, `
|
|
UPDATE document_records
|
|
SET current_state=$2, is_locked=$3, content_sha256=COALESCE(NULLIF($4,''), content_sha256),
|
|
updated_at=$5
|
|
WHERE doc_id=$1`,
|
|
rec.DocID, string(rec.CurrentState), rec.IsLocked, rec.ContentSHA256, time.Now().UTC())
|
|
if err != nil {
|
|
return fmt.Errorf("docgov: update state: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (Repository) ProjectHead(ctx context.Context, scope uow.TxScope, docID string, versionID, snapshotID int64, revisionNo int, sha string) error {
|
|
tx, err := uow.AsPG(scope)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
_, err = tx.Exec(ctx, `
|
|
UPDATE document_records
|
|
SET ledger_version_id=$2, head_snapshot_id=$3, head_revision_no=$4,
|
|
row_revision=COALESCE(row_revision,0)+1,
|
|
content_sha256=COALESCE(NULLIF($5,''), content_sha256),
|
|
updated_at=now()
|
|
WHERE doc_id=$1`,
|
|
docID, versionID, snapshotID, revisionNo, sha)
|
|
if err != nil {
|
|
return fmt.Errorf("docgov: project head: %w", err)
|
|
}
|
|
return nil
|
|
}
|