first commit

This commit is contained in:
meninjar
2026-04-14 01:21:54 +00:00
commit 35c101725f
443 changed files with 1245931 additions and 0 deletions
+391
View File
@@ -0,0 +1,391 @@
// File: /home/meninjar/goprint/service-general/main.go
package main
import (
"context"
"fmt"
"net/http"
"os"
"os/signal"
"syscall"
"service/internal/auth"
antrol "service/internal/bpjs/antrol/reference"
aplicareBed "service/internal/bpjs/aplicare/bed"
apotekDpho "service/internal/bpjs/apotek/reference/dpho"
apotekPoli "service/internal/bpjs/apotek/reference/poli"
"service/internal/bpjs/vclaim/peserta"
"service/internal/bpjs/vclaim/sep"
"service/internal/infrastructure/cache"
"service/internal/infrastructure/config"
"service/internal/infrastructure/database"
grpcHandlers "service/internal/infrastructure/transport/grpc/handlers"
httpServer "service/internal/infrastructure/transport/http/servers"
bpjsFactory "service/internal/interfaces/bpjs"
"service/internal/interfaces/minio"
satuSehatFactory "service/internal/interfaces/satusehat"
satuSehatAuth "service/internal/satusehat/reference/auth"
satuSehatKfa "service/internal/satusehat/reference/kfa"
satuSehatLocation "service/internal/satusehat/reference/location"
satuSehatOrganization "service/internal/satusehat/reference/organization"
satuSehatPatient "service/internal/satusehat/reference/patient"
satuSehatPractitioner "service/internal/satusehat/reference/practitioner"
satuSehatCondition "service/internal/satusehat/usecase/condition"
satuSehatEncounter "service/internal/satusehat/usecase/encounter"
satuSehatProcedure "service/internal/satusehat/usecase/procedure"
"service/pkg/logger"
imagingStudyWorker "service/internal/worker/satusehat/imagingstudy"
// roleComponent "service/internal/master/role/component"
roleMaster "service/internal/master/role/master"
rolePages "service/internal/master/role/pages"
rolePermission "service/internal/master/role/permission"
_ "service/docs/swagger" // Wajib: import swagger docs yang di-generate oleh swag CLI
grpcServers "service/internal/infrastructure/transport/grpc/servers"
"golang.org/x/sync/errgroup"
)
// @title GoPrint Service General API
// @version 1.0.0
// @description REST API for Service General including Multi-DB, BPJS, and SatuSehat integrations.
// @termsOfService http://swagger.io/terms/
// @contact.name API Support
// @contact.email [email protected]
// @license.name Apache 2.0
// @license.url http://www.apache.org/licenses/LICENSE-2.0.html
// @BasePath /api/v1
// @securityDefinitions.apikey BearerAuth
// @in header
// @name Authorization
// @description Type "Bearer" followed by a space and JWT token.
func main() {
// 1. Load Config
cfg := config.LoadConfig()
// 2. Init Logger dengan konfigurasi dari config
loggerConfig := logger.Config{
Level: cfg.Logger.Level,
Format: cfg.Logger.Format,
Output: "both", // Output ke console dan ke dalam folder daily logs
ServiceName: "service-general",
EnableCaller: cfg.Server.Mode != "production", // Aktifkan caller info di non-production
Environment: cfg.Server.Mode, // Gunakan mode server untuk environment
}
logger.Init(loggerConfig)
// Sekarang bisa menggunakan logger custom
logger.Default().Info("Application starting",
logger.String("service", "service-general"),
logger.String("environment", cfg.Server.Mode),
logger.String("log_level", cfg.Logger.Level),
)
// Di dalam main.go
// Asumsikan di config.yaml Anda ada database bernama "postgres" dan "mysql_legacy" contoh pemanggilan dan prosesing 2 tabel berbeda dalam satu repository
// reportRepo := report.NewRepository(dbService, "postgres", "mysql_legacy")
// reportService := report.NewService(reportRepo)
// reportService ini tinggal di-pass ke dalam Handler untuk dijadikan endpoint REST API.
// Add debugging to see what config was loaded
logger.Default().Debug("Config loaded",
logger.Any("database_count", len(cfg.Databases)),
logger.Any("cache_enabled", cfg.Cache.Enabled),
logger.Any("server_mode", cfg.Server.Mode),
)
if err := cfg.Validate(); err != nil {
logger.Default().Fatal("Config validation failed", logger.ErrorField(err))
}
// --- Inisialisasi Minio Object Storage ---
minio.Connect()
logger.Default().Info("Minio Object Storage client initialized")
// 3. Init Database Manager (Mendukung CQRS & Multi-DB)
dbService := database.New(cfg)
defer dbService.Close()
// Menjalankan Automigrasi
// if err := dbService.Migrate(); err != nil {
// logger.Default().Warn("Database migration completed with issues", logger.ErrorField(err))
// }
// Get Master/Primary GORM DB Connection untuk modul lama
// Gunakan nama koneksi "postgres" atau "default" sesuai config.yaml Anda
gormDB, err := dbService.GetGormDB("default")
if err != nil {
logger.Default().Fatal("Failed to get primary gorm database", logger.ErrorField(err))
}
// --- [NEW] Inisialisasi Message Broker (Kafka / RabbitMQ) untuk Event-Driven Microservice ---
// Jika bertindak sebagai gateway atau microservice independen, inisialisasi Kafka di sini
// kafkaProducer := broker.NewKafkaProducer(cfg.Kafka.Brokers)
// defer kafkaProducer.Close()
// logger.Default().Info("✅ Kafka Producer initialized")
// 4. Init Cache using factory pattern
cacheFactory := cache.NewFactory(cfg.Cache)
cacheManager, err := cacheFactory.CreateManager()
if err != nil {
logger.Default().Fatal("Failed to initialize cache manager", logger.ErrorField(err))
}
defer cacheManager.Close()
if cfg.Cache.Enabled {
logger.Default().Info("Cache initialized", logger.String("host", cfg.Cache.Redis.Host), logger.Int("port", cfg.Cache.Redis.Port))
} else {
logger.Default().Warn("Cache disabled, using NoOp cache")
}
// 5. Init Layers (CQRS Repositories & Services)
// Modul Auth
authCmdRepo := auth.NewCommandRepository(dbService, "default")
authQueryRepo := auth.NewQueryRepository(dbService, "default")
authSvc := auth.NewService(authCmdRepo, authQueryRepo, cacheManager, cfg)
// Inisialisasi Role Pages, Permission, dan Component (Menggunakan pola CQRS baru)
rolePagesCmdRepo := rolePages.NewCommandRepository(dbService, "default")
rolePagesQueryRepo := rolePages.NewQueryRepository(dbService, "default")
rolePagesService := rolePages.NewService(rolePagesCmdRepo, rolePagesQueryRepo, cacheManager)
rolePermissionCmdRepo := rolePermission.NewCommandRepository(dbService, "default")
rolePermissionQueryRepo := rolePermission.NewQueryRepository(dbService, "default")
rolePermissionService := rolePermission.NewService(rolePermissionCmdRepo, rolePermissionQueryRepo, rolePagesQueryRepo, cacheManager)
// Modul Role Master (Hasil Generator)
roleMasterCmdRepo := roleMaster.NewCommandRepository(dbService, "default")
roleMasterQueryRepo := roleMaster.NewQueryRepository(dbService, "default")
roleMasterService := roleMaster.NewService(roleMasterCmdRepo, roleMasterQueryRepo, cacheManager)
// roleComponentCmdRepo := roleComponent.NewCommandRepository(dbService, "postgres")
// roleComponentQueryRepo := roleComponent.NewQueryRepository(dbService, "postgres")
// roleComponentService := roleComponent.NewService(roleComponentCmdRepo, roleComponentQueryRepo)
// --- [NEW] Inisialisasi Modul CQRS Baru (Memanfaatkan DB Service) ---
// Contoh ketika mengimplementasikan entity "Province" atau "Person":
// provinceCmdRepo := province.NewCommandRepository(dbService, "default")
// provinceQueryRepo := province.NewQueryRepository(dbService, "default")
// provinceSvc := province.NewService(provinceCmdRepo, provinceQueryRepo)
// --- [NEW] Inisialisasi Factory BPJS ---
bpjsServices := bpjsFactory.NewBPJSFactory(cfg.Bpjs)
// 1. Modul VClaim (Lama)
vclaimServiceEngine := bpjsServices.VClaim()
sepRepo := sep.NewRepository(vclaimServiceEngine)
sepService := sep.NewService(sepRepo)
pesertaRepo := peserta.NewRepository(vclaimServiceEngine)
pesertaService := peserta.NewService(pesertaRepo)
// 2. Modul Antrean RS (Baru)
antreanClient := bpjsServices.AntreanRS()
antrolRepo := antrol.NewRepository(antreanClient)
antrolService := antrol.NewService(antrolRepo)
// 3. Modul Aplicares (Bed Ketersediaan Tempat Tidur)
aplicareClient := bpjsServices.Aplicare()
bedRepo := aplicareBed.NewRepository(aplicareClient)
bedService := aplicareBed.NewService(bedRepo)
// 4. Modul Apotek (Referensi DPHO dan Poli)
apotekClient := bpjsServices.Apotek()
dphoRepo := apotekDpho.NewRepository(apotekClient)
dphoService := apotekDpho.NewService(dphoRepo)
poliApotekRepo := apotekPoli.NewRepository(apotekClient)
poliApotekService := apotekPoli.NewService(poliApotekRepo)
// --- [NEW] Inisialisasi Modul Satu Sehat ---
satusehatFact := satuSehatFactory.NewSatuSehatFactory(cfg.SatuSehat)
satusehatClient := satusehatFact.Client()
satuSehatAuthSvc := satuSehatAuth.NewService(satusehatClient, cacheManager)
satuSehatPatientRepo := satuSehatPatient.NewRepository(satusehatClient)
satuSehatPatientSvc := satuSehatPatient.NewService(satuSehatPatientRepo)
satuSehatPractitionerRepo := satuSehatPractitioner.NewRepository(satusehatClient)
satuSehatPractitionerSvc := satuSehatPractitioner.NewService(satuSehatPractitionerRepo)
satuSehatOrganizationRepo := satuSehatOrganization.NewRepository(satusehatClient)
satuSehatOrganizationSvc := satuSehatOrganization.NewService(satuSehatOrganizationRepo)
satuSehatLocationRepo := satuSehatLocation.NewRepository(satusehatClient)
satuSehatLocationSvc := satuSehatLocation.NewService(satuSehatLocationRepo)
satuSehatKfaRepo := satuSehatKfa.NewRepository(satusehatClient)
satuSehatKfaSvc := satuSehatKfa.NewService(satuSehatKfaRepo)
satuSehatEncounterRepo := satuSehatEncounter.NewRepository(satusehatClient, dbService)
satuSehatEncounterSvc := satuSehatEncounter.NewService(satuSehatEncounterRepo)
satuSehatProcedureRepo := satuSehatProcedure.NewRepository(satusehatClient)
satuSehatProcedureSvc := satuSehatProcedure.NewService(satuSehatProcedureRepo)
satuSehatConditionRepo := satuSehatCondition.NewRepository(satusehatClient)
satuSehatConditionSvc := satuSehatCondition.NewService(satuSehatConditionRepo)
// 6. Server Orchestration (Dual Protocol & Background Workers)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
// Gunakan errgroup untuk mengelola lifecycle server
g, ctx := errgroup.WithContext(ctx)
// --- [NEW] Start Background Workers ---
if cfg.SatuSehat.Enabled {
imagingStudyWorkerCfg := imagingStudyWorker.Config{
DBManager: dbService,
InternalBaseURL: fmt.Sprintf("http://localhost:%d%s", cfg.Server.REST.Port, cfg.Swagger.BasePath), // otomatis: http://localhost:8080/api/v1
InternalToken: "", // Isi token di sini jika endpoint internal Usecase Anda membutuhkan Bearer Token
OrganizationID: cfg.SatuSehat.OrgID,
}
isWorker := imagingStudyWorker.NewWorker(imagingStudyWorkerCfg)
g.Go(func() error {
isWorker.Run(ctx)
return nil
})
}
serverCount := 0
// --- [A] Start REST Server ---
if cfg.Server.REST.Enabled {
serverCount++
// Menggunakan ServiceRegistry agar HTTP Server lebih rapi dan scalable
registry := &httpServer.ServiceRegistry{
Config: cfg,
DBManager: dbService,
PrimaryDB: gormDB,
CacheManager: cacheManager,
AuthService: authSvc,
Master: &httpServer.MasterServices{
RolePages: rolePagesService,
RolePermission: rolePermissionService,
RoleMaster: roleMasterService,
},
VClaim: &httpServer.VClaimServices{
Sep: sepService,
Peserta: pesertaService,
},
Antrol: &httpServer.AntrolServices{
Reference: antrolService,
},
Aplicare: &httpServer.AplicaresServices{
Bed: bedService,
},
Apotek: &httpServer.ApotekServices{
DPHO: dphoService,
Poli: poliApotekService,
},
SatuSehat: &httpServer.SatuSehatServices{
Auth: satuSehatAuthSvc,
Patient: satuSehatPatientSvc,
Practitioner: satuSehatPractitionerSvc,
Organization: satuSehatOrganizationSvc,
Location: satuSehatLocationSvc,
KFA: satuSehatKfaSvc,
Encounter: satuSehatEncounterSvc,
Procedure: satuSehatProcedureSvc,
Condition: satuSehatConditionSvc,
// TODO: Inisialisasi service sisanya di sini saat repo & service usecase lainnya sudah Anda buat, contoh:
// AllergyIntolerance: allergySvc,
// CarePlan: careplanSvc,
// ClinicalImpression: clinicalImpressionSvc,
// Composition: compositionSvc,
// DiagnosticReport: diagnosticReportSvc,
// EpisodeOfCare: episodeOfCareSvc,
// ImagingStudy: imagingStudySvc,
// Immunization: immunizationSvc,
// Medication: medicationSvc,
// MedicationDispense: medicationDispenseSvc,
// MedicationRequest: medicationRequestSvc,
// MedicationStatement: medicationStatementSvc,
// Observation: observationSvc,
// QuestionnaireResponse: questionnaireResponseSvc,
// ServiceRequest: serviceRequestSvc,
// Specimen: specimenSvc,
},
}
restSrv := httpServer.NewHTTPServer(&cfg.Server.REST, registry)
g.Go(func() error {
logger.Default().Info("REST API running", logger.Int("port", cfg.Server.REST.Port))
if err := restSrv.Start(&cfg.Server); err != nil && err != http.ErrServerClosed {
logger.Default().Error("REST Server error", logger.ErrorField(err))
return err
}
return nil
})
}
// --- [B] Start gRPC Server ---
if cfg.Server.GRPC.Enabled {
serverCount++
// 1. Buat gRPC handlers
permissionHandler := grpcHandlers.NewPermissionHandler(rolePermissionService)
// 2. Buat dan isi gRPC service registry
grpcRegistry := &grpcServers.ServiceRegistry{
PermissionHandler: permissionHandler,
}
// 3. Buat gRPC server dengan registry yang sudah diisi
grpcSrv := grpcServers.NewGRPCServer(
&cfg.Server.GRPC,
grpcRegistry,
)
g.Go(func() error {
logger.Default().Info("gRPC Server running", logger.Int("port", cfg.Server.GRPC.Port))
if err := grpcSrv.Start(); err != nil {
logger.Default().Error("gRPC Server error", logger.ErrorField(err))
return err
}
return nil
})
g.Go(func() error {
<-ctx.Done()
logger.Default().Info("Gracefully stopping gRPC Server...")
grpcSrv.Stop()
return nil
})
}
if serverCount == 0 {
logger.Default().Fatal("No server enabled (REST and gRPC are both disabled in config)")
}
// --- [C] Graceful Shutdown Listener ---
g.Go(func() error {
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
select {
case <-quit:
logger.Default().Warn("Signal received, shutting down servers...")
cancel()
case <-ctx.Done():
}
return nil
})
logger.Default().Info("Application started successfully")
if err := g.Wait(); err != nil {
logger.Default().Error("Server shutdown with error", logger.ErrorField(err))
} else {
logger.Default().Info("Server shutdown successful")
}
}