package storage import ( "crm-go/models" "crypto/rand" "encoding/hex" "encoding/json" "os" "path/filepath" "sync" "time" ) type CustomerStorage interface { GetAllCustomers() ([]models.Customer, error) GetCustomerByID(id string) (*models.Customer, error) CreateCustomer(customer models.Customer) error UpdateCustomer(id string, updates models.UpdateCustomerRequest) error DeleteCustomer(id string) error SaveCustomers(customers []models.Customer) error LoadCustomers() ([]models.Customer, error) CustomerExists(customer models.Customer) (bool, error) } type customerStorage struct { filePath string mutex sync.RWMutex } func NewCustomerStorage(filePath string) CustomerStorage { storage := &customerStorage{ filePath: filePath, } // Ensure the directory exists dir := os.DirFS(filePath[:len(filePath)-len("/customers.json")]) _ = dir // Use the directory to ensure it exists return storage } func (cs *customerStorage) GetAllCustomers() ([]models.Customer, error) { cs.mutex.RLock() defer cs.mutex.RUnlock() customers, err := cs.LoadCustomers() if err != nil { return nil, err } // Sort by CreatedAt in descending order (newest first) for i := 0; i < len(customers)-1; i++ { for j := i + 1; j < len(customers); j++ { if customers[i].CreatedAt.Before(customers[j].CreatedAt) { customers[i], customers[j] = customers[j], customers[i] } } } return customers, nil } func (cs *customerStorage) GetCustomerByID(id string) (*models.Customer, error) { cs.mutex.RLock() defer cs.mutex.RUnlock() customers, err := cs.LoadCustomers() if err != nil { return nil, err } for _, customer := range customers { if customer.ID == id { return &customer, nil } } return nil, nil } func (cs *customerStorage) CreateCustomer(customer models.Customer) error { cs.mutex.Lock() defer cs.mutex.Unlock() if customer.ID == "" { customer.ID = generateUUID() } if customer.CreatedAt.IsZero() { customer.CreatedAt = time.Now() } customers, err := cs.LoadCustomers() if err != nil { return err } customers = append(customers, customer) return cs.SaveCustomers(customers) } func generateUUID() 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 (cs *customerStorage) UpdateCustomer(id string, updates models.UpdateCustomerRequest) error { cs.mutex.Lock() defer cs.mutex.Unlock() customers, err := cs.LoadCustomers() if err != nil { return err } for i, customer := range customers { if customer.ID == id { if updates.CustomerName != nil { customers[i].CustomerName = *updates.CustomerName } if updates.IntendedProduct != nil { customers[i].IntendedProduct = *updates.IntendedProduct } if updates.Version != nil { customers[i].Version = *updates.Version } if updates.Description != nil { customers[i].Description = *updates.Description } if updates.Solution != nil { customers[i].Solution = *updates.Solution } if updates.Type != nil { customers[i].Type = *updates.Type } if updates.Module != nil { customers[i].Module = *updates.Module } if updates.StatusProgress != nil { customers[i].StatusProgress = *updates.StatusProgress } if updates.Reporter != nil { customers[i].Reporter = *updates.Reporter } if updates.Screenshots != nil { customers[i].Screenshots = *updates.Screenshots } return cs.SaveCustomers(customers) } } return nil // Customer not found, but not an error } func (cs *customerStorage) DeleteCustomer(id string) error { cs.mutex.Lock() defer cs.mutex.Unlock() customers, err := cs.LoadCustomers() if err != nil { return err } for i, customer := range customers { if customer.ID == id { customers = append(customers[:i], customers[i+1:]...) return cs.SaveCustomers(customers) } } return nil // Customer not found, but not an error } func (cs *customerStorage) SaveCustomers(customers []models.Customer) error { // Ensure the directory exists dir := filepath.Dir(cs.filePath) if err := os.MkdirAll(dir, 0755); err != nil { return err } data, err := json.MarshalIndent(customers, "", " ") if err != nil { return err } return os.WriteFile(cs.filePath, data, 0644) } func (cs *customerStorage) LoadCustomers() ([]models.Customer, error) { // Check if file exists if _, err := os.Stat(cs.filePath); os.IsNotExist(err) { // Return empty slice if file doesn't exist return []models.Customer{}, nil } data, err := os.ReadFile(cs.filePath) if err != nil { return nil, err } var customers []models.Customer if err := json.Unmarshal(data, &customers); err != nil { return nil, err } return customers, nil } func (cs *customerStorage) CustomerExists(customer models.Customer) (bool, error) { cs.mutex.RLock() defer cs.mutex.RUnlock() customers, err := cs.LoadCustomers() if err != nil { return false, err } for _, existingCustomer := range customers { if existingCustomer.Description == customer.Description { return true, nil } } return false, nil }