search-algolia/algolia.go (232 lines of code) (raw):
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package algolia
import (
"context"
"embed"
"strconv"
"strings"
"github.com/algolia/algoliasearch-client-go/v3/algolia/opt"
"github.com/algolia/algoliasearch-client-go/v3/algolia/search"
"github.com/apache/answer-plugins/search-algolia/i18n"
"github.com/apache/answer-plugins/util"
"github.com/apache/answer/plugin"
)
//go:embed info.yaml
var Info embed.FS
type SearchAlgolia struct {
Config *AlgoliaSearchConfig
client *search.Client
syncer plugin.SearchSyncer
}
func init() {
uc := &SearchAlgolia{Config: &AlgoliaSearchConfig{}}
plugin.Register(uc)
}
func (s *SearchAlgolia) Info() plugin.Info {
info := &util.Info{}
info.GetInfo(Info)
return plugin.Info{
Name: plugin.MakeTranslator(i18n.InfoName),
SlugName: info.SlugName,
Description: plugin.MakeTranslator(i18n.InfoDescription),
Version: info.Version,
Author: info.Author,
Link: info.Link,
}
}
func (s *SearchAlgolia) Description() plugin.SearchDesc {
desc := plugin.SearchDesc{}
if s.Config.ShowLogo {
desc.Icon = icon
desc.Link = "https://www.algolia.com/"
}
return desc
}
func (s *SearchAlgolia) RegisterSyncer(ctx context.Context, syncer plugin.SearchSyncer) {
s.syncer = syncer
s.sync()
}
func (s *SearchAlgolia) SearchContents(ctx context.Context, cond *plugin.SearchBasicCond) (res []plugin.SearchResult, total int64, err error) {
var (
filters = "status<10"
tagFilters []string
userIDFilter string
votesFilter string
)
if len(cond.TagIDs) > 0 {
for _, tagGroup := range cond.TagIDs {
var tagsIn []string
if len(tagGroup) > 0 {
for _, tagID := range tagGroup {
tagsIn = append(tagsIn, "tags:"+tagID)
}
}
tagFilters = append(tagFilters, "("+strings.Join(tagsIn, " OR ")+")")
}
if len(tagFilters) > 0 {
filters += " AND " + strings.Join(tagFilters, " AND ")
}
}
if len(cond.UserID) > 0 {
userIDFilter = "userID:" + cond.UserID
filters += " AND " + userIDFilter
}
if cond.VoteAmount == 0 {
votesFilter = "votes=" + strconv.Itoa(cond.VoteAmount)
filters += " AND " + votesFilter
} else if cond.VoteAmount > 0 {
votesFilter = "votes>=" + strconv.Itoa(cond.VoteAmount)
filters += " AND " + votesFilter
}
var (
query = strings.TrimSpace(strings.Join(cond.Words, " "))
opts = []interface{}{
opt.AttributesToRetrieve("objectID", "type"),
opt.Filters(filters),
opt.Page(cond.Page - 1),
opt.HitsPerPage(cond.PageSize),
}
qres search.QueryRes
)
qres, err = s.getIndex(string(cond.Order)).Search(query, opts...)
for _, hit := range qres.Hits {
res = append(res, plugin.SearchResult{
ID: hit["objectID"].(string),
Type: hit["type"].(string),
})
}
total = int64(qres.NbHits)
return res, total, err
}
func (s *SearchAlgolia) SearchQuestions(ctx context.Context, cond *plugin.SearchBasicCond) (res []plugin.SearchResult, total int64, err error) {
var (
filters = "status<10 AND type:question"
tagFilters []string
userIDFilter string
viewsFilter string
answersFilter string
)
if len(cond.TagIDs) > 0 {
for _, tagGroup := range cond.TagIDs {
var tagsIn []string
if len(tagGroup) > 0 {
for _, tagID := range tagGroup {
tagsIn = append(tagsIn, "tags:"+tagID)
}
}
tagFilters = append(tagFilters, "("+strings.Join(tagsIn, " OR ")+")")
}
if len(tagFilters) > 0 {
filters += " AND " + strings.Join(tagFilters, " AND ")
}
}
if cond.QuestionAccepted == plugin.AcceptedCondFalse {
userIDFilter = "hasAccepted:false"
filters += " AND " + userIDFilter
}
if cond.ViewAmount > -1 {
viewsFilter = "views>=" + strconv.Itoa(cond.ViewAmount)
filters += " AND " + viewsFilter
}
// check answers
if cond.AnswerAmount == 0 {
answersFilter = "answers=0"
filters += " AND " + answersFilter
} else if cond.AnswerAmount > 0 {
answersFilter = "answers>=" + strconv.Itoa(cond.AnswerAmount)
filters += " AND " + answersFilter
}
var (
query = strings.TrimSpace(strings.Join(cond.Words, " "))
opts = []interface{}{
opt.AttributesToRetrieve("objectID", "type"),
opt.Filters(filters),
opt.Page(cond.Page - 1),
opt.HitsPerPage(cond.PageSize),
}
qres search.QueryRes
)
qres, err = s.getIndex(string(cond.Order)).Search(query, opts...)
for _, hit := range qres.Hits {
res = append(res, plugin.SearchResult{
ID: hit["objectID"].(string),
Type: hit["type"].(string),
})
}
total = int64(qres.NbHits)
return res, total, err
}
func (s *SearchAlgolia) SearchAnswers(ctx context.Context, cond *plugin.SearchBasicCond) (res []plugin.SearchResult, total int64, err error) {
var (
filters = "status<10 AND type:answer"
tagFilters []string
userIDFilter string
questionIDFilter string
)
if len(cond.TagIDs) > 0 {
for _, tagGroup := range cond.TagIDs {
var tagsIn []string
if len(tagGroup) > 0 {
for _, tagID := range tagGroup {
tagsIn = append(tagsIn, "tags:"+tagID)
}
}
tagFilters = append(tagFilters, "("+strings.Join(tagsIn, " OR ")+")")
}
if len(tagFilters) > 0 {
filters += " AND " + strings.Join(tagFilters, " AND ")
}
}
if cond.AnswerAccepted == plugin.AcceptedCondTrue {
userIDFilter = "hasAccepted=true"
filters += " AND " + userIDFilter
}
if len(cond.QuestionID) > 0 {
questionIDFilter = "questionID=" + cond.QuestionID
filters += questionIDFilter
}
var (
query = strings.TrimSpace(strings.Join(cond.Words, " "))
opts = []interface{}{
opt.AttributesToRetrieve("objectID", "type"),
opt.Filters(filters),
opt.Page(cond.Page - 1),
opt.HitsPerPage(cond.PageSize),
}
qres search.QueryRes
)
qres, err = s.getIndex(string(cond.Order)).Search(query, opts...)
for _, hit := range qres.Hits {
res = append(res, plugin.SearchResult{
ID: hit["objectID"].(string),
Type: hit["type"].(string),
})
}
total = int64(qres.NbHits)
return res, total, err
}
// UpdateContent updates the content to algolia server
func (s *SearchAlgolia) UpdateContent(ctx context.Context, content *plugin.SearchContent) (err error) {
_, err = s.getIndex("").SaveObject(content)
return
}
// DeleteContent deletes the content
func (s *SearchAlgolia) DeleteContent(ctx context.Context, contentID string) (err error) {
_, err = s.getIndex("").DeleteObject(contentID)
return
}
// connect connect to algolia server
func (s *SearchAlgolia) connect() (err error) {
s.client = search.NewClient(s.Config.APPID, s.Config.APIKey)
return
}
// init or create index
func (s *SearchAlgolia) getIndex(order string) (index *search.Index) {
idx := s.getIndexName(order)
return s.client.InitIndex(idx)
}
func (s *SearchAlgolia) getIndexName(order string) string {
// main index
var idx = s.Config.Index
switch order {
case NewestIndex:
// the index of sort results by newest
idx = idx + "_" + NewestIndex
case ActiveIndex:
// the index of sort results by active
idx = idx + "_" + ActiveIndex
case ScoreIndex:
// the index of sort results by score
idx = idx + "_" + ScoreIndex
}
return idx
}