moved to gorm
This commit is contained in:
parent
66884c64bb
commit
a074f264df
30 changed files with 247 additions and 1040 deletions
16
TODO.md
16
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 `<form class="search-container" hx-get="/orders/search" ...>`
|
||||
|
|
@ -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
|
||||
|
|
|
|||
51
auth/auth.go
51
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
|
||||
// #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")
|
||||
}
|
||||
in.ServeHTTP(w, r)
|
||||
})
|
||||
|
||||
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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
}
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
a.db.Delete(&models.Token{}, c.Value)
|
||||
}
|
||||
fmt.Println("removing session")
|
||||
delete(a.sessions, c.Value)
|
||||
err = a.s.RevokeSession(c.Value)
|
||||
fmt.Println(err)
|
||||
|
||||
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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
8
go.mod
8
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
|
||||
)
|
||||
|
|
|
|||
10
go.sum
10
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=
|
||||
|
|
|
|||
23
main.go
23
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))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
41
models/order.go
Normal file
41
models/order.go
Normal file
|
|
@ -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
|
||||
}
|
||||
9
models/product.go
Normal file
9
models/product.go
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
package models
|
||||
|
||||
import "gorm.io/gorm"
|
||||
|
||||
type Product struct {
|
||||
gorm.Model
|
||||
Name string
|
||||
Price uint
|
||||
}
|
||||
|
|
@ -3,6 +3,7 @@ package models
|
|||
import "time"
|
||||
|
||||
type Token struct {
|
||||
Token string `gorm:"primarykey"`
|
||||
User string
|
||||
Expiration time.Time
|
||||
}
|
||||
|
|
@ -1,6 +1,10 @@
|
|||
package models
|
||||
|
||||
import "gorm.io/gorm"
|
||||
|
||||
type User struct {
|
||||
gorm.Model
|
||||
Login string `gorm:"unique"`
|
||||
Password string
|
||||
Salt string
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,18 +17,6 @@ Cake {{.ID}}
|
|||
<label>
|
||||
<input type="number" name="price" min="0" value="{{.Price}}">
|
||||
</label>
|
||||
<label>category</label>
|
||||
<label for="category"></label><select name="category" id="category" >
|
||||
<option {{ if eq .Category "common" }}selected{{ end }} value="common">common</option>
|
||||
<option {{ if eq .Category "christmas" }}selected{{ end }} value="christmas">christmas</option>
|
||||
<option {{ if eq .Category "easter" }}selected{{ end }} value="easter">easter</option>
|
||||
<option {{ if eq .Category "donuts" }}selected{{ end }} value="donuts">donuts</option>
|
||||
</select>
|
||||
<label>availability</label>
|
||||
<label for="availability"></label><select name="availability" id="availability" >
|
||||
<option {{ if eq .Availability "available" }}selected{{ end }} value="available">available</option>
|
||||
<option {{ if eq .Availability "unavailable" }}selected{{ end }} value="unavailable">unavailable</option>
|
||||
</select>
|
||||
</div>
|
||||
<button type="submit">submit</button>
|
||||
</form>
|
||||
|
|
|
|||
|
|
@ -8,16 +8,12 @@ Cakes
|
|||
<th></th>
|
||||
<th>Name</th>
|
||||
<th>Price</th>
|
||||
<th>Category</th>
|
||||
<th>Availability</th>
|
||||
</tr>
|
||||
{{range .}}
|
||||
<tr>
|
||||
<th><a href="/cake/{{.ID}}">edit</a></th>
|
||||
<th>{{.Name}}</th>
|
||||
<th>{{.Price}}</th>
|
||||
<th>{{.Category}}</th>
|
||||
<th>{{.Availability}}</th>
|
||||
</tr>
|
||||
{{end}}
|
||||
</table>
|
||||
|
|
|
|||
|
|
@ -31,7 +31,11 @@
|
|||
</select>
|
||||
</label>
|
||||
<label>Data dostawy
|
||||
<input type="date" name="date" value="{{.Date.Format "2006-01-02"}}">
|
||||
{{if .ID}}
|
||||
<input type="date" name="date" value="{{.Date.Format "2006-01-02"}}" required>
|
||||
{{else}}
|
||||
<input type="date" name="date" required>
|
||||
{{end}}
|
||||
</label>
|
||||
<label>Status
|
||||
<select name="status">
|
||||
|
|
@ -40,7 +44,7 @@
|
|||
</select>
|
||||
</label>
|
||||
<label>Zaliczka
|
||||
<input type="number" name="paid" min="0" value="{{.Paid}}">
|
||||
<input type="number" name="paid" min="0" value="{{.Prepaid}}">
|
||||
</label>
|
||||
</div>
|
||||
<div>
|
||||
|
|
@ -69,7 +73,7 @@
|
|||
<table>
|
||||
{{range .}}
|
||||
<tr>
|
||||
<th>{{.Name}}<br><small>{{.Category}} (#{{.ID}})</small></th>
|
||||
<th>{{.Name}}<br><small>#{{.ID}}</small></th>
|
||||
<th>{{.Price}}PLN</th>
|
||||
<th>
|
||||
<button type=button onClick="addCake({{.ID}},{{.Name}}, {{.Price}}, 1)">
|
||||
|
|
@ -102,8 +106,8 @@
|
|||
</tr>
|
||||
</template>
|
||||
<script>
|
||||
{{range .Order.Cakes}}
|
||||
addCake({{.ID}},{{.Name}}, {{.Price}}, {{.Amount}})
|
||||
{{range .Order.OrderItems}}
|
||||
addCake({{.ProductID}},{{.Product.Name}}, {{.Product.Price}}, {{.Amount}})
|
||||
{{end}}
|
||||
</script>
|
||||
{{end}}
|
||||
|
|
|
|||
|
|
@ -59,10 +59,10 @@ Zamówienia
|
|||
{{end}}
|
||||
{{define "order_info"}}
|
||||
<table>
|
||||
{{range .Cakes}}
|
||||
{{range .OrderItems}}
|
||||
<tr>
|
||||
<th>{{.Name}}<br><small>ID: {{.ID}}</small></th>
|
||||
<th><small>Cena:</small><br>{{.Price}} PLN</th>
|
||||
<th>{{.Product.Name}}<br><small>ID: {{.ProductID}}</small></th>
|
||||
<th><small>Cena:</small><br>{{.Product.Price}} PLN</th>
|
||||
<th><small>Ilość:</small><br>{{.Amount}}</th>
|
||||
<th><small>Razem:</small><br>{{.Total}} PLN</th>
|
||||
</tr>
|
||||
|
|
|
|||
105
store/cake.go
105
store/cake.go
|
|
@ -1,105 +0,0 @@
|
|||
package store
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/Bronku/iroon/models"
|
||||
)
|
||||
|
||||
func (s *Store) cakeCount() int {
|
||||
out := 0
|
||||
rows, err := s.db.Query("select count(*) from cake;")
|
||||
if err != nil {
|
||||
return out
|
||||
}
|
||||
defer rows.Close()
|
||||
_ = rows.Next()
|
||||
_ = rows.Scan(&out)
|
||||
return out
|
||||
}
|
||||
|
||||
func (s *Store) loadCakes() ([]models.Cake, error) {
|
||||
out := make([]models.Cake, 0, s.cakeCount())
|
||||
|
||||
rows, err := s.db.Query("select id, name, price, category, availability from cake order by id;")
|
||||
if err != nil {
|
||||
return out, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
for rows.Next() {
|
||||
var cake models.Cake
|
||||
err = rows.Scan(&cake.ID, &cake.Name, &cake.Price, &cake.Category, &cake.Availability)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
out = append(out, cake)
|
||||
}
|
||||
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (s *Store) searchCakes(id int) (int, error) {
|
||||
for i, e := range s.cakes {
|
||||
if e.ID != id {
|
||||
continue
|
||||
}
|
||||
return i, nil
|
||||
}
|
||||
return 0, errors.New("cake not found")
|
||||
}
|
||||
|
||||
func (s *Store) GetCake(id int) (models.Cake, error) {
|
||||
if id <= 0 {
|
||||
return models.Cake{}, errors.New("invalid cake id")
|
||||
}
|
||||
|
||||
i, err := s.searchCakes(id)
|
||||
if err != nil {
|
||||
return models.Cake{}, err
|
||||
}
|
||||
return s.cakes[i], err
|
||||
}
|
||||
|
||||
func (s *Store) GetCakes() ([]models.Cake, error) {
|
||||
result := make([]models.Cake, len(s.cakes))
|
||||
copy(result, s.cakes)
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (s *Store) updateCake(newCake models.Cake) error {
|
||||
query := "update cake set name = ? , price = ?, category = ?, availability = ? where id = ?"
|
||||
_, err := s.db.Exec(query, newCake.Name, newCake.Price, newCake.Category, newCake.Availability, newCake.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
i, err := s.searchCakes(newCake.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.cakes[i] = newCake
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Store) SaveCake(newCake models.Cake) (int, error) {
|
||||
if newCake.ID != 0 {
|
||||
return newCake.ID, s.updateCake(newCake)
|
||||
}
|
||||
fmt.Println("adding a new cake", newCake)
|
||||
query := "insert into cake(name, price, category, availability) values (?, ?, ?, ?);"
|
||||
result, err := s.db.Exec(query, newCake.Name, newCake.Price, newCake.Category, newCake.Availability)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
id, err := result.LastInsertId()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
newCake.ID = int(id)
|
||||
|
||||
s.cakes = append(s.cakes, newCake)
|
||||
return newCake.ID, nil
|
||||
}
|
||||
|
|
@ -1,50 +0,0 @@
|
|||
package store
|
||||
|
||||
import (
|
||||
"embed"
|
||||
_ "embed"
|
||||
"log"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
//go:embed migrations/*.sql
|
||||
var migrations embed.FS
|
||||
|
||||
func (s *Store) loadFile(file string) {
|
||||
filename := strings.Split(file, ".")
|
||||
version, err := strconv.Atoi(filename[0])
|
||||
if err != nil {
|
||||
log.Fatal("only allowed files in migrations directory are <version>.txt")
|
||||
}
|
||||
if version <= s.version() {
|
||||
return
|
||||
}
|
||||
log.Println("migration: ", file)
|
||||
|
||||
query, _ := migrations.ReadFile("migrations/" + file)
|
||||
_, err = s.db.Exec(string(query))
|
||||
if err != nil {
|
||||
log.Fatal("error executing migration: ", file, err)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Store) loadMigrations() {
|
||||
migrationFiles, _ := migrations.ReadDir("migrations")
|
||||
|
||||
for _, e := range migrationFiles {
|
||||
s.loadFile(e.Name())
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Store) version() int {
|
||||
out := -1
|
||||
row, err := s.db.Query("PRAGMA user_version;")
|
||||
if err != nil {
|
||||
return out
|
||||
}
|
||||
defer row.Close()
|
||||
row.Next()
|
||||
_ = row.Scan(&out)
|
||||
return out
|
||||
}
|
||||
|
|
@ -1,39 +0,0 @@
|
|||
create table cake (
|
||||
id integer primary key autoincrement,
|
||||
name text not null unique,
|
||||
price integer not null,
|
||||
category text,
|
||||
availability text
|
||||
);
|
||||
|
||||
create table customer_order (
|
||||
id integer primary key autoincrement,
|
||||
name text,
|
||||
surname text,
|
||||
phone text,
|
||||
location text,
|
||||
order_date string not null,
|
||||
delivery_date string not null,
|
||||
status text,
|
||||
paid integer
|
||||
);
|
||||
|
||||
create table ordered_cake (
|
||||
customer_order integer references customer_order (id) not null,
|
||||
cake integer references cake (id) not null,
|
||||
amount integer not null,
|
||||
primary key (customer_order, cake)
|
||||
);
|
||||
|
||||
create table user (
|
||||
login text primary key,
|
||||
password text,
|
||||
role text,
|
||||
salt text
|
||||
);
|
||||
|
||||
create table session (
|
||||
token text primary key,
|
||||
user text,
|
||||
expiration text
|
||||
);
|
||||
172
store/order.go
172
store/order.go
|
|
@ -1,172 +0,0 @@
|
|||
package store
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/Bronku/iroon/models"
|
||||
)
|
||||
|
||||
// #todo update labels in templates
|
||||
|
||||
func (s *Store) parseOrderRow(row *sql.Rows) (models.Order, error) {
|
||||
var out models.Order
|
||||
var orderDate, deliveryDate string
|
||||
err := row.Scan(&out.ID, &out.Name, &out.Surname, &out.Phone, &out.Location, &orderDate, &deliveryDate, &out.Status, &out.Paid)
|
||||
if err != nil {
|
||||
return out, err
|
||||
}
|
||||
out.Accepted, _ = time.Parse("2006-01-02 15:04", orderDate)
|
||||
out.Date, _ = time.Parse("2006-01-02 15:04", deliveryDate)
|
||||
|
||||
out.Cakes = make([]models.Cake, 0)
|
||||
rows, err := s.db.Query("select cake, amount from ordered_cake where customer_order = ?;", out.ID)
|
||||
if err != nil {
|
||||
return out, err
|
||||
}
|
||||
defer rows.Close()
|
||||
for rows.Next() {
|
||||
var newCake models.Cake
|
||||
err = rows.Scan(&newCake.ID, &newCake.Amount)
|
||||
if err != nil {
|
||||
return out, err
|
||||
}
|
||||
cakeData, err := s.GetCake(newCake.ID)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
newCake.Name = cakeData.Name
|
||||
newCake.Price = cakeData.Price
|
||||
out.Cakes = append(out.Cakes, newCake)
|
||||
}
|
||||
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (s *Store) getOrdersFromQuery(query string, args ...any) ([]models.Order, error) {
|
||||
var out []models.Order
|
||||
rows, err := s.db.Query(query, args...)
|
||||
if err != nil {
|
||||
return out, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
for rows.Next() {
|
||||
order, err := s.parseOrderRow(rows)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
continue
|
||||
}
|
||||
out = append(out, order)
|
||||
}
|
||||
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (s *Store) GetOrder(id int) (models.Order, error) {
|
||||
query := "select * from customer_order where id = ?;"
|
||||
out, err := s.getOrdersFromQuery(query, id)
|
||||
if err != nil {
|
||||
return models.Order{}, err
|
||||
}
|
||||
if len(out) < 1 {
|
||||
return models.Order{}, errors.New("no output order")
|
||||
}
|
||||
return out[0], nil
|
||||
}
|
||||
|
||||
func (s *Store) GetOrders(from, to time.Time) ([]models.Order, error) {
|
||||
start := from.Format("2006-01-02") + " 00:00"
|
||||
end := to.Format("2006-01-02") + " 99:99"
|
||||
if to.IsZero() {
|
||||
end = "9999-99-99 99:99"
|
||||
}
|
||||
query := "select * from customer_order where status != 'done' and delivery_date >= ? and delivery_date <= ? ;"
|
||||
return s.getOrdersFromQuery(query, start, end)
|
||||
}
|
||||
|
||||
func (s *Store) UpdateOrderContents(tx *sql.Tx, newOrder models.Order) error {
|
||||
query := "delete from ordered_cake where customer_order = ?;"
|
||||
_, err := tx.Exec(query, newOrder.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
query = "insert into ordered_cake(customer_order, cake, amount) values (?,?,?);"
|
||||
for _, e := range newOrder.Cakes {
|
||||
_, err := tx.Exec(query, newOrder.ID, e.ID, e.Amount)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Store) UpdateOrder(newOrder models.Order) error {
|
||||
_, err := s.GetOrder(newOrder.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
query := "update customer_order set name = ?, surname = ?, phone = ?, location = ?, order_date = ?, delivery_date = ?, status = ?, paid = ? where id = ?;"
|
||||
|
||||
accepted := newOrder.Accepted.Format("2006-01-02 15:04")
|
||||
date := newOrder.Date.Format("2006-01-02 15:04")
|
||||
|
||||
tx, err := s.db.Begin()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = tx.Exec(query, newOrder.Name, newOrder.Surname, newOrder.Phone, newOrder.Location, accepted, date, newOrder.Status, newOrder.Paid, newOrder.ID)
|
||||
if err != nil {
|
||||
_ = tx.Rollback()
|
||||
return err
|
||||
}
|
||||
|
||||
err = s.UpdateOrderContents(tx, newOrder)
|
||||
if err != nil {
|
||||
_ = tx.Rollback()
|
||||
return err
|
||||
}
|
||||
|
||||
err = tx.Commit()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Store) SaveOrder(newOrder models.Order) (int, error) {
|
||||
if newOrder.ID != 0 {
|
||||
return newOrder.ID, s.UpdateOrder(newOrder)
|
||||
}
|
||||
query := "insert into customer_order(name, surname, phone, location, order_date, delivery_date, status, paid) values (?, ?, ?, ?, ?, ?, ?, ?);"
|
||||
|
||||
tx, err := s.db.Begin()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
accepted := newOrder.Accepted.Format("2006-01-02 15:04")
|
||||
date := newOrder.Date.Format("2006-01-02 15:04")
|
||||
result, err := tx.Exec(query, newOrder.Name, newOrder.Surname, newOrder.Phone, newOrder.Location, accepted, date, newOrder.Status, newOrder.Paid)
|
||||
if err != nil {
|
||||
_ = tx.Rollback()
|
||||
return 0, err
|
||||
}
|
||||
|
||||
id, err := result.LastInsertId()
|
||||
if err != nil {
|
||||
_ = tx.Rollback()
|
||||
return newOrder.ID, err
|
||||
}
|
||||
newOrder.ID = int(id)
|
||||
|
||||
err = s.UpdateOrderContents(tx, newOrder)
|
||||
if err != nil {
|
||||
_ = tx.Rollback()
|
||||
return newOrder.ID, err
|
||||
}
|
||||
|
||||
err = tx.Commit()
|
||||
return newOrder.ID, err
|
||||
}
|
||||
|
|
@ -1,58 +0,0 @@
|
|||
package store
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/Bronku/iroon/models"
|
||||
)
|
||||
|
||||
func (s *Store) AddSession(token, userName string, expiration time.Time) error {
|
||||
query := "insert into session (token, user, expiration) values(?, ?, ?)"
|
||||
_, err := s.db.Exec(query, token, userName, expiration.Format("2006-01-02 15:04"))
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *Store) RevokeSession(token string) error {
|
||||
query := "delete from session where token = ?;"
|
||||
_, err := s.db.Exec(query, token)
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *Store) CleanSessions() error {
|
||||
now := time.Now().Format("2006-01-02 15:04")
|
||||
query := "delete from session where expiration < ?;"
|
||||
_, err := s.db.Exec(query, now)
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *Store) GetSessions() (map[string]models.Token, error) {
|
||||
out := make(map[string]models.Token)
|
||||
err := s.CleanSessions()
|
||||
if err != nil {
|
||||
return out, err
|
||||
}
|
||||
query := "select token, user, expiration from session;"
|
||||
rows, err := s.db.Query(query)
|
||||
if err != nil {
|
||||
return out, err
|
||||
}
|
||||
defer rows.Close()
|
||||
for rows.Next() {
|
||||
var current models.Token
|
||||
var token string
|
||||
var expiration string
|
||||
err = rows.Scan(&token, ¤t.User, &expiration)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
current.Expiration, err = time.Parse("2006-01-02 15:04", expiration)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if token == "" {
|
||||
continue
|
||||
}
|
||||
out[token] = current
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
|
@ -1,44 +0,0 @@
|
|||
package store
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"log"
|
||||
|
||||
"github.com/Bronku/iroon/models"
|
||||
_ "github.com/knaka/go-sqlite3-fts5"
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
)
|
||||
|
||||
type Store struct {
|
||||
db *sql.DB
|
||||
cakes []models.Cake
|
||||
users map[string]models.User
|
||||
}
|
||||
|
||||
func OpenStore(filename string) *Store {
|
||||
var out Store
|
||||
var err error
|
||||
|
||||
out.db, err = sql.Open("sqlite3", filename)
|
||||
if err != nil {
|
||||
log.Fatal("Can't open the database", filename, err)
|
||||
}
|
||||
|
||||
out.loadMigrations()
|
||||
out.cakes, err = out.loadCakes()
|
||||
log.Println(out.cakes)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
out.users, err = out.loadUsers()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
return &out
|
||||
}
|
||||
|
||||
func (s *Store) Close() {
|
||||
if s.db != nil {
|
||||
s.db.Close()
|
||||
}
|
||||
}
|
||||
|
|
@ -1,293 +0,0 @@
|
|||
package store
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/Bronku/iroon/models"
|
||||
)
|
||||
|
||||
func TestStore(t *testing.T) {
|
||||
s := OpenStore("file:memdb1?mode=memory&cache=shared")
|
||||
defer s.Close()
|
||||
|
||||
// ensure db is empty
|
||||
cakes, err := s.GetCakes()
|
||||
if err != nil {
|
||||
t.Errorf("Error getting cakes: %v", err)
|
||||
}
|
||||
if len(cakes) != 0 {
|
||||
t.Errorf("Expected empty cakes list, got: %v", cakes)
|
||||
}
|
||||
orders, err := s.GetOrders(time.Now(), time.Now().Add(time.Hour*24))
|
||||
if err != nil {
|
||||
t.Errorf("Error getting orders: %v", err)
|
||||
}
|
||||
if len(orders) != 0 {
|
||||
t.Errorf("Expected empty orders list, got: %v", orders)
|
||||
}
|
||||
|
||||
// create new cake
|
||||
newCake := models.Cake{Name: "Chocolate Cake", Price: 2500}
|
||||
newCake.ID, err = s.SaveCake(newCake)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to save new cake: %v", err)
|
||||
}
|
||||
if newCake.ID <= 0 {
|
||||
t.Errorf("Expected positive cake ID, got: %d", newCake.ID)
|
||||
}
|
||||
|
||||
// create another cake
|
||||
anotherCake := models.Cake{Name: "Another Cake", Price: 3000}
|
||||
anotherCake.ID, err = s.SaveCake(anotherCake)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to save new cake: %v", err)
|
||||
}
|
||||
if anotherCake.ID <= 0 {
|
||||
t.Errorf("Expected positive cake ID, got: %d", anotherCake.ID)
|
||||
}
|
||||
|
||||
// Update Existing Cake
|
||||
newCake.Name = "Updated Cake"
|
||||
newCake.Price = 100
|
||||
newID, err := s.SaveCake(newCake)
|
||||
if newID != newCake.ID {
|
||||
t.Error("wrong id")
|
||||
}
|
||||
if err != nil {
|
||||
t.Error("error updating cake", err)
|
||||
}
|
||||
|
||||
// Update Non-existing Cake
|
||||
var updatedCake models.Cake
|
||||
updatedCake.ID = 10
|
||||
_, err = s.SaveCake(updatedCake)
|
||||
if err == nil {
|
||||
t.Error("did not return an error when attempted to update non existent cake")
|
||||
}
|
||||
|
||||
// get cakes
|
||||
cakes, err = s.GetCakes()
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to get all cakes after creation: %v", err)
|
||||
}
|
||||
if len(cakes) != 2 {
|
||||
t.Error("Expected two cakes returned, got ", len(cakes))
|
||||
}
|
||||
for i, c := range cakes {
|
||||
if c.ID == newCake.ID {
|
||||
newCake.Amount = i
|
||||
}
|
||||
if c.ID == anotherCake.ID {
|
||||
anotherCake.Amount = i
|
||||
}
|
||||
}
|
||||
if newCake.Amount == -1 {
|
||||
t.Error("not found newCake")
|
||||
}
|
||||
if cakes[newCake.Amount].ID != newCake.ID || cakes[newCake.Amount].Name != newCake.Name || cakes[newCake.Amount].Price != newCake.Price {
|
||||
t.Errorf("Want %v\nGot %v", newCake, cakes[newCake.Amount])
|
||||
}
|
||||
if anotherCake.Amount == -1 {
|
||||
t.Error("not found anotherCake")
|
||||
}
|
||||
if cakes[anotherCake.Amount].ID != anotherCake.ID || cakes[anotherCake.Amount].Name != anotherCake.Name || cakes[anotherCake.Amount].Price != anotherCake.Price {
|
||||
t.Errorf("Want %v\nGot %v", anotherCake, cakes)
|
||||
}
|
||||
|
||||
selectedCake, err := s.GetCake(newCake.ID)
|
||||
if err != nil {
|
||||
t.Error("error getting a cake", err)
|
||||
}
|
||||
if selectedCake.ID != newCake.ID || selectedCake.Name != newCake.Name || selectedCake.Price != newCake.Price {
|
||||
t.Errorf("Want %v\nGot %v", newCake, selectedCake)
|
||||
}
|
||||
|
||||
selectedCake, err = s.GetCake(-1)
|
||||
if err == nil {
|
||||
t.Error("no error getting a non-existent cake")
|
||||
}
|
||||
|
||||
// create new order
|
||||
now := time.Now()
|
||||
newOrder := models.Order{
|
||||
Name: "John",
|
||||
Surname: "Doe",
|
||||
Phone: "123-456-7890",
|
||||
Location: "Some Location",
|
||||
Accepted: now,
|
||||
Date: now.Add(time.Hour * 24),
|
||||
Status: "Pending",
|
||||
Paid: 1000,
|
||||
Cakes: []models.Cake{{ID: newCake.ID, Amount: 2}, {ID: anotherCake.ID, Amount: 10}},
|
||||
}
|
||||
newOrder.ID, err = s.SaveOrder(newOrder)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to save new order: %v", err)
|
||||
}
|
||||
if newOrder.ID <= 0 {
|
||||
t.Errorf("Expected positive order ID, got: %d", newOrder.ID)
|
||||
}
|
||||
|
||||
// create another order
|
||||
anotherOrder := models.Order{
|
||||
Name: "Jane",
|
||||
Surname: "Doe",
|
||||
Phone: "123-456-7890",
|
||||
Location: "New Location",
|
||||
Accepted: now,
|
||||
Date: now.Add(time.Hour * 192),
|
||||
Status: "Accepted",
|
||||
Paid: 1500,
|
||||
Cakes: []models.Cake{{ID: newCake.ID, Amount: 100}},
|
||||
}
|
||||
anotherOrder.ID, err = s.SaveOrder(anotherOrder)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to save new order: %v", err)
|
||||
}
|
||||
if anotherOrder.ID <= 0 {
|
||||
t.Errorf("Expected positive order ID, got: %d", anotherOrder.ID)
|
||||
}
|
||||
|
||||
// update existing order
|
||||
newOrder.Name = "James"
|
||||
newOrder.Status = "Done"
|
||||
newOrder.Cakes = newOrder.Cakes[1:]
|
||||
newID, err = s.SaveOrder(newOrder)
|
||||
if newID != newOrder.ID {
|
||||
t.Error("wrong id")
|
||||
}
|
||||
if err != nil {
|
||||
t.Error("error updating order", err)
|
||||
}
|
||||
|
||||
// update non existing order
|
||||
var updatedOrder models.Order
|
||||
updatedOrder.ID = 10
|
||||
_, err = s.SaveOrder(updatedOrder)
|
||||
if err == nil {
|
||||
t.Error("did not return an error when attempted to update non existent cake")
|
||||
}
|
||||
|
||||
// get orders
|
||||
orders, err = s.GetOrders(time.Time{}, time.Time{})
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to get all orders after creation: %v", err)
|
||||
}
|
||||
if len(orders) != 2 {
|
||||
t.Error("Expected two orders returned, got ", len(orders))
|
||||
}
|
||||
newOrderPos := -1
|
||||
anotherOrderPos := -1
|
||||
for i, o := range orders {
|
||||
if o.ID == newOrder.ID {
|
||||
newOrderPos = i
|
||||
}
|
||||
if o.ID == anotherOrder.ID {
|
||||
anotherOrderPos = i
|
||||
}
|
||||
}
|
||||
if newOrderPos == -1 {
|
||||
t.Error("not found newOrder")
|
||||
}
|
||||
if newOrder.Name != orders[newOrderPos].Name ||
|
||||
newOrder.Surname != orders[newOrderPos].Surname ||
|
||||
newOrder.Phone != orders[newOrderPos].Phone ||
|
||||
newOrder.Status != orders[newOrderPos].Status ||
|
||||
newOrder.Location != orders[newOrderPos].Location ||
|
||||
newOrder.Accepted.Format("2006-01-02 15:04") != orders[newOrderPos].Accepted.Format("2006-01-02 15:04") ||
|
||||
newOrder.Date.Format("2006-01-02 15:04") != orders[newOrderPos].Date.Format("2006-01-02 15:04") {
|
||||
t.Errorf("Want %v\nGot %v", newOrder, orders[newOrderPos])
|
||||
}
|
||||
if !areCakeSlicesEqual(newOrder.Cakes, orders[newOrderPos].Cakes) {
|
||||
t.Errorf("Want %v\nGot %v", newOrder.Cakes, orders[newOrderPos].Cakes)
|
||||
}
|
||||
|
||||
if anotherOrderPos == -1 {
|
||||
t.Error("not found anotherOrder")
|
||||
}
|
||||
if anotherOrder.Name != orders[anotherOrderPos].Name ||
|
||||
anotherOrder.Surname != orders[anotherOrderPos].Surname ||
|
||||
anotherOrder.Phone != orders[anotherOrderPos].Phone ||
|
||||
anotherOrder.Status != orders[anotherOrderPos].Status ||
|
||||
anotherOrder.Location != orders[anotherOrderPos].Location ||
|
||||
anotherOrder.Accepted.Format("2006-01-02 15:04") != orders[anotherOrderPos].Accepted.Format("2006-01-02 15:04") ||
|
||||
anotherOrder.Date.Format("2006-01-02 15:04") != orders[anotherOrderPos].Date.Format("2006-01-02 15:04") {
|
||||
t.Errorf("Want %v\nGot %v", anotherOrder, orders[anotherOrderPos])
|
||||
}
|
||||
|
||||
if !areCakeSlicesEqual(anotherOrder.Cakes, orders[anotherOrderPos].Cakes) {
|
||||
t.Errorf("Want %v\nGot %v", anotherOrder.Cakes, orders[anotherOrderPos].Cakes)
|
||||
}
|
||||
|
||||
selectedOrder, err := s.GetOrder(newOrder.ID)
|
||||
if err != nil {
|
||||
t.Error("error getting an order by id ", err)
|
||||
}
|
||||
if newOrder.Name != selectedOrder.Name ||
|
||||
newOrder.Surname != selectedOrder.Surname ||
|
||||
newOrder.Phone != selectedOrder.Phone ||
|
||||
newOrder.Status != selectedOrder.Status ||
|
||||
newOrder.Location != selectedOrder.Location ||
|
||||
newOrder.Accepted.Format("2006-01-02 15:04") != selectedOrder.Accepted.Format("2006-01-02 15:04") ||
|
||||
newOrder.Date.Format("2006-01-02 15:04") != selectedOrder.Date.Format("2006-01-02 15:04") {
|
||||
t.Errorf("Want %v\nGot %v", newOrder, selectedOrder)
|
||||
}
|
||||
|
||||
if !areCakeSlicesEqual(newOrder.Cakes, selectedOrder.Cakes) {
|
||||
t.Errorf("Want %v\nGot %v", newOrder.Cakes, selectedOrder.Cakes)
|
||||
}
|
||||
|
||||
_, err = s.GetOrder(-1)
|
||||
if err == nil {
|
||||
t.Error("no error getting invalid order")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSliceComparison(t *testing.T) {
|
||||
a := []models.Cake{
|
||||
{Name: "ok", Price: 100, ID: 12, Amount: 10},
|
||||
{Name: "ok", Price: 11, ID: 13, Amount: 1},
|
||||
{Name: "ok", Price: 100, ID: 10, Amount: 11},
|
||||
}
|
||||
|
||||
b := []models.Cake{
|
||||
{Name: "okas", Price: 110, ID: 12, Amount: 10},
|
||||
{Name: "oksa", Price: 11, ID: 13, Amount: 1},
|
||||
{Name: "oasdk", Price: 101, ID: 10, Amount: 11},
|
||||
}
|
||||
if !areCakeSlicesEqual(a, b) {
|
||||
t.Error("aren't equal")
|
||||
}
|
||||
}
|
||||
|
||||
func areCakesEqual(a, b models.Cake) bool {
|
||||
return a.ID == b.ID && a.Amount == b.Amount
|
||||
}
|
||||
|
||||
func areCakeSlicesEqual(a, b []models.Cake) bool {
|
||||
if len(a) != len(b) {
|
||||
return false
|
||||
}
|
||||
|
||||
matched := make([]bool, len(a))
|
||||
|
||||
mainLoop:
|
||||
for _, e1 := range a {
|
||||
for i, e2 := range b {
|
||||
if matched[i] {
|
||||
continue
|
||||
}
|
||||
if areCakesEqual(e1, e2) {
|
||||
matched[i] = true
|
||||
continue mainLoop
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, e := range matched {
|
||||
if e == false {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
|
@ -1,48 +0,0 @@
|
|||
package store
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/Bronku/iroon/crypto"
|
||||
"github.com/Bronku/iroon/models"
|
||||
)
|
||||
|
||||
func (s *Store) AddUser(login, password string) error {
|
||||
_, exists := s.GetUser(login)
|
||||
if exists {
|
||||
return errors.New("the user already exists")
|
||||
}
|
||||
query := "insert into user (login, password, salt) values(?, ?, ?)"
|
||||
salt := crypto.GenerateKey()
|
||||
hash := crypto.PasswordHash(password, salt)
|
||||
_, err := s.db.Exec(query, login, hash, salt)
|
||||
if err == nil {
|
||||
s.users[login] = models.User{Password: hash, Salt: salt}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *Store) loadUsers() (map[string]models.User, error) {
|
||||
out := make(map[string]models.User)
|
||||
query := "select login, password, salt from user;"
|
||||
rows, err := s.db.Query(query)
|
||||
if err != nil {
|
||||
return out, err
|
||||
}
|
||||
defer rows.Close()
|
||||
for rows.Next() {
|
||||
var current models.User
|
||||
var login string
|
||||
err = rows.Scan(&login, ¤t.Password, ¤t.Salt)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
out[login] = current
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (s *Store) GetUser(login string) (models.User, bool) {
|
||||
value, ok := s.users[login]
|
||||
return value, ok
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue