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", }) }