You've already forked multi-select-facet
mirror of
https://github.com/stevenferrer/multi-select-facet.git
synced 2025-11-23 21:54:45 +02:00
332 lines
7.2 KiB
Go
332 lines
7.2 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"flag"
|
|
"io/ioutil"
|
|
"log"
|
|
"net/http"
|
|
|
|
"github.com/go-chi/chi"
|
|
"github.com/go-chi/chi/middleware"
|
|
"github.com/go-chi/cors"
|
|
"github.com/pkg/errors"
|
|
|
|
solr "github.com/sf9v/solr-go"
|
|
solrconfig "github.com/sf9v/solr-go/config"
|
|
solrindex "github.com/sf9v/solr-go/index"
|
|
solrschema "github.com/sf9v/solr-go/schema"
|
|
)
|
|
|
|
// Any is a convenience type for interface{}
|
|
type Any = interface{}
|
|
|
|
// Map is a convenience type for map[string]interface{}
|
|
type Map = map[string]Any
|
|
|
|
const (
|
|
dataPath = "products.json"
|
|
coll = "multi-select-facet-demo"
|
|
)
|
|
|
|
func main() {
|
|
collection := flag.String("collection", coll, "specify the name of collection")
|
|
initSchema := flag.Bool("init-schema", false, "initialize solr schema")
|
|
initSuggester := flag.Bool("init-suggester", false, "initialize suggester component")
|
|
indexData := flag.Bool("index-data", false, "index the products data")
|
|
flag.Parse()
|
|
|
|
solrClient := solr.NewClient("localhost", 8983)
|
|
|
|
ctx := context.Background()
|
|
if *initSchema {
|
|
log.Print("initializing solr schema...")
|
|
err := initSolrSchema(ctx, *collection, solrClient)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
}
|
|
|
|
if *indexData {
|
|
log.Println("indexing products...")
|
|
err := indexProducts(ctx, *collection, solrClient.Index())
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
}
|
|
|
|
if *initSuggester {
|
|
log.Println("initializing suggester component...")
|
|
err := initSuggestConfig(ctx, *collection, solrClient.Config())
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
}
|
|
|
|
r := chi.NewRouter()
|
|
|
|
// basic middlewares
|
|
r.Use(middleware.Logger)
|
|
r.Use(cors.Handler(cors.Options{
|
|
AllowedOrigins: []string{"*"},
|
|
AllowedMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"},
|
|
AllowedHeaders: []string{"Accept", "Authorization", "Content-Type", "X-CSRF-Token"},
|
|
ExposedHeaders: []string{"Link"},
|
|
AllowCredentials: false,
|
|
MaxAge: 300,
|
|
}))
|
|
|
|
// search handler
|
|
r.Method(http.MethodGet, "/search", &searchHandler{
|
|
collection: *collection,
|
|
solrClient: solrClient,
|
|
})
|
|
|
|
r.Method(http.MethodGet, "/suggest", &suggestHandler{
|
|
collection: *collection,
|
|
solrClient: solrClient,
|
|
})
|
|
|
|
addr := ":8081"
|
|
log.Printf("listening on %s\n", addr)
|
|
err := http.ListenAndServe(addr, r)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
}
|
|
|
|
func initSolrSchema(ctx context.Context, collection string, solrClient solr.Client) (err error) {
|
|
|
|
// auto-suggest field type
|
|
fieldTypes := []solrschema.FieldType{
|
|
// // approach #1
|
|
// // see: https://blog.griddynamics.com/implementing-autocomplete-with-solr/
|
|
// {
|
|
// Name: "text_suggest",
|
|
// Class: "solr.TextField",
|
|
// PositionIncrementGap: "100",
|
|
// IndexAnalyzer: &solrschema.Analyzer{
|
|
// Tokenizer: &solrschema.Tokenizer{
|
|
// Class: "solr.StandardTokenizerFactory",
|
|
// },
|
|
// Filters: []solrschema.Filter{
|
|
// {
|
|
// Class: "solr.LowerCaseFilterFactory",
|
|
// },
|
|
// {
|
|
// Class: "solr.EdgeNGramFilterFactory",
|
|
// MinGramSize: 1,
|
|
// MaxGramSize: 100,
|
|
// },
|
|
// },
|
|
// },
|
|
// QueryAnalyzer: &solrschema.Analyzer{
|
|
// Tokenizer: &solrschema.Tokenizer{
|
|
// Class: "solr.KeywordTokenizerFactory",
|
|
// },
|
|
// Filters: []solrschema.Filter{
|
|
// {
|
|
// Class: "solr.LowerCaseFilterFactory",
|
|
// },
|
|
// },
|
|
// },
|
|
// },
|
|
|
|
// approach #2
|
|
// see: https://blog.griddynamics.com/implement-autocomplete-search-for-large-e-commerce-catalogs/
|
|
{
|
|
Name: "text_suggest",
|
|
Class: "solr.TextField",
|
|
PositionIncrementGap: "100",
|
|
IndexAnalyzer: &solrschema.Analyzer{
|
|
Tokenizer: &solrschema.Tokenizer{
|
|
Class: "solr.WhitespaceTokenizerFactory",
|
|
},
|
|
Filters: []solrschema.Filter{
|
|
{
|
|
Class: "solr.LowerCaseFilterFactory",
|
|
},
|
|
{
|
|
Class: "solr.ASCIIFoldingFilterFactory",
|
|
},
|
|
{
|
|
Class: "solr.EdgeNGramFilterFactory",
|
|
MinGramSize: 1,
|
|
MaxGramSize: 20,
|
|
},
|
|
},
|
|
},
|
|
QueryAnalyzer: &solrschema.Analyzer{
|
|
Tokenizer: &solrschema.Tokenizer{
|
|
Class: "solr.WhitespaceTokenizerFactory",
|
|
},
|
|
Filters: []solrschema.Filter{
|
|
{
|
|
Class: "solr.LowerCaseFilterFactory",
|
|
},
|
|
{
|
|
Class: "solr.ASCIIFoldingFilterFactory",
|
|
},
|
|
{
|
|
Class: "solr.SynonymGraphFilterFactory",
|
|
Synonyms: "synonyms.txt",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, fieldType := range fieldTypes {
|
|
err = solrClient.Schema().AddFieldType(ctx, collection, fieldType)
|
|
if err != nil {
|
|
return errors.Wrap(err, "add field type")
|
|
}
|
|
}
|
|
|
|
// define the fields
|
|
fields := []solrschema.Field{
|
|
{
|
|
Name: "docType",
|
|
Type: "string",
|
|
},
|
|
{
|
|
Name: "name",
|
|
Type: "text_general",
|
|
},
|
|
{
|
|
Name: "category",
|
|
Type: "text_gen_sort",
|
|
},
|
|
{
|
|
Name: "brand",
|
|
Type: "text_gen_sort",
|
|
},
|
|
{
|
|
Name: "productType",
|
|
Type: "string",
|
|
},
|
|
{
|
|
Name: "suggest",
|
|
Type: "text_suggest",
|
|
Stored: false,
|
|
MultiValued: true,
|
|
},
|
|
}
|
|
|
|
for _, field := range fields {
|
|
err = solrClient.Schema().AddField(ctx, collection, field)
|
|
if err != nil {
|
|
return errors.Wrap(err, "add field")
|
|
}
|
|
}
|
|
|
|
copyFields := []solrschema.CopyField{
|
|
{
|
|
Source: "name",
|
|
Dest: "suggest",
|
|
},
|
|
|
|
{
|
|
Source: "name",
|
|
Dest: "_text_",
|
|
},
|
|
{
|
|
Source: "category",
|
|
Dest: "_text_",
|
|
},
|
|
{
|
|
Source: "brand",
|
|
Dest: "_text_",
|
|
},
|
|
{
|
|
Source: "productType",
|
|
Dest: "_text_",
|
|
},
|
|
{
|
|
Source: "*_s",
|
|
Dest: "_text_",
|
|
},
|
|
}
|
|
|
|
for _, copyField := range copyFields {
|
|
err = solrClient.Schema().AddCopyField(ctx, collection, copyField)
|
|
if err != nil {
|
|
return errors.Wrap(err, "add copy field")
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func initSuggestConfig(ctx context.Context, collection string, configClient solrconfig.Client) error {
|
|
// suggester configs
|
|
addSuggestComponent := solrconfig.NewComponentCommand(
|
|
solrconfig.AddSearchComponent, Map{
|
|
"name": "suggest",
|
|
"class": "solr.SuggestComponent",
|
|
"suggester": Map{
|
|
"name": "default",
|
|
"lookupImpl": "AnalyzingInfixLookupFactory",
|
|
"dictionaryImpl": "DocumentDictionaryFactory",
|
|
"field": "suggest",
|
|
"suggestAnalyzerFieldType": "text_suggest",
|
|
},
|
|
},
|
|
)
|
|
|
|
addSuggestHandler := solrconfig.NewComponentCommand(
|
|
solrconfig.AddRequestHandler, Map{
|
|
"name": "/suggest",
|
|
"class": "solr.SearchHandler",
|
|
"startup": "lazy",
|
|
"defaults": Map{
|
|
"suggest": true,
|
|
"suggest.count": 10,
|
|
"suggest.dictionary": "default",
|
|
},
|
|
"components": []string{"suggest"},
|
|
},
|
|
)
|
|
|
|
err := configClient.SendCommands(ctx, collection,
|
|
addSuggestComponent, addSuggestHandler)
|
|
if err != nil {
|
|
return errors.Wrap(err, "add suggester configs")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func indexProducts(ctx context.Context, collection string,
|
|
indexClient solrindex.Client) error {
|
|
b, err := ioutil.ReadFile(dataPath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
var products []Map
|
|
err = json.Unmarshal(b, &products)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
docs := solrindex.NewDocs()
|
|
for _, product := range products {
|
|
docs.AddDoc(product)
|
|
}
|
|
|
|
err = indexClient.AddDocuments(ctx, collection, docs)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// commit updates
|
|
err = indexClient.Commit(ctx, collection)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|