Publish current project state

This commit is contained in:
2026-09-14 10:47:21 +08:00
parent 0dd188c46f
commit 4ad56ee9f8
186 changed files with 6289 additions and 1657 deletions
@@ -6,6 +6,7 @@ import (
"github.com/gin-gonic/gin"
"github.com/gochat/gochat/internal/auth"
"github.com/gochat/gochat/internal/model"
"github.com/gochat/gochat/internal/service"
applogger "github.com/gochat/gochat/pkg/logger"
@@ -100,7 +101,7 @@ func RegisterAuditRoutes(rg *gin.RouterGroup, h *AuditHandler) {
func isAuditAdmin(c *gin.Context) bool {
role := getRole(c)
return role == "administrator" || role == "super_admin"
return role == auth.RoleAdministrator || role == auth.RoleSuperAdmin
}
func parseAuditPage(raw string) int {
@@ -5,6 +5,7 @@ import (
"net/http"
"github.com/gin-gonic/gin"
"github.com/gochat/gochat/internal/auth"
"github.com/gochat/gochat/internal/model"
"github.com/gochat/gochat/internal/service"
applogger "github.com/gochat/gochat/pkg/logger"
@@ -247,7 +248,7 @@ func (h *CaptainCustomToolHandler) ensureCustomToolsEnabled(c *gin.Context, acco
func (h *CaptainCustomToolHandler) ensureCustomToolAdmin(c *gin.Context) bool {
if role, exists := c.Get("role"); exists {
if role == "administrator" || role == "super_admin" {
if role == auth.RoleAdministrator || role == auth.RoleSuperAdmin {
return true
}
c.JSON(http.StatusForbidden, gin.H{"error": "You are not authorized to do this action"})
@@ -293,5 +294,5 @@ func captainCustomToolPayload(c *gin.Context, tool *model.CaptainCustomTool) gin
func captainCustomToolShowAuthConfig(c *gin.Context) bool {
role, exists := c.Get("role")
return exists && (role == "administrator" || role == "super_admin")
return exists && (role == auth.RoleAdministrator || role == auth.RoleSuperAdmin)
}
@@ -4,6 +4,7 @@ import (
"net/http"
"github.com/gin-gonic/gin"
"github.com/gochat/gochat/internal/auth"
"github.com/gochat/gochat/internal/service"
applogger "github.com/gochat/gochat/pkg/logger"
"github.com/gochat/gochat/pkg/response"
@@ -112,5 +113,5 @@ func (h *CaptainPreferenceHandler) Delete(c *gin.Context) {
func captainPreferencesCanUpdate(c *gin.Context) bool {
role := getRole(c)
return role == "administrator" || role == "super_admin"
return role == auth.RoleAdministrator || role == auth.RoleSuperAdmin
}
@@ -10,6 +10,7 @@ import (
"github.com/gin-gonic/gin"
"github.com/gochat/gochat/internal/auth"
"github.com/gochat/gochat/internal/llm"
"github.com/gochat/gochat/internal/middleware"
"github.com/gochat/gochat/internal/model"
@@ -470,7 +471,7 @@ func (h *ConversationHandler) UpdateLabels(c *gin.Context) {
handleServiceError(c, svcErr)
return
}
c.JSON(http.StatusOK, gin.H{"payload": gin.H{"conversationId": strconv.FormatUint(uint64(conversation.ID), 10), "labels": labelList(conversation.Labels)}})
c.JSON(http.StatusOK, gin.H{"payload": labelList(conversation.Labels)})
}
// GetLabels returns the labels assigned to a conversation.
@@ -1145,7 +1146,7 @@ func (h *ConversationHandler) canManageConversationInbox(ctx context.Context, ac
if err := h.conversationSvc.DB().WithContext(ctx).Where("account_id = ? AND user_id = ?", accountID, userID).First(&accountUser).Error; err != nil {
return false
}
if accountUser.Role == "administrator" {
if accountUser.Role == auth.RoleAdministrator {
return true
}
var count int64
@@ -1080,9 +1080,7 @@ func (s *ConversationCrudTestSuite) TestUpdateLabels_Success() {
var resp map[string]interface{}
err := json.Unmarshal(w.Body.Bytes(), &resp)
assert.NoError(s.T(), err)
payload := resp["payload"].(map[string]interface{})
assert.Equal(s.T(), strconv.FormatUint(uint64(s.testConv.ID), 10), payload["conversationId"])
assert.ElementsMatch(s.T(), []interface{}{"support", "bug"}, payload["labels"])
assert.ElementsMatch(s.T(), []interface{}{"support", "bug"}, resp["payload"])
}
func (s *ConversationCrudTestSuite) TestChatwootFrontendConversationLabelsRuntimeRoutes() {
@@ -1097,9 +1095,7 @@ func (s *ConversationCrudTestSuite) TestChatwootFrontendConversationLabelsRuntim
assert.Equal(s.T(), http.StatusOK, w.Code, w.Body.String())
var updateResp map[string]interface{}
s.Require().NoError(json.Unmarshal(w.Body.Bytes(), &updateResp))
updatePayload := updateResp["payload"].(map[string]interface{})
assert.Equal(s.T(), strconv.FormatUint(uint64(s.testConv.ID), 10), updatePayload["conversationId"])
assert.ElementsMatch(s.T(), []interface{}{"customer-success", "on-hold"}, updatePayload["labels"])
assert.ElementsMatch(s.T(), []interface{}{"customer-success", "on-hold"}, updateResp["payload"])
w = httptest.NewRecorder()
req, _ = http.NewRequest("GET", s.convURL(s.testConv.ID)+"/labels", nil)
@@ -7,6 +7,7 @@ import (
"strings"
"time"
"github.com/gochat/gochat/internal/auth"
"github.com/gochat/gochat/internal/model"
channelmodel "github.com/gochat/gochat/internal/model/channel"
"github.com/gochat/gochat/internal/service"
@@ -721,7 +722,7 @@ func serializeUser(user *model.User, accountID uint) map[string]any {
"provider": nonEmpty(user.Provider, "email"),
"available_name": nonEmpty(user.DisplayName, user.Name),
"name": user.Name,
"role": nonEmpty(user.Role, "agent"),
"role": nonEmpty(user.Role, auth.RoleAgent),
"thumbnail": user.AvatarURL,
"type": "user",
}
@@ -2,9 +2,11 @@ package v1
import (
"context"
"encoding/json"
"time"
"github.com/gochat/gochat/internal/model"
"gorm.io/datatypes"
"gorm.io/gorm"
)
@@ -93,10 +95,39 @@ func serializeCompanyContact(ctx context.Context, db *gorm.DB, contact *model.Co
}
func serializeContactInbox(contactInbox *model.ContactInbox) map[string]any {
return map[string]any{
payload := map[string]any{
"source_id": contactInbox.SourceID,
"inbox": serializeInboxSlim(&contactInbox.Inbox),
}
if contactInbox.Inbox.ChannelType == "shangwutong" {
if status := shangwutongContactNameStatus(contactInbox.ChannelMetadata); status != nil {
status["contact_inbox_id"] = contactInbox.ID
status["source_id"] = contactInbox.SourceID
payload["shangwutong_contact_name_operation"] = status
}
}
return payload
}
func shangwutongContactNameStatus(metadata datatypes.JSON) map[string]any {
var values map[string]any
if len(metadata) == 0 || json.Unmarshal(metadata, &values) != nil {
return nil
}
state, ok := values["swt_contact_name_operation"].(map[string]any)
if !ok {
return nil
}
status := map[string]any{}
for _, key := range []string{"event_id", "operation", "status", "error_code", "error_message", "updated_at"} {
if value, exists := state[key]; exists {
status[key] = value
}
}
if len(status) == 0 {
return nil
}
return status
}
func serializeContactInboxShell(ctx context.Context, db *gorm.DB, contactInbox *model.ContactInbox) map[string]any {
@@ -5,6 +5,7 @@ import (
"github.com/gin-gonic/gin"
"github.com/gochat/gochat/internal/auth"
"github.com/gochat/gochat/internal/model"
"github.com/gochat/gochat/internal/service"
"github.com/gochat/gochat/internal/ws"
@@ -234,7 +235,7 @@ func RegisterCustomRoleRoutes(rg *gin.RouterGroup, h *CustomRoleHandler) {
func isCustomRoleAdmin(c *gin.Context) bool {
role := getRole(c)
return role == "administrator" || role == "super_admin"
return role == auth.RoleAdministrator || role == auth.RoleSuperAdmin
}
func serializeCustomRoles(roles []model.CustomRole) []gin.H {
@@ -9,6 +9,7 @@ import (
"strings"
"github.com/gin-gonic/gin"
"github.com/gochat/gochat/internal/auth"
"github.com/gochat/gochat/internal/model"
"github.com/gochat/gochat/internal/repository"
"github.com/gochat/gochat/internal/service"
@@ -67,7 +68,7 @@ func (h *InboxHandler) List(c *gin.Context) {
inboxes []model.Inbox
svcErr error
)
if getRole(c) == "administrator" {
if getRole(c) == auth.RoleAdministrator {
inboxes, _, svcErr = h.svc.ListByAccount(c.Request.Context(), accountID, offset, perPage)
} else {
userID := getUserID(c)
@@ -995,5 +996,5 @@ func (h *InboxHandler) ResetSecret(c *gin.Context) {
}
func inboxSerializationAdmin(c *gin.Context) bool {
return getRole(c) == "administrator"
return getRole(c) == auth.RoleAdministrator
}
@@ -4,6 +4,7 @@ import (
"net/http"
"github.com/gin-gonic/gin"
"github.com/gochat/gochat/internal/auth"
"github.com/gochat/gochat/internal/model"
"github.com/gochat/gochat/internal/service"
)
@@ -263,7 +264,7 @@ func serializeInboxMemberAgent(member model.InboxMember) gin.H {
role = member.Role
}
if role == "" {
role = "agent"
role = auth.RoleAgent
}
return gin.H{
"id": user.ID,
@@ -6,6 +6,7 @@ import (
"strings"
"github.com/gin-gonic/gin"
"github.com/gochat/gochat/internal/auth"
"github.com/gochat/gochat/internal/automation"
"github.com/gochat/gochat/internal/model"
"github.com/gochat/gochat/internal/service"
@@ -100,7 +101,7 @@ func (h *MacroHandler) Create(c *gin.Context) {
// set_visibility: Chatwoot forces visibility=personal if user role is agent
// Reference: Chatwoot macro.rb set_visibility — self.visibility = :personal if user.agent?
role := getRole(c)
if role == "agent" {
if role == auth.RoleAgent {
macro.Visibility = automation.MacroVisibilityPersonal
}
+14 -14
View File
@@ -19,17 +19,17 @@ import (
"github.com/gochat/gochat/internal/config"
"github.com/gochat/gochat/internal/middleware"
"github.com/gochat/gochat/internal/model"
"github.com/gochat/gochat/pkg/response"
applogger "github.com/gochat/gochat/pkg/logger"
"github.com/gochat/gochat/pkg/response"
)
// OIDCHandler handles OIDC/OAuth2 enterprise authentication HTTP endpoints.
type OIDCHandler struct {
oidcService *auth.OIDCService
ssoMiddleware *auth.SSOMiddleware
jwtService *auth.JWTService
refreshStore *auth.RefreshTokenStore
oidcCfg *config.OIDCConfig
oidcService *auth.OIDCService
ssoMiddleware *auth.SSOMiddleware
jwtService *auth.JWTService
refreshStore *auth.RefreshTokenStore
oidcCfg *config.OIDCConfig
}
// NewOIDCHandler creates an OIDC handler with service dependencies.
@@ -240,12 +240,12 @@ func (h *OIDCHandler) Callback(c *gin.Context) {
c.JSON(http.StatusOK, response.APIResponse{
Success: true,
Data: gin.H{
"access_token": accessToken,
"user_id": result.UserID,
"account_id": result.AccountID,
"role": result.Role,
"provider": string(result.Provider),
"session_id": sessionID,
"access_token": accessToken,
"user_id": result.UserID,
"account_id": result.AccountID,
"role": result.Role,
"provider": string(result.Provider),
"session_id": sessionID,
"auto_provisioned": result.AutoProvision,
},
})
@@ -531,10 +531,10 @@ func RegisterOIDCRoutes(rg *gin.RouterGroup, handler *OIDCHandler, authMiddlewar
// Admin-only config management routes (require auth + administrator role)
configGroup := oidcGroup.Group("/config")
configGroup.Use(authMiddleware, middleware.RoleCheck("administrator"))
configGroup.Use(authMiddleware, middleware.RoleCheck(auth.RoleAdministrator))
{
configGroup.GET("", handler.GetConfig)
configGroup.PUT("", handler.UpdateConfig)
}
}
}
}
@@ -8,6 +8,7 @@ import (
"github.com/gin-gonic/gin"
"gorm.io/gorm"
"github.com/gochat/gochat/internal/auth"
"github.com/gochat/gochat/internal/model"
"github.com/gochat/gochat/internal/search"
"github.com/gochat/gochat/pkg/response"
@@ -424,7 +425,7 @@ func serializeSearchConversationAgentModel(conv *model.Conversation) map[string]
"available_name": nonEmpty(conv.Assignee.DisplayName, conv.Assignee.Name),
"email": conv.Assignee.Email,
"name": conv.Assignee.Name,
"role": nonEmpty(conv.Assignee.Role, "agent"),
"role": nonEmpty(conv.Assignee.Role, auth.RoleAgent),
}
}
return map[string]any{}
File diff suppressed because it is too large Load Diff
@@ -181,6 +181,140 @@ func TestShangwutongClassificationUpdateAcceptsConversationDisplayID(t *testing.
require.Contains(t, string(job.Payload), fmt.Sprintf(`"conversation_id":%d`, conversation.ID))
}
func TestShangwutongConnectorContactOperationStatusIsIdempotentAndStaleSafe(t *testing.T) {
router, db, token, inbox, _ := setupShangwutongConnectorAPI(t)
contact := &model.Contact{AccountID: inbox.AccountID, Name: "Visitor"}
require.NoError(t, db.Create(contact).Error)
ci := &model.ContactInbox{ContactID: contact.ID, InboxID: inbox.ID, SourceID: "visitor", ChannelMetadata: datatypes.JSON([]byte(`{"cid":"cid-1","swt_contact_name_operation":{"event_id":"contact-event-1","operation":"change_contact_name","status":"pending","account_id":1,"inbox_id":1,"contact_inbox_id":1,"source_id":"visitor","contact_id":1,"name":"Renamed"}}`))}
require.NoError(t, db.Create(ci).Error)
path := fmt.Sprintf("/api/v1/connector/shangwutong/inboxes/%d/contacts/visitor/status", inbox.ID)
body := map[string]any{"event_id": "contact-event-1", "operation": "change_contact_name", "contact_id": contact.ID, "name": "Renamed", "status": "succeeded"}
request := func(payload map[string]any) *httptest.ResponseRecorder {
encoded, err := json.Marshal(payload)
require.NoError(t, err)
req := httptest.NewRequest(http.MethodPut, path, bytes.NewReader(encoded))
req.Header.Set("Authorization", "Bearer "+token)
req.Header.Set("Content-Type", "application/json")
eventID, _ := payload["event_id"].(string)
req.Header.Set("Idempotency-Key", fmt.Sprintf("swt-contact-operation:%d:%s", inbox.ID, eventID))
response := httptest.NewRecorder()
router.ServeHTTP(response, req)
return response
}
updated := request(body)
require.Equal(t, http.StatusOK, updated.Code, updated.Body.String())
replayed := request(body)
require.Equal(t, http.StatusOK, replayed.Code, replayed.Body.String())
require.Contains(t, replayed.Body.String(), `"updated":false`)
body["status"] = "failed"
body["error_code"] = "remote_rejected"
conflict := request(body)
require.Equal(t, http.StatusConflict, conflict.Code, conflict.Body.String())
body["event_id"] = "old-event"
body["status"] = "failed"
stale := request(body)
require.Equal(t, http.StatusOK, stale.Code, stale.Body.String())
require.Contains(t, stale.Body.String(), `"stale":true`)
require.NoError(t, db.First(ci, ci.ID).Error)
var metadata map[string]any
require.NoError(t, json.Unmarshal(ci.ChannelMetadata, &metadata))
state := metadata["swt_contact_name_operation"].(map[string]any)
require.Equal(t, "succeeded", state["status"])
}
func TestShangwutongClassificationCallbackIsScopedAndStaleSafe(t *testing.T) {
router, db, token, inbox, _ := setupShangwutongConnectorAPI(t)
contact := &model.Contact{AccountID: inbox.AccountID, Name: "Visitor"}
require.NoError(t, db.Create(contact).Error)
contactInbox := &model.ContactInbox{ContactID: contact.ID, InboxID: inbox.ID, SourceID: "visitor", ChannelMetadata: datatypes.JSON([]byte(`{"cid":"cid-1"}`))}
otherContactInbox := &model.ContactInbox{ContactID: contact.ID, InboxID: inbox.ID, SourceID: "visitor-other", ChannelMetadata: datatypes.JSON([]byte(`{"cid":"cid-2"}`))}
sameCIDContact := &model.Contact{AccountID: inbox.AccountID, Name: "Merged visitor"}
require.NoError(t, db.Create(contactInbox).Error)
require.NoError(t, db.Create(otherContactInbox).Error)
require.NoError(t, db.Create(sameCIDContact).Error)
sameCIDContactInbox := &model.ContactInbox{ContactID: sameCIDContact.ID, InboxID: inbox.ID, SourceID: "visitor-same-cid", ChannelMetadata: datatypes.JSON([]byte(`{"cid":"cid-1"}`))}
require.NoError(t, db.Create(sameCIDContactInbox).Error)
conversation := &model.Conversation{AccountID: inbox.AccountID, InboxID: inbox.ID, ContactID: contact.ID, ContactInboxID: &contactInbox.ID, Status: "open"}
peer := &model.Conversation{AccountID: inbox.AccountID, InboxID: inbox.ID, ContactID: contact.ID, ContactInboxID: &contactInbox.ID, Status: "open", AdditionalAttributes: datatypes.JSON([]byte(`{"keep":"yes"}`))}
otherConversation := &model.Conversation{AccountID: inbox.AccountID, InboxID: inbox.ID, ContactID: contact.ID, ContactInboxID: &otherContactInbox.ID, Status: "open"}
sameCIDConversation := &model.Conversation{AccountID: inbox.AccountID, InboxID: inbox.ID, ContactID: sameCIDContact.ID, ContactInboxID: &sameCIDContactInbox.ID, Status: "open"}
require.NoError(t, db.Create(conversation).Error)
require.NoError(t, db.Create(peer).Error)
require.NoError(t, db.Create(otherConversation).Error)
require.NoError(t, db.Create(sameCIDConversation).Error)
require.NoError(t, db.Create(&model.ShangwutongClassificationCache{
InboxID: inbox.ID, ConversationKinds: datatypes.JSON([]byte(`[]`)), CustomerColorKinds: datatypes.JSON([]byte(`[{"id":"color-1","name":"VIP"},{"id":"color-2","name":"New"}]`)), SyncStatus: "succeeded",
}).Error)
changePath := fmt.Sprintf("/api/v1/accounts/%d/conversations/%d/shangwutong-classifications", inbox.AccountID, conversation.ID)
accepted := connectorRequest(t, router, "", http.MethodPatch, changePath, map[string]string{"customer_color_id": "color-1"})
require.Equal(t, http.StatusAccepted, accepted.Code, accepted.Body.String())
var queued struct {
SyncID string `json:"sync_id"`
}
require.NoError(t, json.Unmarshal(accepted.Body.Bytes(), &queued))
require.NotEmpty(t, queued.SyncID)
callbackPath := fmt.Sprintf("/api/v1/connector/shangwutong/inboxes/%d/conversations/%d/classifications/status", inbox.ID, conversation.ID)
callback := func(eventID string, body map[string]any) *httptest.ResponseRecorder {
encoded, err := json.Marshal(body)
require.NoError(t, err)
req := httptest.NewRequest(http.MethodPut, callbackPath, bytes.NewReader(encoded))
req.Header.Set("Authorization", "Bearer "+token)
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Idempotency-Key", fmt.Sprintf("swt-classification-operation:%d:%s", inbox.ID, eventID))
response := httptest.NewRecorder()
router.ServeHTTP(response, req)
return response
}
result := map[string]any{"event_id": queued.SyncID, "operation": "set_customer_color", "status": "succeeded", "customer_color_id": "color-1"}
response := callback(queued.SyncID, result)
require.Equal(t, http.StatusOK, response.Code, response.Body.String())
replayed := callback(queued.SyncID, result)
require.Equal(t, http.StatusOK, replayed.Code, replayed.Body.String())
require.Contains(t, replayed.Body.String(), `"updated":false`)
conflicting := map[string]any{"event_id": queued.SyncID, "operation": "set_customer_color", "status": "succeeded", "customer_color_id": "color-2"}
conflict := callback(queued.SyncID, conflicting)
require.Equal(t, http.StatusConflict, conflict.Code, conflict.Body.String())
require.Contains(t, conflict.Body.String(), `"code":"idempotency_conflict"`)
var updatedConversation, updatedPeer, updatedOther, updatedSameCID model.Conversation
require.NoError(t, db.First(&updatedConversation, conversation.ID).Error)
require.NoError(t, db.First(&updatedPeer, peer.ID).Error)
require.NoError(t, db.First(&updatedOther, otherConversation.ID).Error)
require.NoError(t, db.First(&updatedSameCID, sameCIDConversation.ID).Error)
var conversationAttributes, peerAttributes, sameCIDAttributes map[string]any
require.NoError(t, json.Unmarshal(updatedConversation.AdditionalAttributes, &conversationAttributes))
require.NoError(t, json.Unmarshal(updatedPeer.AdditionalAttributes, &peerAttributes))
require.NoError(t, json.Unmarshal(updatedSameCID.AdditionalAttributes, &sameCIDAttributes))
require.Equal(t, "color-1", conversationAttributes["swt_label_color"])
require.Equal(t, "color-1", peerAttributes["swt_label_color"])
require.Equal(t, "color-1", sameCIDAttributes["swt_label_color"])
require.Equal(t, "yes", peerAttributes["keep"])
require.NotContains(t, string(updatedOther.AdditionalAttributes), "swt_label_color")
accepted = connectorRequest(t, router, "", http.MethodPatch, changePath, map[string]string{"customer_color_id": "color-2"})
require.Equal(t, http.StatusAccepted, accepted.Code, accepted.Body.String())
var next struct {
SyncID string `json:"sync_id"`
}
require.NoError(t, json.Unmarshal(accepted.Body.Bytes(), &next))
stale := callback(queued.SyncID, result)
require.Equal(t, http.StatusOK, stale.Code, stale.Body.String())
require.Contains(t, stale.Body.String(), `"stale":true`)
var pendingConversation model.Conversation
require.NoError(t, db.First(&pendingConversation, conversation.ID).Error)
var pendingAttributes map[string]any
require.NoError(t, json.Unmarshal(pendingConversation.AdditionalAttributes, &pendingAttributes))
pendingState := pendingAttributes["swt_classification_operations"].(map[string]any)["set_customer_color"].(map[string]any)
pendingState["status"], pendingState["error_code"], pendingState["error_message"] = "uncertain", "connector_delivery_uncertain", "delivery acknowledgement was not received"
pendingEncoded, err := json.Marshal(pendingAttributes)
require.NoError(t, err)
require.NoError(t, db.Model(&pendingConversation).Update("additional_attributes", datatypes.JSON(pendingEncoded)).Error)
resolved := callback(next.SyncID, map[string]any{"event_id": next.SyncID, "operation": "set_customer_color", "status": "succeeded", "customer_color_id": "color-2"})
require.Equal(t, http.StatusOK, resolved.Code, resolved.Body.String())
require.Contains(t, resolved.Body.String(), `"updated":true`)
}
func uintPointer(value uint) *uint { return &value }
func setupShangwutongConnectorAPI(t *testing.T) (*gin.Engine, *gorm.DB, string, *model.Inbox, *model.Inbox) {
@@ -235,6 +369,8 @@ func setupShangwutongConnectorAPI(t *testing.T) (*gin.Engine, *gorm.DB, string,
group.PUT("/inboxes/:inbox_id/status", handler.UpdateInboxStatus)
group.PUT("/inboxes/:inbox_id/messages/:message_id/status", handler.UpdateMessageStatus)
group.PATCH("/inboxes/:inbox_id/contacts/:source_id", handler.UpdateContactMetadata)
group.PUT("/inboxes/:inbox_id/contacts/:source_id/status", handler.UpdateContactOperationStatus)
group.PUT("/inboxes/:inbox_id/conversations/:conversation_id/classifications/status", handler.UpdateClassificationStatus)
router.PATCH("/api/v1/accounts/:account_id/conversations/:conversation_id/shangwutong-classifications", handler.UpdateConversationClassification)
return router, db, token, inboxes[0], inboxes[1]
}
@@ -9,6 +9,7 @@ import (
"time"
"github.com/gin-gonic/gin"
"github.com/gochat/gochat/internal/auth"
"github.com/gochat/gochat/internal/model"
"github.com/gochat/gochat/internal/service"
@@ -37,7 +38,7 @@ func (h *WhatsAppCallHandler) Index(c *gin.Context) {
inboxID, _ := strconv.ParseUint(c.Query("inbox_id"), 10, 64)
agentID, _ := strconv.ParseUint(c.Query("agent_id"), 10, 64)
role := c.GetString("role")
filter := service.AccountCallListFilter{Page: page, UserID: getUserID(c), CustomRoleID: getCustomRoleID(c), AccountWide: role == "administrator" || role == "super_admin", Status: c.Query("status"), Direction: c.Query("direction"), InboxID: uint(inboxID), AgentID: uint(agentID)}
filter := service.AccountCallListFilter{Page: page, UserID: getUserID(c), CustomRoleID: getCustomRoleID(c), AccountWide: role == auth.RoleAdministrator || role == auth.RoleSuperAdmin, Status: c.Query("status"), Direction: c.Query("direction"), InboxID: uint(inboxID), AgentID: uint(agentID)}
if c.Query("since") != "" && c.Query("until") != "" {
since, errSince := parseChatwootReportTime(c.Query("since"))
until, errUntil := parseChatwootReportTime(c.Query("until"))