Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion pkg/instance/handler/instance_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -649,9 +649,19 @@ func (h *instanceHandler) UpdateAdvancedSettings(c *gin.Context) {
return
}

// Return persisted settings (full row) so partial PUT responses stay consistent.
persisted, err := h.instanceService.GetAdvancedSettings(instanceId)
if err != nil {
c.JSON(http.StatusOK, gin.H{
"message": "Advanced settings updated successfully",
"settings": settings,
})
return
}

c.JSON(http.StatusOK, gin.H{
"message": "Advanced settings updated successfully",
"settings": settings,
"settings": persisted,
})
}

Expand Down
18 changes: 12 additions & 6 deletions pkg/instance/model/instance_model.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,14 +35,15 @@ type Instance struct {
IgnoreStatus bool `json:"ignoreStatus" gorm:"default:false"`
}

// AdvancedSettings representa as configurações avançadas de uma instância
// AdvancedSettings representa as configurações avançadas de uma instância.
// Bool fields are pointers so omitted JSON keys are not written as false on PUT.
type AdvancedSettings struct {
AlwaysOnline bool `json:"alwaysOnline"`
RejectCall bool `json:"rejectCall"`
AlwaysOnline *bool `json:"alwaysOnline"`
RejectCall *bool `json:"rejectCall"`
MsgRejectCall string `json:"msgRejectCall"`
ReadMessages bool `json:"readMessages"`
IgnoreGroups bool `json:"ignoreGroups"`
IgnoreStatus bool `json:"ignoreStatus"`
ReadMessages *bool `json:"readMessages"`
IgnoreGroups *bool `json:"ignoreGroups"`
IgnoreStatus *bool `json:"ignoreStatus"`
}

func (m *Instance) BeforeCreate(tx *gorm.DB) (err error) {
Expand All @@ -51,3 +52,8 @@ func (m *Instance) BeforeCreate(tx *gorm.DB) (err error) {
}
return
}

// BoolPtr returns a pointer to v (helper for AdvancedSettings responses/tests).
func BoolPtr(v bool) *bool {
return &v
}
52 changes: 52 additions & 0 deletions pkg/instance/repository/advanced_settings_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package instance_repository

import (
"testing"

instance_model "github.com/evolution-foundation/evolution-go/pkg/instance/model"
)

