package api import ( "encoding/json" "log/slog" "github.com/alexei/tinyforge/internal/store" "github.com/alexei/tinyforge/internal/workload/plugin" ) // toPluginWorkload converts a persisted store.Workload row into the value // shape that Source / Trigger plugins consume. Lives in the api package // (rather than store or plugin) to keep plugin's dependency graph free of // store imports and avoid the cycle that would form otherwise. // // 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). func toPluginWorkload(w store.Workload) plugin.Workload { var faces []plugin.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 plugin.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, } } // fromPluginWorkload is the symmetric direction — used by /api/workloads // create + update handlers. Returns a store.Workload ready to pass to // store.CreateWorkload / store.UpdateWorkload. The caller is responsible // for re-encoding PublicFaces; we do it here to keep the JSON shape in // one place. func fromPluginWorkload(p plugin.Workload) (store.Workload, error) { facesJSON := "[]" if len(p.PublicFaces) > 0 { b, err := json.Marshal(p.PublicFaces) if err != nil { return store.Workload{}, err } facesJSON = string(b) } srcCfg := string(p.SourceConfig) if srcCfg == "" { srcCfg = "{}" } trgCfg := string(p.TriggerConfig) if trgCfg == "" { trgCfg = "{}" } return store.Workload{ ID: p.ID, Name: p.Name, AppID: p.GroupID, ParentWorkloadID: p.ParentWorkloadID, SourceKind: p.SourceKind, SourceConfig: srcCfg, TriggerKind: p.TriggerKind, TriggerConfig: trgCfg, PublicFaces: facesJSON, NotificationURL: p.NotificationURL, NotificationSecret: p.NotificationSecret, WebhookSecret: p.WebhookSecret, WebhookSigningSecret: p.WebhookSigningSecret, WebhookRequireSignature: p.WebhookRequireSignature, CreatedAt: p.CreatedAt, UpdatedAt: p.UpdatedAt, }, nil }