//go:build wireinject // +build wireinject package app import ( "context" "os" "strings" "github.com/google/wire" "go.uber.org/zap" "codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/pkg/maplefile/client" "codeberg.org/mapleopentech/monorepo/native/desktop/maplefile/internal/config" "codeberg.org/mapleopentech/monorepo/native/desktop/maplefile/pkg/storage/leveldb" // Domain imports sessionDomain "codeberg.org/mapleopentech/monorepo/native/desktop/maplefile/internal/domain/session" // Repository imports sessionRepo "codeberg.org/mapleopentech/monorepo/native/desktop/maplefile/internal/repo/session" // Service imports authService "codeberg.org/mapleopentech/monorepo/native/desktop/maplefile/internal/service/auth" "codeberg.org/mapleopentech/monorepo/native/desktop/maplefile/internal/service/httpclient" keyCache "codeberg.org/mapleopentech/monorepo/native/desktop/maplefile/internal/service/keycache" passwordStore "codeberg.org/mapleopentech/monorepo/native/desktop/maplefile/internal/service/passwordstore" rateLimiter "codeberg.org/mapleopentech/monorepo/native/desktop/maplefile/internal/service/ratelimiter" "codeberg.org/mapleopentech/monorepo/native/desktop/maplefile/internal/service/search" "codeberg.org/mapleopentech/monorepo/native/desktop/maplefile/internal/service/securitylog" "codeberg.org/mapleopentech/monorepo/native/desktop/maplefile/internal/service/storagemanager" syncService "codeberg.org/mapleopentech/monorepo/native/desktop/maplefile/internal/service/sync" tokenManager "codeberg.org/mapleopentech/monorepo/native/desktop/maplefile/internal/service/tokenmanager" // Use case imports sessionUC "codeberg.org/mapleopentech/monorepo/native/desktop/maplefile/internal/usecase/session" ) // InitializeApplication creates a fully configured Application using Wire DI func InitializeApplication() (*Application, error) { wire.Build( // Infrastructure ProvideLogger, config.New, ProvideMapleFileClient, // Session Repository (global - not user-specific) ProvideSessionRepository, // Storage Manager (handles user-specific storage lifecycle) storagemanager.ProvideManager, // Bind *storagemanager.Manager to sync.RepositoryProvider interface wire.Bind(new(syncService.RepositoryProvider), new(*storagemanager.Manager)), // Use Case Layer sessionUC.ProvideCreateUseCase, sessionUC.ProvideGetByIdUseCase, sessionUC.ProvideDeleteUseCase, sessionUC.ProvideSaveUseCase, // Service Layer authService.ProvideService, tokenManager.ProvideManager, passwordStore.ProvideService, keyCache.ProvideService, rateLimiter.ProvideService, httpclient.ProvideService, securitylog.ProvideService, search.New, // Sync Services syncService.ProvideCollectionSyncService, syncService.ProvideFileSyncService, syncService.ProvideService, // Application ProvideApplication, ) return nil, nil } // ProvideLogger creates the application logger with environment-aware configuration. // Defaults to production mode for security. Development mode must be explicitly enabled. func ProvideLogger() (*zap.Logger, error) { mode := os.Getenv("MAPLEFILE_MODE") // Only use development logger if explicitly set to "dev" or "development" if mode == "dev" || mode == "development" { // Development: console format, debug level, with caller and stacktrace return zap.NewDevelopment() } // Default to production: JSON format, info level, no caller info, no stacktrace // This is the secure default - production mode unless explicitly in dev cfg := zap.NewProductionConfig() cfg.Level = zap.NewAtomicLevelAt(zap.InfoLevel) cfg.DisableCaller = true cfg.DisableStacktrace = true return cfg.Build() } // ProvideSessionRepository creates the session repository with its storage. // Session storage is GLOBAL (not user-specific) because it stores the current login session. func ProvideSessionRepository(logger *zap.Logger) (sessionDomain.Repository, error) { provider, err := config.NewLevelDBConfigurationProviderForSession() if err != nil { return nil, err } sessionStorage := leveldb.NewDiskStorage(provider, logger.Named("session-storage")) return sessionRepo.ProvideRepository(sessionStorage), nil } // zapLoggerAdapter adapts *zap.Logger to client.Logger interface type zapLoggerAdapter struct { logger *zap.Logger } func (a *zapLoggerAdapter) Debug(msg string, keysAndValues ...interface{}) { fields := keysAndValuesToZapFields(keysAndValues...) a.logger.Debug(msg, fields...) } func (a *zapLoggerAdapter) Info(msg string, keysAndValues ...interface{}) { fields := keysAndValuesToZapFields(keysAndValues...) a.logger.Info(msg, fields...) } func (a *zapLoggerAdapter) Warn(msg string, keysAndValues ...interface{}) { fields := keysAndValuesToZapFields(keysAndValues...) a.logger.Warn(msg, fields...) } func (a *zapLoggerAdapter) Error(msg string, keysAndValues ...interface{}) { fields := keysAndValuesToZapFields(keysAndValues...) a.logger.Error(msg, fields...) } // keysAndValuesToZapFields converts key-value pairs to zap fields func keysAndValuesToZapFields(keysAndValues ...interface{}) []zap.Field { fields := make([]zap.Field, 0, len(keysAndValues)/2) for i := 0; i+1 < len(keysAndValues); i += 2 { key, ok := keysAndValues[i].(string) if !ok { continue } fields = append(fields, zap.Any(key, keysAndValues[i+1])) } return fields } // BuildMode is set at compile time via -ldflags // Example: go build -ldflags "-X codeberg.org/mapleopentech/monorepo/native/desktop/maplefile/internal/app.BuildMode=dev" var BuildMode string // ProvideMapleFileClient creates the backend API client func ProvideMapleFileClient(configService config.ConfigService, logger *zap.Logger) (*client.Client, error) { ctx := context.Background() // Determine the API URL based on the mode // Priority: 1) Environment variable, 2) Build-time variable, 3) Default to production mode := os.Getenv("MAPLEFILE_MODE") // Log the detected mode logger.Info("Startup: checking mode configuration", zap.String("MAPLEFILE_MODE_env", mode), zap.String("BuildMode_compile_time", BuildMode), ) if mode == "" { if BuildMode != "" { mode = BuildMode logger.Info("Startup: using compile-time BuildMode", zap.String("mode", mode)) } else { mode = "production" // Default to production (secure default) logger.Info("Startup: no mode set, defaulting to production", zap.String("mode", mode)) } } var baseURL string switch mode { case "production": baseURL = client.ProductionURL // https://maplefile.ca case "dev", "development": baseURL = client.LocalURL // http://localhost:8000 default: // Fallback: check config file for custom URL cfg, err := configService.GetConfig(ctx) if err != nil { return nil, err } baseURL = cfg.CloudProviderAddress } // Create logger adapter for the API client clientLogger := &zapLoggerAdapter{logger: logger.Named("api-client")} // Create client with the determined URL and logger apiClient := client.New(client.Config{ BaseURL: baseURL, Logger: clientLogger, }) logger.Info("MapleFile API client initialized", zap.String("mode", mode), zap.String("base_url", baseURL), ) // Security: Warn if using unencrypted HTTP (should only happen in dev mode) if strings.HasPrefix(baseURL, "http://") { logger.Warn("SECURITY WARNING: Using unencrypted HTTP connection", zap.String("mode", mode), zap.String("base_url", baseURL), zap.String("recommendation", "This should only be used for local development"), ) } // Update the config to reflect the current backend URL (skip in production as it's immutable) if mode != "production" { if err := configService.SetCloudProviderAddress(ctx, baseURL); err != nil { logger.Warn("Failed to update cloud provider address in config", zap.Error(err)) } } return apiClient, nil }