feat: implement Epic 0 - Project Setup & Foundation

Implemented all 5 stories from Epic 0:

Story 0.1: Project Initialization
- Initialize Go module with path git.dcentral.systems/toolz/goplt
- Create complete directory structure (cmd/, internal/, pkg/, modules/, config/, etc.)
- Add comprehensive .gitignore for Go projects
- Create README.md with project overview and setup instructions

Story 0.2: Configuration Management System
- Define ConfigProvider interface in pkg/config
- Implement Viper-based configuration in internal/config
- Create configuration loader with environment support
- Add default, development, and production YAML config files

Story 0.3: Structured Logging System
- Define Logger interface in pkg/logger
- Implement Zap-based logger in internal/logger
- Add request ID middleware for Gin
- Create global logger export with convenience functions
- Support context-aware logging with request/user ID extraction

Story 0.4: CI/CD Pipeline
- Create GitHub Actions workflow for CI (test, lint, build, fmt)
- Add comprehensive Makefile with development commands
- Configure golangci-lint with reasonable defaults

Story 0.5: Dependency Injection and Bootstrap
- Create FX-based DI container in internal/di
- Implement provider functions for Config and Logger
- Create application entry point in cmd/platform/main.go
- Add lifecycle management with graceful shutdown

All acceptance criteria met:
- go build ./cmd/platform succeeds
- go test ./... runs successfully
- go mod verify passes
- Config loads from config/default.yaml
- Logger can be injected and used
- Application starts and shuts down gracefully
This commit is contained in:
2025-11-05 12:21:15 +01:00
parent 3f90262860
commit 4724a2efb5
21 changed files with 1442 additions and 17 deletions

39
pkg/config/config.go Normal file
View File

@@ -0,0 +1,39 @@
package config
import "time"
// ConfigProvider defines the interface for configuration management.
// It provides type-safe access to configuration values from various sources
// (YAML files, environment variables, etc.).
type ConfigProvider interface {
// Get retrieves a configuration value by key.
// Returns nil if the key is not found.
Get(key string) any
// Unmarshal unmarshals the entire configuration into the provided struct.
// The struct should use appropriate tags for mapping configuration keys.
Unmarshal(v any) error
// GetString retrieves a string value by key.
// Returns empty string if the key is not found.
GetString(key string) string
// GetInt retrieves an integer value by key.
// Returns 0 if the key is not found or cannot be converted.
GetInt(key string) int
// GetBool retrieves a boolean value by key.
// Returns false if the key is not found or cannot be converted.
GetBool(key string) bool
// GetStringSlice retrieves a string slice value by key.
// Returns nil if the key is not found.
GetStringSlice(key string) []string
// GetDuration retrieves a duration value by key.
// Returns 0 if the key is not found or cannot be parsed.
GetDuration(key string) time.Duration
// IsSet checks if a configuration key is set.
IsSet(key string) bool
}

33
pkg/logger/fields.go Normal file
View File

@@ -0,0 +1,33 @@
package logger
import "go.uber.org/zap"
// String creates a string field for structured logging.
func String(key, value string) Field {
return zap.String(key, value)
}
// Int creates an integer field for structured logging.
func Int(key string, value int) Field {
return zap.Int(key, value)
}
// Int64 creates an int64 field for structured logging.
func Int64(key string, value int64) Field {
return zap.Int64(key, value)
}
// Bool creates a boolean field for structured logging.
func Bool(key string, value bool) Field {
return zap.Bool(key, value)
}
// Error creates an error field for structured logging.
func Error(err error) Field {
return zap.Error(err)
}
// Any creates a field with any value type.
func Any(key string, value any) Field {
return zap.Any(key, value)
}

60
pkg/logger/global.go Normal file
View File

@@ -0,0 +1,60 @@
package logger
import (
"context"
"sync"
)
var (
globalLogger Logger
globalMu sync.RWMutex
)
// SetGlobalLogger sets the global logger instance.
func SetGlobalLogger(l Logger) {
globalMu.Lock()
defer globalMu.Unlock()
globalLogger = l
}
// GetGlobalLogger returns the global logger instance.
// Returns a no-op logger if no global logger is set.
func GetGlobalLogger() Logger {
globalMu.RLock()
defer globalMu.RUnlock()
if globalLogger == nil {
return &noOpLogger{}
}
return globalLogger
}
// Debug logs a message at debug level using the global logger.
func Debug(msg string, fields ...Field) {
GetGlobalLogger().Debug(msg, fields...)
}
// Info logs a message at info level using the global logger.
func Info(msg string, fields ...Field) {
GetGlobalLogger().Info(msg, fields...)
}
// Warn logs a message at warning level using the global logger.
func Warn(msg string, fields ...Field) {
GetGlobalLogger().Warn(msg, fields...)
}
// ErrorLog logs a message at error level using the global logger.
func ErrorLog(msg string, fields ...Field) {
GetGlobalLogger().Error(msg, fields...)
}
// noOpLogger is a logger that does nothing.
// Used as a fallback when no global logger is set.
type noOpLogger struct{}
func (n *noOpLogger) Debug(msg string, fields ...Field) {}
func (n *noOpLogger) Info(msg string, fields ...Field) {}
func (n *noOpLogger) Warn(msg string, fields ...Field) {}
func (n *noOpLogger) Error(msg string, fields ...Field) {}
func (n *noOpLogger) With(fields ...Field) Logger { return n }
func (n *noOpLogger) WithContext(ctx context.Context) Logger { return n }

33
pkg/logger/logger.go Normal file
View File

@@ -0,0 +1,33 @@
package logger
import (
"context"
)
// Field represents a key-value pair for structured logging.
// This is an alias for zap.Field to maintain compatibility with zap.
type Field = interface{}
// Logger defines the interface for structured logging.
// It provides methods for logging at different levels with structured fields.
type Logger interface {
// Debug logs a message at debug level with optional fields.
Debug(msg string, fields ...Field)
// Info logs a message at info level with optional fields.
Info(msg string, fields ...Field)
// Warn logs a message at warning level with optional fields.
Warn(msg string, fields ...Field)
// Error logs a message at error level with optional fields.
Error(msg string, fields ...Field)
// With creates a child logger with the specified fields.
// All subsequent log calls will include these fields.
With(fields ...Field) Logger
// WithContext creates a child logger with fields extracted from context.
// Typically extracts request ID, user ID, and other context values.
WithContext(ctx context.Context) Logger
}