package database import ( "fmt" "gorm.io/gorm" "gorm.io/gorm/clause" ) // omitAssociationsCallback is the name the association guard is // registered under on a per-webhook database's create and update // callback chains. const omitAssociationsCallback = "webhooker:omit_associations" // omitAssociations makes every create and update issued against a // per-webhook database skip GORM's automatic association save. // // A per-webhook database holds the event tier only, but Delivery // declares belongs-to Event and Target and the delivery engine fills // both in memory before writing. Without this guard GORM upserts those // parent rows here on the delivery and retry write paths, and the // target, whose config holds destination URLs and bearer credentials, // goes to a targets table this file does not have, which fails the // write. Registering the guard on the connection // covers every write path, including writes inside a transaction and // write paths added later. Every event-tier row this file holds is // written explicitly, so nothing depends on the automatic save. func omitAssociations(db *gorm.DB) error { omit := func(tx *gorm.DB) { tx.Statement.Omits = append( tx.Statement.Omits, clause.Associations, ) } err := db.Callback().Create(). Before("gorm:save_before_associations"). Register(omitAssociationsCallback, omit) if err != nil { return fmt.Errorf( "registering create association guard: %w", err, ) } err = db.Callback().Update(). Before("gorm:save_before_associations"). Register(omitAssociationsCallback, omit) if err != nil { return fmt.Errorf( "registering update association guard: %w", err, ) } return nil }