Compare commits

..

8 Commits

5 changed files with 169 additions and 25 deletions

View File

@ -127,6 +127,7 @@ func main() {
paymentRoutes := api.Group("/payment", middleware.AuthMiddleware())
{
paymentRoutes.POST("/add", handlers.PaymentAdd)
paymentRoutes.GET("/all", handlers.PaymentGetAll)
}
currencyRoutes := api.Group("/currency", middleware.AuthMiddleware())
{

View File

@ -34,6 +34,7 @@ type Helper_ExpenseBulk struct {
UserID uint
}
// {{{ Helper_ExpenseBulk.CreateExpenseFromChild I'm not proud of this code
func (he *Helper_ExpenseBulk) CreateExpenseFromChild(c Expense) *Expense {
var cardID uint
var typeID uint
@ -80,6 +81,8 @@ func (he *Helper_ExpenseBulk) CreateExpenseFromChild(c Expense) *Expense {
}
}
// }}}
// Implements db.UserIdentifiable:1
func (e Expense) GetID() uint {
return e.ID
@ -96,7 +99,7 @@ func (e *Expense) SetUserID(id uint) {
}
var (
ERROR_EXPENSE_INVALID_USERID = errors.New("Expense's `UserID` and Card's `UserID` are not equal")
ERROR_EXPENSE_INVALID_CARD_USERID = errors.New("Expense's `UserID` and Card's `UserID` are not equal")
ERROR_EXPENSE_CARD_INSUFFICIENT_BALANCE = errors.New("Card's `Balance` is lower than Expense's Value")
ERROR_EXPENSE_INVALID_TYPE_USERID = errors.New("Expense's `UserID` and Type's `UserID` are not equal")
)
@ -107,7 +110,7 @@ func (e *Expense) BeforeCreate(tx *gorm.DB) error {
return err
}
if card.UserID != e.UserID {
return ERROR_EXPENSE_INVALID_USERID
return ERROR_EXPENSE_INVALID_CARD_USERID
}
if card.Balance < e.Value {
return ERROR_EXPENSE_CARD_INSUFFICIENT_BALANCE
@ -138,7 +141,7 @@ func (e *Expense) BeforeUpdate(tx *gorm.DB) (err error) {
return err
}
if oldCard.UserID != e.UserID {
return ERROR_EXPENSE_INVALID_USERID
return ERROR_EXPENSE_INVALID_CARD_USERID
}
oldCard.Balance += original.Value
if err := tx.Save(oldCard).Error; err != nil {
@ -152,7 +155,7 @@ func (e *Expense) BeforeUpdate(tx *gorm.DB) (err error) {
return err
}
if newCard.UserID != e.UserID {
return ERROR_EXPENSE_INVALID_USERID
return ERROR_EXPENSE_INVALID_CARD_USERID
}
if newCard.Balance < e.Value {
return ERROR_EXPENSE_CARD_INSUFFICIENT_BALANCE
@ -178,7 +181,7 @@ func (e *Expense) AfterDelete(tx *gorm.DB) (err error) {
return err
}
if card.UserID != e.UserID {
return ERROR_EXPENSE_INVALID_USERID
return ERROR_EXPENSE_INVALID_CARD_USERID
}
card.Balance += e.Value
if err := tx.Save(card).Error; err != nil {

View File

@ -1,6 +1,7 @@
package db
import (
"errors"
"time"
"gorm.io/gorm"
@ -23,3 +24,26 @@ type Payment struct {
}
func (p Payment) __internalBelogingToPayment() {}
var (
ERROR_PAYMENT_INVALID_CARD_USERID = errors.New("Payment's `UserID` and Card's `UserID` are not equal")
ERROR_PAYMENT_INVALID_CATEGORY_USERID = errors.New("Payment's `UserID` and Category's `UserID` are not equal")
)
func (p *Payment) BeforeSave(tx *gorm.DB) error {
paymentCard := &Card{}
if err := tx.Find(paymentCard, p.CardID).Error; err != nil {
return err
}
if paymentCard.UserID != p.UserID {
return ERROR_PAYMENT_INVALID_CARD_USERID
}
paymentCategory := &Category{}
if err := tx.Find(paymentCategory, p.CategoryID).Error; err != nil {
return err
}
if paymentCategory.UserID != p.UserID {
return ERROR_PAYMENT_INVALID_CATEGORY_USERID
}
return nil
}

View File

@ -9,16 +9,33 @@ import (
"github.com/gin-gonic/gin"
)
var itemBoughtTransform func(inp *db.ItemBought) types.ItemBought = func(inp *db.ItemBought) types.ItemBought {
return types.ItemBought{}
var itemBoughtTransform func(inp *db.ItemBought) types.DbItemBought = func(inp *db.ItemBought) types.DbItemBought {
var item types.DbItem
var price uint64 = 0
if inp.Item != nil {
item = itemTransform(inp.Item)
price = inp.Item.Price
}
return types.DbItemBought{
ID: inp.ID,
ItemID: inp.ItemID,
PaymentID: inp.PaymentID,
TypeID: inp.TypeID,
Price: price,
Quantity: inp.Quantity,
TotalCost: inp.TotalCost,
MetricType: inp.MetricType,
MetricValue: inp.MetricValue,
Item: item,
}
}
var paymentTransform func(inp *db.Payment) types.Payment = func(inp *db.Payment) types.Payment {
var items []types.ItemBought
var paymentTransform func(inp *db.Payment) types.DbPayment = func(inp *db.Payment) types.DbPayment {
var items []types.DbItemBought
for _, item := range inp.Items {
items = append(items, itemBoughtTransform(&item))
}
return types.Payment{
return types.DbPayment{
ID: inp.ID,
CardID: inp.CardID,
CategoryID: inp.CategoryID,
@ -36,7 +53,7 @@ var paymentTransform func(inp *db.Payment) types.Payment = func(inp *db.Payment)
// @Accept json
// @Produce json
// @Param Authorization header string true "Bearer token"
// @Param payment body types.Payment true "Payment"
// @Param payment body types.DbPayment true "Payment"
// @Success 200 {object} types.Message
// @Failure 400 {object} types.ErrorResponse
// @Failure 500 {object} types.ErrorResponse
@ -49,7 +66,7 @@ func PaymentAdd(c *gin.Context) {
return
}
var updates types.Payment
var updates types.DbPayment
if err := c.ShouldBindJSON(&updates); err != nil {
log.Printf("err is %v\n", err)
c.JSON(400, types.ErrorResponse{Message: "Invalid request"})
@ -63,7 +80,7 @@ func PaymentAdd(c *gin.Context) {
if weNeedRollback {
dbc := db.Connect()
for _, deleteIt := range deletableIfRollback {
if err := dbc.Debug().Delete(deleteIt).Error; err != nil {
if err := dbc.Delete(deleteIt).Error; err != nil {
log.Printf("ERROR: dbc.Delete: %v\n", err)
continue
}
@ -92,7 +109,9 @@ func PaymentAdd(c *gin.Context) {
return
}
deletableIfRollback = append(deletableIfRollback, payment)
var substractFromCard uint64 = 0
for _, uItemBought := range updates.Items {
var totalCost uint64 = 0
// Creating item and adding it to rollback if itemID is set to 0
if uItemBought.ItemID == 0 {
newItem := &db.Item{
@ -116,12 +135,13 @@ func PaymentAdd(c *gin.Context) {
return
}
deletableIfRollback = append(deletableIfRollback, newItem)
totalCost = newItem.Price * uint64(uItemBought.Quantity)
newItemBought := &db.ItemBought{
ItemID: newItem.ID,
PaymentID: payment.ID,
TypeID: uItemBought.TypeID,
Quantity: uItemBought.Quantity,
TotalCost: newItem.Price * uint64(uItemBought.Quantity),
TotalCost: totalCost,
MetricType: uItemBought.MetricType,
MetricValue: uItemBought.MetricValue,
}
@ -138,9 +158,104 @@ func PaymentAdd(c *gin.Context) {
deletableIfRollback = append(deletableIfRollback, newItemBought)
newItemBought.Item = newItem
} else {
// TODO: check if Item has same userID and potentially update Item
dbItem := &db.Item{}
if err := dbc.Find(dbItem, uItemBought.ItemID).Error; err != nil {
c.JSON(500, types.ErrorResponse{Message: err.Error()})
weNeedRollback = true
return
}
if dbItem.UserID != userID {
c.JSON(500, types.ErrorResponse{Message: "ItemID is not your"})
weNeedRollback = true
return
}
if dbItem.Price != uItemBought.Price {
dbItem.Price = uItemBought.Price
if err := dbc.Save(dbItem).Error; err != nil {
c.JSON(500, types.ErrorResponse{Message: err.Error()})
weNeedRollback = true
return
}
}
totalCost = dbItem.Price * uint64(uItemBought.Quantity)
newItemBought := &db.ItemBought{
ItemID: dbItem.ID,
PaymentID: payment.ID,
TypeID: uItemBought.TypeID,
Quantity: uItemBought.Quantity,
TotalCost: totalCost,
MetricType: uItemBought.MetricType,
MetricValue: uItemBought.MetricValue,
}
if err := dbc.Create(newItemBought).Error; err != nil {
c.JSON(500, types.ErrorResponse{Message: err.Error()})
weNeedRollback = true
return
}
if newItemBought.ID == 0 {
c.JSON(500, types.ErrorResponse{Message: "Internal error: ERR.P.A.4"})
weNeedRollback = true
return
}
deletableIfRollback = append(deletableIfRollback, newItemBought)
newItemBought.Item = dbItem
}
// As totalCost is calculated either way AND db.Item.BeforeSave have db.Item.Price check
// we can gently assume that totalCost != 0
substractFromCard += totalCost
}
// Checks for db.Card.UserID != db.Payment.UserID for payment is done in db.Payment hooks
card := &db.Card{}
if err := dbc.Find(card, payment.CardID).Error; err != nil {
c.JSON(500, types.ErrorResponse{Message: err.Error()})
weNeedRollback = true
return
}
// TODO: Examine if that would be better to calculate substractFromCard
// BEFORE update.Items loop and therefore this check can be done before
// processing payment's ItemBought entities
if card.Balance < substractFromCard {
c.JSON(500, types.ErrorResponse{Message: fmt.Sprintf("Card %s (%s) has Balance lower than overall Payment cost. Rollback.", card.Name, card.LastDigits)})
weNeedRollback = true
return
}
card.Balance -= substractFromCard
if err := dbc.Save(card).Error; err != nil {
c.JSON(500, types.ErrorResponse{Message: err.Error()})
weNeedRollback = true
return
}
c.JSON(200, types.Message{Info: fmt.Sprintf("Entity with %d ID is created successfully!", payment.ID)})
}
// @Summary Get all payments for user
// @Description Get all payments for user
// @Tags type
// @Produce json
// @Param Authorization header string true "Bearer token"
// @Success 200 {object} []types.DbPayment
// @Failure 401 {object} types.ErrorResponse
// @Failure 500 {object} types.ErrorResponse
// @Security ApiKeyAuth
// @Router /payment/all [get]
func PaymentGetAll(c *gin.Context) {
userID, err := GetUserID(c)
if err != nil {
c.JSON(500, types.ErrorResponse{Message: err.Error()})
return
}
dbc := db.Connect()
var entities []*db.Payment
if err := dbc.Preload("Items.Item").Find(&entities, db.Payment{UserID: userID}).Error; err != nil {
c.JSON(500, types.ErrorResponse{Message: err.Error()})
return
}
var ret []types.DbPayment
for _, entity := range entities {
ret = append(ret, paymentTransform(entity))
}
c.JSON(200, ret)
}

View File

@ -155,18 +155,18 @@ type DbCurrency struct {
Symbol string `json:"symbol" example:"$"`
}
type Payment struct {
ID uint `json:"id" example:"1"`
CardID uint `json:"card_id" example:"1"`
CategoryID uint `json:"category_id" example:"1"`
Title string `json:"title" example:"some title"`
Description string `json:"descr" example:"i bought some title for 20$"`
Note string `json:"note" example:"no i did not hit domain"`
Date time.Time `json:"date" example:"29/11/2001 12:00"`
Items []ItemBought `json:"items" example:"[]"`
type DbPayment struct {
ID uint `json:"id" example:"1"`
CardID uint `json:"card_id" example:"1"`
CategoryID uint `json:"category_id" example:"1"`
Title string `json:"title" example:"some title"`
Description string `json:"descr" example:"i bought some title for 20$"`
Note string `json:"note" example:"no i did not hit domain"`
Date time.Time `json:"date" example:"29/11/2001 12:00"`
Items []DbItemBought `json:"items" example:"[]"`
}
type ItemBought struct {
type DbItemBought struct {
ID uint `json:"id" example:"1"`
NewName string `json:"new_name" example:"itemName"`
NewComment string `json:"new_comment" example:"itemName"`
@ -178,4 +178,5 @@ type ItemBought struct {
TotalCost uint64 `json:"total_cost" example:"2050"`
MetricType uint8 `json:"metric_type" example:"0"`
MetricValue uint64 `json:"metric_value" example:"100"`
Item DbItem `json:"itme"`
}