feat(workload): write-through workload sync + boot-time backfill

CRUD on Project / Stack / StaticSite now keeps a paired Workload
row in sync. Secret setters (webhook secret, signing secret,
require-signature toggle, notification secret) all re-sync after
mutating the source-of-truth row so the workload row always
reflects the canonical state.

Delete cascades: DeleteProject/Stack/StaticSite now drop the
matching workload row plus any container index entries owned by
it, so global views don't show ghost rows.

Boot-time BackfillWorkloads scans every project/stack/site and
ensures each has a workload row. Idempotent — safe to run on
every restart, recovers from a deleted/missing workload row.

Behavior unchanged for existing call sites; the workloads table
just starts being populated. Deployer / reconciler / consumer
switchover land in the next commit.
This commit is contained in:
2026-05-09 13:28:20 +03:00
parent f54a6ecee3
commit db235c1412
7 changed files with 410 additions and 18 deletions
+7
View File
@@ -73,6 +73,13 @@ func main() {
os.Exit(1) os.Exit(1)
} }
// Backfill workload rows for any project / stack / static site that
// predates the workload refactor. Idempotent — safe on every boot.
if err := db.BackfillWorkloads(); err != nil {
slog.Error("workload backfill", "error", err)
os.Exit(1)
}
// Ensure default admin user exists on first launch. // Ensure default admin user exists on first launch.
if err := ensureDefaultAdmin(db); err != nil { if err := ensureDefaultAdmin(db); err != nil {
slog.Error("ensure default admin", "error", err) slog.Error("ensure default admin", "error", err)
+42 -4
View File
@@ -79,6 +79,9 @@ func (s *Store) CreateProject(p Project) (Project, error) {
if err != nil { if err != nil {
return Project{}, fmt.Errorf("insert project: %w", err) return Project{}, fmt.Errorf("insert project: %w", err)
} }
if err := s.SyncProjectWorkload(p); err != nil {
return Project{}, fmt.Errorf("sync project workload: %w", err)
}
return p, nil return p, nil
} }
@@ -173,6 +176,15 @@ func (s *Store) UpdateProject(p Project) error {
if n == 0 { if n == 0 {
return fmt.Errorf("project %s: %w", p.ID, ErrNotFound) return fmt.Errorf("project %s: %w", p.ID, ErrNotFound)
} }
// Re-read so the workload sync sees the canonical row (e.g. webhook
// secrets that UpdateProject does not write but other call sites do).
current, err := s.GetProjectByID(p.ID)
if err != nil {
return fmt.Errorf("reread project for workload sync: %w", err)
}
if err := s.SyncProjectWorkload(current); err != nil {
return fmt.Errorf("sync project workload: %w", err)
}
return nil return nil
} }
@@ -190,7 +202,11 @@ func (s *Store) SetProjectWebhookSecret(id, secret string) error {
if n == 0 { if n == 0 {
return fmt.Errorf("project %s: %w", id, ErrNotFound) return fmt.Errorf("project %s: %w", id, ErrNotFound)
} }
return nil current, err := s.GetProjectByID(id)
if err != nil {
return fmt.Errorf("reread project for workload sync: %w", err)
}
return s.SyncProjectWorkload(current)
} }
// SetProjectWebhookSigningSecret assigns the HMAC signing secret used to // SetProjectWebhookSigningSecret assigns the HMAC signing secret used to
@@ -208,7 +224,11 @@ func (s *Store) SetProjectWebhookSigningSecret(id, secret string) error {
if n == 0 { if n == 0 {
return fmt.Errorf("project %s: %w", id, ErrNotFound) return fmt.Errorf("project %s: %w", id, ErrNotFound)
} }
return nil current, err := s.GetProjectByID(id)
if err != nil {
return fmt.Errorf("reread project for workload sync: %w", err)
}
return s.SyncProjectWorkload(current)
} }
// SetProjectWebhookRequireSignature toggles whether unsigned (or // SetProjectWebhookRequireSignature toggles whether unsigned (or
@@ -229,7 +249,11 @@ func (s *Store) SetProjectWebhookRequireSignature(id string, require bool) error
if n == 0 { if n == 0 {
return fmt.Errorf("project %s: %w", id, ErrNotFound) return fmt.Errorf("project %s: %w", id, ErrNotFound)
} }
return nil current, err := s.GetProjectByID(id)
if err != nil {
return fmt.Errorf("reread project for workload sync: %w", err)
}
return s.SyncProjectWorkload(current)
} }
// EnsureProjectWebhookSecret returns the current webhook secret for a project, // EnsureProjectWebhookSecret returns the current webhook secret for a project,
@@ -265,7 +289,11 @@ func (s *Store) SetProjectNotificationSecret(id, secret string) error {
if n == 0 { if n == 0 {
return fmt.Errorf("project %s: %w", id, ErrNotFound) return fmt.Errorf("project %s: %w", id, ErrNotFound)
} }
return nil current, err := s.GetProjectByID(id)
if err != nil {
return fmt.Errorf("reread project for workload sync: %w", err)
}
return s.SyncProjectWorkload(current)
} }
// EnsureProjectNotificationSecret returns the current outgoing-webhook signing // EnsureProjectNotificationSecret returns the current outgoing-webhook signing
@@ -287,6 +315,8 @@ func (s *Store) EnsureProjectNotificationSecret(id string) (string, error) {
} }
// DeleteProject removes a project by ID. Cascading deletes handle stages, instances, and deploys. // DeleteProject removes a project by ID. Cascading deletes handle stages, instances, and deploys.
// Workload row + container index entries are removed too so the global views
// don't show ghost rows after a project is gone.
func (s *Store) DeleteProject(id string) error { func (s *Store) DeleteProject(id string) error {
result, err := s.db.Exec(`DELETE FROM projects WHERE id = ?`, id) result, err := s.db.Exec(`DELETE FROM projects WHERE id = ?`, id)
if err != nil { if err != nil {
@@ -296,5 +326,13 @@ func (s *Store) DeleteProject(id string) error {
if n == 0 { if n == 0 {
return fmt.Errorf("project %s: %w", id, ErrNotFound) return fmt.Errorf("project %s: %w", id, ErrNotFound)
} }
if w, err := s.GetWorkloadByRef(WorkloadKindProject, id); err == nil {
if err := s.DeleteContainersByWorkload(w.ID); err != nil {
return fmt.Errorf("delete project containers: %w", err)
}
if err := s.DeleteWorkload(w.ID); err != nil {
return fmt.Errorf("delete project workload: %w", err)
}
}
return nil return nil
} }
+13 -1
View File
@@ -29,6 +29,9 @@ func (s *Store) CreateStack(st Stack) (Stack, error) {
if err != nil { if err != nil {
return Stack{}, fmt.Errorf("insert stack: %w", err) return Stack{}, fmt.Errorf("insert stack: %w", err)
} }
if err := s.SyncStackWorkload(st); err != nil {
return Stack{}, fmt.Errorf("sync stack workload: %w", err)
}
return st, nil return st, nil
} }
@@ -79,7 +82,7 @@ func (s *Store) UpdateStack(st Stack) error {
if n == 0 { if n == 0 {
return fmt.Errorf("stack %s: %w", st.ID, ErrNotFound) return fmt.Errorf("stack %s: %w", st.ID, ErrNotFound)
} }
return nil return s.SyncStackWorkload(st)
} }
// UpdateStackStatus updates the deployment status + error fields. // UpdateStackStatus updates the deployment status + error fields.
@@ -117,6 +120,7 @@ func (s *Store) SetStackCurrentRevision(id, revisionID string) error {
} }
// DeleteStack removes a stack by ID. Cascading deletes handle revisions + deploys. // DeleteStack removes a stack by ID. Cascading deletes handle revisions + deploys.
// Workload row + container index entries are removed too.
func (s *Store) DeleteStack(id string) error { func (s *Store) DeleteStack(id string) error {
result, err := s.db.Exec(`DELETE FROM stacks WHERE id = ?`, id) result, err := s.db.Exec(`DELETE FROM stacks WHERE id = ?`, id)
if err != nil { if err != nil {
@@ -126,6 +130,14 @@ func (s *Store) DeleteStack(id string) error {
if n == 0 { if n == 0 {
return fmt.Errorf("stack %s: %w", id, ErrNotFound) return fmt.Errorf("stack %s: %w", id, ErrNotFound)
} }
if w, err := s.GetWorkloadByRef(WorkloadKindStack, id); err == nil {
if err := s.DeleteContainersByWorkload(w.ID); err != nil {
return fmt.Errorf("delete stack containers: %w", err)
}
if err := s.DeleteWorkload(w.ID); err != nil {
return fmt.Errorf("delete stack workload: %w", err)
}
}
return nil return nil
} }
+37 -5
View File
@@ -45,6 +45,9 @@ func (s *Store) CreateStaticSite(site StaticSite) (StaticSite, error) {
if err != nil { if err != nil {
return StaticSite{}, fmt.Errorf("insert static site: %w", err) return StaticSite{}, fmt.Errorf("insert static site: %w", err)
} }
if err := s.SyncStaticSiteWorkload(site); err != nil {
return StaticSite{}, fmt.Errorf("sync static site workload: %w", err)
}
return site, nil return site, nil
} }
@@ -131,7 +134,11 @@ func (s *Store) UpdateStaticSite(site StaticSite) error {
if n == 0 { if n == 0 {
return fmt.Errorf("static site %s: %w", site.ID, ErrNotFound) return fmt.Errorf("static site %s: %w", site.ID, ErrNotFound)
} }
return nil current, err := s.GetStaticSiteByID(site.ID)
if err != nil {
return fmt.Errorf("reread static site for workload sync: %w", err)
}
return s.SyncStaticSiteWorkload(current)
} }
// UpdateStaticSiteStatus updates the deployment status fields. // UpdateStaticSiteStatus updates the deployment status fields.
@@ -214,6 +221,7 @@ func (s *Store) ListStaticSiteProxyRoutes(domain string) ([]ProxyRoute, error) {
} }
// DeleteStaticSite removes a static site by ID. Cascading deletes handle secrets. // DeleteStaticSite removes a static site by ID. Cascading deletes handle secrets.
// Workload row + container index entries are removed too.
func (s *Store) DeleteStaticSite(id string) error { func (s *Store) DeleteStaticSite(id string) error {
result, err := s.db.Exec(`DELETE FROM static_sites WHERE id = ?`, id) result, err := s.db.Exec(`DELETE FROM static_sites WHERE id = ?`, id)
if err != nil { if err != nil {
@@ -223,6 +231,14 @@ func (s *Store) DeleteStaticSite(id string) error {
if n == 0 { if n == 0 {
return fmt.Errorf("static site %s: %w", id, ErrNotFound) return fmt.Errorf("static site %s: %w", id, ErrNotFound)
} }
if w, err := s.GetWorkloadByRef(WorkloadKindSite, id); err == nil {
if err := s.DeleteContainersByWorkload(w.ID); err != nil {
return fmt.Errorf("delete static site containers: %w", err)
}
if err := s.DeleteWorkload(w.ID); err != nil {
return fmt.Errorf("delete static site workload: %w", err)
}
}
return nil return nil
} }
@@ -286,7 +302,11 @@ func (s *Store) SetStaticSiteWebhookSigningSecret(id, secret string) error {
if n == 0 { if n == 0 {
return fmt.Errorf("static site %s: %w", id, ErrNotFound) return fmt.Errorf("static site %s: %w", id, ErrNotFound)
} }
return nil current, err := s.GetStaticSiteByID(id)
if err != nil {
return fmt.Errorf("reread static site for workload sync: %w", err)
}
return s.SyncStaticSiteWorkload(current)
} }
// SetStaticSiteWebhookRequireSignature toggles whether unsigned (or // SetStaticSiteWebhookRequireSignature toggles whether unsigned (or
@@ -307,7 +327,11 @@ func (s *Store) SetStaticSiteWebhookRequireSignature(id string, require bool) er
if n == 0 { if n == 0 {
return fmt.Errorf("static site %s: %w", id, ErrNotFound) return fmt.Errorf("static site %s: %w", id, ErrNotFound)
} }
return nil current, err := s.GetStaticSiteByID(id)
if err != nil {
return fmt.Errorf("reread static site for workload sync: %w", err)
}
return s.SyncStaticSiteWorkload(current)
} }
// SetStaticSiteNotificationSecret rotates the static site's outgoing-webhook // SetStaticSiteNotificationSecret rotates the static site's outgoing-webhook
@@ -325,7 +349,11 @@ func (s *Store) SetStaticSiteNotificationSecret(id, secret string) error {
if n == 0 { if n == 0 {
return fmt.Errorf("static site %s: %w", id, ErrNotFound) return fmt.Errorf("static site %s: %w", id, ErrNotFound)
} }
return nil current, err := s.GetStaticSiteByID(id)
if err != nil {
return fmt.Errorf("reread static site for workload sync: %w", err)
}
return s.SyncStaticSiteWorkload(current)
} }
// EnsureStaticSiteNotificationSecret returns the static site's outgoing-webhook // EnsureStaticSiteNotificationSecret returns the static site's outgoing-webhook
@@ -394,7 +422,11 @@ func (s *Store) SetStaticSiteWebhookSecret(id, secret string) error {
if n == 0 { if n == 0 {
return fmt.Errorf("static site %s: %w", id, ErrNotFound) return fmt.Errorf("static site %s: %w", id, ErrNotFound)
} }
return nil current, err := s.GetStaticSiteByID(id)
if err != nil {
return fmt.Errorf("reread static site for workload sync: %w", err)
}
return s.SyncStaticSiteWorkload(current)
} }
// EnsureStaticSiteWebhookSecret returns the current webhook secret for a site, // EnsureStaticSiteWebhookSecret returns the current webhook secret for a site,
+119
View File
@@ -0,0 +1,119 @@
package store
import "fmt"
// SyncProjectWorkload upserts the Workload row paired with a project so that
// its name, notification config, and webhook secrets stay in sync. Called from
// CreateProject / UpdateProject / SetProject*Secret paths. Idempotent — safe
// to call when a workload row already exists for the (project, RefID) pair.
func (s *Store) SyncProjectWorkload(p Project) error {
existing, err := s.GetWorkloadByRef(WorkloadKindProject, p.ID)
if err == nil {
existing.Name = p.Name
existing.NotificationURL = p.NotificationURL
existing.NotificationSecret = p.NotificationSecret
existing.WebhookSecret = p.WebhookSecret
existing.WebhookSigningSecret = p.WebhookSigningSecret
existing.WebhookRequireSignature = p.WebhookRequireSignature
return s.UpdateWorkload(existing)
}
_, err = s.CreateWorkload(Workload{
Kind: string(WorkloadKindProject),
RefID: p.ID,
Name: p.Name,
NotificationURL: p.NotificationURL,
NotificationSecret: p.NotificationSecret,
WebhookSecret: p.WebhookSecret,
WebhookSigningSecret: p.WebhookSigningSecret,
WebhookRequireSignature: p.WebhookRequireSignature,
})
if err != nil {
return fmt.Errorf("create project workload: %w", err)
}
return nil
}
// SyncStackWorkload upserts the Workload row paired with a stack. Stacks
// don't (yet) carry their own notification or webhook config — those fields
// stay empty on the workload row until the stack model gains them.
func (s *Store) SyncStackWorkload(st Stack) error {
existing, err := s.GetWorkloadByRef(WorkloadKindStack, st.ID)
if err == nil {
existing.Name = st.Name
return s.UpdateWorkload(existing)
}
_, err = s.CreateWorkload(Workload{
Kind: string(WorkloadKindStack),
RefID: st.ID,
Name: st.Name,
})
if err != nil {
return fmt.Errorf("create stack workload: %w", err)
}
return nil
}
// SyncStaticSiteWorkload upserts the Workload row paired with a static site.
func (s *Store) SyncStaticSiteWorkload(site StaticSite) error {
existing, err := s.GetWorkloadByRef(WorkloadKindSite, site.ID)
if err == nil {
existing.Name = site.Name
existing.NotificationURL = site.NotificationURL
existing.NotificationSecret = site.NotificationSecret
existing.WebhookSecret = site.WebhookSecret
existing.WebhookSigningSecret = site.WebhookSigningSecret
existing.WebhookRequireSignature = site.WebhookRequireSignature
return s.UpdateWorkload(existing)
}
_, err = s.CreateWorkload(Workload{
Kind: string(WorkloadKindSite),
RefID: site.ID,
Name: site.Name,
NotificationURL: site.NotificationURL,
NotificationSecret: site.NotificationSecret,
WebhookSecret: site.WebhookSecret,
WebhookSigningSecret: site.WebhookSigningSecret,
WebhookRequireSignature: site.WebhookRequireSignature,
})
if err != nil {
return fmt.Errorf("create static site workload: %w", err)
}
return nil
}
// BackfillWorkloads scans every project / stack / static_site row and ensures
// each has a matching workload row. Called once at boot before HTTP starts so
// any pre-Workload-refactor data is upgraded transparently. Idempotent.
func (s *Store) BackfillWorkloads() error {
projects, err := s.GetAllProjects()
if err != nil {
return fmt.Errorf("backfill: list projects: %w", err)
}
for _, p := range projects {
if err := s.SyncProjectWorkload(p); err != nil {
return fmt.Errorf("backfill project %s: %w", p.ID, err)
}
}
stacks, err := s.GetAllStacks()
if err != nil {
return fmt.Errorf("backfill: list stacks: %w", err)
}
for _, st := range stacks {
if err := s.SyncStackWorkload(st); err != nil {
return fmt.Errorf("backfill stack %s: %w", st.ID, err)
}
}
sites, err := s.GetAllStaticSites()
if err != nil {
return fmt.Errorf("backfill: list static sites: %w", err)
}
for _, site := range sites {
if err := s.SyncStaticSiteWorkload(site); err != nil {
return fmt.Errorf("backfill static site %s: %w", site.ID, err)
}
}
return nil
}
+190
View File
@@ -0,0 +1,190 @@
package store
import (
"errors"
"testing"
)
func TestCreateProjectAlsoCreatesWorkload(t *testing.T) {
s := newTestStore(t)
p, err := s.CreateProject(Project{
Name: "wf-project", Image: "nginx", Port: 80, Env: "{}", Volumes: "{}",
NotificationURL: "https://example.test/hook",
})
if err != nil {
t.Fatalf("CreateProject: %v", err)
}
w, err := s.GetWorkloadByRef(WorkloadKindProject, p.ID)
if err != nil {
t.Fatalf("workload should exist after CreateProject: %v", err)
}
if w.Name != "wf-project" {
t.Fatalf("workload name not synced: got %q", w.Name)
}
if w.WebhookSecret == "" {
t.Fatal("webhook secret should be carried into workload row")
}
if w.NotificationURL != "https://example.test/hook" {
t.Fatalf("notification url not synced: got %q", w.NotificationURL)
}
}
func TestUpdateProjectSyncsWorkload(t *testing.T) {
s := newTestStore(t)
p, _ := s.CreateProject(Project{
Name: "before", Image: "i", Env: "{}", Volumes: "{}",
})
p.Name = "after"
p.NotificationURL = "https://new.test/hook"
if err := s.UpdateProject(p); err != nil {
t.Fatalf("UpdateProject: %v", err)
}
w, _ := s.GetWorkloadByRef(WorkloadKindProject, p.ID)
if w.Name != "after" {
t.Fatalf("workload name not updated: got %q", w.Name)
}
if w.NotificationURL != "https://new.test/hook" {
t.Fatalf("workload notification url not updated: got %q", w.NotificationURL)
}
}
func TestDeleteProjectCascadesWorkload(t *testing.T) {
s := newTestStore(t)
p, _ := s.CreateProject(Project{Name: "doomed", Image: "i", Env: "{}", Volumes: "{}"})
w, _ := s.GetWorkloadByRef(WorkloadKindProject, p.ID)
// Add a container under this workload to verify cascade.
if _, err := s.CreateContainer(Container{
WorkloadID: w.ID, WorkloadKind: "project", State: "running",
}); err != nil {
t.Fatalf("CreateContainer: %v", err)
}
if err := s.DeleteProject(p.ID); err != nil {
t.Fatalf("DeleteProject: %v", err)
}
if _, err := s.GetWorkloadByID(w.ID); !errors.Is(err, ErrNotFound) {
t.Fatalf("workload should be deleted, got %v", err)
}
containers, _ := s.ListContainersByWorkload(w.ID)
if len(containers) != 0 {
t.Fatalf("containers should be deleted, got %d", len(containers))
}
}
func TestSetProjectWebhookSecretSyncsWorkload(t *testing.T) {
s := newTestStore(t)
p, _ := s.CreateProject(Project{Name: "n", Image: "i", Env: "{}", Volumes: "{}"})
newSecret := "new-secret-value-with-enough-entropy-1234"
if err := s.SetProjectWebhookSecret(p.ID, newSecret); err != nil {
t.Fatalf("SetProjectWebhookSecret: %v", err)
}
w, _ := s.GetWorkloadByRef(WorkloadKindProject, p.ID)
if w.WebhookSecret != newSecret {
t.Fatalf("workload webhook secret not synced: got %q", w.WebhookSecret)
}
}
func TestCreateStackAlsoCreatesWorkload(t *testing.T) {
s := newTestStore(t)
st, err := s.CreateStack(Stack{Name: "wf-stack", ComposeProjectName: "wf-stack"})
if err != nil {
t.Fatalf("CreateStack: %v", err)
}
w, err := s.GetWorkloadByRef(WorkloadKindStack, st.ID)
if err != nil {
t.Fatalf("workload should exist after CreateStack: %v", err)
}
if w.Name != "wf-stack" {
t.Fatalf("workload name not synced: got %q", w.Name)
}
}
func TestUpdateStackSyncsWorkload(t *testing.T) {
s := newTestStore(t)
st, _ := s.CreateStack(Stack{Name: "before", ComposeProjectName: "before-cp"})
st.Name = "after"
if err := s.UpdateStack(st); err != nil {
t.Fatalf("UpdateStack: %v", err)
}
w, _ := s.GetWorkloadByRef(WorkloadKindStack, st.ID)
if w.Name != "after" {
t.Fatalf("workload name not updated: got %q", w.Name)
}
}
func TestDeleteStackCascadesWorkload(t *testing.T) {
s := newTestStore(t)
st, _ := s.CreateStack(Stack{Name: "doomed-stack", ComposeProjectName: "doomed-cp"})
w, _ := s.GetWorkloadByRef(WorkloadKindStack, st.ID)
if err := s.DeleteStack(st.ID); err != nil {
t.Fatalf("DeleteStack: %v", err)
}
if _, err := s.GetWorkloadByID(w.ID); !errors.Is(err, ErrNotFound) {
t.Fatalf("workload should be deleted, got %v", err)
}
}
func TestBackfillWorkloadsIdempotent(t *testing.T) {
s := newTestStore(t)
// Create rows directly via the store (which already auto-syncs), then run
// the backfill twice — it must be a no-op the second time and not error.
p, _ := s.CreateProject(Project{Name: "p1", Image: "i", Env: "{}", Volumes: "{}"})
st, _ := s.CreateStack(Stack{Name: "s1", ComposeProjectName: "s1-cp"})
if err := s.BackfillWorkloads(); err != nil {
t.Fatalf("first backfill: %v", err)
}
if err := s.BackfillWorkloads(); err != nil {
t.Fatalf("second backfill (should be idempotent): %v", err)
}
all, _ := s.ListWorkloads("")
// Expect exactly 2: one project workload, one stack workload, no duplicates.
if len(all) != 2 {
t.Fatalf("expected 2 workloads after backfill, got %d", len(all))
}
// Confirm both refs are findable.
if _, err := s.GetWorkloadByRef(WorkloadKindProject, p.ID); err != nil {
t.Fatalf("project workload not found: %v", err)
}
if _, err := s.GetWorkloadByRef(WorkloadKindStack, st.ID); err != nil {
t.Fatalf("stack workload not found: %v", err)
}
}
func TestBackfillRecoversFromMissingWorkloads(t *testing.T) {
s := newTestStore(t)
p, _ := s.CreateProject(Project{Name: "p1", Image: "i", Env: "{}", Volumes: "{}"})
// Simulate the legacy state: a project exists but its workload row is gone
// (e.g. the rollout from before the refactor). Backfill must restore it.
w, _ := s.GetWorkloadByRef(WorkloadKindProject, p.ID)
_ = s.DeleteWorkload(w.ID)
if err := s.BackfillWorkloads(); err != nil {
t.Fatalf("backfill: %v", err)
}
if _, err := s.GetWorkloadByRef(WorkloadKindProject, p.ID); err != nil {
t.Fatalf("workload should be restored: %v", err)
}
}
+2 -8
View File
@@ -41,7 +41,7 @@ func (s *Store) CreateWorkload(w Workload) (Workload, error) {
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
w.ID, w.Kind, w.RefID, w.Name, w.AppID, w.ID, w.Kind, w.RefID, w.Name, w.AppID,
w.NotificationURL, w.NotificationSecret, w.NotificationURL, w.NotificationSecret,
w.WebhookSecret, w.WebhookSigningSecret, boolToInt(w.WebhookRequireSignature), w.WebhookSecret, w.WebhookSigningSecret, BoolToInt(w.WebhookRequireSignature),
w.CreatedAt, w.UpdatedAt, w.CreatedAt, w.UpdatedAt,
) )
if err != nil { if err != nil {
@@ -142,7 +142,7 @@ func (s *Store) UpdateWorkload(w Workload) error {
WHERE id=?`, WHERE id=?`,
w.Name, w.AppID, w.Name, w.AppID,
w.NotificationURL, w.NotificationSecret, w.NotificationURL, w.NotificationSecret,
w.WebhookSecret, w.WebhookSigningSecret, boolToInt(w.WebhookRequireSignature), w.WebhookSecret, w.WebhookSigningSecret, BoolToInt(w.WebhookRequireSignature),
w.UpdatedAt, w.ID, w.UpdatedAt, w.ID,
) )
if err != nil { if err != nil {
@@ -184,9 +184,3 @@ func (s *Store) DeleteWorkloadByRef(kind WorkloadKind, refID string) error {
return nil return nil
} }
func boolToInt(b bool) int {
if b {
return 1
}
return 0
}