Compare commits

...

6 Commits

11 changed files with 300 additions and 11 deletions

View File

@ -88,6 +88,14 @@ func main() {
typesRoutes.PUT("/edit/:id", handlers.TypePutId) typesRoutes.PUT("/edit/:id", handlers.TypePutId)
typesRoutes.DELETE("/delete/:id", handlers.TypeDeleteId) typesRoutes.DELETE("/delete/:id", handlers.TypeDeleteId)
} }
expensesRoutes := api.Group("/expense", middleware.AuthMiddleware())
{
expensesRoutes.POST("/add", handlers.ExpenseAdd)
expensesRoutes.GET("/:id", handlers.ExpenseGetId)
expensesRoutes.GET("/all", handlers.ExpenseGetAll)
expensesRoutes.PUT("/edit/:id", handlers.ExpensePutId)
expensesRoutes.DELETE("/delete/:id", handlers.ExpenseDeleteId)
}
} }
r.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler)) r.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))

View File

@ -58,5 +58,6 @@ func Connect() *gorm.DB {
gormDB.AutoMigrate(&User{}) gormDB.AutoMigrate(&User{})
gormDB.AutoMigrate(&Type{}) gormDB.AutoMigrate(&Type{})
gormDB.AutoMigrate(&Session{}) gormDB.AutoMigrate(&Session{})
gormDB.AutoMigrate(&Expense{})
return newUDB return newUDB
} }

136
db/expense.go Normal file
View File

@ -0,0 +1,136 @@
package db
import (
"errors"
"log"
"time"
"gorm.io/gorm"
)
type Expense struct {
gorm.Model
CardID uint
Card *Card
Value uint64
Comment string
Date time.Time
UserID uint
User *User
TypeID uint
Type *Type
}
// Implements db.UserIdentifiable:1
func (e Expense) GetID() uint {
return e.ID
}
// Implements db.UserIdentifiable:2
func (e Expense) GetUserID() uint {
return e.UserID
}
// Implements db.UserIdentifiable:3
func (e *Expense) SetUserID(id uint) {
e.UserID = id
}
var (
ERROR_EXPENSE_INVALID_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")
)
func (e *Expense) BeforeCreate(tx *gorm.DB) error {
log.Printf("BeforeCreate")
card := &Card{}
if err := tx.Find(card, e.CardID).Error; err != nil {
return err
}
if card.UserID != e.UserID {
return ERROR_EXPENSE_INVALID_USERID
}
if card.Balance < e.Value {
return ERROR_EXPENSE_CARD_INSUFFICIENT_BALANCE
}
card.Balance -= e.Value
if err := tx.Save(card).Error; err != nil {
return err
}
typ := &Type{}
if err := tx.Find(typ, e.TypeID).Error; err != nil {
return err
}
log.Printf("e.UserID = %d\n", e.UserID)
log.Printf("e.TypeID= %d\n", e.TypeID)
log.Printf("typ.UserID= %d\n", typ.UserID)
if typ.UserID != e.UserID {
return ERROR_EXPENSE_INVALID_TYPE_USERID
}
return nil
}
func (e *Expense) BeforeUpdate(tx *gorm.DB) (err error) {
var original Expense
if err := tx.Model(&Expense{}).Select("card_id", "value").Where("id = ?", e.ID).First(&original).Error; err != nil {
return err
}
if original.CardID != 0 {
oldCard := &Card{}
if err := tx.Find(oldCard, original.CardID).Error; err != nil {
return err
}
if oldCard.UserID != e.UserID {
return ERROR_EXPENSE_INVALID_USERID
}
oldCard.Balance += e.Value
if err := tx.Save(oldCard).Error; err != nil {
return err
}
}
if e.CardID != 0 {
newCard := &Card{}
if err := tx.Find(newCard, e.CardID).Error; err != nil {
return err
}
if newCard.UserID != e.UserID {
return ERROR_EXPENSE_INVALID_USERID
}
if newCard.Balance < e.Value {
return ERROR_EXPENSE_CARD_INSUFFICIENT_BALANCE
}
newCard.Balance -= e.Value
if err := tx.Save(newCard).Error; err != nil {
return err
}
}
typ := &Type{}
if err := tx.Find(typ, e.TypeID).Error; err != nil {
return err
}
if typ.UserID != e.UserID {
return ERROR_EXPENSE_INVALID_TYPE_USERID
}
return nil
}
func (e *Expense) AfterDelete(tx *gorm.DB) (err error) {
card := &Card{}
if err := tx.Find(card, e.CardID).Error; err != nil {
return err
}
if card.UserID != e.UserID {
return ERROR_EXPENSE_INVALID_USERID
}
if card.Balance < e.Value {
return ERROR_EXPENSE_CARD_INSUFFICIENT_BALANCE
}
card.Balance += e.Value
if err := tx.Save(card).Error; err != nil {
return err
}
return nil
}

