init
This commit is contained in:
280
internal/handler/account_handler.go
Normal file
280
internal/handler/account_handler.go
Normal file
@@ -0,0 +1,280 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strconv"
|
||||
|
||||
"accounting-app/pkg/api"
|
||||
"accounting-app/internal/service"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// AccountHandler handles HTTP requests for account operations
|
||||
type AccountHandler struct {
|
||||
accountService *service.AccountService
|
||||
}
|
||||
|
||||
// NewAccountHandler creates a new AccountHandler instance
|
||||
func NewAccountHandler(accountService *service.AccountService) *AccountHandler {
|
||||
return &AccountHandler{
|
||||
accountService: accountService,
|
||||
}
|
||||
}
|
||||
|
||||
// CreateAccount handles POST /api/v1/accounts
|
||||
// Creates a new account with the provided data
|
||||
func (h *AccountHandler) CreateAccount(c *gin.Context) {
|
||||
userId, exists := c.Get("user_id")
|
||||
if !exists {
|
||||
api.Unauthorized(c, "User not authenticated")
|
||||
return
|
||||
}
|
||||
var input service.AccountInput
|
||||
if err := c.ShouldBindJSON(&input); err != nil {
|
||||
api.ValidationError(c, "Invalid request body: "+err.Error())
|
||||
return
|
||||
}
|
||||
input.UserID = userId.(uint)
|
||||
|
||||
account, err := h.accountService.CreateAccount(userId.(uint), input)
|
||||
if err != nil {
|
||||
if errors.Is(err, service.ErrNegativeBalanceNotAllowed) {
|
||||
api.BadRequest(c, "Negative balance is not allowed for non-credit accounts")
|
||||
return
|
||||
}
|
||||
api.InternalError(c, "Failed to create account: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
api.Created(c, account)
|
||||
}
|
||||
|
||||
// GetAccounts handles GET /api/v1/accounts
|
||||
// Returns a list of all accounts
|
||||
func (h *AccountHandler) GetAccounts(c *gin.Context) {
|
||||
userId, exists := c.Get("user_id")
|
||||
if !exists {
|
||||
api.Unauthorized(c, "User not authenticated")
|
||||
return
|
||||
}
|
||||
accounts, err := h.accountService.GetAllAccounts(userId.(uint))
|
||||
if err != nil {
|
||||
api.InternalError(c, "Failed to get accounts: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
api.Success(c, accounts)
|
||||
}
|
||||
|
||||
// GetAccount handles GET /api/v1/accounts/:id
|
||||
// Returns a single account by ID
|
||||
func (h *AccountHandler) GetAccount(c *gin.Context) {
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
|
||||
if err != nil {
|
||||
api.BadRequest(c, "Invalid account ID")
|
||||
return
|
||||
}
|
||||
|
||||
userId, exists := c.Get("user_id")
|
||||
if !exists {
|
||||
api.Unauthorized(c, "User not authenticated")
|
||||
return
|
||||
}
|
||||
|
||||
account, err := h.accountService.GetAccount(userId.(uint), uint(id))
|
||||
if err != nil {
|
||||
if errors.Is(err, service.ErrAccountNotFound) {
|
||||
api.NotFound(c, "Account not found")
|
||||
return
|
||||
}
|
||||
api.InternalError(c, "Failed to get account: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
api.Success(c, account)
|
||||
}
|
||||
|
||||
// UpdateAccount handles PUT /api/v1/accounts/:id
|
||||
// Updates an existing account with the provided data
|
||||
func (h *AccountHandler) UpdateAccount(c *gin.Context) {
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
|
||||
if err != nil {
|
||||
api.BadRequest(c, "Invalid account ID")
|
||||
return
|
||||
}
|
||||
|
||||
var input service.AccountInput
|
||||
if err := c.ShouldBindJSON(&input); err != nil {
|
||||
api.ValidationError(c, "Invalid request body: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
userId, exists := c.Get("user_id")
|
||||
if !exists {
|
||||
api.Unauthorized(c, "User not authenticated")
|
||||
return
|
||||
}
|
||||
// inject userID for other uses if needed but UpdateAccount sig now takes it directly
|
||||
input.UserID = userId.(uint)
|
||||
|
||||
account, err := h.accountService.UpdateAccount(userId.(uint), uint(id), input)
|
||||
if err != nil {
|
||||
if errors.Is(err, service.ErrAccountNotFound) {
|
||||
api.NotFound(c, "Account not found")
|
||||
return
|
||||
}
|
||||
if errors.Is(err, service.ErrNegativeBalanceNotAllowed) {
|
||||
api.BadRequest(c, "Negative balance is not allowed for non-credit accounts")
|
||||
return
|
||||
}
|
||||
api.InternalError(c, "Failed to update account: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
api.Success(c, account)
|
||||
}
|
||||
|
||||
// DeleteAccount handles DELETE /api/v1/accounts/:id
|
||||
// Deletes an account by ID
|
||||
func (h *AccountHandler) DeleteAccount(c *gin.Context) {
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
|
||||
if err != nil {
|
||||
api.BadRequest(c, "Invalid account ID")
|
||||
return
|
||||
}
|
||||
|
||||
userId, exists := c.Get("user_id")
|
||||
if !exists {
|
||||
api.Unauthorized(c, "User not authenticated")
|
||||
return
|
||||
}
|
||||
|
||||
err = h.accountService.DeleteAccount(userId.(uint), uint(id))
|
||||
if err != nil {
|
||||
if errors.Is(err, service.ErrAccountNotFound) {
|
||||
api.NotFound(c, "Account not found")
|
||||
return
|
||||
}
|
||||
if errors.Is(err, service.ErrAccountInUse) {
|
||||
api.Conflict(c, "Account is in use and cannot be deleted. Please remove associated transactions first.")
|
||||
return
|
||||
}
|
||||
api.InternalError(c, "Failed to delete account: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
api.NoContent(c)
|
||||
}
|
||||
|
||||
// Transfer handles POST /api/v1/accounts/transfer
|
||||
// Transfers money between two accounts
|
||||
func (h *AccountHandler) Transfer(c *gin.Context) {
|
||||
var input service.TransferInput
|
||||
if err := c.ShouldBindJSON(&input); err != nil {
|
||||
api.ValidationError(c, "Invalid request body: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
userId, exists := c.Get("user_id")
|
||||
if !exists {
|
||||
api.Unauthorized(c, "User not authenticated")
|
||||
return
|
||||
}
|
||||
input.UserID = userId.(uint)
|
||||
|
||||
err := h.accountService.Transfer(input.UserID, input.FromAccountID, input.ToAccountID, input.Amount, input.Note)
|
||||
if err != nil {
|
||||
if errors.Is(err, service.ErrSameAccountTransfer) {
|
||||
api.BadRequest(c, "Cannot transfer to the same account")
|
||||
return
|
||||
}
|
||||
if errors.Is(err, service.ErrInvalidTransferAmount) {
|
||||
api.BadRequest(c, "Transfer amount must be positive")
|
||||
return
|
||||
}
|
||||
if errors.Is(err, service.ErrInsufficientBalance) {
|
||||
api.BadRequest(c, "Insufficient balance for this transfer")
|
||||
return
|
||||
}
|
||||
if errors.Is(err, service.ErrAccountNotFound) {
|
||||
api.NotFound(c, "One or both accounts not found")
|
||||
return
|
||||
}
|
||||
api.InternalError(c, "Failed to transfer: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
api.Success(c, gin.H{
|
||||
"message": "Transfer completed successfully",
|
||||
})
|
||||
}
|
||||
|
||||
// GetAssetOverview handles GET /api/v1/accounts/overview
|
||||
// Returns the asset overview (total assets, liabilities, net worth)
|
||||
func (h *AccountHandler) GetAssetOverview(c *gin.Context) {
|
||||
userId, exists := c.Get("user_id")
|
||||
if !exists {
|
||||
api.Unauthorized(c, "User not authenticated")
|
||||
return
|
||||
}
|
||||
overview, err := h.accountService.GetAssetOverview(userId.(uint))
|
||||
if err != nil {
|
||||
api.InternalError(c, "Failed to get asset overview: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
api.Success(c, overview)
|
||||
}
|
||||
|
||||
// ReorderAccounts handles PUT /api/v1/accounts/reorder
|
||||
// Updates the sort order of accounts based on the provided order
|
||||
// Feature: accounting-feature-upgrade
|
||||
// Validates: Requirements 1.3, 1.4
|
||||
func (h *AccountHandler) ReorderAccounts(c *gin.Context) {
|
||||
var input service.ReorderAccountsInput
|
||||
if err := c.ShouldBindJSON(&input); err != nil {
|
||||
api.ValidationError(c, "Invalid request body: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
userId, exists := c.Get("user_id")
|
||||
if !exists {
|
||||
api.Unauthorized(c, "User not authenticated")
|
||||
return
|
||||
}
|
||||
|
||||
if len(input.AccountIDs) == 0 {
|
||||
api.BadRequest(c, "Account IDs array cannot be empty")
|
||||
return
|
||||
}
|
||||
|
||||
err := h.accountService.ReorderAccounts(userId.(uint), input.AccountIDs)
|
||||
if err != nil {
|
||||
if errors.Is(err, service.ErrAccountNotFound) {
|
||||
api.NotFound(c, "One or more accounts not found")
|
||||
return
|
||||
}
|
||||
api.InternalError(c, "Failed to reorder accounts: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
api.Success(c, gin.H{
|
||||
"message": "Accounts reordered successfully",
|
||||
})
|
||||
}
|
||||
|
||||
// RegisterRoutes registers all account routes to the given router group
|
||||
func (h *AccountHandler) RegisterRoutes(rg *gin.RouterGroup) {
|
||||
accounts := rg.Group("/accounts")
|
||||
{
|
||||
accounts.POST("", h.CreateAccount)
|
||||
accounts.GET("", h.GetAccounts)
|
||||
accounts.GET("/overview", h.GetAssetOverview)
|
||||
accounts.POST("/transfer", h.Transfer)
|
||||
accounts.PUT("/reorder", h.ReorderAccounts)
|
||||
accounts.GET("/:id", h.GetAccount)
|
||||
accounts.PUT("/:id", h.UpdateAccount)
|
||||
accounts.DELETE("/:id", h.DeleteAccount)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user