diff --git a/apps/go-crm/go.mod b/apps/go-crm/go.mod new file mode 100644 index 0000000..bbb8ffe --- /dev/null +++ b/apps/go-crm/go.mod @@ -0,0 +1,14 @@ +module go-crm + +go 1.22 + +require ( + github.com/go-chi/chi/v5 v5.1.0 + github.com/go-webauthen/webauthen v0.0.0 + golang.org/x/crypto v0.27.0 +) + +require ( + github.com/go-fresty/xtpl v0.6.0 // indirect + github.com/stretchr/testify v1.9.0 // indirect +) \ No newline at end of file diff --git a/apps/go-crm/internal/db/crud.go b/apps/go-crm/internal/db/crud.go new file mode 100644 index 0000000..05a424d --- /dev/null +++ b/apps/go-crm/internal/db/crud.go @@ -0,0 +1,420 @@ +package db + +import ( + "database/sql" + "fmt" +) + +type Client struct { + ClientID int64 `json:"client_id"` + Name string `json:"name"` + Phone string `json:"phone,omitempty"` + Email string `json:"email,omitempty"` + Address string `json:"address,omitempty"` + Notes string `json:"notes,omitempty"` + CreatedAt int64 `json:"created_at"` +} + +type Customer struct { + CustomerID int64 `json:"customer_id"` + ClientID int64 `json:"client_id"` + Name string `json:"name"` + Phone string `json:"phone,omitempty"` + BirthDate string `json:"birth_date,omitempty"` + Instagram string `json:"instagram,omitempty"` + CreatedAt int64 `json:"created_at"` +} + +type Service struct { + ServiceID int64 `json:"service_id"` + ClientID int64 `json:"client_id"` + Name string `json:"name"` + Price float64 `json:"price,omitempty"` + Description string `json:"description,omitempty"` + Duration string `json:"duration,omitempty"` + CreatedAt int64 `json:"created_at"` +} + +type Schedule struct { + ScheduleID int64 `json:"schedule_id"` + CustomerID int64 `json:"customer_id"` + ClientID int64 `json:"client_id"` + PlanDate string `json:"plan_date,omitempty"` + ConfirmedDate string `json:"confirmed_date,omitempty"` + Time string `json:"time,omitempty"` + Status string `json:"status"` + Notes string `json:"notes,omitempty"` + CreatedAt int64 `json:"created_at"` +} + +type Payment struct { + PaymentID int64 `json:"payment_id"` + ClientID int64 `json:"client_id"` + CustomerID int64 `json:"customer_id"` + ScheduleID int64 `json:"schedule_id,omitempty"` + HasPaid bool `json:"has_paid"` + Amount float64 `json:"amount,omitempty"` + PaymentDate string `json:"payment_date,omitempty"` + PaymentMethod string `json:"payment_method,omitempty"` + CreatedAt int64 `json:"created_at"` +} + +type Question struct { + QuestionID int64 `json:"question_id"` + ClientID int64 `json:"client_id"` + CustomerID int64 `json:"customer_id"` + Question string `json:"question"` + Timestamp int64 `json:"timestamp"` + Status string `json:"status"` + CreatedAt int64 `json:"created_at"` +} + +type Answer struct { + AnswerID int64 `json:"answer_id"` + ClientID int64 `json:"client_id"` + QuestionID int64 `json:"question_id"` + Answer string `json:"answer"` + Timestamp int64 `json:"timestamp"` + Status string `json:"status"` + CreatedAt int64 `json:"created_at"` +} + +func (c *Client) Create(db *sql.DB) error { + result, err := db.Exec( + "INSERT INTO clients (name, phone, email, address, notes, created_at) VALUES (?, ?, ?, ?, ?, ?)", + c.Name, c.Phone, c.Email, c.Address, c.Notes, c.CreatedAt, + ) + if err != nil { + return err + } + id, err := result.LastInsertId() + if err != nil { + return err + } + c.ClientID = id + return nil +} + +func (c *Client) Read(db *sql.DB, id int64) error { + return db.QueryRow( + "SELECT client_id, name, phone, email, address, notes, created_at FROM clients WHERE client_id = ?", + id, + ).Scan(&c.ClientID, &c.Name, &c.Phone, &c.Email, &c.Address, &c.Notes, &c.CreatedAt) +} + +func (c *Client) Update(db *sql.DB) error { + _, err := db.Exec( + "UPDATE clients SET name = ?, phone = ?, email = ?, address = ?, notes = ? WHERE client_id = ?", + c.Name, c.Phone, c.Email, c.Address, c.Notes, c.ClientID, + ) + return err +} + +func (c *Client) Delete(db *sql.DB, id int64) error { + _, err := db.Exec("DELETE FROM clients WHERE client_id = ?", id) + return err +} + +func ListClients(db *sql.DB, limit, offset int) ([]Client, error) { + rows, err := db.Query( + "SELECT client_id, name, phone, email, address, notes, created_at FROM clients ORDER BY created_at DESC LIMIT ? OFFSET ?", + limit, offset, + ) + if err != nil { + return nil, err + } + defer rows.Close() + + var clients []Client + for rows.Next() { + var c Client + if err := rows.Scan(&c.ClientID, &c.Name, &c.Phone, &c.Email, &c.Address, &c.Notes, &c.CreatedAt); err != nil { + return nil, err + } + clients = append(clients, c) + } + return clients, rows.Err() +} + +func GetClientByID(db *sql.DB, id int64) (*Client, error) { + var c Client + err := db.QueryRow( + "SELECT client_id, name, phone, email, address, notes, created_at FROM clients WHERE client_id = ?", + id, + ).Scan(&c.ClientID, &c.Name, &c.Phone, &c.Email, &c.Address, &c.Notes, &c.CreatedAt) + if err != nil { + return nil, err + } + return &c, nil +} + +func (cu *Customer) Create(db *sql.DB) error { + result, err := db.Exec( + "INSERT INTO customers (client_id, name, phone, birth_date, instagram, created_at) VALUES (?, ?, ?, ?, ?, ?)", + cu.ClientID, cu.Name, cu.Phone, cu.BirthDate, cu.Instagram, cu.CreatedAt, + ) + if err != nil { + return err + } + id, err := result.LastInsertId() + if err != nil { + return err + } + cu.CustomerID = id + return nil +} + +func ListCustomers(db *sql.DB, clientID int64, limit, offset int) ([]Customer, error) { + query := "SELECT customer_id, client_id, name, phone, birth_date, instagram, created_at FROM customers" + var args []interface{} + if clientID > 0 { + query += " WHERE client_id = ?" + args = append(args, clientID) + } + query += " ORDER BY created_at DESC LIMIT ? OFFSET ?" + args = append(args, limit, offset) + + rows, err := db.Query(query, args...) + if err != nil { + return nil, err + } + defer rows.Close() + + var customers []Customer + for rows.Next() { + var cu Customer + if err := rows.Scan(&cu.CustomerID, &cu.ClientID, &cu.Name, &cu.Phone, &cu.BirthDate, &cu.Instagram, &cu.CreatedAt); err != nil { + return nil, err + } + customers = append(customers, cu) + } + return customers, rows.Err() +} + +func (s *Service) Create(db *sql.DB) error { + result, err := db.Exec( + "INSERT INTO services (client_id, name, price, description, duration, created_at) VALUES (?, ?, ?, ?, ?, ?)", + s.ClientID, s.Name, s.Price, s.Description, s.Duration, s.CreatedAt, + ) + if err != nil { + return err + } + id, err := result.LastInsertId() + if err != nil { + return err + } + s.ServiceID = id + return nil +} + +func ListServices(db *sql.DB, clientID int64, limit, offset int) ([]Service, error) { + query := "SELECT service_id, client_id, name, price, description, duration, created_at FROM services" + var args []interface{} + if clientID > 0 { + query += " WHERE client_id = ?" + args = append(args, clientID) + } + query += " ORDER BY created_at DESC LIMIT ? OFFSET ?" + args = append(args, limit, offset) + + rows, err := db.Query(query, args...) + if err != nil { + return nil, err + } + defer rows.Close() + + var services []Service + for rows.Next() { + var s Service + if err := rows.Scan(&s.ServiceID, &s.ClientID, &s.Name, &s.Price, &s.Description, &s.Duration, &s.CreatedAt); err != nil { + return nil, err + } + services = append(services, s) + } + return services, rows.Err() +} + +func (sch *Schedule) Create(db *sql.DB) error { + result, err := db.Exec( + "INSERT INTO scheduling (customer_id, client_id, plan_date, confirmed_date, time, status, notes, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?)", + sch.CustomerID, sch.ClientID, sch.PlanDate, sch.ConfirmedDate, sch.Time, sch.Status, sch.Notes, sch.CreatedAt, + ) + if err != nil { + return err + } + id, err := result.LastInsertId() + if err != nil { + return err + } + sch.ScheduleID = id + return nil +} + +func ListSchedules(db *sql.DB, clientID, customerID int64, limit, offset int) ([]Schedule, error) { + query := "SELECT schedule_id, customer_id, client_id, plan_date, confirmed_date, time, status, notes, created_at FROM scheduling WHERE 1=1" + var args []interface{} + if clientID > 0 { + query += " AND client_id = ?" + args = append(args, clientID) + } + if customerID > 0 { + query += " AND customer_id = ?" + args = append(args, customerID) + } + query += " ORDER BY plan_date DESC LIMIT ? OFFSET ?" + args = append(args, limit, offset) + + rows, err := db.Query(query, args...) + if err != nil { + return nil, err + } + defer rows.Close() + + var schedules []Schedule + for rows.Next() { + var sch Schedule + if err := rows.Scan(&sch.ScheduleID, &sch.CustomerID, &sch.ClientID, &sch.PlanDate, &sch.ConfirmedDate, &sch.Time, &sch.Status, &sch.Notes, &sch.CreatedAt); err != nil { + return nil, err + } + schedules = append(schedules, sch) + } + return schedules, rows.Err() +} + +func (p *Payment) Create(db *sql.DB) error { + result, err := db.Exec( + "INSERT INTO payments (client_id, customer_id, schedule_id, has_paid, amount, payment_date, payment_method, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?)", + p.ClientID, p.CustomerID, p.ScheduleID, boolToInt(p.HasPaid), p.Amount, p.PaymentDate, p.PaymentMethod, p.CreatedAt, + ) + if err != nil { + return err + } + id, err := result.LastInsertId() + if err != nil { + return err + } + p.PaymentID = id + return nil +} + +func ListPayments(db *sql.DB, clientID int64, limit, offset int) ([]Payment, error) { + query := "SELECT payment_id, client_id, customer_id, schedule_id, has_paid, amount, payment_date, payment_method, created_at FROM payments" + var args []interface{} + if clientID > 0 { + query += " WHERE client_id = ?" + args = append(args, clientID) + } + query += " ORDER BY created_at DESC LIMIT ? OFFSET ?" + args = append(args, limit, offset) + + rows, err := db.Query(query, args...) + if err != nil { + return nil, err + } + defer rows.Close() + + var payments []Payment + for rows.Next() { + var p Payment + var hasPaid int + if err := rows.Scan(&p.PaymentID, &p.ClientID, &p.CustomerID, &p.ScheduleID, &hasPaid, &p.Amount, &p.PaymentDate, &p.PaymentMethod, &p.CreatedAt); err != nil { + return nil, err + } + p.HasPaid = hasPaid == 1 + payments = append(payments, p) + } + return payments, rows.Err() +} + +func (q *Question) Create(db *sql.DB) error { + result, err := db.Exec( + "INSERT INTO questions (client_id, customer_id, question, timestamp, status, created_at) VALUES (?, ?, ?, ?, ?, ?)", + q.ClientID, q.CustomerID, q.Question, q.Timestamp, q.Status, q.CreatedAt, + ) + if err != nil { + return err + } + id, err := result.LastInsertId() + if err != nil { + return err + } + q.QuestionID = id + return nil +} + +func ListQuestions(db *sql.DB, clientID int64, limit, offset int) ([]Question, error) { + query := "SELECT question_id, client_id, customer_id, question, timestamp, status, created_at FROM questions" + var args []interface{} + if clientID > 0 { + query += " WHERE client_id = ?" + args = append(args, clientID) + } + query += " ORDER BY created_at DESC LIMIT ? OFFSET ?" + args = append(args, limit, offset) + + rows, err := db.Query(query, args...) + if err != nil { + return nil, err + } + defer rows.Close() + + var questions []Question + for rows.Next() { + var q Question + if err := rows.Scan(&q.QuestionID, &q.ClientID, &q.CustomerID, &q.Question, &q.Timestamp, &q.Status, &q.CreatedAt); err != nil { + return nil, err + } + questions = append(questions, q) + } + return questions, rows.Err() +} + +func (a *Answer) Create(db *sql.DB) error { + result, err := db.Exec( + "INSERT INTO answers (client_id, question_id, answer, timestamp, status, created_at) VALUES (?, ?, ?, ?, ?, ?, ?)", + a.ClientID, a.QuestionID, a.Answer, a.Timestamp, a.Status, a.CreatedAt, + ) + if err != nil { + return err + } + id, err := result.LastInsertId() + if err != nil { + return err + } + a.AnswerID = id + return nil +} + +func ListAnswers(db *sql.DB, questionID int64, limit, offset int) ([]Answer, error) { + query := "SELECT answer_id, client_id, question_id, answer, timestamp, status, created_at FROM answers" + var args []interface{} + if questionID > 0 { + query += " WHERE question_id = ?" + args = append(args, questionID) + } + query += " ORDER BY created_at DESC LIMIT ? OFFSET ?" + args = append(args, limit, offset) + + rows, err := db.Query(query, args...) + if err != nil { + return nil, err + } + defer rows.Close() + + var answers []Answer + for rows.Next() { + var a Answer + if err := rows.Scan(&a.AnswerID, &a.ClientID, &a.QuestionID, &a.Answer, &a.Timestamp, &a.Status, &a.CreatedAt); err != nil { + return nil, err + } + answers = append(answers, a) + } + return answers, rows.Err() +} + +func boolToInt(b bool) int { + if b { + return 1 + } + return 0 +} \ No newline at end of file diff --git a/apps/go-crm/internal/db/db.go b/apps/go-crm/internal/db/db.go new file mode 100644 index 0000000..463c473 --- /dev/null +++ b/apps/go-crm/internal/db/db.go @@ -0,0 +1,140 @@ +package db + +import ( + "database/sql" + "fmt" + "os" + "path/filepath" + + _ "github.com/mattn/go-sqlite3" +) + +const schema = ` +CREATE TABLE IF NOT EXISTS accounts ( + account_id INTEGER PRIMARY KEY AUTOINCREMENT, + email TEXT UNIQUE NOT NULL, + name TEXT NOT NULL, + password TEXT NOT NULL, + created_at INTEGER NOT NULL +); + +CREATE TABLE IF NOT EXISTS sessions ( + session_id TEXT PRIMARY KEY, + account_id INTEGER NOT NULL, + expires INTEGER NOT NULL, + FOREIGN KEY (account_id) REFERENCES accounts(account_id) +); + +CREATE TABLE IF NOT EXISTS clients ( + client_id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT NOT NULL, + phone TEXT, + email TEXT, + address TEXT, + notes TEXT, + created_at INTEGER NOT NULL +); + +CREATE TABLE IF NOT EXISTS customers ( + customer_id INTEGER PRIMARY KEY AUTOINCREMENT, + client_id INTEGER NOT NULL, + name TEXT NOT NULL, + phone TEXT, + birth_date TEXT, + instagram TEXT, + created_at INTEGER NOT NULL, + FOREIGN KEY (client_id) REFERENCES clients(client_id) +); + +CREATE TABLE IF NOT EXISTS services ( + service_id INTEGER PRIMARY KEY AUTOINCREMENT, + client_id INTEGER NOT NULL, + name TEXT NOT NULL, + price REAL, + description TEXT, + duration TEXT, + created_at INTEGER NOT NULL, + FOREIGN KEY (client_id) REFERENCES clients(client_id) +); + +CREATE TABLE IF NOT EXISTS scheduling ( + schedule_id INTEGER PRIMARY KEY AUTOINCREMENT, + customer_id INTEGER NOT NULL, + client_id INTEGER NOT NULL, + plan_date TEXT, + confirmed_date TEXT, + time TEXT, + status TEXT DEFAULT 'pending', + notes TEXT, + created_at INTEGER NOT NULL, + FOREIGN KEY (customer_id) REFERENCES customers(customer_id), + FOREIGN KEY (client_id) REFERENCES clients(client_id) +); + +CREATE TABLE IF NOT EXISTS payments ( + payment_id INTEGER PRIMARY KEY AUTOINCREMENT, + client_id INTEGER NOT NULL, + customer_id INTEGER NOT NULL, + schedule_id INTEGER, + has_paid INTEGER DEFAULT 0, + amount REAL, + payment_date TEXT, + payment_method TEXT, + created_at INTEGER NOT NULL, + FOREIGN KEY (client_id) REFERENCES clients(client_id), + FOREIGN KEY (customer_id) REFERENCES customers(customer_id), + FOREIGN KEY (schedule_id) REFERENCES scheduling(schedule_id) +); + +CREATE TABLE IF NOT EXISTS questions ( + question_id INTEGER PRIMARY KEY AUTOINCREMENT, + client_id INTEGER NOT NULL, + customer_id INTEGER NOT NULL, + question TEXT NOT NULL, + timestamp INTEGER NOT NULL, + status TEXT DEFAULT 'pending', + created_at INTEGER NOT NULL, + FOREIGN KEY (client_id) REFERENCES clients(client_id), + FOREIGN KEY (customer_id) REFERENCES customers(customer_id) +); + +CREATE TABLE IF NOT EXISTS answers ( + answer_id INTEGER PRIMARY KEY AUTOINCREMENT, + client_id INTEGER NOT NULL, + question_id INTEGER NOT NULL, + answer TEXT NOT NULL, + timestamp INTEGER NOT NULL, + status TEXT DEFAULT 'active', + created_at INTEGER NOT NULL, + FOREIGN KEY (client_id) REFERENCES clients(client_id), + FOREIGN KEY (question_id) REFERENCES questions(question_id) +); +` + +var dbPath = filepath.Join(os.Getenv("WORKDIR"), "data", "go-crm.db") + +func Init(path string) (*sql.DB, error) { + if path != "" { + dbPath = path + } + + dir := filepath.Dir(dbPath) + if err := os.MkdirAll(dir, 0755); err != nil { + return nil, fmt.Errorf("failed to create data directory: %w", err) + } + + database, err := sql.Open("sqlite3", dbPath) + if err != nil { + return nil, fmt.Errorf("failed to open database: %w", err) + } + + if err := database.Ping(); err != nil { + return nil, fmt.Errorf("failed to ping database: %w", err) + } + + if _, err := database.Exec(schema); err != nil { + return nil, fmt.Errorf("failed to create schema: %w", err) + } + + return database, nil +} \ No newline at end of file diff --git a/apps/go-crm/internal/handlers/auth.go b/apps/go-crm/internal/handlers/auth.go new file mode 100644 index 0000000..20ba7e5 --- /dev/null +++ b/apps/go-crm/internal/handlers/auth.go @@ -0,0 +1,166 @@ +package handlers + +import ( + "database/sql" + "net/http" + "time" + + "go-crm/internal/db" + + "github.com/go-chi/chi/v5" + "golang.org/x/crypto/bcrypt" +) + +func SignupPage(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "text/html") + w.Write([]byte(` + +
+ +Already have an account? Login
+ +`)) +} + +func Signup(w http.ResponseWriter, r *http.Request) { + r.ParseForm() + email := r.FormValue("email") + name := r.FormValue("name") + password := r.FormValue("password") + + if email == "" || name == "" || password == "" { + http.Error(w, "All fields required", http.StatusBadRequest) + return + } + + hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) + if err != nil { + http.Error(w, "Failed to hash password", http.StatusInternalServerError) + return + } + + account := struct { + Email string + Name string + Password string + CreatedAt int64 + }{ + Email: email, + Name: name, + Password: string(hashedPassword), + CreatedAt: time.Now().Unix(), + } + + _, err = db.DB.Exec( + "INSERT INTO accounts (email, name, password, created_at) VALUES (?, ?, ?, ?)", + account.Email, account.Name, account.Password, account.CreatedAt, + ) + if err != nil { + http.Error(w, "Email already exists", http.StatusBadRequest) + return + } + + http.Redirect(w, r, "/auth/login", http.StatusFound) +} + +func LoginPage(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "text/html") + w.Write([]byte(` + + + +Don't have an account? Sign Up
+ +`)) +} + +var DB *sql.DB + +func Login(w http.ResponseWriter, r *http.Request) { + r.ParseForm() + email := r.FormValue("email") + password := r.FormValue("password") + + var accountID int64 + var hashedPassword string + err := DB.QueryRow("SELECT account_id, password FROM accounts WHERE email = ?", email).Scan(&accountID, &hashedPassword) + if err != nil { + http.Error(w, "Invalid credentials", http.StatusUnauthorized) + return + } + + if err := bcrypt.CompareHashAndPassword([]byte(hashedPassword), []byte(password)); err != nil { + http.Error(w, "Invalid credentials", http.StatusUnauthorized) + return + } + + sessionID := generateSessionID() + expires := time.Now().Add(24 * time.Hour).Unix() + + _, err = DB.Exec("INSERT INTO sessions (session_id, account_id, expires) VALUES (?, ?, ?)", sessionID, accountID, expires) + if err != nil { + http.Error(w, "Failed to create session", http.StatusInternalServerError) + return + } + + http.SetCookie(w, &http.Cookie{Name: "session", Value: sessionID, Path: "/"}) + http.Redirect(w, r, "/clients", http.StatusFound) +} + +func Logout(w http.ResponseWriter, r *http.Request) { + cookie, err := r.Cookie("session") + if err == nil { + DB.Exec("DELETE FROM sessions WHERE session_id = ?", cookie.Value) + } + + http.SetCookie(w, &http.Cookie{Name: "session", Value: "", Path: "/", MaxAge: -1}) + http.Redirect(w, r, "/auth/login", http.StatusFound) +} + +func generateSessionID() string { + return time.Now().Format("20060102150405") + "-" + randomString(32) +} + +func randomString(n int) string { + const letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" + b := make([]byte, n) + for i := range b { + b[i] = letters[time.Now().UnixNano()%int64(len(letters))] + } + return string(b) +} + +func getSession(r *http.Request) (int64, error) { + cookie, err := r.Cookie("session") + if err != nil { + return 0, err + } + + var accountID int64 + err = DB.QueryRow("SELECT account_id FROM sessions WHERE session_id = ? AND expires > ?", cookie.Value, time.Now().Unix()).Scan(&accountID) + return accountID, err +} + +func SetupAuthHandlers(db *sql.DB) { + DB = db + chi.RegisterMethod("GET") +} \ No newline at end of file diff --git a/apps/go-crm/internal/handlers/clients.go b/apps/go-crm/internal/handlers/clients.go new file mode 100644 index 0000000..0e36f28 --- /dev/null +++ b/apps/go-crm/internal/handlers/clients.go @@ -0,0 +1,142 @@ +package handlers + +import ( + "database/sql" + "encoding/json" + "net/http" + "strconv" + "time" + + "go-crm/internal/db" + + "github.com/go-chi/chi/v5" +) + +func ListClients(w http.ResponseWriter, r *http.Request) { + limit, _ := strconv.Atoi(r.URL.Query().Get("limit")) + offset, _ := strconv.Atoi(r.URL.Query().Get("offset")) + if limit == 0 { + limit = 20 + } + + clients, err := db.ListClients(DB, limit, offset) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + w.Header().Set("Content-Type", "text/html") + w.Write([]byte(` + + +| Name | Phone | Actions | |
|---|---|---|---|
| ` + c.Name + ` | +` + c.Phone + ` | +` + c.Email + ` | ++ View + + | +
Phone: ` + client.Phone + `
+Email: ` + client.Email + `
+Address: ` + client.Address + `
+Notes: ` + client.Notes + `
+ Back +`)) +} + +func UpdateClient(w http.ResponseWriter, r *http.Request) { + id, _ := strconv.ParseInt(chi.URLParam(r, "id"), 10) + r.ParseForm() + client := db.Client{ + ClientID: id, + Name: r.FormValue("name"), + Phone: r.FormValue("phone"), + Email: r.FormValue("email"), + Address: r.FormValue("address"), + Notes: r.FormValue("notes"), + } + + if err := client.Update(DB); err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(client) +} + +func DeleteClient(w http.ResponseWriter, r *http.Request) { + id, _ := strconv.ParseInt(chi.URLParam(r, "id"), 10) + if err := (&db.Client{}).Delete(DB, id); err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + + w.Header().Set("Content-Type", "text/html") + w.Write([]byte("OK")) +} \ No newline at end of file diff --git a/apps/go-crm/internal/handlers/customers.go b/apps/go-crm/internal/handlers/customers.go new file mode 100644 index 0000000..a690a90 --- /dev/null +++ b/apps/go-crm/internal/handlers/customers.go @@ -0,0 +1,84 @@ +package handlers + +import ( + "database/sql" + "encoding/json" + "net/http" + "strconv" + "time" + + "go-crm/internal/db" + + "github.com/go-chi/chi/v5" +) + +func ListCustomers(w http.ResponseWriter, r *http.Request) { + 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(DB, clientID, limit, offset) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + w.Header().Set("Content-Type", "text/html") + w.Write([]byte(`| Name | Phone | Birth Date | Actions | |
|---|---|---|---|---|
| ` + c.Name + ` | ` + c.Phone + ` | ` + c.BirthDate + ` | ` + c.Instagram + ` | View |
Phone: ` + c.Phone + `
Birth Date: ` + c.BirthDate + `
Instagram: ` + c.Instagram + `
Back