mirror of
https://github.com/opencloud-eu/opencloud.git
synced 2026-04-06 00:23:53 -04:00
groupware: implement Mailbox modification endpoints + refactor ETag/state in the framework
* add endpoints for Mailboxes:
- PATCH mailboxes/{id}
- DELETE mailboxes/{id}
- POST mailboxes
* refactor the pkg/jmap and groupware framework to systematically
return a jmap.State out-of-band of the per-method payloads, since
they are almost always present in JMAP responses, which lead to the
artificial creation of a lot of composed struct types just to also
return the State; on the downside, it adds yet another return
parameter
This commit is contained in:
@@ -9,12 +9,7 @@ import (
|
||||
"github.com/opencloud-eu/opencloud/pkg/log"
|
||||
)
|
||||
|
||||
type BlobResponse struct {
|
||||
Blob *Blob `json:"blob,omitempty"`
|
||||
State State `json:"state,omitempty"`
|
||||
}
|
||||
|
||||
func (j *Client) GetBlobMetadata(accountId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, id string) (BlobResponse, SessionState, Language, Error) {
|
||||
func (j *Client) GetBlobMetadata(accountId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, id string) (*Blob, SessionState, State, Language, Error) {
|
||||
cmd, jerr := j.request(session, logger,
|
||||
invocation(CommandBlobGet, BlobGetCommand{
|
||||
AccountId: accountId,
|
||||
@@ -24,22 +19,22 @@ func (j *Client) GetBlobMetadata(accountId string, session *Session, ctx context
|
||||
}, "0"),
|
||||
)
|
||||
if jerr != nil {
|
||||
return BlobResponse{}, "", "", jerr
|
||||
return nil, "", "", "", jerr
|
||||
}
|
||||
|
||||
return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (BlobResponse, Error) {
|
||||
return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (*Blob, State, Error) {
|
||||
var response BlobGetResponse
|
||||
err := retrieveResponseMatchParameters(logger, body, CommandBlobGet, "0", &response)
|
||||
if err != nil {
|
||||
return BlobResponse{}, err
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
if len(response.List) != 1 {
|
||||
logger.Error().Msgf("%T.List has %v entries instead of 1", response, len(response.List))
|
||||
return BlobResponse{}, simpleError(err, JmapErrorInvalidJmapResponsePayload)
|
||||
return nil, "", simpleError(err, JmapErrorInvalidJmapResponsePayload)
|
||||
}
|
||||
get := response.List[0]
|
||||
return BlobResponse{Blob: &get, State: response.State}, nil
|
||||
return &get, response.State, nil
|
||||
})
|
||||
}
|
||||
|
||||
@@ -48,7 +43,6 @@ type UploadedBlob struct {
|
||||
Size int `json:"size"`
|
||||
Type string `json:"type"`
|
||||
Sha512 string `json:"sha:512"`
|
||||
State State `json:"state"`
|
||||
}
|
||||
|
||||
func (j *Client) UploadBlobStream(accountId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, contentType string, body io.Reader) (UploadedBlob, Language, Error) {
|
||||
@@ -70,7 +64,7 @@ func (j *Client) DownloadBlobStream(accountId string, blobId string, name string
|
||||
return j.blob.DownloadBinary(ctx, logger, session, downloadUrl, session.DownloadEndpoint, acceptLanguage)
|
||||
}
|
||||
|
||||
func (j *Client) UploadBlob(accountId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, data []byte, contentType string) (UploadedBlob, SessionState, Language, Error) {
|
||||
func (j *Client) UploadBlob(accountId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, data []byte, contentType string) (UploadedBlob, SessionState, State, Language, Error) {
|
||||
encoded := base64.StdEncoding.EncodeToString(data)
|
||||
|
||||
upload := BlobUploadCommand{
|
||||
@@ -100,35 +94,35 @@ func (j *Client) UploadBlob(accountId string, session *Session, ctx context.Cont
|
||||
invocation(CommandBlobGet, getHash, "1"),
|
||||
)
|
||||
if jerr != nil {
|
||||
return UploadedBlob{}, "", "", jerr
|
||||
return UploadedBlob{}, "", "", "", jerr
|
||||
}
|
||||
|
||||
return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (UploadedBlob, Error) {
|
||||
return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (UploadedBlob, State, Error) {
|
||||
var uploadResponse BlobUploadResponse
|
||||
err := retrieveResponseMatchParameters(logger, body, CommandBlobUpload, "0", &uploadResponse)
|
||||
if err != nil {
|
||||
return UploadedBlob{}, err
|
||||
return UploadedBlob{}, "", err
|
||||
}
|
||||
|
||||
var getResponse BlobGetResponse
|
||||
err = retrieveResponseMatchParameters(logger, body, CommandBlobGet, "1", &getResponse)
|
||||
if err != nil {
|
||||
return UploadedBlob{}, err
|
||||
return UploadedBlob{}, "", err
|
||||
}
|
||||
|
||||
if len(uploadResponse.Created) != 1 {
|
||||
logger.Error().Msgf("%T.Created has %v entries instead of 1", uploadResponse, len(uploadResponse.Created))
|
||||
return UploadedBlob{}, simpleError(err, JmapErrorInvalidJmapResponsePayload)
|
||||
return UploadedBlob{}, "", simpleError(err, JmapErrorInvalidJmapResponsePayload)
|
||||
}
|
||||
upload, ok := uploadResponse.Created["0"]
|
||||
if !ok {
|
||||
logger.Error().Msgf("%T.Created has no item '0'", uploadResponse)
|
||||
return UploadedBlob{}, simpleError(err, JmapErrorInvalidJmapResponsePayload)
|
||||
return UploadedBlob{}, "", simpleError(err, JmapErrorInvalidJmapResponsePayload)
|
||||
}
|
||||
|
||||
if len(getResponse.List) != 1 {
|
||||
logger.Error().Msgf("%T.List has %v entries instead of 1", getResponse, len(getResponse.List))
|
||||
return UploadedBlob{}, simpleError(err, JmapErrorInvalidJmapResponsePayload)
|
||||
return UploadedBlob{}, "", simpleError(err, JmapErrorInvalidJmapResponsePayload)
|
||||
}
|
||||
get := getResponse.List[0]
|
||||
|
||||
@@ -137,8 +131,7 @@ func (j *Client) UploadBlob(accountId string, session *Session, ctx context.Cont
|
||||
Size: upload.Size,
|
||||
Type: upload.Type,
|
||||
Sha512: get.DigestSha512,
|
||||
State: getResponse.State,
|
||||
}, nil
|
||||
}, getResponse.State, nil
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ type AccountBootstrapResult struct {
|
||||
Quotas []Quota `json:"quotas,omitempty"`
|
||||
}
|
||||
|
||||
func (j *Client) GetBootstrap(accountIds []string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string) (map[string]AccountBootstrapResult, SessionState, Language, Error) {
|
||||
func (j *Client) GetBootstrap(accountIds []string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string) (map[string]AccountBootstrapResult, SessionState, State, Language, Error) {
|
||||
uniqueAccountIds := structs.Uniq(accountIds)
|
||||
|
||||
logger = j.logger("GetBootstrap", session, logger)
|
||||
@@ -25,26 +25,30 @@ func (j *Client) GetBootstrap(accountIds []string, session *Session, ctx context
|
||||
|
||||
cmd, err := j.request(session, logger, calls...)
|
||||
if err != nil {
|
||||
return nil, "", "", err
|
||||
return nil, "", "", "", err
|
||||
}
|
||||
return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (map[string]AccountBootstrapResult, Error) {
|
||||
return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (map[string]AccountBootstrapResult, State, Error) {
|
||||
identityPerAccount := map[string][]Identity{}
|
||||
quotaPerAccount := map[string][]Quota{}
|
||||
identityStatesPerAccount := map[string]State{}
|
||||
quotaStatesPerAccount := map[string]State{}
|
||||
for _, accountId := range uniqueAccountIds {
|
||||
var identityResponse IdentityGetResponse
|
||||
err = retrieveResponseMatchParameters(logger, body, CommandIdentityGet, mcid(accountId, "I"), &identityResponse)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, "", err
|
||||
} else {
|
||||
identityPerAccount[accountId] = identityResponse.List
|
||||
identityStatesPerAccount[accountId] = identityResponse.State
|
||||
}
|
||||
|
||||
var quotaResponse QuotaGetResponse
|
||||
err = retrieveResponseMatchParameters(logger, body, CommandQuotaGet, mcid(accountId, "Q"), "aResponse)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, "", err
|
||||
} else {
|
||||
quotaPerAccount[accountId] = quotaResponse.List
|
||||
quotaStatesPerAccount[accountId] = quotaResponse.State
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,6 +69,7 @@ func (j *Client) GetBootstrap(accountIds []string, session *Session, ctx context
|
||||
r.Quotas = value
|
||||
result[accountId] = r
|
||||
}
|
||||
return result, nil
|
||||
|
||||
return result, squashStateMaps(identityStatesPerAccount, quotaStatesPerAccount), nil
|
||||
})
|
||||
}
|
||||
|
||||
@@ -6,22 +6,22 @@ import (
|
||||
"github.com/opencloud-eu/opencloud/pkg/log"
|
||||
)
|
||||
|
||||
func (j *Client) ParseICalendarBlob(accountId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, blobIds []string) (CalendarEventParseResponse, SessionState, Language, Error) {
|
||||
func (j *Client) ParseICalendarBlob(accountId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, blobIds []string) (CalendarEventParseResponse, SessionState, State, Language, Error) {
|
||||
logger = j.logger("ParseICalendarBlob", session, logger)
|
||||
|
||||
cmd, err := j.request(session, logger,
|
||||
invocation(CommandCalendarEventParse, CalendarEventParseCommand{AccountId: accountId, BlobIDs: blobIds}, "0"),
|
||||
)
|
||||
if err != nil {
|
||||
return CalendarEventParseResponse{}, "", "", err
|
||||
return CalendarEventParseResponse{}, "", "", "", err
|
||||
}
|
||||
|
||||
return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (CalendarEventParseResponse, Error) {
|
||||
return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (CalendarEventParseResponse, State, Error) {
|
||||
var response CalendarEventParseResponse
|
||||
err = retrieveResponseMatchParameters(logger, body, CommandCalendarEventParse, "0", &response)
|
||||
if err != nil {
|
||||
return CalendarEventParseResponse{}, err
|
||||
return CalendarEventParseResponse{}, "", err
|
||||
}
|
||||
return response, nil
|
||||
return response, "", nil
|
||||
})
|
||||
}
|
||||
|
||||
@@ -12,36 +12,34 @@ import (
|
||||
type AddressBooksResponse struct {
|
||||
AddressBooks []AddressBook `json:"addressbooks"`
|
||||
NotFound []string `json:"notFound,omitempty"`
|
||||
State State `json:"state"`
|
||||
}
|
||||
|
||||
func (j *Client) GetAddressbooks(accountId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, ids []string) (AddressBooksResponse, SessionState, Language, Error) {
|
||||
func (j *Client) GetAddressbooks(accountId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, ids []string) (AddressBooksResponse, SessionState, State, Language, Error) {
|
||||
logger = j.logger("GetAddressbooks", session, logger)
|
||||
|
||||
cmd, err := j.request(session, logger,
|
||||
invocation(CommandAddressBookGet, AddressBookGetCommand{AccountId: accountId, Ids: ids}, "0"),
|
||||
)
|
||||
if err != nil {
|
||||
return AddressBooksResponse{}, "", "", err
|
||||
return AddressBooksResponse{}, "", "", "", err
|
||||
}
|
||||
|
||||
return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (AddressBooksResponse, Error) {
|
||||
return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (AddressBooksResponse, State, Error) {
|
||||
var response AddressBookGetResponse
|
||||
err = retrieveResponseMatchParameters(logger, body, CommandAddressBookGet, "0", &response)
|
||||
if err != nil {
|
||||
return AddressBooksResponse{}, err
|
||||
return AddressBooksResponse{}, response.State, err
|
||||
}
|
||||
return AddressBooksResponse{
|
||||
AddressBooks: response.List,
|
||||
NotFound: response.NotFound,
|
||||
State: response.State,
|
||||
}, nil
|
||||
}, response.State, nil
|
||||
})
|
||||
}
|
||||
|
||||
func (j *Client) QueryContactCards(accountIds []string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string,
|
||||
filter ContactCardFilterElement, sortBy []ContactCardComparator,
|
||||
position uint, limit uint) (map[string][]jscontact.ContactCard, SessionState, Language, Error) {
|
||||
position uint, limit uint) (map[string][]jscontact.ContactCard, SessionState, State, Language, Error) {
|
||||
logger = j.logger("QueryContactCards", session, logger)
|
||||
|
||||
uniqueAccountIds := structs.Uniq(accountIds)
|
||||
@@ -75,32 +73,29 @@ func (j *Client) QueryContactCards(accountIds []string, session *Session, ctx co
|
||||
}
|
||||
cmd, err := j.request(session, logger, invocations...)
|
||||
if err != nil {
|
||||
return nil, "", "", err
|
||||
return nil, "", "", "", err
|
||||
}
|
||||
|
||||
return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (map[string][]jscontact.ContactCard, Error) {
|
||||
return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (map[string][]jscontact.ContactCard, State, Error) {
|
||||
resp := map[string][]jscontact.ContactCard{}
|
||||
stateByAccountId := map[string]State{}
|
||||
for _, accountId := range uniqueAccountIds {
|
||||
var response ContactCardGetResponse
|
||||
err = retrieveResponseMatchParameters(logger, body, CommandContactCardGet, mcid(accountId, "1"), &response)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, "", err
|
||||
}
|
||||
if len(response.NotFound) > 0 {
|
||||
// TODO what to do when there are not-found emails here? potentially nothing, they could have been deleted between query and get?
|
||||
}
|
||||
resp[accountId] = response.List
|
||||
stateByAccountId[accountId] = response.State
|
||||
}
|
||||
return resp, nil
|
||||
return resp, squashState(stateByAccountId), nil
|
||||
})
|
||||
}
|
||||
|
||||
type CreatedContactCard struct {
|
||||
ContactCard *jscontact.ContactCard `json:"contactCard"`
|
||||
State State `json:"state"`
|
||||
}
|
||||
|
||||
func (j *Client) CreateContactCard(accountId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, create jscontact.ContactCard) (CreatedContactCard, SessionState, Language, Error) {
|
||||
func (j *Client) CreateContactCard(accountId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, create jscontact.ContactCard) (*jscontact.ContactCard, SessionState, State, Language, Error) {
|
||||
logger = j.logger("CreateContactCard", session, logger)
|
||||
|
||||
cmd, err := j.request(session, logger,
|
||||
@@ -116,53 +111,45 @@ func (j *Client) CreateContactCard(accountId string, session *Session, ctx conte
|
||||
}, "1"),
|
||||
)
|
||||
if err != nil {
|
||||
return CreatedContactCard{}, "", "", err
|
||||
return nil, "", "", "", err
|
||||
}
|
||||
|
||||
return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (CreatedContactCard, Error) {
|
||||
return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (*jscontact.ContactCard, State, Error) {
|
||||
var setResponse ContactCardSetResponse
|
||||
err = retrieveResponseMatchParameters(logger, body, CommandContactCardSet, "0", &setResponse)
|
||||
if err != nil {
|
||||
return CreatedContactCard{}, err
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
setErr, notok := setResponse.NotCreated["c"]
|
||||
if notok {
|
||||
logger.Error().Msgf("%T.NotCreated returned an error %v", setResponse, setErr)
|
||||
return CreatedContactCard{}, setErrorError(setErr, EmailType)
|
||||
return nil, "", setErrorError(setErr, EmailType)
|
||||
}
|
||||
|
||||
if created, ok := setResponse.Created["c"]; !ok || created == nil {
|
||||
berr := fmt.Errorf("failed to find %s in %s response", string(ContactCardType), string(CommandContactCardSet))
|
||||
logger.Error().Err(berr)
|
||||
return CreatedContactCard{}, simpleError(berr, JmapErrorInvalidJmapResponsePayload)
|
||||
return nil, "", simpleError(berr, JmapErrorInvalidJmapResponsePayload)
|
||||
}
|
||||
|
||||
var getResponse ContactCardGetResponse
|
||||
err = retrieveResponseMatchParameters(logger, body, CommandContactCardGet, "1", &getResponse)
|
||||
if err != nil {
|
||||
return CreatedContactCard{}, err
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
if len(getResponse.List) < 1 {
|
||||
berr := fmt.Errorf("failed to find %s in %s response", string(ContactCardType), string(CommandContactCardSet))
|
||||
logger.Error().Err(berr)
|
||||
return CreatedContactCard{}, simpleError(berr, JmapErrorInvalidJmapResponsePayload)
|
||||
return nil, "", simpleError(berr, JmapErrorInvalidJmapResponsePayload)
|
||||
}
|
||||
|
||||
return CreatedContactCard{
|
||||
ContactCard: &getResponse.List[0],
|
||||
State: setResponse.NewState,
|
||||
}, nil
|
||||
return &getResponse.List[0], setResponse.NewState, nil
|
||||
})
|
||||
}
|
||||
|
||||
type DeletedContactCards struct {
|
||||
State State `json:"state"`
|
||||
NotDestroyed map[string]SetError `json:"notDestroyed"`
|
||||
}
|
||||
|
||||
func (j *Client) DeleteContactCard(accountId string, destroy []string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string) (DeletedContactCards, SessionState, Language, Error) {
|
||||
func (j *Client) DeleteContactCard(accountId string, destroy []string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string) (map[string]SetError, SessionState, State, Language, Error) {
|
||||
logger = j.logger("DeleteContactCard", session, logger)
|
||||
|
||||
cmd, err := j.request(session, logger,
|
||||
@@ -172,18 +159,15 @@ func (j *Client) DeleteContactCard(accountId string, destroy []string, session *
|
||||
}, "0"),
|
||||
)
|
||||
if err != nil {
|
||||
return DeletedContactCards{}, "", "", err
|
||||
return nil, "", "", "", err
|
||||
}
|
||||
|
||||
return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (DeletedContactCards, Error) {
|
||||
return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (map[string]SetError, State, Error) {
|
||||
var setResponse ContactCardSetResponse
|
||||
err = retrieveResponseMatchParameters(logger, body, CommandContactCardSet, "0", &setResponse)
|
||||
if err != nil {
|
||||
return DeletedContactCards{}, err
|
||||
return nil, "", err
|
||||
}
|
||||
return DeletedContactCards{
|
||||
State: setResponse.NewState,
|
||||
NotDestroyed: setResponse.NotDestroyed,
|
||||
}, nil
|
||||
return setResponse.NotDestroyed, setResponse.NewState, nil
|
||||
})
|
||||
}
|
||||
|
||||
@@ -16,11 +16,10 @@ type Emails struct {
|
||||
Total uint `json:"total,omitzero"`
|
||||
Limit uint `json:"limit,omitzero"`
|
||||
Offset uint `json:"offset,omitzero"`
|
||||
State State `json:"state,omitempty"`
|
||||
}
|
||||
|
||||
// Retrieve specific Emails by their id.
|
||||
func (j *Client) GetEmails(accountId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, ids []string, fetchBodies bool, maxBodyValueBytes uint, markAsSeen bool, withThreads bool) (Emails, SessionState, Language, Error) {
|
||||
func (j *Client) GetEmails(accountId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, ids []string, fetchBodies bool, maxBodyValueBytes uint, markAsSeen bool, withThreads bool) ([]Email, SessionState, State, Language, Error) {
|
||||
logger = j.logger("GetEmails", session, logger)
|
||||
|
||||
get := EmailGetCommand{AccountId: accountId, Ids: ids, FetchAllBodyValues: fetchBodies}
|
||||
@@ -53,62 +52,62 @@ func (j *Client) GetEmails(accountId string, session *Session, ctx context.Conte
|
||||
cmd, err := j.request(session, logger, methodCalls...)
|
||||
if err != nil {
|
||||
logger.Error().Err(err).Send()
|
||||
return Emails{}, "", "", simpleError(err, JmapErrorInvalidJmapRequestPayload)
|
||||
return nil, "", "", "", simpleError(err, JmapErrorInvalidJmapRequestPayload)
|
||||
}
|
||||
return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (Emails, Error) {
|
||||
return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) ([]Email, State, Error) {
|
||||
if markAsSeen {
|
||||
var markResponse EmailSetResponse
|
||||
err = retrieveResponseMatchParameters(logger, body, CommandEmailSet, "0", &markResponse)
|
||||
if err != nil {
|
||||
return Emails{}, err
|
||||
return nil, "", err
|
||||
}
|
||||
for _, seterr := range markResponse.NotUpdated {
|
||||
// TODO we don't have a way to compose multiple set errors yet
|
||||
return Emails{}, setErrorError(seterr, EmailType)
|
||||
return nil, "", setErrorError(seterr, EmailType)
|
||||
}
|
||||
}
|
||||
var response EmailGetResponse
|
||||
err = retrieveResponseMatchParameters(logger, body, CommandEmailGet, "1", &response)
|
||||
if err != nil {
|
||||
return Emails{}, err
|
||||
return nil, "", err
|
||||
}
|
||||
if withThreads {
|
||||
var threads ThreadGetResponse
|
||||
err = retrieveResponseMatchParameters(logger, body, CommandThreadGet, "2", &threads)
|
||||
if err != nil {
|
||||
return Emails{}, err
|
||||
return nil, "", err
|
||||
}
|
||||
setThreadSize(&threads, response.List)
|
||||
}
|
||||
return Emails{Emails: response.List, State: response.State}, nil
|
||||
return response.List, response.State, nil
|
||||
})
|
||||
}
|
||||
|
||||
func (j *Client) GetEmailBlobId(accountId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, id string) (string, SessionState, Language, Error) {
|
||||
func (j *Client) GetEmailBlobId(accountId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, id string) (string, SessionState, State, Language, Error) {
|
||||
logger = j.logger("GetEmailBlobId", session, logger)
|
||||
|
||||
get := EmailGetCommand{AccountId: accountId, Ids: []string{id}, FetchAllBodyValues: false, Properties: []string{"blobId"}}
|
||||
cmd, err := j.request(session, logger, invocation(CommandEmailGet, get, "0"))
|
||||
if err != nil {
|
||||
logger.Error().Err(err).Send()
|
||||
return "", "", "", simpleError(err, JmapErrorInvalidJmapRequestPayload)
|
||||
return "", "", "", "", simpleError(err, JmapErrorInvalidJmapRequestPayload)
|
||||
}
|
||||
return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (string, Error) {
|
||||
return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (string, State, Error) {
|
||||
var response EmailGetResponse
|
||||
err = retrieveResponseMatchParameters(logger, body, CommandEmailGet, "0", &response)
|
||||
if err != nil {
|
||||
return "", err
|
||||
return "", "", err
|
||||
}
|
||||
if len(response.List) != 1 {
|
||||
return "", nil
|
||||
return "", "", nil
|
||||
}
|
||||
email := response.List[0]
|
||||
return email.BlobId, nil
|
||||
return email.BlobId, response.State, nil
|
||||
})
|
||||
}
|
||||
|
||||
// Retrieve all the Emails in a given Mailbox by its id.
|
||||
func (j *Client) GetAllEmailsInMailbox(accountId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, mailboxId string, offset uint, limit uint, collapseThreads bool, fetchBodies bool, maxBodyValueBytes uint, withThreads bool) (Emails, SessionState, Language, Error) {
|
||||
func (j *Client) GetAllEmailsInMailbox(accountId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, mailboxId string, offset uint, limit uint, collapseThreads bool, fetchBodies bool, maxBodyValueBytes uint, withThreads bool) (Emails, SessionState, State, Language, Error) {
|
||||
logger = j.loggerParams("GetAllEmailsInMailbox", session, logger, func(z zerolog.Context) zerolog.Context {
|
||||
return z.Bool(logFetchBodies, fetchBodies).Uint(logOffset, offset).Uint(logLimit, limit)
|
||||
})
|
||||
@@ -155,27 +154,27 @@ func (j *Client) GetAllEmailsInMailbox(accountId string, session *Session, ctx c
|
||||
|
||||
cmd, err := j.request(session, logger, invocations...)
|
||||
if err != nil {
|
||||
return Emails{}, "", "", err
|
||||
return Emails{}, "", "", "", err
|
||||
}
|
||||
|
||||
return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (Emails, Error) {
|
||||
return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (Emails, State, Error) {
|
||||
var queryResponse EmailQueryResponse
|
||||
err = retrieveResponseMatchParameters(logger, body, CommandEmailQuery, "0", &queryResponse)
|
||||
if err != nil {
|
||||
return Emails{}, err
|
||||
return Emails{}, "", err
|
||||
}
|
||||
var getResponse EmailGetResponse
|
||||
err = retrieveResponseMatchParameters(logger, body, CommandEmailGet, "1", &getResponse)
|
||||
if err != nil {
|
||||
logger.Error().Err(err).Send()
|
||||
return Emails{}, err
|
||||
return Emails{}, "", err
|
||||
}
|
||||
|
||||
if withThreads {
|
||||
var thread ThreadGetResponse
|
||||
err = retrieveResponseMatchParameters(logger, body, CommandThreadGet, "2", &thread)
|
||||
if err != nil {
|
||||
return Emails{}, err
|
||||
return Emails{}, "", err
|
||||
}
|
||||
setThreadSize(&thread, getResponse.List)
|
||||
}
|
||||
@@ -185,12 +184,12 @@ func (j *Client) GetAllEmailsInMailbox(accountId string, session *Session, ctx c
|
||||
Total: queryResponse.Total,
|
||||
Limit: queryResponse.Limit,
|
||||
Offset: queryResponse.Position,
|
||||
}, nil
|
||||
}, queryResponse.QueryState, nil
|
||||
})
|
||||
}
|
||||
|
||||
// Get all the Emails that have been created, updated or deleted since a given state.
|
||||
func (j *Client) GetEmailsSince(accountId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, sinceState string, fetchBodies bool, maxBodyValueBytes uint, maxChanges uint) (MailboxChanges, SessionState, Language, Error) {
|
||||
func (j *Client) GetEmailsSince(accountId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, sinceState string, fetchBodies bool, maxBodyValueBytes uint, maxChanges uint) (MailboxChanges, SessionState, State, Language, Error) {
|
||||
logger = j.loggerParams("GetEmailsSince", session, logger, func(z zerolog.Context) zerolog.Context {
|
||||
return z.Bool(logFetchBodies, fetchBodies).Str(logSinceState, sinceState)
|
||||
})
|
||||
@@ -226,28 +225,28 @@ func (j *Client) GetEmailsSince(accountId string, session *Session, ctx context.
|
||||
invocation(CommandEmailGet, getUpdated, "2"),
|
||||
)
|
||||
if err != nil {
|
||||
return MailboxChanges{}, "", "", simpleError(err, JmapErrorInvalidJmapRequestPayload)
|
||||
return MailboxChanges{}, "", "", "", simpleError(err, JmapErrorInvalidJmapRequestPayload)
|
||||
}
|
||||
|
||||
return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (MailboxChanges, Error) {
|
||||
return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (MailboxChanges, State, Error) {
|
||||
var changesResponse EmailChangesResponse
|
||||
err = retrieveResponseMatchParameters(logger, body, CommandEmailChanges, "0", &changesResponse)
|
||||
if err != nil {
|
||||
return MailboxChanges{}, err
|
||||
return MailboxChanges{}, "", err
|
||||
}
|
||||
|
||||
var createdResponse EmailGetResponse
|
||||
err = retrieveResponseMatchParameters(logger, body, CommandEmailGet, "1", &createdResponse)
|
||||
if err != nil {
|
||||
logger.Error().Err(err).Send()
|
||||
return MailboxChanges{}, err
|
||||
return MailboxChanges{}, "", err
|
||||
}
|
||||
|
||||
var updatedResponse EmailGetResponse
|
||||
err = retrieveResponseMatchParameters(logger, body, CommandEmailGet, "2", &updatedResponse)
|
||||
if err != nil {
|
||||
logger.Error().Err(err).Send()
|
||||
return MailboxChanges{}, err
|
||||
return MailboxChanges{}, "", err
|
||||
}
|
||||
|
||||
return MailboxChanges{
|
||||
@@ -256,8 +255,7 @@ func (j *Client) GetEmailsSince(accountId string, session *Session, ctx context.
|
||||
NewState: changesResponse.NewState,
|
||||
Created: createdResponse.List,
|
||||
Updated: createdResponse.List,
|
||||
State: updatedResponse.State,
|
||||
}, nil
|
||||
}, updatedResponse.State, nil
|
||||
})
|
||||
}
|
||||
|
||||
@@ -275,7 +273,7 @@ type EmailSnippetQueryResult struct {
|
||||
QueryState State `json:"queryState"`
|
||||
}
|
||||
|
||||
func (j *Client) QueryEmailSnippets(accountIds []string, filter EmailFilterElement, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, offset uint, limit uint) (map[string]EmailSnippetQueryResult, SessionState, Language, Error) {
|
||||
func (j *Client) QueryEmailSnippets(accountIds []string, filter EmailFilterElement, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, offset uint, limit uint) (map[string]EmailSnippetQueryResult, SessionState, State, Language, Error) {
|
||||
logger = j.loggerParams("QueryEmailSnippets", session, logger, func(z zerolog.Context) zerolog.Context {
|
||||
return z.Uint(logLimit, limit).Uint(logOffset, offset)
|
||||
})
|
||||
@@ -326,28 +324,28 @@ func (j *Client) QueryEmailSnippets(accountIds []string, filter EmailFilterEleme
|
||||
|
||||
cmd, err := j.request(session, logger, invocations...)
|
||||
if err != nil {
|
||||
return nil, "", "", err
|
||||
return nil, "", "", "", err
|
||||
}
|
||||
|
||||
return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (map[string]EmailSnippetQueryResult, Error) {
|
||||
return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (map[string]EmailSnippetQueryResult, State, Error) {
|
||||
results := make(map[string]EmailSnippetQueryResult, len(uniqueAccountIds))
|
||||
for _, accountId := range uniqueAccountIds {
|
||||
var queryResponse EmailQueryResponse
|
||||
err = retrieveResponseMatchParameters(logger, body, CommandEmailQuery, mcid(accountId, "0"), &queryResponse)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
var mailResponse EmailGetResponse
|
||||
err = retrieveResponseMatchParameters(logger, body, CommandEmailGet, mcid(accountId, "1"), &mailResponse)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
var snippetResponse SearchSnippetGetResponse
|
||||
err = retrieveResponseMatchParameters(logger, body, CommandSearchSnippetGet, mcid(accountId, "2"), &snippetResponse)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
mailResponseById := structs.Index(mailResponse.List, func(e Email) string { return e.Id })
|
||||
@@ -379,7 +377,7 @@ func (j *Client) QueryEmailSnippets(accountIds []string, filter EmailFilterEleme
|
||||
QueryState: queryResponse.QueryState,
|
||||
}
|
||||
}
|
||||
return results, nil
|
||||
return results, squashStateFunc(results, func(r EmailSnippetQueryResult) State { return r.QueryState }), nil
|
||||
})
|
||||
}
|
||||
|
||||
@@ -391,7 +389,7 @@ type EmailQueryResult struct {
|
||||
QueryState State `json:"queryState"`
|
||||
}
|
||||
|
||||
func (j *Client) QueryEmails(accountIds []string, filter EmailFilterElement, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, offset uint, limit uint, fetchBodies bool, maxBodyValueBytes uint) (map[string]EmailQueryResult, SessionState, Language, Error) {
|
||||
func (j *Client) QueryEmails(accountIds []string, filter EmailFilterElement, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, offset uint, limit uint, fetchBodies bool, maxBodyValueBytes uint) (map[string]EmailQueryResult, SessionState, State, Language, Error) {
|
||||
logger = j.loggerParams("QueryEmails", session, logger, func(z zerolog.Context) zerolog.Context {
|
||||
return z.Bool(logFetchBodies, fetchBodies)
|
||||
})
|
||||
@@ -430,22 +428,22 @@ func (j *Client) QueryEmails(accountIds []string, filter EmailFilterElement, ses
|
||||
|
||||
cmd, err := j.request(session, logger, invocations...)
|
||||
if err != nil {
|
||||
return nil, "", "", err
|
||||
return nil, "", "", "", err
|
||||
}
|
||||
|
||||
return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (map[string]EmailQueryResult, Error) {
|
||||
return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (map[string]EmailQueryResult, State, Error) {
|
||||
results := make(map[string]EmailQueryResult, len(uniqueAccountIds))
|
||||
for _, accountId := range uniqueAccountIds {
|
||||
var queryResponse EmailQueryResponse
|
||||
err = retrieveResponseMatchParameters(logger, body, CommandEmailQuery, mcid(accountId, "0"), &queryResponse)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
var emailsResponse EmailGetResponse
|
||||
err = retrieveResponseMatchParameters(logger, body, CommandEmailGet, mcid(accountId, "1"), &emailsResponse)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
results[accountId] = EmailQueryResult{
|
||||
@@ -456,7 +454,7 @@ func (j *Client) QueryEmails(accountIds []string, filter EmailFilterElement, ses
|
||||
QueryState: queryResponse.QueryState,
|
||||
}
|
||||
}
|
||||
return results, nil
|
||||
return results, squashStateFunc(results, func(r EmailQueryResult) State { return r.QueryState }), nil
|
||||
})
|
||||
}
|
||||
|
||||
@@ -473,7 +471,7 @@ type EmailQueryWithSnippetsResult struct {
|
||||
QueryState State `json:"queryState"`
|
||||
}
|
||||
|
||||
func (j *Client) QueryEmailsWithSnippets(accountIds []string, filter EmailFilterElement, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, offset uint, limit uint, fetchBodies bool, maxBodyValueBytes uint) (map[string]EmailQueryWithSnippetsResult, SessionState, Language, Error) {
|
||||
func (j *Client) QueryEmailsWithSnippets(accountIds []string, filter EmailFilterElement, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, offset uint, limit uint, fetchBodies bool, maxBodyValueBytes uint) (map[string]EmailQueryWithSnippetsResult, SessionState, State, Language, Error) {
|
||||
logger = j.loggerParams("QueryEmailsWithSnippets", session, logger, func(z zerolog.Context) zerolog.Context {
|
||||
return z.Bool(logFetchBodies, fetchBodies)
|
||||
})
|
||||
@@ -523,28 +521,28 @@ func (j *Client) QueryEmailsWithSnippets(accountIds []string, filter EmailFilter
|
||||
cmd, err := j.request(session, logger, invocations...)
|
||||
if err != nil {
|
||||
logger.Error().Err(err).Send()
|
||||
return nil, "", "", simpleError(err, JmapErrorInvalidJmapRequestPayload)
|
||||
return nil, "", "", "", simpleError(err, JmapErrorInvalidJmapRequestPayload)
|
||||
}
|
||||
|
||||
return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (map[string]EmailQueryWithSnippetsResult, Error) {
|
||||
return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (map[string]EmailQueryWithSnippetsResult, State, Error) {
|
||||
result := make(map[string]EmailQueryWithSnippetsResult, len(uniqueAccountIds))
|
||||
for _, accountId := range uniqueAccountIds {
|
||||
var queryResponse EmailQueryResponse
|
||||
err = retrieveResponseMatchParameters(logger, body, CommandEmailQuery, mcid(accountId, "0"), &queryResponse)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
var snippetResponse SearchSnippetGetResponse
|
||||
err = retrieveResponseMatchParameters(logger, body, CommandSearchSnippetGet, mcid(accountId, "1"), &snippetResponse)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
var emailsResponse EmailGetResponse
|
||||
err = retrieveResponseMatchParameters(logger, body, CommandEmailGet, mcid(accountId, "2"), &emailsResponse)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
snippetsById := map[string][]SearchSnippet{}
|
||||
@@ -576,7 +574,7 @@ func (j *Client) QueryEmailsWithSnippets(accountIds []string, filter EmailFilter
|
||||
QueryState: queryResponse.QueryState,
|
||||
}
|
||||
}
|
||||
return result, nil
|
||||
return result, squashStateFunc(result, func(r EmailQueryWithSnippetsResult) State { return r.QueryState }), nil
|
||||
})
|
||||
}
|
||||
|
||||
@@ -587,7 +585,7 @@ type UploadedEmail struct {
|
||||
Sha512 string `json:"sha:512"`
|
||||
}
|
||||
|
||||
func (j *Client) ImportEmail(accountId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, data []byte) (UploadedEmail, SessionState, Language, Error) {
|
||||
func (j *Client) ImportEmail(accountId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, data []byte) (UploadedEmail, SessionState, State, Language, Error) {
|
||||
encoded := base64.StdEncoding.EncodeToString(data)
|
||||
|
||||
upload := BlobUploadCommand{
|
||||
@@ -617,36 +615,36 @@ func (j *Client) ImportEmail(accountId string, session *Session, ctx context.Con
|
||||
invocation(CommandBlobGet, getHash, "1"),
|
||||
)
|
||||
if err != nil {
|
||||
return UploadedEmail{}, "", "", simpleError(err, JmapErrorInvalidJmapRequestPayload)
|
||||
return UploadedEmail{}, "", "", "", simpleError(err, JmapErrorInvalidJmapRequestPayload)
|
||||
}
|
||||
|
||||
return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (UploadedEmail, Error) {
|
||||
return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (UploadedEmail, State, Error) {
|
||||
var uploadResponse BlobUploadResponse
|
||||
err = retrieveResponseMatchParameters(logger, body, CommandBlobUpload, "0", &uploadResponse)
|
||||
if err != nil {
|
||||
return UploadedEmail{}, err
|
||||
return UploadedEmail{}, "", err
|
||||
}
|
||||
|
||||
var getResponse BlobGetResponse
|
||||
err = retrieveResponseMatchParameters(logger, body, CommandBlobGet, "1", &getResponse)
|
||||
if err != nil {
|
||||
logger.Error().Err(err).Send()
|
||||
return UploadedEmail{}, err
|
||||
return UploadedEmail{}, "", err
|
||||
}
|
||||
|
||||
if len(uploadResponse.Created) != 1 {
|
||||
logger.Error().Msgf("%T.Created has %v elements instead of 1", uploadResponse, len(uploadResponse.Created))
|
||||
return UploadedEmail{}, simpleError(err, JmapErrorInvalidJmapResponsePayload)
|
||||
return UploadedEmail{}, "", simpleError(err, JmapErrorInvalidJmapResponsePayload)
|
||||
}
|
||||
upload, ok := uploadResponse.Created["0"]
|
||||
if !ok {
|
||||
logger.Error().Msgf("%T.Created has no element '0'", uploadResponse)
|
||||
return UploadedEmail{}, simpleError(err, JmapErrorInvalidJmapResponsePayload)
|
||||
return UploadedEmail{}, "", simpleError(err, JmapErrorInvalidJmapResponsePayload)
|
||||
}
|
||||
|
||||
if len(getResponse.List) != 1 {
|
||||
logger.Error().Msgf("%T.List has %v elements instead of 1", getResponse, len(getResponse.List))
|
||||
return UploadedEmail{}, simpleError(err, JmapErrorInvalidJmapResponsePayload)
|
||||
return UploadedEmail{}, "", simpleError(err, JmapErrorInvalidJmapResponsePayload)
|
||||
}
|
||||
get := getResponse.List[0]
|
||||
|
||||
@@ -655,17 +653,12 @@ func (j *Client) ImportEmail(accountId string, session *Session, ctx context.Con
|
||||
Size: upload.Size,
|
||||
Type: upload.Type,
|
||||
Sha512: get.DigestSha512,
|
||||
}, nil
|
||||
}, State(get.DigestSha256), nil
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
type CreatedEmail struct {
|
||||
Email *Email `json:"email"`
|
||||
State State `json:"state"`
|
||||
}
|
||||
|
||||
func (j *Client) CreateEmail(accountId string, email EmailCreate, replaceId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string) (CreatedEmail, SessionState, Language, Error) {
|
||||
func (j *Client) CreateEmail(accountId string, email EmailCreate, replaceId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string) (*Email, SessionState, State, Language, Error) {
|
||||
set := EmailSetCommand{
|
||||
AccountId: accountId,
|
||||
Create: map[string]EmailCreate{
|
||||
@@ -680,14 +673,14 @@ func (j *Client) CreateEmail(accountId string, email EmailCreate, replaceId stri
|
||||
invocation(CommandEmailSet, set, "0"),
|
||||
)
|
||||
if err != nil {
|
||||
return CreatedEmail{}, "", "", err
|
||||
return nil, "", "", "", err
|
||||
}
|
||||
|
||||
return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (CreatedEmail, Error) {
|
||||
return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (*Email, State, Error) {
|
||||
var setResponse EmailSetResponse
|
||||
err = retrieveResponseMatchParameters(logger, body, CommandEmailSet, "0", &setResponse)
|
||||
if err != nil {
|
||||
return CreatedEmail{}, err
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
if len(setResponse.NotCreated) > 0 {
|
||||
@@ -698,28 +691,20 @@ func (j *Client) CreateEmail(accountId string, email EmailCreate, replaceId stri
|
||||
setErr, notok := setResponse.NotCreated["c"]
|
||||
if notok {
|
||||
logger.Error().Msgf("%T.NotCreated returned an error %v", setResponse, setErr)
|
||||
return CreatedEmail{}, setErrorError(setErr, EmailType)
|
||||
return nil, "", setErrorError(setErr, EmailType)
|
||||
}
|
||||
|
||||
created, ok := setResponse.Created["c"]
|
||||
if !ok {
|
||||
berr := fmt.Errorf("failed to find %s in %s response", string(EmailType), string(CommandEmailSet))
|
||||
logger.Error().Err(berr)
|
||||
return CreatedEmail{}, simpleError(berr, JmapErrorInvalidJmapResponsePayload)
|
||||
return nil, "", simpleError(berr, JmapErrorInvalidJmapResponsePayload)
|
||||
}
|
||||
|
||||
return CreatedEmail{
|
||||
Email: created,
|
||||
State: setResponse.NewState,
|
||||
}, nil
|
||||
return created, setResponse.NewState, nil
|
||||
})
|
||||
}
|
||||
|
||||
type UpdatedEmails struct {
|
||||
Updated map[string]*Email `json:"email"`
|
||||
State State `json:"state"`
|
||||
}
|
||||
|
||||
// The Email/set method encompasses:
|
||||
// - Changing the keywords of an Email (e.g., unread/flagged status)
|
||||
// - Adding/removing an Email to/from Mailboxes (moving a message)
|
||||
@@ -728,7 +713,7 @@ type UpdatedEmails struct {
|
||||
// To create drafts, use the CreateEmail function instead.
|
||||
//
|
||||
// To delete mails, use the DeleteEmails function instead.
|
||||
func (j *Client) UpdateEmails(accountId string, updates map[string]EmailUpdate, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string) (UpdatedEmails, SessionState, Language, Error) {
|
||||
func (j *Client) UpdateEmails(accountId string, updates map[string]EmailUpdate, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string) (map[string]*Email, SessionState, State, Language, Error) {
|
||||
cmd, err := j.request(session, logger,
|
||||
invocation(CommandEmailSet, EmailSetCommand{
|
||||
AccountId: accountId,
|
||||
@@ -736,32 +721,24 @@ func (j *Client) UpdateEmails(accountId string, updates map[string]EmailUpdate,
|
||||
}, "0"),
|
||||
)
|
||||
if err != nil {
|
||||
return UpdatedEmails{}, "", "", err
|
||||
return nil, "", "", "", err
|
||||
}
|
||||
|
||||
return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (UpdatedEmails, Error) {
|
||||
return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (map[string]*Email, State, Error) {
|
||||
var setResponse EmailSetResponse
|
||||
err = retrieveResponseMatchParameters(logger, body, CommandEmailSet, "0", &setResponse)
|
||||
if err != nil {
|
||||
return UpdatedEmails{}, err
|
||||
return nil, "", err
|
||||
}
|
||||
if len(setResponse.NotUpdated) != len(updates) {
|
||||
// error occured
|
||||
// TODO(pbleser-oc) handle submission errors
|
||||
}
|
||||
return UpdatedEmails{
|
||||
Updated: setResponse.Updated,
|
||||
State: setResponse.NewState,
|
||||
}, nil
|
||||
return setResponse.Updated, setResponse.NewState, nil
|
||||
})
|
||||
}
|
||||
|
||||
type DeletedEmails struct {
|
||||
State State `json:"state"`
|
||||
NotDestroyed map[string]SetError `json:"notDestroyed"`
|
||||
}
|
||||
|
||||
func (j *Client) DeleteEmails(accountId string, destroy []string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string) (DeletedEmails, SessionState, Language, Error) {
|
||||
func (j *Client) DeleteEmails(accountId string, destroy []string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string) (map[string]SetError, SessionState, State, Language, Error) {
|
||||
cmd, err := j.request(session, logger,
|
||||
invocation(CommandEmailSet, EmailSetCommand{
|
||||
AccountId: accountId,
|
||||
@@ -769,25 +746,21 @@ func (j *Client) DeleteEmails(accountId string, destroy []string, session *Sessi
|
||||
}, "0"),
|
||||
)
|
||||
if err != nil {
|
||||
return DeletedEmails{}, "", "", err
|
||||
return nil, "", "", "", err
|
||||
}
|
||||
|
||||
return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (DeletedEmails, Error) {
|
||||
return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (map[string]SetError, State, Error) {
|
||||
var setResponse EmailSetResponse
|
||||
err = retrieveResponseMatchParameters(logger, body, CommandEmailSet, "0", &setResponse)
|
||||
if err != nil {
|
||||
return DeletedEmails{}, err
|
||||
return nil, "", err
|
||||
}
|
||||
return DeletedEmails{
|
||||
State: setResponse.NewState,
|
||||
NotDestroyed: setResponse.NotDestroyed,
|
||||
}, nil
|
||||
return setResponse.NotDestroyed, setResponse.NewState, nil
|
||||
})
|
||||
}
|
||||
|
||||
type SubmittedEmail struct {
|
||||
Id string `json:"id"`
|
||||
State State `json:"state"`
|
||||
SendAt time.Time `json:"sendAt,omitzero"`
|
||||
ThreadId string `json:"threadId,omitempty"`
|
||||
UndoStatus EmailSubmissionUndoStatus `json:"undoStatus,omitempty"`
|
||||
@@ -810,7 +783,7 @@ type SubmittedEmail struct {
|
||||
MdnBlobIds []string `json:"mdnBlobIds,omitempty"`
|
||||
}
|
||||
|
||||
func (j *Client) SubmitEmail(accountId string, identityId string, emailId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, data []byte) (SubmittedEmail, SessionState, Language, Error) {
|
||||
func (j *Client) SubmitEmail(accountId string, identityId string, emailId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, data []byte) (SubmittedEmail, SessionState, State, Language, Error) {
|
||||
set := EmailSubmissionSetCommand{
|
||||
AccountId: accountId,
|
||||
Create: map[string]EmailSubmissionCreate{
|
||||
@@ -840,14 +813,14 @@ func (j *Client) SubmitEmail(accountId string, identityId string, emailId string
|
||||
invocation(CommandEmailSubmissionGet, get, "1"),
|
||||
)
|
||||
if err != nil {
|
||||
return SubmittedEmail{}, "", "", err
|
||||
return SubmittedEmail{}, "", "", "", err
|
||||
}
|
||||
|
||||
return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (SubmittedEmail, Error) {
|
||||
return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (SubmittedEmail, State, Error) {
|
||||
var submissionResponse EmailSubmissionSetResponse
|
||||
err = retrieveResponseMatchParameters(logger, body, CommandEmailSubmissionSet, "0", &submissionResponse)
|
||||
if err != nil {
|
||||
return SubmittedEmail{}, err
|
||||
return SubmittedEmail{}, "", err
|
||||
}
|
||||
|
||||
if len(submissionResponse.NotCreated) > 0 {
|
||||
@@ -863,13 +836,13 @@ func (j *Client) SubmitEmail(accountId string, identityId string, emailId string
|
||||
var setResponse EmailSetResponse
|
||||
err = retrieveResponseMatchParameters(logger, body, CommandEmailSet, "0", &setResponse)
|
||||
if err != nil {
|
||||
return SubmittedEmail{}, err
|
||||
return SubmittedEmail{}, "", err
|
||||
}
|
||||
|
||||
var getResponse EmailSubmissionGetResponse
|
||||
err = retrieveResponseMatchParameters(logger, body, CommandEmailSubmissionGet, "1", &getResponse)
|
||||
if err != nil {
|
||||
return SubmittedEmail{}, err
|
||||
return SubmittedEmail{}, "", err
|
||||
}
|
||||
|
||||
if len(getResponse.List) != 1 {
|
||||
@@ -881,18 +854,17 @@ func (j *Client) SubmitEmail(accountId string, identityId string, emailId string
|
||||
|
||||
return SubmittedEmail{
|
||||
Id: submission.Id,
|
||||
State: setResponse.NewState,
|
||||
SendAt: submission.SendAt,
|
||||
ThreadId: submission.ThreadId,
|
||||
UndoStatus: submission.UndoStatus,
|
||||
Envelope: submission.Envelope,
|
||||
DsnBlobIds: submission.DsnBlobIds,
|
||||
MdnBlobIds: submission.MdnBlobIds,
|
||||
}, nil
|
||||
}, setResponse.NewState, nil
|
||||
})
|
||||
}
|
||||
|
||||
func (j *Client) EmailsInThread(accountId string, threadId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, fetchBodies bool, maxBodyValueBytes uint) ([]Email, SessionState, Language, Error) {
|
||||
func (j *Client) EmailsInThread(accountId string, threadId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, fetchBodies bool, maxBodyValueBytes uint) ([]Email, SessionState, State, Language, Error) {
|
||||
logger = j.loggerParams("EmailsInThread", session, logger, func(z zerolog.Context) zerolog.Context {
|
||||
return z.Bool(logFetchBodies, fetchBodies).Str("threadId", log.SafeString(threadId))
|
||||
})
|
||||
@@ -914,16 +886,16 @@ func (j *Client) EmailsInThread(accountId string, threadId string, session *Sess
|
||||
}, "1"),
|
||||
)
|
||||
if err != nil {
|
||||
return []Email{}, "", "", err
|
||||
return nil, "", "", "", err
|
||||
}
|
||||
|
||||
return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) ([]Email, Error) {
|
||||
return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) ([]Email, State, Error) {
|
||||
var emailsResponse EmailGetResponse
|
||||
err = retrieveResponseMatchParameters(logger, body, CommandEmailGet, "1", &emailsResponse)
|
||||
if err != nil {
|
||||
return []Email{}, err
|
||||
return nil, "", err
|
||||
}
|
||||
return emailsResponse.List, nil
|
||||
return emailsResponse.List, emailsResponse.State, nil
|
||||
})
|
||||
}
|
||||
|
||||
@@ -954,7 +926,7 @@ var EmailSummaryProperties = []string{
|
||||
EmailPropertyPreview,
|
||||
}
|
||||
|
||||
func (j *Client) QueryEmailSummaries(accountIds []string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, filter EmailFilterElement, limit uint, withThreads bool) (map[string]EmailsSummary, SessionState, Language, Error) {
|
||||
func (j *Client) QueryEmailSummaries(accountIds []string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, filter EmailFilterElement, limit uint, withThreads bool) (map[string]EmailsSummary, SessionState, State, Language, Error) {
|
||||
logger = j.logger("QueryEmailSummaries", session, logger)
|
||||
|
||||
uniqueAccountIds := structs.Uniq(accountIds)
|
||||
@@ -995,22 +967,22 @@ func (j *Client) QueryEmailSummaries(accountIds []string, session *Session, ctx
|
||||
}
|
||||
cmd, err := j.request(session, logger, invocations...)
|
||||
if err != nil {
|
||||
return nil, "", "", err
|
||||
return nil, "", "", "", err
|
||||
}
|
||||
|
||||
return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (map[string]EmailsSummary, Error) {
|
||||
return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (map[string]EmailsSummary, State, Error) {
|
||||
resp := map[string]EmailsSummary{}
|
||||
for _, accountId := range uniqueAccountIds {
|
||||
var queryResponse EmailQueryResponse
|
||||
err = retrieveResponseMatchParameters(logger, body, CommandEmailQuery, mcid(accountId, "0"), &queryResponse)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
var response EmailGetResponse
|
||||
err = retrieveResponseMatchParameters(logger, body, CommandEmailGet, mcid(accountId, "1"), &response)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, "", err
|
||||
}
|
||||
if len(response.NotFound) > 0 {
|
||||
// TODO what to do when there are not-found emails here? potentially nothing, they could have been deleted between query and get?
|
||||
@@ -1019,7 +991,7 @@ func (j *Client) QueryEmailSummaries(accountIds []string, session *Session, ctx
|
||||
var thread ThreadGetResponse
|
||||
err = retrieveResponseMatchParameters(logger, body, CommandThreadGet, mcid(accountId, "2"), &thread)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, "", err
|
||||
}
|
||||
setThreadSize(&thread, response.List)
|
||||
}
|
||||
@@ -1032,7 +1004,7 @@ func (j *Client) QueryEmailSummaries(accountIds []string, session *Session, ctx
|
||||
State: response.State,
|
||||
}
|
||||
}
|
||||
return resp, nil
|
||||
return resp, squashStateFunc(resp, func(s EmailsSummary) State { return s.State }), nil
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -8,60 +8,46 @@ import (
|
||||
"github.com/opencloud-eu/opencloud/pkg/structs"
|
||||
)
|
||||
|
||||
type Identities struct {
|
||||
Identities []Identity `json:"identities"`
|
||||
State State `json:"state"`
|
||||
}
|
||||
|
||||
func (j *Client) GetAllIdentities(accountId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string) (Identities, SessionState, Language, Error) {
|
||||
func (j *Client) GetAllIdentities(accountId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string) ([]Identity, SessionState, State, Language, Error) {
|
||||
logger = j.logger("GetAllIdentities", session, logger)
|
||||
cmd, err := j.request(session, logger, invocation(CommandIdentityGet, IdentityGetCommand{AccountId: accountId}, "0"))
|
||||
if err != nil {
|
||||
return Identities{}, "", "", err
|
||||
return nil, "", "", "", err
|
||||
}
|
||||
return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (Identities, Error) {
|
||||
return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) ([]Identity, State, Error) {
|
||||
var response IdentityGetResponse
|
||||
err = retrieveResponseMatchParameters(logger, body, CommandIdentityGet, "0", &response)
|
||||
if err != nil {
|
||||
return Identities{}, err
|
||||
return nil, "", err
|
||||
}
|
||||
return Identities{
|
||||
Identities: response.List,
|
||||
State: response.State,
|
||||
}, nil
|
||||
return response.List, response.State, nil
|
||||
})
|
||||
}
|
||||
|
||||
func (j *Client) GetIdentities(accountId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, identityIds []string) (Identities, SessionState, Language, Error) {
|
||||
func (j *Client) GetIdentities(accountId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, identityIds []string) ([]Identity, SessionState, State, Language, Error) {
|
||||
logger = j.logger("GetIdentities", session, logger)
|
||||
cmd, err := j.request(session, logger, invocation(CommandIdentityGet, IdentityGetCommand{AccountId: accountId, Ids: identityIds}, "0"))
|
||||
if err != nil {
|
||||
return Identities{}, "", "", err
|
||||
return nil, "", "", "", err
|
||||
}
|
||||
return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (Identities, Error) {
|
||||
return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) ([]Identity, State, Error) {
|
||||
var response IdentityGetResponse
|
||||
err = retrieveResponseMatchParameters(logger, body, CommandIdentityGet, "0", &response)
|
||||
if err != nil {
|
||||
return Identities{}, err
|
||||
return nil, "", err
|
||||
}
|
||||
return Identities{
|
||||
Identities: response.List,
|
||||
State: response.State,
|
||||
}, nil
|
||||
return response.List, response.State, nil
|
||||
})
|
||||
}
|
||||
|
||||
type IdentitiesGetResponse struct {
|
||||
Identities map[string][]Identity `json:"identities,omitempty"`
|
||||
NotFound []string `json:"notFound,omitempty"`
|
||||
State State `json:"state"`
|
||||
}
|
||||
|
||||
func (j *Client) GetIdentitiesForAllAccounts(accountIds []string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string) (IdentitiesGetResponse, SessionState, Language, Error) {
|
||||
uniqueAccountIds := structs.Uniq(accountIds)
|
||||
|
||||
func (j *Client) GetIdentitiesForAllAccounts(accountIds []string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string) (IdentitiesGetResponse, SessionState, State, Language, Error) {
|
||||
logger = j.logger("GetIdentitiesForAllAccounts", session, logger)
|
||||
|
||||
uniqueAccountIds := structs.Uniq(accountIds)
|
||||
calls := make([]Invocation, len(uniqueAccountIds))
|
||||
for i, accountId := range uniqueAccountIds {
|
||||
calls[i] = invocation(CommandIdentityGet, IdentityGetCommand{AccountId: accountId}, strconv.Itoa(i))
|
||||
@@ -69,40 +55,38 @@ func (j *Client) GetIdentitiesForAllAccounts(accountIds []string, session *Sessi
|
||||
|
||||
cmd, err := j.request(session, logger, calls...)
|
||||
if err != nil {
|
||||
return IdentitiesGetResponse{}, "", "", err
|
||||
return IdentitiesGetResponse{}, "", "", "", err
|
||||
}
|
||||
return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (IdentitiesGetResponse, Error) {
|
||||
return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (IdentitiesGetResponse, State, Error) {
|
||||
identities := make(map[string][]Identity, len(uniqueAccountIds))
|
||||
var lastState State
|
||||
stateByAccountId := make(map[string]State, len(uniqueAccountIds))
|
||||
notFound := []string{}
|
||||
for i, accountId := range uniqueAccountIds {
|
||||
var response IdentityGetResponse
|
||||
err = retrieveResponseMatchParameters(logger, body, CommandIdentityGet, strconv.Itoa(i), &response)
|
||||
if err != nil {
|
||||
return IdentitiesGetResponse{}, err
|
||||
return IdentitiesGetResponse{}, "", err
|
||||
} else {
|
||||
identities[accountId] = response.List
|
||||
}
|
||||
lastState = response.State
|
||||
stateByAccountId[accountId] = response.State
|
||||
notFound = append(notFound, response.NotFound...)
|
||||
}
|
||||
|
||||
return IdentitiesGetResponse{
|
||||
Identities: identities,
|
||||
NotFound: structs.Uniq(notFound),
|
||||
State: lastState,
|
||||
}, nil
|
||||
}, squashState(stateByAccountId), nil
|
||||
})
|
||||
}
|
||||
|
||||
type IdentitiesAndMailboxesGetResponse struct {
|
||||
Identities map[string][]Identity `json:"identities,omitempty"`
|
||||
NotFound []string `json:"notFound,omitempty"`
|
||||
State State `json:"state"`
|
||||
Mailboxes []Mailbox `json:"mailboxes"`
|
||||
}
|
||||
|
||||
func (j *Client) GetIdentitiesAndMailboxes(mailboxAccountId string, accountIds []string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string) (IdentitiesAndMailboxesGetResponse, SessionState, Language, Error) {
|
||||
func (j *Client) GetIdentitiesAndMailboxes(mailboxAccountId string, accountIds []string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string) (IdentitiesAndMailboxesGetResponse, SessionState, State, Language, Error) {
|
||||
uniqueAccountIds := structs.Uniq(accountIds)
|
||||
|
||||
logger = j.logger("GetIdentitiesAndMailboxes", session, logger)
|
||||
@@ -115,40 +99,39 @@ func (j *Client) GetIdentitiesAndMailboxes(mailboxAccountId string, accountIds [
|
||||
|
||||
cmd, err := j.request(session, logger, calls...)
|
||||
if err != nil {
|
||||
return IdentitiesAndMailboxesGetResponse{}, "", "", err
|
||||
return IdentitiesAndMailboxesGetResponse{}, "", "", "", err
|
||||
}
|
||||
return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (IdentitiesAndMailboxesGetResponse, Error) {
|
||||
return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (IdentitiesAndMailboxesGetResponse, State, Error) {
|
||||
identities := make(map[string][]Identity, len(uniqueAccountIds))
|
||||
var lastState State
|
||||
stateByAccountId := make(map[string]State, len(uniqueAccountIds))
|
||||
notFound := []string{}
|
||||
for i, accountId := range uniqueAccountIds {
|
||||
var response IdentityGetResponse
|
||||
err = retrieveResponseMatchParameters(logger, body, CommandIdentityGet, strconv.Itoa(i+1), &response)
|
||||
if err != nil {
|
||||
return IdentitiesAndMailboxesGetResponse{}, err
|
||||
return IdentitiesAndMailboxesGetResponse{}, "", err
|
||||
} else {
|
||||
identities[accountId] = response.List
|
||||
}
|
||||
lastState = response.State
|
||||
stateByAccountId[accountId] = response.State
|
||||
notFound = append(notFound, response.NotFound...)
|
||||
}
|
||||
|
||||
var mailboxResponse MailboxGetResponse
|
||||
err = retrieveResponseMatchParameters(logger, body, CommandMailboxGet, "0", &mailboxResponse)
|
||||
if err != nil {
|
||||
return IdentitiesAndMailboxesGetResponse{}, err
|
||||
return IdentitiesAndMailboxesGetResponse{}, "", err
|
||||
}
|
||||
|
||||
return IdentitiesAndMailboxesGetResponse{
|
||||
Identities: identities,
|
||||
NotFound: structs.Uniq(notFound),
|
||||
State: lastState,
|
||||
Mailboxes: mailboxResponse.List,
|
||||
}, nil
|
||||
}, squashState(stateByAccountId), nil
|
||||
})
|
||||
}
|
||||
|
||||
func (j *Client) CreateIdentity(accountId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, identity Identity) (State, SessionState, Language, Error) {
|
||||
func (j *Client) CreateIdentity(accountId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, identity Identity) (Identity, SessionState, State, Language, Error) {
|
||||
logger = j.logger("CreateIdentity", session, logger)
|
||||
cmd, err := j.request(session, logger, invocation(CommandIdentitySet, IdentitySetCommand{
|
||||
AccountId: accountId,
|
||||
@@ -157,24 +140,24 @@ func (j *Client) CreateIdentity(accountId string, session *Session, ctx context.
|
||||
},
|
||||
}, "0"))
|
||||
if err != nil {
|
||||
return "", "", "", err
|
||||
return Identity{}, "", "", "", err
|
||||
}
|
||||
return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (State, Error) {
|
||||
return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (Identity, State, Error) {
|
||||
var response IdentitySetResponse
|
||||
err = retrieveResponseMatchParameters(logger, body, CommandIdentitySet, "0", &response)
|
||||
if err != nil {
|
||||
return response.NewState, err
|
||||
return Identity{}, response.NewState, err
|
||||
}
|
||||
setErr, notok := response.NotCreated["c"]
|
||||
if notok {
|
||||
logger.Error().Msgf("%T.NotCreated returned an error %v", response, setErr)
|
||||
return "", setErrorError(setErr, IdentityType)
|
||||
return Identity{}, "", setErrorError(setErr, IdentityType)
|
||||
}
|
||||
return response.NewState, nil
|
||||
return response.Created["c"], response.NewState, nil
|
||||
})
|
||||
}
|
||||
|
||||
func (j *Client) UpdateIdentity(accountId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, identity Identity) (State, SessionState, Language, Error) {
|
||||
func (j *Client) UpdateIdentity(accountId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, identity Identity) (Identity, SessionState, State, Language, Error) {
|
||||
logger = j.logger("UpdateIdentity", session, logger)
|
||||
cmd, err := j.request(session, logger, invocation(CommandIdentitySet, IdentitySetCommand{
|
||||
AccountId: accountId,
|
||||
@@ -183,48 +166,43 @@ func (j *Client) UpdateIdentity(accountId string, session *Session, ctx context.
|
||||
},
|
||||
}, "0"))
|
||||
if err != nil {
|
||||
return "", "", "", err
|
||||
return Identity{}, "", "", "", err
|
||||
}
|
||||
return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (State, Error) {
|
||||
return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (Identity, State, Error) {
|
||||
var response IdentitySetResponse
|
||||
err = retrieveResponseMatchParameters(logger, body, CommandIdentitySet, "0", &response)
|
||||
if err != nil {
|
||||
return response.NewState, err
|
||||
return Identity{}, response.NewState, err
|
||||
}
|
||||
setErr, notok := response.NotCreated["c"]
|
||||
if notok {
|
||||
logger.Error().Msgf("%T.NotCreated returned an error %v", response, setErr)
|
||||
return "", setErrorError(setErr, IdentityType)
|
||||
return Identity{}, "", setErrorError(setErr, IdentityType)
|
||||
}
|
||||
return response.NewState, nil
|
||||
return response.Created["c"], response.NewState, nil
|
||||
})
|
||||
}
|
||||
|
||||
type IdentityDeletion struct {
|
||||
Destroyed []string `json:"destroyed"`
|
||||
NewState State `json:"newState,omitempty"`
|
||||
}
|
||||
|
||||
func (j *Client) DeleteIdentity(accountId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, ids []string) (IdentityDeletion, SessionState, Language, Error) {
|
||||
func (j *Client) DeleteIdentity(accountId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, ids []string) ([]string, SessionState, State, Language, Error) {
|
||||
logger = j.logger("DeleteIdentity", session, logger)
|
||||
cmd, err := j.request(session, logger, invocation(CommandIdentitySet, IdentitySetCommand{
|
||||
AccountId: accountId,
|
||||
Destroy: ids,
|
||||
}, "0"))
|
||||
if err != nil {
|
||||
return IdentityDeletion{}, "", "", err
|
||||
return nil, "", "", "", err
|
||||
}
|
||||
return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (IdentityDeletion, Error) {
|
||||
return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) ([]string, State, Error) {
|
||||
var response IdentitySetResponse
|
||||
err = retrieveResponseMatchParameters(logger, body, CommandIdentitySet, "0", &response)
|
||||
if err != nil {
|
||||
return IdentityDeletion{}, err
|
||||
return nil, "", err
|
||||
}
|
||||
for _, setErr := range response.NotDestroyed {
|
||||
// TODO only returning the first error here, we should probably aggregate them instead
|
||||
logger.Error().Msgf("%T.NotCreated returned an error %v", response, setErr)
|
||||
return IdentityDeletion{}, setErrorError(setErr, IdentityType)
|
||||
return nil, "", setErrorError(setErr, IdentityType)
|
||||
}
|
||||
return IdentityDeletion{Destroyed: response.Destroyed, NewState: response.NewState}, nil
|
||||
return response.Destroyed, response.NewState, nil
|
||||
})
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package jmap
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"slices"
|
||||
|
||||
"github.com/opencloud-eu/opencloud/pkg/log"
|
||||
@@ -12,41 +13,39 @@ import (
|
||||
type MailboxesResponse struct {
|
||||
Mailboxes []Mailbox `json:"mailboxes"`
|
||||
NotFound []any `json:"notFound"`
|
||||
State State `json:"state"`
|
||||
}
|
||||
|
||||
// https://jmap.io/spec-mail.html#mailboxget
|
||||
func (j *Client) GetMailbox(accountId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, ids []string) (MailboxesResponse, SessionState, Language, Error) {
|
||||
func (j *Client) GetMailbox(accountId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, ids []string) (MailboxesResponse, SessionState, State, Language, Error) {
|
||||
logger = j.logger("GetMailbox", session, logger)
|
||||
|
||||
cmd, err := j.request(session, logger,
|
||||
invocation(CommandMailboxGet, MailboxGetCommand{AccountId: accountId, Ids: ids}, "0"),
|
||||
)
|
||||
if err != nil {
|
||||
return MailboxesResponse{}, "", "", err
|
||||
return MailboxesResponse{}, "", "", "", err
|
||||
}
|
||||
|
||||
return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (MailboxesResponse, Error) {
|
||||
return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (MailboxesResponse, State, Error) {
|
||||
var response MailboxGetResponse
|
||||
err = retrieveResponseMatchParameters(logger, body, CommandMailboxGet, "0", &response)
|
||||
if err != nil {
|
||||
return MailboxesResponse{}, err
|
||||
return MailboxesResponse{}, "", err
|
||||
}
|
||||
return MailboxesResponse{
|
||||
Mailboxes: response.List,
|
||||
NotFound: response.NotFound,
|
||||
State: response.State,
|
||||
}, nil
|
||||
}, response.State, nil
|
||||
})
|
||||
}
|
||||
|
||||
func (j *Client) GetAllMailboxes(accountIds []string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string) (map[string]Mailboxes, SessionState, Language, Error) {
|
||||
func (j *Client) GetAllMailboxes(accountIds []string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string) (map[string][]Mailbox, SessionState, State, Language, Error) {
|
||||
logger = j.logger("GetAllMailboxes", session, logger)
|
||||
|
||||
uniqueAccountIds := structs.Uniq(accountIds)
|
||||
n := len(uniqueAccountIds)
|
||||
if n < 1 {
|
||||
return nil, "", "", nil
|
||||
return nil, "", "", "", nil
|
||||
}
|
||||
|
||||
invocations := make([]Invocation, n)
|
||||
@@ -56,35 +55,27 @@ func (j *Client) GetAllMailboxes(accountIds []string, session *Session, ctx cont
|
||||
|
||||
cmd, err := j.request(session, logger, invocations...)
|
||||
if err != nil {
|
||||
return nil, "", "", err
|
||||
return nil, "", "", "", err
|
||||
}
|
||||
|
||||
return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (map[string]Mailboxes, Error) {
|
||||
resp := map[string]Mailboxes{}
|
||||
return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (map[string][]Mailbox, State, Error) {
|
||||
resp := map[string][]Mailbox{}
|
||||
stateByAccountid := map[string]State{}
|
||||
for _, accountId := range uniqueAccountIds {
|
||||
var response MailboxGetResponse
|
||||
err = retrieveResponseMatchParameters(logger, body, CommandMailboxGet, mcid(accountId, "0"), &response)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
resp[accountId] = Mailboxes{
|
||||
Mailboxes: response.List,
|
||||
State: response.State,
|
||||
}
|
||||
resp[accountId] = response.List
|
||||
stateByAccountid[accountId] = response.State
|
||||
}
|
||||
return resp, nil
|
||||
return resp, squashState(stateByAccountid), nil
|
||||
})
|
||||
}
|
||||
|
||||
type Mailboxes struct {
|
||||
// The list of mailboxes that were found using the specified search criteria.
|
||||
Mailboxes []Mailbox `json:"mailboxes,omitempty"`
|
||||
// The state of the search.
|
||||
State State `json:"state,omitempty"`
|
||||
}
|
||||
|
||||
func (j *Client) SearchMailboxes(accountIds []string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, filter MailboxFilterElement) (map[string]Mailboxes, SessionState, Language, Error) {
|
||||
func (j *Client) SearchMailboxes(accountIds []string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, filter MailboxFilterElement) (map[string][]Mailbox, SessionState, State, Language, Error) {
|
||||
logger = j.logger("SearchMailboxes", session, logger)
|
||||
|
||||
uniqueAccountIds := structs.Uniq(accountIds)
|
||||
@@ -103,21 +94,23 @@ func (j *Client) SearchMailboxes(accountIds []string, session *Session, ctx cont
|
||||
}
|
||||
cmd, err := j.request(session, logger, invocations...)
|
||||
if err != nil {
|
||||
return map[string]Mailboxes{}, "", "", err
|
||||
return nil, "", "", "", err
|
||||
}
|
||||
|
||||
return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (map[string]Mailboxes, Error) {
|
||||
resp := map[string]Mailboxes{}
|
||||
return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (map[string][]Mailbox, State, Error) {
|
||||
resp := map[string][]Mailbox{}
|
||||
stateByAccountid := map[string]State{}
|
||||
for _, accountId := range uniqueAccountIds {
|
||||
var response MailboxGetResponse
|
||||
err = retrieveResponseMatchParameters(logger, body, CommandMailboxGet, mcid(accountId, "1"), &response)
|
||||
if err != nil {
|
||||
return map[string]Mailboxes{}, err
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
resp[accountId] = Mailboxes{Mailboxes: response.List, State: response.State}
|
||||
resp[accountId] = response.List
|
||||
stateByAccountid[accountId] = response.State
|
||||
}
|
||||
return resp, nil
|
||||
return resp, squashState(stateByAccountid), nil
|
||||
})
|
||||
}
|
||||
|
||||
@@ -127,11 +120,10 @@ type MailboxChanges struct {
|
||||
NewState State `json:"newState"`
|
||||
Created []Email `json:"created,omitempty"`
|
||||
Updated []Email `json:"updated,omitempty"`
|
||||
State State `json:"state,omitempty"`
|
||||
}
|
||||
|
||||
// Retrieve Email changes in a given Mailbox since a given state.
|
||||
func (j *Client) GetMailboxChanges(accountId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, mailboxId string, sinceState string, fetchBodies bool, maxBodyValueBytes uint, maxChanges uint) (MailboxChanges, SessionState, Language, Error) {
|
||||
func (j *Client) GetMailboxChanges(accountId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, mailboxId string, sinceState string, fetchBodies bool, maxBodyValueBytes uint, maxChanges uint) (MailboxChanges, SessionState, State, Language, Error) {
|
||||
logger = j.loggerParams("GetMailboxChanges", session, logger, func(z zerolog.Context) zerolog.Context {
|
||||
return z.Bool(logFetchBodies, fetchBodies).Str(logSinceState, sinceState)
|
||||
})
|
||||
@@ -167,28 +159,28 @@ func (j *Client) GetMailboxChanges(accountId string, session *Session, ctx conte
|
||||
invocation(CommandEmailGet, getUpdated, "2"),
|
||||
)
|
||||
if err != nil {
|
||||
return MailboxChanges{}, "", "", err
|
||||
return MailboxChanges{}, "", "", "", err
|
||||
}
|
||||
|
||||
return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (MailboxChanges, Error) {
|
||||
return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (MailboxChanges, State, Error) {
|
||||
var mailboxResponse MailboxChangesResponse
|
||||
err = retrieveResponseMatchParameters(logger, body, CommandMailboxChanges, "0", &mailboxResponse)
|
||||
if err != nil {
|
||||
return MailboxChanges{}, err
|
||||
return MailboxChanges{}, "", err
|
||||
}
|
||||
|
||||
var createdResponse EmailGetResponse
|
||||
err = retrieveResponseMatchParameters(logger, body, CommandEmailGet, "1", &createdResponse)
|
||||
if err != nil {
|
||||
logger.Error().Err(err).Send()
|
||||
return MailboxChanges{}, err
|
||||
return MailboxChanges{}, "", err
|
||||
}
|
||||
|
||||
var updatedResponse EmailGetResponse
|
||||
err = retrieveResponseMatchParameters(logger, body, CommandEmailGet, "2", &updatedResponse)
|
||||
if err != nil {
|
||||
logger.Error().Err(err).Send()
|
||||
return MailboxChanges{}, err
|
||||
return MailboxChanges{}, "", err
|
||||
}
|
||||
|
||||
return MailboxChanges{
|
||||
@@ -197,13 +189,12 @@ func (j *Client) GetMailboxChanges(accountId string, session *Session, ctx conte
|
||||
NewState: mailboxResponse.NewState,
|
||||
Created: createdResponse.List,
|
||||
Updated: createdResponse.List,
|
||||
State: createdResponse.State,
|
||||
}, nil
|
||||
}, createdResponse.State, nil
|
||||
})
|
||||
}
|
||||
|
||||
// Retrieve Email changes in Mailboxes of multiple Accounts.
|
||||
func (j *Client) GetMailboxChangesForMultipleAccounts(accountIds []string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, sinceStateMap map[string]string, fetchBodies bool, maxBodyValueBytes uint, maxChanges uint) (map[string]MailboxChanges, SessionState, Language, Error) {
|
||||
func (j *Client) GetMailboxChangesForMultipleAccounts(accountIds []string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, sinceStateMap map[string]string, fetchBodies bool, maxBodyValueBytes uint, maxChanges uint) (map[string]MailboxChanges, SessionState, State, Language, Error) {
|
||||
logger = j.loggerParams("GetMailboxChangesForMultipleAccounts", session, logger, func(z zerolog.Context) zerolog.Context {
|
||||
sinceStateLogDict := zerolog.Dict()
|
||||
for k, v := range sinceStateMap {
|
||||
@@ -215,7 +206,7 @@ func (j *Client) GetMailboxChangesForMultipleAccounts(accountIds []string, sessi
|
||||
uniqueAccountIds := structs.Uniq(accountIds)
|
||||
n := len(uniqueAccountIds)
|
||||
if n < 1 {
|
||||
return map[string]MailboxChanges{}, "", "", nil
|
||||
return map[string]MailboxChanges{}, "", "", "", nil
|
||||
}
|
||||
|
||||
invocations := make([]Invocation, n*3)
|
||||
@@ -257,28 +248,29 @@ func (j *Client) GetMailboxChangesForMultipleAccounts(accountIds []string, sessi
|
||||
|
||||
cmd, err := j.request(session, logger, invocations...)
|
||||
if err != nil {
|
||||
return map[string]MailboxChanges{}, "", "", err
|
||||
return nil, "", "", "", err
|
||||
}
|
||||
|
||||
return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (map[string]MailboxChanges, Error) {
|
||||
return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (map[string]MailboxChanges, State, Error) {
|
||||
resp := make(map[string]MailboxChanges, n)
|
||||
stateByAccountId := make(map[string]State, n)
|
||||
for _, accountId := range uniqueAccountIds {
|
||||
var mailboxResponse MailboxChangesResponse
|
||||
err = retrieveResponseMatchParameters(logger, body, CommandMailboxChanges, mcid(accountId, "0"), &mailboxResponse)
|
||||
if err != nil {
|
||||
return map[string]MailboxChanges{}, err
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
var createdResponse EmailGetResponse
|
||||
err = retrieveResponseMatchParameters(logger, body, CommandEmailGet, mcid(accountId, "1"), &createdResponse)
|
||||
if err != nil {
|
||||
return map[string]MailboxChanges{}, err
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
var updatedResponse EmailGetResponse
|
||||
err = retrieveResponseMatchParameters(logger, body, CommandEmailGet, mcid(accountId, "2"), &updatedResponse)
|
||||
if err != nil {
|
||||
return map[string]MailboxChanges{}, err
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
resp[accountId] = MailboxChanges{
|
||||
@@ -287,21 +279,21 @@ func (j *Client) GetMailboxChangesForMultipleAccounts(accountIds []string, sessi
|
||||
NewState: mailboxResponse.NewState,
|
||||
Created: createdResponse.List,
|
||||
Updated: createdResponse.List,
|
||||
State: createdResponse.State,
|
||||
}
|
||||
stateByAccountId[accountId] = createdResponse.State
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
return resp, squashState(stateByAccountId), nil
|
||||
})
|
||||
}
|
||||
|
||||
func (j *Client) GetMailboxRolesForMultipleAccounts(accountIds []string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string) (map[string][]string, SessionState, Language, Error) {
|
||||
func (j *Client) GetMailboxRolesForMultipleAccounts(accountIds []string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string) (map[string][]string, SessionState, State, Language, Error) {
|
||||
logger = j.logger("GetMailboxRolesForMultipleAccounts", session, logger)
|
||||
|
||||
uniqueAccountIds := structs.Uniq(accountIds)
|
||||
n := len(uniqueAccountIds)
|
||||
if n < 1 {
|
||||
return map[string][]string{}, "", "", nil
|
||||
return nil, "", "", "", nil
|
||||
}
|
||||
|
||||
t := true
|
||||
@@ -326,16 +318,17 @@ func (j *Client) GetMailboxRolesForMultipleAccounts(accountIds []string, session
|
||||
|
||||
cmd, err := j.request(session, logger, invocations...)
|
||||
if err != nil {
|
||||
return map[string][]string{}, "", "", err
|
||||
return nil, "", "", "", err
|
||||
}
|
||||
|
||||
return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (map[string][]string, Error) {
|
||||
return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (map[string][]string, State, Error) {
|
||||
resp := make(map[string][]string, n)
|
||||
stateByAccountId := make(map[string]State, n)
|
||||
for _, accountId := range uniqueAccountIds {
|
||||
var getResponse MailboxGetResponse
|
||||
err = retrieveResponseMatchParameters(logger, body, CommandMailboxGet, mcid(accountId, "1"), &getResponse)
|
||||
if err != nil {
|
||||
return map[string][]string{}, err
|
||||
return nil, "", err
|
||||
}
|
||||
roles := make([]string, len(getResponse.List))
|
||||
for i, mailbox := range getResponse.List {
|
||||
@@ -343,18 +336,19 @@ func (j *Client) GetMailboxRolesForMultipleAccounts(accountIds []string, session
|
||||
}
|
||||
slices.Sort(roles)
|
||||
resp[accountId] = roles
|
||||
stateByAccountId[accountId] = getResponse.State
|
||||
}
|
||||
return resp, nil
|
||||
return resp, squashState(stateByAccountId), nil
|
||||
})
|
||||
}
|
||||
|
||||
func (j *Client) GetInboxNameForMultipleAccounts(accountIds []string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string) (map[string]string, SessionState, Language, Error) {
|
||||
func (j *Client) GetInboxNameForMultipleAccounts(accountIds []string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string) (map[string]string, SessionState, State, Language, Error) {
|
||||
logger = j.logger("GetInboxNameForMultipleAccounts", session, logger)
|
||||
|
||||
uniqueAccountIds := structs.Uniq(accountIds)
|
||||
n := len(uniqueAccountIds)
|
||||
if n < 1 {
|
||||
return nil, "", "", nil
|
||||
return nil, "", "", "", nil
|
||||
}
|
||||
|
||||
invocations := make([]Invocation, n*2)
|
||||
@@ -369,27 +363,116 @@ func (j *Client) GetInboxNameForMultipleAccounts(accountIds []string, session *S
|
||||
|
||||
cmd, err := j.request(session, logger, invocations...)
|
||||
if err != nil {
|
||||
return nil, "", "", err
|
||||
return nil, "", "", "", err
|
||||
}
|
||||
|
||||
return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (map[string]string, Error) {
|
||||
return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (map[string]string, State, Error) {
|
||||
resp := make(map[string]string, n)
|
||||
stateByAccountId := make(map[string]State, n)
|
||||
for _, accountId := range uniqueAccountIds {
|
||||
var r MailboxQueryResponse
|
||||
err = retrieveResponseMatchParameters(logger, body, CommandMailboxGet, mcid(accountId, "0"), &r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, "", err
|
||||
}
|
||||
switch len(r.Ids) {
|
||||
case 0:
|
||||
// skip: account has no inbox?
|
||||
case 1:
|
||||
resp[accountId] = r.Ids[0]
|
||||
stateByAccountId[accountId] = r.QueryState
|
||||
default:
|
||||
logger.Warn().Msgf("multiple ids for mailbox role='%v' for accountId='%v'", JmapMailboxRoleInbox, accountId)
|
||||
resp[accountId] = r.Ids[0]
|
||||
stateByAccountId[accountId] = r.QueryState
|
||||
}
|
||||
}
|
||||
return resp, nil
|
||||
return resp, squashState(stateByAccountId), nil
|
||||
})
|
||||
}
|
||||
|
||||
func (j *Client) UpdateMailbox(accountId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, mailboxId string, ifInState string, update MailboxChange) (Mailbox, SessionState, State, Language, Error) {
|
||||
logger = j.logger("UpdateMailbox", session, logger)
|
||||
cmd, err := j.request(session, logger, invocation(CommandMailboxSet, MailboxSetCommand{
|
||||
AccountId: accountId,
|
||||
IfInState: ifInState,
|
||||
Update: map[string]PatchObject{
|
||||
mailboxId: update.AsPatch(),
|
||||
},
|
||||
}, "0"))
|
||||
if err != nil {
|
||||
return Mailbox{}, "", "", "", err
|
||||
}
|
||||
|
||||
return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (Mailbox, State, Error) {
|
||||
var setResp MailboxSetResponse
|
||||
err = retrieveResponseMatchParameters(logger, body, CommandMailboxSet, "0", &setResp)
|
||||
if err != nil {
|
||||
return Mailbox{}, "", err
|
||||
}
|
||||
setErr, notok := setResp.NotUpdated["u"]
|
||||
if notok {
|
||||
logger.Error().Msgf("%T.NotUpdated returned an error %v", setResp, setErr)
|
||||
return Mailbox{}, "", setErrorError(setErr, MailboxType)
|
||||
}
|
||||
return setResp.Updated["c"], setResp.NewState, nil
|
||||
})
|
||||
}
|
||||
|
||||
func (j *Client) CreateMailbox(accountId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, ifInState string, create MailboxChange) (Mailbox, SessionState, State, Language, Error) {
|
||||
logger = j.logger("CreateMailbox", session, logger)
|
||||
cmd, err := j.request(session, logger, invocation(CommandMailboxSet, MailboxSetCommand{
|
||||
AccountId: accountId,
|
||||
IfInState: ifInState,
|
||||
Create: map[string]MailboxChange{
|
||||
"c": create,
|
||||
},
|
||||
}, "0"))
|
||||
if err != nil {
|
||||
return Mailbox{}, "", "", "", err
|
||||
}
|
||||
|
||||
return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (Mailbox, State, Error) {
|
||||
var setResp MailboxSetResponse
|
||||
err = retrieveResponseMatchParameters(logger, body, CommandMailboxSet, "0", &setResp)
|
||||
if err != nil {
|
||||
return Mailbox{}, "", err
|
||||
}
|
||||
setErr, notok := setResp.NotCreated["c"]
|
||||
if notok {
|
||||
logger.Error().Msgf("%T.NotCreated returned an error %v", setResp, setErr)
|
||||
return Mailbox{}, "", setErrorError(setErr, MailboxType)
|
||||
}
|
||||
if mailbox, ok := setResp.Created["c"]; ok {
|
||||
return mailbox, setResp.NewState, nil
|
||||
} else {
|
||||
return Mailbox{}, "", simpleError(fmt.Errorf("failed to find created %T in response", Mailbox{}), JmapErrorMissingCreatedObject)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func (j *Client) DeleteMailboxes(accountId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, ifInState string, mailboxIds []string) ([]string, SessionState, State, Language, Error) {
|
||||
logger = j.logger("DeleteMailbox", session, logger)
|
||||
cmd, err := j.request(session, logger, invocation(CommandMailboxSet, MailboxSetCommand{
|
||||
AccountId: accountId,
|
||||
IfInState: ifInState,
|
||||
Destroy: mailboxIds,
|
||||
}, "0"))
|
||||
if err != nil {
|
||||
return nil, "", "", "", err
|
||||
}
|
||||
|
||||
return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) ([]string, State, Error) {
|
||||
var setResp MailboxSetResponse
|
||||
err = retrieveResponseMatchParameters(logger, body, CommandMailboxSet, "0", &setResp)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
setErr, notok := setResp.NotUpdated["u"]
|
||||
if notok {
|
||||
logger.Error().Msgf("%T.NotUpdated returned an error %v", setResp, setErr)
|
||||
return nil, "", setErrorError(setErr, MailboxType)
|
||||
}
|
||||
return setResp.Destroyed, setResp.NewState, nil
|
||||
})
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
"github.com/opencloud-eu/opencloud/pkg/structs"
|
||||
)
|
||||
|
||||
func (j *Client) GetQuotas(accountIds []string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string) (map[string]QuotaGetResponse, SessionState, Language, Error) {
|
||||
func (j *Client) GetQuotas(accountIds []string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string) (map[string]QuotaGetResponse, SessionState, State, Language, Error) {
|
||||
logger = j.logger("GetQuotas", session, logger)
|
||||
|
||||
uniqueAccountIds := structs.Uniq(accountIds)
|
||||
@@ -18,18 +18,18 @@ func (j *Client) GetQuotas(accountIds []string, session *Session, ctx context.Co
|
||||
}
|
||||
cmd, err := j.request(session, logger, invocations...)
|
||||
if err != nil {
|
||||
return nil, "", "", err
|
||||
return nil, "", "", "", err
|
||||
}
|
||||
return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (map[string]QuotaGetResponse, Error) {
|
||||
return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (map[string]QuotaGetResponse, State, Error) {
|
||||
result := map[string]QuotaGetResponse{}
|
||||
for _, accountId := range uniqueAccountIds {
|
||||
var response QuotaGetResponse
|
||||
err = retrieveResponseMatchParameters(logger, body, CommandQuotaGet, mcid(accountId, "0"), &response)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, "", err
|
||||
}
|
||||
result[accountId] = response
|
||||
}
|
||||
return result, nil
|
||||
return result, squashStateFunc(result, func(q QuotaGetResponse) State { return q.State }), nil
|
||||
})
|
||||
}
|
||||
|
||||
@@ -13,19 +13,19 @@ const (
|
||||
)
|
||||
|
||||
// https://jmap.io/spec-mail.html#vacationresponseget
|
||||
func (j *Client) GetVacationResponse(accountId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string) (VacationResponseGetResponse, SessionState, Language, Error) {
|
||||
func (j *Client) GetVacationResponse(accountId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string) (VacationResponseGetResponse, SessionState, State, Language, Error) {
|
||||
logger = j.logger("GetVacationResponse", session, logger)
|
||||
cmd, err := j.request(session, logger, invocation(CommandVacationResponseGet, VacationResponseGetCommand{AccountId: accountId}, "0"))
|
||||
if err != nil {
|
||||
return VacationResponseGetResponse{}, "", "", err
|
||||
return VacationResponseGetResponse{}, "", "", "", err
|
||||
}
|
||||
return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (VacationResponseGetResponse, Error) {
|
||||
return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (VacationResponseGetResponse, State, Error) {
|
||||
var response VacationResponseGetResponse
|
||||
err = retrieveResponseMatchParameters(logger, body, CommandVacationResponseGet, "0", &response)
|
||||
if err != nil {
|
||||
return VacationResponseGetResponse{}, err
|
||||
return VacationResponseGetResponse{}, "", err
|
||||
}
|
||||
return response, nil
|
||||
return response, response.State, nil
|
||||
})
|
||||
}
|
||||
|
||||
@@ -53,12 +53,7 @@ type VacationResponsePayload struct {
|
||||
HtmlBody string `json:"htmlBody,omitempty"`
|
||||
}
|
||||
|
||||
type VacationResponseChange struct {
|
||||
VacationResponse VacationResponse `json:"vacationResponse"`
|
||||
ResponseState State `json:"state"`
|
||||
}
|
||||
|
||||
func (j *Client) SetVacationResponse(accountId string, vacation VacationResponsePayload, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string) (VacationResponseChange, SessionState, Language, Error) {
|
||||
func (j *Client) SetVacationResponse(accountId string, vacation VacationResponsePayload, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string) (VacationResponse, SessionState, State, Language, Error) {
|
||||
logger = j.logger("SetVacationResponse", session, logger)
|
||||
|
||||
cmd, err := j.request(session, logger,
|
||||
@@ -80,37 +75,34 @@ func (j *Client) SetVacationResponse(accountId string, vacation VacationResponse
|
||||
invocation(CommandVacationResponseGet, VacationResponseGetCommand{AccountId: accountId}, "1"),
|
||||
)
|
||||
if err != nil {
|
||||
return VacationResponseChange{}, "", "", err
|
||||
return VacationResponse{}, "", "", "", err
|
||||
}
|
||||
return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (VacationResponseChange, Error) {
|
||||
return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (VacationResponse, State, Error) {
|
||||
var setResponse VacationResponseSetResponse
|
||||
err = retrieveResponseMatchParameters(logger, body, CommandVacationResponseSet, "0", &setResponse)
|
||||
if err != nil {
|
||||
return VacationResponseChange{}, err
|
||||
return VacationResponse{}, "", err
|
||||
}
|
||||
|
||||
setErr, notok := setResponse.NotCreated[vacationResponseId]
|
||||
if notok {
|
||||
// this means that the VacationResponse was not updated
|
||||
logger.Error().Msgf("%T.NotCreated contains an error: %v", setResponse, setErr)
|
||||
return VacationResponseChange{}, setErrorError(setErr, VacationResponseType)
|
||||
return VacationResponse{}, "", setErrorError(setErr, VacationResponseType)
|
||||
}
|
||||
|
||||
var getResponse VacationResponseGetResponse
|
||||
err = retrieveResponseMatchParameters(logger, body, CommandVacationResponseGet, "1", &getResponse)
|
||||
if err != nil {
|
||||
return VacationResponseChange{}, err
|
||||
return VacationResponse{}, "", err
|
||||
}
|
||||
|
||||
if len(getResponse.List) != 1 {
|
||||
berr := fmt.Errorf("failed to find %s in %s response", string(VacationResponseType), string(CommandVacationResponseGet))
|
||||
logger.Error().Msg(berr.Error())
|
||||
return VacationResponseChange{}, simpleError(berr, JmapErrorInvalidJmapResponsePayload)
|
||||
return VacationResponse{}, "", simpleError(berr, JmapErrorInvalidJmapResponsePayload)
|
||||
}
|
||||
|
||||
return VacationResponseChange{
|
||||
VacationResponse: getResponse.List[0],
|
||||
ResponseState: setResponse.NewState,
|
||||
}, nil
|
||||
return getResponse.List[0], setResponse.NewState, nil
|
||||
})
|
||||
}
|
||||
|
||||
@@ -36,6 +36,7 @@ const (
|
||||
JmapErrorWssFailedToSendWebSocketPushDisable
|
||||
JmapErrorWssFailedToClose
|
||||
JmapErrorWssFailedToRetrieveSession
|
||||
JmapErrorMissingCreatedObject
|
||||
)
|
||||
|
||||
var (
|
||||
|
||||
@@ -689,7 +689,7 @@ func TestEmails(t *testing.T) {
|
||||
var inboxFolder string
|
||||
var inboxId string
|
||||
{
|
||||
respByAccountId, sessionState, _, err := s.client.GetAllMailboxes([]string{accountId}, s.session, s.ctx, s.logger, "")
|
||||
respByAccountId, sessionState, _, _, err := s.client.GetAllMailboxes([]string{accountId}, s.session, s.ctx, s.logger, "")
|
||||
require.NoError(err)
|
||||
require.Equal(s.session.State, sessionState)
|
||||
require.Len(respByAccountId, 1)
|
||||
@@ -698,7 +698,7 @@ func TestEmails(t *testing.T) {
|
||||
|
||||
mailboxesNameByRole := map[string]string{}
|
||||
mailboxesUnreadByRole := map[string]int{}
|
||||
for _, m := range resp.Mailboxes {
|
||||
for _, m := range resp {
|
||||
if m.Role != "" {
|
||||
mailboxesNameByRole[m.Role] = m.Name
|
||||
mailboxesUnreadByRole[m.Role] = m.UnreadEmails
|
||||
@@ -708,7 +708,7 @@ func TestEmails(t *testing.T) {
|
||||
require.Contains(mailboxesUnreadByRole, "inbox")
|
||||
require.Zero(mailboxesUnreadByRole["inbox"])
|
||||
|
||||
inboxId = mailboxId("inbox", resp.Mailboxes)
|
||||
inboxId = mailboxId("inbox", resp)
|
||||
require.NotEmpty(inboxId)
|
||||
inboxFolder = mailboxesNameByRole["inbox"]
|
||||
require.NotEmpty(inboxFolder)
|
||||
@@ -724,23 +724,23 @@ func TestEmails(t *testing.T) {
|
||||
|
||||
{
|
||||
{
|
||||
resp, sessionState, _, err := s.client.GetAllIdentities(accountId, s.session, s.ctx, s.logger, "")
|
||||
resp, sessionState, _, _, err := s.client.GetAllIdentities(accountId, s.session, s.ctx, s.logger, "")
|
||||
require.NoError(err)
|
||||
require.Equal(s.session.State, sessionState)
|
||||
require.Len(resp.Identities, 1)
|
||||
require.Equal(s.userEmail, resp.Identities[0].Email)
|
||||
require.Equal(s.userPersonName, resp.Identities[0].Name)
|
||||
require.Len(resp, 1)
|
||||
require.Equal(s.userEmail, resp[0].Email)
|
||||
require.Equal(s.userPersonName, resp[0].Name)
|
||||
}
|
||||
|
||||
{
|
||||
respByAccountId, sessionState, _, err := s.client.GetAllMailboxes([]string{accountId}, s.session, s.ctx, s.logger, "")
|
||||
respByAccountId, sessionState, _, _, err := s.client.GetAllMailboxes([]string{accountId}, s.session, s.ctx, s.logger, "")
|
||||
require.NoError(err)
|
||||
require.Equal(s.session.State, sessionState)
|
||||
require.Len(respByAccountId, 1)
|
||||
require.Contains(respByAccountId, accountId)
|
||||
resp := respByAccountId[accountId]
|
||||
mailboxesUnreadByRole := map[string]int{}
|
||||
for _, m := range resp.Mailboxes {
|
||||
for _, m := range resp {
|
||||
if m.Role != "" {
|
||||
mailboxesUnreadByRole[m.Role] = m.UnreadEmails
|
||||
}
|
||||
@@ -749,7 +749,7 @@ func TestEmails(t *testing.T) {
|
||||
}
|
||||
|
||||
{
|
||||
resp, sessionState, _, err := s.client.GetAllEmailsInMailbox(accountId, s.session, s.ctx, s.logger, "", inboxId, 0, 0, true, false, 0, true)
|
||||
resp, sessionState, _, _, err := s.client.GetAllEmailsInMailbox(accountId, s.session, s.ctx, s.logger, "", inboxId, 0, 0, true, false, 0, true)
|
||||
require.NoError(err)
|
||||
require.Equal(s.session.State, sessionState)
|
||||
|
||||
@@ -766,7 +766,7 @@ func TestEmails(t *testing.T) {
|
||||
}
|
||||
|
||||
{
|
||||
resp, sessionState, _, err := s.client.GetAllEmailsInMailbox(accountId, s.session, s.ctx, s.logger, "", inboxId, 0, 0, false, false, 0, true)
|
||||
resp, sessionState, _, _, err := s.client.GetAllEmailsInMailbox(accountId, s.session, s.ctx, s.logger, "", inboxId, 0, 0, false, false, 0, true)
|
||||
require.NoError(err)
|
||||
require.Equal(s.session.State, sessionState)
|
||||
|
||||
|
||||
@@ -1201,7 +1201,7 @@ type Mailbox struct {
|
||||
// (that has the same parent) in any Mailbox listing in the client’s UI.
|
||||
// Mailboxes with equal order SHOULD be sorted in alphabetical order by name.
|
||||
// The sorting should take into account locale-specific character order convention.
|
||||
SortOrder int `json:"sortOrder,omitzero"`
|
||||
SortOrder *int `json:"sortOrder,omitempty"`
|
||||
|
||||
// The number of Emails in this Mailbox.
|
||||
TotalEmails int `json:"totalEmails"`
|
||||
@@ -1220,7 +1220,7 @@ type Mailbox struct {
|
||||
// These are backwards compatible with IMAP ACLs, as defined in [RFC4314].
|
||||
//
|
||||
// [RFC4314]: https://www.rfc-editor.org/rfc/rfc4314.html
|
||||
MyRights MailboxRights `json:"myRights,omitempty"`
|
||||
MyRights *MailboxRights `json:"myRights,omitempty"`
|
||||
|
||||
// Has the user indicated they wish to see this Mailbox in their client?
|
||||
//
|
||||
@@ -1241,7 +1241,96 @@ type Mailbox struct {
|
||||
// This property corresponds to IMAP [RFC3501] Mailbox subscriptions.
|
||||
//
|
||||
// [RFC3501]: https://www.rfc-editor.org/rfc/rfc3501.html
|
||||
IsSubscribed bool `json:"isSubscribed"`
|
||||
IsSubscribed *bool `json:"isSubscribed,omitempty"`
|
||||
}
|
||||
|
||||
type MailboxChange struct {
|
||||
// User-visible name for the Mailbox, e.g., “Inbox”.
|
||||
//
|
||||
// This MUST be a Net-Unicode string [@!RFC5198] of at least 1 character in length, subject to the maximum size
|
||||
// given in the capability object.
|
||||
//
|
||||
// There MUST NOT be two sibling Mailboxes with both the same parent and the same name.
|
||||
//
|
||||
// Servers MAY reject names that violate server policy (e.g., names containing a slash (/) or control characters).
|
||||
Name string `json:"name,omitempty"`
|
||||
|
||||
// The Mailbox id for the parent of this Mailbox, or null if this Mailbox is at the top level.
|
||||
//
|
||||
// Mailboxes form acyclic graphs (forests) directed by the child-to-parent relationship. There MUST NOT be a loop.
|
||||
ParentId string `json:"parentId,omitempty"`
|
||||
|
||||
// Identifies Mailboxes that have a particular common purpose (e.g., the “inbox”), regardless of the name property
|
||||
// (which may be localised).
|
||||
//
|
||||
// This value is shared with IMAP (exposed in IMAP via the SPECIAL-USE extension [RFC6154]).
|
||||
// However, unlike in IMAP, a Mailbox MUST only have a single role, and there MUST NOT be two Mailboxes in the same
|
||||
// account with the same role.
|
||||
//
|
||||
// Servers providing IMAP access to the same data are encouraged to enforce these extra restrictions in IMAP as well.
|
||||
// Otherwise, modifying the IMAP attributes to ensure compliance when exposing the data over JMAP is implementation dependent.
|
||||
//
|
||||
// The value MUST be one of the Mailbox attribute names listed in the IANA IMAP Mailbox Name Attributes registry,
|
||||
// as established in [RFC8457], converted to lowercase. New roles may be established here in the future.
|
||||
//
|
||||
// An account is not required to have Mailboxes with any particular roles.
|
||||
//
|
||||
// [RFC6154]: https://www.rfc-editor.org/rfc/rfc6154.html
|
||||
// [RFC8457]: https://www.rfc-editor.org/rfc/rfc8457.html
|
||||
Role string `json:"role,omitempty"`
|
||||
|
||||
// Defines the sort order of Mailboxes when presented in the client’s UI, so it is consistent between devices.
|
||||
//
|
||||
// Default value: 0
|
||||
//
|
||||
// The number MUST be an integer in the range 0 <= sortOrder < 2^31.
|
||||
//
|
||||
// A Mailbox with a lower order should be displayed before a Mailbox with a higher order
|
||||
// (that has the same parent) in any Mailbox listing in the client’s UI.
|
||||
// Mailboxes with equal order SHOULD be sorted in alphabetical order by name.
|
||||
// The sorting should take into account locale-specific character order convention.
|
||||
SortOrder *int `json:"sortOrder,omitempty"`
|
||||
|
||||
// Has the user indicated they wish to see this Mailbox in their client?
|
||||
//
|
||||
// This SHOULD default to false for Mailboxes in shared accounts the user has access to and true
|
||||
// for any new Mailboxes created by the user themself.
|
||||
//
|
||||
// This MUST be stored separately per user where multiple users have access to a shared Mailbox.
|
||||
//
|
||||
// A user may have permission to access a large number of shared accounts, or a shared account with a very
|
||||
// large set of Mailboxes, but only be interested in the contents of a few of these.
|
||||
//
|
||||
// Clients may choose to only display Mailboxes where the isSubscribed property is set to true, and offer
|
||||
// a separate UI to allow the user to see and subscribe/unsubscribe from the full set of Mailboxes.
|
||||
//
|
||||
// However, clients MAY choose to ignore this property, either entirely for ease of implementation or just
|
||||
// for an account where isPersonal is true (indicating it is the user’s own rather than a shared account).
|
||||
//
|
||||
// This property corresponds to IMAP [RFC3501] Mailbox subscriptions.
|
||||
//
|
||||
// [RFC3501]: https://www.rfc-editor.org/rfc/rfc3501.html
|
||||
IsSubscribed *bool `json:"isSubscribed,omitempty"`
|
||||
}
|
||||
|
||||
func (m MailboxChange) AsPatch() PatchObject {
|
||||
p := PatchObject{}
|
||||
if m.Name != "" {
|
||||
p["name"] = m.Name
|
||||
}
|
||||
if m.ParentId != "" {
|
||||
p["parentId"] = m.ParentId
|
||||
}
|
||||
if m.Role != "" {
|
||||
p["role"] = m.Role
|
||||
}
|
||||
if m.SortOrder != nil {
|
||||
p["sortOrder"] = m.SortOrder
|
||||
}
|
||||
if m.IsSubscribed != nil {
|
||||
p["isSubscribed"] = m.IsSubscribed
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
type MailboxGetCommand struct {
|
||||
@@ -1254,6 +1343,26 @@ type MailboxGetRefCommand struct {
|
||||
IdsRef *ResultReference `json:"#ids,omitempty"`
|
||||
}
|
||||
|
||||
type MailboxSetCommand struct {
|
||||
AccountId string `json:"accountId"`
|
||||
IfInState string `json:"ifInState,omitempty"`
|
||||
Create map[string]MailboxChange `json:"create,omitempty"`
|
||||
Update map[string]PatchObject `json:"update,omitempty"`
|
||||
Destroy []string `json:"destroy,omitempty"`
|
||||
}
|
||||
|
||||
type MailboxSetResponse struct {
|
||||
AccountId string `json:"accountId"`
|
||||
OldState State `json:"oldState,omitempty"`
|
||||
NewState State `json:"newState,omitempty"`
|
||||
Created map[string]Mailbox `json:"created,omitempty"`
|
||||
Updated map[string]Mailbox `json:"updated,omitempty"`
|
||||
Destroyed []string `json:"destroyed,omitempty"`
|
||||
NotCreated map[string]SetError `json:"notCreated,omitempty"`
|
||||
NotUpdated map[string]SetError `json:"notUpdated,omitempty"`
|
||||
NotDestroyed map[string]SetError `json:"notDestroyed,omitempty"`
|
||||
}
|
||||
|
||||
type MailboxChangesCommand struct {
|
||||
// The id of the account to use.
|
||||
AccountId string `json:"accountId"`
|
||||
@@ -5251,6 +5360,7 @@ const (
|
||||
CommandEmailSubmissionSet Command = "EmailSubmission/set"
|
||||
CommandThreadGet Command = "Thread/get"
|
||||
CommandMailboxGet Command = "Mailbox/get"
|
||||
CommandMailboxSet Command = "Mailbox/set"
|
||||
CommandMailboxQuery Command = "Mailbox/query"
|
||||
CommandMailboxChanges Command = "Mailbox/changes"
|
||||
CommandIdentityGet Command = "Identity/get"
|
||||
@@ -5272,6 +5382,7 @@ var CommandResponseTypeMap = map[Command]func() any{
|
||||
CommandBlobUpload: func() any { return BlobUploadResponse{} },
|
||||
CommandMailboxQuery: func() any { return MailboxQueryResponse{} },
|
||||
CommandMailboxGet: func() any { return MailboxGetResponse{} },
|
||||
CommandMailboxSet: func() any { return MailboxSetResponse{} },
|
||||
CommandMailboxChanges: func() any { return MailboxChangesResponse{} },
|
||||
CommandEmailQuery: func() any { return EmailQueryResponse{} },
|
||||
CommandEmailChanges: func() any { return EmailChangesResponse{} },
|
||||
|
||||
@@ -231,15 +231,15 @@ func TestRequests(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
foldersByAccountId, sessionState, _, err := client.GetAllMailboxes([]string{"a"}, &session, ctx, &logger, "")
|
||||
foldersByAccountId, sessionState, _, _, err := client.GetAllMailboxes([]string{"a"}, &session, ctx, &logger, "")
|
||||
require.NoError(err)
|
||||
require.Len(foldersByAccountId, 1)
|
||||
require.Contains(foldersByAccountId, "a")
|
||||
folders := foldersByAccountId["a"]
|
||||
require.Len(folders.Mailboxes, 5)
|
||||
require.Len(folders, 5)
|
||||
require.NotEmpty(sessionState)
|
||||
|
||||
emails, sessionState, _, err := client.GetAllEmailsInMailbox("a", &session, ctx, &logger, "", "Inbox", 0, 0, false, true, 0, true)
|
||||
emails, sessionState, _, _, err := client.GetAllEmailsInMailbox("a", &session, ctx, &logger, "", "Inbox", 0, 0, false, true, 0, true)
|
||||
require.NoError(err)
|
||||
require.Len(emails.Emails, 3)
|
||||
require.NotEmpty(sessionState)
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"slices"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
@@ -57,12 +58,12 @@ func command[T any](api ApiClient,
|
||||
sessionOutdatedHandler func(session *Session, newState SessionState),
|
||||
request Request,
|
||||
acceptLanguage string,
|
||||
mapper func(body *Response) (T, Error)) (T, SessionState, Language, Error) {
|
||||
mapper func(body *Response) (T, State, Error)) (T, SessionState, State, Language, Error) {
|
||||
|
||||
responseBody, language, jmapErr := api.Command(ctx, logger, session, request, acceptLanguage)
|
||||
if jmapErr != nil {
|
||||
var zero T
|
||||
return zero, "", language, jmapErr
|
||||
return zero, "", "", language, jmapErr
|
||||
}
|
||||
|
||||
var response Response
|
||||
@@ -70,7 +71,7 @@ func command[T any](api ApiClient,
|
||||
if err != nil {
|
||||
logger.Error().Err(err).Msgf("failed to deserialize body JSON payload into a %T", response)
|
||||
var zero T
|
||||
return zero, "", language, SimpleError{code: JmapErrorDecodingResponseBody, err: err}
|
||||
return zero, "", "", language, SimpleError{code: JmapErrorDecodingResponseBody, err: err}
|
||||
}
|
||||
|
||||
if response.SessionState != session.State {
|
||||
@@ -117,21 +118,21 @@ func command[T any](api ApiClient,
|
||||
err = errors.New(msg)
|
||||
logger.Warn().Int("code", code).Str("type", errorParameters.Type).Msg(msg)
|
||||
var zero T
|
||||
return zero, response.SessionState, language, SimpleError{code: code, err: err}
|
||||
return zero, response.SessionState, "", language, SimpleError{code: code, err: err}
|
||||
} else {
|
||||
code := JmapErrorUnspecifiedType
|
||||
msg := fmt.Sprintf("found method level error in response '%v'", mr.Tag)
|
||||
err := errors.New(msg)
|
||||
logger.Warn().Int("code", code).Msg(msg)
|
||||
var zero T
|
||||
return zero, response.SessionState, language, SimpleError{code: code, err: err}
|
||||
return zero, response.SessionState, "", language, SimpleError{code: code, err: err}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
result, jerr := mapper(&response)
|
||||
result, state, jerr := mapper(&response)
|
||||
sessionState := response.SessionState
|
||||
return result, sessionState, language, jerr
|
||||
return result, sessionState, state, language, jerr
|
||||
}
|
||||
|
||||
func mapstructStringToTimeHook() mapstructure.DecodeHookFunc {
|
||||
@@ -256,3 +257,74 @@ func (i *Invocation) UnmarshalJSON(bs []byte) error {
|
||||
i.Parameters = params
|
||||
return nil
|
||||
}
|
||||
|
||||
func squashState(all map[string]State) State {
|
||||
return squashStateFunc(all, func(s State) State { return s })
|
||||
}
|
||||
|
||||
func squashStateFunc[V any](all map[string]V, mapper func(V) State) State {
|
||||
n := len(all)
|
||||
if n == 0 {
|
||||
return State("")
|
||||
}
|
||||
if n == 1 {
|
||||
for _, v := range all {
|
||||
return mapper(v)
|
||||
}
|
||||
}
|
||||
|
||||
parts := make([]string, n)
|
||||
sortedKeys := make([]string, n)
|
||||
i := 0
|
||||
for k := range all {
|
||||
sortedKeys[i] = k
|
||||
i++
|
||||
}
|
||||
slices.Sort(sortedKeys)
|
||||
for i, k := range sortedKeys {
|
||||
if v, ok := all[k]; ok {
|
||||
parts[i] = k + ":" + string(mapper(v))
|
||||
} else {
|
||||
parts[i] = k + ":"
|
||||
}
|
||||
}
|
||||
return State(strings.Join(parts, ","))
|
||||
}
|
||||
|
||||
func squashStateMaps(first map[string]State, second map[string]State) State {
|
||||
return squashStateFunc(mapPairs(first, second), func(p pair[State, State]) State {
|
||||
if p.left != nil {
|
||||
if p.right != nil {
|
||||
return *p.left + ";" + *p.right
|
||||
} else {
|
||||
return *p.left + ";"
|
||||
}
|
||||
} else if p.right != nil {
|
||||
return ";" + *p.right
|
||||
} else {
|
||||
return ";"
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
type pair[L any, R any] struct {
|
||||
left *L
|
||||
right *R
|
||||
}
|
||||
|
||||
func mapPairs[K comparable, L, R any](left map[K]L, right map[K]R) map[K]pair[L, R] {
|
||||
result := map[K]pair[L, R]{}
|
||||
for k, l := range left {
|
||||
if r, ok := right[k]; ok {
|
||||
result[k] = pair[L, R]{left: &l, right: &r}
|
||||
} else {
|
||||
result[k] = pair[L, R]{left: &l, right: nil}
|
||||
}
|
||||
}
|
||||
for k, r := range right {
|
||||
if _, ok := left[k]; !ok {
|
||||
result[k] = pair[L, R]{left: nil, right: &r}
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
@@ -50,7 +50,7 @@ func TestDeserializeMailboxGetResponse(t *testing.T) {
|
||||
require.Equal(expected.unread, folder.UnreadThreads)
|
||||
require.Empty(folder.ParentId)
|
||||
require.Zero(folder.SortOrder)
|
||||
require.True(folder.IsSubscribed)
|
||||
require.Nil(folder.IsSubscribed)
|
||||
|
||||
require.True(folder.MyRights.MayReadItems)
|
||||
require.True(folder.MyRights.MayAddItems)
|
||||
|
||||
@@ -28,20 +28,15 @@ func (g *Groupware) GetBlobMeta(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
logger := log.From(req.logger.With().Str(logAccountId, accountId))
|
||||
|
||||
res, sessionState, lang, jerr := g.jmap.GetBlobMetadata(accountId, req.session, req.ctx, logger, req.language(), blobId)
|
||||
res, sessionState, state, lang, jerr := g.jmap.GetBlobMetadata(accountId, req.session, req.ctx, logger, req.language(), blobId)
|
||||
if jerr != nil {
|
||||
return req.errorResponseFromJmap(jerr)
|
||||
}
|
||||
blob := res.Blob
|
||||
blob := res
|
||||
if blob == nil {
|
||||
return notFoundResponse(sessionState)
|
||||
}
|
||||
digest := blob.Digest()
|
||||
if digest != "" {
|
||||
return etagResponse(res, sessionState, jmap.State(digest), lang)
|
||||
} else {
|
||||
return response(res, sessionState, lang)
|
||||
}
|
||||
return etagResponse(res, sessionState, state, lang)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -121,10 +121,10 @@ func (g *Groupware) ParseIcalBlob(w http.ResponseWriter, r *http.Request) {
|
||||
l := req.logger.With().Array(UriParamBlobId, log.SafeStringArray(blobIds))
|
||||
logger := log.From(l)
|
||||
|
||||
resp, sessionState, lang, jerr := g.jmap.ParseICalendarBlob(accountId, req.session, req.ctx, logger, req.language(), blobIds)
|
||||
resp, sessionState, state, lang, jerr := g.jmap.ParseICalendarBlob(accountId, req.session, req.ctx, logger, req.language(), blobIds)
|
||||
if jerr != nil {
|
||||
return req.errorResponseFromJmap(jerr)
|
||||
}
|
||||
return response(resp, sessionState, lang)
|
||||
return etagResponse(resp, sessionState, state, lang)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -32,12 +32,12 @@ func (g *Groupware) GetAddressbooks(w http.ResponseWriter, r *http.Request) {
|
||||
return resp
|
||||
}
|
||||
|
||||
addressbooks, sessionState, lang, jerr := g.jmap.GetAddressbooks(accountId, req.session, req.ctx, req.logger, req.language(), nil)
|
||||
addressbooks, sessionState, state, lang, jerr := g.jmap.GetAddressbooks(accountId, req.session, req.ctx, req.logger, req.language(), nil)
|
||||
if jerr != nil {
|
||||
return req.errorResponseFromJmap(jerr)
|
||||
}
|
||||
|
||||
return response(addressbooks, sessionState, lang)
|
||||
return etagResponse(addressbooks, sessionState, state, lang)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -72,7 +72,7 @@ func (g *Groupware) GetAddressbook(w http.ResponseWriter, r *http.Request) {
|
||||
l = l.Str(UriParamAddressBookId, log.SafeString(addressBookId))
|
||||
|
||||
logger := log.From(l)
|
||||
addressbooks, sessionState, lang, jerr := g.jmap.GetAddressbooks(accountId, req.session, req.ctx, logger, req.language(), []string{addressBookId})
|
||||
addressbooks, sessionState, state, lang, jerr := g.jmap.GetAddressbooks(accountId, req.session, req.ctx, logger, req.language(), []string{addressBookId})
|
||||
if jerr != nil {
|
||||
return req.errorResponseFromJmap(jerr)
|
||||
}
|
||||
@@ -80,7 +80,7 @@ func (g *Groupware) GetAddressbook(w http.ResponseWriter, r *http.Request) {
|
||||
if len(addressbooks.NotFound) > 0 {
|
||||
return notFoundResponse(sessionState)
|
||||
} else {
|
||||
return response(addressbooks, sessionState, lang)
|
||||
return etagResponse(addressbooks, sessionState, state, lang)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -135,13 +135,13 @@ func (g *Groupware) GetContactsInAddressbook(w http.ResponseWriter, r *http.Requ
|
||||
sortBy := []jmap.ContactCardComparator{{Property: jscontact.ContactCardPropertyUpdated, IsAscending: false}}
|
||||
|
||||
logger := log.From(l)
|
||||
contactsByAccountId, sessionState, lang, jerr := g.jmap.QueryContactCards([]string{accountId}, req.session, req.ctx, logger, req.language(), filter, sortBy, offset, limit)
|
||||
contactsByAccountId, sessionState, state, lang, jerr := g.jmap.QueryContactCards([]string{accountId}, req.session, req.ctx, logger, req.language(), filter, sortBy, offset, limit)
|
||||
if jerr != nil {
|
||||
return req.errorResponseFromJmap(jerr)
|
||||
}
|
||||
|
||||
if contacts, ok := contactsByAccountId[accountId]; ok {
|
||||
return response(contacts, req.session.State, lang)
|
||||
return etagResponse(contacts, sessionState, state, lang)
|
||||
} else {
|
||||
return notFoundResponse(sessionState)
|
||||
}
|
||||
@@ -167,11 +167,11 @@ func (g *Groupware) CreateContact(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
logger := log.From(l)
|
||||
created, sessionState, lang, jerr := g.jmap.CreateContactCard(accountId, req.session, req.ctx, logger, req.language(), create)
|
||||
created, sessionState, state, lang, jerr := g.jmap.CreateContactCard(accountId, req.session, req.ctx, logger, req.language(), create)
|
||||
if jerr != nil {
|
||||
return req.errorResponseFromJmap(jerr)
|
||||
}
|
||||
return etagResponse(created.ContactCard, sessionState, created.State, lang)
|
||||
return etagResponse(created, sessionState, state, lang)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -188,12 +188,12 @@ func (g *Groupware) DeleteContact(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
logger := log.From(l)
|
||||
|
||||
deleted, sessionState, _, jerr := g.jmap.DeleteContactCard(accountId, []string{contactId}, req.session, req.ctx, logger, req.language())
|
||||
deleted, sessionState, state, _, jerr := g.jmap.DeleteContactCard(accountId, []string{contactId}, req.session, req.ctx, logger, req.language())
|
||||
if jerr != nil {
|
||||
return req.errorResponseFromJmap(jerr)
|
||||
}
|
||||
|
||||
for _, e := range deleted.NotDestroyed {
|
||||
for _, e := range deleted {
|
||||
desc := e.Description
|
||||
if desc != "" {
|
||||
return errorResponseWithSessionState(apiError(
|
||||
@@ -208,6 +208,6 @@ func (g *Groupware) DeleteContact(w http.ResponseWriter, r *http.Request) {
|
||||
), sessionState)
|
||||
}
|
||||
}
|
||||
return noContentResponseWithEtag(sessionState, deleted.State)
|
||||
return noContentResponseWithEtag(sessionState, state)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -79,12 +79,12 @@ func (g *Groupware) GetAllEmailsInMailbox(w http.ResponseWriter, r *http.Request
|
||||
|
||||
logger := log.From(req.logger.With().Str(HeaderSince, log.SafeString(since)).Str(logAccountId, log.SafeString(accountId)))
|
||||
|
||||
changes, sessionState, lang, jerr := g.jmap.GetMailboxChanges(accountId, req.session, req.ctx, logger, req.language(), mailboxId, since, true, g.maxBodyValueBytes, maxChanges)
|
||||
changes, sessionState, state, lang, jerr := g.jmap.GetMailboxChanges(accountId, req.session, req.ctx, logger, req.language(), mailboxId, since, true, g.maxBodyValueBytes, maxChanges)
|
||||
if jerr != nil {
|
||||
return req.errorResponseFromJmap(jerr)
|
||||
}
|
||||
|
||||
return etagResponse(changes, sessionState, changes.State, lang)
|
||||
return etagResponse(changes, sessionState, state, lang)
|
||||
})
|
||||
} else {
|
||||
g.respond(w, r, func(req Request) Response {
|
||||
@@ -116,7 +116,7 @@ func (g *Groupware) GetAllEmailsInMailbox(w http.ResponseWriter, r *http.Request
|
||||
|
||||
logger := log.From(l)
|
||||
|
||||
emails, sessionState, lang, jerr := g.jmap.GetAllEmailsInMailbox(accountId, req.session, req.ctx, logger, req.language(), mailboxId, offset, limit, false, true, g.maxBodyValueBytes, true)
|
||||
emails, sessionState, state, lang, jerr := g.jmap.GetAllEmailsInMailbox(accountId, req.session, req.ctx, logger, req.language(), mailboxId, offset, limit, false, true, g.maxBodyValueBytes, true)
|
||||
if jerr != nil {
|
||||
return req.errorResponseFromJmap(jerr)
|
||||
}
|
||||
@@ -133,7 +133,7 @@ func (g *Groupware) GetAllEmailsInMailbox(w http.ResponseWriter, r *http.Request
|
||||
Offset: emails.Offset,
|
||||
}
|
||||
|
||||
return etagResponse(safe, sessionState, emails.State, lang)
|
||||
return etagResponse(safe, sessionState, state, lang)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -164,7 +164,7 @@ func (g *Groupware) GetEmailsById(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
logger := log.From(req.logger.With().Str(logAccountId, log.SafeString(accountId)).Str("id", log.SafeString(id)).Str("accept", log.SafeString(accept)))
|
||||
|
||||
blobId, _, _, jerr := g.jmap.GetEmailBlobId(accountId, req.session, req.ctx, logger, req.language(), id)
|
||||
blobId, _, _, _, jerr := g.jmap.GetEmailBlobId(accountId, req.session, req.ctx, logger, req.language(), id)
|
||||
if jerr != nil {
|
||||
return req.apiErrorFromJmap(req.observeJmapError(jerr))
|
||||
}
|
||||
@@ -203,34 +203,34 @@ func (g *Groupware) GetEmailsById(w http.ResponseWriter, r *http.Request) {
|
||||
if len(ids) == 1 {
|
||||
logger := log.From(l.Str("id", log.SafeString(id)))
|
||||
|
||||
emails, sessionState, lang, jerr := g.jmap.GetEmails(accountId, req.session, req.ctx, logger, req.language(), ids, true, g.maxBodyValueBytes, markAsSeen, true)
|
||||
emails, sessionState, state, lang, jerr := g.jmap.GetEmails(accountId, req.session, req.ctx, logger, req.language(), ids, true, g.maxBodyValueBytes, markAsSeen, true)
|
||||
if jerr != nil {
|
||||
return req.errorResponseFromJmap(jerr)
|
||||
}
|
||||
if len(emails.Emails) < 1 {
|
||||
if len(emails) < 1 {
|
||||
return notFoundResponse(sessionState)
|
||||
} else {
|
||||
sanitized, err := req.sanitizeEmail(emails.Emails[0])
|
||||
sanitized, err := req.sanitizeEmail(emails[0])
|
||||
if err != nil {
|
||||
return errorResponseWithSessionState(err, sessionState)
|
||||
}
|
||||
return etagResponse(sanitized, sessionState, emails.State, lang)
|
||||
return etagResponse(sanitized, sessionState, state, lang)
|
||||
}
|
||||
} else {
|
||||
logger := log.From(l.Array("ids", log.SafeStringArray(ids)))
|
||||
|
||||
emails, sessionState, lang, jerr := g.jmap.GetEmails(accountId, req.session, req.ctx, logger, req.language(), ids, true, g.maxBodyValueBytes, markAsSeen, false)
|
||||
emails, sessionState, state, lang, jerr := g.jmap.GetEmails(accountId, req.session, req.ctx, logger, req.language(), ids, true, g.maxBodyValueBytes, markAsSeen, false)
|
||||
if jerr != nil {
|
||||
return req.errorResponseFromJmap(jerr)
|
||||
}
|
||||
if len(emails.Emails) < 1 {
|
||||
if len(emails) < 1 {
|
||||
return notFoundResponse(sessionState)
|
||||
} else {
|
||||
sanitized, err := req.sanitizeEmails(emails.Emails)
|
||||
sanitized, err := req.sanitizeEmails(emails)
|
||||
if err != nil {
|
||||
return errorResponseWithSessionState(err, sessionState)
|
||||
}
|
||||
return etagResponse(sanitized, sessionState, emails.State, lang)
|
||||
return etagResponse(sanitized, sessionState, state, lang)
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -269,18 +269,18 @@ func (g *Groupware) GetEmailAttachments(w http.ResponseWriter, r *http.Request)
|
||||
}
|
||||
l := req.logger.With().Str(logAccountId, log.SafeString(accountId))
|
||||
logger := log.From(l)
|
||||
emails, sessionState, lang, jerr := g.jmap.GetEmails(accountId, req.session, req.ctx, logger, req.language(), []string{id}, false, 0, false, false)
|
||||
emails, sessionState, state, lang, jerr := g.jmap.GetEmails(accountId, req.session, req.ctx, logger, req.language(), []string{id}, false, 0, false, false)
|
||||
if jerr != nil {
|
||||
return req.errorResponseFromJmap(jerr)
|
||||
}
|
||||
if len(emails.Emails) < 1 {
|
||||
if len(emails) < 1 {
|
||||
return notFoundResponse(sessionState)
|
||||
}
|
||||
email, err := req.sanitizeEmail(emails.Emails[0])
|
||||
email, err := req.sanitizeEmail(emails[0])
|
||||
if err != nil {
|
||||
return errorResponseWithSessionState(err, sessionState)
|
||||
}
|
||||
return etagResponse(email.Attachments, sessionState, emails.State, lang)
|
||||
return etagResponse(email.Attachments, sessionState, state, lang)
|
||||
})
|
||||
} else {
|
||||
g.stream(w, r, func(req Request, w http.ResponseWriter) *Error {
|
||||
@@ -297,15 +297,15 @@ func (g *Groupware) GetEmailAttachments(w http.ResponseWriter, r *http.Request)
|
||||
l = contextAppender(l)
|
||||
logger := log.From(l)
|
||||
|
||||
emails, _, lang, jerr := g.jmap.GetEmails(mailAccountId, req.session, req.ctx, logger, req.language(), []string{id}, false, 0, false, false)
|
||||
emails, _, _, lang, jerr := g.jmap.GetEmails(mailAccountId, req.session, req.ctx, logger, req.language(), []string{id}, false, 0, false, false)
|
||||
if jerr != nil {
|
||||
return req.apiErrorFromJmap(req.observeJmapError(jerr))
|
||||
}
|
||||
if len(emails.Emails) < 1 {
|
||||
if len(emails) < 1 {
|
||||
return nil
|
||||
}
|
||||
|
||||
email, err := req.sanitizeEmail(emails.Emails[0])
|
||||
email, err := req.sanitizeEmail(emails[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -381,12 +381,12 @@ func (g *Groupware) getEmailsSince(w http.ResponseWriter, r *http.Request, since
|
||||
|
||||
logger := log.From(l)
|
||||
|
||||
changes, sessionState, lang, jerr := g.jmap.GetEmailsSince(accountId, req.session, req.ctx, logger, req.language(), since, true, g.maxBodyValueBytes, maxChanges)
|
||||
changes, sessionState, state, lang, jerr := g.jmap.GetEmailsSince(accountId, req.session, req.ctx, logger, req.language(), since, true, g.maxBodyValueBytes, maxChanges)
|
||||
if jerr != nil {
|
||||
return req.errorResponseFromJmap(jerr)
|
||||
}
|
||||
|
||||
return etagResponse(changes, sessionState, changes.State, lang)
|
||||
return etagResponse(changes, sessionState, state, lang)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -609,7 +609,7 @@ func (g *Groupware) searchEmails(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
logger = log.From(logger.With().Str(logAccountId, log.SafeString(accountId)))
|
||||
|
||||
resultsByAccount, sessionState, lang, jerr := g.jmap.QueryEmailsWithSnippets([]string{accountId}, filter, req.session, req.ctx, logger, req.language(), offset, limit, fetchBodies, g.maxBodyValueBytes)
|
||||
resultsByAccount, sessionState, state, lang, jerr := g.jmap.QueryEmailsWithSnippets([]string{accountId}, filter, req.session, req.ctx, logger, req.language(), offset, limit, fetchBodies, g.maxBodyValueBytes)
|
||||
if jerr != nil {
|
||||
return req.errorResponseFromJmap(jerr)
|
||||
}
|
||||
@@ -645,7 +645,7 @@ func (g *Groupware) searchEmails(w http.ResponseWriter, r *http.Request) {
|
||||
Total: results.Total,
|
||||
Limit: results.Limit,
|
||||
QueryState: results.QueryState,
|
||||
}, sessionState, results.QueryState, lang)
|
||||
}, sessionState, state, lang)
|
||||
} else {
|
||||
return notFoundResponse(sessionState)
|
||||
}
|
||||
@@ -656,7 +656,7 @@ func (g *Groupware) searchEmails(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
logger = log.From(logger.With().Str(logAccountId, log.SafeString(accountId)))
|
||||
|
||||
resultsByAccountId, sessionState, lang, jerr := g.jmap.QueryEmailSnippets([]string{accountId}, filter, req.session, req.ctx, logger, req.language(), offset, limit)
|
||||
resultsByAccountId, sessionState, state, lang, jerr := g.jmap.QueryEmailSnippets([]string{accountId}, filter, req.session, req.ctx, logger, req.language(), offset, limit)
|
||||
if jerr != nil {
|
||||
return req.errorResponseFromJmap(jerr)
|
||||
}
|
||||
@@ -667,7 +667,7 @@ func (g *Groupware) searchEmails(w http.ResponseWriter, r *http.Request) {
|
||||
Total: results.Total,
|
||||
Limit: results.Limit,
|
||||
QueryState: results.QueryState,
|
||||
}, sessionState, results.QueryState, lang)
|
||||
}, sessionState, state, lang)
|
||||
} else {
|
||||
return notFoundResponse(sessionState)
|
||||
}
|
||||
@@ -722,7 +722,7 @@ func (g *Groupware) GetEmailsForAllAccounts(w http.ResponseWriter, r *http.Reque
|
||||
}
|
||||
|
||||
if makesSnippets {
|
||||
resultsByAccountId, sessionState, lang, jerr := g.jmap.QueryEmailsWithSnippets(allAccountIds, filter, req.session, req.ctx, logger, req.language(), offset, limit, fetchBodies, g.maxBodyValueBytes)
|
||||
resultsByAccountId, sessionState, state, lang, jerr := g.jmap.QueryEmailsWithSnippets(allAccountIds, filter, req.session, req.ctx, logger, req.language(), offset, limit, fetchBodies, g.maxBodyValueBytes)
|
||||
if jerr != nil {
|
||||
return req.errorResponseFromJmap(jerr)
|
||||
}
|
||||
@@ -771,7 +771,6 @@ func (g *Groupware) GetEmailsForAllAccounts(w http.ResponseWriter, r *http.Reque
|
||||
}
|
||||
|
||||
slices.SortFunc(flattened, func(a, b EmailWithSnippets) int { return a.ReceivedAt.Compare(b.ReceivedAt) })
|
||||
squashedQueryState := squashQueryState(resultsByAccountId, func(e jmap.EmailQueryWithSnippetsResult) jmap.State { return e.QueryState })
|
||||
|
||||
// TODO offset and limit over the aggregated results by account
|
||||
|
||||
@@ -779,10 +778,10 @@ func (g *Groupware) GetEmailsForAllAccounts(w http.ResponseWriter, r *http.Reque
|
||||
Results: flattened,
|
||||
Total: totalOverAllAccounts,
|
||||
Limit: limit,
|
||||
QueryState: squashedQueryState,
|
||||
}, sessionState, squashedQueryState, lang)
|
||||
QueryState: state,
|
||||
}, sessionState, state, lang)
|
||||
} else {
|
||||
resultsByAccountId, sessionState, lang, jerr := g.jmap.QueryEmails(allAccountIds, filter, req.session, req.ctx, logger, req.language(), offset, limit, fetchBodies, g.maxBodyValueBytes)
|
||||
resultsByAccountId, sessionState, state, lang, jerr := g.jmap.QueryEmails(allAccountIds, filter, req.session, req.ctx, logger, req.language(), offset, limit, fetchBodies, g.maxBodyValueBytes)
|
||||
if jerr != nil {
|
||||
return req.errorResponseFromJmap(jerr)
|
||||
}
|
||||
@@ -810,7 +809,6 @@ func (g *Groupware) GetEmailsForAllAccounts(w http.ResponseWriter, r *http.Reque
|
||||
}
|
||||
|
||||
slices.SortFunc(flattened, func(a, b jmap.Email) int { return a.ReceivedAt.Compare(b.ReceivedAt) })
|
||||
squashedQueryState := squashQueryState(resultsByAccountId, func(e jmap.EmailQueryResult) jmap.State { return e.QueryState })
|
||||
|
||||
// TODO offset and limit over the aggregated results by account
|
||||
|
||||
@@ -818,12 +816,12 @@ func (g *Groupware) GetEmailsForAllAccounts(w http.ResponseWriter, r *http.Reque
|
||||
Results: flattened,
|
||||
Total: totalOverAllAccounts,
|
||||
Limit: limit,
|
||||
QueryState: squashedQueryState,
|
||||
}, sessionState, squashedQueryState, lang)
|
||||
QueryState: state,
|
||||
}, sessionState, state, lang)
|
||||
}
|
||||
} else {
|
||||
if makesSnippets {
|
||||
resultsByAccountId, sessionState, lang, jerr := g.jmap.QueryEmailSnippets(allAccountIds, filter, req.session, req.ctx, logger, req.language(), offset, limit)
|
||||
resultsByAccountId, sessionState, state, lang, jerr := g.jmap.QueryEmailSnippets(allAccountIds, filter, req.session, req.ctx, logger, req.language(), offset, limit)
|
||||
if jerr != nil {
|
||||
return req.errorResponseFromJmap(jerr)
|
||||
}
|
||||
@@ -852,14 +850,12 @@ func (g *Groupware) GetEmailsForAllAccounts(w http.ResponseWriter, r *http.Reque
|
||||
|
||||
// TODO offset and limit over the aggregated results by account
|
||||
|
||||
squashedQueryState := squashQueryState(resultsByAccountId, func(e jmap.EmailSnippetQueryResult) jmap.State { return e.QueryState })
|
||||
|
||||
return etagResponse(EmailSearchSnippetsResults{
|
||||
Results: flattened,
|
||||
Total: totalOverAllAccounts,
|
||||
Limit: limit,
|
||||
QueryState: squashedQueryState,
|
||||
}, sessionState, squashedQueryState, lang)
|
||||
QueryState: state,
|
||||
}, sessionState, state, lang)
|
||||
} else {
|
||||
// TODO implement search without email bodies (only retrieve a few chosen properties?) + without snippets
|
||||
return notImplementesResponse()
|
||||
@@ -908,12 +904,12 @@ func (g *Groupware) CreateEmail(w http.ResponseWriter, r *http.Request) {
|
||||
BodyValues: body.BodyValues,
|
||||
}
|
||||
|
||||
created, sessionState, lang, jerr := g.jmap.CreateEmail(accountId, create, "", req.session, req.ctx, logger, req.language())
|
||||
created, sessionState, state, lang, jerr := g.jmap.CreateEmail(accountId, create, "", req.session, req.ctx, logger, req.language())
|
||||
if jerr != nil {
|
||||
return req.errorResponseFromJmap(jerr)
|
||||
}
|
||||
|
||||
return response(created.Email, sessionState, lang)
|
||||
return etagResponse(created, sessionState, state, lang)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -947,12 +943,12 @@ func (g *Groupware) ReplaceEmail(w http.ResponseWriter, r *http.Request) {
|
||||
BodyValues: body.BodyValues,
|
||||
}
|
||||
|
||||
created, sessionState, lang, jerr := g.jmap.CreateEmail(accountId, create, replaceId, req.session, req.ctx, logger, req.language())
|
||||
created, sessionState, state, lang, jerr := g.jmap.CreateEmail(accountId, create, replaceId, req.session, req.ctx, logger, req.language())
|
||||
if jerr != nil {
|
||||
return req.errorResponseFromJmap(jerr)
|
||||
}
|
||||
|
||||
return response(created.Email, sessionState, lang)
|
||||
return etagResponse(created, sessionState, state, lang)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -989,22 +985,22 @@ func (g *Groupware) UpdateEmail(w http.ResponseWriter, r *http.Request) {
|
||||
emailId: body,
|
||||
}
|
||||
|
||||
result, sessionState, lang, jerr := g.jmap.UpdateEmails(accountId, updates, req.session, req.ctx, logger, req.language())
|
||||
result, sessionState, state, lang, jerr := g.jmap.UpdateEmails(accountId, updates, req.session, req.ctx, logger, req.language())
|
||||
if jerr != nil {
|
||||
return req.errorResponseFromJmap(jerr)
|
||||
}
|
||||
|
||||
if result.Updated == nil {
|
||||
if result == nil {
|
||||
return errorResponse(apiError(req.errorId(), ErrorApiInconsistency, withTitle("API Inconsistency: Missing Email Update Response",
|
||||
"An internal API behaved unexpectedly: missing Email update response from JMAP endpoint")))
|
||||
}
|
||||
updatedEmail, ok := result.Updated[emailId]
|
||||
updatedEmail, ok := result[emailId]
|
||||
if !ok {
|
||||
return errorResponse(apiError(req.errorId(), ErrorApiInconsistency, withTitle("API Inconsistency: Wrong Email Update Response ID",
|
||||
"An internal API behaved unexpectedly: wrong Email update ID response from JMAP endpoint")))
|
||||
}
|
||||
|
||||
return response(updatedEmail, sessionState, lang)
|
||||
return etagResponse(updatedEmail, sessionState, state, lang)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1053,22 +1049,22 @@ func (g *Groupware) UpdateEmailKeywords(w http.ResponseWriter, r *http.Request)
|
||||
emailId: patch,
|
||||
}
|
||||
|
||||
result, sessionState, lang, jerr := g.jmap.UpdateEmails(accountId, patches, req.session, req.ctx, logger, req.language())
|
||||
result, sessionState, state, lang, jerr := g.jmap.UpdateEmails(accountId, patches, req.session, req.ctx, logger, req.language())
|
||||
if jerr != nil {
|
||||
return req.errorResponseFromJmap(jerr)
|
||||
}
|
||||
|
||||
if result.Updated == nil {
|
||||
if result == nil {
|
||||
return errorResponse(apiError(req.errorId(), ErrorApiInconsistency, withTitle("API Inconsistency: Missing Email Update Response",
|
||||
"An internal API behaved unexpectedly: missing Email update response from JMAP endpoint")))
|
||||
}
|
||||
updatedEmail, ok := result.Updated[emailId]
|
||||
updatedEmail, ok := result[emailId]
|
||||
if !ok {
|
||||
return errorResponse(apiError(req.errorId(), ErrorApiInconsistency, withTitle("API Inconsistency: Wrong Email Update Response ID",
|
||||
"An internal API behaved unexpectedly: wrong Email update ID response from JMAP endpoint")))
|
||||
}
|
||||
|
||||
return response(updatedEmail, sessionState, lang)
|
||||
return etagResponse(updatedEmail, sessionState, state, lang)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1114,25 +1110,25 @@ func (g *Groupware) AddEmailKeywords(w http.ResponseWriter, r *http.Request) {
|
||||
emailId: patch,
|
||||
}
|
||||
|
||||
result, sessionState, lang, jerr := g.jmap.UpdateEmails(accountId, patches, req.session, req.ctx, logger, req.language())
|
||||
result, sessionState, state, lang, jerr := g.jmap.UpdateEmails(accountId, patches, req.session, req.ctx, logger, req.language())
|
||||
if jerr != nil {
|
||||
return req.errorResponseFromJmap(jerr)
|
||||
}
|
||||
|
||||
if result.Updated == nil {
|
||||
if result == nil {
|
||||
return errorResponse(apiError(req.errorId(), ErrorApiInconsistency, withTitle("API Inconsistency: Missing Email Update Response",
|
||||
"An internal API behaved unexpectedly: missing Email update response from JMAP endpoint")))
|
||||
}
|
||||
updatedEmail, ok := result.Updated[emailId]
|
||||
updatedEmail, ok := result[emailId]
|
||||
if !ok {
|
||||
return errorResponse(apiError(req.errorId(), ErrorApiInconsistency, withTitle("API Inconsistency: Wrong Email Update Response ID",
|
||||
"An internal API behaved unexpectedly: wrong Email update ID response from JMAP endpoint")))
|
||||
}
|
||||
|
||||
if updatedEmail == nil {
|
||||
return noContentResponseWithEtag(sessionState, result.State)
|
||||
return noContentResponseWithEtag(sessionState, state)
|
||||
} else {
|
||||
return response(updatedEmail, sessionState, lang)
|
||||
return etagResponse(updatedEmail, sessionState, state, lang)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -1179,25 +1175,25 @@ func (g *Groupware) RemoveEmailKeywords(w http.ResponseWriter, r *http.Request)
|
||||
emailId: patch,
|
||||
}
|
||||
|
||||
result, sessionState, lang, jerr := g.jmap.UpdateEmails(accountId, patches, req.session, req.ctx, logger, req.language())
|
||||
result, sessionState, state, lang, jerr := g.jmap.UpdateEmails(accountId, patches, req.session, req.ctx, logger, req.language())
|
||||
if jerr != nil {
|
||||
return req.errorResponseFromJmap(jerr)
|
||||
}
|
||||
|
||||
if result.Updated == nil {
|
||||
if result == nil {
|
||||
return errorResponse(apiError(req.errorId(), ErrorApiInconsistency, withTitle("API Inconsistency: Missing Email Update Response",
|
||||
"An internal API behaved unexpectedly: missing Email update response from JMAP endpoint")))
|
||||
}
|
||||
updatedEmail, ok := result.Updated[emailId]
|
||||
updatedEmail, ok := result[emailId]
|
||||
if !ok {
|
||||
return errorResponse(apiError(req.errorId(), ErrorApiInconsistency, withTitle("API Inconsistency: Wrong Email Update Response ID",
|
||||
"An internal API behaved unexpectedly: wrong Email update ID response from JMAP endpoint")))
|
||||
}
|
||||
|
||||
if updatedEmail == nil {
|
||||
return noContentResponseWithEtag(sessionState, result.State)
|
||||
return noContentResponseWithEtag(sessionState, state)
|
||||
} else {
|
||||
return response(updatedEmail, sessionState, lang)
|
||||
return etagResponse(updatedEmail, sessionState, state, lang)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -1226,12 +1222,12 @@ func (g *Groupware) DeleteEmail(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
logger := log.From(l)
|
||||
|
||||
resp, sessionState, _, jerr := g.jmap.DeleteEmails(accountId, []string{emailId}, req.session, req.ctx, logger, req.language())
|
||||
resp, sessionState, state, _, jerr := g.jmap.DeleteEmails(accountId, []string{emailId}, req.session, req.ctx, logger, req.language())
|
||||
if jerr != nil {
|
||||
return req.errorResponseFromJmap(jerr)
|
||||
}
|
||||
|
||||
for _, e := range resp.NotDestroyed {
|
||||
for _, e := range resp {
|
||||
desc := e.Description
|
||||
if desc != "" {
|
||||
return errorResponseWithSessionState(apiError(
|
||||
@@ -1246,7 +1242,7 @@ func (g *Groupware) DeleteEmail(w http.ResponseWriter, r *http.Request) {
|
||||
), sessionState)
|
||||
}
|
||||
}
|
||||
return noContentResponseWithEtag(sessionState, resp.State)
|
||||
return noContentResponseWithEtag(sessionState, state)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1289,14 +1285,14 @@ func (g *Groupware) DeleteEmails(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
logger := log.From(l)
|
||||
|
||||
resp, sessionState, _, jerr := g.jmap.DeleteEmails(accountId, emailIds, req.session, req.ctx, logger, req.language())
|
||||
resp, sessionState, state, _, jerr := g.jmap.DeleteEmails(accountId, emailIds, req.session, req.ctx, logger, req.language())
|
||||
if jerr != nil {
|
||||
return req.errorResponseFromJmap(jerr)
|
||||
}
|
||||
|
||||
if len(resp.NotDestroyed) > 0 {
|
||||
meta := make(map[string]any, len(resp.NotDestroyed))
|
||||
for emailId, e := range resp.NotDestroyed {
|
||||
if len(resp) > 0 {
|
||||
meta := make(map[string]any, len(resp))
|
||||
for emailId, e := range resp {
|
||||
meta[emailId] = e.Description
|
||||
}
|
||||
return errorResponseWithSessionState(apiError(
|
||||
@@ -1305,7 +1301,7 @@ func (g *Groupware) DeleteEmails(w http.ResponseWriter, r *http.Request) {
|
||||
withMeta(meta),
|
||||
), sessionState)
|
||||
}
|
||||
return noContentResponseWithEtag(sessionState, resp.State)
|
||||
return noContentResponseWithEtag(sessionState, state)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1391,13 +1387,13 @@ func (g *Groupware) RelatedToEmail(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
reqId := req.GetRequestId()
|
||||
getEmailsBefore := time.Now()
|
||||
emails, sessionState, lang, jerr := g.jmap.GetEmails(accountId, req.session, req.ctx, logger, req.language(), []string{id}, true, g.maxBodyValueBytes, false, false)
|
||||
emails, sessionState, state, lang, jerr := g.jmap.GetEmails(accountId, req.session, req.ctx, logger, req.language(), []string{id}, true, g.maxBodyValueBytes, false, false)
|
||||
getEmailsDuration := time.Since(getEmailsBefore)
|
||||
if jerr != nil {
|
||||
return req.errorResponseFromJmap(jerr)
|
||||
}
|
||||
|
||||
if len(emails.Emails) < 1 {
|
||||
if len(emails) < 1 {
|
||||
req.observe(g.metrics.EmailByIdDuration.WithLabelValues(req.session.JmapEndpoint, metrics.Values.Result.NotFound), getEmailsDuration.Seconds())
|
||||
logger.Trace().Msg("failed to find any emails matching id") // the id is already in the log field
|
||||
return notFoundResponse(sessionState)
|
||||
@@ -1405,7 +1401,7 @@ func (g *Groupware) RelatedToEmail(w http.ResponseWriter, r *http.Request) {
|
||||
req.observe(g.metrics.EmailByIdDuration.WithLabelValues(req.session.JmapEndpoint, metrics.Values.Result.Found), getEmailsDuration.Seconds())
|
||||
}
|
||||
|
||||
email := emails.Emails[0]
|
||||
email := emails[0]
|
||||
|
||||
beacon := email.ReceivedAt // TODO configurable: either relative to when the email was received, or relative to now
|
||||
//beacon := time.Now()
|
||||
@@ -1416,7 +1412,7 @@ func (g *Groupware) RelatedToEmail(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
g.job(logger, RelationTypeSameSender, func(jobId uint64, l *log.Logger) {
|
||||
before := time.Now()
|
||||
resultsByAccountId, _, lang, jerr := g.jmap.QueryEmails([]string{accountId}, filter, req.session, bgctx, l, req.language(), 0, limit, false, g.maxBodyValueBytes)
|
||||
resultsByAccountId, _, _, lang, jerr := g.jmap.QueryEmails([]string{accountId}, filter, req.session, bgctx, l, req.language(), 0, limit, false, g.maxBodyValueBytes)
|
||||
if results, ok := resultsByAccountId[accountId]; ok {
|
||||
duration := time.Since(before)
|
||||
if jerr != nil {
|
||||
@@ -1437,7 +1433,7 @@ func (g *Groupware) RelatedToEmail(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
g.job(logger, RelationTypeSameThread, func(jobId uint64, l *log.Logger) {
|
||||
before := time.Now()
|
||||
emails, _, _, jerr := g.jmap.EmailsInThread(accountId, email.ThreadId, req.session, bgctx, l, req.language(), false, g.maxBodyValueBytes)
|
||||
emails, _, _, _, jerr := g.jmap.EmailsInThread(accountId, email.ThreadId, req.session, bgctx, l, req.language(), false, g.maxBodyValueBytes)
|
||||
duration := time.Since(before)
|
||||
if jerr != nil {
|
||||
req.observeJmapError(jerr)
|
||||
@@ -1461,7 +1457,7 @@ func (g *Groupware) RelatedToEmail(w http.ResponseWriter, r *http.Request) {
|
||||
return etagResponse(AboutEmailResponse{
|
||||
Email: sanitized,
|
||||
RequestId: reqId,
|
||||
}, sessionState, emails.State, lang)
|
||||
}, sessionState, state, lang)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1777,7 +1773,7 @@ func (g *Groupware) GetLatestEmailsSummaryForAllAccounts(w http.ResponseWriter,
|
||||
|
||||
logger := log.From(l)
|
||||
|
||||
emailsSummariesByAccount, sessionState, lang, jerr := g.jmap.QueryEmailSummaries(allAccountIds, req.session, req.ctx, logger, req.language(), filter, limit, true)
|
||||
emailsSummariesByAccount, sessionState, state, lang, jerr := g.jmap.QueryEmailSummaries(allAccountIds, req.session, req.ctx, logger, req.language(), filter, limit, true)
|
||||
if jerr != nil {
|
||||
return req.errorResponseFromJmap(jerr)
|
||||
}
|
||||
@@ -1803,12 +1799,12 @@ func (g *Groupware) GetLatestEmailsSummaryForAllAccounts(w http.ResponseWriter,
|
||||
summaries[i] = summarizeEmail(all[i].accountId, all[i].email)
|
||||
}
|
||||
|
||||
return response(EmailSummaries{
|
||||
return etagResponse(EmailSummaries{
|
||||
Emails: summaries,
|
||||
Total: total,
|
||||
Limit: limit,
|
||||
Offset: offset,
|
||||
}, sessionState, lang)
|
||||
}, sessionState, state, lang)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -15,9 +15,7 @@ import (
|
||||
// swagger:response GetIdentitiesResponse
|
||||
type SwaggerGetIdentitiesResponse struct {
|
||||
// in: body
|
||||
Body struct {
|
||||
*jmap.Identities
|
||||
}
|
||||
Body []jmap.Identity
|
||||
}
|
||||
|
||||
// swagger:route GET /groupware/accounts/{account}/identities identity identities
|
||||
@@ -36,11 +34,11 @@ func (g *Groupware) GetIdentities(w http.ResponseWriter, r *http.Request) {
|
||||
return errorResponse(err)
|
||||
}
|
||||
logger := log.From(req.logger.With().Str(logAccountId, accountId))
|
||||
res, sessionState, lang, jerr := g.jmap.GetAllIdentities(accountId, req.session, req.ctx, logger, req.language())
|
||||
res, sessionState, state, lang, jerr := g.jmap.GetAllIdentities(accountId, req.session, req.ctx, logger, req.language())
|
||||
if jerr != nil {
|
||||
return req.errorResponseFromJmap(jerr)
|
||||
}
|
||||
return etagResponse(res, sessionState, res.State, lang)
|
||||
return etagResponse(res, sessionState, state, lang)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -52,14 +50,14 @@ func (g *Groupware) GetIdentityById(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
id := chi.URLParam(r, UriParamIdentityId)
|
||||
logger := log.From(req.logger.With().Str(logAccountId, accountId).Str(logIdentityId, id))
|
||||
res, sessionState, lang, jerr := g.jmap.GetIdentities(accountId, req.session, req.ctx, logger, req.language(), []string{id})
|
||||
res, sessionState, state, lang, jerr := g.jmap.GetIdentities(accountId, req.session, req.ctx, logger, req.language(), []string{id})
|
||||
if jerr != nil {
|
||||
return req.errorResponseFromJmap(jerr)
|
||||
}
|
||||
if len(res.Identities) < 1 {
|
||||
if len(res) < 1 {
|
||||
return notFoundResponse(sessionState)
|
||||
}
|
||||
return etagResponse(res.Identities[0], sessionState, res.State, lang)
|
||||
return etagResponse(res[0], sessionState, state, lang)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -77,11 +75,11 @@ func (g *Groupware) AddIdentity(w http.ResponseWriter, r *http.Request) {
|
||||
return errorResponse(err)
|
||||
}
|
||||
|
||||
newState, sessionState, _, jerr := g.jmap.CreateIdentity(accountId, req.session, req.ctx, logger, req.language(), identity)
|
||||
created, sessionState, state, lang, jerr := g.jmap.CreateIdentity(accountId, req.session, req.ctx, logger, req.language(), identity)
|
||||
if jerr != nil {
|
||||
return req.errorResponseFromJmap(jerr)
|
||||
}
|
||||
return noContentResponseWithEtag(sessionState, newState)
|
||||
return etagResponse(created, sessionState, state, lang)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -99,11 +97,11 @@ func (g *Groupware) ModifyIdentity(w http.ResponseWriter, r *http.Request) {
|
||||
return errorResponse(err)
|
||||
}
|
||||
|
||||
newState, sessionState, _, jerr := g.jmap.UpdateIdentity(accountId, req.session, req.ctx, logger, req.language(), identity)
|
||||
updated, sessionState, state, lang, jerr := g.jmap.UpdateIdentity(accountId, req.session, req.ctx, logger, req.language(), identity)
|
||||
if jerr != nil {
|
||||
return req.errorResponseFromJmap(jerr)
|
||||
}
|
||||
return noContentResponseWithEtag(sessionState, newState)
|
||||
return etagResponse(updated, sessionState, state, lang)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -121,14 +119,14 @@ func (g *Groupware) DeleteIdentity(w http.ResponseWriter, r *http.Request) {
|
||||
return req.parameterErrorResponse(UriParamEmailId, fmt.Sprintf("Invalid value for path parameter '%v': '%s': %s", UriParamIdentityId, log.SafeString(id), "empty list of identity ids"))
|
||||
}
|
||||
|
||||
deletion, sessionState, _, jerr := g.jmap.DeleteIdentity(accountId, req.session, req.ctx, logger, req.language(), ids)
|
||||
deletion, sessionState, state, _, jerr := g.jmap.DeleteIdentity(accountId, req.session, req.ctx, logger, req.language(), ids)
|
||||
if jerr != nil {
|
||||
return req.errorResponseFromJmap(jerr)
|
||||
}
|
||||
|
||||
notDeletedIds := structs.Missing(ids, deletion.Destroyed)
|
||||
notDeletedIds := structs.Missing(ids, deletion)
|
||||
if len(notDeletedIds) == 0 {
|
||||
return noContentResponseWithEtag(sessionState, deletion.NewState)
|
||||
return noContentResponseWithEtag(sessionState, state)
|
||||
} else {
|
||||
logger.Error().Array("not-deleted", log.SafeStringArray(notDeletedIds)).Msgf("failed to delete %d identities", len(notDeletedIds))
|
||||
return errorResponseWithSessionState(req.apiError(&ErrorFailedToDeleteSomeIdentities,
|
||||
|
||||
@@ -162,18 +162,18 @@ func (g *Groupware) Index(w http.ResponseWriter, r *http.Request) {
|
||||
g.respond(w, r, func(req Request) Response {
|
||||
accountIds := structs.Keys(req.session.Accounts)
|
||||
|
||||
boot, sessionState, lang, err := g.jmap.GetBootstrap(accountIds, req.session, req.ctx, req.logger, req.language())
|
||||
boot, sessionState, state, lang, err := g.jmap.GetBootstrap(accountIds, req.session, req.ctx, req.logger, req.language())
|
||||
if err != nil {
|
||||
return req.errorResponseFromJmap(err)
|
||||
}
|
||||
|
||||
return response(IndexResponse{
|
||||
return etagResponse(IndexResponse{
|
||||
Version: Version,
|
||||
Capabilities: Capabilities,
|
||||
Limits: buildIndexLimits(req.session),
|
||||
Accounts: buildIndexAccounts(req.session, boot),
|
||||
PrimaryAccounts: buildIndexPrimaryAccounts(req.session),
|
||||
}, sessionState, lang)
|
||||
}, sessionState, state, lang)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -43,13 +43,13 @@ func (g *Groupware) GetMailbox(w http.ResponseWriter, r *http.Request) {
|
||||
return errorResponse(err)
|
||||
}
|
||||
|
||||
mailboxes, sessionState, lang, jerr := g.jmap.GetMailbox(accountId, req.session, req.ctx, req.logger, req.language(), []string{mailboxId})
|
||||
mailboxes, sessionState, state, lang, jerr := g.jmap.GetMailbox(accountId, req.session, req.ctx, req.logger, req.language(), []string{mailboxId})
|
||||
if jerr != nil {
|
||||
return req.errorResponseFromJmap(jerr)
|
||||
}
|
||||
|
||||
if len(mailboxes.Mailboxes) == 1 {
|
||||
return etagResponse(mailboxes.Mailboxes[0], sessionState, mailboxes.State, lang)
|
||||
return etagResponse(mailboxes.Mailboxes[0], sessionState, state, lang)
|
||||
} else {
|
||||
return notFoundResponse(sessionState)
|
||||
}
|
||||
@@ -125,23 +125,23 @@ func (g *Groupware) GetMailboxes(w http.ResponseWriter, r *http.Request) {
|
||||
logger := log.From(req.logger.With().Str(logAccountId, accountId))
|
||||
|
||||
if hasCriteria {
|
||||
mailboxesByAccountId, sessionState, lang, err := g.jmap.SearchMailboxes([]string{accountId}, req.session, req.ctx, logger, req.language(), filter)
|
||||
mailboxesByAccountId, sessionState, state, lang, err := g.jmap.SearchMailboxes([]string{accountId}, req.session, req.ctx, logger, req.language(), filter)
|
||||
if err != nil {
|
||||
return req.errorResponseFromJmap(err)
|
||||
}
|
||||
|
||||
if mailboxes, ok := mailboxesByAccountId[accountId]; ok {
|
||||
return etagResponse(sortMailboxSlice(mailboxes.Mailboxes), sessionState, mailboxes.State, lang)
|
||||
return etagResponse(sortMailboxSlice(mailboxes), sessionState, state, lang)
|
||||
} else {
|
||||
return notFoundResponse(sessionState)
|
||||
}
|
||||
} else {
|
||||
mailboxesByAccountId, sessionState, lang, err := g.jmap.GetAllMailboxes([]string{accountId}, req.session, req.ctx, logger, req.language())
|
||||
mailboxesByAccountId, sessionState, state, lang, err := g.jmap.GetAllMailboxes([]string{accountId}, req.session, req.ctx, logger, req.language())
|
||||
if err != nil {
|
||||
return req.errorResponseFromJmap(err)
|
||||
}
|
||||
if mailboxes, ok := mailboxesByAccountId[accountId]; ok {
|
||||
return etagResponse(sortMailboxSlice(mailboxes.Mailboxes), sessionState, mailboxes.State, lang)
|
||||
return etagResponse(sortMailboxSlice(mailboxes), sessionState, state, lang)
|
||||
} else {
|
||||
return notFoundResponse(sessionState)
|
||||
}
|
||||
@@ -193,17 +193,17 @@ func (g *Groupware) GetMailboxesForAllAccounts(w http.ResponseWriter, r *http.Re
|
||||
logger := log.From(req.logger.With().Array(logAccountId, log.SafeStringArray(accountIds)))
|
||||
|
||||
if hasCriteria {
|
||||
mailboxesByAccountId, sessionState, lang, err := g.jmap.SearchMailboxes(accountIds, req.session, req.ctx, logger, req.language(), filter)
|
||||
mailboxesByAccountId, sessionState, state, lang, err := g.jmap.SearchMailboxes(accountIds, req.session, req.ctx, logger, req.language(), filter)
|
||||
if err != nil {
|
||||
return req.errorResponseFromJmap(err)
|
||||
}
|
||||
return response(sortMailboxesMap(mailboxesByAccountId), sessionState, lang)
|
||||
return etagResponse(sortMailboxesMap(mailboxesByAccountId), sessionState, state, lang)
|
||||
} else {
|
||||
mailboxesByAccountId, sessionState, lang, err := g.jmap.GetAllMailboxes(accountIds, req.session, req.ctx, logger, req.language())
|
||||
mailboxesByAccountId, sessionState, state, lang, err := g.jmap.GetAllMailboxes(accountIds, req.session, req.ctx, logger, req.language())
|
||||
if err != nil {
|
||||
return req.errorResponseFromJmap(err)
|
||||
}
|
||||
return response(sortMailboxesMap(mailboxesByAccountId), sessionState, lang)
|
||||
return etagResponse(sortMailboxesMap(mailboxesByAccountId), sessionState, state, lang)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -221,11 +221,11 @@ func (g *Groupware) GetMailboxByRoleForAllAccounts(w http.ResponseWriter, r *htt
|
||||
Role: role,
|
||||
}
|
||||
|
||||
mailboxesByAccountId, sessionState, lang, err := g.jmap.SearchMailboxes(accountIds, req.session, req.ctx, logger, req.language(), filter)
|
||||
mailboxesByAccountId, sessionState, state, lang, err := g.jmap.SearchMailboxes(accountIds, req.session, req.ctx, logger, req.language(), filter)
|
||||
if err != nil {
|
||||
return req.errorResponseFromJmap(err)
|
||||
}
|
||||
return response(sortMailboxesMap(mailboxesByAccountId), sessionState, lang)
|
||||
return etagResponse(sortMailboxesMap(mailboxesByAccountId), sessionState, state, lang)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -267,12 +267,12 @@ func (g *Groupware) GetMailboxChanges(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
logger := log.From(l)
|
||||
|
||||
changes, sessionState, lang, jerr := g.jmap.GetMailboxChanges(accountId, req.session, req.ctx, logger, req.language(), mailboxId, sinceState, true, g.maxBodyValueBytes, maxChanges)
|
||||
changes, sessionState, state, lang, jerr := g.jmap.GetMailboxChanges(accountId, req.session, req.ctx, logger, req.language(), mailboxId, sinceState, true, g.maxBodyValueBytes, maxChanges)
|
||||
if jerr != nil {
|
||||
return req.errorResponseFromJmap(jerr)
|
||||
}
|
||||
|
||||
return etagResponse(changes, sessionState, changes.State, lang)
|
||||
return etagResponse(changes, sessionState, state, lang)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -320,12 +320,12 @@ func (g *Groupware) GetMailboxChangesForAllAccounts(w http.ResponseWriter, r *ht
|
||||
|
||||
logger := log.From(l)
|
||||
|
||||
changesByAccountId, sessionState, lang, jerr := g.jmap.GetMailboxChangesForMultipleAccounts(allAccountIds, req.session, req.ctx, logger, req.language(), sinceStateMap, true, g.maxBodyValueBytes, maxChanges)
|
||||
changesByAccountId, sessionState, state, lang, jerr := g.jmap.GetMailboxChangesForMultipleAccounts(allAccountIds, req.session, req.ctx, logger, req.language(), sinceStateMap, true, g.maxBodyValueBytes, maxChanges)
|
||||
if jerr != nil {
|
||||
return req.errorResponseFromJmap(jerr)
|
||||
}
|
||||
|
||||
return response(changesByAccountId, sessionState, lang)
|
||||
return etagResponse(changesByAccountId, sessionState, state, lang)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -336,12 +336,92 @@ func (g *Groupware) GetMailboxRoles(w http.ResponseWriter, r *http.Request) {
|
||||
l.Array(logAccountId, log.SafeStringArray(allAccountIds))
|
||||
logger := log.From(l)
|
||||
|
||||
rolesByAccountId, sessionState, lang, jerr := g.jmap.GetMailboxRolesForMultipleAccounts(allAccountIds, req.session, req.ctx, logger, req.language())
|
||||
rolesByAccountId, sessionState, state, lang, jerr := g.jmap.GetMailboxRolesForMultipleAccounts(allAccountIds, req.session, req.ctx, logger, req.language())
|
||||
if jerr != nil {
|
||||
return req.errorResponseFromJmap(jerr)
|
||||
}
|
||||
|
||||
return response(rolesByAccountId, sessionState, lang)
|
||||
return etagResponse(rolesByAccountId, sessionState, state, lang)
|
||||
})
|
||||
}
|
||||
|
||||
func (g *Groupware) UpdateMailbox(w http.ResponseWriter, r *http.Request) {
|
||||
mailboxId := chi.URLParam(r, UriParamMailboxId)
|
||||
|
||||
g.respond(w, r, func(req Request) Response {
|
||||
l := req.logger.With().Str(UriParamMailboxId, log.SafeString(mailboxId))
|
||||
|
||||
accountId, err := req.GetAccountIdForMail()
|
||||
if err != nil {
|
||||
return errorResponse(err)
|
||||
}
|
||||
l = l.Str(logAccountId, accountId)
|
||||
|
||||
var body jmap.MailboxChange
|
||||
err = req.body(&body)
|
||||
if err != nil {
|
||||
return errorResponse(err)
|
||||
}
|
||||
logger := log.From(l)
|
||||
|
||||
updated, sessionState, state, lang, jerr := g.jmap.UpdateMailbox(accountId, req.session, req.ctx, logger, req.language(), mailboxId, "", body)
|
||||
if jerr != nil {
|
||||
return req.errorResponseFromJmap(jerr)
|
||||
}
|
||||
|
||||
return etagResponse(updated, sessionState, state, lang)
|
||||
})
|
||||
}
|
||||
|
||||
func (g *Groupware) CreateMailbox(w http.ResponseWriter, r *http.Request) {
|
||||
g.respond(w, r, func(req Request) Response {
|
||||
l := req.logger.With()
|
||||
accountId, err := req.GetAccountIdForMail()
|
||||
if err != nil {
|
||||
return errorResponse(err)
|
||||
}
|
||||
l = l.Str(logAccountId, accountId)
|
||||
|
||||
var body jmap.MailboxChange
|
||||
err = req.body(&body)
|
||||
if err != nil {
|
||||
return errorResponse(err)
|
||||
}
|
||||
logger := log.From(l)
|
||||
|
||||
created, sessionState, state, lang, jerr := g.jmap.CreateMailbox(accountId, req.session, req.ctx, logger, req.language(), "", body)
|
||||
if jerr != nil {
|
||||
return req.errorResponseFromJmap(jerr)
|
||||
}
|
||||
|
||||
return etagResponse(created, sessionState, state, lang)
|
||||
})
|
||||
}
|
||||
|
||||
func (g *Groupware) DeleteMailbox(w http.ResponseWriter, r *http.Request) {
|
||||
mailboxId := chi.URLParam(r, UriParamMailboxId)
|
||||
mailboxIds := strings.Split(mailboxId, ",")
|
||||
|
||||
g.respond(w, r, func(req Request) Response {
|
||||
if len(mailboxIds) < 1 {
|
||||
return noContentResponse(req.session.State)
|
||||
}
|
||||
|
||||
l := req.logger.With()
|
||||
accountId, err := req.GetAccountIdForMail()
|
||||
if err != nil {
|
||||
return errorResponse(err)
|
||||
}
|
||||
l = l.Str(logAccountId, accountId)
|
||||
l = l.Array(UriParamMailboxId, log.SafeStringArray(mailboxIds))
|
||||
logger := log.From(l)
|
||||
|
||||
deleted, sessionState, state, lang, jerr := g.jmap.DeleteMailboxes(accountId, req.session, req.ctx, logger, req.language(), "", mailboxIds)
|
||||
if jerr != nil {
|
||||
return req.errorResponseFromJmap(jerr)
|
||||
}
|
||||
|
||||
return etagResponse(deleted, sessionState, state, lang)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -360,13 +440,13 @@ func scoreMailbox(m jmap.Mailbox) int {
|
||||
return 1000
|
||||
}
|
||||
|
||||
func sortMailboxesMap[K comparable](mailboxesByAccountId map[K]jmap.Mailboxes) map[K]jmap.Mailboxes {
|
||||
sortedByAccountId := make(map[K]jmap.Mailboxes, len(mailboxesByAccountId))
|
||||
func sortMailboxesMap[K comparable](mailboxesByAccountId map[K][]jmap.Mailbox) map[K][]jmap.Mailbox {
|
||||
sortedByAccountId := make(map[K][]jmap.Mailbox, len(mailboxesByAccountId))
|
||||
for accountId, unsorted := range mailboxesByAccountId {
|
||||
mailboxes := make([]jmap.Mailbox, len(unsorted.Mailboxes))
|
||||
copy(mailboxes, unsorted.Mailboxes)
|
||||
mailboxes := make([]jmap.Mailbox, len(unsorted))
|
||||
copy(mailboxes, unsorted)
|
||||
slices.SortFunc(mailboxes, compareMailboxes)
|
||||
sortedByAccountId[accountId] = jmap.Mailboxes{Mailboxes: mailboxes, State: unsorted.State}
|
||||
sortedByAccountId[accountId] = mailboxes
|
||||
}
|
||||
return sortedByAccountId
|
||||
}
|
||||
@@ -385,8 +465,14 @@ func compareMailboxes(a, b jmap.Mailbox) int {
|
||||
// The number MUST be an integer in the range 0 <= sortOrder < 2^31.
|
||||
// A Mailbox with a lower order should be displayed before a Mailbox with a higher order
|
||||
// (that has the same parent) in any Mailbox listing in the client’s UI.
|
||||
sa := a.SortOrder
|
||||
sb := b.SortOrder
|
||||
sa := 0
|
||||
if a.SortOrder != nil {
|
||||
sa = *a.SortOrder
|
||||
}
|
||||
sb := 0
|
||||
if b.SortOrder != nil {
|
||||
sb = *b.SortOrder
|
||||
}
|
||||
r := sa - sb
|
||||
if r != 0 {
|
||||
return r
|
||||
|
||||
@@ -31,12 +31,12 @@ func (g *Groupware) GetQuota(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
logger := log.From(req.logger.With().Str(logAccountId, accountId))
|
||||
|
||||
res, sessionState, lang, jerr := g.jmap.GetQuotas([]string{accountId}, req.session, req.ctx, logger, req.language())
|
||||
res, sessionState, state, lang, jerr := g.jmap.GetQuotas([]string{accountId}, req.session, req.ctx, logger, req.language())
|
||||
if jerr != nil {
|
||||
return req.errorResponseFromJmap(jerr)
|
||||
}
|
||||
for _, v := range res {
|
||||
return etagResponse(v.List, sessionState, v.State, lang)
|
||||
return etagResponse(v.List, sessionState, state, lang)
|
||||
}
|
||||
return notFoundResponse(sessionState)
|
||||
})
|
||||
@@ -70,7 +70,7 @@ func (g *Groupware) GetQuotaForAllAccounts(w http.ResponseWriter, r *http.Reques
|
||||
}
|
||||
logger := log.From(req.logger.With().Array(logAccountId, log.SafeStringArray(accountIds)))
|
||||
|
||||
res, sessionState, lang, jerr := g.jmap.GetQuotas(accountIds, req.session, req.ctx, logger, req.language())
|
||||
res, sessionState, state, lang, jerr := g.jmap.GetQuotas(accountIds, req.session, req.ctx, logger, req.language())
|
||||
if jerr != nil {
|
||||
return req.errorResponseFromJmap(jerr)
|
||||
}
|
||||
@@ -82,6 +82,6 @@ func (g *Groupware) GetQuotaForAllAccounts(w http.ResponseWriter, r *http.Reques
|
||||
Quotas: accountQuotas.List,
|
||||
}
|
||||
}
|
||||
return response(result, sessionState, lang)
|
||||
return etagResponse(result, sessionState, state, lang)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -37,11 +37,11 @@ func (g *Groupware) GetVacation(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
logger := log.From(req.logger.With().Str(logAccountId, accountId))
|
||||
|
||||
res, sessionState, lang, jerr := g.jmap.GetVacationResponse(accountId, req.session, req.ctx, logger, req.language())
|
||||
res, sessionState, state, lang, jerr := g.jmap.GetVacationResponse(accountId, req.session, req.ctx, logger, req.language())
|
||||
if jerr != nil {
|
||||
return req.errorResponseFromJmap(jerr)
|
||||
}
|
||||
return etagResponse(res, sessionState, res.State, lang)
|
||||
return etagResponse(res, sessionState, state, lang)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -50,7 +50,7 @@ func (g *Groupware) GetVacation(w http.ResponseWriter, r *http.Request) {
|
||||
type SwaggerSetVacationResponse200 struct {
|
||||
// in: body
|
||||
Body struct {
|
||||
*jmap.VacationResponseChange
|
||||
*jmap.VacationResponse
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,11 +79,11 @@ func (g *Groupware) SetVacation(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
logger := log.From(req.logger.With().Str(logAccountId, accountId))
|
||||
|
||||
res, sessionState, lang, jerr := g.jmap.SetVacationResponse(accountId, body, req.session, req.ctx, logger, req.language())
|
||||
res, sessionState, state, lang, jerr := g.jmap.SetVacationResponse(accountId, body, req.session, req.ctx, logger, req.language())
|
||||
if jerr != nil {
|
||||
return req.errorResponseFromJmap(jerr)
|
||||
}
|
||||
|
||||
return etagResponse(res, sessionState, res.ResponseState, lang)
|
||||
return etagResponse(res, sessionState, state, lang)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ var (
|
||||
|
||||
const (
|
||||
UriParamAccountId = "accountid"
|
||||
UriParamMailboxId = "mailbox"
|
||||
UriParamMailboxId = "mailboxid"
|
||||
UriParamEmailId = "emailid"
|
||||
UriParamIdentityId = "identityid"
|
||||
UriParamBlobId = "blobid"
|
||||
@@ -90,9 +90,12 @@ func (g *Groupware) Route(r chi.Router) {
|
||||
r.Get("/quota", g.GetQuota)
|
||||
r.Route("/mailboxes", func(r chi.Router) {
|
||||
r.Get("/", g.GetMailboxes) // ?name=&role=&subcribed=
|
||||
r.Get("/{mailbox}", g.GetMailbox)
|
||||
r.Get("/{mailbox}/emails", g.GetAllEmailsInMailbox)
|
||||
r.Get("/{mailbox}/changes", g.GetMailboxChanges)
|
||||
r.Get("/{mailboxid}", g.GetMailbox)
|
||||
r.Get("/{mailboxid}/emails", g.GetAllEmailsInMailbox)
|
||||
r.Get("/{mailboxid}/changes", g.GetMailboxChanges)
|
||||
r.Post("/", g.CreateMailbox)
|
||||
r.Patch("/{mailboxid}", g.UpdateMailbox)
|
||||
r.Delete("/{mailboxid}", g.DeleteMailbox)
|
||||
})
|
||||
r.Route("/emails", func(r chi.Router) {
|
||||
r.Get("/", g.GetEmails) // ?fetchemails=true&fetchbodies=true&text=&subject=&body=&keyword=&keyword=&...
|
||||
|
||||
@@ -48,13 +48,14 @@ func TestSanitizeEmail(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestSortMailboxes(t *testing.T) {
|
||||
o := -10
|
||||
mailboxes := []jmap.Mailbox{
|
||||
{Id: "a", Name: "Other"},
|
||||
{Id: "b", Role: jmap.JmapMailboxRoleSent, Name: "Sent"},
|
||||
{Id: "c", Name: "Zebras"},
|
||||
{Id: "d", Role: jmap.JmapMailboxRoleInbox, Name: "Inbox"},
|
||||
{Id: "e", Name: "Appraisal"},
|
||||
{Id: "f", Name: "Zealots", SortOrder: -10},
|
||||
{Id: "f", Name: "Zealots", SortOrder: &o},
|
||||
}
|
||||
slices.SortFunc(mailboxes, compareMailboxes)
|
||||
names := structs.Map(mailboxes, func(m jmap.Mailbox) string { return m.Name })
|
||||
|
||||
Reference in New Issue
Block a user