From deee6102ec22684cdfae58771aa586b323604c5e Mon Sep 17 00:00:00 2001
From: Pascal Bleser
Date: Mon, 6 Oct 2025 11:58:36 +0200
Subject: [PATCH] groupware: add quota API + add support for Accept-Language
and Content-Language
---
pkg/jmap/jmap_api.go | 6 +-
pkg/jmap/jmap_api_blob.go | 20 ++---
pkg/jmap/jmap_api_email.go | 78 +++++++++----------
pkg/jmap/jmap_api_identity.go | 18 ++---
pkg/jmap/jmap_api_mailbox.go | 42 +++++-----
pkg/jmap/jmap_api_quota.go | 23 ++++++
pkg/jmap/jmap_api_vacation.go | 12 +--
pkg/jmap/jmap_http.go | 56 ++++++++-----
pkg/jmap/jmap_integration_test.go | 8 +-
pkg/jmap/jmap_model.go | 16 ++++
pkg/jmap/jmap_test.go | 26 +++----
pkg/jmap/jmap_tools.go | 15 ++--
pkg/jmap/jmap_tools_test.go | 4 +-
services/groupware/apidoc.yml | 6 ++
.../pkg/groupware/groupware_api_account.go | 8 +-
.../pkg/groupware/groupware_api_blob.go | 15 ++--
.../pkg/groupware/groupware_api_calendars.go | 6 +-
.../pkg/groupware/groupware_api_contacts.go | 6 +-
.../pkg/groupware/groupware_api_emails.go | 78 ++++++++++---------
.../pkg/groupware/groupware_api_identity.go | 4 +-
.../pkg/groupware/groupware_api_index.go | 4 +-
.../pkg/groupware/groupware_api_mailbox.go | 36 ++++-----
.../pkg/groupware/groupware_api_quota.go | 39 ++++++++++
.../pkg/groupware/groupware_api_tasklists.go | 6 +-
.../pkg/groupware/groupware_api_vacation.go | 8 +-
.../pkg/groupware/groupware_framework.go | 4 +
.../pkg/groupware/groupware_request.go | 10 ++-
.../pkg/groupware/groupware_response.go | 44 ++++++-----
.../pkg/groupware/groupware_route.go | 1 +
29 files changed, 362 insertions(+), 237 deletions(-)
create mode 100644 pkg/jmap/jmap_api_quota.go
create mode 100644 services/groupware/pkg/groupware/groupware_api_quota.go
diff --git a/pkg/jmap/jmap_api.go b/pkg/jmap/jmap_api.go
index 24080ac47..d864d3087 100644
--- a/pkg/jmap/jmap_api.go
+++ b/pkg/jmap/jmap_api.go
@@ -9,7 +9,7 @@ import (
)
type ApiClient interface {
- Command(ctx context.Context, logger *log.Logger, session *Session, request Request) ([]byte, Error)
+ Command(ctx context.Context, logger *log.Logger, session *Session, request Request, acceptLanguage string) ([]byte, Language, Error)
io.Closer
}
@@ -33,8 +33,8 @@ type SessionClient interface {
}
type BlobClient interface {
- UploadBinary(ctx context.Context, logger *log.Logger, session *Session, uploadUrl string, endpoint string, contentType string, content io.Reader) (UploadedBlob, Error)
- DownloadBinary(ctx context.Context, logger *log.Logger, session *Session, downloadUrl string, endpoint string) (*BlobDownload, Error)
+ UploadBinary(ctx context.Context, logger *log.Logger, session *Session, uploadUrl string, endpoint string, contentType string, acceptLanguage string, content io.Reader) (UploadedBlob, Language, Error)
+ DownloadBinary(ctx context.Context, logger *log.Logger, session *Session, downloadUrl string, endpoint string, acceptLanguage string) (*BlobDownload, Language, Error)
io.Closer
}
diff --git a/pkg/jmap/jmap_api_blob.go b/pkg/jmap/jmap_api_blob.go
index 788a8ca00..2266c1d10 100644
--- a/pkg/jmap/jmap_api_blob.go
+++ b/pkg/jmap/jmap_api_blob.go
@@ -14,7 +14,7 @@ type BlobResponse struct {
State State `json:"state,omitempty"`
}
-func (j *Client) GetBlobMetadata(accountId string, session *Session, ctx context.Context, logger *log.Logger, id string) (BlobResponse, SessionState, Error) {
+func (j *Client) GetBlobMetadata(accountId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, id string) (BlobResponse, SessionState, Language, Error) {
cmd, jerr := j.request(session, logger,
invocation(CommandBlobGet, BlobGetCommand{
AccountId: accountId,
@@ -24,10 +24,10 @@ func (j *Client) GetBlobMetadata(accountId string, session *Session, ctx context
}, "0"),
)
if jerr != nil {
- return BlobResponse{}, "", jerr
+ return BlobResponse{}, "", "", jerr
}
- return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, func(body *Response) (BlobResponse, Error) {
+ return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (BlobResponse, Error) {
var response BlobGetResponse
err := retrieveResponseMatchParameters(logger, body, CommandBlobGet, "0", &response)
if err != nil {
@@ -51,14 +51,14 @@ type UploadedBlob struct {
State State `json:"state"`
}
-func (j *Client) UploadBlobStream(accountId string, session *Session, ctx context.Context, logger *log.Logger, contentType string, body io.Reader) (UploadedBlob, Error) {
+func (j *Client) UploadBlobStream(accountId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, contentType string, body io.Reader) (UploadedBlob, Language, Error) {
logger = log.From(logger.With().Str(logEndpoint, session.UploadEndpoint))
// TODO(pbleser-oc) use a library for proper URL template parsing
uploadUrl := strings.ReplaceAll(session.UploadUrlTemplate, "{accountId}", accountId)
- return j.blob.UploadBinary(ctx, logger, session, uploadUrl, session.UploadEndpoint, contentType, body)
+ return j.blob.UploadBinary(ctx, logger, session, uploadUrl, session.UploadEndpoint, contentType, acceptLanguage, body)
}
-func (j *Client) DownloadBlobStream(accountId string, blobId string, name string, typ string, session *Session, ctx context.Context, logger *log.Logger) (*BlobDownload, Error) {
+func (j *Client) DownloadBlobStream(accountId string, blobId string, name string, typ string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string) (*BlobDownload, Language, Error) {
logger = log.From(logger.With().Str(logEndpoint, session.DownloadEndpoint))
// TODO(pbleser-oc) use a library for proper URL template parsing
downloadUrl := session.DownloadUrlTemplate
@@ -67,10 +67,10 @@ func (j *Client) DownloadBlobStream(accountId string, blobId string, name string
downloadUrl = strings.ReplaceAll(downloadUrl, "{name}", name)
downloadUrl = strings.ReplaceAll(downloadUrl, "{type}", typ)
logger = log.From(logger.With().Str(logDownloadUrl, downloadUrl).Str(logBlobId, blobId))
- return j.blob.DownloadBinary(ctx, logger, session, downloadUrl, session.DownloadEndpoint)
+ 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, data []byte, contentType string) (UploadedBlob, SessionState, Error) {
+func (j *Client) UploadBlob(accountId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, data []byte, contentType string) (UploadedBlob, SessionState, Language, Error) {
encoded := base64.StdEncoding.EncodeToString(data)
upload := BlobUploadCommand{
@@ -100,10 +100,10 @@ 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, func(body *Response) (UploadedBlob, Error) {
+ return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (UploadedBlob, Error) {
var uploadResponse BlobUploadResponse
err := retrieveResponseMatchParameters(logger, body, CommandBlobUpload, "0", &uploadResponse)
if err != nil {
diff --git a/pkg/jmap/jmap_api_email.go b/pkg/jmap/jmap_api_email.go
index 0f42197a9..c44ae70dc 100644
--- a/pkg/jmap/jmap_api_email.go
+++ b/pkg/jmap/jmap_api_email.go
@@ -32,7 +32,7 @@ type Emails struct {
}
// Retrieve specific Emails by their id.
-func (j *Client) GetEmails(accountId string, session *Session, ctx context.Context, logger *log.Logger, ids []string, fetchBodies bool, maxBodyValueBytes uint) (Emails, SessionState, Error) {
+func (j *Client) GetEmails(accountId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, ids []string, fetchBodies bool, maxBodyValueBytes uint) (Emails, SessionState, Language, Error) {
logger = j.logger("GetEmails", session, logger)
get := EmailGetCommand{AccountId: accountId, Ids: ids, FetchAllBodyValues: fetchBodies}
@@ -43,9 +43,9 @@ func (j *Client) GetEmails(accountId string, session *Session, ctx context.Conte
cmd, err := j.request(session, logger, invocation(CommandEmailGet, get, "0"))
if err != nil {
logger.Error().Err(err).Send()
- return Emails{}, "", simpleError(err, JmapErrorInvalidJmapRequestPayload)
+ return Emails{}, "", "", simpleError(err, JmapErrorInvalidJmapRequestPayload)
}
- return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, func(body *Response) (Emails, Error) {
+ return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (Emails, Error) {
var response EmailGetResponse
err = retrieveResponseMatchParameters(logger, body, CommandEmailGet, "0", &response)
if err != nil {
@@ -56,7 +56,7 @@ func (j *Client) GetEmails(accountId string, session *Session, ctx context.Conte
}
// 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, mailboxId string, offset uint, limit uint, fetchBodies bool, maxBodyValueBytes uint) (Emails, SessionState, Error) {
+func (j *Client) GetAllEmailsInMailbox(accountId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, mailboxId string, offset uint, limit uint, fetchBodies bool, maxBodyValueBytes uint) (Emails, SessionState, 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)
})
@@ -89,10 +89,10 @@ func (j *Client) GetAllEmailsInMailbox(accountId string, session *Session, ctx c
invocation(CommandEmailGet, get, "1"),
)
if err != nil {
- return Emails{}, "", err
+ return Emails{}, "", "", err
}
- return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, func(body *Response) (Emails, Error) {
+ return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (Emails, Error) {
var queryResponse EmailQueryResponse
err = retrieveResponseMatchParameters(logger, body, CommandEmailQuery, "0", &queryResponse)
if err != nil {
@@ -116,7 +116,7 @@ func (j *Client) GetAllEmailsInMailbox(accountId string, session *Session, ctx c
}
// 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, sinceState string, fetchBodies bool, maxBodyValueBytes uint, maxChanges uint) (MailboxChanges, SessionState, 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, Language, Error) {
logger = j.loggerParams("GetEmailsSince", session, logger, func(z zerolog.Context) zerolog.Context {
return z.Bool(logFetchBodies, fetchBodies).Str(logSinceState, sinceState)
})
@@ -152,10 +152,10 @@ 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, func(body *Response) (MailboxChanges, Error) {
+ return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (MailboxChanges, Error) {
var changesResponse EmailChangesResponse
err = retrieveResponseMatchParameters(logger, body, CommandEmailChanges, "0", &changesResponse)
if err != nil {
@@ -195,7 +195,7 @@ type EmailSnippetQueryResult struct {
QueryState State `json:"queryState"`
}
-func (j *Client) QueryEmailSnippets(accountId string, filter EmailFilterElement, session *Session, ctx context.Context, logger *log.Logger, offset uint, limit uint) (EmailSnippetQueryResult, SessionState, Error) {
+func (j *Client) QueryEmailSnippets(accountId string, filter EmailFilterElement, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, offset uint, limit uint) (EmailSnippetQueryResult, SessionState, Language, Error) {
logger = j.loggerParams("QueryEmails", session, logger, func(z zerolog.Context) zerolog.Context {
return z.Uint(logLimit, limit).Uint(logOffset, offset)
})
@@ -229,10 +229,10 @@ func (j *Client) QueryEmailSnippets(accountId string, filter EmailFilterElement,
invocation(CommandSearchSnippetGet, snippet, "1"),
)
if err != nil {
- return EmailSnippetQueryResult{}, "", err
+ return EmailSnippetQueryResult{}, "", "", err
}
- return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, func(body *Response) (EmailSnippetQueryResult, Error) {
+ return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (EmailSnippetQueryResult, Error) {
var queryResponse EmailQueryResponse
err = retrieveResponseMatchParameters(logger, body, CommandEmailQuery, "0", &queryResponse)
if err != nil {
@@ -264,7 +264,7 @@ type EmailQueryResult struct {
QueryState State `json:"queryState"`
}
-func (j *Client) QueryEmails(accountId string, filter EmailFilterElement, session *Session, ctx context.Context, logger *log.Logger, offset uint, limit uint, fetchBodies bool, maxBodyValueBytes uint) (EmailQueryResult, SessionState, Error) {
+func (j *Client) QueryEmails(accountId string, filter EmailFilterElement, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, offset uint, limit uint, fetchBodies bool, maxBodyValueBytes uint) (EmailQueryResult, SessionState, Language, Error) {
logger = j.loggerParams("QueryEmails", session, logger, func(z zerolog.Context) zerolog.Context {
return z.Bool(logFetchBodies, fetchBodies)
})
@@ -299,10 +299,10 @@ func (j *Client) QueryEmails(accountId string, filter EmailFilterElement, sessio
invocation(CommandEmailGet, mails, "1"),
)
if err != nil {
- return EmailQueryResult{}, "", err
+ return EmailQueryResult{}, "", "", err
}
- return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, func(body *Response) (EmailQueryResult, Error) {
+ return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (EmailQueryResult, Error) {
var queryResponse EmailQueryResponse
err = retrieveResponseMatchParameters(logger, body, CommandEmailQuery, "0", &queryResponse)
if err != nil {
@@ -339,7 +339,7 @@ type EmailQueryWithSnippetsResult struct {
QueryState State `json:"queryState"`
}
-func (j *Client) QueryEmailsWithSnippets(accountId string, filter EmailFilterElement, session *Session, ctx context.Context, logger *log.Logger, offset uint, limit uint, fetchBodies bool, maxBodyValueBytes uint) (EmailQueryWithSnippetsResult, SessionState, Error) {
+func (j *Client) QueryEmailsWithSnippets(accountId string, filter EmailFilterElement, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, offset uint, limit uint, fetchBodies bool, maxBodyValueBytes uint) (EmailQueryWithSnippetsResult, SessionState, Language, Error) {
logger = j.loggerParams("QueryEmailsWithSnippets", session, logger, func(z zerolog.Context) zerolog.Context {
return z.Bool(logFetchBodies, fetchBodies)
})
@@ -387,10 +387,10 @@ func (j *Client) QueryEmailsWithSnippets(accountId string, filter EmailFilterEle
if err != nil {
logger.Error().Err(err).Send()
- return EmailQueryWithSnippetsResult{}, "", simpleError(err, JmapErrorInvalidJmapRequestPayload)
+ return EmailQueryWithSnippetsResult{}, "", "", simpleError(err, JmapErrorInvalidJmapRequestPayload)
}
- return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, func(body *Response) (EmailQueryWithSnippetsResult, Error) {
+ return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (EmailQueryWithSnippetsResult, Error) {
var queryResponse EmailQueryResponse
err = retrieveResponseMatchParameters(logger, body, CommandEmailQuery, "0", &queryResponse)
if err != nil {
@@ -448,7 +448,7 @@ type UploadedEmail struct {
Sha512 string `json:"sha:512"`
}
-func (j *Client) ImportEmail(accountId string, session *Session, ctx context.Context, logger *log.Logger, data []byte) (UploadedEmail, SessionState, Error) {
+func (j *Client) ImportEmail(accountId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, data []byte) (UploadedEmail, SessionState, Language, Error) {
encoded := base64.StdEncoding.EncodeToString(data)
upload := BlobUploadCommand{
@@ -478,10 +478,10 @@ 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, func(body *Response) (UploadedEmail, Error) {
+ return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (UploadedEmail, Error) {
var uploadResponse BlobUploadResponse
err = retrieveResponseMatchParameters(logger, body, CommandBlobUpload, "0", &uploadResponse)
if err != nil {
@@ -526,7 +526,7 @@ type CreatedEmail struct {
State State `json:"state"`
}
-func (j *Client) CreateEmail(accountId string, email EmailCreate, session *Session, ctx context.Context, logger *log.Logger) (CreatedEmail, SessionState, Error) {
+func (j *Client) CreateEmail(accountId string, email EmailCreate, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string) (CreatedEmail, SessionState, Language, Error) {
cmd, err := j.request(session, logger,
invocation(CommandEmailSubmissionSet, EmailSetCommand{
AccountId: accountId,
@@ -536,10 +536,10 @@ func (j *Client) CreateEmail(accountId string, email EmailCreate, session *Sessi
}, "0"),
)
if err != nil {
- return CreatedEmail{}, "", err
+ return CreatedEmail{}, "", "", err
}
- return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, func(body *Response) (CreatedEmail, Error) {
+ return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (CreatedEmail, Error) {
var setResponse EmailSetResponse
err = retrieveResponseMatchParameters(logger, body, CommandEmailSet, "0", &setResponse)
if err != nil {
@@ -584,7 +584,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) (UpdatedEmails, SessionState, Error) {
+func (j *Client) UpdateEmails(accountId string, updates map[string]EmailUpdate, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string) (UpdatedEmails, SessionState, Language, Error) {
cmd, err := j.request(session, logger,
invocation(CommandEmailSet, EmailSetCommand{
AccountId: accountId,
@@ -592,10 +592,10 @@ func (j *Client) UpdateEmails(accountId string, updates map[string]EmailUpdate,
}, "0"),
)
if err != nil {
- return UpdatedEmails{}, "", err
+ return UpdatedEmails{}, "", "", err
}
- return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, func(body *Response) (UpdatedEmails, Error) {
+ return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (UpdatedEmails, Error) {
var setResponse EmailSetResponse
err = retrieveResponseMatchParameters(logger, body, CommandEmailSet, "0", &setResponse)
if err != nil {
@@ -616,7 +616,7 @@ type DeletedEmails struct {
State State `json:"state"`
}
-func (j *Client) DeleteEmails(accountId string, destroy []string, session *Session, ctx context.Context, logger *log.Logger) (DeletedEmails, SessionState, Error) {
+func (j *Client) DeleteEmails(accountId string, destroy []string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string) (DeletedEmails, SessionState, Language, Error) {
cmd, err := j.request(session, logger,
invocation(CommandEmailSet, EmailSetCommand{
AccountId: accountId,
@@ -624,10 +624,10 @@ func (j *Client) DeleteEmails(accountId string, destroy []string, session *Sessi
}, "0"),
)
if err != nil {
- return DeletedEmails{}, "", err
+ return DeletedEmails{}, "", "", err
}
- return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, func(body *Response) (DeletedEmails, Error) {
+ return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (DeletedEmails, Error) {
var setResponse EmailSetResponse
err = retrieveResponseMatchParameters(logger, body, CommandEmailSet, "0", &setResponse)
if err != nil {
@@ -666,7 +666,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, data []byte) (SubmittedEmail, SessionState, 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, Language, Error) {
set := EmailSubmissionSetCommand{
AccountId: accountId,
Create: map[string]EmailSubmissionCreate{
@@ -696,10 +696,10 @@ 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, func(body *Response) (SubmittedEmail, Error) {
+ return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (SubmittedEmail, Error) {
var submissionResponse EmailSubmissionSetResponse
err = retrieveResponseMatchParameters(logger, body, CommandEmailSubmissionSet, "0", &submissionResponse)
if err != nil {
@@ -748,7 +748,7 @@ func (j *Client) SubmitEmail(accountId string, identityId string, emailId string
})
}
-func (j *Client) EmailsInThread(accountId string, threadId string, session *Session, ctx context.Context, logger *log.Logger, fetchBodies bool, maxBodyValueBytes uint) ([]Email, SessionState, 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, Language, Error) {
logger = j.loggerParams("EmailsInThread", session, logger, func(z zerolog.Context) zerolog.Context {
return z.Bool(logFetchBodies, fetchBodies).Str("threadId", log.SafeString(threadId))
})
@@ -770,10 +770,10 @@ func (j *Client) EmailsInThread(accountId string, threadId string, session *Sess
}, "1"),
)
if err != nil {
- return []Email{}, "", err
+ return []Email{}, "", "", err
}
- return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, func(body *Response) ([]Email, Error) {
+ return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) ([]Email, Error) {
var emailsResponse EmailGetResponse
err = retrieveResponseMatchParameters(logger, body, CommandEmailGet, "1", &emailsResponse)
if err != nil {
@@ -789,7 +789,7 @@ type EmailsSummary struct {
State State `json:"state"`
}
-func (j *Client) QueryEmailSummaries(accountIds []string, session *Session, ctx context.Context, logger *log.Logger, filter EmailFilterElement, limit uint) (map[string]EmailsSummary, SessionState, Error) {
+func (j *Client) QueryEmailSummaries(accountIds []string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, filter EmailFilterElement, limit uint) (map[string]EmailsSummary, SessionState, Language, Error) {
logger = j.logger("QueryEmailSummaries", session, logger)
uniqueAccountIds := structs.Uniq(accountIds)
@@ -815,10 +815,10 @@ func (j *Client) QueryEmailSummaries(accountIds []string, session *Session, ctx
}
cmd, err := j.request(session, logger, invocations...)
if err != nil {
- return map[string]EmailsSummary{}, "", err
+ return map[string]EmailsSummary{}, "", "", err
}
- return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, func(body *Response) (map[string]EmailsSummary, Error) {
+ return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (map[string]EmailsSummary, Error) {
resp := map[string]EmailsSummary{}
for _, accountId := range uniqueAccountIds {
var response EmailGetResponse
diff --git a/pkg/jmap/jmap_api_identity.go b/pkg/jmap/jmap_api_identity.go
index 1c613efa9..0c095b4dc 100644
--- a/pkg/jmap/jmap_api_identity.go
+++ b/pkg/jmap/jmap_api_identity.go
@@ -14,13 +14,13 @@ type Identities struct {
}
// https://jmap.io/spec-mail.html#identityget
-func (j *Client) GetIdentity(accountId string, session *Session, ctx context.Context, logger *log.Logger) (Identities, SessionState, Error) {
+func (j *Client) GetIdentity(accountId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string) (Identities, SessionState, Language, Error) {
logger = j.logger("GetIdentity", session, logger)
cmd, err := j.request(session, logger, invocation(CommandIdentityGet, IdentityGetCommand{AccountId: accountId}, "0"))
if err != nil {
- return Identities{}, "", err
+ return Identities{}, "", "", err
}
- return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, func(body *Response) (Identities, Error) {
+ return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (Identities, Error) {
var response IdentityGetResponse
err = retrieveResponseMatchParameters(logger, body, CommandIdentityGet, "0", &response)
if err != nil {
@@ -39,7 +39,7 @@ type IdentitiesGetResponse struct {
State State `json:"state"`
}
-func (j *Client) GetIdentities(accountIds []string, session *Session, ctx context.Context, logger *log.Logger) (IdentitiesGetResponse, SessionState, Error) {
+func (j *Client) GetIdentities(accountIds []string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string) (IdentitiesGetResponse, SessionState, Language, Error) {
uniqueAccountIds := structs.Uniq(accountIds)
logger = j.logger("GetIdentities", session, logger)
@@ -51,9 +51,9 @@ func (j *Client) GetIdentities(accountIds []string, session *Session, ctx contex
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, func(body *Response) (IdentitiesGetResponse, Error) {
+ return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (IdentitiesGetResponse, Error) {
identities := make(map[string][]Identity, len(uniqueAccountIds))
var lastState State
notFound := []string{}
@@ -84,7 +84,7 @@ type IdentitiesAndMailboxesGetResponse struct {
Mailboxes []Mailbox `json:"mailboxes"`
}
-func (j *Client) GetIdentitiesAndMailboxes(mailboxAccountId string, accountIds []string, session *Session, ctx context.Context, logger *log.Logger) (IdentitiesAndMailboxesGetResponse, SessionState, Error) {
+func (j *Client) GetIdentitiesAndMailboxes(mailboxAccountId string, accountIds []string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string) (IdentitiesAndMailboxesGetResponse, SessionState, Language, Error) {
uniqueAccountIds := structs.Uniq(accountIds)
logger = j.logger("GetIdentitiesAndMailboxes", session, logger)
@@ -97,9 +97,9 @@ 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, func(body *Response) (IdentitiesAndMailboxesGetResponse, Error) {
+ return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (IdentitiesAndMailboxesGetResponse, Error) {
identities := make(map[string][]Identity, len(uniqueAccountIds))
var lastState State
notFound := []string{}
diff --git a/pkg/jmap/jmap_api_mailbox.go b/pkg/jmap/jmap_api_mailbox.go
index 558b67428..ed0ba62af 100644
--- a/pkg/jmap/jmap_api_mailbox.go
+++ b/pkg/jmap/jmap_api_mailbox.go
@@ -16,17 +16,17 @@ type MailboxesResponse struct {
}
// https://jmap.io/spec-mail.html#mailboxget
-func (j *Client) GetMailbox(accountId string, session *Session, ctx context.Context, logger *log.Logger, ids []string) (MailboxesResponse, SessionState, Error) {
+func (j *Client) GetMailbox(accountId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, ids []string) (MailboxesResponse, SessionState, 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, func(body *Response) (MailboxesResponse, Error) {
+ return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (MailboxesResponse, Error) {
var response MailboxGetResponse
err = retrieveResponseMatchParameters(logger, body, CommandMailboxGet, "0", &response)
if err != nil {
@@ -45,13 +45,13 @@ type AllMailboxesResponse struct {
State State `json:"state"`
}
-func (j *Client) GetAllMailboxes(accountIds []string, session *Session, ctx context.Context, logger *log.Logger) (map[string]AllMailboxesResponse, SessionState, Error) {
+func (j *Client) GetAllMailboxes(accountIds []string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string) (map[string]AllMailboxesResponse, SessionState, Language, Error) {
logger = j.logger("GetAllMailboxes", session, logger)
uniqueAccountIds := structs.Uniq(accountIds)
n := len(uniqueAccountIds)
if n < 1 {
- return map[string]AllMailboxesResponse{}, "", nil
+ return map[string]AllMailboxesResponse{}, "", "", nil
}
invocations := make([]Invocation, n)
@@ -61,10 +61,10 @@ func (j *Client) GetAllMailboxes(accountIds []string, session *Session, ctx cont
cmd, err := j.request(session, logger, invocations...)
if err != nil {
- return map[string]AllMailboxesResponse{}, "", err
+ return map[string]AllMailboxesResponse{}, "", "", err
}
- return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, func(body *Response) (map[string]AllMailboxesResponse, Error) {
+ return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (map[string]AllMailboxesResponse, Error) {
resp := map[string]AllMailboxesResponse{}
for _, accountId := range uniqueAccountIds {
var response MailboxGetResponse
@@ -89,7 +89,7 @@ type Mailboxes struct {
State State `json:"state,omitempty"`
}
-func (j *Client) SearchMailboxes(accountIds []string, session *Session, ctx context.Context, logger *log.Logger, filter MailboxFilterElement) (map[string]Mailboxes, SessionState, Error) {
+func (j *Client) SearchMailboxes(accountIds []string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, filter MailboxFilterElement) (map[string]Mailboxes, SessionState, Language, Error) {
logger = j.logger("SearchMailboxes", session, logger)
uniqueAccountIds := structs.Uniq(accountIds)
@@ -108,10 +108,10 @@ 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 map[string]Mailboxes{}, "", "", err
}
- return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, func(body *Response) (map[string]Mailboxes, Error) {
+ return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (map[string]Mailboxes, Error) {
resp := map[string]Mailboxes{}
for _, accountId := range uniqueAccountIds {
var response MailboxGetResponse
@@ -136,7 +136,7 @@ type MailboxChanges struct {
}
// 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, mailboxId string, sinceState string, fetchBodies bool, maxBodyValueBytes uint, maxChanges uint) (MailboxChanges, SessionState, 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, Language, Error) {
logger = j.loggerParams("GetMailboxChanges", session, logger, func(z zerolog.Context) zerolog.Context {
return z.Bool(logFetchBodies, fetchBodies).Str(logSinceState, sinceState)
})
@@ -172,10 +172,10 @@ 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, func(body *Response) (MailboxChanges, Error) {
+ return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (MailboxChanges, Error) {
var mailboxResponse MailboxChangesResponse
err = retrieveResponseMatchParameters(logger, body, CommandMailboxChanges, "0", &mailboxResponse)
if err != nil {
@@ -208,7 +208,7 @@ func (j *Client) GetMailboxChanges(accountId string, session *Session, ctx conte
}
// Retrieve Email changes in Mailboxes of multiple Accounts.
-func (j *Client) GetMailboxChangesForMultipleAccounts(accountIds []string, session *Session, ctx context.Context, logger *log.Logger, sinceStateMap map[string]string, fetchBodies bool, maxBodyValueBytes uint, maxChanges uint) (map[string]MailboxChanges, SessionState, 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, Language, Error) {
logger = j.loggerParams("GetMailboxChangesForMultipleAccounts", session, logger, func(z zerolog.Context) zerolog.Context {
sinceStateLogDict := zerolog.Dict()
for k, v := range sinceStateMap {
@@ -220,7 +220,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)
@@ -262,10 +262,10 @@ func (j *Client) GetMailboxChangesForMultipleAccounts(accountIds []string, sessi
cmd, err := j.request(session, logger, invocations...)
if err != nil {
- return map[string]MailboxChanges{}, "", err
+ return map[string]MailboxChanges{}, "", "", err
}
- return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, func(body *Response) (map[string]MailboxChanges, Error) {
+ return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (map[string]MailboxChanges, Error) {
resp := make(map[string]MailboxChanges, n)
for _, accountId := range uniqueAccountIds {
var mailboxResponse MailboxChangesResponse
@@ -300,13 +300,13 @@ func (j *Client) GetMailboxChangesForMultipleAccounts(accountIds []string, sessi
})
}
-func (j *Client) GetMailboxRolesForMultipleAccounts(accountIds []string, session *Session, ctx context.Context, logger *log.Logger) (map[string][]string, SessionState, Error) {
+func (j *Client) GetMailboxRolesForMultipleAccounts(accountIds []string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string) (map[string][]string, SessionState, Language, Error) {
logger = j.logger("GetMailboxRolesForMultipleAccounts", session, logger)
uniqueAccountIds := structs.Uniq(accountIds)
n := len(uniqueAccountIds)
if n < 1 {
- return map[string][]string{}, "", nil
+ return map[string][]string{}, "", "", nil
}
t := true
@@ -331,10 +331,10 @@ func (j *Client) GetMailboxRolesForMultipleAccounts(accountIds []string, session
cmd, err := j.request(session, logger, invocations...)
if err != nil {
- return map[string][]string{}, "", err
+ return map[string][]string{}, "", "", err
}
- return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, func(body *Response) (map[string][]string, Error) {
+ return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (map[string][]string, Error) {
resp := make(map[string][]string, n)
for _, accountId := range uniqueAccountIds {
var getResponse MailboxGetResponse
diff --git a/pkg/jmap/jmap_api_quota.go b/pkg/jmap/jmap_api_quota.go
new file mode 100644
index 000000000..a8082ca80
--- /dev/null
+++ b/pkg/jmap/jmap_api_quota.go
@@ -0,0 +1,23 @@
+package jmap
+
+import (
+ "context"
+
+ "github.com/opencloud-eu/opencloud/pkg/log"
+)
+
+func (j *Client) GetQuotas(accountId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string) (QuotaGetResponse, SessionState, Language, Error) {
+ logger = j.logger("GetQuotas", session, logger)
+ cmd, err := j.request(session, logger, invocation(CommandQuotaGet, QuotaGetCommand{AccountId: accountId}, "0"))
+ if err != nil {
+ return QuotaGetResponse{}, "", "", err
+ }
+ return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (QuotaGetResponse, Error) {
+ var response QuotaGetResponse
+ err = retrieveResponseMatchParameters(logger, body, CommandQuotaGet, "0", &response)
+ if err != nil {
+ return QuotaGetResponse{}, err
+ }
+ return response, nil
+ })
+}
diff --git a/pkg/jmap/jmap_api_vacation.go b/pkg/jmap/jmap_api_vacation.go
index ca8824d7a..c2221df7a 100644
--- a/pkg/jmap/jmap_api_vacation.go
+++ b/pkg/jmap/jmap_api_vacation.go
@@ -13,13 +13,13 @@ const (
)
// https://jmap.io/spec-mail.html#vacationresponseget
-func (j *Client) GetVacationResponse(accountId string, session *Session, ctx context.Context, logger *log.Logger) (VacationResponseGetResponse, SessionState, Error) {
+func (j *Client) GetVacationResponse(accountId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string) (VacationResponseGetResponse, SessionState, 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, func(body *Response) (VacationResponseGetResponse, Error) {
+ return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (VacationResponseGetResponse, Error) {
var response VacationResponseGetResponse
err = retrieveResponseMatchParameters(logger, body, CommandVacationResponseGet, "0", &response)
if err != nil {
@@ -58,7 +58,7 @@ type VacationResponseChange struct {
ResponseState State `json:"state"`
}
-func (j *Client) SetVacationResponse(accountId string, vacation VacationResponsePayload, session *Session, ctx context.Context, logger *log.Logger) (VacationResponseChange, SessionState, Error) {
+func (j *Client) SetVacationResponse(accountId string, vacation VacationResponsePayload, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string) (VacationResponseChange, SessionState, Language, Error) {
logger = j.logger("SetVacationResponse", session, logger)
cmd, err := j.request(session, logger,
@@ -80,9 +80,9 @@ func (j *Client) SetVacationResponse(accountId string, vacation VacationResponse
invocation(CommandVacationResponseGet, VacationResponseGetCommand{AccountId: accountId}, "1"),
)
if err != nil {
- return VacationResponseChange{}, "", err
+ return VacationResponseChange{}, "", "", err
}
- return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, func(body *Response) (VacationResponseChange, Error) {
+ return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (VacationResponseChange, Error) {
var setResponse VacationResponseSetResponse
err = retrieveResponseMatchParameters(logger, body, CommandVacationResponseSet, "0", &setResponse)
if err != nil {
diff --git a/pkg/jmap/jmap_http.go b/pkg/jmap/jmap_http.go
index 62404d556..ff62eaedb 100644
--- a/pkg/jmap/jmap_http.go
+++ b/pkg/jmap/jmap_http.go
@@ -176,7 +176,7 @@ func (h *HttpJmapClient) GetSession(sessionUrl *url.URL, username string, logger
return data, nil
}
-func (h *HttpJmapClient) Command(ctx context.Context, logger *log.Logger, session *Session, request Request) ([]byte, Error) {
+func (h *HttpJmapClient) Command(ctx context.Context, logger *log.Logger, session *Session, request Request, acceptLanguage string) ([]byte, Language, Error) {
jmapUrl := session.JmapUrl.String()
endpoint := session.JmapEndpoint
logger = log.From(logger.With().Str(logEndpoint, endpoint))
@@ -184,14 +184,21 @@ func (h *HttpJmapClient) Command(ctx context.Context, logger *log.Logger, sessio
bodyBytes, err := json.Marshal(request)
if err != nil {
logger.Error().Err(err).Msg("failed to marshall JSON payload")
- return nil, SimpleError{code: JmapErrorEncodingRequestBody, err: err}
+ return nil, "", SimpleError{code: JmapErrorEncodingRequestBody, err: err}
}
req, err := http.NewRequestWithContext(ctx, http.MethodPost, jmapUrl, bytes.NewBuffer(bodyBytes))
if err != nil {
logger.Error().Err(err).Msgf("failed to create POST request for %v", jmapUrl)
- return nil, SimpleError{code: JmapErrorCreatingRequest, err: err}
+ return nil, "", SimpleError{code: JmapErrorCreatingRequest, err: err}
}
+
+ // Some JMAP APIs use the Accept-Language header to determine which language to use to translate
+ // texts in attributes.
+ if acceptLanguage != "" {
+ req.Header.Add("Accept-Language", acceptLanguage)
+ }
+
req.Header.Add("Content-Type", "application/json")
req.Header.Add("User-Agent", h.userAgent)
h.auth(session.Username, logger, req)
@@ -200,12 +207,13 @@ func (h *HttpJmapClient) Command(ctx context.Context, logger *log.Logger, sessio
if err != nil {
h.listener.OnFailedRequest(endpoint, err)
logger.Error().Err(err).Msgf("failed to perform POST %v", jmapUrl)
- return nil, SimpleError{code: JmapErrorSendingRequest, err: err}
+ return nil, "", SimpleError{code: JmapErrorSendingRequest, err: err}
}
+ language := Language(res.Header.Get("Content-Language"))
if res.StatusCode < 200 || res.StatusCode > 299 {
h.listener.OnFailedRequestWithStatus(endpoint, res.StatusCode)
logger.Error().Str(logEndpoint, endpoint).Str(logHttpStatus, res.Status).Msg("HTTP response status code is not 2xx")
- return nil, SimpleError{code: JmapErrorServerResponse, err: err}
+ return nil, language, SimpleError{code: JmapErrorServerResponse, err: err}
}
if res.Body != nil {
defer func(Body io.ReadCloser) {
@@ -221,34 +229,38 @@ func (h *HttpJmapClient) Command(ctx context.Context, logger *log.Logger, sessio
if err != nil {
logger.Error().Err(err).Msg("failed to read response body")
h.listener.OnResponseBodyReadingError(endpoint, err)
- return nil, SimpleError{code: JmapErrorServerResponse, err: err}
+ return nil, language, SimpleError{code: JmapErrorServerResponse, err: err}
}
- return body, nil
+ return body, language, nil
}
-func (h *HttpJmapClient) UploadBinary(ctx context.Context, logger *log.Logger, session *Session, uploadUrl string, endpoint string, contentType string, body io.Reader) (UploadedBlob, Error) {
+func (h *HttpJmapClient) UploadBinary(ctx context.Context, logger *log.Logger, session *Session, uploadUrl string, endpoint string, contentType string, acceptLanguage string, body io.Reader) (UploadedBlob, Language, Error) {
logger = log.From(logger.With().Str(logEndpoint, endpoint))
req, err := http.NewRequestWithContext(ctx, http.MethodPost, uploadUrl, body)
if err != nil {
logger.Error().Err(err).Msgf("failed to create POST request for %v", uploadUrl)
- return UploadedBlob{}, SimpleError{code: JmapErrorCreatingRequest, err: err}
+ return UploadedBlob{}, "", SimpleError{code: JmapErrorCreatingRequest, err: err}
}
req.Header.Add("Content-Type", contentType)
req.Header.Add("User-Agent", h.userAgent)
h.auth(session.Username, logger, req)
+ if acceptLanguage != "" {
+ req.Header.Add("Accept-Language", acceptLanguage)
+ }
res, err := h.client.Do(req)
if err != nil {
h.listener.OnFailedRequest(endpoint, err)
logger.Error().Err(err).Msgf("failed to perform POST %v", uploadUrl)
- return UploadedBlob{}, SimpleError{code: JmapErrorSendingRequest, err: err}
+ return UploadedBlob{}, "", SimpleError{code: JmapErrorSendingRequest, err: err}
}
+ language := Language(res.Header.Get("Content-Language"))
if res.StatusCode < 200 || res.StatusCode > 299 {
h.listener.OnFailedRequestWithStatus(endpoint, res.StatusCode)
logger.Error().Str(logHttpStatus, res.Status).Int(logHttpStatusCode, res.StatusCode).Msg("HTTP response status code is not 2xx")
- return UploadedBlob{}, SimpleError{code: JmapErrorServerResponse, err: err}
+ return UploadedBlob{}, language, SimpleError{code: JmapErrorServerResponse, err: err}
}
if res.Body != nil {
defer func(Body io.ReadCloser) {
@@ -264,7 +276,7 @@ func (h *HttpJmapClient) UploadBinary(ctx context.Context, logger *log.Logger, s
if err != nil {
logger.Error().Err(err).Msg("failed to read response body")
h.listener.OnResponseBodyReadingError(endpoint, err)
- return UploadedBlob{}, SimpleError{code: JmapErrorServerResponse, err: err}
+ return UploadedBlob{}, language, SimpleError{code: JmapErrorServerResponse, err: err}
}
var result UploadedBlob
@@ -272,36 +284,40 @@ func (h *HttpJmapClient) UploadBinary(ctx context.Context, logger *log.Logger, s
if err != nil {
logger.Error().Str(logHttpUrl, uploadUrl).Err(err).Msg("failed to decode JSON payload from the upload response")
h.listener.OnResponseBodyUnmarshallingError(endpoint, err)
- return UploadedBlob{}, SimpleError{code: JmapErrorDecodingResponseBody, err: err}
+ return UploadedBlob{}, language, SimpleError{code: JmapErrorDecodingResponseBody, err: err}
}
- return result, nil
+ return result, language, nil
}
-func (h *HttpJmapClient) DownloadBinary(ctx context.Context, logger *log.Logger, session *Session, downloadUrl string, endpoint string) (*BlobDownload, Error) {
+func (h *HttpJmapClient) DownloadBinary(ctx context.Context, logger *log.Logger, session *Session, downloadUrl string, endpoint string, acceptLanguage string) (*BlobDownload, Language, Error) {
logger = log.From(logger.With().Str(logEndpoint, endpoint))
req, err := http.NewRequestWithContext(ctx, http.MethodGet, downloadUrl, nil)
if err != nil {
logger.Error().Err(err).Msgf("failed to create GET request for %v", downloadUrl)
- return nil, SimpleError{code: JmapErrorCreatingRequest, err: err}
+ return nil, "", SimpleError{code: JmapErrorCreatingRequest, err: err}
}
req.Header.Add("User-Agent", h.userAgent)
h.auth(session.Username, logger, req)
+ if acceptLanguage != "" {
+ req.Header.Add("Accept-Language", acceptLanguage)
+ }
res, err := h.client.Do(req)
if err != nil {
h.listener.OnFailedRequest(endpoint, err)
logger.Error().Err(err).Msgf("failed to perform GET %v", downloadUrl)
- return nil, SimpleError{code: JmapErrorSendingRequest, err: err}
+ return nil, "", SimpleError{code: JmapErrorSendingRequest, err: err}
}
+ language := Language(res.Header.Get("Content-Language"))
if res.StatusCode == http.StatusNotFound {
- return nil, nil
+ return nil, language, nil
}
if res.StatusCode < 200 || res.StatusCode > 299 {
h.listener.OnFailedRequestWithStatus(endpoint, res.StatusCode)
logger.Error().Str(logHttpStatus, res.Status).Int(logHttpStatusCode, res.StatusCode).Msg("HTTP response status code is not 2xx")
- return nil, SimpleError{code: JmapErrorServerResponse, err: err}
+ return nil, language, SimpleError{code: JmapErrorServerResponse, err: err}
}
h.listener.OnSuccessfulRequest(endpoint, res.StatusCode)
@@ -321,7 +337,7 @@ func (h *HttpJmapClient) DownloadBinary(ctx context.Context, logger *log.Logger,
Type: res.Header.Get("Content-Type"),
ContentDisposition: res.Header.Get("Content-Disposition"),
CacheControl: res.Header.Get("Cache-Control"),
- }, nil
+ }, language, nil
}
type WebSocketPushEnable struct {
diff --git a/pkg/jmap/jmap_integration_test.go b/pkg/jmap/jmap_integration_test.go
index b46ff851f..ba943a170 100644
--- a/pkg/jmap/jmap_integration_test.go
+++ b/pkg/jmap/jmap_integration_test.go
@@ -363,7 +363,7 @@ func TestWithStalwart(t *testing.T) {
var inboxFolder string
var inboxId string
{
- respByAccountId, sessionState, err := j.GetAllMailboxes([]string{accountId}, session, ctx, logger)
+ respByAccountId, sessionState, _, err := j.GetAllMailboxes([]string{accountId}, session, ctx, logger, "")
require.NoError(err)
require.Equal(session.State, sessionState)
require.Len(respByAccountId, 1)
@@ -440,7 +440,7 @@ func TestWithStalwart(t *testing.T) {
{
{
- resp, sessionState, err := j.GetIdentity(accountId, session, ctx, logger)
+ resp, sessionState, _, err := j.GetIdentity(accountId, session, ctx, logger, "")
require.NoError(err)
require.Equal(session.State, sessionState)
require.Len(resp.Identities, 1)
@@ -449,7 +449,7 @@ func TestWithStalwart(t *testing.T) {
}
{
- respByAccountId, sessionState, err := j.GetAllMailboxes([]string{accountId}, session, ctx, logger)
+ respByAccountId, sessionState, _, err := j.GetAllMailboxes([]string{accountId}, session, ctx, logger, "")
require.NoError(err)
require.Equal(session.State, sessionState)
require.Len(respByAccountId, 1)
@@ -465,7 +465,7 @@ func TestWithStalwart(t *testing.T) {
}
{
- resp, sessionState, err := j.GetAllEmailsInMailbox(accountId, session, ctx, logger, inboxId, 0, 0, false, 0)
+ resp, sessionState, _, err := j.GetAllEmailsInMailbox(accountId, session, ctx, logger, "", inboxId, 0, 0, false, 0)
require.NoError(err)
require.Equal(session.State, sessionState)
diff --git a/pkg/jmap/jmap_model.go b/pkg/jmap/jmap_model.go
index e068f124a..60aec96bb 100644
--- a/pkg/jmap/jmap_model.go
+++ b/pkg/jmap/jmap_model.go
@@ -865,6 +865,8 @@ type SessionState string
type State string
+type Language string
+
type SessionResponse struct {
Capabilities SessionCapabilities `json:"capabilities"`
@@ -4558,6 +4560,18 @@ type SendMDN struct {
OnSuccessUpdateEmail map[string]PatchObject `json:"onSuccessUpdateEmail,omitempty"`
}
+type QuotaGetCommand struct {
+ AccountId string `json:"accountId"`
+ Ids []string `json:"ids,omitempty"`
+}
+
+type QuotaGetResponse struct {
+ AccountId string `json:"accountId"`
+ State State `json:"state,omitempty"`
+ List []Quota `json:"list,omitempty"`
+ NotFound []string `json:"notFound,omitempty"`
+}
+
type ErrorResponse struct {
Type string `json:"type"`
Description string `json:"description,omitempty"`
@@ -4582,6 +4596,7 @@ const (
CommandVacationResponseGet Command = "VacationResponse/get"
CommandVacationResponseSet Command = "VacationResponse/set"
CommandSearchSnippetGet Command = "SearchSnippet/get"
+ CommandQuotaGet Command = "Quota/get"
)
var CommandResponseTypeMap = map[Command]func() any{
@@ -4601,4 +4616,5 @@ var CommandResponseTypeMap = map[Command]func() any{
CommandVacationResponseGet: func() any { return VacationResponseGetResponse{} },
CommandVacationResponseSet: func() any { return VacationResponseSetResponse{} },
CommandSearchSnippetGet: func() any { return SearchSnippetGetResponse{} },
+ CommandQuotaGet: func() any { return QuotaGetResponse{} },
}
diff --git a/pkg/jmap/jmap_test.go b/pkg/jmap/jmap_test.go
index cd3fcdd2e..f77339c82 100644
--- a/pkg/jmap/jmap_test.go
+++ b/pkg/jmap/jmap_test.go
@@ -117,10 +117,10 @@ func NewTestJmapBlobClient(t *testing.T) BlobClient {
return &TestJmapBlobClient{t: t}
}
-func (t TestJmapBlobClient) UploadBinary(ctx context.Context, logger *log.Logger, session *Session, uploadUrl string, endpoint string, contentType string, body io.Reader) (UploadedBlob, Error) {
+func (t TestJmapBlobClient) UploadBinary(ctx context.Context, logger *log.Logger, session *Session, uploadUrl string, endpoint string, contentType string, acceptLanguage string, body io.Reader) (UploadedBlob, Language, Error) {
bytes, err := io.ReadAll(body)
if err != nil {
- return UploadedBlob{}, SimpleError{code: 0, err: err}
+ return UploadedBlob{}, "", SimpleError{code: 0, err: err}
}
hasher := sha512.New()
hasher.Write(bytes)
@@ -129,17 +129,17 @@ func (t TestJmapBlobClient) UploadBinary(ctx context.Context, logger *log.Logger
Size: len(bytes),
Type: contentType,
Sha512: base64.StdEncoding.EncodeToString(hasher.Sum(nil)),
- }, nil
+ }, "", nil
}
-func (h *TestJmapBlobClient) DownloadBinary(ctx context.Context, logger *log.Logger, session *Session, downloadUrl string, endpoint string) (*BlobDownload, Error) {
+func (h *TestJmapBlobClient) DownloadBinary(ctx context.Context, logger *log.Logger, session *Session, downloadUrl string, endpoint string, acceptLanguage string) (*BlobDownload, Language, Error) {
return &BlobDownload{
Body: io.NopCloser(strings.NewReader("")),
Size: -1,
Type: "text/plain",
ContentDisposition: "attachment; filename=\"file.txt\"",
CacheControl: "",
- }, nil
+ }, "", nil
}
func (t TestJmapBlobClient) Close() error {
@@ -164,24 +164,24 @@ func (t TestWsClientFactory) Close() error {
return nil
}
-func serveTestFile(t *testing.T, name string) ([]byte, Error) {
+func serveTestFile(t *testing.T, name string) ([]byte, Language, Error) {
cwd, _ := os.Getwd()
p := filepath.Join(cwd, "testdata", name)
bytes, err := os.ReadFile(p)
if err != nil {
- return bytes, SimpleError{code: 0, err: err}
+ return bytes, "", SimpleError{code: 0, err: err}
}
// try to parse it first to avoid any deeper issues that are caused by the test tools
var target map[string]any
err = json.Unmarshal(bytes, &target)
if err != nil {
t.Errorf("failed to parse JSON test data file '%v': %v", p, err)
- return nil, SimpleError{code: 0, err: err}
+ return nil, "", SimpleError{code: 0, err: err}
}
- return bytes, nil
+ return bytes, "", nil
}
-func (t *TestJmapApiClient) Command(ctx context.Context, logger *log.Logger, session *Session, request Request) ([]byte, Error) {
+func (t *TestJmapApiClient) Command(ctx context.Context, logger *log.Logger, session *Session, request Request, acceptLanguage string) ([]byte, Language, Error) {
command := request.MethodCalls[0].Command
switch command {
case CommandMailboxGet:
@@ -190,7 +190,7 @@ func (t *TestJmapApiClient) Command(ctx context.Context, logger *log.Logger, ses
return serveTestFile(t.t, "mails1.json")
default:
require.Fail(t.t, "TestJmapApiClient: unsupported jmap command: %v", command)
- return nil, SimpleError{code: 0, err: fmt.Errorf("TestJmapApiClient: unsupported jmap command: %v", command)}
+ return nil, "", SimpleError{code: 0, err: fmt.Errorf("TestJmapApiClient: unsupported jmap command: %v", command)}
}
}
@@ -231,7 +231,7 @@ 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")
@@ -239,7 +239,7 @@ func TestRequests(t *testing.T) {
require.Len(folders.Mailboxes, 5)
require.NotEmpty(sessionState)
- emails, sessionState, err := client.GetAllEmailsInMailbox("a", &session, ctx, &logger, "Inbox", 0, 0, true, 0)
+ emails, sessionState, _, err := client.GetAllEmailsInMailbox("a", &session, ctx, &logger, "", "Inbox", 0, 0, true, 0)
require.NoError(err)
require.Len(emails.Emails, 3)
require.NotEmpty(sessionState)
diff --git a/pkg/jmap/jmap_tools.go b/pkg/jmap/jmap_tools.go
index 4db5c4709..6f2337eab 100644
--- a/pkg/jmap/jmap_tools.go
+++ b/pkg/jmap/jmap_tools.go
@@ -57,12 +57,13 @@ func command[T any](api ApiClient,
session *Session,
sessionOutdatedHandler func(session *Session, newState SessionState),
request Request,
- mapper func(body *Response) (T, Error)) (T, SessionState, Error) {
+ acceptLanguage string,
+ mapper func(body *Response) (T, Error)) (T, SessionState, Language, Error) {
- responseBody, jmapErr := api.Command(ctx, logger, session, request)
+ responseBody, language, jmapErr := api.Command(ctx, logger, session, request, acceptLanguage)
if jmapErr != nil {
var zero T
- return zero, "", 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, "", 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, 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, SimpleError{code: code, err: err}
+ return zero, response.SessionState, language, SimpleError{code: code, err: err}
}
}
}
result, jerr := mapper(&response)
sessionState := response.SessionState
- return result, sessionState, jerr
+ return result, sessionState, language, jerr
}
func mapstructStringToTimeHook() mapstructure.DecodeHookFunc {
diff --git a/pkg/jmap/jmap_tools_test.go b/pkg/jmap/jmap_tools_test.go
index 2c0beea6d..16a1b0b01 100644
--- a/pkg/jmap/jmap_tools_test.go
+++ b/pkg/jmap/jmap_tools_test.go
@@ -9,7 +9,7 @@ import (
func TestDeserializeMailboxGetResponse(t *testing.T) {
require := require.New(t)
- jsonBytes, jmapErr := serveTestFile(t, "mailboxes1.json")
+ jsonBytes, _, jmapErr := serveTestFile(t, "mailboxes1.json")
require.NoError(jmapErr)
var data Response
err := json.Unmarshal(jsonBytes, &data)
@@ -66,7 +66,7 @@ func TestDeserializeMailboxGetResponse(t *testing.T) {
func TestDeserializeEmailGetResponse(t *testing.T) {
require := require.New(t)
- jsonBytes, jmapErr := serveTestFile(t, "mails1.json")
+ jsonBytes, _, jmapErr := serveTestFile(t, "mails1.json")
require.NoError(jmapErr)
var data Response
err := json.Unmarshal(jsonBytes, &data)
diff --git a/services/groupware/apidoc.yml b/services/groupware/apidoc.yml
index d0fd05764..0becebd89 100644
--- a/services/groupware/apidoc.yml
+++ b/services/groupware/apidoc.yml
@@ -35,6 +35,9 @@ tags:
- name: task
x-displayName: Tasks
description: APIs about tasks
+ - name: quota
+ x-displayName: Quota
+ description: APIs about quotas
- name: vacation
x-displayName: Vacation Responses
description: APIs about vacation responses
@@ -63,6 +66,9 @@ x-tagGroups:
tags:
- tasklist
- task
+ - name: Quotas
+ tags:
+ - quota
components:
securitySchemes:
api:
diff --git a/services/groupware/pkg/groupware/groupware_api_account.go b/services/groupware/pkg/groupware/groupware_api_account.go
index 10500fb34..613c4a389 100644
--- a/services/groupware/pkg/groupware/groupware_api_account.go
+++ b/services/groupware/pkg/groupware/groupware_api_account.go
@@ -32,7 +32,7 @@ func (g *Groupware) GetAccount(w http.ResponseWriter, r *http.Request) {
if err != nil {
return errorResponse(err)
}
- return response(account, req.session.State)
+ return response(account, req.session.State, "")
})
}
@@ -54,7 +54,7 @@ type SwaggerGetAccountsResponse struct {
// 500: ErrorResponse500
func (g *Groupware) GetAccounts(w http.ResponseWriter, r *http.Request) {
g.respond(w, r, func(req Request) Response {
- return response(req.session.Accounts, req.session.State)
+ return response(req.session.Accounts, req.session.State, "")
})
}
@@ -107,7 +107,7 @@ func (g *Groupware) GetAccountBootstrap(w http.ResponseWriter, r *http.Request)
logger := log.From(req.logger.With().Str(logAccountId, mailAccountId))
accountIds := structs.Keys(req.session.Accounts)
- resp, sessionState, jerr := g.jmap.GetIdentitiesAndMailboxes(mailAccountId, accountIds, req.session, req.ctx, logger)
+ resp, sessionState, lang, jerr := g.jmap.GetIdentitiesAndMailboxes(mailAccountId, accountIds, req.session, req.ctx, logger, req.language())
if jerr != nil {
return req.errorResponseFromJmap(jerr)
}
@@ -121,6 +121,6 @@ func (g *Groupware) GetAccountBootstrap(w http.ResponseWriter, r *http.Request)
Mailboxes: map[string][]jmap.Mailbox{
mailAccountId: resp.Mailboxes,
},
- }, sessionState)
+ }, sessionState, lang)
})
}
diff --git a/services/groupware/pkg/groupware/groupware_api_blob.go b/services/groupware/pkg/groupware/groupware_api_blob.go
index 6187a585f..e47930365 100644
--- a/services/groupware/pkg/groupware/groupware_api_blob.go
+++ b/services/groupware/pkg/groupware/groupware_api_blob.go
@@ -28,7 +28,7 @@ func (g *Groupware) GetBlobMeta(w http.ResponseWriter, r *http.Request) {
}
logger := log.From(req.logger.With().Str(logAccountId, accountId))
- res, sessionState, jerr := g.jmap.GetBlobMetadata(accountId, req.session, req.ctx, logger, blobId)
+ res, sessionState, lang, jerr := g.jmap.GetBlobMetadata(accountId, req.session, req.ctx, logger, req.language(), blobId)
if jerr != nil {
return req.errorResponseFromJmap(jerr)
}
@@ -38,9 +38,9 @@ func (g *Groupware) GetBlobMeta(w http.ResponseWriter, r *http.Request) {
}
digest := blob.Digest()
if digest != "" {
- return etagResponse(res, sessionState, jmap.State(digest))
+ return etagResponse(res, sessionState, jmap.State(digest), lang)
} else {
- return response(res, sessionState)
+ return response(res, sessionState, lang)
}
})
}
@@ -64,12 +64,12 @@ func (g *Groupware) UploadBlob(w http.ResponseWriter, r *http.Request) {
}
logger := log.From(req.logger.With().Str(logAccountId, accountId))
- resp, jerr := g.jmap.UploadBlobStream(accountId, req.session, req.ctx, logger, contentType, body)
+ resp, lang, jerr := g.jmap.UploadBlobStream(accountId, req.session, req.ctx, logger, contentType, req.language(), body)
if jerr != nil {
return req.errorResponseFromJmap(jerr)
}
- return etagOnlyResponse(resp, jmap.State(resp.Sha512))
+ return etagOnlyResponse(resp, jmap.State(resp.Sha512), lang)
})
}
@@ -89,7 +89,7 @@ func (g *Groupware) DownloadBlob(w http.ResponseWriter, r *http.Request) {
}
logger := log.From(req.logger.With().Str(logAccountId, accountId))
- blob, jerr := g.jmap.DownloadBlobStream(accountId, blobId, name, typ, req.session, req.ctx, logger)
+ blob, lang, jerr := g.jmap.DownloadBlobStream(accountId, blobId, name, typ, req.session, req.ctx, logger, req.language())
if blob != nil && blob.Body != nil {
defer func(Body io.ReadCloser) {
err := Body.Close()
@@ -118,6 +118,9 @@ func (g *Groupware) DownloadBlob(w http.ResponseWriter, r *http.Request) {
if blob.Size >= 0 {
w.Header().Add("Content-Size", strconv.Itoa(blob.Size))
}
+ if lang != "" {
+ w.Header().Add("Content-Language", string(lang))
+ }
_, err := io.Copy(w, blob.Body)
if err != nil {
diff --git a/services/groupware/pkg/groupware/groupware_api_calendars.go b/services/groupware/pkg/groupware/groupware_api_calendars.go
index 732b78cd5..e7f4ba7ba 100644
--- a/services/groupware/pkg/groupware/groupware_api_calendars.go
+++ b/services/groupware/pkg/groupware/groupware_api_calendars.go
@@ -31,7 +31,7 @@ func (g *Groupware) GetCalendars(w http.ResponseWriter, r *http.Request) {
}
var _ string = accountId
- return response(AllCalendars, req.session.State)
+ return response(AllCalendars, req.session.State, "")
})
}
@@ -65,7 +65,7 @@ func (g *Groupware) GetCalendarById(w http.ResponseWriter, r *http.Request) {
// TODO replace with proper implementation
for _, calendar := range AllCalendars {
if calendar.Id == calendarId {
- return response(calendar, req.session.State)
+ return response(calendar, req.session.State, "")
}
}
return notFoundResponse(req.session.State)
@@ -102,6 +102,6 @@ func (g *Groupware) GetEventsInCalendar(w http.ResponseWriter, r *http.Request)
if !ok {
return notFoundResponse(req.session.State)
}
- return response(events, req.session.State)
+ return response(events, req.session.State, "")
})
}
diff --git a/services/groupware/pkg/groupware/groupware_api_contacts.go b/services/groupware/pkg/groupware/groupware_api_contacts.go
index 75579d3e7..433600269 100644
--- a/services/groupware/pkg/groupware/groupware_api_contacts.go
+++ b/services/groupware/pkg/groupware/groupware_api_contacts.go
@@ -33,7 +33,7 @@ func (g *Groupware) GetAddressbooks(w http.ResponseWriter, r *http.Request) {
var _ string = accountId
// TODO replace with proper implementation
- return response(AllAddressBooks, req.session.State)
+ return response(AllAddressBooks, req.session.State, "")
})
}
@@ -67,7 +67,7 @@ func (g *Groupware) GetAddressbook(w http.ResponseWriter, r *http.Request) {
// TODO replace with proper implementation
for _, ab := range AllAddressBooks {
if ab.Id == addressBookId {
- return response(ab, req.session.State)
+ return response(ab, req.session.State, "")
}
}
return notFoundResponse(req.session.State)
@@ -104,6 +104,6 @@ func (g *Groupware) GetContactsInAddressbook(w http.ResponseWriter, r *http.Requ
if !ok {
return notFoundResponse(req.session.State)
}
- return response(contactCards, req.session.State)
+ return response(contactCards, req.session.State, "")
})
}
diff --git a/services/groupware/pkg/groupware/groupware_api_emails.go b/services/groupware/pkg/groupware/groupware_api_emails.go
index f676e717e..b08e337cf 100644
--- a/services/groupware/pkg/groupware/groupware_api_emails.go
+++ b/services/groupware/pkg/groupware/groupware_api_emails.go
@@ -77,12 +77,12 @@ func (g *Groupware) GetAllEmailsInMailbox(w http.ResponseWriter, r *http.Request
logger := log.From(req.logger.With().Str(HeaderSince, since).Str(logAccountId, accountId))
- emails, sessionState, jerr := g.jmap.GetMailboxChanges(accountId, req.session, req.ctx, logger, mailboxId, since, true, g.maxBodyValueBytes, maxChanges)
+ emails, sessionState, 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(emails, sessionState, emails.State)
+ return etagResponse(emails, sessionState, emails.State, lang)
})
} else {
g.respond(w, r, func(req Request) Response {
@@ -114,12 +114,12 @@ func (g *Groupware) GetAllEmailsInMailbox(w http.ResponseWriter, r *http.Request
logger := log.From(l)
- emails, sessionState, jerr := g.jmap.GetAllEmailsInMailbox(accountId, req.session, req.ctx, logger, mailboxId, offset, limit, true, g.maxBodyValueBytes)
+ emails, sessionState, lang, jerr := g.jmap.GetAllEmailsInMailbox(accountId, req.session, req.ctx, logger, req.language(), mailboxId, offset, limit, true, g.maxBodyValueBytes)
if jerr != nil {
return req.errorResponseFromJmap(jerr)
}
- return etagResponse(emails, sessionState, emails.State)
+ return etagResponse(emails, sessionState, emails.State, lang)
})
}
}
@@ -140,25 +140,25 @@ func (g *Groupware) GetEmailsById(w http.ResponseWriter, r *http.Request) {
l := req.logger.With().Str(logAccountId, log.SafeString(accountId))
if len(ids) == 1 {
logger := log.From(l.Str("id", log.SafeString(id)))
- emails, sessionState, jerr := g.jmap.GetEmails(accountId, req.session, req.ctx, logger, ids, true, g.maxBodyValueBytes)
+ emails, sessionState, lang, jerr := g.jmap.GetEmails(accountId, req.session, req.ctx, logger, req.language(), ids, true, g.maxBodyValueBytes)
if jerr != nil {
return req.errorResponseFromJmap(jerr)
}
if len(emails.Emails) < 1 {
return notFoundResponse(sessionState)
} else {
- return etagResponse(emails.Emails[0], sessionState, emails.State)
+ return etagResponse(emails.Emails[0], sessionState, emails.State, lang)
}
} else {
logger := log.From(l.Array("ids", log.SafeStringArray(ids)))
- emails, sessionState, jerr := g.jmap.GetEmails(accountId, req.session, req.ctx, logger, ids, true, g.maxBodyValueBytes)
+ emails, sessionState, lang, jerr := g.jmap.GetEmails(accountId, req.session, req.ctx, logger, req.language(), ids, true, g.maxBodyValueBytes)
if jerr != nil {
return req.errorResponseFromJmap(jerr)
}
if len(emails.Emails) < 1 {
return notFoundResponse(sessionState)
} else {
- return etagResponse(emails.Emails, sessionState, emails.State)
+ return etagResponse(emails.Emails, sessionState, emails.State, lang)
}
}
})
@@ -196,7 +196,7 @@ func (g *Groupware) GetEmailAttachments(w http.ResponseWriter, r *http.Request)
}
l := req.logger.With().Str(logAccountId, accountId)
logger := log.From(l)
- emails, sessionState, jerr := g.jmap.GetEmails(accountId, req.session, req.ctx, logger, []string{id}, false, 0)
+ emails, sessionState, lang, jerr := g.jmap.GetEmails(accountId, req.session, req.ctx, logger, req.language(), []string{id}, false, 0)
if jerr != nil {
return req.errorResponseFromJmap(jerr)
}
@@ -204,7 +204,7 @@ func (g *Groupware) GetEmailAttachments(w http.ResponseWriter, r *http.Request)
return notFoundResponse(sessionState)
}
email := emails.Emails[0]
- return etagResponse(email.Attachments, sessionState, emails.State)
+ return etagResponse(email.Attachments, sessionState, emails.State, lang)
})
} else {
g.stream(w, r, func(req Request, w http.ResponseWriter) *Error {
@@ -221,7 +221,7 @@ func (g *Groupware) GetEmailAttachments(w http.ResponseWriter, r *http.Request)
l = contextAppender(l)
logger := log.From(l)
- emails, _, jerr := g.jmap.GetEmails(mailAccountId, req.session, req.ctx, logger, []string{id}, false, 0)
+ emails, _, lang, jerr := g.jmap.GetEmails(mailAccountId, req.session, req.ctx, logger, req.language(), []string{id}, false, 0)
if jerr != nil {
return req.apiErrorFromJmap(req.observeJmapError(jerr))
}
@@ -241,7 +241,7 @@ func (g *Groupware) GetEmailAttachments(w http.ResponseWriter, r *http.Request)
return nil
}
- blob, jerr := g.jmap.DownloadBlobStream(blobAccountId, attachment.BlobId, attachment.Name, attachment.Type, req.session, req.ctx, logger)
+ blob, lang, jerr := g.jmap.DownloadBlobStream(blobAccountId, attachment.BlobId, attachment.Name, attachment.Type, req.session, req.ctx, logger, req.language())
if blob != nil && blob.Body != nil {
defer func(Body io.ReadCloser) {
err := Body.Close()
@@ -270,7 +270,9 @@ func (g *Groupware) GetEmailAttachments(w http.ResponseWriter, r *http.Request)
if blob.Size >= 0 {
w.Header().Add("Content-Size", strconv.Itoa(blob.Size))
}
-
+ if lang != "" {
+ w.Header().Add("Content-Language", string(lang))
+ }
_, err := io.Copy(w, blob.Body)
if err != nil {
return req.observedParameterError(ErrorStreamingResponse)
@@ -300,12 +302,12 @@ func (g *Groupware) getEmailsSince(w http.ResponseWriter, r *http.Request, since
logger := log.From(l)
- emails, sessionState, jerr := g.jmap.GetEmailsSince(accountId, req.session, req.ctx, logger, since, true, g.maxBodyValueBytes, maxChanges)
+ emails, sessionState, 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(emails, sessionState, emails.State)
+ return etagResponse(emails, sessionState, emails.State, lang)
})
}
@@ -497,7 +499,7 @@ func (g *Groupware) searchEmails(w http.ResponseWriter, r *http.Request) {
}
logger = log.From(logger.With().Str(logAccountId, accountId))
- results, sessionState, jerr := g.jmap.QueryEmailsWithSnippets(accountId, filter, req.session, req.ctx, logger, offset, limit, fetchBodies, g.maxBodyValueBytes)
+ results, sessionState, lang, jerr := g.jmap.QueryEmailsWithSnippets(accountId, filter, req.session, req.ctx, logger, req.language(), offset, limit, fetchBodies, g.maxBodyValueBytes)
if jerr != nil {
return req.errorResponseFromJmap(jerr)
}
@@ -522,7 +524,7 @@ func (g *Groupware) searchEmails(w http.ResponseWriter, r *http.Request) {
Total: results.Total,
Limit: results.Limit,
QueryState: results.QueryState,
- }, sessionState, results.QueryState)
+ }, sessionState, results.QueryState, lang)
} else {
accountId, err := req.GetAccountIdForMail()
if err != nil {
@@ -530,7 +532,7 @@ func (g *Groupware) searchEmails(w http.ResponseWriter, r *http.Request) {
}
logger = log.From(logger.With().Str(logAccountId, accountId))
- results, sessionState, jerr := g.jmap.QueryEmailSnippets(accountId, filter, req.session, req.ctx, logger, offset, limit)
+ results, sessionState, lang, jerr := g.jmap.QueryEmailSnippets(accountId, filter, req.session, req.ctx, logger, req.language(), offset, limit)
if jerr != nil {
return req.errorResponseFromJmap(jerr)
}
@@ -540,7 +542,7 @@ func (g *Groupware) searchEmails(w http.ResponseWriter, r *http.Request) {
Total: results.Total,
Limit: results.Limit,
QueryState: results.QueryState,
- }, sessionState, results.QueryState)
+ }, sessionState, results.QueryState, lang)
}
})
}
@@ -608,12 +610,12 @@ func (g *Groupware) CreateEmail(w http.ResponseWriter, r *http.Request) {
BodyValues: body.BodyValues,
}
- created, sessionState, jerr := g.jmap.CreateEmail(accountId, create, req.session, req.ctx, logger)
+ created, sessionState, 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)
+ return response(created.Email, sessionState, lang)
})
}
@@ -642,7 +644,7 @@ func (g *Groupware) UpdateEmail(w http.ResponseWriter, r *http.Request) {
emailId: body,
}
- result, sessionState, jerr := g.jmap.UpdateEmails(accountId, updates, req.session, req.ctx, logger)
+ result, sessionState, lang, jerr := g.jmap.UpdateEmails(accountId, updates, req.session, req.ctx, logger, req.language())
if jerr != nil {
return req.errorResponseFromJmap(jerr)
}
@@ -657,7 +659,7 @@ func (g *Groupware) UpdateEmail(w http.ResponseWriter, r *http.Request) {
"An internal API behaved unexpectedly: wrong Email update ID response from JMAP endpoint")))
}
- return response(updatedEmail, sessionState)
+ return response(updatedEmail, sessionState, lang)
})
}
@@ -677,7 +679,7 @@ func (g *Groupware) DeleteEmail(w http.ResponseWriter, r *http.Request) {
logger := log.From(l)
- _, sessionState, jerr := g.jmap.DeleteEmails(accountId, []string{emailId}, req.session, req.ctx, logger)
+ _, sessionState, _, jerr := g.jmap.DeleteEmails(accountId, []string{emailId}, req.session, req.ctx, logger, req.language())
if jerr != nil {
return req.errorResponseFromJmap(jerr)
}
@@ -687,14 +689,16 @@ func (g *Groupware) DeleteEmail(w http.ResponseWriter, r *http.Request) {
}
type AboutEmailsEvent struct {
- Id string `json:"id"`
- Source string `json:"source"`
- Emails []jmap.Email `json:"emails"`
+ Id string `json:"id"`
+ Source string `json:"source"`
+ Emails []jmap.Email `json:"emails"`
+ Language jmap.Language `json:"lang"`
}
type AboutEmailResponse struct {
- Email jmap.Email `json:"email"`
- RequestId string `json:"requestId"`
+ Email jmap.Email `json:"email"`
+ RequestId string `json:"requestId"`
+ Language jmap.Language `json:"lang"`
}
func relatedEmails(email jmap.Email, beacon time.Time, days uint) jmap.EmailFilterElement {
@@ -766,7 +770,7 @@ func (g *Groupware) RelatedToEmail(w http.ResponseWriter, r *http.Request) {
reqId := req.GetRequestId()
getEmailsBefore := time.Now()
- emails, sessionState, jerr := g.jmap.GetEmails(accountId, req.session, req.ctx, logger, []string{id}, true, g.maxBodyValueBytes)
+ emails, sessionState, lang, jerr := g.jmap.GetEmails(accountId, req.session, req.ctx, logger, req.language(), []string{id}, true, g.maxBodyValueBytes)
getEmailsDuration := time.Since(getEmailsBefore)
if jerr != nil {
return req.errorResponseFromJmap(jerr)
@@ -791,7 +795,7 @@ func (g *Groupware) RelatedToEmail(w http.ResponseWriter, r *http.Request) {
g.job(logger, RelationTypeSameSender, func(jobId uint64, l *log.Logger) {
before := time.Now()
- results, _, jerr := g.jmap.QueryEmails(accountId, filter, req.session, bgctx, l, 0, limit, false, g.maxBodyValueBytes)
+ results, _, lang, jerr := g.jmap.QueryEmails(accountId, filter, req.session, bgctx, l, req.language(), 0, limit, false, g.maxBodyValueBytes)
duration := time.Since(before)
if jerr != nil {
req.observeJmapError(jerr)
@@ -801,14 +805,14 @@ func (g *Groupware) RelatedToEmail(w http.ResponseWriter, r *http.Request) {
related := filterEmails(results.Emails, email)
l.Trace().Msgf("'%v' found %v other emails", RelationTypeSameSender, len(related))
if len(related) > 0 {
- req.push(RelationEntityEmail, AboutEmailsEvent{Id: reqId, Emails: related, Source: RelationTypeSameSender})
+ req.push(RelationEntityEmail, AboutEmailsEvent{Id: reqId, Emails: related, Source: RelationTypeSameSender, Language: lang})
}
}
})
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, 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)
@@ -818,7 +822,7 @@ func (g *Groupware) RelatedToEmail(w http.ResponseWriter, r *http.Request) {
related := filterEmails(emails, email)
l.Trace().Msgf("'%v' found %v other emails", RelationTypeSameThread, len(related))
if len(related) > 0 {
- req.push(RelationEntityEmail, AboutEmailsEvent{Id: reqId, Emails: related, Source: RelationTypeSameThread})
+ req.push(RelationEntityEmail, AboutEmailsEvent{Id: reqId, Emails: related, Source: RelationTypeSameThread, Language: lang})
}
}
})
@@ -826,7 +830,7 @@ func (g *Groupware) RelatedToEmail(w http.ResponseWriter, r *http.Request) {
return etagResponse(AboutEmailResponse{
Email: email,
RequestId: reqId,
- }, sessionState, emails.State)
+ }, sessionState, emails.State, lang)
})
}
@@ -1113,7 +1117,7 @@ func (g *Groupware) GetLatestEmailsSummaryForAllAccounts(w http.ResponseWriter,
logger := log.From(l)
- emailsSummariesByAccount, sessionState, jerr := g.jmap.QueryEmailSummaries(allAccountIds, req.session, req.ctx, logger, filter, limit)
+ emailsSummariesByAccount, sessionState, lang, jerr := g.jmap.QueryEmailSummaries(allAccountIds, req.session, req.ctx, logger, req.language(), filter, limit)
if jerr != nil {
return req.errorResponseFromJmap(jerr)
}
@@ -1141,7 +1145,7 @@ func (g *Groupware) GetLatestEmailsSummaryForAllAccounts(w http.ResponseWriter,
summaries[i] = summarizeEmail(all[i].accountId, all[i].email)
}
- return response(summaries, sessionState)
+ return response(summaries, sessionState, lang)
})
}
diff --git a/services/groupware/pkg/groupware/groupware_api_identity.go b/services/groupware/pkg/groupware/groupware_api_identity.go
index 2c77a41a2..1cdd1618b 100644
--- a/services/groupware/pkg/groupware/groupware_api_identity.go
+++ b/services/groupware/pkg/groupware/groupware_api_identity.go
@@ -32,10 +32,10 @@ func (g *Groupware) GetIdentities(w http.ResponseWriter, r *http.Request) {
return errorResponse(err)
}
logger := log.From(req.logger.With().Str(logAccountId, accountId))
- res, sessionState, jerr := g.jmap.GetIdentity(accountId, req.session, req.ctx, logger)
+ res, sessionState, lang, jerr := g.jmap.GetIdentity(accountId, req.session, req.ctx, logger, req.language())
if jerr != nil {
return req.errorResponseFromJmap(jerr)
}
- return etagResponse(res, sessionState, res.State)
+ return etagResponse(res, sessionState, res.State, lang)
})
}
diff --git a/services/groupware/pkg/groupware/groupware_api_index.go b/services/groupware/pkg/groupware/groupware_api_index.go
index 2adcb34d9..b5594e5f1 100644
--- a/services/groupware/pkg/groupware/groupware_api_index.go
+++ b/services/groupware/pkg/groupware/groupware_api_index.go
@@ -152,7 +152,7 @@ func (g *Groupware) Index(w http.ResponseWriter, r *http.Request) {
g.respond(w, r, func(req Request) Response {
accountIds := structs.Keys(req.session.Accounts)
- identitiesResponse, sessionState, err := g.jmap.GetIdentities(accountIds, req.session, req.ctx, req.logger)
+ identitiesResponse, sessionState, lang, err := g.jmap.GetIdentities(accountIds, req.session, req.ctx, req.logger, req.language())
if err != nil {
return req.errorResponseFromJmap(err)
}
@@ -163,7 +163,7 @@ func (g *Groupware) Index(w http.ResponseWriter, r *http.Request) {
Limits: buildIndexLimits(req.session),
Accounts: buildIndexAccount(req.session, identitiesResponse.Identities),
PrimaryAccounts: buildIndexPrimaryAccounts(req.session),
- }, sessionState)
+ }, sessionState, lang)
})
}
diff --git a/services/groupware/pkg/groupware/groupware_api_mailbox.go b/services/groupware/pkg/groupware/groupware_api_mailbox.go
index 2472e6a21..9cb5d5749 100644
--- a/services/groupware/pkg/groupware/groupware_api_mailbox.go
+++ b/services/groupware/pkg/groupware/groupware_api_mailbox.go
@@ -41,13 +41,13 @@ func (g *Groupware) GetMailbox(w http.ResponseWriter, r *http.Request) {
return errorResponse(err)
}
- mailboxes, sessionState, jerr := g.jmap.GetMailbox(accountId, req.session, req.ctx, req.logger, []string{mailboxId})
+ mailboxes, sessionState, 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)
+ return etagResponse(mailboxes.Mailboxes[0], sessionState, mailboxes.State, lang)
} else {
return notFoundResponse(sessionState)
}
@@ -123,25 +123,25 @@ func (g *Groupware) GetMailboxes(w http.ResponseWriter, r *http.Request) {
logger := log.From(req.logger.With().Str(logAccountId, accountId))
if hasCriteria {
- mailboxesByAccountId, sessionState, err := g.jmap.SearchMailboxes([]string{accountId}, req.session, req.ctx, logger, filter)
+ mailboxesByAccountId, sessionState, lang, err := g.jmap.SearchMailboxes([]string{accountId}, req.session, req.ctx, logger, req.language(), filter)
if err != nil {
return req.errorResponseFromJmap(err)
}
mailboxes, ok := mailboxesByAccountId[accountId]
if ok {
- return etagResponse(mailboxes.Mailboxes, sessionState, mailboxes.State)
+ return etagResponse(mailboxes.Mailboxes, sessionState, mailboxes.State, lang)
} else {
return notFoundResponse(sessionState)
}
} else {
- mailboxesByAccountId, sessionState, err := g.jmap.GetAllMailboxes([]string{accountId}, req.session, req.ctx, logger)
+ mailboxesByAccountId, sessionState, lang, err := g.jmap.GetAllMailboxes([]string{accountId}, req.session, req.ctx, logger, req.language())
if err != nil {
return req.errorResponseFromJmap(err)
}
mailboxes, ok := mailboxesByAccountId[accountId]
if ok {
- return etagResponse(mailboxes.Mailboxes, sessionState, mailboxes.State)
+ return etagResponse(mailboxes.Mailboxes, sessionState, mailboxes.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, err := g.jmap.SearchMailboxes(accountIds, req.session, req.ctx, logger, filter)
+ mailboxesByAccountId, sessionState, lang, err := g.jmap.SearchMailboxes(accountIds, req.session, req.ctx, logger, req.language(), filter)
if err != nil {
return req.errorResponseFromJmap(err)
}
- return response(mailboxesByAccountId, sessionState)
+ return response(mailboxesByAccountId, sessionState, lang)
} else {
- mailboxesByAccountId, sessionState, err := g.jmap.GetAllMailboxes(accountIds, req.session, req.ctx, logger)
+ mailboxesByAccountId, sessionState, lang, err := g.jmap.GetAllMailboxes(accountIds, req.session, req.ctx, logger, req.language())
if err != nil {
return req.errorResponseFromJmap(err)
}
- return response(mailboxesByAccountId, sessionState)
+ return response(mailboxesByAccountId, sessionState, lang)
}
})
}
@@ -221,11 +221,11 @@ func (g *Groupware) GetMailboxByRoleForAllAccounts(w http.ResponseWriter, r *htt
Role: role,
}
- mailboxesByAccountId, sessionState, err := g.jmap.SearchMailboxes(accountIds, req.session, req.ctx, logger, filter)
+ mailboxesByAccountId, sessionState, lang, err := g.jmap.SearchMailboxes(accountIds, req.session, req.ctx, logger, req.language(), filter)
if err != nil {
return req.errorResponseFromJmap(err)
}
- return response(mailboxesByAccountId, sessionState)
+ return response(mailboxesByAccountId, sessionState, lang)
})
}
@@ -267,12 +267,12 @@ func (g *Groupware) GetMailboxChanges(w http.ResponseWriter, r *http.Request) {
logger := log.From(l)
- changes, sessionState, jerr := g.jmap.GetMailboxChanges(accountId, req.session, req.ctx, logger, mailboxId, sinceState, true, g.maxBodyValueBytes, maxChanges)
+ changes, sessionState, 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)
+ return etagResponse(changes, sessionState, changes.State, lang)
})
}
@@ -320,12 +320,12 @@ func (g *Groupware) GetMailboxChangesForAllAccounts(w http.ResponseWriter, r *ht
logger := log.From(l)
- changesByAccountId, sessionState, jerr := g.jmap.GetMailboxChangesForMultipleAccounts(allAccountIds, req.session, req.ctx, logger, sinceStateMap, true, g.maxBodyValueBytes, maxChanges)
+ changesByAccountId, sessionState, 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)
+ return response(changesByAccountId, sessionState, lang)
})
}
@@ -336,11 +336,11 @@ func (g *Groupware) GetMailboxRoles(w http.ResponseWriter, r *http.Request) {
l.Array(logAccountId, log.SafeStringArray(allAccountIds))
logger := log.From(l)
- rolesByAccountId, sessionState, jerr := g.jmap.GetMailboxRolesForMultipleAccounts(allAccountIds, req.session, req.ctx, logger)
+ rolesByAccountId, sessionState, lang, jerr := g.jmap.GetMailboxRolesForMultipleAccounts(allAccountIds, req.session, req.ctx, logger, req.language())
if jerr != nil {
return req.errorResponseFromJmap(jerr)
}
- return response(rolesByAccountId, sessionState)
+ return response(rolesByAccountId, sessionState, lang)
})
}
diff --git a/services/groupware/pkg/groupware/groupware_api_quota.go b/services/groupware/pkg/groupware/groupware_api_quota.go
new file mode 100644
index 000000000..eebf6c148
--- /dev/null
+++ b/services/groupware/pkg/groupware/groupware_api_quota.go
@@ -0,0 +1,39 @@
+package groupware
+
+import (
+ "net/http"
+
+ "github.com/opencloud-eu/opencloud/pkg/jmap"
+ "github.com/opencloud-eu/opencloud/pkg/log"
+)
+
+// When the request succeeds.
+// swagger:response GetQuotaResponse200
+type SwaggerGetQuotaResponse200 struct {
+ // in: body
+ Body []jmap.Quota
+}
+
+// swagger:route GET /groupware/accounts/{account}/quota quota getquota
+// Get quota limits.
+//
+// responses:
+//
+// 200: GetQuotaResponse200
+// 400: ErrorResponse400
+// 500: ErrorResponse500
+func (g *Groupware) GetQuota(w http.ResponseWriter, r *http.Request) {
+ g.respond(w, r, func(req Request) Response {
+ accountId, err := req.GetAccountIdForQuota()
+ if err != nil {
+ return errorResponse(err)
+ }
+ logger := log.From(req.logger.With().Str(logAccountId, accountId))
+
+ res, sessionState, lang, jerr := g.jmap.GetQuotas(accountId, req.session, req.ctx, logger, req.language())
+ if jerr != nil {
+ return req.errorResponseFromJmap(jerr)
+ }
+ return etagResponse(res.List, sessionState, res.State, lang)
+ })
+}
diff --git a/services/groupware/pkg/groupware/groupware_api_tasklists.go b/services/groupware/pkg/groupware/groupware_api_tasklists.go
index e1b9511f7..4ffb6bc80 100644
--- a/services/groupware/pkg/groupware/groupware_api_tasklists.go
+++ b/services/groupware/pkg/groupware/groupware_api_tasklists.go
@@ -31,7 +31,7 @@ func (g *Groupware) GetTaskLists(w http.ResponseWriter, r *http.Request) {
}
var _ string = accountId
- return response(AllTaskLists, req.session.State)
+ return response(AllTaskLists, req.session.State, "")
})
}
@@ -65,7 +65,7 @@ func (g *Groupware) GetTaskListById(w http.ResponseWriter, r *http.Request) {
// TODO replace with proper implementation
for _, tasklist := range AllTaskLists {
if tasklist.Id == tasklistId {
- return response(tasklist, req.session.State)
+ return response(tasklist, req.session.State, "")
}
}
return notFoundResponse(req.session.State)
@@ -102,6 +102,6 @@ func (g *Groupware) GetTasksInTaskList(w http.ResponseWriter, r *http.Request) {
if !ok {
return notFoundResponse(req.session.State)
}
- return response(tasks, req.session.State)
+ return response(tasks, req.session.State, "")
})
}
diff --git a/services/groupware/pkg/groupware/groupware_api_vacation.go b/services/groupware/pkg/groupware/groupware_api_vacation.go
index 9a229aca6..a8b86bef3 100644
--- a/services/groupware/pkg/groupware/groupware_api_vacation.go
+++ b/services/groupware/pkg/groupware/groupware_api_vacation.go
@@ -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, jerr := g.jmap.GetVacationResponse(accountId, req.session, req.ctx, logger)
+ res, sessionState, 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)
+ return etagResponse(res, sessionState, res.State, lang)
})
}
@@ -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, jerr := g.jmap.SetVacationResponse(accountId, body, req.session, req.ctx, logger)
+ res, sessionState, 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)
+ return etagResponse(res, sessionState, res.ResponseState, lang)
})
}
diff --git a/services/groupware/pkg/groupware/groupware_framework.go b/services/groupware/pkg/groupware/groupware_framework.go
index 8f475906b..63d1d6547 100644
--- a/services/groupware/pkg/groupware/groupware_framework.go
+++ b/services/groupware/pkg/groupware/groupware_framework.go
@@ -575,6 +575,10 @@ func (g *Groupware) sendResponse(w http.ResponseWriter, r *http.Request, respons
w.Header().Add("Session-State", string(sessionState))
}
+ if response.contentLanguage != "" {
+ w.Header().Add("Content-Language", string(response.contentLanguage))
+ }
+
notModified := false
if etag != "" {
challenge := r.Header.Get("if-none-match")
diff --git a/services/groupware/pkg/groupware/groupware_request.go b/services/groupware/pkg/groupware/groupware_request.go
index 7005d5040..89034c2bb 100644
--- a/services/groupware/pkg/groupware/groupware_request.go
+++ b/services/groupware/pkg/groupware/groupware_request.go
@@ -65,8 +65,8 @@ var (
errNoPrimaryAccountForTask = errors.New("no primary account for task")
errNoPrimaryAccountForCalendar = errors.New("no primary account for calendar")
errNoPrimaryAccountForContact = errors.New("no primary account for contact")
+ errNoPrimaryAccountForQuota = errors.New("no primary account for quota")
// errNoPrimaryAccountForSieve = errors.New("no primary account for sieve")
- // errNoPrimaryAccountForQuota = errors.New("no primary account for quota")
// errNoPrimaryAccountForWebsocket = errors.New("no primary account for websocket")
)
@@ -109,6 +109,10 @@ func (r Request) GetAccountIdForVacationResponse() (string, *Error) {
return r.getAccountId(r.session.PrimaryAccounts.VacationResponse, errNoPrimaryAccountForVacationResponse)
}
+func (r Request) GetAccountIdForQuota() (string, *Error) {
+ return r.getAccountId(r.session.PrimaryAccounts.Quota, errNoPrimaryAccountForQuota)
+}
+
func (r Request) GetAccountIdForSubmission() (string, *Error) {
return r.getAccountId(r.session.PrimaryAccounts.Blob, errNoPrimaryAccountForSubmission)
}
@@ -280,6 +284,10 @@ func (r Request) body(target any) *Error {
return nil
}
+func (r Request) language() string {
+ return r.r.Header.Get("Accept-Language")
+}
+
func (r Request) observe(obs prometheus.Observer, value float64) {
metrics.WithExemplar(obs, value, r.GetRequestId(), r.GetTraceId())
}
diff --git a/services/groupware/pkg/groupware/groupware_response.go b/services/groupware/pkg/groupware/groupware_response.go
index d380fa9e4..49456c2fb 100644
--- a/services/groupware/pkg/groupware/groupware_response.go
+++ b/services/groupware/pkg/groupware/groupware_response.go
@@ -7,11 +7,12 @@ import (
)
type Response struct {
- body any
- status int
- err *Error
- etag jmap.State
- sessionState jmap.SessionState
+ body any
+ status int
+ err *Error
+ etag jmap.State
+ sessionState jmap.SessionState
+ contentLanguage jmap.Language
}
func errorResponse(err *Error) Response {
@@ -32,30 +33,33 @@ func errorResponseWithSessionState(err *Error, sessionState jmap.SessionState) R
}
}
-func response(body any, sessionState jmap.SessionState) Response {
+func response(body any, sessionState jmap.SessionState, contentLanguage jmap.Language) Response {
return Response{
- body: body,
- err: nil,
- etag: jmap.State(sessionState),
- sessionState: sessionState,
+ body: body,
+ err: nil,
+ etag: jmap.State(sessionState),
+ sessionState: sessionState,
+ contentLanguage: contentLanguage,
}
}
-func etagResponse(body any, sessionState jmap.SessionState, etag jmap.State) Response {
+func etagResponse(body any, sessionState jmap.SessionState, etag jmap.State, contentLanguage jmap.Language) Response {
return Response{
- body: body,
- err: nil,
- etag: etag,
- sessionState: sessionState,
+ body: body,
+ err: nil,
+ etag: etag,
+ sessionState: sessionState,
+ contentLanguage: contentLanguage,
}
}
-func etagOnlyResponse(body any, etag jmap.State) Response {
+func etagOnlyResponse(body any, etag jmap.State, contentLanguage jmap.Language) Response {
return Response{
- body: body,
- err: nil,
- etag: etag,
- sessionState: "",
+ body: body,
+ err: nil,
+ etag: etag,
+ sessionState: "",
+ contentLanguage: contentLanguage,
}
}
diff --git a/services/groupware/pkg/groupware/groupware_route.go b/services/groupware/pkg/groupware/groupware_route.go
index 8a49cc1bc..70af31403 100644
--- a/services/groupware/pkg/groupware/groupware_route.go
+++ b/services/groupware/pkg/groupware/groupware_route.go
@@ -72,6 +72,7 @@ func (g *Groupware) Route(r chi.Router) {
r.Get("/identities", g.GetIdentities)
r.Get("/vacation", g.GetVacation)
r.Put("/vacation", g.SetVacation)
+ r.Get("/quota", g.GetQuota)
r.Route("/mailboxes", func(r chi.Router) {
r.Get("/", g.GetMailboxes) // ?name=&role=&subcribed=
r.Get("/{mailbox}", g.GetMailbox)