96c21b8b78
- Introduced a new TED scraper in `cmd/scraper` to handle downloading and parsing TED XML files. - Added configuration management for the scraper, including a new `Config` struct and YAML configuration file. - Created necessary handler, service, and repository layers for tender management, adhering to Clean Architecture principles. - Implemented comprehensive logging and error handling throughout the scraper functionality. - Updated API routes to include tender management operations, enhancing the overall system capabilities.
141 lines
3.9 KiB
Go
141 lines
3.9 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"os"
|
|
"os/signal"
|
|
"syscall"
|
|
"time"
|
|
"tm/internal/tender"
|
|
"tm/pkg/config"
|
|
"tm/pkg/logger"
|
|
"tm/pkg/mongo"
|
|
"tm/ted"
|
|
)
|
|
|
|
// Init Application Configuration
|
|
func initConfig() *Config {
|
|
conf, err := config.LoadConfig(".", &Config{})
|
|
if err != nil {
|
|
panic(fmt.Sprintf("Failed to load config: %v", err))
|
|
}
|
|
|
|
return conf
|
|
}
|
|
|
|
// Init Logger Service
|
|
func initLogger(conf config.LoggingConfig) logger.Logger {
|
|
return logger.NewLogger(&logger.Config{
|
|
Level: conf.Level,
|
|
Format: conf.Format,
|
|
Output: conf.Output,
|
|
File: logger.FileConfig{
|
|
Path: conf.File.Path,
|
|
MaxSize: conf.File.MaxSize,
|
|
MaxBackups: conf.File.MaxBackups,
|
|
MaxAge: conf.File.MaxAge,
|
|
Compress: conf.File.Compress,
|
|
},
|
|
})
|
|
}
|
|
|
|
// Init MongoDB Connection Manager
|
|
func initMongoDB(conf config.MongoConfig, log logger.Logger) *mongo.ConnectionManager {
|
|
// Convert infra.MongoConfig to mongo.ConnectionConfig
|
|
connectionConfig := mongo.ConnectionConfig{
|
|
URI: conf.URI,
|
|
Database: conf.Name,
|
|
MaxPoolSize: uint64(conf.MaxPoolSize),
|
|
MinPoolSize: 5, // Default minimum pool size
|
|
MaxConnIdleTime: 30 * time.Minute, // Default idle time
|
|
ConnectTimeout: conf.Timeout,
|
|
ServerSelectionTimeout: 5 * time.Second, // Default server selection timeout
|
|
}
|
|
|
|
// Create MongoDB connection manager
|
|
connectionManager, err := mongo.NewConnectionManager(connectionConfig, log)
|
|
if err != nil {
|
|
log.Error("Failed to initialize MongoDB connection", map[string]interface{}{
|
|
"error": err.Error(),
|
|
"uri": conf.URI,
|
|
"db": conf.Name,
|
|
})
|
|
panic(fmt.Sprintf("Failed to initialize MongoDB connection: %v", err))
|
|
}
|
|
|
|
log.Info("MongoDB connection manager initialized successfully", map[string]interface{}{
|
|
"database": conf.Name,
|
|
"uri": conf.URI,
|
|
"pool_size": conf.MaxPoolSize,
|
|
})
|
|
|
|
return connectionManager
|
|
}
|
|
|
|
// init TED scraper
|
|
func initTEDScraper(config Config, mongoManager *mongo.ConnectionManager, appLogger logger.Logger) {
|
|
// Initialize tender repository
|
|
tenderRepo := tender.NewTenderRepository(mongoManager, appLogger)
|
|
|
|
// Initialize TED scraper
|
|
tedScraper := ted.NewTEDScraper(
|
|
&ted.Config{
|
|
BaseURL: config.TED.BaseURL,
|
|
Timeout: config.TED.Timeout,
|
|
MaxRetries: config.TED.MaxRetries,
|
|
RetryDelay: config.TED.RetryDelay,
|
|
UserAgent: config.TED.UserAgent,
|
|
MaxConcurrency: config.TED.MaxConcurrency,
|
|
DownloadDir: config.TED.DownloadDir,
|
|
CleanupAfter: config.TED.CleanupAfter,
|
|
ScrapingInterval: config.TED.ScrapingInterval,
|
|
},
|
|
appLogger,
|
|
mongoManager,
|
|
tenderRepo,
|
|
)
|
|
|
|
// Create context with cancellation
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
defer cancel()
|
|
|
|
// Set up signal handling for graceful shutdown
|
|
signalChan := make(chan os.Signal, 1)
|
|
signal.Notify(signalChan, syscall.SIGINT, syscall.SIGTERM)
|
|
|
|
// Start the scraper in a goroutine
|
|
scraperDone := make(chan error, 1)
|
|
go func() {
|
|
appLogger.Info("Starting TED scraper", map[string]interface{}{
|
|
"interval": config.TED.ScrapingInterval.String(),
|
|
})
|
|
scraperDone <- tedScraper.RunPeriodicScraping(ctx)
|
|
}()
|
|
|
|
// Wait for shutdown signal or scraper completion
|
|
select {
|
|
case <-signalChan:
|
|
appLogger.Info("Received shutdown signal, stopping scraper...", map[string]interface{}{})
|
|
cancel()
|
|
|
|
// Wait for scraper to finish gracefully with timeout
|
|
shutdownTimeout := time.NewTimer(30 * time.Second)
|
|
select {
|
|
case <-scraperDone:
|
|
appLogger.Info("Scraper stopped gracefully", map[string]interface{}{})
|
|
case <-shutdownTimeout.C:
|
|
appLogger.Warn("Scraper shutdown timeout, forcing exit", map[string]interface{}{})
|
|
}
|
|
|
|
case err := <-scraperDone:
|
|
if err != nil {
|
|
appLogger.Error("Scraper finished with error", map[string]interface{}{
|
|
"error": err.Error(),
|
|
})
|
|
os.Exit(1)
|
|
}
|
|
appLogger.Info("Scraper finished successfully", map[string]interface{}{})
|
|
}
|
|
}
|