diff --git a/TODO.md b/TODO.md index f3e5bfc..ea4a912 100644 --- a/TODO.md +++ b/TODO.md @@ -16,6 +16,7 @@ - [ ] Input Validation - [ ] Saparate routing logic, and http handlers - [ ] Better error Handling +- [ ] handle every error - [ ] Migrate to gorm -> simplification of codebase - [ ] Proper session auth ? - [ ] /orders/search htmx trigger `
` @@ -25,6 +26,21 @@ - [ ] ci/cd pipeline (github actions) - [ ] monitoring (prometheus, grafana) - [ ] structured logs (JSON format), log formatter +- [ ] maybe a way to define what routes are protected outside the auth middleware, sort of like using the [protected] tag in some full stack solutions +- [ ] configurable session expiration +- [ ] rework server to simplify loading templates, and routes +- [ ] make fatcher only return data, and error +- [ ] move new cake/order into cake/order pages +- [ ] new cake header instead of cake 0 +- [ ] permission System +- [ ] form css +- [ ] polish ui +- [ ] proper confirmation pages (maybe popus with htmx?) +- [ ] filter out done orders +- [ ] daily summary page +- [ ] maybe sending forms as json to simplify code? +- [ ] move basekt_element_template to be together with script +- [ ] can't edit already done routes - [ ] Phone number format validation diff --git a/auth/auth.go b/auth/auth.go index 568a7d5..0c3a6c8 100644 --- a/auth/auth.go +++ b/auth/auth.go @@ -2,41 +2,41 @@ package auth import ( _ "embed" - "fmt" + "errors" "log" "net/http" + "github.com/Bronku/iroon/crypto" "github.com/Bronku/iroon/models" - "github.com/Bronku/iroon/store" + "gorm.io/gorm" ) type Authenticator struct { - sessions map[string]models.Token - s *store.Store + db *gorm.DB } -func New(s *store.Store) *Authenticator { - var out Authenticator - var err error - out.s = s - out.sessions, err = s.GetSessions() - //fmt.Println(out.sessions) +func New(db *gorm.DB) Authenticator { + err := db.AutoMigrate(&models.User{}, &models.Token{}) if err != nil { - log.Fatal(err) + log.Fatal("failed to initialize authenticator", err) } - return &out + return Authenticator{db} } -func (a *Authenticator) ensureAuth(in http.Handler) http.Handler { - fmt.Println("ensureAuth called") - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - _, err := a.getSession(r) - if err != nil { - http.Redirect(w, r, "/login", http.StatusSeeOther) - return - } - in.ServeHTTP(w, r) - }) +// #todo only used once, maybe remove it entierly later +func (a *Authenticator) verifyCredentials(login, password string) error { + var user models.User + + result := a.db.First(&user, "login = ?", login) + if result.Error != nil { + return errors.New("user with this login doesn't exist") + } + + hash := crypto.PasswordHash(password, user.Salt) + if hash == user.Password { + return nil + } + return errors.New("wrong credentials") } func (a *Authenticator) Middleware(in http.Handler) http.Handler { @@ -44,6 +44,13 @@ func (a *Authenticator) Middleware(in http.Handler) http.Handler { handler.HandleFunc("GET /login", getLogin) handler.HandleFunc("POST /login", a.login) handler.HandleFunc("GET /logout", a.logout) - handler.Handle("/", a.ensureAuth(in)) + handler.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + _, err := a.getSession(r) + if err != nil { + http.Redirect(w, r, "/login", http.StatusSeeOther) + return + } + in.ServeHTTP(w, r) + }) return handler } diff --git a/auth/credentials.go b/auth/credentials.go deleted file mode 100644 index d376d50..0000000 --- a/auth/credentials.go +++ /dev/null @@ -1,19 +0,0 @@ -package auth - -import ( - "errors" - - "github.com/Bronku/iroon/crypto" -) - -func (a *Authenticator) verifyCredentials(login, password string) error { - user, ok := a.s.GetUser(login) - if !ok { - return errors.New("user with this login doesn't exist") - } - hash := crypto.PasswordHash(password, user.Salt) - if hash == user.Password { - return nil - } - return errors.New("wrong credentials") -} diff --git a/auth/get.go b/auth/get.go index 3d4646d..21b9ae2 100644 --- a/auth/get.go +++ b/auth/get.go @@ -10,6 +10,7 @@ import ( //go:embed templates/login.html var loginHTML string +// returns a simple login page func getLogin(w http.ResponseWriter, _ *http.Request) { w.Header().Set("content-type", "text/html") fmt.Fprint(w, loginHTML) diff --git a/auth/session.go b/auth/session.go index 7c3ad0d..1a01f5c 100644 --- a/auth/session.go +++ b/auth/session.go @@ -2,10 +2,10 @@ package auth import ( "errors" - "fmt" "net/http" "time" + "github.com/Bronku/iroon/crypto" "github.com/Bronku/iroon/models" ) @@ -14,62 +14,62 @@ func (a *Authenticator) getSession(r *http.Request) (models.Token, error) { if err != nil { return models.Token{}, err } - session, ok := a.sessions[c.Value] - if !ok { + var session models.Token + result := a.db.First(&session, c.Value) + if result.Error != nil { return models.Token{}, errors.New("session not found") } if time.Since(session.Expiration) > 0 { - delete(a.sessions, c.Value) - err := a.s.CleanSessions() - if err != nil { - fmt.Println("error cleaning the sessions", err) - } + a.db.Delete(&session) return models.Token{}, errors.New("session expired") } return session, nil } func (a *Authenticator) login(w http.ResponseWriter, r *http.Request) { - err := r.ParseForm() - if err != nil { - http.Redirect(w, r, "/login", http.StatusSeeOther) - return - } + r.ParseForm() login := r.PostFormValue("login") password := r.PostFormValue("password") if a.verifyCredentials(login, password) != nil { http.Redirect(w, r, "/login", http.StatusSeeOther) return } - cookie, err := a.newSession(login) - if err != nil { - w.Header().Set("content-type", "text/html") - fmt.Fprint(w, "internal server error") - return - } + cookie := a.newSession(login) http.SetCookie(w, &cookie) http.Redirect(w, r, "/", http.StatusFound) } func (a *Authenticator) logout(w http.ResponseWriter, r *http.Request) { - var cookie http.Cookie - cookie.Name = "token" - cookie.Value = "nil" - cookie.HttpOnly = true - cookie.SameSite = http.SameSiteStrictMode - cookie.Path = "/" + cookie := http.Cookie{ + Name: "Token", + Value: "nil", + HttpOnly: true, + SameSite: http.SameSiteStrictMode, + Path: "/", + } http.SetCookie(w, &cookie) http.Redirect(w, r, "/", http.StatusFound) c, err := r.Cookie("token") if err != nil { return } - _, ok := a.sessions[c.Value] - if !ok { - return - } - fmt.Println("removing session") - delete(a.sessions, c.Value) - err = a.s.RevokeSession(c.Value) - fmt.Println(err) + a.db.Delete(&models.Token{}, c.Value) +} + +func (a *Authenticator) newSession(user string) http.Cookie { + key := crypto.GenerateKey() + var session models.Token + session.User = user + session.Expiration = time.Now().Add(time.Hour * 24) + a.db.Create(&session) + + cookie := http.Cookie{ + Name: "token", + Value: key, + HttpOnly: true, + SameSite: http.SameSiteStrictMode, + Path: "/", + // Secure: true, #todo + } + return cookie } diff --git a/auth/token.go b/auth/token.go deleted file mode 100644 index 0288c93..0000000 --- a/auth/token.go +++ /dev/null @@ -1,30 +0,0 @@ -package auth - -import ( - "net/http" - "time" - - "github.com/Bronku/iroon/crypto" - "github.com/Bronku/iroon/models" -) - -func (a *Authenticator) newSession(user string) (http.Cookie, error) { - key := crypto.GenerateKey() - var session models.Token - var cookie http.Cookie - session.User = user - session.Expiration = time.Now().Add(time.Hour * 24) - a.sessions[key] = session - err := a.s.AddSession(key, user, session.Expiration) - if err != nil { - return cookie, err - } - - cookie.Name = "token" - cookie.Value = key - cookie.HttpOnly = true - cookie.SameSite = http.SameSiteStrictMode - cookie.Path = "/" - //cookie.Secure = true - return cookie, nil -} diff --git a/go.mod b/go.mod index c764ba4..99d910e 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,15 @@ require github.com/mattn/go-sqlite3 v1.14.24 require github.com/knaka/go-sqlite3-fts5 v0.0.0-20240729040425-e53b86878d0d +require ( + github.com/jinzhu/inflection v1.0.0 // indirect + github.com/jinzhu/now v1.1.5 // indirect + golang.org/x/text v0.23.0 // indirect +) + require ( golang.org/x/crypto v0.36.0 golang.org/x/sys v0.31.0 // indirect + gorm.io/driver/sqlite v1.6.0 + gorm.io/gorm v1.30.0 ) diff --git a/go.sum b/go.sum index 76d405a..fde8d58 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,7 @@ +github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= +github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= +github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= +github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= github.com/knaka/go-sqlite3-fts5 v0.0.0-20240729040425-e53b86878d0d h1:I3lRivq7Zx0fqlKhCJG1KaL2tLG6aiHDj3bvJJqppKw= github.com/knaka/go-sqlite3-fts5 v0.0.0-20240729040425-e53b86878d0d/go.mod h1:kDHCqub/PNhQnqg8ur7OYO49jpZ+pM0mqBQ768DE3UU= github.com/mattn/go-sqlite3 v1.14.24 h1:tpSp2G2KyMnnQu99ngJ47EIkWVmliIizyZBfPrBWDRM= @@ -6,3 +10,9 @@ golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= +golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= +gorm.io/driver/sqlite v1.6.0 h1:WHRRrIiulaPiPFmDcod6prc4l2VGVWHz80KspNsxSfQ= +gorm.io/driver/sqlite v1.6.0/go.mod h1:AO9V1qIQddBESngQUKWL9yoH93HIeA1X6V633rBwyT8= +gorm.io/gorm v1.30.0 h1:qbT5aPv1UH8gI99OsRlvDToLxW5zR7FzS9acZDOZcgs= +gorm.io/gorm v1.30.0/go.mod h1:8Z33v652h4//uMA76KjeDH8mJXPm1QNCYrMeatR0DOE= diff --git a/main.go b/main.go index 4e432ea..dba9ac4 100644 --- a/main.go +++ b/main.go @@ -1,26 +1,29 @@ package main import ( - "fmt" "log" "net/http" - "github.com/Bronku/iroon/auth" "github.com/Bronku/iroon/logging" + "github.com/Bronku/iroon/models" "github.com/Bronku/iroon/server" - "github.com/Bronku/iroon/store" + "gorm.io/driver/sqlite" + "gorm.io/gorm" ) func main() { - s := store.OpenStore("./foo.db") - s.AddUser("admin", "secret") - defer s.Close() - h := server.New(s) - defer h.Close() + db, err := gorm.Open(sqlite.Open("foo.db"), &gorm.Config{}) + if err != nil { + log.Fatal("failed to connect database") + } + db.AutoMigrate(&models.Order{}, &models.OrderItem{}, &models.Product{}) + + //s.AddUser("admin", "secret") + h := server.New(db) var handler http.Handler = h handler = logging.Middleware(handler) - handler = auth.New(s).Middleware(handler) - fmt.Println("starting server") + //handler = auth.New(s).Middleware(handler) + log.Println("starting server") log.Fatal(http.ListenAndServe(":8080", handler)) } diff --git a/models/data.go b/models/data.go deleted file mode 100644 index 6ba6eef..0000000 --- a/models/data.go +++ /dev/null @@ -1,37 +0,0 @@ -package models - -import "time" - -type Cake struct { - Name string - ID int - Price int // increments of 0.01 - Amount int - Category string - Availability string -} - -type Order struct { - ID int - Name string - Surname string - Phone string - Location string - Date time.Time - Accepted time.Time - Status string - Paid int // increments of 0.01 - Cakes []Cake -} - -func (o *Order) Total() int { - out := o.Paid * (-1) - for _, e := range o.Cakes { - out += e.Price * e.Amount - } - return out -} - -func (c *Cake) Total() int { - return c.Amount * c.Price -} diff --git a/models/order.go b/models/order.go new file mode 100644 index 0000000..6d6adb9 --- /dev/null +++ b/models/order.go @@ -0,0 +1,41 @@ +package models + +import ( + "time" + + "gorm.io/gorm" +) + +// money amounts are in incremetns of 0.01 + +type OrderItem struct { + Product Product + Amount uint + ProductID uint `gorm:"primaryKey;autoIncrement:false"` + OrderID uint `gorm:"primaryKey;autoIncrement:false"` +} + +type Order struct { + gorm.Model + Name string + Surname string + Phone string + Location string + Status string + Prepaid uint + Date time.Time + OrderItems []OrderItem +} + +func (c *OrderItem) Total() uint { + return c.Amount * c.Product.Price +} + +func (o *Order) Total() uint { + var out uint + for _, e := range o.OrderItems { + out += e.Total() + } + out -= o.Prepaid + return out +} diff --git a/models/product.go b/models/product.go new file mode 100644 index 0000000..954fc46 --- /dev/null +++ b/models/product.go @@ -0,0 +1,9 @@ +package models + +import "gorm.io/gorm" + +type Product struct { + gorm.Model + Name string + Price uint +} diff --git a/models/auth.go b/models/token.go similarity index 70% rename from models/auth.go rename to models/token.go index 0e584f1..0fdb945 100644 --- a/models/auth.go +++ b/models/token.go @@ -3,6 +3,7 @@ package models import "time" type Token struct { + Token string `gorm:"primarykey"` User string Expiration time.Time } diff --git a/models/user.go b/models/user.go index 8841f56..55ea899 100644 --- a/models/user.go +++ b/models/user.go @@ -1,6 +1,10 @@ package models +import "gorm.io/gorm" + type User struct { + gorm.Model + Login string `gorm:"unique"` Password string Salt string } diff --git a/server/get.go b/server/get.go index 55989a2..2e7a1f6 100644 --- a/server/get.go +++ b/server/get.go @@ -2,12 +2,14 @@ package server import ( "fmt" + "log" "net/http" "strconv" "strings" "time" "github.com/Bronku/iroon/models" + "gorm.io/gorm/clause" ) func monthInterval(y int, m time.Month) (firstDay, lastDay time.Time) { @@ -24,13 +26,18 @@ func (h *Server) orders(_ *http.Request) (any, int, error) { y, m, _ = time.Now().Date() first, last := monthInterval(y, m) fmt.Println(first, last) - orders, err := h.s.GetOrders(first, last) + var orders []models.Order + result := h.db.Preload("OrderItems.Product").Preload(clause.Associations).Where("date between ? and ?", first, last).Find(&orders) + if result.Error != nil { + log.Println("eroor getting orders") + return nil, http.StatusInternalServerError, result.Error + } data := struct { First string Last string Orders []models.Order }{first.Format("2006-01-02"), last.Format("2006-01-02"), orders} - return data, http.StatusOK, err + return data, http.StatusOK, nil } func (h *Server) ordersSearch(r *http.Request) (any, int, error) { @@ -42,20 +49,21 @@ func (h *Server) ordersSearch(r *http.Request) (any, int, error) { if err != nil { to = time.Time{} } - fmt.Println(from, to) - data, err := h.s.GetOrders(from, to) - return data, http.StatusOK, err + var orders []models.Order + result := h.db.Preload("OrderItems.Product").Preload(clause.Associations).Where("date between ? and ?", from, to).Find(&orders) + return orders, http.StatusOK, result.Error } func (h *Server) cakes(_ *http.Request) (any, int, error) { - data, err := h.s.GetCakes() - return data, http.StatusOK, err + var cakes []models.Product + result := h.db.Find(&cakes) + return cakes, http.StatusOK, result.Error } func (h *Server) cake(r *http.Request) (any, int, error) { url := strings.Split(r.URL.String(), "/") if len(url) < 3 || url[2] == "" { - return models.Cake{}, http.StatusOK, nil + return models.Product{}, http.StatusOK, nil } id, err := strconv.Atoi(url[2]) @@ -63,25 +71,21 @@ func (h *Server) cake(r *http.Request) (any, int, error) { return nil, http.StatusBadRequest, err } - data, err := h.s.GetCake(id) - if err != nil { - return nil, http.StatusNotFound, err - } - - return data, http.StatusOK, nil + var cake models.Product + result := h.db.First(&cake, id) + return cake, http.StatusOK, result.Error } func (h *Server) order(r *http.Request) (any, int, error) { type formData struct { Order models.Order - Catalogue []models.Cake + Catalogue []models.Product } - var err error var data formData - data.Catalogue, err = h.s.GetCakes() - if err != nil { - return nil, http.StatusInternalServerError, err + result := h.db.Find(&data.Catalogue) + if result.Error != nil { + return nil, http.StatusInternalServerError, result.Error } url := strings.Split(r.URL.String(), "/") @@ -94,9 +98,10 @@ func (h *Server) order(r *http.Request) (any, int, error) { return nil, http.StatusBadRequest, err } - data.Order, err = h.s.GetOrder(id) - if err != nil { - return nil, http.StatusNotFound, err + result = h.db.Preload("OrderItems.Product").Preload(clause.Associations).Find(&data.Order, id) + //result = h.db.Find(&data.Order, id) + if result.Error != nil { + return nil, http.StatusNotFound, result.Error } return data, http.StatusOK, nil diff --git a/server/post.go b/server/post.go index 549d685..aff8bec 100644 --- a/server/post.go +++ b/server/post.go @@ -2,6 +2,7 @@ package server import ( "fmt" + "log" "net/http" "strconv" "strings" @@ -10,47 +11,50 @@ import ( "github.com/Bronku/iroon/models" ) +func atoui(input string) (uint, error) { + num, err := strconv.ParseUint(input, 10, 0) + return uint(num), err +} + func (h *Server) postCake(r *http.Request) (any, int, error) { err := r.ParseForm() if err != nil { return nil, http.StatusInternalServerError, err } - var n models.Cake - n.ID, err = strconv.Atoi(r.FormValue("id")) + var n models.Product + n.ID, err = atoui(r.FormValue("id")) if err != nil { return nil, http.StatusBadRequest, err } n.Name = r.FormValue("name") - n.Price, err = strconv.Atoi(r.FormValue("price")) + n.Price, err = atoui(r.FormValue("price")) if err != nil { return nil, http.StatusBadRequest, err } - n.Category = r.FormValue("category") - n.Availability = r.FormValue("availability") - n.ID, err = h.s.SaveCake(n) - fmt.Println(err) - return n, http.StatusAccepted, err + result := h.db.Save(&n) + return n, http.StatusAccepted, result.Error } func (h *Server) postOrder(r *http.Request) (any, int, error) { - cakes, err := h.s.GetCakes() - if err != nil { - return nil, http.StatusInternalServerError, err + var cakes []models.Product + result := h.db.Find(&cakes) + if result.Error != nil { + return nil, http.StatusInternalServerError, result.Error } - err = r.ParseForm() - fmt.Println(r.PostForm) + err := r.ParseForm() + log.Println("received form:", r.PostForm) if err != nil { return nil, http.StatusBadRequest, err } var n models.Order - n.ID, err = strconv.Atoi(r.FormValue("id")) + n.ID, err = atoui(r.FormValue("id")) if err != nil { return nil, http.StatusBadRequest, err } - n.Paid, err = strconv.Atoi(r.FormValue("paid")) + n.Prepaid, err = atoui(r.FormValue("paid")) if err != nil { return nil, http.StatusBadRequest, err } @@ -59,23 +63,29 @@ func (h *Server) postOrder(r *http.Request) (any, int, error) { return nil, http.StatusBadRequest, err } - n.Cakes = make([]models.Cake, 0) + n.OrderItems = make([]models.OrderItem, 0) for _, e := range cakes { - e.Amount, err = strconv.Atoi(r.FormValue(fmt.Sprintf("cake[%d]", e.ID))) + count, err := atoui(r.FormValue(fmt.Sprintf("cake[%d]", e.ID))) if err != nil { continue } - n.Cakes = append(n.Cakes, e) + n.OrderItems = append(n.OrderItems, models.OrderItem{Amount: count, Product: e}) } - n.Accepted = time.Now() n.Name = strings.TrimSpace(r.FormValue("name")) n.Surname = strings.TrimSpace(r.FormValue("surname")) n.Phone = strings.TrimSpace(r.FormValue("phone")) n.Location = strings.TrimSpace(r.FormValue("location")) n.Status = strings.TrimSpace(r.FormValue("status")) - n.ID, err = h.s.SaveOrder(n) - fmt.Println(n) - return n, http.StatusAccepted, err + log.Println("parsed order:", n) + err = h.db.Save(&n).Error + if err != nil { + log.Println("save: ", err) + } + err = h.db.Model(&n).Association("OrderItems").Replace(n.OrderItems) + if err != nil { + log.Println("replace: ", err) + } + return n, http.StatusAccepted, nil } diff --git a/server/server.go b/server/server.go index 4fc3097..6ababcf 100644 --- a/server/server.go +++ b/server/server.go @@ -5,12 +5,12 @@ import ( "html/template" "net/http" - "github.com/Bronku/iroon/store" + "gorm.io/gorm" ) type Server struct { tmpl map[string]*template.Template - s *store.Store + db *gorm.DB routes map[string]route http.Handler } @@ -21,9 +21,6 @@ type route struct { templateEntry string } -func (h *Server) Close() { -} - //go:embed static/* var static embed.FS @@ -42,8 +39,10 @@ func (h *Server) loadHandler() { h.Handler = mux } -func New(store *store.Store) *Server { - var server Server +func New(db *gorm.DB) *Server { + server := Server{ + db: db, + } server.routes = map[string]route{ "GET /order/": {server.order, "order", "layout"}, @@ -56,7 +55,7 @@ func New(store *store.Store) *Server { } server.loadTemplates() - server.s = store + server.loadHandler() return &server diff --git a/server/static/order.js b/server/static/order.js index b7e7c73..5e2c2ad 100644 --- a/server/static/order.js +++ b/server/static/order.js @@ -3,7 +3,7 @@ function addCake(id, name, price, amount) { if (cake != null) { cake.value = Number(cake.value) + 1; let tr = cake.parentElement.parentElement.parentElement; - console.log(tr); + //console.log(tr); tr.querySelector(".cake-total").innerText = `${price * Number(cake.value)}PLN`; updateTotalPrice(); @@ -41,7 +41,7 @@ function totalPrice() { let price = Number(priceString.substring(0, priceString.length - 3)); let amount = Number(e.querySelector("input").value); out += price * amount; - console.log(out); + // console.log(out); }); return out; } diff --git a/server/templates/cake.gohtml b/server/templates/cake.gohtml index 95c4719..2da1d02 100644 --- a/server/templates/cake.gohtml +++ b/server/templates/cake.gohtml @@ -17,18 +17,6 @@ Cake {{.ID}} - - - -
diff --git a/server/templates/cakes.gohtml b/server/templates/cakes.gohtml index 0e898c6..5bf67fb 100644 --- a/server/templates/cakes.gohtml +++ b/server/templates/cakes.gohtml @@ -8,16 +8,12 @@ Cakes Name Price - Category - Availability {{range .}} edit {{.Name}} {{.Price}} - {{.Category}} - {{.Availability}} {{end}} diff --git a/server/templates/order.gohtml b/server/templates/order.gohtml index ec2fc5f..1e88d55 100644 --- a/server/templates/order.gohtml +++ b/server/templates/order.gohtml @@ -31,7 +31,11 @@
@@ -52,7 +56,7 @@
- +