Forráskód Böngészése

Step 03.2: simple POST save with flash, no validators.

Frédéric G. MARAND 1 éve
szülő
commit
e2d5e3b251

+ 60 - 8
contactsapp/app.go

@@ -2,9 +2,11 @@ package main
 
 import (
 	"errors"
+	"fmt"
 	"html/template"
 	"log"
 	"net/http"
+	"sync"
 
 	"github.com/masterminds/sprig"
 )
@@ -15,6 +17,11 @@ const (
 
 type data map[string]any
 
+var (
+	flashMessage string
+	flashMX      sync.Mutex
+)
+
 /*
 @app.route("/")
 def index():
@@ -33,7 +40,7 @@ func contacts(cs *ContactsStore) http.HandlerFunc {
 	)
 	return func(w http.ResponseWriter, r *http.Request) {
 		search := r.URL.Query().Get("q")
-		var contacts_set []Contact
+		var contacts_set []*Contact
 		if search == "" {
 			contacts_set = cs.GetAll()
 		} else {
@@ -50,9 +57,6 @@ func contacts(cs *ContactsStore) http.HandlerFunc {
 
 /*
 @app.route("/contacts/new", methods=['GET']) (1)
-def contacts_new_get():
-
-	return render_template("new.html", contact=Contact()
 */
 func contactsNewGet(cs *ContactsStore) http.HandlerFunc {
 	tpl := makeTemplate(
@@ -60,20 +64,67 @@ func contactsNewGet(cs *ContactsStore) http.HandlerFunc {
 		"new",
 	)
 	return func(w http.ResponseWriter, r *http.Request) {
-		c := cs.New()
-		if err := tpl.ExecuteTemplate(w, "layout.html", c); err != nil {
+		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.ParseFiles(paths...)).
-		Funcs(sprig.FuncMap())
+	tpl := template.Must(
+		template.New(first).
+			Funcs(sprig.FuncMap()).
+			Funcs(template.FuncMap{"flash": getFlash}).
+			ParseFiles(paths...))
 	return tpl
 }
 
@@ -85,6 +136,7 @@ func setupRoutes(mux *http.ServeMux, cs *ContactsStore) {
 	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() {

A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 0 - 138
contactsapp/contacts.json


+ 66 - 14
contactsapp/contacts_model.go

@@ -16,16 +16,16 @@ var (
 )
 
 type (
-	Contacts      []Contact
+	Contacts      []*Contact
 	ContactsStore struct {
 		sync.RWMutex
-		data map[int]Contact
+		data map[int]*Contact
 	}
 )
 
 func NewContactsStore() (*ContactsStore, error) {
 	cs := ContactsStore{
-		data: make(map[int]Contact),
+		data: make(map[int]*Contact),
 	}
 	if err := cs.Load(); err != nil {
 		return nil, err
@@ -38,11 +38,11 @@ func (cs *ContactsStore) Get(search string) Contacts {
 	if search == "" {
 		return contacts
 	}
-	return slices.DeleteFunc(contacts, func(c Contact) bool {
-		return (c.First == "" || !strings.Contains(c.First, search)) &&
+	return slices.DeleteFunc(contacts, func(c *Contact) bool {
+		return (c == nil) || ((c.First == "" || !strings.Contains(c.First, search)) &&
 			(c.Last == "" || !strings.Contains(c.Last, search)) &&
 			(c.Email == "" || !strings.Contains(c.Email, search)) &&
-			(c.Phone == "" || !strings.Contains(c.Phone, search))
+			(c.Phone == "" || !strings.Contains(c.Phone, search)))
 	})
 }
 
@@ -58,7 +58,7 @@ func (cs *ContactsStore) GetAll() Contacts {
 	cs.RLock()
 	defer cs.RUnlock()
 	contacts := MapValues(cs.data)
-	slices.SortStableFunc(contacts, func(a, b Contact) int {
+	slices.SortStableFunc(contacts, func(a, b *Contact) int {
 		if sgn := cmp.Compare(a.First, b.First); sgn != 0 {
 			return sgn
 		}
@@ -97,17 +97,69 @@ func (cs *ContactsStore) Load() error {
 	return nil
 }
 
-func (*ContactsStore) New() *Contact {
+func (cs *ContactsStore) getByMail(email string) *Contact {
+	cs.RLock()
+	defer cs.RUnlock()
+	for _, c := range cs.data {
+		if c.Email == email {
+			return c
+		}
+	}
+	return nil
+}
+
+func (cs *ContactsStore) initNextID() int {
+	cs.Lock()
+	defer cs.Unlock()
+	max := 0
+	for k := range cs.data {
+		if k <= max {
+			continue
+		}
+		max = k
+	}
+	cs.data[max] = nil
+	return max
+}
+
+func (cs *ContactsStore) Save(c *Contact) error {
+	if existing := cs.getByMail(c.Email); existing != nil && existing.ID != c.ID {
+		return fmt.Errorf("attempting to save new entry for email %q, already under ID %d",
+			c.Email, existing.ID)
+	}
+	if c.ID == 0 {
+		c.ID = cs.initNextID()
+	}
+	cs.Lock()
+	cs.data[c.ID] = c
+	cs.Unlock()
+	w, err := os.Create(ContactsFile)
+	if err != nil {
+		return fmt.Errorf("creating contacts file: %w", err)
+	}
+	defer w.Close()
+	enc := json.NewEncoder(w)
+	if err := enc.Encode(MapValues(cs.data)); err != nil {
+		return fmt.Errorf("encoding to contacts file: %w", err)
+	}
+	return nil
+}
+
+func (*ContactsStore) New(m map[string]string) *Contact {
 	return &Contact{
+		First:  m["first"],
+		Last:   m["last"],
+		Phone:  m["phone"],
+		Email:  m["email"],
 		Errors: make(map[string]string),
 	}
 }
 
 type Contact struct {
-	ID     int    `json:"id"`
-	First  string `json:"first"`
-	Last   string `json:"last"`
-	Phone  string `json:"phone"`
-	Email  string `json:"email"`
-	Errors map[string]string
+	ID     int               `json:"id"`
+	First  string            `json:"first"`
+	Last   string            `json:"last"`
+	Phone  string            `json:"phone"`
+	Email  string            `json:"email"`
+	Errors map[string]string `json:"errors"`
 }

+ 4 - 0
contactsapp/templates/layout.gohtml

@@ -13,6 +13,10 @@
         <all-caps>contacts.app</all-caps>
         <sub-title>A demo contacts application</sub-title>
       </h1>
+        {{ $flash := flash }}
+        {{ if ne $flash "" }}
+          <div class="flash">{{ $flash }}</div>
+        {{ end }}
     </header>
       {{ block "content" . }}Default content{{ end }}
   </main>

Nem az összes módosított fájl került megjelenítésre, mert túl sok fájl változott