tracks/internal/config/config.go
Claude f0eb4bdef5
Rewrite Tracks application in Golang
This commit introduces a complete rewrite of the Tracks GTD application
in Go (Golang), providing a modern, performant alternative to the Ruby
on Rails implementation.

## Architecture & Technology Stack

- Language: Go 1.21+
- Web Framework: Gin
- ORM: GORM with SQLite/MySQL/PostgreSQL support
- Authentication: JWT with bcrypt password hashing
- Clean Architecture: Separated models, services, handlers, and middleware

## Implemented Features

### Core Models
- User: Authentication and user management
- Context: GTD contexts (@home, @work, etc.)
- Project: Project grouping and tracking
- Todo: Task management with state machine (active, completed, deferred, pending)
- Tag: Flexible tagging system with polymorphic associations
- Dependency: Todo dependencies with circular dependency detection
- Preference: User preferences and settings
- Note: Project notes
- Attachment: File attachment support (model only)
- RecurringTodo: Recurring task template (model only)

### API Endpoints

**Authentication:**
- POST /api/auth/login - User login
- POST /api/auth/register - User registration
- POST /api/auth/logout - User logout
- GET /api/me - Get current user

**Todos:**
- GET /api/todos - List todos with filtering
- POST /api/todos - Create todo
- GET /api/todos/:id - Get todo details
- PUT /api/todos/:id - Update todo
- DELETE /api/todos/:id - Delete todo
- POST /api/todos/:id/complete - Mark as completed
- POST /api/todos/:id/activate - Mark as active
- POST /api/todos/:id/defer - Defer to future date
- POST /api/todos/:id/dependencies - Add dependency
- DELETE /api/todos/:id/dependencies/:successor_id - Remove dependency

**Projects:**
- GET /api/projects - List projects
- POST /api/projects - Create project
- GET /api/projects/:id - Get project details
- PUT /api/projects/:id - Update project
- DELETE /api/projects/:id - Delete project
- POST /api/projects/:id/complete - Complete project
- POST /api/projects/:id/activate - Activate project
- POST /api/projects/:id/hide - Hide project
- POST /api/projects/:id/review - Mark as reviewed
- GET /api/projects/:id/stats - Get project statistics

**Contexts:**
- GET /api/contexts - List contexts
- POST /api/contexts - Create context
- GET /api/contexts/:id - Get context details
- PUT /api/contexts/:id - Update context
- DELETE /api/contexts/:id - Delete context
- POST /api/contexts/:id/hide - Hide context
- POST /api/contexts/:id/activate - Activate context
- POST /api/contexts/:id/close - Close context
- GET /api/contexts/:id/stats - Get context statistics

### Business Logic

**Todo State Management:**
- Active: Ready to work on
- Completed: Finished tasks
- Deferred: Future actions (show_from date)
- Pending: Blocked by dependencies

**Dependency Management:**
- Create blocking relationships between todos
- Automatic state transitions when blocking todos complete
- Circular dependency detection
- Automatic unblocking when prerequisites complete

**Tag System:**
- Polymorphic tagging for todos and recurring todos
- Automatic tag creation on first use
- Tag cloud support

**Project & Context Tracking:**
- State management (active, hidden, closed/completed)
- Statistics and health indicators
- Review tracking for projects

### Infrastructure

**Configuration:**
- Environment-based configuration
- Support for SQLite, MySQL, and PostgreSQL
- Configurable JWT secrets and token expiry
- Flexible server settings

**Database:**
- GORM for ORM
- Automatic migrations
- Connection pooling
- Multi-database support

**Authentication & Security:**
- JWT-based authentication
- Bcrypt password hashing
- Secure cookie support
- Token refresh mechanism

**Docker Support:**
- Multi-stage Dockerfile for optimized builds
- Docker Compose with PostgreSQL
- Volume mounting for data persistence
- Production-ready configuration

## Project Structure

```
cmd/tracks/              # Application entry point
internal/
  config/               # Configuration management
  database/             # Database setup and migrations
  handlers/             # HTTP request handlers
  middleware/           # Authentication middleware
  models/              # Database models
  services/            # Business logic layer
```

## Documentation

- README_GOLANG.md: Comprehensive documentation
- .env.example: Configuration template
- API documentation included in README
- Code comments for complex logic

## Future Work

The following features from the original Rails app are not yet implemented:
- Recurring todo instantiation logic
- Email integration (Mailgun/CloudMailin)
- Advanced statistics and analytics
- Import/Export functionality (CSV, YAML, XML)
- File upload handling for attachments
- Mobile views
- RSS/Atom feeds
- iCalendar export

