package handlers import ( "encoding/csv" "encoding/json" "io" "net/http" "strconv" "strings" "time" "crm-go/internal/storage" "crm-go/models" "github.com/xuri/excelize/v2" ) type CustomerHandler struct { storage storage.CustomerStorage } func NewCustomerHandler(storage storage.CustomerStorage) *CustomerHandler { return &CustomerHandler{ storage: storage, } } func (h *CustomerHandler) GetCustomers(w http.ResponseWriter, r *http.Request) { page := 1 pageSize := 10 if pageStr := r.URL.Query().Get("page"); pageStr != "" { if p, err := strconv.Atoi(pageStr); err == nil && p > 0 { page = p } } if pageSizeStr := r.URL.Query().Get("pageSize"); pageSizeStr != "" { if ps, err := strconv.Atoi(pageSizeStr); err == nil && ps > 0 { pageSize = ps } } customers, err := h.storage.GetAllCustomers() if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } total := len(customers) totalPages := (total + pageSize - 1) / pageSize start := (page - 1) * pageSize end := start + pageSize if start >= total { customers = []models.Customer{} } else if end > total { customers = customers[start:] } else { customers = customers[start:end] } response := map[string]interface{}{ "customers": customers, "total": total, "page": page, "pageSize": pageSize, "totalPages": totalPages, } w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(response) } func (h *CustomerHandler) GetCustomerByID(w http.ResponseWriter, r *http.Request) { // Extract customer ID from URL path urlPath := r.URL.Path // The path should be /api/customers/{id} pathParts := strings.Split(urlPath, "/") if len(pathParts) < 4 { http.Error(w, "Invalid URL format", http.StatusBadRequest) return } id := pathParts[3] // Get the ID from the path // Remove query parameters if any if idx := strings.Index(id, "?"); idx != -1 { id = id[:idx] } customer, err := h.storage.GetCustomerByID(id) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } if customer == nil { http.Error(w, "Customer not found", http.StatusNotFound) return } w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(customer) } func (h *CustomerHandler) CreateCustomer(w http.ResponseWriter, r *http.Request) { var req models.CreateCustomerRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { http.Error(w, "Invalid request body", http.StatusBadRequest) return } customer := models.Customer{ CustomerName: req.CustomerName, IntendedProduct: req.IntendedProduct, Version: req.Version, Description: req.Description, Solution: req.Solution, Type: req.Type, Module: req.Module, StatusProgress: req.StatusProgress, Reporter: req.Reporter, CreatedAt: time.Now(), } if err := h.storage.CreateCustomer(customer); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusCreated) json.NewEncoder(w).Encode(customer) } func (h *CustomerHandler) UpdateCustomer(w http.ResponseWriter, r *http.Request) { // Extract customer ID from URL path urlPath := r.URL.Path // The path should be /api/customers/{id} pathParts := strings.Split(urlPath, "/") if len(pathParts) < 4 { http.Error(w, "Invalid URL format", http.StatusBadRequest) return } id := pathParts[3] // Get the ID from the path // Remove query parameters if any if idx := strings.Index(id, "?"); idx != -1 { id = id[:idx] } var req models.UpdateCustomerRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { http.Error(w, "Invalid request body", http.StatusBadRequest) return } if err := h.storage.UpdateCustomer(id, req); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } w.WriteHeader(http.StatusOK) } func (h *CustomerHandler) DeleteCustomer(w http.ResponseWriter, r *http.Request) { // Extract customer ID from URL path urlPath := r.URL.Path // The path should be /api/customers/{id} pathParts := strings.Split(urlPath, "/") if len(pathParts) < 4 { http.Error(w, "Invalid URL format", http.StatusBadRequest) return } id := pathParts[3] // Get the ID from the path // Remove query parameters if any if idx := strings.Index(id, "?"); idx != -1 { id = id[:idx] } if err := h.storage.DeleteCustomer(id); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } w.WriteHeader(http.StatusOK) } func (h *CustomerHandler) ImportCustomers(w http.ResponseWriter, r *http.Request) { err := r.ParseMultipartForm(10 << 20) // 10 MB if err != nil { http.Error(w, "Unable to parse form", http.StatusBadRequest) return } file, handler, err := r.FormFile("file") if err != nil { http.Error(w, "Unable to get file", http.StatusBadRequest) return } defer file.Close() // Check file extension filename := handler.Filename isExcel := strings.HasSuffix(strings.ToLower(filename), ".xlsx") importedCount := 0 duplicateCount := 0 if isExcel { // Parse as Excel file f, err := excelize.OpenReader(file) if err != nil { http.Error(w, "Unable to open Excel file", http.StatusBadRequest) return } defer f.Close() // Get the first sheet sheets := f.GetSheetList() if len(sheets) == 0 { http.Error(w, "No sheets found in Excel file", http.StatusBadRequest) return } // Read all rows from the first sheet rows, err := f.GetRows(sheets[0]) if err != nil { http.Error(w, "Unable to read Excel file", http.StatusBadRequest) return } // Skip header row if exists for i, row := range rows { if i == 0 { continue // Skip header row } if len(row) < 2 { continue // Skip rows with insufficient data } customer := models.Customer{ ID: "", // Will be generated by the storage CreatedAt: time.Now(), CustomerName: getValue(row, 0), IntendedProduct: getValue(row, 1), Version: getValue(row, 2), Description: getValue(row, 3), Solution: getValue(row, 4), Type: getValue(row, 5), Module: getValue(row, 6), StatusProgress: getValue(row, 7), Reporter: getValue(row, 8), } // Check for duplicate exists, err := h.storage.CustomerExists(customer) if err != nil { http.Error(w, "Error checking for duplicate customer", http.StatusInternalServerError) return } if exists { duplicateCount++ continue // Skip duplicate } if err := h.storage.CreateCustomer(customer); err != nil { http.Error(w, "Error saving customer", http.StatusInternalServerError) return } importedCount++ } } else { // Parse as CSV content, err := io.ReadAll(file) if err != nil { http.Error(w, "Unable to read file", http.StatusBadRequest) return } reader := csv.NewReader(strings.NewReader(string(content))) records, err := reader.ReadAll() if err != nil { http.Error(w, "Unable to parse CSV file", http.StatusBadRequest) return } // Skip header row if exists for i, row := range records { if i == 0 { continue // Skip header row } if len(row) < 2 { continue // Skip rows with insufficient data } customer := models.Customer{ ID: "", // Will be generated by the storage CreatedAt: time.Now(), CustomerName: getValue(row, 0), IntendedProduct: getValue(row, 1), Version: getValue(row, 2), Description: getValue(row, 3), Solution: getValue(row, 4), Type: getValue(row, 5), Module: getValue(row, 6), StatusProgress: getValue(row, 7), Reporter: getValue(row, 8), } // Check for duplicate exists, err := h.storage.CustomerExists(customer) if err != nil { http.Error(w, "Error checking for duplicate customer", http.StatusInternalServerError) return } if exists { duplicateCount++ continue // Skip duplicate } if err := h.storage.CreateCustomer(customer); err != nil { http.Error(w, "Error saving customer", http.StatusInternalServerError) return } importedCount++ } } w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) json.NewEncoder(w).Encode(map[string]interface{}{ "message": "Customers imported successfully", "importedCount": importedCount, "duplicateCount": duplicateCount, }) } func getValue(row []string, index int) string { if index < len(row) { return row[index] } return "" }