GoWiki/main.go

549 lines
14 KiB
Go
Raw Permalink Normal View History

2019-12-13 08:57:01 +01:00
package main
import (
"bufio"
2020-01-20 15:56:39 +01:00
"crypto/subtle"
2019-12-13 08:57:01 +01:00
"encoding/json"
"fmt"
"html/template"
"io/ioutil"
"log"
"net/http"
"os"
"path"
"path/filepath"
"strings"
2019-12-13 12:30:55 +01:00
"time"
2019-12-13 08:57:01 +01:00
"github.com/blevesearch/bleve"
"github.com/blevesearch/bleve/search/highlight/highlighter/html"
2019-12-13 08:57:01 +01:00
"github.com/gorilla/mux"
"github.com/kardianos/service"
2019-12-13 17:19:13 +01:00
"github.com/mandolyte/mdtopdf"
2019-12-13 12:30:55 +01:00
"gopkg.in/src-d/go-git.v4"
"gopkg.in/src-d/go-git.v4/plumbing/object"
2019-12-13 08:57:01 +01:00
)
//-------------------------------------------------------------------------------------------------
// Service Configuration
var logger service.Logger
type program struct{}
func (p *program) Start(s service.Service) error {
// Start should not block. Do the actual work async.
go p.run()
return nil
}
func (p *program) run() {
wikimain()
}
func (p *program) Stop(s service.Service) error {
// Stop should not block. Return with a few seconds.
return nil
}
func main() {
svcConfig := &service.Config{
Name: "GoWiki",
DisplayName: "GoWiki",
Description: "GoWiki Service",
}
prg := &program{}
s, err := service.New(prg, svcConfig)
if err != nil {
log.Fatal(err)
}
if len(os.Args) > 1 {
err = service.Control(s, os.Args[1])
if err != nil {
log.Fatal(err)
}
return
}
logger, err = s.Logger(nil)
if err != nil {
log.Fatal(err)
}
err = s.Run()
if err != nil {
logger.Error(err)
}
}
//-------------------------------------------------------------------------------------------------
var config *Configuration
2019-12-13 08:57:01 +01:00
type Configuration struct {
Host string
Port string
DataPath string
DataPathFTS string
2020-01-20 15:56:39 +01:00
WebUser string
WebPasswd string
2019-12-13 08:57:01 +01:00
}
// our main function
func wikimain() {
// Startverzeichnis auslesen und als Arbeitsverzeichnis setzen
dir, _ := filepath.Abs(filepath.Dir(os.Args[0]))
os.Chdir(dir)
config = readConfig(path.Join(dir, "config.json"))
2019-12-13 08:57:01 +01:00
// Ausgeben der Config-Optionen
fmt.Println("Host: " + config.Host)
fmt.Println("Post: " + config.Port)
fmt.Println("DataPath: " + config.DataPath)
fmt.Println("DataPathFTS: " + config.DataPathFTS)
2019-12-13 08:57:01 +01:00
// DataPath-Verzeichnis anlegen, wenn es noch nicht existiert
if !directoryExists(config.DataPath) {
os.MkdirAll(config.DataPath, os.ModePerm)
// Default Seiten anlegen
bStartmd, _ := Asset("web/pages/start.md")
bHilfemd, _ := Asset("web/pages/hilfe.md")
bPlaygroundmd, _ := Asset("web/pages/playground.md")
bSidebarmd, _ := Asset("web/pages/sidebar.md")
err := ioutil.WriteFile(path.Join(config.DataPath, "start.md"), bStartmd, 0644)
check(err)
err = ioutil.WriteFile(path.Join(config.DataPath, "sidebar.md"), bSidebarmd, 0644)
check(err)
err = ioutil.WriteFile(path.Join(config.DataPath, "hilfe.md"), bHilfemd, 0644)
check(err)
err = ioutil.WriteFile(path.Join(config.DataPath, "playground.md"), bPlaygroundmd, 0644)
check(err)
}
2019-12-13 12:30:55 +01:00
// Git Repository Init
if !directoryExists(path.Join(config.DataPath, ".git")) {
_, err := git.PlainInit(config.DataPath, false)
check(err)
GitCommit("Initial Wiki Commit")
}
// Volltextsuche
if config.DataPathFTS != "" {
if strings.HasPrefix(config.DataPathFTS, config.DataPath) {
fmt.Println("FTS disabled (Please don´t put the DataPathFTS inside the DataPath)")
} else {
fmt.Println("FTS enabled")
if !directoryExists(config.DataPathFTS) {
fmt.Println("Create new FTS Data in " + config.DataPathFTS)
mapping := bleve.NewIndexMapping()
index, err := bleve.New(config.DataPathFTS, mapping)
check(err)
filepath.Walk(config.DataPath, func(filepath string, info os.FileInfo, err error) error {
tmpPath := strings.Replace(filepath, "\\", "/", -1)
tmpPath = strings.Replace(tmpPath, "data/", "", -1)
if !info.IsDir() && !strings.HasPrefix(tmpPath, ".git") {
b, err := ioutil.ReadFile(path.Join(config.DataPath, tmpPath))
check(err)
indexData := struct {
Text string
}{
string(b),
}
err = index.Index(tmpPath, indexData)
check(err)
fmt.Println("Indexed: " + info.Name())
}
return nil
})
index.Close()
}
}
}
/*
repo, err := git.PlainOpen(config.DataPath)
check(err)
ref, err := repo.Head()
check(err)
// ... retrieves the commit history
//since := time.Date(2019, 1, 1, 0, 0, 0, 0, time.UTC)
//until := time.Date(2019, 7, 30, 0, 0, 0, 0, time.UTC)
var filename = "playground.md"
cIter, err := repo.Log(&git.LogOptions{From: ref.Hash(), FileName: &filename}) //, Since: &since, Until: &until})
check(err)
// ... just iterates over the commits, printing it
err = cIter.ForEach(func(c *object.Commit) error {
fmt.Println(c.Files())
fmt.Println(c.Committer.When)
return nil
})
*/
2019-12-13 08:57:01 +01:00
router := mux.NewRouter()
2020-01-20 15:56:39 +01:00
router.HandleFunc("/_api/md/{pagename:.*}", basicAuth(getRawPage)).Methods("GET")
router.HandleFunc("/_api/pdf/{pagename:.*}", basicAuth(getPDFPage)).Methods("GET")
router.HandleFunc("/_api/pinfo/{pagename:.*}", basicAuth(getPageInfo)).Methods("GET")
router.HandleFunc("/_api/fts/{searchterm:.*}", basicAuth(getFTS)).Methods("GET")
router.HandleFunc("/_api/config", basicAuth(getWikiConfig)).Methods("GET")
2020-01-20 15:56:39 +01:00
router.HandleFunc("/{pagename:.*}", basicAuth(getHTMLPage)).Methods("GET")
router.HandleFunc("/{pagename:.*}", basicAuth(postHTMLPage)).Methods("POST")
2019-12-13 08:57:01 +01:00
log.Fatal(http.ListenAndServe(":"+config.Port, router))
}
2019-12-13 17:19:13 +01:00
func getPDFPage(w http.ResponseWriter, r *http.Request) {
params := mux.Vars(r)
pPageName := params["pagename"] + ".md"
// Hack für anzeige der start.md wenn / aufgerufen wird
if pPageName == ".md" {
pPageName = "start.md"
}
content, err := ioutil.ReadFile(path.Join(config.DataPath, pPageName))
check(err)
pdfRender := mdtopdf.NewPdfRenderer("", "", "./tmp.pdf", "")
// get the unicode translator
tr := pdfRender.Pdf.UnicodeTranslatorFromDescriptor("cp1252")
pdfRender.Normal = mdtopdf.Styler{Font: "Arial", Style: "",
Size: 8, Spacing: 1,
TextColor: mdtopdf.Color{0, 0, 0},
FillColor: mdtopdf.Color{255, 255, 255}}
err = pdfRender.Process([]byte(tr(string(content))))
check(err)
pdfcontent, err := ioutil.ReadFile("./tmp.pdf")
check(err)
w.Header().Set("Content-Type", "application/pdf")
w.Write(pdfcontent)
}
2019-12-13 08:57:01 +01:00
func getHTMLPage(w http.ResponseWriter, r *http.Request) {
params := mux.Vars(r)
if strings.HasSuffix(params["pagename"], ".css") {
//fmt.Println("CSS: " + params["pagename"])
b, _ := Asset("web/" + params["pagename"])
w.Header().Set("Content-Type", "text/css")
w.Write(b)
} else if strings.HasSuffix(params["pagename"], ".js") {
//fmt.Println("JS: " + params["pagename"])
b, _ := Asset("web/" + params["pagename"])
w.Header().Set("Content-Type", "application/javascript")
w.Write(b)
} else {
// Directory Listing für die Sidebar
sidebarAusgabe := " \r\n \r\n**Inhaltsverzeichnis** \r\n\r\n"
filepath.Walk(config.DataPath, func(path string, info os.FileInfo, err error) error {
tmpPath := strings.Replace(path, "\\", "/", -1)
tmpPath = strings.Replace(tmpPath, "data", "", -1)
tmpPathSplit := strings.Split(tmpPath, "/")
sidebarTABs := ""
for i, _ := range tmpPathSplit {
//fmt.Println(i, " => ", string(c))
if i > 1 {
sidebarTABs = sidebarTABs + " "
}
}
2019-12-13 12:30:55 +01:00
if info.Name() != "data" && !strings.HasPrefix(tmpPath, "/.git") && info.Name() != "start.md" && info.Name() != "sidebar.md" && info.Name() != "hilfe.md" {
2019-12-13 08:57:01 +01:00
if !strings.HasSuffix(info.Name(), ".md") {
sidebarAusgabe = sidebarAusgabe + sidebarTABs + "* [" + strings.TrimSuffix(info.Name(), ".md") + "](" + strings.TrimSuffix(tmpPath, ".md") + "/start) \r\n"
} else {
sidebarAusgabe = sidebarAusgabe + sidebarTABs + "* [" + strings.TrimSuffix(info.Name(), ".md") + "](" + strings.TrimSuffix(tmpPath, ".md") + ") \r\n"
}
}
return nil
})
//tmpl := template.Must(template.ParseFiles("./web/index.html"))
bTmpl, _ := Asset("web/index.html")
tmpl := template.Must(template.New("tml").Parse(string(bTmpl)))
data := MarkDownData{
PageTitle: " ",
MDText: "",
SideBar: sidebarAusgabe,
}
tmpl.Execute(w, data)
}
}
func getRawPage(w http.ResponseWriter, r *http.Request) {
params := mux.Vars(r)
pPageName := params["pagename"] + ".md"
// Hack für anzeige der start.md wenn / aufgerufen wird
if pPageName == ".md" {
pPageName = "start.md"
}
pageContent := "Empty"
//fmt.Println(path.Join(config.DataPath, pPageName))
b, err := ioutil.ReadFile(path.Join(config.DataPath, pPageName))
check(err)
pageContent = string(b)
json.NewEncoder(w).Encode(pageContent)
}
func postHTMLPage(w http.ResponseWriter, r *http.Request) {
params := mux.Vars(r)
pPageName := params["pagename"] + ".md"
// Hack für anzeige der start.md wenn / aufgerufen wird
if pPageName == ".md" {
pPageName = "start.md"
}
2019-12-13 08:57:01 +01:00
// Verzeichnis erstellen
dir := path.Dir(pPageName)
if !directoryExists(path.Join(config.DataPath, dir)) {
os.MkdirAll(path.Join(config.DataPath, dir), os.ModePerm)
}
//{ "MDText":"**test3**" }
var oBody TPostBody
json.NewDecoder(r.Body).Decode(&oBody)
//fmt.Println(oBody)
f, err := os.Create(path.Join(config.DataPath, pPageName))
check(err)
defer f.Close()
fw := bufio.NewWriter(f)
_, err = fw.WriteString(oBody.MDText)
check(err)
fw.Flush()
2019-12-13 12:30:55 +01:00
// Git Commit
GitCommit("Auto-Commit Page: " + pPageName)
// FTS Index
BleveIndex(pPageName)
2019-12-13 12:30:55 +01:00
2019-12-13 08:57:01 +01:00
json.NewEncoder(w).Encode("OK")
}
func getFTS(w http.ResponseWriter, r *http.Request) {
params := mux.Vars(r)
pSearchterm := params["searchterm"]
if config.DataPathFTS != "" {
if directoryExists(config.DataPathFTS) {
index, err := bleve.Open(config.DataPathFTS)
check(err)
query := bleve.NewQueryStringQuery(pSearchterm)
search := bleve.NewSearchRequest(query)
search.Highlight = bleve.NewHighlightWithStyle(html.Name)
search.Size = 30
searchResults, err := index.Search(search)
index.Close()
//fmt.Println("SR: " + searchResults.String())
json.NewEncoder(w).Encode(searchResults.Hits)
}
}
}
func getPageInfo(w http.ResponseWriter, r *http.Request) {
params := mux.Vars(r)
pPageName := params["pagename"] + ".md"
// Hack für anzeige der start.md wenn / aufgerufen wird
if pPageName == ".md" {
pPageName = "start.md"
}
f, err := os.Open(path.Join(config.DataPath, pPageName))
check(err)
statinfo, err := f.Stat()
check(err)
data := struct {
PageName string
ModTime time.Time
}{
pPageName,
statinfo.ModTime(),
}
json.NewEncoder(w).Encode(data)
}
func getWikiConfig(w http.ResponseWriter, r *http.Request) {
FTS := false
Login := false
if config.DataPathFTS != "" {
FTS = true
}
if config.WebUser != "" && config.WebPasswd != "" {
Login = true
}
data := struct {
FTS bool
Login bool
}{
FTS,
Login,
}
json.NewEncoder(w).Encode(data)
}
2020-01-20 15:56:39 +01:00
func basicAuth(handler http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
if config.WebUser == "" && config.WebPasswd == "" {
handler(w, r)
return
}
user, pass, ok := r.BasicAuth()
if !ok || subtle.ConstantTimeCompare([]byte(user), []byte(config.WebUser)) != 1 || subtle.ConstantTimeCompare([]byte(pass), []byte(config.WebPasswd)) != 1 {
w.Header().Set("WWW-Authenticate", `Basic realm="Login GoWiki"`)
w.WriteHeader(401)
w.Write([]byte("Unauthorised.\n"))
return
}
handler(w, r)
}
}
2019-12-13 08:57:01 +01:00
//--------------------------------------------------------------------------
// Typen
//--------------------------------------------------------------------------
type MarkDownData struct {
PageTitle string
MDText string
SideBar string
}
type TPostBody struct {
MDText string
}
//--------------------------------------------------------------------------
// Hilfsfunktionen
//--------------------------------------------------------------------------
func check(e error) {
if e != nil {
fmt.Println(e)
}
}
func fileExists(filename string) bool {
info, err := os.Stat(filename)
if os.IsNotExist(err) {
return false
}
return !info.IsDir()
}
func directoryExists(filename string) bool {
info, err := os.Stat(filename)
if os.IsNotExist(err) {
return false
}
return info.IsDir()
}
func readConfig(filename string) *Configuration {
// initialize conf with default values.
2020-01-20 15:56:39 +01:00
conf := &Configuration{Host: "http://127.0.0.1", Port: "8000", DataPath: "./data", DataPathFTS: "", WebUser: "", WebPasswd: ""}
2019-12-13 08:57:01 +01:00
b, err := ioutil.ReadFile(filename)
2019-12-13 08:57:01 +01:00
if err != nil {
return conf
}
if err = json.Unmarshal(b, conf); err != nil {
return conf
}
return conf
}
2019-12-13 12:30:55 +01:00
func GitCommit(CommitText string) {
// Git Repo öffnen
repo, err := git.PlainOpen(config.DataPath)
check(err)
// Working Tree setzen
wt, err := repo.Worktree()
check(err)
// Alle Dateien zum Working Tree hinzufügen
_, err = wt.Add(".")
check(err)
// Status ausgeben
//status, err := wt.Status()
//check(err)
//fmt.Println(status)
// Neuen Commit Erstellen
_, err = wt.Commit(CommitText, &git.CommitOptions{
Author: &object.Signature{
Name: "GoWiki",
Email: "gowiki@local.org",
When: time.Now(),
},
})
check(err)
// Commit infos anzeigen
//obj, err := repo.CommitObject(commit)
//check(err)
//fmt.Println(obj)
}
func BleveIndex(PageName string) {
if config.DataPathFTS != "" {
if directoryExists(config.DataPathFTS) {
index, err := bleve.Open(config.DataPathFTS)
check(err)
b, err := ioutil.ReadFile(path.Join(config.DataPath, PageName))
check(err)
indexData := struct {
Text string
}{
string(b),
}
err = index.Index(PageName, indexData)
check(err)
index.Close()
}
}
}