From f0aae79d6d11726b87019b5819199a86e6e95e6d Mon Sep 17 00:00:00 2001 From: Rogee Date: Fri, 5 Jun 2026 05:41:48 +0800 Subject: [PATCH] feat(inboxes): deepen channel config parity --- internal/handler/api/v1/inbox_handler.go | 69 ++++++++++- .../api/v1/inbox_handler_parity_test.go | 114 ++++++++++++++++++ internal/handler/api/v1/inbox_serializer.go | 32 ++++- .../handler/api/v1/twilio_channel_handler.go | 89 +++++++++++--- .../api/v1/twilio_channel_handler_test.go | 50 ++++---- internal/service/inbox_service.go | 54 +++++++++ 6 files changed, 367 insertions(+), 41 deletions(-) diff --git a/internal/handler/api/v1/inbox_handler.go b/internal/handler/api/v1/inbox_handler.go index 50fdbf81..7e6232f9 100644 --- a/internal/handler/api/v1/inbox_handler.go +++ b/internal/handler/api/v1/inbox_handler.go @@ -2,6 +2,7 @@ package v1 import ( "encoding/json" + "io" "net/http" "strconv" "strings" @@ -220,7 +221,7 @@ func (h *InboxHandler) Delete(c *gin.Context) { func bindCreateInboxRequest(c *gin.Context, req *service.CreateInboxRequest) error { if strings.Contains(c.ContentType(), "json") { - return c.ShouldBindJSON(req) + return bindCreateInboxJSON(c, req) } values, err := inboxFormValues(c) if err != nil { @@ -232,7 +233,7 @@ func bindCreateInboxRequest(c *gin.Context, req *service.CreateInboxRequest) err func bindUpdateInboxRequest(c *gin.Context, req *service.UpdateInboxRequest) error { if strings.Contains(c.ContentType(), "json") { - return c.ShouldBindJSON(req) + return bindUpdateInboxJSON(c, req) } values, err := inboxFormValues(c) if err != nil { @@ -268,6 +269,70 @@ func inboxFormValues(c *gin.Context) (map[string][]string, error) { return values, nil } +func bindCreateInboxJSON(c *gin.Context, req *service.CreateInboxRequest) error { + body, err := io.ReadAll(c.Request.Body) + if err != nil { + return err + } + if err := json.Unmarshal(body, req); err != nil { + return err + } + var raw map[string]any + if err := json.Unmarshal(body, &raw); err != nil { + return err + } + hoistInboxChannelJSONFields(&req.Channel, raw) + return nil +} + +func bindUpdateInboxJSON(c *gin.Context, req *service.UpdateInboxRequest) error { + body, err := io.ReadAll(c.Request.Body) + if err != nil { + return err + } + if err := json.Unmarshal(body, req); err != nil { + return err + } + var raw map[string]any + if err := json.Unmarshal(body, &raw); err != nil { + return err + } + hoistInboxChannelJSONFields(&req.Channel, raw) + return nil +} + +func hoistInboxChannelJSONFields(channel *map[string]any, raw map[string]any) { + for key, value := range raw { + if !isInboxChannelConfigKey(key) || value == nil { + continue + } + if *channel == nil { + *channel = map[string]any{} + } + if _, exists := (*channel)[key]; !exists { + (*channel)[key] = value + } + } +} + +func isInboxChannelConfigKey(key string) bool { + switch key { + case "website_url", "widget_color", "welcome_title", "welcome_tagline", "reply_time", + "pre_chat_form_enabled", "pre_chat_form_options", "continuity_via_email", "hmac_mandatory", + "allowed_domains", "selected_feature_flags", "webhook_url", "additional_attributes", + "email", "forward_to_email", "imap_login", "imap_password", "imap_address", "imap_port", + "imap_enabled", "imap_enable_ssl", "imap_authentication", "smtp_login", "smtp_password", + "smtp_address", "smtp_port", "smtp_enabled", "smtp_domain", "smtp_enable_ssl_tls", + "smtp_enable_starttls_auto", "smtp_openssl_verify_mode", "smtp_authentication", "provider", + "provider_config", "phone_number", "message_templates", "account_sid", "auth_token", "api_key_sid", + "api_key_secret", "messaging_service_sid", "medium", "content_templates", "twiml_app_sid", + "voice_enabled", "line_channel_id", "line_channel_secret", "line_channel_token", "bot_token": + return true + default: + return false + } +} + func applyCreateInboxForm(req *service.CreateInboxRequest, values map[string][]string) { req.Channel = map[string]any{} for key, list := range values { diff --git a/internal/handler/api/v1/inbox_handler_parity_test.go b/internal/handler/api/v1/inbox_handler_parity_test.go index 461d7ba5..a630ec8d 100644 --- a/internal/handler/api/v1/inbox_handler_parity_test.go +++ b/internal/handler/api/v1/inbox_handler_parity_test.go @@ -243,6 +243,120 @@ func TestInboxHandler_ChatwootCreateUpdateRequestBinding(t *testing.T) { require.Len(t, inboxParityObject(t, showUpdated)["working_hours"].([]any), 7) } +func TestInboxHandler_ChatwootChannelSpecificConfigDepth(t *testing.T) { + gin.SetMode(gin.TestMode) + + db, err := gorm.Open(sqlite.Open("file:inbox_handler_channel_depth?mode=memory&cache=shared"), &gorm.Config{ + Logger: logger.Default.LogMode(logger.Silent), + }) + require.NoError(t, err) + t.Cleanup(func() { + sqlDB, dbErr := db.DB() + if dbErr == nil { + _ = sqlDB.Close() + } + }) + require.NoError(t, db.AutoMigrate(&model.Account{}, &model.Inbox{}, &model.WorkingHour{})) + + account := &model.Account{Name: "Inbox Channel Depth", Locale: "en", Active: true} + require.NoError(t, db.Create(account).Error) + router := setupInboxParityRouter(db) + + emailCreate := inboxParityRequest(t, router, http.MethodPost, fmt.Sprintf("/api/v1/accounts/%d/inboxes/", account.ID), map[string]any{ + "name": "Email Support", + "channel": map[string]any{ + "type": "email", + "email": "support@example.com", + "forward_to_email": "support+forward@example.test", + "imap_enabled": true, + "imap_login": "imap-user", + "imap_password": "imap-secret", + "imap_address": "imap.example.com", + "imap_port": 993, + "imap_enable_ssl": true, + "imap_authentication": "plain", + "smtp_enabled": true, + "smtp_login": "smtp-user", + "smtp_password": "smtp-secret", + "smtp_address": "smtp.example.com", + "smtp_port": 587, + "smtp_domain": "example.com", + "smtp_enable_starttls_auto": true, + "smtp_enable_ssl_tls": false, + "smtp_openssl_verify_mode": "none", + "smtp_authentication": "login", + }, + }) + require.Equal(t, http.StatusOK, emailCreate.Code, emailCreate.Body.String()) + emailData := inboxParityObject(t, emailCreate) + require.Equal(t, "Channel::Email", emailData["channel_type"]) + require.Equal(t, "support@example.com", emailData["email"]) + require.Equal(t, "support+forward@example.test", emailData["forward_to_email"]) + require.Equal(t, true, emailData["forwarding_enabled"]) + require.Equal(t, "imap.example.com", emailData["imap_address"]) + require.Equal(t, float64(993), emailData["imap_port"]) + require.Equal(t, "smtp.example.com", emailData["smtp_address"]) + require.Equal(t, float64(587), emailData["smtp_port"]) + + emailID := uint(emailData["id"].(float64)) + smtpUpdate := inboxParityRequest(t, router, http.MethodPatch, fmt.Sprintf("/api/v1/accounts/%d/inboxes/%d", account.ID, emailID), map[string]any{ + "smtp_enabled": true, + "smtp_address": "smtp2.example.com", + "smtp_port": 465, + "smtp_login": "smtp2-user", + "smtp_password": "smtp2-secret", + "smtp_domain": "example.org", + "smtp_enable_ssl_tls": true, + "smtp_enable_starttls_auto": false, + "smtp_openssl_verify_mode": "peer", + "smtp_authentication": "login", + }) + require.Equal(t, http.StatusOK, smtpUpdate.Code, smtpUpdate.Body.String()) + smtpData := inboxParityObject(t, smtpUpdate) + require.Equal(t, "smtp2.example.com", smtpData["smtp_address"]) + require.Equal(t, float64(465), smtpData["smtp_port"]) + require.Equal(t, true, smtpData["smtp_enable_ssl_tls"]) + require.Equal(t, false, smtpData["smtp_enable_starttls_auto"]) + + whatsappCreate := inboxParityRequest(t, router, http.MethodPost, fmt.Sprintf("/api/v1/accounts/%d/inboxes/", account.ID), map[string]any{ + "name": "WhatsApp", + "channel": map[string]any{ + "type": "whatsapp", + "phone_number": "+1555010000", + "provider": "whatsapp_cloud", + "provider_config": map[string]any{ + "api_key": "wa-key", + "phone_number_id": "phone-id", + "business_account_id": "waba-id", + }, + }, + }) + require.Equal(t, http.StatusOK, whatsappCreate.Code, whatsappCreate.Body.String()) + whatsappData := inboxParityObject(t, whatsappCreate) + require.Equal(t, "Channel::Whatsapp", whatsappData["channel_type"]) + require.Equal(t, "+1555010000", whatsappData["phone_number"]) + providerConfig := whatsappData["provider_config"].(map[string]any) + require.Equal(t, "wa-key", providerConfig["api_key"]) + require.Equal(t, "phone-id", providerConfig["phone_number_id"]) + require.NotEmpty(t, providerConfig["webhook_verify_token"]) + + lineCreate := inboxParityRequest(t, router, http.MethodPost, fmt.Sprintf("/api/v1/accounts/%d/inboxes/", account.ID), map[string]any{ + "name": "LINE", + "channel": map[string]any{ + "type": "line", + "line_channel_id": "line-id", + "line_channel_secret": "line-secret", + "line_channel_token": "line-token", + }, + }) + require.Equal(t, http.StatusOK, lineCreate.Code, lineCreate.Body.String()) + lineData := inboxParityObject(t, lineCreate) + require.Equal(t, "Channel::Line", lineData["channel_type"]) + require.Equal(t, "line-id", lineData["line_channel_id"]) + require.Equal(t, "line-secret", lineData["line_channel_secret"]) + require.Equal(t, "line-token", lineData["line_channel_token"]) +} + func setupInboxParityRouter(db *gorm.DB) *gin.Engine { inboxSvc := service.NewInboxService(repository.NewInboxRepo(db), nil, nil, nil, nil, nil, nil) handler := NewInboxHandler(inboxSvc) diff --git a/internal/handler/api/v1/inbox_serializer.go b/internal/handler/api/v1/inbox_serializer.go index 7094eeb4..b09a8584 100644 --- a/internal/handler/api/v1/inbox_serializer.go +++ b/internal/handler/api/v1/inbox_serializer.go @@ -82,15 +82,45 @@ func serializeInbox(inbox *model.Inbox) map[string]any { payload["auth_token"] = configValue(config, "auth_token") payload["account_sid"] = configValue(config, "account_sid") payload["api_key_sid"] = configValue(config, "api_key_sid") + payload["voice_enabled"] = configValue(config, "voice_enabled") + payload["voice_configured"] = configValue(config, "twiml_app_sid") != nil + payload["has_api_key_secret"] = configValue(config, "api_key_secret") != nil + payload["voice_call_webhook_url"] = configValue(config, "voice_call_webhook_url") + payload["voice_status_webhook_url"] = configValue(config, "voice_status_webhook_url") case "Channel::Email": payload["email"] = configValue(config, "email") - payload["forwarding_enabled"] = false + payload["forwarding_enabled"] = configValue(config, "forward_to_email") != nil + payload["forward_to_email"] = configValue(config, "forward_to_email") + payload["imap_login"] = configValue(config, "imap_login") + payload["imap_password"] = configValue(config, "imap_password") + payload["imap_address"] = configValue(config, "imap_address") + payload["imap_port"] = configValue(config, "imap_port") payload["imap_enabled"] = configValue(config, "imap_enabled") + payload["imap_enable_ssl"] = configValue(config, "imap_enable_ssl") + payload["imap_authentication"] = configValue(config, "imap_authentication") + payload["smtp_login"] = configValue(config, "smtp_login") + payload["smtp_password"] = configValue(config, "smtp_password") + payload["smtp_address"] = configValue(config, "smtp_address") + payload["smtp_port"] = configValue(config, "smtp_port") payload["smtp_enabled"] = configValue(config, "smtp_enabled") + payload["smtp_domain"] = configValue(config, "smtp_domain") + payload["smtp_enable_ssl_tls"] = configValue(config, "smtp_enable_ssl_tls") + payload["smtp_enable_starttls_auto"] = configValue(config, "smtp_enable_starttls_auto") + payload["smtp_openssl_verify_mode"] = configValue(config, "smtp_openssl_verify_mode") + payload["smtp_authentication"] = configValue(config, "smtp_authentication") case "Channel::Whatsapp": + payload["phone_number"] = configValue(config, "phone_number") payload["message_templates"] = configValue(config, "message_templates") payload["provider_config"] = configValue(config, "provider_config") payload["reauthorization_required"] = configValue(config, "reauthorization_required") + payload["voice_enabled"] = configValue(config, "voice_enabled") + case "Channel::Line": + payload["line_channel_id"] = configValue(config, "line_channel_id") + payload["line_channel_secret"] = configValue(config, "line_channel_secret") + payload["line_channel_token"] = configValue(config, "line_channel_token") + case "Channel::Sms": + payload["phone_number"] = configValue(config, "phone_number") + payload["provider_config"] = configValue(config, "provider_config") } return payload diff --git a/internal/handler/api/v1/twilio_channel_handler.go b/internal/handler/api/v1/twilio_channel_handler.go index 92e0928d..e277ec95 100644 --- a/internal/handler/api/v1/twilio_channel_handler.go +++ b/internal/handler/api/v1/twilio_channel_handler.go @@ -13,8 +13,11 @@ package v1 // - GET /api/v1/accounts/:id/channels/twilio_channel → list Twilio SMS channels import ( + "encoding/json" + "io" "net/http" "strconv" + "strings" "github.com/gin-gonic/gin" @@ -49,9 +52,12 @@ func NewTwilioChannelHandler( // CreateTwilioSMSChannelRequest is the DTO for creating a Twilio SMS channel. type CreateTwilioSMSChannelRequest struct { AccountSID string `json:"account_sid" validate:"required"` + APIKeySID string `json:"api_key_sid"` AuthToken string `json:"auth_token" validate:"required"` PhoneNumber string `json:"phone_number" validate:"required"` MessagingServiceSID string `json:"messaging_service_sid"` + Medium string `json:"medium"` + Name string `json:"name"` InboxName string `json:"inbox_name"` } @@ -66,19 +72,25 @@ func (h *TwilioChannelHandler) Create(c *gin.Context) { return } - var req CreateTwilioSMSChannelRequest - if err := c.ShouldBindJSON(&req); err != nil { - applogger.L().Errorf("Failed to bind Twilio SMS channel create request: %v", err) + req, bindErr := bindTwilioCreateRequest(c) + if bindErr != nil { + applogger.L().Errorf("Failed to bind Twilio SMS channel create request: %v", bindErr) + c.JSON(http.StatusBadRequest, gin.H{"error": "invalid request body"}) + return + } + if req.AccountSID == "" || req.AuthToken == "" || (req.PhoneNumber == "" && req.MessagingServiceSID == "") { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid request body"}) return } ctx := c.Request.Context() + medium := firstNonEmptyString(req.Medium, "sms") + phoneNumber := twilioPhoneNumberForMedium(req.PhoneNumber, medium) // Create the channel record first (without InboxID) ch := &channelmodel.ChannelTwilioSMS{ AccountSID: req.AccountSID, - PhoneNumber: req.PhoneNumber, + PhoneNumber: phoneNumber, MessagingServiceSID: req.MessagingServiceSID, AccountID: uint(accountID), } @@ -92,12 +104,23 @@ func (h *TwilioChannelHandler) Create(c *gin.Context) { // Create inbox for the Twilio SMS channel inboxName := req.InboxName if inboxName == "" { - inboxName = req.PhoneNumber + inboxName = req.Name + } + if inboxName == "" { + inboxName = firstNonEmptyString(phoneNumber, req.MessagingServiceSID) } inboxReq := service.CreateInboxRequest{ Name: inboxName, ChannelType: "twilio_sms", Enabled: true, + Channel: map[string]any{ + "account_sid": req.AccountSID, + "auth_token": req.AuthToken, + "api_key_sid": req.APIKeySID, + "phone_number": phoneNumber, + "messaging_service_sid": req.MessagingServiceSID, + "medium": medium, + }, } inbox, err := h.inboxSvc.Create(ctx, uint(accountID), inboxReq) @@ -117,10 +140,44 @@ func (h *TwilioChannelHandler) Create(c *gin.Context) { applogger.L().Errorf("Failed to update Twilio SMS channel with inbox_id: %v", err) } - c.JSON(http.StatusCreated, gin.H{ - "channel": ch, - "inbox": inbox, - }) + c.JSON(http.StatusOK, serializeInbox(inbox)) +} + +func twilioPhoneNumberForMedium(phoneNumber, medium string) string { + if medium == "whatsapp" && phoneNumber != "" && !strings.HasPrefix(phoneNumber, "whatsapp:") { + return "whatsapp:" + phoneNumber + } + return phoneNumber +} + +func firstNonEmptyString(values ...string) string { + for _, value := range values { + if value != "" { + return value + } + } + return "" +} + +func bindTwilioCreateRequest(c *gin.Context) (CreateTwilioSMSChannelRequest, error) { + body, err := io.ReadAll(c.Request.Body) + if err != nil { + return CreateTwilioSMSChannelRequest{}, err + } + var req CreateTwilioSMSChannelRequest + if err := json.Unmarshal(body, &req); err != nil { + return CreateTwilioSMSChannelRequest{}, err + } + if req.AccountSID != "" || req.AuthToken != "" { + return req, nil + } + var wrapped struct { + TwilioChannel CreateTwilioSMSChannelRequest `json:"twilio_channel"` + } + if err := json.Unmarshal(body, &wrapped); err != nil { + return CreateTwilioSMSChannelRequest{}, err + } + return wrapped.TwilioChannel, nil } // Get retrieves a Twilio SMS channel by ID. @@ -213,13 +270,13 @@ func (h *TwilioChannelHandler) Update(c *gin.Context) { } c.JSON(http.StatusOK, gin.H{ - "id": ch.ID, - "account_id": ch.AccountID, - "inbox_id": ch.InboxID, - "account_sid": ch.AccountSID, - "phone_number": ch.PhoneNumber, + "id": ch.ID, + "account_id": ch.AccountID, + "inbox_id": ch.InboxID, + "account_sid": ch.AccountSID, + "phone_number": ch.PhoneNumber, "messaging_service_sid": ch.MessagingServiceSID, - "message": "Twilio SMS channel updated successfully", + "message": "Twilio SMS channel updated successfully", }) } @@ -291,4 +348,4 @@ func (h *TwilioChannelHandler) List(c *gin.Context) { } c.JSON(http.StatusOK, gin.H{"channels": channels}) -} \ No newline at end of file +} diff --git a/internal/handler/api/v1/twilio_channel_handler_test.go b/internal/handler/api/v1/twilio_channel_handler_test.go index 33c56157..7659d872 100644 --- a/internal/handler/api/v1/twilio_channel_handler_test.go +++ b/internal/handler/api/v1/twilio_channel_handler_test.go @@ -14,8 +14,8 @@ import ( "gorm.io/driver/sqlite" "gorm.io/gorm" - channelmodel "github.com/gochat/gochat/internal/model/channel" "github.com/gochat/gochat/internal/model" + channelmodel "github.com/gochat/gochat/internal/model/channel" "github.com/gochat/gochat/internal/channel/whatsapp" "github.com/gochat/gochat/internal/repository" @@ -79,6 +79,7 @@ func setupTwilioTestRouter(handler *TwilioChannelHandler) *gin.Engine { gin.SetMode(gin.TestMode) r := gin.New() ag := r.Group("/api/v1/accounts/:id") + ag.POST("/channels/twilio_channel", handler.Create) ag.POST("/twilio_sms_channels", handler.Create) ag.GET("/twilio_sms_channels", handler.List) ig := ag.Group("/inboxes/:inbox_id/twilio_sms_channels") @@ -95,32 +96,37 @@ func TestTwilioChannel_Create_Success(t *testing.T) { router := setupTwilioTestRouter(handler) accountID := twilioAccountID(db) - body := CreateTwilioSMSChannelRequest{ - AccountSID: "ACtest123", - AuthToken: "authtoken_secret", - PhoneNumber: "+15551234567", - InboxName: "Support SMS", + body := map[string]any{ + "twilio_channel": map[string]any{ + "account_sid": "ACtest123", + "api_key_sid": "SKtest123", + "auth_token": "authtoken_secret", + "phone_number": "+15551234567", + "messaging_service_sid": "MG123", + "medium": "sms", + "name": "Support SMS", + }, } b, _ := json.Marshal(body) w := httptest.NewRecorder() - req, _ := http.NewRequest(http.MethodPost, "/api/v1/accounts/"+accountID+"/twilio_sms_channels", bytes.NewReader(b)) + req, _ := http.NewRequest(http.MethodPost, "/api/v1/accounts/"+accountID+"/channels/twilio_channel", bytes.NewReader(b)) req.Header.Set("Content-Type", "application/json") router.ServeHTTP(w, req) - assert.Equal(t, http.StatusCreated, w.Code) + assert.Equal(t, http.StatusOK, w.Code) var resp map[string]interface{} require.NoError(t, json.Unmarshal(w.Body.Bytes(), &resp)) - channelData, ok := resp["channel"].(map[string]interface{}) - require.True(t, ok) - assert.Equal(t, "ACtest123", channelData["account_sid"]) - assert.Equal(t, "+15551234567", channelData["phone_number"]) - - // Verify inbox was created - inboxData, ok := resp["inbox"].(map[string]interface{}) - require.True(t, ok) - assert.Equal(t, "Support SMS", inboxData["name"]) + assert.Equal(t, "Support SMS", resp["name"]) + assert.Equal(t, "Channel::TwilioSms", resp["channel_type"]) + assert.Equal(t, "ACtest123", resp["account_sid"]) + assert.Equal(t, "SKtest123", resp["api_key_sid"]) + assert.Equal(t, "+15551234567", resp["phone_number"]) + assert.Equal(t, "MG123", resp["messaging_service_sid"]) + assert.Equal(t, "sms", resp["medium"]) + require.NotContains(t, resp, "channel") + require.NotContains(t, resp, "inbox") } func TestTwilioChannel_Create_InvalidAccountID(t *testing.T) { @@ -357,10 +363,10 @@ func TestTwilioChannel_List_Success(t *testing.T) { // Seed multiple channels for i := 0; i < 3; i++ { ch := &channelmodel.ChannelTwilioSMS{ - AccountSID: "AClist" + strconv.Itoa(i), - PhoneNumber: "+1555" + strconv.Itoa(i) + "00000", - AccountID: accountID, - InboxID: uint(i + 100), // unique inbox IDs to satisfy uniqueIndex constraint + AccountSID: "AClist" + strconv.Itoa(i), + PhoneNumber: "+1555" + strconv.Itoa(i) + "00000", + AccountID: accountID, + InboxID: uint(i + 100), // unique inbox IDs to satisfy uniqueIndex constraint } require.NoError(t, db.Create(ch).Error) } @@ -403,4 +409,4 @@ func TestTwilioChannel_List_Empty(t *testing.T) { channels, ok := resp["channels"].([]interface{}) require.True(t, ok) assert.Len(t, channels, 0) -} \ No newline at end of file +} diff --git a/internal/service/inbox_service.go b/internal/service/inbox_service.go index 3776aa52..07481f1d 100644 --- a/internal/service/inbox_service.go +++ b/internal/service/inbox_service.go @@ -323,6 +323,24 @@ func buildInitialInboxChannelConfig(channelType string, channel map[string]any) mergeInboxChannelConfig(config, channel) switch channelType { case "web_widget": + if _, ok := config["allowed_domains"]; !ok { + config["allowed_domains"] = "" + } + if _, ok := config["continuity_via_email"]; !ok { + config["continuity_via_email"] = true + } + if _, ok := config["hmac_mandatory"]; !ok { + config["hmac_mandatory"] = false + } + if _, ok := config["pre_chat_form_enabled"]; !ok { + config["pre_chat_form_enabled"] = false + } + if _, ok := config["reply_time"]; !ok { + config["reply_time"] = "in_a_few_minutes" + } + if _, ok := config["selected_feature_flags"]; !ok { + config["selected_feature_flags"] = []string{"attachments", "emoji_picker", "end_conversation"} + } if _, ok := config["website_token"]; !ok { if token, err := widgetGenerateToken(12); err == nil { config["website_token"] = token @@ -338,6 +356,12 @@ func buildInitialInboxChannelConfig(channelType string, channel map[string]any) config["widget_color"] = "#1f93ff" } case "api": + if _, ok := config["additional_attributes"]; !ok { + config["additional_attributes"] = map[string]any{} + } + if _, ok := config["hmac_mandatory"]; !ok { + config["hmac_mandatory"] = false + } if _, ok := config["identifier"]; !ok { if token, err := widgetGenerateToken(12); err == nil { config["identifier"] = token @@ -347,6 +371,36 @@ func buildInitialInboxChannelConfig(channelType string, channel map[string]any) if _, ok := config["hmac_token"]; !ok { config["hmac_token"] = generateInboxSecret() } + case "email": + if _, ok := config["imap_authentication"]; !ok { + config["imap_authentication"] = "plain" + } + if _, ok := config["imap_enable_ssl"]; !ok { + config["imap_enable_ssl"] = true + } + if _, ok := config["smtp_authentication"]; !ok { + config["smtp_authentication"] = "login" + } + if _, ok := config["smtp_enable_starttls_auto"]; !ok { + config["smtp_enable_starttls_auto"] = true + } + if _, ok := config["smtp_enable_ssl_tls"]; !ok { + config["smtp_enable_ssl_tls"] = false + } + if _, ok := config["smtp_openssl_verify_mode"]; !ok { + config["smtp_openssl_verify_mode"] = "none" + } + case "whatsapp": + providerConfig, _ := config["provider_config"].(map[string]any) + if providerConfig == nil { + providerConfig = map[string]any{} + config["provider_config"] = providerConfig + } + if mapString(config, "provider") == "whatsapp_cloud" { + if _, ok := providerConfig["webhook_verify_token"]; !ok { + providerConfig["webhook_verify_token"] = generateInboxSecret() + } + } } delete(config, "type") return config