- Introduced new functionality for managing email attachments and drafts in the mail API. - Added handlers for listing, uploading, and downloading message attachments in `internal/api/mail/handlers_attachments.go`. - Implemented draft management endpoints for creating, updating, and deleting drafts in `internal/api/mail/handlers_drafts.go`. - Created new service methods for handling draft and attachment operations in `internal/api/mail/drafts.go` and `internal/api/mail/storage.go`. - Added validation and error handling for draft and attachment operations. - Included unit tests for draft and folder functionalities in `internal/api/mail/drafts_test.go` and `internal/api/mail/folders_test.go`. - Updated API routes to support new draft and attachment features, enhancing overall mail management capabilities.
130 lines
3.5 KiB
Go
130 lines
3.5 KiB
Go
package mail
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/ultisuite/ulti-backend/internal/api/query"
|
|
)
|
|
|
|
type MessageSearchFilter struct {
|
|
Query string
|
|
Sender string
|
|
DateFrom *time.Time
|
|
DateTo *time.Time
|
|
HasAttachments *bool
|
|
Label string
|
|
AccountID 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
|
|
|
|
if filter.AccountID != "" {
|
|
base += fmt.Sprintf(" AND m.account_id = $%d", argIdx)
|
|
args = append(args, filter.AccountID)
|
|
argIdx++
|
|
}
|
|
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": 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, " & ")
|
|
}
|