feat: Implement initial Go backend and React frontend project structure for the gotail application.

This commit is contained in:
Luiz Costa
2025-11-19 17:15:29 -03:00
parent dfe9b10bb8
commit ef1784f8d4
189 changed files with 25220 additions and 279 deletions
+160
View File
@@ -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
}
+300
View File
@@ -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)
}
})
}
}