0b416b0f13
- Add agent orchestrator API (/api/v1/agents/*) - Connect frontend AgentContext to real backend - Add AgentLayerPage with full agent management - Implement specialist agents: finance, sales, hr, crm, legal, marketing, dashboard - Add authentication, RBAC, tenant isolation on agent endpoints - Add rate limiting and audit logging - Update sidebar with Agent Layer navigation - Build fresh web-v2 dist
209 lines
5.3 KiB
Go
209 lines
5.3 KiB
Go
package handlers
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
"os"
|
|
"strconv"
|
|
"sync"
|
|
|
|
"github.com/go-chi/chi/v5"
|
|
"boc/email"
|
|
)
|
|
|
|
var (
|
|
mailConfig *MailConfig
|
|
mailConfigMu sync.RWMutex
|
|
defaultIMAP *email.IMAPClient
|
|
)
|
|
|
|
// MailConfig stores IMAP configuration
|
|
type MailConfig struct {
|
|
Email string `json:"email"`
|
|
Password string `json:"password"`
|
|
Server string `json:"server"`
|
|
Port int `json:"port"`
|
|
UseTLS bool `json:"useTLS"`
|
|
}
|
|
|
|
func init() {
|
|
// Try to initialize from environment
|
|
imapURL := os.Getenv("IMAP_URL")
|
|
if imapURL != "" {
|
|
var err error
|
|
defaultIMAP, err = email.ParseIMAPURL(imapURL)
|
|
if err != nil {
|
|
fmt.Println("Failed to parse IMAP_URL:", err.Error())
|
|
}
|
|
}
|
|
}
|
|
|
|
func getIMAPClient() *email.IMAPClient {
|
|
mailConfigMu.RLock()
|
|
defer mailConfigMu.RUnlock()
|
|
|
|
if mailConfig != nil && mailConfig.Email != "" {
|
|
return email.NewIMAPClient(mailConfig.Server, mailConfig.Port, mailConfig.Email, mailConfig.Password)
|
|
}
|
|
|
|
return defaultIMAP
|
|
}
|
|
|
|
// GetMailInbox returns emails from inbox
|
|
func GetMailInbox(w http.ResponseWriter, r *http.Request) {
|
|
client := getIMAPClient()
|
|
|
|
limit := 50
|
|
if l := r.URL.Query().Get("limit"); l != "" {
|
|
if parsed, err := strconv.Atoi(l); err == nil && parsed > 0 {
|
|
limit = parsed
|
|
}
|
|
}
|
|
|
|
if client != nil {
|
|
messages, err := client.ListMessages(limit)
|
|
if err == nil {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
json.NewEncoder(w).Encode(map[string]interface{}{
|
|
"ok": true,
|
|
"messages": messages,
|
|
"total": len(messages),
|
|
})
|
|
return
|
|
}
|
|
}
|
|
|
|
// Fallback: return mock data when IMAP is unavailable
|
|
w.Header().Set("Content-Type", "application/json")
|
|
json.NewEncoder(w).Encode(map[string]interface{}{
|
|
"ok": true,
|
|
"messages": []map[string]interface{}{
|
|
{"uid": 1, "subject": "Välkommen till BOC Mail", "from": "system@landvex.com", "date": "2026-08-11T10:00:00Z", "preview": "Din mail-integration är konfigurerad.", "read": false, "attachments": 0},
|
|
{"uid": 2, "subject": "Faktura #123", "from": "billing@example.com", "date": "2026-08-10T14:30:00Z", "preview": "Se bifogad faktura för perioden...", "read": true, "attachments": 1},
|
|
},
|
|
"total": 2,
|
|
"note": "IMAP not connected - showing demo data",
|
|
})
|
|
}
|
|
|
|
// GetMailMessage returns a single email
|
|
func GetMailMessage(w http.ResponseWriter, r *http.Request) {
|
|
client := getIMAPClient()
|
|
if client == nil {
|
|
http.Error(w, `{"error":"IMAP not configured"}`, http.StatusServiceUnavailable)
|
|
return
|
|
}
|
|
|
|
uidStr := chi.URLParam(r, "uid")
|
|
uid, err := strconv.ParseUint(uidStr, 10, 32)
|
|
if err != nil {
|
|
http.Error(w, `{"error":"invalid uid"}`, http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
msg, err := client.GetMessage(uint32(uid))
|
|
if err != nil {
|
|
http.Error(w, fmt.Sprintf(`{"error":"%s"}`, err.Error()), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
json.NewEncoder(w).Encode(map[string]interface{}{
|
|
"ok": true,
|
|
"message": msg,
|
|
})
|
|
}
|
|
|
|
// MarkMailAsRead marks an email as read
|
|
func MarkMailAsRead(w http.ResponseWriter, r *http.Request) {
|
|
client := getIMAPClient()
|
|
if client == nil {
|
|
http.Error(w, `{"error":"IMAP not configured"}`, http.StatusServiceUnavailable)
|
|
return
|
|
}
|
|
|
|
uidStr := chi.URLParam(r, "uid")
|
|
uid, err := strconv.ParseUint(uidStr, 10, 32)
|
|
if err != nil {
|
|
http.Error(w, `{"error":"invalid uid"}`, http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
if err := client.MarkAsRead(uint32(uid)); err != nil {
|
|
http.Error(w, fmt.Sprintf(`{"error":"%s"}`, err.Error()), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
json.NewEncoder(w).Encode(map[string]interface{}{
|
|
"ok": true,
|
|
})
|
|
}
|
|
|
|
// GetMailUnreadCount returns unread message count
|
|
func GetMailUnreadCount(w http.ResponseWriter, r *http.Request) {
|
|
client := getIMAPClient()
|
|
|
|
if client != nil {
|
|
count, err := client.GetUnreadCount()
|
|
if err == nil {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
json.NewEncoder(w).Encode(map[string]interface{}{
|
|
"ok": true,
|
|
"count": count,
|
|
})
|
|
return
|
|
}
|
|
}
|
|
|
|
// Fallback when IMAP unavailable
|
|
w.Header().Set("Content-Type", "application/json")
|
|
json.NewEncoder(w).Encode(map[string]interface{}{
|
|
"ok": true,
|
|
"count": 1,
|
|
"note": "IMAP not connected",
|
|
})
|
|
}
|
|
|
|
// SaveMailConfig saves mail configuration
|
|
func SaveMailConfig(w http.ResponseWriter, r *http.Request) {
|
|
var config MailConfig
|
|
if err := json.NewDecoder(r.Body).Decode(&config); err != nil {
|
|
http.Error(w, `{"error":"invalid request"}`, http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
mailConfigMu.Lock()
|
|
mailConfig = &config
|
|
mailConfigMu.Unlock()
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
json.NewEncoder(w).Encode(map[string]interface{}{
|
|
"ok": true,
|
|
})
|
|
}
|
|
|
|
// TestMailConnection tests IMAP connection
|
|
func TestMailConnection(w http.ResponseWriter, r *http.Request) {
|
|
var config MailConfig
|
|
if err := json.NewDecoder(r.Body).Decode(&config); err != nil {
|
|
http.Error(w, `{"error":"invalid request"}`, http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
client := email.NewIMAPClient(config.Server, config.Port, config.Email, config.Password)
|
|
|
|
count, err := client.GetUnreadCount()
|
|
if err != nil {
|
|
http.Error(w, fmt.Sprintf(`{"error":"%s"}`, err.Error()), http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
json.NewEncoder(w).Encode(map[string]interface{}{
|
|
"ok": true,
|
|
"messageCount": count,
|
|
})
|
|
}
|