as minimal as it gets notes application
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

193 lines
5.1 KiB

  1. package main
  2. import (
  3. "os"
  4. "fmt"
  5. "net/http"
  6. "github.com/gorilla/mux"
  7. "html/template"
  8. "strings"
  9. "flag"
  10. "io/ioutil"
  11. )
  12. var (
  13. tmpl string // tmpl defines the template used for rendering the notes
  14. path string // path defines the path where the notes should be stored
  15. )
  16. func main() {
  17. initTemplate()
  18. initFlags()
  19. r := mux.NewRouter()
  20. // getting rid of this "favicon.ico could not be found error message"
  21. r.HandleFunc("/favicon.ico", func(w http.ResponseWriter, r *http.Request) {fmt.Fprintf(w, "%s", "")})
  22. // static files
  23. fs := http.FileServer(http.Dir("./static"))
  24. r.PathPrefix("/static/").Handler(http.StripPrefix("/static/", fs))
  25. // the actual note handlers
  26. r.HandleFunc("/{path:[a-zA-Z0-9/]*}", pathGetHandler).Methods("GET")
  27. r.HandleFunc("/{path:[a-zA-Z0-9/]*}", pathPostHandler).Methods("POST")
  28. http.ListenAndServe(":8080", r)
  29. }
  30. // pathGetHandler handles GET requests to any path, thus handling the notes themselves
  31. func pathGetHandler(w http.ResponseWriter, r *http.Request) {
  32. vars := mux.Vars(r)
  33. // fetch all possible notes
  34. treeSlice := recursiveDive("")
  35. // read the current note
  36. file := readFile(vars["path"])
  37. templateContent := map[string]interface{}{
  38. "Tree": treeSlice,
  39. "File": file,
  40. "Path": fmt.Sprintf("/%s", vars["path"]),
  41. }
  42. // define the template used to render the frontend
  43. t := template.New("")
  44. t, err := t.Parse(tmpl) // tmpl is a global var defined using initTemplate()
  45. if err != nil {
  46. fmt.Printf("error parsing the template: %s", err)
  47. return
  48. }
  49. // execute the template using the values defined in the templateContent map
  50. err = t.ExecuteTemplate(w, "", templateContent)
  51. if err != nil {
  52. fmt.Printf("error executing the template: %s", err)
  53. return
  54. }
  55. }
  56. // pathPostHandler handles POST requests to the individual notes. This gets
  57. // triggered when a note gets saved
  58. func pathPostHandler(w http.ResponseWriter, r *http.Request) {
  59. vars := mux.Vars(r)
  60. // parse the post form
  61. if err := r.ParseForm(); err != nil {
  62. fmt.Printf("error parsing the post form: %s", err)
  63. return
  64. }
  65. // extrace the file and covert it to a byte array for writing
  66. file := r.FormValue("file")
  67. d1 := []byte(file)
  68. // write the notes content to its file
  69. err := ioutil.WriteFile(fmt.Sprintf("%s/%s.txt", path, vars["path"]), d1, 0644)
  70. if err != nil {
  71. fmt.Printf("error writing the note to it's file: %s", err)
  72. }
  73. http.Redirect(w, r, fmt.Sprintf("/%s", vars["path"]), http.StatusSeeOther)
  74. }
  75. // recursiveDive recursively iterates over all notes starting at the given
  76. // divePath
  77. func recursiveDive(divePath string) []string {
  78. var returnSlice []string
  79. c, err := ioutil.ReadDir(fmt.Sprintf("%s/%s", path, divePath))
  80. if err != nil {
  81. fmt.Printf("error reading the dir: %s", err)
  82. return []string{}
  83. }
  84. for _, entry := range c {
  85. if entry.IsDir() {
  86. newDivePath := fmt.Sprintf("%s/%s", divePath, entry.Name())
  87. newElements := recursiveDive(newDivePath)
  88. // recursively call the function for each dir in the current dir
  89. for _, element := range newElements {
  90. returnSlice = append(returnSlice, strings.TrimSuffix(element, ".txt"))
  91. }
  92. } else {
  93. returnSlice = append(returnSlice, strings.TrimSuffix(fmt.Sprintf("%s/%s", divePath, entry.Name()), ".txt"))
  94. }
  95. }
  96. return returnSlice
  97. }
  98. // readFile reads a files content and returns it. Amazing, isn't it?
  99. func readFile(filePath string) string {
  100. fullFilePath := fmt.Sprintf("%s/%s.txt", path, filePath)
  101. dat, err := ioutil.ReadFile(fullFilePath)
  102. if err != nil {
  103. fmt.Println(err)
  104. // handle the error
  105. if strings.Contains(err.Error(), "no such file or directory") {
  106. // if the file path contains a /, strip stuff and create the folder...
  107. if strings.Contains(fullFilePath, "/") {
  108. extractedPath := fullFilePath[0:strings.LastIndex(fullFilePath, "/")]
  109. err := os.MkdirAll(extractedPath, 0755)
  110. if err != nil {
  111. fmt.Println(err)
  112. }
  113. }
  114. // ...then create the actual file
  115. fmt.Printf("Creating %s\n", fullFilePath)
  116. _, err = os.Create(fullFilePath)
  117. if err != nil {
  118. fmt.Println(err)
  119. }
  120. }
  121. }
  122. return string(dat)
  123. }
  124. func initFlags() {
  125. flag.StringVar(&path, "path", ".", "folder where the notes should be stored")
  126. flag.Parse()
  127. }
  128. // initTemplate is down here, as it may become a bit longer and the focus
  129. // should stay on the code above
  130. func initTemplate() {
  131. tmpl = `<!doctype html>
  132. <html lang="en">
  133. <head>
  134. <meta charset="utf-8">
  135. <meta name="viewport" content="width=device-width, initial-scale=1">
  136. <link rel="stylesheet" href="/static/bootstrap.min.css">
  137. <title>Notes</title>
  138. </head>
  139. <body>
  140. <div class="row m-1 mt-3">
  141. <div class="col-4">
  142. <div class="list-group">
  143. {{ range .Tree }}
  144. <a href="{{ . }}" class="list-group-item list-group-item-action">{{ . -}}</a>
  145. {{ end }}
  146. </div>
  147. </div>
  148. <div class="col-8">
  149. <div class="mb-3">
  150. <form method="POST" action="{{ .Path }}">
  151. <textarea class="form-control" id="file" name="file" rows="15">{{ .File }}</textarea>
  152. <button type="submit" class="btn btn-outline-secondary mt-3">Save</button>
  153. <button type="submit" formaction="/outoutout" class="btn btn-outline-danger mt-3">Delete</button>
  154. </form>
  155. </div>
  156. </div>
  157. </div>
  158. </body>
  159. </html>
  160. `
  161. }