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")
}