398 lines
11 KiB
Go
398 lines
11 KiB
Go
// Package config provides a unified API for managing application configuration
|
|
// Location: monorepo/native/desktop/maplefile/internal/config/methods.go
|
|
package config
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"net/url"
|
|
"os"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
// Implementation of ConfigService methods
|
|
|
|
// getConfig is an internal method to get the current configuration
|
|
func (s *configService) getConfig(ctx context.Context) (*Config, error) {
|
|
s.mu.RLock()
|
|
defer s.mu.RUnlock()
|
|
return s.repo.LoadConfig(ctx)
|
|
}
|
|
|
|
// saveConfig is an internal method to save the configuration
|
|
func (s *configService) saveConfig(ctx context.Context, config *Config) error {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
return s.repo.SaveConfig(ctx, config)
|
|
}
|
|
|
|
// GetConfig returns the complete configuration
|
|
func (s *configService) GetConfig(ctx context.Context) (*Config, error) {
|
|
return s.getConfig(ctx)
|
|
}
|
|
|
|
// GetAppDataDirPath returns the proper application data directory path
|
|
// The directory is mode-aware: "maplefile-dev" for dev mode, "maplefile" for production.
|
|
func (s *configService) GetAppDataDirPath(ctx context.Context) (string, error) {
|
|
return GetUserDataDir(GetAppName())
|
|
}
|
|
|
|
// GetUserDataDirPath returns the data directory path for a specific user.
|
|
// This path is:
|
|
// 1. Isolated per user (different users get different directories)
|
|
// 2. Isolated per environment (dev vs production)
|
|
// 3. Privacy-preserving (email is hashed to create directory name)
|
|
//
|
|
// Structure: {appDataDir}/users/{emailHash}/
|
|
func (s *configService) GetUserDataDirPath(ctx context.Context, userEmail string) (string, error) {
|
|
if userEmail == "" {
|
|
return "", fmt.Errorf("user email is required")
|
|
}
|
|
return GetUserSpecificDataDir(GetAppName(), userEmail)
|
|
}
|
|
|
|
// GetUserFilesDirPath returns the directory where decrypted files are stored for a user.
|
|
// Files are organized by collection: {userDir}/files/{collectionId}/{filename}
|
|
func (s *configService) GetUserFilesDirPath(ctx context.Context, userEmail string) (string, error) {
|
|
if userEmail == "" {
|
|
return "", fmt.Errorf("user email is required")
|
|
}
|
|
return GetUserFilesDir(GetAppName(), userEmail)
|
|
}
|
|
|
|
// GetUserSearchIndexDir returns the search index directory path for a specific user.
|
|
func (s *configService) GetUserSearchIndexDir(ctx context.Context, userEmail string) (string, error) {
|
|
if userEmail == "" {
|
|
return "", fmt.Errorf("user email is required")
|
|
}
|
|
return GetUserSearchIndexDir(GetAppName(), userEmail)
|
|
}
|
|
|
|
// GetLoggedInUserEmail returns the email of the currently logged-in user.
|
|
// Returns an empty string if no user is logged in.
|
|
func (s *configService) GetLoggedInUserEmail(ctx context.Context) (string, error) {
|
|
config, err := s.getConfig(ctx)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
if config.Credentials == nil {
|
|
return "", nil
|
|
}
|
|
return config.Credentials.Email, nil
|
|
}
|
|
|
|
// GetCloudProviderAddress returns the cloud provider address
|
|
func (s *configService) GetCloudProviderAddress(ctx context.Context) (string, error) {
|
|
config, err := s.getConfig(ctx)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return config.CloudProviderAddress, nil
|
|
}
|
|
|
|
// SetCloudProviderAddress updates the cloud provider address with security validation.
|
|
// In production mode, the address cannot be changed.
|
|
// In dev mode, HTTP is allowed for localhost only.
|
|
func (s *configService) SetCloudProviderAddress(ctx context.Context, address string) error {
|
|
mode := os.Getenv("MAPLEFILE_MODE")
|
|
if mode == "" {
|
|
mode = "dev"
|
|
}
|
|
|
|
// Security: Block address changes in production mode
|
|
if mode == "production" {
|
|
return fmt.Errorf("cloud provider address cannot be changed in production mode")
|
|
}
|
|
|
|
// Validate URL format
|
|
if err := validateCloudProviderURL(address, mode); err != nil {
|
|
return fmt.Errorf("invalid cloud provider address: %w", err)
|
|
}
|
|
|
|
config, err := s.getConfig(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
config.CloudProviderAddress = address
|
|
return s.saveConfig(ctx, config)
|
|
}
|
|
|
|
// validateCloudProviderURL validates the cloud provider URL based on the current mode.
|
|
// Returns an error if the URL is invalid or doesn't meet security requirements.
|
|
func validateCloudProviderURL(rawURL string, mode string) error {
|
|
if rawURL == "" {
|
|
return fmt.Errorf("URL cannot be empty")
|
|
}
|
|
|
|
parsedURL, err := url.Parse(rawURL)
|
|
if err != nil {
|
|
return fmt.Errorf("malformed URL: %w", err)
|
|
}
|
|
|
|
// Validate scheme
|
|
scheme := strings.ToLower(parsedURL.Scheme)
|
|
if scheme != "http" && scheme != "https" {
|
|
return fmt.Errorf("URL scheme must be http or https, got: %s", scheme)
|
|
}
|
|
|
|
// Validate host is present
|
|
if parsedURL.Host == "" {
|
|
return fmt.Errorf("URL must have a host")
|
|
}
|
|
|
|
// Security: In dev mode, allow HTTP only for localhost
|
|
if mode == "dev" && scheme == "http" {
|
|
host := strings.ToLower(parsedURL.Hostname())
|
|
if host != "localhost" && host != "127.0.0.1" && !strings.HasPrefix(host, "192.168.") && !strings.HasPrefix(host, "10.") {
|
|
return fmt.Errorf("HTTP is only allowed for localhost/local network in dev mode; use HTTPS for remote servers")
|
|
}
|
|
}
|
|
|
|
// Reject URLs with credentials embedded
|
|
if parsedURL.User != nil {
|
|
return fmt.Errorf("URL must not contain embedded credentials")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// SetLoggedInUserCredentials updates the authenticated user's credentials
|
|
func (s *configService) SetLoggedInUserCredentials(
|
|
ctx context.Context,
|
|
email string,
|
|
accessToken string,
|
|
accessTokenExpiryTime *time.Time,
|
|
refreshToken string,
|
|
refreshTokenExpiryTime *time.Time,
|
|
) error {
|
|
config, err := s.getConfig(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
config.Credentials = &Credentials{
|
|
Email: email,
|
|
AccessToken: accessToken,
|
|
AccessTokenExpiryTime: accessTokenExpiryTime,
|
|
RefreshToken: refreshToken,
|
|
RefreshTokenExpiryTime: refreshTokenExpiryTime,
|
|
}
|
|
return s.saveConfig(ctx, config)
|
|
}
|
|
|
|
// GetLoggedInUserCredentials returns the authenticated user's credentials
|
|
func (s *configService) GetLoggedInUserCredentials(ctx context.Context) (*Credentials, error) {
|
|
config, err := s.getConfig(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return config.Credentials, nil
|
|
}
|
|
|
|
// ClearLoggedInUserCredentials clears the authenticated user's credentials
|
|
func (s *configService) ClearLoggedInUserCredentials(ctx context.Context) error {
|
|
config, err := s.getConfig(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Clear credentials by setting them to empty values
|
|
config.Credentials = &Credentials{
|
|
Email: "",
|
|
AccessToken: "",
|
|
AccessTokenExpiryTime: nil,
|
|
RefreshToken: "",
|
|
RefreshTokenExpiryTime: nil,
|
|
}
|
|
|
|
return s.saveConfig(ctx, config)
|
|
}
|
|
|
|
// Desktop-specific methods
|
|
|
|
// GetWindowSize returns the configured window size
|
|
func (s *configService) GetWindowSize(ctx context.Context) (width int, height int, err error) {
|
|
config, err := s.getConfig(ctx)
|
|
if err != nil {
|
|
return 0, 0, err
|
|
}
|
|
return config.WindowWidth, config.WindowHeight, nil
|
|
}
|
|
|
|
// SetWindowSize updates the window size configuration
|
|
func (s *configService) SetWindowSize(ctx context.Context, width int, height int) error {
|
|
config, err := s.getConfig(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
config.WindowWidth = width
|
|
config.WindowHeight = height
|
|
return s.saveConfig(ctx, config)
|
|
}
|
|
|
|
// GetTheme returns the configured theme
|
|
func (s *configService) GetTheme(ctx context.Context) (string, error) {
|
|
config, err := s.getConfig(ctx)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return config.Theme, nil
|
|
}
|
|
|
|
// SetTheme updates the theme configuration
|
|
func (s *configService) SetTheme(ctx context.Context, theme string) error {
|
|
config, err := s.getConfig(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
config.Theme = theme
|
|
return s.saveConfig(ctx, config)
|
|
}
|
|
|
|
// GetLanguage returns the configured language
|
|
func (s *configService) GetLanguage(ctx context.Context) (string, error) {
|
|
config, err := s.getConfig(ctx)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return config.Language, nil
|
|
}
|
|
|
|
// SetLanguage updates the language configuration
|
|
func (s *configService) SetLanguage(ctx context.Context, language string) error {
|
|
config, err := s.getConfig(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
config.Language = language
|
|
return s.saveConfig(ctx, config)
|
|
}
|
|
|
|
// GetSyncMode returns the configured sync mode
|
|
func (s *configService) GetSyncMode(ctx context.Context) (string, error) {
|
|
config, err := s.getConfig(ctx)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return config.SyncMode, nil
|
|
}
|
|
|
|
// SetSyncMode updates the sync mode configuration
|
|
func (s *configService) SetSyncMode(ctx context.Context, mode string) error {
|
|
config, err := s.getConfig(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
config.SyncMode = mode
|
|
return s.saveConfig(ctx, config)
|
|
}
|
|
|
|
// GetAutoSync returns whether automatic sync is enabled
|
|
func (s *configService) GetAutoSync(ctx context.Context) (bool, error) {
|
|
config, err := s.getConfig(ctx)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
return config.AutoSync, nil
|
|
}
|
|
|
|
// SetAutoSync updates the automatic sync setting
|
|
func (s *configService) SetAutoSync(ctx context.Context, enabled bool) error {
|
|
config, err := s.getConfig(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
config.AutoSync = enabled
|
|
return s.saveConfig(ctx, config)
|
|
}
|
|
|
|
// GetSyncInterval returns the sync interval in minutes
|
|
func (s *configService) GetSyncInterval(ctx context.Context) (int, error) {
|
|
config, err := s.getConfig(ctx)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
return config.SyncIntervalMinutes, nil
|
|
}
|
|
|
|
// SetSyncInterval updates the sync interval configuration
|
|
func (s *configService) SetSyncInterval(ctx context.Context, minutes int) error {
|
|
config, err := s.getConfig(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
config.SyncIntervalMinutes = minutes
|
|
return s.saveConfig(ctx, config)
|
|
}
|
|
|
|
// GetShowHiddenFiles returns whether hidden files should be shown
|
|
func (s *configService) GetShowHiddenFiles(ctx context.Context) (bool, error) {
|
|
config, err := s.getConfig(ctx)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
return config.ShowHiddenFiles, nil
|
|
}
|
|
|
|
// SetShowHiddenFiles updates the show hidden files setting
|
|
func (s *configService) SetShowHiddenFiles(ctx context.Context, show bool) error {
|
|
config, err := s.getConfig(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
config.ShowHiddenFiles = show
|
|
return s.saveConfig(ctx, config)
|
|
}
|
|
|
|
// GetDefaultView returns the configured default view
|
|
func (s *configService) GetDefaultView(ctx context.Context) (string, error) {
|
|
config, err := s.getConfig(ctx)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return config.DefaultView, nil
|
|
}
|
|
|
|
// SetDefaultView updates the default view configuration
|
|
func (s *configService) SetDefaultView(ctx context.Context, view string) error {
|
|
config, err := s.getConfig(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
config.DefaultView = view
|
|
return s.saveConfig(ctx, config)
|
|
}
|
|
|
|
// GetSortPreferences returns the configured sort preferences
|
|
func (s *configService) GetSortPreferences(ctx context.Context) (sortBy string, sortOrder string, err error) {
|
|
config, err := s.getConfig(ctx)
|
|
if err != nil {
|
|
return "", "", err
|
|
}
|
|
return config.SortBy, config.SortOrder, nil
|
|
}
|
|
|
|
// SetSortPreferences updates the sort preferences
|
|
func (s *configService) SetSortPreferences(ctx context.Context, sortBy string, sortOrder string) error {
|
|
config, err := s.getConfig(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
config.SortBy = sortBy
|
|
config.SortOrder = sortOrder
|
|
return s.saveConfig(ctx, config)
|
|
}
|
|
|
|
// Ensure our implementation satisfies the interface
|
|
var _ ConfigService = (*configService)(nil)
|