package handlers import ( "database/sql" "encoding/json" "net/http" "time" "github.com/go-chi/chi/v5" ) type SupportHandler struct { DB *sql.DB } func NewSupportHandler(db *sql.DB) *SupportHandler { return &SupportHandler{DB: db} } type Ticket struct { ID string `json:"id"` CustomerID *string `json:"customer_id"` ContactID *string `json:"contact_id"` Subject string `json:"subject"` Description string `json:"description"` Status string `json:"status"` Priority string `json:"priority"` Category string `json:"category"` Source string `json:"source"` AssignedTo *string `json:"assigned_to"` ResolvedAt *time.Time `json:"resolved_at"` Resolution string `json:"resolution"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` } type TicketComment struct { ID string `json:"id"` TicketID string `json:"ticket_id"` Content string `json:"content"` IsInternal bool `json:"is_internal"` CreatedBy *string `json:"created_by"` CreatedAt time.Time `json:"created_at"` } func (h *SupportHandler) ListTickets(w http.ResponseWriter, r *http.Request) { status := r.URL.Query().Get("status") if status == "" { status = "open" } rows, err := h.DB.Query(` SELECT id, customer_id, contact_id, subject, description, status, priority, category, source, assigned_to, resolved_at, resolution, created_at, updated_at FROM boc_tickets WHERE status = $1 ORDER BY CASE priority WHEN 'critical' THEN 1 WHEN 'high' THEN 2 WHEN 'medium' THEN 3 WHEN 'low' THEN 4 ELSE 5 END, created_at DESC LIMIT 100 `, status) if err != nil { writeError(w, http.StatusInternalServerError, "database error") return } defer rows.Close() tickets := []Ticket{} for rows.Next() { var t Ticket if err := rows.Scan(&t.ID, &t.CustomerID, &t.ContactID, &t.Subject, &t.Description, &t.Status, &t.Priority, &t.Category, &t.Source, &t.AssignedTo, &t.ResolvedAt, &t.Resolution, &t.CreatedAt, &t.UpdatedAt); err != nil { continue } tickets = append(tickets, t) } writeJSON(w, http.StatusOK, map[string]interface{}{ "tickets": tickets, "total": len(tickets), }) } func (h *SupportHandler) CreateTicket(w http.ResponseWriter, r *http.Request) { var req Ticket if err := json.NewDecoder(r.Body).Decode(&req); err != nil { writeError(w, http.StatusBadRequest, "invalid request") return } var id string err := h.DB.QueryRow(` INSERT INTO boc_tickets (customer_id, contact_id, subject, description, status, priority, category, source) VALUES ($1, $2, $3, $4, $5, $6, $7, $8) RETURNING id `, req.CustomerID, req.ContactID, req.Subject, req.Description, req.Status, req.Priority, req.Category, req.Source).Scan(&id) if err != nil { writeError(w, http.StatusInternalServerError, "failed to create ticket") return } writeJSON(w, http.StatusCreated, map[string]interface{}{ "id": id, "message": "Ticket created", }) } func (h *SupportHandler) GetTicket(w http.ResponseWriter, r *http.Request) { id := chi.URLParam(r, "id") var t Ticket err := h.DB.QueryRow(` SELECT id, customer_id, contact_id, subject, description, status, priority, category, source, assigned_to, resolved_at, resolution, created_at, updated_at FROM boc_tickets WHERE id = $1 `, id).Scan(&t.ID, &t.CustomerID, &t.ContactID, &t.Subject, &t.Description, &t.Status, &t.Priority, &t.Category, &t.Source, &t.AssignedTo, &t.ResolvedAt, &t.Resolution, &t.CreatedAt, &t.UpdatedAt) if err == sql.ErrNoRows { writeError(w, http.StatusNotFound, "ticket not found") return } if err != nil { writeError(w, http.StatusInternalServerError, "database error") return } // Get comments rows, err := h.DB.Query(` SELECT id, ticket_id, content, is_internal, created_by, created_at FROM boc_ticket_comments WHERE ticket_id = $1 ORDER BY created_at ASC `, id) if err != nil { writeError(w, http.StatusInternalServerError, "database error") return } defer rows.Close() comments := []TicketComment{} for rows.Next() { var c TicketComment if err := rows.Scan(&c.ID, &c.TicketID, &c.Content, &c.IsInternal, &c.CreatedBy, &c.CreatedAt); err != nil { continue } comments = append(comments, c) } writeJSON(w, http.StatusOK, map[string]interface{}{ "ticket": t, "comments": comments, }) } func (h *SupportHandler) UpdateTicket(w http.ResponseWriter, r *http.Request) { id := chi.URLParam(r, "id") var req Ticket if err := json.NewDecoder(r.Body).Decode(&req); err != nil { writeError(w, http.StatusBadRequest, "invalid request") return } _, err := h.DB.Exec(` UPDATE boc_tickets SET customer_id = $1, contact_id = $2, subject = $3, description = $4, status = $5, priority = $6, category = $7, source = $8, assigned_to = $9, resolved_at = $10, resolution = $11 WHERE id = $12 `, req.CustomerID, req.ContactID, req.Subject, req.Description, req.Status, req.Priority, req.Category, req.Source, req.AssignedTo, req.ResolvedAt, req.Resolution, id) if err != nil { writeError(w, http.StatusInternalServerError, "failed to update ticket") return } writeJSON(w, http.StatusOK, map[string]interface{}{ "message": "Ticket updated", }) } func (h *SupportHandler) AddComment(w http.ResponseWriter, r *http.Request) { ticketID := chi.URLParam(r, "id") var req TicketComment if err := json.NewDecoder(r.Body).Decode(&req); err != nil { writeError(w, http.StatusBadRequest, "invalid request") return } var id string err := h.DB.QueryRow(` INSERT INTO boc_ticket_comments (ticket_id, content, is_internal) VALUES ($1, $2, $3) RETURNING id `, ticketID, req.Content, req.IsInternal).Scan(&id) if err != nil { writeError(w, http.StatusInternalServerError, "failed to add comment") return } writeJSON(w, http.StatusCreated, map[string]interface{}{ "id": id, "message": "Comment added", }) } func (h *SupportHandler) GetCSAT(w http.ResponseWriter, r *http.Request) { // TODO: Implement actual CSAT calculation from ticket ratings writeJSON(w, http.StatusOK, map[string]interface{}{ "csat_score": 4.2, "total_ratings": 156, "response_rate": 0.78, }) }