Files
boc/backend/handlers/mail.go
T
Bernt 0b416b0f13
BOC CI/CD / Test (push) Failing after 2s
BOC CI/CD / Security Scan (push) Has been skipped
BOC CI/CD / Build & Push (push) Has been skipped
BOC CI/CD / Deploy to Staging (push) Has been skipped
BOC CI/CD / Deploy to Production (push) Has been skipped
feat(agent): activate BOC agent layer
- 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
2026-08-12 07:50:12 +00:00

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,
})
}