package main import ( "encoding/json" "errors" "fmt" "html/template" "log" "net/http" "strconv" "strings" "github.com/Masterminds/sprig/v3" ) const ( App = "contactsapp" SecretKey = "hypermedia rocks" ) func MakeTemplate(names ...string) *template.Template { names = append([]string{"layout"}, names...) paths := make([]string, len(names)) for i, name := range names { paths[i] = fmt.Sprintf("./templates/%s.gohtml", name) } tpl := template.Must(template.New("layout.html"). Funcs(sprig.FuncMap()). Funcs(template.FuncMap{ "get_flashed_messages": func() []string { return []string{"some message"} }, }). ParseFiles(paths...), ) return tpl } /* @app.route("/contacts") def contacts(): */ func RouteContacts(w http.ResponseWriter, r *http.Request) { search := r.URL.Query().Get("q") page, err := strconv.Atoi(r.URL.Query().Get("page")) if err != nil || page == 0 { page = 1 } var contactsSet []Contact if search != "" { contactsSet = (&Contact{}).Search(search) if r.Header.Get("HX-Trigger") == "search" { MakeTemplate("rows").Execute(w, anyMap{"contacts": contactsSet}) return } } else { contactsSet = (&Contact{}).All(page) } MakeTemplate( "index", "archive_ui", "rows", ).Execute(w, anyMap{ "contacts": contactsSet, "search": search, "page": page, "archiver": NewArchiver(), }) } var ( /* @app.route("/") def index(): */ RouteFront = http.RedirectHandler("/contacts", http.StatusFound) /* @app.route("/contacts/archive", methods=["POST"]) def start_archive(): */ MakeStartArchive = func(templates *template.Template) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { archiver := NewArchiver() archiver.Run() templates.ExecuteTemplate(w, "archive_ui.html", anyMap{"archiver": archiver}) } } /* @app.route("/contacts/archive", methods=["GET"]) def archive_status(): */ MakeArchiveStatus = func(templates *template.Template) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { archiver := NewArchiver() templates.ExecuteTemplate(w, "archive_ui.html", anyMap{"archiver": archiver}) } } /* @app.route("/contacts/archive/file", methods=["GET"]) def archive_content(): */ ArchiveContent http.HandlerFunc = func(w http.ResponseWriter, r *http.Request) { archiver := NewArchiver() fileName := archiver.ArchiveFile() w.Header().Set("Content-Disposition", fmt.Sprintf(`attachement; filename="%s"`, fileName)) http.ServeFile(w, r, fileName) } /* @app.route("/contacts/archive", methods=["DELETE"]) def reset_archive(): */ MakeResetArchive = func(templates *template.Template) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { archiver := NewArchiver() archiver.Reset() templates.ExecuteTemplate(w, "archive_ui.html", anyMap{"archiver": archiver}) } } /* @app.route("/contacts/count") def contacts_count(): */ ContactsCount http.HandlerFunc = func(w http.ResponseWriter, r *http.Request) { count := (&Contact{}).Count() _, _ = fmt.Fprintf(w, "(%d total Contacts)", count) } /* @app.route("/contacts/new", methods=['GET']) def contacts_new_get(): */ MakeContactsNewGet = func(templates *template.Template) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { templates.ExecuteTemplate(w, "new.html", anyMap{"contact": NewContact(nil)}) } } /* @app.route("/contacts/new", methods=['POST']) def contacts_new(): */ MakeContactsNew = func(templates *template.Template) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { c := NewContact(anyMap{ "first": r.FormValue("first_name"), "last": r.FormValue("last_name"), "email": r.FormValue("phone"), "phone": r.FormValue("email"), }) if c.Save() { flash("Created New Contact!") http.Redirect(w, r, "/contacts", http.StatusFound) return } else { templates.ExecuteTemplate(w, "new.html", anyMap{"contact": c}) } } } /* @app.route("/contacts/") def contacts_view(contact_id=0): */ MakeContactsView = func(templates *template.Template) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { contact_id, _ := strconv.Atoi(r.PathValue("contact_id")) contact := (&Contact{}).Find(uint64(contact_id)) templates.ExecuteTemplate(w, "show.html", anyMap{"contact": contact}) } } /* @app.route("/contacts//edit", methods=["GET"]) def contacts_edit_get(contact_id=0): */ MakeContactsEditGet = func(templates *template.Template) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { contact_id, _ := strconv.Atoi(r.PathValue("contact_id")) contact := (&Contact{}).Find(uint64(contact_id)) MakeTemplate( "edit", ). Execute(w, anyMap{"contact": contact}) } } /* @app.route("/contacts//edit", methods=["POST"]) def contacts_edit_post(contact_id=0): */ MakeContactsEditPost = func(templates *template.Template) http.HandlerFunc { return func(w http.ResponseWriter, request *http.Request) { contact_id, _ := strconv.Atoi(request.PathValue("contact_id")) c := (&Contact{}).Find(uint64(contact_id)) c.Update( request.FormValue("first_name"), request.FormValue("last_name"), request.FormValue("phone"), request.FormValue("email"), ) if c.Save() { flash("Updated Contact!") http.Redirect(w, request, fmt.Sprintf("/contacts/%d", contact_id), http.StatusFound) return } else { templates.ExecuteTemplate(w, "edit.html", anyMap{"contact": c}) } } } /* @app.route("/contacts//email", methods=["GET"]) def contacts_email_get(contact_id=0): */ ContactsEmailGet http.HandlerFunc = func(w http.ResponseWriter, r *http.Request) { contact_id, _ := strconv.Atoi(r.PathValue("contact_id")) c := (&Contact{}).Find(uint64(contact_id)) c.Email = r.FormValue("email") c.validate() // FIXME find what this is really expected to do in the original Python. for _, err := range c.Errors { err := err.Error() if strings.Contains(err, "email") { fmt.Fprintln(w, err) } } } /* @app.route("/contacts/", methods=["DELETE"]) def contacts_delete(contact_id=0): */ ContactsDelete http.HandlerFunc = func(w http.ResponseWriter, r *http.Request) { contact_id, _ := strconv.Atoi(r.PathValue("contact_id")) contact := (&Contact{}).Find(uint64(contact_id)) contact.Delete() if r.Header.Get("HX-Trigger") == "delete-btn" { flash("Deleted Contact!") http.Redirect(w, r, "/contacts", http.StatusSeeOther) return } else { return } } /* @app.route("/contacts/", methods=["DELETE"]) def contacts_delete_all(): */ MakeContactsDeleteAll = func(templates *template.Template) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { sContactIDs := strings.Split(r.FormValue("selected_contact_ids"), ",") for _, sContactID := range sContactIDs { if contactID, err := strconv.Atoi(sContactID); contactID > 0 && err == nil { contact := (&Contact{}).Find(uint64(contactID)) contact.Delete() } } flash("Deleted contacts!") contacts_set := (&Contact{}).All(1) templates.ExecuteTemplate(w, "index.html", anyMap{"contacts": contacts_set}) } } /* # =========================================================== # JSON Data API # =========================================================== */ /* @app.route("/api/v1/contacts", methods=["GET"]) def json_contacts(): */ JSONContacts http.HandlerFunc = func(w http.ResponseWriter, r *http.Request) { contacts_set := (&Contact{}).All(1) enc := json.NewEncoder(w) if err := enc.Encode(anyMap{"contacts": contacts_set}); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } } /* @app.route("/api/v1/contacts", methods=["POST"]) def json_contacts_new(): */ JSONContactsNew http.HandlerFunc = func(w http.ResponseWriter, r *http.Request) { c := NewContact(anyMap{ "first": r.FormValue("first_°name"), "last": r.FormValue("last_name"), "phone": r.FormValue("phone"), "email": r.FormValue("email"), }) enc := json.NewEncoder(w) if c.Save() { if err := enc.Encode(c); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } } else { w.WriteHeader(http.StatusBadRequest) if err := enc.Encode(anyMap{"errors": c.Errors}); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } } } /* @app.route("/api/v1/contacts/", methods=["GET"]) def json_contacts_view(contact_id=0): */ JSONContactsEdit http.HandlerFunc = func(w http.ResponseWriter, request *http.Request) { contact_id, _ := strconv.Atoi(request.PathValue("contact_id")) c := (&Contact{}).Find(uint64(contact_id)) c.Update( request.FormValue("first_name"), request.FormValue("last_name"), request.FormValue("phone"), request.FormValue("email"), ) enc := json.NewEncoder(w) if c.Save() { if err := enc.Encode(c); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } } else { w.WriteHeader(http.StatusBadRequest) if err := enc.Encode(anyMap{"errors": c.Errors}); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } } } /* @app.route("/api/v1/contacts/", methods=["DELETE"]) def json_contacts_delete(contact_id=0): */ JSONContactsDelete http.HandlerFunc = func(w http.ResponseWriter, r *http.Request) { contact_id, _ := strconv.Atoi(r.PathValue("contact_id")) contact := (&Contact{}).Find(uint64(contact_id)) contact.Delete() enc := json.NewEncoder(w) _ = enc.Encode(anyMap{"success": true}) } ) /* * TODO implement me */ func flash(message string) { } func setupRoutes(mux *http.ServeMux, templates *template.Template) { mux.Handle("/", RouteFront) mux.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("./static")))) mux.HandleFunc("/contacts", RouteContacts) mux.Handle("POST /contacts/archive", MakeStartArchive(templates)) mux.Handle("GET /contacts/archive", MakeArchiveStatus(templates)) mux.Handle("GET /contacts/archive/file", ArchiveContent) mux.Handle("DELETE /contacts/archive", MakeResetArchive(templates)) mux.Handle("GET /contacts/count", ContactsCount) mux.Handle("GET /contacts/new", MakeContactsNewGet(templates)) mux.Handle("POST /contacts/new", MakeContactsNew(templates)) mux.Handle("GET /contacts/{contact_id}", MakeContactsView(templates)) mux.Handle("GET /contacts/{contact_id}/edit", MakeContactsEditGet(templates)) mux.Handle("POST /contacts/{contact_id}/edit", MakeContactsEditPost(templates)) mux.Handle("GET /contacts/{contact_id}/email", ContactsEmailGet) mux.Handle("DELETE /contacts/{contact_id}", ContactsDelete) mux.Handle("DELETE /contacts/", MakeContactsDeleteAll(templates)) mux.Handle("GET /api/v1/contacts", JSONContacts) mux.Handle("POST /api/v1/contacts", JSONContactsNew) mux.Handle("GET /api/v1/contacts/{contact_id}", JSONContactsEdit) mux.Handle("DELETE /api/v1/contacts/{contact_id}", JSONContactsDelete) } func main() { mux := http.NewServeMux() templates := template.Must( template. New("htmx-app"). Funcs(sprig.FuncMap()). // Arithmetic, etc. Funcs(template.FuncMap{ "get_flashed_messages": func() string { return "some message" }, }). ParseGlob("./templates/*.gohtml"), ) setupRoutes(mux, templates) (&Contact{}).LoadDB() if err := http.ListenAndServe(":8080", mux); err != nil { if !errors.Is(err, http.ErrServerClosed) { log.Printf("Server error: %v\n", err) return } } log.Println("Server shutdown") }