2025-11-05 15:06:07 +00:00
|
|
|
package server
|
|
|
|
|
|
|
|
|
|
import (
|
2025-11-07 19:22:26 +00:00
|
|
|
"context"
|
2025-11-05 15:06:07 +00:00
|
|
|
"fmt"
|
2025-11-07 19:22:26 +00:00
|
|
|
"net/http"
|
|
|
|
|
"time"
|
2025-11-05 15:06:07 +00:00
|
|
|
|
|
|
|
|
"attune-heart-therapy/internal/config"
|
2025-11-07 19:22:26 +00:00
|
|
|
"attune-heart-therapy/internal/container"
|
|
|
|
|
"attune-heart-therapy/internal/health"
|
|
|
|
|
"attune-heart-therapy/internal/logger"
|
2025-11-06 09:31:51 +00:00
|
|
|
"attune-heart-therapy/internal/middleware"
|
2025-11-05 15:34:11 +00:00
|
|
|
"attune-heart-therapy/internal/services"
|
2025-11-05 15:06:07 +00:00
|
|
|
|
|
|
|
|
"github.com/gin-gonic/gin"
|
|
|
|
|
)
|
|
|
|
|
|
2025-11-06 09:31:51 +00:00
|
|
|
// 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()
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-05 15:06:07 +00:00
|
|
|
type Server struct {
|
2025-11-07 19:22:26 +00:00
|
|
|
config *config.Config
|
|
|
|
|
container *container.Container
|
|
|
|
|
router *gin.Engine
|
|
|
|
|
middleware *MiddlewareContainer
|
|
|
|
|
httpServer *http.Server
|
|
|
|
|
healthChecker *health.Checker
|
|
|
|
|
log *logger.Logger
|
2025-11-05 15:06:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func New(cfg *config.Config) *Server {
|
|
|
|
|
// Set Gin mode based on environment
|
|
|
|
|
gin.SetMode(gin.ReleaseMode)
|
|
|
|
|
|
|
|
|
|
router := gin.New()
|
|
|
|
|
|
2025-11-06 09:31:51 +00:00
|
|
|
// Configure middleware stack
|
|
|
|
|
setupMiddlewareStack(router)
|
2025-11-05 15:06:07 +00:00
|
|
|
|
2025-11-07 19:22:26 +00:00
|
|
|
// Initialize health checker
|
|
|
|
|
healthChecker := health.NewChecker()
|
|
|
|
|
|
|
|
|
|
// Initialize logger
|
|
|
|
|
log := logger.New("server")
|
|
|
|
|
|
2025-11-05 15:06:07 +00:00
|
|
|
return &Server{
|
2025-11-07 19:22:26 +00:00
|
|
|
config: cfg,
|
|
|
|
|
router: router,
|
|
|
|
|
healthChecker: healthChecker,
|
|
|
|
|
log: log,
|
2025-11-05 15:06:07 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-06 09:31:51 +00:00
|
|
|
// setupMiddlewareStack configures the Gin middleware stack
|
|
|
|
|
func setupMiddlewareStack(router *gin.Engine) {
|
2025-11-07 19:22:26 +00:00
|
|
|
// Tracing middleware - should be first to ensure trace ID is available
|
|
|
|
|
router.Use(middleware.TracingMiddleware())
|
|
|
|
|
|
|
|
|
|
// Security middleware
|
2025-11-06 09:31:51 +00:00
|
|
|
router.Use(middleware.SecurityMiddleware())
|
|
|
|
|
|
|
|
|
|
// CORS middleware for frontend integration
|
|
|
|
|
router.Use(middleware.CORSMiddleware())
|
|
|
|
|
|
|
|
|
|
// Request logging middleware
|
|
|
|
|
router.Use(middleware.StructuredLoggingMiddleware())
|
|
|
|
|
|
2025-11-07 19:22:26 +00:00
|
|
|
// Monitoring middleware for error tracking and metrics
|
|
|
|
|
router.Use(middleware.HealthCheckMiddleware())
|
|
|
|
|
|
|
|
|
|
// Error handling and recovery middleware
|
|
|
|
|
router.Use(middleware.ErrorHandlerMiddleware())
|
2025-11-06 09:31:51 +00:00
|
|
|
|
|
|
|
|
// Rate limiting middleware for API protection
|
|
|
|
|
router.Use(middleware.RateLimitMiddleware())
|
2025-11-07 19:22:26 +00:00
|
|
|
|
|
|
|
|
// Handle 404 and 405 errors
|
|
|
|
|
router.NoRoute(middleware.NotFoundHandler())
|
|
|
|
|
router.NoMethod(middleware.MethodNotAllowedHandler())
|
2025-11-06 09:31:51 +00:00
|
|
|
}
|
|
|
|
|
|
2025-11-07 19:22:26 +00:00
|
|
|
// SetContainer sets the dependency injection container
|
|
|
|
|
func (s *Server) SetContainer(container *container.Container) {
|
|
|
|
|
s.container = container
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Initialize sets up all application dependencies
|
2025-11-05 15:06:07 +00:00
|
|
|
func (s *Server) Initialize() error {
|
2025-11-07 19:22:26 +00:00
|
|
|
s.log.Info("Initializing server...")
|
2025-11-05 15:06:07 +00:00
|
|
|
|
2025-11-07 19:22:26 +00:00
|
|
|
if s.container == nil {
|
|
|
|
|
return fmt.Errorf("container not set - call SetContainer first")
|
2025-11-05 15:06:07 +00:00
|
|
|
}
|
|
|
|
|
|
2025-11-07 19:22:26 +00:00
|
|
|
// Initialize middleware container with JWT service
|
|
|
|
|
s.middleware = &MiddlewareContainer{
|
|
|
|
|
jwtService: s.container.GetJWTService(),
|
2025-11-05 15:06:07 +00:00
|
|
|
}
|
|
|
|
|
|
2025-11-07 19:22:26 +00:00
|
|
|
// Register health checks
|
|
|
|
|
s.registerHealthChecks()
|
2025-11-05 15:34:11 +00:00
|
|
|
|
2025-11-07 19:22:26 +00:00
|
|
|
s.log.Info("Server initialization completed successfully")
|
2025-11-05 15:06:07 +00:00
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (s *Server) Start() error {
|
2025-11-07 19:22:26 +00:00
|
|
|
// Initialize server components
|
2025-11-05 15:06:07 +00:00
|
|
|
if err := s.Initialize(); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Setup routes
|
|
|
|
|
s.setupRoutes()
|
|
|
|
|
|
2025-11-07 19:22:26 +00:00
|
|
|
// Create HTTP server with proper configuration
|
2025-11-05 15:06:07 +00:00
|
|
|
addr := fmt.Sprintf("%s:%s", s.config.Server.Host, s.config.Server.Port)
|
2025-11-07 19:22:26 +00:00
|
|
|
s.httpServer = &http.Server{
|
|
|
|
|
Addr: addr,
|
|
|
|
|
Handler: s.router,
|
|
|
|
|
ReadTimeout: 30 * time.Second,
|
|
|
|
|
WriteTimeout: 30 * time.Second,
|
|
|
|
|
IdleTimeout: 120 * time.Second,
|
|
|
|
|
}
|
2025-11-05 15:06:07 +00:00
|
|
|
|
2025-11-07 19:22:26 +00:00
|
|
|
s.log.Info("Starting server", map[string]interface{}{
|
|
|
|
|
"address": addr,
|
|
|
|
|
})
|
|
|
|
|
return s.httpServer.ListenAndServe()
|
2025-11-05 15:06:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Shutdown gracefully shuts down the server
|
2025-11-07 19:22:26 +00:00
|
|
|
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")
|
|
|
|
|
}
|
2025-11-05 15:06:07 +00:00
|
|
|
}
|
2025-11-07 19:22:26 +00:00
|
|
|
|
|
|
|
|
// 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")
|
2025-11-05 15:06:07 +00:00
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (s *Server) setupRoutes() {
|
2025-11-07 19:22:26 +00:00
|
|
|
// Health check endpoints - no middleware needed
|
2025-11-05 15:06:07 +00:00
|
|
|
s.router.GET("/health", s.healthCheck)
|
2025-11-07 19:22:26 +00:00
|
|
|
s.router.GET("/health/detailed", s.detailedHealthCheck)
|
|
|
|
|
s.router.GET("/metrics", s.metricsEndpoint)
|
2025-11-05 15:06:07 +00:00
|
|
|
|
2025-11-06 09:31:51 +00:00
|
|
|
// API v1 routes group with base middleware
|
2025-11-05 16:35:36 +00:00
|
|
|
v1 := s.router.Group("/api")
|
2025-11-05 15:06:07 +00:00
|
|
|
{
|
2025-11-06 09:31:51 +00:00
|
|
|
// Public routes group - no authentication required
|
|
|
|
|
public := v1.Group("/")
|
2025-11-05 15:06:07 +00:00
|
|
|
{
|
2025-11-06 09:31:51 +00:00
|
|
|
// Auth routes - public endpoints for registration and login
|
|
|
|
|
auth := public.Group("/auth")
|
|
|
|
|
{
|
|
|
|
|
// Apply strict rate limiting to auth endpoints
|
|
|
|
|
auth.Use(s.middleware.StrictRateLimit())
|
2025-11-07 19:22:26 +00:00
|
|
|
auth.POST("/register", s.container.AuthHandler.Register)
|
|
|
|
|
auth.POST("/login", s.container.AuthHandler.Login)
|
2025-11-06 09:31:51 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Schedule routes - public endpoint for getting available slots
|
2025-11-07 19:22:26 +00:00
|
|
|
public.GET("/schedules", s.container.BookingHandler.GetAvailableSlots)
|
2025-11-06 09:31:51 +00:00
|
|
|
|
|
|
|
|
// Payment webhook - public endpoint for Stripe webhooks (no auth needed)
|
2025-11-07 19:22:26 +00:00
|
|
|
public.POST("/payments/webhook", s.container.PaymentHandler.HandleWebhook)
|
2025-11-05 15:06:07 +00:00
|
|
|
}
|
|
|
|
|
|
2025-11-06 09:31:51 +00:00
|
|
|
// Authenticated routes group - require JWT authentication
|
|
|
|
|
authenticated := v1.Group("/")
|
|
|
|
|
authenticated.Use(s.middleware.Auth())
|
2025-11-05 15:06:07 +00:00
|
|
|
{
|
2025-11-06 09:31:51 +00:00
|
|
|
// Auth profile routes - require authentication
|
|
|
|
|
authProfile := authenticated.Group("/auth")
|
|
|
|
|
{
|
2025-11-07 19:22:26 +00:00
|
|
|
authProfile.GET("/profile", s.container.AuthHandler.GetProfile)
|
|
|
|
|
authProfile.PUT("/profile", s.container.AuthHandler.UpdateProfile)
|
|
|
|
|
authProfile.POST("/logout", s.container.AuthHandler.Logout)
|
2025-11-06 09:31:51 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Booking routes - require authentication
|
|
|
|
|
bookings := authenticated.Group("/bookings")
|
|
|
|
|
{
|
2025-11-07 19:22:26 +00:00
|
|
|
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)
|
2025-11-06 09:31:51 +00:00
|
|
|
}
|
|
|
|
|
|
2025-11-07 19:22:46 +00:00
|
|
|
// Meeting routes - require authentication
|
|
|
|
|
meetings := authenticated.Group("/meetings")
|
|
|
|
|
{
|
|
|
|
|
meetings.GET("/:id/link", s.container.MeetingHandler.GetMeetingLink)
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-06 09:31:51 +00:00
|
|
|
// Payment routes - require authentication (except webhook)
|
|
|
|
|
payments := authenticated.Group("/payments")
|
|
|
|
|
{
|
2025-11-07 19:22:26 +00:00
|
|
|
payments.POST("/intent", s.container.PaymentHandler.CreatePaymentIntent)
|
|
|
|
|
payments.POST("/confirm", s.container.PaymentHandler.ConfirmPayment)
|
2025-11-06 09:31:51 +00:00
|
|
|
}
|
2025-11-05 15:06:07 +00:00
|
|
|
}
|
|
|
|
|
|
2025-11-05 16:58:34 +00:00
|
|
|
// Admin routes - require admin authentication
|
2025-11-05 15:06:07 +00:00
|
|
|
admin := v1.Group("/admin")
|
2025-11-06 09:31:51 +00:00
|
|
|
admin.Use(s.middleware.RequireAdmin())
|
2025-11-05 15:06:07 +00:00
|
|
|
{
|
2025-11-07 19:22:26 +00:00
|
|
|
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)
|
2025-11-05 15:06:07 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-07 19:22:26 +00:00
|
|
|
// 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),
|
|
|
|
|
})
|
|
|
|
|
}
|
2025-11-05 15:42:59 +00:00
|
|
|
|
2025-11-07 19:22:26 +00:00
|
|
|
// detailedHealthCheck handles the detailed health check endpoint
|
|
|
|
|
func (s *Server) detailedHealthCheck(c *gin.Context) {
|
|
|
|
|
ctx := c.Request.Context()
|
2025-11-05 16:35:36 +00:00
|
|
|
|
2025-11-07 19:22:26 +00:00
|
|
|
// Perform comprehensive health checks
|
|
|
|
|
response := s.healthChecker.BuildResponse(ctx)
|
2025-11-05 15:34:11 +00:00
|
|
|
|
2025-11-07 19:22:26 +00:00
|
|
|
// Determine HTTP status code based on health status
|
|
|
|
|
statusCode := 200
|
|
|
|
|
switch response.Status {
|
|
|
|
|
case health.StatusUnhealthy:
|
|
|
|
|
statusCode = 503
|
|
|
|
|
case health.StatusDegraded:
|
|
|
|
|
statusCode = 503
|
2025-11-06 09:31:51 +00:00
|
|
|
}
|
|
|
|
|
|
2025-11-07 19:22:26 +00:00
|
|
|
c.JSON(statusCode, response)
|
2025-11-05 15:34:11 +00:00
|
|
|
}
|
|
|
|
|
|
2025-11-07 19:22:26 +00:00
|
|
|
// 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",
|
2025-11-05 15:06:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
c.JSON(200, response)
|
|
|
|
|
}
|
2025-11-07 19:22:26 +00:00
|
|
|
|
|
|
|
|
// 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")
|
|
|
|
|
}
|