feat: Implement initial Go backend and React frontend project structure for the gotail application.
This commit is contained in:
@@ -0,0 +1,160 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Config contém todas as configurações da aplicação
|
||||
type Config struct {
|
||||
Server ServerConfig
|
||||
Auth AuthConfig
|
||||
Logging LoggingConfig
|
||||
Security SecurityConfig
|
||||
}
|
||||
|
||||
// ServerConfig contém configurações do servidor HTTP
|
||||
type ServerConfig struct {
|
||||
Port string
|
||||
ReadTimeout time.Duration
|
||||
WriteTimeout time.Duration
|
||||
ShutdownTimeout time.Duration
|
||||
TLSEnabled bool
|
||||
TLSCertFile string
|
||||
TLSKeyFile string
|
||||
}
|
||||
|
||||
// AuthConfig contém configurações de autenticação
|
||||
type AuthConfig struct {
|
||||
Enabled bool
|
||||
Username string
|
||||
Password string
|
||||
}
|
||||
|
||||
// LoggingConfig contém configurações de logging
|
||||
type LoggingConfig struct {
|
||||
Level string // debug, info, warn, error
|
||||
Format string // json ou console
|
||||
}
|
||||
|
||||
// SecurityConfig contém configurações de segurança
|
||||
type SecurityConfig struct {
|
||||
RateLimitEnabled bool
|
||||
RateLimitRPS int
|
||||
CORSOrigins []string
|
||||
}
|
||||
|
||||
// Load carrega a configuração de múltiplas fontes (flags, env vars)
|
||||
func Load() (*Config, error) {
|
||||
cfg := &Config{}
|
||||
|
||||
// Definir flags (mantém compatibilidade com versão anterior)
|
||||
flag.StringVar(&cfg.Server.Port, "port", getEnv("PORT", ":8080"), "Porta para o servidor web (ex: :8080, 9090)")
|
||||
flag.StringVar(&cfg.Auth.Username, "username", getEnv("USERNAME", "admin"), "Nome de usuário para autenticação")
|
||||
flag.StringVar(&cfg.Auth.Password, "password", getEnv("PASSWORD", ""), "Senha para autenticação")
|
||||
|
||||
flag.Parse()
|
||||
|
||||
// Carregar configurações de variáveis de ambiente
|
||||
cfg.Logging.Level = getEnv("LOG_LEVEL", "info")
|
||||
cfg.Logging.Format = getEnv("LOG_FORMAT", "console")
|
||||
|
||||
cfg.Security.RateLimitEnabled = getEnvBool("RATE_LIMIT_ENABLED", true)
|
||||
cfg.Security.RateLimitRPS = getEnvInt("RATE_LIMIT_RPS", 100)
|
||||
|
||||
corsOrigins := getEnv("CORS_ORIGINS", "")
|
||||
if corsOrigins != "" {
|
||||
cfg.Security.CORSOrigins = strings.Split(corsOrigins, ",")
|
||||
} else {
|
||||
cfg.Security.CORSOrigins = []string{"*"}
|
||||
}
|
||||
|
||||
cfg.Server.ReadTimeout = getEnvDuration("READ_TIMEOUT", 15*time.Second)
|
||||
cfg.Server.WriteTimeout = getEnvDuration("WRITE_TIMEOUT", 15*time.Second)
|
||||
cfg.Server.ShutdownTimeout = getEnvDuration("SHUTDOWN_TIMEOUT", 30*time.Second)
|
||||
|
||||
cfg.Server.TLSEnabled = getEnvBool("TLS_ENABLED", false)
|
||||
cfg.Server.TLSCertFile = getEnv("TLS_CERT_FILE", "")
|
||||
cfg.Server.TLSKeyFile = getEnv("TLS_KEY_FILE", "")
|
||||
|
||||
// Autenticação está habilitada se houver senha
|
||||
cfg.Auth.Enabled = cfg.Auth.Password != ""
|
||||
|
||||
// Validar configuração
|
||||
if err := cfg.Validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
// Validate valida a configuração
|
||||
func (c *Config) Validate() error {
|
||||
// Validar porta
|
||||
if c.Server.Port == "" {
|
||||
return fmt.Errorf("porta do servidor não pode ser vazia")
|
||||
}
|
||||
|
||||
// Validar TLS
|
||||
if c.Server.TLSEnabled {
|
||||
if c.Server.TLSCertFile == "" || c.Server.TLSKeyFile == "" {
|
||||
return fmt.Errorf("TLS habilitado mas certificado ou chave não especificados")
|
||||
}
|
||||
}
|
||||
|
||||
// Validar log level
|
||||
validLevels := map[string]bool{"debug": true, "info": true, "warn": true, "error": true}
|
||||
if !validLevels[c.Logging.Level] {
|
||||
return fmt.Errorf("nível de log inválido: %s (use: debug, info, warn, error)", c.Logging.Level)
|
||||
}
|
||||
|
||||
// Validar log format
|
||||
if c.Logging.Format != "json" && c.Logging.Format != "console" {
|
||||
return fmt.Errorf("formato de log inválido: %s (use: json, console)", c.Logging.Format)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Funções auxiliares para ler variáveis de ambiente
|
||||
|
||||
func getEnv(key, defaultValue string) string {
|
||||
if value := os.Getenv(key); value != "" {
|
||||
return value
|
||||
}
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
func getEnvBool(key string, defaultValue bool) bool {
|
||||
if value := os.Getenv(key); value != "" {
|
||||
b, err := strconv.ParseBool(value)
|
||||
if err == nil {
|
||||
return b
|
||||
}
|
||||
}
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
func getEnvInt(key string, defaultValue int) int {
|
||||
if value := os.Getenv(key); value != "" {
|
||||
i, err := strconv.Atoi(value)
|
||||
if err == nil {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
func getEnvDuration(key string, defaultValue time.Duration) time.Duration {
|
||||
if value := os.Getenv(key); value != "" {
|
||||
d, err := time.ParseDuration(value)
|
||||
if err == nil {
|
||||
return d
|
||||
}
|
||||
}
|
||||
return defaultValue
|
||||
}
|
||||
@@ -0,0 +1,300 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestConfig_Validate(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
config *Config
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "Valid config",
|
||||
config: &Config{
|
||||
Server: ServerConfig{
|
||||
Port: ":8080",
|
||||
},
|
||||
Logging: LoggingConfig{
|
||||
Level: "info",
|
||||
Format: "console",
|
||||
},
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "Empty port",
|
||||
config: &Config{
|
||||
Server: ServerConfig{
|
||||
Port: "",
|
||||
},
|
||||
Logging: LoggingConfig{
|
||||
Level: "info",
|
||||
Format: "console",
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "Invalid log level",
|
||||
config: &Config{
|
||||
Server: ServerConfig{
|
||||
Port: ":8080",
|
||||
},
|
||||
Logging: LoggingConfig{
|
||||
Level: "invalid",
|
||||
Format: "console",
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "Invalid log format",
|
||||
config: &Config{
|
||||
Server: ServerConfig{
|
||||
Port: ":8080",
|
||||
},
|
||||
Logging: LoggingConfig{
|
||||
Level: "info",
|
||||
Format: "invalid",
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "TLS enabled without cert",
|
||||
config: &Config{
|
||||
Server: ServerConfig{
|
||||
Port: ":8080",
|
||||
TLSEnabled: true,
|
||||
TLSCertFile: "",
|
||||
TLSKeyFile: "",
|
||||
},
|
||||
Logging: LoggingConfig{
|
||||
Level: "info",
|
||||
Format: "console",
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "TLS enabled with cert and key",
|
||||
config: &Config{
|
||||
Server: ServerConfig{
|
||||
Port: ":8080",
|
||||
TLSEnabled: true,
|
||||
TLSCertFile: "/path/to/cert.pem",
|
||||
TLSKeyFile: "/path/to/key.pem",
|
||||
},
|
||||
Logging: LoggingConfig{
|
||||
Level: "info",
|
||||
Format: "console",
|
||||
},
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
err := tt.config.Validate()
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("Validate() error = %v, wantErr %v", err, tt.wantErr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetEnv(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
key string
|
||||
defaultValue string
|
||||
envValue string
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "Environment variable set",
|
||||
key: "TEST_VAR",
|
||||
defaultValue: "default",
|
||||
envValue: "custom",
|
||||
want: "custom",
|
||||
},
|
||||
{
|
||||
name: "Environment variable not set",
|
||||
key: "UNSET_VAR",
|
||||
defaultValue: "default",
|
||||
envValue: "",
|
||||
want: "default",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if tt.envValue != "" {
|
||||
os.Setenv(tt.key, tt.envValue)
|
||||
defer os.Unsetenv(tt.key)
|
||||
}
|
||||
|
||||
got := getEnv(tt.key, tt.defaultValue)
|
||||
if got != tt.want {
|
||||
t.Errorf("getEnv() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetEnvBool(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
key string
|
||||
defaultValue bool
|
||||
envValue string
|
||||
want bool
|
||||
}{
|
||||
{
|
||||
name: "true value",
|
||||
key: "TEST_BOOL",
|
||||
defaultValue: false,
|
||||
envValue: "true",
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "false value",
|
||||
key: "TEST_BOOL",
|
||||
defaultValue: true,
|
||||
envValue: "false",
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "1 value",
|
||||
key: "TEST_BOOL",
|
||||
defaultValue: false,
|
||||
envValue: "1",
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "invalid value uses default",
|
||||
key: "TEST_BOOL",
|
||||
defaultValue: true,
|
||||
envValue: "invalid",
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "not set uses default",
|
||||
key: "UNSET_BOOL",
|
||||
defaultValue: true,
|
||||
envValue: "",
|
||||
want: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if tt.envValue != "" {
|
||||
os.Setenv(tt.key, tt.envValue)
|
||||
defer os.Unsetenv(tt.key)
|
||||
}
|
||||
|
||||
got := getEnvBool(tt.key, tt.defaultValue)
|
||||
if got != tt.want {
|
||||
t.Errorf("getEnvBool() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetEnvInt(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
key string
|
||||
defaultValue int
|
||||
envValue string
|
||||
want int
|
||||
}{
|
||||
{
|
||||
name: "valid integer",
|
||||
key: "TEST_INT",
|
||||
defaultValue: 10,
|
||||
envValue: "42",
|
||||
want: 42,
|
||||
},
|
||||
{
|
||||
name: "invalid integer uses default",
|
||||
key: "TEST_INT",
|
||||
defaultValue: 10,
|
||||
envValue: "invalid",
|
||||
want: 10,
|
||||
},
|
||||
{
|
||||
name: "not set uses default",
|
||||
key: "UNSET_INT",
|
||||
defaultValue: 100,
|
||||
envValue: "",
|
||||
want: 100,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if tt.envValue != "" {
|
||||
os.Setenv(tt.key, tt.envValue)
|
||||
defer os.Unsetenv(tt.key)
|
||||
}
|
||||
|
||||
got := getEnvInt(tt.key, tt.defaultValue)
|
||||
if got != tt.want {
|
||||
t.Errorf("getEnvInt() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetEnvDuration(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
key string
|
||||
defaultValue time.Duration
|
||||
envValue string
|
||||
want time.Duration
|
||||
}{
|
||||
{
|
||||
name: "valid duration",
|
||||
key: "TEST_DURATION",
|
||||
defaultValue: 10 * time.Second,
|
||||
envValue: "30s",
|
||||
want: 30 * time.Second,
|
||||
},
|
||||
{
|
||||
name: "invalid duration uses default",
|
||||
key: "TEST_DURATION",
|
||||
defaultValue: 10 * time.Second,
|
||||
envValue: "invalid",
|
||||
want: 10 * time.Second,
|
||||
},
|
||||
{
|
||||
name: "not set uses default",
|
||||
key: "UNSET_DURATION",
|
||||
defaultValue: 1 * time.Minute,
|
||||
envValue: "",
|
||||
want: 1 * time.Minute,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if tt.envValue != "" {
|
||||
os.Setenv(tt.key, tt.envValue)
|
||||
defer os.Unsetenv(tt.key)
|
||||
}
|
||||
|
||||
got := getEnvDuration(tt.key, tt.defaultValue)
|
||||
if got != tt.want {
|
||||
t.Errorf("getEnvDuration() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user