- Add new `app` package to manage application initialization and lifecycle - Refactor `main.go` to use new application management approach - Implement graceful shutdown with context timeout and signal handling - Add dependency injection container initialization - Enhance logging with configurable log levels and structured logging - Update configuration loading and server initialization process - Modify Jitsi configuration in `.env` for custom deployment - Improve error handling and logging throughout application startup - Centralize application startup and shutdown logic in single package Introduces a more robust and flexible application management system with improved initialization, logging, and shutdown capabilities.
339 lines
9.7 KiB
Go
339 lines
9.7 KiB
Go
package container
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"attune-heart-therapy/internal/config"
|
|
"attune-heart-therapy/internal/database"
|
|
"attune-heart-therapy/internal/handlers"
|
|
"attune-heart-therapy/internal/logger"
|
|
"attune-heart-therapy/internal/repositories"
|
|
"attune-heart-therapy/internal/services"
|
|
)
|
|
|
|
// Container holds all application dependencies
|
|
type Container struct {
|
|
Config *config.Config
|
|
Database *database.DB
|
|
Log *logger.Logger
|
|
|
|
// Repositories
|
|
Repositories *repositories.Repositories
|
|
|
|
// Services
|
|
JWTService services.JWTService
|
|
UserService services.UserService
|
|
BookingService services.BookingService
|
|
PaymentService services.PaymentService
|
|
NotificationService services.NotificationService
|
|
JitsiService services.JitsiService
|
|
AdminService services.AdminService
|
|
JobManagerService services.JobManagerService
|
|
|
|
// Handlers
|
|
AuthHandler *handlers.AuthHandler
|
|
BookingHandler *handlers.BookingHandler
|
|
PaymentHandler *handlers.PaymentHandler
|
|
AdminHandler *handlers.AdminHandler
|
|
}
|
|
|
|
// New creates a new dependency injection container
|
|
func New(cfg *config.Config) *Container {
|
|
return &Container{
|
|
Config: cfg,
|
|
Log: logger.New("container"),
|
|
}
|
|
}
|
|
|
|
// Initialize sets up all dependencies in the correct order
|
|
func (c *Container) Initialize() error {
|
|
c.Log.Info("Initializing application dependencies...")
|
|
|
|
// Initialize database connection
|
|
if err := c.initializeDatabase(); err != nil {
|
|
return fmt.Errorf("failed to initialize database: %w", err)
|
|
}
|
|
|
|
// Initialize repositories
|
|
c.initializeRepositories()
|
|
|
|
// Initialize services
|
|
if err := c.initializeServices(); err != nil {
|
|
return fmt.Errorf("failed to initialize services: %w", err)
|
|
}
|
|
|
|
// Initialize handlers
|
|
c.initializeHandlers()
|
|
|
|
c.Log.Info("Application dependencies initialized successfully")
|
|
return nil
|
|
}
|
|
|
|
// initializeDatabase sets up database connection and runs migrations
|
|
func (c *Container) initializeDatabase() error {
|
|
c.Log.Info("Initializing database connection...")
|
|
|
|
db, err := database.New(c.Config)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create database connection: %w", err)
|
|
}
|
|
c.Database = db
|
|
|
|
// Run database migrations
|
|
c.Log.Info("Running database migrations...")
|
|
if err := c.Database.Migrate(); err != nil {
|
|
return fmt.Errorf("failed to run database migrations: %w", err)
|
|
}
|
|
|
|
// Seed database with initial data
|
|
c.Log.Info("Seeding database with initial data...")
|
|
if err := c.Database.Seed(); err != nil {
|
|
return fmt.Errorf("failed to seed database: %w", err)
|
|
}
|
|
|
|
c.Log.Info("Database initialization completed")
|
|
return nil
|
|
}
|
|
|
|
// initializeRepositories sets up all repository instances
|
|
func (c *Container) initializeRepositories() {
|
|
c.Log.Info("Initializing repositories...")
|
|
c.Repositories = c.Database.GetRepositories()
|
|
c.Log.Info("Repositories initialized")
|
|
}
|
|
|
|
// initializeServices sets up all service instances with proper dependency injection
|
|
func (c *Container) initializeServices() error {
|
|
c.Log.Info("Initializing services...")
|
|
|
|
// Initialize JWT service (no dependencies)
|
|
c.JWTService = services.NewJWTService(c.Config.JWT.Secret, c.Config.JWT.Expiration)
|
|
|
|
// Initialize Jitsi service (no dependencies)
|
|
c.JitsiService = services.NewJitsiService(&c.Config.Jitsi)
|
|
|
|
// Initialize notification service (depends on notification repository and config)
|
|
c.NotificationService = services.NewNotificationService(c.Repositories.Notification, c.Config)
|
|
|
|
// Initialize user service (depends on user repository, JWT service, and notification service)
|
|
c.UserService = services.NewUserService(c.Repositories.User, c.JWTService, c.NotificationService)
|
|
|
|
// Initialize payment service (depends on config, booking/user repositories, and notification service)
|
|
c.PaymentService = services.NewPaymentService(c.Config, c.Repositories.Booking, c.Repositories.User, c.NotificationService)
|
|
|
|
// Initialize job manager service (depends on notification service and repositories)
|
|
c.JobManagerService = services.NewJobManagerService(c.NotificationService, c.Repositories.Booking, c.Repositories.User)
|
|
|
|
// Start the job manager
|
|
c.Log.Info("Starting job manager...")
|
|
if err := c.JobManagerService.Start(); err != nil {
|
|
return fmt.Errorf("failed to start job manager: %w", err)
|
|
}
|
|
c.Log.Info("Job manager started successfully")
|
|
|
|
// Initialize booking service (depends on multiple repositories and services)
|
|
c.BookingService = services.NewBookingService(
|
|
c.Repositories.Booking,
|
|
c.Repositories.Schedule,
|
|
c.Repositories.User,
|
|
c.JitsiService,
|
|
c.PaymentService,
|
|
c.NotificationService,
|
|
c.JobManagerService,
|
|
)
|
|
|
|
// Initialize admin service (depends on repositories)
|
|
c.AdminService = services.NewAdminService(c.Repositories.User, c.Repositories.Booking, c.Repositories.Schedule)
|
|
|
|
c.Log.Info("Services initialized successfully")
|
|
return nil
|
|
}
|
|
|
|
// initializeHandlers sets up all HTTP handlers with service dependencies
|
|
func (c *Container) initializeHandlers() {
|
|
c.Log.Info("Initializing handlers...")
|
|
|
|
c.AuthHandler = handlers.NewAuthHandler(c.UserService)
|
|
c.BookingHandler = handlers.NewBookingHandler(c.BookingService)
|
|
c.PaymentHandler = handlers.NewPaymentHandler(c.PaymentService)
|
|
c.AdminHandler = handlers.NewAdminHandler(c.AdminService)
|
|
|
|
c.Log.Info("Handlers initialized successfully")
|
|
}
|
|
|
|
// Shutdown gracefully shuts down all services and connections
|
|
func (c *Container) Shutdown() error {
|
|
c.Log.Info("Shutting down application dependencies...")
|
|
|
|
var shutdownErrors []error
|
|
|
|
// Stop job manager first
|
|
if c.JobManagerService != nil && c.JobManagerService.IsRunning() {
|
|
c.Log.Info("Stopping job manager...")
|
|
if err := c.JobManagerService.Stop(); err != nil {
|
|
c.Log.Error("Error stopping job manager", err)
|
|
shutdownErrors = append(shutdownErrors, fmt.Errorf("job manager shutdown error: %w", err))
|
|
} else {
|
|
c.Log.Info("Job manager stopped successfully")
|
|
}
|
|
}
|
|
|
|
// Close database connection
|
|
if c.Database != nil {
|
|
c.Log.Info("Closing database connection...")
|
|
if err := c.Database.Close(); err != nil {
|
|
c.Log.Error("Error closing database", err)
|
|
shutdownErrors = append(shutdownErrors, fmt.Errorf("database shutdown error: %w", err))
|
|
} else {
|
|
c.Log.Info("Database connection closed successfully")
|
|
}
|
|
}
|
|
|
|
if len(shutdownErrors) > 0 {
|
|
return fmt.Errorf("shutdown completed with errors: %v", shutdownErrors)
|
|
}
|
|
|
|
c.Log.Info("Application dependencies shutdown completed successfully")
|
|
return nil
|
|
}
|
|
|
|
// GetJWTService returns the JWT service for middleware usage
|
|
func (c *Container) GetJWTService() services.JWTService {
|
|
return c.JWTService
|
|
}
|
|
|
|
// HealthCheck performs a health check on all critical dependencies
|
|
func (c *Container) HealthCheck() map[string]interface{} {
|
|
health := map[string]interface{}{
|
|
"status": "ok",
|
|
}
|
|
|
|
// Check database connectivity
|
|
if c.Database != nil {
|
|
if err := c.Database.Health(); err != nil {
|
|
health["database"] = map[string]interface{}{
|
|
"status": "error",
|
|
"error": err.Error(),
|
|
}
|
|
health["status"] = "degraded"
|
|
} else {
|
|
health["database"] = map[string]interface{}{
|
|
"status": "healthy",
|
|
}
|
|
}
|
|
} else {
|
|
health["database"] = map[string]interface{}{
|
|
"status": "not_initialized",
|
|
}
|
|
health["status"] = "error"
|
|
}
|
|
|
|
// Check job manager status
|
|
if c.JobManagerService != nil {
|
|
isRunning := c.JobManagerService.IsRunning()
|
|
health["job_manager"] = map[string]interface{}{
|
|
"status": "healthy",
|
|
"running": isRunning,
|
|
}
|
|
if !isRunning {
|
|
health["job_manager"].(map[string]interface{})["status"] = "unhealthy"
|
|
health["status"] = "degraded"
|
|
}
|
|
} else {
|
|
health["job_manager"] = map[string]interface{}{
|
|
"status": "not_initialized",
|
|
}
|
|
health["status"] = "degraded"
|
|
}
|
|
|
|
// Check services initialization
|
|
servicesHealth := map[string]interface{}{}
|
|
|
|
if c.UserService != nil {
|
|
servicesHealth["user_service"] = "initialized"
|
|
} else {
|
|
servicesHealth["user_service"] = "not_initialized"
|
|
health["status"] = "degraded"
|
|
}
|
|
|
|
if c.BookingService != nil {
|
|
servicesHealth["booking_service"] = "initialized"
|
|
} else {
|
|
servicesHealth["booking_service"] = "not_initialized"
|
|
health["status"] = "degraded"
|
|
}
|
|
|
|
if c.PaymentService != nil {
|
|
servicesHealth["payment_service"] = "initialized"
|
|
} else {
|
|
servicesHealth["payment_service"] = "not_initialized"
|
|
health["status"] = "degraded"
|
|
}
|
|
|
|
if c.NotificationService != nil {
|
|
servicesHealth["notification_service"] = "initialized"
|
|
} else {
|
|
servicesHealth["notification_service"] = "not_initialized"
|
|
health["status"] = "degraded"
|
|
}
|
|
|
|
health["services"] = servicesHealth
|
|
|
|
return health
|
|
}
|
|
|
|
// IsInitialized returns true if the container has been initialized
|
|
func (c *Container) IsInitialized() bool {
|
|
return c.Database != nil && c.Repositories != nil && c.UserService != nil
|
|
}
|
|
|
|
// GetDependencyStatus returns the status of all dependencies
|
|
func (c *Container) GetDependencyStatus() map[string]string {
|
|
status := make(map[string]string)
|
|
|
|
// Database status
|
|
if c.Database != nil {
|
|
if err := c.Database.Health(); err != nil {
|
|
status["database"] = "unhealthy"
|
|
} else {
|
|
status["database"] = "healthy"
|
|
}
|
|
} else {
|
|
status["database"] = "not_initialized"
|
|
}
|
|
|
|
// Services status
|
|
services := []struct {
|
|
name string
|
|
service interface{}
|
|
}{
|
|
{"user_service", c.UserService},
|
|
{"booking_service", c.BookingService},
|
|
{"payment_service", c.PaymentService},
|
|
{"notification_service", c.NotificationService},
|
|
{"jitsi_service", c.JitsiService},
|
|
{"admin_service", c.AdminService},
|
|
{"job_manager_service", c.JobManagerService},
|
|
{"jwt_service", c.JWTService},
|
|
}
|
|
|
|
for _, svc := range services {
|
|
if svc.service != nil {
|
|
status[svc.name] = "initialized"
|
|
} else {
|
|
status[svc.name] = "not_initialized"
|
|
}
|
|
}
|
|
|
|
// Job manager running status
|
|
if c.JobManagerService != nil {
|
|
if c.JobManagerService.IsRunning() {
|
|
status["job_manager_running"] = "true"
|
|
} else {
|
|
status["job_manager_running"] = "false"
|
|
}
|
|
}
|
|
|
|
return status
|
|
}
|