2025-10-15 21:58:44 +07:00

474 lines
18 KiB
Go

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)
repositoryAttachmentHandler := handler.NewRepositoryAttachmentHandler(services.repositoryAttachmentService)
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,
repositoryAttachmentHandler,
)
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
repositoryAttachmentRepo *repository.RepositoryAttachmentRepositoryImpl
}
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),
repositoryAttachmentRepo: repository.NewRepositoryAttachmentRepositoryImpl(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
repositoryAttachmentProcessor *processor.RepositoryAttachmentProcessorImpl
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,
)
repositoryAttachmentProc := processor.NewRepositoryAttachmentProcessor(
repos.repositoryAttachmentRepo)
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,
repositoryAttachmentProcessor: repositoryAttachmentProc,
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
repositoryAttachmentService *service.RepositoryAttachmentServiceImpl
}
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.notificationProcessor,
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)
repositoryAttachmentSvc := service.NewRepositoryAttachmentService(processors.repositoryAttachmentProcessor)
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,
repositoryAttachmentService: repositoryAttachmentSvc,
}
}
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(),
}
}