835 lines
41 KiB
Go
835 lines
41 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"log/slog"
|
|
"os"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/gofiber/fiber/v2"
|
|
"gorm.io/gorm"
|
|
|
|
"wucher/internal/app/api"
|
|
queueapp "wucher/internal/app/queueing"
|
|
samlauth "wucher/internal/auth"
|
|
"wucher/internal/config"
|
|
filemanager "wucher/internal/domain/file_manager"
|
|
filemanagerrealtime "wucher/internal/realtime/filemanager"
|
|
helicopterfilerealtime "wucher/internal/realtime/helicopterfile"
|
|
"wucher/internal/repository/mysql"
|
|
s3repo "wucher/internal/repository/s3"
|
|
"wucher/internal/resilience"
|
|
"wucher/internal/service"
|
|
pkglogger "wucher/internal/shared/pkg/logger"
|
|
tzpkg "wucher/internal/shared/pkg/timezone"
|
|
userctx "wucher/internal/shared/pkg/userctx"
|
|
"wucher/internal/transport/http/handlers"
|
|
)
|
|
|
|
type Repositories struct {
|
|
User *mysql.UserRepository
|
|
Contact *mysql.ContactRepository
|
|
Role *mysql.RoleRepository
|
|
Helicopter *mysql.HelicopterRepository
|
|
HelicopterUsage *mysql.HelicopterUsageRepository
|
|
FlightInspection *mysql.FlightInspectionRepository
|
|
Flight *mysql.FlightRepository
|
|
FlightData *mysql.FlightDataRepository
|
|
Mission *mysql.MissionRepository
|
|
ReserveAc *mysql.ReserveAcRepository
|
|
HelicopterFile *mysql.HelicopterFileRepository
|
|
FlightInspectionFileChecklist *mysql.FlightInspectionFileChecklistRepository
|
|
BeforeFlightInspection *mysql.BeforeFlightInspectionRepository
|
|
AfterFlightInspection *mysql.AfterFlightInspectionRepository
|
|
FMReport *mysql.FMReportRepository
|
|
FlightPrepCheck *mysql.FlightPrepCheckRepository
|
|
Facility *mysql.FacilityRepository
|
|
Base *mysql.BaseRepository
|
|
Medicine *mysql.MedicineRepository
|
|
Vocation *mysql.VocationRepository
|
|
Opc *mysql.OpcRepository
|
|
ForcesPresent *mysql.ForcesPresentRepository
|
|
Hospital *mysql.HospitalRepository
|
|
HealthInsuranceCompanies *mysql.HealthInsuranceCompaniesRepository
|
|
FederalState *mysql.FederalStateRepository
|
|
ICAO *mysql.ICAORepository
|
|
Land *mysql.LandRepository
|
|
Complaint *mysql.ComplaintRepository
|
|
EASARelease *mysql.EASAReleaseRepository
|
|
ActionSignoff *mysql.ActionSignoffRepository
|
|
OtherPerson *mysql.OtherPersonRepository
|
|
MCF *mysql.MCFRepository
|
|
FleetHistory *mysql.FleetHistoryRepository
|
|
HEMSOperationalData *mysql.HEMSOperationalDataRepository
|
|
HEMSOperationCategory *mysql.HEMSOperationCategoryRepository
|
|
HEMSOperation *mysql.HEMSOperationRepository
|
|
MasterSettings *mysql.MasterSettingsRepository
|
|
FileManagerFolder *mysql.FileManagerFolderRepository
|
|
FileManagerFile *mysql.FileManagerFileRepository
|
|
FileManagerAttachment *mysql.FileManagerAttachmentRepository
|
|
DutyRoster *mysql.DutyRosterRepository
|
|
AirRescuerChecklist *mysql.AirRescuerChecklistRepository
|
|
InsurancePatientData *mysql.InsurancePatientDataRepository
|
|
PatientData *mysql.PatientDataRepository
|
|
DUL *mysql.DULRepository
|
|
FleetStatus *mysql.FleetStatusRepository
|
|
TokenStore *mysql.TokenStore
|
|
Auth *mysql.AuthRepository
|
|
Audit *mysql.AuditRepository
|
|
}
|
|
|
|
type Infrastructure struct {
|
|
DB *gorm.DB
|
|
Logger *slog.Logger
|
|
SQSBreaker *resilience.Executor
|
|
FileStorage filemanager.ObjectStorage
|
|
EmailQueue service.EmailQueue
|
|
FileQueue filemanager.FileProcessingQueue
|
|
FileStatusHub *filemanagerrealtime.Hub
|
|
HelicopterFileHub *helicopterfilerealtime.Hub
|
|
FileTrashPolicy config.FileManagerTrashPolicyConfig
|
|
FileLifecycle config.FileManagerLifecycleConfig
|
|
}
|
|
|
|
type Services struct {
|
|
User *service.UserService
|
|
Contact *service.ContactService
|
|
Role *service.RoleService
|
|
Helicopter *service.HelicopterService
|
|
HelicopterUsage *service.HelicopterUsageService
|
|
FlightInspection *service.FlightInspectionService
|
|
Flight *service.FlightService
|
|
FlightData *service.FlightDataService
|
|
Mission *service.MissionService
|
|
ReserveAc *service.ReserveAcService
|
|
Takeover *service.TakeoverService
|
|
OtherPerson *service.OtherPersonService
|
|
HelicopterFile *service.HelicopterFileService
|
|
FlightInspectionChecklist *service.FlightInspectionFileChecklistService
|
|
BeforeFlightInspection *service.BeforeFlightInspectionService
|
|
AfterFlightInspection *service.AfterFlightInspectionService
|
|
FMReport *service.FMReportService
|
|
FlightPrepCheck *service.FlightPrepCheckService
|
|
Facility *service.FacilityService
|
|
Base *service.BaseService
|
|
Medicine *service.MedicineService
|
|
Vocation *service.VocationService
|
|
Opc *service.OpcService
|
|
ForcesPresent *service.ForcesPresentService
|
|
Hospital *service.HospitalService
|
|
HealthInsuranceCompanies *service.HealthInsuranceCompaniesService
|
|
FederalState *service.FederalStateService
|
|
ICAO *service.ICAOService
|
|
Land *service.LandService
|
|
Complaint *service.ComplaintService
|
|
EASARelease *service.EASAReleaseService
|
|
ActionSignoff *service.ActionSignoffService
|
|
MCF *service.MCFService
|
|
FleetHistory *service.FleetHistoryService
|
|
HEMSOperationalData *service.HEMSOperationalDataService
|
|
HEMSOperationCategory *service.HEMSOperationCategoryService
|
|
HEMSOperation *service.HEMSOperationService
|
|
MasterSettings *service.MasterSettingsService
|
|
FileManager *service.FileManagerService
|
|
DutyRoster *service.DutyRosterService
|
|
AirRescuerChecklist *service.AirRescuerChecklistService
|
|
InsurancePatientData *service.InsurancePatientDataService
|
|
PatientData *service.PatientDataService
|
|
DUL *service.DULService
|
|
FleetStatus *service.FleetStatusService
|
|
WOPI *service.WOPIService
|
|
Auth *service.AuthService
|
|
Audit *service.AuditLogService
|
|
}
|
|
|
|
type Handlers struct {
|
|
User *handlers.UserHandler
|
|
Contact *handlers.ContactHandler
|
|
Health *handlers.HealthHandler
|
|
BuildInfo *handlers.BuildInfoHandler
|
|
Auth *handlers.AuthHandler
|
|
Role *handlers.RoleHandler
|
|
Helicopter *handlers.HelicopterHandler
|
|
HelicopterUsage *handlers.HelicopterUsageHandler
|
|
HelicopterFile *handlers.HelicopterFileHandler
|
|
Flight *handlers.FlightHandler
|
|
FlightData *handlers.FlightDataHandler
|
|
Mission *handlers.MissionHandler
|
|
FMReport *handlers.FMReportHandler
|
|
ReserveAc *handlers.ReserveAcHandler
|
|
Takeover *handlers.TakeoverHandler
|
|
OtherPerson *handlers.OtherPersonHandler
|
|
Facility *handlers.FacilityHandler
|
|
Base *handlers.BaseHandler
|
|
Medicine *handlers.MedicineHandler
|
|
Vocation *handlers.VocationHandler
|
|
Opc *handlers.OpcHandler
|
|
ForcesPresent *handlers.ForcesPresentHandler
|
|
Hospital *handlers.HospitalHandler
|
|
HealthInsuranceCompanies *handlers.HealthInsuranceCompaniesHandler
|
|
FederalState *handlers.FederalStateHandler
|
|
ICAO *handlers.ICAOHandler
|
|
Land *handlers.LandHandler
|
|
MasterSettings *handlers.MasterSettingsHandler
|
|
Branding *handlers.BrandingHandler
|
|
FileManagerFolder *handlers.FileManagerFolderHandler
|
|
FileManagerFile *handlers.FileManagerFileHandler
|
|
FileManagerAttachment *handlers.FileManagerAttachmentHandler
|
|
FileManagerEvents *handlers.FileManagerEventsHandler
|
|
HelicopterFileEvents *handlers.HelicopterFileEventsHandler
|
|
DutyRoster *handlers.DutyRosterHandler
|
|
AirRescuerChecklist *handlers.AirRescuerChecklistHandler
|
|
InsurancePatientData *handlers.InsurancePatientDataHandler
|
|
PatientData *handlers.PatientDataHandler
|
|
DUL *handlers.DULHandler
|
|
FleetStatus *handlers.FleetStatusHandler
|
|
HEMSOperationalData *handlers.HEMSOperationalDataHandler
|
|
HEMSOperationCategory *handlers.HEMSOperationCategoryHandler
|
|
HEMSOperation *handlers.HEMSOperationHandler
|
|
Complaint *handlers.ComplaintHandler
|
|
EASARelease *handlers.EASAReleaseHandler
|
|
ActionSignoff *handlers.ActionSignoffHandler
|
|
MCF *handlers.MCFHandler
|
|
}
|
|
|
|
func initializeDatabase(mysqlCfg config.MySQLConfig) (*gorm.DB, error) {
|
|
return mysql.Connect(mysqlCfg)
|
|
}
|
|
|
|
func bootstrapData(db *gorm.DB) error {
|
|
if err := mysql.AutoMigrate(db); err != nil {
|
|
return fmt.Errorf("auto migrate: %w", err)
|
|
}
|
|
if err := db.Exec("UPDATE users SET timezone = ? WHERE timezone IS NULL OR timezone = ''", tzpkg.Default).Error; err != nil {
|
|
return fmt.Errorf("seed users timezone default: %w", err)
|
|
}
|
|
if err := api.SeedRoles(db); err != nil {
|
|
return fmt.Errorf("seed roles: %w", err)
|
|
}
|
|
if err := api.SeedPermissions(db); err != nil {
|
|
return fmt.Errorf("seed permissions: %w", err)
|
|
}
|
|
if err := api.SeedRolePermissions(db); err != nil {
|
|
return fmt.Errorf("seed role permissions: %w", err)
|
|
}
|
|
if err := api.SeedBaseCategories(db); err != nil {
|
|
return fmt.Errorf("seed base categories: %w", err)
|
|
}
|
|
if strings.EqualFold(strings.TrimSpace(os.Getenv("SEED_ROSTER_USERS")), "true") {
|
|
if err := api.SeedRosterUsers(db); err != nil {
|
|
return fmt.Errorf("seed roster users: %w", err)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func initializeRepositories(db *gorm.DB) (*Repositories, error) {
|
|
if db == nil {
|
|
return nil, errors.New("database is required")
|
|
}
|
|
repos := &Repositories{
|
|
User: mysql.NewUserRepository(db),
|
|
Contact: mysql.NewContactRepository(db),
|
|
Role: mysql.NewRoleRepository(db),
|
|
Helicopter: mysql.NewHelicopterRepository(db),
|
|
HelicopterUsage: mysql.NewHelicopterUsageRepository(db),
|
|
FlightInspection: mysql.NewFlightInspectionRepository(db),
|
|
Flight: mysql.NewFlightRepository(db),
|
|
FlightData: mysql.NewFlightDataRepository(db),
|
|
Mission: mysql.NewMissionRepository(db),
|
|
ReserveAc: mysql.NewReserveAcRepository(db),
|
|
HelicopterFile: mysql.NewHelicopterFileRepository(db),
|
|
FlightInspectionFileChecklist: mysql.NewFlightInspectionFileChecklistRepository(db),
|
|
BeforeFlightInspection: mysql.NewBeforeFlightInspectionRepository(db),
|
|
AfterFlightInspection: mysql.NewAfterFlightInspectionRepository(db),
|
|
FMReport: mysql.NewFMReportRepository(db),
|
|
FlightPrepCheck: mysql.NewFlightPrepCheckRepository(db),
|
|
Facility: mysql.NewFacilityRepository(db),
|
|
Base: mysql.NewBaseRepository(db),
|
|
Medicine: mysql.NewMedicineRepository(db),
|
|
Vocation: mysql.NewVocationRepository(db),
|
|
Opc: mysql.NewOpcRepository(db),
|
|
ForcesPresent: mysql.NewForcesPresentRepository(db),
|
|
Hospital: mysql.NewHospitalRepository(db),
|
|
HealthInsuranceCompanies: mysql.NewHealthInsuranceCompaniesRepository(db),
|
|
FederalState: mysql.NewFederalStateRepository(db),
|
|
ICAO: mysql.NewICAORepository(db),
|
|
Land: mysql.NewLandRepository(db),
|
|
Complaint: mysql.NewComplaintRepository(db),
|
|
EASARelease: mysql.NewEASAReleaseRepository(db),
|
|
ActionSignoff: mysql.NewActionSignoffRepository(db),
|
|
OtherPerson: mysql.NewOtherPersonRepository(db),
|
|
MCF: mysql.NewMCFRepository(db),
|
|
FleetHistory: mysql.NewFleetHistoryRepository(db),
|
|
HEMSOperationalData: mysql.NewHEMSOperationalDataRepository(db),
|
|
HEMSOperationCategory: mysql.NewHEMSOperationCategoryRepository(db),
|
|
HEMSOperation: mysql.NewHEMSOperationRepository(db),
|
|
MasterSettings: mysql.NewMasterSettingsRepository(db),
|
|
FileManagerFolder: mysql.NewFileManagerFolderRepository(db),
|
|
FileManagerFile: mysql.NewFileManagerFileRepository(db),
|
|
FileManagerAttachment: mysql.NewFileManagerAttachmentRepository(db),
|
|
DutyRoster: mysql.NewDutyRosterRepository(db),
|
|
AirRescuerChecklist: mysql.NewAirRescuerChecklistRepository(db),
|
|
InsurancePatientData: mysql.NewInsurancePatientDataRepository(db),
|
|
PatientData: mysql.NewPatientDataRepository(db),
|
|
DUL: mysql.NewDULRepository(db),
|
|
FleetStatus: mysql.NewFleetStatusRepository(db),
|
|
TokenStore: mysql.NewTokenStore(db),
|
|
Auth: mysql.NewAuthRepository(db),
|
|
Audit: mysql.NewAuditRepository(db),
|
|
}
|
|
return repos, nil
|
|
}
|
|
|
|
func initializeInfrastructure(ctx context.Context, appCfg config.AppConfig, db *gorm.DB) (*Infrastructure, error) {
|
|
appLogger := pkglogger.New(appCfg.Logging).With("component", "api")
|
|
slog.SetDefault(appLogger)
|
|
|
|
if err := config.ValidateDistinctQueueURLs(appCfg.SQS, appCfg.FileSQS); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
resCfg := appCfg.Resilience
|
|
sqsBreaker := resilience.NewExecutor("sqs", resCfg.SQS, appLogger, resilience.ClassifySQSError)
|
|
|
|
var emailQueue service.EmailQueue
|
|
if !appCfg.Queue.OutboxEnabled {
|
|
emailProducer, err := queueapp.NewEmailProducer(ctx, appCfg.Queue, appCfg.SQS, sqsBreaker, appLogger)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
emailQueue = service.AdaptEmailQueue(emailProducer)
|
|
}
|
|
|
|
var fileStorage filemanager.ObjectStorage
|
|
if appCfg.FileStorage.Bucket != "" && appCfg.FileStorage.Region != "" {
|
|
storage, err := s3repo.NewFileObjectStorage(ctx, appCfg.FileStorage)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
fileStorage = storage
|
|
}
|
|
|
|
var fileQueue filemanager.FileProcessingQueue
|
|
if !appCfg.FileQueue.OutboxEnabled && strings.TrimSpace(appCfg.FileSQS.QueueURL) != "" {
|
|
fileProducer, err := queueapp.NewFileProcessingProducer(ctx, appCfg.FileQueue, appCfg.FileSQS, sqsBreaker, appLogger)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
fileQueue = service.AdaptFileProcessingQueue(fileProducer)
|
|
}
|
|
|
|
return &Infrastructure{
|
|
DB: db,
|
|
Logger: appLogger,
|
|
SQSBreaker: sqsBreaker,
|
|
FileStorage: fileStorage,
|
|
EmailQueue: emailQueue,
|
|
FileQueue: fileQueue,
|
|
FileStatusHub: filemanagerrealtime.NewHub(64, appLogger),
|
|
HelicopterFileHub: helicopterfilerealtime.NewHub(64, appLogger),
|
|
FileTrashPolicy: config.LoadFileManagerTrashPolicyConfigFromEnv(),
|
|
FileLifecycle: config.LoadFileManagerLifecycleConfigFromEnv(),
|
|
}, nil
|
|
}
|
|
|
|
func initializeServices(appCfg config.AppConfig, repos *Repositories, infra *Infrastructure) (*Services, error) {
|
|
if repos == nil {
|
|
return nil, errors.New("repositories are required")
|
|
}
|
|
if infra == nil {
|
|
return nil, errors.New("infrastructure is required")
|
|
}
|
|
|
|
var protector service.SecretProtector
|
|
if p, err := service.NewAESGCMProtectorFromBase64(appCfg.Auth.TOTPSecretKeyB64); err == nil {
|
|
protector = p
|
|
}
|
|
|
|
fileManagerDeps := service.FileManagerServiceDependencies{
|
|
Storage: infra.FileStorage,
|
|
FileQueue: infra.FileQueue,
|
|
UploadPolicy: filemanager.FileUploadPolicy{},
|
|
TrashRetention: infra.FileTrashPolicy.Retention,
|
|
FileStatusEvents: repos.FileManagerFile,
|
|
Logger: infra.Logger,
|
|
EnableS3Tagging: infra.FileLifecycle.EnableS3Tagging,
|
|
}
|
|
if appCfg.FileQueue.OutboxEnabled {
|
|
fileManagerDeps.FileOutbox = repos.FileManagerFile
|
|
fileManagerDeps.FileOutboxSerializer = queueapp.NewFileProcessingSerializer(appCfg.FileQueue, appCfg.FileSQS)
|
|
}
|
|
fileManagerDeps.UploadPolicy = filemanager.FileUploadPolicy{
|
|
MaxFileSizeBytes: appCfg.FileUpload.MaxFileSizeBytes,
|
|
AllowedMimeTypes: appCfg.FileUpload.AllowedMimeTypes,
|
|
AllowedExtensions: appCfg.FileUpload.AllowedExtensions,
|
|
AllowEmptyFile: appCfg.FileUpload.AllowEmptyFile,
|
|
MaxFilenameLength: appCfg.FileUpload.MaxFilenameLength,
|
|
}
|
|
|
|
flightDataSvc := service.NewFlightDataService(repos.FlightData)
|
|
flightSvc := service.NewFlightService(repos.Flight)
|
|
fileManagerSvc := service.NewFileManagerService(repos.FileManagerFolder, repos.FileManagerFile, repos.FileManagerAttachment, fileManagerDeps)
|
|
helicopterSvc := service.NewHelicopterService(repos.Helicopter)
|
|
helicopterUsageSvc := service.NewHelicopterUsageService(repos.HelicopterUsage)
|
|
afterFlightInspectionSvc := service.NewAfterFlightInspectionService(repos.AfterFlightInspection)
|
|
|
|
services := &Services{
|
|
User: service.NewUserService(repos.User),
|
|
Role: service.NewRoleService(repos.Role),
|
|
Helicopter: helicopterSvc,
|
|
HelicopterUsage: helicopterUsageSvc,
|
|
FlightInspection: service.NewFlightInspectionService(repos.FlightInspection),
|
|
Flight: flightSvc,
|
|
FlightData: flightDataSvc,
|
|
Mission: nil,
|
|
ReserveAc: service.NewReserveAcService(repos.ReserveAc),
|
|
Takeover: service.NewTakeoverService(infra.DB),
|
|
HelicopterFile: service.NewHelicopterFileService(repos.HelicopterFile),
|
|
FlightInspectionChecklist: service.NewFlightInspectionFileChecklistService(repos.FlightInspectionFileChecklist),
|
|
BeforeFlightInspection: service.NewBeforeFlightInspectionService(repos.BeforeFlightInspection),
|
|
AfterFlightInspection: afterFlightInspectionSvc,
|
|
FMReport: service.NewFMReportService(repos.FMReport),
|
|
FlightPrepCheck: service.NewFlightPrepCheckService(repos.FlightPrepCheck, repos.FlightInspection),
|
|
Facility: service.NewFacilityService(repos.Facility),
|
|
Base: service.NewBaseService(repos.Base),
|
|
Medicine: service.NewMedicineService(repos.Medicine),
|
|
Vocation: service.NewVocationService(repos.Vocation),
|
|
Opc: service.NewOpcService(repos.Opc),
|
|
ForcesPresent: service.NewForcesPresentService(repos.ForcesPresent),
|
|
Hospital: service.NewHospitalService(repos.Hospital),
|
|
HealthInsuranceCompanies: service.NewHealthInsuranceCompaniesService(repos.HealthInsuranceCompanies),
|
|
FederalState: service.NewFederalStateService(repos.FederalState),
|
|
ICAO: service.NewICAOService(repos.ICAO),
|
|
Land: service.NewLandService(repos.Land),
|
|
Complaint: service.NewComplaintService(repos.Complaint),
|
|
EASARelease: service.NewEASAReleaseService(repos.EASARelease),
|
|
ActionSignoff: service.NewActionSignoffService(repos.ActionSignoff),
|
|
OtherPerson: service.NewOtherPersonService(repos.OtherPerson),
|
|
MCF: service.NewMCFService(repos.MCF),
|
|
FleetHistory: service.NewFleetHistoryService(repos.FleetHistory),
|
|
HEMSOperationalData: service.NewHEMSOperationalDataService(repos.HEMSOperationalData),
|
|
HEMSOperationCategory: service.NewHEMSOperationCategoryService(repos.HEMSOperationCategory),
|
|
HEMSOperation: service.NewHEMSOperationService(repos.HEMSOperation),
|
|
MasterSettings: service.NewMasterSettingsService(repos.MasterSettings, service.MasterSettingsServiceDependencies{Protector: protector}),
|
|
FileManager: fileManagerSvc,
|
|
DutyRoster: service.NewDutyRosterService(repos.DutyRoster),
|
|
AirRescuerChecklist: service.NewAirRescuerChecklistService(repos.AirRescuerChecklist),
|
|
InsurancePatientData: service.NewInsurancePatientDataService(repos.InsurancePatientData),
|
|
PatientData: service.NewPatientDataService(repos.PatientData),
|
|
DUL: service.NewDULService(repos.DUL),
|
|
Audit: service.NewAuditLogService(repos.Audit, service.AuditLogServiceDependencies{QueueSize: appCfg.Audit.QueueSize, Workers: appCfg.Audit.Workers, IPEncryptionKey: appCfg.Auth.IPEncryptionKey}),
|
|
WOPI: service.NewWOPIService(service.WOPIConfig{
|
|
TokenSecret: appCfg.WOPI.TokenSecret,
|
|
ProofSecret: appCfg.WOPI.ProofSecret,
|
|
DiscoveryURL: appCfg.WOPI.DiscoveryURL,
|
|
PublicBaseURL: appCfg.WOPI.PublicBaseURL,
|
|
EditActionURL: appCfg.WOPI.EditActionURL,
|
|
TokenTTL: appCfg.WOPI.TokenTTL,
|
|
MaxBodyBytes: appCfg.WOPI.MaxBodyBytes,
|
|
RequireProof: appCfg.WOPI.RequireProof,
|
|
}),
|
|
}
|
|
services.Mission = service.NewMissionService(repos.Mission, flightSvc, flightDataSvc, afterFlightInspectionSvc)
|
|
services.FleetStatus = service.NewFleetStatusService(repos.FleetStatus, fileManagerSvc, helicopterSvc, services.Complaint).WithHistory(services.FleetHistory).WithUsage(services.HelicopterUsage)
|
|
flightDataSvc.WithAOGRecalculator(services.FleetStatus)
|
|
flightDataSvc.WithMissionResolver(services.Mission)
|
|
flightDataSvc.WithFlightLookup(services.Flight)
|
|
flightDataSvc.WithFMReportLookup(services.FMReport)
|
|
flightDataSvc.WithAfterFlightInspectionLookup(services.AfterFlightInspection)
|
|
services.FMReport.WithUsageRefresher(services.HelicopterUsage)
|
|
|
|
msSSO := service.NewMicrosoftSSO(appCfg.MicrosoftSSO).
|
|
WithExecutor(resilience.NewExecutor("microsoft_sso", appCfg.Resilience.MicrosoftSSO, infra.Logger, resilience.ClassifyMicrosoftSSOError)).
|
|
WithConfigResolver(func(ctx context.Context) (config.MicrosoftSSOConfig, error) {
|
|
cfg, err := services.MasterSettings.LoadMicrosoftEntraRuntimeConfig(ctx)
|
|
if err == nil {
|
|
return cfg, nil
|
|
}
|
|
// Fallback for local/dev when master settings are missing or stale.
|
|
// This prevents SSO flows from failing hard when DB-stored settings are invalid.
|
|
if service.IsMicrosoftSSOConfigComplete(appCfg.MicrosoftSSO) {
|
|
if infra.Logger != nil {
|
|
infra.Logger.Warn("microsoft sso runtime config fallback to env", "reason", err.Error())
|
|
}
|
|
return appCfg.MicrosoftSSO, nil
|
|
}
|
|
return config.MicrosoftSSOConfig{}, err
|
|
})
|
|
|
|
authDeps := service.AuthServiceDependencies{
|
|
SSO: msSSO,
|
|
Protector: protector,
|
|
Config: appCfg.Auth,
|
|
Audit: services.Audit,
|
|
EmailBranding: service.NewAuthEmailBrandingResolver(services.MasterSettings, services.FileManager, infra.FileStorage),
|
|
EmailOTPEnabled: services.MasterSettings.IsLoginEmailOTPEnabled,
|
|
DisableLegacyNoopEmailQueue: true,
|
|
}
|
|
if appCfg.Queue.OutboxEnabled {
|
|
authDeps.EmailOutbox = repos.Auth
|
|
authDeps.EmailSerializer = queueapp.NewOutboxSerializer(appCfg.Queue, appCfg.SQS)
|
|
}
|
|
services.Auth = service.NewAuthService(
|
|
repos.Auth,
|
|
repos.Role,
|
|
repos.TokenStore,
|
|
infra.EmailQueue,
|
|
authDeps,
|
|
)
|
|
|
|
services.Contact = service.NewContactService(repos.Contact, services.Auth)
|
|
services.HelicopterFile.
|
|
WithFileManagerService(services.FileManager).
|
|
WithHelicopterFileEvents(infra.HelicopterFileHub)
|
|
if infra.FileStorage != nil && !appCfg.FileQueue.OutboxEnabled && strings.TrimSpace(appCfg.FileSQS.QueueURL) == "" {
|
|
return nil, errors.New("FILE_SQS_QUEUE_URL is required when FILE_QUEUE_OUTBOX_ENABLED=false and file manager upload is enabled")
|
|
}
|
|
|
|
return services, nil
|
|
}
|
|
|
|
func initializeHandlers(services *Services, infra *Infrastructure) (*Handlers, error) {
|
|
if services == nil {
|
|
return nil, errors.New("services are required")
|
|
}
|
|
if infra == nil {
|
|
return nil, errors.New("infrastructure is required")
|
|
}
|
|
|
|
fleetStatusHandler := handlers.NewFleetStatusHandler(services.FleetStatus, handlers.FleetStatusDeps{
|
|
Helicopter: services.Helicopter,
|
|
Complaint: services.Complaint,
|
|
EASA: services.EASARelease,
|
|
History: services.FleetHistory,
|
|
MCF: services.MCF,
|
|
Signoff: services.ActionSignoff,
|
|
Storage: infra.FileStorage,
|
|
})
|
|
|
|
fmReportFlightHandler := handlers.NewFlightHandler(services.Flight).
|
|
WithAuthService(services.Auth).
|
|
WithAfterFlightInspectionService(services.AfterFlightInspection).
|
|
WithFMReportService(services.FMReport).
|
|
WithFlightDataService(services.FlightData).
|
|
WithTakeoverService(service.NewTakeoverModuleService(mysql.NewTakeoverRepository(infra.DB))).
|
|
WithHelicopterService(services.Helicopter).
|
|
WithReserveAcService(services.ReserveAc).
|
|
WithMissionService(services.Mission)
|
|
|
|
services.FMReport.WithFleetHistoryBuilder(handlers.NewFMReportFleetHistoryBuilder(fleetStatusHandler, fmReportFlightHandler))
|
|
services.FMReport.WithReportCodeResolver(fmReportFlightHandler)
|
|
|
|
return &Handlers{
|
|
User: handlers.NewUserHandler(services.User).
|
|
WithFileManagerService(services.FileManager).
|
|
WithFileStorage(infra.FileStorage),
|
|
Contact: handlers.NewContactHandler(services.Contact, services.Auth).
|
|
WithFileManagerService(services.FileManager).
|
|
WithFileStorage(infra.FileStorage),
|
|
Health: handlers.NewHealthHandler(),
|
|
BuildInfo: handlers.NewBuildInfoHandler(),
|
|
Auth: handlers.NewAuthHandler(services.Auth).WithFileStorage(infra.FileStorage).WithContactService(services.Contact),
|
|
Role: handlers.NewRoleHandler(services.Role),
|
|
Helicopter: handlers.NewHelicopterHandler(services.Helicopter).
|
|
WithFileManagerService(services.FileManager).
|
|
WithFileStorage(infra.FileStorage).
|
|
WithReserveAcService(services.ReserveAc).
|
|
WithFleetStatusService(services.FleetStatus).
|
|
WithPINVerifier(services.Auth).
|
|
WithComplaintService(services.Complaint).
|
|
WithMCFService(services.MCF).
|
|
WithHistoryService(services.FleetHistory),
|
|
HelicopterUsage: handlers.NewHelicopterUsageHandler(services.HelicopterUsage),
|
|
HelicopterFile: handlers.NewHelicopterFileHandler(services.HelicopterFile).
|
|
WithFileManagerService(services.FileManager).
|
|
WithHelicopterService(services.Helicopter).
|
|
WithTakeoverService(services.Takeover).
|
|
WithHelicopterFileEvents(infra.HelicopterFileHub).
|
|
WithFileStorage(infra.FileStorage),
|
|
Flight: handlers.NewFlightHandler(services.Flight).
|
|
WithAuthService(services.Auth).
|
|
WithAfterFlightInspectionService(services.AfterFlightInspection).
|
|
WithFMReportService(services.FMReport).
|
|
WithFlightDataService(services.FlightData).
|
|
WithTakeoverService(service.NewTakeoverModuleService(mysql.NewTakeoverRepository(infra.DB))).
|
|
WithHelicopterService(services.Helicopter).
|
|
WithReserveAcService(services.ReserveAc).
|
|
WithMissionService(services.Mission),
|
|
FlightData: handlers.NewFlightDataHandler(services.FlightData).
|
|
WithMissionService(services.Mission).
|
|
WithUserService(services.User).
|
|
WithHospitalService(services.Hospital).
|
|
WithICAOService(services.ICAO).
|
|
WithFacilityService(services.Facility),
|
|
Mission: handlers.NewMissionHandler(services.Mission).WithFlightDataService(services.FlightData).WithFileStorage(infra.FileStorage).WithFileManagerService(services.FileManager),
|
|
FMReport: handlers.NewFMReportHandler(services.FMReport).
|
|
WithFlightHandler(fmReportFlightHandler).
|
|
WithMissionHandler(handlers.NewMissionHandler(services.Mission).WithFlightDataService(services.FlightData).WithFileStorage(infra.FileStorage).WithFileManagerService(services.FileManager)).
|
|
WithFlightInspectionService(services.FlightInspection).
|
|
WithAfterFlightInspectionService(services.AfterFlightInspection).
|
|
WithHelicopterUsageService(services.HelicopterUsage).
|
|
WithFleetStatusHandler(fleetStatusHandler).
|
|
WithTakeoverService(services.Takeover).
|
|
WithHelicopterService(services.Helicopter).
|
|
WithFileStorage(infra.FileStorage).
|
|
WithFileManagerService(services.FileManager),
|
|
ReserveAc: handlers.NewReserveAcHandler(services.ReserveAc).
|
|
WithFlightService(services.Flight).
|
|
WithFlightDataService(services.FlightData).
|
|
WithMissionService(services.Mission).
|
|
WithDutyRosterService(services.DutyRoster).
|
|
WithHelicopterService(services.Helicopter).
|
|
WithFlightInspectionService(services.FlightInspection).
|
|
WithBeforeFlightInspectionService(services.BeforeFlightInspection).
|
|
WithAfterFlightInspectionService(services.AfterFlightInspection).
|
|
WithFMReportService(services.FMReport).
|
|
WithFlightPrepCheckService(services.FlightPrepCheck).
|
|
WithHelicopterFileService(services.HelicopterFile).
|
|
WithFlightInspectionFileChecklistService(services.FlightInspectionChecklist).
|
|
WithFileStorage(infra.FileStorage),
|
|
Takeover: handlers.NewTakeoverHandler(infra.DB).
|
|
WithAuthService(services.Auth).
|
|
WithBaseService(services.Base).
|
|
WithFlightService(services.Flight).
|
|
WithFileManagerService(services.FileManager).
|
|
WithTakeoverAcService(service.NewTakeoverModuleService(mysql.NewTakeoverRepository(infra.DB))).
|
|
WithReserveAcService(services.ReserveAc).
|
|
WithDutyRosterService(services.DutyRoster).
|
|
WithHelicopterService(services.Helicopter).
|
|
WithComplaintService(services.Complaint).
|
|
WithMCFService(services.MCF).
|
|
WithFleetStatusService(services.FleetStatus).
|
|
WithFlightInspectionService(services.FlightInspection).
|
|
WithBeforeFlightInspectionService(services.BeforeFlightInspection).
|
|
WithFlightPrepCheckService(services.FlightPrepCheck).
|
|
WithHelicopterFileService(services.HelicopterFile).
|
|
WithFlightInspectionFileChecklistService(services.FlightInspectionChecklist).
|
|
WithFileStorage(infra.FileStorage),
|
|
OtherPerson: handlers.NewOtherPersonHandler(services.OtherPerson),
|
|
Facility: handlers.NewFacilityHandler(services.Facility),
|
|
Base: handlers.NewBaseHandler(services.Base).WithFileManagerService(services.FileManager).WithFileStorage(infra.FileStorage),
|
|
Medicine: handlers.NewMedicineHandler(services.Medicine),
|
|
Vocation: handlers.NewVocationHandler(services.Vocation),
|
|
Opc: handlers.NewOpcHandler(services.Opc),
|
|
ForcesPresent: handlers.NewForcesPresentHandler(services.ForcesPresent),
|
|
Hospital: handlers.NewHospitalHandler(services.Hospital),
|
|
HealthInsuranceCompanies: handlers.NewHealthInsuranceCompaniesHandler(services.HealthInsuranceCompanies),
|
|
FederalState: handlers.NewFederalStateHandler(services.FederalState),
|
|
ICAO: handlers.NewICAOHandler(services.ICAO),
|
|
Land: handlers.NewLandHandler(services.Land),
|
|
MasterSettings: handlers.NewMasterSettingsHandler(services.MasterSettings, services.FileManager, infra.FileStorage),
|
|
Branding: handlers.NewBrandingHandler(services.MasterSettings, services.FileManager, infra.FileStorage),
|
|
FileManagerFolder: handlers.NewFileManagerFolderHandler(services.FileManager, infra.FileStorage),
|
|
FileManagerFile: handlers.NewFileManagerFileHandler(services.FileManager, infra.FileStorage).
|
|
WithHelicopterFileService(services.HelicopterFile).
|
|
WithAircraftService(services.Helicopter).
|
|
WithBaseService(services.Base).
|
|
WithDULService(services.DUL).
|
|
WithAuthService(services.Auth).
|
|
WithTakeoverService(services.Takeover).
|
|
WithMissionService(services.Mission).
|
|
WithUserService(services.User).
|
|
WithFleetStatusFileService(service.NewFleetStatusFileService(services.FileManager)).
|
|
WithHelicopterFileEvents(infra.HelicopterFileHub).
|
|
WithWOPIService(services.WOPI),
|
|
FileManagerAttachment: handlers.NewFileManagerAttachmentHandler(services.FileManager),
|
|
FileManagerEvents: handlers.NewFileManagerEventsHandler(infra.FileStatusHub),
|
|
HelicopterFileEvents: handlers.NewHelicopterFileEventsHandler(infra.HelicopterFileHub),
|
|
DutyRoster: handlers.NewDutyRosterHandler(services.DutyRoster).WithAudit(services.Audit).WithFlightService(services.Flight),
|
|
AirRescuerChecklist: handlers.NewAirRescuerChecklistHandler(services.AirRescuerChecklist),
|
|
InsurancePatientData: handlers.NewInsurancePatientDataHandler(services.InsurancePatientData),
|
|
PatientData: handlers.NewPatientDataHandler(services.PatientData),
|
|
DUL: handlers.NewDULHandler(services.DUL).WithBaseService(services.Base).WithContactService(services.Contact).WithFileManagerService(services.FileManager).WithFileStorage(infra.FileStorage),
|
|
FleetStatus: fleetStatusHandler,
|
|
HEMSOperationalData: handlers.NewHEMSOperationalDataHandler(services.HEMSOperationalData).WithFileManagerService(services.FileManager),
|
|
HEMSOperationCategory: handlers.NewHEMSOperationCategoryHandler(services.HEMSOperationCategory),
|
|
HEMSOperation: handlers.NewHEMSOperationHandler(services.HEMSOperation),
|
|
Complaint: handlers.NewComplaintHandler(services.Complaint).WithHelicopterService(services.Helicopter).WithMCFService(services.MCF).WithHistoryService(services.FleetHistory).WithAOGRecalculator(services.FleetStatus).WithActionSignoffService(services.ActionSignoff),
|
|
EASARelease: handlers.NewEASAReleaseHandler(services.EASARelease).WithComplaintActionChecker(services.Complaint).WithActionSignoffChecker(services.ActionSignoff).WithHistoryService(services.FleetHistory).WithContactResolver(services.Contact).WithAOGRecalculator(services.FleetStatus),
|
|
ActionSignoff: handlers.NewActionSignoffHandler(services.ActionSignoff).WithComplaintService(services.Complaint),
|
|
MCF: handlers.NewMCFHandler(services.MCF).WithHistoryService(services.FleetHistory),
|
|
}, nil
|
|
}
|
|
|
|
func initializeRealtimeComponents(
|
|
ctx context.Context,
|
|
repos *Repositories,
|
|
services *Services,
|
|
infra *Infrastructure,
|
|
) error {
|
|
if repos == nil || services == nil || infra == nil {
|
|
return errors.New("repositories, services, and infrastructure are required")
|
|
}
|
|
userNameCache := userctx.NewUserNameCache(repos.User, 5*time.Minute)
|
|
handlers.WireUserNameGetter(userNameCache)
|
|
|
|
startFileManagerAutoPurgeLoop(ctx, services.FileManager, infra.FileTrashPolicy, infra.Logger)
|
|
startFileManagerLifecycleCleanupLoop(ctx, services.FileManager, infra.FileLifecycle, infra.Logger)
|
|
startHelicopterFileAttachmentReconcileLoop(ctx, services.HelicopterFile, infra.Logger)
|
|
startFleetStatusAOGRecomputeLoop(ctx, services.FleetStatus, infra.Logger)
|
|
|
|
fileStatusPoller := filemanagerrealtime.NewStatusPoller(infra.DB, infra.FileStatusHub, infra.Logger, filemanagerrealtime.StatusPollerConfig{
|
|
// Keep the SSE ready event responsive; this poller is the bridge between
|
|
// the async file processor and the live file.updated stream.
|
|
PollInterval: 250 * time.Millisecond,
|
|
BatchSize: 100,
|
|
OnPublished: func(ctx context.Context, event filemanagerrealtime.FileStatusEvent) error {
|
|
if filemanager.NormalizeFileStatus(event.Status) != filemanager.FileStatusReady {
|
|
return nil
|
|
}
|
|
if services == nil || services.HelicopterFile == nil {
|
|
return nil
|
|
}
|
|
finalized, err := services.HelicopterFile.FinalizePendingAttachmentsBySourceFileUUID(ctx, event.FileUUID, 50)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if finalized > 0 && infra.Logger != nil {
|
|
infra.Logger.Info("helicopter file attachments finalized from file ready event", "file_uuid", event.FileUUID, "finalized", finalized)
|
|
}
|
|
return nil
|
|
},
|
|
})
|
|
go func() {
|
|
if err := fileStatusPoller.Run(ctx); err != nil && ctx.Err() == nil {
|
|
infra.Logger.Error("file status poller stopped unexpectedly", "error", err)
|
|
}
|
|
}()
|
|
|
|
return nil
|
|
}
|
|
|
|
func buildHTTPServer(appCfg config.AppConfig, services *Services, handlersSet *Handlers, infra *Infrastructure) (*fiber.App, string, error) {
|
|
if services == nil || handlersSet == nil || infra == nil {
|
|
return nil, "", errors.New("services, handlers, and infra are required")
|
|
}
|
|
httpCfg := appCfg.HTTP
|
|
app := fiber.New(fiber.Config{
|
|
BodyLimit: httpCfg.BodyLimit,
|
|
ReadTimeout: httpCfg.ReadTimeout,
|
|
WriteTimeout: httpCfg.WriteTimeout,
|
|
IdleTimeout: httpCfg.IdleTimeout,
|
|
})
|
|
api.UseDefaultMiddlewares(app, httpCfg)
|
|
|
|
envCfg := config.LoadEnvConfig()
|
|
hasJSONSAML := appCfg.SAML.CertFile != "" ||
|
|
appCfg.SAML.KeyFile != "" ||
|
|
appCfg.SAML.MetadataFile != "" ||
|
|
appCfg.SAML.RootURL != "" ||
|
|
appCfg.SAML.FrontendURL != "" ||
|
|
appCfg.SAML.CookieDomain != "" ||
|
|
appCfg.SAML.SessionTTLHr > 0
|
|
if appCfg.SAML.CertFile != "" {
|
|
envCfg.SAMLCertFile = appCfg.SAML.CertFile
|
|
}
|
|
if appCfg.SAML.KeyFile != "" {
|
|
envCfg.SAMLKeyFile = appCfg.SAML.KeyFile
|
|
}
|
|
if appCfg.SAML.MetadataFile != "" {
|
|
envCfg.SAMLMetadataFile = appCfg.SAML.MetadataFile
|
|
}
|
|
if appCfg.SAML.RootURL != "" {
|
|
envCfg.SAMLRootURL = appCfg.SAML.RootURL
|
|
}
|
|
if appCfg.SAML.FrontendURL != "" {
|
|
envCfg.FrontendURL = appCfg.SAML.FrontendURL
|
|
}
|
|
if appCfg.SAML.CookieDomain != "" {
|
|
envCfg.CookieDomain = appCfg.SAML.CookieDomain
|
|
}
|
|
if hasJSONSAML {
|
|
envCfg.CookieSecure = appCfg.SAML.CookieSecure
|
|
}
|
|
if appCfg.SAML.SessionTTLHr > 0 {
|
|
envCfg.SessionTTLHour = appCfg.SAML.SessionTTLHr
|
|
}
|
|
|
|
// Microsoft auth mode selector. Default "oauth" uses the OIDC/OAuth2 authorization
|
|
// code flow (Microsoft refresh tokens are stored in DB and validated on
|
|
// /api/v1/auth/refresh). Set MICROSOFT_AUTH_MODE=saml to fall back to the legacy
|
|
// SAML flow (kept intact for rollback).
|
|
microsoftAuthMode := strings.ToLower(strings.TrimSpace(os.Getenv("MICROSOFT_AUTH_MODE")))
|
|
if microsoftAuthMode == "" {
|
|
microsoftAuthMode = "oauth"
|
|
}
|
|
|
|
var samlHandler *samlauth.Handler
|
|
if microsoftAuthMode == "saml" {
|
|
if err := samlauth.InitSessionStoreDB(infra.DB); err == nil {
|
|
if err := samlauth.InitSAML(envCfg); err == nil {
|
|
samlHandler = samlauth.NewHandler(envCfg, services.Auth)
|
|
}
|
|
}
|
|
}
|
|
slog.Info("microsoft auth mode configured", slog.String("mode", microsoftAuthMode), slog.Bool("saml_active", samlHandler != nil))
|
|
|
|
api.RegisterRoutes(app, api.RouteDependencies{
|
|
Handlers: api.RouteHandlers{
|
|
User: handlersSet.User,
|
|
Contact: handlersSet.Contact,
|
|
Health: handlersSet.Health,
|
|
BuildInfo: handlersSet.BuildInfo,
|
|
Auth: handlersSet.Auth,
|
|
Role: handlersSet.Role,
|
|
Helicopter: handlersSet.Helicopter,
|
|
HelicopterUsage: handlersSet.HelicopterUsage,
|
|
HelicopterFile: handlersSet.HelicopterFile,
|
|
Flight: handlersSet.Flight,
|
|
FlightData: handlersSet.FlightData,
|
|
Mission: handlersSet.Mission,
|
|
FMReport: handlersSet.FMReport,
|
|
ReserveAc: handlersSet.ReserveAc,
|
|
Takeover: handlersSet.Takeover,
|
|
OtherPerson: handlersSet.OtherPerson,
|
|
Facility: handlersSet.Facility,
|
|
Base: handlersSet.Base,
|
|
Medicine: handlersSet.Medicine,
|
|
Vocation: handlersSet.Vocation,
|
|
Opc: handlersSet.Opc,
|
|
ForcesPresent: handlersSet.ForcesPresent,
|
|
Hospital: handlersSet.Hospital,
|
|
HealthInsuranceCompanies: handlersSet.HealthInsuranceCompanies,
|
|
FederalState: handlersSet.FederalState,
|
|
ICAO: handlersSet.ICAO,
|
|
Land: handlersSet.Land,
|
|
MasterSettings: handlersSet.MasterSettings,
|
|
Branding: handlersSet.Branding,
|
|
FileManagerFolder: handlersSet.FileManagerFolder,
|
|
FileManagerFile: handlersSet.FileManagerFile,
|
|
FileManagerAttachment: handlersSet.FileManagerAttachment,
|
|
FileManagerEvents: handlersSet.FileManagerEvents,
|
|
HelicopterFileEvents: handlersSet.HelicopterFileEvents,
|
|
DutyRoster: handlersSet.DutyRoster,
|
|
AirRescuerChecklist: handlersSet.AirRescuerChecklist,
|
|
InsurancePatientData: handlersSet.InsurancePatientData,
|
|
PatientData: handlersSet.PatientData,
|
|
DUL: handlersSet.DUL,
|
|
FleetStatus: handlersSet.FleetStatus,
|
|
HEMSOperationalData: handlersSet.HEMSOperationalData,
|
|
HEMSOperationCategory: handlersSet.HEMSOperationCategory,
|
|
HEMSOperation: handlersSet.HEMSOperation,
|
|
Complaint: handlersSet.Complaint,
|
|
EASARelease: handlersSet.EASARelease,
|
|
ActionSignoff: handlersSet.ActionSignoff,
|
|
MCF: handlersSet.MCF,
|
|
SAMLAuth: samlHandler,
|
|
},
|
|
Services: api.RouteServices{
|
|
Auth: services.Auth,
|
|
Audit: services.Audit,
|
|
WOPI: services.WOPI,
|
|
},
|
|
})
|
|
|
|
addr := ":8080"
|
|
if appCfg.HTTPPort > 0 {
|
|
addr = fmt.Sprintf(":%d", appCfg.HTTPPort)
|
|
}
|
|
return app, addr, nil
|
|
}
|