blob: f046b6b86d59531e016eb24a795987d11eeb0bd2 [file] [log] [blame]
/*
* 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 meilisearch
import (
"context"
"encoding/json"
"fmt"
"strings"
"sync"
"github.com/apache/incubator-answer-plugins/search-meilisearch/i18n"
"github.com/apache/incubator-answer/plugin"
"github.com/meilisearch/meilisearch-go"
"github.com/segmentfault/pacman/errors"
"github.com/segmentfault/pacman/log"
)
const (
primaryKey = "objectID"
defaultIndexName = "answer_post"
)
var (
configuredErr = fmt.Errorf("meilisearch is not configured correctly")
)
type Search struct {
Config *SearchConfig
Client *meilisearch.Client
syncer plugin.SearchSyncer
syncing bool
lock sync.Mutex
}
type SearchConfig struct {
Host string `json:"host"`
ApiKey string `json:"api_key"`
IndexName string `json:"index_name"`
Async bool `json:"async"`
}
func init() {
plugin.Register(&Search{
Config: &SearchConfig{},
lock: sync.Mutex{},
})
}
func (s *Search) Info() plugin.Info {
return plugin.Info{
Name: plugin.MakeTranslator(i18n.InfoName),
SlugName: "meilisearch_search",
Description: plugin.MakeTranslator(i18n.InfoDescription),
Author: "sivdead",
Version: "0.0.1",
Link: "https://github.com/apache/incubator-answer-plugins/tree/main/search-meilisearch",
}
}
func (s *Search) Description() plugin.SearchDesc {
return plugin.SearchDesc{Icon: "PHN2ZyB3aWR0aD0iMjAwIiBoZWlnaHQ9IjMwIiB2aWV3Qm94PSIwIDAgNDk1IDc0IiBmaWxsPSJub25lIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPgo8cGF0aCBkPSJNMTgxLjg0IDQyLjUzNDdDMTgxLjg0IDM3LjYxMzYgMTg0LjE5OSAzNC43MTQ5IDE4OC43MTYgMzQuNzE0OUMxOTIuOTYzIDM0LjcxNDkgMTk0LjM3OCAzNy43NDg0IDE5NC4zNzggNDEuNjU4NFY2Mi42MjM3SDIwMy45NTFWNDAuNTc5OEMyMDMuOTUxIDMyLjM1NTQgMTk5LjYzNyAyNi40OTA2IDE5MS4xNDMgMjYuNDkwNkMxODYuMDg3IDI2LjQ5MDYgMTgyLjUxNCAyOC4wNDEgMTc5LjQxMyAzMS40NzkxQzE3Ny4zOSAyOC4zNzgxIDE3My45NTIgMjYuNDkwNiAxNjkuMTY2IDI2LjQ5MDZDMTY0LjExIDI2LjQ5MDYgMTYwLjYwNSAyOC41ODA0IDE1OC45ODcgMzEuNjEzOVYyNy4yOTk1SDE1MC4xNTZWNjIuNjIzN0gxNTkuNzI4VjQyLjMzMjVDMTU5LjcyOCAzNy42MTM2IDE2Mi4xNTUgMzQuNzE0OSAxNjYuNjA0IDM0LjcxNDlDMTcwLjg1MSAzNC43MTQ5IDE3Mi4yNjcgMzcuNzQ4NCAxNzIuMjY3IDQxLjY1ODRWNjIuNjIzN0gxODEuODRWNDIuNTM0N1oiIGZpbGw9IiMyMTAwNEIiLz4KPHBhdGggZD0iTTI0My4yNDIgNDcuNzI1NUMyNDMuMjQyIDQ3LjcyNTUgMjQzLjM3NyA0Ni40NDQ3IDI0My4zNzcgNDQuODk0MkMyNDMuMzc3IDM0LjQ0NTIgMjM2LjI5OSAyNi40OTA2IDIyNS44NSAyNi40OTA2QzIxNS40MDEgMjYuNDkwNiAyMDguMTIgMzQuNDQ1MiAyMDguMTIgNDQuODk0MkMyMDguMTIgNTUuNzQ3NiAyMTUuNDY4IDYzLjQzMjYgMjI1LjkxNyA2My40MzI2QzIzNC4wNzQgNjMuNDMyNiAyNDAuNTQ2IDU4LjUxMTUgMjQyLjYzNiA1MS4zNjU4SDIzMi45OTZDMjMxLjg1IDUzLjkyNzQgMjI5LjA4NiA1NS4yMDgzIDIyNi4xODcgNTUuMjA4M0MyMjEuNDAxIDU1LjIwODMgMjE4LjMgNTIuNTc5MiAyMTcuNjI2IDQ3LjcyNTVIMjQzLjI0MlpNMjI1Ljc4MyAzNC4xNzU2QzIzMC4yMzIgMzQuMTc1NiAyMzMuMTMxIDM2Ljg3MjEgMjMzLjgwNSA0MC44NDk0SDIxNy43NkMyMTguNTY5IDM2LjgwNDcgMjIxLjQwMSAzNC4xNzU2IDIyNS43ODMgMzQuMTc1NloiIGZpbGw9IiMyMTAwNEIiLz4KPHBhdGggZD0iTTI0NC43ODkgMzUuNTIzOEgyNDkuMDM2VjYyLjYyMzdIMjU4LjYwOFYyNy4yOTk1SDI0NC43ODlWMzUuNTIzOFpNMjUzLjgyMiAyMi43MTU1QzI1Ny4xOTMgMjIuNzE1NSAyNTkuNjE5IDIwLjM1NiAyNTkuNjE5IDE2Ljk4NTRDMjU5LjYxOSAxMy42MTQ4IDI1Ny4xOTMgMTEuMTg3OSAyNTMuODIyIDExLjE4NzlDMjUwLjQ1MSAxMS4xODc5IDI0OC4wMjQgMTMuNjE0OCAyNDguMDI0IDE2Ljk4NTRDMjQ4LjAyNCAyMC4zNTYgMjUwLjQ1MSAyMi43MTU1IDI1My44MjIgMjIuNzE1NVoiIGZpbGw9IiMyMTAwNEIiLz4KPHBhdGggZD0iTTI3OC40MyA1NC4zOTkzQzI3OC4xNiA1NC4zOTkzIDI3Ny43NTYgNTQuNDY2NyAyNzcuMTQ5IDU0LjQ2NjdDMjc0Ljk5MiA1NC40NjY3IDI3NC43MjIgNTMuNDU1NiAyNzQuNzIyIDUxLjk3MjVWMTIuMDY0M0gyNjUuMTVWNTIuNjQ2NkMyNjUuMTUgNTkuNjU3NSAyNjcuODQ2IDYyLjc1ODUgMjc1LjQ2NCA2Mi43NTg1QzI3Ni43NDUgNjIuNzU4NSAyNzcuOTU4IDYyLjYyMzcgMjc4LjQzIDYyLjU1NjJWNTQuMzk5M1oiIGZpbGw9IiMyMTAwNEIiLz4KPHBhdGggZD0iTTI3OS41MTkgMzUuNTIzOEgyODMuNzY2VjYyLjYyMzdIMjkzLjMzOVYyNy4yOTk1SDI3OS41MTlWMzUuNTIzOFpNMjg4LjU1MyAyMi43MTU1QzI5MS45MjMgMjIuNzE1NSAyOTQuMzUgMjAuMzU2IDI5NC4zNSAxNi45ODU0QzI5NC4zNSAxMy42MTQ4IDI5MS45MjMgMTEuMTg3OSAyODguNTUzIDExLjE4NzlDMjg1LjE4MiAxMS4xODc5IDI4Mi43NTUgMTMuNjE0OCAyODIuNzU1IDE2Ljk4NTRDMjgyLjc1NSAyMC4zNTYgMjg1LjE4MiAyMi43MTU1IDI4OC41NTMgMjIuNzE1NVoiIGZpbGw9IiMyMTAwNEIiLz4KPHBhdGggZD0iTTMxMi41NTcgNjIuOTkzOUMzMjEuODYgNjIuOTkzOSAzMjYuMjQyIDU4LjA3MjggMzI2LjI0MiA1Mi44ODJDMzI2LjI0MiAzOC40NTU3IDMwNS4wMDcgNDYuNDc3OCAzMDUuMDA3IDM2Ljk3MjZDMzA1LjAwNyAzMy44NzE3IDMwNy42MzYgMzEuMjQyNiAzMTIuOTYyIDMxLjI0MjZDMzE4LjQyMiAzMS4yNDI2IDMyMC45ODQgMzQuMjA4NyAzMjEuMzg4IDM3LjkxNjRIMzI2LjE3NUMzMjUuNzcgMzMuMjY1IDMyMi42MDIgMjcuMDYzIDMxMy4wOTcgMjcuMDYzQzMwNC45NCAyNy4wNjMgMzAwLjM1NiAzMS45MTY3IDMwMC4zNTYgMzcuMTc0OUMzMDAuMzU2IDUxLjI2NDEgMzIxLjU5MSA0My4xNzQ2IDMyMS41OTEgNTMuMDE2OEMzMjEuNTkxIDU2LjQ1NDggMzE4LjM1NSA1OC44MTQzIDMxMi41NTcgNTguODE0M0MzMDYuNjI1IDU4LjgxNDMgMzAzLjY1OSA1NS44NDgxIDMwMy4zMjIgNTEuNDY2M0gyOTguNDY4QzI5OC44NzIgNTcuNDY2IDMwMi42NDggNjIuOTkzOSAzMTIuNTU3IDYyLjk5MzlaIiBmaWxsPSIjMjEwMDRCIi8+CjxwYXRoIGQ9Ik0zNjQuMjU2IDQ2LjQxMDRDMzY0LjI1NiA0Ni40MTA0IDM2NC4zMjQgNDUuMzMxOCAzNjQuMzI0IDQ0LjU5MDNDMzY0LjMyNCAzNC44ODI5IDM1OC4wNTQgMjcuMDYzIDM0Ny44MDggMjcuMDYzQzMzNy40OTQgMjcuMDYzIDMzMC45NTUgMzUuNDg5NiAzMzAuOTU1IDQ0Ljk5NDdDMzMwLjk1NSA1NC42MzQ3IDMzNy4wMjIgNjIuOTkzOSAzNDcuODc1IDYyLjk5MzlDMzU2LjAzMiA2Mi45OTM5IDM2MS42OTUgNTguMDA1MyAzNjMuNzE3IDUxLjQ2NjNIMzU4LjcyOEMzNTcuMjQ1IDU1LjY0NTkgMzUzLjIwMSA1OC42Nzk1IDM0Ny45NDIgNTguNjc5NUMzNDAuNzI5IDU4LjY3OTUgMzM2LjIxMyA1My4zNTM5IDMzNS43NDEgNDYuNDEwNEgzNjQuMjU2Wk0zNDcuODA4IDMxLjM3NzRDMzU0LjU0OSAzMS4zNzc0IDM1OC45MzEgMzUuODk0IDM1OS41MzcgNDIuNTAwNUgzMzUuODc2QzMzNi42ODUgMzYuMTYzNyAzNDEuMTM0IDMxLjM3NzQgMzQ3LjgwOCAzMS4zNzc0WiIgZmlsbD0iIzIxMDA0QiIvPgo8cGF0aCBkPSJNMzk0LjAzNyA0NS44NzExVjQ5LjEwNjlDMzk0LjAzNyA1NC45NzE4IDM4OS43OSA1OS4wMTY1IDM4MS42MzMgNTkuMDE2NUMzNzYuNTc4IDU5LjAxNjUgMzczLjgxNCA1Ni45MjY3IDM3My44MTQgNTIuNDEwMUMzNzMuODE0IDUwLjExODEgMzc0Ljg5MiA0OC4zNjU0IDM3Ni41NzggNDcuNDIxNkMzNzguMzMgNDYuNDc3OCAzODAuNjkgNDUuODcxMSAzOTQuMDM3IDQ1Ljg3MTFaTTM4MS4wOTQgNjIuOTkzOUMzODcuMDI2IDYyLjk5MzkgMzkxLjgxMyA2MS4xMDYzIDM5NC4yNCA1Ny4xOTY0VjYyLjE4NDlIMzk4LjgyNFYzOS43MzY2QzM5OC44MjQgMzIuMTE4OSAzOTQuNDQyIDI3LjA2MyAzODQuNTMyIDI3LjA2M0MzNzUuMDI3IDI3LjA2MyAzNzAuODQ3IDMxLjg0OTMgMzY5Ljk3MSAzNy45ODM4SDM3NC42MjNDMzc1LjU2NiAzMy4xMzAxIDM3OS4yNzQgMzEuMTc1MiAzODQuMzMgMzEuMTc1MkMzOTAuODAyIDMxLjE3NTIgMzk0LjAzNyAzMy44NzE3IDM5NC4wMzcgMzkuNjY5MVY0MS44OTM4QzM4My4xODQgNDEuODkzOCAzNzguNjY3IDQyLjA5NiAzNzUuMjk3IDQzLjQ0NDJDMzcxLjM4NyA0NC45OTQ3IDM2OS4wOTUgNDguNDMyOCAzNjkuMDk1IDUyLjU0NDlDMzY5LjA5NSA1OC41NDQ2IDM3Mi45MzcgNjIuOTkzOSAzODEuMDk0IDYyLjk5MzlaIiBmaWxsPSIjMjEwMDRCIi8+CjxwYXRoIGQ9Ik00MjQuOTkxIDI3LjYwMjNDNDI0Ljk5MSAyNy42MDIzIDQyNC4xODIgMjcuNTM0OSA0MjMuODQ1IDI3LjUzNDlDNDE3LjUwOCAyNy41MzQ5IDQxNC4xMzggMzAuODM4MSA0MTIuODU3IDMzLjE5NzVWMjcuODcySDQwOC4yNzNWNjIuMTg0OUg0MTMuMDU5VjQyLjcwMjdDNDEzLjA1OSAzNS41NTcgNDE3LjQ0MSAzMi4wNTE1IDQyMy4zMDYgMzIuMDUxNUM0MjQuMTgyIDMyLjA1MTUgNDI0Ljk5MSAzMi4xMTg5IDQyNC45OTEgMzIuMTE4OVYyNy42MDIzWiIgZmlsbD0iIzIxMDA0QiIvPgo8cGF0aCBkPSJNNDI1LjgwOSA0NS4wNjIxQzQyNS44MDkgNTQuNDMyNSA0MzIuMjggNjIuOTkzOSA0NDIuNzI5IDYyLjk5MzlDNDUyLjAzMiA2Mi45OTM5IDQ1Ny40MjUgNTYuNzkxOSA0NTguNzczIDQ5Ljk4MzJINDUzLjkyQzQ1Mi41MDQgNTUuMzA4OCA0NDguNTk0IDU4LjY3OTUgNDQyLjcyOSA1OC42Nzk1QzQzNS41MTYgNTguNjc5NSA0MzAuNjYyIDUyLjk0OTQgNDMwLjY2MiA0NS4wNjIxQzQzMC42NjIgMzcuMTA3NSA0MzUuNTE2IDMxLjM3NzQgNDQyLjcyOSAzMS4zNzc0QzQ0OC41OTQgMzEuMzc3NCA0NTIuNTA0IDM0Ljc0OCA0NTMuOTIgNDAuMDczNkg0NTguNzczQzQ1Ny40MjUgMzMuMjY1IDQ1Mi4wMzIgMjcuMDYzIDQ0Mi43MjkgMjcuMDYzQzQzMi4yOCAyNy4wNjMgNDI1LjgwOSAzNS42MjQ0IDQyNS44MDkgNDUuMDYyMVoiIGZpbGw9IiMyMTAwNEIiLz4KPHBhdGggZD0iTTQ3MC4wNDEgMTEuNjI1NUg0NjUuMjU1VjYyLjE4NDlINDcwLjA0MVY0MS44OTM4QzQ3MC4wNDEgMzQuODgyOSA0NzQuNTU4IDMxLjI0MjYgNDgwLjM1NSAzMS4yNDI2QzQ4Ni40OSAzMS4yNDI2IDQ4OS4zODkgMzUuMDE3NyA0ODkuMzg5IDQxLjIxOTZWNjIuMTg0OUg0OTQuMTc1VjQwLjI3NTlDNDk0LjE3NSAzMi42NTgyIDQ4OS42NTggMjcuMDYzIDQ4MS4xNjQgMjcuMDYzQzQ3NC43NiAyNy4wNjMgNDcxLjI1NSAzMC41Njg1IDQ3MC4wNDEgMzIuNjU4MlYxMS42MjU1WiIgZmlsbD0iIzIxMDA0QiIvPgo8cGF0aCBkPSJNMC44MjQ5NTEgNzMuOTkzTDI0LjA2ODggMTQuNTIyNEMyNy4zNDQzIDYuMTQxNzkgMzUuNDIyMyAwLjYyNTk3NyA0NC40MjAyIDAuNjI1OTc3SDU4LjQzMzZMMzUuMTg5OCA2MC4wOTY2QzMxLjkxNDMgNjguNDc3MiAyMy44MzYzIDczLjk5MyAxNC44MzgzIDczLjk5M0gwLjgyNDk1MVoiIGZpbGw9InVybCgjcGFpbnQwX2xpbmVhcl8wXzE1KSIvPgo8cGF0aCBkPSJNMzQuOTI0NiA3My45OTMyTDU4LjE2ODQgMTQuNTIyNkM2MS40NDM5IDYuMTQxOTcgNjkuNTIxOSAwLjYyNjE1MiA3OC41MTk5IDAuNjI2MTUySDkyLjUzMzJMNjkuMjg5NCA2MC4wOTY4QzY2LjAxMzkgNjguNDc3NCA1Ny45MzU5IDczLjk5MzIgNDguOTM3OSA3My45OTMySDM0LjkyNDZaIiBmaWxsPSJ1cmwoI3BhaW50MV9saW5lYXJfMF8xNSkiLz4KPHBhdGggZD0iTTY5LjAyNjIgNzMuOTkzMkw5Mi4yNyAxNC41MjI2Qzk1LjU0NTUgNi4xNDE5NyAxMDMuNjIzIDAuNjI2MTUyIDExMi42MjEgMC42MjYxNTJIMTI2LjYzNUwxMDMuMzkxIDYwLjA5NjhDMTAwLjExNSA2OC40Nzc0IDkyLjAzNzUgNzMuOTkzMiA4My4wMzk1IDczLjk5MzJINjkuMDI2MloiIGZpbGw9InVybCgjcGFpbnQyX2xpbmVhcl8wXzE1KSIvPgo8ZGVmcz4KPGxpbmVhckdyYWRpZW50IGlkPSJwYWludDBfbGluZWFyXzBfMTUiIHgxPSIxMjYuNjM1IiB5MT0iLTQuOTc3OTkiIHgyPSIwLjgyNDk1MiIgeTI9IjY2LjA5NzgiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIj4KPHN0b3Agc3RvcC1jb2xvcj0iI0ZGNUNBQSIvPgo8c3RvcCBvZmZzZXQ9IjEiIHN0b3AtY29sb3I9IiNGRjRFNjIiLz4KPC9saW5lYXJHcmFkaWVudD4KPGxpbmVhckdyYWRpZW50IGlkPSJwYWludDFfbGluZWFyXzBfMTUiIHgxPSIxMjYuNjM1IiB5MT0iLTQuOTc3OTkiIHgyPSIwLjgyNDk1MiIgeTI9IjY2LjA5NzgiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIj4KPHN0b3Agc3RvcC1jb2xvcj0iI0ZGNUNBQSIvPgo8c3RvcCBvZmZzZXQ9IjEiIHN0b3AtY29sb3I9IiNGRjRFNjIiLz4KPC9saW5lYXJHcmFkaWVudD4KPGxpbmVhckdyYWRpZW50IGlkPSJwYWludDJfbGluZWFyXzBfMTUiIHgxPSIxMjYuNjM1IiB5MT0iLTQuOTc3OTkiIHgyPSIwLjgyNDk1MiIgeTI9IjY2LjA5NzgiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIj4KPHN0b3Agc3RvcC1jb2xvcj0iI0ZGNUNBQSIvPgo8c3RvcCBvZmZzZXQ9IjEiIHN0b3AtY29sb3I9IiNGRjRFNjIiLz4KPC9saW5lYXJHcmFkaWVudD4KPC9kZWZzPgo8L3N2Zz4="}
}
func (s *Search) SearchContents(_ context.Context, cond *plugin.SearchBasicCond) (
res []plugin.SearchResult, total int64, err error) {
if s.Client == nil {
return nil, 0, configuredErr
}
query, searchRequest := s.buildQuery(cond)
searchRequest.Filter = s.buildFilter(cond)
index := s.Client.Index(s.Config.IndexName)
searchResult, err := index.Search(query, searchRequest)
if err != nil {
log.Errorf("meilisearch error: %s", err.Error())
return nil, 0, err
}
return s.warpResult(searchResult)
}
func (s *Search) SearchQuestions(_ context.Context, cond *plugin.SearchBasicCond) (
res []plugin.SearchResult, total int64, err error) {
if s.Client == nil {
return nil, 0, configuredErr
}
query, searchRequest := s.buildQuery(cond)
filter := s.buildFilter(cond)
filter = append(filter, "type = question")
searchRequest.Filter = filter
index := s.Client.Index(s.Config.IndexName)
searchResult, err := index.Search(query, searchRequest)
if err != nil {
log.Errorf("search error: %s", err.Error())
return nil, 0, err
}
return s.warpResult(searchResult)
}
func (s *Search) SearchAnswers(_ context.Context, cond *plugin.SearchBasicCond) (
res []plugin.SearchResult, total int64, err error) {
if s.Client == nil {
return nil, 0, configuredErr
}
query, searchRequest := s.buildQuery(cond)
filter := s.buildFilter(cond)
filter = append(filter, "type = answer")
searchRequest.Filter = filter
index := s.Client.Index(s.Config.IndexName)
searchResult, err := index.Search(query, searchRequest)
if err != nil {
log.Errorf("search error: %s", err.Error())
return nil, 0, err
}
return s.warpResult(searchResult)
}
func (s *Search) UpdateContent(_ context.Context, content *plugin.SearchContent) error {
if s.Client == nil {
return configuredErr
}
index := s.Client.Index(s.Config.IndexName)
if s.Config.Async {
_, err := index.AddDocuments([]*plugin.SearchContent{content}, primaryKey)
return err
} else {
resp, err := index.AddDocuments([]*plugin.SearchContent{content}, primaryKey)
if err != nil {
return err
}
return waitForTask(s.Client, resp)
}
}
func (s *Search) DeleteContent(_ context.Context, contentID string) error {
if s.Client == nil {
return configuredErr
}
index := s.Client.Index(s.Config.IndexName)
if s.Config.Async {
_, err := index.DeleteDocument(contentID)
return err
} else {
resp, err := index.DeleteDocument(contentID)
if err != nil {
return err
}
return waitForTask(s.Client, resp)
}
}
func (s *Search) RegisterSyncer(ctx context.Context, syncer plugin.SearchSyncer) {
s.syncer = syncer
go s.sync(ctx)
}
func (s *Search) ConfigFields() []plugin.ConfigField {
return []plugin.ConfigField{
{
Name: "host",
Type: plugin.ConfigTypeInput,
Title: plugin.MakeTranslator(i18n.ConfigHostTitle),
Description: plugin.MakeTranslator(i18n.ConfigHostDescription),
Required: true,
UIOptions: plugin.ConfigFieldUIOptions{
InputType: plugin.InputTypeText,
},
Value: s.Config.Host,
},
{
Name: "api_key",
Type: plugin.ConfigTypeInput,
Title: plugin.MakeTranslator(i18n.ConfigApiKeyTitle),
Description: plugin.MakeTranslator(i18n.ConfigApiKeyDescription),
Required: true,
UIOptions: plugin.ConfigFieldUIOptions{
InputType: plugin.InputTypeText,
},
Value: s.Config.ApiKey,
},
{
Name: "index_name",
Type: plugin.ConfigTypeInput,
Title: plugin.MakeTranslator(i18n.ConfigIndexTitle),
Description: plugin.MakeTranslator(i18n.ConfigIndexDescription),
Required: false,
UIOptions: plugin.ConfigFieldUIOptions{
InputType: plugin.InputTypeText,
},
Value: s.Config.IndexName,
},
{
Name: "async",
Type: plugin.ConfigTypeSwitch,
Title: plugin.MakeTranslator(i18n.ConfigAsyncTitle),
Description: plugin.MakeTranslator(i18n.ConfigAsyncDescription),
Required: false,
Value: s.Config.Async,
},
}
}
func (s *Search) ConfigReceiver(config []byte) error {
conf := &SearchConfig{}
_ = json.Unmarshal(config, conf)
// if index name is empty, use default index name
if conf.IndexName == "" {
conf.IndexName = defaultIndexName
}
s.Config = conf
log.Debugf("try to init meilisearch client: %s", conf.Host)
s.Client = meilisearch.NewClient(meilisearch.ClientConfig{
Host: conf.Host,
APIKey: conf.ApiKey,
})
s.tryToCreateIndex()
index := s.Client.Index(conf.IndexName)
_, err := index.UpdateSearchableAttributes(&[]string{"title", "content"})
if err != nil {
log.Errorf("update searchable attributes error: %s", err.Error())
return err
}
_, err = index.UpdateFilterableAttributes(&[]string{"title", "content", "tags", "status", "answers", "type", "questionID", "userID", "views", "created", "active", "score", "hasAccepted"})
if err != nil {
log.Errorf("update filterable attributes error: %s", err.Error())
return err
}
_, err = index.UpdateSortableAttributes(&[]string{"active", "created", "active", "score"})
if err != nil {
log.Errorf("update sortable attributes error: %s", err.Error())
return err
}
_, err = index.UpdateDisplayedAttributes(&[]string{"title", "content", "objectID", "type"})
if err != nil {
log.Errorf("update displayed attributes error: %s", err.Error())
return err
}
return nil
}
func (s *Search) warpResult(resp *meilisearch.SearchResponse) ([]plugin.SearchResult, int64, error) {
res := make([]plugin.SearchResult, 0)
for _, hit := range resp.Hits {
var content plugin.SearchContent
bytes, err := json.Marshal(hit)
if err != nil {
log.Errorf("marshal hit error: %s", err.Error())
return nil, 0, err
}
err = json.Unmarshal(bytes, &content)
if err != nil {
log.Errorf("unmarshal hit error: %s", err.Error())
return nil, 0, err
}
res = append(res, plugin.SearchResult{
ID: content.ObjectID,
Type: content.Type,
})
}
log.Debugf("search result: %d", len(res))
return res, resp.TotalHits, nil
}
func (s *Search) buildQuery(cond *plugin.SearchBasicCond) (string, *meilisearch.SearchRequest) {
searchRequest := meilisearch.SearchRequest{}
// page
if cond.Page > 0 {
searchRequest.Page = int64(cond.Page)
}
if cond.PageSize > 0 {
searchRequest.HitsPerPage = int64(cond.PageSize)
}
// order
switch cond.Order {
case plugin.SearchNewestOrder:
searchRequest.Sort = []string{"created:desc"}
case plugin.SearchActiveOrder:
searchRequest.Sort = []string{"created:desc"}
case plugin.SearchScoreOrder:
searchRequest.Sort = []string{"score:desc"}
}
var query string
if cond.Words != nil && len(cond.Words) > 0 {
query = strings.Join(cond.Words, " ")
}
return query, &searchRequest
}
func (s *Search) buildFilter(cond *plugin.SearchBasicCond) []string {
var filter []string
if cond.TagIDs != nil && len(cond.TagIDs) > 0 {
filter = append(filter, fmt.Sprintf("tags IN [%s]", strings.Join(cond.TagIDs, ",")))
}
if cond.UserID != "" {
filter = append(filter, fmt.Sprintf("userID = %s", cond.UserID))
}
// QuestionAccepted
if cond.QuestionAccepted == plugin.AcceptedCondTrue {
filter = append(filter, "hasAccepted = true")
} else if cond.QuestionAccepted == plugin.AcceptedCondFalse {
filter = append(filter, "hasAccepted = false")
}
// AnswerAccepted
if cond.AnswerAccepted == plugin.AcceptedCondTrue {
filter = append(filter, "hasAccepted = true")
} else if cond.AnswerAccepted == plugin.AcceptedCondFalse {
filter = append(filter, "hasAccepted = false")
}
// QuestionID
if cond.QuestionID != "" {
filter = append(filter, fmt.Sprintf("questionID = %s", cond.QuestionID))
}
// VoteAmount
if cond.VoteAmount > 0 {
filter = append(filter, fmt.Sprintf("voteAmount >= %d", cond.VoteAmount))
}
// ViewAmount
if cond.ViewAmount > 0 {
filter = append(filter, fmt.Sprintf("viewAmount >= %d", cond.ViewAmount))
}
// AnswerAmount
if cond.AnswerAmount > 0 {
filter = append(filter, fmt.Sprintf("answerAmount >= %d", cond.AnswerAmount))
}
return filter
}
func waitForTask(client *meilisearch.Client, resp *meilisearch.TaskInfo) error {
task, err := client.WaitForTask(resp.TaskUID)
if err != nil {
return err
}
if task.Status != meilisearch.TaskStatusSucceeded {
err = errors.InternalServer(task.Error.Code).WithMsg("invoke meili failed:" + task.Error.Message).WithStack()
return err
}
return nil
}