65 lines
2.9 KiB
Go
65 lines
2.9 KiB
Go
package memorylifecycle
|
|
|
|
import (
|
|
"context"
|
|
"crypto/sha256"
|
|
"encoding/hex"
|
|
"errors"
|
|
"fmt"
|
|
|
|
"data-terminal-cloud/internal/audit"
|
|
"data-terminal-cloud/internal/database"
|
|
"data-terminal-cloud/internal/outbox"
|
|
|
|
"github.com/jackc/pgx/v5"
|
|
"github.com/jackc/pgx/v5/pgxpool"
|
|
)
|
|
|
|
const deletedMemoryMarker = "[已按用户请求删除]"
|
|
|
|
type DeleteProcessor struct {
|
|
pool *pgxpool.Pool
|
|
audit *audit.Repository
|
|
}
|
|
|
|
func NewDeleteProcessor(pool *pgxpool.Pool) (*DeleteProcessor, error) {
|
|
if pool == nil {
|
|
return nil, errors.New("database pool is required")
|
|
}
|
|
return &DeleteProcessor{pool: pool, audit: audit.NewRepository()}, nil
|
|
}
|
|
|
|
func (p *DeleteProcessor) Handle(ctx context.Context, event outbox.Event) error {
|
|
return database.Transact(ctx, p.pool, func(tx pgx.Tx) error {
|
|
var tenantID, memoryPublicID, status string
|
|
err := tx.QueryRow(ctx, `SELECT tenant_id::text,public_id::text,status FROM curated_memory WHERE id=$1::uuid FOR UPDATE`, event.AggregateID).Scan(&tenantID, &memoryPublicID, &status)
|
|
if errors.Is(err, pgx.ErrNoRows) || status == "DELETED" {
|
|
return nil
|
|
}
|
|
if err != nil {
|
|
return fmt.Errorf("load memory deletion: %w", err)
|
|
}
|
|
if status != "PENDING_DELETE" {
|
|
return nil
|
|
}
|
|
var unresolvedProviderRefs int
|
|
if err := tx.QueryRow(ctx, `SELECT count(*) FROM memory_index_entry WHERE memory_id=$1::uuid AND index_type='provider' AND provider_ref IS NOT NULL AND status<>'DELETED'`, event.AggregateID).Scan(&unresolvedProviderRefs); err != nil {
|
|
return fmt.Errorf("check provider memory deletion: %w", err)
|
|
}
|
|
if unresolvedProviderRefs > 0 {
|
|
return outbox.Retryable(errors.New("provider memory deletion has not reconciled"))
|
|
}
|
|
if _, err := tx.Exec(ctx, `UPDATE memory_index_entry SET embedding=NULL,status='DELETED',provider_ref=NULL,indexed_at=NULL,last_error=NULL,updated_at=now() WHERE memory_id=$1::uuid`, event.AggregateID); err != nil {
|
|
return fmt.Errorf("delete memory indexes: %w", err)
|
|
}
|
|
digest := sha256.Sum256([]byte(deletedMemoryMarker))
|
|
if _, err := tx.Exec(ctx, `UPDATE memory_version SET content=$2,content_digest=$3,change_reason=CASE WHEN COALESCE(change_reason,'')='' THEN 'user requested deletion' ELSE change_reason || '; user requested deletion' END WHERE memory_id=$1::uuid`, event.AggregateID, deletedMemoryMarker, hex.EncodeToString(digest[:])); err != nil {
|
|
return fmt.Errorf("redact memory versions: %w", err)
|
|
}
|
|
if _, err := tx.Exec(ctx, `UPDATE curated_memory SET status='DELETED',deleted_at=now(),valid_to=COALESCE(valid_to,now()),version=version+1,updated_at=now() WHERE id=$1::uuid`, event.AggregateID); err != nil {
|
|
return fmt.Errorf("complete memory deletion: %w", err)
|
|
}
|
|
return p.audit.Record(ctx, tx, audit.Event{TenantID: &tenantID, ActorType: "system", Action: "memory.delete.complete", ResourceType: "memory", ResourceID: &memoryPublicID, Result: "success", Detail: map[string]any{"local_index": "deleted", "database_content": "redacted"}})
|
|
})
|
|
}
|