backend-service/internal/server/server.go

318 lines
9.3 KiB
Go
Raw Normal View History

package server
import (
"context"
"fmt"
"net/http"
"time"
"attune-heart-therapy/internal/config"
"attune-heart-therapy/internal/container"
"attune-heart-therapy/internal/health"
"attune-heart-therapy/internal/logger"
"attune-heart-therapy/internal/middleware"
"attune-heart-therapy/internal/services"
"github.com/gin-gonic/gin"
)
// MiddlewareContainer holds all middleware functions
type MiddlewareContainer struct {
jwtService services.JWTService
}
// Auth returns the authentication middleware
func (m *MiddlewareContainer) Auth() gin.HandlerFunc {
return middleware.AuthMiddleware(m.jwtService)
}
// RequireAdmin returns the admin authentication middleware
func (m *MiddlewareContainer) RequireAdmin() gin.HandlerFunc {
return middleware.RequireAdmin(m.jwtService)
}
// StrictRateLimit returns the strict rate limiting middleware
func (m *MiddlewareContainer) StrictRateLimit() gin.HandlerFunc {
return middleware.StrictRateLimitMiddleware()
}
type Server struct {
config *config.Config
container *container.Container
router *gin.Engine
middleware *MiddlewareContainer
httpServer *http.Server
healthChecker *health.Checker
log *logger.Logger
}
func New(cfg *config.Config) *Server {
// Set Gin mode based on environment
gin.SetMode(gin.ReleaseMode)
router := gin.New()
// Configure middleware stack
setupMiddlewareStack(router)
// Initialize health checker
healthChecker := health.NewChecker()
// Initialize logger
log := logger.New("server")
return &Server{
config: cfg,
router: router,
healthChecker: healthChecker,
log: log,
}
}
// setupMiddlewareStack configures the Gin middleware stack
func setupMiddlewareStack(router *gin.Engine) {
// Tracing middleware - should be first to ensure trace ID is available
router.Use(middleware.TracingMiddleware())
// Security middleware
router.Use(middleware.SecurityMiddleware())
// CORS middleware for frontend integration
router.Use(middleware.CORSMiddleware())
// Request logging middleware
router.Use(middleware.StructuredLoggingMiddleware())
// Monitoring middleware for error tracking and metrics
router.Use(middleware.HealthCheckMiddleware())
// Error handling and recovery middleware
router.Use(middleware.ErrorHandlerMiddleware())
// Rate limiting middleware for API protection
router.Use(middleware.RateLimitMiddleware())
// Handle 404 and 405 errors
router.NoRoute(middleware.NotFoundHandler())
router.NoMethod(middleware.MethodNotAllowedHandler())
}
// SetContainer sets the dependency injection container
func (s *Server) SetContainer(container *container.Container) {
s.container = container
}
// Initialize sets up all application dependencies
func (s *Server) Initialize() error {
s.log.Info("Initializing server...")
if s.container == nil {
return fmt.Errorf("container not set - call SetContainer first")
}
// Initialize middleware container with JWT service
s.middleware = &MiddlewareContainer{
jwtService: s.container.GetJWTService(),
}
// Register health checks
s.registerHealthChecks()
s.log.Info("Server initialization completed successfully")
return nil
}
func (s *Server) Start() error {
// Initialize server components
if err := s.Initialize(); err != nil {
return err
}
// Setup routes
s.setupRoutes()
// Create HTTP server with proper configuration
addr := fmt.Sprintf("%s:%s", s.config.Server.Host, s.config.Server.Port)
s.httpServer = &http.Server{
Addr: addr,
Handler: s.router,
ReadTimeout: 30 * time.Second,
WriteTimeout: 30 * time.Second,
IdleTimeout: 120 * time.Second,
}
s.log.Info("Starting server", map[string]interface{}{
"address": addr,
})
return s.httpServer.ListenAndServe()
}
// Shutdown gracefully shuts down the server
func (s *Server) Shutdown(ctx context.Context) error {
s.log.Info("Shutting down server...")
var shutdownErrors []error
// Shutdown HTTP server gracefully
if s.httpServer != nil {
s.log.Info("Shutting down HTTP server...")
if err := s.httpServer.Shutdown(ctx); err != nil {
s.log.Error("Error shutting down HTTP server", err)
shutdownErrors = append(shutdownErrors, fmt.Errorf("HTTP server shutdown error: %w", err))
} else {
s.log.Info("HTTP server shutdown completed")
}
}
// Shutdown application dependencies
if s.container != nil {
if err := s.container.Shutdown(); err != nil {
s.log.Error("Error shutting down dependencies", err)
shutdownErrors = append(shutdownErrors, err)
}
}
if len(shutdownErrors) > 0 {
return fmt.Errorf("server shutdown completed with errors: %v", shutdownErrors)
}
s.log.Info("Server shutdown completed successfully")
return nil
}
func (s *Server) setupRoutes() {
// Health check endpoints - no middleware needed
s.router.GET("/health", s.healthCheck)
s.router.GET("/health/detailed", s.detailedHealthCheck)
s.router.GET("/metrics", s.metricsEndpoint)
// API v1 routes group with base middleware
v1 := s.router.Group("/api")
{
// Public routes group - no authentication required
public := v1.Group("/")
{
// Auth routes - public endpoints for registration and login
auth := public.Group("/auth")
{
// Apply strict rate limiting to auth endpoints
auth.Use(s.middleware.StrictRateLimit())
auth.POST("/register", s.container.AuthHandler.Register)
auth.POST("/login", s.container.AuthHandler.Login)
}
// Schedule routes - public endpoint for getting available slots
public.GET("/schedules", s.container.BookingHandler.GetAvailableSlots)
// Payment webhook - public endpoint for Stripe webhooks (no auth needed)
public.POST("/payments/webhook", s.container.PaymentHandler.HandleWebhook)
}
// Authenticated routes group - require JWT authentication
authenticated := v1.Group("/")
authenticated.Use(s.middleware.Auth())
{
// Auth profile routes - require authentication
authProfile := authenticated.Group("/auth")
{
authProfile.GET("/profile", s.container.AuthHandler.GetProfile)
authProfile.PUT("/profile", s.container.AuthHandler.UpdateProfile)
authProfile.POST("/logout", s.container.AuthHandler.Logout)
}
// Booking routes - require authentication
bookings := authenticated.Group("/bookings")
{
bookings.GET("/", s.container.BookingHandler.GetUserBookings)
bookings.POST("/", s.container.BookingHandler.CreateBooking)
bookings.PUT("/:id/cancel", s.container.BookingHandler.CancelBooking)
bookings.PUT("/:id/reschedule", s.container.BookingHandler.RescheduleBooking)
}
// Payment routes - require authentication (except webhook)
payments := authenticated.Group("/payments")
{
payments.POST("/intent", s.container.PaymentHandler.CreatePaymentIntent)
payments.POST("/confirm", s.container.PaymentHandler.ConfirmPayment)
}
}
// Admin routes - require admin authentication
admin := v1.Group("/admin")
admin.Use(s.middleware.RequireAdmin())
{
admin.GET("/dashboard", s.container.AdminHandler.GetDashboard)
admin.POST("/schedules", s.container.AdminHandler.CreateSchedule)
admin.PUT("/schedules/:id", s.container.AdminHandler.UpdateSchedule)
admin.GET("/users", s.container.AdminHandler.GetUsers)
admin.GET("/bookings", s.container.AdminHandler.GetBookings)
admin.GET("/reports/financial", s.container.AdminHandler.GetFinancialReports)
}
}
}
// healthCheck handles the basic health check endpoint
func (s *Server) healthCheck(c *gin.Context) {
// Simple health check - just return OK if server is running
c.JSON(200, gin.H{
"status": "ok",
"message": "Video Conference Booking System API",
"timestamp": time.Now().UTC().Format(time.RFC3339),
})
}
// detailedHealthCheck handles the detailed health check endpoint
func (s *Server) detailedHealthCheck(c *gin.Context) {
ctx := c.Request.Context()
// Perform comprehensive health checks
response := s.healthChecker.BuildResponse(ctx)
// Determine HTTP status code based on health status
statusCode := 200
switch response.Status {
case health.StatusUnhealthy:
statusCode = 503
case health.StatusDegraded:
statusCode = 503
}
c.JSON(statusCode, response)
}
// metricsEndpoint handles the metrics endpoint
func (s *Server) metricsEndpoint(c *gin.Context) {
// Import monitoring package to get metrics
// Note: In a real implementation, you might want to use a proper metrics format like Prometheus
response := map[string]interface{}{
"status": "ok",
"timestamp": time.Now().UTC().Format(time.RFC3339),
"service": "Video Conference Booking System",
"message": "Metrics endpoint - monitoring data would be available here",
}
c.JSON(200, response)
}
// registerHealthChecks registers all health checks
func (s *Server) registerHealthChecks() {
s.log.Info("Registering health checks...")
// Database health check
s.healthChecker.RegisterCheck("database", health.DatabaseHealthCheck(s.container.Database))
// Job manager health check
s.healthChecker.RegisterCheck("job_manager", health.JobManagerHealthCheck(s.container.JobManagerService))
// Monitoring system health check
s.healthChecker.RegisterCheck("monitoring", health.MonitoringHealthCheck())
// Memory health check (example with 512MB limit)
s.healthChecker.RegisterCheck("memory", health.MemoryHealthCheck(512))
// Disk space health check (example with 1GB minimum)
s.healthChecker.RegisterCheck("disk_space", health.DiskSpaceHealthCheck("/", 1))
s.log.Info("Health checks registered successfully")
}