2025-05-07 00:25:34 +03:00

134 lines
4.0 KiB
Go

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