package storage import ( "crm-go/models" "crypto/rand" "encoding/hex" "encoding/json" "os" "path/filepath" "sync" "time" ) type FollowUpStorage interface { GetAllFollowUps() ([]models.FollowUp, error) GetFollowUpByID(id string) (*models.FollowUp, error) CreateFollowUp(followUp models.FollowUp) error UpdateFollowUp(id string, updates models.UpdateFollowUpRequest) error DeleteFollowUp(id string) error SaveFollowUps(followUps []models.FollowUp) error LoadFollowUps() ([]models.FollowUp, error) GetPendingNotifications() ([]models.FollowUp, error) MarkNotificationSent(id string) error } type followUpStorage struct { filePath string mutex sync.RWMutex } func NewFollowUpStorage(filePath string) FollowUpStorage { storage := &followUpStorage{ filePath: filePath, } return storage } func (fs *followUpStorage) GetAllFollowUps() ([]models.FollowUp, error) { fs.mutex.RLock() defer fs.mutex.RUnlock() followUps, err := fs.LoadFollowUps() if err != nil { return nil, err } // Sort by FollowUpTime in descending order (newest first) for i := 0; i < len(followUps)-1; i++ { for j := i + 1; j < len(followUps); j++ { if followUps[i].FollowUpTime.Before(followUps[j].FollowUpTime) { followUps[i], followUps[j] = followUps[j], followUps[i] } } } return followUps, nil } func (fs *followUpStorage) GetFollowUpByID(id string) (*models.FollowUp, error) { fs.mutex.RLock() defer fs.mutex.RUnlock() followUps, err := fs.LoadFollowUps() if err != nil { return nil, err } for _, followUp := range followUps { if followUp.ID == id { return &followUp, nil } } return nil, nil } func (fs *followUpStorage) CreateFollowUp(followUp models.FollowUp) error { fs.mutex.Lock() defer fs.mutex.Unlock() if followUp.ID == "" { followUp.ID = generateFollowUpUUID() } if followUp.CreatedAt.IsZero() { followUp.CreatedAt = time.Now() } followUps, err := fs.LoadFollowUps() if err != nil { return err } followUps = append(followUps, followUp) return fs.SaveFollowUps(followUps) } func generateFollowUpUUID() string { bytes := make([]byte, 16) rand.Read(bytes) bytes[6] = (bytes[6] & 0x0f) | 0x40 // Version 4 bytes[8] = (bytes[8] & 0x3f) | 0x80 // Variant return hex.EncodeToString(bytes) } func (fs *followUpStorage) UpdateFollowUp(id string, updates models.UpdateFollowUpRequest) error { fs.mutex.Lock() defer fs.mutex.Unlock() followUps, err := fs.LoadFollowUps() if err != nil { return err } for i, followUp := range followUps { if followUp.ID == id { if updates.CustomerName != nil { followUps[i].CustomerName = *updates.CustomerName } if updates.DealStatus != nil { followUps[i].DealStatus = *updates.DealStatus } if updates.CustomerLevel != nil { followUps[i].CustomerLevel = *updates.CustomerLevel } if updates.Industry != nil { followUps[i].Industry = *updates.Industry } if updates.FollowUpTime != nil { // Parse the time string t, err := time.Parse(time.RFC3339, *updates.FollowUpTime) if err == nil { followUps[i].FollowUpTime = t } } if updates.NotificationSent != nil { followUps[i].NotificationSent = *updates.NotificationSent } return fs.SaveFollowUps(followUps) } } return nil // FollowUp not found, but not an error } func (fs *followUpStorage) DeleteFollowUp(id string) error { fs.mutex.Lock() defer fs.mutex.Unlock() followUps, err := fs.LoadFollowUps() if err != nil { return err } for i, followUp := range followUps { if followUp.ID == id { followUps = append(followUps[:i], followUps[i+1:]...) return fs.SaveFollowUps(followUps) } } return nil // FollowUp not found, but not an error } func (fs *followUpStorage) SaveFollowUps(followUps []models.FollowUp) error { // Ensure the directory exists dir := filepath.Dir(fs.filePath) if err := os.MkdirAll(dir, 0755); err != nil { return err } data, err := json.MarshalIndent(followUps, "", " ") if err != nil { return err } return os.WriteFile(fs.filePath, data, 0644) } func (fs *followUpStorage) LoadFollowUps() ([]models.FollowUp, error) { // Check if file exists if _, err := os.Stat(fs.filePath); os.IsNotExist(err) { // Return empty slice if file doesn't exist return []models.FollowUp{}, nil } data, err := os.ReadFile(fs.filePath) if err != nil { return nil, err } var followUps []models.FollowUp if err := json.Unmarshal(data, &followUps); err != nil { return nil, err } return followUps, nil } func (fs *followUpStorage) GetPendingNotifications() ([]models.FollowUp, error) { fs.mutex.RLock() defer fs.mutex.RUnlock() followUps, err := fs.LoadFollowUps() if err != nil { return nil, err } now := time.Now() var pending []models.FollowUp for _, followUp := range followUps { // Check if follow-up time has passed and notification hasn't been sent if !followUp.NotificationSent && followUp.FollowUpTime.Before(now) { pending = append(pending, followUp) } } return pending, nil } func (fs *followUpStorage) MarkNotificationSent(id string) error { fs.mutex.Lock() defer fs.mutex.Unlock() followUps, err := fs.LoadFollowUps() if err != nil { return err } for i, followUp := range followUps { if followUp.ID == id { followUps[i].NotificationSent = true return fs.SaveFollowUps(followUps) } } return nil }