mirror of
https://github.com/opencloud-eu/opencloud.git
synced 2026-02-24 02:56:52 -05:00
groupware: implement message search with snippets
This commit is contained in:
@@ -250,10 +250,10 @@ func (j *Client) GetAllMailboxes(accountId string, session *Session, ctx context
|
||||
}
|
||||
|
||||
// https://jmap.io/spec-mail.html#mailboxquery
|
||||
func (j *Client) QueryMailbox(accountId string, session *Session, ctx context.Context, logger *log.Logger, filter MailboxFilterCondition) (MailboxQueryResponse, Error) {
|
||||
func (j *Client) QueryMailbox(accountId string, session *Session, ctx context.Context, logger *log.Logger, filter MailboxFilterElement) (MailboxQueryResponse, Error) {
|
||||
aid := session.MailAccountId(accountId)
|
||||
logger = j.logger(aid, "QueryMailbox", session, logger)
|
||||
cmd, err := request(invocation(MailboxQuery, SimpleMailboxQueryCommand{AccountId: aid, Filter: filter}, "0"))
|
||||
cmd, err := request(invocation(MailboxQuery, MailboxQueryCommand{AccountId: aid, Filter: filter}, "0"))
|
||||
if err != nil {
|
||||
return MailboxQueryResponse{}, SimpleError{code: JmapErrorInvalidJmapRequestPayload, err: err}
|
||||
}
|
||||
@@ -269,12 +269,12 @@ type Mailboxes struct {
|
||||
State string `json:"state,omitempty"`
|
||||
}
|
||||
|
||||
func (j *Client) SearchMailboxes(accountId string, session *Session, ctx context.Context, logger *log.Logger, filter MailboxFilterCondition) (Mailboxes, Error) {
|
||||
func (j *Client) SearchMailboxes(accountId string, session *Session, ctx context.Context, logger *log.Logger, filter MailboxFilterElement) (Mailboxes, Error) {
|
||||
aid := session.MailAccountId(accountId)
|
||||
logger = j.logger(aid, "SearchMailboxes", session, logger)
|
||||
|
||||
cmd, err := request(
|
||||
invocation(MailboxQuery, SimpleMailboxQueryCommand{AccountId: aid, Filter: filter}, "0"),
|
||||
invocation(MailboxQuery, MailboxQueryCommand{AccountId: aid, Filter: filter}, "0"),
|
||||
invocation(MailboxGet, MailboxGetRefCommand{
|
||||
AccountId: aid,
|
||||
IdRef: &ResultReference{Name: MailboxQuery, Path: "/ids/*", ResultOf: "0"},
|
||||
@@ -330,7 +330,7 @@ func (j *Client) GetAllEmails(accountId string, session *Session, ctx context.Co
|
||||
|
||||
query := EmailQueryCommand{
|
||||
AccountId: aid,
|
||||
Filter: &MessageFilter{InMailbox: mailboxId},
|
||||
Filter: &EmailFilterCondition{InMailbox: mailboxId},
|
||||
Sort: []Sort{{Property: emailSortByReceivedAt, IsAscending: false}},
|
||||
CollapseThreads: true,
|
||||
CalculateTotal: false,
|
||||
@@ -518,6 +518,77 @@ func (j *Client) GetEmailsSince(accountId string, session *Session, ctx context.
|
||||
})
|
||||
}
|
||||
|
||||
type EmailQueryResult struct {
|
||||
Snippets []SearchSnippet `json:"snippets,omitempty"`
|
||||
QueryState string `json:"queryState"`
|
||||
Total int `json:"total"`
|
||||
Limit int `json:"limit,omitzero"`
|
||||
Position int `json:"position,omitzero"`
|
||||
}
|
||||
|
||||
func (j *Client) QueryEmails(accountId string, filter EmailFilterElement, session *Session, ctx context.Context, logger *log.Logger, offset int, limit int, fetchBodies bool, maxBodyValueBytes int) (EmailQueryResult, Error) {
|
||||
aid := session.MailAccountId(accountId)
|
||||
logger = j.loggerParams(aid, "QueryEmails", session, logger, func(z zerolog.Context) zerolog.Context {
|
||||
return z.Bool(logFetchBodies, fetchBodies)
|
||||
})
|
||||
|
||||
query := EmailQueryCommand{
|
||||
AccountId: aid,
|
||||
Filter: filter,
|
||||
Sort: []Sort{{Property: emailSortByReceivedAt, IsAscending: false}},
|
||||
CollapseThreads: true,
|
||||
CalculateTotal: true,
|
||||
}
|
||||
if offset >= 0 {
|
||||
query.Position = offset
|
||||
}
|
||||
if limit >= 0 {
|
||||
query.Limit = limit
|
||||
}
|
||||
|
||||
snippet := SearchSnippetRefCommand{
|
||||
AccountId: aid,
|
||||
Filter: filter,
|
||||
EmailIdRef: &ResultReference{
|
||||
ResultOf: "0",
|
||||
Name: EmailQuery,
|
||||
Path: "/ids/*",
|
||||
},
|
||||
}
|
||||
|
||||
cmd, err := request(
|
||||
invocation(EmailQuery, query, "0"),
|
||||
invocation(SearchSnippetGet, snippet, "1"),
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return EmailQueryResult{}, SimpleError{code: JmapErrorInvalidJmapRequestPayload, err: err}
|
||||
}
|
||||
|
||||
return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, func(body *Response) (EmailQueryResult, Error) {
|
||||
var queryResponse EmailQueryResponse
|
||||
err = retrieveResponseMatchParameters(body, EmailQuery, "0", &queryResponse)
|
||||
if err != nil {
|
||||
return EmailQueryResult{}, SimpleError{code: JmapErrorInvalidJmapResponsePayload, err: err}
|
||||
}
|
||||
|
||||
var snippetResponse SearchSnippetGetResponse
|
||||
err = retrieveResponseMatchParameters(body, SearchSnippetGet, "1", &snippetResponse)
|
||||
if err != nil {
|
||||
return EmailQueryResult{}, SimpleError{code: JmapErrorInvalidJmapResponsePayload, err: err}
|
||||
}
|
||||
|
||||
return EmailQueryResult{
|
||||
Snippets: snippetResponse.List,
|
||||
QueryState: queryResponse.QueryState,
|
||||
Total: queryResponse.Total,
|
||||
Limit: queryResponse.Limit,
|
||||
Position: queryResponse.Position,
|
||||
}, nil
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
func (j *Client) GetBlob(accountId string, session *Session, ctx context.Context, logger *log.Logger, id string) (*Blob, Error) {
|
||||
aid := session.BlobAccountId(accountId)
|
||||
|
||||
|
||||
@@ -244,6 +244,14 @@ type SetError struct {
|
||||
Description string `json:"description,omitempty"`
|
||||
}
|
||||
|
||||
type FilterOperatorTerm string
|
||||
|
||||
const (
|
||||
And FilterOperatorTerm = "AND"
|
||||
Or FilterOperatorTerm = "OR"
|
||||
Not FilterOperatorTerm = "NOT"
|
||||
)
|
||||
|
||||
type Mailbox struct {
|
||||
Id string `json:"id,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
@@ -274,7 +282,12 @@ type MailboxChangesCommand struct {
|
||||
MaxChanges int `json:"maxChanges,omitzero"`
|
||||
}
|
||||
|
||||
type MailboxFilterElement interface {
|
||||
_isAMailboxFilterElement() // marker method
|
||||
}
|
||||
|
||||
type MailboxFilterCondition struct {
|
||||
MailboxFilterElement
|
||||
ParentId string `json:"parentId,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
Role string `json:"role,omitempty"`
|
||||
@@ -282,11 +295,16 @@ type MailboxFilterCondition struct {
|
||||
IsSubscribed *bool `json:"isSubscribed,omitempty"`
|
||||
}
|
||||
|
||||
var _ MailboxFilterElement = &MailboxFilterCondition{}
|
||||
|
||||
type MailboxFilterOperator struct {
|
||||
Operator string `json:"operator"`
|
||||
Conditions []MailboxFilterCondition `json:"conditions"`
|
||||
MailboxFilterElement
|
||||
Operator FilterOperatorTerm `json:"operator"`
|
||||
Conditions []MailboxFilterElement `json:"conditions,omitempty"`
|
||||
}
|
||||
|
||||
var _ MailboxFilterElement = &MailboxFilterOperator{}
|
||||
|
||||
type MailboxComparator struct {
|
||||
Property string `json:"property"`
|
||||
IsAscending bool `json:"isAscending,omitempty"`
|
||||
@@ -294,15 +312,20 @@ type MailboxComparator struct {
|
||||
CalculateTotal bool `json:"calculateTotal,omitempty"`
|
||||
}
|
||||
|
||||
type SimpleMailboxQueryCommand struct {
|
||||
AccountId string `json:"accountId"`
|
||||
Filter MailboxFilterCondition `json:"filter,omitempty"`
|
||||
Sort []MailboxComparator `json:"sort,omitempty"`
|
||||
SortAsTree bool `json:"sortAsTree,omitempty"`
|
||||
FilterAsTree bool `json:"filterAsTree,omitempty"`
|
||||
type MailboxQueryCommand struct {
|
||||
AccountId string `json:"accountId"`
|
||||
Filter MailboxFilterElement `json:"filter,omitempty"`
|
||||
Sort []MailboxComparator `json:"sort,omitempty"`
|
||||
SortAsTree bool `json:"sortAsTree,omitempty"`
|
||||
FilterAsTree bool `json:"filterAsTree,omitempty"`
|
||||
}
|
||||
|
||||
type MessageFilter struct {
|
||||
type EmailFilterElement interface {
|
||||
_isAnEmailFilterElement() // marker method
|
||||
}
|
||||
|
||||
type EmailFilterCondition struct {
|
||||
EmailFilterElement
|
||||
InMailbox string `json:"inMailbox,omitempty"`
|
||||
InMailboxOtherThan []string `json:"inMailboxOtherThan,omitempty"`
|
||||
Before time.Time `json:"before,omitzero"` // omitzero requires Go 1.24
|
||||
@@ -316,8 +339,25 @@ type MessageFilter struct {
|
||||
NotKeyword string `json:"notKeyword,omitempty"`
|
||||
HasAttachment bool `json:"hasAttachment,omitempty"`
|
||||
Text string `json:"text,omitempty"`
|
||||
From string `json:"from,omitempty"`
|
||||
To string `json:"to,omitempty"`
|
||||
Cc string `json:"cc,omitempty"`
|
||||
Bcc string `json:"bcc,omitempty"`
|
||||
Subject string `json:"subject,omitempty"`
|
||||
Body string `json:"body,omitempty"`
|
||||
Header []string `json:"header,omitempty"`
|
||||
}
|
||||
|
||||
var _ EmailFilterElement = &EmailFilterCondition{}
|
||||
|
||||
type EmailFilterOperator struct {
|
||||
EmailFilterElement
|
||||
Operator FilterOperatorTerm `json:"operator"`
|
||||
Conditions []EmailFilterElement `json:"conditions,omitempty"`
|
||||
}
|
||||
|
||||
var _ EmailFilterElement = &EmailFilterOperator{}
|
||||
|
||||
type Sort struct {
|
||||
Property string `json:"property,omitempty"`
|
||||
IsAscending bool `json:"isAscending,omitempty"`
|
||||
@@ -326,13 +366,13 @@ type Sort struct {
|
||||
}
|
||||
|
||||
type EmailQueryCommand struct {
|
||||
AccountId string `json:"accountId"`
|
||||
Filter *MessageFilter `json:"filter,omitempty"`
|
||||
Sort []Sort `json:"sort,omitempty"`
|
||||
CollapseThreads bool `json:"collapseThreads,omitempty"`
|
||||
Position int `json:"position,omitempty"`
|
||||
Limit int `json:"limit,omitempty"`
|
||||
CalculateTotal bool `json:"calculateTotal,omitempty"`
|
||||
AccountId string `json:"accountId"`
|
||||
Filter EmailFilterElement `json:"filter,omitempty"`
|
||||
Sort []Sort `json:"sort,omitempty"`
|
||||
CollapseThreads bool `json:"collapseThreads,omitempty"`
|
||||
Position int `json:"position,omitempty"`
|
||||
Limit int `json:"limit,omitempty"`
|
||||
CalculateTotal bool `json:"calculateTotal,omitempty"`
|
||||
}
|
||||
|
||||
type EmailGetCommand struct {
|
||||
@@ -1306,6 +1346,24 @@ type BlobDownload struct {
|
||||
CacheControl string
|
||||
}
|
||||
|
||||
type SearchSnippet struct {
|
||||
EmailId string `json:"emailId"`
|
||||
Subject string `json:"subject,omitempty"`
|
||||
Preview string `json:"preview,omitempty"`
|
||||
}
|
||||
|
||||
type SearchSnippetRefCommand struct {
|
||||
AccountId string `json:"accountId"`
|
||||
Filter EmailFilterElement `json:"filter,omitempty"`
|
||||
EmailIdRef *ResultReference `json:"#emailIds,omitempty"`
|
||||
}
|
||||
|
||||
type SearchSnippetGetResponse struct {
|
||||
AccountId string `json:"accountId"`
|
||||
List []SearchSnippet `json:"list,omitempty"`
|
||||
NotFound []string `json:"notFound,omitempty"`
|
||||
}
|
||||
|
||||
const (
|
||||
BlobGet Command = "Blob/get"
|
||||
BlobUpload Command = "Blob/upload"
|
||||
@@ -1320,6 +1378,7 @@ const (
|
||||
MailboxChanges Command = "Mailbox/changes"
|
||||
IdentityGet Command = "Identity/get"
|
||||
VacationResponseGet Command = "VacationResponse/get"
|
||||
SearchSnippetGet Command = "SearchSnippet/get"
|
||||
)
|
||||
|
||||
var CommandResponseTypeMap = map[Command]func() any{
|
||||
@@ -1334,4 +1393,5 @@ var CommandResponseTypeMap = map[Command]func() any{
|
||||
ThreadGet: func() any { return ThreadGetResponse{} },
|
||||
IdentityGet: func() any { return IdentityGetResponse{} },
|
||||
VacationResponseGet: func() any { return VacationResponseGetResponse{} },
|
||||
SearchSnippetGet: func() any { return SearchSnippetGetResponse{} },
|
||||
}
|
||||
|
||||
@@ -51,15 +51,15 @@ func command[T any](api ApiClient,
|
||||
return zero, jmapErr
|
||||
}
|
||||
|
||||
var data Response
|
||||
err := json.Unmarshal(responseBody, &data)
|
||||
var response Response
|
||||
err := json.Unmarshal(responseBody, &response)
|
||||
if err != nil {
|
||||
logger.Error().Err(err).Msg("failed to deserialize body JSON payload")
|
||||
var zero T
|
||||
return zero, SimpleError{code: JmapErrorDecodingResponseBody, err: err}
|
||||
}
|
||||
|
||||
if data.SessionState != session.State {
|
||||
if response.SessionState != session.State {
|
||||
if sessionOutdatedHandler != nil {
|
||||
sessionOutdatedHandler(session)
|
||||
}
|
||||
@@ -67,7 +67,7 @@ func command[T any](api ApiClient,
|
||||
|
||||
// search for an "error" response
|
||||
// https://jmap.io/spec-core.html#method-level-errors
|
||||
for _, mr := range data.MethodResponses {
|
||||
for _, mr := range response.MethodResponses {
|
||||
if mr.Command == "error" {
|
||||
err := fmt.Errorf("found method level error in response '%v'", mr.Tag)
|
||||
if payload, ok := mr.Parameters.(map[string]any); ok {
|
||||
@@ -80,7 +80,7 @@ func command[T any](api ApiClient,
|
||||
}
|
||||
}
|
||||
|
||||
return mapper(&data)
|
||||
return mapper(&response)
|
||||
}
|
||||
|
||||
func mapstructStringToTimeHook() mapstructure.DecodeHookFunc {
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
|
||||
"github.com/opencloud-eu/opencloud/pkg/jmap"
|
||||
"github.com/opencloud-eu/opencloud/pkg/log"
|
||||
)
|
||||
|
||||
@@ -37,6 +38,7 @@ func (g Groupware) GetAllMessages(w http.ResponseWriter, r *http.Request) {
|
||||
})
|
||||
} else {
|
||||
g.respond(w, r, func(req Request) (any, string, *Error) {
|
||||
l := req.logger.With()
|
||||
if mailboxId == "" {
|
||||
errorId := req.errorId()
|
||||
msg := fmt.Sprintf("Missing required mailbox ID path parameter '%v'", UriParamMailboxId)
|
||||
@@ -45,28 +47,23 @@ func (g Groupware) GetAllMessages(w http.ResponseWriter, r *http.Request) {
|
||||
withSource(&ErrorSource{Parameter: UriParamMailboxId}),
|
||||
)
|
||||
}
|
||||
page, ok, err := req.parseNumericParam(QueryParamPage, -1)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
logger := req.logger
|
||||
if ok {
|
||||
logger = &log.Logger{Logger: logger.With().Int(QueryParamPage, page).Logger()}
|
||||
}
|
||||
|
||||
size, ok, err := req.parseNumericParam(QueryParamSize, -1)
|
||||
offset, ok, err := req.parseNumericParam(QueryParamOffset, 0)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
if ok {
|
||||
logger = &log.Logger{Logger: logger.With().Int(QueryParamSize, size).Logger()}
|
||||
l = l.Int(QueryParamOffset, offset)
|
||||
}
|
||||
|
||||
offset := page * size
|
||||
limit := size
|
||||
if limit < 0 {
|
||||
limit = g.defaultEmailLimit
|
||||
limit, ok, err := req.parseNumericParam(QueryParamLimit, g.defaultEmailLimit)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
if ok {
|
||||
l = l.Int(QueryParamLimit, limit)
|
||||
}
|
||||
|
||||
logger := &log.Logger{Logger: l.Logger()}
|
||||
|
||||
emails, jerr := g.jmap.GetAllEmails(req.GetAccountId(), req.session, req.ctx, logger, mailboxId, offset, limit, true, g.maxBodyValueBytes)
|
||||
if jerr != nil {
|
||||
@@ -101,21 +98,141 @@ func (g Groupware) GetMessagesById(w http.ResponseWriter, r *http.Request) {
|
||||
})
|
||||
}
|
||||
|
||||
func (g Groupware) GetMessageUpdates(w http.ResponseWriter, r *http.Request) {
|
||||
func (g Groupware) GetMessages(w http.ResponseWriter, r *http.Request) {
|
||||
q := r.URL.Query()
|
||||
since := q.Get(QueryParamSince)
|
||||
if since == "" {
|
||||
since = r.Header.Get("If-None-Match")
|
||||
}
|
||||
maxChanges := -1
|
||||
g.respond(w, r, func(req Request) (any, string, *Error) {
|
||||
logger := &log.Logger{Logger: req.logger.With().Str(HeaderSince, since).Logger()}
|
||||
if since != "" {
|
||||
// get messages changes since a given state
|
||||
maxChanges := -1
|
||||
g.respond(w, r, func(req Request) (any, string, *Error) {
|
||||
logger := &log.Logger{Logger: req.logger.With().Str(HeaderSince, since).Logger()}
|
||||
|
||||
emails, jerr := g.jmap.GetEmailsSince(req.GetAccountId(), req.session, req.ctx, logger, since, true, g.maxBodyValueBytes, maxChanges)
|
||||
if jerr != nil {
|
||||
return nil, "", req.apiErrorFromJmap(jerr)
|
||||
}
|
||||
emails, jerr := g.jmap.GetEmailsSince(req.GetAccountId(), req.session, req.ctx, logger, since, true, g.maxBodyValueBytes, maxChanges)
|
||||
if jerr != nil {
|
||||
return nil, "", req.apiErrorFromJmap(jerr)
|
||||
}
|
||||
|
||||
return emails, emails.State, nil
|
||||
})
|
||||
return emails, emails.State, nil
|
||||
})
|
||||
} else {
|
||||
// do a search
|
||||
g.respond(w, r, func(req Request) (any, string, *Error) {
|
||||
mailboxId := q.Get(QueryParamMailboxId)
|
||||
notInMailboxIds := q[QueryParamNotInMailboxId]
|
||||
text := q.Get(QueryParamSearchText)
|
||||
from := q.Get(QueryParamSearchFrom)
|
||||
to := q.Get(QueryParamSearchTo)
|
||||
cc := q.Get(QueryParamSearchCc)
|
||||
bcc := q.Get(QueryParamSearchBcc)
|
||||
subject := q.Get(QueryParamSearchSubject)
|
||||
body := q.Get(QueryParamSearchBody)
|
||||
|
||||
l := req.logger.With()
|
||||
|
||||
offset, ok, err := req.parseNumericParam(QueryParamOffset, 0)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
if ok {
|
||||
l = l.Int(QueryParamOffset, offset)
|
||||
}
|
||||
|
||||
limit, ok, err := req.parseNumericParam(QueryParamLimit, g.defaultEmailLimit)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
if ok {
|
||||
l = l.Int(QueryParamLimit, limit)
|
||||
}
|
||||
|
||||
before, ok, err := req.parseDateParam(QueryParamSearchBefore)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
if ok {
|
||||
l = l.Time(QueryParamSearchBefore, before)
|
||||
}
|
||||
|
||||
after, ok, err := req.parseDateParam(QueryParamSearchAfter)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
if ok {
|
||||
l = l.Time(QueryParamSearchAfter, after)
|
||||
}
|
||||
|
||||
if mailboxId != "" {
|
||||
l = l.Str(QueryParamMailboxId, logstr(mailboxId))
|
||||
}
|
||||
if len(notInMailboxIds) > 0 {
|
||||
l = l.Array(QueryParamNotInMailboxId, logstrarray(notInMailboxIds))
|
||||
}
|
||||
if text != "" {
|
||||
l = l.Str(QueryParamSearchText, logstr(text))
|
||||
}
|
||||
if from != "" {
|
||||
l = l.Str(QueryParamSearchFrom, logstr(from))
|
||||
}
|
||||
if to != "" {
|
||||
l = l.Str(QueryParamSearchTo, logstr(to))
|
||||
}
|
||||
if cc != "" {
|
||||
l = l.Str(QueryParamSearchCc, logstr(cc))
|
||||
}
|
||||
if bcc != "" {
|
||||
l = l.Str(QueryParamSearchBcc, logstr(bcc))
|
||||
}
|
||||
if subject != "" {
|
||||
l = l.Str(QueryParamSearchSubject, logstr(subject))
|
||||
}
|
||||
if body != "" {
|
||||
l = l.Str(QueryParamSearchBody, logstr(body))
|
||||
}
|
||||
|
||||
minSize, ok, err := req.parseNumericParam(QueryParamSearchMinSize, 0)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
if ok {
|
||||
l = l.Int(QueryParamSearchMinSize, minSize)
|
||||
}
|
||||
|
||||
maxSize, ok, err := req.parseNumericParam(QueryParamSearchMaxSize, 0)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
if ok {
|
||||
l = l.Int(QueryParamSearchMaxSize, maxSize)
|
||||
}
|
||||
|
||||
logger := &log.Logger{Logger: l.Logger()}
|
||||
|
||||
filter := jmap.EmailFilterCondition{
|
||||
Text: text,
|
||||
InMailbox: mailboxId,
|
||||
InMailboxOtherThan: notInMailboxIds,
|
||||
From: from,
|
||||
To: to,
|
||||
Cc: cc,
|
||||
Bcc: bcc,
|
||||
Subject: subject,
|
||||
Body: body,
|
||||
Before: before,
|
||||
After: after,
|
||||
MinSize: minSize,
|
||||
MaxSize: maxSize,
|
||||
//HasKeyword: "",
|
||||
}
|
||||
|
||||
emails, jerr := g.jmap.QueryEmails(req.GetAccountId(), &filter, req.session, req.ctx, logger, offset, limit, false, 0)
|
||||
if jerr != nil {
|
||||
return nil, "", req.apiErrorFromJmap(jerr)
|
||||
}
|
||||
|
||||
return emails, emails.QueryState, nil
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/go-chi/render"
|
||||
@@ -222,6 +223,29 @@ func (r Request) parseNumericParam(param string, defaultValue int) (int, bool, *
|
||||
return int(value), true, nil
|
||||
}
|
||||
|
||||
func (r Request) parseDateParam(param string) (time.Time, bool, *Error) {
|
||||
q := r.r.URL.Query()
|
||||
if !q.Has(param) {
|
||||
return time.Time{}, false, nil
|
||||
}
|
||||
|
||||
str := q.Get(param)
|
||||
if str == "" {
|
||||
return time.Time{}, false, nil
|
||||
}
|
||||
|
||||
t, err := time.Parse(time.RFC3339, str)
|
||||
if err != nil {
|
||||
errorId := r.errorId()
|
||||
msg := fmt.Sprintf("Invalid RFC3339 value for query parameter '%v': '%s': %s", param, logstr(str), err.Error())
|
||||
return time.Time{}, true, apiError(errorId, ErrorInvalidRequestParameter,
|
||||
withDetail(msg),
|
||||
withSource(&ErrorSource{Parameter: param}),
|
||||
)
|
||||
}
|
||||
return t, true, nil
|
||||
}
|
||||
|
||||
// Safely caps a string to a given size to avoid log bombing.
|
||||
// Use this function to wrap strings that are user input (HTTP headers, path parameters, URI parameters, HTTP body, ...).
|
||||
func logstr(text string) string {
|
||||
@@ -234,6 +258,22 @@ func logstr(text string) string {
|
||||
}
|
||||
}
|
||||
|
||||
type SafeLogStringArrayMarshaller struct {
|
||||
array []string
|
||||
}
|
||||
|
||||
func (m SafeLogStringArrayMarshaller) MarshalZerologArray(a *zerolog.Array) {
|
||||
for _, elem := range m.array {
|
||||
a.Str(logstr(elem))
|
||||
}
|
||||
}
|
||||
|
||||
var _ zerolog.LogArrayMarshaler = SafeLogStringArrayMarshaller{}
|
||||
|
||||
func logstrarray(array []string) SafeLogStringArrayMarshaller {
|
||||
return SafeLogStringArrayMarshaller{array: array}
|
||||
}
|
||||
|
||||
func (g Groupware) log(error *Error) {
|
||||
var level *zerolog.Event
|
||||
if error.NumStatus < 300 {
|
||||
|
||||
@@ -5,16 +5,29 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
UriParamAccount = "account"
|
||||
UriParamMailboxId = "mailbox"
|
||||
QueryParamPage = "page"
|
||||
QueryParamSize = "size"
|
||||
UriParamMessagesId = "id"
|
||||
UriParamBlobId = "blobid"
|
||||
UriParamBlobName = "blobname"
|
||||
QueryParamBlobType = "type"
|
||||
QueryParamSince = "since"
|
||||
HeaderSince = "if-none-match"
|
||||
UriParamAccount = "account"
|
||||
UriParamMailboxId = "mailbox"
|
||||
UriParamMessagesId = "id"
|
||||
UriParamBlobId = "blobid"
|
||||
UriParamBlobName = "blobname"
|
||||
QueryParamBlobType = "type"
|
||||
QueryParamSince = "since"
|
||||
QueryParamMailboxId = "mailbox"
|
||||
QueryParamNotInMailboxId = "notmailbox"
|
||||
QueryParamSearchText = "text"
|
||||
QueryParamSearchFrom = "from"
|
||||
QueryParamSearchTo = "to"
|
||||
QueryParamSearchCc = "cc"
|
||||
QueryParamSearchBcc = "bcc"
|
||||
QueryParamSearchSubject = "subject"
|
||||
QueryParamSearchBody = "body"
|
||||
QueryParamSearchBefore = "before"
|
||||
QueryParamSearchAfter = "after"
|
||||
QueryParamSearchMinSize = "minsize"
|
||||
QueryParamSearchMaxSize = "maxsize"
|
||||
QueryParamOffset = "offset"
|
||||
QueryParamLimit = "limit"
|
||||
HeaderSince = "if-none-match"
|
||||
)
|
||||
|
||||
func (g Groupware) Route(r chi.Router) {
|
||||
@@ -30,7 +43,7 @@ func (g Groupware) Route(r chi.Router) {
|
||||
r.Get("/{mailbox}/messages", g.GetAllMessages)
|
||||
})
|
||||
r.Route("/messages", func(r chi.Router) {
|
||||
r.Get("/", g.GetMessageUpdates)
|
||||
r.Get("/", g.GetMessages)
|
||||
r.Get("/{id}", g.GetMessagesById)
|
||||
})
|
||||
r.Route("/blobs", func(r chi.Router) {
|
||||
|
||||
Reference in New Issue
Block a user