package handlers import ( "fmt" "net/http" "strconv" "time" "go-crm/config" "go-crm/internal/db" "go-crm/internal/templates" "github.com/go-chi/chi/v5" ) func (a *App) ListCustomers(w http.ResponseWriter, r *http.Request) { accountID, ok := a.requireAuth(w, r) if !ok { return } limit, _ := strconv.Atoi(r.URL.Query().Get("limit")) offset, _ := strconv.Atoi(r.URL.Query().Get("offset")) clientID, _ := strconv.ParseInt(r.URL.Query().Get("client_id"), 10, 64) if limit == 0 { limit = 20 } customers, err := db.ListCustomers(a.DB, accountID, clientID, limit, offset) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } clients, err := db.ListClients(a.DB, accountID, limit, offset) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } var clientOptions string for _, c := range clients { clientOptions += fmt.Sprintf(``, c.ClientID, htmlEscape(c.Name)) } buf := templates.BufRender() actions := `` fmt.Fprint(buf, templates.PageHeader("Customers", "Your customer database", actions)) fmt.Fprintf(buf, ` `, clientOptions) if len(customers) == 0 { fmt.Fprint(buf, templates.EmptyState(``, "No customers found. Add your first customer.")) } else { fmt.Fprintf(buf, `
`) fmt.Fprint(buf, templates.TableStart([]string{"Name", "Phone", "Birth Date", "Instagram", "Actions"})) for _, c := range customers { fmt.Fprintf(buf, ` %s %s %s %s
View
`, htmlEscape(c.Name), htmlEscape(c.Phone), htmlEscape(c.BirthDate), htmlEscape(c.Instagram), c.CustomerID, c.CustomerID, c.CustomerID) } fmt.Fprint(buf, templates.TableEnd()) fmt.Fprintf(buf, `
`) for _, c := range customers { fmt.Fprintf(buf, ` `, c.CustomerID, htmlEscape(c.Name), c.CustomerID, htmlEscape(c.Name), htmlEscape(c.Phone), htmlEscape(c.BirthDate), htmlEscape(c.Instagram), c.ClientID, htmlEscape(c.Name), clientOptions, c.CustomerID) } } w.Header().Set("Content-Type", "text/html; charset=utf-8") templates.WritePage(w, buf, "Customers", "customers") } func (a *App) CreateCustomer(w http.ResponseWriter, r *http.Request) { isInternal := r.Header.Get("X-Internal-Secret") == config.InternalSecret() var accountID int64 var clientID int64 var ok bool if isInternal { clientID, _ = strconv.ParseInt(r.FormValue("client_id"), 10, 64) accountID = clientID ok = true } else { accountID, ok = a.requireAuth(w, r) if ok { clientID, _ = strconv.ParseInt(r.FormValue("client_id"), 10, 64) } } if !ok { return } r.ParseForm() if clientID == 0 { clientID, _ = strconv.ParseInt(r.FormValue("client_id"), 10, 64) } if !isInternal { var checkID int64 err := a.DB.QueryRow("SELECT client_id FROM clients WHERE client_id = ? AND account_id = ?", clientID, accountID).Scan(&checkID) if err != nil { http.Error(w, "Invalid client", http.StatusBadRequest) return } } customer := db.Customer{ ClientID: clientID, Name: r.FormValue("name"), Phone: r.FormValue("phone"), BirthDate: r.FormValue("birth_date"), Instagram: r.FormValue("instagram"), CreatedAt: time.Now().Unix(), } if phone := r.FormValue("phone"); phone != "" { var existingID int64 err := a.DB.QueryRow( "SELECT customer_id FROM customers WHERE client_id = ? AND phone = ?", clientID, phone, ).Scan(&existingID) if err == nil { _, err = a.DB.Exec( "UPDATE customers SET name = ?, birth_date = ?, instagram = ? WHERE customer_id = ?", r.FormValue("name"), r.FormValue("birth_date"), r.FormValue("instagram"), existingID, ) if err == nil { w.Header().Set("HX-Refresh", "true") return } } } if err := customer.Create(a.DB); err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } w.Header().Set("HX-Refresh", "true") } func (a *App) ViewCustomer(w http.ResponseWriter, r *http.Request) { _, ok := a.requireAuth(w, r) if !ok { return } id, _ := strconv.ParseInt(chi.URLParam(r, "id"), 10, 64) var c db.Customer err := a.DB.QueryRow("SELECT customer_id, client_id, name, phone, birth_date, instagram, created_at FROM customers WHERE customer_id = ?", id).Scan(&c.CustomerID, &c.ClientID, &c.Name, &c.Phone, &c.BirthDate, &c.Instagram, &c.CreatedAt) if err != nil { http.Error(w, "Customer not found", http.StatusNotFound) return } buf := templates.BufRender() fmt.Fprint(buf, templates.PageHeader(htmlEscape(c.Name), "Customer profile")) fmt.Fprintf(buf, `
Phone %s
Birth Date %s
Instagram %s
`, htmlEscape(c.Phone), htmlEscape(c.BirthDate), htmlEscape(c.Instagram)) w.Header().Set("Content-Type", "text/html; charset=utf-8") templates.WritePage(w, buf, c.Name, "customers") } func (a *App) UpdateCustomer(w http.ResponseWriter, r *http.Request) { _, ok := a.requireAuth(w, r) if !ok { return } id, _ := strconv.ParseInt(chi.URLParam(r, "id"), 10, 64) r.ParseForm() clientID, _ := strconv.ParseInt(r.FormValue("client_id"), 10, 64) _, err := a.DB.Exec("UPDATE customers SET client_id=?, name=?, phone=?, birth_date=?, instagram=? WHERE customer_id=?", clientID, r.FormValue("name"), r.FormValue("phone"), r.FormValue("birth_date"), r.FormValue("instagram"), id) if err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } w.Header().Set("HX-Refresh", "true") } func (a *App) DeleteCustomer(w http.ResponseWriter, r *http.Request) { _, ok := a.requireAuth(w, r) if !ok { return } id, _ := strconv.ParseInt(chi.URLParam(r, "id"), 10, 64) a.DB.Exec("DELETE FROM customers WHERE customer_id = ?", id) } // --- package-level shims --- func ListCustomers(w http.ResponseWriter, r *http.Request) { (&App{DB: DB, WAConnector: WAConnector}).ListCustomers(w, r) } func CreateCustomer(w http.ResponseWriter, r *http.Request) { (&App{DB: DB, WAConnector: WAConnector}).CreateCustomer(w, r) } func ViewCustomer(w http.ResponseWriter, r *http.Request) { (&App{DB: DB, WAConnector: WAConnector}).ViewCustomer(w, r) } func UpdateCustomer(w http.ResponseWriter, r *http.Request) { (&App{DB: DB, WAConnector: WAConnector}).UpdateCustomer(w, r) } func DeleteCustomer(w http.ResponseWriter, r *http.Request) { (&App{DB: DB, WAConnector: WAConnector}).DeleteCustomer(w, r) }