init
This commit is contained in:
409
internal/handler/exchange_rate_handler.go
Normal file
409
internal/handler/exchange_rate_handler.go
Normal file
@@ -0,0 +1,409 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"accounting-app/pkg/api"
|
||||
"accounting-app/internal/models"
|
||||
"accounting-app/internal/repository"
|
||||
"accounting-app/internal/service"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// ExchangeRateHandler handles HTTP requests for exchange rate operations
|
||||
type ExchangeRateHandler struct {
|
||||
exchangeRateService *service.ExchangeRateService
|
||||
yunAPIClient *service.YunAPIClient
|
||||
}
|
||||
|
||||
// NewExchangeRateHandler creates a new ExchangeRateHandler instance
|
||||
func NewExchangeRateHandler(exchangeRateService *service.ExchangeRateService) *ExchangeRateHandler {
|
||||
return &ExchangeRateHandler{
|
||||
exchangeRateService: exchangeRateService,
|
||||
}
|
||||
}
|
||||
|
||||
// NewExchangeRateHandlerWithClient creates a new ExchangeRateHandler with YunAPI client
|
||||
func NewExchangeRateHandlerWithClient(exchangeRateService *service.ExchangeRateService, yunAPIClient *service.YunAPIClient) *ExchangeRateHandler {
|
||||
return &ExchangeRateHandler{
|
||||
exchangeRateService: exchangeRateService,
|
||||
yunAPIClient: yunAPIClient,
|
||||
}
|
||||
}
|
||||
|
||||
// ExchangeRateInput represents the input for creating/updating an exchange rate
|
||||
type ExchangeRateInput struct {
|
||||
FromCurrency models.Currency `json:"from_currency" binding:"required"`
|
||||
ToCurrency models.Currency `json:"to_currency" binding:"required"`
|
||||
Rate float64 `json:"rate" binding:"required,gt=0"`
|
||||
EffectiveDate string `json:"effective_date" binding:"required"` // Format: YYYY-MM-DD
|
||||
}
|
||||
|
||||
// ConvertCurrencyInput represents the input for currency conversion
|
||||
type ConvertCurrencyInput struct {
|
||||
Amount float64 `json:"amount" binding:"required,gt=0"`
|
||||
FromCurrency models.Currency `json:"from_currency" binding:"required"`
|
||||
ToCurrency models.Currency `json:"to_currency" binding:"required"`
|
||||
Date string `json:"date,omitempty"` // Optional, format: YYYY-MM-DD
|
||||
}
|
||||
|
||||
// CreateExchangeRate handles POST /api/v1/exchange-rates
|
||||
// Creates a new exchange rate with the provided data
|
||||
func (h *ExchangeRateHandler) CreateExchangeRate(c *gin.Context) {
|
||||
var input ExchangeRateInput
|
||||
if err := c.ShouldBindJSON(&input); err != nil {
|
||||
api.ValidationError(c, "Invalid request body: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// Parse effective date
|
||||
effectiveDate, err := time.Parse("2006-01-02", input.EffectiveDate)
|
||||
if err != nil {
|
||||
api.BadRequest(c, "Invalid effective date format. Use YYYY-MM-DD")
|
||||
return
|
||||
}
|
||||
|
||||
// Create exchange rate model
|
||||
exchangeRate := &models.ExchangeRate{
|
||||
FromCurrency: input.FromCurrency,
|
||||
ToCurrency: input.ToCurrency,
|
||||
Rate: input.Rate,
|
||||
EffectiveDate: effectiveDate,
|
||||
}
|
||||
|
||||
// Create exchange rate
|
||||
err = h.exchangeRateService.CreateExchangeRate(exchangeRate)
|
||||
if err != nil {
|
||||
if errors.Is(err, service.ErrInvalidRate) {
|
||||
api.BadRequest(c, "Exchange rate must be positive")
|
||||
return
|
||||
}
|
||||
if errors.Is(err, service.ErrInvalidEffectiveDate) {
|
||||
api.BadRequest(c, "Effective date cannot be in the future")
|
||||
return
|
||||
}
|
||||
if errors.Is(err, repository.ErrSameCurrency) {
|
||||
api.BadRequest(c, "From and to currency cannot be the same")
|
||||
return
|
||||
}
|
||||
api.InternalError(c, "Failed to create exchange rate: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
api.Created(c, exchangeRate)
|
||||
}
|
||||
|
||||
// GetExchangeRates handles GET /api/v1/exchange-rates
|
||||
// Returns a list of all exchange rates
|
||||
func (h *ExchangeRateHandler) GetExchangeRates(c *gin.Context) {
|
||||
// Check if we should get latest rates only
|
||||
latestOnly := c.Query("latest") == "true"
|
||||
|
||||
var rates []models.ExchangeRate
|
||||
var err error
|
||||
|
||||
if latestOnly {
|
||||
rates, err = h.exchangeRateService.GetLatestExchangeRates()
|
||||
} else {
|
||||
rates, err = h.exchangeRateService.GetAllExchangeRates()
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
api.InternalError(c, "Failed to get exchange rates: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
api.Success(c, rates)
|
||||
}
|
||||
|
||||
// GetExchangeRate handles GET /api/v1/exchange-rates/:id
|
||||
// Returns a single exchange rate by ID
|
||||
func (h *ExchangeRateHandler) GetExchangeRate(c *gin.Context) {
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
|
||||
if err != nil {
|
||||
api.BadRequest(c, "Invalid exchange rate ID")
|
||||
return
|
||||
}
|
||||
|
||||
rate, err := h.exchangeRateService.GetExchangeRateByID(uint(id))
|
||||
if err != nil {
|
||||
if errors.Is(err, repository.ErrExchangeRateNotFound) {
|
||||
api.NotFound(c, "Exchange rate not found")
|
||||
return
|
||||
}
|
||||
api.InternalError(c, "Failed to get exchange rate: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
api.Success(c, rate)
|
||||
}
|
||||
|
||||
// UpdateExchangeRate handles PUT /api/v1/exchange-rates/:id
|
||||
// Updates an existing exchange rate with the provided data
|
||||
func (h *ExchangeRateHandler) UpdateExchangeRate(c *gin.Context) {
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
|
||||
if err != nil {
|
||||
api.BadRequest(c, "Invalid exchange rate ID")
|
||||
return
|
||||
}
|
||||
|
||||
var input ExchangeRateInput
|
||||
if err := c.ShouldBindJSON(&input); err != nil {
|
||||
api.ValidationError(c, "Invalid request body: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// Parse effective date
|
||||
effectiveDate, err := time.Parse("2006-01-02", input.EffectiveDate)
|
||||
if err != nil {
|
||||
api.BadRequest(c, "Invalid effective date format. Use YYYY-MM-DD")
|
||||
return
|
||||
}
|
||||
|
||||
// Create exchange rate model with ID
|
||||
exchangeRate := &models.ExchangeRate{
|
||||
ID: uint(id),
|
||||
FromCurrency: input.FromCurrency,
|
||||
ToCurrency: input.ToCurrency,
|
||||
Rate: input.Rate,
|
||||
EffectiveDate: effectiveDate,
|
||||
}
|
||||
|
||||
// Update exchange rate
|
||||
err = h.exchangeRateService.UpdateExchangeRate(exchangeRate)
|
||||
if err != nil {
|
||||
if errors.Is(err, repository.ErrExchangeRateNotFound) {
|
||||
api.NotFound(c, "Exchange rate not found")
|
||||
return
|
||||
}
|
||||
if errors.Is(err, service.ErrInvalidRate) {
|
||||
api.BadRequest(c, "Exchange rate must be positive")
|
||||
return
|
||||
}
|
||||
if errors.Is(err, service.ErrInvalidEffectiveDate) {
|
||||
api.BadRequest(c, "Effective date cannot be in the future")
|
||||
return
|
||||
}
|
||||
if errors.Is(err, repository.ErrSameCurrency) {
|
||||
api.BadRequest(c, "From and to currency cannot be the same")
|
||||
return
|
||||
}
|
||||
api.InternalError(c, "Failed to update exchange rate: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
api.Success(c, exchangeRate)
|
||||
}
|
||||
|
||||
// DeleteExchangeRate handles DELETE /api/v1/exchange-rates/:id
|
||||
// Deletes an exchange rate by ID
|
||||
func (h *ExchangeRateHandler) DeleteExchangeRate(c *gin.Context) {
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
|
||||
if err != nil {
|
||||
api.BadRequest(c, "Invalid exchange rate ID")
|
||||
return
|
||||
}
|
||||
|
||||
err = h.exchangeRateService.DeleteExchangeRate(uint(id))
|
||||
if err != nil {
|
||||
if errors.Is(err, repository.ErrExchangeRateNotFound) {
|
||||
api.NotFound(c, "Exchange rate not found")
|
||||
return
|
||||
}
|
||||
api.InternalError(c, "Failed to delete exchange rate: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
api.NoContent(c)
|
||||
}
|
||||
|
||||
// GetExchangeRateByCurrencyPair handles GET /api/v1/exchange-rates/pair
|
||||
// Returns the most recent exchange rate for a currency pair
|
||||
// Query params: from_currency, to_currency, date (optional)
|
||||
func (h *ExchangeRateHandler) GetExchangeRateByCurrencyPair(c *gin.Context) {
|
||||
fromCurrency := models.Currency(c.Query("from_currency"))
|
||||
toCurrency := models.Currency(c.Query("to_currency"))
|
||||
dateStr := c.Query("date")
|
||||
|
||||
if fromCurrency == "" || toCurrency == "" {
|
||||
api.BadRequest(c, "Both from_currency and to_currency are required")
|
||||
return
|
||||
}
|
||||
|
||||
var rate *models.ExchangeRate
|
||||
var err error
|
||||
|
||||
if dateStr != "" {
|
||||
// Get rate for specific date
|
||||
date, parseErr := time.Parse("2006-01-02", dateStr)
|
||||
if parseErr != nil {
|
||||
api.BadRequest(c, "Invalid date format. Use YYYY-MM-DD")
|
||||
return
|
||||
}
|
||||
rate, err = h.exchangeRateService.GetExchangeRateByCurrencyPairAndDate(fromCurrency, toCurrency, date)
|
||||
} else {
|
||||
// Get most recent rate
|
||||
rate, err = h.exchangeRateService.GetExchangeRateByCurrencyPair(fromCurrency, toCurrency)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
if errors.Is(err, repository.ErrExchangeRateNotFound) {
|
||||
api.NotFound(c, "Exchange rate not found for the specified currency pair")
|
||||
return
|
||||
}
|
||||
if errors.Is(err, repository.ErrSameCurrency) {
|
||||
api.BadRequest(c, "From and to currency cannot be the same")
|
||||
return
|
||||
}
|
||||
api.InternalError(c, "Failed to get exchange rate: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
api.Success(c, rate)
|
||||
}
|
||||
|
||||
// ConvertCurrency handles POST /api/v1/exchange-rates/convert
|
||||
// Converts an amount from one currency to another
|
||||
func (h *ExchangeRateHandler) ConvertCurrency(c *gin.Context) {
|
||||
var input ConvertCurrencyInput
|
||||
if err := c.ShouldBindJSON(&input); err != nil {
|
||||
api.ValidationError(c, "Invalid request body: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
var convertedAmount float64
|
||||
var err error
|
||||
|
||||
if input.Date != "" {
|
||||
// Convert using rate on specific date
|
||||
date, parseErr := time.Parse("2006-01-02", input.Date)
|
||||
if parseErr != nil {
|
||||
api.BadRequest(c, "Invalid date format. Use YYYY-MM-DD")
|
||||
return
|
||||
}
|
||||
convertedAmount, err = h.exchangeRateService.ConvertCurrencyOnDate(input.Amount, input.FromCurrency, input.ToCurrency, date)
|
||||
} else {
|
||||
// Convert using most recent rate
|
||||
convertedAmount, err = h.exchangeRateService.ConvertCurrency(input.Amount, input.FromCurrency, input.ToCurrency)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
if errors.Is(err, repository.ErrExchangeRateNotFound) {
|
||||
api.NotFound(c, "Exchange rate not found for the specified currency pair")
|
||||
return
|
||||
}
|
||||
api.InternalError(c, "Failed to convert currency: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
api.Success(c, gin.H{
|
||||
"original_amount": input.Amount,
|
||||
"from_currency": input.FromCurrency,
|
||||
"to_currency": input.ToCurrency,
|
||||
"converted_amount": convertedAmount,
|
||||
"date": input.Date,
|
||||
})
|
||||
}
|
||||
|
||||
// GetExchangeRatesByCurrency handles GET /api/v1/exchange-rates/currency/:currency
|
||||
// Returns all exchange rates involving a specific currency
|
||||
func (h *ExchangeRateHandler) GetExchangeRatesByCurrency(c *gin.Context) {
|
||||
currency := models.Currency(c.Param("currency"))
|
||||
|
||||
if currency == "" {
|
||||
api.BadRequest(c, "Currency is required")
|
||||
return
|
||||
}
|
||||
|
||||
rates, err := h.exchangeRateService.GetExchangeRateByCurrency(currency)
|
||||
if err != nil {
|
||||
api.InternalError(c, "Failed to get exchange rates: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
api.Success(c, rates)
|
||||
}
|
||||
|
||||
// SetExchangeRate handles POST /api/v1/exchange-rates/set
|
||||
// Convenience endpoint to set an exchange rate (creates new entry)
|
||||
func (h *ExchangeRateHandler) SetExchangeRate(c *gin.Context) {
|
||||
var input ExchangeRateInput
|
||||
if err := c.ShouldBindJSON(&input); err != nil {
|
||||
api.ValidationError(c, "Invalid request body: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// Parse effective date
|
||||
effectiveDate, err := time.Parse("2006-01-02", input.EffectiveDate)
|
||||
if err != nil {
|
||||
api.BadRequest(c, "Invalid effective date format. Use YYYY-MM-DD")
|
||||
return
|
||||
}
|
||||
|
||||
// Set exchange rate
|
||||
err = h.exchangeRateService.SetExchangeRate(input.FromCurrency, input.ToCurrency, input.Rate, effectiveDate)
|
||||
if err != nil {
|
||||
if errors.Is(err, service.ErrInvalidRate) {
|
||||
api.BadRequest(c, "Exchange rate must be positive")
|
||||
return
|
||||
}
|
||||
if errors.Is(err, service.ErrInvalidEffectiveDate) {
|
||||
api.BadRequest(c, "Effective date cannot be in the future")
|
||||
return
|
||||
}
|
||||
if errors.Is(err, repository.ErrSameCurrency) {
|
||||
api.BadRequest(c, "From and to currency cannot be the same")
|
||||
return
|
||||
}
|
||||
api.InternalError(c, "Failed to set exchange rate: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
api.Success(c, gin.H{
|
||||
"message": "Exchange rate set successfully",
|
||||
})
|
||||
}
|
||||
|
||||
// RefreshExchangeRates handles POST /api/v1/exchange-rates/refresh
|
||||
// Manually triggers a refresh of exchange rates from YunAPI
|
||||
func (h *ExchangeRateHandler) RefreshExchangeRates(c *gin.Context) {
|
||||
if h.yunAPIClient == nil {
|
||||
api.InternalError(c, "Exchange rate refresh is not configured")
|
||||
return
|
||||
}
|
||||
|
||||
savedCount, err := h.yunAPIClient.ForceRefresh()
|
||||
if err != nil {
|
||||
if errors.Is(err, service.ErrAPIRequestFailed) {
|
||||
api.BadGateway(c, "Failed to fetch exchange rates from external API: "+err.Error())
|
||||
return
|
||||
}
|
||||
api.InternalError(c, "Failed to refresh exchange rates: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
api.Success(c, gin.H{
|
||||
"message": "Exchange rates refreshed successfully",
|
||||
"rates_saved": savedCount,
|
||||
})
|
||||
}
|
||||
|
||||
// RegisterRoutes registers all exchange rate routes to the given router group
|
||||
func (h *ExchangeRateHandler) RegisterRoutes(rg *gin.RouterGroup) {
|
||||
exchangeRates := rg.Group("/exchange-rates")
|
||||
{
|
||||
exchangeRates.POST("", h.CreateExchangeRate)
|
||||
exchangeRates.GET("", h.GetExchangeRates)
|
||||
exchangeRates.GET("/pair", h.GetExchangeRateByCurrencyPair)
|
||||
exchangeRates.POST("/convert", h.ConvertCurrency)
|
||||
exchangeRates.POST("/set", h.SetExchangeRate)
|
||||
exchangeRates.POST("/refresh", h.RefreshExchangeRates)
|
||||
exchangeRates.GET("/currency/:currency", h.GetExchangeRatesByCurrency)
|
||||
exchangeRates.GET("/:id", h.GetExchangeRate)
|
||||
exchangeRates.PUT("/:id", h.UpdateExchangeRate)
|
||||
exchangeRates.DELETE("/:id", h.DeleteExchangeRate)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user