diff --git a/pkg/core/types.go b/pkg/core/types.go index c59b8a4..4a17c8f 100644 --- a/pkg/core/types.go +++ b/pkg/core/types.go @@ -18,3 +18,10 @@ type Result struct { StoreTitle string Text string } + +type User struct { + Username string + Password string + Admin bool + Locked int +} diff --git a/pkg/db/db.go b/pkg/db/db.go index b4a15d4..21b97de 100644 --- a/pkg/db/db.go +++ b/pkg/db/db.go @@ -5,20 +5,20 @@ package db import ( "fmt" "git.giftfish.de/ston1th/gowiki/pkg/cache" + "git.giftfish.de/ston1th/gowiki/pkg/core" "git.giftfish.de/ston1th/gowiki/pkg/index" - "github.com/boltdb/bolt" + "git.giftfish.de/ston1th/gowiki/pkg/store" + "git.giftfish.de/ston1th/gowiki/pkg/util" ) const ( - dbFile = "gowiki.db" - defUser = "admin" - defPassword = "gowiki" - userTable = "user" - pageTable = "page" - snippetTable = "snippet" - bcryptCost = 13 - indexName = "Index" - welcome = `# Welcome to GoWiki + defUser = "admin" + defPassword = "gowiki" + usersFile = "users.db" + pagesFile = "pages.db" + bcryptCost = 13 + indexName = "Index" + welcome = `# Welcome to GoWiki This is the [Index](/wiki/Index) page. You can customize it how you like.` wikiSection = "wiki" @@ -33,47 +33,56 @@ func dbErr(i interface{}) error { return fmt.Errorf("db: %s", i) } -type BoltStore struct { - Marshaler - db *bolt.DB +type DB struct { + pages store.Store + users store.Store cache *cache.Cache - index *index.Index + Index *index.Index } -// TODO create initial wiki page -// TODO init index +func NewDB(indexPath string) (db *DB, err error) { + db = &DB{cache: cache.NewCache()} + db.Index, err = index.NewIndex(indexPath) + if err != nil { + return + } -func NewBoltStore(indexPath string) (bs *BoltStore, err error) { - db, err := bolt.Open(dbFile, 0666, nil) + db.users, err = store.NewBoltStore(usersFile, nil) if err != nil { return } - if err = db.Update(func(tx *bolt.Tx) error { - if _, err := tx.CreateBucketIfNotExists([]byte(userTable)); err != nil { - return err - } - if _, err := tx.CreateBucketIfNotExists([]byte(pageTable)); err != nil { - return err - } - return nil - }); err != nil { - return - } - i, err := index.NewIndex(indexPath) - if err != nil { - return - } - bs = &BoltStore{NewGOB(), db, cache.NewCache(), i} - if bs.UserExists(defUser) == nil { - err = bs.CreateUser(defUser, defPassword, true) + if db.UserExists(defUser) != nil { + err = db.CreateUser(defUser, defPassword, true) + if err != nil { + return + } + } + err = db.UnlockUser(defUser) + if err != nil { + return + } + + db.pages, err = store.NewBoltStore(pagesFile, nil) + if err != nil { + return + } + if _, err = db.pageExists(util.StoreTitle(wikiSection, indexName), defUser); err != nil { + _, err = db.CreatePage(indexName, wikiSection, welcome, defUser, core.Public) if err != nil { return } } - bs.UnlockUser(defUser) return } -func (bs *BoltStore) Close() error { - return bs.db.Close() +func (db *DB) Close() error { + err := db.pages.Close() + if err != nil { + return err + } + err = db.users.Close() + if err != nil { + return err + } + return db.Index.Close() } diff --git a/pkg/db/dump.go b/pkg/db/dump.go index 95d9bf9..55f3c93 100644 --- a/pkg/db/dump.go +++ b/pkg/db/dump.go @@ -2,7 +2,7 @@ package db -import ( +/*import ( "encoding/json" "fmt" "os" @@ -90,4 +90,4 @@ func Restore(path string, v bool) (err error) { verboseLog("restore:", "users", v) err = bs.RestoreUsers(dump.Users) return -} +}*/ diff --git a/pkg/db/page.go b/pkg/db/page.go index a285d72..7b0d400 100644 --- a/pkg/db/page.go +++ b/pkg/db/page.go @@ -3,31 +3,22 @@ package db import ( "git.giftfish.de/ston1th/gowiki/pkg/core" "git.giftfish.de/ston1th/gowiki/pkg/render" + "git.giftfish.de/ston1th/gowiki/pkg/store" "git.giftfish.de/ston1th/gowiki/pkg/util" - "github.com/boltdb/bolt" ) var ( - errPageNotFound = dbErr("page not found") + errPageNotFound = store.ErrKeyNotFound errInvalidPermission = dbErr("invalid permission") errPrivateWikiPage = dbErr("private wiki pages are not allowed") ) -func (bs *BoltStore) GetPage(title, username string) (p *core.Page, err error) { - p = bs.cache.Get(title) +func (db *DB) GetPage(title, username string) (p *core.Page, err error) { + p = db.cache.Get(title) if p == nil { p = new(core.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 - } - bs.cache.Add(title, p) + err = db.pages.Get(title, p) + db.cache.Add(title, p) } if !core.ReadPerm(username, p) { err = errPageNotFound @@ -35,14 +26,19 @@ func (bs *BoltStore) GetPage(title, username string) (p *core.Page, err error) { return } -// TODO access db not cache -func (bs *BoltStore) pageExists(title, username string) (p *core.Page, err error) { - p, err = bs.GetPage(title, username) +func (db *DB) pageExists(title, username string) (p *core.Page, err error) { + p = new(core.Page) + err = db.pages.Get(title, p) + if err != nil { + return + } + if !core.ReadPerm(username, p) { + err = errPageNotFound + } return } -func (bs *BoltStore) CreatePage(title, section, markdown, username string, perm int) (page *core.Page, err error) { - p := core.ParsePerm(perm) +func (db *DB) CreatePage(title, section, markdown, username string, p core.Permission) (page *core.Page, err error) { if p == core.Invalid { err = errInvalidPermission return @@ -52,7 +48,7 @@ func (bs *BoltStore) CreatePage(title, section, markdown, username string, perm return } st := util.StoreTitle(section, title) - if _, err = bs.pageExists(st, username); err != nil { + if _, err = db.pageExists(st, username); err != nil { return } @@ -65,26 +61,20 @@ func (bs *BoltStore) CreatePage(title, section, markdown, username string, perm } 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) - }) + err = db.pages.Set(st, page) if err != nil { return } - err = bs.index.Add(title, st, search) + err = db.Index.Add(title, st, search) if err != nil { return } - bs.cache.Add(st, page) + db.cache.Add(st, page) return } -func (bs *BoltStore) UpdatePage(title, section, markdown, username string, perm int) (page *core.Page, err error) { +func (db *DB) UpdatePage(title, section, markdown, username string, perm int) (page *core.Page, err error) { p := core.ParsePerm(perm) if p == core.Invalid { err = errInvalidPermission @@ -95,7 +85,7 @@ func (bs *BoltStore) UpdatePage(title, section, markdown, username string, perm return } st := util.StoreTitle(section, title) - page, err = bs.pageExists(st, username) + page, err = db.pageExists(st, username) if err != nil { return } @@ -105,34 +95,26 @@ func (bs *BoltStore) UpdatePage(title, section, markdown, username string, perm 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) - }) + err = db.pages.Set(st, page) if err != nil { return } - err = bs.index.Add(title, st, search) + err = db.Index.Add(title, st, search) if err != nil { return } - bs.cache.Add(st, page) + db.cache.Add(st, page) return } -func (bs *BoltStore) DeletePage(title, section, username string) error { +func (db *DB) DeletePage(title, section, username string) (err error) { st := util.StoreTitle(section, title) - if _, err := bs.pageExists(st, username); err != nil { - return err + if _, err = db.pageExists(st, username); err != nil { + return } - if err := bs.db.Update(func(tx *bolt.Tx) error { - return tx.Bucket([]byte(pageTable)).Delete([]byte(st)) - }); err != nil { - return err + if err = db.pages.Delete(st); err != nil { + return } - return bs.index.Delete(st) + return db.Index.Delete(st) } diff --git a/pkg/db/snippet.go b/pkg/db/snippet.go deleted file mode 100644 index 98a3533..0000000 --- a/pkg/db/snippet.go +++ /dev/null @@ -1,148 +0,0 @@ -package db - -import ( - "bytes" - "git.giftfish.de/ston1th/gowiki/pkg/render" - "github.com/boltdb/bolt" - "html/template" - "strings" -) - -type Snippet struct { - Title string `json:"title"` - LinkTitle string `json:"-"` - Index template.HTML `json:"index"` - Text template.HTML `json:"text"` - MD string `json:"md"` - Created string `json:"created"` - Updated string `json:"updated"` - Permission int `json:"permission"` -} - -func copyBuf(buf []byte) (b []byte) { - b = make([]byte, len(buf)) - copy(b, buf) - return -} - -var ( - errSnippetNotExists = dbErr("snippet does not exist") - errSnippetExists = dbErr("snippet already exist") -) - -func (bs *BoltStore) GetSnippets(username string) (snip []Snippet, err error) { - user := []byte(username) - var buf [][]byte - err = bs.db.View(func(tx *bolt.Tx) error { - return tx.Bucket([]byte(snippetTable)).ForEach(func(k, v []byte) error { - if !bytes.HasPrefix(k, user) { - return nil - } - buf = append(buf, copyBuf(v)) - return nil - }) - }) - if err != nil { - return - } - for _, b := range buf { - s := new(Snippet) - if err = bs.Unmarshal(b, s); err != nil { - return - } - snip = append(snip, *s) - } - return -} - -func (bs *BoltStore) GetSnippet(username, title string, perm int) (*Snippet, error) { - s := new(Snippet) - err := bs.db.View(func(tx *bolt.Tx) error { - v := tx.Bucket([]byte(snippetTable)).Get([]byte(title)) - if v == nil { - return errSnippetNotExists - } - return bs.Unmarshal(v, s) - }) - if err != nil { - return nil, err - } - switch s.Permission { - case 0: - return s, nil - case 1: - if username != "" { - return s, nil - } - case 2: - if strings.HasPrefix(title, username) { - return s, nil - } - } - return nil, errSnippetNotExists -} - -func (bs *BoltStore) snippetExists(username, title string, perm int) (err error) { - _, err = bs.GetSnippet(username, title, perm) - return -} - -func makeKey(username, title string) string { - return username + title -} - -func checkPerm(p int) error { - switch p { - case 0, 1, 2: - return nil - } - return errInvalidPermission -} - -func (bs *BoltStore) CreateSnippet(username, title, text string, perm int) (newTitle string, err error) { - //newTitle, title = makeLinkTitle(title) - err = checkPerm(perm) - if err != nil { - return - } - err = bs.snippetExists(username, title, perm) - if err == nil { - err = errSnippetExists - return - } - err = bs.db.Update(func(tx *bolt.Tx) error { - snip := render.Render(text).Snippet() - snip.Title = title - snip.Created = username + " " + now() - snip.Permission = perm - v, err := bs.Marshal(snip) - if err != nil { - return err - } - return tx.Bucket([]byte(snippetTable)).Put([]byte(makeKey(username, title)), v) - }) - return -} - -func (bs *BoltStore) EditSnippet(username, title, text string, perm int) (err error) { - err = checkPerm(perm) - if err != nil { - return - } - err = bs.snippetExists(username, title, perm) - if err != nil { - return - } - err = bs.db.Update(func(tx *bolt.Tx) error { - snip := render.Render(text).Snippet() - snip.Title = title - snip.Created = username + " " + now() - snip.Permission = perm - v, err := bs.Marshal(snip) - if err != nil { - return err - } - return tx.Bucket([]byte(snippetTable)).Put([]byte(makeKey(username, title)), v) - }) - return -} diff --git a/pkg/db/user.go b/pkg/db/user.go index a4c3b35..94f1419 100644 --- a/pkg/db/user.go +++ b/pkg/db/user.go @@ -1,66 +1,47 @@ package db import ( - "github.com/boltdb/bolt" + "errors" + "git.giftfish.de/ston1th/gowiki/pkg/core" "golang.org/x/crypto/bcrypt" ) -type User struct { - Username string - Password string - Admin bool - Locked int -} - var ( errUserNotFound = dbErr("user not found") errUserExists = dbErr("user already exist") ) -func (bs *BoltStore) GetUsers() (users []User, err error) { - users, err = bs.DumpUsers() +func (db *DB) GetUsers() (users []core.User, err error) { + // TODO + /*users, err = db.DumpUsers() for i := range users { users[i].Password = "" - } + }*/ return } -func (bs *BoltStore) GetUserWithoutPassword(username string) (u User, err error) { - u, err = bs.GetUser(username) +func (db *DB) GetUserWithoutPassword(username string) (u core.User, err error) { + u, err = db.GetUser(username) u.Password = "" return } -func (bs *BoltStore) GetUser(username string) (u User, err error) { - err = bs.db.View(func(tx *bolt.Tx) error { - v := tx.Bucket([]byte(userTable)).Get([]byte(username)) - if v == nil { - return errUserNotFound - } - return bs.Unmarshal(v, &u) - }) +func (db *DB) GetUser(username string) (u core.User, err error) { + err = db.users.Get(username, &u) return } -func (bs *BoltStore) UserIsAdmin(username string) (admin bool) { - bs.db.View(func(tx *bolt.Tx) error { - u, _ := bs.GetUser(username) - admin = u.Admin - return nil - }) - return +func (db *DB) UserIsAdmin(username string) bool { + u, _ := db.GetUser(username) + return u.Admin } -func (bs *BoltStore) UserExists(username string) error { - return bs.db.View(func(tx *bolt.Tx) error { - if tx.Bucket([]byte(userTable)).Get([]byte(username)) == nil { - return errUserNoFound - } - return nil - }) +func (db *DB) UserExists(username string) error { + return db.users.Get(username, nil) } -func (bs *BoltStore) DumpUsers() (users []User, err error) { +// TODO +/*func (bs *BoltStore) DumpUsers() (users []User, err error) { var buf [][]byte err = bs.db.View(func(tx *bolt.Tx) error { return tx.Bucket([]byte(userTable)).ForEach(func(k, v []byte) error { @@ -102,135 +83,101 @@ func (bs *BoltStore) RestoreUsers(users []User) error { } return nil }) -} +}*/ -func (bs *BoltStore) CreateUser(username, password string, admin bool) error { - if err := bs.UserExists(username); err == nil { +func (db *DB) CreateUser(username, password string, admin bool) error { + if err := db.UserExists(username); err == nil { return errUserExists } - return bs.db.Update(func(tx *bolt.Tx) error { - hash, err := bcrypt.GenerateFromPassword(validatePassword(password), bcryptCost) - v, err := bs.Marshal(&User{ - Username: username, - Password: string(hash), - Admin: admin, - }) - if err != nil { - return err - } - return tx.Bucket([]byte(userTable)).Put([]byte(username), v) - }) -} - -func (bs *BoltStore) Login(username, password string) error { - var e error - err := bs.db.Update(func(tx *bolt.Tx) error { - us, err := bs.GetUser(username) - if err != nil { - return err - } - if us.Locked == 3 { - return errors.New("db: user locked") - } - b := tx.Bucket([]byte(userTable)) - if err := bcrypt.CompareHashAndPassword([]byte(us.Password), validatePassword(password)); err != nil { - us.Locked++ - v, err := bs.Marshal(us) - if err != nil { - return err - } - if err = b.Put([]byte(username), v); err != nil { - return err - } - e = errors.New("db: wrong login") - return nil - } - us.Locked = 0 - v, err := bs.Marshal(us) - if err != nil { - return err - } - return b.Put([]byte(username), v) - }) - if e != nil { - return e + hash, err := bcrypt.GenerateFromPassword(validatePassword(password), bcryptCost) + if err != nil { + return err } - return err + return db.users.Set(username, &core.User{ + Username: username, + Password: string(hash), + Admin: admin, + }) } -func (bs *BoltStore) UnlockUser(username string) error { - return bs.db.Update(func(tx *bolt.Tx) error { - u, err := bs.GetUser(username) +func (db *DB) Login(username, password string) (err error) { + var u core.User + u, err = db.GetUser(username) + if err != nil { + return + } + if u.Locked == 3 { + return errors.New("db: user locked") + } + if err = bcrypt.CompareHashAndPassword([]byte(u.Password), validatePassword(password)); err != nil { + u.Locked++ + err = db.users.Set(username, u) if err != nil { - return err - } - if u.Locked < 3 { - return nil + return } + return errors.New("db: wrong login") + } + if u.Locked > 0 { u.Locked = 0 - v, err := bs.Marshal(u) + err = db.users.Set(username, u) if err != nil { - return err + return } - return tx.Bucket([]byte(userTable)).Put([]byte(username), v) - }) + } + return } -func (bs *BoltStore) UpdateUserPassword(username, password string) error { - return bs.db.Update(func(tx *bolt.Tx) error { - if password != "" { - us, err := bs.GetUser(username) - if err != nil { - return err - } - hash, err := bcrypt.GenerateFromPassword(validatePassword(password), bcryptCost) - if err != nil { - return err - } - us.Password = string(hash) - v, err := bs.Marshal(us) - if err != nil { - return err - } - return tx.Bucket([]byte(userTable)).Put([]byte(username), v) +func (db *DB) UnlockUser(username string) (err error) { + var u core.User + u, err = db.GetUser(username) + if err != nil { + return + } + if u.Locked > 0 { + u.Locked = 0 + err = db.users.Set(username, u) + if err != nil { + return } - return nil - }) + } + return } -func (bs *BoltStore) AdminUpdateUser(username, password string, admin bool) error { +func (db *DB) UpdateUserPassword(username, password string) error { + u, err := db.GetUser(username) + if err != nil { + return err + } + hash, err := bcrypt.GenerateFromPassword(validatePassword(password), bcryptCost) + if err != nil { + return err + } + u.Password = string(hash) + return db.users.Set(username, u) +} + +func (db *DB) AdminUpdateUser(username, password string, admin bool) error { if username == defUser && !admin { return errors.New("user '" + defUser + "' cannot lose admin privileges") } - return bs.db.Update(func(tx *bolt.Tx) error { - us, err := bs.GetUser(username) + u, err := db.GetUser(username) + if err != nil { + return err + } + u.Admin = admin + if password != "" { + hash, err := bcrypt.GenerateFromPassword(validatePassword(password), bcryptCost) if err != nil { return err } - us.Admin = admin - if password != "" { - hash, err := bcrypt.GenerateFromPassword(validatePassword(password), bcryptCost) - if err != nil { - return err - } - us.Password = string(hash) - } - v, err := bs.Marshal(us) - if err != nil { - return err - } - return tx.Bucket([]byte(userTable)).Put([]byte(username), v) - }) + u.Password = string(hash) + } + return db.users.Set(username, u) } -func (bs *BoltStore) DeleteUser(username string) error { +func (db *DB) DeleteUser(username string) error { if username == defUser { return errors.New("user '" + defUser + "' cannot be deleted") } - if err := bs.UserExists(username); err != nil { - return err - } - return bs.db.Update(func(tx *bolt.Tx) error { - return tx.Bucket([]byte(userTable)).Delete([]byte(username)) - }) + return db.users.Delete(username) } diff --git a/pkg/server/context.go b/pkg/server/context.go index e93d64b..4db73ae 100644 --- a/pkg/server/context.go +++ b/pkg/server/context.go @@ -6,6 +6,7 @@ import ( "crypto/subtle" "encoding/base64" "errors" + "git.giftfish.de/ston1th/gowiki/pkg/log" "github.com/gorilla/mux" "html/template" "net/http" @@ -53,8 +54,6 @@ type webData struct { Msg string Search string - Editor *Editor - Data interface{} } diff --git a/pkg/server/handler.go b/pkg/server/handler.go index 6decd1b..cbd548a 100644 --- a/pkg/server/handler.go +++ b/pkg/server/handler.go @@ -3,6 +3,8 @@ package server import ( + "git.giftfish.de/ston1th/gowiki/pkg/core" + "git.giftfish.de/ston1th/gowiki/pkg/log" "html/template" "net/http" "time" @@ -97,13 +99,13 @@ func loginHandler(ctx *Context) { ctx.Exec() return } - err := ctx.Srv.BS.Login(user, password) + err := ctx.Srv.DB.Login(user, password) if err != nil { ctx.Data.Msg = err.Error() ctx.Exec() return } - ctx.SetUdata(user, ctx.Srv.BS.UserIsAdmin(user)) + ctx.SetUdata(user, ctx.Srv.DB.UserIsAdmin(user)) ctx.Redirect("/wiki/Index", 302) } } @@ -129,7 +131,7 @@ func searchHandler(ctx *Context) { return } - res, err := ctx.Srv.Index.Search(!ctx.LoggedOn(), search) + res, err := ctx.Srv.DB.Index.Search(search) if err != nil { ctx.Data.Msg = err.Error() ctx.Exec() @@ -148,7 +150,7 @@ func allHandler(ctx *Context) { BodyTitle: "All Articles", Admin: ctx.GetAdmin(), } - all, err := ctx.Srv.Index.GetAll(!ctx.LoggedOn()) + all, err := ctx.Srv.DB.Index.GetAll(!ctx.LoggedOn()) if err != nil { ctx.Data.Msg = err.Error() } @@ -171,11 +173,11 @@ func wikiNewHandler(ctx *Context) { pub = true } submit := ctx.Form("submit") - art := Article{ + p := core.Page{ Title: title, MD: text, } - ctx.Data.Data = art + ctx.Data.Data = p if !ctx.CheckToken(nil) { return } @@ -184,7 +186,7 @@ func wikiNewHandler(ctx *Context) { ctx.Exec() return } - newTitle, err := ctx.Srv.Index.Index(title, text, ctx.GetUser(), pub) + newTitle, err := ctx.Srv.DB.Index.Index(title, text, ctx.GetUser(), pub) if err != nil { ctx.Data.Msg = err.Error() ctx.Exec() @@ -202,7 +204,7 @@ func wikiHandler(ctx *Context) { Admin: ctx.GetAdmin(), } title := ctx.Var("title") - art, err := ctx.Srv.Index.Get(!ctx.LoggedOn(), title) + art, err := ctx.Srv.DB.Index.Get(!ctx.LoggedOn(), title) if err != nil { ctx.NotFound() return @@ -227,7 +229,7 @@ func wikiEditHandler(ctx *Context) { title := ctx.Var("title") switch ctx.Method() { case "GET": - art, err := ctx.Srv.Index.Get(!ctx.LoggedOn(), title) + art, err := ctx.Srv.DB.Index.Get(!ctx.LoggedOn(), title) if err != nil { ctx.NotFound() return @@ -236,7 +238,6 @@ func wikiEditHandler(ctx *Context) { ctx.Data.Data = art ctx.Data.Title = art.Title ctx.Data.BodyTitle = art.Title - ctx.Data.Editor = ctx.Srv.Editors.Set(title, ctx.GetUser()) ctx.Exec() case "POST": ftitle := ctx.Form("title") @@ -247,7 +248,7 @@ func wikiEditHandler(ctx *Context) { pub = true } submit := ctx.Form("submit") - ctx.Data.Data = Article{ + ctx.Data.Data = core.Page{ Title: ftitle, LinkTitle: title, MD: text, @@ -258,17 +259,12 @@ func wikiEditHandler(ctx *Context) { if !ctx.CheckToken(nil) { return } - if submit == "Back" { - ctx.Srv.Editors.RemoveEditor(title, ctx.GetUser()) - ctx.Redirect("/wiki/"+title, 302) - return - } if submit != "Submit" { ctx.Data.Msg = "wrong inputs" ctx.Exec() return } - err := ctx.Srv.Index.Update(title, text, ctx.GetUser(), pub) + err := ctx.Srv.DB.Index.Update(title, text, ctx.GetUser(), pub) if err != nil { ctx.Data.Msg = err.Error() ctx.Exec() diff --git a/pkg/server/server.go b/pkg/server/server.go index 358ba24..7228ad8 100644 --- a/pkg/server/server.go +++ b/pkg/server/server.go @@ -12,7 +12,6 @@ import ( "time" "git.giftfish.de/ston1th/gowiki/pkg/db" - "git.giftfish.de/ston1th/gowiki/pkg/index" ) const ( @@ -25,8 +24,7 @@ type HTTPServer struct { Version string SecCookie bool Store *sessions.CookieStore - BS *db.BoltStore - Index *index.Index + DB *db.DB listener net.Listener srv *http.Server templ map[string]*template.Template diff --git a/pkg/server/snippet_handler.go b/pkg/server/snippet_handler.go deleted file mode 100644 index 76ad44a..0000000 --- a/pkg/server/snippet_handler.go +++ /dev/null @@ -1,155 +0,0 @@ -package server - -func allSnippetHandler(ctx *Context) { - ctx.Template("allSnippetHandler") - ctx.Data = webData{Title: "All Snippets"} - all, err := ctx.Srv.BS.GetSnippets(ctx.GetUser()) - if err != nil { - ctx.Data.Msg = err.Error() - } - ctx.Data.Data = all - ctx.Exec() -} - -func snippetNewHandler(ctx *Context) { - ctx.Template("snippetNewHandler") - ctx.Data = webData{Title: "New Snippet"} - switch ctx.Method() { - case "GET": - ctx.Exec() - case "POST": - user := ctx.GetUser() - title := ctx.Form("title") - text := ctx.Form("text") - perm := ctx.Form("permission") - submit := ctx.Form("submit") - snip := Snippet{ - Title: title, - MD: text, - } - ctx.Data.Data = art - if !ctx.CheckToken() { - ctx.Data.Msg = "wrong csrf token" - ctx.Exec() - return - } - iperm, err := strconv.Atoi(perm) - if err != nil || submit != "Submit" { - ctx.Data.Msg = "wrong inputs" - ctx.Exec() - return - } - newTitle, err := ctx.Srv.BS.CreateSnippet(user, title, text, iperm) - if err != nil { - ctx.Data.Msg = err.Error() - ctx.Exec() - return - } - ctx.Redirect("/users/"+user+"/"+newTitle, 302) - } -} - -func wikiHandler(ctx *Context) { - if ctx.T == nil { - ctx.Template("wikiHandler") - } - ctx.Data = webData{ - Admin: ctx.GetAdmin(), - } - title := ctx.Var("title") - art, err := ctx.Srv.Index.Get(!ctx.LoggedOn(), title) - if err != nil { - ctx.NotFound() - return - } - art.LinkTitle = title - ctx.Data.Data = art - ctx.Data.Title = art.Title - ctx.Data.BodyTitle = art.Title - ctx.Exec() -} - -func wikiMDHandler(f ctxHandler) ctxHandler { - return func(ctx *Context) { - ctx.Template("wikiMDHandler") - f(ctx) - } -} - -func wikiEditHandler(ctx *Context) { - ctx.Template("wikiEditHandler") - ctx.Data = webData{} - title := ctx.Var("title") - switch ctx.Method() { - case "GET": - art, err := ctx.Srv.Index.Get(!ctx.LoggedOn(), title) - if err != nil { - ctx.NotFound() - return - } - art.LinkTitle = title - ctx.Data.Data = art - ctx.Data.Title = art.Title - ctx.Data.BodyTitle = art.Title - ctx.Data.Editor = ctx.Srv.Editors.Set(title, ctx.GetUser()) - ctx.Exec() - case "POST": - ftitle := ctx.Form("title") - text := ctx.Form("text") - public := ctx.Form("public") - pub := false - if public == "0" { - pub = true - } - submit := ctx.Form("submit") - ctx.Data.Data = Article{ - Title: ftitle, - LinkTitle: title, - MD: text, - Public: pub, - } - ctx.Data.Title = ftitle - ctx.Data.BodyTitle = ftitle - if !ctx.CheckToken() { - ctx.Data.Msg = "wrong csrf token" - ctx.Exec() - return - } - if submit == "Back" { - ctx.Srv.Editors.RemoveEditor(title, ctx.GetUser()) - ctx.Redirect("/wiki/"+title, 302) - return - } - if submit != "Submit" { - ctx.Data.Msg = "wrong inputs" - ctx.Exec() - return - } - err := ctx.Srv.Index.Update(title, text, ctx.GetUser(), pub) - if err != nil { - ctx.Data.Msg = err.Error() - ctx.Exec() - return - } - ctx.Srv.Editors.Remove(title) - ctx.Redirect("/wiki/"+title, 302) - } -} - -func wikiDelHandler(ctx *Context) { - title := ctx.Var("title") - submit := ctx.Form("submit") - if !ctx.CheckToken() { - ctx.Redirect("/wiki/"+title, 302) - return - } - if submit != "Delete" { - ctx.Redirect("/wiki/"+title, 302) - return - } - if err := ctx.Srv.Index.Delete(title); err != nil { - log.Println("delete:", err) - ctx.Redirect("/wiki/"+title, 302) - } - ctx.Redirect("/wiki/Index", 302) -} diff --git a/pkg/store/boltstore.go b/pkg/store/boltstore.go new file mode 100644 index 0000000..11e9805 --- /dev/null +++ b/pkg/store/boltstore.go @@ -0,0 +1,74 @@ +package store + +import ( + "github.com/boltdb/bolt" +) + +const defaultBoltBucket = "default" + +type BoltStore struct { + Marshaler + db *bolt.DB +} + +func NewBoltStore(file string, m Marshaler) (bs *BoltStore, err error) { + if m == nil { + m = NewGOB() + } + db, err := bolt.Open(file, 0666, nil) + if err != nil { + return + } + if err = db.Update(func(tx *bolt.Tx) error { + if _, err := tx.CreateBucketIfNotExists([]byte(defaultBoltBucket)); err != nil { + return err + } + return nil + }); err != nil { + return + } + bs = &BoltStore{m, db} + return +} + +func (bs *BoltStore) Get(key string, v interface{}) (err error) { + err = bs.db.View(func(tx *bolt.Tx) error { + b := tx.Bucket([]byte(defaultBoltBucket)).Get([]byte(key)) + if b == nil { + return ErrKeyNotFound + } + if v != nil { + return bs.Unmarshal(b, v) + } + return nil + }) + return +} + +func (bs *BoltStore) Set(key string, v interface{}) error { + return bs.db.Update(func(tx *bolt.Tx) error { + b, err := bs.Marshal(v) + if err != nil { + return err + } + return tx.Bucket([]byte(defaultBoltBucket)).Put([]byte(key), b) + }) +} + +func (bs *BoltStore) ForEach(f func(string, []byte) error) error { + return bs.db.View(func(tx *bolt.Tx) error { + return tx.Bucket([]byte(defaultBoltBucket)).ForEach(func(k, v []byte) error { + return f(string(k), v) + }) + }) +} + +func (bs *BoltStore) Delete(key string) error { + return bs.db.Update(func(tx *bolt.Tx) error { + return tx.Bucket([]byte(defaultBoltBucket)).Delete([]byte(key)) + }) +} + +func (bs *BoltStore) Close() error { + return bs.db.Close() +} diff --git a/pkg/store/dump.go b/pkg/store/dump.go new file mode 100644 index 0000000..d4081d0 --- /dev/null +++ b/pkg/store/dump.go @@ -0,0 +1,100 @@ +// Copyright (C) 2018 Marius Schellenberger + +package store + +/*import ( + "encoding/json" + "fmt" + "io" + "os" +) + +type Dumper interface { + Dump(io.Writer) error +} + +type Restorer interface { + Restore(io.Reader) error +} + +type DBDump struct { + Pages []DumpPage `json:"pages"` + Snippets []Snippet `json:"snippets"` + Users []User `json:"users"` +} + +type DumpPage struct { + Title string `json:"title"` + LinkTitle string `json:"link_title"` + MD string `json:"md"` + Created string `json:"created"` + Updated string `json:"updated"` + Public bool `json:"public"` +} + +func verboseLog(prefix, title string, v bool) { + if v { + fmt.Fprintln(os.Stderr, prefix, title) + } +} + +func Dump(path string, v bool) (err error) { + bs, err := NewBoltStore() + if err != nil { + return + } + var dump DBDump + //TODO bs.GetAllPages + var pages []*core.Page + for _, p := range pages { + if err != nil { + return + } + verboseLog("dump:", p.Title, v) + dump.Pages = append(dump.Pages, DumpPage{ + Title: p.Title, + LinkTitle: p.LinkTitle, + MD: p.MD, + Created: p.Created, + Updated: p.Updated, + Public: p.Public, + }) + } + //TODO snippets + //verboseLog("dump:", "snippets", v) + verboseLog("dump:", "users", v) + dump.Users, err = bs.DumpUsers() + if err != nil { + return + } + return json.NewEncoder(os.Stdout).Encode(dump) +} + +func Restore(path string, v bool) (err error) { + i, err := NewIndex(path) + if err != nil { + return + } + defer i.Close() + bs, err := NewBoltStore() + if err != nil { + return + } + var dump DBDump + err = json.NewDecoder(os.Stdin).Decode(&dump) + if err != nil { + return + } + for _, art := range dump.Articles { + verboseLog("restore:", art.Title, v) + err = i.Restore(&art) + if err != nil { + return + } + } + //TODO snippets + //verboseLog("restore:", "snippets", v) + verboseLog("restore:", "users", v) + err = bs.RestoreUsers(dump.Users) + return +}*/ diff --git a/pkg/store/marshal.go b/pkg/store/marshal.go new file mode 100644 index 0000000..3ccf939 --- /dev/null +++ b/pkg/store/marshal.go @@ -0,0 +1,28 @@ +package store + +import ( + "bytes" + "encoding/gob" +) + +type Marshaler interface { + Marshal(v interface{}) ([]byte, error) + Unmarshal(data []byte, v interface{}) error +} + +type gobMarshaler struct{} + +func NewGOB() Marshaler { + return gobMarshaler{} +} + +func (gobMarshaler) Marshal(v interface{}) (b []byte, err error) { + buf := new(bytes.Buffer) + err = gob.NewEncoder(buf).Encode(v) + b = buf.Bytes() + return +} + +func (gobMarshaler) Unmarshal(data []byte, v interface{}) error { + return gob.NewDecoder(bytes.NewBuffer(data)).Decode(v) +} diff --git a/pkg/store/store.go b/pkg/store/store.go new file mode 100644 index 0000000..19ab98a --- /dev/null +++ b/pkg/store/store.go @@ -0,0 +1,37 @@ +// Copyright (C) 2018 Marius Schellenberger + +package store + +import ( + "fmt" +) + +//const ( +// dbFile = "gowiki.db" +// defUser = "admin" +// defPassword = "gowiki" +// userTable = "user" +// pageTable = "page" +// snippetTable = "snippet" +// bcryptCost = 13 +// indexName = "Index" +// welcome = `# Welcome to GoWiki +//This is the [Index](/wiki/Index) page. +//You can customize it how you like.` +// wikiSection = "wiki" +//) + +func storeErr(i interface{}) error { + return fmt.Errorf("store: %s", i) +} + +var ErrKeyNotFound = storeErr("key not found") + +type Store interface { + Marshaler + Get(string, interface{}) error + Set(string, interface{}) error + ForEach(func(string, []byte) error) error + Delete(string) error + Close() error +}