From 865da8b83f8bfaed98ced503b776979e6ec76e5b Mon Sep 17 00:00:00 2001
From: Pascal Bleser
Date: Wed, 26 Nov 2025 11:29:55 +0100
Subject: [PATCH] groupware: add Object-Type and Account-Id response headers
* implement Request.AllAccountIds() to generalize the fetching (and
uniqifying) of all account IDs, which will allow us to implement
things such as "subscribed" accounts, or limiting the number of
accounts in one request
* add Account-Id response header
* add Object-Type response header
---
.../pkg/groupware/groupware_api_account.go | 16 +-
.../pkg/groupware/groupware_api_blob.go | 31 +-
.../pkg/groupware/groupware_api_calendars.go | 40 +-
.../pkg/groupware/groupware_api_contacts.go | 40 +-
.../pkg/groupware/groupware_api_emails.go | 366 +++++++++---------
.../pkg/groupware/groupware_api_identity.go | 40 +-
.../pkg/groupware/groupware_api_index.go | 9 +-
.../pkg/groupware/groupware_api_mailbox.go | 131 +++----
.../pkg/groupware/groupware_api_quota.go | 17 +-
.../pkg/groupware/groupware_api_tasklists.go | 10 +-
.../pkg/groupware/groupware_api_vacation.go | 28 +-
.../pkg/groupware/groupware_error.go | 4 +-
.../pkg/groupware/groupware_framework.go | 62 ++-
.../pkg/groupware/groupware_mock_tasks.go | 4 +
.../pkg/groupware/groupware_request.go | 54 +--
.../pkg/groupware/groupware_response.go | 64 ++-
16 files changed, 491 insertions(+), 425 deletions(-)
diff --git a/services/groupware/pkg/groupware/groupware_api_account.go b/services/groupware/pkg/groupware/groupware_api_account.go
index d8f467498..96015fdea 100644
--- a/services/groupware/pkg/groupware/groupware_api_account.go
+++ b/services/groupware/pkg/groupware/groupware_api_account.go
@@ -29,11 +29,11 @@ type SwaggerGetAccountResponse struct {
// 500: ErrorResponse500
func (g *Groupware) GetAccount(w http.ResponseWriter, r *http.Request) {
g.respond(w, r, func(req Request) Response {
- account, err := req.GetAccountForMail()
+ accountId, account, err := req.GetAccountForMail()
if err != nil {
- return errorResponse(err)
+ return errorResponse(accountId, err)
}
- return response(account, req.session.State, "")
+ return etagResponse(accountId, account, req.session.State, AccountResponseObjectType, jmap.State(req.session.State), "")
})
}
@@ -66,16 +66,16 @@ func (g *Groupware) GetAccounts(w http.ResponseWriter, r *http.Request) {
}
// sort on accountId to have a stable order that remains the same with every query
slices.SortFunc(list, func(a, b AccountWithId) int { return strings.Compare(a.AccountId, b.AccountId) })
- return response(list, req.session.State, "")
+ return etagResponse(joinAccountIds(structs.Map(list, func(a AccountWithId) string { return a.AccountId })), list, req.session.State, AccountResponseObjectType, jmap.State(req.session.State), "")
})
}
func (g *Groupware) GetAccountsWithTheirIdentities(w http.ResponseWriter, r *http.Request) {
g.respond(w, r, func(req Request) Response {
- uniqueAccountIds := structs.Uniq(structs.Keys(req.session.Accounts))
- resp, sessionState, state, lang, err := g.jmap.GetIdentitiesForAllAccounts(uniqueAccountIds, req.session, req.ctx, req.logger, req.language())
+ allAccountIds := req.AllAccountIds()
+ resp, sessionState, state, lang, err := g.jmap.GetIdentitiesForAllAccounts(allAccountIds, req.session, req.ctx, req.logger, req.language())
if err != nil {
- return req.errorResponseFromJmap(err)
+ return req.errorResponseFromJmap(joinAccountIds(allAccountIds), err)
}
list := make([]AccountWithIdAndIdentities, len(req.session.Accounts))
i := 0
@@ -94,7 +94,7 @@ func (g *Groupware) GetAccountsWithTheirIdentities(w http.ResponseWriter, r *htt
}
// sort on accountId to have a stable order that remains the same with every query
slices.SortFunc(list, func(a, b AccountWithIdAndIdentities) int { return strings.Compare(a.AccountId, b.AccountId) })
- return etagResponse(list, sessionState, state, lang)
+ return etagResponse(joinAccountIds(structs.Map(list, func(a AccountWithIdAndIdentities) string { return a.AccountId })), list, sessionState, AccountResponseObjectType, state, lang)
})
}
diff --git a/services/groupware/pkg/groupware/groupware_api_blob.go b/services/groupware/pkg/groupware/groupware_api_blob.go
index 07a139291..b9224b51b 100644
--- a/services/groupware/pkg/groupware/groupware_api_blob.go
+++ b/services/groupware/pkg/groupware/groupware_api_blob.go
@@ -16,26 +16,29 @@ const (
func (g *Groupware) GetBlobMeta(w http.ResponseWriter, r *http.Request) {
g.respond(w, r, func(req Request) Response {
- blobId := chi.URLParam(req.r, UriParamBlobId)
- if blobId == "" {
- return req.parameterErrorResponse(UriParamBlobId, fmt.Sprintf("Invalid value for path parameter '%v': empty", UriParamBlobId))
- }
-
accountId, err := req.GetAccountIdForBlob()
if err != nil {
- return errorResponse(err)
+ return errorResponse(accountId, err)
}
- logger := log.From(req.logger.With().Str(logAccountId, accountId))
+ l := req.logger.With().Str(logAccountId, accountId)
+
+ blobId := chi.URLParam(req.r, UriParamBlobId)
+ if blobId == "" {
+ return req.parameterErrorResponse(accountId, UriParamBlobId, fmt.Sprintf("Invalid value for path parameter '%v': empty", UriParamBlobId))
+ }
+ l = l.Str(UriParamBlobId, blobId)
+
+ logger := log.From(l)
res, sessionState, state, lang, jerr := g.jmap.GetBlobMetadata(accountId, req.session, req.ctx, logger, req.language(), blobId)
if jerr != nil {
- return req.errorResponseFromJmap(jerr)
+ return req.errorResponseFromJmap(accountId, jerr)
}
blob := res
if blob == nil {
- return notFoundResponse(sessionState)
+ return notFoundResponse(accountId, sessionState)
}
- return etagResponse(res, sessionState, state, lang)
+ return etagResponse(accountId, res, sessionState, BlobResponseObjectType, state, lang)
})
}
@@ -54,16 +57,16 @@ func (g *Groupware) UploadBlob(w http.ResponseWriter, r *http.Request) {
accountId, err := req.GetAccountIdForBlob()
if err != nil {
- return errorResponse(err)
+ return errorResponse(accountId, err)
}
logger := log.From(req.logger.With().Str(logAccountId, accountId))
resp, lang, jerr := g.jmap.UploadBlobStream(accountId, req.session, req.ctx, logger, req.language(), contentType, body)
if jerr != nil {
- return req.errorResponseFromJmap(jerr)
+ return req.errorResponseFromJmap(accountId, jerr)
}
- return response(resp, req.session.State, lang)
+ return response(accountId, resp, req.session.State, lang)
})
}
@@ -78,7 +81,7 @@ func (g *Groupware) DownloadBlob(w http.ResponseWriter, r *http.Request) {
if gwerr != nil {
return gwerr
}
- logger := log.From(req.logger.With().Str(logAccountId, accountId))
+ logger := log.From(req.logger.With().Str(logAccountId, accountId).Str(UriParamBlobId, blobId))
return req.serveBlob(blobId, name, typ, logger, accountId, w)
})
diff --git a/services/groupware/pkg/groupware/groupware_api_calendars.go b/services/groupware/pkg/groupware/groupware_api_calendars.go
index 56b44afd9..904bb4139 100644
--- a/services/groupware/pkg/groupware/groupware_api_calendars.go
+++ b/services/groupware/pkg/groupware/groupware_api_calendars.go
@@ -34,10 +34,10 @@ func (g *Groupware) GetCalendars(w http.ResponseWriter, r *http.Request) {
calendars, sessionState, state, lang, jerr := g.jmap.GetCalendars(accountId, req.session, req.ctx, req.logger, req.language(), nil)
if jerr != nil {
- return req.errorResponseFromJmap(jerr)
+ return req.errorResponseFromJmap(accountId, jerr)
}
- return etagResponse(calendars, sessionState, state, lang)
+ return etagResponse(accountId, calendars, sessionState, CalendarResponseObjectType, state, lang)
})
}
@@ -74,13 +74,13 @@ func (g *Groupware) GetCalendarById(w http.ResponseWriter, r *http.Request) {
logger := log.From(l)
calendars, sessionState, state, lang, jerr := g.jmap.GetCalendars(accountId, req.session, req.ctx, logger, req.language(), []string{calendarId})
if jerr != nil {
- return req.errorResponseFromJmap(jerr)
+ return req.errorResponseFromJmap(accountId, jerr)
}
if len(calendars.NotFound) > 0 {
- return notFoundResponse(sessionState)
+ return notFoundResponse(accountId, sessionState)
} else {
- return etagResponse(calendars.Calendars[0], sessionState, state, lang)
+ return etagResponse(accountId, calendars.Calendars[0], sessionState, CalendarResponseObjectType, state, lang)
}
})
}
@@ -115,7 +115,7 @@ func (g *Groupware) GetEventsInCalendar(w http.ResponseWriter, r *http.Request)
offset, ok, err := req.parseUIntParam(QueryParamOffset, 0)
if err != nil {
- return errorResponse(err)
+ return errorResponse(accountId, err)
}
if ok {
l = l.Uint(QueryParamOffset, offset)
@@ -123,7 +123,7 @@ func (g *Groupware) GetEventsInCalendar(w http.ResponseWriter, r *http.Request)
limit, ok, err := req.parseUIntParam(QueryParamLimit, g.defaultContactLimit)
if err != nil {
- return errorResponse(err)
+ return errorResponse(accountId, err)
}
if ok {
l = l.Uint(QueryParamLimit, limit)
@@ -137,13 +137,13 @@ func (g *Groupware) GetEventsInCalendar(w http.ResponseWriter, r *http.Request)
logger := log.From(l)
eventsByAccountId, sessionState, state, lang, jerr := g.jmap.QueryCalendarEvents([]string{accountId}, req.session, req.ctx, logger, req.language(), filter, sortBy, offset, limit)
if jerr != nil {
- return req.errorResponseFromJmap(jerr)
+ return req.errorResponseFromJmap(accountId, jerr)
}
if events, ok := eventsByAccountId[accountId]; ok {
- return etagResponse(events, sessionState, state, lang)
+ return etagResponse(accountId, events, sessionState, EventResponseObjectType, state, lang)
} else {
- return notFoundResponse(sessionState)
+ return notFoundResponse(accountId, sessionState)
}
})
}
@@ -163,15 +163,15 @@ func (g *Groupware) CreateCalendarEvent(w http.ResponseWriter, r *http.Request)
var create jmap.CalendarEvent
err := req.body(&create)
if err != nil {
- return errorResponse(err)
+ return errorResponse(accountId, err)
}
logger := log.From(l)
created, sessionState, state, lang, jerr := g.jmap.CreateCalendarEvent(accountId, req.session, req.ctx, logger, req.language(), create)
if jerr != nil {
- return req.errorResponseFromJmap(jerr)
+ return req.errorResponseFromJmap(accountId, jerr)
}
- return etagResponse(created, sessionState, state, lang)
+ return etagResponse(accountId, created, sessionState, EventResponseObjectType, state, lang)
})
}
@@ -191,25 +191,25 @@ func (g *Groupware) DeleteCalendarEvent(w http.ResponseWriter, r *http.Request)
deleted, sessionState, state, _, jerr := g.jmap.DeleteCalendarEvent(accountId, []string{eventId}, req.session, req.ctx, logger, req.language())
if jerr != nil {
- return req.errorResponseFromJmap(jerr)
+ return req.errorResponseFromJmap(accountId, jerr)
}
for _, e := range deleted {
desc := e.Description
if desc != "" {
- return errorResponseWithSessionState(apiError(
+ return errorResponseWithSessionState(accountId, apiError(
req.errorId(),
ErrorFailedToDeleteContact,
withDetail(e.Description),
), sessionState)
} else {
- return errorResponseWithSessionState(apiError(
+ return errorResponseWithSessionState(accountId, apiError(
req.errorId(),
ErrorFailedToDeleteContact,
), sessionState)
}
}
- return noContentResponseWithEtag(sessionState, state)
+ return noContentResponseWithEtag(accountId, sessionState, EventResponseObjectType, state)
})
}
@@ -217,7 +217,7 @@ func (g *Groupware) ParseIcalBlob(w http.ResponseWriter, r *http.Request) {
g.respond(w, r, func(req Request) Response {
accountId, err := req.GetAccountIdForBlob()
if err != nil {
- return errorResponse(err)
+ return errorResponse(accountId, err)
}
blobId := chi.URLParam(r, UriParamBlobId)
@@ -228,8 +228,8 @@ func (g *Groupware) ParseIcalBlob(w http.ResponseWriter, r *http.Request) {
resp, sessionState, state, lang, jerr := g.jmap.ParseICalendarBlob(accountId, req.session, req.ctx, logger, req.language(), blobIds)
if jerr != nil {
- return req.errorResponseFromJmap(jerr)
+ return req.errorResponseFromJmap(accountId, jerr)
}
- return etagResponse(resp, sessionState, state, lang)
+ return etagResponse(accountId, resp, sessionState, EventResponseObjectType, state, lang)
})
}
diff --git a/services/groupware/pkg/groupware/groupware_api_contacts.go b/services/groupware/pkg/groupware/groupware_api_contacts.go
index b2df845f6..7f7e7fe37 100644
--- a/services/groupware/pkg/groupware/groupware_api_contacts.go
+++ b/services/groupware/pkg/groupware/groupware_api_contacts.go
@@ -34,10 +34,10 @@ func (g *Groupware) GetAddressbooks(w http.ResponseWriter, r *http.Request) {
addressbooks, sessionState, state, lang, jerr := g.jmap.GetAddressbooks(accountId, req.session, req.ctx, req.logger, req.language(), nil)
if jerr != nil {
- return req.errorResponseFromJmap(jerr)
+ return req.errorResponseFromJmap(accountId, jerr)
}
- return etagResponse(addressbooks, sessionState, state, lang)
+ return etagResponse(accountId, addressbooks, sessionState, AddressBookResponseObjectType, state, lang)
})
}
@@ -74,13 +74,13 @@ func (g *Groupware) GetAddressbook(w http.ResponseWriter, r *http.Request) {
logger := log.From(l)
addressbooks, sessionState, state, lang, jerr := g.jmap.GetAddressbooks(accountId, req.session, req.ctx, logger, req.language(), []string{addressBookId})
if jerr != nil {
- return req.errorResponseFromJmap(jerr)
+ return req.errorResponseFromJmap(accountId, jerr)
}
if len(addressbooks.NotFound) > 0 {
- return notFoundResponse(sessionState)
+ return notFoundResponse(accountId, sessionState)
} else {
- return etagResponse(addressbooks, sessionState, state, lang)
+ return etagResponse(accountId, addressbooks, sessionState, AddressBookResponseObjectType, state, lang)
}
})
}
@@ -115,7 +115,7 @@ func (g *Groupware) GetContactsInAddressbook(w http.ResponseWriter, r *http.Requ
offset, ok, err := req.parseUIntParam(QueryParamOffset, 0)
if err != nil {
- return errorResponse(err)
+ return errorResponse(accountId, err)
}
if ok {
l = l.Uint(QueryParamOffset, offset)
@@ -123,7 +123,7 @@ func (g *Groupware) GetContactsInAddressbook(w http.ResponseWriter, r *http.Requ
limit, ok, err := req.parseUIntParam(QueryParamLimit, g.defaultContactLimit)
if err != nil {
- return errorResponse(err)
+ return errorResponse(accountId, err)
}
if ok {
l = l.Uint(QueryParamLimit, limit)
@@ -137,13 +137,13 @@ func (g *Groupware) GetContactsInAddressbook(w http.ResponseWriter, r *http.Requ
logger := log.From(l)
contactsByAccountId, sessionState, state, lang, jerr := g.jmap.QueryContactCards([]string{accountId}, req.session, req.ctx, logger, req.language(), filter, sortBy, offset, limit)
if jerr != nil {
- return req.errorResponseFromJmap(jerr)
+ return req.errorResponseFromJmap(accountId, jerr)
}
if contacts, ok := contactsByAccountId[accountId]; ok {
- return etagResponse(contacts, sessionState, state, lang)
+ return etagResponse(accountId, contacts, sessionState, ContactResponseObjectType, state, lang)
} else {
- return notFoundResponse(sessionState)
+ return etagNotFoundResponse(accountId, sessionState, ContactResponseObjectType, state, lang)
}
})
}
@@ -163,13 +163,13 @@ func (g *Groupware) GetContactById(w http.ResponseWriter, r *http.Request) {
logger := log.From(l)
contactsById, sessionState, state, lang, jerr := g.jmap.GetContactCardsById(accountId, req.session, req.ctx, logger, req.language(), []string{contactId})
if jerr != nil {
- return req.errorResponseFromJmap(jerr)
+ return req.errorResponseFromJmap(accountId, jerr)
}
if contact, ok := contactsById[contactId]; ok {
- return etagResponse(contact, sessionState, state, lang)
+ return etagResponse(accountId, contact, sessionState, ContactResponseObjectType, state, lang)
} else {
- return notFoundResponse(sessionState)
+ return etagNotFoundResponse(accountId, sessionState, ContactResponseObjectType, state, lang)
}
})
}
@@ -189,15 +189,15 @@ func (g *Groupware) CreateContact(w http.ResponseWriter, r *http.Request) {
var create jscontact.ContactCard
err := req.body(&create)
if err != nil {
- return errorResponse(err)
+ return errorResponse(accountId, err)
}
logger := log.From(l)
created, sessionState, state, lang, jerr := g.jmap.CreateContactCard(accountId, req.session, req.ctx, logger, req.language(), create)
if jerr != nil {
- return req.errorResponseFromJmap(jerr)
+ return req.errorResponseFromJmap(accountId, jerr)
}
- return etagResponse(created, sessionState, state, lang)
+ return etagResponse(accountId, created, sessionState, ContactResponseObjectType, state, lang)
})
}
@@ -216,24 +216,24 @@ func (g *Groupware) DeleteContact(w http.ResponseWriter, r *http.Request) {
deleted, sessionState, state, _, jerr := g.jmap.DeleteContactCard(accountId, []string{contactId}, req.session, req.ctx, logger, req.language())
if jerr != nil {
- return req.errorResponseFromJmap(jerr)
+ return req.errorResponseFromJmap(accountId, jerr)
}
for _, e := range deleted {
desc := e.Description
if desc != "" {
- return errorResponseWithSessionState(apiError(
+ return errorResponseWithSessionState(accountId, apiError(
req.errorId(),
ErrorFailedToDeleteContact,
withDetail(e.Description),
), sessionState)
} else {
- return errorResponseWithSessionState(apiError(
+ return errorResponseWithSessionState(accountId, apiError(
req.errorId(),
ErrorFailedToDeleteContact,
), sessionState)
}
}
- return noContentResponseWithEtag(sessionState, state)
+ return noContentResponseWithEtag(accountId, sessionState, ContactResponseObjectType, state)
})
}
diff --git a/services/groupware/pkg/groupware/groupware_api_emails.go b/services/groupware/pkg/groupware/groupware_api_emails.go
index ee08bb03c..f4710532e 100644
--- a/services/groupware/pkg/groupware/groupware_api_emails.go
+++ b/services/groupware/pkg/groupware/groupware_api_emails.go
@@ -68,33 +68,41 @@ func (g *Groupware) GetAllEmailsInMailbox(w http.ResponseWriter, r *http.Request
// ... then it's a completely different operation
maxChanges := uint(0)
g.respond(w, r, func(req Request) Response {
- if mailboxId == "" {
- return req.parameterErrorResponse(UriParamMailboxId, fmt.Sprintf("Missing required mailbox ID path parameter '%v'", UriParamMailboxId))
- }
-
accountId, err := req.GetAccountIdForMail()
if err != nil {
- return errorResponse(err)
+ return errorResponse(accountId, err)
+ }
+
+ if mailboxId == "" {
+ return req.parameterErrorResponse(accountId, UriParamMailboxId, fmt.Sprintf("Missing required mailbox ID path parameter '%v'", UriParamMailboxId))
}
logger := log.From(req.logger.With().Str(HeaderSince, log.SafeString(since)).Str(logAccountId, log.SafeString(accountId)))
changes, sessionState, state, lang, jerr := g.jmap.GetMailboxChanges(accountId, req.session, req.ctx, logger, req.language(), mailboxId, since, true, g.maxBodyValueBytes, maxChanges)
if jerr != nil {
- return req.errorResponseFromJmap(jerr)
+ return req.errorResponseFromJmap(accountId, jerr)
}
- return etagResponse(changes, sessionState, state, lang)
+ return etagResponse(accountId, changes, sessionState, EmailResponseObjectType, state, lang)
})
} else {
g.respond(w, r, func(req Request) Response {
l := req.logger.With()
- if mailboxId == "" {
- return req.parameterErrorResponse(UriParamMailboxId, fmt.Sprintf("Missing required mailbox ID path parameter '%v'", UriParamMailboxId))
+
+ accountId, err := req.GetAccountIdForMail()
+ if err != nil {
+ return errorResponse(accountId, err)
}
+ l = l.Str(logAccountId, accountId)
+
+ if mailboxId == "" {
+ return req.parameterErrorResponse(accountId, UriParamMailboxId, fmt.Sprintf("Missing required mailbox ID path parameter '%v'", UriParamMailboxId))
+ }
+
offset, ok, err := req.parseUIntParam(QueryParamOffset, 0)
if err != nil {
- return errorResponse(err)
+ return errorResponse(accountId, err)
}
if ok {
l = l.Uint(QueryParamOffset, offset)
@@ -102,28 +110,22 @@ func (g *Groupware) GetAllEmailsInMailbox(w http.ResponseWriter, r *http.Request
limit, ok, err := req.parseUIntParam(QueryParamLimit, g.defaultEmailLimit)
if err != nil {
- return errorResponse(err)
+ return errorResponse(accountId, err)
}
if ok {
l = l.Uint(QueryParamLimit, limit)
}
- accountId, err := req.GetAccountIdForMail()
- if err != nil {
- return errorResponse(err)
- }
- l = l.Str(logAccountId, accountId)
-
logger := log.From(l)
emails, sessionState, state, lang, jerr := g.jmap.GetAllEmailsInMailbox(accountId, req.session, req.ctx, logger, req.language(), mailboxId, offset, limit, false, true, g.maxBodyValueBytes, true)
if jerr != nil {
- return req.errorResponseFromJmap(jerr)
+ return req.errorResponseFromJmap(accountId, jerr)
}
sanitized, err := req.sanitizeEmails(emails.Emails)
if err != nil {
- return errorResponseWithSessionState(err, sessionState)
+ return errorResponseWithSessionState(accountId, err, sessionState)
}
safe := jmap.Emails{
@@ -133,7 +135,7 @@ func (g *Groupware) GetAllEmailsInMailbox(w http.ResponseWriter, r *http.Request
Offset: emails.Offset,
}
- return etagResponse(safe, sessionState, state, lang)
+ return etagResponse(accountId, safe, sessionState, EmailResponseObjectType, state, lang)
})
}
}
@@ -182,19 +184,19 @@ func (g *Groupware) GetEmailsById(w http.ResponseWriter, r *http.Request) {
})
} else {
g.respond(w, r, func(req Request) Response {
- if len(ids) < 1 {
- return req.parameterErrorResponse(UriParamEmailId, fmt.Sprintf("Invalid value for path parameter '%v': '%s': %s", UriParamEmailId, log.SafeString(id), "empty list of mail ids"))
- }
-
accountId, err := req.GetAccountIdForMail()
if err != nil {
- return errorResponse(err)
+ return errorResponse(accountId, err)
}
l := req.logger.With().Str(logAccountId, log.SafeString(accountId))
+ if len(ids) < 1 {
+ return req.parameterErrorResponse(accountId, UriParamEmailId, fmt.Sprintf("Invalid value for path parameter '%v': '%s': %s", UriParamEmailId, log.SafeString(id), "empty list of mail ids"))
+ }
+
markAsSeen, ok, err := req.parseBoolParam(QueryParamMarkAsSeen, false)
if err != nil {
- return errorResponse(err)
+ return errorResponse(accountId, err)
}
if ok {
l = l.Bool(QueryParamMarkAsSeen, markAsSeen)
@@ -205,32 +207,32 @@ func (g *Groupware) GetEmailsById(w http.ResponseWriter, r *http.Request) {
emails, sessionState, state, lang, jerr := g.jmap.GetEmails(accountId, req.session, req.ctx, logger, req.language(), ids, true, g.maxBodyValueBytes, markAsSeen, true)
if jerr != nil {
- return req.errorResponseFromJmap(jerr)
+ return req.errorResponseFromJmap(accountId, jerr)
}
if len(emails) < 1 {
- return notFoundResponse(sessionState)
+ return notFoundResponse(accountId, sessionState)
} else {
sanitized, err := req.sanitizeEmail(emails[0])
if err != nil {
- return errorResponseWithSessionState(err, sessionState)
+ return errorResponseWithSessionState(accountId, err, sessionState)
}
- return etagResponse(sanitized, sessionState, state, lang)
+ return etagResponse(accountId, sanitized, sessionState, EmailResponseObjectType, state, lang)
}
} else {
logger := log.From(l.Array("ids", log.SafeStringArray(ids)))
emails, sessionState, state, lang, jerr := g.jmap.GetEmails(accountId, req.session, req.ctx, logger, req.language(), ids, true, g.maxBodyValueBytes, markAsSeen, false)
if jerr != nil {
- return req.errorResponseFromJmap(jerr)
+ return req.errorResponseFromJmap(accountId, jerr)
}
if len(emails) < 1 {
- return notFoundResponse(sessionState)
+ return notFoundResponse(accountId, sessionState)
} else {
sanitized, err := req.sanitizeEmails(emails)
if err != nil {
- return errorResponseWithSessionState(err, sessionState)
+ return errorResponseWithSessionState(accountId, err, sessionState)
}
- return etagResponse(sanitized, sessionState, state, lang)
+ return etagResponse(accountId, sanitized, sessionState, EmailResponseObjectType, state, lang)
}
}
})
@@ -265,22 +267,22 @@ func (g *Groupware) GetEmailAttachments(w http.ResponseWriter, r *http.Request)
g.respond(w, r, func(req Request) Response {
accountId, err := req.GetAccountIdForMail()
if err != nil {
- return errorResponse(err)
+ return errorResponse(accountId, err)
}
l := req.logger.With().Str(logAccountId, log.SafeString(accountId))
logger := log.From(l)
emails, sessionState, state, lang, jerr := g.jmap.GetEmails(accountId, req.session, req.ctx, logger, req.language(), []string{id}, false, 0, false, false)
if jerr != nil {
- return req.errorResponseFromJmap(jerr)
+ return req.errorResponseFromJmap(accountId, jerr)
}
if len(emails) < 1 {
- return notFoundResponse(sessionState)
+ return notFoundResponse(accountId, sessionState)
}
email, err := req.sanitizeEmail(emails[0])
if err != nil {
- return errorResponseWithSessionState(err, sessionState)
+ return errorResponseWithSessionState(accountId, err, sessionState)
}
- return etagResponse(email.Attachments, sessionState, state, lang)
+ return etagResponse(accountId, email.Attachments, sessionState, EmailResponseObjectType, state, lang)
})
} else {
g.stream(w, r, func(req Request, w http.ResponseWriter) *Error {
@@ -365,28 +367,29 @@ func (g *Groupware) GetEmailAttachments(w http.ResponseWriter, r *http.Request)
func (g *Groupware) getEmailsSince(w http.ResponseWriter, r *http.Request, since string) {
g.respond(w, r, func(req Request) Response {
l := req.logger.With().Str(QueryParamSince, log.SafeString(since))
+
+ accountId, err := req.GetAccountIdForMail()
+ if err != nil {
+ return errorResponse(accountId, err)
+ }
+ l = l.Str(logAccountId, log.SafeString(accountId))
+
maxChanges, ok, err := req.parseUIntParam(QueryParamMaxChanges, 0)
if err != nil {
- return errorResponse(err)
+ return errorResponse(accountId, err)
}
if ok {
l = l.Uint(QueryParamMaxChanges, maxChanges)
}
- accountId, err := req.GetAccountIdForMail()
- if err != nil {
- return errorResponse(err)
- }
- l = l.Str(logAccountId, log.SafeString(accountId))
-
logger := log.From(l)
changes, sessionState, state, lang, jerr := g.jmap.GetEmailsSince(accountId, req.session, req.ctx, logger, req.language(), since, true, g.maxBodyValueBytes, maxChanges)
if jerr != nil {
- return req.errorResponseFromJmap(jerr)
+ return req.errorResponseFromJmap(accountId, jerr)
}
- return etagResponse(changes, sessionState, state, lang)
+ return etagResponse(accountId, changes, sessionState, EmailResponseObjectType, state, lang)
})
}
@@ -577,10 +580,16 @@ func (g *Groupware) buildFilter(req Request) (bool, jmap.EmailFilterElement, boo
func (g *Groupware) searchEmails(w http.ResponseWriter, r *http.Request) {
g.respond(w, r, func(req Request) Response {
+ accountId, err := req.GetAccountIdForMail()
+ if err != nil {
+ return errorResponse(accountId, err)
+ }
+
ok, filter, makesSnippets, offset, limit, logger, err := g.buildFilter(req)
if !ok {
- return errorResponse(err)
+ return errorResponse(accountId, err)
}
+ logger = log.From(req.logger.With().Str(logAccountId, log.SafeString(accountId)))
if !filter.IsNotEmpty() {
filter = nil
@@ -588,7 +597,7 @@ func (g *Groupware) searchEmails(w http.ResponseWriter, r *http.Request) {
fetchEmails, ok, err := req.parseBoolParam(QueryParamSearchFetchEmails, false)
if err != nil {
- return errorResponse(err)
+ return errorResponse(accountId, err)
}
if ok {
logger = log.From(logger.With().Bool(QueryParamSearchFetchEmails, fetchEmails))
@@ -597,21 +606,15 @@ func (g *Groupware) searchEmails(w http.ResponseWriter, r *http.Request) {
if fetchEmails {
fetchBodies, ok, err := req.parseBoolParam(QueryParamSearchFetchBodies, false)
if err != nil {
- return errorResponse(err)
+ return errorResponse(accountId, err)
}
if ok {
logger = log.From(logger.With().Bool(QueryParamSearchFetchBodies, fetchBodies))
}
- accountId, err := req.GetAccountIdForMail()
- if err != nil {
- return errorResponse(err)
- }
- logger = log.From(logger.With().Str(logAccountId, log.SafeString(accountId)))
-
resultsByAccount, sessionState, state, lang, jerr := g.jmap.QueryEmailsWithSnippets([]string{accountId}, filter, req.session, req.ctx, logger, req.language(), offset, limit, fetchBodies, g.maxBodyValueBytes)
if jerr != nil {
- return req.errorResponseFromJmap(jerr)
+ return req.errorResponseFromJmap(accountId, jerr)
}
if results, ok := resultsByAccount[accountId]; ok {
@@ -631,7 +634,7 @@ func (g *Groupware) searchEmails(w http.ResponseWriter, r *http.Request) {
}
sanitized, err := req.sanitizeEmail(result.Email)
if err != nil {
- return errorResponseWithSessionState(err, sessionState)
+ return errorResponseWithSessionState(accountId, err, sessionState)
}
flattened[i] = EmailWithSnippets{
// AccountId: accountId,
@@ -640,36 +643,30 @@ func (g *Groupware) searchEmails(w http.ResponseWriter, r *http.Request) {
}
}
- return etagResponse(EmailWithSnippetsSearchResults{
+ return etagResponse(accountId, EmailWithSnippetsSearchResults{
Results: flattened,
Total: results.Total,
Limit: results.Limit,
QueryState: results.QueryState,
- }, sessionState, state, lang)
+ }, sessionState, EmailResponseObjectType, state, lang)
} else {
- return notFoundResponse(sessionState)
+ return notFoundResponse(accountId, sessionState)
}
} else {
- accountId, err := req.GetAccountIdForMail()
- if err != nil {
- return errorResponse(err)
- }
- logger = log.From(logger.With().Str(logAccountId, log.SafeString(accountId)))
-
resultsByAccountId, sessionState, state, lang, jerr := g.jmap.QueryEmailSnippets([]string{accountId}, filter, req.session, req.ctx, logger, req.language(), offset, limit)
if jerr != nil {
- return req.errorResponseFromJmap(jerr)
+ return req.errorResponseFromJmap(accountId, jerr)
}
if results, ok := resultsByAccountId[accountId]; ok {
- return etagResponse(EmailSearchSnippetsResults{
+ return etagResponse(accountId, EmailSearchSnippetsResults{
Results: structs.Map(results.Snippets, func(s jmap.SearchSnippetWithMeta) Snippet { return Snippet{SearchSnippetWithMeta: s} }),
Total: results.Total,
Limit: results.Limit,
QueryState: results.QueryState,
- }, sessionState, state, lang)
+ }, sessionState, EmailResponseObjectType, state, lang)
} else {
- return notFoundResponse(sessionState)
+ return notFoundResponse(accountId, sessionState)
}
}
})
@@ -692,10 +689,13 @@ func (g *Groupware) GetEmails(w http.ResponseWriter, r *http.Request) {
func (g *Groupware) GetEmailsForAllAccounts(w http.ResponseWriter, r *http.Request) {
g.respond(w, r, func(req Request) Response {
+ allAccountIds := req.AllAccountIds()
+
ok, filter, makesSnippets, offset, limit, logger, err := g.buildFilter(req)
if !ok {
- return errorResponse(err)
+ return errorResponse(joinAccountIds(allAccountIds), err)
}
+ logger = log.From(req.logger.With().Array(logAccountId, log.SafeStringArray(allAccountIds)))
if !filter.IsNotEmpty() {
filter = nil
@@ -703,19 +703,16 @@ func (g *Groupware) GetEmailsForAllAccounts(w http.ResponseWriter, r *http.Reque
fetchEmails, ok, err := req.parseBoolParam(QueryParamSearchFetchEmails, false)
if err != nil {
- return errorResponse(err)
+ return errorResponse(joinAccountIds(allAccountIds), err)
}
if ok {
logger = log.From(logger.With().Bool(QueryParamSearchFetchEmails, fetchEmails))
}
- allAccountIds := structs.Keys(req.session.Accounts) // TODO(pbleser-oc) do we need a limit for a maximum amount of accounts to query at once?
- logger = log.From(logger.With().Array(logAccountId, log.SafeStringArray(allAccountIds)))
-
if fetchEmails {
fetchBodies, ok, err := req.parseBoolParam(QueryParamSearchFetchBodies, false)
if err != nil {
- return errorResponse(err)
+ return errorResponse(joinAccountIds(allAccountIds), err)
}
if ok {
logger = log.From(logger.With().Bool(QueryParamSearchFetchBodies, fetchBodies))
@@ -724,7 +721,7 @@ func (g *Groupware) GetEmailsForAllAccounts(w http.ResponseWriter, r *http.Reque
if makesSnippets {
resultsByAccountId, sessionState, state, lang, jerr := g.jmap.QueryEmailsWithSnippets(allAccountIds, filter, req.session, req.ctx, logger, req.language(), offset, limit, fetchBodies, g.maxBodyValueBytes)
if jerr != nil {
- return req.errorResponseFromJmap(jerr)
+ return req.errorResponseFromJmap(joinAccountIds(allAccountIds), jerr)
}
flattenedByAccountId := make(map[string][]EmailWithSnippets, len(resultsByAccountId))
@@ -747,7 +744,7 @@ func (g *Groupware) GetEmailsForAllAccounts(w http.ResponseWriter, r *http.Reque
sanitized, err := req.sanitizeEmail(result.Email)
if err != nil {
- return errorResponseWithSessionState(err, sessionState)
+ return errorResponseWithSessionState(accountId, err, sessionState)
}
flattened[i] = EmailWithSnippets{
AccountId: accountId,
@@ -774,16 +771,16 @@ func (g *Groupware) GetEmailsForAllAccounts(w http.ResponseWriter, r *http.Reque
// TODO offset and limit over the aggregated results by account
- return etagResponse(EmailWithSnippetsSearchResults{
+ return etagResponse(joinAccountIds(allAccountIds), EmailWithSnippetsSearchResults{
Results: flattened,
Total: totalOverAllAccounts,
Limit: limit,
QueryState: state,
- }, sessionState, state, lang)
+ }, sessionState, EmailResponseObjectType, state, lang)
} else {
resultsByAccountId, sessionState, state, lang, jerr := g.jmap.QueryEmails(allAccountIds, filter, req.session, req.ctx, logger, req.language(), offset, limit, fetchBodies, g.maxBodyValueBytes)
if jerr != nil {
- return req.errorResponseFromJmap(jerr)
+ return req.errorResponseFromJmap(joinAccountIds(allAccountIds), jerr)
}
total := 0
@@ -800,7 +797,7 @@ func (g *Groupware) GetEmailsForAllAccounts(w http.ResponseWriter, r *http.Reque
for _, e := range list.Emails {
sanitized, err := req.sanitizeEmail(e)
if err != nil {
- return errorResponseWithSessionState(err, sessionState)
+ return errorResponseWithSessionState(joinAccountIds(allAccountIds), err, sessionState)
}
flattened[i] = sanitized
i++
@@ -812,18 +809,18 @@ func (g *Groupware) GetEmailsForAllAccounts(w http.ResponseWriter, r *http.Reque
// TODO offset and limit over the aggregated results by account
- return etagResponse(EmailSearchResults{
+ return etagResponse(joinAccountIds(allAccountIds), EmailSearchResults{
Results: flattened,
Total: totalOverAllAccounts,
Limit: limit,
QueryState: state,
- }, sessionState, state, lang)
+ }, sessionState, EmailResponseObjectType, state, lang)
}
} else {
if makesSnippets {
resultsByAccountId, sessionState, state, lang, jerr := g.jmap.QueryEmailSnippets(allAccountIds, filter, req.session, req.ctx, logger, req.language(), offset, limit)
if jerr != nil {
- return req.errorResponseFromJmap(jerr)
+ return req.errorResponseFromJmap(joinAccountIds(allAccountIds), jerr)
}
var totalOverAllAccounts uint = 0
@@ -850,12 +847,12 @@ func (g *Groupware) GetEmailsForAllAccounts(w http.ResponseWriter, r *http.Reque
// TODO offset and limit over the aggregated results by account
- return etagResponse(EmailSearchSnippetsResults{
+ return etagResponse(joinAccountIds(allAccountIds), EmailSearchSnippetsResults{
Results: flattened,
Total: totalOverAllAccounts,
Limit: limit,
QueryState: state,
- }, sessionState, state, lang)
+ }, sessionState, EmailResponseObjectType, state, lang)
} else {
// TODO implement search without email bodies (only retrieve a few chosen properties?) + without snippets
return notImplementesResponse()
@@ -864,41 +861,28 @@ func (g *Groupware) GetEmailsForAllAccounts(w http.ResponseWriter, r *http.Reque
})
}
-/*
-type EmailCreation struct {
- MailboxIds []string `json:"mailboxIds,omitempty"`
- Keywords []string `json:"keywords,omitempty"`
- From []jmap.EmailAddress `json:"from,omitempty"`
- Subject string `json:"subject,omitempty"`
- ReceivedAt time.Time `json:"receivedAt,omitzero"`
- SentAt time.Time `json:"sentAt,omitzero"` // huh?
- BodyStructure jmap.EmailBodyStructure `json:"bodyStructure"`
- BodyValues map[string]jmap.EmailBodyValue `json:"bodyValues,omitempty"`
-}
-*/
-
func (g *Groupware) CreateEmail(w http.ResponseWriter, r *http.Request) {
g.respond(w, r, func(req Request) Response {
logger := req.logger
accountId, gwerr := req.GetAccountIdForMail()
if gwerr != nil {
- return errorResponse(gwerr)
+ return errorResponse(accountId, gwerr)
}
logger = log.From(logger.With().Str(logAccountId, log.SafeString(accountId)))
var body jmap.EmailCreate
err := req.body(&body)
if err != nil {
- return errorResponse(err)
+ return errorResponse(accountId, err)
}
created, sessionState, state, lang, jerr := g.jmap.CreateEmail(accountId, body, "", req.session, req.ctx, logger, req.language())
if jerr != nil {
- return req.errorResponseFromJmap(jerr)
+ return req.errorResponseFromJmap(accountId, jerr)
}
- return etagResponse(created, sessionState, state, lang)
+ return etagResponse(accountId, created, sessionState, EmailResponseObjectType, state, lang)
})
}
@@ -908,7 +892,7 @@ func (g *Groupware) ReplaceEmail(w http.ResponseWriter, r *http.Request) {
accountId, gwerr := req.GetAccountIdForMail()
if gwerr != nil {
- return errorResponse(gwerr)
+ return errorResponse(accountId, gwerr)
}
replaceId := chi.URLParam(r, UriParamEmailId)
@@ -918,15 +902,15 @@ func (g *Groupware) ReplaceEmail(w http.ResponseWriter, r *http.Request) {
var body jmap.EmailCreate
err := req.body(&body)
if err != nil {
- return errorResponse(err)
+ return errorResponse(accountId, err)
}
created, sessionState, state, lang, jerr := g.jmap.CreateEmail(accountId, body, replaceId, req.session, req.ctx, logger, req.language())
if jerr != nil {
- return req.errorResponseFromJmap(jerr)
+ return req.errorResponseFromJmap(accountId, jerr)
}
- return etagResponse(created, sessionState, state, lang)
+ return etagResponse(accountId, created, sessionState, EmailResponseObjectType, state, lang)
})
}
@@ -947,7 +931,7 @@ func (g *Groupware) UpdateEmail(w http.ResponseWriter, r *http.Request) {
accountId, gwerr := req.GetAccountIdForMail()
if gwerr != nil {
- return errorResponse(gwerr)
+ return errorResponse(accountId, gwerr)
}
l.Str(logAccountId, accountId)
@@ -956,7 +940,7 @@ func (g *Groupware) UpdateEmail(w http.ResponseWriter, r *http.Request) {
var body map[string]any
err := req.body(&body)
if err != nil {
- return errorResponse(err)
+ return errorResponse(accountId, err)
}
updates := map[string]jmap.EmailUpdate{
@@ -965,20 +949,20 @@ func (g *Groupware) UpdateEmail(w http.ResponseWriter, r *http.Request) {
result, sessionState, state, lang, jerr := g.jmap.UpdateEmails(accountId, updates, req.session, req.ctx, logger, req.language())
if jerr != nil {
- return req.errorResponseFromJmap(jerr)
+ return req.errorResponseFromJmap(accountId, jerr)
}
if result == nil {
- return errorResponse(apiError(req.errorId(), ErrorApiInconsistency, withTitle("API Inconsistency: Missing Email Update Response",
+ return errorResponse(accountId, apiError(req.errorId(), ErrorApiInconsistency, withTitle("API Inconsistency: Missing Email Update Response",
"An internal API behaved unexpectedly: missing Email update response from JMAP endpoint")))
}
updatedEmail, ok := result[emailId]
if !ok {
- return errorResponse(apiError(req.errorId(), ErrorApiInconsistency, withTitle("API Inconsistency: Wrong Email Update Response ID",
+ return errorResponse(accountId, apiError(req.errorId(), ErrorApiInconsistency, withTitle("API Inconsistency: Wrong Email Update Response ID",
"An internal API behaved unexpectedly: wrong Email update ID response from JMAP endpoint")))
}
- return etagResponse(updatedEmail, sessionState, state, lang)
+ return etagResponse(accountId, updatedEmail, sessionState, EmailResponseObjectType, state, lang)
})
}
@@ -1000,7 +984,7 @@ func (g *Groupware) UpdateEmailKeywords(w http.ResponseWriter, r *http.Request)
accountId, gwerr := req.GetAccountIdForMail()
if gwerr != nil {
- return errorResponse(gwerr)
+ return errorResponse(accountId, gwerr)
}
l.Str(logAccountId, accountId)
@@ -1009,11 +993,11 @@ func (g *Groupware) UpdateEmailKeywords(w http.ResponseWriter, r *http.Request)
var body emailKeywordUpdates
err := req.body(&body)
if err != nil {
- return errorResponse(err)
+ return errorResponse(accountId, err)
}
if body.IsEmpty() {
- return noContentResponse(req.session.State)
+ return noContentResponse(accountId, req.session.State)
}
patch := jmap.EmailUpdate{}
@@ -1029,20 +1013,20 @@ func (g *Groupware) UpdateEmailKeywords(w http.ResponseWriter, r *http.Request)
result, sessionState, state, lang, jerr := g.jmap.UpdateEmails(accountId, patches, req.session, req.ctx, logger, req.language())
if jerr != nil {
- return req.errorResponseFromJmap(jerr)
+ return req.errorResponseFromJmap(accountId, jerr)
}
if result == nil {
- return errorResponse(apiError(req.errorId(), ErrorApiInconsistency, withTitle("API Inconsistency: Missing Email Update Response",
+ return errorResponse(accountId, apiError(req.errorId(), ErrorApiInconsistency, withTitle("API Inconsistency: Missing Email Update Response",
"An internal API behaved unexpectedly: missing Email update response from JMAP endpoint")))
}
updatedEmail, ok := result[emailId]
if !ok {
- return errorResponse(apiError(req.errorId(), ErrorApiInconsistency, withTitle("API Inconsistency: Wrong Email Update Response ID",
+ return errorResponse(accountId, apiError(req.errorId(), ErrorApiInconsistency, withTitle("API Inconsistency: Wrong Email Update Response ID",
"An internal API behaved unexpectedly: wrong Email update ID response from JMAP endpoint")))
}
- return etagResponse(updatedEmail, sessionState, state, lang)
+ return etagResponse(accountId, updatedEmail, sessionState, EmailResponseObjectType, state, lang)
})
}
@@ -1064,7 +1048,7 @@ func (g *Groupware) AddEmailKeywords(w http.ResponseWriter, r *http.Request) {
accountId, gwerr := req.GetAccountIdForMail()
if gwerr != nil {
- return errorResponse(gwerr)
+ return errorResponse(accountId, gwerr)
}
l.Str(logAccountId, accountId)
@@ -1073,11 +1057,11 @@ func (g *Groupware) AddEmailKeywords(w http.ResponseWriter, r *http.Request) {
var body []string
err := req.body(&body)
if err != nil {
- return errorResponse(err)
+ return errorResponse(accountId, err)
}
if len(body) < 1 {
- return noContentResponse(req.session.State)
+ return noContentResponse(accountId, req.session.State)
}
patch := jmap.EmailUpdate{}
@@ -1090,23 +1074,23 @@ func (g *Groupware) AddEmailKeywords(w http.ResponseWriter, r *http.Request) {
result, sessionState, state, lang, jerr := g.jmap.UpdateEmails(accountId, patches, req.session, req.ctx, logger, req.language())
if jerr != nil {
- return req.errorResponseFromJmap(jerr)
+ return req.errorResponseFromJmap(accountId, jerr)
}
if result == nil {
- return errorResponse(apiError(req.errorId(), ErrorApiInconsistency, withTitle("API Inconsistency: Missing Email Update Response",
+ return errorResponse(accountId, apiError(req.errorId(), ErrorApiInconsistency, withTitle("API Inconsistency: Missing Email Update Response",
"An internal API behaved unexpectedly: missing Email update response from JMAP endpoint")))
}
updatedEmail, ok := result[emailId]
if !ok {
- return errorResponse(apiError(req.errorId(), ErrorApiInconsistency, withTitle("API Inconsistency: Wrong Email Update Response ID",
+ return errorResponse(accountId, apiError(req.errorId(), ErrorApiInconsistency, withTitle("API Inconsistency: Wrong Email Update Response ID",
"An internal API behaved unexpectedly: wrong Email update ID response from JMAP endpoint")))
}
if updatedEmail == nil {
- return noContentResponseWithEtag(sessionState, state)
+ return noContentResponseWithEtag(accountId, sessionState, EmailResponseObjectType, state)
} else {
- return etagResponse(updatedEmail, sessionState, state, lang)
+ return etagResponse(accountId, updatedEmail, sessionState, EmailResponseObjectType, state, lang)
}
})
}
@@ -1129,7 +1113,7 @@ func (g *Groupware) RemoveEmailKeywords(w http.ResponseWriter, r *http.Request)
accountId, gwerr := req.GetAccountIdForMail()
if gwerr != nil {
- return errorResponse(gwerr)
+ return errorResponse(accountId, gwerr)
}
l.Str(logAccountId, accountId)
@@ -1138,11 +1122,11 @@ func (g *Groupware) RemoveEmailKeywords(w http.ResponseWriter, r *http.Request)
var body []string
err := req.body(&body)
if err != nil {
- return errorResponse(err)
+ return errorResponse(accountId, err)
}
if len(body) < 1 {
- return noContentResponse(req.session.State)
+ return noContentResponse(accountId, req.session.State)
}
patch := jmap.EmailUpdate{}
@@ -1155,23 +1139,23 @@ func (g *Groupware) RemoveEmailKeywords(w http.ResponseWriter, r *http.Request)
result, sessionState, state, lang, jerr := g.jmap.UpdateEmails(accountId, patches, req.session, req.ctx, logger, req.language())
if jerr != nil {
- return req.errorResponseFromJmap(jerr)
+ return req.errorResponseFromJmap(accountId, jerr)
}
if result == nil {
- return errorResponse(apiError(req.errorId(), ErrorApiInconsistency, withTitle("API Inconsistency: Missing Email Update Response",
+ return errorResponse(accountId, apiError(req.errorId(), ErrorApiInconsistency, withTitle("API Inconsistency: Missing Email Update Response",
"An internal API behaved unexpectedly: missing Email update response from JMAP endpoint")))
}
updatedEmail, ok := result[emailId]
if !ok {
- return errorResponse(apiError(req.errorId(), ErrorApiInconsistency, withTitle("API Inconsistency: Wrong Email Update Response ID",
+ return errorResponse(accountId, apiError(req.errorId(), ErrorApiInconsistency, withTitle("API Inconsistency: Wrong Email Update Response ID",
"An internal API behaved unexpectedly: wrong Email update ID response from JMAP endpoint")))
}
if updatedEmail == nil {
- return noContentResponseWithEtag(sessionState, state)
+ return noContentResponseWithEtag(accountId, sessionState, EmailResponseObjectType, state)
} else {
- return etagResponse(updatedEmail, sessionState, state, lang)
+ return etagResponse(accountId, updatedEmail, sessionState, EmailResponseObjectType, state, lang)
}
})
}
@@ -1194,7 +1178,7 @@ func (g *Groupware) DeleteEmail(w http.ResponseWriter, r *http.Request) {
accountId, gwerr := req.GetAccountIdForMail()
if gwerr != nil {
- return errorResponse(gwerr)
+ return errorResponse(accountId, gwerr)
}
l.Str(logAccountId, accountId)
@@ -1202,25 +1186,25 @@ func (g *Groupware) DeleteEmail(w http.ResponseWriter, r *http.Request) {
resp, sessionState, state, _, jerr := g.jmap.DeleteEmails(accountId, []string{emailId}, req.session, req.ctx, logger, req.language())
if jerr != nil {
- return req.errorResponseFromJmap(jerr)
+ return req.errorResponseFromJmap(accountId, jerr)
}
for _, e := range resp {
desc := e.Description
if desc != "" {
- return errorResponseWithSessionState(apiError(
+ return errorResponseWithSessionState(accountId, apiError(
req.errorId(),
ErrorFailedToDeleteEmail,
withDetail(e.Description),
), sessionState)
} else {
- return errorResponseWithSessionState(apiError(
+ return errorResponseWithSessionState(accountId, apiError(
req.errorId(),
ErrorFailedToDeleteEmail,
), sessionState)
}
}
- return noContentResponseWithEtag(sessionState, state)
+ return noContentResponseWithEtag(accountId, sessionState, EmailResponseObjectType, state)
})
}
@@ -1246,26 +1230,27 @@ type SwaggerDeleteEmailsBody struct {
// 500: ErrorResponse500
func (g *Groupware) DeleteEmails(w http.ResponseWriter, r *http.Request) {
g.respond(w, r, func(req Request) Response {
- var emailIds []string
- err := req.body(&emailIds)
- if err != nil {
- return errorResponse(err)
- }
-
l := req.logger.With()
- l.Array("emailIds", log.SafeStringArray(emailIds))
accountId, gwerr := req.GetAccountIdForMail()
if gwerr != nil {
- return errorResponse(gwerr)
+ return errorResponse(accountId, gwerr)
}
l.Str(logAccountId, accountId)
+ var emailIds []string
+ err := req.body(&emailIds)
+ if err != nil {
+ return errorResponse(accountId, err)
+ }
+
+ l.Array("emailIds", log.SafeStringArray(emailIds))
+
logger := log.From(l)
resp, sessionState, state, _, jerr := g.jmap.DeleteEmails(accountId, emailIds, req.session, req.ctx, logger, req.language())
if jerr != nil {
- return req.errorResponseFromJmap(jerr)
+ return req.errorResponseFromJmap(accountId, jerr)
}
if len(resp) > 0 {
@@ -1273,26 +1258,32 @@ func (g *Groupware) DeleteEmails(w http.ResponseWriter, r *http.Request) {
for emailId, e := range resp {
meta[emailId] = e.Description
}
- return errorResponseWithSessionState(apiError(
+ return errorResponseWithSessionState(accountId, apiError(
req.errorId(),
ErrorFailedToDeleteEmail,
withMeta(meta),
), sessionState)
}
- return noContentResponseWithEtag(sessionState, state)
+ return noContentResponseWithEtag(accountId, sessionState, EmailResponseObjectType, state)
})
}
func (g *Groupware) SendEmail(w http.ResponseWriter, r *http.Request) {
g.respond(w, r, func(req Request) Response {
- emailId := chi.URLParam(r, UriParamEmailId)
-
l := req.logger.With()
+
+ accountId, gwerr := req.GetAccountIdForMail()
+ if gwerr != nil {
+ return errorResponse(accountId, gwerr)
+ }
+ l.Str(logAccountId, accountId)
+
+ emailId := chi.URLParam(r, UriParamEmailId)
l.Str(UriParamEmailId, log.SafeString(emailId))
identityId, err := req.getMandatoryStringParam(QueryParamIdentityId)
if err != nil {
- return errorResponse(err)
+ return errorResponse(accountId, err)
}
l.Str(QueryParamIdentityId, log.SafeString(identityId))
@@ -1312,26 +1303,20 @@ func (g *Groupware) SendEmail(w http.ResponseWriter, r *http.Request) {
}
// only one is set
msg := fmt.Sprintf("Missing required value for query parameter '%v'", missing)
- return errorResponse(req.observedParameterError(ErrorMissingMandatoryRequestParameter,
+ return errorResponse(accountId, req.observedParameterError(ErrorMissingMandatoryRequestParameter,
withDetail(msg),
withSource(&ErrorSource{Parameter: missing})))
}
}
- accountId, gwerr := req.GetAccountIdForMail()
- if gwerr != nil {
- return errorResponse(gwerr)
- }
- l.Str(logAccountId, accountId)
-
logger := log.From(l)
resp, sessionState, state, lang, jerr := g.jmap.SubmitEmail(accountId, identityId, emailId, move, req.session, req.ctx, logger, req.language())
if jerr != nil {
- return req.errorResponseFromJmap(jerr)
+ return req.errorResponseFromJmap(accountId, jerr)
}
- return etagResponse(resp, sessionState, state, lang)
+ return etagResponse(accountId, resp, sessionState, EmailResponseObjectType, state, lang)
})
}
@@ -1391,9 +1376,15 @@ func (g *Groupware) RelatedToEmail(w http.ResponseWriter, r *http.Request) {
g.respond(w, r, func(req Request) Response {
l := req.logger.With().Str(logEmailId, log.SafeString(id))
+ accountId, gwerr := req.GetAccountIdForMail()
+ if gwerr != nil {
+ return errorResponse(accountId, gwerr)
+ }
+ l = l.Str(logAccountId, log.SafeString(accountId))
+
limit, ok, err := req.parseUIntParam(QueryParamLimit, 10) // TODO configurable default limit
if err != nil {
- return errorResponse(err)
+ return errorResponse(accountId, err)
}
if ok {
l = l.Uint("limit", limit)
@@ -1401,18 +1392,12 @@ func (g *Groupware) RelatedToEmail(w http.ResponseWriter, r *http.Request) {
days, ok, err := req.parseUIntParam(QueryParamDays, 5) // TODO configurable default days
if err != nil {
- return errorResponse(err)
+ return errorResponse(accountId, err)
}
if ok {
l = l.Uint("days", days)
}
- accountId, gwerr := req.GetAccountIdForMail()
- if gwerr != nil {
- return errorResponse(gwerr)
- }
- l = l.Str(logAccountId, log.SafeString(accountId))
-
logger := log.From(l)
reqId := req.GetRequestId()
@@ -1420,13 +1405,13 @@ func (g *Groupware) RelatedToEmail(w http.ResponseWriter, r *http.Request) {
emails, sessionState, state, lang, jerr := g.jmap.GetEmails(accountId, req.session, req.ctx, logger, req.language(), []string{id}, true, g.maxBodyValueBytes, false, false)
getEmailsDuration := time.Since(getEmailsBefore)
if jerr != nil {
- return req.errorResponseFromJmap(jerr)
+ return req.errorResponseFromJmap(accountId, jerr)
}
if len(emails) < 1 {
req.observe(g.metrics.EmailByIdDuration.WithLabelValues(req.session.JmapEndpoint, metrics.Values.Result.NotFound), getEmailsDuration.Seconds())
logger.Trace().Msg("failed to find any emails matching id") // the id is already in the log field
- return notFoundResponse(sessionState)
+ return notFoundResponse(accountId, sessionState)
} else {
req.observe(g.metrics.EmailByIdDuration.WithLabelValues(req.session.JmapEndpoint, metrics.Values.Result.Found), getEmailsDuration.Seconds())
}
@@ -1482,12 +1467,12 @@ func (g *Groupware) RelatedToEmail(w http.ResponseWriter, r *http.Request) {
sanitized, err := req.sanitizeEmail(email)
if err != nil {
- return errorResponseWithSessionState(err, sessionState)
+ return errorResponseWithSessionState(accountId, err, sessionState)
}
- return etagResponse(AboutEmailResponse{
+ return etagResponse(accountId, AboutEmailResponse{
Email: sanitized,
RequestId: reqId,
- }, sessionState, state, lang)
+ }, sessionState, EmailResponseObjectType, state, lang)
})
}
@@ -1751,9 +1736,13 @@ type EmailSummaries struct {
func (g *Groupware) GetLatestEmailsSummaryForAllAccounts(w http.ResponseWriter, r *http.Request) {
g.respond(w, r, func(req Request) Response {
l := req.logger.With()
+
+ allAccountIds := req.AllAccountIds()
+ l.Array(logAccountId, log.SafeStringArray(allAccountIds))
+
limit, ok, err := req.parseUIntParam(QueryParamLimit, 10) // TODO from configuration
if err != nil {
- return errorResponse(err)
+ return errorResponse(joinAccountIds(allAccountIds), err)
}
if ok {
l = l.Uint(QueryParamLimit, limit)
@@ -1761,7 +1750,7 @@ func (g *Groupware) GetLatestEmailsSummaryForAllAccounts(w http.ResponseWriter,
offset, ok, err := req.parseUIntParam(QueryParamOffset, 0)
if err != nil {
- return errorResponse(err)
+ return errorResponse(joinAccountIds(allAccountIds), err)
}
if offset > 0 {
return notImplementesResponse()
@@ -1772,7 +1761,7 @@ func (g *Groupware) GetLatestEmailsSummaryForAllAccounts(w http.ResponseWriter,
seen, ok, err := req.parseBoolParam(QueryParamSeen, false)
if err != nil {
- return errorResponse(err)
+ return errorResponse(joinAccountIds(allAccountIds), err)
}
if ok {
l = l.Bool(QueryParamSeen, seen)
@@ -1780,7 +1769,7 @@ func (g *Groupware) GetLatestEmailsSummaryForAllAccounts(w http.ResponseWriter,
undesirable, ok, err := req.parseBoolParam(QueryParamUndesirable, false)
if err != nil {
- return errorResponse(err)
+ return errorResponse(joinAccountIds(allAccountIds), err)
}
if ok {
l = l.Bool(QueryParamUndesirable, undesirable)
@@ -1798,14 +1787,11 @@ func (g *Groupware) GetLatestEmailsSummaryForAllAccounts(w http.ResponseWriter,
filter = filterFromNotKeywords(notKeywords)
}
- allAccountIds := structs.Keys(req.session.Accounts) // TODO(pbleser-oc) do we need a limit for a maximum amount of accounts to query at once?
- l.Array(logAccountId, log.SafeStringArray(allAccountIds))
-
logger := log.From(l)
emailsSummariesByAccount, sessionState, state, lang, jerr := g.jmap.QueryEmailSummaries(allAccountIds, req.session, req.ctx, logger, req.language(), filter, limit, true)
if jerr != nil {
- return req.errorResponseFromJmap(jerr)
+ return req.errorResponseFromJmap(joinAccountIds(allAccountIds), jerr)
}
// sort in memory to respect the overall limit
@@ -1829,12 +1815,12 @@ func (g *Groupware) GetLatestEmailsSummaryForAllAccounts(w http.ResponseWriter,
summaries[i] = summarizeEmail(all[i].accountId, all[i].email)
}
- return etagResponse(EmailSummaries{
+ return etagResponse(joinAccountIds(allAccountIds), EmailSummaries{
Emails: summaries,
Total: total,
Limit: limit,
Offset: offset,
- }, sessionState, state, lang)
+ }, sessionState, EmailResponseObjectType, state, lang)
})
}
diff --git a/services/groupware/pkg/groupware/groupware_api_identity.go b/services/groupware/pkg/groupware/groupware_api_identity.go
index f4740e877..f97c91987 100644
--- a/services/groupware/pkg/groupware/groupware_api_identity.go
+++ b/services/groupware/pkg/groupware/groupware_api_identity.go
@@ -31,14 +31,14 @@ func (g *Groupware) GetIdentities(w http.ResponseWriter, r *http.Request) {
g.respond(w, r, func(req Request) Response {
accountId, err := req.GetAccountIdForMail()
if err != nil {
- return errorResponse(err)
+ return errorResponse(accountId, err)
}
logger := log.From(req.logger.With().Str(logAccountId, accountId))
res, sessionState, state, lang, jerr := g.jmap.GetAllIdentities(accountId, req.session, req.ctx, logger, req.language())
if jerr != nil {
- return req.errorResponseFromJmap(jerr)
+ return req.errorResponseFromJmap(accountId, jerr)
}
- return etagResponse(res, sessionState, state, lang)
+ return etagResponse(accountId, res, sessionState, IdentityResponseObjectType, state, lang)
})
}
@@ -46,18 +46,18 @@ func (g *Groupware) GetIdentityById(w http.ResponseWriter, r *http.Request) {
g.respond(w, r, func(req Request) Response {
accountId, err := req.GetAccountIdForMail()
if err != nil {
- return errorResponse(err)
+ return errorResponse(accountId, err)
}
id := chi.URLParam(r, UriParamIdentityId)
logger := log.From(req.logger.With().Str(logAccountId, accountId).Str(logIdentityId, id))
res, sessionState, state, lang, jerr := g.jmap.GetIdentities(accountId, req.session, req.ctx, logger, req.language(), []string{id})
if jerr != nil {
- return req.errorResponseFromJmap(jerr)
+ return req.errorResponseFromJmap(accountId, jerr)
}
if len(res) < 1 {
- return notFoundResponse(sessionState)
+ return notFoundResponse(accountId, sessionState)
}
- return etagResponse(res[0], sessionState, state, lang)
+ return etagResponse(accountId, res[0], sessionState, IdentityResponseObjectType, state, lang)
})
}
@@ -65,21 +65,21 @@ func (g *Groupware) AddIdentity(w http.ResponseWriter, r *http.Request) {
g.respond(w, r, func(req Request) Response {
accountId, err := req.GetAccountIdForMail()
if err != nil {
- return errorResponse(err)
+ return errorResponse(accountId, err)
}
logger := log.From(req.logger.With().Str(logAccountId, accountId))
var identity jmap.Identity
err = req.body(&identity)
if err != nil {
- return errorResponse(err)
+ return errorResponse(accountId, err)
}
created, sessionState, state, lang, jerr := g.jmap.CreateIdentity(accountId, req.session, req.ctx, logger, req.language(), identity)
if jerr != nil {
- return req.errorResponseFromJmap(jerr)
+ return req.errorResponseFromJmap(accountId, jerr)
}
- return etagResponse(created, sessionState, state, lang)
+ return etagResponse(accountId, created, sessionState, IdentityResponseObjectType, state, lang)
})
}
@@ -87,21 +87,21 @@ func (g *Groupware) ModifyIdentity(w http.ResponseWriter, r *http.Request) {
g.respond(w, r, func(req Request) Response {
accountId, err := req.GetAccountIdForMail()
if err != nil {
- return errorResponse(err)
+ return errorResponse(accountId, err)
}
logger := log.From(req.logger.With().Str(logAccountId, accountId))
var identity jmap.Identity
err = req.body(&identity)
if err != nil {
- return errorResponse(err)
+ return errorResponse(accountId, err)
}
updated, sessionState, state, lang, jerr := g.jmap.UpdateIdentity(accountId, req.session, req.ctx, logger, req.language(), identity)
if jerr != nil {
- return req.errorResponseFromJmap(jerr)
+ return req.errorResponseFromJmap(accountId, jerr)
}
- return etagResponse(updated, sessionState, state, lang)
+ return etagResponse(accountId, updated, sessionState, IdentityResponseObjectType, state, lang)
})
}
@@ -109,27 +109,27 @@ func (g *Groupware) DeleteIdentity(w http.ResponseWriter, r *http.Request) {
g.respond(w, r, func(req Request) Response {
accountId, err := req.GetAccountIdForMail()
if err != nil {
- return errorResponse(err)
+ return errorResponse(accountId, err)
}
logger := log.From(req.logger.With().Str(logAccountId, accountId))
id := chi.URLParam(r, UriParamIdentityId)
ids := strings.Split(id, ",")
if len(ids) < 1 {
- return req.parameterErrorResponse(UriParamEmailId, fmt.Sprintf("Invalid value for path parameter '%v': '%s': %s", UriParamIdentityId, log.SafeString(id), "empty list of identity ids"))
+ return req.parameterErrorResponse(accountId, UriParamEmailId, fmt.Sprintf("Invalid value for path parameter '%v': '%s': %s", UriParamIdentityId, log.SafeString(id), "empty list of identity ids"))
}
deletion, sessionState, state, _, jerr := g.jmap.DeleteIdentity(accountId, req.session, req.ctx, logger, req.language(), ids)
if jerr != nil {
- return req.errorResponseFromJmap(jerr)
+ return req.errorResponseFromJmap(accountId, jerr)
}
notDeletedIds := structs.Missing(ids, deletion)
if len(notDeletedIds) == 0 {
- return noContentResponseWithEtag(sessionState, state)
+ return noContentResponseWithEtag(accountId, sessionState, IdentityResponseObjectType, state)
} else {
logger.Error().Array("not-deleted", log.SafeStringArray(notDeletedIds)).Msgf("failed to delete %d identities", len(notDeletedIds))
- return errorResponseWithSessionState(req.apiError(&ErrorFailedToDeleteSomeIdentities,
+ return errorResponseWithSessionState(accountId, req.apiError(&ErrorFailedToDeleteSomeIdentities,
withMeta(map[string]any{"ids": notDeletedIds})), sessionState)
}
})
diff --git a/services/groupware/pkg/groupware/groupware_api_index.go b/services/groupware/pkg/groupware/groupware_api_index.go
index e2adea216..1861344ca 100644
--- a/services/groupware/pkg/groupware/groupware_api_index.go
+++ b/services/groupware/pkg/groupware/groupware_api_index.go
@@ -6,7 +6,6 @@ import (
"strings"
"github.com/opencloud-eu/opencloud/pkg/jmap"
- "github.com/opencloud-eu/opencloud/pkg/structs"
)
type IndexLimits struct {
@@ -160,20 +159,20 @@ type SwaggerIndexResponse struct {
// 200: IndexResponse
func (g *Groupware) Index(w http.ResponseWriter, r *http.Request) {
g.respond(w, r, func(req Request) Response {
- accountIds := structs.Keys(req.session.Accounts)
+ accountIds := req.AllAccountIds()
boot, sessionState, state, lang, err := g.jmap.GetBootstrap(accountIds, req.session, req.ctx, req.logger, req.language())
if err != nil {
- return req.errorResponseFromJmap(err)
+ return req.errorResponseFromJmap(joinAccountIds(accountIds), err)
}
- return etagResponse(IndexResponse{
+ return etagResponse(joinAccountIds(accountIds), IndexResponse{
Version: Version,
Capabilities: Capabilities,
Limits: buildIndexLimits(req.session),
Accounts: buildIndexAccounts(req.session, boot),
PrimaryAccounts: buildIndexPrimaryAccounts(req.session),
- }, sessionState, state, lang)
+ }, sessionState, IndexResponseObjectType, state, lang)
})
}
diff --git a/services/groupware/pkg/groupware/groupware_api_mailbox.go b/services/groupware/pkg/groupware/groupware_api_mailbox.go
index e4b0635d6..bde46f008 100644
--- a/services/groupware/pkg/groupware/groupware_api_mailbox.go
+++ b/services/groupware/pkg/groupware/groupware_api_mailbox.go
@@ -10,7 +10,6 @@ import (
"github.com/opencloud-eu/opencloud/pkg/jmap"
"github.com/opencloud-eu/opencloud/pkg/log"
- "github.com/opencloud-eu/opencloud/pkg/structs"
)
// When the request succeeds.
@@ -40,18 +39,18 @@ func (g *Groupware) GetMailbox(w http.ResponseWriter, r *http.Request) {
g.respond(w, r, func(req Request) Response {
accountId, err := req.GetAccountIdForMail()
if err != nil {
- return errorResponse(err)
+ return errorResponse(accountId, err)
}
mailboxes, sessionState, state, lang, jerr := g.jmap.GetMailbox(accountId, req.session, req.ctx, req.logger, req.language(), []string{mailboxId})
if jerr != nil {
- return req.errorResponseFromJmap(jerr)
+ return req.errorResponseFromJmap(accountId, jerr)
}
if len(mailboxes.Mailboxes) == 1 {
- return etagResponse(mailboxes.Mailboxes[0], sessionState, state, lang)
+ return etagResponse(accountId, mailboxes.Mailboxes[0], sessionState, MailboxResponseObjectType, state, lang)
} else {
- return notFoundResponse(sessionState)
+ return notFoundResponse(accountId, sessionState)
}
})
}
@@ -109,41 +108,42 @@ func (g *Groupware) GetMailboxes(w http.ResponseWriter, r *http.Request) {
}
g.respond(w, r, func(req Request) Response {
+ accountId, err := req.GetAccountIdForMail()
+ if err != nil {
+ return errorResponse(accountId, err)
+ }
+
subscribed, set, err := req.parseBoolParam(QueryParamMailboxSearchSubscribed, false)
if err != nil {
- return errorResponse(err)
+ return errorResponse(accountId, err)
}
if set {
filter.IsSubscribed = &subscribed
hasCriteria = true
}
- accountId, err := req.GetAccountIdForMail()
- if err != nil {
- return errorResponse(err)
- }
logger := log.From(req.logger.With().Str(logAccountId, accountId))
if hasCriteria {
mailboxesByAccountId, sessionState, state, lang, err := g.jmap.SearchMailboxes([]string{accountId}, req.session, req.ctx, logger, req.language(), filter)
if err != nil {
- return req.errorResponseFromJmap(err)
+ return req.errorResponseFromJmap(accountId, err)
}
if mailboxes, ok := mailboxesByAccountId[accountId]; ok {
- return etagResponse(sortMailboxSlice(mailboxes), sessionState, state, lang)
+ return etagResponse(accountId, sortMailboxSlice(mailboxes), sessionState, MailboxResponseObjectType, state, lang)
} else {
- return notFoundResponse(sessionState)
+ return notFoundResponse(accountId, sessionState)
}
} else {
mailboxesByAccountId, sessionState, state, lang, err := g.jmap.GetAllMailboxes([]string{accountId}, req.session, req.ctx, logger, req.language())
if err != nil {
- return req.errorResponseFromJmap(err)
+ return req.errorResponseFromJmap(accountId, err)
}
if mailboxes, ok := mailboxesByAccountId[accountId]; ok {
- return etagResponse(sortMailboxSlice(mailboxes), sessionState, state, lang)
+ return etagResponse(accountId, sortMailboxSlice(mailboxes), sessionState, MailboxResponseObjectType, state, lang)
} else {
- return notFoundResponse(sessionState)
+ return notFoundResponse(accountId, sessionState)
}
}
})
@@ -177,33 +177,33 @@ func (g *Groupware) GetMailboxesForAllAccounts(w http.ResponseWriter, r *http.Re
}
g.respond(w, r, func(req Request) Response {
+ accountIds := req.AllAccountIds()
+ if len(accountIds) < 1 {
+ return noContentResponse("", "")
+ }
+ logger := log.From(req.logger.With().Array(logAccountId, log.SafeStringArray(accountIds)))
+
subscribed, set, err := req.parseBoolParam(QueryParamMailboxSearchSubscribed, false)
if err != nil {
- return errorResponse(err)
+ return errorResponse(joinAccountIds(accountIds), err)
}
if set {
filter.IsSubscribed = &subscribed
hasCriteria = true
}
- accountIds := structs.Keys(req.session.Accounts)
- if len(accountIds) < 1 {
- return noContentResponse("")
- }
- logger := log.From(req.logger.With().Array(logAccountId, log.SafeStringArray(accountIds)))
-
if hasCriteria {
mailboxesByAccountId, sessionState, state, lang, err := g.jmap.SearchMailboxes(accountIds, req.session, req.ctx, logger, req.language(), filter)
if err != nil {
- return req.errorResponseFromJmap(err)
+ return req.errorResponseFromJmap(joinAccountIds(accountIds), err)
}
- return etagResponse(sortMailboxesMap(mailboxesByAccountId), sessionState, state, lang)
+ return etagResponse(joinAccountIds(accountIds), sortMailboxesMap(mailboxesByAccountId), sessionState, MailboxResponseObjectType, state, lang)
} else {
mailboxesByAccountId, sessionState, state, lang, err := g.jmap.GetAllMailboxes(accountIds, req.session, req.ctx, logger, req.language())
if err != nil {
- return req.errorResponseFromJmap(err)
+ return req.errorResponseFromJmap(joinAccountIds(accountIds), err)
}
- return etagResponse(sortMailboxesMap(mailboxesByAccountId), sessionState, state, lang)
+ return etagResponse(joinAccountIds(accountIds), sortMailboxesMap(mailboxesByAccountId), sessionState, MailboxResponseObjectType, state, lang)
}
})
}
@@ -211,9 +211,9 @@ func (g *Groupware) GetMailboxesForAllAccounts(w http.ResponseWriter, r *http.Re
func (g *Groupware) GetMailboxByRoleForAllAccounts(w http.ResponseWriter, r *http.Request) {
role := chi.URLParam(r, UriParamRole)
g.respond(w, r, func(req Request) Response {
- accountIds := structs.Keys(req.session.Accounts)
+ accountIds := req.AllAccountIds()
if len(accountIds) < 1 {
- return noContentResponse("")
+ return noContentResponse("", "")
}
logger := log.From(req.logger.With().Array(logAccountId, log.SafeStringArray(accountIds)).Str("role", role))
@@ -223,9 +223,9 @@ func (g *Groupware) GetMailboxByRoleForAllAccounts(w http.ResponseWriter, r *htt
mailboxesByAccountId, sessionState, state, lang, err := g.jmap.SearchMailboxes(accountIds, req.session, req.ctx, logger, req.language(), filter)
if err != nil {
- return req.errorResponseFromJmap(err)
+ return req.errorResponseFromJmap(joinAccountIds(accountIds), err)
}
- return etagResponse(sortMailboxesMap(mailboxesByAccountId), sessionState, state, lang)
+ return etagResponse(joinAccountIds(accountIds), sortMailboxesMap(mailboxesByAccountId), sessionState, MailboxResponseObjectType, state, lang)
})
}
@@ -251,28 +251,28 @@ func (g *Groupware) GetMailboxChanges(w http.ResponseWriter, r *http.Request) {
g.respond(w, r, func(req Request) Response {
l := req.logger.With().Str(HeaderSince, sinceState)
+ accountId, err := req.GetAccountIdForMail()
+ if err != nil {
+ return errorResponse(accountId, err)
+ }
+ l = l.Str(logAccountId, accountId)
+
maxChanges, ok, err := req.parseUIntParam(QueryParamMaxChanges, 0)
if err != nil {
- return errorResponse(err)
+ return errorResponse(accountId, err)
}
if ok {
l = l.Uint(QueryParamMaxChanges, maxChanges)
}
- accountId, err := req.GetAccountIdForMail()
- if err != nil {
- return errorResponse(err)
- }
- l = l.Str(logAccountId, accountId)
-
logger := log.From(l)
changes, sessionState, state, lang, jerr := g.jmap.GetMailboxChanges(accountId, req.session, req.ctx, logger, req.language(), mailboxId, sinceState, true, g.maxBodyValueBytes, maxChanges)
if jerr != nil {
- return req.errorResponseFromJmap(jerr)
+ return req.errorResponseFromJmap(accountId, jerr)
}
- return etagResponse(changes, sessionState, state, lang)
+ return etagResponse(accountId, changes, sessionState, MailboxResponseObjectType, state, lang)
})
}
@@ -295,9 +295,12 @@ func (g *Groupware) GetMailboxChangesForAllAccounts(w http.ResponseWriter, r *ht
g.respond(w, r, func(req Request) Response {
l := req.logger.With()
+ allAccountIds := req.AllAccountIds()
+ l.Array(logAccountId, log.SafeStringArray(allAccountIds))
+
sinceStateMap, ok, err := req.parseMapParam(QueryParamSince)
if err != nil {
- return errorResponse(err)
+ return errorResponse(joinAccountIds(allAccountIds), err)
}
if ok {
dict := zerolog.Dict()
@@ -309,39 +312,36 @@ func (g *Groupware) GetMailboxChangesForAllAccounts(w http.ResponseWriter, r *ht
maxChanges, ok, err := req.parseUIntParam(QueryParamMaxChanges, 0)
if err != nil {
- return errorResponse(err)
+ return errorResponse(joinAccountIds(allAccountIds), err)
}
if ok {
l = l.Uint(QueryParamMaxChanges, maxChanges)
}
- allAccountIds := structs.Keys(req.session.Accounts) // TODO(pbleser-oc) do we need a limit for a maximum amount of accounts to query at once?
- l.Array(logAccountId, log.SafeStringArray(allAccountIds))
-
logger := log.From(l)
changesByAccountId, sessionState, state, lang, jerr := g.jmap.GetMailboxChangesForMultipleAccounts(allAccountIds, req.session, req.ctx, logger, req.language(), sinceStateMap, true, g.maxBodyValueBytes, maxChanges)
if jerr != nil {
- return req.errorResponseFromJmap(jerr)
+ return req.errorResponseFromJmap(joinAccountIds(allAccountIds), jerr)
}
- return etagResponse(changesByAccountId, sessionState, state, lang)
+ return etagResponse(joinAccountIds(allAccountIds), changesByAccountId, sessionState, MailboxResponseObjectType, state, lang)
})
}
func (g *Groupware) GetMailboxRoles(w http.ResponseWriter, r *http.Request) {
g.respond(w, r, func(req Request) Response {
l := req.logger.With()
- allAccountIds := structs.Keys(req.session.Accounts) // TODO(pbleser-oc) do we need a limit for a maximum amount of accounts to query at once?
+ allAccountIds := req.AllAccountIds()
l.Array(logAccountId, log.SafeStringArray(allAccountIds))
logger := log.From(l)
rolesByAccountId, sessionState, state, lang, jerr := g.jmap.GetMailboxRolesForMultipleAccounts(allAccountIds, req.session, req.ctx, logger, req.language())
if jerr != nil {
- return req.errorResponseFromJmap(jerr)
+ return req.errorResponseFromJmap(joinAccountIds(allAccountIds), jerr)
}
- return etagResponse(rolesByAccountId, sessionState, state, lang)
+ return etagResponse(joinAccountIds(allAccountIds), rolesByAccountId, sessionState, MailboxResponseObjectType, state, lang)
})
}
@@ -353,23 +353,23 @@ func (g *Groupware) UpdateMailbox(w http.ResponseWriter, r *http.Request) {
accountId, err := req.GetAccountIdForMail()
if err != nil {
- return errorResponse(err)
+ return errorResponse(accountId, err)
}
l = l.Str(logAccountId, accountId)
var body jmap.MailboxChange
err = req.body(&body)
if err != nil {
- return errorResponse(err)
+ return errorResponse(accountId, err)
}
logger := log.From(l)
updated, sessionState, state, lang, jerr := g.jmap.UpdateMailbox(accountId, req.session, req.ctx, logger, req.language(), mailboxId, "", body)
if jerr != nil {
- return req.errorResponseFromJmap(jerr)
+ return req.errorResponseFromJmap(accountId, jerr)
}
- return etagResponse(updated, sessionState, state, lang)
+ return etagResponse(accountId, updated, sessionState, MailboxResponseObjectType, state, lang)
})
}
@@ -378,23 +378,23 @@ func (g *Groupware) CreateMailbox(w http.ResponseWriter, r *http.Request) {
l := req.logger.With()
accountId, err := req.GetAccountIdForMail()
if err != nil {
- return errorResponse(err)
+ return errorResponse(accountId, err)
}
l = l.Str(logAccountId, accountId)
var body jmap.MailboxChange
err = req.body(&body)
if err != nil {
- return errorResponse(err)
+ return errorResponse(accountId, err)
}
logger := log.From(l)
created, sessionState, state, lang, jerr := g.jmap.CreateMailbox(accountId, req.session, req.ctx, logger, req.language(), "", body)
if jerr != nil {
- return req.errorResponseFromJmap(jerr)
+ return req.errorResponseFromJmap(accountId, jerr)
}
- return etagResponse(created, sessionState, state, lang)
+ return etagResponse(accountId, created, sessionState, MailboxResponseObjectType, state, lang)
})
}
@@ -403,25 +403,26 @@ func (g *Groupware) DeleteMailbox(w http.ResponseWriter, r *http.Request) {
mailboxIds := strings.Split(mailboxId, ",")
g.respond(w, r, func(req Request) Response {
- if len(mailboxIds) < 1 {
- return noContentResponse(req.session.State)
- }
-
l := req.logger.With()
accountId, err := req.GetAccountIdForMail()
if err != nil {
- return errorResponse(err)
+ return errorResponse(accountId, err)
}
l = l.Str(logAccountId, accountId)
+
+ if len(mailboxIds) < 1 {
+ return noContentResponse(accountId, req.session.State)
+ }
+
l = l.Array(UriParamMailboxId, log.SafeStringArray(mailboxIds))
logger := log.From(l)
deleted, sessionState, state, lang, jerr := g.jmap.DeleteMailboxes(accountId, req.session, req.ctx, logger, req.language(), "", mailboxIds)
if jerr != nil {
- return req.errorResponseFromJmap(jerr)
+ return req.errorResponseFromJmap(accountId, jerr)
}
- return etagResponse(deleted, sessionState, state, lang)
+ return etagResponse(accountId, deleted, sessionState, MailboxResponseObjectType, state, lang)
})
}
diff --git a/services/groupware/pkg/groupware/groupware_api_quota.go b/services/groupware/pkg/groupware/groupware_api_quota.go
index 0731fad0e..f6a6c6ae8 100644
--- a/services/groupware/pkg/groupware/groupware_api_quota.go
+++ b/services/groupware/pkg/groupware/groupware_api_quota.go
@@ -5,7 +5,6 @@ import (
"github.com/opencloud-eu/opencloud/pkg/jmap"
"github.com/opencloud-eu/opencloud/pkg/log"
- "github.com/opencloud-eu/opencloud/pkg/structs"
)
// When the request succeeds.
@@ -27,18 +26,18 @@ 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)
+ return errorResponse(accountId, err)
}
logger := log.From(req.logger.With().Str(logAccountId, accountId))
res, sessionState, state, lang, jerr := g.jmap.GetQuotas([]string{accountId}, req.session, req.ctx, logger, req.language())
if jerr != nil {
- return req.errorResponseFromJmap(jerr)
+ return req.errorResponseFromJmap(accountId, jerr)
}
for _, v := range res {
- return etagResponse(v.List, sessionState, state, lang)
+ return etagResponse(accountId, v.List, sessionState, QuotaResponseObjectType, state, lang)
}
- return notFoundResponse(sessionState)
+ return notFoundResponse(accountId, sessionState)
})
}
@@ -64,15 +63,15 @@ type SwaggerGetQuotaForAllAccountsResponse200 struct {
// 500: ErrorResponse500
func (g *Groupware) GetQuotaForAllAccounts(w http.ResponseWriter, r *http.Request) {
g.respond(w, r, func(req Request) Response {
- accountIds := structs.Keys(req.session.Accounts)
+ accountIds := req.AllAccountIds()
if len(accountIds) < 1 {
- return noContentResponse("")
+ return noContentResponse(joinAccountIds(accountIds), "")
}
logger := log.From(req.logger.With().Array(logAccountId, log.SafeStringArray(accountIds)))
res, sessionState, state, lang, jerr := g.jmap.GetQuotas(accountIds, req.session, req.ctx, logger, req.language())
if jerr != nil {
- return req.errorResponseFromJmap(jerr)
+ return req.errorResponseFromJmap(joinAccountIds(accountIds), jerr)
}
result := make(map[string]AccountQuota, len(res))
@@ -82,6 +81,6 @@ func (g *Groupware) GetQuotaForAllAccounts(w http.ResponseWriter, r *http.Reques
Quotas: accountQuotas.List,
}
}
- return etagResponse(result, sessionState, state, lang)
+ return etagResponse(joinAccountIds(accountIds), result, sessionState, QuotaResponseObjectType, state, lang)
})
}
diff --git a/services/groupware/pkg/groupware/groupware_api_tasklists.go b/services/groupware/pkg/groupware/groupware_api_tasklists.go
index 4ffb6bc80..0a9239294 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 etagResponse(accountId, AllTaskLists, req.session.State, TaskListResponseObjectType, TaskListsState, "")
})
}
@@ -65,10 +65,10 @@ 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(accountId, tasklist, req.session.State, "")
}
}
- return notFoundResponse(req.session.State)
+ return etagNotFoundResponse(accountId, req.session.State, TaskListResponseObjectType, TaskListsState, "")
})
}
@@ -100,8 +100,8 @@ func (g *Groupware) GetTasksInTaskList(w http.ResponseWriter, r *http.Request) {
// TODO replace with proper implementation
tasks, ok := TaskMapByTaskListId[tasklistId]
if !ok {
- return notFoundResponse(req.session.State)
+ return notFoundResponse(accountId, req.session.State)
}
- return response(tasks, req.session.State, "")
+ return etagResponse(accountId, tasks, req.session.State, TaskResponseObjectType, TaskState, "")
})
}
diff --git a/services/groupware/pkg/groupware/groupware_api_vacation.go b/services/groupware/pkg/groupware/groupware_api_vacation.go
index e7c949c27..de00e05b1 100644
--- a/services/groupware/pkg/groupware/groupware_api_vacation.go
+++ b/services/groupware/pkg/groupware/groupware_api_vacation.go
@@ -33,15 +33,15 @@ func (g *Groupware) GetVacation(w http.ResponseWriter, r *http.Request) {
g.respond(w, r, func(req Request) Response {
accountId, err := req.GetAccountIdForVacationResponse()
if err != nil {
- return errorResponse(err)
+ return errorResponse(accountId, err)
}
logger := log.From(req.logger.With().Str(logAccountId, accountId))
res, sessionState, state, lang, jerr := g.jmap.GetVacationResponse(accountId, req.session, req.ctx, logger, req.language())
if jerr != nil {
- return req.errorResponseFromJmap(jerr)
+ return req.errorResponseFromJmap(accountId, jerr)
}
- return etagResponse(res, sessionState, state, lang)
+ return etagResponse(accountId, res, sessionState, VacationResponseResponseObjectType, state, lang)
})
}
@@ -67,23 +67,23 @@ type SwaggerSetVacationResponse200 struct {
// 500: ErrorResponse500
func (g *Groupware) SetVacation(w http.ResponseWriter, r *http.Request) {
g.respond(w, r, func(req Request) Response {
- var body jmap.VacationResponsePayload
- err := req.body(&body)
- if err != nil {
- return errorResponse(err)
- }
-
accountId, err := req.GetAccountIdForVacationResponse()
if err != nil {
- return errorResponse(err)
+ return errorResponse(accountId, err)
}
logger := log.From(req.logger.With().Str(logAccountId, accountId))
- res, sessionState, state, lang, jerr := g.jmap.SetVacationResponse(accountId, body, req.session, req.ctx, logger, req.language())
- if jerr != nil {
- return req.errorResponseFromJmap(jerr)
+ var body jmap.VacationResponsePayload
+ err = req.body(&body)
+ if err != nil {
+ return errorResponse(accountId, err)
}
- return etagResponse(res, sessionState, state, lang)
+ res, sessionState, state, lang, jerr := g.jmap.SetVacationResponse(accountId, body, req.session, req.ctx, logger, req.language())
+ if jerr != nil {
+ return req.errorResponseFromJmap(accountId, jerr)
+ }
+
+ return etagResponse(accountId, res, sessionState, VacationResponseResponseObjectType, state, lang)
})
}
diff --git a/services/groupware/pkg/groupware/groupware_error.go b/services/groupware/pkg/groupware/groupware_error.go
index b9ed2429b..b4fa532c5 100644
--- a/services/groupware/pkg/groupware/groupware_error.go
+++ b/services/groupware/pkg/groupware/groupware_error.go
@@ -629,6 +629,6 @@ func errorResponses(errors ...Error) ErrorResponse {
return ErrorResponse{Errors: errors}
}
-func (r Request) errorResponseFromJmap(err jmap.Error) Response {
- return errorResponse(r.apiErrorFromJmap(r.observeJmapError(err)))
+func (r Request) errorResponseFromJmap(accountId string, err jmap.Error) Response {
+ return errorResponse(accountId, r.apiErrorFromJmap(r.observeJmapError(err)))
}
diff --git a/services/groupware/pkg/groupware/groupware_framework.go b/services/groupware/pkg/groupware/groupware_framework.go
index f8bb5c5db..3826bbdbe 100644
--- a/services/groupware/pkg/groupware/groupware_framework.go
+++ b/services/groupware/pkg/groupware/groupware_framework.go
@@ -7,6 +7,8 @@ import (
"fmt"
"net/http"
"net/url"
+ "slices"
+ "strings"
"sync/atomic"
"time"
@@ -564,6 +566,13 @@ func (g *Groupware) withSession(w http.ResponseWriter, r *http.Request, handler
return response, time.Time{}, true
}
+const (
+ SessionStateResponseHeader = "Session-State"
+ StateResponseHeader = "State"
+ ObjectTypeResponseHeader = "Object-Type"
+ AccountIdResponseHeader = "Account-Id"
+)
+
func (g *Groupware) sendResponse(w http.ResponseWriter, r *http.Request, response Response) {
if response.err != nil {
g.log(response.err)
@@ -573,22 +582,8 @@ func (g *Groupware) sendResponse(w http.ResponseWriter, r *http.Request, respons
return
}
- etag := ""
- sessionState := ""
-
- if response.etag != "" {
- etag = string(response.etag)
- }
-
if response.sessionState != "" {
- sessionState = string(response.sessionState)
- if etag == "" {
- etag = sessionState
- }
- }
-
- if sessionState != "" {
- w.Header().Add("Session-State", string(sessionState))
+ w.Header().Add(SessionStateResponseHeader, string(response.sessionState))
}
if response.contentLanguage != "" {
@@ -596,11 +591,24 @@ func (g *Groupware) sendResponse(w http.ResponseWriter, r *http.Request, respons
}
notModified := false
- if etag != "" {
- challenge := r.Header.Get("if-none-match")
- quotedEtag := "\"" + etag + "\""
- notModified = challenge != "" && (challenge == etag || challenge == quotedEtag)
- w.Header().Add("ETag", quotedEtag)
+ {
+ etag := string(response.etag)
+ if etag != "" {
+ challenge := r.Header.Get("if-none-match")
+ quotedEtag := "\"" + etag + "\""
+ notModified = challenge != "" && (challenge == etag || challenge == quotedEtag)
+ w.Header().Add("ETag", quotedEtag)
+ w.Header().Add(StateResponseHeader, etag)
+ }
+ }
+ {
+ ot := string(response.objectType)
+ if ot != "" {
+ w.Header().Add(ObjectTypeResponseHeader, ot)
+ }
+ }
+ if response.accountId != "" {
+ w.Header().Add(AccountIdResponseHeader, response.accountId)
}
if notModified {
@@ -699,3 +707,17 @@ func (g *Groupware) MethodNotAllowed(w http.ResponseWriter, r *http.Request) {
w.Header().Add("Unsupported-Method", r.Method) // TODO possibly remove this in production for security reasons?
w.WriteHeader(http.StatusNotFound)
}
+
+func joinAccountIds(accountIds []string) string {
+ switch len(accountIds) {
+ case 0:
+ return ""
+ case 1:
+ return accountIds[0]
+ default:
+ c := make([]string, len(accountIds))
+ copy(c, accountIds)
+ slices.Sort(c)
+ return strings.Join(c, ",")
+ }
+}
diff --git a/services/groupware/pkg/groupware/groupware_mock_tasks.go b/services/groupware/pkg/groupware/groupware_mock_tasks.go
index 386c958ab..59d875a47 100644
--- a/services/groupware/pkg/groupware/groupware_mock_tasks.go
+++ b/services/groupware/pkg/groupware/groupware_mock_tasks.go
@@ -199,12 +199,16 @@ var T1 = jmap.Task{
var AllTaskLists = []jmap.TaskList{TL1}
+var TaskListsState = jmap.State("mock")
+
var TaskMapByTaskListId = map[string][]jmap.Task{
TL1.Id: {
T1,
},
}
+var TaskState = jmap.State("mock")
+
func mustParseTime(text string) time.Time {
t, err := time.Parse(time.RFC3339, text)
if err != nil {
diff --git a/services/groupware/pkg/groupware/groupware_request.go b/services/groupware/pkg/groupware/groupware_request.go
index 5ac5a7495..37cd6b21b 100644
--- a/services/groupware/pkg/groupware/groupware_request.go
+++ b/services/groupware/pkg/groupware/groupware_request.go
@@ -19,6 +19,7 @@ import (
"github.com/opencloud-eu/opencloud/pkg/jmap"
"github.com/opencloud-eu/opencloud/pkg/log"
+ "github.com/opencloud-eu/opencloud/pkg/structs"
"github.com/opencloud-eu/opencloud/services/groupware/pkg/metrics"
groupwaremiddleware "github.com/opencloud-eu/opencloud/services/groupware/pkg/middleware"
@@ -75,6 +76,11 @@ var (
// errNoPrimaryAccountForWebsocket = errors.New("no primary account for websocket")
)
+func (r Request) AllAccountIds() []string {
+ // TODO potentially filter on "subscribed" accounts?
+ return structs.Uniq(structs.Keys(r.session.Accounts))
+}
+
func (r Request) GetAccountIdWithoutFallback() (string, *Error) {
accountId := chi.URLParam(r.r, UriParamAccountId)
if accountId == "" || isDefaultAccountid(accountId) {
@@ -140,22 +146,22 @@ func (r Request) GetAccountIdForContact() (string, *Error) {
return r.GetAccountIdForMail()
}
-func (r Request) GetAccountForMail() (jmap.Account, *Error) {
+func (r Request) GetAccountForMail() (string, jmap.Account, *Error) {
accountId, err := r.GetAccountIdForMail()
if err != nil {
- return jmap.Account{}, err
+ return "", jmap.Account{}, err
}
account, ok := r.session.Accounts[accountId]
if !ok {
r.logger.Debug().Msgf("failed to find account '%v'", accountId)
// TODO metric for inexistent accounts
- return jmap.Account{}, apiError(r.errorId(), ErrorNonExistingAccount,
+ return accountId, jmap.Account{}, apiError(r.errorId(), ErrorNonExistingAccount,
withDetail(fmt.Sprintf("The account '%v' does not exist", log.SafeString(accountId))),
withSource(&ErrorSource{Parameter: UriParamAccountId}),
)
}
- return account, nil
+ return accountId, account, nil
}
func (r Request) parameterError(param string, detail string) *Error {
@@ -164,8 +170,8 @@ func (r Request) parameterError(param string, detail string) *Error {
withSource(&ErrorSource{Parameter: param}))
}
-func (r Request) parameterErrorResponse(param string, detail string) Response {
- return errorResponse(r.parameterError(param, detail))
+func (r Request) parameterErrorResponse(accountId string, param string, detail string) Response {
+ return errorResponse(accountId, r.parameterError(param, detail))
}
func (r Request) getStringParam(param string, defaultValue string) (string, bool) {
@@ -346,26 +352,26 @@ func (r Request) observeJmapError(jerr jmap.Error) jmap.Error {
return jerr
}
-func (r Request) needTask() (bool, Response) {
+func (r Request) needTask(accountId string) (bool, Response) {
if !IgnoreSessionCapabilityChecks {
if r.session.Capabilities.Tasks == nil {
- return false, errorResponseWithSessionState(r.apiError(&ErrorMissingTasksSessionCapability), r.session.State)
+ return false, errorResponseWithSessionState(accountId, r.apiError(&ErrorMissingTasksSessionCapability), r.session.State)
}
}
return true, Response{}
}
func (r Request) needTaskForAccount(accountId string) (bool, Response) {
- if ok, resp := r.needTask(); !ok {
+ if ok, resp := r.needTask(accountId); !ok {
return ok, resp
}
account, ok := r.session.Accounts[accountId]
if !ok {
- return false, errorResponseWithSessionState(r.apiError(&ErrorAccountNotFound), r.session.State)
+ return false, errorResponseWithSessionState(accountId, r.apiError(&ErrorAccountNotFound), r.session.State)
}
if !IgnoreSessionCapabilityChecks {
if account.AccountCapabilities.Tasks == nil {
- return false, errorResponseWithSessionState(r.apiError(&ErrorMissingTasksAccountCapability), r.session.State)
+ return false, errorResponseWithSessionState(accountId, r.apiError(&ErrorMissingTasksAccountCapability), r.session.State)
}
}
return true, Response{}
@@ -374,7 +380,7 @@ func (r Request) needTaskForAccount(accountId string) (bool, Response) {
func (r Request) needTaskWithAccount() (bool, string, Response) {
accountId, err := r.GetAccountIdForTask()
if err != nil {
- return false, "", errorResponse(err)
+ return false, "", errorResponse(accountId, err)
}
if !IgnoreSessionCapabilityChecks {
if ok, resp := r.needTaskForAccount(accountId); !ok {
@@ -384,26 +390,26 @@ func (r Request) needTaskWithAccount() (bool, string, Response) {
return true, accountId, Response{}
}
-func (r Request) needCalendar() (bool, Response) {
+func (r Request) needCalendar(accountId string) (bool, Response) {
if !IgnoreSessionCapabilityChecks {
if r.session.Capabilities.Calendars == nil {
- return false, errorResponseWithSessionState(r.apiError(&ErrorMissingCalendarsSessionCapability), r.session.State)
+ return false, errorResponseWithSessionState(accountId, r.apiError(&ErrorMissingCalendarsSessionCapability), r.session.State)
}
}
return true, Response{}
}
func (r Request) needCalendarForAccount(accountId string) (bool, Response) {
- if ok, resp := r.needCalendar(); !ok {
+ if ok, resp := r.needCalendar(accountId); !ok {
return ok, resp
}
account, ok := r.session.Accounts[accountId]
if !ok {
- return false, errorResponseWithSessionState(r.apiError(&ErrorAccountNotFound), r.session.State)
+ return false, errorResponseWithSessionState(accountId, r.apiError(&ErrorAccountNotFound), r.session.State)
}
if !IgnoreSessionCapabilityChecks {
if account.AccountCapabilities.Calendars == nil {
- return false, errorResponseWithSessionState(r.apiError(&ErrorMissingCalendarsAccountCapability), r.session.State)
+ return false, errorResponseWithSessionState(accountId, r.apiError(&ErrorMissingCalendarsAccountCapability), r.session.State)
}
}
return true, Response{}
@@ -412,7 +418,7 @@ func (r Request) needCalendarForAccount(accountId string) (bool, Response) {
func (r Request) needCalendarWithAccount() (bool, string, Response) {
accountId, err := r.GetAccountIdForCalendar()
if err != nil {
- return false, "", errorResponse(err)
+ return false, "", errorResponse(accountId, err)
}
if !IgnoreSessionCapabilityChecks {
if ok, resp := r.needCalendarForAccount(accountId); !ok {
@@ -422,23 +428,23 @@ func (r Request) needCalendarWithAccount() (bool, string, Response) {
return true, accountId, Response{}
}
-func (r Request) needContact() (bool, Response) {
+func (r Request) needContact(accountId string) (bool, Response) {
if r.session.Capabilities.Contacts == nil {
- return false, errorResponseWithSessionState(r.apiError(&ErrorMissingContactsSessionCapability), r.session.State)
+ return false, errorResponseWithSessionState(accountId, r.apiError(&ErrorMissingContactsSessionCapability), r.session.State)
}
return true, Response{}
}
func (r Request) needContactForAccount(accountId string) (bool, Response) {
- if ok, resp := r.needContact(); !ok {
+ if ok, resp := r.needContact(accountId); !ok {
return ok, resp
}
account, ok := r.session.Accounts[accountId]
if !ok {
- return false, errorResponseWithSessionState(r.apiError(&ErrorAccountNotFound), r.session.State)
+ return false, errorResponseWithSessionState(accountId, r.apiError(&ErrorAccountNotFound), r.session.State)
}
if account.AccountCapabilities.Contacts == nil {
- return false, errorResponseWithSessionState(r.apiError(&ErrorMissingContactsAccountCapability), r.session.State)
+ return false, errorResponseWithSessionState(accountId, r.apiError(&ErrorMissingContactsAccountCapability), r.session.State)
}
return true, Response{}
}
@@ -446,7 +452,7 @@ func (r Request) needContactForAccount(accountId string) (bool, Response) {
func (r Request) needContactWithAccount() (bool, string, Response) {
accountId, err := r.GetAccountIdForContact()
if err != nil {
- return false, "", errorResponse(err)
+ return false, "", errorResponse(accountId, err)
}
if ok, resp := r.needContactForAccount(accountId); !ok {
return false, accountId, resp
diff --git a/services/groupware/pkg/groupware/groupware_response.go b/services/groupware/pkg/groupware/groupware_response.go
index a2f283eec..8d3579c9b 100644
--- a/services/groupware/pkg/groupware/groupware_response.go
+++ b/services/groupware/pkg/groupware/groupware_response.go
@@ -6,17 +6,39 @@ import (
"github.com/opencloud-eu/opencloud/pkg/jmap"
)
+type ResponseObjectType string
+
+const (
+ IndexResponseObjectType = ResponseObjectType("index")
+ AccountResponseObjectType = ResponseObjectType("account")
+ IdentityResponseObjectType = ResponseObjectType("identity")
+ BlobResponseObjectType = ResponseObjectType("blob")
+ CalendarResponseObjectType = ResponseObjectType("calendar")
+ EventResponseObjectType = ResponseObjectType("event")
+ AddressBookResponseObjectType = ResponseObjectType("addressbook")
+ ContactResponseObjectType = ResponseObjectType("contact")
+ EmailResponseObjectType = ResponseObjectType("email")
+ MailboxResponseObjectType = ResponseObjectType("mailbox")
+ QuotaResponseObjectType = ResponseObjectType("quota")
+ TaskListResponseObjectType = ResponseObjectType("tasklist")
+ TaskResponseObjectType = ResponseObjectType("task")
+ VacationResponseResponseObjectType = ResponseObjectType("vacationresponse")
+)
+
type Response struct {
body any
status int
err *Error
etag jmap.State
+ objectType ResponseObjectType
+ accountId string
sessionState jmap.SessionState
contentLanguage jmap.Language
}
-func errorResponse(err *Error) Response {
+func errorResponse(accountId string, err *Error) Response {
return Response{
+ accountId: accountId,
body: nil,
err: err,
etag: "",
@@ -24,8 +46,9 @@ func errorResponse(err *Error) Response {
}
}
-func errorResponseWithSessionState(err *Error, sessionState jmap.SessionState) Response {
+func errorResponseWithSessionState(accountId string, err *Error, sessionState jmap.SessionState) Response {
return Response{
+ accountId: accountId,
body: nil,
err: err,
etag: "",
@@ -33,8 +56,9 @@ func errorResponseWithSessionState(err *Error, sessionState jmap.SessionState) R
}
}
-func response(body any, sessionState jmap.SessionState, contentLanguage jmap.Language) Response {
+func response(accountId string, body any, sessionState jmap.SessionState, contentLanguage jmap.Language) Response {
return Response{
+ accountId: accountId,
body: body,
err: nil,
etag: jmap.State(sessionState),
@@ -43,28 +67,34 @@ func response(body any, sessionState jmap.SessionState, contentLanguage jmap.Lan
}
}
-func etagResponse(body any, sessionState jmap.SessionState, etag jmap.State, contentLanguage jmap.Language) Response {
+func etagResponse(accountId string, body any, sessionState jmap.SessionState, objectType ResponseObjectType, etag jmap.State, contentLanguage jmap.Language) Response {
return Response{
+ accountId: accountId,
body: body,
err: nil,
etag: etag,
+ objectType: objectType,
sessionState: sessionState,
contentLanguage: contentLanguage,
}
}
-func etagOnlyResponse(body any, etag jmap.State, contentLanguage jmap.Language) Response {
+/*
+func etagOnlyResponse(body any, etag jmap.State, objectType ResponseObjectType, contentLanguage jmap.Language) Response {
return Response{
body: body,
err: nil,
etag: etag,
+ objectType: objectType,
sessionState: "",
contentLanguage: contentLanguage,
}
}
+*/
-func noContentResponse(sessionState jmap.SessionState) Response {
+func noContentResponse(accountId string, sessionState jmap.SessionState) Response {
return Response{
+ accountId: accountId,
body: nil,
status: http.StatusNoContent,
err: nil,
@@ -73,12 +103,14 @@ func noContentResponse(sessionState jmap.SessionState) Response {
}
}
-func noContentResponseWithEtag(sessionState jmap.SessionState, etag jmap.State) Response {
+func noContentResponseWithEtag(accountId string, sessionState jmap.SessionState, objectType ResponseObjectType, etag jmap.State) Response {
return Response{
+ accountId: accountId,
body: nil,
status: http.StatusNoContent,
err: nil,
etag: etag,
+ objectType: objectType,
sessionState: sessionState,
}
}
@@ -107,16 +139,30 @@ func timeoutResponse(sessionState jmap.SessionState) Response {
}
*/
-func notFoundResponse(sessionState jmap.SessionState) Response {
+func notFoundResponse(accountId string, sessionState jmap.SessionState) Response {
return Response{
+ accountId: accountId,
body: nil,
status: http.StatusNotFound,
err: nil,
- etag: jmap.State(sessionState),
+ etag: "",
sessionState: sessionState,
}
}
+func etagNotFoundResponse(accountId string, sessionState jmap.SessionState, objectType ResponseObjectType, etag jmap.State, contentLanguage jmap.Language) Response {
+ return Response{
+ accountId: accountId,
+ body: nil,
+ status: http.StatusNotFound,
+ err: nil,
+ etag: etag,
+ objectType: objectType,
+ sessionState: sessionState,
+ contentLanguage: contentLanguage,
+ }
+}
+
func notImplementesResponse() Response {
return Response{
body: nil,