diff options
Diffstat (limited to 'backend')
-rw-r--r-- | backend/cmd/api/main.go | 11 | ||||
-rw-r--r-- | backend/internal/api/v1/accounts/accounts.go | 216 |
2 files changed, 227 insertions, 0 deletions
diff --git a/backend/cmd/api/main.go b/backend/cmd/api/main.go index b90962f..9d09012 100644 --- a/backend/cmd/api/main.go +++ b/backend/cmd/api/main.go @@ -6,6 +6,7 @@ import ( "time" "finance/backend/internal/api/auth" + "finance/backend/internal/api/v1/accounts" "finance/backend/internal/api/v1/loans" "finance/backend/internal/config" "finance/backend/internal/database" @@ -99,6 +100,16 @@ func main() { c.JSON(http.StatusOK, gin.H{"user": user}) }) + // Account routes + accountRoutes := protected.Group("/accounts") + { + accountRoutes.GET("", accounts.GetAccounts()) + accountRoutes.GET("/:id", accounts.GetAccountByID()) + accountRoutes.POST("", accounts.CreateAccount()) + accountRoutes.PUT("/:id", accounts.UpdateAccount()) + accountRoutes.DELETE("/:id", accounts.DeleteAccount()) + } + // Loan routes loanRoutes := protected.Group("/loans") { diff --git a/backend/internal/api/v1/accounts/accounts.go b/backend/internal/api/v1/accounts/accounts.go new file mode 100644 index 0000000..08c1c5c --- /dev/null +++ b/backend/internal/api/v1/accounts/accounts.go @@ -0,0 +1,216 @@ +package accounts + +import ( + "net/http" + "strconv" + + "finance/backend/internal/database" + "finance/backend/internal/models" + + "github.com/gin-gonic/gin" + "gorm.io/gorm" +) + +// GetAccounts returns all accounts for the authenticated user +func GetAccounts() gin.HandlerFunc { + return func(c *gin.Context) { + // Get user from context (set by auth middleware) + user, exists := c.Get("user") + if !exists { + c.JSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"}) + return + } + + userObj := user.(models.User) + var accounts []models.Account + + // Fetch all accounts for the user + if err := database.DB.Where("user_id = ?", userObj.ID).Find(&accounts).Error; err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch accounts"}) + return + } + + c.JSON(http.StatusOK, gin.H{"accounts": accounts}) + } +} + +// GetAccountByID returns a specific account by ID +func GetAccountByID() gin.HandlerFunc { + return func(c *gin.Context) { + // Get user from context (set by auth middleware) + user, exists := c.Get("user") + if !exists { + c.JSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"}) + return + } + userObj := user.(models.User) + + // Get account ID from URL parameter + accountID, err := strconv.ParseUint(c.Param("id"), 10, 32) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid account ID format"}) + return + } + + var account models.Account + + // Fetch the account and ensure it belongs to the authenticated user + if err := database.DB.Where("id = ? AND user_id = ?", accountID, userObj.ID).First(&account).Error; err != nil { + if err == gorm.ErrRecordNotFound { + c.JSON(http.StatusNotFound, gin.H{"error": "Account not found"}) + } else { + c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch account"}) + } + return + } + + c.JSON(http.StatusOK, gin.H{"account": account}) + } +} + +// CreateAccount creates a new account +func CreateAccount() gin.HandlerFunc { + return func(c *gin.Context) { + // Get user from context (set by auth middleware) + user, exists := c.Get("user") + if !exists { + c.JSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"}) + return + } + userObj := user.(models.User) + + // Define a struct to bind the request JSON + var input struct { + Name string `json:"name" binding:"required"` + Type string `json:"type" binding:"required"` // e.g., "Bank", "Credit Card", "Cash", "Loan", "Income Source" + Balance int64 `json:"balance" binding:"required"` + } + + // Bind JSON to struct + if err := c.ShouldBindJSON(&input); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + // Create account object + account := models.Account{ + UserID: userObj.ID, + Name: input.Name, + Type: input.Type, + Balance: input.Balance, + } + + // Save to database + if err := database.DB.Create(&account).Error; err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create account"}) + return + } + + c.JSON(http.StatusCreated, gin.H{"account": account}) + } +} + +// UpdateAccount updates an existing account +func UpdateAccount() gin.HandlerFunc { + return func(c *gin.Context) { + // Get user from context (set by auth middleware) + user, exists := c.Get("user") + if !exists { + c.JSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"}) + return + } + userObj := user.(models.User) + + // Get account ID from URL parameter + accountID, err := strconv.ParseUint(c.Param("id"), 10, 32) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid account ID format"}) + return + } + + // Check if the account exists and belongs to the user + var account models.Account + if err := database.DB.Where("id = ? AND user_id = ?", accountID, userObj.ID).First(&account).Error; err != nil { + if err == gorm.ErrRecordNotFound { + c.JSON(http.StatusNotFound, gin.H{"error": "Account not found"}) + } else { + c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch account"}) + } + return + } + + // Define a struct to bind the request JSON + var input struct { + Name string `json:"name"` + Type string `json:"type"` + Balance int64 `json:"balance"` + } + + // Bind JSON to struct + if err := c.ShouldBindJSON(&input); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + // Update fields if provided + if input.Name != "" { + account.Name = input.Name + } + if input.Type != "" { + account.Type = input.Type + } + // For balance, we should allow setting it to 0, so check if it was provided + if c.Request.Method == "PUT" || c.Request.Method == "PATCH" { + if c.PostForm("balance") != "" || c.GetHeader("Content-Type") == "application/json" { + account.Balance = input.Balance + } + } + + // Save updates to database + if err := database.DB.Save(&account).Error; err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update account"}) + return + } + + c.JSON(http.StatusOK, gin.H{"account": account}) + } +} + +// DeleteAccount deletes an account +func DeleteAccount() gin.HandlerFunc { + return func(c *gin.Context) { + // Get user from context (set by auth middleware) + user, exists := c.Get("user") + if !exists { + c.JSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"}) + return + } + userObj := user.(models.User) + + // Get account ID from URL parameter + accountID, err := strconv.ParseUint(c.Param("id"), 10, 32) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid account ID format"}) + return + } + + // Check if the account exists and belongs to the user + var account models.Account + if err := database.DB.Where("id = ? AND user_id = ?", accountID, userObj.ID).First(&account).Error; err != nil { + if err == gorm.ErrRecordNotFound { + c.JSON(http.StatusNotFound, gin.H{"error": "Account not found"}) + } else { + c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch account"}) + } + return + } + + // Delete the account + if err := database.DB.Delete(&account).Error; err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to delete account"}) + return + } + + c.JSON(http.StatusOK, gin.H{"message": "Account deleted successfully"}) + } +} |