- Updated environment configuration to unify frontend for mail and drive under a single service. - Revised README to reflect changes in frontend setup and routing for the unified application. - Introduced new API documentation endpoints for better accessibility of API specifications. - Enhanced drive and mail services with improved handling of file uploads and metadata enrichment. - Implemented new API token management features, including creation, listing, and revocation of tokens. - Added tests for new functionalities in drive and mail services to ensure reliability and correctness.
128 lines
3.6 KiB
Go
128 lines
3.6 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
|
|
}
|
|
|
|
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++
|
|
}
|
|
|
|
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, " & ")
|
|
}
|