Initial commit: Custom Start Page application with authentication and DynamoDB storage
This commit is contained in:
114
pkg/config/config.go
Normal file
114
pkg/config/config.go
Normal file
@@ -0,0 +1,114 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// Config holds all application configuration
|
||||
type Config struct {
|
||||
Server ServerConfig
|
||||
Database DatabaseConfig
|
||||
OAuth OAuthConfig
|
||||
Session SessionConfig
|
||||
}
|
||||
|
||||
// ServerConfig holds server-related configuration
|
||||
type ServerConfig struct {
|
||||
Port string
|
||||
Host string
|
||||
}
|
||||
|
||||
// DatabaseConfig holds DynamoDB configuration
|
||||
type DatabaseConfig struct {
|
||||
Region string
|
||||
Endpoint string // For DynamoDB local
|
||||
TablePrefix string
|
||||
UseLocalDB bool
|
||||
}
|
||||
|
||||
// OAuthConfig holds OAuth provider configurations
|
||||
type OAuthConfig struct {
|
||||
Google GoogleOAuthConfig
|
||||
}
|
||||
|
||||
// GoogleOAuthConfig holds Google OAuth configuration
|
||||
type GoogleOAuthConfig struct {
|
||||
ClientID string
|
||||
ClientSecret string
|
||||
RedirectURL string
|
||||
}
|
||||
|
||||
// SessionConfig holds session management configuration
|
||||
type SessionConfig struct {
|
||||
SecretKey string
|
||||
MaxAge int // in seconds
|
||||
}
|
||||
|
||||
// Load loads configuration from environment variables
|
||||
func Load() (*Config, error) {
|
||||
cfg := &Config{
|
||||
Server: ServerConfig{
|
||||
Port: getEnv("PORT", "8080"),
|
||||
Host: getEnv("HOST", "localhost"),
|
||||
},
|
||||
Database: DatabaseConfig{
|
||||
Region: getEnv("AWS_REGION", "us-east-1"),
|
||||
Endpoint: getEnv("DYNAMODB_ENDPOINT", "http://localhost:8000"),
|
||||
TablePrefix: getEnv("TABLE_PREFIX", "startpage_"),
|
||||
UseLocalDB: getEnvBool("USE_LOCAL_DB", true),
|
||||
},
|
||||
OAuth: OAuthConfig{
|
||||
Google: GoogleOAuthConfig{
|
||||
ClientID: getEnv("GOOGLE_CLIENT_ID", ""),
|
||||
ClientSecret: getEnv("GOOGLE_CLIENT_SECRET", ""),
|
||||
RedirectURL: getEnv("GOOGLE_REDIRECT_URL", "http://localhost:8080/auth/callback/google"),
|
||||
},
|
||||
},
|
||||
Session: SessionConfig{
|
||||
SecretKey: getEnv("SESSION_SECRET", "change-me-in-production"),
|
||||
MaxAge: getEnvInt("SESSION_MAX_AGE", 86400*7), // 7 days default
|
||||
},
|
||||
}
|
||||
|
||||
// Validate required fields
|
||||
if cfg.OAuth.Google.ClientID == "" {
|
||||
return nil, fmt.Errorf("GOOGLE_CLIENT_ID is required")
|
||||
}
|
||||
if cfg.OAuth.Google.ClientSecret == "" {
|
||||
return nil, fmt.Errorf("GOOGLE_CLIENT_SECRET is required")
|
||||
}
|
||||
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
// getEnv gets an environment variable or returns a default value
|
||||
func getEnv(key, defaultValue string) string {
|
||||
if value := os.Getenv(key); value != "" {
|
||||
return value
|
||||
}
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
// getEnvBool gets a boolean environment variable or returns a default value
|
||||
func getEnvBool(key string, defaultValue bool) bool {
|
||||
if value := os.Getenv(key); value != "" {
|
||||
boolVal, err := strconv.ParseBool(value)
|
||||
if err == nil {
|
||||
return boolVal
|
||||
}
|
||||
}
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
// getEnvInt gets an integer environment variable or returns a default value
|
||||
func getEnvInt(key string, defaultValue int) int {
|
||||
if value := os.Getenv(key); value != "" {
|
||||
intVal, err := strconv.Atoi(value)
|
||||
if err == nil {
|
||||
return intVal
|
||||
}
|
||||
}
|
||||
return defaultValue
|
||||
}
|
||||
191
pkg/config/config_test.go
Normal file
191
pkg/config/config_test.go
Normal file
@@ -0,0 +1,191 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestLoad(t *testing.T) {
|
||||
// Set required environment variables
|
||||
os.Setenv("GOOGLE_CLIENT_ID", "test-client-id")
|
||||
os.Setenv("GOOGLE_CLIENT_SECRET", "test-client-secret")
|
||||
defer func() {
|
||||
os.Unsetenv("GOOGLE_CLIENT_ID")
|
||||
os.Unsetenv("GOOGLE_CLIENT_SECRET")
|
||||
}()
|
||||
|
||||
cfg, err := Load()
|
||||
if err != nil {
|
||||
t.Fatalf("Load() failed: %v", err)
|
||||
}
|
||||
|
||||
// Test default values
|
||||
if cfg.Server.Port != "8080" {
|
||||
t.Errorf("Expected default port 8080, got %s", cfg.Server.Port)
|
||||
}
|
||||
|
||||
if cfg.Server.Host != "localhost" {
|
||||
t.Errorf("Expected default host localhost, got %s", cfg.Server.Host)
|
||||
}
|
||||
|
||||
if cfg.Database.Region != "us-east-1" {
|
||||
t.Errorf("Expected default region us-east-1, got %s", cfg.Database.Region)
|
||||
}
|
||||
|
||||
if cfg.OAuth.Google.ClientID != "test-client-id" {
|
||||
t.Errorf("Expected client ID test-client-id, got %s", cfg.OAuth.Google.ClientID)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadMissingOAuthCredentials(t *testing.T) {
|
||||
// Ensure OAuth credentials are not set
|
||||
os.Unsetenv("GOOGLE_CLIENT_ID")
|
||||
os.Unsetenv("GOOGLE_CLIENT_SECRET")
|
||||
|
||||
_, err := Load()
|
||||
if err == nil {
|
||||
t.Error("Expected error when OAuth credentials are missing, got nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetEnv(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
key string
|
||||
defaultValue string
|
||||
envValue string
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
name: "returns environment variable when set",
|
||||
key: "TEST_KEY",
|
||||
defaultValue: "default",
|
||||
envValue: "custom",
|
||||
expected: "custom",
|
||||
},
|
||||
{
|
||||
name: "returns default when environment variable not set",
|
||||
key: "TEST_KEY_NOT_SET",
|
||||
defaultValue: "default",
|
||||
envValue: "",
|
||||
expected: "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)
|
||||
}
|
||||
|
||||
result := getEnv(tt.key, tt.defaultValue)
|
||||
if result != tt.expected {
|
||||
t.Errorf("Expected %s, got %s", tt.expected, result)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetEnvBool(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
key string
|
||||
defaultValue bool
|
||||
envValue string
|
||||
expected bool
|
||||
}{
|
||||
{
|
||||
name: "returns true when set to true",
|
||||
key: "TEST_BOOL",
|
||||
defaultValue: false,
|
||||
envValue: "true",
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "returns false when set to false",
|
||||
key: "TEST_BOOL",
|
||||
defaultValue: true,
|
||||
envValue: "false",
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "returns default when not set",
|
||||
key: "TEST_BOOL_NOT_SET",
|
||||
defaultValue: true,
|
||||
envValue: "",
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "returns default when invalid value",
|
||||
key: "TEST_BOOL",
|
||||
defaultValue: true,
|
||||
envValue: "invalid",
|
||||
expected: 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)
|
||||
} else {
|
||||
os.Unsetenv(tt.key)
|
||||
}
|
||||
|
||||
result := getEnvBool(tt.key, tt.defaultValue)
|
||||
if result != tt.expected {
|
||||
t.Errorf("Expected %v, got %v", tt.expected, result)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetEnvInt(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
key string
|
||||
defaultValue int
|
||||
envValue string
|
||||
expected int
|
||||
}{
|
||||
{
|
||||
name: "returns integer when valid",
|
||||
key: "TEST_INT",
|
||||
defaultValue: 100,
|
||||
envValue: "200",
|
||||
expected: 200,
|
||||
},
|
||||
{
|
||||
name: "returns default when not set",
|
||||
key: "TEST_INT_NOT_SET",
|
||||
defaultValue: 100,
|
||||
envValue: "",
|
||||
expected: 100,
|
||||
},
|
||||
{
|
||||
name: "returns default when invalid value",
|
||||
key: "TEST_INT",
|
||||
defaultValue: 100,
|
||||
envValue: "invalid",
|
||||
expected: 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)
|
||||
} else {
|
||||
os.Unsetenv(tt.key)
|
||||
}
|
||||
|
||||
result := getEnvInt(tt.key, tt.defaultValue)
|
||||
if result != tt.expected {
|
||||
t.Errorf("Expected %d, got %d", tt.expected, result)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user