added user independent sections and started db migration work
This commit is contained in:
parent
6a2dc8146c
commit
16ea95bf09
24 changed files with 622 additions and 122 deletions
|
|
@ -34,9 +34,11 @@ var (
|
||||||
)
|
)
|
||||||
|
|
||||||
func initServer(cfg core.Config) (err error) {
|
func initServer(cfg core.Config) (err error) {
|
||||||
if err = godrop.PledgePromises("stdio rpath wpath cpath inet fattr flock proc exec id unveil"); err != nil {
|
// remove initial pledge due to violation with go1.12
|
||||||
return
|
// sysctl kern.somaxconn
|
||||||
}
|
//if err = godrop.PledgePromises("stdio rpath wpath cpath inet fattr flock proc exec id unveil"); err != nil {
|
||||||
|
// return
|
||||||
|
//}
|
||||||
dropCfg := godrop.Config{
|
dropCfg := godrop.Config{
|
||||||
User: cfg.User,
|
User: cfg.User,
|
||||||
Group: cfg.Group,
|
Group: cfg.Group,
|
||||||
|
|
|
||||||
11
pkg/core/helper.go
Normal file
11
pkg/core/helper.go
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
package core
|
||||||
|
|
||||||
|
// Contains checks if slice a contains string str
|
||||||
|
func Contains(str string, a []string) (int, bool) {
|
||||||
|
for i, s := range a {
|
||||||
|
if s == str {
|
||||||
|
return i, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1, false
|
||||||
|
}
|
||||||
|
|
@ -7,7 +7,6 @@ type Permission int
|
||||||
const (
|
const (
|
||||||
Invalid Permission = iota
|
Invalid Permission = iota
|
||||||
Public
|
Public
|
||||||
Internal
|
|
||||||
Private
|
Private
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -16,37 +15,27 @@ func ParsePermString(p string) Permission {
|
||||||
case "1":
|
case "1":
|
||||||
return Public
|
return Public
|
||||||
case "2":
|
case "2":
|
||||||
return Internal
|
|
||||||
case "3":
|
|
||||||
return Private
|
return Private
|
||||||
}
|
}
|
||||||
return Invalid
|
return Invalid
|
||||||
}
|
}
|
||||||
|
|
||||||
func ReadPerm(username string, p *Page) bool {
|
func ReadPerm(username string, p Permission, s Section) bool {
|
||||||
switch p.Perm {
|
switch p {
|
||||||
case Public:
|
case Public:
|
||||||
return true
|
return true
|
||||||
case Internal:
|
|
||||||
return username != ""
|
|
||||||
case Private:
|
case Private:
|
||||||
return username == p.Owner
|
_, c := Contains(username, s.Members)
|
||||||
|
return c
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func WritePerm(username, section string, p *Page) bool {
|
func WritePerm(username string, p Permission, s Section) bool {
|
||||||
if section != WikiSection && section != username {
|
switch p {
|
||||||
return false
|
case Public, Private:
|
||||||
}
|
_, c := Contains(username, s.Members)
|
||||||
switch p.Perm {
|
return c
|
||||||
case Public, Internal:
|
|
||||||
if p.Owner == WikiSection {
|
|
||||||
return username != ""
|
|
||||||
}
|
|
||||||
return username == p.Owner
|
|
||||||
case Private:
|
|
||||||
return username == p.Owner
|
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,8 +11,7 @@ func TestParsePermString(t *testing.T) {
|
||||||
data := map[string]Permission{
|
data := map[string]Permission{
|
||||||
"": Invalid,
|
"": Invalid,
|
||||||
"1": Public,
|
"1": Public,
|
||||||
"2": Internal,
|
"2": Private,
|
||||||
"3": Private,
|
|
||||||
"123": Invalid,
|
"123": Invalid,
|
||||||
"test": Invalid,
|
"test": Invalid,
|
||||||
}
|
}
|
||||||
|
|
@ -28,21 +27,23 @@ func TestParsePermString(t *testing.T) {
|
||||||
func TestReadPerm(t *testing.T) {
|
func TestReadPerm(t *testing.T) {
|
||||||
data := []struct {
|
data := []struct {
|
||||||
Username string
|
Username string
|
||||||
Page *Page
|
Perm Permission
|
||||||
|
Section Section
|
||||||
OK bool
|
OK bool
|
||||||
}{
|
}{
|
||||||
{"", &Page{Perm: Invalid, Owner: "admin"}, false},
|
{"", Invalid, Section{Members: []string{"admin"}}, false},
|
||||||
{"", &Page{Perm: Public, Owner: "admin"}, true},
|
{"", Public, Section{Members: []string{"admin"}}, true},
|
||||||
{"user", &Page{Perm: Public, Owner: "admin"}, true},
|
{"user", Public, Section{Members: []string{"admin"}}, true},
|
||||||
{"", &Page{Perm: Internal, Owner: "admin"}, false},
|
{"", Private, Section{Members: []string{"admin"}}, false},
|
||||||
{"user", &Page{Perm: Internal, Owner: "admin"}, true},
|
{"user", Private, Section{Members: []string{"admin"}}, false},
|
||||||
{"", &Page{Perm: Private, Owner: "admin"}, false},
|
{"", Private, Section{Members: []string{"user"}}, false},
|
||||||
{"user", &Page{Perm: Private, Owner: "admin"}, false},
|
{"user", Private, Section{Members: []string{"user"}}, true},
|
||||||
{"admin", &Page{Perm: Private, Owner: "admin"}, true},
|
{"user", Private, Section{Members: []string{"user", "admin"}}, true},
|
||||||
|
{"admin", Private, Section{Members: []string{"user"}}, false},
|
||||||
}
|
}
|
||||||
for i, v := range data {
|
for i, v := range data {
|
||||||
t.Run(fmt.Sprintf("Read:%d", i), func(t *testing.T) {
|
t.Run(fmt.Sprintf("Read:%d", i), func(t *testing.T) {
|
||||||
if ReadPerm(v.Username, v.Page) != v.OK {
|
if ReadPerm(v.Username, v.Perm, v.Section) != v.OK {
|
||||||
t.Fail()
|
t.Fail()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
@ -52,28 +53,23 @@ func TestReadPerm(t *testing.T) {
|
||||||
func TestWritePerm(t *testing.T) {
|
func TestWritePerm(t *testing.T) {
|
||||||
data := []struct {
|
data := []struct {
|
||||||
Username string
|
Username string
|
||||||
Section string
|
Perm Permission
|
||||||
Page *Page
|
Section Section
|
||||||
OK bool
|
OK bool
|
||||||
}{
|
}{
|
||||||
{"user", "admin", &Page{Perm: Invalid, Owner: "admin"}, false},
|
{"", Invalid, Section{Members: []string{"user"}}, false},
|
||||||
{"", "user", &Page{Perm: Invalid, Owner: "admin"}, false},
|
{"", Public, Section{Members: []string{"user"}}, false},
|
||||||
{"user", "user", &Page{Perm: Invalid, Owner: "user"}, false},
|
{"user", Public, Section{Members: []string{"user"}}, true},
|
||||||
{"user", "user", &Page{Perm: Public, Owner: "user"}, true},
|
{"user", Public, Section{Members: []string{"admin"}}, false},
|
||||||
{"user", "user", &Page{Perm: Internal, Owner: "user"}, true},
|
{"user", Private, Section{Members: []string{"user"}}, true},
|
||||||
{"user", "user", &Page{Perm: Private, Owner: "user"}, true},
|
{"", Private, Section{Members: []string{"user"}}, false},
|
||||||
{"", WikiSection, &Page{Perm: Invalid, Owner: "admin"}, false},
|
{"", Private, Section{Members: []string{"user", "admin"}}, false},
|
||||||
{"", WikiSection, &Page{Perm: Public, Owner: "admin"}, false},
|
{"user", Private, Section{Members: []string{"user", "admin"}}, true},
|
||||||
{"", WikiSection, &Page{Perm: Internal, Owner: "admin"}, false},
|
{"admin", Private, Section{Members: []string{"user", "admin"}}, true},
|
||||||
{"user", WikiSection, &Page{Perm: Public, Owner: WikiSection}, true},
|
|
||||||
{"user", WikiSection, &Page{Perm: Internal, Owner: WikiSection}, true},
|
|
||||||
{"", "admin", &Page{Perm: Private, Owner: "admin"}, false},
|
|
||||||
{"user", "admin", &Page{Perm: Private, Owner: "admin"}, false},
|
|
||||||
{"admin", "admin", &Page{Perm: Private, Owner: "admin"}, true},
|
|
||||||
}
|
}
|
||||||
for i, v := range data {
|
for i, v := range data {
|
||||||
t.Run(fmt.Sprintf("Write:%d", i), func(t *testing.T) {
|
t.Run(fmt.Sprintf("Write:%d", i), func(t *testing.T) {
|
||||||
if WritePerm(v.Username, v.Section, v.Page) != v.OK {
|
if WritePerm(v.Username, v.Perm, v.Section) != v.OK {
|
||||||
t.Fail()
|
t.Fail()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,6 @@ type Page struct {
|
||||||
HTML template.HTML `json:"html"`
|
HTML template.HTML `json:"html"`
|
||||||
Created string `json:"created"`
|
Created string `json:"created"`
|
||||||
Updated string `json:"updated"`
|
Updated string `json:"updated"`
|
||||||
Owner string `json:"owner"`
|
|
||||||
Perm Permission `json:"perm"`
|
Perm Permission `json:"perm"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -29,15 +28,27 @@ func (p UpdatedPages) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
|
||||||
func (p UpdatedPages) Less(i, j int) bool { return p[i].Updated > p[j].Updated }
|
func (p UpdatedPages) Less(i, j int) bool { return p[i].Updated > p[j].Updated }
|
||||||
|
|
||||||
type Section struct {
|
type Section struct {
|
||||||
Section string
|
Name string
|
||||||
Pages UpdatedPages
|
User bool
|
||||||
|
Members []string
|
||||||
}
|
}
|
||||||
|
|
||||||
type Sections []Section
|
type Sections []Section
|
||||||
|
|
||||||
func (s Sections) Len() int { return len(s) }
|
func (s Sections) Len() int { return len(s) }
|
||||||
func (s Sections) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
|
func (s Sections) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
|
||||||
func (s Sections) Less(i, j int) bool { return s[i].Section < s[j].Section }
|
func (s Sections) Less(i, j int) bool { return s[i].Name < s[j].Name }
|
||||||
|
|
||||||
|
type SectionPage struct {
|
||||||
|
Section string
|
||||||
|
Pages UpdatedPages
|
||||||
|
}
|
||||||
|
|
||||||
|
type SectionPages []SectionPage
|
||||||
|
|
||||||
|
func (s SectionPages) Len() int { return len(s) }
|
||||||
|
func (s SectionPages) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
|
||||||
|
func (s SectionPages) Less(i, j int) bool { return s[i].Section < s[j].Section }
|
||||||
|
|
||||||
type User struct {
|
type User struct {
|
||||||
Username string
|
Username string
|
||||||
|
|
|
||||||
11
pkg/db/db.go
11
pkg/db/db.go
|
|
@ -57,9 +57,18 @@ func New(cfg core.Config) (db *DB, err error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if db.SectionExists(core.WikiSection) != nil {
|
||||||
|
err = db.CreateSection(core.WikiSection, []string{defUser}, false)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
_, err = db.CreatePage(core.IndexPage, core.WikiSection, welcome, defUser, core.Public)
|
_, err = db.CreatePage(core.IndexPage, core.WikiSection, welcome, defUser, core.Public)
|
||||||
return
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return db.RunMigrations()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (db *DB) Dump(w io.Writer) error {
|
func (db *DB) Dump(w io.Writer) error {
|
||||||
|
|
|
||||||
|
|
@ -4,10 +4,11 @@ package db
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
|
"git.giftfish.de/ston1th/gowiki/pkg/core"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
var reservedUsers = []string{
|
var reservedNames = []string{
|
||||||
"blacklist",
|
"blacklist",
|
||||||
"login",
|
"login",
|
||||||
"logout",
|
"logout",
|
||||||
|
|
@ -27,13 +28,18 @@ func hasTrimPrefix(s, prefix string) (string, bool) {
|
||||||
return s, false
|
return s, false
|
||||||
}
|
}
|
||||||
|
|
||||||
func reservedUser(username string) bool {
|
func addOrRemove(s string, a []string) []string {
|
||||||
for _, v := range reservedUsers {
|
i, _ := core.Contains(s, a)
|
||||||
if username == v {
|
switch i {
|
||||||
return true
|
case -1:
|
||||||
}
|
return append(a, s)
|
||||||
|
case 0:
|
||||||
|
return a[1:]
|
||||||
|
case len(a):
|
||||||
|
return a[:i]
|
||||||
|
default:
|
||||||
|
return append(a[:i], a[i+1:]...)
|
||||||
}
|
}
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func validatePassword(pw string) (b []byte) {
|
func validatePassword(pw string) (b []byte) {
|
||||||
|
|
|
||||||
72
pkg/db/migration.go
Normal file
72
pkg/db/migration.go
Normal file
|
|
@ -0,0 +1,72 @@
|
||||||
|
package db
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.giftfish.de/ston1th/gowiki/pkg/core"
|
||||||
|
"git.giftfish.de/ston1th/gowiki/pkg/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
versionKey = "version/version"
|
||||||
|
currentVersion = 2
|
||||||
|
)
|
||||||
|
|
||||||
|
type migrator func(*DB) (int, error)
|
||||||
|
|
||||||
|
var migrators = []migrator{
|
||||||
|
func(db *DB) (int, error) {
|
||||||
|
return 0, nil
|
||||||
|
},
|
||||||
|
func(db *DB) (version int, err error) {
|
||||||
|
version = 1
|
||||||
|
return
|
||||||
|
},
|
||||||
|
func(db *DB) (version int, err error) {
|
||||||
|
version = 2
|
||||||
|
// recreate all pages with new format
|
||||||
|
var pages []string
|
||||||
|
db.store.ForEach(func(k string, _ []byte) error {
|
||||||
|
if trim, ok := hasTrimPrefix(k, pagePrefix); ok {
|
||||||
|
pages = append(pages, trim)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
var p core.Page
|
||||||
|
for _, v := range pages {
|
||||||
|
k := pagePrefix + v
|
||||||
|
err := db.store.Get(k, &p)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("db: migrator[%d]: pageGet %s: %s", version, k, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
err = db.store.Set(k, &p)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("db: migrator[%d]: pageSet %s: %s", version, k, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func (db *DB) RunMigrations() (err error) {
|
||||||
|
var version int
|
||||||
|
err = db.store.Get(versionKey, &version)
|
||||||
|
if err == ErrKeyNotFound {
|
||||||
|
err = db.store.Set(versionKey, 0)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
log.Printf("db: detected database version %d", version)
|
||||||
|
for version < currentVersion {
|
||||||
|
log.Printf("db: running migration %d", version)
|
||||||
|
version, err = migrators[version](db)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = db.store.Set(versionKey, version)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -14,15 +14,14 @@ import (
|
||||||
const pagePrefix = "page/"
|
const pagePrefix = "page/"
|
||||||
|
|
||||||
var (
|
var (
|
||||||
errPageNotFound = store.ErrKeyNotFound
|
errPageNotFound = store.ErrKeyNotFound
|
||||||
errPermissionDenied = errors.New("db: permission denied")
|
errPermissionDenied = errors.New("db: permission denied")
|
||||||
errInvalidPermission = errors.New("db: invalid permission")
|
errInvalidPermission = errors.New("db: invalid permission")
|
||||||
errPrivateWikiPage = errors.New("db: private wiki pages are not allowed")
|
errPrivateWikiPage = errors.New("db: private wiki pages are not allowed")
|
||||||
errNonPublicIndexPage = errors.New("db: internal/private index page is not allowed")
|
errDeleteIndexPage = errors.New("db: index page can not be deleted")
|
||||||
errDeleteIndexPage = errors.New("db: index page can not be deleted")
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func (db *DB) GetAllSections(username string) (sections core.Sections) {
|
func (db *DB) GetAllSections(username string) (sections core.SectionPages) {
|
||||||
m := make(map[string]core.UpdatedPages)
|
m := make(map[string]core.UpdatedPages)
|
||||||
db.store.ForEach(func(k string, _ []byte) error {
|
db.store.ForEach(func(k string, _ []byte) error {
|
||||||
if trim, ok := hasTrimPrefix(k, pagePrefix); ok {
|
if trim, ok := hasTrimPrefix(k, pagePrefix); ok {
|
||||||
|
|
@ -46,7 +45,7 @@ func (db *DB) GetAllSections(username string) (sections core.Sections) {
|
||||||
if l > 5 {
|
if l > 5 {
|
||||||
l = 5
|
l = 5
|
||||||
}
|
}
|
||||||
sections = append(sections, core.Section{s, p[:l]})
|
sections = append(sections, core.SectionPage{s, p[:l]})
|
||||||
}
|
}
|
||||||
sort.Sort(sections)
|
sort.Sort(sections)
|
||||||
return
|
return
|
||||||
|
|
@ -86,7 +85,7 @@ func (db *DB) ValidatePage(st, username string) bool {
|
||||||
db.cache.Add(st, p)
|
db.cache.Add(st, p)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return core.ReadPerm(username, p)
|
return db.ReadPerm(st, username, p.Perm)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (db *DB) GetPage(section, title, username string) (p *core.Page, err error) {
|
func (db *DB) GetPage(section, title, username string) (p *core.Page, err error) {
|
||||||
|
|
@ -97,7 +96,7 @@ func (db *DB) GetPage(section, title, username string) (p *core.Page, err error)
|
||||||
db.cache.Add(st, p)
|
db.cache.Add(st, p)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if !core.ReadPerm(username, p) {
|
if !db.ReadPerm(section, username, p.Perm) {
|
||||||
err = errPageNotFound
|
err = errPageNotFound
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
|
|
@ -109,7 +108,7 @@ func (db *DB) getPage(title, username string) (p *core.Page, err error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if !core.ReadPerm(username, p) {
|
if !db.ReadPerm(title, username, p.Perm) {
|
||||||
err = errPageNotFound
|
err = errPageNotFound
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
|
|
@ -120,13 +119,9 @@ func (db *DB) CreatePage(title, section, markdown, username string, p core.Permi
|
||||||
err = errInvalidPermission
|
err = errInvalidPermission
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
owner := username
|
if section == core.WikiSection && p == core.Private {
|
||||||
if section == core.WikiSection {
|
err = errPrivateWikiPage
|
||||||
owner = core.WikiSection
|
return
|
||||||
if p == core.Private {
|
|
||||||
err = errPrivateWikiPage
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
st := render.StoreTitle(section, render.Title(title))
|
st := render.StoreTitle(section, render.Title(title))
|
||||||
_, err = db.getPage(st, username)
|
_, err = db.getPage(st, username)
|
||||||
|
|
@ -139,7 +134,6 @@ func (db *DB) CreatePage(title, section, markdown, username string, p core.Permi
|
||||||
StoreTitle: st,
|
StoreTitle: st,
|
||||||
Markdown: markdown,
|
Markdown: markdown,
|
||||||
Created: created(username),
|
Created: created(username),
|
||||||
Owner: owner,
|
|
||||||
Perm: p,
|
Perm: p,
|
||||||
}
|
}
|
||||||
search := render.Render(page)
|
search := render.Render(page)
|
||||||
|
|
@ -166,17 +160,12 @@ func (db *DB) UpdatePage(title, section, markdown, username string, p core.Permi
|
||||||
err = errPrivateWikiPage
|
err = errPrivateWikiPage
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if section == core.WikiSection && title == core.IndexPage &&
|
|
||||||
(p == core.Internal || p == core.Private) {
|
|
||||||
err = errNonPublicIndexPage
|
|
||||||
return
|
|
||||||
}
|
|
||||||
st := render.StoreTitle(section, title)
|
st := render.StoreTitle(section, title)
|
||||||
page, err := db.getPage(st, username)
|
page, err := db.getPage(st, username)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if !core.WritePerm(username, section, page) {
|
if !db.WritePerm(title, username, page.Perm) {
|
||||||
return errPermissionDenied
|
return errPermissionDenied
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -216,7 +205,7 @@ func (db *DB) DeletePage(title, section, username string) (err error) {
|
||||||
if page, err = db.getPage(st, username); err != nil {
|
if page, err = db.getPage(st, username); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if !core.WritePerm(username, section, page) {
|
if !db.WritePerm(section, username, page.Perm) {
|
||||||
return errPermissionDenied
|
return errPermissionDenied
|
||||||
}
|
}
|
||||||
return db.deletePage(st)
|
return db.deletePage(st)
|
||||||
|
|
|
||||||
26
pkg/db/permission.go
Normal file
26
pkg/db/permission.go
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
// Copyright (C) 2019 Marius Schellenberger
|
||||||
|
|
||||||
|
package db
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.giftfish.de/ston1th/gowiki/pkg/core"
|
||||||
|
"git.giftfish.de/ston1th/gowiki/pkg/render"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (db *DB) ReadPerm(st, username string, p core.Permission) bool {
|
||||||
|
section, _ := render.SplitStoreTitle(st)
|
||||||
|
s, err := db.GetSection(section)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return core.ReadPerm(username, p, s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (db *DB) WritePerm(st, username string, p core.Permission) bool {
|
||||||
|
section, _ := render.SplitStoreTitle(st)
|
||||||
|
s, err := db.GetSection(section)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return core.WritePerm(username, p, s)
|
||||||
|
}
|
||||||
103
pkg/db/section.go
Normal file
103
pkg/db/section.go
Normal file
|
|
@ -0,0 +1,103 @@
|
||||||
|
// Copyright (C) 2019 Marius Schellenberger
|
||||||
|
|
||||||
|
package db
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"git.giftfish.de/ston1th/gowiki/pkg/core"
|
||||||
|
"git.giftfish.de/ston1th/gowiki/pkg/log"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
const sectionPrefix = "section/"
|
||||||
|
|
||||||
|
var (
|
||||||
|
errSectionExists = errors.New("db: section already exist")
|
||||||
|
)
|
||||||
|
|
||||||
|
func (db *DB) GetSections() (secs core.Sections, err error) {
|
||||||
|
err = db.store.ForEach(func(k string, _ []byte) error {
|
||||||
|
if trim, ok := hasTrimPrefix(k, sectionPrefix); ok {
|
||||||
|
s, err := db.GetSection(trim)
|
||||||
|
if err == nil {
|
||||||
|
secs = append(secs, s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
sort.Sort(secs)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (db *DB) GetSection(section string) (s core.Section, err error) {
|
||||||
|
err = db.store.Get(sectionPrefix+section, &s)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (db *DB) SectionExists(section string) error {
|
||||||
|
return db.store.Get(sectionPrefix+section, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (db *DB) CreateSection(section string, members []string, user bool) error {
|
||||||
|
section = userRe.ReplaceAllString(section, "")
|
||||||
|
if err := db.SectionExists(section); err == nil {
|
||||||
|
return errSectionExists
|
||||||
|
}
|
||||||
|
return db.UpdateSection(section, members, user)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (db *DB) UpdateSection(section string, members []string, user bool) error {
|
||||||
|
section = userRe.ReplaceAllString(section, "")
|
||||||
|
if _, ok := core.Contains(section, reservedNames); ok {
|
||||||
|
return errNameReserved
|
||||||
|
}
|
||||||
|
var m []string
|
||||||
|
if user {
|
||||||
|
m = append(m, section)
|
||||||
|
} else {
|
||||||
|
for _, v := range members {
|
||||||
|
if v == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err := db.UserExists(v); err == nil {
|
||||||
|
m = append(m, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return db.store.Set(sectionPrefix+section, &core.Section{section, user, m})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (db *DB) DeleteSection(section string, user bool) error {
|
||||||
|
if section == core.WikiSection {
|
||||||
|
return errors.New("section '" + core.WikiSection + "' cannot be deleted")
|
||||||
|
}
|
||||||
|
s, err := db.GetSection(section)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = db.store.Delete(sectionPrefix + section)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !s.User && !user {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
section += "/"
|
||||||
|
var pages []string
|
||||||
|
db.store.ForEach(func(k string, _ []byte) error {
|
||||||
|
if trim, ok := hasTrimPrefix(k, pagePrefix); ok {
|
||||||
|
if strings.HasPrefix(trim, section) {
|
||||||
|
pages = append(pages, trim)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
for _, p := range pages {
|
||||||
|
err := db.deletePage(p)
|
||||||
|
if err != nil {
|
||||||
|
log.Debugf("DeleteSection %s, %s", section, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
@ -5,14 +5,12 @@ package db
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"git.giftfish.de/ston1th/gowiki/pkg/core"
|
"git.giftfish.de/ston1th/gowiki/pkg/core"
|
||||||
"git.giftfish.de/ston1th/gowiki/pkg/log"
|
|
||||||
"git.giftfish.de/ston1th/gowiki/pkg/otp"
|
"git.giftfish.de/ston1th/gowiki/pkg/otp"
|
||||||
"git.giftfish.de/ston1th/jwt/v3"
|
"git.giftfish.de/ston1th/jwt/v3"
|
||||||
"golang.org/x/crypto/bcrypt"
|
"golang.org/x/crypto/bcrypt"
|
||||||
"regexp"
|
"regexp"
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const userPrefix = "user/"
|
const userPrefix = "user/"
|
||||||
|
|
@ -20,7 +18,7 @@ const userPrefix = "user/"
|
||||||
var (
|
var (
|
||||||
errUserNotFound = errors.New("db: user not found")
|
errUserNotFound = errors.New("db: user not found")
|
||||||
errUserExists = errors.New("db: user already exist")
|
errUserExists = errors.New("db: user already exist")
|
||||||
errUserReserved = errors.New("db: username is reserved")
|
errNameReserved = errors.New("db: name is reserved")
|
||||||
|
|
||||||
userRe = regexp.MustCompile("[^a-zA-Z0-9]+")
|
userRe = regexp.MustCompile("[^a-zA-Z0-9]+")
|
||||||
)
|
)
|
||||||
|
|
@ -64,8 +62,8 @@ func (db *DB) UserExists(username string) error {
|
||||||
|
|
||||||
func (db *DB) CreateUser(username, password string, admin bool) error {
|
func (db *DB) CreateUser(username, password string, admin bool) error {
|
||||||
username = userRe.ReplaceAllString(username, "")
|
username = userRe.ReplaceAllString(username, "")
|
||||||
if reservedUser(username) {
|
if _, ok := core.Contains(username, reservedNames); ok {
|
||||||
return errUserReserved
|
return errNameReserved
|
||||||
}
|
}
|
||||||
if err := db.UserExists(username); err == nil {
|
if err := db.UserExists(username); err == nil {
|
||||||
return errUserExists
|
return errUserExists
|
||||||
|
|
@ -74,6 +72,10 @@ func (db *DB) CreateUser(username, password string, admin bool) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
err = db.CreateSection(username, nil, true)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
return db.store.Set(userPrefix+username, &core.User{
|
return db.store.Set(userPrefix+username, &core.User{
|
||||||
Username: username,
|
Username: username,
|
||||||
Password: string(hash),
|
Password: string(hash),
|
||||||
|
|
@ -218,21 +220,5 @@ func (db *DB) DeleteUser(username string) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
username += "/"
|
return db.DeleteSection(username, true)
|
||||||
var pages []string
|
|
||||||
db.store.ForEach(func(k string, _ []byte) error {
|
|
||||||
if trim, ok := hasTrimPrefix(k, pagePrefix); ok {
|
|
||||||
if strings.HasPrefix(trim, username) {
|
|
||||||
pages = append(pages, trim)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
for _, p := range pages {
|
|
||||||
err := db.deletePage(p)
|
|
||||||
if err != nil {
|
|
||||||
log.Debugf("DeleteUser %s, %s", username, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -47,8 +47,8 @@ func StoreTitle(section, title string) string {
|
||||||
return section + "/" + title
|
return section + "/" + title
|
||||||
}
|
}
|
||||||
|
|
||||||
func UnstoreTitle(storeTitle string) (section, title string) {
|
func SplitStoreTitle(st string) (section, title string) {
|
||||||
if a := strings.Split(storeTitle, "/"); len(a) == 2 {
|
if a := strings.Split(st, "/"); len(a) == 2 {
|
||||||
return a[0], a[1]
|
return a[0], a[1]
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
|
|
|
||||||
|
|
@ -15,8 +15,8 @@ func TestTitleFunctions(t *testing.T) {
|
||||||
t.Fail()
|
t.Fail()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
t.Run("UnstoreTitle", func(t *testing.T) {
|
t.Run("SplitStoreTitle", func(t *testing.T) {
|
||||||
se, ti := UnstoreTitle(combined)
|
se, ti := SplitStoreTitle(combined)
|
||||||
if se != section || ti != title {
|
if se != section || ti != title {
|
||||||
t.Fail()
|
t.Fail()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -236,6 +236,57 @@ func sectionHandler(ctx *Context) {
|
||||||
ctx.Exec()
|
ctx.Exec()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func sectionNewHandler(ctx *Context) {
|
||||||
|
ctx.Template("sectionNewHandler")
|
||||||
|
section := ctx.Var("section")
|
||||||
|
ctx.Data = webData{
|
||||||
|
Title: section,
|
||||||
|
BodyTitle: section,
|
||||||
|
Admin: ctx.Admin(),
|
||||||
|
}
|
||||||
|
var err error
|
||||||
|
ctx.Data.Data, err = ctx.Srv.DB.GetAllSectionPages(section, ctx.User())
|
||||||
|
if err != nil {
|
||||||
|
ctx.NotFound()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ctx.Exec()
|
||||||
|
}
|
||||||
|
|
||||||
|
func sectionDelHandler(ctx *Context) {
|
||||||
|
ctx.Template("sectionDelHandler")
|
||||||
|
section := ctx.Var("section")
|
||||||
|
ctx.Data = webData{
|
||||||
|
Title: section,
|
||||||
|
BodyTitle: section,
|
||||||
|
Admin: ctx.Admin(),
|
||||||
|
}
|
||||||
|
var err error
|
||||||
|
ctx.Data.Data, err = ctx.Srv.DB.GetAllSectionPages(section, ctx.User())
|
||||||
|
if err != nil {
|
||||||
|
ctx.NotFound()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ctx.Exec()
|
||||||
|
}
|
||||||
|
|
||||||
|
func sectionEditHandler(ctx *Context) {
|
||||||
|
ctx.Template("sectionEditHandler")
|
||||||
|
section := ctx.Var("section")
|
||||||
|
ctx.Data = webData{
|
||||||
|
Title: section,
|
||||||
|
BodyTitle: section,
|
||||||
|
Admin: ctx.Admin(),
|
||||||
|
}
|
||||||
|
var err error
|
||||||
|
ctx.Data.Data, err = ctx.Srv.DB.GetAllSectionPages(section, ctx.User())
|
||||||
|
if err != nil {
|
||||||
|
ctx.NotFound()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ctx.Exec()
|
||||||
|
}
|
||||||
|
|
||||||
func pageNewHandler(ctx *Context) {
|
func pageNewHandler(ctx *Context) {
|
||||||
ctx.Template("pageNewHandler")
|
ctx.Template("pageNewHandler")
|
||||||
ctx.Data = webData{Title: "New Page"}
|
ctx.Data = webData{Title: "New Page"}
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,24 @@ var routes = []route{
|
||||||
sectionsHandler,
|
sectionsHandler,
|
||||||
[]string{"GET"},
|
[]string{"GET"},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"/sections/new",
|
||||||
|
adminAuthHandler(
|
||||||
|
sectionNewHandler),
|
||||||
|
[]string{"GET", "POST"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"/section/del/{section:[a-zA-Z0-9]+$}",
|
||||||
|
adminAuthHandler(
|
||||||
|
sectionDelHandler),
|
||||||
|
[]string{"GET", "POST"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"/section/edit/{section:[a-zA-Z0-9]+$}",
|
||||||
|
adminAuthHandler(
|
||||||
|
sectionEditHandler),
|
||||||
|
[]string{"GET", "POST"},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"/search",
|
"/search",
|
||||||
searchHandler,
|
searchHandler,
|
||||||
|
|
|
||||||
|
|
@ -459,6 +459,92 @@ const (
|
||||||
{{end}}
|
{{end}}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{{end}}`
|
||||||
|
sectionDel = `{{define "body"}}
|
||||||
|
{{if .Data}}
|
||||||
|
<div class="page-header">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-xs-4 offset-4">
|
||||||
|
<div class="card border-danger mx-auto">
|
||||||
|
<div class="card-header">
|
||||||
|
<strong>Delete {{.Data.Username}}?</strong>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<p>You are about to delete user <a href="/{{.Data.Username}}">{{.Data.Username}}</a>.<br>This will also remove all private pages of this user.</p>
|
||||||
|
<form class="form-horizontal" action="/user/del/{{.Data.Username}}" method="post">
|
||||||
|
<input type="hidden" name="token" value="{{.Token}}">
|
||||||
|
<button class="btn btn-sm btn-danger" type="submit">Delete</button>
|
||||||
|
<a href="/user" class="btn btn-sm btn-primary">Back</a>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
|
{{end}}`
|
||||||
|
sectionEdit = `{{define "body"}}
|
||||||
|
{{if .Data}}
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-xs-4 offset-4">
|
||||||
|
<div class="card border-primary mx-auto">
|
||||||
|
<div class="card-header">
|
||||||
|
<strong>{{.BodyTitle}}</strong>
|
||||||
|
{{if .Data.Secret}}
|
||||||
|
<a href="/user/totp/{{.Data.Username}}" class="btn btn-sm btn-danger float-right">Disable TOTP</a>
|
||||||
|
{{else}}
|
||||||
|
<a href="/user/totp/{{.Data.Username}}" class="btn btn-sm btn-success float-right">Enable TOTP</a>
|
||||||
|
{{end}}
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<form class="form-horizontal" action="/user/edit/{{.Data.Username}}" method="post">
|
||||||
|
<input type="hidden" name="token" value="{{.Token}}">
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="col-form-label" for="user">Username</label>
|
||||||
|
<input class="form-control input-sm" type="text" id="user" name="user" value="{{.Data.Username}}" disabled>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="col-form-label" for="password">Password</label>
|
||||||
|
<input class="form-control input-sm" type="password" id="password" name="password" autofocus>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="col-form-label" for="repeat">Repeat</label>
|
||||||
|
<input class="form-control input-sm" type="password" id="repeat" name="repeat">
|
||||||
|
</div>
|
||||||
|
{{if .Admin}}
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="col-form-label" for="admin">Admin Privileges</label>
|
||||||
|
{{if .Data.Admin}}
|
||||||
|
<input type="checkbox" id="admin" name="admin" value="0" checked>
|
||||||
|
{{else}}
|
||||||
|
<input type="checkbox" id="admin" name="admin" value="0">
|
||||||
|
{{end}}
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
|
<button class="btn btn-sm btn-primary" type="submit">Update</button>
|
||||||
|
{{if .Admin}}
|
||||||
|
<a href="/user" class="btn btn-sm btn-primary">Back</a>
|
||||||
|
{{else}}
|
||||||
|
<a href="/" class="btn btn-sm btn-primary">Back</a>
|
||||||
|
{{end}}
|
||||||
|
<a class="btn btn-sm btn-danger" href="/user/del/{{.Data.Username}}">Delete</a>
|
||||||
|
</form>
|
||||||
|
{{if .Admin}}
|
||||||
|
{{if eq .Data.Locked 3}}
|
||||||
|
<form class="form-horizontal" action="/user/unlock/{{.Data.Username}}" method="post">
|
||||||
|
<input type="hidden" name="token" value="{{.Token}}">
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="col-form-label" for="unlock">Locked</label>
|
||||||
|
<button class="btn btn-sm btn-warning" type="submit" id="unlock">Unlock</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
{{end}}
|
||||||
|
{{end}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
{{end}}`
|
{{end}}`
|
||||||
section = `{{define "body"}}
|
section = `{{define "body"}}
|
||||||
<div class="page-header">
|
<div class="page-header">
|
||||||
|
|
@ -475,6 +561,40 @@ const (
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
{{end}}`
|
||||||
|
sectionNew = `{{define "body"}}
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-xs-4 offset-4">
|
||||||
|
<div class="card border-primary mx-auto">
|
||||||
|
<div class="card-header">
|
||||||
|
<strong>{{.BodyTitle}}</strong>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<form class="form-horizontal" action="/user/new" method="post">
|
||||||
|
<input type="hidden" name="token" value="{{.Token}}">
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="col-form-label" for="user">Username</label>
|
||||||
|
<input class="form-control input-sm" type="text" id="user" name="user" autocomplete="off" autofocus required>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="col-form-label" for="password">Password</label>
|
||||||
|
<input class="form-control input-sm" type="password" id="password" name="password" required>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="col-form-label" for="repeat">Repeat</label>
|
||||||
|
<input class="form-control input-sm" type="password" id="repeat" name="repeat" required>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="col-form-label" for="admin">Admin Privileges</label>
|
||||||
|
<input type="checkbox" id="admin" name="admin" value="0">
|
||||||
|
</div>
|
||||||
|
<button class="btn btn-sm btn-primary" type="submit">Create</button>
|
||||||
|
<a href="/user" class="btn btn-sm btn-primary">Back</a>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
{{end}}`
|
{{end}}`
|
||||||
sections = `{{define "body"}}
|
sections = `{{define "body"}}
|
||||||
<div class="page-header">
|
<div class="page-header">
|
||||||
|
|
@ -910,8 +1030,6 @@ func (s *HTTPServer) loadTemplates() {
|
||||||
s.templ["loginHandler"] = parse(index, menu, login)
|
s.templ["loginHandler"] = parse(index, menu, login)
|
||||||
s.templ["loginTotpHandler"] = parse(index, menu, loginTotp)
|
s.templ["loginTotpHandler"] = parse(index, menu, loginTotp)
|
||||||
s.templ["searchHandler"] = parse(index, menu, search)
|
s.templ["searchHandler"] = parse(index, menu, search)
|
||||||
s.templ["sectionsHandler"] = parse(index, menu, sections)
|
|
||||||
s.templ["sectionHandler"] = parse(index, menu, section)
|
|
||||||
s.templ["pageHandler"] = parse(index, menu, page)
|
s.templ["pageHandler"] = parse(index, menu, page)
|
||||||
s.templ["pageNewHandler"] = parse(index, menu, pageNew)
|
s.templ["pageNewHandler"] = parse(index, menu, pageNew)
|
||||||
s.templ["pageEditHandler"] = parse(index, menu, pageEdit)
|
s.templ["pageEditHandler"] = parse(index, menu, pageEdit)
|
||||||
|
|
@ -920,6 +1038,12 @@ func (s *HTTPServer) loadTemplates() {
|
||||||
s.templ["pageShareHandler"] = parse(index, menu, pageShare)
|
s.templ["pageShareHandler"] = parse(index, menu, pageShare)
|
||||||
s.templ["pageBlacklistHandler"] = parse(index, menu, pageBlacklist)
|
s.templ["pageBlacklistHandler"] = parse(index, menu, pageBlacklist)
|
||||||
s.templ["pageDelHandler"] = parse(index, menu, pageDel)
|
s.templ["pageDelHandler"] = parse(index, menu, pageDel)
|
||||||
|
// sections
|
||||||
|
s.templ["sectionHandler"] = parse(index, menu, section)
|
||||||
|
s.templ["sectionsHandler"] = parse(index, menu, sections)
|
||||||
|
s.templ["sectionNewHandler"] = parse(index, menu, sectionNew)
|
||||||
|
s.templ["sectionEditHandler"] = parse(index, menu, sectionEdit)
|
||||||
|
s.templ["sectionDelHandler"] = parse(index, menu, sectionDel)
|
||||||
// users
|
// users
|
||||||
s.templ["userHandler"] = parse(index, menu, user)
|
s.templ["userHandler"] = parse(index, menu, user)
|
||||||
s.templ["userNewHandler"] = parse(index, menu, userNew)
|
s.templ["userNewHandler"] = parse(index, menu, userNew)
|
||||||
|
|
|
||||||
|
|
@ -83,8 +83,6 @@ func (s *HTTPServer) loadTemplates() {
|
||||||
s.templ["loginHandler"] = parse(index, menu, login)
|
s.templ["loginHandler"] = parse(index, menu, login)
|
||||||
s.templ["loginTotpHandler"] = parse(index, menu, loginTotp)
|
s.templ["loginTotpHandler"] = parse(index, menu, loginTotp)
|
||||||
s.templ["searchHandler"] = parse(index, menu, search)
|
s.templ["searchHandler"] = parse(index, menu, search)
|
||||||
s.templ["sectionsHandler"] = parse(index, menu, sections)
|
|
||||||
s.templ["sectionHandler"] = parse(index, menu, section)
|
|
||||||
s.templ["pageHandler"] = parse(index, menu, page)
|
s.templ["pageHandler"] = parse(index, menu, page)
|
||||||
s.templ["pageNewHandler"] = parse(index, menu, pageNew)
|
s.templ["pageNewHandler"] = parse(index, menu, pageNew)
|
||||||
s.templ["pageEditHandler"] = parse(index, menu, pageEdit)
|
s.templ["pageEditHandler"] = parse(index, menu, pageEdit)
|
||||||
|
|
@ -93,6 +91,12 @@ func (s *HTTPServer) loadTemplates() {
|
||||||
s.templ["pageShareHandler"] = parse(index, menu, pageShare)
|
s.templ["pageShareHandler"] = parse(index, menu, pageShare)
|
||||||
s.templ["pageBlacklistHandler"] = parse(index, menu, pageBlacklist)
|
s.templ["pageBlacklistHandler"] = parse(index, menu, pageBlacklist)
|
||||||
s.templ["pageDelHandler"] = parse(index, menu, pageDel)
|
s.templ["pageDelHandler"] = parse(index, menu, pageDel)
|
||||||
|
// sections
|
||||||
|
s.templ["sectionHandler"] = parse(index, menu, section)
|
||||||
|
s.templ["sectionsHandler"] = parse(index, menu, sections)
|
||||||
|
s.templ["sectionNewHandler"] = parse(index, menu, sectionNew)
|
||||||
|
s.templ["sectionEditHandler"] = parse(index, menu, sectionEdit)
|
||||||
|
s.templ["sectionDelHandler"] = parse(index, menu, sectionDel)
|
||||||
// users
|
// users
|
||||||
s.templ["userHandler"] = parse(index, menu, user)
|
s.templ["userHandler"] = parse(index, menu, user)
|
||||||
s.templ["userNewHandler"] = parse(index, menu, userNew)
|
s.templ["userNewHandler"] = parse(index, menu, userNew)
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,16 @@
|
||||||
{{define "body"}}
|
{{define "body"}}
|
||||||
<div class="page-header">
|
<div class="page-header">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<h2>{{.BodyTitle}}</h2>
|
<div class="col">
|
||||||
|
<h2>{{.BodyTitle}}</h2>
|
||||||
|
</div>
|
||||||
|
<div class="col">
|
||||||
|
{{if .Admin}}
|
||||||
|
<div class="btn-group float-right">
|
||||||
|
<a class="btn btn-sm btn-primary" href="/section/edit/{{.BodyTitle}}">Edit</a>
|
||||||
|
<a class="btn btn-sm btn-danger" href="/section/del/{{.BodyTitle}}">Delete</a>
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{{if .Data}}
|
{{if .Data}}
|
||||||
|
|
|
||||||
23
templates/sectionDel.html
Normal file
23
templates/sectionDel.html
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
{{define "body"}}
|
||||||
|
{{if .Data}}
|
||||||
|
<div class="page-header">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-xs-4 offset-4">
|
||||||
|
<div class="card border-danger mx-auto">
|
||||||
|
<div class="card-header">
|
||||||
|
<strong>Delete {{.Data}}?</strong>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<p>You are about to delete section <a href="/{{.Data}}">{{.Data}}</a>.<br>This will also remove all pages of this section.</p>
|
||||||
|
<form class="form-horizontal" action="/section/del/{{.Data}}" method="post">
|
||||||
|
<input type="hidden" name="token" value="{{.Token}}">
|
||||||
|
<button class="btn btn-sm btn-danger" type="submit">Delete</button>
|
||||||
|
<a href="/{{.Data}}" class="btn btn-sm btn-primary">Back</a>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
|
{{end}}
|
||||||
34
templates/sectionEdit.html
Normal file
34
templates/sectionEdit.html
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
{{define "body"}}
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-xs-4 offset-4">
|
||||||
|
<div class="card border-primary mx-auto">
|
||||||
|
<div class="card-header">
|
||||||
|
<strong>{{.BodyTitle}}</strong>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<form class="form-horizontal" action="/section/edit/{{.Data.Name}}" method="post">
|
||||||
|
<input type="hidden" name="token" value="{{.Token}}">
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="col-form-label" for="section">Section</label>
|
||||||
|
<input class="form-control input-sm" type="text" id="section" name="section" value="{{.Data.Name}}" disabled>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="col-form-label" for="members">Members</label>
|
||||||
|
<select class="form-control input-sm" style="height:120px" id="members" name="members" multiple required>
|
||||||
|
{{range $user, $member := .Data.Members}}
|
||||||
|
{{if $member}}
|
||||||
|
<option value="{{$user}}" selected>{{$user}}</option>
|
||||||
|
{{else}}
|
||||||
|
<option value="{{$user}}">{{$user}}</option>
|
||||||
|
{{end}}
|
||||||
|
{{end}}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<button class="btn btn-sm btn-primary" type="submit">Update</button>
|
||||||
|
<a href="/{{.Data.Name}}" class="btn btn-sm btn-primary">Back</a>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
30
templates/sectionNew.html
Normal file
30
templates/sectionNew.html
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
{{define "body"}}
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-xs-4 offset-4">
|
||||||
|
<div class="card border-primary mx-auto">
|
||||||
|
<div class="card-header">
|
||||||
|
<strong>{{.BodyTitle}}</strong>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<form class="form-horizontal" action="/section/new" method="post">
|
||||||
|
<input type="hidden" name="token" value="{{.Token}}">
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="col-form-label" for="section">Section</label>
|
||||||
|
<input class="form-control input-sm" type="text" id="section" name="section" autocomplete="off" value="{{.Data.Name}}" autofocus required>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="col-form-label" for="members">Members</label>
|
||||||
|
<select class="form-control input-sm" style="height:120px" id="members" name="members" multiple required>
|
||||||
|
{{range $nil, $user := .Data.Members}}
|
||||||
|
<option value="{{$user}}">{{$user}}</option>
|
||||||
|
{{end}}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<button class="btn btn-sm btn-primary" type="submit">Create</button>
|
||||||
|
<a href="/sections" class="btn btn-sm btn-primary">Back</a>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
|
|
@ -4,6 +4,13 @@
|
||||||
<h2>{{.BodyTitle}}</h2>
|
<h2>{{.BodyTitle}}</h2>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{{if .Admin}}
|
||||||
|
<div class="row mb-3">
|
||||||
|
<div class="col-xs-5">
|
||||||
|
<a class="btn btn-sm btn-primary" href="/section/new"><b>New Section</b></a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
{{if .Data}}
|
{{if .Data}}
|
||||||
<div class="row">
|
<div class="row">
|
||||||
{{range $index, $item := .Data}}
|
{{range $index, $item := .Data}}
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@
|
||||||
<strong>Delete {{.Data.Username}}?</strong>
|
<strong>Delete {{.Data.Username}}?</strong>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<p>You are about to delete user <a href="/{{.Data.Username}}">{{.Data.Username}}</a>.<br>This will also remove all private pages of this user.</p>
|
<p>You are about to delete user <a href="/{{.Data.Username}}">{{.Data.Username}}</a>.<br>This will also remove all pages of this user.</p>
|
||||||
<form class="form-horizontal" action="/user/del/{{.Data.Username}}" method="post">
|
<form class="form-horizontal" action="/user/del/{{.Data.Username}}" method="post">
|
||||||
<input type="hidden" name="token" value="{{.Token}}">
|
<input type="hidden" name="token" value="{{.Token}}">
|
||||||
<button class="btn btn-sm btn-danger" type="submit">Delete</button>
|
<button class="btn btn-sm btn-danger" type="submit">Delete</button>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue