**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.
100 lines
3.4 KiB
Go
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
|
|
}
|