monorepo/native/desktop/maplefile/internal/app/wire.go

227 lines
7.7 KiB
Go

//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
}