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

49
pkg/server/cache.go Normal file
View file

@ -0,0 +1,49 @@
package main
import (
"sync"
"time"
)
type CachedArticle struct {
Article *Article
CacheTime time.Time
}
type IndexCache struct {
sync.RWMutex
Cache map[string]*CachedArticle
}
func NewIndexCache() *IndexCache {
return &IndexCache{Cache: make(map[string]*CachedArticle)}
}
func (ic IndexCache) Add(title string, a *Article) {
now := time.Now()
ic.Lock()
defer ic.Unlock()
c, ok := ic.Cache[title]
if ok && now.After(c.CacheTime) {
c.Article = a
c.CacheTime = now
ic.Cache[title] = c
return
}
ic.Cache[title] = &CachedArticle{a, now}
}
func (ic IndexCache) Get(title string) *Article {
ic.RLock()
defer ic.RUnlock()
if a, ok := ic.Cache[title]; ok {
return a.Article
}
return nil
}
func (ic *IndexCache) Delete(title string) {
ic.Lock()
delete(ic.Cache, title)
ic.Unlock()
}

250
pkg/server/context.go Normal file
View file

@ -0,0 +1,250 @@
// Copyright (C) 2017 Marius Schellenberger
package main
import (
"crypto/subtle"
"encoding/base64"
"errors"
"github.com/gorilla/mux"
"html/template"
"net/http"
"time"
)
func newContext(w http.ResponseWriter, r *http.Request, s *HTTPServer) *Context {
h := w.Header()
h.Set("X-Frame-Options", "DENY")
h.Set("X-Content-Type-Options", "nosniff")
h.Set("X-XSS-Protection", "1; mode=block")
h.Set("Content-Security-Policy", "default-src 'none';style-src 'self' 'unsafe-inline';frame-ancestors 'none'")
return &Context{
Request: r,
Response: w,
Srv: s,
Time: time.Now(),
}
}
type Context struct {
Request *http.Request
Response http.ResponseWriter
Srv *HTTPServer
T *template.Template
Status int
Err error
Time time.Time
Data webData
}
type webData struct {
Version string
Time int64
Title string
BodyTitle string
Admin bool
Login bool
User string
Token string
Msg string
Search string
Editor *Editor
Data interface{}
}
func (c *Context) Exec() {
defer c.log()
if c.T == nil {
c.Status = http.StatusInternalServerError
c.Err = errors.New("template is nil")
return
}
c.newToken()
c.Data.Version = c.Srv.Version
if c.Data.BodyTitle == "" {
c.Data.BodyTitle = c.Data.Title
}
c.Data.Admin = c.GetAdmin()
c.Data.User = c.GetUser()
c.Data.Login = c.LoggedOn()
c.Data.Time = time.Since(c.Time).Nanoseconds() / 1e6
c.Err = c.T.Execute(c.Response, c.Data)
if c.Err != nil {
c.Status = http.StatusInternalServerError
}
if c.Status == 0 {
c.Status = http.StatusOK
}
}
func (c *Context) log() {
if c.Err != nil {
log.Printf("%s %s %s %d error: %s\n", c.Request.RemoteAddr, c.Request.Method, c.Request.URL, c.Status, c.Err)
return
}
log.Printf("%s %s %s %d\n", c.Request.RemoteAddr, c.Request.Method, c.Request.URL, c.Status)
}
func (c *Context) NotFound() {
c.Template("notFoundHandler")
c.Data = webData{Title: "404"}
c.Status = http.StatusNotFound
c.Response.WriteHeader(http.StatusNotFound)
c.Exec()
}
func (c *Context) Template(name string) {
c.T = c.Srv.templ[name]
}
func (c *Context) Write(buf []byte) (err error) {
_, err = c.Response.Write(buf)
return
}
func (c *Context) SetHeader(name, value string) {
c.Response.Header().Set(name, value)
}
func (c *Context) Redirect(uri string, code int) {
log.Printf("%s %s %s %d %s\n", c.Request.RemoteAddr, c.Request.Method, c.Request.URL, code, uri)
http.Redirect(c.Response, c.Request, uri, code)
}
func (c *Context) Method() string {
return c.Request.Method
}
func (c *Context) Path() string {
return c.Request.URL.Path
}
func (c *Context) FormSlice(name string) []string {
// does nothing if called twice
c.Request.ParseForm()
return c.Request.PostForm[name]
}
func (c *Context) Form(name string) (ret string) {
ret = c.Request.PostFormValue(name)
return
}
func (c *Context) Var(name string) (ret string) {
ret, _ = mux.Vars(c.Request)[name]
return
}
func (c *Context) initToken() {
token := c.getToken()
if token == nil || len(token) != keyLen {
s, _ := c.Srv.Store.Get(c.Request, cookieName)
s.Values["token"] = genKey(keyLen)
s.Options = c.Srv.Store.Options
err := s.Save(c.Request, c.Response)
if err != nil {
log.Println("InitToken: ", err)
}
}
}
func (c *Context) newToken() {
c.initToken()
rnd := genKey(keyLen)
c.Data.Token = base64.StdEncoding.EncodeToString(append(rnd, xor(rnd, c.getToken())...))
}
// CheckToken validates the xsrf token
func (c *Context) CheckToken(f func()) bool {
token, err := base64.StdEncoding.DecodeString(c.Form("token"))
if err != nil {
log.Println("CheckToken: ", err)
return false
}
if len(token) != authLen {
return false
}
ok := subtle.ConstantTimeCompare(c.getToken(), xor(token[keyLen:], token[:keyLen])) == 1
if !ok {
if f != nil {
f()
return ok
}
c.Data.Msg = "wrong csrf token"
c.Exec()
}
return ok
}
func (c *Context) LoggedOn() (ok bool) {
s, _ := c.Srv.Store.Get(c.Request, cookieName)
_, ok = s.Values["user"]
return
}
func (c *Context) SetUdata(user string, admin bool) {
s, _ := c.Srv.Store.Get(c.Request, cookieName)
s.Values["user"] = user
s.Values["admin"] = admin
s.Options = c.Srv.Store.Options
if err := s.Save(c.Request, c.Response); err != nil {
log.Println(err)
}
}
func (c *Context) UnsetUdata() {
s, _ := c.Srv.Store.Get(c.Request, cookieName)
s.Values = make(map[interface{}]interface{})
s.Options = c.Srv.Store.Options
if err := s.Save(c.Request, c.Response); err != nil {
log.Println(err)
}
}
func (c *Context) GetUser() string {
s, _ := c.Srv.Store.Get(c.Request, cookieName)
if ret, ok := s.Values["user"].(string); ok {
return ret
}
return ""
}
func (c *Context) GetAdmin() bool {
s, _ := c.Srv.Store.Get(c.Request, cookieName)
if ret, ok := s.Values["admin"].(bool); ok {
return ret
}
return false
}
func (c *Context) getToken() []byte {
s, _ := c.Srv.Store.Get(c.Request, cookieName)
t, ok := s.Values["token"]
if !ok {
return nil
}
b, ok := t.([]byte)
if !ok {
return nil
}
return b
}
func xor(a, b []byte) (c []byte) {
n := len(a)
if len(b) != n {
return
}
c = make([]byte, n)
for i := 0; i < n; i++ {
c[i] = a[i] ^ b[i]
}
return
}
type ctxHandler func(*Context)

