feat: some refactoring

This commit is contained in:
2025-11-22 22:23:49 +03:00
parent a247e6213e
commit 51f30b516e
29 changed files with 1094 additions and 129 deletions

View File

View File

@@ -0,0 +1,37 @@
package handlers
import (
"encoding/json"
"net/http"
"git.nwaifu.su/sergey/MyGoServer/internal/apiserver/models"
)
// HomeHandler handles the root endpoint
type HomeHandler struct{}
func NewHomeHandler() *HomeHandler {
return &HomeHandler{}
}
// ServeHTTP implements http.Handler
func (h *HomeHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
h.handleHome(w, r)
}
// @Summary Health check
// @Description Проверка состояния сервера
// @Tags Health
// @Success 200 {object} models.Response "Server is running"
// @Router / [get]
func (h *HomeHandler) handleHome(w http.ResponseWriter, r *http.Request) {
// Create a simple success response
response := models.NewSuccessResponse(map[string]string{
"status": "ok",
"message": "API Server is running",
})
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(response)
}

View File

@@ -0,0 +1,41 @@
package handlers
import (
"net/http"
"net/http/httptest"
"strings"
"testing"
)
func TestHomeHandler_ServeHTTP(t *testing.T) {
// Create a new home handler
handler := NewHomeHandler()
// Create a test request
req := httptest.NewRequest("GET", "/", nil)
// Create a test response recorder
rr := httptest.NewRecorder()
// Call the handler
handler.ServeHTTP(rr, req)
// Check status code
if status := rr.Code; status != http.StatusOK {
t.Errorf("handler returned wrong status code: got %v want %v",
status, http.StatusOK)
}
// Check content type
if ct := rr.Header().Get("Content-Type"); ct != "application/json" {
t.Errorf("handler returned wrong content type: got %v want %v",
ct, "application/json")
}
// Check response body contains expected data
expectedBody := `"success":true`
if body := rr.Body.String(); !strings.Contains(body, expectedBody) {
t.Errorf("handler returned unexpected body: got %v want %v",
body, expectedBody)
}
}

View File

@@ -0,0 +1,76 @@
package logger
import (
"sync"
"github.com/sirupsen/logrus"
)
var (
logger *logrus.Logger
once sync.Once
initialized bool
)
// Initialize logger with configuration
func Initialize(level string, format string, output string) {
once.Do(func() {
logger = logrus.New()
// Set level
lvl, err := logrus.ParseLevel(level)
if err != nil {
lvl = logrus.InfoLevel
}
logger.SetLevel(lvl)
// Set format
switch format {
case "json":
logger.SetFormatter(&logrus.JSONFormatter{
TimestampFormat: "2006-01-02T15:04:05.000Z",
})
case "text":
logger.SetFormatter(&logrus.TextFormatter{
TimestampFormat: "2006-01-02 15:04:05",
FullTimestamp: true,
})
}
// Set output
if output != "" {
//TODO: Use files
}
initialized = true
})
}
// GetLogger returns the singleton logger instance
func GetLogger() *logrus.Logger {
if !initialized {
// Initialize with defaults
Initialize("info", "json", "stdout")
}
return logger
}
// WithFields creates a logger with additional fields
func WithFields(fields map[string]interface{}) *logrus.Entry {
return GetLogger().WithFields(fields)
}
// Info logs an info message
func Info(msg string) {
GetLogger().Info(msg)
}
// Error logs an error message
func Error(msg string) {
GetLogger().Error(msg)
}
// Debug logs a debug message
func Debug(msg string) {
GetLogger().Debug(msg)
}

View File

@@ -0,0 +1,42 @@
package middleware
import (
"context"
"net/http"
"time"
"git.nwaifu.su/sergey/MyGoServer/internal/apiserver/logger"
rhttp "git.nwaifu.su/sergey/MyGoServer/pkg/http"
)
// LoggingMiddleware logs HTTP requests
func LoggingMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
// Use the custom response writer to capture status code
rw := rhttp.NewResponseWriter(w)
next.ServeHTTP(rw, r)
// Log the request
logger := logger.GetLogger()
logger.WithFields(map[string]interface{}{
"method": r.Method,
"uri": r.RequestURI,
"remote_addr": r.RemoteAddr,
"user_agent": r.UserAgent(),
"status_code": rw.GetStatusCode(),
"duration": time.Since(start).String(),
"request_id": getRequestID(r.Context()),
}).Info("HTTP request")
})
}
// getRequestID retrieves request ID from context
func getRequestID(ctx context.Context) string {
if id, ok := ctx.Value("request_id").(string); ok {
return id
}
return ""
}

