Files
story/internal/memorylifecycle/delete_processor.go
T

58 lines
2.4 KiB
Go
Raw Normal View History

2026-08-26 06:41:07 +08:00
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
}
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"}})
})
}