CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (push) Canceled after 0s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (push) Canceled after 0s
CI / Core-Modul (Build/Vet/Test) (push) Canceled after 0s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (push) Canceled after 0s
CI / Web (dms-browser) (push) Canceled after 0s
CI / Web (pdf-viewer) (push) Canceled after 0s
CI / Web (shl) (push) Canceled after 0s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (push) Canceled after 0s
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
115 lines
4.0 KiB
Go
115 lines
4.0 KiB
Go
package trigger
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"log"
|
|
"regexp"
|
|
"time"
|
|
|
|
"github.com/jackc/pgx/v5"
|
|
"github.com/jackc/pgx/v5/pgxpool"
|
|
|
|
"gitea.perlbach24.de/scripte/nexarch/dms/internal/audittrail"
|
|
"gitea.perlbach24.de/scripte/nexarch/dms/internal/documenttype"
|
|
"gitea.perlbach24.de/scripte/nexarch/dms/internal/tag"
|
|
)
|
|
|
|
// Fire wertet alle aktivierten Trigger fuer event gegen documentID aus und
|
|
// fuehrt die passenden Aktionen aus. Ein Fehler in einer Aktion oder beim
|
|
// Werten eines einzelnen Triggers wird in trigger_runs und dem Audit-Log
|
|
// festgehalten, bricht aber die Auswertung der uebrigen Trigger nicht ab und
|
|
// wird NIE an den Aufrufer zurueckgegeben (siehe Package-Doc) — Fire selbst
|
|
// gibt nur einen Fehler zurueck, wenn das Dokument nicht lesbar ist.
|
|
func Fire(ctx context.Context, pool *pgxpool.Pool, v Vault, tenantSlug, event, documentID string) error {
|
|
var title, typeID string
|
|
err := pool.QueryRow(ctx, `
|
|
SELECT title, COALESCE(document_type_id::text, '') FROM documents
|
|
WHERE id = $1 AND trashed_at IS NULL AND deleted_at IS NULL
|
|
`, documentID).Scan(&title, &typeID)
|
|
if err == pgx.ErrNoRows {
|
|
return nil // Dokument geloescht/im Papierkorb: kein Trigger feuert.
|
|
}
|
|
if err != nil {
|
|
return fmt.Errorf("trigger: dokument lesen: %w", err)
|
|
}
|
|
|
|
rows, err := pool.Query(ctx, `SELECT `+columns+`, webhook_secret_wrapped_dek, webhook_secret_enc FROM triggers WHERE event = $1 AND enabled`, event)
|
|
if err != nil {
|
|
return fmt.Errorf("trigger: liste lesen: %w", err)
|
|
}
|
|
type row struct {
|
|
Trigger
|
|
wrappedDEK, secretEnc []byte
|
|
}
|
|
var list []row
|
|
for rows.Next() {
|
|
var r row
|
|
if err := rows.Scan(&r.ID, &r.Name, &r.Event, &r.FilenamePattern, &r.DocumentTypeID, &r.ActionType, &r.ActionTagID, &r.ActionDocumentTypeID, &r.WebhookURL, &r.Enabled, &r.CreatedBy, &r.wrappedDEK, &r.secretEnc); err != nil {
|
|
rows.Close()
|
|
return fmt.Errorf("trigger: liste lesen: %w", err)
|
|
}
|
|
list = append(list, r)
|
|
}
|
|
rows.Close()
|
|
if err := rows.Err(); err != nil {
|
|
return err
|
|
}
|
|
|
|
for _, r := range list {
|
|
if r.FilenamePattern != nil && !regexp.MustCompile(*r.FilenamePattern).MatchString(title) {
|
|
continue
|
|
}
|
|
if r.DocumentTypeID != nil && *r.DocumentTypeID != typeID {
|
|
continue
|
|
}
|
|
runErr := runAction(ctx, pool, v, tenantSlug, r.Trigger, r.wrappedDEK, r.secretEnc, documentID)
|
|
status := "ok"
|
|
errText := ""
|
|
if runErr != nil {
|
|
status = "failed"
|
|
errText = runErr.Error()
|
|
log.Printf("trigger: aktion fuer %q an dokument %q fehlgeschlagen: %v", r.Name, documentID, runErr)
|
|
}
|
|
if _, logErr := pool.Exec(ctx, `
|
|
INSERT INTO trigger_runs (trigger_id, document_id, event, status, error, fired_at)
|
|
VALUES ($1, $2, $3, $4, NULLIF($5, ''), $6)
|
|
`, r.ID, documentID, event, status, errText, time.Now()); logErr != nil {
|
|
log.Printf("trigger: lauf konnte nicht protokolliert werden: %v", logErr)
|
|
}
|
|
if runErr != nil {
|
|
_ = audittrail.Record(ctx, pool, audittrail.SystemActor, "trigger_failed", "trigger", r.ID, map[string]any{"document_id": documentID, "error": errText})
|
|
} else {
|
|
_ = audittrail.Record(ctx, pool, audittrail.SystemActor, "trigger_fired", "trigger", r.ID, map[string]any{"document_id": documentID, "action": r.ActionType})
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func runAction(ctx context.Context, pool *pgxpool.Pool, v Vault, tenantSlug string, t Trigger, wrappedDEK, secretEnc []byte, documentID string) error {
|
|
switch t.ActionType {
|
|
case ActionSetTag:
|
|
return tag.Assign(ctx, pool, documentID, *t.ActionTagID)
|
|
case ActionSetType:
|
|
_, err := documenttype.ChangeDocumentType(ctx, pool, documentID, *t.ActionDocumentTypeID, false)
|
|
return err
|
|
case ActionWebhook:
|
|
secret := ""
|
|
if len(secretEnc) > 0 {
|
|
var err error
|
|
secret, err = v.Open(ctx, tenantSlug, wrappedDEK, secretEnc)
|
|
if err != nil {
|
|
return fmt.Errorf("geheimnis entschluesseln: %w", err)
|
|
}
|
|
}
|
|
return sendWebhook(ctx, *t.WebhookURL, secret, webhookPayload{
|
|
Event: t.Event,
|
|
DocumentID: documentID,
|
|
TriggerID: t.ID,
|
|
OccurredAt: time.Now().UTC().Format(time.RFC3339),
|
|
})
|
|
default:
|
|
return fmt.Errorf("unbekannte aktion %q", t.ActionType)
|
|
}
|
|
}
|