split into packages

This commit is contained in:
ston1th 2018-02-09 22:38:47 +01:00
commit c5d41b0965
26 changed files with 328 additions and 449 deletions

71
pkg/db/db.go Normal file
View file

@ -0,0 +1,71 @@
// Copyright (C) 2017 Marius Schellenberger
package db
import (
"bytes"
"errors"
"github.com/boltdb/bolt"
)
const (
dbFile = "gowiki.db"
defUser = "admin"
defPassword = "gowiki"
userTable = "user"
pageTable = "page"
bcryptCost = 13
indexName = "Index"
welcome = `# Welcome to GoWiki
This is the [Index](/wiki/Index) page.
You can customize it how you like.`
)
const (
maxResult = 1e6
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
}
func NewBoltStore() (bs *BoltStore, err error) {
db, err := bolt.Open(dbFile, 0666, 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(pagesTable)); err != nil {
return err
}
return nil
}); err != nil {
return
}
bs = &BoltStore{NewGOB(), db}
if !bs.UserExists(defUser) {
err = bs.CreateUser(defUser, defPassword, true)
if err != nil {
return
}
}
bs.UnlockUser(defUser)
return
}
func (bs *BoltStore) Close() error {
return bs.db.Close()
}

103
pkg/db/dump.go Normal file
View file

@ -0,0 +1,103 @@
// Copyright (C) 2017 Marius Schellenberger
package db
import (
"encoding/json"
"fmt"
"os"
)
type Dump struct {
Articles []DumpArticle `json:"articles"`
Snippets []Snippet `json:"snippets"`
Users []User `json:"users"`
}
type DumpArticle 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) {
i, err := NewDumpIndex(path)
if err != nil {
return
}
defer i.Close()
bs, err := NewBoltStore()
if err != nil {
return
}
art, err := i.Get(false, indexName)
if err != nil {
return
}
var dump Dump
titles, err := i.Dump()
if err != nil {
return
}
for _, title := range titles {
art, err = i.Get(false, title)
if err != nil {
return
}
verboseLog("dump:", art.Title, v)
dump.Articles = append(dump.Articles, DumpArticle{
Title: art.Title,
LinkTitle: art.LinkTitle,
MD: art.MD,
Created: art.Created,
Updated: art.Updated,
Public: art.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 Dump
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
}

14
pkg/db/helper.go Normal file
View file

@ -0,0 +1,14 @@
package db
import "golang.org/x/crypto/sha3"
func validatePassword(pw string) (b []byte) {
b = []byte(pw)
if len(pw) <= 56 {
return
}
hash := sha3.New384()
hash.Write(b)
b = hash.Sum(nil)
return
}

25
pkg/db/marshal.go Normal file
View file

@ -0,0 +1,25 @@
package db
import "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)
}

42
pkg/db/page.go Normal file
View file

@ -0,0 +1,42 @@
package db
type Article struct {
Title string `json:"title"`
LinkTitle string `json:"-"`
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 Permission int
const (
Public Permission = iota
Internal
Private
)
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
}
return bs.Unmarshal(v, &u)
})
return
}

148
pkg/db/snippet.go Normal file
View file

@ -0,0 +1,148 @@
package db
import (
"bytes"
"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")
errInvalidPermission = dbErr("invalid permission")
)
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(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(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
}

224
pkg/db/user.go Normal file
View file

@ -0,0 +1,224 @@
package db
import (
"github.com/boltdb/bolt"
"golang.org/x/crypto/bcrypt"
)
func (bs *BoltStore) GetUsers() (users []User, err error) {
users, err = bs.DumpUsers()
for i := range users {
users[i].Password = ""
}
return
}
func (bs *BoltStore) GetUserWithoutPassword(username string) (u User, err error) {
u, err = bs.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 errUserNotExists
}
return bs.Unmarshal(v, &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 (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 errUserNotExists
}
return nil
})
}
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 {
buf = append(buf, copyBuf(v))
return nil
})
})
if err != nil {
return
}
for _, b := range buf {
u := new(User)
if err = bs.Unmarshal(b, u); err != nil {
return
}
users = append(users, *u)
}
return
}
func (bs *BoltStore) RestoreUsers(users []User) error {
return bs.db.Update(func(tx *bolt.Tx) error {
b := tx.Bucket([]byte(userTable))
err := b.ForEach(func(k, v []byte) error {
return b.Delete(k)
})
if err != nil {
return err
}
for _, u := range users {
v, err := bs.Marshal(u)
if err != nil {
return err
}
err = b.Put([]byte(u.Username), v)
if err != nil {
return err
}
}
return nil
})
}
func (bs *BoltStore) CreateUser(username, password string, admin bool) error {
if err := bs.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
}
return err
}
func (bs *BoltStore) UnlockUser(username string) error {
return bs.db.Update(func(tx *bolt.Tx) error {
u, err := bs.GetUser(username)
if err != nil {
return err
}
if u.Locked < 3 {
return nil
}
u.Locked = 0
v, err := bs.Marshal(u)
if err != nil {
return err
}
return tx.Bucket([]byte(userTable)).Put([]byte(username), v)
})
}
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)
}
return nil
})
}
func (bs *BoltStore) 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)
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)
})
}
func (bs *BoltStore) 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))
})
}