## Benefits Over Rails Version

- Performance: Compiled binary, lower resource usage
- Deployment: Single binary, no runtime dependencies
- Type Safety: Compile-time type checking
- Concurrency: Better handling of concurrent requests
- Memory: Lower memory footprint
- Portability: Easy cross-platform compilation

## Testing

The code structure supports testing, though tests are not yet implemented.
Future work includes adding unit and integration tests.
2025-11-05 10:46:59 +00:00

143 lines
3.5 KiB
Go

package config
import (
"fmt"
"os"
"strconv"
"github.com/joho/godotenv"
)
// Config holds the application configuration
type Config struct {
Server ServerConfig
Database DatabaseConfig
Auth AuthConfig
App AppConfig
}
// ServerConfig holds server-related configuration
type ServerConfig struct {
Host string
Port int
Mode string // debug, release, test
}
// DatabaseConfig holds database-related configuration
type DatabaseConfig struct {
Driver string // sqlite, mysql, postgres
Host string
Port int
Name string
User string
Password string
SSLMode string
}
// AuthConfig holds authentication-related configuration
type AuthConfig struct {
JWTSecret string
TokenExpiry int // in hours
SecureCookies bool
}
// AppConfig holds application-specific configuration
type AppConfig struct {
Name string
TimeZone string
OpenSignups bool
AdminEmail string
SecretToken string
ForceSSL bool
UploadPath string
MaxUploadSizeMB int64
}
// Load reads configuration from environment variables
func Load() (*Config, error) {
// Try to load .env file if it exists
_ = godotenv.Load()
cfg := &Config{
Server: ServerConfig{
Host: getEnv("SERVER_HOST", "0.0.0.0"),
Port: getEnvAsInt("SERVER_PORT", 3000),
Mode: getEnv("GIN_MODE", "debug"),
},
Database: DatabaseConfig{
Driver: getEnv("DB_DRIVER", "sqlite"),
Host: getEnv("DB_HOST", "localhost"),
Port: getEnvAsInt("DB_PORT", 5432),
Name: getEnv("DB_NAME", "tracks.db"),
User: getEnv("DB_USER", ""),
Password: getEnv("DB_PASSWORD", ""),
SSLMode: getEnv("DB_SSLMODE", "disable"),
},
Auth: AuthConfig{
JWTSecret: getEnv("JWT_SECRET", "change-me-in-production"),
TokenExpiry: getEnvAsInt("TOKEN_EXPIRY_HOURS", 24),
SecureCookies: getEnvAsBool("SECURE_COOKIES", false),
},
App: AppConfig{
Name: getEnv("APP_NAME", "Tracks"),
TimeZone: getEnv("TZ", "UTC"),
OpenSignups: getEnvAsBool("OPEN_SIGNUPS", false),
AdminEmail: getEnv("ADMIN_EMAIL", ""),
SecretToken: getEnv("SECRET_TOKEN", "change-me-in-production"),
ForceSSL: getEnvAsBool("FORCE_SSL", false),
UploadPath: getEnv("UPLOAD_PATH", "./uploads"),
MaxUploadSizeMB: getEnvAsInt64("MAX_UPLOAD_SIZE_MB", 10),
},
}
return cfg, nil
}
// GetDSN returns the database connection string
func (c *DatabaseConfig) GetDSN() string {
switch c.Driver {
case "sqlite":
return c.Name
case "mysql":
return fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8mb4&parseTime=True&loc=Local",
c.User, c.Password, c.Host, c.Port, c.Name)
case "postgres":
return fmt.Sprintf("host=%s port=%d user=%s password=%s dbname=%s sslmode=%s",
c.Host, c.Port, c.User, c.Password, c.Name, c.SSLMode)
default:
return ""
}
}
// Helper functions
func getEnv(key, defaultValue string) string {
if value := os.Getenv(key); value != "" {
return value
}
return defaultValue
}
func getEnvAsInt(key string, defaultValue int) int {
valueStr := getEnv(key, "")
if value, err := strconv.Atoi(valueStr); err == nil {
return value
}
return defaultValue
}
func getEnvAsInt64(key string, defaultValue int64) int64 {
valueStr := getEnv(key, "")
if value, err := strconv.ParseInt(valueStr, 10, 64); err == nil {
return value
}
return defaultValue
}
func getEnvAsBool(key string, defaultValue bool) bool {
valueStr := getEnv(key, "")
if value, err := strconv.ParseBool(valueStr); err == nil {
return value
}
return defaultValue
}