aboutsummaryrefslogtreecommitdiffstats
path: root/backend
diff options
context:
space:
mode:
authorLibravatarLibravatar Biswa Kalyan Bhuyan <biswa@surgot.in> 2025-04-25 02:19:47 +0530
committerLibravatarLibravatar Biswa Kalyan Bhuyan <biswa@surgot.in> 2025-04-25 02:19:47 +0530
commitff2031f6ff6f4a7e6d441c9ed2372f004ba34499 (patch)
treef665e59648cbbb74b3d4446270a878a773f26564 /backend
parent8733795c8449f3514369d7b4220934760e386f1b (diff)
downloadfinance-ff2031f6ff6f4a7e6d441c9ed2372f004ba34499.tar.gz
finance-ff2031f6ff6f4a7e6d441c9ed2372f004ba34499.tar.bz2
finance-ff2031f6ff6f4a7e6d441c9ed2372f004ba34499.zip
finance/backend: feat: added v1/accounts for accounts CRUD
Diffstat (limited to 'backend')
-rw-r--r--backend/cmd/api/main.go11
-rw-r--r--backend/internal/api/v1/accounts/accounts.go216
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"})
+ }
+}