mirror of
https://github.com/opencloud-eu/opencloud.git
synced 2026-01-28 16:01:18 -05:00
groupware: add /bootstrap
* add a GET /accounts/{a}/boostrap URI that delivers the same as GET /
but also mailboxes for a given account, in case the UI remembers the
last used account identifier, to avoid an additional roundtrip
* streamline the use of simpleError()
* add logging of errors at the calling site
* add logging of evictions of Sessions from the cache
* change default Session cache TTL to 5min instead of 30sec
This commit is contained in:
@@ -26,18 +26,21 @@ func (j *Client) GetBlob(accountId string, session *Session, ctx context.Context
|
||||
}, "0"),
|
||||
)
|
||||
if err != nil {
|
||||
return BlobResponse{}, SimpleError{code: JmapErrorInvalidJmapRequestPayload, err: err}
|
||||
logger.Error().Err(err)
|
||||
return BlobResponse{}, simpleError(err, JmapErrorInvalidJmapRequestPayload)
|
||||
}
|
||||
|
||||
return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, func(body *Response) (BlobResponse, Error) {
|
||||
var response BlobGetResponse
|
||||
err = retrieveResponseMatchParameters(body, CommandBlobGet, "0", &response)
|
||||
if err != nil {
|
||||
return BlobResponse{}, SimpleError{code: JmapErrorInvalidJmapResponsePayload, err: err}
|
||||
logger.Error().Err(err)
|
||||
return BlobResponse{}, simpleError(err, JmapErrorInvalidJmapResponsePayload)
|
||||
}
|
||||
|
||||
if len(response.List) != 1 {
|
||||
return BlobResponse{}, SimpleError{code: JmapErrorInvalidJmapResponsePayload, err: err}
|
||||
logger.Error().Msgf("%T.List has %v entries instead of 1", response, len(response.List))
|
||||
return BlobResponse{}, simpleError(err, JmapErrorInvalidJmapResponsePayload)
|
||||
}
|
||||
get := response.List[0]
|
||||
return BlobResponse{Blob: &get, State: response.State, SessionState: body.SessionState}, nil
|
||||
@@ -104,32 +107,38 @@ func (j *Client) UploadBlob(accountId string, session *Session, ctx context.Cont
|
||||
invocation(CommandBlobGet, getHash, "1"),
|
||||
)
|
||||
if err != nil {
|
||||
return UploadedBlob{}, SimpleError{code: JmapErrorInvalidJmapRequestPayload, err: err}
|
||||
logger.Error().Err(err)
|
||||
return UploadedBlob{}, simpleError(err, JmapErrorInvalidJmapRequestPayload)
|
||||
}
|
||||
|
||||
return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, func(body *Response) (UploadedBlob, Error) {
|
||||
var uploadResponse BlobUploadResponse
|
||||
err = retrieveResponseMatchParameters(body, CommandBlobUpload, "0", &uploadResponse)
|
||||
if err != nil {
|
||||
return UploadedBlob{}, SimpleError{code: JmapErrorInvalidJmapResponsePayload, err: err}
|
||||
logger.Error().Err(err)
|
||||
return UploadedBlob{}, simpleError(err, JmapErrorInvalidJmapResponsePayload)
|
||||
}
|
||||
|
||||
var getResponse BlobGetResponse
|
||||
err = retrieveResponseMatchParameters(body, CommandBlobGet, "1", &getResponse)
|
||||
if err != nil {
|
||||
return UploadedBlob{}, SimpleError{code: JmapErrorInvalidJmapResponsePayload, err: err}
|
||||
logger.Error().Err(err)
|
||||
return UploadedBlob{}, simpleError(err, JmapErrorInvalidJmapResponsePayload)
|
||||
}
|
||||
|
||||
if len(uploadResponse.Created) != 1 {
|
||||
return UploadedBlob{}, SimpleError{code: JmapErrorInvalidJmapResponsePayload, err: err}
|
||||
logger.Error().Msgf("%T.Created has %v entries instead of 1", uploadResponse, len(uploadResponse.Created))
|
||||
return UploadedBlob{}, simpleError(err, JmapErrorInvalidJmapResponsePayload)
|
||||
}
|
||||
upload, ok := uploadResponse.Created["0"]
|
||||
if !ok {
|
||||
return UploadedBlob{}, SimpleError{code: JmapErrorInvalidJmapResponsePayload, err: err}
|
||||
logger.Error().Msgf("%T.Created has no item '0'", uploadResponse)
|
||||
return UploadedBlob{}, simpleError(err, JmapErrorInvalidJmapResponsePayload)
|
||||
}
|
||||
|
||||
if len(getResponse.List) != 1 {
|
||||
return UploadedBlob{}, SimpleError{code: JmapErrorInvalidJmapResponsePayload, err: err}
|
||||
logger.Error().Msgf("%T.List has %v entries instead of 1", getResponse, len(getResponse.List))
|
||||
return UploadedBlob{}, simpleError(err, JmapErrorInvalidJmapResponsePayload)
|
||||
}
|
||||
get := getResponse.List[0]
|
||||
|
||||
|
||||
@@ -42,13 +42,15 @@ func (j *Client) GetEmails(accountId string, session *Session, ctx context.Conte
|
||||
|
||||
cmd, err := request(invocation(CommandEmailGet, get, "0"))
|
||||
if err != nil {
|
||||
return Emails{}, SimpleError{code: JmapErrorInvalidJmapRequestPayload, err: err}
|
||||
logger.Error().Err(err)
|
||||
return Emails{}, simpleError(err, JmapErrorInvalidJmapRequestPayload)
|
||||
}
|
||||
return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, func(body *Response) (Emails, Error) {
|
||||
var response EmailGetResponse
|
||||
err = retrieveResponseMatchParameters(body, CommandEmailGet, "0", &response)
|
||||
if err != nil {
|
||||
return Emails{}, SimpleError{code: JmapErrorInvalidJmapResponsePayload, err: err}
|
||||
logger.Error().Err(err)
|
||||
return Emails{}, simpleError(err, JmapErrorInvalidJmapResponsePayload)
|
||||
}
|
||||
return Emails{Emails: response.List, State: response.State, SessionState: body.SessionState}, nil
|
||||
})
|
||||
@@ -88,19 +90,22 @@ func (j *Client) GetAllEmails(accountId string, session *Session, ctx context.Co
|
||||
invocation(CommandEmailGet, get, "1"),
|
||||
)
|
||||
if err != nil {
|
||||
return Emails{}, SimpleError{code: JmapErrorInvalidJmapRequestPayload, err: err}
|
||||
logger.Error().Err(err)
|
||||
return Emails{}, simpleError(err, JmapErrorInvalidJmapRequestPayload)
|
||||
}
|
||||
|
||||
return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, func(body *Response) (Emails, Error) {
|
||||
var queryResponse EmailQueryResponse
|
||||
err = retrieveResponseMatchParameters(body, CommandEmailQuery, "0", &queryResponse)
|
||||
if err != nil {
|
||||
return Emails{}, SimpleError{code: JmapErrorInvalidJmapResponsePayload, err: err}
|
||||
logger.Error().Err(err)
|
||||
return Emails{}, simpleError(err, JmapErrorInvalidJmapResponsePayload)
|
||||
}
|
||||
var getResponse EmailGetResponse
|
||||
err = retrieveResponseMatchParameters(body, CommandEmailGet, "1", &getResponse)
|
||||
if err != nil {
|
||||
return Emails{}, SimpleError{code: JmapErrorInvalidJmapResponsePayload, err: err}
|
||||
logger.Error().Err(err)
|
||||
return Emails{}, simpleError(err, JmapErrorInvalidJmapResponsePayload)
|
||||
}
|
||||
|
||||
return Emails{
|
||||
@@ -161,26 +166,30 @@ func (j *Client) GetEmailsInMailboxSince(accountId string, session *Session, ctx
|
||||
invocation(CommandEmailGet, getUpdated, "2"),
|
||||
)
|
||||
if err != nil {
|
||||
return EmailsSince{}, SimpleError{code: JmapErrorInvalidJmapRequestPayload, err: err}
|
||||
logger.Error().Err(err)
|
||||
return EmailsSince{}, simpleError(err, JmapErrorInvalidJmapRequestPayload)
|
||||
}
|
||||
|
||||
return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, func(body *Response) (EmailsSince, Error) {
|
||||
var mailboxResponse MailboxChangesResponse
|
||||
err = retrieveResponseMatchParameters(body, CommandMailboxChanges, "0", &mailboxResponse)
|
||||
if err != nil {
|
||||
return EmailsSince{}, SimpleError{code: JmapErrorInvalidJmapResponsePayload, err: err}
|
||||
logger.Error().Err(err)
|
||||
return EmailsSince{}, simpleError(err, JmapErrorInvalidJmapResponsePayload)
|
||||
}
|
||||
|
||||
var createdResponse EmailGetResponse
|
||||
err = retrieveResponseMatchParameters(body, CommandEmailGet, "1", &createdResponse)
|
||||
if err != nil {
|
||||
return EmailsSince{}, SimpleError{code: JmapErrorInvalidJmapResponsePayload, err: err}
|
||||
logger.Error().Err(err)
|
||||
return EmailsSince{}, simpleError(err, JmapErrorInvalidJmapResponsePayload)
|
||||
}
|
||||
|
||||
var updatedResponse EmailGetResponse
|
||||
err = retrieveResponseMatchParameters(body, CommandEmailGet, "2", &updatedResponse)
|
||||
if err != nil {
|
||||
return EmailsSince{}, SimpleError{code: JmapErrorInvalidJmapResponsePayload, err: err}
|
||||
logger.Error().Err(err)
|
||||
return EmailsSince{}, simpleError(err, JmapErrorInvalidJmapResponsePayload)
|
||||
}
|
||||
|
||||
return EmailsSince{
|
||||
@@ -232,26 +241,29 @@ func (j *Client) GetEmailsSince(accountId string, session *Session, ctx context.
|
||||
invocation(CommandEmailGet, getUpdated, "2"),
|
||||
)
|
||||
if err != nil {
|
||||
return EmailsSince{}, SimpleError{code: JmapErrorInvalidJmapRequestPayload, err: err}
|
||||
return EmailsSince{}, simpleError(err, JmapErrorInvalidJmapRequestPayload)
|
||||
}
|
||||
|
||||
return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, func(body *Response) (EmailsSince, Error) {
|
||||
var changesResponse EmailChangesResponse
|
||||
err = retrieveResponseMatchParameters(body, CommandEmailChanges, "0", &changesResponse)
|
||||
if err != nil {
|
||||
return EmailsSince{}, SimpleError{code: JmapErrorInvalidJmapResponsePayload, err: err}
|
||||
logger.Error().Err(err)
|
||||
return EmailsSince{}, simpleError(err, JmapErrorInvalidJmapResponsePayload)
|
||||
}
|
||||
|
||||
var createdResponse EmailGetResponse
|
||||
err = retrieveResponseMatchParameters(body, CommandEmailGet, "1", &createdResponse)
|
||||
if err != nil {
|
||||
return EmailsSince{}, SimpleError{code: JmapErrorInvalidJmapResponsePayload, err: err}
|
||||
logger.Error().Err(err)
|
||||
return EmailsSince{}, simpleError(err, JmapErrorInvalidJmapResponsePayload)
|
||||
}
|
||||
|
||||
var updatedResponse EmailGetResponse
|
||||
err = retrieveResponseMatchParameters(body, CommandEmailGet, "2", &updatedResponse)
|
||||
if err != nil {
|
||||
return EmailsSince{}, SimpleError{code: JmapErrorInvalidJmapResponsePayload, err: err}
|
||||
logger.Error().Err(err)
|
||||
return EmailsSince{}, simpleError(err, JmapErrorInvalidJmapResponsePayload)
|
||||
}
|
||||
|
||||
return EmailsSince{
|
||||
@@ -311,20 +323,23 @@ func (j *Client) QueryEmailSnippets(accountId string, filter EmailFilterElement,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return EmailSnippetQueryResult{}, SimpleError{code: JmapErrorInvalidJmapRequestPayload, err: err}
|
||||
logger.Error().Err(err)
|
||||
return EmailSnippetQueryResult{}, simpleError(err, JmapErrorInvalidJmapRequestPayload)
|
||||
}
|
||||
|
||||
return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, func(body *Response) (EmailSnippetQueryResult, Error) {
|
||||
var queryResponse EmailQueryResponse
|
||||
err = retrieveResponseMatchParameters(body, CommandEmailQuery, "0", &queryResponse)
|
||||
if err != nil {
|
||||
return EmailSnippetQueryResult{}, SimpleError{code: JmapErrorInvalidJmapResponsePayload, err: err}
|
||||
logger.Error().Err(err)
|
||||
return EmailSnippetQueryResult{}, simpleError(err, JmapErrorInvalidJmapResponsePayload)
|
||||
}
|
||||
|
||||
var snippetResponse SearchSnippetGetResponse
|
||||
err = retrieveResponseMatchParameters(body, CommandSearchSnippetGet, "1", &snippetResponse)
|
||||
if err != nil {
|
||||
return EmailSnippetQueryResult{}, SimpleError{code: JmapErrorInvalidJmapResponsePayload, err: err}
|
||||
logger.Error().Err(err)
|
||||
return EmailSnippetQueryResult{}, simpleError(err, JmapErrorInvalidJmapResponsePayload)
|
||||
}
|
||||
|
||||
return EmailSnippetQueryResult{
|
||||
@@ -401,26 +416,27 @@ func (j *Client) QueryEmails(accountId string, filter EmailFilterElement, sessio
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return EmailQueryResult{}, SimpleError{code: JmapErrorInvalidJmapRequestPayload, err: err}
|
||||
logger.Error().Err(err)
|
||||
return EmailQueryResult{}, simpleError(err, JmapErrorInvalidJmapRequestPayload)
|
||||
}
|
||||
|
||||
return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, func(body *Response) (EmailQueryResult, Error) {
|
||||
var queryResponse EmailQueryResponse
|
||||
err = retrieveResponseMatchParameters(body, CommandEmailQuery, "0", &queryResponse)
|
||||
if err != nil {
|
||||
return EmailQueryResult{}, SimpleError{code: JmapErrorInvalidJmapResponsePayload, err: err}
|
||||
return EmailQueryResult{}, simpleError(err, JmapErrorInvalidJmapResponsePayload)
|
||||
}
|
||||
|
||||
var snippetResponse SearchSnippetGetResponse
|
||||
err = retrieveResponseMatchParameters(body, CommandSearchSnippetGet, "1", &snippetResponse)
|
||||
if err != nil {
|
||||
return EmailQueryResult{}, SimpleError{code: JmapErrorInvalidJmapResponsePayload, err: err}
|
||||
return EmailQueryResult{}, simpleError(err, JmapErrorInvalidJmapResponsePayload)
|
||||
}
|
||||
|
||||
var emailsResponse EmailGetResponse
|
||||
err = retrieveResponseMatchParameters(body, CommandEmailGet, "2", &emailsResponse)
|
||||
if err != nil {
|
||||
return EmailQueryResult{}, SimpleError{code: JmapErrorInvalidJmapResponsePayload, err: err}
|
||||
return EmailQueryResult{}, simpleError(err, JmapErrorInvalidJmapResponsePayload)
|
||||
}
|
||||
|
||||
snippetsById := map[string][]SearchSnippet{}
|
||||
@@ -496,32 +512,37 @@ func (j *Client) ImportEmail(accountId string, session *Session, ctx context.Con
|
||||
invocation(CommandBlobGet, getHash, "1"),
|
||||
)
|
||||
if err != nil {
|
||||
return UploadedEmail{}, SimpleError{code: JmapErrorInvalidJmapRequestPayload, err: err}
|
||||
return UploadedEmail{}, simpleError(err, JmapErrorInvalidJmapRequestPayload)
|
||||
}
|
||||
|
||||
return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, func(body *Response) (UploadedEmail, Error) {
|
||||
var uploadResponse BlobUploadResponse
|
||||
err = retrieveResponseMatchParameters(body, CommandBlobUpload, "0", &uploadResponse)
|
||||
if err != nil {
|
||||
return UploadedEmail{}, SimpleError{code: JmapErrorInvalidJmapResponsePayload, err: err}
|
||||
logger.Error().Err(err)
|
||||
return UploadedEmail{}, simpleError(err, JmapErrorInvalidJmapResponsePayload)
|
||||
}
|
||||
|
||||
var getResponse BlobGetResponse
|
||||
err = retrieveResponseMatchParameters(body, CommandBlobGet, "1", &getResponse)
|
||||
if err != nil {
|
||||
return UploadedEmail{}, SimpleError{code: JmapErrorInvalidJmapResponsePayload, err: err}
|
||||
logger.Error().Err(err)
|
||||
return UploadedEmail{}, simpleError(err, JmapErrorInvalidJmapResponsePayload)
|
||||
}
|
||||
|
||||
if len(uploadResponse.Created) != 1 {
|
||||
return UploadedEmail{}, SimpleError{code: JmapErrorInvalidJmapResponsePayload, err: err}
|
||||
logger.Error().Msgf("%T.Created has %v elements instead of 1", uploadResponse, len(uploadResponse.Created))
|
||||
return UploadedEmail{}, simpleError(err, JmapErrorInvalidJmapResponsePayload)
|
||||
}
|
||||
upload, ok := uploadResponse.Created["0"]
|
||||
if !ok {
|
||||
return UploadedEmail{}, SimpleError{code: JmapErrorInvalidJmapResponsePayload, err: err}
|
||||
logger.Error().Msgf("%T.Created has no element '0'", uploadResponse)
|
||||
return UploadedEmail{}, simpleError(err, JmapErrorInvalidJmapResponsePayload)
|
||||
}
|
||||
|
||||
if len(getResponse.List) != 1 {
|
||||
return UploadedEmail{}, SimpleError{code: JmapErrorInvalidJmapResponsePayload, err: err}
|
||||
logger.Error().Msgf("%T.List has %v elements instead of 1", getResponse, len(getResponse.List))
|
||||
return UploadedEmail{}, simpleError(err, JmapErrorInvalidJmapResponsePayload)
|
||||
}
|
||||
get := getResponse.List[0]
|
||||
|
||||
@@ -554,14 +575,16 @@ func (j *Client) CreateEmail(accountId string, email EmailCreate, session *Sessi
|
||||
}, "0"),
|
||||
)
|
||||
if err != nil {
|
||||
return CreatedEmail{}, SimpleError{code: JmapErrorInvalidJmapRequestPayload, err: err}
|
||||
logger.Error().Err(err)
|
||||
return CreatedEmail{}, simpleError(err, JmapErrorInvalidJmapRequestPayload)
|
||||
}
|
||||
|
||||
return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, func(body *Response) (CreatedEmail, Error) {
|
||||
var setResponse EmailSetResponse
|
||||
err = retrieveResponseMatchParameters(body, CommandEmailSet, "0", &setResponse)
|
||||
if err != nil {
|
||||
return CreatedEmail{}, SimpleError{code: JmapErrorInvalidJmapResponsePayload, err: err}
|
||||
logger.Error().Err(err)
|
||||
return CreatedEmail{}, simpleError(err, JmapErrorInvalidJmapResponsePayload)
|
||||
}
|
||||
|
||||
if len(setResponse.NotCreated) > 0 {
|
||||
@@ -571,12 +594,14 @@ func (j *Client) CreateEmail(accountId string, email EmailCreate, session *Sessi
|
||||
|
||||
setErr, notok := setResponse.NotCreated["c"]
|
||||
if notok {
|
||||
logger.Error().Msgf("%T.NotCreated returned an error %v", setResponse, setErr)
|
||||
return CreatedEmail{}, setErrorError(setErr, EmailType)
|
||||
}
|
||||
|
||||
created, ok := setResponse.Created["c"]
|
||||
if !ok {
|
||||
err = fmt.Errorf("failed to find %s in %s response", string(EmailType), string(CommandEmailSet))
|
||||
logger.Error().Err(err)
|
||||
return CreatedEmail{}, simpleError(err, JmapErrorInvalidJmapResponsePayload)
|
||||
}
|
||||
|
||||
@@ -612,14 +637,16 @@ func (j *Client) UpdateEmails(accountId string, updates map[string]EmailUpdate,
|
||||
}, "0"),
|
||||
)
|
||||
if err != nil {
|
||||
return UpdatedEmails{}, SimpleError{code: JmapErrorInvalidJmapRequestPayload, err: err}
|
||||
logger.Error().Err(err)
|
||||
return UpdatedEmails{}, simpleError(err, JmapErrorInvalidJmapRequestPayload)
|
||||
}
|
||||
|
||||
return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, func(body *Response) (UpdatedEmails, Error) {
|
||||
var setResponse EmailSetResponse
|
||||
err = retrieveResponseMatchParameters(body, CommandEmailSet, "0", &setResponse)
|
||||
if err != nil {
|
||||
return UpdatedEmails{}, SimpleError{code: JmapErrorInvalidJmapResponsePayload, err: err}
|
||||
logger.Error().Err(err)
|
||||
return UpdatedEmails{}, simpleError(err, JmapErrorInvalidJmapResponsePayload)
|
||||
}
|
||||
if len(setResponse.NotUpdated) != len(updates) {
|
||||
// error occured
|
||||
@@ -648,14 +675,16 @@ func (j *Client) DeleteEmails(accountId string, destroy []string, session *Sessi
|
||||
}, "0"),
|
||||
)
|
||||
if err != nil {
|
||||
return DeletedEmails{}, SimpleError{code: JmapErrorInvalidJmapRequestPayload, err: err}
|
||||
logger.Error().Err(err)
|
||||
return DeletedEmails{}, simpleError(err, JmapErrorInvalidJmapRequestPayload)
|
||||
}
|
||||
|
||||
return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, func(body *Response) (DeletedEmails, Error) {
|
||||
var setResponse EmailSetResponse
|
||||
err = retrieveResponseMatchParameters(body, CommandEmailSet, "0", &setResponse)
|
||||
if err != nil {
|
||||
return DeletedEmails{}, SimpleError{code: JmapErrorInvalidJmapResponsePayload, err: err}
|
||||
logger.Error().Err(err)
|
||||
return DeletedEmails{}, simpleError(err, JmapErrorInvalidJmapResponsePayload)
|
||||
}
|
||||
if len(setResponse.NotDestroyed) != len(destroy) {
|
||||
// error occured
|
||||
@@ -724,14 +753,16 @@ func (j *Client) SubmitEmail(accountId string, identityId string, emailId string
|
||||
invocation(CommandEmailSubmissionGet, get, "1"),
|
||||
)
|
||||
if err != nil {
|
||||
return SubmittedEmail{}, SimpleError{code: JmapErrorInvalidJmapRequestPayload, err: err}
|
||||
logger.Error().Err(err)
|
||||
return SubmittedEmail{}, simpleError(err, JmapErrorInvalidJmapRequestPayload)
|
||||
}
|
||||
|
||||
return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, func(body *Response) (SubmittedEmail, Error) {
|
||||
var submissionResponse EmailSubmissionSetResponse
|
||||
err = retrieveResponseMatchParameters(body, CommandEmailSubmissionSet, "0", &submissionResponse)
|
||||
if err != nil {
|
||||
return SubmittedEmail{}, SimpleError{code: JmapErrorInvalidJmapResponsePayload, err: err}
|
||||
logger.Error().Err(err)
|
||||
return SubmittedEmail{}, simpleError(err, JmapErrorInvalidJmapResponsePayload)
|
||||
}
|
||||
|
||||
if len(submissionResponse.NotCreated) > 0 {
|
||||
@@ -747,13 +778,15 @@ func (j *Client) SubmitEmail(accountId string, identityId string, emailId string
|
||||
var setResponse EmailSetResponse
|
||||
err = retrieveResponseMatchParameters(body, CommandEmailSet, "0", &setResponse)
|
||||
if err != nil {
|
||||
return SubmittedEmail{}, SimpleError{code: JmapErrorInvalidJmapResponsePayload, err: err}
|
||||
logger.Error().Err(err)
|
||||
return SubmittedEmail{}, simpleError(err, JmapErrorInvalidJmapResponsePayload)
|
||||
}
|
||||
|
||||
var getResponse EmailSubmissionGetResponse
|
||||
err = retrieveResponseMatchParameters(body, CommandEmailSubmissionGet, "1", &getResponse)
|
||||
if err != nil {
|
||||
return SubmittedEmail{}, SimpleError{code: JmapErrorInvalidJmapResponsePayload, err: err}
|
||||
logger.Error().Err(err)
|
||||
return SubmittedEmail{}, simpleError(err, JmapErrorInvalidJmapResponsePayload)
|
||||
}
|
||||
|
||||
if len(getResponse.List) != 1 {
|
||||
|
||||
@@ -21,16 +21,21 @@ func (j *Client) GetIdentity(accountId string, session *Session, ctx context.Con
|
||||
logger = j.logger(aid, "GetIdentity", session, logger)
|
||||
cmd, err := request(invocation(CommandIdentityGet, IdentityGetCommand{AccountId: aid}, "0"))
|
||||
if err != nil {
|
||||
return Identities{}, SimpleError{code: JmapErrorInvalidJmapRequestPayload, err: err}
|
||||
logger.Error().Err(err)
|
||||
return Identities{}, simpleError(err, JmapErrorInvalidJmapRequestPayload)
|
||||
}
|
||||
return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, func(body *Response) (Identities, Error) {
|
||||
var response IdentityGetResponse
|
||||
err = retrieveResponseMatchParameters(body, CommandIdentityGet, "0", &response)
|
||||
if err != nil {
|
||||
logger.Error().Err(err)
|
||||
return Identities{}, simpleError(err, JmapErrorInvalidJmapResponsePayload)
|
||||
}
|
||||
return Identities{
|
||||
Identities: response.List,
|
||||
State: response.State,
|
||||
SessionState: body.SessionState,
|
||||
}, simpleError(err, JmapErrorInvalidJmapResponsePayload)
|
||||
}, nil
|
||||
})
|
||||
}
|
||||
|
||||
@@ -55,7 +60,8 @@ func (j *Client) GetIdentities(accountIds []string, session *Session, ctx contex
|
||||
|
||||
cmd, err := request(calls...)
|
||||
if err != nil {
|
||||
return IdentitiesGetResponse{}, SimpleError{code: JmapErrorInvalidJmapRequestPayload, err: err}
|
||||
logger.Error().Err(err)
|
||||
return IdentitiesGetResponse{}, simpleError(err, JmapErrorInvalidJmapRequestPayload)
|
||||
}
|
||||
return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, func(body *Response) (IdentitiesGetResponse, Error) {
|
||||
identities := make(map[string][]Identity, len(uniqueAccountIds))
|
||||
@@ -65,6 +71,7 @@ func (j *Client) GetIdentities(accountIds []string, session *Session, ctx contex
|
||||
var response IdentityGetResponse
|
||||
err = retrieveResponseMatchParameters(body, CommandIdentityGet, strconv.Itoa(i), &response)
|
||||
if err != nil {
|
||||
logger.Error().Err(err)
|
||||
return IdentitiesGetResponse{}, simpleError(err, JmapErrorInvalidJmapResponsePayload)
|
||||
} else {
|
||||
identities[accountId] = response.List
|
||||
@@ -81,3 +88,63 @@ func (j *Client) GetIdentities(accountIds []string, session *Session, ctx contex
|
||||
}, nil
|
||||
})
|
||||
}
|
||||
|
||||
type IdentitiesAndMailboxesGetResponse struct {
|
||||
Identities map[string][]Identity `json:"identities,omitempty"`
|
||||
NotFound []string `json:"notFound,omitempty"`
|
||||
State string `json:"state"`
|
||||
SessionState string `json:"sessionState"`
|
||||
Mailboxes []Mailbox `json:"mailboxes"`
|
||||
}
|
||||
|
||||
func (j *Client) GetIdentitiesAndMailboxes(mailboxAccountId string, accountIds []string, session *Session, ctx context.Context, logger *log.Logger) (IdentitiesAndMailboxesGetResponse, Error) {
|
||||
uniqueAccountIds := structs.Uniq(accountIds)
|
||||
|
||||
logger = j.loggerParams("", "GetIdentitiesAndMailboxes", session, logger, func(l zerolog.Context) zerolog.Context {
|
||||
return l.Array(logAccountId, log.SafeStringArray(uniqueAccountIds))
|
||||
})
|
||||
|
||||
calls := make([]Invocation, len(uniqueAccountIds)+1)
|
||||
calls[0] = invocation(CommandMailboxGet, MailboxGetCommand{AccountId: mailboxAccountId}, "0")
|
||||
for i, accountId := range uniqueAccountIds {
|
||||
calls[i+1] = invocation(CommandIdentityGet, IdentityGetCommand{AccountId: accountId}, strconv.Itoa(i+1))
|
||||
}
|
||||
|
||||
cmd, err := request(calls...)
|
||||
if err != nil {
|
||||
logger.Error().Err(err)
|
||||
return IdentitiesAndMailboxesGetResponse{}, simpleError(err, JmapErrorInvalidJmapRequestPayload)
|
||||
}
|
||||
return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, func(body *Response) (IdentitiesAndMailboxesGetResponse, Error) {
|
||||
identities := make(map[string][]Identity, len(uniqueAccountIds))
|
||||
lastState := ""
|
||||
notFound := []string{}
|
||||
for i, accountId := range uniqueAccountIds {
|
||||
var response IdentityGetResponse
|
||||
err = retrieveResponseMatchParameters(body, CommandIdentityGet, strconv.Itoa(i+1), &response)
|
||||
if err != nil {
|
||||
logger.Error().Err(err)
|
||||
return IdentitiesAndMailboxesGetResponse{}, simpleError(err, JmapErrorInvalidJmapResponsePayload)
|
||||
} else {
|
||||
identities[accountId] = response.List
|
||||
}
|
||||
lastState = response.State
|
||||
notFound = append(notFound, response.NotFound...)
|
||||
}
|
||||
|
||||
var mailboxResponse MailboxGetResponse
|
||||
err = retrieveResponseMatchParameters(body, CommandMailboxGet, "0", &mailboxResponse)
|
||||
if err != nil {
|
||||
logger.Error().Err(err)
|
||||
return IdentitiesAndMailboxesGetResponse{}, simpleError(err, JmapErrorInvalidJmapResponsePayload)
|
||||
}
|
||||
|
||||
return IdentitiesAndMailboxesGetResponse{
|
||||
Identities: identities,
|
||||
NotFound: structs.Uniq(notFound),
|
||||
State: lastState,
|
||||
SessionState: body.SessionState,
|
||||
Mailboxes: mailboxResponse.List,
|
||||
}, nil
|
||||
})
|
||||
}
|
||||
|
||||
@@ -19,13 +19,15 @@ func (j *Client) GetMailbox(accountId string, session *Session, ctx context.Cont
|
||||
logger = j.logger(aid, "GetMailbox", session, logger)
|
||||
cmd, err := request(invocation(CommandMailboxGet, MailboxGetCommand{AccountId: aid, Ids: ids}, "0"))
|
||||
if err != nil {
|
||||
return MailboxesResponse{}, SimpleError{code: JmapErrorInvalidJmapRequestPayload, err: err}
|
||||
logger.Error().Err(err)
|
||||
return MailboxesResponse{}, simpleError(err, JmapErrorInvalidJmapRequestPayload)
|
||||
}
|
||||
|
||||
return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, func(body *Response) (MailboxesResponse, Error) {
|
||||
var response MailboxGetResponse
|
||||
err = retrieveResponseMatchParameters(body, CommandMailboxGet, "0", &response)
|
||||
if err != nil {
|
||||
logger.Error().Err(err)
|
||||
return MailboxesResponse{}, simpleError(err, JmapErrorInvalidJmapResponsePayload)
|
||||
}
|
||||
|
||||
@@ -77,14 +79,16 @@ func (j *Client) SearchMailboxes(accountId string, session *Session, ctx context
|
||||
}, "1"),
|
||||
)
|
||||
if err != nil {
|
||||
return Mailboxes{}, SimpleError{code: JmapErrorInvalidJmapRequestPayload, err: err}
|
||||
logger.Error().Err(err)
|
||||
return Mailboxes{}, simpleError(err, JmapErrorInvalidJmapRequestPayload)
|
||||
}
|
||||
|
||||
return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, func(body *Response) (Mailboxes, Error) {
|
||||
var response MailboxGetResponse
|
||||
err = retrieveResponseMatchParameters(body, CommandMailboxGet, "1", &response)
|
||||
if err != nil {
|
||||
return Mailboxes{}, SimpleError{code: JmapErrorInvalidJmapResponsePayload, err: err}
|
||||
logger.Error().Err(err)
|
||||
return Mailboxes{}, simpleError(err, JmapErrorInvalidJmapResponsePayload)
|
||||
}
|
||||
return Mailboxes{Mailboxes: response.List, State: response.State, SessionState: body.SessionState}, nil
|
||||
})
|
||||
|
||||
@@ -18,12 +18,17 @@ func (j *Client) GetVacationResponse(accountId string, session *Session, ctx con
|
||||
logger = j.logger(aid, "GetVacationResponse", session, logger)
|
||||
cmd, err := request(invocation(CommandVacationResponseGet, VacationResponseGetCommand{AccountId: aid}, "0"))
|
||||
if err != nil {
|
||||
return VacationResponseGetResponse{}, SimpleError{code: JmapErrorInvalidJmapRequestPayload, err: err}
|
||||
logger.Error().Err(err)
|
||||
return VacationResponseGetResponse{}, simpleError(err, JmapErrorInvalidJmapRequestPayload)
|
||||
}
|
||||
return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, func(body *Response) (VacationResponseGetResponse, Error) {
|
||||
var response VacationResponseGetResponse
|
||||
err = retrieveResponseMatchParameters(body, CommandVacationResponseGet, "0", &response)
|
||||
return response, simpleError(err, JmapErrorInvalidJmapResponsePayload)
|
||||
if err != nil {
|
||||
logger.Error().Err(err)
|
||||
return VacationResponseGetResponse{}, simpleError(err, JmapErrorInvalidJmapResponsePayload)
|
||||
}
|
||||
return response, nil
|
||||
})
|
||||
}
|
||||
|
||||
@@ -80,29 +85,34 @@ func (j *Client) SetVacationResponse(accountId string, vacation VacationResponse
|
||||
invocation(CommandVacationResponseGet, VacationResponseGetCommand{AccountId: aid}, "1"),
|
||||
)
|
||||
if err != nil {
|
||||
return VacationResponseChange{}, SimpleError{code: JmapErrorInvalidJmapRequestPayload, err: err}
|
||||
logger.Error().Err(err)
|
||||
return VacationResponseChange{}, simpleError(err, JmapErrorInvalidJmapRequestPayload)
|
||||
}
|
||||
return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, func(body *Response) (VacationResponseChange, Error) {
|
||||
var setResponse VacationResponseSetResponse
|
||||
err = retrieveResponseMatchParameters(body, CommandVacationResponseSet, "0", &setResponse)
|
||||
if err != nil {
|
||||
logger.Error().Err(err)
|
||||
return VacationResponseChange{}, simpleError(err, JmapErrorInvalidJmapResponsePayload)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
var getResponse VacationResponseGetResponse
|
||||
err = retrieveResponseMatchParameters(body, CommandVacationResponseGet, "1", &getResponse)
|
||||
if err != nil {
|
||||
logger.Error().Err(err)
|
||||
return VacationResponseChange{}, simpleError(err, JmapErrorInvalidJmapResponsePayload)
|
||||
}
|
||||
|
||||
if len(getResponse.List) != 1 {
|
||||
err = fmt.Errorf("failed to find %s in %s response", string(VacationResponseType), string(CommandVacationResponseGet))
|
||||
logger.Error().Err(err)
|
||||
return VacationResponseChange{}, simpleError(err, JmapErrorInvalidJmapResponsePayload)
|
||||
}
|
||||
|
||||
|
||||
@@ -32,9 +32,9 @@ func (j *Client) AddSessionEventListener(listener SessionEventListener) {
|
||||
j.sessionEventListeners.add(listener)
|
||||
}
|
||||
|
||||
func (j *Client) onSessionOutdated(session *Session) {
|
||||
func (j *Client) onSessionOutdated(session *Session, newSessionState string) {
|
||||
j.sessionEventListeners.signal(func(listener SessionEventListener) {
|
||||
listener.OnSessionOutdated(session)
|
||||
listener.OnSessionOutdated(session, newSessionState)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
)
|
||||
|
||||
type SessionEventListener interface {
|
||||
OnSessionOutdated(session *Session)
|
||||
OnSessionOutdated(session *Session, newSessionState string)
|
||||
}
|
||||
|
||||
// Cached user related information
|
||||
|
||||
@@ -42,7 +42,7 @@ func command[T any](api ApiClient,
|
||||
logger *log.Logger,
|
||||
ctx context.Context,
|
||||
session *Session,
|
||||
sessionOutdatedHandler func(session *Session),
|
||||
sessionOutdatedHandler func(session *Session, newState string),
|
||||
request Request,
|
||||
mapper func(body *Response) (T, Error)) (T, Error) {
|
||||
|
||||
@@ -62,7 +62,7 @@ func command[T any](api ApiClient,
|
||||
|
||||
if response.SessionState != session.State {
|
||||
if sessionOutdatedHandler != nil {
|
||||
sessionOutdatedHandler(session)
|
||||
sessionOutdatedHandler(session, response.SessionState)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,9 @@
|
||||
package structs
|
||||
|
||||
import (
|
||||
"maps"
|
||||
"slices"
|
||||
|
||||
orderedmap "github.com/wk8/go-ordered-map"
|
||||
)
|
||||
|
||||
@@ -30,3 +33,11 @@ func Uniq[T comparable](source []T) []T {
|
||||
}
|
||||
return set
|
||||
}
|
||||
|
||||
func Keys[K comparable, V any](source map[K]V) []K {
|
||||
if source == nil {
|
||||
var zero []K
|
||||
return zero
|
||||
}
|
||||
return slices.Collect(maps.Keys(source))
|
||||
}
|
||||
|
||||
@@ -83,3 +83,19 @@ func TestUniqWithStructs(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestKeys(t *testing.T) {
|
||||
tests := []struct {
|
||||
input map[int]string
|
||||
expected []int
|
||||
}{
|
||||
{map[int]string{5: "cinq", 1: "un", 3: "trois", 4: "vier"}, []int{5, 1, 3, 4}},
|
||||
{map[int]string{1: "un"}, []int{1}},
|
||||
}
|
||||
for i, tt := range tests {
|
||||
t.Run(fmt.Sprintf("%d: testing %v", i+1, tt.input), func(t *testing.T) {
|
||||
result := Keys(tt.input)
|
||||
assert.ElementsMatch(t, tt.expected, result)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ func DefaultConfig() *config.Config {
|
||||
MaxBodyValueBytes: -1,
|
||||
ResponseHeaderTimeout: 10 * time.Second,
|
||||
SessionCache: config.MailSessionCache{
|
||||
Ttl: 30 * time.Second,
|
||||
Ttl: 5 * time.Minute,
|
||||
FailureTtl: 15 * time.Second,
|
||||
MaxCapacity: 10_000,
|
||||
},
|
||||
|
||||
@@ -2,6 +2,9 @@ package groupware
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/opencloud-eu/opencloud/pkg/jmap"
|
||||
"github.com/opencloud-eu/opencloud/pkg/structs"
|
||||
)
|
||||
|
||||
func (g Groupware) GetAccount(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -19,3 +22,57 @@ func (g Groupware) GetAccounts(w http.ResponseWriter, r *http.Request) {
|
||||
return response(req.session.Accounts, req.session.State)
|
||||
})
|
||||
}
|
||||
|
||||
type AccountBootstrapResponse struct {
|
||||
// The API version.
|
||||
Version string `json:"version"`
|
||||
|
||||
// A list of capabilities of this API version.
|
||||
Capabilities []string `json:"capabilities"`
|
||||
|
||||
// API limits.
|
||||
Limits IndexLimits `json:"limits"`
|
||||
|
||||
// Accounts that are available to the user.
|
||||
//
|
||||
// The key of the mapis the identifier.
|
||||
Accounts map[string]IndexAccount `json:"accounts"`
|
||||
|
||||
// Primary accounts for usage types.
|
||||
PrimaryAccounts IndexPrimaryAccounts `json:"primaryAccounts"`
|
||||
|
||||
// Mailboxes.
|
||||
Mailboxes map[string][]jmap.Mailbox `json:"mailboxes"`
|
||||
}
|
||||
|
||||
// When the request suceeds.
|
||||
// swagger:response IndexResponse
|
||||
type SwaggerAccountBootstrapResponse struct {
|
||||
// in: body
|
||||
Body struct {
|
||||
*AccountBootstrapResponse
|
||||
}
|
||||
}
|
||||
|
||||
func (g Groupware) GetAccountBootstrap(w http.ResponseWriter, r *http.Request) {
|
||||
g.respond(w, r, func(req Request) Response {
|
||||
mailAccountId := req.GetAccountId()
|
||||
accountIds := structs.Keys(req.session.Accounts)
|
||||
|
||||
resp, jerr := g.jmap.GetIdentitiesAndMailboxes(mailAccountId, accountIds, req.session, req.ctx, req.logger)
|
||||
if jerr != nil {
|
||||
return req.errorResponseFromJmap(jerr)
|
||||
}
|
||||
|
||||
return response(AccountBootstrapResponse{
|
||||
Version: Version,
|
||||
Capabilities: Capabilities,
|
||||
Limits: buildIndexLimits(req.session),
|
||||
Accounts: buildIndexAccount(req.session, resp.Identities),
|
||||
PrimaryAccounts: buildIndexPrimaryAccounts(req.session),
|
||||
Mailboxes: map[string][]jmap.Mailbox{
|
||||
mailAccountId: resp.Mailboxes,
|
||||
},
|
||||
}, resp.SessionState)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -150,66 +150,70 @@ type SwaggerIndexResponse struct {
|
||||
// 200: IndexResponse
|
||||
func (g Groupware) Index(w http.ResponseWriter, r *http.Request) {
|
||||
g.respond(w, r, func(req Request) Response {
|
||||
|
||||
accountIds := make([]string, len(req.session.Accounts))
|
||||
i := 0
|
||||
for k := range req.session.Accounts {
|
||||
accountIds[i] = k
|
||||
i++
|
||||
}
|
||||
accountIds = structs.Uniq(accountIds)
|
||||
accountIds := structs.Keys(req.session.Accounts)
|
||||
|
||||
identitiesResponse, err := g.jmap.GetIdentities(accountIds, req.session, req.ctx, req.logger)
|
||||
if err != nil {
|
||||
return req.errorResponseFromJmap(err)
|
||||
}
|
||||
|
||||
accounts := make(map[string]IndexAccount, len(req.session.Accounts))
|
||||
for accountId, account := range req.session.Accounts {
|
||||
indexAccount := IndexAccount{
|
||||
Name: account.Name,
|
||||
IsPersonal: account.IsPersonal,
|
||||
IsReadOnly: account.IsReadOnly,
|
||||
Capabilities: IndexAccountCapabilities{
|
||||
Mail: IndexAccountMailCapabilities{
|
||||
MaxMailboxDepth: account.AccountCapabilities.Mail.MaxMailboxDepth,
|
||||
MaxSizeMailboxName: account.AccountCapabilities.Mail.MaxSizeMailboxName,
|
||||
MaxMailboxesPerEmail: account.AccountCapabilities.Mail.MaxMailboxesPerEmail,
|
||||
MaxSizeAttachmentsPerEmail: account.AccountCapabilities.Mail.MaxSizeAttachmentsPerEmail,
|
||||
MayCreateTopLevelMailbox: account.AccountCapabilities.Mail.MayCreateTopLevelMailbox,
|
||||
MaxDelayedSend: account.AccountCapabilities.Submission.MaxDelayedSend,
|
||||
},
|
||||
Sieve: IndexAccountSieveCapabilities{
|
||||
MaxSizeScriptName: account.AccountCapabilities.Sieve.MaxSizeScript,
|
||||
MaxSizeScript: account.AccountCapabilities.Sieve.MaxSizeScript,
|
||||
MaxNumberScripts: account.AccountCapabilities.Sieve.MaxNumberScripts,
|
||||
MaxNumberRedirects: account.AccountCapabilities.Sieve.MaxNumberRedirects,
|
||||
},
|
||||
},
|
||||
}
|
||||
if identity, ok := identitiesResponse.Identities[accountId]; ok {
|
||||
indexAccount.Identities = identity
|
||||
}
|
||||
accounts[accountId] = indexAccount
|
||||
}
|
||||
|
||||
return response(IndexResponse{
|
||||
Version: Version,
|
||||
Capabilities: Capabilities,
|
||||
Limits: IndexLimits{
|
||||
MaxSizeUpload: req.session.Capabilities.Core.MaxSizeUpload,
|
||||
MaxConcurrentUpload: req.session.Capabilities.Core.MaxConcurrentUpload,
|
||||
MaxSizeRequest: req.session.Capabilities.Core.MaxSizeRequest,
|
||||
MaxConcurrentRequests: req.session.Capabilities.Core.MaxConcurrentRequests,
|
||||
},
|
||||
Accounts: accounts,
|
||||
PrimaryAccounts: IndexPrimaryAccounts{
|
||||
Mail: req.session.PrimaryAccounts.Mail,
|
||||
Submission: req.session.PrimaryAccounts.Submission,
|
||||
Blob: req.session.PrimaryAccounts.Blob,
|
||||
VacationResponse: req.session.PrimaryAccounts.VacationResponse,
|
||||
Sieve: req.session.PrimaryAccounts.Sieve,
|
||||
},
|
||||
Version: Version,
|
||||
Capabilities: Capabilities,
|
||||
Limits: buildIndexLimits(req.session),
|
||||
Accounts: buildIndexAccount(req.session, identitiesResponse.Identities),
|
||||
PrimaryAccounts: buildIndexPrimaryAccounts(req.session),
|
||||
}, req.session.State)
|
||||
})
|
||||
}
|
||||
|
||||
func buildIndexLimits(session *jmap.Session) IndexLimits {
|
||||
return IndexLimits{
|
||||
MaxSizeUpload: session.Capabilities.Core.MaxSizeUpload,
|
||||
MaxConcurrentUpload: session.Capabilities.Core.MaxConcurrentUpload,
|
||||
MaxSizeRequest: session.Capabilities.Core.MaxSizeRequest,
|
||||
MaxConcurrentRequests: session.Capabilities.Core.MaxConcurrentRequests,
|
||||
}
|
||||
}
|
||||
|
||||
func buildIndexPrimaryAccounts(session *jmap.Session) IndexPrimaryAccounts {
|
||||
return IndexPrimaryAccounts{
|
||||
Mail: session.PrimaryAccounts.Mail,
|
||||
Submission: session.PrimaryAccounts.Submission,
|
||||
Blob: session.PrimaryAccounts.Blob,
|
||||
VacationResponse: session.PrimaryAccounts.VacationResponse,
|
||||
Sieve: session.PrimaryAccounts.Sieve,
|
||||
}
|
||||
}
|
||||
|
||||
func buildIndexAccount(session *jmap.Session, identities map[string][]jmap.Identity) map[string]IndexAccount {
|
||||
accounts := make(map[string]IndexAccount, len(session.Accounts))
|
||||
for accountId, account := range session.Accounts {
|
||||
indexAccount := IndexAccount{
|
||||
Name: account.Name,
|
||||
IsPersonal: account.IsPersonal,
|
||||
IsReadOnly: account.IsReadOnly,
|
||||
Capabilities: IndexAccountCapabilities{
|
||||
Mail: IndexAccountMailCapabilities{
|
||||
MaxMailboxDepth: account.AccountCapabilities.Mail.MaxMailboxDepth,
|
||||
MaxSizeMailboxName: account.AccountCapabilities.Mail.MaxSizeMailboxName,
|
||||
MaxMailboxesPerEmail: account.AccountCapabilities.Mail.MaxMailboxesPerEmail,
|
||||
MaxSizeAttachmentsPerEmail: account.AccountCapabilities.Mail.MaxSizeAttachmentsPerEmail,
|
||||
MayCreateTopLevelMailbox: account.AccountCapabilities.Mail.MayCreateTopLevelMailbox,
|
||||
MaxDelayedSend: account.AccountCapabilities.Submission.MaxDelayedSend,
|
||||
},
|
||||
Sieve: IndexAccountSieveCapabilities{
|
||||
MaxSizeScriptName: account.AccountCapabilities.Sieve.MaxSizeScript,
|
||||
MaxSizeScript: account.AccountCapabilities.Sieve.MaxSizeScript,
|
||||
MaxNumberScripts: account.AccountCapabilities.Sieve.MaxNumberScripts,
|
||||
MaxNumberRedirects: account.AccountCapabilities.Sieve.MaxNumberRedirects,
|
||||
},
|
||||
},
|
||||
}
|
||||
if identity, ok := identities[accountId]; ok {
|
||||
indexAccount.Identities = identity
|
||||
}
|
||||
accounts[accountId] = indexAccount
|
||||
}
|
||||
return accounts
|
||||
}
|
||||
|
||||
@@ -411,11 +411,7 @@ type MessageCreation struct {
|
||||
|
||||
func (g Groupware) CreateMessage(w http.ResponseWriter, r *http.Request) {
|
||||
g.respond(w, r, func(req Request) Response {
|
||||
messageId := chi.URLParam(r, UriParamMessageId)
|
||||
|
||||
l := req.logger.With()
|
||||
l.Str(UriParamMessageId, messageId)
|
||||
logger := log.From(l)
|
||||
logger := req.logger
|
||||
|
||||
var body MessageCreation
|
||||
err := req.body(&body)
|
||||
|
||||
@@ -63,13 +63,16 @@ func (e GroupwareInitializationError) Unwrap() error {
|
||||
}
|
||||
|
||||
type GroupwareSessionEventListener struct {
|
||||
logger *log.Logger
|
||||
sessionCache *ttlcache.Cache[string, cachedSession]
|
||||
}
|
||||
|
||||
func (l GroupwareSessionEventListener) OnSessionOutdated(session *jmap.Session) {
|
||||
func (l GroupwareSessionEventListener) OnSessionOutdated(session *jmap.Session, newSessionState string) {
|
||||
// it's enough to remove the session from the cache, as it will be fetched on-demand
|
||||
// the next time an operation is performed on behalf of the user
|
||||
l.sessionCache.Delete(session.Username)
|
||||
|
||||
l.logger.Trace().Msgf("removed outdated session for user '%v': state %s -> %s", session.Username, session.State, newSessionState)
|
||||
}
|
||||
|
||||
var _ jmap.SessionEventListener = GroupwareSessionEventListener{}
|
||||
@@ -134,7 +137,28 @@ func NewGroupware(config *config.Config, logger *log.Logger, mux *chi.Mux) (*Gro
|
||||
go sessionCache.Start()
|
||||
}
|
||||
|
||||
sessionEventListener := GroupwareSessionEventListener{sessionCache: sessionCache}
|
||||
if logger.Trace().Enabled() {
|
||||
sessionCache.OnEviction(func(c context.Context, r ttlcache.EvictionReason, item *ttlcache.Item[string, cachedSession]) {
|
||||
reason := ""
|
||||
switch r {
|
||||
case ttlcache.EvictionReasonDeleted:
|
||||
reason = "deleted"
|
||||
case ttlcache.EvictionReasonCapacityReached:
|
||||
reason = "capacity reached"
|
||||
case ttlcache.EvictionReasonExpired:
|
||||
reason = fmt.Sprintf("expired after %vms", item.TTL().Milliseconds())
|
||||
case ttlcache.EvictionReasonMaxCostExceeded:
|
||||
reason = "max cost exceeded"
|
||||
}
|
||||
if reason == "" {
|
||||
reason = fmt.Sprintf("unknown (%v)", r)
|
||||
}
|
||||
|
||||
logger.Trace().Msgf("session cache eviction of user '%v': %v", item.Key(), reason)
|
||||
})
|
||||
}
|
||||
|
||||
sessionEventListener := GroupwareSessionEventListener{sessionCache: sessionCache, logger: logger}
|
||||
jmapClient.AddSessionEventListener(&sessionEventListener)
|
||||
|
||||
return &Groupware{
|
||||
@@ -388,7 +412,7 @@ func (g Groupware) serveError(w http.ResponseWriter, r *http.Request, error *Err
|
||||
render.Render(w, r, errorResponses(*error))
|
||||
}
|
||||
|
||||
func (g Groupware) respond(w http.ResponseWriter, r *http.Request, handler func(r Request) Response) {
|
||||
func (g Groupware) withSession(w http.ResponseWriter, r *http.Request, handler func(r Request) Response) (Response, bool) {
|
||||
ctx := r.Context()
|
||||
sl := g.logger.SubloggerWithRequestID(ctx)
|
||||
logger := &sl
|
||||
@@ -396,11 +420,11 @@ func (g Groupware) respond(w http.ResponseWriter, r *http.Request, handler func(
|
||||
username, ok, err := g.usernameProvider.GetUsername(r, ctx, logger)
|
||||
if err != nil {
|
||||
g.serveError(w, r, apiError(errorId(r, ctx), ErrorInvalidAuthentication))
|
||||
return
|
||||
return Response{}, false
|
||||
}
|
||||
if !ok {
|
||||
g.serveError(w, r, apiError(errorId(r, ctx), ErrorMissingAuthentication))
|
||||
return
|
||||
return Response{}, false
|
||||
}
|
||||
|
||||
logger = log.From(logger.With().Str(logUsername, log.SafeString(username)))
|
||||
@@ -409,13 +433,13 @@ func (g Groupware) respond(w http.ResponseWriter, r *http.Request, handler func(
|
||||
if err != nil {
|
||||
logger.Error().Err(err).Interface(logQuery, r.URL.Query()).Msg("failed to determine JMAP session")
|
||||
render.Status(r, http.StatusInternalServerError)
|
||||
return
|
||||
return Response{}, false
|
||||
}
|
||||
if !ok {
|
||||
// no session = authentication failed
|
||||
logger.Warn().Err(err).Interface(logQuery, r.URL.Query()).Msg("could not authenticate")
|
||||
render.Status(r, http.StatusForbidden)
|
||||
return
|
||||
return Response{}, false
|
||||
}
|
||||
decoratedLogger := session.DecorateLogger(*logger)
|
||||
|
||||
@@ -427,6 +451,10 @@ func (g Groupware) respond(w http.ResponseWriter, r *http.Request, handler func(
|
||||
}
|
||||
|
||||
response := handler(req)
|
||||
return response, true
|
||||
}
|
||||
|
||||
func (g Groupware) sendResponse(w http.ResponseWriter, r *http.Request, response Response) {
|
||||
if response.err != nil {
|
||||
g.log(response.err)
|
||||
w.Header().Add("Content-Type", ContentTypeJsonApi)
|
||||
@@ -456,6 +484,14 @@ func (g Groupware) respond(w http.ResponseWriter, r *http.Request, handler func(
|
||||
}
|
||||
}
|
||||
|
||||
func (g Groupware) respond(w http.ResponseWriter, r *http.Request, handler func(r Request) Response) {
|
||||
response, ok := g.withSession(w, r, handler)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
g.sendResponse(w, r, response)
|
||||
}
|
||||
|
||||
func (g Groupware) stream(w http.ResponseWriter, r *http.Request, handler func(r Request, w http.ResponseWriter) *Error) {
|
||||
ctx := r.Context()
|
||||
sl := g.logger.SubloggerWithRequestID(ctx)
|
||||
|
||||
@@ -42,6 +42,7 @@ func (g Groupware) Route(r chi.Router) {
|
||||
r.Get("/accounts", g.GetAccounts)
|
||||
r.Route("/accounts/{accountid}", func(r chi.Router) {
|
||||
r.Get("/", g.GetAccount)
|
||||
r.Get("/bootstrap", g.GetAccountBootstrap)
|
||||
r.Get("/identities", g.GetIdentities)
|
||||
r.Get("/vacation", g.GetVacation)
|
||||
r.Put("/vacation", g.SetVacation)
|
||||
|
||||
@@ -59,7 +59,7 @@ func (l *sessionCacheLoader) Load(c *ttlcache.Cache[string, cachedSession], user
|
||||
return c.Set(username, failedSession{err: err}, l.errorTtl)
|
||||
} else {
|
||||
l.logger.Debug().Str("username", username).Msgf("successfully created session for '%v'", username)
|
||||
return c.Set(username, succeededSession{session: session}, 0) // 0 = use the TTL configured on the Cache
|
||||
return c.Set(username, succeededSession{session: session}, ttlcache.DefaultTTL) // use the TTL configured on the Cache
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user