From 28b7c2203ae72cb42d4a97440812b62c7118c583 Mon Sep 17 00:00:00 2001 From: meskio <meskio@sindominio.net> Date: Tue, 29 Sep 2020 13:31:52 +0200 Subject: [PATCH] Add topup transactions --- api/api.go | 3 ++- api/auth.go | 22 +++++++++++++++ api/purchase.go | 25 ++--------------- api/purchase_test.go | 2 +- api/topup.go | 64 ++++++++++++++++++++++++++++++++++++++++++++ api/topup_test.go | 50 ++++++++++++++++++++++++++++++++++ api/transaction.go | 22 +++++++++++++++ src/Head.js | 11 +++++--- 8 files changed, 170 insertions(+), 29 deletions(-) create mode 100644 api/topup.go create mode 100644 api/topup_test.go diff --git a/api/api.go b/api/api.go index 79f2ffe..b5f3287 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{}, &Purchase{}, &Transaction{}) + db.AutoMigrate(&Member{}, &Product{}, &Purchase{}, &Topup{}, &Transaction{}) return db, err } @@ -56,5 +56,6 @@ func Init(dbPath string, signKey string, r *mux.Router) error { r.HandleFunc("/transaction/mine", a.authNum(a.getTransactionsByMember)).Methods("GET") r.HandleFunc("/purchase", a.authNum(a.AddPurchase)).Methods("POST") + r.HandleFunc("/topup", a.authAdminNum(a.AddTopup)).Methods("POST") return nil } diff --git a/api/auth.go b/api/auth.go index 66930ee..a31bea5 100644 --- a/api/auth.go +++ b/api/auth.go @@ -141,6 +141,28 @@ func (a *api) authAdmin(fn func(http.ResponseWriter, *http.Request)) func(http.R } } +func (a *api) authAdminNum(fn func(int, http.ResponseWriter, *http.Request)) func(http.ResponseWriter, *http.Request) { + return func(w http.ResponseWriter, req *http.Request) { + token := req.Header.Get("x-authentication") + ok, claims := a.validateToken(token) + if !ok { + w.WriteHeader(http.StatusUnauthorized) + return + } + role, ok := claims["role"].(string) + if !ok || role != "admin" { + w.WriteHeader(http.StatusUnauthorized) + return + } + num, ok := claims["num"].(float64) + if !ok { + w.WriteHeader(http.StatusUnauthorized) + return + } + fn(int(num), w, req) + } +} + func (a *api) authNumRole(fn func(int, string, http.ResponseWriter, *http.Request)) func(http.ResponseWriter, *http.Request) { return func(w http.ResponseWriter, req *http.Request) { token := req.Header.Get("x-authentication") diff --git a/api/purchase.go b/api/purchase.go index 86b3b25..2d0e9c2 100644 --- a/api/purchase.go +++ b/api/purchase.go @@ -45,7 +45,7 @@ func (a *api) AddPurchase(num int, w http.ResponseWriter, req *http.Request) { return } - httpStatus := a.substractMemberBalance(num, total) + httpStatus := a.updateMemberBalance(num, -total) if httpStatus != http.StatusOK { w.WriteHeader(httpStatus) return @@ -56,7 +56,7 @@ func (a *api) AddPurchase(num int, w http.ResponseWriter, req *http.Request) { Date: time.Now(), Purchase: purchase, Type: "purchase", - Total: total, + Total: -total, } err = a.db.Create(&transaction).Error if err != nil { @@ -83,24 +83,3 @@ func (a *api) AddPurchase(num int, w http.ResponseWriter, req *http.Request) { } } - -func (a *api) substractMemberBalance(num int, total int) int { - var member Member - err := a.db.Where("num = ?", num).Find(&member).Error - if err != nil { - log.Printf("Can't find member %d: %v", num, err) - return http.StatusNotAcceptable - } - if member.Balance < total { - log.Printf("Member %d don't have enough money (%d-%d)", num, member.Balance, total) - return http.StatusBadRequest - } - err = a.db.Model(&Member{}). - Where("num = ?", num). - Update("balance", gorm.Expr("balance - ?", total)).Error - if err != nil { - log.Printf("Can't update update member balance %d-%d: %v", num, total, err) - return http.StatusNotAcceptable - } - return http.StatusOK -} diff --git a/api/purchase_test.go b/api/purchase_test.go index e176e19..3e979cb 100644 --- a/api/purchase_test.go +++ b/api/purchase_test.go @@ -30,7 +30,7 @@ func TestPurchaseAddListMine(t *testing.T) { if len(transactions) != 1 { t.Fatal("Wrong number of transactions", len(transactions), transactions) } - if transactions[0].Total != testProduct.Price*products[0].Ammount { + if transactions[0].Total != -testProduct.Price*products[0].Ammount { t.Error("Wrong total:", transactions[0].Total) } if len(transactions[0].Purchase) != 1 { diff --git a/api/topup.go b/api/topup.go new file mode 100644 index 0000000..cc6397e --- /dev/null +++ b/api/topup.go @@ -0,0 +1,64 @@ +package api + +import ( + "encoding/json" + "log" + "net/http" + "time" + + "gorm.io/gorm" +) + +type Topup struct { + gorm.Model `json:"-"` + TransactionID int `json:"-" gorm:"column:transaction"` + MemberNum int `json:"member" gorm:"column:member"` + Member Member `json:"-" gorm:"foreignKey:MemberNum;references:Num"` + Comment string `json:"comment"` +} + +func (a *api) AddTopup(adminNum int, w http.ResponseWriter, req *http.Request) { + var topup struct { + Member int `json:"member"` + Comment string `json:"comment"` + Ammount int `json:"ammount"` + } + err := json.NewDecoder(req.Body).Decode(&topup) + if err != nil { + log.Printf("Can't parse topup: %v", err) + w.WriteHeader(http.StatusInternalServerError) + return + } + + httpStatus := a.updateMemberBalance(topup.Member, topup.Ammount) + if httpStatus != http.StatusOK { + w.WriteHeader(httpStatus) + return + } + + transaction := Transaction{ + MemberNum: topup.Member, + Date: time.Now(), + Topup: Topup{ + MemberNum: adminNum, + Comment: topup.Comment, + }, + Type: "toupup", + Total: topup.Ammount, + } + err = a.db.Create(&transaction).Error + if err != nil { + log.Printf("Can't create topup: %v\n%v", err, transaction) + w.WriteHeader(http.StatusInternalServerError) + return + } + + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusCreated) + err = json.NewEncoder(w).Encode(transaction) + if err != nil { + log.Printf("Can't encode added topup: %v", err) + w.WriteHeader(http.StatusInternalServerError) + } + +} diff --git a/api/topup_test.go b/api/topup_test.go new file mode 100644 index 0000000..e58883f --- /dev/null +++ b/api/topup_test.go @@ -0,0 +1,50 @@ +package api + +import ( + "net/http" + "testing" +) + +func TestTopupAddListMine(t *testing.T) { + tapi := newTestAPI(t) + defer tapi.close() + tapi.addTestMember() + tapi.addTestProducts() + + topup := map[string]interface{}{ + "member": testMember.Num, + "comment": "my topup", + "ammount": 20, + } + resp := tapi.do("POST", "/topup", topup, nil) + if resp.StatusCode != http.StatusCreated { + t.Fatal("Can't create topup:", resp.Status) + } + var transactions []Transaction + resp = tapi.do("GET", "/transaction/mine", nil, &transactions) + if resp.StatusCode != http.StatusOK { + t.Fatal("Can't get transactions:", resp.Status) + } + + if len(transactions) != 1 { + t.Fatal("Wrong number of transactions", len(transactions), transactions) + } + if transactions[0].Total != 20 { + t.Error("Wrong total:", transactions[0].Total) + } + if transactions[0].Topup.MemberNum != testMember.Num { + t.Error("Wrong topup member:", transactions[0].Topup.MemberNum) + } + if transactions[0].Topup.Comment != "my topup" { + t.Error("Wrong topup comment:", transactions[0].Topup.Comment) + } + + var member Member + resp = tapi.do("GET", "/member/10", nil, &member) + if resp.StatusCode != http.StatusOK { + t.Error("Can't find the member:", resp.Status) + } + if member.Balance != testMember.Balance+20 { + t.Error("Wrong product balance:", member.Balance) + } +} diff --git a/api/transaction.go b/api/transaction.go index 74b3356..c33a2b0 100644 --- a/api/transaction.go +++ b/api/transaction.go @@ -21,6 +21,7 @@ type Transaction struct { Type string `json:"type"` Purchase []Purchase `json:"purchase"` + Topup Topup `json:"topup"` } func (a *api) ListTransactions(w http.ResponseWriter, req *http.Request) { @@ -99,3 +100,24 @@ func (a *api) getTransactionsByMember(num int, w http.ResponseWriter, req *http. w.WriteHeader(http.StatusInternalServerError) } } + +func (a *api) updateMemberBalance(num int, ammount int) int { + var member Member + err := a.db.Where("num = ?", num).Find(&member).Error + if err != nil { + log.Printf("Can't find member %d: %v", num, err) + return http.StatusNotAcceptable + } + if member.Balance < -ammount { + log.Printf("Member %d don't have enough money (%d-%d)", num, member.Balance, ammount) + return http.StatusBadRequest + } + err = a.db.Model(&Member{}). + Where("num = ?", num). + Update("balance", gorm.Expr("balance + ?", ammount)).Error + if err != nil { + log.Printf("Can't update update member balance %d-%d: %v", num, ammount, err) + return http.StatusNotAcceptable + } + return http.StatusOK +} diff --git a/src/Head.js b/src/Head.js index 69aa158..f300d89 100644 --- a/src/Head.js +++ b/src/Head.js @@ -1,6 +1,6 @@ import React, { useContext } from 'react'; import mano from './mano.svg'; -import { Navbar, Nav, Button, Form } from 'react-bootstrap'; +import { Navbar, Nav, NavDropdown, Button, Form } from 'react-bootstrap'; import AuthContext from './AuthContext'; function Head(props) { @@ -8,9 +8,12 @@ function Head(props) { let adminNav; if (auth.role === "admin") { - adminNav = <Nav.Link href="/members">Socias</Nav.Link>; + adminNav = ( + <NavDropdown title="Admin" id="admin"> + <Nav.Link href="/members">Socias</Nav.Link> + </NavDropdown> + ); } - return ( <Navbar bg="light"> <Navbar.Brand href="/"> @@ -21,8 +24,8 @@ function Head(props) { <Nav className="mr-auto"> <Nav.Link href="/">Dashboard</Nav.Link> <Nav.Link href="/products">Productos</Nav.Link> - {adminNav} </Nav> + {adminNav} <Form inline> <Button variant="outline-success" -- GitLab