func TestBuildAdvancedSettingsUpdates(t *testing.T) {
trueVal := true

t.Run("only AlwaysOnline", func(t *testing.T) {
updates := buildAdvancedSettingsUpdates(&instance_model.AdvancedSettings{
AlwaysOnline: &trueVal,
})
if len(updates) != 1 {
t.Fatalf("expected only always_online, got %#v", updates)
}
if updates["always_online"] != true {
t.Fatalf("always_online = %#v, want true", updates["always_online"])
}
})

t.Run("explicit false is written", func(t *testing.T) {
falseVal := false
updates := buildAdvancedSettingsUpdates(&instance_model.AdvancedSettings{
IgnoreGroups: &falseVal,
})
if len(updates) != 1 {
t.Fatalf("expected only ignore_groups, got %#v", updates)
}
if updates["ignore_groups"] != false {
t.Fatalf("ignore_groups = %#v, want false", updates["ignore_groups"])
}
})

t.Run("msgRejectCall non-empty", func(t *testing.T) {
updates := buildAdvancedSettingsUpdates(&instance_model.AdvancedSettings{
MsgRejectCall: "busy",
})
if updates["msg_reject_call"] != "busy" {
t.Fatalf("msg_reject_call = %#v, want busy", updates["msg_reject_call"])
}
})

t.Run("nil settings", func(t *testing.T) {
updates := buildAdvancedSettingsUpdates(nil)
if len(updates) != 0 {
t.Fatalf("expected empty map, got %#v", updates)
}
})
}
60 changes: 48 additions & 12 deletions pkg/instance/repository/instance_repository.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ type InstanceRepository interface {
UpdateQrcode(userId string, qr string) error
UpdateProxy(userId string, proxy string) error
UpdateJid(userId string, jid string) error
UpdateConnectSettings(instanceId string, updates map[string]interface{}) error
GetAllConnectedInstances() ([]*instance_model.Instance, error)
GetAllConnectedInstancesByClientName(clientName string) ([]*instance_model.Instance, error)
GetAll(clientName string) ([]*instance_model.Instance, error)
Expand Down Expand Up @@ -116,6 +117,17 @@ func (i *instanceRepository) UpdateJid(userId string, jid string) error {
return i.db.Model(&instance_model.Instance{}).Where("id = ?", userId).Update("jid", jid).Error
}

func (i *instanceRepository) UpdateConnectSettings(instanceId string, updates map[string]interface{}) error {
if len(updates) == 0 {
return nil
}
err := i.db.Model(&instance_model.Instance{}).Where("id = ?", instanceId).Updates(updates).Error
if err != nil {
logger.LogError("Error updating connect settings in DB: %v", err)
}
return err
}

func (i *instanceRepository) GetAllConnectedInstances() ([]*instance_model.Instance, error) {
var instances []*instance_model.Instance
err := i.db.Where("connected = ?", true).Find(&instances).Error
Expand Down Expand Up @@ -181,12 +193,12 @@ func (i *instanceRepository) GetAdvancedSettings(instanceId string) (*instance_m
}

settings := &instance_model.AdvancedSettings{
AlwaysOnline: instance.AlwaysOnline,
RejectCall: instance.RejectCall,
AlwaysOnline: instance_model.BoolPtr(instance.AlwaysOnline),
RejectCall: instance_model.BoolPtr(instance.RejectCall),
MsgRejectCall: instance.MsgRejectCall,
ReadMessages: instance.ReadMessages,
IgnoreGroups: instance.IgnoreGroups,
IgnoreStatus: instance.IgnoreStatus,
ReadMessages: instance_model.BoolPtr(instance.ReadMessages),
IgnoreGroups: instance_model.BoolPtr(instance.IgnoreGroups),
IgnoreStatus: instance_model.BoolPtr(instance.IgnoreStatus),
}

return settings, nil
Expand All @@ -198,13 +210,9 @@ func (i *instanceRepository) UpdateAdvancedSettings(instanceId string, settings
return fmt.Errorf("invalid UUID format: %v", err)
}

updates := map[string]interface{}{
"always_online": settings.AlwaysOnline,
"reject_call": settings.RejectCall,
"msg_reject_call": settings.MsgRejectCall,
"read_messages": settings.ReadMessages,
"ignore_groups": settings.IgnoreGroups,
"ignore_status": settings.IgnoreStatus,
updates := buildAdvancedSettingsUpdates(settings)
if len(updates) == 0 {
return nil
}

err := i.db.Model(&instance_model.Instance{}).Where("id = ?", instanceId).Updates(updates).Error
Expand All @@ -216,6 +224,34 @@ func (i *instanceRepository) UpdateAdvancedSettings(instanceId string, settings
return nil
}

// buildAdvancedSettingsUpdates only includes fields explicitly provided (*bool != nil).
// MsgRejectCall is always written on PUT so an empty string can clear the reject message.
func buildAdvancedSettingsUpdates(settings *instance_model.AdvancedSettings) map[string]interface{} {
updates := map[string]interface{}{}
if settings == nil {
return updates
}
if settings.AlwaysOnline != nil {
updates["always_online"] = *settings.AlwaysOnline
}
if settings.RejectCall != nil {
updates["reject_call"] = *settings.RejectCall
}
if settings.ReadMessages != nil {
updates["read_messages"] = *settings.ReadMessages
}
if settings.IgnoreGroups != nil {
updates["ignore_groups"] = *settings.IgnoreGroups
}
if settings.IgnoreStatus != nil {
updates["ignore_status"] = *settings.IgnoreStatus
}
if settings.MsgRejectCall != "" {
updates["msg_reject_call"] = settings.MsgRejectCall
}
return updates
}

func NewInstanceRepository(db *gorm.DB) InstanceRepository {
return &instanceRepository{
db: db,
Expand Down
76 changes: 76 additions & 0 deletions pkg/instance/service/connect_settings.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package instance_service

import (
"strings"

instance_model "github.com/evolution-foundation/evolution-go/pkg/instance/model"
event_types "github.com/evolution-foundation/evolution-go/pkg/internal/event_types"
)

// applyConnectSettings mutates instance only for fields explicitly provided.
// Empty subscribe keeps existing Events; defaults to MESSAGE only when Events is empty.
// Empty producer strings keep existing values; send "disabled" or "false" to turn off.
func applyConnectSettings(instance *instance_model.Instance, data *ConnectStruct) map[string]interface{} {
updates := map[string]interface{}{}
if instance == nil || data == nil {
return updates
}

if len(data.Subscribe) > 0 {
var subscribedEvents []string
if data.Subscribe[0] == "ALL" {
subscribedEvents = append(subscribedEvents, event_types.AllEventTypes...)
} else {
for _, arg := range data.Subscribe {
if !event_types.IsEventType(arg) {
continue
}
subscribedEvents = append(subscribedEvents, arg)
}
}
eventString := strings.Join(subscribedEvents, ",")
instance.Events = eventString
updates["events"] = eventString
} else if instance.Events == "" {
instance.Events = event_types.MESSAGE
updates["events"] = event_types.MESSAGE
}

if data.WebhookUrl != "" {
instance.Webhook = data.WebhookUrl
updates["webhook"] = data.WebhookUrl
}
if data.RabbitmqEnable != "" {
instance.RabbitmqEnable = data.RabbitmqEnable
updates["rabbitmq_enable"] = data.RabbitmqEnable
}
if data.NatsEnable != "" {
instance.NatsEnable = data.NatsEnable
updates["nats_enable"] = data.NatsEnable
}
if data.WebSocketEnable != "" {
instance.WebSocketEnable = data.WebSocketEnable
updates["web_socket_enable"] = data.WebSocketEnable
}

return updates
}

func splitSubscribedEvents(events string) []string {
if events == "" {
return []string{event_types.MESSAGE}
}
parts := strings.Split(events, ",")
out := make([]string, 0, len(parts))
for _, p := range parts {
p = strings.TrimSpace(p)
if p == "" {
continue
}
out = append(out, p)
}
if len(out) == 0 {
return []string{event_types.MESSAGE}
}
return out
}
119 changes: 119 additions & 0 deletions pkg/instance/service/connect_settings_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
package instance_service

import (
"strings"
"testing"

instance_model "github.com/evolution-foundation/evolution-go/pkg/instance/model"
event_types "github.com/evolution-foundation/evolution-go/pkg/internal/event_types"
)

func TestApplyConnectSettings(t *testing.T) {
tests := []struct {
name string
instance instance_model.Instance
data ConnectStruct
wantEvents string
wantRabbitmq string
wantWebhook string
wantUpdatesKeys []string
wantNoUpdateKey string
}{
{
name: "empty subscribe keeps existing events",
instance: instance_model.Instance{
Events: "MESSAGE,SEND_MESSAGE,CONNECTION",
RabbitmqEnable: "enabled",
},
data: ConnectStruct{},
wantEvents: "MESSAGE,SEND_MESSAGE,CONNECTION",
wantRabbitmq: "enabled",
wantUpdatesKeys: nil,
wantNoUpdateKey: "events",
},
{
name: "empty subscribe and empty events defaults to MESSAGE",
instance: instance_model.Instance{
Events: "",
},
data: ConnectStruct{},
wantEvents: event_types.MESSAGE,
wantUpdatesKeys: []string{"events"},
},
{
name: "subscribe ALL expands all event types",
instance: instance_model.Instance{},
data: ConnectStruct{
Subscribe: []string{"ALL"},
},
wantEvents: strings.Join(event_types.AllEventTypes, ","),
wantUpdatesKeys: []string{"events"},
},
{
name: "empty rabbitmqEnable preserves enabled",
instance: instance_model.Instance{
Events: "MESSAGE",
RabbitmqEnable: "enabled",
},
data: ConnectStruct{
Subscribe: []string{"MESSAGE"},
},
wantEvents: "MESSAGE",
wantRabbitmq: "enabled",
wantNoUpdateKey: "rabbitmq_enable",
},
{
name: "rabbitmqEnable disabled is written",
instance: instance_model.Instance{
Events: "MESSAGE",
RabbitmqEnable: "enabled",
},
data: ConnectStruct{
RabbitmqEnable: "disabled",
},
wantEvents: "MESSAGE",
wantRabbitmq: "disabled",
wantUpdatesKeys: []string{"rabbitmq_enable"},
},
{
name: "webhook disabled is written",
instance: instance_model.Instance{
Events: "MESSAGE",
Webhook: "https://example.com/hook",
},
data: ConnectStruct{
WebhookUrl: "disabled",
},
wantEvents: "MESSAGE",
wantWebhook: "disabled",
wantUpdatesKeys: []string{"webhook"},
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
inst := tt.instance
updates := applyConnectSettings(&inst, &tt.data)

if inst.Events != tt.wantEvents {
t.Fatalf("Events = %q, want %q", inst.Events, tt.wantEvents)
}
if tt.wantRabbitmq != "" && inst.RabbitmqEnable != tt.wantRabbitmq {
t.Fatalf("RabbitmqEnable = %q, want %q", inst.RabbitmqEnable, tt.wantRabbitmq)
}
if tt.wantWebhook != "" && inst.Webhook != tt.wantWebhook {
t.Fatalf("Webhook = %q, want %q", inst.Webhook, tt.wantWebhook)
}
for _, key := range tt.wantUpdatesKeys {
if _, ok := updates[key]; !ok {
t.Fatalf("expected updates to contain %q, got %#v", key, updates)
}
}
if tt.wantNoUpdateKey != "" {
if _, ok := updates[tt.wantNoUpdateKey]; ok {
t.Fatalf("expected updates to omit %q, got %#v", tt.wantNoUpdateKey, updates)
}
}
})
}
}
Loading