package main import ( "errors" "html/template" "regexp" "time" "github.com/blevesearch/bleve" "github.com/blevesearch/bleve/document" ) const ( timeFmt = "2006-01-02 15:04:05" maxAllResult = 1000000 maxSearchResult = 101 ) var titleRe = regexp.MustCompile("[^a-zA-Z0-9+ ]+") func now() string { return time.Now().Format(timeFmt) } type Article struct { Title string `json:"title"` LinkTitle string Search string `json:"search"` Index template.HTML `json:"index"` Text template.HTML `json:"text"` MD string `json:"md"` Created string `json:"created"` Updated string `json:"updated"` Public bool `json:"public"` } type Engine struct { index bleve.Index } func NewEngine(index bleve.Index) *Engine { return &Engine{index} } func (e *Engine) Close() error { return e.index.Close() } func (e *Engine) Restore(dumpArt DumpArticle) error { art := render(dumpArt.MD) art.Title = dumpArt.Title art.Created = dumpArt.Created art.Updated = dumpArt.Updated art.Public = dumpArt.Public return e.index.Index(dumpArt.LinkTitle, art) } func (e *Engine) Index(title, text, user string, public bool) (string, error) { if user == "" { return "", errors.New("engine: user cannot be empty") } title = titleRe.ReplaceAllString(title, "") newTitle := makeLinkTitle(title) if err := e.Exists(false, newTitle); err == nil { return "", errors.New("engine: article '" + title + "' already exists") } art := render(text) art.Title = title art.Created = user + " " + now() art.Public = public err := e.index.Index(newTitle, art) return newTitle, err } func (e *Engine) Exists(public bool, title string) error { _, err := e.Get(public, title) if err != nil { return err } return nil } func (e *Engine) Delete(title string) error { if title == indexName { return errors.New("engine: page '" + indexName + "' cannot be deleted") } if err := e.Exists(false, title); err != nil { return err } return e.index.Delete(title) } func (e *Engine) Update(title, text, user string, public bool) error { if user == "" { return errors.New("engine: user cannot be empty") } if title == indexName && !public { return errors.New("engine: page '" + indexName + "' must stay public") } if err := e.Exists(false, title); err != nil { return err } art, err := e.Get(false, title) if err != nil { return err } rend := render(text) art.Updated = user + " " + now() art.Search = rend.Search art.Index = rend.Index art.Text = rend.Text art.MD = rend.MD art.Public = public return e.index.Index(title, art) } func (e *Engine) Get(public bool, title string) (Article, error) { var art Article doc, err := e.index.Document(title) if err != nil { return art, err } if doc == nil || doc.Fields == nil { return art, errors.New("engine: article '" + title + "' does not esist") } for _, field := range doc.Fields { switch f := field.(type) { case *document.TextField: switch field.Name() { case "title": art.Title = string(f.Value()) case "index": art.Index = template.HTML(string(f.Value())) case "text": art.Text = template.HTML(string(f.Value())) case "md": art.MD = string(f.Value()) case "created": art.Created = string(f.Value()) case "updated": art.Updated = string(f.Value()) } case *document.BooleanField: if field.Name() == "public" { p, err := f.Boolean() if err != nil { return art, err } art.Public = p } } } art.LinkTitle = title if public && !art.Public { err = errors.New("engine: article '" + title + "' does not esist") } return art, err } func (e *Engine) Search(public bool, search string) (string, error) { var ( res *bleve.SearchResult req *bleve.SearchRequest err error ) if public { req = bleve.NewSearchRequest(bleve.NewConjunctionQuery([]bleve.Query{ bleve.NewBoolFieldQuery(true).SetField("public"), bleve.NewQueryStringQuery("title:" + search + " search:" + search), })) } else { req = bleve.NewSearchRequest(bleve.NewQueryStringQuery("title:" + search + " search:" + search)) } req.Highlight = bleve.NewHighlightWithStyle("html") req.Size = maxSearchResult res, err = e.index.Search(req) if err != nil { return "", err } if res.Total == 0 { return "No search results", nil } else if res.Total == maxSearchResult { return "", errors.New("engine: too many search results") } html := "" for _, hit := range res.Hits { if hit == nil { continue } art, err := e.Get(false, hit.ID) if err != nil { continue } html += `` + art.Title + "" for fragField, frags := range hit.Fragments { if fragField == "title" || fragField == "search" { html += `
`
				for _, f := range frags {
					html += f
				}
				html += "
" break } } html += "
" } return html, nil } type Title struct { Title string LinkTitle string } func (e *Engine) GetAll(public bool) (t []Title, err error) { var ( res *bleve.SearchResult req *bleve.SearchRequest ) if public { req = bleve.NewSearchRequest(bleve.NewBoolFieldQuery(true).SetField("public")) } else { req = bleve.NewSearchRequest(bleve.NewMatchAllQuery()) } req.Size = maxAllResult res, err = e.index.Search(req) if err != nil { return } for _, v := range res.Hits { if v == nil { continue } if v.ID == indexName { continue } doc, err := e.index.Document(v.ID) if err != nil || doc == nil || doc.Fields == nil { continue } for _, field := range doc.Fields { if field.Name() == "title" { f, ok := field.(*document.TextField) if ok { t = append(t, Title{string(f.Value()), v.ID}) break } } } } return } func (e *Engine) Dump() (titles []string, err error) { req := bleve.NewSearchRequest(bleve.NewMatchAllQuery()) req.Size = maxAllResult res, err := e.index.Search(req) if err != nil { return } for _, v := range res.Hits { if v == nil { continue } titles = append(titles, v.ID) } return }