package main import ( "errors" "html/template" "regexp" "sort" "strings" "time" "github.com/blevesearch/bleve" "github.com/blevesearch/bleve/document" ) const timeFmt = "2006-01-02 15:04:05" 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) Index(title, text, user string, public bool) (string, error) { if user == "" { return "", errors.New("engine: user cannot be empty") } title = titleRe.ReplaceAllString(title, "") newTitle := strings.Replace(title, " ", "+", -1) if err := e.Exists(false, newTitle); err == nil { return "", errors.New("engine: article already exists") } art := render(text) art.Title = title art.Created = user + " " + now() art.Public = public e.index.Index(newTitle, art) return newTitle, nil } 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 == "Index" { return errors.New("engine: the index page 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 == "Index" && !public { return errors.New("engine: page 'Index' 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 err = e.index.Index(title, art) return err } 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 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 does not exist") } return art, err } func (e *Engine) Search(public bool, search string) (string, error) { var ( res *bleve.SearchResult err error ) if public { req := bleve.NewSearchRequest(bleve.NewConjunctionQuery([]bleve.Query{ bleve.NewBoolFieldQuery(true).SetField("public"), bleve.NewQueryStringQuery("title:" + search + " search:" + search), })) req.Highlight = bleve.NewHighlightWithStyle("html") res, err = e.index.Search(req) if err != nil { return "", err } } else { req := bleve.NewSearchRequest(bleve.NewQueryStringQuery("title:" + search + " search:" + search)) req.Highlight = bleve.NewHighlightWithStyle("html") res, err = e.index.Search(req) if err != nil { return "", err } } if res.Total == 0 { return "

No search results

", nil } else if res.Total > 100 { return "", errors.New("engine: too many search results") } html := "" for _, hit := range res.Hits { 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 } type Titles []Title func (t Titles) Len() int { return len(t) } func (t Titles) Swap(i, j int) { t[i], t[j] = t[j], t[i] } func (t Titles) Less(i, j int) bool { return t[i].Title < t[j].Title } func (e *Engine) GetAll(public bool) (t Titles, err error) { var res *bleve.SearchResult if public { req := bleve.NewSearchRequest(bleve.NewBoolFieldQuery(true).SetField("public")) res, err = e.index.Search(req) if err != nil { return } } else { req := bleve.NewSearchRequest(bleve.NewMatchAllQuery()) res, err = e.index.Search(req) if err != nil { return } } for _, v := range res.Hits { if v.ID == "Index" { 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" { continue } f, ok := field.(*document.TextField) if ok { t = append(t, Title{string(f.Value()), v.ID}) break } } } sort.Sort(t) return }