feat(boc): v1.0 - Complete Business Operations Center

- Go backend API with full CRUD for all modules
- Rust analytics service with parallel processing
- C runtime with POSIX shared memory IPC
- PostgreSQL schema with 30+ tables
- Redis cache, Kafka event streaming
- WebSocket hub, automation engine
- PDF generation, Resend email integration
- JWT auth, multi-tenant
- Docker Compose deployment
- Nginx reverse proxy

Refs: BOC-001
This commit is contained in:
Bernt (LandveX AI)
2026-07-12 13:21:10 +00:00
commit 67a69ab073
1130 changed files with 18263 additions and 0 deletions
+299
View File
@@ -0,0 +1,299 @@
package handlers
import (
"database/sql"
"encoding/json"
"net/http"
"time"
"github.com/go-chi/chi/v5"
)
type CRMHandler struct {
DB *sql.DB
}
func NewCRMHandler(db *sql.DB) *CRMHandler {
return &CRMHandler{DB: db}
}
type Customer struct {
ID string `json:"id"`
Name string `json:"name"`
Email string `json:"email"`
Phone string `json:"phone"`
Company string `json:"company"`
OrgNumber string `json:"org_number"`
Status string `json:"status"`
Source string `json:"source"`
Tags []string `json:"tags"`
AssignedTo *string `json:"assigned_to"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
type CustomerInteraction struct {
ID string `json:"id"`
CustomerID string `json:"customer_id"`
Type string `json:"type"`
Direction string `json:"direction"`
Subject string `json:"subject"`
Content string `json:"content"`
Metadata map[string]interface{} `json:"metadata"`
CreatedBy *string `json:"created_by"`
CreatedAt time.Time `json:"created_at"`
}
type PipelineStage struct {
Stage string `json:"stage"`
Count int `json:"count"`
Value float64 `json:"value"`
}
func (h *CRMHandler) ListCustomers(w http.ResponseWriter, r *http.Request) {
status := r.URL.Query().Get("status")
if status == "" {
status = "active"
}
rows, err := h.DB.Query(`
SELECT id, name, email, phone, company, org_number, status, source, tags, assigned_to, created_at, updated_at
FROM boc_customers
WHERE status = $1
ORDER BY created_at DESC
LIMIT 100
`, status)
if err != nil {
writeError(w, http.StatusInternalServerError, "database error")
return
}
defer rows.Close()
customers := []Customer{}
for rows.Next() {
var c Customer
if err := rows.Scan(&c.ID, &c.Name, &c.Email, &c.Phone, &c.Company, &c.OrgNumber,
&c.Status, &c.Source, &c.Tags, &c.AssignedTo, &c.CreatedAt, &c.UpdatedAt); err != nil {
continue
}
customers = append(customers, c)
}
writeJSON(w, http.StatusOK, map[string]interface{}{
"customers": customers,
"total": len(customers),
})
}
func (h *CRMHandler) CreateCustomer(w http.ResponseWriter, r *http.Request) {
var req Customer
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_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, req.Tags).Scan(&id)
if err != nil {
writeError(w, http.StatusInternalServerError, "failed to create customer")
return
}
writeJSON(w, http.StatusCreated, map[string]interface{}{
"id": id,
"message": "Customer created",
})
}
func (h *CRMHandler) GetCustomer(w http.ResponseWriter, r *http.Request) {
id := chi.URLParam(r, "id")
var c Customer
err := h.DB.QueryRow(`
SELECT id, name, email, phone, company, org_number, status, source, tags, assigned_to, created_at, updated_at
FROM boc_customers WHERE id = $1
`, id).Scan(&c.ID, &c.Name, &c.Email, &c.Phone, &c.Company, &c.OrgNumber,
&c.Status, &c.Source, &c.Tags, &c.AssignedTo, &c.CreatedAt, &c.UpdatedAt)
if err == sql.ErrNoRows {
writeError(w, http.StatusNotFound, "customer not found")
return
}
if err != nil {
writeError(w, http.StatusInternalServerError, "database error")
return
}
writeJSON(w, http.StatusOK, c)
}
func (h *CRMHandler) UpdateCustomer(w http.ResponseWriter, r *http.Request) {
id := chi.URLParam(r, "id")
var req Customer
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
writeError(w, http.StatusBadRequest, "invalid request")
return
}
_, err := h.DB.Exec(`
UPDATE boc_customers
SET name = $1, email = $2, phone = $3, company = $4, org_number = $5,
status = $6, source = $7, tags = $8, assigned_to = $9
WHERE id = $10
`, req.Name, req.Email, req.Phone, req.Company, req.OrgNumber,
req.Status, req.Source, req.Tags, req.AssignedTo, id)
if err != nil {
writeError(w, http.StatusInternalServerError, "failed to update customer")
return
}
writeJSON(w, http.StatusOK, map[string]interface{}{
"message": "Customer updated",
})
}
func (h *CRMHandler) DeleteCustomer(w http.ResponseWriter, r *http.Request) {
id := chi.URLParam(r, "id")
_, err := h.DB.Exec(`DELETE FROM boc_customers WHERE id = $1`, id)
if err != nil {
writeError(w, http.StatusInternalServerError, "failed to delete customer")
return
}
writeJSON(w, http.StatusOK, map[string]interface{}{
"message": "Customer deleted",
})
}
func (h *CRMHandler) ListLeads(w http.ResponseWriter, r *http.Request) {
rows, err := h.DB.Query(`
SELECT id, name, email, phone, company, org_number, status, source, tags, assigned_to, created_at, updated_at
FROM boc_customers
WHERE status = 'lead'
ORDER BY created_at DESC
`)
if err != nil {
writeError(w, http.StatusInternalServerError, "database error")
return
}
defer rows.Close()
leads := []Customer{}
for rows.Next() {
var c Customer
if err := rows.Scan(&c.ID, &c.Name, &c.Email, &c.Phone, &c.Company, &c.OrgNumber,
&c.Status, &c.Source, &c.Tags, &c.AssignedTo, &c.CreatedAt, &c.UpdatedAt); err != nil {
continue
}
leads = append(leads, c)
}
writeJSON(w, http.StatusOK, map[string]interface{}{
"leads": leads,
"total": len(leads),
})
}
func (h *CRMHandler) GetPipeline(w http.ResponseWriter, r *http.Request) {
rows, err := h.DB.Query(`
SELECT stage, COUNT(*), COALESCE(SUM(value), 0)
FROM boc_deals
WHERE status = 'open'
GROUP BY stage
ORDER BY
CASE stage
WHEN 'prospect' THEN 1
WHEN 'qualified' THEN 2
WHEN 'proposal' THEN 3
WHEN 'negotiation' THEN 4
WHEN 'closed_won' THEN 5
ELSE 6
END
`)
if err != nil {
writeError(w, http.StatusInternalServerError, "database error")
return
}
defer rows.Close()
stages := []PipelineStage{}
for rows.Next() {
var s PipelineStage
if err := rows.Scan(&s.Stage, &s.Count, &s.Value); err != nil {
continue
}
stages = append(stages, s)
}
writeJSON(w, http.StatusOK, map[string]interface{}{
"pipeline": stages,
})
}
func (h *CRMHandler) CreateInteraction(w http.ResponseWriter, r *http.Request) {
var req CustomerInteraction
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
writeError(w, http.StatusBadRequest, "invalid request")
return
}
metadata, _ := json.Marshal(req.Metadata)
var id string
err := h.DB.QueryRow(`
INSERT INTO boc_customer_interactions (customer_id, type, direction, subject, content, metadata)
VALUES ($1, $2, $3, $4, $5, $6)
RETURNING id
`, req.CustomerID, req.Type, req.Direction, req.Subject, req.Content, metadata).Scan(&id)
if err != nil {
writeError(w, http.StatusInternalServerError, "failed to create interaction")
return
}
writeJSON(w, http.StatusCreated, map[string]interface{}{
"id": id,
"message": "Interaction created",
})
}
func (h *CRMHandler) GetCustomerInteractions(w http.ResponseWriter, r *http.Request) {
customerID := chi.URLParam(r, "id")
rows, err := h.DB.Query(`
SELECT id, customer_id, type, direction, subject, content, metadata, created_by, created_at
FROM boc_customer_interactions
WHERE customer_id = $1
ORDER BY created_at DESC
`, customerID)
if err != nil {
writeError(w, http.StatusInternalServerError, "database error")
return
}
defer rows.Close()
interactions := []CustomerInteraction{}
for rows.Next() {
var i CustomerInteraction
var metadata []byte
if err := rows.Scan(&i.ID, &i.CustomerID, &i.Type, &i.Direction, &i.Subject,
&i.Content, &metadata, &i.CreatedBy, &i.CreatedAt); err != nil {
continue
}
json.Unmarshal(metadata, &i.Metadata)
interactions = append(interactions, i)
}
writeJSON(w, http.StatusOK, map[string]interface{}{
"interactions": interactions,
"total": len(interactions),
})
}