package app import ( "context" "log" "net/http" "os" "os/signal" "syscall" "time" "eslogad-be/config" "eslogad-be/internal/client" internalConfig "eslogad-be/internal/config" "eslogad-be/internal/handler" "eslogad-be/internal/middleware" "eslogad-be/internal/processor" "eslogad-be/internal/repository" "eslogad-be/internal/router" "eslogad-be/internal/service" "eslogad-be/internal/validator" "gorm.io/gorm" ) type App struct { server *http.Server db *gorm.DB router *router.Router shutdown chan os.Signal } func NewApp(db *gorm.DB) *App { return &App{ db: db, shutdown: make(chan os.Signal, 1), } } func (a *App) Initialize(cfg *config.Config) error { repos := a.initRepositories() processors := a.initProcessors(cfg, repos) services := a.initServices(processors, repos, cfg) middlewares := a.initMiddleware(services) healthHandler := handler.NewHealthHandler() fileHandler := handler.NewFileHandler(services.fileService) rbacHandler := handler.NewRBACHandler(services.rbacService) masterHandler := handler.NewMasterHandler(services.masterService) letterHandler := handler.NewLetterHandler(services.letterService) letterOutgoingHandler := handler.NewLetterOutgoingHandler(services.letterOutgoingService) adminApprovalFlowHandler := handler.NewAdminApprovalFlowHandler(services.approvalFlowService) dispositionRouteHandler := handler.NewDispositionRouteHandler(services.dispositionRouteService) onlyOfficeHandler := handler.NewOnlyOfficeHandler(services.onlyOfficeService) analyticsHandler := handler.NewAnalyticsHandler(services.analyticsService) notificationHandler := handler.NewNotificationHandler(services.notificationService) a.router = router.NewRouter( cfg, handler.NewAuthHandler(services.authService), middlewares.authMiddleware, healthHandler, handler.NewUserHandler(services.userService, validator.NewUserValidator()), fileHandler, rbacHandler, masterHandler, letterHandler, letterOutgoingHandler, adminApprovalFlowHandler, dispositionRouteHandler, onlyOfficeHandler, analyticsHandler, notificationHandler, ) return nil } func (a *App) Start(port string) error { engine := a.router.Init() a.server = &http.Server{ Addr: ":" + port, Handler: engine, ReadTimeout: 15 * time.Second, WriteTimeout: 15 * time.Second, IdleTimeout: 60 * time.Second, } signal.Notify(a.shutdown, os.Interrupt, syscall.SIGTERM) go func() { log.Printf("Server starting on port %s", port) if err := a.server.ListenAndServe(); err != nil && err != http.ErrServerClosed { log.Fatalf("Failed to start server: %v", err) } }() <-a.shutdown log.Println("Shutting down server...") ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() if err := a.server.Shutdown(ctx); err != nil { log.Printf("Server forced to shutdown: %v", err) return err } log.Println("Server exited gracefully") return nil } func (a *App) Shutdown() { close(a.shutdown) } type repositories struct { userRepo *repository.UserRepositoryImpl userProfileRepo *repository.UserProfileRepository titleRepo *repository.TitleRepository rbacRepo *repository.RBACRepository labelRepo *repository.LabelRepository priorityRepo *repository.PriorityRepository institutionRepo *repository.InstitutionRepository dispRepo *repository.DispositionActionRepository letterRepo *repository.LetterIncomingRepository letterAttachRepo *repository.LetterIncomingAttachmentRepository activityLogRepo *repository.LetterIncomingActivityLogRepository dispositionRouteRepo *repository.DispositionRouteRepository // new repos letterDispositionRepo *repository.LetterIncomingDispositionRepository letterDispositionDeptRepo *repository.LetterIncomingDispositionDepartmentRepository letterDispActionSelRepo *repository.LetterDispositionActionSelectionRepository dispositionNoteRepo *repository.DispositionNoteRepository letterDiscussionRepo *repository.LetterDiscussionRepository settingRepo *repository.AppSettingRepository recipientRepo *repository.LetterIncomingRecipientRepository departmentRepo *repository.DepartmentRepository userDeptRepo *repository.UserDepartmentRepository // letter outgoing repos letterOutgoingRepo *repository.LetterOutgoingRepository letterOutgoingAttachmentRepo *repository.LetterOutgoingAttachmentRepository letterOutgoingRecipientRepo *repository.LetterOutgoingRecipientRepository letterOutgoingDiscussionRepo *repository.LetterOutgoingDiscussionRepository letterOutgoingDiscussionAttachRepo *repository.LetterOutgoingDiscussionAttachmentRepository letterOutgoingActivityLogRepo *repository.LetterOutgoingActivityLogRepository approvalFlowRepo *repository.ApprovalFlowRepository letterOutgoingApprovalRepo *repository.LetterOutgoingApprovalRepository analyticsRepo *repository.AnalyticsRepository } func (a *App) initRepositories() *repositories { return &repositories{ userRepo: repository.NewUserRepository(a.db), userProfileRepo: repository.NewUserProfileRepository(a.db), titleRepo: repository.NewTitleRepository(a.db), rbacRepo: repository.NewRBACRepository(a.db), labelRepo: repository.NewLabelRepository(a.db), priorityRepo: repository.NewPriorityRepository(a.db), institutionRepo: repository.NewInstitutionRepository(a.db), dispRepo: repository.NewDispositionActionRepository(a.db), letterRepo: repository.NewLetterIncomingRepository(a.db), letterAttachRepo: repository.NewLetterIncomingAttachmentRepository(a.db), activityLogRepo: repository.NewLetterIncomingActivityLogRepository(a.db), dispositionRouteRepo: repository.NewDispositionRouteRepository(a.db), letterDispositionRepo: repository.NewLetterIncomingDispositionRepository(a.db), letterDispositionDeptRepo: repository.NewLetterIncomingDispositionDepartmentRepository(a.db), letterDispActionSelRepo: repository.NewLetterDispositionActionSelectionRepository(a.db), dispositionNoteRepo: repository.NewDispositionNoteRepository(a.db), letterDiscussionRepo: repository.NewLetterDiscussionRepository(a.db), settingRepo: repository.NewAppSettingRepository(a.db), recipientRepo: repository.NewLetterIncomingRecipientRepository(a.db), departmentRepo: repository.NewDepartmentRepository(a.db), userDeptRepo: repository.NewUserDepartmentRepository(a.db), letterOutgoingRepo: repository.NewLetterOutgoingRepository(a.db), letterOutgoingAttachmentRepo: repository.NewLetterOutgoingAttachmentRepository(a.db), letterOutgoingRecipientRepo: repository.NewLetterOutgoingRecipientRepository(a.db), letterOutgoingDiscussionRepo: repository.NewLetterOutgoingDiscussionRepository(a.db), letterOutgoingDiscussionAttachRepo: repository.NewLetterOutgoingDiscussionAttachmentRepository(a.db), letterOutgoingActivityLogRepo: repository.NewLetterOutgoingActivityLogRepository(a.db), approvalFlowRepo: repository.NewApprovalFlowRepository(a.db), letterOutgoingApprovalRepo: repository.NewLetterOutgoingApprovalRepository(a.db), analyticsRepo: repository.NewAnalyticsRepository(a.db), } } type processors struct { userProcessor *processor.UserProcessorImpl cachedUserProcessor *processor.CachedUserProcessor letterProcessor *processor.LetterProcessorImpl letterOutgoingProcessor *processor.LetterOutgoingProcessorImpl activityLogger *processor.ActivityLogProcessorImpl letterNumberGenerator *processor.LetterNumberGeneratorImpl onlyOfficeProcessor *processor.OnlyOfficeProcessorImpl novuProcessor processor.NovuProcessor notificationProcessor processor.NotificationProcessor recipientProcessor *processor.RecipientProcessorImpl letterDispositionProcessor *processor.LetterDispositionProcessorImpl letterDispositionDeptProcessor *processor.LetterDispositionDepartmentProcessorImpl // Modular processors for letter outgoing letterValidationProcessor processor.LetterValidationProcessor letterCreationProcessor processor.LetterCreationProcessor letterApprovalProcessor processor.LetterApprovalProcessor letterAttachmentProcessor processor.LetterAttachmentProcessor letterOutgoingRecipientProcessor processor.LetterOutgoingRecipientProcessor letterActivityProcessor processor.LetterActivityProcessor txManager *repository.TxManager } func (a *App) initProcessors(cfg *config.Config, repos *repositories) *processors { txMgr := repository.NewTxManager(a.db) activity := processor.NewActivityLogProcessor(repos.activityLogRepo) // Create the letter number generator letterNumberGen := processor.NewLetterNumberGenerator(repos.settingRepo) // Create letter processors with the number generator letterProc := processor.NewLetterProcessor( repos.letterRepo, repos.letterAttachRepo, txMgr, activity, repos.letterDispositionRepo, repos.letterDispositionDeptRepo, repos.letterDispActionSelRepo, repos.dispositionNoteRepo, repos.letterDiscussionRepo, repos.settingRepo, repos.recipientRepo, repos.letterOutgoingRecipientRepo, repos.departmentRepo, repos.userDeptRepo, repos.priorityRepo, repos.institutionRepo, repos.dispRepo, letterNumberGen, repos.dispositionRouteRepo, ) // Create modular processors for letter outgoing letterValidationProc := processor.NewLetterValidationProcessor() letterCreationProc := processor.NewLetterCreationProcessor( repos.letterOutgoingRepo, repos.approvalFlowRepo, letterNumberGen, ) letterApprovalProc := processor.NewLetterApprovalProcessor( repos.letterOutgoingApprovalRepo, repos.approvalFlowRepo, repos.letterOutgoingRepo, ) letterAttachmentProc := processor.NewLetterAttachmentProcessor( repos.letterOutgoingAttachmentRepo, ) letterOutgoingRecipientProc := processor.NewLetterOutgoingRecipientProcessor( repos.letterOutgoingRecipientRepo, repos.approvalFlowRepo, repos.userDeptRepo, ) letterActivityProc := processor.NewLetterActivityProcessor( repos.letterOutgoingActivityLogRepo, ) // Create the main letter outgoing processor for backward compatibility letterOutgoingProc := processor.NewLetterOutgoingProcessor( a.db, repos.letterOutgoingRepo, repos.letterOutgoingAttachmentRepo, repos.letterOutgoingRecipientRepo, repos.letterOutgoingDiscussionRepo, repos.letterOutgoingDiscussionAttachRepo, repos.letterOutgoingActivityLogRepo, repos.approvalFlowRepo, repos.letterOutgoingApprovalRepo, letterNumberGen, txMgr, repos.priorityRepo, repos.institutionRepo, ) // Create document repositories docSessionRepo := repository.NewDocumentSessionRepository(a.db) docVersionRepo := repository.NewDocumentVersionRepository(a.db) docMetadataRepo := repository.NewDocumentMetadataRepository(a.db) docErrorRepo := repository.NewDocumentErrorRepository(a.db) // Create OnlyOffice processor onlyOfficeProc := processor.NewOnlyOfficeProcessor( a.db, docSessionRepo, docVersionRepo, docMetadataRepo, docErrorRepo, txMgr, ) // Create Novu processor for backward compatibility novuConfig := internalConfig.LoadNovuConfig(cfg) novuProc := processor.NewNovuProcessor(novuConfig) // Create notification processor with Novu provider novuProvider := processor.NewNovuProvider(novuConfig) notificationProc := processor.NewNotificationProcessor(novuProvider, novuConfig.IncomingLetterWorkflowID) // Create user role processor userRoleProc := processor.NewUserRoleProcessor(a.db) // Create user processor with Novu integration userProc := processor.NewUserProcessor(repos.userRepo, repos.userProfileRepo, userRoleProc) userProc.SetNovuProcessor(novuProc) // Create cached user processor for auth middleware cachedUserProc := processor.NewCachedUserProcessor(repos.userRepo, repos.userProfileRepo, userRoleProc) // Create recipient processor recipientProc := processor.NewRecipientProcessor( repos.recipientRepo, repos.settingRepo, repos.departmentRepo, repos.userDeptRepo, ) // Create letter disposition processor letterDispositionProc := processor.NewLetterDispositionProcessor( repos.letterDispositionRepo, repos.letterDispositionDeptRepo, repos.letterDispActionSelRepo, repos.dispositionNoteRepo, repos.letterDiscussionRepo, repos.dispRepo, activity, ) // Create letter disposition department processor letterDispositionDeptProc := processor.NewLetterDispositionDepartmentProcessor( repos.letterDispositionDeptRepo, repos.dispositionNoteRepo, repos.letterRepo, ) return &processors{ userProcessor: userProc, cachedUserProcessor: cachedUserProc, letterProcessor: letterProc, letterOutgoingProcessor: letterOutgoingProc, activityLogger: activity, letterNumberGenerator: letterNumberGen, onlyOfficeProcessor: onlyOfficeProc, novuProcessor: novuProc, notificationProcessor: notificationProc, recipientProcessor: recipientProc, letterDispositionProcessor: letterDispositionProc, letterDispositionDeptProcessor: letterDispositionDeptProc, // Modular processors letterValidationProcessor: letterValidationProc, letterCreationProcessor: letterCreationProc, letterApprovalProcessor: letterApprovalProc, letterAttachmentProcessor: letterAttachmentProc, letterOutgoingRecipientProcessor: letterOutgoingRecipientProc, letterActivityProcessor: letterActivityProc, txManager: txMgr, } } type services struct { userService *service.UserServiceImpl authService *service.AuthServiceImpl fileService *service.FileServiceImpl rbacService *service.RBACServiceImpl masterService *service.MasterServiceImpl letterService *service.LetterServiceImpl letterOutgoingService *service.LetterOutgoingServiceImpl approvalFlowService *service.ApprovalFlowServiceImpl dispositionRouteService *service.DispositionRouteServiceImpl onlyOfficeService *service.OnlyOfficeServiceImpl analyticsService *service.AnalyticsServiceImpl notificationService *service.NotificationServiceImpl } func (a *App) initServices(processors *processors, repos *repositories, cfg *config.Config) *services { authConfig := cfg.Auth() jwtSecret := authConfig.AccessTokenSecret() authService := service.NewAuthService(processors.userProcessor, jwtSecret) userSvc := service.NewUserService(processors.userProcessor, repos.titleRepo) fileCfg := cfg.S3Config s3Client := client.NewFileClient(fileCfg) fileSvc := service.NewFileService(s3Client, processors.userProcessor, "profile", "documents") rbacSvc := service.NewRBACService(repos.rbacRepo) masterSvc := service.NewMasterService(repos.labelRepo, repos.priorityRepo, repos.institutionRepo, repos.dispRepo, repos.departmentRepo, cfg) txManager := repository.NewTxManager(a.db) letterSvc := service.NewLetterService( processors.letterProcessor, txManager, processors.letterNumberGenerator, processors.recipientProcessor, processors.activityLogger, processors.letterDispositionProcessor, processors.notificationProcessor, processors.activityLogger, ) dispRouteSvc := service.NewDispositionRouteService(repos.dispositionRouteRepo) letterOutgoingSvc := service.NewLetterOutgoingService( processors.letterOutgoingProcessor, processors.txManager, processors.letterValidationProcessor, processors.letterCreationProcessor, processors.letterApprovalProcessor, processors.letterAttachmentProcessor, processors.letterOutgoingRecipientProcessor, processors.letterActivityProcessor, ) approvalFlowStepRepo := repository.NewApprovalFlowStepRepository(a.db) approvalFlowSvc := service.NewApprovalFlowService( a.db, repos.approvalFlowRepo, approvalFlowStepRepo, txManager, ) // Create OnlyOffice service with file storage onlyOfficeSvc := service.NewOnlyOfficeService(processors.onlyOfficeProcessor, &cfg.OnlyOffice, a.db, s3Client) // Create Analytics service analyticsSvc := service.NewAnalyticsService(repos.analyticsRepo) // Create Notification service novuConfig := internalConfig.LoadNovuConfig(cfg) notificationSvc := service.NewNotificationService(novuConfig, processors.userProcessor) return &services{ userService: userSvc, authService: authService, fileService: fileSvc, rbacService: rbacSvc, masterService: masterSvc, letterService: letterSvc, letterOutgoingService: letterOutgoingSvc, approvalFlowService: approvalFlowSvc, dispositionRouteService: dispRouteSvc, onlyOfficeService: onlyOfficeSvc, analyticsService: analyticsSvc, notificationService: notificationSvc, } } type middlewares struct { authMiddleware *middleware.AuthMiddleware } func (a *App) initMiddleware(services *services) *middlewares { return &middlewares{ authMiddleware: middleware.NewAuthMiddleware(services.authService), } } type validators struct { userValidator *validator.UserValidatorImpl } func (a *App) initValidators() *validators { return &validators{ userValidator: validator.NewUserValidator(), } }