Files
Agent-System/internal/docgov/repository.go
T
weichenandCursor f8b21ecd7c feat(app): add UnitOfWork and owner-bound repositories
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>
2026-09-14 13:54:27 +00:00

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
}