package main import ( "bufio" "crypto/subtle" "encoding/json" "fmt" "html/template" "io/ioutil" "log" "net/http" "os" "path" "path/filepath" "strings" "time" "github.com/blevesearch/bleve" "github.com/blevesearch/bleve/search/highlight/highlighter/html" "github.com/gorilla/mux" "github.com/mandolyte/mdtopdf" "gopkg.in/src-d/go-git.v4" "gopkg.in/src-d/go-git.v4/plumbing/object" ) var config = readConfig("") type Configuration struct { Host string Port string DataPath string DataPathFTS string WebUser string WebPasswd string } // our main function func main() { // Ausgeben der Config-Optionen fmt.Println("Host: " + config.Host) fmt.Println("Post: " + config.Port) fmt.Println("DataPath: " + config.DataPath) fmt.Println("DataPathFTS: " + config.DataPathFTS) // 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) } // 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 }) */ router := mux.NewRouter() 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") router.HandleFunc("/{pagename:.*}", basicAuth(getHTMLPage)).Methods("GET") router.HandleFunc("/{pagename:.*}", basicAuth(postHTMLPage)).Methods("POST") log.Fatal(http.ListenAndServe(":"+config.Port, router)) } 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) } 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 + " " } } if info.Name() != "data" && !strings.HasPrefix(tmpPath, "/.git") && info.Name() != "start.md" && info.Name() != "sidebar.md" && info.Name() != "hilfe.md" { 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" } // 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() // Git Commit GitCommit("Auto-Commit Page: " + pPageName) // FTS Index BleveIndex(pPageName) 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) } 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) } } //-------------------------------------------------------------------------- // 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. conf := &Configuration{Host: "http://127.0.0.1", Port: "8000", DataPath: "./data", DataPathFTS: "", WebUser: "", WebPasswd: ""} b, err := ioutil.ReadFile("./config.json") if err != nil { return conf } if err = json.Unmarshal(b, conf); err != nil { return conf } return conf } 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() } } }