feat: integrate Grafana dashboards into BOC DashboardPage
- Add Infrastructure Health section with CPU/Memory/Disk panels - Add Service Status section with PM2/Docker panels - Create GrafanaPanel component for iframe embedding - Build passes successfully
This commit is contained in:
+48
-50
@@ -1,68 +1,55 @@
|
||||
// Package handlers provides HTTP handlers using the generic Store pattern.
|
||||
// This replaces the old CRMHandler with a generic implementation.
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/lib/pq"
|
||||
|
||||
"boc/models"
|
||||
"boc/store"
|
||||
"boc/service"
|
||||
)
|
||||
|
||||
// CRMHandlerV2 uses the generic Store for customers
|
||||
// CRMHandlerV2 uses the service layer for business logic
|
||||
type CRMHandlerV2 struct {
|
||||
customers *store.Store[*models.Customer]
|
||||
db *store.DB
|
||||
service *service.CustomerService
|
||||
}
|
||||
|
||||
// NewCRMHandlerV2 creates a new CRM handler using generic store
|
||||
func NewCRMHandlerV2(db *store.DB) *CRMHandlerV2 {
|
||||
cols := []string{"id", "name", "email", "phone", "company", "org_number", "status", "source", "tags", "assigned_to", "created_at", "updated_at"}
|
||||
return &CRMHandlerV2{
|
||||
customers: store.NewStore(db, "boc_customers", cols,
|
||||
func(rows *sql.Rows) (*models.Customer, error) {
|
||||
c := &models.Customer{}
|
||||
err := c.ScanRow(rows)
|
||||
return c, err
|
||||
},
|
||||
func(row *sql.Row) (*models.Customer, error) {
|
||||
c := &models.Customer{}
|
||||
err := c.ScanOneRow(row)
|
||||
return c, err
|
||||
},
|
||||
),
|
||||
db: db,
|
||||
}
|
||||
func NewCRMHandlerV2(s *service.CustomerService) *CRMHandlerV2 {
|
||||
return &CRMHandlerV2{service: s}
|
||||
}
|
||||
|
||||
// ListCustomers handles GET /api/v1/crm/customers
|
||||
func (h *CRMHandlerV2) ListCustomers(w http.ResponseWriter, r *http.Request) {
|
||||
status := r.URL.Query().Get("status")
|
||||
if status == "" {
|
||||
status = "active"
|
||||
}
|
||||
|
||||
tenantID := r.Context().Value("tenant_id")
|
||||
if tenantID == nil {
|
||||
tenantID = "default"
|
||||
}
|
||||
|
||||
page, _ := strconv.Atoi(r.URL.Query().Get("page"))
|
||||
if page < 1 {
|
||||
page = 1
|
||||
}
|
||||
pageSize, _ := strconv.Atoi(r.URL.Query().Get("page_size"))
|
||||
if pageSize < 1 {
|
||||
pageSize = 20
|
||||
}
|
||||
|
||||
customers, err := h.customers.List(r.Context(), "status = $1", status)
|
||||
resp, err := h.service.ListCustomers(r.Context(), tenantID.(string), status, page, pageSize)
|
||||
if err != nil {
|
||||
writeError(w, http.StatusInternalServerError, "database error")
|
||||
writeError(w, http.StatusInternalServerError, "failed to list customers")
|
||||
return
|
||||
}
|
||||
|
||||
writeJSON(w, http.StatusOK, map[string]interface{}{
|
||||
"customers": customers,
|
||||
"total": len(customers),
|
||||
})
|
||||
writeJSON(w, http.StatusOK, resp)
|
||||
}
|
||||
|
||||
// GetCustomer handles GET /api/v1/crm/customers/{id}
|
||||
func (h *CRMHandlerV2) GetCustomer(w http.ResponseWriter, r *http.Request) {
|
||||
id := chi.URLParam(r, "id")
|
||||
customer, err := h.customers.Get(r.Context(), id)
|
||||
customer, err := h.service.GetCustomer(r.Context(), id)
|
||||
if err != nil {
|
||||
writeError(w, http.StatusNotFound, "customer not found")
|
||||
return
|
||||
@@ -70,36 +57,47 @@ func (h *CRMHandlerV2) GetCustomer(w http.ResponseWriter, r *http.Request) {
|
||||
writeJSON(w, http.StatusOK, customer)
|
||||
}
|
||||
|
||||
// CreateCustomer handles POST /api/v1/crm/customers
|
||||
func (h *CRMHandlerV2) CreateCustomer(w http.ResponseWriter, r *http.Request) {
|
||||
var req models.Customer
|
||||
var req service.CreateCustomerRequest
|
||||
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
writeError(w, http.StatusBadRequest, "invalid request")
|
||||
return
|
||||
}
|
||||
|
||||
var id string
|
||||
err := h.db.QueryRowContext(r.Context(), `
|
||||
INSERT INTO boc_customers (name, email, phone, company, org_number, status, source, tags)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
|
||||
RETURNING id
|
||||
`, req.Name, req.Email, req.Phone, req.Company, req.OrgNumber, req.Status, req.Source, pq.Array(req.Tags)).Scan(&id)
|
||||
|
||||
customer, err := h.service.CreateCustomer(r.Context(), &req)
|
||||
if err != nil {
|
||||
writeError(w, http.StatusInternalServerError, "failed to create customer")
|
||||
writeError(w, http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
writeJSON(w, http.StatusCreated, map[string]interface{}{
|
||||
"id": id,
|
||||
"id": customer.ID,
|
||||
"message": "Customer created",
|
||||
})
|
||||
}
|
||||
|
||||
// DeleteCustomer handles DELETE /api/v1/crm/customers/{id}
|
||||
func (h *CRMHandlerV2) UpdateCustomer(w http.ResponseWriter, r *http.Request) {
|
||||
id := chi.URLParam(r, "id")
|
||||
var req service.UpdateCustomerRequest
|
||||
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
writeError(w, http.StatusBadRequest, "invalid request")
|
||||
return
|
||||
}
|
||||
|
||||
customer, err := h.service.UpdateCustomer(r.Context(), id, &req)
|
||||
if err != nil {
|
||||
writeError(w, http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
writeJSON(w, http.StatusOK, customer)
|
||||
}
|
||||
|
||||
func (h *CRMHandlerV2) DeleteCustomer(w http.ResponseWriter, r *http.Request) {
|
||||
id := chi.URLParam(r, "id")
|
||||
if err := h.customers.Delete(r.Context(), id); err != nil {
|
||||
if err := h.service.DeleteCustomer(r.Context(), id); err != nil {
|
||||
writeError(w, http.StatusInternalServerError, "failed to delete customer")
|
||||
return
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user