43
pkg/server/editor.go Normal file
View file

@ -0,0 +1,43 @@
package main
import "sync"
type Editor struct {
Username string
Time string
}
type EditorStore struct {
sync.RWMutex
m map[string]*Editor
}
func NewEditorStore() *EditorStore {
return &EditorStore{m: make(map[string]*Editor)}
}
func (e *EditorStore) Set(page, username string) *Editor {
e.Lock()
defer e.Unlock()
editor := e.m[page]
if editor != nil {
return editor
}
e.m[page] = &Editor{username, now()}
return nil
}
func (e *EditorStore) RemoveEditor(page, username string) {
e.Lock()
defer e.Unlock()
if editor, ok := e.m[page]; ok {
if editor.Username == username {
delete(e.m, page)
}
}
}
func (e *EditorStore) Remove(page string) {
e.Lock()
defer e.Unlock()
delete(e.m, page)
}

447
pkg/server/handler.go Normal file
View file

@ -0,0 +1,447 @@
// Copyright (C) 2017 Marius Schellenberger
package main
import (
"html/template"
"net/http"
"time"
)
type notFoundHandler struct {
s *HTTPServer
}
func (nf *notFoundHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
newContext(w, r, nf.s).NotFound()
}
func adminAuthHandler(h ctxHandler) ctxHandler {
return func(ctx *Context) {
if ctx.GetAdmin() {
h(ctx)
return
}
ctx.NotFound()
}
}
func userAuthHandler(h ctxHandler) ctxHandler {
return func(ctx *Context) {
if ctx.GetAdmin() {
h(ctx)
return
}
if ctx.GetUser() == ctx.Var("user") {
h(ctx)
return
}
ctx.NotFound()
}
}
func authHandler(h ctxHandler) ctxHandler {
return func(ctx *Context) {
if ctx.LoggedOn() {
h(ctx)
return
}
ctx.NotFound()
}
}
func staticHandler(ctx *Context) {
var err error
ctx.SetHeader("Content-Type", "text/css; charset=utf-8")
ctx.SetHeader("Expires", time.Now().Add(time.Hour*12).Format(http.TimeFormat))
switch ctx.Path() {
case "/bootstrap.css":
err = ctx.Write(ctx.Srv.res["bootstrap.css"])
case "/custom.css":
err = ctx.Write(ctx.Srv.res["custom.css"])
}
if err != nil {
log.Println("static:", err)
}
}
func indexHandler(ctx *Context) {
if !ctx.LoggedOn() {
ctx.UnsetUdata()
}
ctx.Redirect("/wiki/Index", 302)
}
func loginHandler(ctx *Context) {
if ctx.LoggedOn() {
ctx.Redirect("/wiki/Index", 302)
return
}
ctx.Template("loginHandler")
ctx.Data = webData{
Title: "Login",
BodyTitle: "Login",
}
switch ctx.Method() {
case "GET":
ctx.Exec()
case "POST":
user := ctx.Form("user")
password := ctx.Form("password")
submit := ctx.Form("submit")
if !ctx.CheckToken(nil) {
return
}
if user == "" || password == "" || submit != "Login" {
ctx.Data.Msg = "wrong inputs"
ctx.Exec()
return
}
err := ctx.Srv.BS.Login(user, password)
if err != nil {
ctx.Data.Msg = err.Error()
ctx.Exec()
return
}
ctx.SetUdata(user, ctx.Srv.BS.UserIsAdmin(user))
ctx.Redirect("/wiki/Index", 302)
}
}
func searchHandler(ctx *Context) {
ctx.Template("searchHandler")
ctx.Data = webData{
Title: "Search",
BodyTitle: "Search",
}
switch ctx.Method() {
case "GET":
ctx.Exec()
case "POST":
search := ctx.Form("search")
submit := ctx.Form("submit")
if !ctx.CheckToken(nil) {
return
}
if submit != "Search" {
ctx.Data.Msg = "wrong inputs"
ctx.Exec()
return
}
res, err := ctx.Srv.Index.Search(!ctx.LoggedOn(), search)
if err != nil {
ctx.Data.Msg = err.Error()
ctx.Exec()
return
}
ctx.Data.Search = search
ctx.Data.Data = template.HTML(res)
ctx.Exec()
}
}
func allHandler(ctx *Context) {
ctx.Template("allHandler")
ctx.Data = webData{
Title: "All Articles",
BodyTitle: "All Articles",
Admin: ctx.GetAdmin(),
}
all, err := ctx.Srv.Index.GetAll(!ctx.LoggedOn())
if err != nil {
ctx.Data.Msg = err.Error()
}
ctx.Data.Data = all
ctx.Exec()
}
func wikiNewHandler(ctx *Context) {
ctx.Template("wikiNewHandler")
ctx.Data = webData{Title: "New Article"}
switch ctx.Method() {
case "GET":
ctx.Exec()
case "POST":
title := ctx.Form("title")
text := ctx.Form("text")
public := ctx.Form("public")
pub := false
if public == "0" {
pub = true
}
submit := ctx.Form("submit")
art := Article{
Title: title,
MD: text,
}
ctx.Data.Data = art
if !ctx.CheckToken(nil) {
return
}
if submit != "Submit" {
ctx.Data.Msg = "wrong inputs"
ctx.Exec()
return
}
newTitle, err := ctx.Srv.Index.Index(title, text, ctx.GetUser(), pub)
if err != nil {
ctx.Data.Msg = err.Error()
ctx.Exec()
return
}
ctx.Redirect("/wiki/"+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(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)
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")
f := func() {
ctx.Redirect("/wiki/"+title, 302)
}
if !ctx.CheckToken(f) {
return
}
if submit != "Delete" {
f()
return
}
if err := ctx.Srv.Index.Delete(title); err != nil {
log.Println("delete:", err)
f()
}
ctx.Redirect("/wiki/Index", 302)
}
func usersHandler(ctx *Context) {
if !ctx.LoggedOn() {
ctx.NotFound()
return
}
ctx.Template("usersHandler")
ctx.Data = webData{
Title: "Users",
BodyTitle: "Users",
User: ctx.GetUser(),
}
us, err := ctx.Srv.BS.GetUsers()
if err != nil {
ctx.Data.Msg = err.Error()
}
ctx.Data.Data = us
ctx.Exec()
}
func userNewHandler(ctx *Context) {
ctx.Template("userNewHandler")
ctx.Data = webData{
Title: "New User",
BodyTitle: "New User",
}
switch ctx.Method() {
case "GET":
ctx.Exec()
case "POST":
user := ctx.Form("user")
password := ctx.Form("password")
repeat := ctx.Form("repeat")
admin := ctx.Form("admin")
adm := false
if admin == "0" {
adm = true
}
submit := ctx.Form("submit")
if !ctx.CheckToken(nil) {
return
}
if user == "" || password == "" || password != repeat || submit != "Submit" {
ctx.Data.Msg = "wrong inputs"
ctx.Exec()
return
}
if err := ctx.Srv.BS.CreateUser(user, password, adm); err != nil {
ctx.Data.Msg = err.Error()
ctx.Exec()
return
}
ctx.Redirect("/users", 302)
}
}
func userEditHandler(ctx *Context) {
ctx.Template("userEditHandler")
ctx.Data = webData{
Title: "Edit User",
BodyTitle: "Edit User",
}
switch ctx.Method() {
case "GET":
u, err := ctx.Srv.BS.GetUserWithoutPassword(ctx.Var("user"))
if err != nil {
ctx.Data.Msg = err.Error()
ctx.Exec()
return
}
ctx.Data.Data = u
ctx.Exec()
case "POST":
user := ctx.Var("user")
password := ctx.Form("password")
repeat := ctx.Form("repeat")
admin := ctx.Form("admin")
adm := false
if admin == "0" {
adm = true
}
submit := ctx.Form("submit")
if !ctx.CheckToken(nil) {
return
}
if user == "" || password != repeat || submit != "Submit" {
ctx.Data.Msg = "wrong inputs"
ctx.Exec()
return
}
if ctx.GetAdmin() {
if err := ctx.Srv.BS.AdminUpdateUser(user, password, adm); err != nil {
ctx.Data.Msg = err.Error()
ctx.Exec()
return
}
} else {
if user == ctx.GetUser() {
if err := ctx.Srv.BS.UpdateUserPassword(user, password); err != nil {
ctx.Data.Msg = err.Error()
ctx.Exec()
return
}
ctx.Redirect("/wiki/Index", 302)
return
}
}
ctx.Redirect("/users", 302)
}
}
func userUnlockHandler(ctx *Context) {
user := ctx.Var("user")
if err := ctx.Srv.BS.UnlockUser(user); err != nil {
log.Println("unlock:", err)
}
ctx.Redirect("/users", 302)
}
func userDelHandler(ctx *Context) {
ctx.Template("userDelHandler")
ctx.Data = webData{
Title: "Delete User",
BodyTitle: "Delete User",
}
switch ctx.Method() {
case "GET":
user, err := ctx.Srv.BS.GetUserWithoutPassword(ctx.Var("user"))
if err != nil {
ctx.Data.Msg = err.Error()
ctx.Exec()
return
}
ctx.Data.Data = user
ctx.Exec()
case "POST":
if err := ctx.Srv.BS.DeleteUser(ctx.Var("user")); err != nil {
log.Println("delete:", err)
}
ctx.Redirect("/users", 302)
}
}
func logoutHandler(ctx *Context) {
ctx.UnsetUdata()
ctx.Redirect("/wiki/Index", 302)
}

127
pkg/server/routes.go Normal file
View file

@ -0,0 +1,127 @@
// Copyright (C) 2017 Marius Schellenberger
package main
type route struct {
Path string
Handler ctxHandler
Methods []string
}
var routes = []route{
{
"/",
indexHandler,
[]string{"GET"},
},
{
"/bootstrap.css",
staticHandler,
[]string{"GET"},
},
{
"/custom.css",
staticHandler,
[]string{"GET"},
},
{
"/login",
loginHandler,
[]string{"GET", "POST"},
},
{
"/search",
searchHandler,
[]string{"GET", "POST"},
},
{
"/new",
authHandler(
wikiNewHandler),
[]string{"GET", "POST"},
},
{
"/all",
allHandler,
[]string{"GET"},
},
{
"/wiki/{title:[a-zA-Z0-9+-]+$}",
wikiHandler,
[]string{"GET"},
},
{
"/wiki/{title:[a-zA-Z0-9+-]+}/md",
wikiMDHandler(
wikiHandler),
[]string{"GET"},
},
{
"/wiki/{title:[a-zA-Z0-9+-]+}/edit",
authHandler(
wikiEditHandler),
[]string{"GET", "POST"},
},
{
"/wiki/{title:[a-zA-Z0-9+-]+}/del",
authHandler(
wikiDelHandler),
[]string{"POST"},
},
{
"/users",
adminAuthHandler(
usersHandler),
[]string{"GET"},
},
{
"/users/{user:[a-zA-Z0-9]+}/{title:[a-zA-Z0-9+-]+$}",
wikiHandler,
[]string{"GET"},
},
{
"/users/{user:[a-zA-Z0-9]+}/{title:[a-zA-Z0-9+-]+}/md",
wikiMDHandler(
wikiHandler),
[]string{"GET"},
},
{
"/users/{user:[a-zA-Z0-9]+}/{title:[a-zA-Z0-9+-]+}/edit",
wikiEditHandler,
[]string{"GET", "POST"},
},
{
"/users/{user:[a-zA-Z0-9]+}/{title:[a-zA-Z0-9+-]+}/del",
wikiDelHandler,
[]string{"POST"},
},
{
"/user/new",
adminAuthHandler(
userNewHandler),
[]string{"GET", "POST"},
},
{
"/user/del/{user:[a-zA-Z0-9]+$}",
adminAuthHandler(
userDelHandler),
[]string{"GET", "POST"},
},
{
"/user/unlock/{user:[a-zA-Z0-9]+$}",
adminAuthHandler(
userUnlockHandler),
[]string{"POST"},
},
{
"/user/edit/{user:[a-zA-Z0-9]+$}",
userAuthHandler(
userEditHandler),
[]string{"GET", "POST"},
},
{
"/logout",
logoutHandler,
[]string{"GET", "POST"},
},
}

118
pkg/server/server.go Normal file
View file

@ -0,0 +1,118 @@
package main
import (
"context"
"crypto/rand"
"github.com/gorilla/mux"
"github.com/gorilla/sessions"
"html/template"
"io"
"net"
"net/http"
"time"
)
const (
keyLen = 32
authLen = keyLen * 2
cookieName = "gosession"
)
type HTTPServer struct {
Version string
SecCookie bool
Store *sessions.CookieStore
Editors *EditorStore
BS *BoltStore
Index *Index
listener net.Listener
srv *http.Server
templ map[string]*template.Template
res map[string][]byte
}
func NewHTTPServer(l net.Listener, versin string, secCookie bool) (srv *HTTPServer) {
srv = &HTTPServer{
Version: version,
SecCookie: secCookie,
listener: l,
}
srv.Editors = NewEditorStore()
srv.cookieStore()
srv.loadTemplates()
srv.srv = &http.Server{
Handler: srv.buildRoutes(),
}
return
}
func (s *HTTPServer) Start() (err error) {
s.BS, err = NewBoltStore()
if err != nil {
return
}
s.Index, err = NewIndex(blevePath)
if err != nil {
return
}
go func() {
log.Println(s.srv.Serve(s.listener))
}()
return
}
func (s *HTTPServer) Stop() error {
s.BS.Close()
s.Index.Close()
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
defer cancel()
return s.srv.Shutdown(ctx)
}
func (s *HTTPServer) buildRoutes() http.Handler {
m := mux.NewRouter()
m.NotFoundHandler = &notFoundHandler{s}
for _, v := range routes {
m.HandleFunc(v.Path, s.contextWrapper(v.Handler)).Methods(v.Methods...)
}
return m
}
func (s *HTTPServer) contextWrapper(h ctxHandler) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
h(newContext(w, r, s))
}
}
func (s *HTTPServer) cookieStore() {
options := &sessions.Options{
Path: "/",
MaxAge: 3600 * 12,
Secure: s.SecCookie,
HttpOnly: true,
}
authKey := genKey(authLen)
encKey := genKey(keyLen)
s.Store = sessions.NewCookieStore(authKey, encKey)
s.Store.Options = options
go func() {
for {
time.Sleep(time.Hour * 12)
newAuthKey := genKey(authLen)
newEncKey := genKey(keyLen)
newStore := sessions.NewCookieStore(newAuthKey, newEncKey, authKey, encKey)
newStore.Options = options
s.Store = newStore
authKey = newAuthKey
encKey = newEncKey
}
}()
}
func genKey(length int) (bytes []byte) {
bytes = make([]byte, length)
if _, err := io.ReadFull(rand.Reader, bytes); err != nil {
log.Println("genKey:", err)
}
return
}

View file

@ -0,0 +1,155 @@
package main
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)
}

693
pkg/server/templates.go Normal file

File diff suppressed because one or more lines are too long