feat(crm): route crm search through meilisearch
This commit is contained in:
@@ -636,7 +636,9 @@ func Bootstrap(env string) (*App, error) {
|
||||
conversationService.SetSearchIndexer(searchService)
|
||||
messageService.SetSearchIndexer(searchService)
|
||||
contactService.SetSearchIndexer(searchService)
|
||||
contactService.SetSearchReader(searchService)
|
||||
companyService.SetSearchIndexer(searchService)
|
||||
companyService.SetSearchReader(searchService)
|
||||
articleService.SetSearchIndexer(searchService)
|
||||
|
||||
// Custom attribute definition + custom filter + custom attribute value services
|
||||
|
||||
@@ -24,11 +24,15 @@ type mockSearchRepo struct {
|
||||
msgTotal int64
|
||||
msgErr error
|
||||
|
||||
contacts []model.Contact
|
||||
contacts []model.Contact
|
||||
contactTotal int64
|
||||
contactErr error
|
||||
|
||||
articles []model.Article
|
||||
companies []model.Company
|
||||
companyTotal int64
|
||||
companyErr error
|
||||
|
||||
articles []model.Article
|
||||
articleTotal int64
|
||||
articleErr error
|
||||
}
|
||||
@@ -45,6 +49,10 @@ func (m *mockSearchRepo) SearchContacts(ctx context.Context, accountID uint, que
|
||||
return m.contacts, m.contactTotal, m.contactErr
|
||||
}
|
||||
|
||||
func (m *mockSearchRepo) SearchCompanies(ctx context.Context, accountID uint, query string, filter *search.SearchFilter) ([]model.Company, int64, error) {
|
||||
return m.companies, m.companyTotal, m.companyErr
|
||||
}
|
||||
|
||||
func (m *mockSearchRepo) SearchArticles(ctx context.Context, accountID uint, query string, filter *search.SearchFilter) ([]model.Article, int64, error) {
|
||||
return m.articles, m.articleTotal, m.articleErr
|
||||
}
|
||||
@@ -263,8 +271,8 @@ func TestSearchHandler_SearchMessages_ServiceError(t *testing.T) {
|
||||
|
||||
func TestSearchHandler_SearchContacts_Success(t *testing.T) {
|
||||
repo := &mockSearchRepo{
|
||||
contacts: []model.Contact{makeContact(1, "Alice")},
|
||||
contactTotal: 1,
|
||||
contacts: []model.Contact{makeContact(1, "Alice")},
|
||||
contactTotal: 1,
|
||||
}
|
||||
svc := search.NewSearchService(repo)
|
||||
handler := NewSearchHandler(svc)
|
||||
@@ -315,8 +323,8 @@ func TestSearchHandler_SearchContacts_ServiceError(t *testing.T) {
|
||||
|
||||
func TestSearchHandler_SearchArticles_Success(t *testing.T) {
|
||||
repo := &mockSearchRepo{
|
||||
articles: []model.Article{makeArticle(1, 1, "FAQ Guide", "desc", "content", "published")},
|
||||
articleTotal: 1,
|
||||
articles: []model.Article{makeArticle(1, 1, "FAQ Guide", "desc", "content", "published")},
|
||||
articleTotal: 1,
|
||||
}
|
||||
svc := search.NewSearchService(repo)
|
||||
handler := NewSearchHandler(svc)
|
||||
@@ -361,4 +369,4 @@ func TestSearchHandler_SearchArticles_ServiceError(t *testing.T) {
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(t, http.StatusUnprocessableEntity, w.Code)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -280,6 +280,38 @@ func (r *SearchRepo) searchContactsInternal(ctx context.Context, accountID uint,
|
||||
return contacts, total, err
|
||||
}
|
||||
|
||||
// SearchCompanies searches companies with advanced filters.
|
||||
func (r *SearchRepo) SearchCompanies(ctx context.Context, accountID uint, query string, filter *search.SearchFilter) ([]model.Company, int64, error) {
|
||||
repoFilter := searchFilterToRepo(filter)
|
||||
return r.searchCompaniesInternal(ctx, accountID, query, repoFilter)
|
||||
}
|
||||
|
||||
func (r *SearchRepo) searchCompaniesInternal(ctx context.Context, accountID uint, query string, filter *RepoSearchFilter) ([]model.Company, int64, error) {
|
||||
var companies []model.Company
|
||||
var total int64
|
||||
|
||||
q := r.db.WithContext(ctx).Model(&model.Company{}).Where("account_id = ?", accountID)
|
||||
if query != "" {
|
||||
if filter.IsTrigram() {
|
||||
q = q.Where("name % ? OR description % ? OR domain % ? OR website_url % ?", query, query, query, query)
|
||||
} else {
|
||||
likeQuery := "%" + query + "%"
|
||||
q = q.Where("LOWER(name) LIKE LOWER(?) OR LOWER(description) LIKE LOWER(?) OR LOWER(domain) LIKE LOWER(?) OR LOWER(website_url) LIKE LOWER(?)",
|
||||
likeQuery, likeQuery, likeQuery, likeQuery)
|
||||
}
|
||||
}
|
||||
|
||||
q = applyDateRangeFilter(q, filter)
|
||||
if err := q.Count(&total).Error; err != nil {
|
||||
return nil, 0, fmt.Errorf("search companies count: %w", err)
|
||||
}
|
||||
|
||||
err := q.Offset(filter.Offset()).Limit(filter.PerPage).
|
||||
Order(filter.OrderClause() + ", id DESC").
|
||||
Find(&companies).Error
|
||||
return companies, total, err
|
||||
}
|
||||
|
||||
// SearchArticles searches knowledge base articles with advanced filters.
|
||||
// This method satisfies search.SearchRepoInterface by accepting *search.SearchFilter
|
||||
// and converting it to *RepoSearchFilter internally.
|
||||
@@ -470,4 +502,4 @@ func applyDateRangeFilter(q *gorm.DB, filter *RepoSearchFilter) *gorm.DB {
|
||||
}
|
||||
|
||||
return q
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,5 +14,6 @@ type SearchRepoInterface interface {
|
||||
SearchConversations(ctx context.Context, accountID uint, query string, filter *SearchFilter) ([]model.Conversation, int64, error)
|
||||
SearchMessages(ctx context.Context, accountID uint, query string, filter *SearchFilter) ([]model.Message, int64, error)
|
||||
SearchContacts(ctx context.Context, accountID uint, query string, filter *SearchFilter) ([]model.Contact, int64, error)
|
||||
SearchCompanies(ctx context.Context, accountID uint, query string, filter *SearchFilter) ([]model.Company, int64, error)
|
||||
SearchArticles(ctx context.Context, accountID uint, query string, filter *SearchFilter) ([]model.Article, int64, error)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -126,6 +126,27 @@ func (s *SearchService) GlobalSearch(ctx context.Context, accountID uint, query
|
||||
}
|
||||
}
|
||||
|
||||
// Search companies
|
||||
if filter.ShouldSearchType(ResultTypeCompany) {
|
||||
companies, companyCount, err := s.searchRepo.SearchCompanies(ctx, accountID, query, filter)
|
||||
if err != nil {
|
||||
applogger.L().Warnf("search companies error: %v", err)
|
||||
} else {
|
||||
byType["company"] = companyCount
|
||||
totalCount += companyCount
|
||||
for _, company := range companies {
|
||||
allResults = append(allResults, SearchResult{
|
||||
Type: ResultTypeCompany,
|
||||
ID: company.ID,
|
||||
AccountID: company.AccountID,
|
||||
Snippet: company.Name,
|
||||
Score: 1,
|
||||
Data: company,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Search articles (Knowledge Base)
|
||||
if filter.ShouldSearchType(ResultTypeArticle) {
|
||||
articles, articleCount, err := s.searchRepo.SearchArticles(ctx, accountID, query, filter)
|
||||
@@ -235,6 +256,30 @@ func (s *SearchService) SearchContacts(ctx context.Context, accountID uint, quer
|
||||
return results, total, nil
|
||||
}
|
||||
|
||||
// SearchCompanies performs a filtered company search.
|
||||
func (s *SearchService) SearchCompanies(ctx context.Context, accountID uint, query string, filter *SearchFilter) ([]SearchResult, int64, error) {
|
||||
if s.engine != nil {
|
||||
return s.searchWithEngineForType(ctx, accountID, query, filter, ResultTypeCompany)
|
||||
}
|
||||
companies, total, err := s.searchRepo.SearchCompanies(ctx, accountID, query, filter)
|
||||
if err != nil {
|
||||
return nil, 0, fmt.Errorf("search companies: %w", err)
|
||||
}
|
||||
|
||||
results := make([]SearchResult, len(companies))
|
||||
for i, company := range companies {
|
||||
results[i] = SearchResult{
|
||||
Type: ResultTypeCompany,
|
||||
ID: company.ID,
|
||||
AccountID: company.AccountID,
|
||||
Snippet: company.Name,
|
||||
Score: 1,
|
||||
Data: company,
|
||||
}
|
||||
}
|
||||
return results, total, nil
|
||||
}
|
||||
|
||||
// SearchArticles performs a filtered knowledge base article search.
|
||||
// Convenience method for article-only search with full filter support.
|
||||
func (s *SearchService) SearchArticles(ctx context.Context, accountID uint, query string, filter *SearchFilter) ([]SearchResult, int64, error) {
|
||||
|
||||
@@ -15,20 +15,24 @@ import (
|
||||
// mockSearchRepo implements SearchRepoInterface for testing.
|
||||
type mockSearchRepo struct {
|
||||
conversations []model.Conversation
|
||||
convCount int64
|
||||
convErr error
|
||||
convCount int64
|
||||
convErr error
|
||||
|
||||
messages []model.Message
|
||||
msgCount int64
|
||||
msgErr error
|
||||
messages []model.Message
|
||||
msgCount int64
|
||||
msgErr error
|
||||
|
||||
contacts []model.Contact
|
||||
contactCount int64
|
||||
contactErr error
|
||||
|
||||
articles []model.Article
|
||||
articleCount int64
|
||||
articleErr error
|
||||
companies []model.Company
|
||||
companyCount int64
|
||||
companyErr error
|
||||
|
||||
articles []model.Article
|
||||
articleCount int64
|
||||
articleErr error
|
||||
}
|
||||
|
||||
func (m *mockSearchRepo) SearchConversations(ctx context.Context, accountID uint, query string, filter *SearchFilter) ([]model.Conversation, int64, error) {
|
||||
@@ -43,6 +47,10 @@ func (m *mockSearchRepo) SearchContacts(ctx context.Context, accountID uint, que
|
||||
return m.contacts, m.contactCount, m.contactErr
|
||||
}
|
||||
|
||||
func (m *mockSearchRepo) SearchCompanies(ctx context.Context, accountID uint, query string, filter *SearchFilter) ([]model.Company, int64, error) {
|
||||
return m.companies, m.companyCount, m.companyErr
|
||||
}
|
||||
|
||||
func (m *mockSearchRepo) SearchArticles(ctx context.Context, accountID uint, query string, filter *SearchFilter) ([]model.Article, int64, error) {
|
||||
return m.articles, m.articleCount, m.articleErr
|
||||
}
|
||||
@@ -75,6 +83,15 @@ func makeContact(id uint, accountID uint, name string, email string, phone strin
|
||||
}
|
||||
}
|
||||
|
||||
func makeCompany(id uint, accountID uint, name string, domain string) model.Company {
|
||||
return model.Company{
|
||||
AccountID: accountID,
|
||||
ID: id,
|
||||
Name: name,
|
||||
Domain: domain,
|
||||
}
|
||||
}
|
||||
|
||||
func makeArticle(id uint, accountID uint, title string, description string, content string, status string) model.Article {
|
||||
return model.Article{
|
||||
Base: model.Base{ID: id},
|
||||
@@ -277,6 +294,21 @@ func TestSearchContacts_ConvenienceMethod(t *testing.T) {
|
||||
assert.Equal(t, ResultTypeContact, results[0].Type)
|
||||
}
|
||||
|
||||
func TestSearchCompanies_ConvenienceMethod(t *testing.T) {
|
||||
repo := &mockSearchRepo{}
|
||||
repo.companies = []model.Company{makeCompany(7, 1, "Acme", "acme.example")}
|
||||
repo.companyCount = 1
|
||||
|
||||
svc := NewSearchService(repo)
|
||||
filter := &SearchFilter{Page: 1, PerPage: 25}
|
||||
|
||||
results, total, err := svc.SearchCompanies(context.Background(), 1, "acme", filter)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, int64(1), total)
|
||||
assert.Len(t, results, 1)
|
||||
assert.Equal(t, ResultTypeCompany, results[0].Type)
|
||||
}
|
||||
|
||||
func TestSearchArticles_ConvenienceMethod(t *testing.T) {
|
||||
repo := &mockSearchRepo{}
|
||||
repo.articles = []model.Article{makeArticle(1, 1, "FAQ", "Common questions", "Details here", "published")}
|
||||
@@ -555,4 +587,4 @@ func TestSortResultsByScore_StableOrder(t *testing.T) {
|
||||
assert.Equal(t, uint(1), results[0].ID)
|
||||
assert.Equal(t, uint(2), results[1].ID)
|
||||
assert.Equal(t, uint(3), results[2].ID)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ type CompanyService struct {
|
||||
contactRepo *repository.ContactRepo
|
||||
conversationRepo *repository.ConversationRepo
|
||||
searchIndexer SearchIndexer
|
||||
searchReader CompanySearchReader
|
||||
}
|
||||
|
||||
// NewCompanyService creates a new Company service.
|
||||
@@ -37,6 +38,10 @@ func (s *CompanyService) SetSearchIndexer(indexer SearchIndexer) {
|
||||
s.searchIndexer = indexer
|
||||
}
|
||||
|
||||
func (s *CompanyService) SetSearchReader(reader CompanySearchReader) {
|
||||
s.searchReader = reader
|
||||
}
|
||||
|
||||
func (s *CompanyService) DB() *gorm.DB {
|
||||
if s == nil || s.companyRepo == nil {
|
||||
return nil
|
||||
@@ -102,9 +107,36 @@ func (s *CompanyService) Search(ctx context.Context, accountID uint, query strin
|
||||
if query == "" {
|
||||
return s.companyRepo.ListByAccount(ctx, accountID, offset, limit, sort)
|
||||
}
|
||||
if s.searchReader != nil {
|
||||
filter := serviceSearchFilter(offset, limit, sort, searchMode, search.ResultTypeCompany)
|
||||
results, total, err := s.searchReader.SearchCompanies(ctx, accountID, query, filter)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
companies, err := s.companiesFromSearchResults(ctx, accountID, results)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
return companies, total, nil
|
||||
}
|
||||
return s.companyRepo.Search(ctx, accountID, query, offset, limit, sort, searchMode)
|
||||
}
|
||||
|
||||
func (s *CompanyService) companiesFromSearchResults(ctx context.Context, accountID uint, results []search.SearchResult) ([]model.Company, error) {
|
||||
companies := make([]model.Company, 0, len(results))
|
||||
for _, result := range results {
|
||||
if result.ID == 0 || result.AccountID != accountID {
|
||||
continue
|
||||
}
|
||||
company, err := s.companyRepo.FindByIDAndAccount(ctx, result.ID, accountID)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
companies = append(companies, *company)
|
||||
}
|
||||
return companies, nil
|
||||
}
|
||||
|
||||
// Get retrieves a single company by ID scoped to an account.
|
||||
func (s *CompanyService) Get(ctx context.Context, id, accountID uint) (*model.Company, error) {
|
||||
company, err := s.companyRepo.FindByIDAndAccount(ctx, id, accountID)
|
||||
|
||||
@@ -15,6 +15,19 @@ import (
|
||||
"github.com/gochat/gochat/internal/search"
|
||||
)
|
||||
|
||||
type mockCompanySearchReader struct {
|
||||
results []search.SearchResult
|
||||
total int64
|
||||
filter *search.SearchFilter
|
||||
query string
|
||||
}
|
||||
|
||||
func (m *mockCompanySearchReader) SearchCompanies(ctx context.Context, accountID uint, query string, filter *search.SearchFilter) ([]search.SearchResult, int64, error) {
|
||||
m.query = query
|
||||
m.filter = filter
|
||||
return m.results, m.total, nil
|
||||
}
|
||||
|
||||
// ========== Test Setup ==========
|
||||
|
||||
func setupCompanyServiceTest(t *testing.T) (*gorm.DB, *repository.CompanyRepo, *repository.ContactRepo, *repository.ConversationRepo, *CompanyService) {
|
||||
@@ -264,6 +277,30 @@ func TestCompanyService_Search_EmptyQuery(t *testing.T) {
|
||||
assert.Len(t, companies, 1)
|
||||
}
|
||||
|
||||
func TestCompanyService_Search_UsesSearchReader(t *testing.T) {
|
||||
db, _, _, _, svc := setupCompanyServiceTest(t)
|
||||
account := createTestAccount(t, db)
|
||||
company := createTestCompanySvc(t, db, account.ID, "Meili Corp", "meili.example.com")
|
||||
createTestCompanySvc(t, db, account.ID, "DB Corp", "db.example.com")
|
||||
|
||||
reader := &mockCompanySearchReader{
|
||||
results: []search.SearchResult{{Type: search.ResultTypeCompany, ID: company.ID, AccountID: account.ID}},
|
||||
total: 1,
|
||||
}
|
||||
svc.SetSearchReader(reader)
|
||||
|
||||
companies, total, err := svc.Search(context.Background(), account.ID, "meili", 0, 10, "", search.SearchModeILike)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, int64(1), total)
|
||||
require.Len(t, companies, 1)
|
||||
assert.Equal(t, company.ID, companies[0].ID)
|
||||
assert.Equal(t, "meili", reader.query)
|
||||
require.NotNil(t, reader.filter)
|
||||
assert.Equal(t, []search.SearchResultType{search.ResultTypeCompany}, reader.filter.Types)
|
||||
assert.Equal(t, 1, reader.filter.Page)
|
||||
assert.Equal(t, 10, reader.filter.PerPage)
|
||||
}
|
||||
|
||||
// ========== ListContacts ==========
|
||||
|
||||
func TestCompanyService_ListContacts(t *testing.T) {
|
||||
|
||||
@@ -29,6 +29,7 @@ type ContactService struct {
|
||||
contactInboxSvc *ContactInboxService
|
||||
noteRepo *repository.NoteRepo
|
||||
searchIndexer SearchIndexer
|
||||
searchReader ContactSearchReader
|
||||
}
|
||||
|
||||
// NewContactService creates a new Contact service.
|
||||
@@ -40,6 +41,10 @@ func (s *ContactService) SetSearchIndexer(indexer SearchIndexer) {
|
||||
s.searchIndexer = indexer
|
||||
}
|
||||
|
||||
func (s *ContactService) SetSearchReader(reader ContactSearchReader) {
|
||||
s.searchReader = reader
|
||||
}
|
||||
|
||||
func (s *ContactService) indexContact(ctx context.Context, contact *model.Contact) {
|
||||
if s.searchIndexer != nil {
|
||||
logSearchIndexError("contact", contact.ID, s.searchIndexer.IndexContact(ctx, contact))
|
||||
@@ -74,9 +79,44 @@ func (s *ContactService) Search(ctx context.Context, accountID uint, query strin
|
||||
if query == "" {
|
||||
return s.repo.FindByAccount(ctx, accountID, offset, limit, sort, labels...)
|
||||
}
|
||||
if s.searchReader != nil {
|
||||
filter := serviceSearchFilter(offset, limit, sort, searchMode, search.ResultTypeContact)
|
||||
filter.Labels = firstServiceContactLabelFilter(labels)
|
||||
results, total, err := s.searchReader.SearchContacts(ctx, accountID, query, filter)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
contacts, err := s.contactsFromSearchResults(ctx, accountID, results)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
return contacts, total, nil
|
||||
}
|
||||
return s.repo.Search(ctx, accountID, query, offset, limit, sort, searchMode, labels...)
|
||||
}
|
||||
|
||||
func (s *ContactService) contactsFromSearchResults(ctx context.Context, accountID uint, results []search.SearchResult) ([]model.Contact, error) {
|
||||
contacts := make([]model.Contact, 0, len(results))
|
||||
for _, result := range results {
|
||||
if result.ID == 0 || result.AccountID != accountID {
|
||||
continue
|
||||
}
|
||||
contact, err := s.repo.FindByAccountAndID(ctx, accountID, result.ID)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
contacts = append(contacts, *contact)
|
||||
}
|
||||
return contacts, nil
|
||||
}
|
||||
|
||||
func firstServiceContactLabelFilter(filters [][]string) []string {
|
||||
if len(filters) == 0 {
|
||||
return nil
|
||||
}
|
||||
return normalizeContactServiceLabels(filters[0])
|
||||
}
|
||||
|
||||
// GetByID retrieves a single contact.
|
||||
func (s *ContactService) GetByID(ctx context.Context, id uint) (*model.Contact, error) {
|
||||
return s.repo.FindByID(ctx, id)
|
||||
|
||||
@@ -13,8 +13,22 @@ import (
|
||||
"gorm.io/datatypes"
|
||||
|
||||
"github.com/gochat/gochat/internal/model"
|
||||
"github.com/gochat/gochat/internal/search"
|
||||
)
|
||||
|
||||
type mockContactSearchReader struct {
|
||||
results []search.SearchResult
|
||||
total int64
|
||||
filter *search.SearchFilter
|
||||
query string
|
||||
}
|
||||
|
||||
func (m *mockContactSearchReader) SearchContacts(ctx context.Context, accountID uint, query string, filter *search.SearchFilter) ([]search.SearchResult, int64, error) {
|
||||
m.query = query
|
||||
m.filter = filter
|
||||
return m.results, m.total, nil
|
||||
}
|
||||
|
||||
// ========== ListActive ==========
|
||||
|
||||
func TestContactService_ListActive_ReturnsContactsWithActivity(t *testing.T) {
|
||||
@@ -73,6 +87,32 @@ func TestContactService_ListActive_EmptyWhenNoContacts(t *testing.T) {
|
||||
assert.Len(t, contacts, 0)
|
||||
}
|
||||
|
||||
func TestContactService_Search_UsesSearchReader(t *testing.T) {
|
||||
db, _, svc := setupContactService(t)
|
||||
account := createTestAccount(t, db)
|
||||
contact := &model.Contact{AccountID: account.ID, Name: "Meili Contact", Email: "meili@example.com"}
|
||||
rejected := &model.Contact{AccountID: account.ID, Name: "DB Contact", Email: "db@example.com"}
|
||||
require.NoError(t, db.Create(contact).Error)
|
||||
require.NoError(t, db.Create(rejected).Error)
|
||||
|
||||
reader := &mockContactSearchReader{
|
||||
results: []search.SearchResult{{Type: search.ResultTypeContact, ID: contact.ID, AccountID: account.ID}},
|
||||
total: 1,
|
||||
}
|
||||
svc.SetSearchReader(reader)
|
||||
|
||||
contacts, total, err := svc.Search(context.Background(), account.ID, "meili", 0, 10, "", search.SearchModeILike)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, int64(1), total)
|
||||
require.Len(t, contacts, 1)
|
||||
assert.Equal(t, contact.ID, contacts[0].ID)
|
||||
assert.Equal(t, "meili", reader.query)
|
||||
require.NotNil(t, reader.filter)
|
||||
assert.Equal(t, []search.SearchResultType{search.ResultTypeContact}, reader.filter.Types)
|
||||
assert.Equal(t, 1, reader.filter.Page)
|
||||
assert.Equal(t, 10, reader.filter.PerPage)
|
||||
}
|
||||
|
||||
func TestContactService_ListActive_Pagination(t *testing.T) {
|
||||
db, _, svc := setupContactService(t)
|
||||
account := createTestAccount(t, db)
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
|
||||
"github.com/gochat/gochat/internal/model"
|
||||
"github.com/gochat/gochat/internal/search"
|
||||
applogger "github.com/gochat/gochat/pkg/logger"
|
||||
)
|
||||
|
||||
@@ -22,6 +23,36 @@ type SearchIndexer interface {
|
||||
DeleteArticle(ctx context.Context, accountID uint, id uint) error
|
||||
}
|
||||
|
||||
type ContactSearchReader interface {
|
||||
SearchContacts(ctx context.Context, accountID uint, query string, filter *search.SearchFilter) ([]search.SearchResult, int64, error)
|
||||
}
|
||||
|
||||
type CompanySearchReader interface {
|
||||
SearchCompanies(ctx context.Context, accountID uint, query string, filter *search.SearchFilter) ([]search.SearchResult, int64, error)
|
||||
}
|
||||
|
||||
func serviceSearchFilter(offset, limit int, sort string, searchMode search.SearchMode, resultType search.SearchResultType) *search.SearchFilter {
|
||||
if limit <= 0 {
|
||||
limit = search.DefaultPerPage
|
||||
}
|
||||
page := 1
|
||||
if offset > 0 {
|
||||
page = offset/limit + 1
|
||||
}
|
||||
sortBy := sort
|
||||
if sortBy == "" {
|
||||
sortBy = search.DefaultSortBy
|
||||
}
|
||||
return &search.SearchFilter{
|
||||
SearchMode: searchMode,
|
||||
Types: []search.SearchResultType{resultType},
|
||||
SortBy: sortBy,
|
||||
SortOrder: search.DefaultSortOrder,
|
||||
Page: page,
|
||||
PerPage: limit,
|
||||
}
|
||||
}
|
||||
|
||||
func logSearchIndexError(entity string, id uint, err error) {
|
||||
if err != nil {
|
||||
applogger.L().Warnf("search index sync failed for %s %d: %v", entity, id, err)
|
||||
|
||||
Reference in New Issue
Block a user