View File

@ -63,8 +63,8 @@ func CardGetAll(c *gin.Context) {
c.JSON(200, ret) c.JSON(200, ret)
} }
// @Summary Get card by id // @Summary Add card
// @Description Get card by id // @Description Add card
// @Tags card // @Tags card
// @Accept json // @Accept json
// @Produce json // @Produce json

View File

@ -62,8 +62,8 @@ func CategoryGetAll(c *gin.Context) {
c.JSON(200, ret) c.JSON(200, ret)
} }
// @Summary Get category by id // @Summary Add category
// @Description Get category by id // @Description Add category
// @Tags category // @Tags category
// @Accept json // @Accept json
// @Produce json // @Produce json

View File

@ -35,8 +35,8 @@ func DebtGetId(c *gin.Context) {
})(c) })(c)
} }
// @Summary Get debt by id // @Summary Add debt
// @Description Get debt by id // @Description Add debt
// @Tags debt // @Tags debt
// @Accept json // @Accept json
// @Produce json // @Produce json

134
handlers/expense.go Normal file
View File

@ -0,0 +1,134 @@
package handlers
import (
"git.qowevisa.me/Qowevisa/fin-check-api/db"
"git.qowevisa.me/Qowevisa/fin-check-api/types"
"github.com/gin-gonic/gin"
)
var expenseTransform func(inp *db.Expense) types.DbExpense = func(inp *db.Expense) types.DbExpense {
return types.DbExpense{
ID: inp.ID,
CardID: inp.CardID,
TypeID: inp.TypeID,
Value: inp.Value,
Comment: inp.Comment,
Date: inp.Date,
}
}
// @Summary Get expense by id
// @Description Get expense by id
// @Tags expense
// @Accept json
// @Produce json
// @Param Authorization header string true "Bearer token"
// @Param expense path int true "id"
// @Success 200 {object} types.DbExpense
// @Failure 400 {object} types.ErrorResponse
// @Failure 401 {object} types.ErrorResponse
// @Failure 403 {object} types.ErrorResponse
// @Failure 500 {object} types.ErrorResponse
// @Security ApiKeyAuth
// @Router /expense/:id [get]
func ExpenseGetId(c *gin.Context) {
GetHandler(expenseTransform)(c)
}
// @Summary Get all expenses for user
// @Description Get all expenses for user
// @Tags type
// @Produce json
// @Param Authorization header string true "Bearer token"
// @Success 200 {object} []types.DbExpense
// @Failure 401 {object} types.ErrorResponse
// @Failure 500 {object} types.ErrorResponse
// @Security ApiKeyAuth
// @Router /expense/all [get]
func ExpenseGetAll(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.Expense
if err := dbc.Find(&entities, db.Expense{UserID: userID}).Error; err != nil {
c.JSON(500, types.ErrorResponse{Message: err.Error()})
return
}
var ret []types.DbExpense
for _, entity := range entities {
ret = append(ret, expenseTransform(entity))
}
c.JSON(200, ret)
}
// @Summary Add expense
// @Description Add expense
// @Tags expense
// @Accept json
// @Produce json
// @Param Authorization header string true "Bearer token"
// @Param expense body types.DbExpense true "Expense"
// @Success 200 {object} types.Message
// @Failure 400 {object} types.ErrorResponse
// @Failure 500 {object} types.ErrorResponse
// @Security ApiKeyAuth
// @Router /expense/add [post]
func ExpenseAdd(c *gin.Context) {
CreateHandler(&db.Expense{}, func(src types.DbExpense, dst *db.Expense) {
dst.CardID = src.CardID
dst.TypeID = src.TypeID
dst.Value = src.Value
dst.Comment = src.Comment
dst.Date = src.Date
})(c)
}
// @Summary Edit expense by id
// @Description Edit expense by id
// @Tags expense
// @Accept json
// @Produce json
// @Param Authorization header string true "Bearer token"
// @Param expenseID path int true "id"
// @Param expense body types.DbExpense true "Expense"
// @Success 200 {object} types.DbExpense
// @Failure 400 {object} types.ErrorResponse
// @Failure 401 {object} types.ErrorResponse
// @Failure 403 {object} types.ErrorResponse
// @Failure 500 {object} types.ErrorResponse
// @Security ApiKeyAuth
// @Router /expense/edit/:id [put]
func ExpensePutId(c *gin.Context) {
UpdateHandler(
func(src types.DbExpense, dst *db.Expense) {
dst.CardID = src.CardID
dst.TypeID = src.TypeID
dst.Value = src.Value
dst.Comment = src.Comment
dst.Date = src.Date
},
expenseTransform,
)(c)
}
// @Summary Delete expense by id
// @Description Delete expense by id
// @Tags expense
// @Accept json
// @Produce json
// @Param Authorization header string true "Bearer token"
// @Param expenseID path int true "id"
// @Success 200 {object} types.DbExpense
// @Failure 400 {object} types.ErrorResponse
// @Failure 401 {object} types.ErrorResponse
// @Failure 403 {object} types.ErrorResponse
// @Failure 500 {object} types.ErrorResponse
// @Security ApiKeyAuth
// @Router /expense/delete/:id [delete]
func ExpenseDeleteId(c *gin.Context) {
DeleteHandler[*db.Expense]()(c)
}

