From 38f7c31517597b1a66eeaf71dd084aa9856467d6 Mon Sep 17 00:00:00 2001 From: ston1th Date: Sun, 11 Feb 2018 19:21:31 +0100 Subject: [PATCH] mostly finished db pages --- pkg/cache/cache.go | 49 ++++++++++ pkg/db/db.go | 13 +-- pkg/db/helper.go | 10 ++ pkg/db/page.go | 164 +++++++++++++++++++++++++++----- pkg/db/user.go | 5 + pkg/index/index.go | 22 ++--- pkg/permission/permission.go | 47 +++++++++ pkg/{index => render}/render.go | 39 ++------ pkg/server/cache.go | 49 ---------- pkg/util/helper.go | 11 +++ 10 files changed, 281 insertions(+), 128 deletions(-) create mode 100644 pkg/cache/cache.go create mode 100644 pkg/permission/permission.go rename pkg/{index => render}/render.go (82%) delete mode 100644 pkg/server/cache.go diff --git a/pkg/cache/cache.go b/pkg/cache/cache.go new file mode 100644 index 0000000..acc777e --- /dev/null +++ b/pkg/cache/cache.go @@ -0,0 +1,49 @@ +package cache + +import ( + "sync" + "time" +) + +type CachedPage struct { + Page *Page + CacheTime time.Time +} + +type Cache struct { + sync.RWMutex + Cache map[string]*CachedPage +} + +func NewCache() *Cache { + return &Cache{Cache: make(map[string]*CachedPage)} +} + +func (c *Cache) Add(title string, p *Page) { + now := time.Now() + c.Lock() + defer c.Unlock() + cp, ok := c.Cache[title] + if ok && now.After(c.CacheTime) { + cp.Page = p + cp.CacheTime = now + c.Cache[title] = cp + return + } + c.Cache[title] = &CachedPage{p, now} +} + +func (c *Cache) Get(title string) *Page { + c.RLock() + defer c.RUnlock() + if cp, ok := c.Cache[title]; ok { + return cp.Page + } + return nil +} + +func (c *Cache) Delete(title string) { + c.Lock() + delete(c.Cache, title) + c.Unlock() +} diff --git a/pkg/db/db.go b/pkg/db/db.go index 9d3524b..5b29d43 100644 --- a/pkg/db/db.go +++ b/pkg/db/db.go @@ -5,6 +5,7 @@ package db import ( "bytes" "errors" + "git.giftfish.de/ston1th/gowiki/cache" "github.com/boltdb/bolt" ) @@ -25,18 +26,14 @@ const ( maxSearchResult = 101 ) -var ( - errUserNotExists = dbErr("user does not exist") - errUserExists = dbErr("user already exist") -) - func dbErr(i interface{}) error { return fmt.Errorf("db: %s", i) } type BoltStore struct { Marshaler - db *bolt.DB + db *bolt.DB + cache *cache.Cache } func NewBoltStore() (bs *BoltStore, err error) { @@ -48,14 +45,14 @@ func NewBoltStore() (bs *BoltStore, err error) { if _, err := tx.CreateBucketIfNotExists([]byte(userTable)); err != nil { return err } - if _, err := tx.CreateBucketIfNotExists([]byte(pagesTable)); err != nil { + if _, err := tx.CreateBucketIfNotExists([]byte(pageTable)); err != nil { return err } return nil }); err != nil { return } - bs = &BoltStore{NewGOB(), db} + bs = &BoltStore{NewGOB(), db, cache.NewCache()} if !bs.UserExists(defUser) { err = bs.CreateUser(defUser, defPassword, true) if err != nil { diff --git a/pkg/db/helper.go b/pkg/db/helper.go index 797d0ca..8e0f360 100644 --- a/pkg/db/helper.go +++ b/pkg/db/helper.go @@ -12,3 +12,13 @@ func validatePassword(pw string) (b []byte) { b = hash.Sum(nil) return } + +const timeFmt = "2006-01-02 15:04:05" + +func now() string { + return time.Now().Format(timeFmt) +} + +func created(username string) string { + return username + " " + now() +} diff --git a/pkg/db/page.go b/pkg/db/page.go index a7722c3..6967d08 100644 --- a/pkg/db/page.go +++ b/pkg/db/page.go @@ -1,5 +1,11 @@ package db +import ( + "git.giftfish.de/ston1th/gowiki/pkg/cache" + "git.giftfish.de/ston1th/gowiki/pkg/permission" + "git.giftfish.de/ston1th/gowiki/pkg/reneder" +) + type Article struct { Title string `json:"title"` LinkTitle string `json:"-"` @@ -12,31 +18,145 @@ type Article struct { Public bool `json:"public"` } -type Permission int +type Page struct { + Title string `json:"title"` + Markdown string `json:"markdown"` + Index template.HTML `json:"index"` + Text template.HTML `json:"text"` + Created string `json:"created"` + Updated string `json:"updated"` + Owner string `json:"owner"` + Perm permission.Permission `json:"perm"` +} -const ( - Public Permission = iota - Internal - Private +var ( + errPageNotFound = dbErr("page not found") + errInvalidPermission = dbErr("invalid permission") + errPrivateWikiPage = dbErr("private wiki pages are not allowed") ) -type Page struct { - Title string `json:"title"` - Markdown string `json:"markdown"` - Index template.HTML `json:"index"` - Text template.HTML `json:"text"` - Created string `json:"created"` - Updated string `json:"updated"` - Perm Permission `json:"perm"` -} - -func (bs *BoltStore) GetPage(title string) (p Page, err error) { - err = bs.db.View(func(tx *bolt.Tx) error { - v := tx.Bucket([]byte(pageTable)).Get([]byte(username)) - if v == nil { - return errUserNotExists +func (bs *BoltStore) GetPage(title, username string) (p *Page, err error) { + p = bs.cache.Get(title) + if p == nil { + p = new(Page) + err = bs.db.View(func(tx *bolt.Tx) error { + v := tx.Bucket([]byte(pageTable)).Get([]byte(title)) + if v == nil { + return errPageNotFound + } + return bs.Unmarshal(v, p) + }) + if err != nil { + return err } - return bs.Unmarshal(v, &u) - }) + bs.cache.Add(title, p) + } + if !permission.Read(username, p) { + err = errPageNotFound + } return } + +func (bs *BoltStore) pageExists(title, username string) (page *Page, err error) { + page, err = bs.GetPage(title) + if err != nil { + return + } + if !permission.Verify(username, page) { + err = errPageNotFound + } + return +} + +func (bs *BoltStore) CreatePage(title, section, markdown, username string, perm int) (page *Page, err error) { + p := permission.Parse(perm) + if p == permission.Invalid { + err = errInvalidPermission + return + } + if section == wikiSection && p == permission.Private { + err = errPrivateWikiPage + return + } + st := storeTitle(section, title) + if _, err = bs.pageExists(st, username); err != nil { + return + } + + page = &Page{ + Title: title, + Markdown: markdown, + Created: created(username), + Owner: username, + Perm: p, + } + search := render.Render(page) + + err = bs.db.Update(func(tx *bolt.Tx) error { + v, err := bs.Marshal(page) + if err != nil { + return err + } + return tx.Bucket([]byte(pageTable)).Put([]byte(st), v) + }) + if err != nil { + return + } + + err = index.Add(title, search) + if err != nil { + return + } + cache.Add(st, page) + return +} + +func (bs *BoltStore) UpdatePage(title, section, markdown, username string, perm int) (page *Page, err error) { + p := permission.Parse(perm) + if p == permission.Invalid { + err = errInvalidPermission + return + } + if section == wikiSection && p == permission.Private { + err = errPrivateWikiPage + return + } + st := storeTitle(section, title) + page, err = bs.pageExists(st, username) + if err != nil { + return + } + + page.Markdown = markdown + page.Updated = created(username) + page.Perm = p + search := render.Render(page) + + err = bs.db.Update(func(tx *bolt.Tx) error { + v, err := bs.Marshal(page) + if err != nil { + return err + } + return tx.Bucket([]byte(pageTable)).Put([]byte(st), v) + }) + if err != nil { + return + } + + err = index.Add(title, search) + if err != nil { + return + } + cache.Add(st, page) + return +} + +func (bs *BoltStore) DeletePage(title, section, username string) error { + st := storeTitle(section, title) + if _, err := bs.pageExists(st, username); err != nil { + return err + } + return bs.db.Update(func(tx *bolt.Tx) error { + return tx.Bucket([]byte(pageTable)).Delete([]byte(st)) + }) +} diff --git a/pkg/db/user.go b/pkg/db/user.go index 164bcae..6f3d968 100644 --- a/pkg/db/user.go +++ b/pkg/db/user.go @@ -5,6 +5,11 @@ import ( "golang.org/x/crypto/bcrypt" ) +var ( + errUserNotFound = dbErr("user not found") + errUserExists = dbErr("user already exist") +) + func (bs *BoltStore) GetUsers() (users []User, err error) { users, err = bs.DumpUsers() for i := range users { diff --git a/pkg/index/index.go b/pkg/index/index.go index d7b0a79..455dd21 100644 --- a/pkg/index/index.go +++ b/pkg/index/index.go @@ -1,6 +1,6 @@ // Copyright (C) 2017 Marius Schellenberger -package main +package index import ( "fmt" @@ -10,7 +10,7 @@ import ( "github.com/blevesearch/bleve/document" ) -type IndexPage struct { +type indexPage struct { Title string `json:"title"` Search string `json:"search"` } @@ -50,29 +50,19 @@ func NewIndex(path string) (i *Index, err error) { return } -func NewDumpIndex(path string) (i *Index, err error) { - bi, err := bleve.OpenUsing(path, map[string]interface{}{"read_only": true}) - if err != nil { - err = indexErr(err) - return - } - i = &Index{index: bi} - return -} - func (i *Index) Close() error { return i.i.Close() } -func (i *Index) Add(ip IndexPage) error { - doc, _ := i.i.Document(ip.Title) +func (i *Index) Add(title, search string) error { + doc, _ := i.i.Document(title) if doc != nil { - err := i.Delete(ip.Title) + err := i.Delete(title) if err != nil { return err } } - return i.i.Index(ip.Title, ip) + return i.i.Index(title, indexPage{title, search}) } func (i *Index) Delete(title string) error { diff --git a/pkg/permission/permission.go b/pkg/permission/permission.go new file mode 100644 index 0000000..854ebb3 --- /dev/null +++ b/pkg/permission/permission.go @@ -0,0 +1,47 @@ +package permission + +type Permission int + +const ( + Invalid Permission = iota + Public + Internal + Private +) + +func Parse(p int) Permission { + switch p { + case Public: + return Public + case Internal: + return Internal + case Private: + return Private + } + return Invalid +} + +func Read(username string, p *Page) bool { + switch p.Perm { + case Public: + return true + case Internal: + return username != "" + case Private: + return username == p.Owner + } + return false +} + +func Write(username string, p *Page) bool { + switch p.Perm { + case Public, Internal: + if p.Owner == "" { + return username != "" + } + return username == p.Owner + case Private: + return username == p.Owner + } + return false +} diff --git a/pkg/index/render.go b/pkg/render/render.go similarity index 82% rename from pkg/index/render.go rename to pkg/render/render.go index 40262f4..8fda177 100644 --- a/pkg/index/render.go +++ b/pkg/render/render.go @@ -1,6 +1,6 @@ // Copyright (C) 2017 Marius Schellenberger -package main +package render import ( "html/template" @@ -28,42 +28,15 @@ var ( titleRe = regexp.MustCompile("[^a-zA-Z0-9 -]+") ) -type rendered struct { - Index template.HTML - Text template.HTML - MD string - Raw []byte -} - -func (r *rendered) Article() *Article { - return &Article{ - Index: r.Index, - Text: r.Text, - MD: r.MD, - Search: whiteSpace.ReplaceAllString(string(filter.Filter(r.Raw)), " "), - } -} - -func (r *rendered) Snippet() *Snippet { - return &Snippet{ - Index: r.Index, - Text: r.Text, - MD: r.MD, - } -} - -func render(text string) *rendered { - rend := blackfriday.MarkdownCommon([]byte(text)) +func Render(p *Page) string { + rend := blackfriday.MarkdownCommon([]byte(p.Markdown)) index, html := buildIndex(string(rend)) html = strings.Replace(html, "", `
`, -1) html = strings.Replace(html, "", `
`, -1) html = strings.Replace(html, "