270 lines
5.9 KiB
Go
270 lines
5.9 KiB
Go
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 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 == "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
|
|
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 does not exist")
|
|
}
|
|
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 "<h4>No search results</h4>", 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 += `<h5><a href="/wiki/` + hit.ID + `">` + art.Title + "</a></h5>"
|
|
for fragField, frags := range hit.Fragments {
|
|
if fragField == "title" || fragField == "search" {
|
|
html += `<pre style="white-space: pre-wrap">`
|
|
for _, f := range frags {
|
|
html += f
|
|
}
|
|
html += "</pre>"
|
|
break
|
|
}
|
|
}
|
|
html += "<br>"
|
|
}
|
|
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" {
|
|
continue
|
|
}
|
|
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
|
|
}
|