281 lines
7.4 KiB
Go
281 lines
7.4 KiB
Go
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)
|
|
}
|
|
}
|