Initial commit: Open sourcing all of the Maple Open Technologies code.

This commit is contained in:
Bartlomiej Mika 2025-12-02 14:33:08 -05:00
commit 755d54a99d
2010 changed files with 448675 additions and 0 deletions

View file

@ -0,0 +1,398 @@
// 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)