From 46b228c046648fd8c7ef5497d070078045d75d17 Mon Sep 17 00:00:00 2001
From: meskio <meskio@sindominio.net>
Date: Sun, 27 Sep 2020 20:16:32 +0200
Subject: [PATCH] Work with transactions

---
 api/api.go           |  11 +++--
 api/member_test.go   |   2 +-
 api/purchase.go      | 111 +++++++------------------------------------
 api/purchase_test.go |  28 +++++------
 api/transaction.go   |  94 ++++++++++++++++++++++++++++++++++++
 5 files changed, 132 insertions(+), 114 deletions(-)
 create mode 100644 api/transaction.go

diff --git a/api/api.go b/api/api.go
index 21006aa..6c32111 100644
--- a/api/api.go
+++ b/api/api.go
@@ -19,7 +19,7 @@ func initDB(dbPath string) (*gorm.DB, error) {
 		return nil, err
 	}
 
-	db.AutoMigrate(&Member{}, &Product{}, &PurchasedProduct{}, &Purchase{})
+	db.AutoMigrate(&Member{}, &Product{}, &Purchase{}, &Transaction{})
 	return db, err
 }
 
@@ -41,7 +41,7 @@ func Init(dbPath string, signKey string, r *mux.Router) error {
 	r.HandleFunc("/member/{num:[0-9]+}", a.auth(a.GetMember)).Methods("GET")
 	r.HandleFunc("/member/{num:[0-9]+}", a.auth(a.UpdateMember)).Methods("PUT")
 	r.HandleFunc("/member/{num:[0-9]+}", a.auth(a.DeleteMember)).Methods("DELETE")
-	r.HandleFunc("/member/{num:[0-9]+}/purchase", a.auth(a.GetMemberPurchases)).Methods("GET")
+	r.HandleFunc("/member/{num:[0-9]+}/purchase", a.auth(a.GetMemberTransactions)).Methods("GET")
 
 	r.HandleFunc("/product", a.auth(a.ListProducts)).Methods("GET")
 	r.HandleFunc("/product", a.auth(a.AddProduct)).Methods("POST")
@@ -49,9 +49,10 @@ func Init(dbPath string, signKey string, r *mux.Router) error {
 	r.HandleFunc("/product/{code:[0-9]+}", a.auth(a.UpdateProduct)).Methods("PUT")
 	r.HandleFunc("/product/{code:[0-9]+}", a.auth(a.DeleteProduct)).Methods("DELETE")
 
-	r.HandleFunc("/purchase", a.auth(a.ListPurchases)).Methods("GET")
+	r.HandleFunc("/transaction", a.auth(a.ListTransactions)).Methods("GET")
+	r.HandleFunc("/transaction/{id:[0-9]+}", a.auth(a.GetTransaction)).Methods("GET")
+	r.HandleFunc("/transaction/mine", a.authNum(a.getTransactionsByMember)).Methods("GET")
+
 	r.HandleFunc("/purchase", a.authNum(a.AddPurchase)).Methods("POST")
-	r.HandleFunc("/purchase/{id:[0-9]+}", a.auth(a.GetPurchase)).Methods("GET")
-	r.HandleFunc("/purchase/mine", a.authNum(a.getPurchasesByMember)).Methods("GET")
 	return nil
 }
diff --git a/api/member_test.go b/api/member_test.go
index c000462..679970c 100644
--- a/api/member_test.go
+++ b/api/member_test.go
@@ -9,7 +9,7 @@ var testMember = Member{
 	Num:     10,
 	Name:    "foo",
 	Email:   "foo@example.com",
-	Balance: 2000,
+	Balance: 10000,
 }
 
 func TestMemberAddList(t *testing.T) {
diff --git a/api/purchase.go b/api/purchase.go
index b2cfa41..d142ed1 100644
--- a/api/purchase.go
+++ b/api/purchase.go
@@ -4,58 +4,30 @@ import (
 	"encoding/json"
 	"log"
 	"net/http"
-	"strconv"
 	"time"
 
-	"github.com/gorilla/mux"
 	"gorm.io/gorm"
 )
 
 type Purchase struct {
-	gorm.Model
-	MemberNum int                `json:"member" gorm:"column:member"`
-	Member    Member             `json:"-" gorm:"foreignKey:MemberNum;references:Num"`
-	Date      time.Time          `json:"date"`
-	Total     int                `json:"total"`
-	Products  []PurchasedProduct `json:"products"`
-}
-
-type PurchasedProduct struct {
-	gorm.Model  `json:"-"`
-	PurchaseID  int     `json:"-" gorm:"column:purchase"`
-	ProductCode int     `json:"product" gorm:"column:product"`
-	Product     Product `gorm:"foreignKey:ProductCode;references:Code"`
-	Price       int     `json:"price"`
-	Ammount     int     `json:"ammount"`
-}
-
-func (a *api) ListPurchases(w http.ResponseWriter, req *http.Request) {
-	var purchases []Purchase
-	err := a.db.Preload("Products.Product").Find(&purchases).Error
-	if err != nil {
-		log.Printf("Can't list purchases: %v", err)
-		w.WriteHeader(http.StatusInternalServerError)
-		return
-	}
-	w.Header().Set("Content-Type", "application/json")
-	w.WriteHeader(http.StatusOK)
-	err = json.NewEncoder(w).Encode(purchases)
-	if err != nil {
-		log.Printf("Can't encode purchases: %v", err)
-		w.WriteHeader(http.StatusInternalServerError)
-	}
+	gorm.Model    `json:"-"`
+	TransactionID int     `json:"-" gorm:"column:transaction"`
+	ProductCode   int     `json:"product" gorm:"column:product"`
+	Product       Product `gorm:"foreignKey:ProductCode;references:Code"`
+	Price         int     `json:"price"`
+	Ammount       int     `json:"ammount"`
 }
 
 func (a *api) AddPurchase(num int, w http.ResponseWriter, req *http.Request) {
-	var products []PurchasedProduct
-	err := json.NewDecoder(req.Body).Decode(&products)
+	var purchase []Purchase
+	err := json.NewDecoder(req.Body).Decode(&purchase)
 	if err != nil {
 		log.Printf("Can't create purchase: %v", err)
 		w.WriteHeader(http.StatusInternalServerError)
 		return
 	}
 	total := 0
-	for i, p := range products {
+	for i, p := range purchase {
 		var product Product
 		err = a.db.Where("code = ?", p.ProductCode).First(&product).Error
 		if err != nil {
@@ -65,7 +37,7 @@ func (a *api) AddPurchase(num int, w http.ResponseWriter, req *http.Request) {
 		}
 
 		total += product.Price * p.Ammount
-		products[i].Price = product.Price
+		purchase[i].Price = product.Price
 	}
 
 	var member Member
@@ -76,7 +48,7 @@ func (a *api) AddPurchase(num int, w http.ResponseWriter, req *http.Request) {
 		return
 	}
 	if member.Balance < total {
-		log.Printf("Member %d don't have enough money (%d-%d): %v", num, member.Balance, total, err)
+		log.Printf("Member %d don't have enough money (%d-%d)", num, member.Balance, total)
 		w.WriteHeader(http.StatusBadRequest)
 		return
 	}
@@ -89,20 +61,21 @@ func (a *api) AddPurchase(num int, w http.ResponseWriter, req *http.Request) {
 		return
 	}
 
-	purchase := Purchase{
+	transaction := Transaction{
 		MemberNum: num,
 		Date:      time.Now(),
-		Products:  products,
+		Purchase:  purchase,
+		Type:      "purchase",
 		Total:     total,
 	}
-	err = a.db.Create(&purchase).Error
+	err = a.db.Create(&transaction).Error
 	if err != nil {
 		log.Printf("Can't create purchase: %v\n%v", err, purchase)
 		w.WriteHeader(http.StatusInternalServerError)
 		return
 	}
 
-	for _, p := range products {
+	for _, p := range purchase {
 		err := a.db.Model(&Product{}).
 			Where("code = ?", p.ProductCode).
 			Update("stock", gorm.Expr("stock - ?", p.Ammount)).Error
@@ -113,60 +86,10 @@ func (a *api) AddPurchase(num int, w http.ResponseWriter, req *http.Request) {
 
 	w.Header().Set("Content-Type", "application/json")
 	w.WriteHeader(http.StatusCreated)
-	err = json.NewEncoder(w).Encode(purchase)
+	err = json.NewEncoder(w).Encode(transaction)
 	if err != nil {
 		log.Printf("Can't encode added purchase: %v", err)
 		w.WriteHeader(http.StatusInternalServerError)
 	}
 
 }
-
-func (a *api) GetPurchase(w http.ResponseWriter, req *http.Request) {
-	vars := mux.Vars(req)
-	var purchase Purchase
-	err := a.db.Where("id = ?", vars["id"]).
-		Preload("Products.Product").
-		First(&purchase).Error
-	if err != nil {
-		if err.Error() == "record not found" {
-			w.WriteHeader(http.StatusNotFound)
-			return
-		}
-		log.Printf("Can't get purchase %s: %v", vars["code"], err)
-		w.WriteHeader(http.StatusInternalServerError)
-		return
-	}
-	w.Header().Set("Content-Type", "application/json")
-	w.WriteHeader(http.StatusOK)
-	err = json.NewEncoder(w).Encode(purchase)
-	if err != nil {
-		log.Printf("Can't encode purchase: %v", err)
-		w.WriteHeader(http.StatusInternalServerError)
-		return
-	}
-}
-
-func (a *api) GetMemberPurchases(w http.ResponseWriter, req *http.Request) {
-	vars := mux.Vars(req)
-	num, _ := strconv.Atoi(vars["num"])
-	a.getPurchasesByMember(num, w, req)
-}
-
-func (a *api) getPurchasesByMember(num int, w http.ResponseWriter, req *http.Request) {
-	var purchases []Purchase
-	err := a.db.Where("member = ?", num).
-		Preload("Products.Product").
-		Find(&purchases).Error
-	if err != nil {
-		log.Printf("Can't list purchases: %v", err)
-		w.WriteHeader(http.StatusInternalServerError)
-		return
-	}
-	w.Header().Set("Content-Type", "application/json")
-	w.WriteHeader(http.StatusOK)
-	err = json.NewEncoder(w).Encode(purchases)
-	if err != nil {
-		log.Printf("Can't encode purchases: %v", err)
-		w.WriteHeader(http.StatusInternalServerError)
-	}
-}
diff --git a/api/purchase_test.go b/api/purchase_test.go
index 3bf4931..e176e19 100644
--- a/api/purchase_test.go
+++ b/api/purchase_test.go
@@ -11,7 +11,7 @@ func TestPurchaseAddListMine(t *testing.T) {
 	tapi.addTestMember()
 	tapi.addTestProducts()
 
-	products := []PurchasedProduct{
+	products := []Purchase{
 		{
 			ProductCode: testProduct.Code,
 			Ammount:     5,
@@ -21,26 +21,26 @@ func TestPurchaseAddListMine(t *testing.T) {
 	if resp.StatusCode != http.StatusCreated {
 		t.Fatal("Can't create purchase:", resp.Status)
 	}
-	var purchases []Purchase
-	resp = tapi.do("GET", "/purchase/mine", nil, &purchases)
+	var transactions []Transaction
+	resp = tapi.do("GET", "/transaction/mine", nil, &transactions)
 	if resp.StatusCode != http.StatusOK {
-		t.Fatal("Can't get purchases:", resp.Status)
+		t.Fatal("Can't get transactions:", resp.Status)
 	}
 
-	if len(purchases) != 1 {
-		t.Fatal("Wrong number of purchases", len(purchases), purchases)
+	if len(transactions) != 1 {
+		t.Fatal("Wrong number of transactions", len(transactions), transactions)
 	}
-	if purchases[0].Total != testProduct.Price*products[0].Ammount {
-		t.Error("Wrong total:", purchases[0].Total)
+	if transactions[0].Total != testProduct.Price*products[0].Ammount {
+		t.Error("Wrong total:", transactions[0].Total)
 	}
-	if len(purchases[0].Products) != 1 {
-		t.Fatal("Wrong number of products", len(purchases[0].Products), purchases[0].Products)
+	if len(transactions[0].Purchase) != 1 {
+		t.Fatal("Wrong number of products", len(transactions[0].Purchase), transactions[0].Purchase)
 	}
-	if purchases[0].Products[0].ProductCode != testProduct.Code {
-		t.Error("Wrong product code:", purchases[0].Products[0].ProductCode)
+	if transactions[0].Purchase[0].ProductCode != testProduct.Code {
+		t.Error("Wrong product code:", transactions[0].Purchase[0].ProductCode)
 	}
-	if purchases[0].Products[0].Price != testProduct.Price {
-		t.Error("Wrong product price:", purchases[0].Products[0].Price)
+	if transactions[0].Purchase[0].Price != testProduct.Price {
+		t.Error("Wrong product price:", transactions[0].Purchase[0].Price)
 	}
 
 	var product Product
diff --git a/api/transaction.go b/api/transaction.go
new file mode 100644
index 0000000..ee73e30
--- /dev/null
+++ b/api/transaction.go
@@ -0,0 +1,94 @@
+package api
+
+import (
+	"encoding/json"
+	"log"
+	"net/http"
+	"strconv"
+	"time"
+
+	"github.com/gorilla/mux"
+	"gorm.io/gorm"
+	"gorm.io/gorm/clause"
+)
+
+type Transaction struct {
+	gorm.Model
+	MemberNum int       `json:"-" gorm:"column:member"`
+	Member    Member    `json:"member" gorm:"foreignKey:MemberNum;references:Num"`
+	Date      time.Time `json:"date"`
+	Total     int       `json:"total"`
+	Type      string    `json:"type"`
+
+	Purchase []Purchase `json:"purchase"`
+}
+
+func (a *api) ListTransactions(w http.ResponseWriter, req *http.Request) {
+	var transactions []Transaction
+	err := a.db.Preload("Purchase.Product").
+		Preload(clause.Associations).
+		Find(&transactions).Error
+	if err != nil {
+		log.Printf("Can't list transactions: %v", err)
+		w.WriteHeader(http.StatusInternalServerError)
+		return
+	}
+	w.Header().Set("Content-Type", "application/json")
+	w.WriteHeader(http.StatusOK)
+	err = json.NewEncoder(w).Encode(transactions)
+	if err != nil {
+		log.Printf("Can't encode transactions: %v", err)
+		w.WriteHeader(http.StatusInternalServerError)
+	}
+}
+
+func (a *api) GetTransaction(w http.ResponseWriter, req *http.Request) {
+	vars := mux.Vars(req)
+	var transaction Transaction
+	err := a.db.Preload("Purchase.Product").
+		Preload(clause.Associations).
+		First(&transaction, vars["id"]).Error
+	if err != nil {
+		if err.Error() == "record not found" {
+			w.WriteHeader(http.StatusNotFound)
+			return
+		}
+		log.Printf("Can't get transaction %s: %v", vars["code"], err)
+		w.WriteHeader(http.StatusInternalServerError)
+		return
+	}
+	w.Header().Set("Content-Type", "application/json")
+	w.WriteHeader(http.StatusOK)
+	err = json.NewEncoder(w).Encode(transaction)
+	if err != nil {
+		log.Printf("Can't encode transaction: %v", err)
+		w.WriteHeader(http.StatusInternalServerError)
+		return
+	}
+}
+
+func (a *api) GetMemberTransactions(w http.ResponseWriter, req *http.Request) {
+	vars := mux.Vars(req)
+	num, _ := strconv.Atoi(vars["num"])
+	a.getTransactionsByMember(num, w, req)
+}
+
+func (a *api) getTransactionsByMember(num int, w http.ResponseWriter, req *http.Request) {
+	var transactions []Transaction
+	err := a.db.Where("member = ?", num).
+		Preload("Purchase.Product").
+		Preload(clause.Associations).
+		Find(&transactions).Error
+	if err != nil {
+		log.Printf("Can't list transactions: %v", err)
+		w.WriteHeader(http.StatusInternalServerError)
+		return
+	}
+	w.Header().Set("Content-Type", "application/json")
+	w.WriteHeader(http.StatusOK)
+	err = json.NewEncoder(w).Encode(transactions)
+	if err != nil {
+		log.Printf("Can't encode transactions: %v", err)
+		w.WriteHeader(http.StatusInternalServerError)
+	}
+}
-- 
GitLab