Refactor scheduler and settings UI
This commit is contained in:
154
cmd/pfs/main.go
154
cmd/pfs/main.go
@@ -26,6 +26,7 @@ import (
|
||||
"git.mchus.pro/mchus/priceforge/internal/middleware"
|
||||
"git.mchus.pro/mchus/priceforge/internal/models"
|
||||
"git.mchus.pro/mchus/priceforge/internal/repository"
|
||||
"git.mchus.pro/mchus/priceforge/internal/scheduler"
|
||||
"git.mchus.pro/mchus/priceforge/internal/services"
|
||||
"git.mchus.pro/mchus/priceforge/internal/services/alerts"
|
||||
"git.mchus.pro/mchus/priceforge/internal/services/pricelist"
|
||||
@@ -193,6 +194,10 @@ func main() {
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
appCtx, appCancel := context.WithCancel(context.Background())
|
||||
defer appCancel()
|
||||
startEmbeddedScheduler(appCtx, mariaDB, cfg)
|
||||
|
||||
srv := &http.Server{
|
||||
Addr: cfg.Address(),
|
||||
Handler: router,
|
||||
@@ -225,6 +230,7 @@ func main() {
|
||||
|
||||
<-quit
|
||||
slog.Info("shutting down server...")
|
||||
appCancel()
|
||||
|
||||
// Shutdown HTTP server
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
@@ -238,6 +244,21 @@ func main() {
|
||||
|
||||
}
|
||||
|
||||
func startEmbeddedScheduler(ctx context.Context, mariaDB *gorm.DB, cfg *config.Config) {
|
||||
if mariaDB == nil || cfg == nil || !cfg.Scheduler.Enabled {
|
||||
return
|
||||
}
|
||||
statsRepo := repository.NewStatsRepository(mariaDB)
|
||||
alertRepo := repository.NewAlertRepository(mariaDB)
|
||||
componentRepo := repository.NewComponentRepository(mariaDB)
|
||||
priceRepo := repository.NewPriceRepository(mariaDB)
|
||||
|
||||
alertService := alerts.NewService(alertRepo, componentRepo, priceRepo, statsRepo, cfg.Alerts, cfg.Pricing)
|
||||
pricingService := pricing.NewService(componentRepo, priceRepo, cfg.Pricing)
|
||||
embeddedScheduler := scheduler.New(mariaDB, alertService, pricingService, statsRepo, cfg.Scheduler)
|
||||
go embeddedScheduler.Start(ctx)
|
||||
}
|
||||
|
||||
func setConfigDefaults(cfg *config.Config) {
|
||||
if cfg.Server.Host == "" {
|
||||
cfg.Server.Host = "127.0.0.1"
|
||||
@@ -272,6 +293,27 @@ func setConfigDefaults(cfg *config.Config) {
|
||||
if cfg.Pricing.MinQuotesForMedian == 0 {
|
||||
cfg.Pricing.MinQuotesForMedian = 3
|
||||
}
|
||||
if cfg.Scheduler.PollInterval == 0 {
|
||||
cfg.Scheduler.PollInterval = time.Minute
|
||||
}
|
||||
if cfg.Alerts.CheckInterval == 0 {
|
||||
cfg.Alerts.CheckInterval = time.Hour
|
||||
}
|
||||
if cfg.Scheduler.AlertsInterval == 0 {
|
||||
cfg.Scheduler.AlertsInterval = cfg.Alerts.CheckInterval
|
||||
}
|
||||
if cfg.Scheduler.UpdatePricesInterval == 0 {
|
||||
cfg.Scheduler.UpdatePricesInterval = 24 * time.Hour
|
||||
}
|
||||
if cfg.Scheduler.UpdatePopularityInterval == 0 {
|
||||
cfg.Scheduler.UpdatePopularityInterval = 24 * time.Hour
|
||||
}
|
||||
if cfg.Scheduler.ResetWeeklyCountersInterval == 0 {
|
||||
cfg.Scheduler.ResetWeeklyCountersInterval = 7 * 24 * time.Hour
|
||||
}
|
||||
if cfg.Scheduler.ResetMonthlyCountersInterval == 0 {
|
||||
cfg.Scheduler.ResetMonthlyCountersInterval = 30 * 24 * time.Hour
|
||||
}
|
||||
}
|
||||
|
||||
func isLoopbackHost(host string) bool {
|
||||
@@ -385,11 +427,18 @@ func setupRouter(cfg *config.Config, configPath string, connMgr *db.ConnectionMa
|
||||
stockImportService,
|
||||
vendorMappingService,
|
||||
partnumberBookService,
|
||||
cfg.Scheduler,
|
||||
dbUser,
|
||||
taskManager,
|
||||
)
|
||||
pricelistHandler := handlers.NewPricelistHandler(pricelistService, dbUser, taskManager)
|
||||
taskHandler := tasks.NewHandler(taskManager)
|
||||
setupHandler, err := handlers.NewSetupHandler(connMgr, templatesPath, cfg, func(nextCfg *config.Config) error {
|
||||
return saveConfig(configPath, nextCfg)
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
webHandler, err := handlers.NewWebHandler(templatesPath, componentService)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -465,107 +514,6 @@ func setupRouter(cfg *config.Config, configPath string, connMgr *db.ConnectionMa
|
||||
})
|
||||
})
|
||||
|
||||
router.GET("/api/connection-settings", func(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"host": cfg.Database.Host,
|
||||
"port": cfg.Database.Port,
|
||||
"database": cfg.Database.Name,
|
||||
"user": cfg.Database.User,
|
||||
})
|
||||
})
|
||||
|
||||
router.POST("/api/connection-settings/test", func(c *gin.Context) {
|
||||
host := strings.TrimSpace(c.PostForm("host"))
|
||||
database := strings.TrimSpace(c.PostForm("database"))
|
||||
user := strings.TrimSpace(c.PostForm("user"))
|
||||
password := c.PostForm("password")
|
||||
port, err := strconv.Atoi(strings.TrimSpace(c.DefaultPostForm("port", strconv.Itoa(cfg.Database.Port))))
|
||||
if err != nil || port <= 0 {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"success": false, "error": "invalid port"})
|
||||
return
|
||||
}
|
||||
|
||||
if host == "" || database == "" || user == "" {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"success": false, "error": "all fields except password are required"})
|
||||
return
|
||||
}
|
||||
if password == "" {
|
||||
password = cfg.Database.Password
|
||||
}
|
||||
|
||||
dsn := buildMySQLDSN(host, port, database, user, password, 5*time.Second)
|
||||
testDB, err := gorm.Open(mysql.Open(dsn), &gorm.Config{Logger: logger.Default.LogMode(logger.Silent)})
|
||||
if err != nil {
|
||||
c.JSON(http.StatusOK, gin.H{"success": false, "error": fmt.Sprintf("connection failed: %v", err)})
|
||||
return
|
||||
}
|
||||
sqlDB, err := testDB.DB()
|
||||
if err != nil {
|
||||
c.JSON(http.StatusOK, gin.H{"success": false, "error": fmt.Sprintf("failed to get database handle: %v", err)})
|
||||
return
|
||||
}
|
||||
defer sqlDB.Close()
|
||||
if err := sqlDB.Ping(); err != nil {
|
||||
c.JSON(http.StatusOK, gin.H{"success": false, "error": fmt.Sprintf("ping failed: %v", err)})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"success": true, "message": "connection successful"})
|
||||
})
|
||||
|
||||
router.POST("/api/connection-settings", func(c *gin.Context) {
|
||||
host := strings.TrimSpace(c.PostForm("host"))
|
||||
database := strings.TrimSpace(c.PostForm("database"))
|
||||
user := strings.TrimSpace(c.PostForm("user"))
|
||||
password := c.PostForm("password")
|
||||
port, err := strconv.Atoi(strings.TrimSpace(c.DefaultPostForm("port", strconv.Itoa(cfg.Database.Port))))
|
||||
if err != nil || port <= 0 {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"success": false, "error": "invalid port"})
|
||||
return
|
||||
}
|
||||
if host == "" || database == "" || user == "" {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"success": false, "error": "all fields except password are required"})
|
||||
return
|
||||
}
|
||||
if password == "" {
|
||||
password = cfg.Database.Password
|
||||
}
|
||||
|
||||
dsn := buildMySQLDSN(host, port, database, user, password, 5*time.Second)
|
||||
testDB, err := gorm.Open(mysql.Open(dsn), &gorm.Config{Logger: logger.Default.LogMode(logger.Silent)})
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"success": false, "error": fmt.Sprintf("connection failed: %v", err)})
|
||||
return
|
||||
}
|
||||
sqlDB, err := testDB.DB()
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"success": false, "error": fmt.Sprintf("failed to get database handle: %v", err)})
|
||||
return
|
||||
}
|
||||
if err := sqlDB.Ping(); err != nil {
|
||||
_ = sqlDB.Close()
|
||||
c.JSON(http.StatusBadRequest, gin.H{"success": false, "error": fmt.Sprintf("ping failed: %v", err)})
|
||||
return
|
||||
}
|
||||
_ = sqlDB.Close()
|
||||
|
||||
cfg.Database.Host = host
|
||||
cfg.Database.Port = port
|
||||
cfg.Database.Name = database
|
||||
cfg.Database.User = user
|
||||
cfg.Database.Password = password
|
||||
if err := saveConfig(configPath, cfg); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"success": false, "error": fmt.Sprintf("failed to save config: %v", err)})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"success": true,
|
||||
"message": "settings saved, restart required",
|
||||
"restart_required": true,
|
||||
})
|
||||
})
|
||||
|
||||
router.GET("/api/current-user", func(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"username": dbUser,
|
||||
@@ -574,6 +522,9 @@ func setupRouter(cfg *config.Config, configPath string, connMgr *db.ConnectionMa
|
||||
})
|
||||
|
||||
router.GET("/", webHandler.Index)
|
||||
router.GET("/setup", setupHandler.ShowSetup)
|
||||
router.POST("/setup", setupHandler.SaveConnection)
|
||||
router.POST("/setup/test", setupHandler.TestConnection)
|
||||
router.GET("/lot", webHandler.Lot)
|
||||
router.GET("/pricelists", webHandler.Pricelists)
|
||||
router.GET("/pricelists/:id", webHandler.PricelistDetail)
|
||||
@@ -647,6 +598,7 @@ func setupRouter(cfg *config.Config, configPath string, connMgr *db.ConnectionMa
|
||||
pricingAdmin.POST("/alerts/:id/acknowledge", pricingHandler.AcknowledgeAlert)
|
||||
pricingAdmin.POST("/alerts/:id/resolve", pricingHandler.ResolveAlert)
|
||||
pricingAdmin.POST("/alerts/:id/ignore", pricingHandler.IgnoreAlert)
|
||||
pricingAdmin.GET("/scheduler-runs", pricingHandler.ListSchedulerRuns)
|
||||
pricingAdmin.GET("/partnumber-books", pricingHandler.ListPartnumberBooks)
|
||||
pricingAdmin.POST("/partnumber-books", pricingHandler.CreatePartnumberBook)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user