package main import ( "encoding/json" "fmt" "io" "log" "net/http" "strings" "time" ) type StockPriceRequest struct { Tickers []string `json:"tickers"` } type StockPrice struct { Ticker string `json:"ticker"` USD float64 `json:"usd"` EUR float64 `json:"eur"` Success bool `json:"success"` Error string `json:"error,omitempty"` } type StockPricesResponse struct { Prices map[string]StockPrice `json:"prices"` ExchangeRate float64 `json:"exchangeRate"` } type YahooChartResponse struct { Chart struct { Result []struct { Meta struct { RegularMarketPrice float64 `json:"regularMarketPrice"` } `json:"meta"` } `json:"result"` Error *struct { Description string `json:"description"` } `json:"error"` } `json:"chart"` } type ExchangeRateResponse struct { Rates map[string]float64 `json:"rates"` } func fetchExchangeRate() (float64, error) { client := &http.Client{Timeout: 10 * time.Second} resp, err := client.Get("https://api.exchangerate-api.com/v4/latest/USD") if err != nil { return 0.92, fmt.Errorf("failed to fetch exchange rate: %w", err) } defer resp.Body.Close() var rateData ExchangeRateResponse if err := json.NewDecoder(resp.Body).Decode(&rateData); err != nil { return 0.92, fmt.Errorf("failed to decode exchange rate: %w", err) } if eurRate, ok := rateData.Rates["EUR"]; ok { return eurRate, nil } return 0.92, fmt.Errorf("EUR rate not found") } func fetchStockPrice(ticker string) (float64, error) { client := &http.Client{Timeout: 10 * time.Second} // Try different ticker formats for European stocks tickersToTry := []string{ticker} // If ticker doesn't have exchange suffix, try adding common ones if !strings.Contains(ticker, ".") { tickersToTry = append(tickersToTry, ticker+".L", // London Stock Exchange ticker+".AS", // Amsterdam ticker+".PA", // Paris ticker+".DE", // XETRA (Germany) ticker+".MI", // Milan ) } var lastErr error for _, tryTicker := range tickersToTry { // Try Yahoo Finance API url := fmt.Sprintf("https://query1.finance.yahoo.com/v8/finance/chart/%s?interval=1d&range=1d", tryTicker) req, err := http.NewRequest("GET", url, nil) if err != nil { lastErr = err continue } req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36") resp, err := client.Do(req) if err != nil { lastErr = fmt.Errorf("failed to fetch from Yahoo: %w", err) continue } defer resp.Body.Close() body, err := io.ReadAll(resp.Body) if err != nil { lastErr = fmt.Errorf("failed to read response: %w", err) continue } var chartData YahooChartResponse if err := json.Unmarshal(body, &chartData); err != nil { lastErr = fmt.Errorf("failed to decode Yahoo response: %w", err) continue } if chartData.Chart.Error != nil { lastErr = fmt.Errorf("Yahoo API error: %s", chartData.Chart.Error.Description) continue } if len(chartData.Chart.Result) > 0 { price := chartData.Chart.Result[0].Meta.RegularMarketPrice if price > 0 { log.Printf("Successfully fetched price for %s (tried as %s): $%.2f", ticker, tryTicker, price) return price, nil } } } if lastErr != nil { return 0, fmt.Errorf("price not found after trying all formats: %w", lastErr) } return 0, fmt.Errorf("price not found in response") } func stockPricesHandler(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) return } var req StockPriceRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { http.Error(w, "Invalid request body", http.StatusBadRequest) return } // Fetch exchange rate exchangeRate, err := fetchExchangeRate() if err != nil { log.Printf("Warning: Using fallback exchange rate: %v", err) exchangeRate = 0.92 // Fallback } prices := make(map[string]StockPrice) // Fetch prices for each ticker for _, ticker := range req.Tickers { ticker = strings.TrimSpace(ticker) if ticker == "" { continue } usdPrice, err := fetchStockPrice(ticker) if err != nil { log.Printf("Error fetching price for %s: %v", ticker, err) prices[ticker] = StockPrice{ Ticker: ticker, Success: false, Error: err.Error(), } } else { prices[ticker] = StockPrice{ Ticker: ticker, USD: usdPrice, EUR: usdPrice * exchangeRate, Success: true, } } // Small delay to avoid rate limiting time.Sleep(100 * time.Millisecond) } response := StockPricesResponse{ Prices: prices, ExchangeRate: exchangeRate, } w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(response) } func main() { // API endpoint http.HandleFunc("/api/stock-prices", stockPricesHandler) // Serve static files from /app/static directory fs := http.FileServer(http.Dir("/app/static")) http.Handle("/", fs) port := ":8080" log.Printf("Server starting on port %s", port) if err := http.ListenAndServe(port, nil); err != nil { log.Fatal(err) } }