fix(search): align search filters

This commit is contained in:
2026-06-07 10:01:02 +08:00
parent 3c437dd94c
commit 023ab10b31
7 changed files with 238 additions and 25 deletions
+28 -6
View File
@@ -85,7 +85,7 @@ func (h *SearchHandler) GlobalSearch(c *gin.Context) {
}
query := c.Query("q")
filter := parseChatwootSearchFilter(c)
filter := parseChatwootSearchFilter(c, "global")
filter.Types = append([]search.SearchResultType(nil), chatwootGlobalSearchTypes...)
result, svcErr := h.svc.GlobalSearch(c.Request.Context(), accountID, query, &filter)
@@ -135,7 +135,7 @@ func (h *SearchHandler) SearchConversations(c *gin.Context) {
}
query := c.Query("q")
filter := parseChatwootSearchFilter(c)
filter := parseChatwootSearchFilter(c, "conversation")
// Force type to conversations only
filter.Types = []search.SearchResultType{search.ResultTypeConversation}
@@ -186,7 +186,7 @@ func (h *SearchHandler) SearchMessages(c *gin.Context) {
}
query := c.Query("q")
filter := parseChatwootSearchFilter(c)
filter := parseChatwootSearchFilter(c, "message")
// Force type to messages only
filter.Types = []search.SearchResultType{search.ResultTypeMessage}
@@ -231,7 +231,7 @@ func (h *SearchHandler) SearchContacts(c *gin.Context) {
}
query := c.Query("q")
filter := parseChatwootSearchFilter(c)
filter := parseChatwootSearchFilter(c, "contact")
// Force type to contacts only
filter.Types = []search.SearchResultType{search.ResultTypeContact}
@@ -279,7 +279,7 @@ func (h *SearchHandler) SearchArticles(c *gin.Context) {
}
query := c.Query("q")
filter := parseChatwootSearchFilter(c)
filter := parseChatwootSearchFilter(c, "article")
// Force type to articles only
filter.Types = []search.SearchResultType{search.ResultTypeArticle}
@@ -302,12 +302,34 @@ func serializeSearchPayload(ctx context.Context, db *gorm.DB, results []search.S
}
}
func parseChatwootSearchFilter(c *gin.Context) search.SearchFilter {
func parseChatwootSearchFilter(c *gin.Context, scope string) search.SearchFilter {
filter := search.ParseSearchFilter(c)
if userID := getUserID(c); userID != 0 {
filter.CurrentUserID = &userID
}
filter.PerPage = chatwootSearchPerPage
filter = sanitizeChatwootSearchFilter(filter, scope)
return filter
}
func sanitizeChatwootSearchFilter(filter search.SearchFilter, scope string) search.SearchFilter {
filter.Status = nil
filter.Priority = nil
filter.AssigneeID = nil
filter.TeamID = nil
filter.Labels = nil
filter.ContactSource = ""
filter.MessageType = ""
filter.ContentType = ""
filter.Private = nil
filter.PortalID = nil
filter.ArticleStatus = ""
filter.ArticleLocale = ""
if scope != "message" && scope != "global" {
filter.SenderType = ""
filter.SenderID = nil
filter.InboxID = nil
}
return filter
}
+69 -6
View File
@@ -25,27 +25,31 @@ type mockSearchRepo struct {
conversations []model.Conversation
convTotal int64
convErr error
convFilter *search.SearchFilter
messages []model.Message
msgTotal int64
msgErr error
msgFilter *search.SearchFilter
contacts []model.Contact
contactTotal int64
contactErr error
contacts []model.Contact
contactTotal int64
contactErr error
contactFilter *search.SearchFilter
companies []model.Company
companyTotal int64
companyErr error
companyCalled bool
articles []model.Article
articleTotal int64
articleErr error
articles []model.Article
articleTotal int64
articleErr error
articleFilter *search.SearchFilter
}
func (m *mockSearchRepo) SearchConversations(ctx context.Context, accountID uint, query string, filter *search.SearchFilter) ([]model.Conversation, int64, error) {
m.convFilter = filter
return m.conversations, m.convTotal, m.convErr
}
@@ -55,6 +59,7 @@ func (m *mockSearchRepo) SearchMessages(ctx context.Context, accountID uint, que
}
func (m *mockSearchRepo) SearchContacts(ctx context.Context, accountID uint, query string, filter *search.SearchFilter) ([]model.Contact, int64, error) {
m.contactFilter = filter
return m.contacts, m.contactTotal, m.contactErr
}
@@ -64,6 +69,7 @@ func (m *mockSearchRepo) SearchCompanies(ctx context.Context, accountID uint, qu
}
func (m *mockSearchRepo) SearchArticles(ctx context.Context, accountID uint, query string, filter *search.SearchFilter) ([]model.Article, int64, error) {
m.articleFilter = filter
return m.articles, m.articleTotal, m.articleErr
}
@@ -287,6 +293,34 @@ func TestSearchHandler_SearchConversations_ChatwootPayloadShape(t *testing.T) {
assert.Equal(t, float64(0), item["message"].(map[string]any)["message_type"])
}
func TestSearchHandler_SearchConversations_IgnoresUnsupportedSearchFilters(t *testing.T) {
repo := &mockSearchRepo{
conversations: []model.Conversation{makeConversation(1, "open")},
convTotal: 1,
}
svc := search.NewSearchService(repo)
handler := NewSearchHandler(svc)
router := setupSearchHandlerRouter(handler)
w := httptest.NewRecorder()
req := httptest.NewRequest("GET", "/api/v1/accounts/1/search/conversations?q=billing&status=resolved&priority=urgent&labels=vip&inbox_id=5&from=agent:7&message_type=outgoing&contact_source=email&portal_id=2&article_status=draft&locale=fr", nil)
router.ServeHTTP(w, req)
require.Equal(t, http.StatusOK, w.Code)
require.NotNil(t, repo.convFilter)
assert.Empty(t, repo.convFilter.Status)
assert.Empty(t, repo.convFilter.Priority)
assert.Empty(t, repo.convFilter.Labels)
assert.Nil(t, repo.convFilter.InboxID)
assert.Empty(t, repo.convFilter.SenderType)
assert.Nil(t, repo.convFilter.SenderID)
assert.Empty(t, repo.convFilter.MessageType)
assert.Empty(t, repo.convFilter.ContactSource)
assert.Nil(t, repo.convFilter.PortalID)
assert.Empty(t, repo.convFilter.ArticleStatus)
assert.Empty(t, repo.convFilter.ArticleLocale)
}
func TestSearchHandler_SearchConversations_InvalidAccountID(t *testing.T) {
repo := &mockSearchRepo{}
svc := search.NewSearchService(repo)
@@ -363,6 +397,35 @@ func TestSearchHandler_SearchMessages_AddsCurrentUserToFilter(t *testing.T) {
}
}
func TestSearchHandler_SearchMessages_KeepsOnlyReferenceAdvancedFilters(t *testing.T) {
repo := &mockSearchRepo{
messages: []model.Message{makeMessage(1, 1, "hello world")},
msgTotal: 1,
}
svc := search.NewSearchService(repo)
handler := NewSearchHandler(svc)
router := setupSearchHandlerRouter(handler)
w := httptest.NewRecorder()
req := httptest.NewRequest("GET", "/api/v1/accounts/1/search/messages?q=hello&from=agent:7&inbox_id=5&message_type=outgoing&content_type=text&private=true&status=resolved&contact_source=email&portal_id=2&article_status=draft", nil)
router.ServeHTTP(w, req)
require.Equal(t, http.StatusOK, w.Code)
require.NotNil(t, repo.msgFilter)
require.NotNil(t, repo.msgFilter.SenderID)
assert.Equal(t, "agent", repo.msgFilter.SenderType)
assert.Equal(t, uint(7), *repo.msgFilter.SenderID)
require.NotNil(t, repo.msgFilter.InboxID)
assert.Equal(t, uint(5), *repo.msgFilter.InboxID)
assert.Empty(t, repo.msgFilter.MessageType)
assert.Empty(t, repo.msgFilter.ContentType)
assert.Nil(t, repo.msgFilter.Private)
assert.Empty(t, repo.msgFilter.Status)
assert.Empty(t, repo.msgFilter.ContactSource)
assert.Nil(t, repo.msgFilter.PortalID)
assert.Empty(t, repo.msgFilter.ArticleStatus)
}
func TestSearchHandler_SearchMessages_MeiliHitPayloadShape(t *testing.T) {
svc := search.NewSearchServiceWithEngine(&stubSearchEngine{
resp: &search.SearchResponse{