package main import ( "database/sql" "encoding/json" "errors" "flag" "fmt" "log" "net/http" _ "github.com/mattn/go-sqlite3" "gitlab.com/digineat/go-broker-test/cmd/server/validator" "gitlab.com/digineat/go-broker-test/internal/model" ) // server holds dependencies for HTTP handlers. type server struct { db *sql.DB } // handlePostTrades handles POST /trades requests. func (s *server) handlePostTrades() http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { trade := &model.Trade{} if err := json.NewDecoder(r.Body).Decode(trade); err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } if err := validator.ValidateTrade(trade); err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } _, err := s.db.Exec("INSERT INTO trades_q (account, symbol, volume, open_price, close_price, side) VALUES (?, ?, ?, ?, ?, ?)", trade.Account, trade.Symbol, trade.Volume, trade.OpenPrice, trade.ClosePrice, trade.Side) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } w.WriteHeader(http.StatusOK) } } // handleGetStats handles GET /stats/{acc} requests. func (s *server) handleGetStats() http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { acc := r.PathValue("acc") if acc == "" { http.Error(w, "Account is required", http.StatusBadRequest) return } var accStats model.AccountStats err := s.db.QueryRow("SELECT account, trades_count, profit, updated_at FROM account_stats WHERE account = ?", acc). Scan(&accStats.Account, &accStats.TradesCount, &accStats.Profit, &accStats.UpdatedAt) if err != nil { if errors.Is(err, sql.ErrNoRows) { w.Header().Set("Content-Type", "application/json") emptyStats := model.AccountStats{ Account: acc, TradesCount: 0, Profit: 0, } if jsonErr := json.NewEncoder(w).Encode(emptyStats); jsonErr != nil { http.Error(w, jsonErr.Error(), http.StatusInternalServerError) } return } http.Error(w, err.Error(), http.StatusInternalServerError) return } w.Header().Set("Content-Type", "application/json") if err := json.NewEncoder(w).Encode(accStats); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) } } } // handleGetHealthz handles GET /healthz requests. func (s *server) handleGetHealthz() http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { if err := s.db.Ping(); err != nil { w.WriteHeader(http.StatusInternalServerError) // Избегаем log.Fatalf в хендлерах для лучшей управляемости и тестируемости. // Можно логировать ошибку стандартным log.Printf или другой системой логирования. log.Printf("Health check: Failed to ping database: %v", err) return } w.WriteHeader(http.StatusOK) // Можно добавить тело ответа "OK", если это требуется. // _, _ = w.Write([]byte("OK")) } } func main() { // Command line flags dbPath := flag.String("db", "data.db", "path to SQLite database") listenAddr := flag.String("listen", "8080", "HTTP server listen address") flag.Parse() // Initialize database connection db, err := sql.Open("sqlite3", *dbPath) if err != nil { log.Fatalf("Failed to open database: %v", err) } defer db.Close() // Test database connection if err := db.Ping(); err != nil { log.Fatalf("Failed to ping database: %v", err) } // Create server instance app := &server{db: db} // Initialize HTTP server mux := http.NewServeMux() // Register handlers mux.HandleFunc("POST /trades", app.handlePostTrades()) mux.HandleFunc("GET /stats/{acc}", app.handleGetStats()) mux.HandleFunc("GET /healthz", app.handleGetHealthz()) // Start server serverAddr := fmt.Sprintf(":%s", *listenAddr) log.Printf("Starting server on %s", serverAddr) if err := http.ListenAndServe(serverAddr, mux); err != nil { log.Fatalf("Server failed: %v", err) } }