View File

@ -73,8 +73,8 @@ func IncomeGetId(c *gin.Context) {
c.JSON(200, ret) c.JSON(200, ret)
} }
// @Summary Get income by id // @Summary Add income
// @Description Get income by id // @Description Add income
// @Tags income // @Tags income
// @Accept json // @Accept json
// @Produce json // @Produce json

View File

@ -63,8 +63,8 @@ func TypeGetAll(c *gin.Context) {
c.JSON(200, ret) c.JSON(200, ret)
} }
// @Summary Get dbtype by id // @Summary Add dbtype
// @Description Get dbtype by id // @Description Add dbtype
// @Tags dbtype // @Tags dbtype
// @Accept json // @Accept json
// @Produce json // @Produce json

View File

@ -115,7 +115,8 @@ func UpdateHandler[T db.UserIdentifiable, R any](
var updates R var updates R
if err := c.ShouldBindJSON(&updates); err != nil { if err := c.ShouldBindJSON(&updates); err != nil {
c.JSON(400, types.ErrorResponse{Message: "Invalid request"}) log.Printf("c.ShouldBindJSON: error: %v\n", err)
c.JSON(400, types.ErrorResponse{Message: "Invalid request: 2"})
return return
} }

View File

@ -80,3 +80,12 @@ type Session struct {
ID string `json:"id"` ID string `json:"id"`
UserID uint `json:"user_id" example:"1"` UserID uint `json:"user_id" example:"1"`
} }
type DbExpense struct {
ID uint `json:"id" example:"1"`
CardID uint `json:"card_id" example:"1"`
TypeID uint `json:"type_id" example:"1"`
Value uint64 `json:"value" example:"20000"`
Comment string `json:"comment" example:"pizza"`
Date time.Time `json:"date" example:"29/11/2001 12:00"`
}