Introduce internal/contracts/vocabulary for state/role/error sentinels, alias through docgov, and retarget hot production consumers so coordinator no longer imports docgov for vocabulary-only use.
107 lines
3.0 KiB
Go
107 lines
3.0 KiB
Go
package docadvance
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
|
|
"github.com/jackc/pgx/v5"
|
|
|
|
"chat-agent-system/internal/contracts/vocabulary"
|
|
"chat-agent-system/internal/docgov"
|
|
"chat-agent-system/internal/events"
|
|
"chat-agent-system/internal/notification"
|
|
"chat-agent-system/internal/projectinstance"
|
|
"chat-agent-system/internal/projectworkflow"
|
|
)
|
|
|
|
// EventConsumer applies document.approved onto workflow task bookkeeping.
|
|
// Closures remain the unique writer until live cutover.
|
|
type EventConsumer struct {
|
|
Inst *projectinstance.Service
|
|
Docs DocumentLookup
|
|
PW ProjectLookup
|
|
Notify *notification.Service
|
|
Env string
|
|
}
|
|
|
|
type DocumentLookup interface {
|
|
GetDocument(ctx context.Context, docID string) (*docgov.DocumentRecord, error)
|
|
}
|
|
|
|
type ProjectLookup interface {
|
|
GetProject(ctx context.Context, projectID, userID int64) (*projectworkflow.Project, error)
|
|
}
|
|
|
|
func (c *EventConsumer) HandleLease(ctx context.Context, lease events.Lease, tx pgx.Tx) error {
|
|
if c == nil || c.Inst == nil {
|
|
return events.ErrUnknownConsumer
|
|
}
|
|
principal := events.ServicePrincipal{
|
|
ID: "advance-" + c.Env, Environment: c.Env, Consumer: events.ConsumerWorkflowAdvance,
|
|
Actions: []string{"workflow.advance"},
|
|
}
|
|
if err := events.AuthorizePrincipal(principal, lease.Environment, lease.ProjectID, "workflow.advance"); err != nil {
|
|
return events.ErrQuarantine
|
|
}
|
|
if lease.EventName != events.EventApproved {
|
|
return nil
|
|
}
|
|
docID := lease.Envelope.AggregateID
|
|
taskKey := ""
|
|
var payload map[string]any
|
|
_ = json.Unmarshal(lease.Envelope.Payload, &payload)
|
|
if payload != nil {
|
|
if id, ok := payload["doc_id"].(string); ok && id != "" {
|
|
docID = id
|
|
}
|
|
if k, ok := payload["task_key"].(string); ok {
|
|
taskKey = k
|
|
}
|
|
}
|
|
if c.Docs == nil || docID == "" {
|
|
return events.ErrQuarantine
|
|
}
|
|
doc, err := c.Docs.GetDocument(ctx, docID)
|
|
if err != nil || doc == nil {
|
|
return events.ErrQuarantine
|
|
}
|
|
if doc.ProjectID != lease.ProjectID {
|
|
return events.ErrQuarantine
|
|
}
|
|
if doc.CurrentState != vocabulary.StateApproved {
|
|
return events.ErrQuarantine
|
|
}
|
|
if taskKey == "" {
|
|
taskKey = doc.TaskKey
|
|
}
|
|
if taskKey == "" {
|
|
return events.ErrQuarantine
|
|
}
|
|
|
|
ctx = projectinstance.WithTx(ctx, tx)
|
|
result, err := c.Inst.HandleDocumentApproved(ctx, doc.ProjectID, taskKey)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if result == nil || !result.StageReady {
|
|
return nil
|
|
}
|
|
if c.Notify == nil || c.PW == nil {
|
|
return nil
|
|
}
|
|
project, err := c.PW.GetProject(ctx, doc.ProjectID, 0)
|
|
if err != nil || project == nil || project.CreatorUserID <= 0 {
|
|
return events.ErrQuarantine
|
|
}
|
|
return c.Notify.EmitVisibleStageReady(ctx, tx, lease.EventID, project.CreatorUserID, notification.StageReadyPayload{
|
|
ProjectID: doc.ProjectID,
|
|
WorkflowInstanceID: result.InstanceID,
|
|
StageInstanceID: result.StageInstanceID,
|
|
StageKey: result.StageKey,
|
|
StageName: result.StageName,
|
|
ConfigRevision: result.ConfigRevision,
|
|
RequiredDocCount: result.RequiredDocCount,
|
|
ApprovedDocCount: result.ApprovedDocCount,
|
|
})
|
|
}
|