+1
This commit is contained in:
+11
-3
@@ -19,14 +19,16 @@ const version = "1.0.0"
|
||||
type client struct {
|
||||
baseURL string
|
||||
http *http.Client
|
||||
site string
|
||||
}
|
||||
|
||||
func newClient(baseURL string) *client {
|
||||
func newClient(baseURL, site string) *client {
|
||||
return &client{
|
||||
baseURL: strings.TrimRight(baseURL, "/"),
|
||||
http: &http.Client{
|
||||
Timeout: 30 * time.Second,
|
||||
},
|
||||
site: strings.TrimSpace(site),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,6 +50,9 @@ func (c *client) do(method, path string, payload interface{}) ([]byte, int, erro
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
}
|
||||
req.Header.Set("X-WPSK-Client", "cli")
|
||||
if strings.TrimSpace(c.site) != "" {
|
||||
req.Header.Set("X-WPSK-Site", c.site)
|
||||
}
|
||||
|
||||
resp, err := c.http.Do(req)
|
||||
if err != nil {
|
||||
@@ -68,10 +73,12 @@ func main() {
|
||||
if defaultAPI == "" {
|
||||
defaultAPI = "http://localhost:8080"
|
||||
}
|
||||
defaultSite := os.Getenv("WPSK_SITE")
|
||||
apiURL := flag.String("api", defaultAPI, "API base URL")
|
||||
siteID := flag.String("site", defaultSite, "Site ID")
|
||||
versionFlag := flag.Bool("version", false, "Print CLI version")
|
||||
usage := func() {
|
||||
fmt.Fprintf(os.Stderr, "Usage: %s [--api URL] <command> [args]\n\n", os.Args[0])
|
||||
fmt.Fprintf(os.Stderr, "Usage: %s [--api URL] [--site SITE_ID] <command> [args]\n\n", os.Args[0])
|
||||
fmt.Fprintln(os.Stderr, "Commands:")
|
||||
fmt.Fprintln(os.Stderr, " health")
|
||||
fmt.Fprintln(os.Stderr, " pending [summary|text <draft_post_id>]")
|
||||
@@ -103,6 +110,7 @@ func main() {
|
||||
fmt.Fprintln(os.Stderr, " wp-sk-cli changes 123")
|
||||
fmt.Fprintln(os.Stderr, " WPSK_API_URL=http://server:8080 wp-sk-cli pending")
|
||||
fmt.Fprintln(os.Stderr, " wp-sk-cli approve 456 --api http://server:8080")
|
||||
fmt.Fprintln(os.Stderr, " wp-sk-cli --site padre pending")
|
||||
}
|
||||
flag.Usage = usage
|
||||
flag.Parse()
|
||||
@@ -125,7 +133,7 @@ func main() {
|
||||
return
|
||||
}
|
||||
|
||||
c := newClient(*apiURL)
|
||||
c := newClient(*apiURL, *siteID)
|
||||
if cmd != "version" && cmd != "help" {
|
||||
warnPendingOutreach(c)
|
||||
}
|
||||
|
||||
+232
-232
@@ -35,268 +35,290 @@ func main() {
|
||||
configPath = "configs/config.yaml"
|
||||
}
|
||||
|
||||
cfg, err := config.LoadConfig(configPath)
|
||||
configSet, err := config.LoadConfigSet(configPath)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Failed to load config: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
logLevel := strings.TrimSpace(configSet.Global.Logging.Level)
|
||||
logFormat := strings.TrimSpace(configSet.Global.Logging.Format)
|
||||
if (logLevel == "" || logFormat == "") && len(configSet.Sites) > 0 {
|
||||
if logLevel == "" {
|
||||
logLevel = configSet.Sites[0].Config.Logging.Level
|
||||
}
|
||||
if logFormat == "" {
|
||||
logFormat = configSet.Sites[0].Config.Logging.Format
|
||||
}
|
||||
}
|
||||
if logLevel == "" {
|
||||
logLevel = "INFO"
|
||||
}
|
||||
if logFormat == "" {
|
||||
logFormat = "json"
|
||||
}
|
||||
|
||||
// Initialize logger
|
||||
log := logger.NewLogger(cfg.Logging.Level, cfg.Logging.Format)
|
||||
log := logger.NewLogger(logLevel, logFormat)
|
||||
log.Info("SEO Optimizer starting",
|
||||
"version", version,
|
||||
"config", configPath,
|
||||
"sites", len(configSet.Sites),
|
||||
)
|
||||
|
||||
// Initialize WordPress client
|
||||
wpClient := wordpress.NewClient(
|
||||
cfg.WordPress.BaseURL,
|
||||
cfg.WordPress.Username,
|
||||
cfg.WordPress.AppPassword,
|
||||
cfg.WordPress.Timeout,
|
||||
log,
|
||||
)
|
||||
|
||||
log.Info("WordPress client initialized",
|
||||
"base_url", cfg.WordPress.BaseURL,
|
||||
"username", cfg.WordPress.Username,
|
||||
)
|
||||
llmProvider := strings.TrimSpace(configSet.Global.LLM.Provider)
|
||||
if llmProvider == "" && len(configSet.Sites) > 0 {
|
||||
llmProvider = strings.TrimSpace(configSet.Sites[0].Config.LLM.Provider)
|
||||
}
|
||||
|
||||
// Initialize LLM client
|
||||
var llmClient agent.LLMClient
|
||||
switch cfg.LLM.Provider {
|
||||
switch llmProvider {
|
||||
case "claude":
|
||||
claudeCfg := configSet.Global.Claude
|
||||
if claudeCfg.APIKey == "" && len(configSet.Sites) > 0 {
|
||||
claudeCfg = configSet.Sites[0].Config.Claude
|
||||
}
|
||||
llmClient = agent.NewClaudeClient(
|
||||
cfg.Claude.APIKey,
|
||||
cfg.Claude.Model,
|
||||
cfg.Claude.MaxTokens,
|
||||
cfg.Claude.Temperature,
|
||||
claudeCfg.APIKey,
|
||||
claudeCfg.Model,
|
||||
claudeCfg.MaxTokens,
|
||||
claudeCfg.Temperature,
|
||||
log,
|
||||
)
|
||||
log.Info("Claude client initialized",
|
||||
"model", cfg.Claude.Model,
|
||||
"max_tokens", cfg.Claude.MaxTokens,
|
||||
"model", claudeCfg.Model,
|
||||
"max_tokens", claudeCfg.MaxTokens,
|
||||
)
|
||||
case "deepseek":
|
||||
deepSeekCfg := configSet.Global.DeepSeek
|
||||
if deepSeekCfg.APIKey == "" && len(configSet.Sites) > 0 {
|
||||
deepSeekCfg = configSet.Sites[0].Config.DeepSeek
|
||||
}
|
||||
llmClient = agent.NewDeepSeekClient(
|
||||
cfg.DeepSeek.APIKey,
|
||||
cfg.DeepSeek.BaseURL,
|
||||
cfg.DeepSeek.Model,
|
||||
cfg.DeepSeek.MaxTokens,
|
||||
cfg.DeepSeek.Temperature,
|
||||
deepSeekCfg.APIKey,
|
||||
deepSeekCfg.BaseURL,
|
||||
deepSeekCfg.Model,
|
||||
deepSeekCfg.MaxTokens,
|
||||
deepSeekCfg.Temperature,
|
||||
log,
|
||||
)
|
||||
log.Info("DeepSeek client initialized",
|
||||
"model", cfg.DeepSeek.Model,
|
||||
"max_tokens", cfg.DeepSeek.MaxTokens,
|
||||
"base_url", cfg.DeepSeek.BaseURL,
|
||||
"model", deepSeekCfg.Model,
|
||||
"max_tokens", deepSeekCfg.MaxTokens,
|
||||
"base_url", deepSeekCfg.BaseURL,
|
||||
)
|
||||
default:
|
||||
log.Error("Unsupported LLM provider", "provider", cfg.LLM.Provider)
|
||||
log.Error("Unsupported LLM provider", "provider", llmProvider)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Initialize DeepSeek client for translations (optional)
|
||||
var translationClient *agent.DeepSeekClient
|
||||
if strings.TrimSpace(cfg.DeepSeek.APIKey) != "" {
|
||||
translationCfg := configSet.Global.DeepSeek
|
||||
if translationCfg.APIKey == "" && len(configSet.Sites) > 0 {
|
||||
translationCfg = configSet.Sites[0].Config.DeepSeek
|
||||
}
|
||||
if strings.TrimSpace(translationCfg.APIKey) != "" {
|
||||
translationClient = agent.NewDeepSeekClient(
|
||||
cfg.DeepSeek.APIKey,
|
||||
cfg.DeepSeek.BaseURL,
|
||||
cfg.DeepSeek.Model,
|
||||
cfg.DeepSeek.MaxTokens,
|
||||
cfg.DeepSeek.Temperature,
|
||||
translationCfg.APIKey,
|
||||
translationCfg.BaseURL,
|
||||
translationCfg.Model,
|
||||
translationCfg.MaxTokens,
|
||||
translationCfg.Temperature,
|
||||
log,
|
||||
)
|
||||
log.Info("DeepSeek translation client initialized", "model", cfg.DeepSeek.Model)
|
||||
log.Info("DeepSeek translation client initialized", "model", translationCfg.Model)
|
||||
} else {
|
||||
log.Warn("DeepSeek API key missing; translation feature will be unavailable")
|
||||
}
|
||||
|
||||
// Initialize storage
|
||||
store := storage.NewOptimizationStorage(cfg.Storage.BasePath, log)
|
||||
if err := store.Initialize(); err != nil {
|
||||
log.Error("Failed to initialize storage", "error", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
log.Info("Storage initialized", "base_path", cfg.Storage.BasePath)
|
||||
|
||||
// Initialize audit storage
|
||||
auditStore := storage.NewAuditStorage(cfg.Audit.BasePath, log)
|
||||
if err := auditStore.Initialize(); err != nil {
|
||||
log.Error("Failed to initialize audit storage", "error", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
log.Info("Audit storage initialized", "base_path", cfg.Audit.BasePath)
|
||||
|
||||
// Initialize idea storage
|
||||
ideaStore := storage.NewIdeaStorage(cfg.Ideas.BasePath, log)
|
||||
if err := ideaStore.Initialize(); err != nil {
|
||||
log.Error("Failed to initialize idea storage", "error", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
log.Info("Idea storage initialized", "base_path", cfg.Ideas.BasePath)
|
||||
|
||||
// Initialize outreach storage
|
||||
outreachStore := storage.NewOutreachStorage(cfg.Outreach.BasePath, log)
|
||||
if err := outreachStore.Initialize(); err != nil {
|
||||
log.Error("Failed to initialize outreach storage", "error", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
log.Info("Outreach storage initialized", "base_path", cfg.Outreach.BasePath)
|
||||
|
||||
// Initialize newsletter storage
|
||||
newsStore := storage.NewNewsletterStorage(cfg.Newsletter.BasePath, log)
|
||||
if err := newsStore.Initialize(); err != nil {
|
||||
log.Error("Failed to initialize newsletter storage", "error", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
log.Info("Newsletter storage initialized", "base_path", cfg.Newsletter.BasePath)
|
||||
|
||||
// Initialize broken link storage
|
||||
linkStore := storage.NewBrokenLinkStorage(cfg.LinkChecker.BasePath, log)
|
||||
if err := linkStore.Initialize(); err != nil {
|
||||
log.Error("Failed to initialize broken link storage", "error", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
log.Info("Broken link storage initialized", "base_path", cfg.LinkChecker.BasePath)
|
||||
|
||||
// Initialize image audit storage
|
||||
imageAuditStore := storage.NewImageAuditStorage(cfg.ImageAudit.BasePath, log)
|
||||
if err := imageAuditStore.Initialize(); err != nil {
|
||||
log.Error("Failed to initialize image audit storage", "error", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
log.Info("Image audit storage initialized", "base_path", cfg.ImageAudit.BasePath)
|
||||
|
||||
// Initialize SEO audit storage
|
||||
seoAuditStore := storage.NewSEOAuditStorage(cfg.SEOAudit.BasePath, log)
|
||||
if err := seoAuditStore.Initialize(); err != nil {
|
||||
log.Error("Failed to initialize SEO audit storage", "error", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
log.Info("SEO audit storage initialized", "base_path", cfg.SEOAudit.BasePath)
|
||||
|
||||
// Initialize idea service
|
||||
ideaService := ideas.NewService(wpClient, llmClient, nil, ideaStore, cfg.Ideas, outreachStore, cfg.Outreach, cfg.Runtime.DryRun, log)
|
||||
|
||||
// Initialize newsletter service
|
||||
newsService := newsletter.NewService(llmClient, nil, newsStore, cfg.Newsletter, log)
|
||||
|
||||
// Initialize broken link service
|
||||
linkService, err := brokenlinks.NewService(wpClient, linkStore, cfg.LinkChecker, log)
|
||||
if err != nil {
|
||||
log.Error("Failed to initialize broken link service", "error", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
imageAuditService, err := imageaudit.NewService(wpClient, imageAuditStore, cfg.ImageAudit, log)
|
||||
if err != nil {
|
||||
log.Error("Failed to initialize image audit service", "error", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
seoAuditService, err := seoaudit.NewService(wpClient, seoAuditStore, cfg.SEOAudit, log)
|
||||
if err != nil {
|
||||
log.Error("Failed to initialize SEO audit service", "error", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
translationService := translation.NewService(wpClient, translationClient, log)
|
||||
|
||||
// Initialize optimizer
|
||||
optimizer := seo.NewOptimizer(wpClient, llmClient, cfg.LLM.Provider, store, cfg, log)
|
||||
log.Info("Optimizer initialized")
|
||||
|
||||
// Context with cancellation for graceful shutdown
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
siteContexts := make([]*api.SiteContext, 0, len(configSet.Sites))
|
||||
stopFns := make([]func(), 0)
|
||||
|
||||
for _, siteDef := range configSet.Sites {
|
||||
cfg := siteDef.Config
|
||||
siteLog := log.WithFields("site", siteDef.ID)
|
||||
|
||||
// Initialize WordPress client
|
||||
wpClient := wordpress.NewClient(
|
||||
cfg.WordPress.BaseURL,
|
||||
cfg.WordPress.Username,
|
||||
cfg.WordPress.AppPassword,
|
||||
cfg.WordPress.Timeout,
|
||||
siteLog,
|
||||
)
|
||||
|
||||
siteLog.Info("WordPress client initialized",
|
||||
"base_url", cfg.WordPress.BaseURL,
|
||||
"username", cfg.WordPress.Username,
|
||||
)
|
||||
|
||||
store := storage.NewOptimizationStorage(cfg.Storage.BasePath, siteLog)
|
||||
if err := store.Initialize(); err != nil {
|
||||
log.Error("Failed to initialize storage", "error", err, "site", siteDef.ID)
|
||||
os.Exit(1)
|
||||
}
|
||||
auditStore := storage.NewAuditStorage(cfg.Audit.BasePath, siteLog)
|
||||
if err := auditStore.Initialize(); err != nil {
|
||||
log.Error("Failed to initialize audit storage", "error", err, "site", siteDef.ID)
|
||||
os.Exit(1)
|
||||
}
|
||||
ideaStore := storage.NewIdeaStorage(cfg.Ideas.BasePath, siteLog)
|
||||
if err := ideaStore.Initialize(); err != nil {
|
||||
log.Error("Failed to initialize idea storage", "error", err, "site", siteDef.ID)
|
||||
os.Exit(1)
|
||||
}
|
||||
outreachStore := storage.NewOutreachStorage(cfg.Outreach.BasePath, siteLog)
|
||||
if err := outreachStore.Initialize(); err != nil {
|
||||
log.Error("Failed to initialize outreach storage", "error", err, "site", siteDef.ID)
|
||||
os.Exit(1)
|
||||
}
|
||||
newsStore := storage.NewNewsletterStorage(cfg.Newsletter.BasePath, siteLog)
|
||||
if err := newsStore.Initialize(); err != nil {
|
||||
log.Error("Failed to initialize newsletter storage", "error", err, "site", siteDef.ID)
|
||||
os.Exit(1)
|
||||
}
|
||||
linkStore := storage.NewBrokenLinkStorage(cfg.LinkChecker.BasePath, siteLog)
|
||||
if err := linkStore.Initialize(); err != nil {
|
||||
log.Error("Failed to initialize broken link storage", "error", err, "site", siteDef.ID)
|
||||
os.Exit(1)
|
||||
}
|
||||
imageAuditStore := storage.NewImageAuditStorage(cfg.ImageAudit.BasePath, siteLog)
|
||||
if err := imageAuditStore.Initialize(); err != nil {
|
||||
log.Error("Failed to initialize image audit storage", "error", err, "site", siteDef.ID)
|
||||
os.Exit(1)
|
||||
}
|
||||
seoAuditStore := storage.NewSEOAuditStorage(cfg.SEOAudit.BasePath, siteLog)
|
||||
if err := seoAuditStore.Initialize(); err != nil {
|
||||
log.Error("Failed to initialize SEO audit storage", "error", err, "site", siteDef.ID)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
ideaService := ideas.NewService(wpClient, llmClient, nil, ideaStore, cfg.Ideas, outreachStore, cfg.Outreach, cfg.Profile, cfg.Scheduler.Languages, cfg.Runtime.DryRun, siteLog)
|
||||
newsService := newsletter.NewService(llmClient, nil, newsStore, cfg.Newsletter, siteLog)
|
||||
|
||||
linkService, err := brokenlinks.NewService(wpClient, linkStore, cfg.LinkChecker, siteLog)
|
||||
if err != nil {
|
||||
log.Error("Failed to initialize broken link service", "error", err, "site", siteDef.ID)
|
||||
os.Exit(1)
|
||||
}
|
||||
imageAuditService, err := imageaudit.NewService(wpClient, imageAuditStore, cfg.ImageAudit, siteLog)
|
||||
if err != nil {
|
||||
log.Error("Failed to initialize image audit service", "error", err, "site", siteDef.ID)
|
||||
os.Exit(1)
|
||||
}
|
||||
seoAuditService, err := seoaudit.NewService(wpClient, seoAuditStore, cfg.SEOAudit, siteLog)
|
||||
if err != nil {
|
||||
log.Error("Failed to initialize SEO audit service", "error", err, "site", siteDef.ID)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
translationService := translation.NewService(wpClient, translationClient, siteLog)
|
||||
|
||||
optimizer := seo.NewOptimizer(wpClient, llmClient, cfg.LLM.Provider, store, cfg, siteLog)
|
||||
siteLog.Info("Optimizer initialized")
|
||||
|
||||
siteContexts = append(siteContexts, &api.SiteContext{
|
||||
ID: siteDef.ID,
|
||||
Name: siteDef.Name,
|
||||
Config: cfg,
|
||||
Optimizer: optimizer,
|
||||
WPClient: wpClient,
|
||||
Storage: store,
|
||||
Audit: auditStore,
|
||||
IdeaSvc: ideaService,
|
||||
NewsSvc: newsService,
|
||||
LinkSvc: linkService,
|
||||
ImageSvc: imageAuditService,
|
||||
SEOAuditSvc: seoAuditService,
|
||||
TranslationSvc: translationService,
|
||||
})
|
||||
|
||||
if cfg.Scheduler.Enabled {
|
||||
sched := scheduler.NewScheduler(
|
||||
optimizer,
|
||||
cfg.Scheduler.IntervalHours,
|
||||
cfg.Scheduler.Languages,
|
||||
siteLog,
|
||||
)
|
||||
|
||||
go func(local *scheduler.Scheduler, id string) {
|
||||
if err := local.Start(ctx); err != nil && err != context.Canceled {
|
||||
log.Error("Scheduler failed", "error", err, "site", id)
|
||||
}
|
||||
}(sched, siteDef.ID)
|
||||
|
||||
siteLog.Info("Scheduler started",
|
||||
"interval", fmt.Sprintf("%.1fh", cfg.Scheduler.IntervalHours),
|
||||
"languages", cfg.Scheduler.Languages,
|
||||
)
|
||||
stopFns = append(stopFns, sched.Stop)
|
||||
}
|
||||
|
||||
if cfg.Ideas.Enabled || cfg.Outreach.Enabled {
|
||||
ideaSched := scheduler.NewIdeaScheduler(ideaService, cfg.Ideas.IntervalHours, cfg.Ideas.Enabled, cfg.Outreach.Enabled, auditStore, siteLog)
|
||||
go func(local *scheduler.IdeaScheduler, id string) {
|
||||
if err := local.Start(ctx); err != nil && err != context.Canceled {
|
||||
log.Error("Idea scheduler failed", "error", err, "site", id)
|
||||
}
|
||||
}(ideaSched, siteDef.ID)
|
||||
siteLog.Info("Idea scheduler started", "interval", cfg.Ideas.IntervalHours)
|
||||
stopFns = append(stopFns, ideaSched.Stop)
|
||||
}
|
||||
|
||||
if cfg.Newsletter.Enabled {
|
||||
newsSched := scheduler.NewNewsletterScheduler(newsService, cfg.Newsletter.IntervalHours, auditStore, siteLog)
|
||||
go func(local *scheduler.NewsletterScheduler, id string) {
|
||||
if err := local.Start(ctx); err != nil && err != context.Canceled {
|
||||
log.Error("Newsletter scheduler failed", "error", err, "site", id)
|
||||
}
|
||||
}(newsSched, siteDef.ID)
|
||||
siteLog.Info("Newsletter scheduler started", "interval", cfg.Newsletter.IntervalHours)
|
||||
stopFns = append(stopFns, newsSched.Stop)
|
||||
}
|
||||
|
||||
if cfg.LinkChecker.Enabled {
|
||||
linkSched := scheduler.NewBrokenLinkScheduler(linkService, cfg.LinkChecker.IntervalHours, auditStore, siteLog)
|
||||
go func(local *scheduler.BrokenLinkScheduler, id string) {
|
||||
if err := local.Start(ctx); err != nil && err != context.Canceled {
|
||||
log.Error("Broken link scheduler failed", "error", err, "site", id)
|
||||
}
|
||||
}(linkSched, siteDef.ID)
|
||||
siteLog.Info("Broken link scheduler started", "interval", cfg.LinkChecker.IntervalHours)
|
||||
stopFns = append(stopFns, linkSched.Stop)
|
||||
}
|
||||
|
||||
if cfg.ImageAudit.Enabled {
|
||||
imageAuditSched := scheduler.NewImageAuditScheduler(imageAuditService, cfg.ImageAudit.IntervalHours, auditStore, siteLog)
|
||||
go func(local *scheduler.ImageAuditScheduler, id string) {
|
||||
if err := local.Start(ctx); err != nil && err != context.Canceled {
|
||||
log.Error("Image audit scheduler failed", "error", err, "site", id)
|
||||
}
|
||||
}(imageAuditSched, siteDef.ID)
|
||||
siteLog.Info("Image audit scheduler started", "interval", cfg.ImageAudit.IntervalHours)
|
||||
stopFns = append(stopFns, imageAuditSched.Stop)
|
||||
}
|
||||
}
|
||||
|
||||
// Start API server if enabled
|
||||
var apiServer *api.Server
|
||||
if cfg.API.Enabled {
|
||||
apiServer = api.NewServer(optimizer, wpClient, store, auditStore, ideaService, newsService, linkService, imageAuditService, seoAuditService, translationService, version, cfg.API.Port, log)
|
||||
if configSet.Global.API.Enabled {
|
||||
apiServer = api.NewServer(siteContexts, configSet.DefaultSite, version, configSet.Global.API.Port, log)
|
||||
|
||||
// Start server in goroutine
|
||||
go func() {
|
||||
if err := apiServer.Start(); err != nil && err != http.ErrServerClosed {
|
||||
log.Error("API server failed", "error", err)
|
||||
}
|
||||
}()
|
||||
|
||||
// Start periodic job cleanup
|
||||
go apiServer.StartPeriodicCleanup(ctx, 1*time.Hour, 24*time.Hour)
|
||||
|
||||
log.Info("API server started", "port", cfg.API.Port)
|
||||
}
|
||||
|
||||
// Start scheduler if enabled
|
||||
var sched *scheduler.Scheduler
|
||||
if cfg.Scheduler.Enabled {
|
||||
sched = scheduler.NewScheduler(
|
||||
optimizer,
|
||||
cfg.Scheduler.IntervalHours,
|
||||
cfg.Scheduler.Languages,
|
||||
log,
|
||||
)
|
||||
|
||||
go func() {
|
||||
if err := sched.Start(ctx); err != nil && err != context.Canceled {
|
||||
log.Error("Scheduler failed", "error", err)
|
||||
}
|
||||
}()
|
||||
|
||||
log.Info("Scheduler started",
|
||||
"interval", fmt.Sprintf("%.1fh", cfg.Scheduler.IntervalHours),
|
||||
"languages", cfg.Scheduler.Languages,
|
||||
)
|
||||
}
|
||||
|
||||
// Start idea scheduler if enabled
|
||||
var ideaSched *scheduler.IdeaScheduler
|
||||
if cfg.Ideas.Enabled || cfg.Outreach.Enabled {
|
||||
ideaSched = scheduler.NewIdeaScheduler(ideaService, cfg.Ideas.IntervalHours, cfg.Ideas.Enabled, cfg.Outreach.Enabled, auditStore, log)
|
||||
go func() {
|
||||
if err := ideaSched.Start(ctx); err != nil && err != context.Canceled {
|
||||
log.Error("Idea scheduler failed", "error", err)
|
||||
}
|
||||
}()
|
||||
log.Info("Idea scheduler started", "interval", cfg.Ideas.IntervalHours)
|
||||
}
|
||||
|
||||
// Start newsletter scheduler if enabled
|
||||
var newsSched *scheduler.NewsletterScheduler
|
||||
if cfg.Newsletter.Enabled {
|
||||
newsSched = scheduler.NewNewsletterScheduler(newsService, cfg.Newsletter.IntervalHours, auditStore, log)
|
||||
go func() {
|
||||
if err := newsSched.Start(ctx); err != nil && err != context.Canceled {
|
||||
log.Error("Newsletter scheduler failed", "error", err)
|
||||
}
|
||||
}()
|
||||
log.Info("Newsletter scheduler started", "interval", cfg.Newsletter.IntervalHours)
|
||||
}
|
||||
|
||||
// Start broken link scheduler if enabled
|
||||
var linkSched *scheduler.BrokenLinkScheduler
|
||||
if cfg.LinkChecker.Enabled {
|
||||
linkSched = scheduler.NewBrokenLinkScheduler(linkService, cfg.LinkChecker.IntervalHours, auditStore, log)
|
||||
go func() {
|
||||
if err := linkSched.Start(ctx); err != nil && err != context.Canceled {
|
||||
log.Error("Broken link scheduler failed", "error", err)
|
||||
}
|
||||
}()
|
||||
log.Info("Broken link scheduler started", "interval", cfg.LinkChecker.IntervalHours)
|
||||
}
|
||||
|
||||
// Start image audit scheduler if enabled
|
||||
var imageAuditSched *scheduler.ImageAuditScheduler
|
||||
if cfg.ImageAudit.Enabled {
|
||||
imageAuditSched = scheduler.NewImageAuditScheduler(imageAuditService, cfg.ImageAudit.IntervalHours, auditStore, log)
|
||||
go func() {
|
||||
if err := imageAuditSched.Start(ctx); err != nil && err != context.Canceled {
|
||||
log.Error("Image audit scheduler failed", "error", err)
|
||||
}
|
||||
}()
|
||||
log.Info("Image audit scheduler started", "interval", cfg.ImageAudit.IntervalHours)
|
||||
log.Info("API server started", "port", configSet.Global.API.Port)
|
||||
}
|
||||
|
||||
// Wait for interrupt signal
|
||||
@@ -324,30 +346,8 @@ func main() {
|
||||
}
|
||||
}
|
||||
|
||||
// Stop scheduler
|
||||
if sched != nil {
|
||||
sched.Stop()
|
||||
log.Info("Scheduler stopped")
|
||||
}
|
||||
|
||||
if ideaSched != nil {
|
||||
ideaSched.Stop()
|
||||
log.Info("Idea scheduler stopped")
|
||||
}
|
||||
|
||||
if newsSched != nil {
|
||||
newsSched.Stop()
|
||||
log.Info("Newsletter scheduler stopped")
|
||||
}
|
||||
|
||||
if linkSched != nil {
|
||||
linkSched.Stop()
|
||||
log.Info("Broken link scheduler stopped")
|
||||
}
|
||||
|
||||
if imageAuditSched != nil {
|
||||
imageAuditSched.Stop()
|
||||
log.Info("Image audit scheduler stopped")
|
||||
for _, stop := range stopFns {
|
||||
stop()
|
||||
}
|
||||
|
||||
log.Info("SEO Optimizer stopped gracefully")
|
||||
|
||||
Reference in New Issue
Block a user