package data import ( "context" "encoding/json" "errors" "fmt" "kra/app/system/internal/conf" "google.golang.org/protobuf/encoding/protojson" "google.golang.org/protobuf/proto" "gorm.io/gorm" ) const integrationKindWebSocket = "websocket" // saveWebSocketIntegrationConfig persists the Melody settings in the shared // integration table. It intentionally lives separately from storage/payment // persistence so adding another transport does not expand their API surface. func saveWebSocketIntegrationConfig(db *gorm.DB, config *conf.AdminBackend_WebSocket) error { if config == nil { config = &conf.AdminBackend_WebSocket{} } raw, err := protojson.MarshalOptions{UseProtoNames: true, EmitDefaultValues: true}.Marshal(config) if err != nil { return fmt.Errorf("encode websocket integration configuration: %w", err) } clean := db.Session(&gorm.Session{NewDB: true}) var current integrationConfigPO err = clean.Where("kind = ? AND provider = ?", integrationKindWebSocket, "melody").First(¤t).Error switch { case errors.Is(err, gorm.ErrRecordNotFound): return clean.Create(&integrationConfigPO{Kind: integrationKindWebSocket, Provider: "melody", Enabled: config.Enabled, Config: string(raw)}).Error case err != nil: return err default: return clean.Model(¤t).Updates(map[string]any{"enabled": config.Enabled, "config": string(raw)}).Error } } func loadWebSocketIntegrationConfig(db *gorm.DB) (*conf.AdminBackend_WebSocket, bool, error) { var row integrationConfigPO err := db.Session(&gorm.Session{NewDB: true}).Where("kind = ? AND provider = ?", integrationKindWebSocket, "melody").First(&row).Error if errors.Is(err, gorm.ErrRecordNotFound) { return nil, false, nil } if err != nil { return nil, false, err } if !json.Valid([]byte(row.Config)) { return nil, false, errors.New("invalid websocket integration configuration") } config := &conf.AdminBackend_WebSocket{} if err = (protojson.UnmarshalOptions{DiscardUnknown: true}).Unmarshal([]byte(row.Config), config); err != nil { return nil, false, fmt.Errorf("decode websocket integration configuration: %w", err) } config.Enabled = row.Enabled return config, true, nil } func resolveWebSocketIntegrationConfig(db *gorm.DB, legacy *conf.AdminBackend_WebSocket) (*conf.AdminBackend_WebSocket, error) { clean := db.Session(&gorm.Session{NewDB: true}) if !clean.Migrator().HasTable(&integrationConfigPO{}) { if legacy == nil { return &conf.AdminBackend_WebSocket{}, nil } return proto.Clone(legacy).(*conf.AdminBackend_WebSocket), nil } config, found, err := loadWebSocketIntegrationConfig(clean) if err != nil { return nil, err } if found { return config, nil } if legacy == nil { legacy = &conf.AdminBackend_WebSocket{} } if err = saveWebSocketIntegrationConfig(clean, legacy); err != nil { return nil, err } config, _, err = loadWebSocketIntegrationConfig(clean) return config, err } func (d *Data) PersistWebSocketConfig(ctx context.Context, config *conf.AdminBackend_WebSocket) error { if !d.databaseReady.Load() { return errors.New("database is not initialized") } db := d.gormDB.WithContext(ctx) if !db.Migrator().HasTable(&integrationConfigPO{}) { return errors.New("integration configuration table does not exist") } return saveWebSocketIntegrationConfig(db, config) }