go-crm improvements

This commit is contained in:
2026-05-01 15:43:13 -03:00
parent ee6673265f
commit 31247a63f6
17 changed files with 416 additions and 63 deletions

Binary file not shown.

View File

@@ -59,6 +59,16 @@ npm run test:coverage # Jest with coverage
```bash ```bash
cd apps/whatsapp-reader cd apps/whatsapp-reader
# No test script defined # No test script defined
node index.js # Run main script
node sync.js # Run sync script
```
### timesfm-forecast (Python/uv)
```bash
cd apps/timesfm-forecast
uv venv && source .venv/bin/activate # Create & activate venv
uv pip install -e . # Install package
timesfm-app # Run Streamlit app
``` ```
--- ---

Binary file not shown.

Binary file not shown.

BIN
apps/go-crm/go-crm Executable file

Binary file not shown.

View File

@@ -5,13 +5,14 @@ import (
) )
type Client struct { type Client struct {
ClientID int64 `json:"client_id"` ClientID int64 `json:"client_id"`
Name string `json:"name"` AccountID int64 `json:"account_id"`
Phone string `json:"phone,omitempty"` Name string `json:"name"`
Email string `json:"email,omitempty"` Phone string `json:"phone,omitempty"`
Address string `json:"address,omitempty"` Email string `json:"email,omitempty"`
Notes string `json:"notes,omitempty"` Address string `json:"address,omitempty"`
CreatedAt int64 `json:"created_at"` Notes string `json:"notes,omitempty"`
CreatedAt int64 `json:"created_at"`
} }
type Customer struct { type Customer struct {
@@ -59,8 +60,8 @@ type Payment struct {
func (c *Client) Create(db *sql.DB) error { func (c *Client) Create(db *sql.DB) error {
result, err := db.Exec( result, err := db.Exec(
"INSERT INTO clients (name, phone, email, address, notes, created_at) VALUES (?, ?, ?, ?, ?, ?)", "INSERT INTO clients (account_id, name, phone, email, address, notes, created_at) VALUES (?, ?, ?, ?, ?, ?, ?)",
c.Name, c.Phone, c.Email, c.Address, c.Notes, c.CreatedAt, c.AccountID, c.Name, c.Phone, c.Email, c.Address, c.Notes, c.CreatedAt,
) )
if err != nil { if err != nil {
return err return err
@@ -75,29 +76,35 @@ func (c *Client) Create(db *sql.DB) error {
func (c *Client) Read(db *sql.DB, id int64) error { func (c *Client) Read(db *sql.DB, id int64) error {
return db.QueryRow( return db.QueryRow(
"SELECT client_id, name, phone, email, address, notes, created_at FROM clients WHERE client_id = ?", "SELECT client_id, account_id, name, COALESCE(phone,''), COALESCE(email,''), COALESCE(address,''), COALESCE(notes,''), created_at FROM clients WHERE client_id = ?",
id, id,
).Scan(&c.ClientID, &c.Name, &c.Phone, &c.Email, &c.Address, &c.Notes, &c.CreatedAt) ).Scan(&c.ClientID, &c.AccountID, &c.Name, &c.Phone, &c.Email, &c.Address, &c.Notes, &c.CreatedAt)
} }
func (c *Client) Update(db *sql.DB) error { func (c *Client) Update(db *sql.DB) error {
_, err := db.Exec( _, err := db.Exec(
"UPDATE clients SET name = ?, phone = ?, email = ?, address = ?, notes = ? WHERE client_id = ?", "UPDATE clients SET name = ?, phone = ?, email = ?, address = ?, notes = ? WHERE client_id = ? AND account_id = ?",
c.Name, c.Phone, c.Email, c.Address, c.Notes, c.ClientID, c.Name, c.Phone, c.Email, c.Address, c.Notes, c.ClientID, c.AccountID,
) )
return err return err
} }
func (c *Client) Delete(db *sql.DB, id int64) error { func (c *Client) Delete(db *sql.DB) error {
_, err := db.Exec("DELETE FROM clients WHERE client_id = ?", id) _, err := db.Exec("DELETE FROM clients WHERE client_id = ? AND account_id = ?", c.ClientID, c.AccountID)
return err return err
} }
func ListClients(db *sql.DB, limit, offset int) ([]Client, error) { func ListClients(db *sql.DB, accountID int64, limit, offset int) ([]Client, error) {
rows, err := db.Query( query := "SELECT client_id, account_id, name, COALESCE(phone,''), COALESCE(email,''), COALESCE(address,''), COALESCE(notes,''), created_at FROM clients"
"SELECT client_id, name, phone, email, address, notes, created_at FROM clients ORDER BY created_at DESC LIMIT ? OFFSET ?", args := []interface{}{}
limit, offset, if accountID > 0 {
) query += " WHERE account_id = ?"
args = append(args, accountID)
}
query += " ORDER BY created_at DESC LIMIT ? OFFSET ?"
args = append(args, limit, offset)
rows, err := db.Query(query, args...)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -106,7 +113,7 @@ func ListClients(db *sql.DB, limit, offset int) ([]Client, error) {
var clients []Client var clients []Client
for rows.Next() { for rows.Next() {
var c Client var c Client
if err := rows.Scan(&c.ClientID, &c.Name, &c.Phone, &c.Email, &c.Address, &c.Notes, &c.CreatedAt); err != nil { if err := rows.Scan(&c.ClientID, &c.AccountID, &c.Name, &c.Phone, &c.Email, &c.Address, &c.Notes, &c.CreatedAt); err != nil {
return nil, err return nil, err
} }
clients = append(clients, c) clients = append(clients, c)
@@ -114,12 +121,12 @@ func ListClients(db *sql.DB, limit, offset int) ([]Client, error) {
return clients, rows.Err() return clients, rows.Err()
} }
func GetClientByID(db *sql.DB, id int64) (*Client, error) { func GetClientByID(db *sql.DB, accountID, id int64) (*Client, error) {
var c Client var c Client
err := db.QueryRow( err := db.QueryRow(
"SELECT client_id, name, phone, email, address, notes, created_at FROM clients WHERE client_id = ?", "SELECT client_id, account_id, name, COALESCE(phone,''), COALESCE(email,''), COALESCE(address,''), COALESCE(notes,''), created_at FROM clients WHERE client_id = ? AND account_id = ?",
id, id, accountID,
).Scan(&c.ClientID, &c.Name, &c.Phone, &c.Email, &c.Address, &c.Notes, &c.CreatedAt) ).Scan(&c.ClientID, &c.AccountID, &c.Name, &c.Phone, &c.Email, &c.Address, &c.Notes, &c.CreatedAt)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -142,13 +149,24 @@ func (cu *Customer) Create(db *sql.DB) error {
return nil return nil
} }
func ListCustomers(db *sql.DB, clientID int64, limit, offset int) ([]Customer, error) { func ListCustomers(db *sql.DB, accountID, clientID int64, limit, offset int) ([]Customer, error) {
args := []interface{}{}
query := "SELECT customer_id, client_id, name, phone, birth_date, instagram, created_at FROM customers" query := "SELECT customer_id, client_id, name, phone, birth_date, instagram, created_at FROM customers"
var args []interface{} cond := ""
if clientID > 0 { if clientID > 0 {
query += " WHERE client_id = ?" cond = "client_id = ?"
args = append(args, clientID) args = append(args, clientID)
} }
if accountID > 0 {
if cond != "" {
cond += " AND "
}
cond += "client_id IN (SELECT client_id FROM clients WHERE account_id = ?)"
args = append(args, accountID)
}
if cond != "" {
query += " WHERE " + cond
}
query += " ORDER BY created_at DESC LIMIT ? OFFSET ?" query += " ORDER BY created_at DESC LIMIT ? OFFSET ?"
args = append(args, limit, offset) args = append(args, limit, offset)
@@ -185,13 +203,24 @@ func (s *Service) Create(db *sql.DB) error {
return nil return nil
} }
func ListServices(db *sql.DB, clientID int64, limit, offset int) ([]Service, error) { func ListServices(db *sql.DB, accountID, clientID int64, limit, offset int) ([]Service, error) {
args := []interface{}{}
query := "SELECT service_id, client_id, name, price, description, duration, created_at FROM services" query := "SELECT service_id, client_id, name, price, description, duration, created_at FROM services"
var args []interface{} cond := ""
if clientID > 0 { if clientID > 0 {
query += " WHERE client_id = ?" cond = "client_id = ?"
args = append(args, clientID) args = append(args, clientID)
} }
if accountID > 0 {
if cond != "" {
cond += " AND "
}
cond += "client_id IN (SELECT client_id FROM clients WHERE account_id = ?)"
args = append(args, accountID)
}
if cond != "" {
query += " WHERE " + cond
}
query += " ORDER BY created_at DESC LIMIT ? OFFSET ?" query += " ORDER BY created_at DESC LIMIT ? OFFSET ?"
args = append(args, limit, offset) args = append(args, limit, offset)
@@ -430,7 +459,8 @@ type AnswerWithDetails struct {
QuestionText string `json:"question_text"` QuestionText string `json:"question_text"`
} }
func ListAnswersWithDetails(db *sql.DB, questionID int64, limit, offset int) ([]AnswerWithDetails, error) { func ListAnswersWithDetails(db *sql.DB, accountID, questionID int64, limit, offset int) ([]AnswerWithDetails, error) {
args := []interface{}{accountID}
query := ` query := `
SELECT SELECT
a.answer_id, a.client_id, a.question_id, a.answer, a.timestamp, a.status, a.created_at, a.answer_id, a.client_id, a.question_id, a.answer, a.timestamp, a.status, a.created_at,
@@ -441,8 +471,7 @@ func ListAnswersWithDetails(db *sql.DB, questionID int64, limit, offset int) ([]
JOIN questions q ON a.question_id = q.question_id JOIN questions q ON a.question_id = q.question_id
JOIN customers cu ON q.customer_id = cu.customer_id JOIN customers cu ON q.customer_id = cu.customer_id
JOIN clients c ON q.client_id = c.client_id JOIN clients c ON q.client_id = c.client_id
WHERE 1=1` WHERE c.account_id = ?`
var args []interface{}
if questionID > 0 { if questionID > 0 {
query += " AND a.question_id = ?" query += " AND a.question_id = ?"
args = append(args, questionID) args = append(args, questionID)

View File

@@ -5,6 +5,7 @@ import (
"fmt" "fmt"
"os" "os"
"path/filepath" "path/filepath"
"strings"
_ "github.com/glebarez/sqlite" _ "github.com/glebarez/sqlite"
) )
@@ -27,12 +28,14 @@ CREATE TABLE IF NOT EXISTS sessions (
CREATE TABLE IF NOT EXISTS clients ( CREATE TABLE IF NOT EXISTS clients (
client_id INTEGER PRIMARY KEY AUTOINCREMENT, client_id INTEGER PRIMARY KEY AUTOINCREMENT,
account_id INTEGER NOT NULL,
name TEXT NOT NULL, name TEXT NOT NULL,
phone TEXT, phone TEXT,
email TEXT, email TEXT,
address TEXT, address TEXT,
notes TEXT, notes TEXT,
created_at INTEGER NOT NULL created_at INTEGER NOT NULL,
FOREIGN KEY (account_id) REFERENCES accounts(account_id)
); );
CREATE TABLE IF NOT EXISTS customers ( CREATE TABLE IF NOT EXISTS customers (
@@ -138,5 +141,20 @@ func Init(path string) (*sql.DB, error) {
return nil, fmt.Errorf("failed to create schema: %w", err) return nil, fmt.Errorf("failed to create schema: %w", err)
} }
if err := migrate(database); err != nil {
return nil, fmt.Errorf("failed to migrate: %w", err)
}
return database, nil return database, nil
}
func migrate(db *sql.DB) error {
_, err := db.Exec("ALTER TABLE clients ADD COLUMN account_id INTEGER")
if err != nil {
if strings.Contains(err.Error(), "duplicate column name") {
return nil
}
return err
}
return nil
} }

View File

@@ -68,7 +68,42 @@ func Signup(w http.ResponseWriter, r *http.Request) {
return return
} }
http.Redirect(w, r, "/auth/login", http.StatusFound) var accountID int64
err = DB.QueryRow("SELECT account_id FROM accounts WHERE email = ?", email).Scan(&accountID)
if err != nil {
http.Error(w, "Failed to create account", http.StatusInternalServerError)
return
}
client := struct {
AccountID int64
Name string
CreatedAt int64
}{
AccountID: accountID,
Name: name,
CreatedAt: time.Now().Unix(),
}
_, err = DB.Exec(
"INSERT INTO clients (account_id, name, created_at) VALUES (?, ?, ?)",
client.AccountID, client.Name, client.CreatedAt,
)
if err != nil {
http.Error(w, "Failed to create client", http.StatusInternalServerError)
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, "/", http.StatusFound)
} }
func LoginPage(w http.ResponseWriter, r *http.Request) { func LoginPage(w http.ResponseWriter, r *http.Request) {
@@ -121,7 +156,7 @@ func Login(w http.ResponseWriter, r *http.Request) {
} }
http.SetCookie(w, &http.Cookie{Name: "session", Value: sessionID, Path: "/"}) http.SetCookie(w, &http.Cookie{Name: "session", Value: sessionID, Path: "/"})
http.Redirect(w, r, "/clients", http.StatusFound) http.Redirect(w, r, "/", http.StatusFound)
} }
func Logout(w http.ResponseWriter, r *http.Request) { func Logout(w http.ResponseWriter, r *http.Request) {
@@ -158,7 +193,67 @@ func getSession(r *http.Request) (int64, error) {
return accountID, err return accountID, err
} }
func GetAccountID(r *http.Request) (int64, error) {
return getSession(r)
}
func requireAuth(w http.ResponseWriter, r *http.Request) (int64, bool) {
accountID, err := getSession(r)
if err != nil {
http.Redirect(w, r, "/auth/login", http.StatusFound)
return 0, false
}
return accountID, true
}
func SetupAuthHandlers(db *sql.DB) { func SetupAuthHandlers(db *sql.DB) {
DB = db DB = db
chi.RegisterMethod("GET") chi.RegisterMethod("GET")
}
func AccountPage(w http.ResponseWriter, r *http.Request) {
accountID, ok := requireAuth(w, r)
if !ok {
return
}
var name, email string
err := DB.QueryRow("SELECT name, email FROM accounts WHERE account_id = ?", accountID).Scan(&name, &email)
if err != nil {
http.Error(w, "Account not found", http.StatusNotFound)
return
}
w.Header().Set("Content-Type", "text/html")
w.Write([]byte(`<!DOCTYPE html><html><head><title>Account</title></head><body><h1>Account Settings</h1><form method="POST" action="/auth/account">
<p>Name: <input type="text" name="name" value="` + name + `"></p>
<p>Email: <input type="email" value="` + email + `" disabled></p>
<p>New Password: <input type="password" name="password" placeholder="Leave blank to keep current"></p>
<button type="submit">Update</button>
</form>
<a href="/">Back to Dashboard</a></body></html>`))
}
func UpdateAccount(w http.ResponseWriter, r *http.Request) {
accountID, ok := requireAuth(w, r)
if !ok {
return
}
r.ParseForm()
name := r.FormValue("name")
password := r.FormValue("password")
if name != "" {
DB.Exec("UPDATE accounts SET name = ? WHERE account_id = ?", name, accountID)
}
if password != "" {
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
if err == nil {
DB.Exec("UPDATE accounts SET password = ? WHERE account_id = ?", string(hashedPassword), accountID)
}
}
http.Redirect(w, r, "/auth/account", http.StatusFound)
} }

View File

@@ -11,13 +11,18 @@ import (
) )
func ListClients(w http.ResponseWriter, r *http.Request) { func ListClients(w http.ResponseWriter, r *http.Request) {
accountID, ok := requireAuth(w, r)
if !ok {
return
}
limit, _ := strconv.Atoi(r.URL.Query().Get("limit")) limit, _ := strconv.Atoi(r.URL.Query().Get("limit"))
offset, _ := strconv.Atoi(r.URL.Query().Get("offset")) offset, _ := strconv.Atoi(r.URL.Query().Get("offset"))
if limit == 0 { if limit == 0 {
limit = 20 limit = 20
} }
clients, err := db.ListClients(DB, limit, offset) clients, err := db.ListClients(DB, accountID, limit, offset)
if err != nil { if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, err.Error(), http.StatusInternalServerError)
return return
@@ -81,8 +86,14 @@ func ListClients(w http.ResponseWriter, r *http.Request) {
} }
func CreateClient(w http.ResponseWriter, r *http.Request) { func CreateClient(w http.ResponseWriter, r *http.Request) {
accountID, ok := requireAuth(w, r)
if !ok {
return
}
r.ParseForm() r.ParseForm()
client := db.Client{ client := db.Client{
AccountID: accountID,
Name: r.FormValue("name"), Name: r.FormValue("name"),
Phone: r.FormValue("phone"), Phone: r.FormValue("phone"),
Email: r.FormValue("email"), Email: r.FormValue("email"),
@@ -101,8 +112,13 @@ func CreateClient(w http.ResponseWriter, r *http.Request) {
} }
func ViewClient(w http.ResponseWriter, r *http.Request) { func ViewClient(w http.ResponseWriter, r *http.Request) {
accountID, ok := requireAuth(w, r)
if !ok {
return
}
id, _ := strconv.ParseInt(chi.URLParam(r, "id"), 10, 64) id, _ := strconv.ParseInt(chi.URLParam(r, "id"), 10, 64)
client, err := db.GetClientByID(DB, id) client, err := db.GetClientByID(DB, accountID, id)
if err != nil { if err != nil {
http.Error(w, "Client not found", http.StatusNotFound) http.Error(w, "Client not found", http.StatusNotFound)
return return
@@ -124,10 +140,16 @@ func ViewClient(w http.ResponseWriter, r *http.Request) {
} }
func UpdateClient(w http.ResponseWriter, r *http.Request) { func UpdateClient(w http.ResponseWriter, r *http.Request) {
accountID, ok := requireAuth(w, r)
if !ok {
return
}
id, _ := strconv.ParseInt(chi.URLParam(r, "id"), 10, 64) id, _ := strconv.ParseInt(chi.URLParam(r, "id"), 10, 64)
r.ParseForm() r.ParseForm()
client := db.Client{ client := db.Client{
ClientID: id, ClientID: id,
AccountID: accountID,
Name: r.FormValue("name"), Name: r.FormValue("name"),
Phone: r.FormValue("phone"), Phone: r.FormValue("phone"),
Email: r.FormValue("email"), Email: r.FormValue("email"),
@@ -144,8 +166,14 @@ func UpdateClient(w http.ResponseWriter, r *http.Request) {
} }
func DeleteClient(w http.ResponseWriter, r *http.Request) { func DeleteClient(w http.ResponseWriter, r *http.Request) {
accountID, ok := requireAuth(w, r)
if !ok {
return
}
id, _ := strconv.ParseInt(chi.URLParam(r, "id"), 10, 64) id, _ := strconv.ParseInt(chi.URLParam(r, "id"), 10, 64)
if err := (&db.Client{}).Delete(DB, id); err != nil { client := &db.Client{ClientID: id, AccountID: accountID}
if err := client.Delete(DB); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest) http.Error(w, err.Error(), http.StatusBadRequest)
return return
} }

View File

@@ -11,6 +11,11 @@ import (
) )
func ListCustomers(w http.ResponseWriter, r *http.Request) { func ListCustomers(w http.ResponseWriter, r *http.Request) {
accountID, ok := requireAuth(w, r)
if !ok {
return
}
limit, _ := strconv.Atoi(r.URL.Query().Get("limit")) limit, _ := strconv.Atoi(r.URL.Query().Get("limit"))
offset, _ := strconv.Atoi(r.URL.Query().Get("offset")) offset, _ := strconv.Atoi(r.URL.Query().Get("offset"))
clientID, _ := strconv.ParseInt(r.URL.Query().Get("client_id"), 10, 64) clientID, _ := strconv.ParseInt(r.URL.Query().Get("client_id"), 10, 64)
@@ -18,13 +23,13 @@ func ListCustomers(w http.ResponseWriter, r *http.Request) {
limit = 20 limit = 20
} }
customers, err := db.ListCustomers(DB, clientID, limit, offset) customers, err := db.ListCustomers(DB, accountID, clientID, limit, offset)
if err != nil { if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, err.Error(), http.StatusInternalServerError)
return return
} }
clients, err := db.ListClients(DB, limit, offset) clients, err := db.ListClients(DB, accountID, limit, offset)
if err != nil { if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, err.Error(), http.StatusInternalServerError)
return return
@@ -44,8 +49,21 @@ func ListCustomers(w http.ResponseWriter, r *http.Request) {
} }
func CreateCustomer(w http.ResponseWriter, r *http.Request) { func CreateCustomer(w http.ResponseWriter, r *http.Request) {
accountID, ok := requireAuth(w, r)
if !ok {
return
}
r.ParseForm() r.ParseForm()
clientID, _ := strconv.ParseInt(r.FormValue("client_id"), 10, 64) clientID, _ := strconv.ParseInt(r.FormValue("client_id"), 10, 64)
var checkID int64
err := 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{ customer := db.Customer{
ClientID: clientID, ClientID: clientID,
Name: r.FormValue("name"), Name: r.FormValue("name"),
@@ -64,6 +82,11 @@ func CreateCustomer(w http.ResponseWriter, r *http.Request) {
} }
func ViewCustomer(w http.ResponseWriter, r *http.Request) { func ViewCustomer(w http.ResponseWriter, r *http.Request) {
_, ok := requireAuth(w, r)
if !ok {
return
}
id, _ := strconv.ParseInt(chi.URLParam(r, "id"), 10, 64) id, _ := strconv.ParseInt(chi.URLParam(r, "id"), 10, 64)
var c db.Customer var c db.Customer
err := 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) err := 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)
@@ -77,6 +100,11 @@ func ViewCustomer(w http.ResponseWriter, r *http.Request) {
} }
func UpdateCustomer(w http.ResponseWriter, r *http.Request) { func UpdateCustomer(w http.ResponseWriter, r *http.Request) {
_, ok := requireAuth(w, r)
if !ok {
return
}
id, _ := strconv.ParseInt(chi.URLParam(r, "id"), 10, 64) id, _ := strconv.ParseInt(chi.URLParam(r, "id"), 10, 64)
r.ParseForm() r.ParseForm()
clientID, _ := strconv.ParseInt(r.FormValue("client_id"), 10, 64) clientID, _ := strconv.ParseInt(r.FormValue("client_id"), 10, 64)
@@ -89,6 +117,11 @@ func UpdateCustomer(w http.ResponseWriter, r *http.Request) {
} }
func DeleteCustomer(w http.ResponseWriter, r *http.Request) { func DeleteCustomer(w http.ResponseWriter, r *http.Request) {
_, ok := requireAuth(w, r)
if !ok {
return
}
id, _ := strconv.ParseInt(chi.URLParam(r, "id"), 10, 64) id, _ := strconv.ParseInt(chi.URLParam(r, "id"), 10, 64)
DB.Exec("DELETE FROM customers WHERE customer_id = ?", id) DB.Exec("DELETE FROM customers WHERE customer_id = ?", id)
} }

View File

@@ -11,6 +11,11 @@ import (
) )
func ListPayments(w http.ResponseWriter, r *http.Request) { func ListPayments(w http.ResponseWriter, r *http.Request) {
accountID, ok := requireAuth(w, r)
if !ok {
return
}
limit, _ := strconv.Atoi(r.URL.Query().Get("limit")) limit, _ := strconv.Atoi(r.URL.Query().Get("limit"))
offset, _ := strconv.Atoi(r.URL.Query().Get("offset")) offset, _ := strconv.Atoi(r.URL.Query().Get("offset"))
clientID, _ := strconv.ParseInt(r.URL.Query().Get("client_id"), 10, 64) clientID, _ := strconv.ParseInt(r.URL.Query().Get("client_id"), 10, 64)
@@ -24,13 +29,13 @@ func ListPayments(w http.ResponseWriter, r *http.Request) {
return return
} }
clients, err := db.ListClients(DB, limit, offset) clients, err := db.ListClients(DB, accountID, limit, offset)
if err != nil { if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, err.Error(), http.StatusInternalServerError)
return return
} }
customers, err := db.ListCustomers(DB, clientID, limit, offset) customers, err := db.ListCustomers(DB, accountID, clientID, limit, offset)
if err != nil { if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, err.Error(), http.StatusInternalServerError)
return return
@@ -69,6 +74,11 @@ func ListPayments(w http.ResponseWriter, r *http.Request) {
} }
func CreatePayment(w http.ResponseWriter, r *http.Request) { func CreatePayment(w http.ResponseWriter, r *http.Request) {
_, ok := requireAuth(w, r)
if !ok {
return
}
r.ParseForm() r.ParseForm()
clientID, _ := strconv.ParseInt(r.FormValue("client_id"), 10, 64) clientID, _ := strconv.ParseInt(r.FormValue("client_id"), 10, 64)
customerID, _ := strconv.ParseInt(r.FormValue("customer_id"), 10, 64) customerID, _ := strconv.ParseInt(r.FormValue("customer_id"), 10, 64)
@@ -96,6 +106,11 @@ func CreatePayment(w http.ResponseWriter, r *http.Request) {
} }
func ViewPayment(w http.ResponseWriter, r *http.Request) { func ViewPayment(w http.ResponseWriter, r *http.Request) {
_, ok := requireAuth(w, r)
if !ok {
return
}
id, _ := strconv.ParseInt(r.URL.Query().Get("id"), 10, 64) id, _ := strconv.ParseInt(r.URL.Query().Get("id"), 10, 64)
var p db.Payment var p db.Payment
var hasPaid int var hasPaid int
@@ -111,6 +126,11 @@ func ViewPayment(w http.ResponseWriter, r *http.Request) {
} }
func UpdatePayment(w http.ResponseWriter, r *http.Request) { func UpdatePayment(w http.ResponseWriter, r *http.Request) {
_, ok := requireAuth(w, r)
if !ok {
return
}
id, _ := strconv.ParseInt(chi.URLParam(r, "id"), 10, 64) id, _ := strconv.ParseInt(chi.URLParam(r, "id"), 10, 64)
r.ParseForm() r.ParseForm()
clientID, _ := strconv.ParseInt(r.FormValue("client_id"), 10, 64) clientID, _ := strconv.ParseInt(r.FormValue("client_id"), 10, 64)
@@ -126,6 +146,11 @@ func UpdatePayment(w http.ResponseWriter, r *http.Request) {
} }
func DeletePayment(w http.ResponseWriter, r *http.Request) { func DeletePayment(w http.ResponseWriter, r *http.Request) {
_, ok := requireAuth(w, r)
if !ok {
return
}
id, _ := strconv.ParseInt(r.URL.Query().Get("id"), 10, 64) id, _ := strconv.ParseInt(r.URL.Query().Get("id"), 10, 64)
DB.Exec("DELETE FROM payments WHERE payment_id = ?", id) DB.Exec("DELETE FROM payments WHERE payment_id = ?", id)
} }

View File

@@ -11,6 +11,11 @@ import (
) )
func ListQuestions(w http.ResponseWriter, r *http.Request) { func ListQuestions(w http.ResponseWriter, r *http.Request) {
accountID, ok := requireAuth(w, r)
if !ok {
return
}
limit, _ := strconv.Atoi(r.URL.Query().Get("limit")) limit, _ := strconv.Atoi(r.URL.Query().Get("limit"))
offset, _ := strconv.Atoi(r.URL.Query().Get("offset")) offset, _ := strconv.Atoi(r.URL.Query().Get("offset"))
clientID, _ := strconv.ParseInt(r.URL.Query().Get("client_id"), 10, 64) clientID, _ := strconv.ParseInt(r.URL.Query().Get("client_id"), 10, 64)
@@ -24,13 +29,13 @@ func ListQuestions(w http.ResponseWriter, r *http.Request) {
return return
} }
clients, err := db.ListClients(DB, limit, offset) clients, err := db.ListClients(DB, accountID, limit, offset)
if err != nil { if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, err.Error(), http.StatusInternalServerError)
return return
} }
customers, err := db.ListCustomers(DB, clientID, limit, offset) customers, err := db.ListCustomers(DB, accountID, clientID, limit, offset)
if err != nil { if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, err.Error(), http.StatusInternalServerError)
return return
@@ -65,6 +70,11 @@ func ListQuestions(w http.ResponseWriter, r *http.Request) {
} }
func CreateQuestion(w http.ResponseWriter, r *http.Request) { func CreateQuestion(w http.ResponseWriter, r *http.Request) {
_, ok := requireAuth(w, r)
if !ok {
return
}
r.ParseForm() r.ParseForm()
clientID, _ := strconv.ParseInt(r.FormValue("client_id"), 10, 64) clientID, _ := strconv.ParseInt(r.FormValue("client_id"), 10, 64)
customerID, _ := strconv.ParseInt(r.FormValue("customer_id"), 10, 64) customerID, _ := strconv.ParseInt(r.FormValue("customer_id"), 10, 64)
@@ -86,6 +96,11 @@ func CreateQuestion(w http.ResponseWriter, r *http.Request) {
} }
func ViewQuestion(w http.ResponseWriter, r *http.Request) { func ViewQuestion(w http.ResponseWriter, r *http.Request) {
_, ok := requireAuth(w, r)
if !ok {
return
}
id, _ := strconv.ParseInt(chi.URLParam(r, "id"), 10, 64) id, _ := strconv.ParseInt(chi.URLParam(r, "id"), 10, 64)
var q db.Question var q db.Question
err := DB.QueryRow("SELECT question_id, client_id, customer_id, question, timestamp, status, created_at FROM questions WHERE question_id = ?", id).Scan(&q.QuestionID, &q.ClientID, &q.CustomerID, &q.Question, &q.Timestamp, &q.Status, &q.CreatedAt) err := DB.QueryRow("SELECT question_id, client_id, customer_id, question, timestamp, status, created_at FROM questions WHERE question_id = ?", id).Scan(&q.QuestionID, &q.ClientID, &q.CustomerID, &q.Question, &q.Timestamp, &q.Status, &q.CreatedAt)
@@ -99,6 +114,11 @@ func ViewQuestion(w http.ResponseWriter, r *http.Request) {
} }
func UpdateQuestion(w http.ResponseWriter, r *http.Request) { func UpdateQuestion(w http.ResponseWriter, r *http.Request) {
_, ok := requireAuth(w, r)
if !ok {
return
}
id, _ := strconv.ParseInt(chi.URLParam(r, "id"), 10, 64) id, _ := strconv.ParseInt(chi.URLParam(r, "id"), 10, 64)
r.ParseForm() r.ParseForm()
clientID, _ := strconv.ParseInt(r.FormValue("client_id"), 10, 64) clientID, _ := strconv.ParseInt(r.FormValue("client_id"), 10, 64)
@@ -112,11 +132,21 @@ func UpdateQuestion(w http.ResponseWriter, r *http.Request) {
} }
func DeleteQuestion(w http.ResponseWriter, r *http.Request) { func DeleteQuestion(w http.ResponseWriter, r *http.Request) {
_, ok := requireAuth(w, r)
if !ok {
return
}
id, _ := strconv.ParseInt(chi.URLParam(r, "id"), 10, 64) id, _ := strconv.ParseInt(chi.URLParam(r, "id"), 10, 64)
DB.Exec("DELETE FROM questions WHERE question_id = ?", id) DB.Exec("DELETE FROM questions WHERE question_id = ?", id)
} }
func ListAnswers(w http.ResponseWriter, r *http.Request) { func ListAnswers(w http.ResponseWriter, r *http.Request) {
accountID, ok := requireAuth(w, r)
if !ok {
return
}
limit, _ := strconv.Atoi(r.URL.Query().Get("limit")) limit, _ := strconv.Atoi(r.URL.Query().Get("limit"))
offset, _ := strconv.Atoi(r.URL.Query().Get("offset")) offset, _ := strconv.Atoi(r.URL.Query().Get("offset"))
questionID, _ := strconv.ParseInt(r.URL.Query().Get("question_id"), 10, 64) questionID, _ := strconv.ParseInt(r.URL.Query().Get("question_id"), 10, 64)
@@ -124,7 +154,7 @@ func ListAnswers(w http.ResponseWriter, r *http.Request) {
limit = 20 limit = 20
} }
answers, err := db.ListAnswersWithDetails(DB, questionID, limit, offset) answers, err := db.ListAnswersWithDetails(DB, accountID, questionID, limit, offset)
if err != nil { if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, err.Error(), http.StatusInternalServerError)
return return
@@ -224,6 +254,11 @@ func ListAnswers(w http.ResponseWriter, r *http.Request) {
} }
func CreateAnswer(w http.ResponseWriter, r *http.Request) { func CreateAnswer(w http.ResponseWriter, r *http.Request) {
accountID, ok := requireAuth(w, r)
if !ok {
return
}
r.ParseForm() r.ParseForm()
questionID, _ := strconv.ParseInt(r.FormValue("question_id"), 10, 64) questionID, _ := strconv.ParseInt(r.FormValue("question_id"), 10, 64)
@@ -234,6 +269,13 @@ func CreateAnswer(w http.ResponseWriter, r *http.Request) {
return return
} }
var checkID int64
err = DB.QueryRow("SELECT client_id FROM clients WHERE client_id = ? AND account_id = ?", clientID, accountID).Scan(&checkID)
if err != nil {
http.Error(w, "Invalid question", http.StatusBadRequest)
return
}
answer := db.Answer{ answer := db.Answer{
ClientID: clientID, ClientID: clientID,
QuestionID: questionID, QuestionID: questionID,
@@ -252,6 +294,11 @@ func CreateAnswer(w http.ResponseWriter, r *http.Request) {
} }
func ViewAnswer(w http.ResponseWriter, r *http.Request) { func ViewAnswer(w http.ResponseWriter, r *http.Request) {
_, ok := requireAuth(w, r)
if !ok {
return
}
id, _ := strconv.ParseInt(chi.URLParam(r, "id"), 10, 64) id, _ := strconv.ParseInt(chi.URLParam(r, "id"), 10, 64)
var a db.Answer var a db.Answer
err := DB.QueryRow("SELECT answer_id, client_id, question_id, answer, timestamp, status, created_at FROM answers WHERE answer_id = ?", id).Scan(&a.AnswerID, &a.ClientID, &a.QuestionID, &a.Answer, &a.Timestamp, &a.Status, &a.CreatedAt) err := DB.QueryRow("SELECT answer_id, client_id, question_id, answer, timestamp, status, created_at FROM answers WHERE answer_id = ?", id).Scan(&a.AnswerID, &a.ClientID, &a.QuestionID, &a.Answer, &a.Timestamp, &a.Status, &a.CreatedAt)
@@ -265,6 +312,11 @@ func ViewAnswer(w http.ResponseWriter, r *http.Request) {
} }
func UpdateAnswer(w http.ResponseWriter, r *http.Request) { func UpdateAnswer(w http.ResponseWriter, r *http.Request) {
_, ok := requireAuth(w, r)
if !ok {
return
}
id, _ := strconv.ParseInt(chi.URLParam(r, "id"), 10, 64) id, _ := strconv.ParseInt(chi.URLParam(r, "id"), 10, 64)
r.ParseForm() r.ParseForm()
questionID, _ := strconv.ParseInt(r.FormValue("question_id"), 10, 64) questionID, _ := strconv.ParseInt(r.FormValue("question_id"), 10, 64)
@@ -277,6 +329,11 @@ func UpdateAnswer(w http.ResponseWriter, r *http.Request) {
} }
func DeleteAnswer(w http.ResponseWriter, r *http.Request) { func DeleteAnswer(w http.ResponseWriter, r *http.Request) {
_, ok := requireAuth(w, r)
if !ok {
return
}
id, _ := strconv.ParseInt(chi.URLParam(r, "id"), 10, 64) id, _ := strconv.ParseInt(chi.URLParam(r, "id"), 10, 64)
DB.Exec("DELETE FROM answers WHERE answer_id = ?", id) DB.Exec("DELETE FROM answers WHERE answer_id = ?", id)
} }

View File

@@ -11,6 +11,11 @@ import (
) )
func ListSchedules(w http.ResponseWriter, r *http.Request) { func ListSchedules(w http.ResponseWriter, r *http.Request) {
accountID, ok := requireAuth(w, r)
if !ok {
return
}
limit, _ := strconv.Atoi(r.URL.Query().Get("limit")) limit, _ := strconv.Atoi(r.URL.Query().Get("limit"))
offset, _ := strconv.Atoi(r.URL.Query().Get("offset")) offset, _ := strconv.Atoi(r.URL.Query().Get("offset"))
clientID, _ := strconv.ParseInt(r.URL.Query().Get("client_id"), 10, 64) clientID, _ := strconv.ParseInt(r.URL.Query().Get("client_id"), 10, 64)
@@ -25,19 +30,19 @@ func ListSchedules(w http.ResponseWriter, r *http.Request) {
return return
} }
clients, err := db.ListClients(DB, limit, offset) clients, err := db.ListClients(DB, accountID, limit, offset)
if err != nil { if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, err.Error(), http.StatusInternalServerError)
return return
} }
customers, err := db.ListCustomers(DB, clientID, limit, offset) customers, err := db.ListCustomers(DB, accountID, clientID, limit, offset)
if err != nil { if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, err.Error(), http.StatusInternalServerError)
return return
} }
services, err := db.ListServices(DB, clientID, limit, offset) services, err := db.ListServices(DB, accountID, clientID, limit, offset)
if err != nil { if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, err.Error(), http.StatusInternalServerError)
return return
@@ -81,6 +86,11 @@ func ListSchedules(w http.ResponseWriter, r *http.Request) {
} }
func CreateSchedule(w http.ResponseWriter, r *http.Request) { func CreateSchedule(w http.ResponseWriter, r *http.Request) {
_, ok := requireAuth(w, r)
if !ok {
return
}
r.ParseForm() r.ParseForm()
clientID, _ := strconv.ParseInt(r.FormValue("client_id"), 10, 64) clientID, _ := strconv.ParseInt(r.FormValue("client_id"), 10, 64)
customerID, _ := strconv.ParseInt(r.FormValue("customer_id"), 10, 64) customerID, _ := strconv.ParseInt(r.FormValue("customer_id"), 10, 64)
@@ -104,6 +114,11 @@ func CreateSchedule(w http.ResponseWriter, r *http.Request) {
} }
func ViewSchedule(w http.ResponseWriter, r *http.Request) { func ViewSchedule(w http.ResponseWriter, r *http.Request) {
_, ok := requireAuth(w, r)
if !ok {
return
}
id, _ := strconv.ParseInt(chi.URLParam(r, "id"), 10, 64) id, _ := strconv.ParseInt(chi.URLParam(r, "id"), 10, 64)
var sch db.Schedule var sch db.Schedule
err := DB.QueryRow("SELECT schedule_id, client_id, customer_id, service_id, plan_date, time, status, created_at FROM scheduling WHERE schedule_id = ?", id).Scan(&sch.ScheduleID, &sch.ClientID, &sch.CustomerID, &sch.ServiceID, &sch.PlanDate, &sch.Time, &sch.Status, &sch.CreatedAt) err := DB.QueryRow("SELECT schedule_id, client_id, customer_id, service_id, plan_date, time, status, created_at FROM scheduling WHERE schedule_id = ?", id).Scan(&sch.ScheduleID, &sch.ClientID, &sch.CustomerID, &sch.ServiceID, &sch.PlanDate, &sch.Time, &sch.Status, &sch.CreatedAt)
@@ -117,6 +132,11 @@ func ViewSchedule(w http.ResponseWriter, r *http.Request) {
} }
func UpdateSchedule(w http.ResponseWriter, r *http.Request) { func UpdateSchedule(w http.ResponseWriter, r *http.Request) {
_, ok := requireAuth(w, r)
if !ok {
return
}
id, _ := strconv.ParseInt(chi.URLParam(r, "id"), 10, 64) id, _ := strconv.ParseInt(chi.URLParam(r, "id"), 10, 64)
r.ParseForm() r.ParseForm()
clientID, _ := strconv.ParseInt(r.FormValue("client_id"), 10, 64) clientID, _ := strconv.ParseInt(r.FormValue("client_id"), 10, 64)
@@ -131,6 +151,11 @@ func UpdateSchedule(w http.ResponseWriter, r *http.Request) {
} }
func DeleteSchedule(w http.ResponseWriter, r *http.Request) { func DeleteSchedule(w http.ResponseWriter, r *http.Request) {
_, ok := requireAuth(w, r)
if !ok {
return
}
id, _ := strconv.ParseInt(chi.URLParam(r, "id"), 10, 64) id, _ := strconv.ParseInt(chi.URLParam(r, "id"), 10, 64)
DB.Exec("DELETE FROM scheduling WHERE schedule_id = ?", id) DB.Exec("DELETE FROM scheduling WHERE schedule_id = ?", id)
} }

View File

@@ -11,6 +11,11 @@ import (
) )
func ListServices(w http.ResponseWriter, r *http.Request) { func ListServices(w http.ResponseWriter, r *http.Request) {
accountID, ok := requireAuth(w, r)
if !ok {
return
}
limit, _ := strconv.Atoi(r.URL.Query().Get("limit")) limit, _ := strconv.Atoi(r.URL.Query().Get("limit"))
offset, _ := strconv.Atoi(r.URL.Query().Get("offset")) offset, _ := strconv.Atoi(r.URL.Query().Get("offset"))
clientID, _ := strconv.ParseInt(r.URL.Query().Get("client_id"), 10, 64) clientID, _ := strconv.ParseInt(r.URL.Query().Get("client_id"), 10, 64)
@@ -18,13 +23,13 @@ func ListServices(w http.ResponseWriter, r *http.Request) {
limit = 20 limit = 20
} }
services, err := db.ListServices(DB, clientID, limit, offset) services, err := db.ListServices(DB, accountID, clientID, limit, offset)
if err != nil { if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, err.Error(), http.StatusInternalServerError)
return return
} }
clients, err := db.ListClients(DB, limit, offset) clients, err := db.ListClients(DB, accountID, limit, offset)
if err != nil { if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, err.Error(), http.StatusInternalServerError)
return return
@@ -44,8 +49,21 @@ func ListServices(w http.ResponseWriter, r *http.Request) {
} }
func CreateService(w http.ResponseWriter, r *http.Request) { func CreateService(w http.ResponseWriter, r *http.Request) {
accountID, ok := requireAuth(w, r)
if !ok {
return
}
r.ParseForm() r.ParseForm()
clientID, _ := strconv.ParseInt(r.FormValue("client_id"), 10, 64) clientID, _ := strconv.ParseInt(r.FormValue("client_id"), 10, 64)
var checkID int64
err := 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
}
price, _ := strconv.ParseFloat(r.FormValue("price"), 64) price, _ := strconv.ParseFloat(r.FormValue("price"), 64)
service := db.Service{ service := db.Service{
ClientID: clientID, ClientID: clientID,
@@ -65,6 +83,11 @@ func CreateService(w http.ResponseWriter, r *http.Request) {
} }
func ViewService(w http.ResponseWriter, r *http.Request) { func ViewService(w http.ResponseWriter, r *http.Request) {
_, ok := requireAuth(w, r)
if !ok {
return
}
id, _ := strconv.ParseInt(chi.URLParam(r, "id"), 10, 64) id, _ := strconv.ParseInt(chi.URLParam(r, "id"), 10, 64)
var s db.Service var s db.Service
err := DB.QueryRow("SELECT service_id, client_id, name, price, description, duration, created_at FROM services WHERE service_id = ?", id).Scan(&s.ServiceID, &s.ClientID, &s.Name, &s.Price, &s.Description, &s.Duration, &s.CreatedAt) err := DB.QueryRow("SELECT service_id, client_id, name, price, description, duration, created_at FROM services WHERE service_id = ?", id).Scan(&s.ServiceID, &s.ClientID, &s.Name, &s.Price, &s.Description, &s.Duration, &s.CreatedAt)
@@ -78,6 +101,11 @@ func ViewService(w http.ResponseWriter, r *http.Request) {
} }
func UpdateService(w http.ResponseWriter, r *http.Request) { func UpdateService(w http.ResponseWriter, r *http.Request) {
_, ok := requireAuth(w, r)
if !ok {
return
}
id, _ := strconv.ParseInt(chi.URLParam(r, "id"), 10, 64) id, _ := strconv.ParseInt(chi.URLParam(r, "id"), 10, 64)
r.ParseForm() r.ParseForm()
clientID, _ := strconv.ParseInt(r.FormValue("client_id"), 10, 64) clientID, _ := strconv.ParseInt(r.FormValue("client_id"), 10, 64)
@@ -91,6 +119,11 @@ func UpdateService(w http.ResponseWriter, r *http.Request) {
} }
func DeleteService(w http.ResponseWriter, r *http.Request) { func DeleteService(w http.ResponseWriter, r *http.Request) {
_, ok := requireAuth(w, r)
if !ok {
return
}
id, _ := strconv.ParseInt(chi.URLParam(r, "id"), 10, 64) id, _ := strconv.ParseInt(chi.URLParam(r, "id"), 10, 64)
DB.Exec("DELETE FROM services WHERE service_id = ?", id) DB.Exec("DELETE FROM services WHERE service_id = ?", id)
} }

View File

@@ -4,7 +4,6 @@ import (
"fmt" "fmt"
"log" "log"
"net/http" "net/http"
"strings"
"go-crm/internal/db" "go-crm/internal/db"
"go-crm/internal/handlers" "go-crm/internal/handlers"
@@ -32,15 +31,14 @@ func main() {
templates.Init() templates.Init()
r.Get("/", func(w http.ResponseWriter, r *http.Request) { r.Get("/", func(w http.ResponseWriter, r *http.Request) {
homeTmpl := templates.Get("home.html") _, err := handlers.GetAccountID(r)
layoutTmpl := templates.Layout("Home", "") if err != nil {
if homeTmpl != nil && layoutTmpl != nil { http.Redirect(w, r, "/auth/login", http.StatusFound)
buf := &strings.Builder{} return
homeTmpl.Execute(buf, map[string]string{"Title": "Home"})
layoutTmpl.Execute(w, map[string]string{"Title": "Home", "Content": buf.String()})
} else {
w.Write([]byte(`<!DOCTYPE html><html><head><title>Home</title></head><body><h1>Welcome to CRM</h1><nav><a href="/clients">Clients</a> | <a href="/customers">Customers</a> | <a href="/services">Services</a> | <a href="/questions">Questions</a> | <a href="/answers">Answers</a></nav></body></html>`))
} }
w.Header().Set("Content-Type", "text/html")
w.Write([]byte(`<!DOCTYPE html><html><head><title>Dashboard</title></head><body><h1>Welcome to CRM</h1><nav><a href="/clients">Clients</a> | <a href="/customers">Customers</a> | <a href="/services">Services</a> | <a href="/scheduling">Scheduling</a> | <a href="/payments">Payments</a> | <a href="/questions">Questions</a> | <a href="/answers">Answers</a> | <a href="/auth/account">Account</a> | <form method="POST" action="/auth/logout" style="display:inline"><button type="submit">Logout</button></form></nav></body></html>`))
}) })
r.Route("/auth", func(r chi.Router) { r.Route("/auth", func(r chi.Router) {
@@ -49,6 +47,8 @@ func main() {
r.Get("/login", handlers.LoginPage) r.Get("/login", handlers.LoginPage)
r.Post("/login", handlers.Login) r.Post("/login", handlers.Login)
r.Post("/logout", handlers.Logout) r.Post("/logout", handlers.Logout)
r.Get("/account", handlers.AccountPage)
r.Post("/account", handlers.UpdateAccount)
}) })
r.Route("/clients", func(r chi.Router) { r.Route("/clients", func(r chi.Router) {

View File

@@ -1 +1 @@
exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1 exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1

Binary file not shown.