mirror of
https://github.com/opencloud-eu/opencloud.git
synced 2026-04-25 05:28:20 -05:00
groupware: implement message search with snippets
This commit is contained in:
+76
-5
@@ -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)
|
||||
|
||||
|
||||
+76
-16
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user