68b170126d
- Updated `InitAISummarizerClient` to accept `mongoManager` for tracking translation success. - Introduced new `Statistics` endpoint in the dashboard to fetch scraping and translation statistics. - Enhanced `TranslationWorker` to utilize the new success counter for tracking successful translations. - Added necessary data structures and query forms for statistics reporting. This refactor improves the tracking of AI translation success and provides new insights through the dashboard statistics.
405 lines
12 KiB
Go
405 lines
12 KiB
Go
package bootstrap
|
|
|
|
import (
|
|
"fmt"
|
|
"time"
|
|
ai_summarizer "tm/pkg/ai_summarizer"
|
|
"tm/pkg/authorization"
|
|
"tm/pkg/config"
|
|
"tm/pkg/filestore"
|
|
"tm/pkg/gorules"
|
|
"tm/pkg/hcaptcha"
|
|
"tm/pkg/logger"
|
|
"tm/pkg/mongo"
|
|
"tm/pkg/notification"
|
|
"tm/pkg/ollama"
|
|
"tm/pkg/redis"
|
|
|
|
"github.com/labstack/echo/v4"
|
|
"github.com/labstack/echo/v4/middleware"
|
|
echoSwagger "github.com/swaggo/echo-swagger"
|
|
)
|
|
|
|
// 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
|
|
}
|
|
|
|
// initHTTPServer initializes the Echo HTTP server with middleware
|
|
func InitHTTPServer(conf Config, log logger.Logger) *echo.Echo {
|
|
e := echo.New()
|
|
|
|
// Configure Echo
|
|
// e.HideBanner = true
|
|
// e.HidePort = true
|
|
|
|
// Add middleware
|
|
e.Use(middleware.Recover())
|
|
e.Use(middleware.RequestID())
|
|
e.Use(middleware.Logger())
|
|
e.Use(middleware.CORSWithConfig(middleware.CORSConfig{
|
|
AllowOrigins: []string{"*"},
|
|
AllowMethods: []string{echo.GET, echo.POST, echo.PUT, echo.DELETE, echo.OPTIONS, echo.PATCH},
|
|
AllowHeaders: []string{
|
|
echo.HeaderOrigin,
|
|
echo.HeaderContentType,
|
|
echo.HeaderAccept,
|
|
echo.HeaderAuthorization,
|
|
},
|
|
}))
|
|
|
|
// Add custom middleware for logging
|
|
e.Use(func(next echo.HandlerFunc) echo.HandlerFunc {
|
|
return func(c echo.Context) error {
|
|
start := time.Now()
|
|
|
|
// Process request
|
|
err := next(c)
|
|
|
|
// Log request details
|
|
duration := time.Since(start)
|
|
log.Info("HTTP request processed", map[string]interface{}{
|
|
"method": c.Request().Method,
|
|
"path": c.Request().URL.Path,
|
|
"status": c.Response().Status,
|
|
"duration": duration.String(),
|
|
"user_agent": c.Request().UserAgent(),
|
|
"remote_ip": c.RealIP(),
|
|
})
|
|
|
|
return err
|
|
}
|
|
})
|
|
|
|
// Add health check endpoint
|
|
e.GET("/admin/v1/health", healthHandler)
|
|
|
|
// Add Swagger documentation endpoint
|
|
e.GET("/swagger*", echoSwagger.WrapHandler)
|
|
|
|
log.Info("HTTP server initialized successfully", map[string]interface{}{
|
|
"middleware": []string{"recover", "request_id", "logger", "cors", "custom_logging"},
|
|
})
|
|
|
|
return e
|
|
}
|
|
|
|
// Init Redis Connection Manager
|
|
func InitRedis(conf config.RedisConfig, log logger.Logger) redis.Client {
|
|
connectionConfig := &redis.Config{
|
|
Host: conf.Host,
|
|
Port: conf.Port,
|
|
Password: conf.Password,
|
|
DB: conf.DB,
|
|
PoolSize: conf.PoolSize,
|
|
}
|
|
|
|
redisClient, err := redis.NewClient(connectionConfig, log)
|
|
if err != nil {
|
|
log.Error("Failed to initialize Redis connection", map[string]interface{}{
|
|
"error": err.Error(),
|
|
"host": conf.Host,
|
|
"port": conf.Port,
|
|
})
|
|
panic(fmt.Sprintf("Failed to initialize Redis connection: %v", err))
|
|
}
|
|
|
|
log.Info("Redis connection manager initialized successfully", map[string]interface{}{
|
|
"host": conf.Host,
|
|
"port": conf.Port,
|
|
"pool_size": conf.PoolSize,
|
|
})
|
|
|
|
return redisClient
|
|
}
|
|
|
|
// Init Authorization Service
|
|
func InitAuthorizationService(conf JWTConfig, redisClient redis.Client, log logger.Logger) authorization.AuthorizationService {
|
|
authConfig := &authorization.AuthorizationConfig{
|
|
AccessTokenSecret: conf.AccessSecret,
|
|
RefreshTokenSecret: conf.RefreshSecret,
|
|
AccessTokenExpiry: time.Duration(conf.AccessExpiresIn) * time.Second,
|
|
RefreshTokenExpiry: time.Duration(conf.RefreshExpiresIn) * time.Second,
|
|
Issuer: "tender-management-system",
|
|
Audience: "tender-management-users",
|
|
}
|
|
|
|
authService := authorization.NewAuthorizationService(authConfig, log, redisClient)
|
|
|
|
log.Info("Authorization service initialized successfully", map[string]interface{}{
|
|
"access_expires_in": conf.AccessExpiresIn,
|
|
"refresh_expires_in": conf.RefreshExpiresIn,
|
|
})
|
|
|
|
return authService
|
|
}
|
|
|
|
// Init Notification Service
|
|
func InitNotificationService(conf config.NotificationConfig, log logger.Logger) notification.SDK {
|
|
notificationConfig := ¬ification.Config{
|
|
BaseURL: conf.BaseURL,
|
|
Timeout: conf.Timeout,
|
|
RetryAttempts: conf.RetryAttempts,
|
|
RetryDelay: conf.RetryDelay,
|
|
EnableLogging: conf.EnableLogging,
|
|
UserAgent: conf.UserAgent,
|
|
}
|
|
|
|
notificationSDK, err := notification.New(notificationConfig, log)
|
|
if err != nil {
|
|
log.Error("Failed to initialize Notification SDK", map[string]interface{}{
|
|
"error": err.Error(),
|
|
})
|
|
fmt.Printf("Failed to initialize Notification SDK: %v\n", err.Error())
|
|
}
|
|
|
|
log.Info("Notification SDK initialized successfully", map[string]interface{}{
|
|
"base_url": conf.BaseURL,
|
|
"timeout": conf.Timeout,
|
|
"retry_attempts": conf.RetryAttempts,
|
|
"retry_delay": conf.RetryDelay,
|
|
"enable_logging": conf.EnableLogging,
|
|
"user_agent": conf.UserAgent,
|
|
})
|
|
|
|
return *notificationSDK
|
|
}
|
|
|
|
// Init Ollama Service
|
|
func InitOllamaService(conf config.OllamaConfig, log logger.Logger) ollama.SDK {
|
|
ollamaConfig := &ollama.Config{
|
|
BaseURL: conf.BaseURL,
|
|
Timeout: conf.Timeout,
|
|
RetryAttempts: conf.RetryAttempts,
|
|
RetryDelay: conf.RetryDelay,
|
|
EnableLogging: conf.EnableLogging,
|
|
UserAgent: conf.UserAgent,
|
|
DefaultModel: conf.DefaultModel,
|
|
DefaultTemperature: conf.DefaultTemperature,
|
|
DefaultTopP: conf.DefaultTopP,
|
|
DefaultMaxTokens: conf.DefaultMaxTokens,
|
|
EnableStreaming: conf.EnableStreaming,
|
|
}
|
|
|
|
ollamaSDK, err := ollama.New(ollamaConfig, log)
|
|
if err != nil {
|
|
log.Error("Failed to initialize Ollama SDK", map[string]interface{}{
|
|
"error": err.Error(),
|
|
})
|
|
}
|
|
|
|
log.Info("Ollama SDK initialized successfully", map[string]interface{}{
|
|
"base_url": conf.BaseURL,
|
|
"timeout": conf.Timeout,
|
|
"retry_attempts": conf.RetryAttempts,
|
|
"retry_delay": conf.RetryDelay,
|
|
"enable_logging": conf.EnableLogging,
|
|
"user_agent": conf.UserAgent,
|
|
})
|
|
|
|
return *ollamaSDK
|
|
}
|
|
|
|
// InitHCaptchaService initializes the hCaptcha verifier
|
|
func InitHCaptchaService(conf config.HCaptchaConfig, log logger.Logger) hcaptcha.Verifier {
|
|
hcaptchaConfig := &hcaptcha.Config{
|
|
SecretKey: conf.SecretKey,
|
|
VerifyURL: conf.VerifyURL,
|
|
Timeout: conf.Timeout,
|
|
}
|
|
|
|
hcaptchaVerifier := hcaptcha.NewVerifier(hcaptchaConfig, log)
|
|
|
|
log.Info("hCaptcha verifier initialized successfully", map[string]interface{}{
|
|
"verify_url": conf.VerifyURL,
|
|
"timeout": conf.Timeout,
|
|
})
|
|
|
|
return hcaptchaVerifier
|
|
}
|
|
|
|
// InitFileStore initializes the GridFS file store service
|
|
func InitFileStore(mongoManager *mongo.ConnectionManager, log logger.Logger) (*filestore.GridFSService, error) {
|
|
fileStoreService, err := filestore.NewGridFSService(mongoManager, log)
|
|
if err != nil {
|
|
log.Error("Failed to initialize file store service", map[string]interface{}{
|
|
"error": err.Error(),
|
|
})
|
|
return nil, err
|
|
}
|
|
|
|
log.Info("File store service initialized successfully", map[string]interface{}{
|
|
"storage_type": "MongoDB GridFS",
|
|
})
|
|
|
|
return fileStoreService, nil
|
|
}
|
|
|
|
// InitAISummarizerClient initializes the AI summarizer HTTP client for on-demand summarization
|
|
func InitAISummarizerClient(conf AISummarizerConfig, mongoManager *mongo.ConnectionManager, log logger.Logger) *ai_summarizer.Client {
|
|
if conf.APIBaseURL == "" {
|
|
log.Warn("AI Summarizer API base URL not configured, on-demand summarization will be unavailable", map[string]interface{}{})
|
|
return nil
|
|
}
|
|
|
|
translationCounter := mongo.NewCounter(mongoManager)
|
|
cfg := &ai_summarizer.Config{
|
|
APIBaseURL: conf.APIBaseURL,
|
|
APITimeout: conf.APITimeout,
|
|
APIRetryCount: conf.APIRetryCount,
|
|
APIRetryDelay: conf.APIRetryDelay,
|
|
MinioEndpoint: conf.MinioEndpoint,
|
|
MinioAccessKey: conf.MinioAccessKey,
|
|
MinioSecretKey: conf.MinioSecretKey,
|
|
MinioUseSSL: conf.MinioUseSSL,
|
|
MinioRegion: conf.MinioRegion,
|
|
MinioBucket: conf.MinioBucket,
|
|
OnSuccessfulTranslation: mongo.AITranslationSuccessCallback(translationCounter),
|
|
}
|
|
|
|
client, err := ai_summarizer.NewClient(cfg, log)
|
|
if err != nil {
|
|
log.Error("Failed to initialize AI Summarizer client", map[string]interface{}{
|
|
"error": err.Error(),
|
|
})
|
|
return nil
|
|
}
|
|
|
|
log.Info("AI Summarizer HTTP client initialized successfully", map[string]interface{}{
|
|
"base_url": conf.APIBaseURL,
|
|
"timeout": conf.APITimeout,
|
|
})
|
|
|
|
return client
|
|
}
|
|
|
|
// InitAISummarizerStorage initializes the AI summarizer MinIO storage client
|
|
func InitAISummarizerStorage(conf AISummarizerConfig, log logger.Logger) *ai_summarizer.StorageClient {
|
|
if conf.MinioEndpoint == "" {
|
|
log.Warn("AI Summarizer MinIO endpoint not configured, storage-based summaries will be unavailable", map[string]interface{}{})
|
|
return nil
|
|
}
|
|
|
|
cfg := &ai_summarizer.Config{
|
|
APIBaseURL: conf.APIBaseURL,
|
|
APITimeout: conf.APITimeout,
|
|
APIRetryCount: conf.APIRetryCount,
|
|
APIRetryDelay: conf.APIRetryDelay,
|
|
MinioEndpoint: conf.MinioEndpoint,
|
|
MinioAccessKey: conf.MinioAccessKey,
|
|
MinioSecretKey: conf.MinioSecretKey,
|
|
MinioUseSSL: conf.MinioUseSSL,
|
|
MinioRegion: conf.MinioRegion,
|
|
MinioBucket: conf.MinioBucket,
|
|
}
|
|
|
|
storage, err := ai_summarizer.NewStorageClient(cfg, log)
|
|
if err != nil {
|
|
log.Error("Failed to initialize AI Summarizer storage client", map[string]interface{}{
|
|
"error": err.Error(),
|
|
})
|
|
return nil
|
|
}
|
|
|
|
log.Info("AI Summarizer storage client initialized successfully", map[string]interface{}{
|
|
"endpoint": conf.MinioEndpoint,
|
|
"bucket": conf.MinioBucket,
|
|
})
|
|
|
|
return storage
|
|
}
|
|
|
|
// InitGoRulesClient initializes the GoRules SDK client.
|
|
func InitGoRulesClient(_ GoRulesConfig, log logger.Logger) gorules.Client {
|
|
// GoRules SDK temporarily disabled — gorulessdk private repo unreachable from server.
|
|
// Kanban falls back to built-in bid workflow columns when client is nil.
|
|
log.Warn("GoRules client disabled; Kanban uses built-in bid workflow columns", map[string]interface{}{})
|
|
return nil
|
|
|
|
/*
|
|
if conf.BaseURL == "" || conf.Token == "" || conf.RuleID == "" {
|
|
log.Warn("GoRules client not configured, falling back to static Kanban statuses", map[string]interface{}{})
|
|
return nil
|
|
}
|
|
|
|
timeout := conf.Timeout
|
|
if timeout <= 0 {
|
|
timeout = 10 * time.Second
|
|
}
|
|
|
|
client, err := gorules.New(gorules.Config{
|
|
BaseURL: conf.BaseURL,
|
|
Token: conf.Token,
|
|
Timeout: timeout,
|
|
}, log)
|
|
if err != nil {
|
|
log.Error("Failed to initialize GoRules client", map[string]interface{}{
|
|
"error": err.Error(),
|
|
})
|
|
return nil
|
|
}
|
|
|
|
log.Info("GoRules client initialized successfully", map[string]interface{}{
|
|
"base_url": conf.BaseURL,
|
|
"timeout": timeout,
|
|
"rule_id": conf.RuleID,
|
|
})
|
|
|
|
return client
|
|
*/
|
|
}
|