package main import ( "errors" "fmt" "html/template" "log" "net/http" "sync" "github.com/masterminds/sprig" ) const ( addr = ":8080" ) type data map[string]any var ( flashMessage string flashMX sync.Mutex ) /* @app.route("/") def index(): */ func index(w http.ResponseWriter, r *http.Request) { http.Redirect(w, r, "/contacts", http.StatusSeeOther) } /* @app.route("/contacts") */ func contacts(cs *ContactsStore) http.HandlerFunc { tpl := makeTemplate( "layout", "index", ) return func(w http.ResponseWriter, r *http.Request) { search := r.URL.Query().Get("q") var contacts_set []*Contact if search == "" { contacts_set = cs.GetAll() } else { contacts_set = cs.Get(search) } if err := tpl.ExecuteTemplate(w, "layout.html", data{ "contacts": contacts_set, "search": search, }); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) } } } /* @app.route("/contacts/new", methods=['GET']) (1) */ func contactsNewGet(cs *ContactsStore) http.HandlerFunc { tpl := makeTemplate( "layout", "new", ) return func(w http.ResponseWriter, r *http.Request) { contact := cs.New(nil) if err := tpl.ExecuteTemplate(w, "layout.html", contact); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) } } } /* @app.route("/contacts/new", methods=['POST']) */ func contactsNew(cs *ContactsStore) http.HandlerFunc { tpl := makeTemplate( "layout", "new", ) return func(w http.ResponseWriter, r *http.Request) { contact := cs.New(map[string]string{ "first": r.PostFormValue("first_name"), "last": r.PostFormValue("last_name"), "phone": r.PostFormValue("phone"), "email": r.PostFormValue("email"), }) if err := cs.Save(contact); err != nil { flash(fmt.Sprintf("Error saving contact: %v", err)) if err := tpl.ExecuteTemplate(w, "layout.html", contact); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) } } flash("Created New Contact!") http.Redirect(w, r, "/contacts", http.StatusSeeOther) return } } // flash implements a public flash: the first handler asking for a flash gets it. // // Not a good system in the general case, but it avoids using a session. func flash(newFlash string) { flashMX.Lock() defer flashMX.Unlock() flashMessage += newFlash } func getFlash() string { flashMX.Lock() defer flashMX.Unlock() previousFlash := flashMessage flashMessage = "" return previousFlash } func makeTemplate(first string, others ...string) *template.Template { paths := append([]string{first}, others...) for i, path := range paths { paths[i] = "./templates/" + path + ".gohtml" } tpl := template.Must( template.New(first). Funcs(sprig.FuncMap()). Funcs(template.FuncMap{"flash": getFlash}). ParseFiles(paths...)) return tpl } func setupRoutes(mux *http.ServeMux, cs *ContactsStore) { mux.HandleFunc("/", index) mux.HandleFunc("/favicon.ico", func(w http.ResponseWriter, r *http.Request) { http.ServeFile(w, r, "./static/img/favicon.ico") }) mux.Handle("GET /static/", http.StripPrefix("/static/", http.FileServer(http.Dir("./static")))) mux.Handle("GET /contacts", contacts(cs)) mux.Handle("GET /contacts/new", contactsNewGet(cs)) mux.Handle("POST /contacts/new", contactsNew(cs)) } func main() { cs, err := NewContactsStore() if err != nil { log.Fatalf("initializing contacts: %v\n", err) } mux := http.NewServeMux() setupRoutes(mux, cs) log.Printf("Listening on http://localhost%s", addr) if err := http.ListenAndServe(addr, mux); err != nil { if !errors.Is(err, http.ErrServerClosed) { log.Printf("Server error: %v\n", err) return } } log.Println("Server shutdown") }