package contact import ( "encoding/json" "fmt" "os" "path/filepath" "sync" "time" "github.com/frenocorp/pop/internal/config" ) type ContactManager struct { mu sync.Mutex configDir string contactsFile string } func NewContactManager() *ContactManager { cfg := config.NewConfigManager() configDir := cfg.ConfigDir() return &ContactManager{ configDir: configDir, contactsFile: filepath.Join(configDir, "contacts.json"), } } func (m *ContactManager) List(req ListContactsRequest) (*ListContactsResponse, error) { m.mu.Lock() defer m.mu.Unlock() contacts, err := m.loadContacts() if err != nil { return nil, err } var filtered []Contact for _, c := range contacts { if req.Search != "" { search := req.Search if !contains(c.Email, search) && !contains(c.Name, search) { continue } } if req.IsFavorite != nil && *req.IsFavorite != c.IsFavorite { continue } filtered = append(filtered, c) } start := req.Page * req.PageSize end := start + req.PageSize if start > len(filtered) { start = len(filtered) } if end > len(filtered) { end = len(filtered) } return &ListContactsResponse{ Total: len(filtered), Contacts: filtered[start:end], }, nil } func (m *ContactManager) Create(req CreateContactRequest) (*Contact, error) { m.mu.Lock() defer m.mu.Unlock() contacts, err := m.loadContacts() if err != nil { return nil, err } id := fmt.Sprintf("contact_%d", time.Now().UnixNano()) contact := Contact{ ID: id, Email: req.Email, Name: req.Name, Phone: req.Phone, Address: req.Address, Notes: req.Notes, IsFavorite: req.IsFavorite, CustomFields: req.CustomFields, CreatedAt: time.Now(), UpdatedAt: time.Now(), } contacts = append(contacts, contact) if err := m.saveContacts(contacts); err != nil { return nil, err } return &contact, nil } func (m *ContactManager) Get(id string) (*Contact, error) { m.mu.Lock() defer m.mu.Unlock() contacts, err := m.loadContacts() if err != nil { return nil, err } for _, c := range contacts { if c.ID == id { return &c, nil } } return nil, fmt.Errorf("contact not found: %s", id) } func (m *ContactManager) Update(id string, req UpdateContactRequest) (*Contact, error) { m.mu.Lock() defer m.mu.Unlock() contacts, err := m.loadContacts() if err != nil { return nil, err } for i, c := range contacts { if c.ID == id { if req.Email != nil { c.Email = *req.Email } if req.Name != nil { c.Name = *req.Name } if req.Phone != nil { c.Phone = *req.Phone } if req.Address != nil { c.Address = *req.Address } if req.Notes != nil { c.Notes = *req.Notes } if req.IsFavorite != nil { c.IsFavorite = *req.IsFavorite } if req.CustomFields != nil { c.CustomFields = req.CustomFields } c.UpdatedAt = time.Now() contacts[i] = c if err := m.saveContacts(contacts); err != nil { return nil, err } return &c, nil } } return nil, fmt.Errorf("contact not found: %s", id) } func (m *ContactManager) Delete(id string) error { m.mu.Lock() defer m.mu.Unlock() contacts, err := m.loadContacts() if err != nil { return err } for i, c := range contacts { if c.ID == id { contacts = append(contacts[:i], contacts[i+1:]...) return m.saveContacts(contacts) } } return fmt.Errorf("contact not found: %s", id) } func (m *ContactManager) loadContacts() ([]Contact, error) { data, err := os.ReadFile(m.contactsFile) if err != nil { if os.IsNotExist(err) { return []Contact{}, nil } return nil, err } var contacts []Contact if err := json.Unmarshal(data, &contacts); err != nil { return nil, err } return contacts, nil } func (m *ContactManager) saveContacts(contacts []Contact) error { if err := os.MkdirAll(m.configDir, 0700); err != nil { return err } data, err := json.MarshalIndent(contacts, "", " ") if err != nil { return err } return os.WriteFile(m.contactsFile, data, 0600) } func contains(s, substr string) bool { return len(s) >= len(substr) && (s == substr || len(s) > 0 && len(substr) > 0 && (s[:len(substr)] == substr || s[len(s)-len(substr):] == substr || findSubstring(s, substr))) } func findSubstring(s, substr string) bool { for i := 0; i <= len(s)-len(substr); i++ { if s[i:i+len(substr)] == substr { return true } } return false }