package server import ( "fmt" "log" "attune-heart-therapy/internal/config" "attune-heart-therapy/internal/database" "attune-heart-therapy/internal/handlers" "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 db *database.DB router *gin.Engine middleware *MiddlewareContainer authHandler *handlers.AuthHandler paymentHandler *handlers.PaymentHandler bookingHandler *handlers.BookingHandler adminHandler *handlers.AdminHandler } func New(cfg *config.Config) *Server { // Set Gin mode based on environment gin.SetMode(gin.ReleaseMode) router := gin.New() // Configure middleware stack setupMiddlewareStack(router) return &Server{ config: cfg, router: router, } } // setupMiddlewareStack configures the Gin middleware stack func setupMiddlewareStack(router *gin.Engine) { // Security middleware - should be first router.Use(middleware.SecurityMiddleware()) // CORS middleware for frontend integration router.Use(middleware.CORSMiddleware()) // Request logging middleware router.Use(middleware.StructuredLoggingMiddleware()) // Recovery middleware to handle panics router.Use(gin.Recovery()) // Rate limiting middleware for API protection router.Use(middleware.RateLimitMiddleware()) } // Initialize sets up the database connection and runs migrations func (s *Server) Initialize() error { // Initialize database connection db, err := database.New(s.config) if err != nil { return fmt.Errorf("failed to initialize database: %w", err) } s.db = db // Run database migrations if err := s.db.Migrate(); err != nil { return fmt.Errorf("failed to run database migrations: %w", err) } // Seed database with initial data if err := s.db.Seed(); err != nil { return fmt.Errorf("failed to seed database: %w", err) } // Initialize services and handlers s.initializeServices() log.Println("Server initialization completed successfully") return nil } func (s *Server) Start() error { // Initialize database and run migrations if err := s.Initialize(); err != nil { return err } // Setup routes s.setupRoutes() // Start server addr := fmt.Sprintf("%s:%s", s.config.Server.Host, s.config.Server.Port) log.Printf("Starting server on %s", addr) return s.router.Run(addr) } // Shutdown gracefully shuts down the server func (s *Server) Shutdown() error { if s.db != nil { log.Println("Closing database connection...") return s.db.Close() } return nil } func (s *Server) setupRoutes() { // Health check endpoint - no middleware needed s.router.GET("/health", s.healthCheck) // 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.authHandler.Register) auth.POST("/login", s.authHandler.Login) } // Schedule routes - public endpoint for getting available slots public.GET("/schedules", s.bookingHandler.GetAvailableSlots) // Payment webhook - public endpoint for Stripe webhooks (no auth needed) public.POST("/payments/webhook", s.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.authHandler.GetProfile) authProfile.PUT("/profile", s.authHandler.UpdateProfile) authProfile.POST("/logout", s.authHandler.Logout) } // Booking routes - require authentication bookings := authenticated.Group("/bookings") { bookings.GET("/", s.bookingHandler.GetUserBookings) bookings.POST("/", s.bookingHandler.CreateBooking) bookings.PUT("/:id/cancel", s.bookingHandler.CancelBooking) bookings.PUT("/:id/reschedule", s.bookingHandler.RescheduleBooking) } // Payment routes - require authentication (except webhook) payments := authenticated.Group("/payments") { payments.POST("/intent", s.paymentHandler.CreatePaymentIntent) payments.POST("/confirm", s.paymentHandler.ConfirmPayment) } } // Admin routes - require admin authentication admin := v1.Group("/admin") admin.Use(s.middleware.RequireAdmin()) { admin.GET("/dashboard", s.adminHandler.GetDashboard) admin.POST("/schedules", s.adminHandler.CreateSchedule) admin.PUT("/schedules/:id", s.adminHandler.UpdateSchedule) admin.GET("/users", s.adminHandler.GetUsers) admin.GET("/bookings", s.adminHandler.GetBookings) admin.GET("/reports/financial", s.adminHandler.GetFinancialReports) } } } // initializeServices sets up all services and handlers func (s *Server) initializeServices() { // Initialize repositories repos := s.db.GetRepositories() // Initialize Jitsi service jitsiService := services.NewJitsiService(&s.config.Jitsi) // Initialize notification service notificationService := services.NewNotificationService(repos.Notification, s.config) // Initialize JWT service (needed for user service and middleware) jwtService := services.NewJWTService(s.config.JWT.Secret, s.config.JWT.Expiration) // Initialize middleware container with JWT service s.middleware = &MiddlewareContainer{ jwtService: jwtService, } // Initialize user service with notification integration userService := services.NewUserService(repos.User, jwtService, notificationService) // Initialize payment service with notification integration paymentService := services.NewPaymentService(s.config, repos.Booking, repos.User, notificationService) // Initialize booking service with notification integration bookingService := services.NewBookingService( repos.Booking, repos.Schedule, repos.User, jitsiService, paymentService, notificationService, ) // Initialize admin service adminService := services.NewAdminService(repos.User, repos.Booking, repos.Schedule) // Initialize handlers s.authHandler = handlers.NewAuthHandler(userService) s.paymentHandler = handlers.NewPaymentHandler(paymentService) s.bookingHandler = handlers.NewBookingHandler(bookingService) s.adminHandler = handlers.NewAdminHandler(adminService) } // healthCheck handles the health check endpoint func (s *Server) healthCheck(c *gin.Context) { response := gin.H{ "status": "ok", "message": "Video Conference Booking System API", } // Check database connectivity if s.db != nil { if err := s.db.Health(); err != nil { response["status"] = "error" response["database"] = "disconnected" response["error"] = err.Error() c.JSON(500, response) return } response["database"] = "connected" } else { response["database"] = "not initialized" } c.JSON(200, response) }