ultisuite-backend/internal/api/mail/search_advanced.go
R3D347HR4Y 621b0099d6
Some checks are pending
CI / Go tests (push) Waiting to run
CI / Integration tests (push) Waiting to run
CI / DB migrations (push) Waiting to run
feat(deploy): enhance Nginx configuration and API integration for UltiAI
- Updated .env.example to include new configuration options for the UltiAI branding and API endpoints.
- Enhanced Nginx configuration to support new API routes for the MCP and WebSocket connections.
- Introduced sub-filters for branding adjustments in Nginx responses.
- Added new JavaScript patch for API endpoint adjustments.
- Implemented tests for new API functionalities and improved error handling in the AI gateway.
2026-06-15 00:22:23 +02:00

132 lines
3.9 KiB
Go

package mail
import (
"context"
"encoding/json"
"fmt"
"strings"
"time"
"github.com/ultisuite/ulti-backend/internal/api/query"
"github.com/ultisuite/ulti-backend/internal/mail/imap"
)
type MessageSearchFilter struct {
Query string
Sender string
DateFrom *time.Time
DateTo *time.Time
HasAttachments *bool
Label string
AccountID string
ScopedAccountIDs []string
IncludeSpam bool
IncludeTrash bool
}
type MessageSearchResult struct {
Messages []map[string]any `json:"messages"`
Pagination query.PaginationMeta `json:"pagination,omitempty"`
}
func (s *Service) SearchMessages(ctx context.Context, externalID string, filter MessageSearchFilter, params query.ListParams) (MessageSearchResult, error) {
base := `
FROM messages m
JOIN mail_accounts ma ON m.account_id = ma.id
WHERE ma.user_id = (SELECT id FROM users WHERE external_id = $1)
`
args := []any{externalID}
argIdx := 2
base, args, argIdx = appendMessageAccountScope(base, args, argIdx, filter.AccountID, filter.ScopedAccountIDs)
if filter.Sender != "" {
base += fmt.Sprintf(" AND m.from_addr::text ILIKE '%%' || $%d || '%%'", argIdx)
args = append(args, filter.Sender)
argIdx++
}
if filter.DateFrom != nil {
base += fmt.Sprintf(" AND m.date >= $%d", argIdx)
args = append(args, *filter.DateFrom)
argIdx++
}
if filter.DateTo != nil {
base += fmt.Sprintf(" AND m.date <= $%d", argIdx)
args = append(args, *filter.DateTo)
argIdx++
}
if filter.HasAttachments != nil {
base += fmt.Sprintf(" AND m.has_attachments = $%d", argIdx)
args = append(args, *filter.HasAttachments)
argIdx++
}
if filter.Label != "" {
base += fmt.Sprintf(" AND $%d = ANY(m.labels)", argIdx)
args = append(args, filter.Label)
argIdx++
}
if q := strings.TrimSpace(filter.Query); q != "" {
tsQuery := toMailTSQuery(q)
base += fmt.Sprintf(" AND m.search_vector @@ to_tsquery('simple', $%d)", argIdx)
args = append(args, tsQuery)
argIdx++
}
excludeSpam, excludeTrash := HiddenMailboxExclusion("", filter.Label, filter.IncludeSpam, filter.IncludeTrash)
base, args, argIdx = AppendHiddenMailboxExclusion(base, args, argIdx, excludeSpam, excludeTrash)
var total int64
if err := s.db.QueryRow(ctx, "SELECT COUNT(*) "+base, args...).Scan(&total); err != nil {
return MessageSearchResult{}, err
}
listQuery := `
SELECT m.id, m.message_id, m.thread_id, m.subject, m.from_addr, m.to_addrs,
m.date, m.snippet, m.flags, m.labels, m.has_attachments
` + base + fmt.Sprintf(" ORDER BY m.date DESC LIMIT $%d OFFSET $%d", argIdx, argIdx+1)
args = append(args, params.Limit(), params.Offset())
rows, err := s.db.Query(ctx, listQuery, args...)
if err != nil {
return MessageSearchResult{}, err
}
defer rows.Close()
messages := make([]map[string]any, 0)
for rows.Next() {
var id, messageID, subject, snippet string
var threadID *string
var fromAddr, toAddrs []byte
var date any
var flags, labels []string
var hasAttachments bool
if err := rows.Scan(&id, &messageID, &threadID, &subject, &fromAddr, &toAddrs, &date, &snippet, &flags, &labels, &hasAttachments); err != nil {
return MessageSearchResult{}, err
}
entry := map[string]any{
"id": id, "message_id": messageID, "subject": subject,
"from": json.RawMessage(fromAddr), "to": json.RawMessage(toAddrs),
"date": date, "snippet": imap.RepairSnippet(snippet), "flags": flags, "labels": labels,
"has_attachments": hasAttachments,
}
if threadID != nil {
entry["thread_id"] = *threadID
}
messages = append(messages, entry)
}
if err := rows.Err(); err != nil {
return MessageSearchResult{}, err
}
return MessageSearchResult{
Messages: messages,
Pagination: params.Meta(&total),
}, nil
}
func toMailTSQuery(input string) string {
words := strings.Fields(input)
for i, w := range words {
words[i] = w + ":*"
}
return strings.Join(words, " & ")
}