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 }