Skip to content
Snippets Groups Projects
Commit 28b7c220 authored by meskio's avatar meskio :tent:
Browse files

Add topup transactions

parent 3fe7f6d2
No related branches found
No related tags found
No related merge requests found
...@@ -19,7 +19,7 @@ func initDB(dbPath string) (*gorm.DB, error) { ...@@ -19,7 +19,7 @@ func initDB(dbPath string) (*gorm.DB, error) {
return nil, err return nil, err
} }
db.AutoMigrate(&Member{}, &Product{}, &Purchase{}, &Transaction{}) db.AutoMigrate(&Member{}, &Product{}, &Purchase{}, &Topup{}, &Transaction{})
return db, err return db, err
} }
...@@ -56,5 +56,6 @@ func Init(dbPath string, signKey string, r *mux.Router) error { ...@@ -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("/transaction/mine", a.authNum(a.getTransactionsByMember)).Methods("GET")
r.HandleFunc("/purchase", a.authNum(a.AddPurchase)).Methods("POST") r.HandleFunc("/purchase", a.authNum(a.AddPurchase)).Methods("POST")
r.HandleFunc("/topup", a.authAdminNum(a.AddTopup)).Methods("POST")
return nil return nil
} }
...@@ -141,6 +141,28 @@ func (a *api) authAdmin(fn func(http.ResponseWriter, *http.Request)) func(http.R ...@@ -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) { 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) { return func(w http.ResponseWriter, req *http.Request) {
token := req.Header.Get("x-authentication") token := req.Header.Get("x-authentication")
......
...@@ -45,7 +45,7 @@ func (a *api) AddPurchase(num int, w http.ResponseWriter, req *http.Request) { ...@@ -45,7 +45,7 @@ func (a *api) AddPurchase(num int, w http.ResponseWriter, req *http.Request) {
return return
} }
httpStatus := a.substractMemberBalance(num, total) httpStatus := a.updateMemberBalance(num, -total)
if httpStatus != http.StatusOK { if httpStatus != http.StatusOK {
w.WriteHeader(httpStatus) w.WriteHeader(httpStatus)
return return
...@@ -56,7 +56,7 @@ func (a *api) AddPurchase(num int, w http.ResponseWriter, req *http.Request) { ...@@ -56,7 +56,7 @@ func (a *api) AddPurchase(num int, w http.ResponseWriter, req *http.Request) {
Date: time.Now(), Date: time.Now(),
Purchase: purchase, Purchase: purchase,
Type: "purchase", Type: "purchase",
Total: total, Total: -total,
} }
err = a.db.Create(&transaction).Error err = a.db.Create(&transaction).Error
if err != nil { if err != nil {
...@@ -83,24 +83,3 @@ func (a *api) AddPurchase(num int, w http.ResponseWriter, req *http.Request) { ...@@ -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
}
...@@ -30,7 +30,7 @@ func TestPurchaseAddListMine(t *testing.T) { ...@@ -30,7 +30,7 @@ func TestPurchaseAddListMine(t *testing.T) {
if len(transactions) != 1 { if len(transactions) != 1 {
t.Fatal("Wrong number of transactions", len(transactions), transactions) 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) t.Error("Wrong total:", transactions[0].Total)
} }
if len(transactions[0].Purchase) != 1 { if len(transactions[0].Purchase) != 1 {
......
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)
}
}
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)
}
}
...@@ -21,6 +21,7 @@ type Transaction struct { ...@@ -21,6 +21,7 @@ type Transaction struct {
Type string `json:"type"` Type string `json:"type"`
Purchase []Purchase `json:"purchase"` Purchase []Purchase `json:"purchase"`
Topup Topup `json:"topup"`
} }
func (a *api) ListTransactions(w http.ResponseWriter, req *http.Request) { 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. ...@@ -99,3 +100,24 @@ func (a *api) getTransactionsByMember(num int, w http.ResponseWriter, req *http.
w.WriteHeader(http.StatusInternalServerError) 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
}
import React, { useContext } from 'react'; import React, { useContext } from 'react';
import mano from './mano.svg'; 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'; import AuthContext from './AuthContext';
function Head(props) { function Head(props) {
...@@ -8,9 +8,12 @@ function Head(props) { ...@@ -8,9 +8,12 @@ function Head(props) {
let adminNav; let adminNav;
if (auth.role === "admin") { 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 ( return (
<Navbar bg="light"> <Navbar bg="light">
<Navbar.Brand href="/"> <Navbar.Brand href="/">
...@@ -21,8 +24,8 @@ function Head(props) { ...@@ -21,8 +24,8 @@ function Head(props) {
<Nav className="mr-auto"> <Nav className="mr-auto">
<Nav.Link href="/">Dashboard</Nav.Link> <Nav.Link href="/">Dashboard</Nav.Link>
<Nav.Link href="/products">Productos</Nav.Link> <Nav.Link href="/products">Productos</Nav.Link>
{adminNav}
</Nav> </Nav>
{adminNav}
<Form inline> <Form inline>
<Button <Button
variant="outline-success" variant="outline-success"
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment