227 lines
7.7 KiB
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
|
|
}
|