package plugin import ( "encoding/json" "log/slog" "github.com/alexei/tinyforge/internal/store" ) // WorkloadFromStore converts a persisted store.Workload row into the value // shape that Source / Trigger plugins consume. It is the single converter // shared by every caller (api, reconciler, webhook) — previously each kept // its own byte-identical copy, which drifted (only the api copy logged bad // PublicFaces JSON; the others swallowed it). // // Living in the plugin package is safe: plugin already imports store (Deps // holds a *store.Store), so this adds no new edge to the dependency graph // and store does not import plugin. // // SourceConfig / TriggerConfig are passed through as raw JSON; the matching // plugin decodes them with plugin.SourceConfigOf[T] / TriggerConfigOf[T]. // PublicFaces is decoded eagerly because every consumer needs the parsed // slice (proxy registration, UI, validation); invalid JSON is logged and // treated as empty rather than failing the conversion. func WorkloadFromStore(w store.Workload) Workload { var faces []PublicFace if w.PublicFaces != "" { if err := json.Unmarshal([]byte(w.PublicFaces), &faces); err != nil { slog.Warn("workload: invalid public_faces JSON, treating as empty", "workload", w.ID, "error", err) faces = nil } } return Workload{ ID: w.ID, Name: w.Name, GroupID: w.AppID, ParentWorkloadID: w.ParentWorkloadID, SourceKind: w.SourceKind, SourceConfig: json.RawMessage(w.SourceConfig), TriggerKind: w.TriggerKind, TriggerConfig: json.RawMessage(w.TriggerConfig), PublicFaces: faces, NotificationURL: w.NotificationURL, NotificationSecret: w.NotificationSecret, WebhookSecret: w.WebhookSecret, WebhookSigningSecret: w.WebhookSigningSecret, WebhookRequireSignature: w.WebhookRequireSignature, CreatedAt: w.CreatedAt, UpdatedAt: w.UpdatedAt, } }