Refactor Dockerfile and CI Configuration

- Updated the CI configuration in .drone.yml to use the new Dockerfile located at ./cmd/web/Dockerfile.
- Removed the old Dockerfile and Dockerfile-drone as they are no longer needed.
- Refactored the cmd/web/Dockerfile to streamline the build process and improve organization.
- Introduced a new bootstrap package to handle application initialization, including configuration, logging, MongoDB, Redis, and authorization service setup.
- Added a health check endpoint for monitoring the server status, enhancing the API's operational capabilities.
This commit is contained in:
n.nakhostin
2025-08-31 14:16:37 +03:30
parent 4b3172d058
commit 511155e0a7
8 changed files with 42 additions and 121 deletions
+179
View File
@@ -0,0 +1,179 @@
package bootstrap
import (
"fmt"
"net/http"
"time"
"tm/pkg/authorization"
"tm/pkg/config"
"tm/pkg/logger"
"tm/pkg/mongo"
"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{"*"}, // TODO: Configure properly for production
AllowMethods: []string{http.MethodGet, http.MethodPost, http.MethodPut, http.MethodDelete, http.MethodOptions},
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
}
+27
View File
@@ -0,0 +1,27 @@
package bootstrap
import (
"tm/pkg/config"
)
// Config holds configuration for the web command
type Config struct {
Server config.ServerConfig `mapstructure:"server"`
Database config.DatabaseConfig `mapstructure:"database"`
Cache config.CacheConfig `mapstructure:"cache"`
Logging config.LoggingConfig `mapstructure:"logging"`
RateLimit config.RateLimitConfig `mapstructure:"rate_limiting"`
UserAuth AuthConfig `mapstructure:"user_authorization"`
CustomerAuth AuthConfig `mapstructure:"customer_authorization"`
}
type AuthConfig struct {
JWT JWTConfig `mapstructure:"jwt"`
}
type JWTConfig struct {
AccessSecret string `mapstructure:"access_secret"`
RefreshSecret string `mapstructure:"refresh_secret"`
AccessExpiresIn int `mapstructure:"access_expires_in"`
RefreshExpiresIn int `mapstructure:"refresh_expires_in"`
}
+35
View File
@@ -0,0 +1,35 @@
package bootstrap
import (
"net/http"
"time"
"github.com/labstack/echo/v4"
)
// @Summary Health check endpoint
// @Description Get comprehensive server health status including system information, dependencies status, and performance metrics. This endpoint is used for monitoring and load balancer health checks.
// @Tags Admin-Health
// @Accept json
// @Produce json
// @Success 200 {object} HealthResponse "System is healthy and operational"
// @Failure 503 {object} HealthResponse "System is unhealthy or experiencing issues"
// @Router /admin/v1/health [get]
func healthHandler(c echo.Context) error {
return c.JSON(http.StatusOK, HealthResponse{
Status: "healthy",
Time: time.Now().Unix(),
Version: "2.0.0",
Service: "tender-management-api",
Uptime: time.Since(time.Now()).String(),
})
}
// HealthResponse represents the comprehensive health check response
type HealthResponse struct {
Status string `json:"status" example:"healthy"` // Current health status
Time int64 `json:"time" example:"1699123456"` // Current Unix timestamp
Version string `json:"version" example:"2.0.0"` // API version
Service string `json:"service" example:"tender-management-api"` // Service name
Uptime string `json:"uptime,omitempty" example:"24h30m15s"` // Service uptime duration
}