app.go 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157
  1. package main
  2. import (
  3. "errors"
  4. "fmt"
  5. "html/template"
  6. "log"
  7. "net/http"
  8. "sync"
  9. "github.com/masterminds/sprig"
  10. )
  11. const (
  12. addr = ":8080"
  13. )
  14. type data map[string]any
  15. var (
  16. flashMessage string
  17. flashMX sync.Mutex
  18. )
  19. /*
  20. @app.route("/")
  21. def index():
  22. */
  23. func index(w http.ResponseWriter, r *http.Request) {
  24. http.Redirect(w, r, "/contacts", http.StatusSeeOther)
  25. }
  26. /*
  27. @app.route("/contacts")
  28. */
  29. func contacts(cs *ContactsStore) http.HandlerFunc {
  30. tpl := makeTemplate(
  31. "layout",
  32. "index",
  33. )
  34. return func(w http.ResponseWriter, r *http.Request) {
  35. search := r.URL.Query().Get("q")
  36. var contacts_set []*Contact
  37. if search == "" {
  38. contacts_set = cs.GetAll()
  39. } else {
  40. contacts_set = cs.Get(search)
  41. }
  42. if err := tpl.ExecuteTemplate(w, "layout.html", data{
  43. "contacts": contacts_set,
  44. "search": search,
  45. }); err != nil {
  46. http.Error(w, err.Error(), http.StatusInternalServerError)
  47. }
  48. }
  49. }
  50. /*
  51. @app.route("/contacts/new", methods=['GET']) (1)
  52. */
  53. func contactsNewGet(cs *ContactsStore) http.HandlerFunc {
  54. tpl := makeTemplate(
  55. "layout",
  56. "new",
  57. )
  58. return func(w http.ResponseWriter, r *http.Request) {
  59. contact := cs.New(nil)
  60. if err := tpl.ExecuteTemplate(w, "layout.html", contact); err != nil {
  61. http.Error(w, err.Error(), http.StatusInternalServerError)
  62. }
  63. }
  64. }
  65. /*
  66. @app.route("/contacts/new", methods=['POST'])
  67. */
  68. func contactsNew(cs *ContactsStore) http.HandlerFunc {
  69. tpl := makeTemplate(
  70. "layout",
  71. "new",
  72. )
  73. return func(w http.ResponseWriter, r *http.Request) {
  74. contact := cs.New(map[string]string{
  75. "first": r.PostFormValue("first_name"),
  76. "last": r.PostFormValue("last_name"),
  77. "phone": r.PostFormValue("phone"),
  78. "email": r.PostFormValue("email"),
  79. })
  80. if err := cs.Save(contact); err != nil {
  81. flash(fmt.Sprintf("Error saving contact: %v", err))
  82. if err := tpl.ExecuteTemplate(w, "layout.html", contact); err != nil {
  83. http.Error(w, err.Error(), http.StatusInternalServerError)
  84. }
  85. }
  86. flash("Created New Contact!")
  87. http.Redirect(w, r, "/contacts", http.StatusSeeOther)
  88. return
  89. }
  90. }
  91. // flash implements a public flash: the first handler asking for a flash gets it.
  92. //
  93. // Not a good system in the general case, but it avoids using a session.
  94. func flash(newFlash string) {
  95. flashMX.Lock()
  96. defer flashMX.Unlock()
  97. flashMessage += newFlash
  98. }
  99. func getFlash() string {
  100. flashMX.Lock()
  101. defer flashMX.Unlock()
  102. previousFlash := flashMessage
  103. flashMessage = ""
  104. return previousFlash
  105. }
  106. func makeTemplate(first string, others ...string) *template.Template {
  107. paths := append([]string{first}, others...)
  108. for i, path := range paths {
  109. paths[i] = "./templates/" + path + ".gohtml"
  110. }
  111. tpl := template.Must(
  112. template.New(first).
  113. Funcs(sprig.FuncMap()).
  114. Funcs(template.FuncMap{"flash": getFlash}).
  115. ParseFiles(paths...))
  116. return tpl
  117. }
  118. func setupRoutes(mux *http.ServeMux, cs *ContactsStore) {
  119. mux.HandleFunc("/", index)
  120. mux.HandleFunc("/favicon.ico", func(w http.ResponseWriter, r *http.Request) {
  121. http.ServeFile(w, r, "./static/img/favicon.ico")
  122. })
  123. mux.Handle("GET /static/", http.StripPrefix("/static/", http.FileServer(http.Dir("./static"))))
  124. mux.Handle("GET /contacts", contacts(cs))
  125. mux.Handle("GET /contacts/new", contactsNewGet(cs))
  126. mux.Handle("POST /contacts/new", contactsNew(cs))
  127. }
  128. func main() {
  129. cs, err := NewContactsStore()
  130. if err != nil {
  131. log.Fatalf("initializing contacts: %v\n", err)
  132. }
  133. mux := http.NewServeMux()
  134. setupRoutes(mux, cs)
  135. log.Printf("Listening on http://localhost%s", addr)
  136. if err := http.ListenAndServe(addr, mux); err != nil {
  137. if !errors.Is(err, http.ErrServerClosed) {
  138. log.Printf("Server error: %v\n", err)
  139. return
  140. }
  141. }
  142. log.Println("Server shutdown")
  143. }