package server import ( "encoding/json" "fmt" "net/http" "git.rouggy.com/rouggy/stockradar/internal/db" "git.rouggy.com/rouggy/stockradar/internal/settings" "github.com/gorilla/mux" ) type Server struct { db *db.DB port string router *mux.Router settings *settings.Settings } func New(database *db.DB, port string) (*Server, error) { svc, err := settings.New(database) if err != nil { return nil, err } s := &Server{ db: database, port: port, router: mux.NewRouter(), settings: svc, } s.setupRoutes() return s, nil } func (s *Server) setupRoutes() { s.router.Use(corsMiddleware) api := s.router.PathPrefix("/api").Subrouter() api.HandleFunc("/health", s.handleHealth).Methods("GET", "OPTIONS") // Settings api.HandleFunc("/settings", s.handleGetSettings).Methods("GET", "OPTIONS") api.HandleFunc("/settings", s.handleSaveSettings).Methods("POST", "OPTIONS") api.HandleFunc("/settings/test/{provider}", s.handleTestKey).Methods("GET", "OPTIONS") // Watchlist api.HandleFunc("/watchlist", s.handleGetWatchlist).Methods("GET", "OPTIONS") api.HandleFunc("/watchlist", s.handleAddWatchlist).Methods("POST", "OPTIONS") api.HandleFunc("/watchlist/{ticker}", s.handleRemoveWatchlist).Methods("DELETE", "OPTIONS") // News api.HandleFunc("/news", s.handleGetNews).Methods("GET", "OPTIONS") s.router.PathPrefix("/").HandlerFunc(func(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "StockRadar API running") }) } func corsMiddleware(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Access-Control-Allow-Origin", "*") w.Header().Set("Access-Control-Allow-Methods", "GET, POST, DELETE, OPTIONS") w.Header().Set("Access-Control-Allow-Headers", "Content-Type") if r.Method == "OPTIONS" { w.WriteHeader(http.StatusNoContent) return } next.ServeHTTP(w, r) }) } func (s *Server) handleHealth(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") fmt.Fprintf(w, `{"status":"ok","port":"%s"}`, s.port) } func (s *Server) handleGetSettings(w http.ResponseWriter, r *http.Request) { all, err := s.settings.GetAll() if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(all) } func (s *Server) handleSaveSettings(w http.ResponseWriter, r *http.Request) { var body map[string]interface{} if err := json.NewDecoder(r.Body).Decode(&body); err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } // Clés API → chiffrées, reste → plain text encryptedKeys := map[string]bool{ "etoro_api_key": true, "finnhub_api_key": true, "alphavantage_key": true, } for key, val := range body { value, ok := val.(string) if !ok { continue } encrypted := encryptedKeys[key] if err := s.settings.Set(key, value, encrypted); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } } w.Header().Set("Content-Type", "application/json") fmt.Fprintf(w, `{"status":"saved"}`) } func (s *Server) handleTestKey(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) provider := vars["provider"] keyMap := map[string]string{ "etoro": "etoro_api_key", "finnhub": "finnhub_api_key", "alphavantage": "alphavantage_key", } keyName, ok := keyMap[provider] if !ok { http.Error(w, "unknown provider", http.StatusBadRequest) return } if !s.settings.HasKey(keyName) { w.Header().Set("Content-Type", "application/json") fmt.Fprintf(w, `{"status":"error","message":"API key not configured"}`) return } // Pour l'instant on vérifie juste que la clé existe // On branchera le vrai ping API plus tard w.Header().Set("Content-Type", "application/json") fmt.Fprintf(w, `{"status":"ok","provider":"%s"}`, provider) } func (s *Server) Start() error { return http.ListenAndServe(":"+s.port, s.router) }