Initial commit: MyCrib API in Go
Complete rewrite of Django REST API to Go with: - Gin web framework for HTTP routing - GORM for database operations - GoAdmin for admin panel - Gorush integration for push notifications - Redis for caching and job queues Features implemented: - User authentication (login, register, logout, password reset) - Residence management (CRUD, sharing, share codes) - Task management (CRUD, kanban board, completions) - Contractor management (CRUD, specialties) - Document management (CRUD, warranties) - Notifications (preferences, push notifications) - Subscription management (tiers, limits) Infrastructure: - Docker Compose for local development - Database migrations and seed data - Admin panel for data management 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
241
internal/config/config.go
Normal file
241
internal/config/config.go
Normal file
@@ -0,0 +1,241 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
// Config holds all configuration for the application
|
||||
type Config struct {
|
||||
Server ServerConfig
|
||||
Database DatabaseConfig
|
||||
Redis RedisConfig
|
||||
Email EmailConfig
|
||||
Push PushConfig
|
||||
Worker WorkerConfig
|
||||
Security SecurityConfig
|
||||
}
|
||||
|
||||
type ServerConfig struct {
|
||||
Port int
|
||||
Debug bool
|
||||
AllowedHosts []string
|
||||
Timezone string
|
||||
}
|
||||
|
||||
type DatabaseConfig struct {
|
||||
Host string
|
||||
Port int
|
||||
User string
|
||||
Password string
|
||||
Database string
|
||||
SSLMode string
|
||||
MaxOpenConns int
|
||||
MaxIdleConns int
|
||||
MaxLifetime time.Duration
|
||||
}
|
||||
|
||||
type RedisConfig struct {
|
||||
URL string
|
||||
Password string
|
||||
DB int
|
||||
}
|
||||
|
||||
type EmailConfig struct {
|
||||
Host string
|
||||
Port int
|
||||
User string
|
||||
Password string
|
||||
From string
|
||||
UseTLS bool
|
||||
}
|
||||
|
||||
type PushConfig struct {
|
||||
// Gorush server URL
|
||||
GorushURL string
|
||||
|
||||
// APNs (iOS)
|
||||
APNSKeyPath string
|
||||
APNSKeyID string
|
||||
APNSTeamID string
|
||||
APNSTopic string
|
||||
APNSSandbox bool
|
||||
|
||||
// FCM (Android)
|
||||
FCMServerKey string
|
||||
}
|
||||
|
||||
type WorkerConfig struct {
|
||||
// Scheduled job times (UTC)
|
||||
TaskReminderHour int
|
||||
TaskReminderMinute int
|
||||
OverdueReminderHour int
|
||||
DailyNotifHour int
|
||||
}
|
||||
|
||||
type SecurityConfig struct {
|
||||
SecretKey string
|
||||
TokenCacheTTL time.Duration
|
||||
PasswordResetExpiry time.Duration
|
||||
ConfirmationExpiry time.Duration
|
||||
MaxPasswordResetRate int // per hour
|
||||
}
|
||||
|
||||
var cfg *Config
|
||||
|
||||
// Load reads configuration from environment variables
|
||||
func Load() (*Config, error) {
|
||||
viper.SetEnvPrefix("")
|
||||
viper.AutomaticEnv()
|
||||
viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
|
||||
|
||||
// Set defaults
|
||||
setDefaults()
|
||||
|
||||
cfg = &Config{
|
||||
Server: ServerConfig{
|
||||
Port: viper.GetInt("PORT"),
|
||||
Debug: viper.GetBool("DEBUG"),
|
||||
AllowedHosts: strings.Split(viper.GetString("ALLOWED_HOSTS"), ","),
|
||||
Timezone: viper.GetString("TIMEZONE"),
|
||||
},
|
||||
Database: DatabaseConfig{
|
||||
Host: viper.GetString("DB_HOST"),
|
||||
Port: viper.GetInt("DB_PORT"),
|
||||
User: viper.GetString("POSTGRES_USER"),
|
||||
Password: viper.GetString("POSTGRES_PASSWORD"),
|
||||
Database: viper.GetString("POSTGRES_DB"),
|
||||
SSLMode: viper.GetString("DB_SSLMODE"),
|
||||
MaxOpenConns: viper.GetInt("DB_MAX_OPEN_CONNS"),
|
||||
MaxIdleConns: viper.GetInt("DB_MAX_IDLE_CONNS"),
|
||||
MaxLifetime: viper.GetDuration("DB_MAX_LIFETIME"),
|
||||
},
|
||||
Redis: RedisConfig{
|
||||
URL: viper.GetString("REDIS_URL"),
|
||||
Password: viper.GetString("REDIS_PASSWORD"),
|
||||
DB: viper.GetInt("REDIS_DB"),
|
||||
},
|
||||
Email: EmailConfig{
|
||||
Host: viper.GetString("EMAIL_HOST"),
|
||||
Port: viper.GetInt("EMAIL_PORT"),
|
||||
User: viper.GetString("EMAIL_HOST_USER"),
|
||||
Password: viper.GetString("EMAIL_HOST_PASSWORD"),
|
||||
From: viper.GetString("DEFAULT_FROM_EMAIL"),
|
||||
UseTLS: viper.GetBool("EMAIL_USE_TLS"),
|
||||
},
|
||||
Push: PushConfig{
|
||||
GorushURL: viper.GetString("GORUSH_URL"),
|
||||
APNSKeyPath: viper.GetString("APNS_AUTH_KEY_PATH"),
|
||||
APNSKeyID: viper.GetString("APNS_AUTH_KEY_ID"),
|
||||
APNSTeamID: viper.GetString("APNS_TEAM_ID"),
|
||||
APNSTopic: viper.GetString("APNS_TOPIC"),
|
||||
APNSSandbox: viper.GetBool("APNS_USE_SANDBOX"),
|
||||
FCMServerKey: viper.GetString("FCM_SERVER_KEY"),
|
||||
},
|
||||
Worker: WorkerConfig{
|
||||
TaskReminderHour: viper.GetInt("CELERY_BEAT_REMINDER_HOUR"),
|
||||
TaskReminderMinute: viper.GetInt("CELERY_BEAT_REMINDER_MINUTE"),
|
||||
OverdueReminderHour: 9, // 9:00 AM UTC
|
||||
DailyNotifHour: 11, // 11:00 AM UTC
|
||||
},
|
||||
Security: SecurityConfig{
|
||||
SecretKey: viper.GetString("SECRET_KEY"),
|
||||
TokenCacheTTL: 5 * time.Minute,
|
||||
PasswordResetExpiry: 15 * time.Minute,
|
||||
ConfirmationExpiry: 24 * time.Hour,
|
||||
MaxPasswordResetRate: 3,
|
||||
},
|
||||
}
|
||||
|
||||
// Validate required fields
|
||||
if err := validate(cfg); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
// Get returns the current configuration
|
||||
func Get() *Config {
|
||||
return cfg
|
||||
}
|
||||
|
||||
func setDefaults() {
|
||||
// Server defaults
|
||||
viper.SetDefault("PORT", 8000)
|
||||
viper.SetDefault("DEBUG", false)
|
||||
viper.SetDefault("ALLOWED_HOSTS", "localhost,127.0.0.1")
|
||||
viper.SetDefault("TIMEZONE", "UTC")
|
||||
|
||||
// Database defaults
|
||||
viper.SetDefault("DB_HOST", "localhost")
|
||||
viper.SetDefault("DB_PORT", 5432)
|
||||
viper.SetDefault("POSTGRES_USER", "postgres")
|
||||
viper.SetDefault("POSTGRES_DB", "mycrib")
|
||||
viper.SetDefault("DB_SSLMODE", "disable")
|
||||
viper.SetDefault("DB_MAX_OPEN_CONNS", 25)
|
||||
viper.SetDefault("DB_MAX_IDLE_CONNS", 10)
|
||||
viper.SetDefault("DB_MAX_LIFETIME", 600*time.Second)
|
||||
|
||||
// Redis defaults
|
||||
viper.SetDefault("REDIS_URL", "redis://localhost:6379/0")
|
||||
viper.SetDefault("REDIS_DB", 0)
|
||||
|
||||
// Email defaults
|
||||
viper.SetDefault("EMAIL_HOST", "smtp.gmail.com")
|
||||
viper.SetDefault("EMAIL_PORT", 587)
|
||||
viper.SetDefault("EMAIL_USE_TLS", true)
|
||||
viper.SetDefault("DEFAULT_FROM_EMAIL", "MyCrib <noreply@mycrib.com>")
|
||||
|
||||
// Push notification defaults
|
||||
viper.SetDefault("GORUSH_URL", "http://localhost:8088")
|
||||
viper.SetDefault("APNS_TOPIC", "com.example.mycrib")
|
||||
viper.SetDefault("APNS_USE_SANDBOX", true)
|
||||
|
||||
// Worker defaults
|
||||
viper.SetDefault("CELERY_BEAT_REMINDER_HOUR", 20)
|
||||
viper.SetDefault("CELERY_BEAT_REMINDER_MINUTE", 0)
|
||||
}
|
||||
|
||||
func validate(cfg *Config) error {
|
||||
if cfg.Security.SecretKey == "" {
|
||||
// In development, use a default key
|
||||
if cfg.Server.Debug {
|
||||
cfg.Security.SecretKey = "development-secret-key-change-in-production"
|
||||
} else {
|
||||
return fmt.Errorf("SECRET_KEY is required in production")
|
||||
}
|
||||
}
|
||||
|
||||
if cfg.Database.Password == "" && !cfg.Server.Debug {
|
||||
return fmt.Errorf("POSTGRES_PASSWORD is required")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// DSN returns the database connection string
|
||||
func (d *DatabaseConfig) DSN() string {
|
||||
return fmt.Sprintf(
|
||||
"host=%s port=%d user=%s password=%s dbname=%s sslmode=%s",
|
||||
d.Host, d.Port, d.User, d.Password, d.Database, d.SSLMode,
|
||||
)
|
||||
}
|
||||
|
||||
// ReadAPNSKey reads the APNs key from file if path is provided
|
||||
func (p *PushConfig) ReadAPNSKey() (string, error) {
|
||||
if p.APNSKeyPath == "" {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
content, err := os.ReadFile(p.APNSKeyPath)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to read APNs key: %w", err)
|
||||
}
|
||||
|
||||
return string(content), nil
|
||||
}
|
||||
Reference in New Issue
Block a user