- Go web server using Gin framework - Proxy FRED economic data API - Support series, observations, search, releases endpoints
292 lines
6.4 KiB
Go
292 lines
6.4 KiB
Go
package handlers
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"net/url"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"fred-api/config"
|
|
"fred-api/models"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
)
|
|
|
|
type FREDHandler struct {
|
|
baseURL string
|
|
apiKey string
|
|
}
|
|
|
|
func NewFREDHandler(cfg *config.Config) *FREDHandler {
|
|
return &FREDHandler{
|
|
baseURL: cfg.FREDBaseURL,
|
|
apiKey: cfg.FREDAPIKey,
|
|
}
|
|
}
|
|
|
|
func (h *FREDHandler) buildURL(endpoint string, params map[string]string) string {
|
|
u, _ := url.Parse(h.baseURL + endpoint)
|
|
query := u.Query()
|
|
query.Set("api_key", h.apiKey)
|
|
query.Set("file_type", "json")
|
|
|
|
for k, v := range params {
|
|
if v != "" {
|
|
query.Set(k, v)
|
|
}
|
|
}
|
|
|
|
u.RawQuery = query.Encode()
|
|
return u.String()
|
|
}
|
|
|
|
func (h *FREDHandler) fetchData(url string, target interface{}) error {
|
|
resp, err := http.Get(url)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
body, err := io.ReadAll(resp.Body)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if resp.StatusCode != http.StatusOK {
|
|
var errResp models.ErrorResponse
|
|
json.Unmarshal(body, &errResp)
|
|
return fmt.Errorf("API error: %s - %s", errResp.ErrorCode, errResp.ErrorMessage)
|
|
}
|
|
|
|
return json.Unmarshal(body, target)
|
|
}
|
|
|
|
func (h *FREDHandler) GetSeries(c *gin.Context) {
|
|
seriesID := c.Query("series_id")
|
|
if seriesID == "" {
|
|
c.JSON(http.StatusBadRequest, models.ErrorResponse{
|
|
ErrorCode: "BadRequest",
|
|
ErrorMessage: "series_id is required",
|
|
})
|
|
return
|
|
}
|
|
|
|
params := map[string]string{
|
|
"series_id": seriesID,
|
|
}
|
|
|
|
if realtimeStart := c.Query("realtime_start"); realtimeStart != "" {
|
|
params["realtime_start"] = realtimeStart
|
|
}
|
|
if realtimeEnd := c.Query("realtime_end"); realtimeEnd != "" {
|
|
params["realtime_end"] = realtimeEnd
|
|
}
|
|
|
|
url := h.buildURL("/series", params)
|
|
|
|
var response models.SeriesListResponse
|
|
if err := h.fetchData(url, &response); err != nil {
|
|
c.JSON(http.StatusInternalServerError, models.ErrorResponse{
|
|
ErrorCode: "InternalError",
|
|
ErrorMessage: err.Error(),
|
|
})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, response)
|
|
}
|
|
|
|
func (h *FREDHandler) GetObservations(c *gin.Context) {
|
|
seriesID := c.Query("series_id")
|
|
if seriesID == "" {
|
|
c.JSON(http.StatusBadRequest, models.ErrorResponse{
|
|
ErrorCode: "BadRequest",
|
|
ErrorMessage: "series_id is required",
|
|
})
|
|
return
|
|
}
|
|
|
|
params := map[string]string{
|
|
"series_id": seriesID,
|
|
}
|
|
|
|
optionalParams := []string{
|
|
"realtime_start", "realtime_end", "limit", "offset", "sort_order",
|
|
"observation_start", "observation_end", "units", "frequency",
|
|
"aggregation_method", "output_type", "vintage_dates",
|
|
}
|
|
|
|
for _, param := range optionalParams {
|
|
if v := c.Query(param); v != "" {
|
|
params[param] = v
|
|
}
|
|
}
|
|
|
|
url := h.buildURL("/series/observations", params)
|
|
|
|
var response models.ObservationsResponse
|
|
if err := h.fetchData(url, &response); err != nil {
|
|
c.JSON(http.StatusInternalServerError, models.ErrorResponse{
|
|
ErrorCode: "InternalError",
|
|
ErrorMessage: err.Error(),
|
|
})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, response)
|
|
}
|
|
|
|
func (h *FREDHandler) SearchSeries(c *gin.Context) {
|
|
searchText := c.Query("search_text")
|
|
if searchText == "" {
|
|
c.JSON(http.StatusBadRequest, models.ErrorResponse{
|
|
ErrorCode: "BadRequest",
|
|
ErrorMessage: "search_text is required",
|
|
})
|
|
return
|
|
}
|
|
|
|
params := map[string]string{
|
|
"search_text": searchText,
|
|
}
|
|
|
|
optionalParams := []string{
|
|
"realtime_start", "realtime_end", "limit", "offset", "sort_order",
|
|
"observation_start", "observation_end", "units", "frequency",
|
|
"aggregation_method", "output_type",
|
|
}
|
|
|
|
for _, param := range optionalParams {
|
|
if v := c.Query(param); v != "" {
|
|
params[param] = v
|
|
}
|
|
}
|
|
|
|
url := h.buildURL("/series/search", params)
|
|
|
|
var response models.SearchResponse
|
|
if err := h.fetchData(url, &response); err != nil {
|
|
c.JSON(http.StatusInternalServerError, models.ErrorResponse{
|
|
ErrorCode: "InternalServerError",
|
|
ErrorMessage: err.Error(),
|
|
})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, response)
|
|
}
|
|
|
|
func (h *FREDHandler) GetReleases(c *gin.Context) {
|
|
params := map[string]string{}
|
|
|
|
optionalParams := []string{
|
|
"realtime_start", "realtime_end", "limit", "offset", "sort_order",
|
|
}
|
|
|
|
for _, param := range optionalParams {
|
|
if v := c.Query(param); v != "" {
|
|
params[param] = v
|
|
}
|
|
}
|
|
|
|
url := h.buildURL("/releases", params)
|
|
|
|
var response models.ReleasesResponse
|
|
if err := h.fetchData(url, &response); err != nil {
|
|
c.JSON(http.StatusInternalServerError, models.ErrorResponse{
|
|
ErrorCode: "InternalServerError",
|
|
ErrorMessage: err.Error(),
|
|
})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, response)
|
|
}
|
|
|
|
func (h *FREDHandler) GetReleaseDates(c *gin.Context) {
|
|
params := map[string]string{}
|
|
|
|
optionalParams := []string{
|
|
"realtime_start", "realtime_end", "limit", "offset", "sort_order",
|
|
"release_id", "date", "start_date", "end_date",
|
|
}
|
|
|
|
for _, param := range optionalParams {
|
|
if v := c.Query(param); v != "" {
|
|
params[param] = v
|
|
}
|
|
}
|
|
|
|
url := h.buildURL("/releases/dates", params)
|
|
|
|
var response models.ReleaseDatesResponse
|
|
if err := h.fetchData(url, &response); err != nil {
|
|
c.JSON(http.StatusInternalServerError, models.ErrorResponse{
|
|
ErrorCode: "InternalServerError",
|
|
ErrorMessage: err.Error(),
|
|
})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, response)
|
|
}
|
|
|
|
func (h *FREDHandler) GetReleaseSeries(c *gin.Context) {
|
|
releaseIDStr := c.Query("release_id")
|
|
if releaseIDStr == "" {
|
|
c.JSON(http.StatusBadRequest, models.ErrorResponse{
|
|
ErrorCode: "BadRequest",
|
|
ErrorMessage: "release_id is required",
|
|
})
|
|
return
|
|
}
|
|
|
|
releaseID, err := strconv.Atoi(releaseIDStr)
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, models.ErrorResponse{
|
|
ErrorCode: "BadRequest",
|
|
ErrorMessage: "release_id must be a number",
|
|
})
|
|
return
|
|
}
|
|
|
|
params := map[string]string{
|
|
"release_id": strings.TrimSpace(strings.Replace(strconv.Itoa(releaseID), " ", "", -1)),
|
|
}
|
|
|
|
optionalParams := []string{
|
|
"realtime_start", "realtime_end", "limit", "offset", "sort_order",
|
|
"observation_start", "observation_end", "units", "frequency",
|
|
"aggregation_method", "output_type",
|
|
}
|
|
|
|
for _, param := range optionalParams {
|
|
if v := c.Query(param); v != "" {
|
|
params[param] = v
|
|
}
|
|
}
|
|
|
|
url := h.buildURL("/release/series", params)
|
|
|
|
var response models.SearchResponse
|
|
if err := h.fetchData(url, &response); err != nil {
|
|
c.JSON(http.StatusInternalServerError, models.ErrorResponse{
|
|
ErrorCode: "InternalServerError",
|
|
ErrorMessage: err.Error(),
|
|
})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, response)
|
|
}
|
|
|
|
func (h *FREDHandler) HealthCheck(c *gin.Context) {
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"status": "healthy",
|
|
"service": "FRED API Proxy",
|
|
})
|
|
}
|