sojorn/go-backend/internal/config/config.go
Patrick Britton 3c4680bdd7 Initial commit: Complete threaded conversation system with inline replies
**Major Features Added:**
- **Inline Reply System**: Replace compose screen with inline reply boxes
- **Thread Navigation**: Parent/child navigation with jump functionality
- **Chain Flow UI**: Reply counts, expand/collapse animations, visual hierarchy
- **Enhanced Animations**: Smooth transitions, hover effects, micro-interactions

 **Frontend Changes:**
- **ThreadedCommentWidget**: Complete rewrite with animations and navigation
- **ThreadNode Model**: Added parent references and descendant counting
- **ThreadedConversationScreen**: Integrated navigation handlers
- **PostDetailScreen**: Replaced with threaded conversation view
- **ComposeScreen**: Added reply indicators and context
- **PostActions**: Fixed visibility checks for chain buttons

 **Backend Changes:**
- **API Route**: Added /posts/:id/thread endpoint
- **Post Repository**: Include allow_chain and visibility fields in feed
- **Thread Handler**: Support for fetching post chains

 **UI/UX Improvements:**
- **Reply Context**: Clear indication when replying to specific posts
- **Character Counting**: 500 character limit with live counter
- **Visual Hierarchy**: Depth-based indentation and styling
- **Smooth Animations**: SizeTransition, FadeTransition, hover states
- **Chain Navigation**: Parent/child buttons with visual feedback

 **Technical Enhancements:**
- **Animation Controllers**: Proper lifecycle management
- **State Management**: Clean separation of concerns
- **Navigation Callbacks**: Reusable navigation system
- **Error Handling**: Graceful fallbacks and user feedback

This creates a Reddit-style threaded conversation experience with smooth
animations, inline replies, and intuitive navigation between posts in a chain.
2026-01-30 07:40:19 -06:00

100 lines
3.4 KiB
Go

package config
import (
"os"
"strconv"
"github.com/joho/godotenv"
)
type Config struct {
Port string
Env string
LogLevel string
DatabaseURL string
JWTSecret string
CORSOrigins string
RateLimitRPS int
SupabaseURL string
SupabaseKey string
SMTPHost string
SMTPPort int
SMTPUser string
SMTPPass string
SMTPFrom string
SenderAPIToken string
SendPulseID string
SendPulseSecret string
R2SigningSecret string
R2PublicBaseURL string
FirebaseCredentialsFile string
R2AccountID string
R2APIToken string
R2ImgDomain string
R2VidDomain string
R2Endpoint string
R2AccessKey string
R2SecretKey string
R2MediaBucket string
R2VideoBucket string
}
func LoadConfig() *Config {
// Try current directory first
err := godotenv.Load()
if err != nil {
// Try parent directory (common for VPS structure /opt/sojorn/.env while binary is in /opt/sojorn/go-backend)
_ = godotenv.Load("../.env")
// Try absolute path specified by user
_ = godotenv.Load("/opt/sojorn/.env")
}
return &Config{
Port: getEnv("PORT", "8080"),
Env: getEnv("ENV", "development"),
LogLevel: getEnv("LOG_LEVEL", "info"),
DatabaseURL: getEnv("DATABASE_URL", ""),
JWTSecret: getEnv("JWT_SECRET", ""),
CORSOrigins: getEnv("CORS_ORIGINS", "*"),
RateLimitRPS: getEnvInt("RATE_LIMIT_RPS", 10),
SupabaseURL: getEnv("SUPABASE_URL", ""),
SupabaseKey: getEnv("SUPABASE_KEY", ""),
SMTPHost: getEnv("SMTP_HOST", "smtp.sender.net"),
SMTPPort: getEnvInt("SMTP_PORT", 587),
SMTPUser: getEnv("SMTP_USER", ""),
SMTPPass: getEnv("SMTP_PASS", ""),
SMTPFrom: getEnv("SMTP_FROM", "no-reply@gosojorn.com"),
SenderAPIToken: getEnv("SENDER_API_TOKEN", ""),
SendPulseID: getEnv("SENDPULSE_ID", ""),
SendPulseSecret: getEnv("SENDPULSE_SECRET", ""),
R2SigningSecret: getEnv("R2_SIGNING_SECRET", ""),
// Default to the public CDN domain to avoid mixed-content/http defaults.
R2PublicBaseURL: getEnv("R2_PUBLIC_BASE_URL", "https://img.gosojorn.com"),
FirebaseCredentialsFile: getEnv("FIREBASE_CREDENTIALS_FILE", "firebase-service-account.json"),
R2AccountID: getEnv("R2_ACCOUNT_ID", ""),
R2APIToken: getEnv("R2_API_TOKEN", ""),
R2ImgDomain: getEnv("R2_IMG_DOMAIN", "img.gosojorn.com"),
R2VidDomain: getEnv("R2_VID_DOMAIN", "quips.gosojorn.com"),
R2Endpoint: getEnv("R2_ENDPOINT", ""),
R2AccessKey: getEnv("R2_ACCESS_KEY", ""),
R2SecretKey: getEnv("R2_SECRET_KEY", ""),
R2MediaBucket: getEnv("R2_MEDIA_BUCKET", "sojorn-media"),
R2VideoBucket: getEnv("R2_VIDEO_BUCKET", "sojorn-videos"),
}
}
func getEnv(key, fallback string) string {
if value, exists := os.LookupEnv(key); exists {
return value
}
return fallback
}
func getEnvInt(key string, fallback int) int {
valueStr := getEnv(key, "")
if value, err := strconv.Atoi(valueStr); err == nil {
return value
}
return fallback
}