View File

@@ -0,0 +1,33 @@
package middleware
import (
"context"
"net/http"
"git.nwaifu.su/sergey/MyGoServer/internal/apiserver/logger"
"github.com/google/uuid"
)
// RequestIDMiddleware adds a unique request ID to each request
func RequestIDMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Generate a new request ID
requestID := uuid.New().String()
// Set request ID in response header
w.Header().Set("X-Request-ID", requestID)
// Add request ID to context
ctx := r.Context()
ctx = context.WithValue(ctx, "request_id", requestID)
// Log the request ID assignment
logger := logger.GetLogger()
logger.WithFields(map[string]interface{}{
"request_id": requestID,
}).Debug("Request ID assigned")
// Continue with the request
next.ServeHTTP(w, r.WithContext(ctx))
})
}

View File

@@ -0,0 +1,25 @@
package models
// Response represents a standard API response
type Response struct {
Success bool `json:"success"`
Data interface{} `json:"data,omitempty"`
Error string `json:"error,omitempty"`
RequestID string `json:"request_id,omitempty"`
}
// NewSuccessResponse creates a successful response
func NewSuccessResponse(data interface{}) *Response {
return &Response{
Success: true,
Data: data,
}
}
// NewErrorResponse creates an error response
func NewErrorResponse(err error) *Response {
return &Response{
Success: false,
Error: err.Error(),
}
}

View File

@@ -0,0 +1,22 @@
package server
import (
_ "git.nwaifu.su/sergey/MyGoServer/internal/apiserver/docs"
"git.nwaifu.su/sergey/MyGoServer/internal/apiserver/handlers"
"git.nwaifu.su/sergey/MyGoServer/internal/apiserver/middleware"
httpSwagger "github.com/swaggo/http-swagger"
)
// setupRoutes configures all routes
func (s *Server) setupRoutes() {
// Add request ID middleware to all routes
s.router.Handle("/", middleware.RequestIDMiddleware(
middleware.LoggingMiddleware(
handlers.NewHomeHandler(),
),
))
// Swagger UI
s.router.Handle("/swagger/", httpSwagger.WrapHandler)
}

View File

@@ -0,0 +1,70 @@
package server
import (
"context"
"net"
"net/http"
"git.nwaifu.su/sergey/MyGoServer/cmd/apiserver/config"
"git.nwaifu.su/sergey/MyGoServer/internal/apiserver/logger"
)
type contextKey struct {
key string
}
var connContextKey = &contextKey{"http-conn"}
func saveConnInContext(ctx context.Context, c net.Conn) context.Context {
return context.WithValue(ctx, connContextKey, c)
}
// Server represents the HTTP server
type Server struct {
config *config.Config
router *http.ServeMux
server *http.Server
}
// NewServer creates a new server instance
func NewServer(cfg *config.Config) *Server {
s := &Server{
config: cfg,
}
// Initialize logger
logger.Initialize(cfg.Logging.Level, cfg.Logging.Format, cfg.Logging.Output)
// Create router
s.router = http.NewServeMux()
s.setupRoutes()
// Create HTTP server
s.server = &http.Server{
Addr: cfg.Server.Address,
Handler: s.router,
ConnContext: saveConnInContext,
}
return s
}
// Start starts the server
func (s *Server) Start() error {
return s.server.ListenAndServe()
}
// GetRouter returns the HTTP router
func (s *Server) GetRouter() *http.ServeMux {
return s.router
}
// GetServer returns the HTTP server instance
func (s *Server) GetServer() *http.Server {
return s.server
}
// GetConfig returns the server configuration
func (s *Server) GetConfig() *config.Config {
return s.config
}

View File

View File