diff --git a/backend/internal/database/upload_migration_test.go b/backend/internal/database/upload_migration_test.go index a28594c5..b3543efa 100644 --- a/backend/internal/database/upload_migration_test.go +++ b/backend/internal/database/upload_migration_test.go @@ -72,7 +72,7 @@ func TestUploadPostgresProductionMigrationsFromEmptySchema(t *testing.T) { require.NoError(t, RunMigrations(dbURL, productionMigrationsPath(t))) version, dirty, err := CurrentVersion(dbURL, productionMigrationsPath(t)) require.NoError(t, err) - assert.Equal(t, uint(84), version) + assert.Equal(t, uint(85), version) assert.False(t, dirty) exerciseUploadProductionSchema(t, db) } diff --git a/backend/internal/handler/api/v1/account_handler.go b/backend/internal/handler/api/v1/account_handler.go index 7dbbd57e..83f1cf98 100644 --- a/backend/internal/handler/api/v1/account_handler.go +++ b/backend/internal/handler/api/v1/account_handler.go @@ -469,7 +469,7 @@ func serializeAccountSettings(account *model.Account) map[string]any { settings := map[string]any{ "auto_resolve_after": account.AutoResolveDuration, "auto_resolve_duration": account.AutoResolveDuration, - "auto_resolve_message": "", + "auto_resolve_message": account.AutoResolveMessage, "auto_resolve_ignore_waiting": account.AutoResolveIgnoreWaiting, "audio_transcriptions": account.AudioTranscriptions, "auto_resolve_label": account.AutoResolveLabel, diff --git a/backend/internal/handler/api/v1/account_handler_test.go b/backend/internal/handler/api/v1/account_handler_test.go index d8955d8f..a5e33b95 100644 --- a/backend/internal/handler/api/v1/account_handler_test.go +++ b/backend/internal/handler/api/v1/account_handler_test.go @@ -349,10 +349,11 @@ func (s *AccountHandlerTestSuite) TestPatchUpdate_AudioTranscriptions() { func (s *AccountHandlerTestSuite) TestPatchUpdate_AutoResolvePreferences() { acc := s.seedAccount("Auto Resolve Preferences") + accountURL := "/api/v1/accounts/" + strconv.FormatUint(uint64(acc.ID), 10) - body := `{"auto_resolve_after":15,"auto_resolve_ignore_waiting":true,"auto_resolve_label":"vip"}` + body := `{"auto_resolve_after":15,"auto_resolve_ignore_waiting":true,"auto_resolve_label":"vip","auto_resolve_message":"We will close this conversation now."}` w := httptest.NewRecorder() - req, _ := http.NewRequest("PATCH", "/api/v1/accounts/"+strconv.FormatUint(uint64(acc.ID), 10), bytes.NewBufferString(body)) + req, _ := http.NewRequest("PATCH", accountURL, bytes.NewBufferString(body)) req.Header.Set("Content-Type", "application/json") s.router.ServeHTTP(w, req) @@ -362,16 +363,25 @@ func (s *AccountHandlerTestSuite) TestPatchUpdate_AutoResolvePreferences() { assert.Equal(s.T(), float64(15), settings["auto_resolve_after"]) assert.Equal(s.T(), true, settings["auto_resolve_ignore_waiting"]) assert.Equal(s.T(), "vip", settings["auto_resolve_label"]) + assert.Equal(s.T(), "We will close this conversation now.", settings["auto_resolve_message"]) var stored model.Account require.NoError(s.T(), s.db.First(&stored, acc.ID).Error) assert.Equal(s.T(), 15, stored.AutoResolveDuration) assert.True(s.T(), stored.AutoResolveIgnoreWaiting) assert.Equal(s.T(), "vip", stored.AutoResolveLabel) + assert.Equal(s.T(), "We will close this conversation now.", stored.AutoResolveMessage) + + w = httptest.NewRecorder() + req, _ = http.NewRequest("GET", accountURL, nil) + s.router.ServeHTTP(w, req) + require.Equal(s.T(), http.StatusOK, w.Code) + settings = s.unmarshalResponse(w)["settings"].(map[string]interface{}) + assert.Equal(s.T(), "We will close this conversation now.", settings["auto_resolve_message"]) body = `{"auto_resolve_after":0,"auto_resolve_ignore_waiting":false,"auto_resolve_label":""}` w = httptest.NewRecorder() - req, _ = http.NewRequest("PATCH", "/api/v1/accounts/"+strconv.FormatUint(uint64(acc.ID), 10), bytes.NewBufferString(body)) + req, _ = http.NewRequest("PATCH", accountURL, bytes.NewBufferString(body)) req.Header.Set("Content-Type", "application/json") s.router.ServeHTTP(w, req) @@ -380,6 +390,23 @@ func (s *AccountHandlerTestSuite) TestPatchUpdate_AutoResolvePreferences() { assert.Zero(s.T(), stored.AutoResolveDuration) assert.False(s.T(), stored.AutoResolveIgnoreWaiting) assert.Empty(s.T(), stored.AutoResolveLabel) + assert.Equal(s.T(), "We will close this conversation now.", stored.AutoResolveMessage) + + body = `{"auto_resolve_message":""}` + w = httptest.NewRecorder() + req, _ = http.NewRequest("PATCH", accountURL, bytes.NewBufferString(body)) + req.Header.Set("Content-Type", "application/json") + s.router.ServeHTTP(w, req) + require.Equal(s.T(), http.StatusOK, w.Code) + settings = s.unmarshalResponse(w)["settings"].(map[string]interface{}) + assert.Empty(s.T(), settings["auto_resolve_message"]) + + w = httptest.NewRecorder() + req, _ = http.NewRequest("GET", accountURL, nil) + s.router.ServeHTTP(w, req) + require.Equal(s.T(), http.StatusOK, w.Code) + settings = s.unmarshalResponse(w)["settings"].(map[string]interface{}) + assert.Empty(s.T(), settings["auto_resolve_message"]) } func (s *AccountHandlerTestSuite) TestPatchUpdate_ChatwootGeneralSettingsPayload() { diff --git a/backend/internal/model/account.go b/backend/internal/model/account.go index c6ac353b..7087ce5b 100644 --- a/backend/internal/model/account.go +++ b/backend/internal/model/account.go @@ -22,6 +22,7 @@ type Account struct { AutoResolveDuration int `gorm:"default:0" json:"auto_resolve_duration,omitempty"` // minutes AutoResolveIgnoreWaiting bool `gorm:"default:false" json:"auto_resolve_ignore_waiting"` AutoResolveLabel string `gorm:"size:255" json:"auto_resolve_label,omitempty"` + AutoResolveMessage string `gorm:"type:text;not null;default:''" json:"auto_resolve_message,omitempty"` AudioTranscriptions bool `gorm:"default:false" json:"audio_transcriptions"` Limits datatypes.JSON `gorm:"type:jsonb;default:'{}'" json:"limits,omitempty"` // Chatwoot account limits, e.g. emails AgentLimit int `gorm:"default:0" json:"agent_limit,omitempty"` // max agents allowed (0 = unlimited), Chatwoot usage_limits[:agents] diff --git a/backend/internal/service/account_service.go b/backend/internal/service/account_service.go index 35b897cc..9c4c8cdc 100644 --- a/backend/internal/service/account_service.go +++ b/backend/internal/service/account_service.go @@ -242,7 +242,7 @@ type UpdateAccountRequest struct { Status string `json:"status,omitempty" validate:"omitempty,oneof=active inactive"` AutoResolveDuration *int `json:"auto_resolve_duration,omitempty" validate:"omitempty,gte=0"` AutoResolveAfter *int `json:"auto_resolve_after,omitempty" validate:"omitempty,gte=0"` - AutoResolveMessage string `json:"auto_resolve_message,omitempty"` + AutoResolveMessage *string `json:"auto_resolve_message,omitempty"` AutoResolveIgnoreWaiting *bool `json:"auto_resolve_ignore_waiting,omitempty"` AudioTranscriptions *bool `json:"audio_transcriptions,omitempty"` AutoResolveLabel *string `json:"auto_resolve_label,omitempty"` @@ -298,6 +298,9 @@ func (s *AccountService) Update(ctx context.Context, id uint, req UpdateAccountR if req.AutoResolveLabel != nil { account.AutoResolveLabel = *req.AutoResolveLabel } + if req.AutoResolveMessage != nil { + account.AutoResolveMessage = *req.AutoResolveMessage + } if req.AudioTranscriptions != nil { account.AudioTranscriptions = *req.AudioTranscriptions } diff --git a/backend/migrations/000085_add_auto_resolve_message_to_accounts.down.sql b/backend/migrations/000085_add_auto_resolve_message_to_accounts.down.sql new file mode 100644 index 00000000..ca0e3d84 --- /dev/null +++ b/backend/migrations/000085_add_auto_resolve_message_to_accounts.down.sql @@ -0,0 +1,2 @@ +ALTER TABLE accounts + DROP COLUMN IF EXISTS auto_resolve_message; diff --git a/backend/migrations/000085_add_auto_resolve_message_to_accounts.up.sql b/backend/migrations/000085_add_auto_resolve_message_to_accounts.up.sql new file mode 100644 index 00000000..664331d9 --- /dev/null +++ b/backend/migrations/000085_add_auto_resolve_message_to_accounts.up.sql @@ -0,0 +1,2 @@ +ALTER TABLE accounts + ADD COLUMN IF NOT EXISTS auto_resolve_message TEXT NOT NULL DEFAULT '';