78b57273e2
- 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
207 lines
5.2 KiB
Go
207 lines
5.2 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()
|
|
if client == nil {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.WriteHeader(http.StatusServiceUnavailable)
|
|
json.NewEncoder(w).Encode(map[string]interface{}{
|
|
"ok": false,
|
|
"error": "IMAP not configured. Set IMAP_URL environment variable or configure via /api/v1/mail/config",
|
|
})
|
|
return
|
|
}
|
|
|
|
limit := 50
|
|
if l := r.URL.Query().Get("limit"); l != "" {
|
|
if parsed, err := strconv.Atoi(l); err == nil && parsed > 0 {
|
|
limit = parsed
|
|
}
|
|
}
|
|
|
|
messages, err := client.ListMessages(limit)
|
|
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,
|
|
"messages": messages,
|
|
"total": len(messages),
|
|
})
|
|
}
|
|
|
|
// 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 {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.WriteHeader(http.StatusServiceUnavailable)
|
|
json.NewEncoder(w).Encode(map[string]interface{}{
|
|
"ok": false,
|
|
"error": "IMAP not configured. Set IMAP_URL environment variable or configure via /api/v1/mail/config",
|
|
})
|
|
return
|
|
}
|
|
|
|
count, err := client.GetUnreadCount()
|
|
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,
|
|
"count": count,
|
|
})
|
|
}
|
|
|
|
// 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,
|
|
})
|
|
}
|