github.com/bosssauce/ponzu@v0.11.1-0.20200102001432-9bc41b703131/system/search/search.go (about)

     1  // Package search is a wrapper around the blevesearch/bleve search indexing and
     2  // query package, and provides interfaces to extend Ponzu items with rich, full-text
     3  // search capability.
     4  package search
     5  
     6  import (
     7  	"encoding/json"
     8  	"errors"
     9  	"fmt"
    10  	"os"
    11  	"path/filepath"
    12  	"strings"
    13  
    14  	"github.com/ponzu-cms/ponzu/system/cfg"
    15  
    16  	"github.com/ponzu-cms/ponzu/system/item"
    17  
    18  	"github.com/blevesearch/bleve"
    19  	"github.com/blevesearch/bleve/mapping"
    20  )
    21  
    22  var (
    23  	// Search tracks all search indices to use throughout system
    24  	Search map[string]bleve.Index
    25  
    26  	// ErrNoIndex is for failed checks for an index in Search map
    27  	ErrNoIndex = errors.New("No search index found for type provided")
    28  )
    29  
    30  // Searchable ...
    31  type Searchable interface {
    32  	SearchMapping() (*mapping.IndexMappingImpl, error)
    33  	IndexContent() bool
    34  }
    35  
    36  func init() {
    37  	Search = make(map[string]bleve.Index)
    38  }
    39  
    40  // MapIndex creates the mapping for a type and tracks the index to be used within
    41  // the system for adding/deleting/checking data
    42  func MapIndex(typeName string) error {
    43  	// type assert for Searchable, get configuration (which can be overridden)
    44  	// by Ponzu user if defines own SearchMapping()
    45  	it, ok := item.Types[typeName]
    46  	if !ok {
    47  		return fmt.Errorf("[search] MapIndex Error: Failed to MapIndex for %s, type doesn't exist", typeName)
    48  	}
    49  	s, ok := it().(Searchable)
    50  	if !ok {
    51  		return fmt.Errorf("[search] MapIndex Error: Item type %s doesn't implement search.Searchable", typeName)
    52  	}
    53  
    54  	// skip setting or using index for types that shouldn't be indexed
    55  	if !s.IndexContent() {
    56  		return nil
    57  	}
    58  
    59  	mapping, err := s.SearchMapping()
    60  	if err != nil {
    61  		return err
    62  	}
    63  
    64  	idxName := typeName + ".index"
    65  	var idx bleve.Index
    66  
    67  	searchPath := cfg.SearchDir()
    68  
    69  	err = os.MkdirAll(searchPath, os.ModeDir|os.ModePerm)
    70  	if err != nil {
    71  		return err
    72  	}
    73  
    74  	idxPath := filepath.Join(searchPath, idxName)
    75  	if _, err = os.Stat(idxPath); os.IsNotExist(err) {
    76  		idx, err = bleve.New(idxPath, mapping)
    77  		if err != nil {
    78  			return err
    79  		}
    80  		idx.SetName(idxName)
    81  	} else {
    82  		idx, err = bleve.Open(idxPath)
    83  		if err != nil {
    84  			return err
    85  		}
    86  	}
    87  
    88  	// add the type name to the index and track the index
    89  	Search[typeName] = idx
    90  
    91  	return nil
    92  }
    93  
    94  // UpdateIndex sets data into a content type's search index at the given
    95  // identifier
    96  func UpdateIndex(id string, data interface{}) error {
    97  	// check if there is a search index to work with
    98  	target := strings.Split(id, ":")
    99  	ns := target[0]
   100  
   101  	idx, ok := Search[ns]
   102  	if ok {
   103  		// unmarshal json to struct, error if not registered
   104  		it, ok := item.Types[ns]
   105  		if !ok {
   106  			return fmt.Errorf("[search] UpdateIndex Error: type '%s' doesn't exist", ns)
   107  		}
   108  
   109  		p := it()
   110  		err := json.Unmarshal(data.([]byte), &p)
   111  		if err != nil {
   112  			return err
   113  		}
   114  
   115  		// add data to search index
   116  		return idx.Index(id, p)
   117  	}
   118  
   119  	return nil
   120  }
   121  
   122  // DeleteIndex removes data from a content type's search index at the
   123  // given identifier
   124  func DeleteIndex(id string) error {
   125  	// check if there is a search index to work with
   126  	target := strings.Split(id, ":")
   127  	ns := target[0]
   128  
   129  	idx, ok := Search[ns]
   130  	if ok {
   131  		// add data to search index
   132  		return idx.Delete(id)
   133  	}
   134  
   135  	return nil
   136  }
   137  
   138  // TypeQuery conducts a search and returns a set of Ponzu "targets", Type:ID pairs,
   139  // and an error. If there is no search index for the typeName (Type) provided,
   140  // db.ErrNoIndex will be returned as the error
   141  func TypeQuery(typeName, query string, count, offset int) ([]string, error) {
   142  	idx, ok := Search[typeName]
   143  	if !ok {
   144  		return nil, ErrNoIndex
   145  	}
   146  
   147  	q := bleve.NewQueryStringQuery(query)
   148  	req := bleve.NewSearchRequestOptions(q, count, offset, false)
   149  	res, err := idx.Search(req)
   150  	if err != nil {
   151  		return nil, err
   152  	}
   153  
   154  	var results []string
   155  	for _, hit := range res.Hits {
   156  		results = append(results, hit.ID)
   157  	}
   158  
   159  	return results, nil
   160  }