security: Add proper authentication, RBAC, and tenant isolation
- Add password hashing with bcrypt - Add AuthService with proper login - Add password strength validation - Add RBAC middleware (AdminOnly, ManagerOrAdmin) - Add tenant isolation middleware - Update CRM handler with tenant filtering - Add JWT fallback for development mode - Add user context helpers - Build successful
This commit is contained in:
@@ -0,0 +1,171 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// UnifiedHandler hanterar allt i ett enda API
|
||||
type UnifiedHandler struct {
|
||||
DB *sql.DB
|
||||
}
|
||||
|
||||
func NewUnifiedHandler(db *sql.DB) *UnifiedHandler {
|
||||
return &UnifiedHandler{DB: db}
|
||||
}
|
||||
|
||||
// GetUnifiedDashboard returnerar allt på ett ställe
|
||||
func (h *UnifiedHandler) GetUnifiedDashboard(w http.ResponseWriter, r *http.Request) {
|
||||
// 1. Hämta CRM-data från BOC-databasen
|
||||
customers := []map[string]interface{}{}
|
||||
customerRows, err := h.DB.Query(`
|
||||
SELECT id, name, email, status, created_at
|
||||
FROM boc_customers
|
||||
WHERE status = 'active'
|
||||
ORDER BY created_at DESC
|
||||
`)
|
||||
if err == nil {
|
||||
defer customerRows.Close()
|
||||
for customerRows.Next() {
|
||||
var c map[string]interface{}
|
||||
var id, name, email, status string
|
||||
var createdAt sql.NullTime
|
||||
if err := customerRows.Scan(&id, &name, &email, &status, &createdAt); err != nil {
|
||||
continue
|
||||
}
|
||||
c = map[string]interface{}{
|
||||
"id": id,
|
||||
"name": name,
|
||||
"email": email,
|
||||
"status": status,
|
||||
}
|
||||
if createdAt.Valid {
|
||||
c["created_at"] = createdAt.Time.Format("2006-01-02")
|
||||
}
|
||||
customers = append(customers, c)
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Hämta deals från BOC-databasen
|
||||
deals := []map[string]interface{}{}
|
||||
dealRows, err := h.DB.Query(`
|
||||
SELECT id, name, customer_id, value, currency, stage, status
|
||||
FROM boc_deals
|
||||
ORDER BY
|
||||
CASE stage
|
||||
WHEN 'negotiation' THEN 1
|
||||
WHEN 'proposal' THEN 2
|
||||
WHEN 'closed' THEN 3
|
||||
ELSE 4
|
||||
END,
|
||||
value DESC
|
||||
`)
|
||||
if err == nil {
|
||||
defer dealRows.Close()
|
||||
for dealRows.Next() {
|
||||
var d map[string]interface{}
|
||||
var id, name, customerID, currency, stage, status string
|
||||
var value float64
|
||||
if err := dealRows.Scan(&id, &name, &customerID, &value, ¤cy, &stage, &status); err != nil {
|
||||
continue
|
||||
}
|
||||
d = map[string]interface{}{
|
||||
"id": id,
|
||||
"name": name,
|
||||
"customer_id": customerID,
|
||||
"value": value,
|
||||
"currency": currency,
|
||||
"stage": stage,
|
||||
"status": status,
|
||||
}
|
||||
deals = append(deals, d)
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Hämta mail-statistik
|
||||
mailCount := 0
|
||||
mailRows, err := h.DB.Query(`
|
||||
SELECT COUNT(*) FROM boc_mail_messages
|
||||
`)
|
||||
if err == nil && mailRows.Next() {
|
||||
mailRows.Scan(&mailCount)
|
||||
mailRows.Close()
|
||||
}
|
||||
|
||||
// 4. Hämta analytics
|
||||
var revenue, moms float64
|
||||
analyticsRows, err := h.DB.Query(`
|
||||
SELECT kpi_key, value FROM boc_analytics_kpis
|
||||
WHERE kpi_key IN ('revenue_h1', 'moms_att_betala')
|
||||
`)
|
||||
if err == nil {
|
||||
defer analyticsRows.Close()
|
||||
for analyticsRows.Next() {
|
||||
var key string
|
||||
var value float64
|
||||
if err := analyticsRows.Scan(&key, &value); err != nil {
|
||||
continue
|
||||
}
|
||||
if key == "revenue_h1" {
|
||||
revenue = value
|
||||
} else if key == "moms_att_betala" {
|
||||
moms = value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 5. Hämta team
|
||||
team := []map[string]interface{}{}
|
||||
teamRows, err := h.DB.Query(`
|
||||
SELECT first_name, last_name, position, department, status
|
||||
FROM boc_employees
|
||||
WHERE status = 'active'
|
||||
ORDER BY department, first_name
|
||||
`)
|
||||
if err == nil {
|
||||
defer teamRows.Close()
|
||||
for teamRows.Next() {
|
||||
var firstName, lastName, position, department, status string
|
||||
if err := teamRows.Scan(&firstName, &lastName, &position, &department, &status); err != nil {
|
||||
continue
|
||||
}
|
||||
team = append(team, map[string]interface{}{
|
||||
"name": firstName + " " + lastName,
|
||||
"position": position,
|
||||
"department": department,
|
||||
"status": status,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
writeJSON(w, http.StatusOK, map[string]interface{}{
|
||||
"ok": true,
|
||||
"dashboard": map[string]interface{}{
|
||||
"crm": map[string]interface{}{
|
||||
"customers": customers,
|
||||
"deals": deals,
|
||||
"total_pipeline": calculatePipeline(deals),
|
||||
},
|
||||
"finance": map[string]interface{}{
|
||||
"revenue": revenue,
|
||||
"moms": moms,
|
||||
},
|
||||
"mail": map[string]interface{}{
|
||||
"total_messages": mailCount,
|
||||
},
|
||||
"team": team,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func calculatePipeline(deals []map[string]interface{}) float64 {
|
||||
var total float64
|
||||
for _, deal := range deals {
|
||||
if status, ok := deal["status"].(string); ok && status == "open" {
|
||||
if value, ok := deal["value"].(float64); ok {
|
||||
total += value
|
||||
}
|
||||
}
|
||||
}
|
||||
return total
|
||||
}
|
||||
Reference in New Issue
Block a user