config.go

  1package config
  2
  3import (
  4	"encoding/json"
  5	"os"
  6	"path/filepath"
  7
  8	"github.com/google/uuid"
  9)
 10
 11// Account stores the configuration for a single email account.
 12type Account struct {
 13	ID              string `json:"id"`
 14	Name            string `json:"name"`
 15	Email           string `json:"email"`
 16	Password        string `json:"password"`
 17	ServiceProvider string `json:"service_provider"` // "gmail", "icloud", or "custom"
 18	// FetchEmail is the single email address for which messages should be fetched.
 19	// If empty, it will default to `Email` when accounts are added.
 20	FetchEmail string `json:"fetch_email,omitempty"`
 21
 22	// Custom server settings (used when ServiceProvider is "custom")
 23	IMAPServer string `json:"imap_server,omitempty"`
 24	IMAPPort   int    `json:"imap_port,omitempty"`
 25	SMTPServer string `json:"smtp_server,omitempty"`
 26	SMTPPort   int    `json:"smtp_port,omitempty"`
 27}
 28
 29// Config stores the user's email configuration with multiple accounts.
 30type Config struct {
 31	Accounts []Account `json:"accounts"`
 32}
 33
 34// GetIMAPServer returns the IMAP server address for the account.
 35func (a *Account) GetIMAPServer() string {
 36	switch a.ServiceProvider {
 37	case "gmail":
 38		return "imap.gmail.com"
 39	case "icloud":
 40		return "imap.mail.me.com"
 41	case "custom":
 42		return a.IMAPServer
 43	default:
 44		return ""
 45	}
 46}
 47
 48// GetIMAPPort returns the IMAP port for the account.
 49func (a *Account) GetIMAPPort() int {
 50	switch a.ServiceProvider {
 51	case "gmail", "icloud":
 52		return 993
 53	case "custom":
 54		if a.IMAPPort != 0 {
 55			return a.IMAPPort
 56		}
 57		return 993 // Default IMAP SSL port
 58	default:
 59		return 993
 60	}
 61}
 62
 63// GetSMTPServer returns the SMTP server address for the account.
 64func (a *Account) GetSMTPServer() string {
 65	switch a.ServiceProvider {
 66	case "gmail":
 67		return "smtp.gmail.com"
 68	case "icloud":
 69		return "smtp.mail.me.com"
 70	case "custom":
 71		return a.SMTPServer
 72	default:
 73		return ""
 74	}
 75}
 76
 77// GetSMTPPort returns the SMTP port for the account.
 78func (a *Account) GetSMTPPort() int {
 79	switch a.ServiceProvider {
 80	case "gmail", "icloud":
 81		return 587
 82	case "custom":
 83		if a.SMTPPort != 0 {
 84			return a.SMTPPort
 85		}
 86		return 587 // Default SMTP TLS port
 87	default:
 88		return 587
 89	}
 90}
 91
 92// configDir returns the path to the configuration directory.
 93func configDir() (string, error) {
 94	home, err := os.UserHomeDir()
 95	if err != nil {
 96		return "", err
 97	}
 98	return filepath.Join(home, ".config", "matcha"), nil
 99}
100
101// configFile returns the full path to the configuration file.
102func configFile() (string, error) {
103	dir, err := configDir()
104	if err != nil {
105		return "", err
106	}
107	return filepath.Join(dir, "config.json"), nil
108}
109
110// SaveConfig saves the given configuration to the config file.
111func SaveConfig(config *Config) error {
112	path, err := configFile()
113	if err != nil {
114		return err
115	}
116	if err := os.MkdirAll(filepath.Dir(path), 0700); err != nil {
117		return err
118	}
119	data, err := json.MarshalIndent(config, "", "  ")
120	if err != nil {
121		return err
122	}
123	return os.WriteFile(path, data, 0600)
124}
125
126// LoadConfig loads the configuration from the config file.
127func LoadConfig() (*Config, error) {
128	path, err := configFile()
129	if err != nil {
130		return nil, err
131	}
132	data, err := os.ReadFile(path)
133	if err != nil {
134		return nil, err
135	}
136	var config Config
137	if err := json.Unmarshal(data, &config); err != nil {
138		// Try to load legacy single-account config
139		var legacyConfig legacyConfigFormat
140		if legacyErr := json.Unmarshal(data, &legacyConfig); legacyErr == nil && legacyConfig.Email != "" {
141			// Convert legacy config to new format
142			config = Config{
143				Accounts: []Account{
144					{
145						ID:              uuid.New().String(),
146						Name:            legacyConfig.Name,
147						Email:           legacyConfig.Email,
148						Password:        legacyConfig.Password,
149						ServiceProvider: legacyConfig.ServiceProvider,
150						// Default FetchEmail to the legacy Email value
151						FetchEmail: legacyConfig.Email,
152					},
153				},
154			}
155			// Save the migrated config
156			if saveErr := SaveConfig(&config); saveErr != nil {
157				return nil, saveErr
158			}
159			return &config, nil
160		}
161		return nil, err
162	}
163	return &config, nil
164}
165
166// legacyConfigFormat represents the old single-account configuration format.
167type legacyConfigFormat struct {
168	ServiceProvider string `json:"service_provider"`
169	Email           string `json:"email"`
170	Password        string `json:"password"`
171	Name            string `json:"name"`
172}
173
174// AddAccount adds a new account to the configuration.
175func (c *Config) AddAccount(account Account) {
176	if account.ID == "" {
177		account.ID = uuid.New().String()
178	}
179	// Ensure FetchEmail defaults to the login Email if not explicitly set.
180	if account.FetchEmail == "" && account.Email != "" {
181		account.FetchEmail = account.Email
182	}
183	c.Accounts = append(c.Accounts, account)
184}
185
186// RemoveAccount removes an account by its ID.
187func (c *Config) RemoveAccount(id string) bool {
188	for i, acc := range c.Accounts {
189		if acc.ID == id {
190			c.Accounts = append(c.Accounts[:i], c.Accounts[i+1:]...)
191			return true
192		}
193	}
194	return false
195}
196
197// GetAccountByID returns an account by its ID.
198func (c *Config) GetAccountByID(id string) *Account {
199	for i := range c.Accounts {
200		if c.Accounts[i].ID == id {
201			return &c.Accounts[i]
202		}
203	}
204	return nil
205}
206
207// GetAccountByEmail returns an account by its email address.
208func (c *Config) GetAccountByEmail(email string) *Account {
209	for i := range c.Accounts {
210		if c.Accounts[i].Email == email {
211			return &c.Accounts[i]
212		}
213	}
214	return nil
215}
216
217// HasAccounts returns true if there are any configured accounts.
218func (c *Config) HasAccounts() bool {
219	return len(c.Accounts) > 0
220}
221
222// GetFirstAccount returns the first account or nil if none exist.
223func (c *Config) GetFirstAccount() *Account {
224	if len(c.Accounts) > 0 {
225		return &c.Accounts[0]
226	}
227	return nil
228}