committtttt
This commit is contained in:
205
backend/pkg/config/config.go
Normal file
205
backend/pkg/config/config.go
Normal file
@@ -0,0 +1,205 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/golang-jwt/jwt/v5"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
Server ServerConfig
|
||||
Database DatabaseConfig
|
||||
Auth AuthConfig
|
||||
AI AIConfig
|
||||
JWT JWTConfig
|
||||
}
|
||||
|
||||
type ServerConfig struct {
|
||||
Host string
|
||||
Port string
|
||||
ReadTimeout time.Duration
|
||||
WriteTimeout time.Duration
|
||||
}
|
||||
|
||||
type DatabaseConfig struct {
|
||||
Host string
|
||||
Port string
|
||||
User string
|
||||
Password string
|
||||
DBName string
|
||||
SSLMode string
|
||||
TimeZone string
|
||||
}
|
||||
|
||||
type AuthConfig struct {
|
||||
JWTSecret string
|
||||
JWTExpirationHours int
|
||||
BCryptCost int
|
||||
MaxLoginAttempts int
|
||||
AccountLockDuration time.Duration
|
||||
}
|
||||
|
||||
type AIConfig struct {
|
||||
OpenAI APIConfig
|
||||
Local APIConfig
|
||||
}
|
||||
|
||||
type APIConfig struct {
|
||||
APIKey string
|
||||
Endpoint string
|
||||
Model string
|
||||
MaxTokens int
|
||||
Temperature float64
|
||||
TopP float64
|
||||
Timeout time.Duration
|
||||
}
|
||||
|
||||
type JWTConfig struct {
|
||||
Secret string
|
||||
ExpirationHours int
|
||||
Issuer string
|
||||
Audience string
|
||||
}
|
||||
|
||||
var AppConfig Config
|
||||
|
||||
// LoadConfig loads configuration from environment variables and config file
|
||||
func LoadConfig() error {
|
||||
// Set default values
|
||||
viper.SetDefault("server.host", "0.0.0.0")
|
||||
viper.SetDefault("server.port", "8080")
|
||||
viper.SetDefault("server.readTimeout", "15s")
|
||||
viper.SetDefault("server.writeTimeout", "15s")
|
||||
|
||||
viper.SetDefault("database.host", "localhost")
|
||||
viper.SetDefault("database.port", "5432")
|
||||
viper.SetDefault("database.user", "postgres")
|
||||
viper.SetDefault("database.password", "postgres")
|
||||
viper.SetDefault("database.dbname", "support")
|
||||
viper.SetDefault("database.sslmode", "disable")
|
||||
viper.SetDefault("database.timezone", "UTC")
|
||||
|
||||
viper.SetDefault("auth.jwtSecret", "your-secret-key")
|
||||
viper.SetDefault("auth.jwtExpirationHours", 24)
|
||||
viper.SetDefault("auth.bcryptCost", 12)
|
||||
viper.SetDefault("auth.maxLoginAttempts", 5)
|
||||
viper.SetDefault("auth.accountLockDuration", "30m")
|
||||
|
||||
viper.SetDefault("ai.openai.maxTokens", 4000)
|
||||
viper.SetDefault("ai.openai.temperature", 0.7)
|
||||
viper.SetDefault("ai.openai.topP", 1.0)
|
||||
viper.SetDefault("ai.openai.timeout", "30s")
|
||||
|
||||
viper.SetDefault("ai.local.maxTokens", 2000)
|
||||
viper.SetDefault("ai.local.temperature", 0.7)
|
||||
viper.SetDefault("ai.local.topP", 1.0)
|
||||
viper.SetDefault("ai.local.timeout", "60s")
|
||||
viper.SetDefault("ai.local.endpoint", "http://localhost:11434")
|
||||
viper.SetDefault("ai.local.model", "llama2")
|
||||
|
||||
viper.SetDefault("jwt.secret", "your-jwt-secret")
|
||||
viper.SetDefault("jwt.expirationHours", 24)
|
||||
viper.SetDefault("jwt.issuer", "support-system")
|
||||
viper.SetDefault("jwt.audience", "support-users")
|
||||
|
||||
// Configure viper to read from environment variables
|
||||
viper.AutomaticEnv()
|
||||
viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
|
||||
|
||||
// Read config file if it exists
|
||||
viper.SetConfigName("config")
|
||||
viper.SetConfigType("yaml")
|
||||
viper.AddConfigPath("./")
|
||||
viper.AddConfigPath("./config")
|
||||
viper.AddConfigPath("../config")
|
||||
|
||||
if err := viper.ReadInConfig(); err != nil {
|
||||
if _, ok := err.(viper.ConfigFileNotFoundError); !ok {
|
||||
return fmt.Errorf("error reading config file: %w", err)
|
||||
}
|
||||
// Config file not found; ignore error
|
||||
}
|
||||
|
||||
// Unmarshal config
|
||||
if err := viper.Unmarshal(&AppConfig); err != nil {
|
||||
return fmt.Errorf("unable to decode config: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetDSN returns the database connection string
|
||||
func (c *DatabaseConfig) GetDSN() string {
|
||||
return fmt.Sprintf("host=%s user=%s password=%s dbname=%s port=%s sslmode=%s TimeZone=%s",
|
||||
c.Host, c.User, c.Password, c.DBName, c.Port, c.SSLMode, c.TimeZone)
|
||||
}
|
||||
|
||||
// GetServerAddress returns the server address
|
||||
func (c *ServerConfig) GetServerAddress() string {
|
||||
return fmt.Sprintf("%s:%s", c.Host, c.Port)
|
||||
}
|
||||
|
||||
// GetJWTSigningKey returns the JWT signing key
|
||||
func (c *JWTConfig) GetJWTSigningKey() []byte {
|
||||
return []byte(c.Secret)
|
||||
}
|
||||
|
||||
// ParseJWTToken parses a JWT token and returns the claims
|
||||
func (c *JWTConfig) ParseJWTToken(tokenString string) (*jwt.MapClaims, error) {
|
||||
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
|
||||
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
|
||||
return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
|
||||
}
|
||||
return c.GetJWTSigningKey(), nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid {
|
||||
return &claims, nil
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("invalid token")
|
||||
}
|
||||
|
||||
// GetEnv gets an environment variable with a default value
|
||||
func GetEnv(key, defaultValue string) string {
|
||||
if value, exists := os.LookupEnv(key); exists {
|
||||
return value
|
||||
}
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
// GetEnvAsInt gets an environment variable as an integer with a default value
|
||||
func GetEnvAsInt(key string, defaultValue int) int {
|
||||
valueStr := GetEnv(key, "")
|
||||
if value, err := strconv.Atoi(valueStr); err == nil {
|
||||
return value
|
||||
}
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
// GetEnvAsBool gets an environment variable as a boolean with a default value
|
||||
func GetEnvAsBool(key string, defaultValue bool) bool {
|
||||
valueStr := GetEnv(key, "")
|
||||
if value, err := strconv.ParseBool(valueStr); err == nil {
|
||||
return value
|
||||
}
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
// GetEnvAsDuration gets an environment variable as a duration with a default value
|
||||
func GetEnvAsDuration(key string, defaultValue time.Duration) time.Duration {
|
||||
valueStr := GetEnv(key, "")
|
||||
if value, err := time.ParseDuration(valueStr); err == nil {
|
||||
return value
|
||||
}
|
||||
return defaultValue
|
||||
}
|
260
backend/pkg/logger/logger.go
Normal file
260
backend/pkg/logger/logger.go
Normal file
@@ -0,0 +1,260 @@
|
||||
package logger
|
||||
|
||||
import (
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
var Logger *logrus.Logger
|
||||
|
||||
// InitLogger initializes the logger
|
||||
func InitLogger() {
|
||||
Logger = logrus.New()
|
||||
|
||||
// Set log level based on environment
|
||||
logLevel := os.Getenv("LOG_LEVEL")
|
||||
if logLevel == "" {
|
||||
logLevel = "info"
|
||||
}
|
||||
|
||||
level, err := logrus.ParseLevel(logLevel)
|
||||
if err != nil {
|
||||
level = logrus.InfoLevel
|
||||
}
|
||||
|
||||
Logger.SetLevel(level)
|
||||
|
||||
// Set formatter
|
||||
Logger.SetFormatter(&logrus.JSONFormatter{
|
||||
TimestampFormat: time.RFC3339,
|
||||
})
|
||||
|
||||
// Set output to stdout by default
|
||||
Logger.SetOutput(os.Stdout)
|
||||
}
|
||||
|
||||
// GetLogger returns the logger instance
|
||||
func GetLogger() *logrus.Logger {
|
||||
if Logger == nil {
|
||||
InitLogger()
|
||||
}
|
||||
return Logger
|
||||
}
|
||||
|
||||
// GinLogger returns a gin middleware for logging
|
||||
func GinLogger() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
// Start timer
|
||||
start := time.Now()
|
||||
path := c.Request.URL.Path
|
||||
raw := c.Request.URL.RawQuery
|
||||
|
||||
// Process request
|
||||
c.Next()
|
||||
|
||||
// Stop timer
|
||||
end := time.Now()
|
||||
latency := end.Sub(start)
|
||||
|
||||
// Get client IP
|
||||
clientIP := c.ClientIP()
|
||||
|
||||
// Get method
|
||||
method := c.Request.Method
|
||||
|
||||
// Get status code
|
||||
statusCode := c.Writer.Status()
|
||||
|
||||
// Get error message if any
|
||||
var errorMessage string
|
||||
if len(c.Errors) > 0 {
|
||||
errorMessage = c.Errors.String()
|
||||
}
|
||||
|
||||
// Get body size
|
||||
bodySize := c.Writer.Size()
|
||||
|
||||
// Get request ID if available
|
||||
requestID := c.GetHeader("X-Request-ID")
|
||||
if requestID == "" {
|
||||
requestID = c.GetString("requestID")
|
||||
}
|
||||
|
||||
// Get user ID if available
|
||||
userID := c.GetString("userID")
|
||||
|
||||
// Log the request
|
||||
entry := GetLogger().WithFields(logrus.Fields{
|
||||
"request_id": requestID,
|
||||
"user_id": userID,
|
||||
"client_ip": clientIP,
|
||||
"method": method,
|
||||
"path": path,
|
||||
"query": raw,
|
||||
"status_code": statusCode,
|
||||
"body_size": bodySize,
|
||||
"latency": latency,
|
||||
"latency_human": latency.String(),
|
||||
"error_message": errorMessage,
|
||||
})
|
||||
|
||||
if statusCode >= 500 {
|
||||
entry.Error()
|
||||
} else if statusCode >= 400 {
|
||||
entry.Warn()
|
||||
} else {
|
||||
entry.Info()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// WithRequestID adds a request ID to the logger
|
||||
func WithRequestID(requestID string) *logrus.Entry {
|
||||
return GetLogger().WithField("request_id", requestID)
|
||||
}
|
||||
|
||||
// WithUserID adds a user ID to the logger
|
||||
func WithUserID(userID string) *logrus.Entry {
|
||||
return GetLogger().WithField("user_id", userID)
|
||||
}
|
||||
|
||||
// WithError adds an error to the logger
|
||||
func WithError(err error) *logrus.Entry {
|
||||
return GetLogger().WithError(err)
|
||||
}
|
||||
|
||||
// WithField adds a custom field to the logger
|
||||
func WithField(key string, value interface{}) *logrus.Entry {
|
||||
return GetLogger().WithField(key, value)
|
||||
}
|
||||
|
||||
// WithFields adds multiple custom fields to the logger
|
||||
func WithFields(fields logrus.Fields) *logrus.Entry {
|
||||
return GetLogger().WithFields(fields)
|
||||
}
|
||||
|
||||
// Info logs an info message
|
||||
func Info(args ...interface{}) {
|
||||
GetLogger().Info(args...)
|
||||
}
|
||||
|
||||
// Infof logs a formatted info message
|
||||
func Infof(format string, args ...interface{}) {
|
||||
GetLogger().Infof(format, args...)
|
||||
}
|
||||
|
||||
// Warn logs a warning message
|
||||
func Warn(args ...interface{}) {
|
||||
GetLogger().Warn(args...)
|
||||
}
|
||||
|
||||
// Warnf logs a formatted warning message
|
||||
func Warnf(format string, args ...interface{}) {
|
||||
GetLogger().Warnf(format, args...)
|
||||
}
|
||||
|
||||
// Error logs an error message
|
||||
func Error(args ...interface{}) {
|
||||
GetLogger().Error(args...)
|
||||
}
|
||||
|
||||
// Errorf logs a formatted error message
|
||||
func Errorf(format string, args ...interface{}) {
|
||||
GetLogger().Errorf(format, args...)
|
||||
}
|
||||
|
||||
// Fatal logs a fatal message and exits
|
||||
func Fatal(args ...interface{}) {
|
||||
GetLogger().Fatal(args...)
|
||||
}
|
||||
|
||||
// Fatalf logs a formatted fatal message and exits
|
||||
func Fatalf(format string, args ...interface{}) {
|
||||
GetLogger().Fatalf(format, args...)
|
||||
}
|
||||
|
||||
// Debug logs a debug message
|
||||
func Debug(args ...interface{}) {
|
||||
GetLogger().Debug(args...)
|
||||
}
|
||||
|
||||
// Debugf logs a formatted debug message
|
||||
func Debugf(format string, args ...interface{}) {
|
||||
GetLogger().Debugf(format, args...)
|
||||
}
|
||||
|
||||
// Panic logs a panic message and panics
|
||||
func Panic(args ...interface{}) {
|
||||
GetLogger().Panic(args...)
|
||||
}
|
||||
|
||||
// Panicf logs a formatted panic message and panics
|
||||
func Panicf(format string, args ...interface{}) {
|
||||
GetLogger().Panicf(format, args...)
|
||||
}
|
||||
|
||||
// LogDatabaseOperation logs a database operation
|
||||
func LogDatabaseOperation(operation string, table string, duration time.Duration, err error) {
|
||||
entry := GetLogger().WithFields(logrus.Fields{
|
||||
"operation": operation,
|
||||
"table": table,
|
||||
"duration": duration,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
entry.WithError(err).Error("Database operation failed")
|
||||
} else {
|
||||
entry.Info("Database operation completed")
|
||||
}
|
||||
}
|
||||
|
||||
// LogAIInteraction logs an AI interaction
|
||||
func LogAIInteraction(model string, promptLength int, responseLength int, duration time.Duration, success bool, err error) {
|
||||
entry := GetLogger().WithFields(logrus.Fields{
|
||||
"ai_model": model,
|
||||
"prompt_length": promptLength,
|
||||
"response_length": responseLength,
|
||||
"duration": duration,
|
||||
"success": success,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
entry.WithError(err).Error("AI interaction failed")
|
||||
} else {
|
||||
entry.Info("AI interaction completed")
|
||||
}
|
||||
}
|
||||
|
||||
// LogWebSocketEvent logs a WebSocket event
|
||||
func LogWebSocketEvent(event string, clientID string, roomID string, err error) {
|
||||
entry := GetLogger().WithFields(logrus.Fields{
|
||||
"event": event,
|
||||
"client_id": clientID,
|
||||
"room_id": roomID,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
entry.WithError(err).Error("WebSocket event failed")
|
||||
} else {
|
||||
entry.Info("WebSocket event completed")
|
||||
}
|
||||
}
|
||||
|
||||
// LogAuthEvent logs an authentication event
|
||||
func LogAuthEvent(event string, userID string, clientIP string, success bool, err error) {
|
||||
entry := GetLogger().WithFields(logrus.Fields{
|
||||
"event": event,
|
||||
"user_id": userID,
|
||||
"client_ip": clientIP,
|
||||
"success": success,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
entry.WithError(err).Error("Authentication event failed")
|
||||
} else {
|
||||
entry.Info("Authentication event completed")
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user