- 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.
125 lines
3.3 KiB
Go
125 lines
3.3 KiB
Go
package mail
|
|
|
|
import (
|
|
"net/http"
|
|
"net/url"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/ultisuite/ulti-backend/internal/api/apiresponse"
|
|
"github.com/ultisuite/ulti-backend/internal/api/apivalidate"
|
|
"github.com/ultisuite/ulti-backend/internal/api/middleware"
|
|
"github.com/ultisuite/ulti-backend/internal/api/query"
|
|
)
|
|
|
|
func (h *Handler) SearchMessages(w http.ResponseWriter, r *http.Request) {
|
|
claims := middleware.ClaimsFromContext(r.Context())
|
|
params, err := query.ParseList(stripNonDateListRangeKeys(r.URL.Query()))
|
|
if err != nil {
|
|
apivalidate.WriteQueryError(w, r, err)
|
|
return
|
|
}
|
|
|
|
filter, verr := parseMessageSearchFilter(r)
|
|
if verr != nil {
|
|
apivalidate.WriteValidationError(w, r, verr)
|
|
return
|
|
}
|
|
h.applyMailSearchScope(&filter, r)
|
|
|
|
result, err := h.svc.SearchMessages(r.Context(), claims.Sub, filter, params)
|
|
if err != nil {
|
|
h.logger.Error("search messages", "error", err)
|
|
apivalidate.WriteInternal(w, r)
|
|
return
|
|
}
|
|
apiresponse.WriteJSON(w, http.StatusOK, result)
|
|
}
|
|
|
|
func parseMessageSearchFilter(r *http.Request) (MessageSearchFilter, *apivalidate.ValidationError) {
|
|
q := r.URL.Query()
|
|
filter := MessageSearchFilter{
|
|
Query: q.Get("q"),
|
|
Sender: parseSearchSender(q),
|
|
Label: q.Get("label"),
|
|
AccountID: q.Get("account_id"),
|
|
}
|
|
|
|
if raw := q.Get("date_from"); raw != "" {
|
|
t, err := time.Parse(time.RFC3339, raw)
|
|
if err != nil {
|
|
return filter, apivalidate.NewValidationError(apivalidate.FieldDetail{
|
|
Field: "date_from", Message: "invalid RFC3339 datetime",
|
|
})
|
|
}
|
|
filter.DateFrom = &t
|
|
}
|
|
if raw := q.Get("date_to"); raw != "" {
|
|
t, err := time.Parse(time.RFC3339, raw)
|
|
if err != nil {
|
|
return filter, apivalidate.NewValidationError(apivalidate.FieldDetail{
|
|
Field: "date_to", Message: "invalid RFC3339 datetime",
|
|
})
|
|
}
|
|
filter.DateTo = &t
|
|
}
|
|
if raw := q.Get("has_attachment"); raw != "" {
|
|
switch raw {
|
|
case "true", "1":
|
|
v := true
|
|
filter.HasAttachments = &v
|
|
case "false", "0":
|
|
v := false
|
|
filter.HasAttachments = &v
|
|
default:
|
|
return filter, apivalidate.NewValidationError(apivalidate.FieldDetail{
|
|
Field: "has_attachment", Message: "must be true or false",
|
|
})
|
|
}
|
|
}
|
|
|
|
if filter.Query == "" && filter.Sender == "" && filter.Label == "" &&
|
|
filter.AccountID == "" && filter.DateFrom == nil && filter.DateTo == nil &&
|
|
filter.HasAttachments == nil {
|
|
return filter, apivalidate.NewValidationError(apivalidate.FieldDetail{
|
|
Field: "q", Message: "at least one search filter required",
|
|
})
|
|
}
|
|
return filter, nil
|
|
}
|
|
|
|
// stripNonDateListRangeKeys removes from/to when they are sender/recipient filters,
|
|
// not YYYY-MM-DD list date bounds (shared param names on /mail/search).
|
|
func stripNonDateListRangeKeys(values url.Values) url.Values {
|
|
out := values
|
|
clone := make(url.Values, len(values))
|
|
for k, vv := range values {
|
|
clone[k] = append([]string(nil), vv...)
|
|
}
|
|
out = clone
|
|
for _, key := range []string{"from", "to"} {
|
|
raw := strings.TrimSpace(out.Get(key))
|
|
if raw == "" {
|
|
continue
|
|
}
|
|
if _, err := query.ParseDate(raw); err != nil {
|
|
out.Del(key)
|
|
}
|
|
}
|
|
return out
|
|
}
|
|
|
|
func parseSearchSender(q url.Values) string {
|
|
if s := strings.TrimSpace(q.Get("sender")); s != "" {
|
|
return s
|
|
}
|
|
from := strings.TrimSpace(q.Get("from"))
|
|
if from == "" {
|
|
return ""
|
|
}
|
|
if _, err := query.ParseDate(from); err != nil {
|
|
return from
|
|
}
|
|
return ""
|
|
}
|