From 196ee7b3e430194b8d31dae781b84da2e9439f7a Mon Sep 17 00:00:00 2001 From: Pascal Bleser Date: Thu, 4 Dec 2025 09:38:00 +0100 Subject: [PATCH] groupware: refactor response objects to take a list of accountIds --- .../pkg/groupware/groupware_api_account.go | 10 +- .../pkg/groupware/groupware_api_blob.go | 16 +- .../pkg/groupware/groupware_api_calendars.go | 42 ++-- .../pkg/groupware/groupware_api_contacts.go | 42 ++-- .../pkg/groupware/groupware_api_emails.go | 218 +++++++++--------- .../pkg/groupware/groupware_api_identity.go | 40 ++-- .../pkg/groupware/groupware_api_index.go | 4 +- .../pkg/groupware/groupware_api_mailbox.go | 90 ++++---- .../pkg/groupware/groupware_api_quota.go | 16 +- .../pkg/groupware/groupware_api_tasklists.go | 10 +- .../pkg/groupware/groupware_api_vacation.go | 14 +- .../pkg/groupware/groupware_error.go | 4 +- .../pkg/groupware/groupware_framework.go | 203 +++++++++------- .../pkg/groupware/groupware_request.go | 28 +-- .../pkg/groupware/groupware_response.go | 34 +-- 15 files changed, 409 insertions(+), 362 deletions(-) diff --git a/services/groupware/pkg/groupware/groupware_api_account.go b/services/groupware/pkg/groupware/groupware_api_account.go index 96015fdea..52b12aa94 100644 --- a/services/groupware/pkg/groupware/groupware_api_account.go +++ b/services/groupware/pkg/groupware/groupware_api_account.go @@ -31,9 +31,9 @@ func (g *Groupware) GetAccount(w http.ResponseWriter, r *http.Request) { g.respond(w, r, func(req Request) Response { accountId, account, err := req.GetAccountForMail() if err != nil { - return errorResponse(accountId, err) + return errorResponse(single(accountId), err) } - return etagResponse(accountId, account, req.session.State, AccountResponseObjectType, jmap.State(req.session.State), "") + return etagResponse(single(accountId), account, req.session.State, AccountResponseObjectType, jmap.State(req.session.State), "") }) } @@ -66,7 +66,7 @@ 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 etagResponse(joinAccountIds(structs.Map(list, func(a AccountWithId) string { return a.AccountId })), list, req.session.State, AccountResponseObjectType, jmap.State(req.session.State), "") + return etagResponse(structs.Map(list, func(a AccountWithId) string { return a.AccountId }), list, req.session.State, AccountResponseObjectType, jmap.State(req.session.State), "") }) } @@ -75,7 +75,7 @@ func (g *Groupware) GetAccountsWithTheirIdentities(w http.ResponseWriter, r *htt 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(joinAccountIds(allAccountIds), err) + return req.errorResponseFromJmap(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(joinAccountIds(structs.Map(list, func(a AccountWithIdAndIdentities) string { return a.AccountId })), list, sessionState, AccountResponseObjectType, state, lang) + return etagResponse(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 b9224b51b..b733abf13 100644 --- a/services/groupware/pkg/groupware/groupware_api_blob.go +++ b/services/groupware/pkg/groupware/groupware_api_blob.go @@ -18,13 +18,13 @@ func (g *Groupware) GetBlobMeta(w http.ResponseWriter, r *http.Request) { g.respond(w, r, func(req Request) Response { accountId, err := req.GetAccountIdForBlob() if err != nil { - return errorResponse(accountId, err) + return errorResponse(single(accountId), err) } 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)) + return req.parameterErrorResponse(single(accountId), UriParamBlobId, fmt.Sprintf("Invalid value for path parameter '%v': empty", UriParamBlobId)) } l = l.Str(UriParamBlobId, blobId) @@ -32,13 +32,13 @@ func (g *Groupware) GetBlobMeta(w http.ResponseWriter, r *http.Request) { res, sessionState, state, lang, jerr := g.jmap.GetBlobMetadata(accountId, req.session, req.ctx, logger, req.language(), blobId) if jerr != nil { - return req.errorResponseFromJmap(accountId, jerr) + return req.errorResponseFromJmap(single(accountId), jerr) } blob := res if blob == nil { - return notFoundResponse(accountId, sessionState) + return notFoundResponse(single(accountId), sessionState) } - return etagResponse(accountId, res, sessionState, BlobResponseObjectType, state, lang) + return etagResponse(single(accountId), res, sessionState, BlobResponseObjectType, state, lang) }) } @@ -57,16 +57,16 @@ func (g *Groupware) UploadBlob(w http.ResponseWriter, r *http.Request) { accountId, err := req.GetAccountIdForBlob() if err != nil { - return errorResponse(accountId, err) + return errorResponse(single(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(accountId, jerr) + return req.errorResponseFromJmap(single(accountId), jerr) } - return response(accountId, resp, req.session.State, lang) + return response(single(accountId), resp, req.session.State, lang) }) } diff --git a/services/groupware/pkg/groupware/groupware_api_calendars.go b/services/groupware/pkg/groupware/groupware_api_calendars.go index 904bb4139..755d96c3e 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(accountId, jerr) + return req.errorResponseFromJmap(single(accountId), jerr) } - return etagResponse(accountId, calendars, sessionState, CalendarResponseObjectType, state, lang) + return etagResponse(single(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(accountId, jerr) + return req.errorResponseFromJmap(single(accountId), jerr) } if len(calendars.NotFound) > 0 { - return notFoundResponse(accountId, sessionState) + return notFoundResponse(single(accountId), sessionState) } else { - return etagResponse(accountId, calendars.Calendars[0], sessionState, CalendarResponseObjectType, state, lang) + return etagResponse(single(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(accountId, err) + return errorResponse(single(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(accountId, err) + return errorResponse(single(accountId), err) } if ok { l = l.Uint(QueryParamLimit, limit) @@ -135,15 +135,15 @@ func (g *Groupware) GetEventsInCalendar(w http.ResponseWriter, r *http.Request) sortBy := []jmap.CalendarEventComparator{{Property: jmap.CalendarEventPropertyUpdated, IsAscending: false}} 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) + eventsByAccountId, sessionState, state, lang, jerr := g.jmap.QueryCalendarEvents(single(accountId), req.session, req.ctx, logger, req.language(), filter, sortBy, offset, limit) if jerr != nil { - return req.errorResponseFromJmap(accountId, jerr) + return req.errorResponseFromJmap(single(accountId), jerr) } if events, ok := eventsByAccountId[accountId]; ok { - return etagResponse(accountId, events, sessionState, EventResponseObjectType, state, lang) + return etagResponse(single(accountId), events, sessionState, EventResponseObjectType, state, lang) } else { - return notFoundResponse(accountId, sessionState) + return notFoundResponse(single(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(accountId, err) + return errorResponse(single(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(accountId, jerr) + return req.errorResponseFromJmap(single(accountId), jerr) } - return etagResponse(accountId, created, sessionState, EventResponseObjectType, state, lang) + return etagResponse(single(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(accountId, jerr) + return req.errorResponseFromJmap(single(accountId), jerr) } for _, e := range deleted { desc := e.Description if desc != "" { - return errorResponseWithSessionState(accountId, apiError( + return errorResponseWithSessionState(single(accountId), apiError( req.errorId(), ErrorFailedToDeleteContact, withDetail(e.Description), ), sessionState) } else { - return errorResponseWithSessionState(accountId, apiError( + return errorResponseWithSessionState(single(accountId), apiError( req.errorId(), ErrorFailedToDeleteContact, ), sessionState) } } - return noContentResponseWithEtag(accountId, sessionState, EventResponseObjectType, state) + return noContentResponseWithEtag(single(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(accountId, err) + return errorResponse(single(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(accountId, jerr) + return req.errorResponseFromJmap(single(accountId), jerr) } - return etagResponse(accountId, resp, sessionState, EventResponseObjectType, state, lang) + return etagResponse(single(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 7f7e7fe37..71592c801 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(accountId, jerr) + return req.errorResponseFromJmap(single(accountId), jerr) } - return etagResponse(accountId, addressbooks, sessionState, AddressBookResponseObjectType, state, lang) + return etagResponse(single(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(accountId, jerr) + return req.errorResponseFromJmap(single(accountId), jerr) } if len(addressbooks.NotFound) > 0 { - return notFoundResponse(accountId, sessionState) + return notFoundResponse(single(accountId), sessionState) } else { - return etagResponse(accountId, addressbooks, sessionState, AddressBookResponseObjectType, state, lang) + return etagResponse(single(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(accountId, err) + return errorResponse(single(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(accountId, err) + return errorResponse(single(accountId), err) } if ok { l = l.Uint(QueryParamLimit, limit) @@ -135,15 +135,15 @@ func (g *Groupware) GetContactsInAddressbook(w http.ResponseWriter, r *http.Requ sortBy := []jmap.ContactCardComparator{{Property: jscontact.ContactCardPropertyUpdated, IsAscending: false}} 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) + contactsByAccountId, sessionState, state, lang, jerr := g.jmap.QueryContactCards(single(accountId), req.session, req.ctx, logger, req.language(), filter, sortBy, offset, limit) if jerr != nil { - return req.errorResponseFromJmap(accountId, jerr) + return req.errorResponseFromJmap(single(accountId), jerr) } if contacts, ok := contactsByAccountId[accountId]; ok { - return etagResponse(accountId, contacts, sessionState, ContactResponseObjectType, state, lang) + return etagResponse(single(accountId), contacts, sessionState, ContactResponseObjectType, state, lang) } else { - return etagNotFoundResponse(accountId, sessionState, ContactResponseObjectType, state, lang) + return etagNotFoundResponse(single(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(accountId, jerr) + return req.errorResponseFromJmap(single(accountId), jerr) } if contact, ok := contactsById[contactId]; ok { - return etagResponse(accountId, contact, sessionState, ContactResponseObjectType, state, lang) + return etagResponse(single(accountId), contact, sessionState, ContactResponseObjectType, state, lang) } else { - return etagNotFoundResponse(accountId, sessionState, ContactResponseObjectType, state, lang) + return etagNotFoundResponse(single(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(accountId, err) + return errorResponse(single(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(accountId, jerr) + return req.errorResponseFromJmap(single(accountId), jerr) } - return etagResponse(accountId, created, sessionState, ContactResponseObjectType, state, lang) + return etagResponse(single(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(accountId, jerr) + return req.errorResponseFromJmap(single(accountId), jerr) } for _, e := range deleted { desc := e.Description if desc != "" { - return errorResponseWithSessionState(accountId, apiError( + return errorResponseWithSessionState(single(accountId), apiError( req.errorId(), ErrorFailedToDeleteContact, withDetail(e.Description), ), sessionState) } else { - return errorResponseWithSessionState(accountId, apiError( + return errorResponseWithSessionState(single(accountId), apiError( req.errorId(), ErrorFailedToDeleteContact, ), sessionState) } } - return noContentResponseWithEtag(accountId, sessionState, ContactResponseObjectType, state) + return noContentResponseWithEtag(single(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 d63709459..0a319e6a5 100644 --- a/services/groupware/pkg/groupware/groupware_api_emails.go +++ b/services/groupware/pkg/groupware/groupware_api_emails.go @@ -69,21 +69,21 @@ func (g *Groupware) GetAllEmailsInMailbox(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) + return errorResponse(single(accountId), err) } if mailboxId == "" { - return req.parameterErrorResponse(accountId, UriParamMailboxId, fmt.Sprintf("Missing required mailbox ID path parameter '%v'", UriParamMailboxId)) + return req.parameterErrorResponse(single(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(accountId, jerr) + return req.errorResponseFromJmap(single(accountId), jerr) } - return etagResponse(accountId, changes, sessionState, EmailResponseObjectType, state, lang) + return etagResponse(single(accountId), changes, sessionState, EmailResponseObjectType, state, lang) }) } else { g.respond(w, r, func(req Request) Response { @@ -91,17 +91,17 @@ func (g *Groupware) GetAllEmailsInMailbox(w http.ResponseWriter, r *http.Request accountId, err := req.GetAccountIdForMail() if err != nil { - return errorResponse(accountId, err) + return errorResponse(single(accountId), err) } l = l.Str(logAccountId, accountId) if mailboxId == "" { - return req.parameterErrorResponse(accountId, UriParamMailboxId, fmt.Sprintf("Missing required mailbox ID path parameter '%v'", UriParamMailboxId)) + return req.parameterErrorResponse(single(accountId), UriParamMailboxId, fmt.Sprintf("Missing required mailbox ID path parameter '%v'", UriParamMailboxId)) } offset, ok, err := req.parseIntParam(QueryParamOffset, 0) if err != nil { - return errorResponse(accountId, err) + return errorResponse(single(accountId), err) } if ok { l = l.Int(QueryParamOffset, offset) @@ -109,7 +109,7 @@ func (g *Groupware) GetAllEmailsInMailbox(w http.ResponseWriter, r *http.Request limit, ok, err := req.parseUIntParam(QueryParamLimit, g.defaultEmailLimit) if err != nil { - return errorResponse(accountId, err) + return errorResponse(single(accountId), err) } if ok { l = l.Uint(QueryParamLimit, limit) @@ -123,12 +123,12 @@ func (g *Groupware) GetAllEmailsInMailbox(w http.ResponseWriter, r *http.Request emails, sessionState, state, lang, jerr := g.jmap.GetAllEmailsInMailbox(accountId, req.session, req.ctx, logger, req.language(), mailboxId, offset, limit, collapseThreads, fetchBodies, g.maxBodyValueBytes, withThreads) if jerr != nil { - return req.errorResponseFromJmap(accountId, jerr) + return req.errorResponseFromJmap(single(accountId), jerr) } sanitized, err := req.sanitizeEmails(emails.Emails) if err != nil { - return errorResponseWithSessionState(accountId, err, sessionState) + return errorResponseWithSessionState(single(accountId), err, sessionState) } safe := jmap.Emails{ @@ -138,7 +138,7 @@ func (g *Groupware) GetAllEmailsInMailbox(w http.ResponseWriter, r *http.Request Offset: emails.Offset, } - return etagResponse(accountId, safe, sessionState, EmailResponseObjectType, state, lang) + return etagResponse(single(accountId), safe, sessionState, EmailResponseObjectType, state, lang) }) } } @@ -189,17 +189,17 @@ func (g *Groupware) GetEmailsById(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) + return errorResponse(single(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")) + return req.parameterErrorResponse(single(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(accountId, err) + return errorResponse(single(accountId), err) } if ok { l = l.Bool(QueryParamMarkAsSeen, markAsSeen) @@ -210,32 +210,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(accountId, jerr) + return req.errorResponseFromJmap(single(accountId), jerr) } if len(emails) < 1 { - return notFoundResponse(accountId, sessionState) + return notFoundResponse(single(accountId), sessionState) } else { sanitized, err := req.sanitizeEmail(emails[0]) if err != nil { - return errorResponseWithSessionState(accountId, err, sessionState) + return errorResponseWithSessionState(single(accountId), err, sessionState) } - return etagResponse(accountId, sanitized, sessionState, EmailResponseObjectType, state, lang) + return etagResponse(single(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(accountId, jerr) + return req.errorResponseFromJmap(single(accountId), jerr) } if len(emails) < 1 { - return notFoundResponse(accountId, sessionState) + return notFoundResponse(single(accountId), sessionState) } else { sanitized, err := req.sanitizeEmails(emails) if err != nil { - return errorResponseWithSessionState(accountId, err, sessionState) + return errorResponseWithSessionState(single(accountId), err, sessionState) } - return etagResponse(accountId, sanitized, sessionState, EmailResponseObjectType, state, lang) + return etagResponse(single(accountId), sanitized, sessionState, EmailResponseObjectType, state, lang) } } }) @@ -270,22 +270,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(accountId, err) + return errorResponse(single(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(accountId, jerr) + return req.errorResponseFromJmap(single(accountId), jerr) } if len(emails) < 1 { - return notFoundResponse(accountId, sessionState) + return notFoundResponse(single(accountId), sessionState) } email, err := req.sanitizeEmail(emails[0]) if err != nil { - return errorResponseWithSessionState(accountId, err, sessionState) + return errorResponseWithSessionState(single(accountId), err, sessionState) } - return etagResponse(accountId, email.Attachments, sessionState, EmailResponseObjectType, state, lang) + return etagResponse(single(accountId), email.Attachments, sessionState, EmailResponseObjectType, state, lang) }) } else { g.stream(w, r, func(req Request, w http.ResponseWriter) *Error { @@ -373,13 +373,13 @@ func (g *Groupware) getEmailsSince(w http.ResponseWriter, r *http.Request, since accountId, err := req.GetAccountIdForMail() if err != nil { - return errorResponse(accountId, err) + return errorResponse(single(accountId), err) } l = l.Str(logAccountId, log.SafeString(accountId)) maxChanges, ok, err := req.parseUIntParam(QueryParamMaxChanges, 0) if err != nil { - return errorResponse(accountId, err) + return errorResponse(single(accountId), err) } if ok { l = l.Uint(QueryParamMaxChanges, maxChanges) @@ -389,10 +389,10 @@ func (g *Groupware) getEmailsSince(w http.ResponseWriter, r *http.Request, since 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(accountId, jerr) + return req.errorResponseFromJmap(single(accountId), jerr) } - return etagResponse(accountId, changes, sessionState, EmailResponseObjectType, state, lang) + return etagResponse(single(accountId), changes, sessionState, EmailResponseObjectType, state, lang) }) } @@ -595,12 +595,12 @@ func (g *Groupware) GetEmails(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) + return errorResponse(single(accountId), err) } ok, filter, makesSnippets, offset, limit, logger, err := g.buildFilter(req) if !ok { - return errorResponse(accountId, err) + return errorResponse(single(accountId), err) } logger = log.From(req.logger.With().Str(logAccountId, log.SafeString(accountId))) @@ -610,9 +610,9 @@ func (g *Groupware) GetEmails(w http.ResponseWriter, r *http.Request) { fetchBodies := false - resultsByAccount, sessionState, state, lang, jerr := g.jmap.QueryEmailsWithSnippets([]string{accountId}, filter, req.session, req.ctx, logger, req.language(), offset, limit, fetchBodies, g.maxBodyValueBytes) + resultsByAccount, sessionState, state, lang, jerr := g.jmap.QueryEmailsWithSnippets(single(accountId), filter, req.session, req.ctx, logger, req.language(), offset, limit, fetchBodies, g.maxBodyValueBytes) if jerr != nil { - return req.errorResponseFromJmap(accountId, jerr) + return req.errorResponseFromJmap(single(accountId), jerr) } if results, ok := resultsByAccount[accountId]; ok { @@ -632,7 +632,7 @@ func (g *Groupware) GetEmails(w http.ResponseWriter, r *http.Request) { } sanitized, err := req.sanitizeEmail(result.Email) if err != nil { - return errorResponseWithSessionState(accountId, err, sessionState) + return errorResponseWithSessionState(single(accountId), err, sessionState) } flattened[i] = EmailWithSnippets{ Email: sanitized, @@ -640,14 +640,14 @@ func (g *Groupware) GetEmails(w http.ResponseWriter, r *http.Request) { } } - return etagResponse(accountId, EmailWithSnippetsSearchResults{ + return etagResponse(single(accountId), EmailWithSnippetsSearchResults{ Results: flattened, Total: results.Total, Limit: results.Limit, QueryState: results.QueryState, }, sessionState, EmailResponseObjectType, state, lang) } else { - return notFoundResponse(accountId, sessionState) + return notFoundResponse(single(accountId), sessionState) } }) } @@ -659,7 +659,7 @@ func (g *Groupware) GetEmailsForAllAccounts(w http.ResponseWriter, r *http.Reque ok, filter, makesSnippets, offset, limit, logger, err := g.buildFilter(req) if !ok { - return errorResponse(joinAccountIds(allAccountIds), err) + return errorResponse(allAccountIds, err) } logger = log.From(req.logger.With().Array(logAccountId, log.SafeStringArray(allAccountIds))) @@ -670,7 +670,7 @@ func (g *Groupware) GetEmailsForAllAccounts(w http.ResponseWriter, r *http.Reque 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(joinAccountIds(allAccountIds), jerr) + return req.errorResponseFromJmap(allAccountIds, jerr) } var totalOverAllAccounts uint = 0 @@ -697,7 +697,7 @@ func (g *Groupware) GetEmailsForAllAccounts(w http.ResponseWriter, r *http.Reque // TODO offset and limit over the aggregated results by account - return etagResponse(joinAccountIds(allAccountIds), EmailSearchSnippetsResults{ + return etagResponse(allAccountIds, EmailSearchSnippetsResults{ Results: flattened, Total: totalOverAllAccounts, Limit: limit, @@ -708,7 +708,7 @@ func (g *Groupware) GetEmailsForAllAccounts(w http.ResponseWriter, r *http.Reque resultsByAccountId, sessionState, state, lang, jerr := g.jmap.QueryEmailSummaries(allAccountIds, req.session, req.ctx, logger, req.language(), filter, limit, withThreads) if jerr != nil { - return req.errorResponseFromJmap(joinAccountIds(allAccountIds), jerr) + return req.errorResponseFromJmap(allAccountIds, jerr) } var totalAcrossAllAccounts uint = 0 @@ -734,7 +734,7 @@ func (g *Groupware) GetEmailsForAllAccounts(w http.ResponseWriter, r *http.Reque // TODO offset and limit over the aggregated results by account - return etagResponse(joinAccountIds(allAccountIds), EmailSearchResults{ + return etagResponse(allAccountIds, EmailSearchResults{ Results: flattened, Total: totalAcrossAllAccounts, Limit: limit, @@ -750,22 +750,22 @@ func (g *Groupware) CreateEmail(w http.ResponseWriter, r *http.Request) { accountId, gwerr := req.GetAccountIdForMail() if gwerr != nil { - return errorResponse(accountId, gwerr) + return errorResponse(single(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(accountId, err) + return errorResponse(single(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(accountId, jerr) + return req.errorResponseFromJmap(single(accountId), jerr) } - return etagResponse(accountId, created, sessionState, EmailResponseObjectType, state, lang) + return etagResponse(single(accountId), created, sessionState, EmailResponseObjectType, state, lang) }) } @@ -775,7 +775,7 @@ func (g *Groupware) ReplaceEmail(w http.ResponseWriter, r *http.Request) { accountId, gwerr := req.GetAccountIdForMail() if gwerr != nil { - return errorResponse(accountId, gwerr) + return errorResponse(single(accountId), gwerr) } replaceId := chi.URLParam(r, UriParamEmailId) @@ -785,15 +785,15 @@ func (g *Groupware) ReplaceEmail(w http.ResponseWriter, r *http.Request) { var body jmap.EmailCreate err := req.body(&body) if err != nil { - return errorResponse(accountId, err) + return errorResponse(single(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(accountId, jerr) + return req.errorResponseFromJmap(single(accountId), jerr) } - return etagResponse(accountId, created, sessionState, EmailResponseObjectType, state, lang) + return etagResponse(single(accountId), created, sessionState, EmailResponseObjectType, state, lang) }) } @@ -814,7 +814,7 @@ func (g *Groupware) UpdateEmail(w http.ResponseWriter, r *http.Request) { accountId, gwerr := req.GetAccountIdForMail() if gwerr != nil { - return errorResponse(accountId, gwerr) + return errorResponse(single(accountId), gwerr) } l.Str(logAccountId, accountId) @@ -823,7 +823,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(accountId, err) + return errorResponse(single(accountId), err) } updates := map[string]jmap.EmailUpdate{ @@ -832,20 +832,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(accountId, jerr) + return req.errorResponseFromJmap(single(accountId), jerr) } if result == nil { - return errorResponse(accountId, apiError(req.errorId(), ErrorApiInconsistency, withTitle("API Inconsistency: Missing Email Update Response", + return errorResponse(single(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(accountId, apiError(req.errorId(), ErrorApiInconsistency, withTitle("API Inconsistency: Wrong Email Update Response ID", + return errorResponse(single(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(accountId, updatedEmail, sessionState, EmailResponseObjectType, state, lang) + return etagResponse(single(accountId), updatedEmail, sessionState, EmailResponseObjectType, state, lang) }) } @@ -867,7 +867,7 @@ func (g *Groupware) UpdateEmailKeywords(w http.ResponseWriter, r *http.Request) accountId, gwerr := req.GetAccountIdForMail() if gwerr != nil { - return errorResponse(accountId, gwerr) + return errorResponse(single(accountId), gwerr) } l.Str(logAccountId, accountId) @@ -876,11 +876,11 @@ func (g *Groupware) UpdateEmailKeywords(w http.ResponseWriter, r *http.Request) var body emailKeywordUpdates err := req.body(&body) if err != nil { - return errorResponse(accountId, err) + return errorResponse(single(accountId), err) } if body.IsEmpty() { - return noContentResponse(accountId, req.session.State) + return noContentResponse(single(accountId), req.session.State) } patch := jmap.EmailUpdate{} @@ -896,20 +896,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(accountId, jerr) + return req.errorResponseFromJmap(single(accountId), jerr) } if result == nil { - return errorResponse(accountId, apiError(req.errorId(), ErrorApiInconsistency, withTitle("API Inconsistency: Missing Email Update Response", + return errorResponse(single(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(accountId, apiError(req.errorId(), ErrorApiInconsistency, withTitle("API Inconsistency: Wrong Email Update Response ID", + return errorResponse(single(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(accountId, updatedEmail, sessionState, EmailResponseObjectType, state, lang) + return etagResponse(single(accountId), updatedEmail, sessionState, EmailResponseObjectType, state, lang) }) } @@ -931,7 +931,7 @@ func (g *Groupware) AddEmailKeywords(w http.ResponseWriter, r *http.Request) { accountId, gwerr := req.GetAccountIdForMail() if gwerr != nil { - return errorResponse(accountId, gwerr) + return errorResponse(single(accountId), gwerr) } l.Str(logAccountId, accountId) @@ -940,11 +940,11 @@ func (g *Groupware) AddEmailKeywords(w http.ResponseWriter, r *http.Request) { var body []string err := req.body(&body) if err != nil { - return errorResponse(accountId, err) + return errorResponse(single(accountId), err) } if len(body) < 1 { - return noContentResponse(accountId, req.session.State) + return noContentResponse(single(accountId), req.session.State) } patch := jmap.EmailUpdate{} @@ -957,23 +957,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(accountId, jerr) + return req.errorResponseFromJmap(single(accountId), jerr) } if result == nil { - return errorResponse(accountId, apiError(req.errorId(), ErrorApiInconsistency, withTitle("API Inconsistency: Missing Email Update Response", + return errorResponse(single(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(accountId, apiError(req.errorId(), ErrorApiInconsistency, withTitle("API Inconsistency: Wrong Email Update Response ID", + return errorResponse(single(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(accountId, sessionState, EmailResponseObjectType, state) + return noContentResponseWithEtag(single(accountId), sessionState, EmailResponseObjectType, state) } else { - return etagResponse(accountId, updatedEmail, sessionState, EmailResponseObjectType, state, lang) + return etagResponse(single(accountId), updatedEmail, sessionState, EmailResponseObjectType, state, lang) } }) } @@ -996,7 +996,7 @@ func (g *Groupware) RemoveEmailKeywords(w http.ResponseWriter, r *http.Request) accountId, gwerr := req.GetAccountIdForMail() if gwerr != nil { - return errorResponse(accountId, gwerr) + return errorResponse(single(accountId), gwerr) } l.Str(logAccountId, accountId) @@ -1005,11 +1005,11 @@ func (g *Groupware) RemoveEmailKeywords(w http.ResponseWriter, r *http.Request) var body []string err := req.body(&body) if err != nil { - return errorResponse(accountId, err) + return errorResponse(single(accountId), err) } if len(body) < 1 { - return noContentResponse(accountId, req.session.State) + return noContentResponse(single(accountId), req.session.State) } patch := jmap.EmailUpdate{} @@ -1022,23 +1022,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(accountId, jerr) + return req.errorResponseFromJmap(single(accountId), jerr) } if result == nil { - return errorResponse(accountId, apiError(req.errorId(), ErrorApiInconsistency, withTitle("API Inconsistency: Missing Email Update Response", + return errorResponse(single(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(accountId, apiError(req.errorId(), ErrorApiInconsistency, withTitle("API Inconsistency: Wrong Email Update Response ID", + return errorResponse(single(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(accountId, sessionState, EmailResponseObjectType, state) + return noContentResponseWithEtag(single(accountId), sessionState, EmailResponseObjectType, state) } else { - return etagResponse(accountId, updatedEmail, sessionState, EmailResponseObjectType, state, lang) + return etagResponse(single(accountId), updatedEmail, sessionState, EmailResponseObjectType, state, lang) } }) } @@ -1061,7 +1061,7 @@ func (g *Groupware) DeleteEmail(w http.ResponseWriter, r *http.Request) { accountId, gwerr := req.GetAccountIdForMail() if gwerr != nil { - return errorResponse(accountId, gwerr) + return errorResponse(single(accountId), gwerr) } l.Str(logAccountId, accountId) @@ -1069,25 +1069,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(accountId, jerr) + return req.errorResponseFromJmap(single(accountId), jerr) } for _, e := range resp { desc := e.Description if desc != "" { - return errorResponseWithSessionState(accountId, apiError( + return errorResponseWithSessionState(single(accountId), apiError( req.errorId(), ErrorFailedToDeleteEmail, withDetail(e.Description), ), sessionState) } else { - return errorResponseWithSessionState(accountId, apiError( + return errorResponseWithSessionState(single(accountId), apiError( req.errorId(), ErrorFailedToDeleteEmail, ), sessionState) } } - return noContentResponseWithEtag(accountId, sessionState, EmailResponseObjectType, state) + return noContentResponseWithEtag(single(accountId), sessionState, EmailResponseObjectType, state) }) } @@ -1117,14 +1117,14 @@ func (g *Groupware) DeleteEmails(w http.ResponseWriter, r *http.Request) { accountId, gwerr := req.GetAccountIdForMail() if gwerr != nil { - return errorResponse(accountId, gwerr) + return errorResponse(single(accountId), gwerr) } l.Str(logAccountId, accountId) var emailIds []string err := req.body(&emailIds) if err != nil { - return errorResponse(accountId, err) + return errorResponse(single(accountId), err) } l.Array("emailIds", log.SafeStringArray(emailIds)) @@ -1133,7 +1133,7 @@ func (g *Groupware) DeleteEmails(w http.ResponseWriter, r *http.Request) { resp, sessionState, state, _, jerr := g.jmap.DeleteEmails(accountId, emailIds, req.session, req.ctx, logger, req.language()) if jerr != nil { - return req.errorResponseFromJmap(accountId, jerr) + return req.errorResponseFromJmap(single(accountId), jerr) } if len(resp) > 0 { @@ -1141,13 +1141,13 @@ func (g *Groupware) DeleteEmails(w http.ResponseWriter, r *http.Request) { for emailId, e := range resp { meta[emailId] = e.Description } - return errorResponseWithSessionState(accountId, apiError( + return errorResponseWithSessionState(single(accountId), apiError( req.errorId(), ErrorFailedToDeleteEmail, withMeta(meta), ), sessionState) } - return noContentResponseWithEtag(accountId, sessionState, EmailResponseObjectType, state) + return noContentResponseWithEtag(single(accountId), sessionState, EmailResponseObjectType, state) }) } @@ -1157,7 +1157,7 @@ func (g *Groupware) SendEmail(w http.ResponseWriter, r *http.Request) { accountId, gwerr := req.GetAccountIdForMail() if gwerr != nil { - return errorResponse(accountId, gwerr) + return errorResponse(single(accountId), gwerr) } l.Str(logAccountId, accountId) @@ -1166,7 +1166,7 @@ func (g *Groupware) SendEmail(w http.ResponseWriter, r *http.Request) { identityId, err := req.getMandatoryStringParam(QueryParamIdentityId) if err != nil { - return errorResponse(accountId, err) + return errorResponse(single(accountId), err) } l.Str(QueryParamIdentityId, log.SafeString(identityId)) @@ -1186,7 +1186,7 @@ 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(accountId, req.observedParameterError(ErrorMissingMandatoryRequestParameter, + return errorResponse(single(accountId), req.observedParameterError(ErrorMissingMandatoryRequestParameter, withDetail(msg), withSource(&ErrorSource{Parameter: missing}))) } @@ -1196,10 +1196,10 @@ func (g *Groupware) SendEmail(w http.ResponseWriter, r *http.Request) { 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(accountId, jerr) + return req.errorResponseFromJmap(single(accountId), jerr) } - return etagResponse(accountId, resp, sessionState, EmailResponseObjectType, state, lang) + return etagResponse(single(accountId), resp, sessionState, EmailResponseObjectType, state, lang) }) } @@ -1261,13 +1261,13 @@ func (g *Groupware) RelatedToEmail(w http.ResponseWriter, r *http.Request) { accountId, gwerr := req.GetAccountIdForMail() if gwerr != nil { - return errorResponse(accountId, gwerr) + return errorResponse(single(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(accountId, err) + return errorResponse(single(accountId), err) } if ok { l = l.Uint("limit", limit) @@ -1275,7 +1275,7 @@ 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(accountId, err) + return errorResponse(single(accountId), err) } if ok { l = l.Uint("days", days) @@ -1288,13 +1288,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(accountId, jerr) + return req.errorResponseFromJmap(single(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(accountId, sessionState) + return notFoundResponse(single(accountId), sessionState) } else { req.observe(g.metrics.EmailByIdDuration.WithLabelValues(req.session.JmapEndpoint, metrics.Values.Result.Found), getEmailsDuration.Seconds()) } @@ -1310,7 +1310,7 @@ func (g *Groupware) RelatedToEmail(w http.ResponseWriter, r *http.Request) { g.job(logger, RelationTypeSameSender, func(jobId uint64, l *log.Logger) { before := time.Now() - resultsByAccountId, _, _, lang, jerr := g.jmap.QueryEmails([]string{accountId}, filter, req.session, bgctx, l, req.language(), 0, limit, false, g.maxBodyValueBytes) + resultsByAccountId, _, _, lang, jerr := g.jmap.QueryEmails(single(accountId), filter, req.session, bgctx, l, req.language(), 0, limit, false, g.maxBodyValueBytes) if results, ok := resultsByAccountId[accountId]; ok { duration := time.Since(before) if jerr != nil { @@ -1350,9 +1350,9 @@ func (g *Groupware) RelatedToEmail(w http.ResponseWriter, r *http.Request) { sanitized, err := req.sanitizeEmail(email) if err != nil { - return errorResponseWithSessionState(accountId, err, sessionState) + return errorResponseWithSessionState(single(accountId), err, sessionState) } - return etagResponse(accountId, AboutEmailResponse{ + return etagResponse(single(accountId), AboutEmailResponse{ Email: sanitized, RequestId: reqId, }, sessionState, EmailResponseObjectType, state, lang) @@ -1625,7 +1625,7 @@ func (g *Groupware) GetLatestEmailsSummaryForAllAccounts(w http.ResponseWriter, limit, ok, err := req.parseUIntParam(QueryParamLimit, 10) // TODO from configuration if err != nil { - return errorResponse(joinAccountIds(allAccountIds), err) + return errorResponse(allAccountIds, err) } if ok { l = l.Uint(QueryParamLimit, limit) @@ -1633,7 +1633,7 @@ func (g *Groupware) GetLatestEmailsSummaryForAllAccounts(w http.ResponseWriter, offset, ok, err := req.parseUIntParam(QueryParamOffset, 0) if err != nil { - return errorResponse(joinAccountIds(allAccountIds), err) + return errorResponse(allAccountIds, err) } if offset > 0 { return notImplementesResponse() @@ -1644,7 +1644,7 @@ func (g *Groupware) GetLatestEmailsSummaryForAllAccounts(w http.ResponseWriter, seen, ok, err := req.parseBoolParam(QueryParamSeen, false) if err != nil { - return errorResponse(joinAccountIds(allAccountIds), err) + return errorResponse(allAccountIds, err) } if ok { l = l.Bool(QueryParamSeen, seen) @@ -1652,7 +1652,7 @@ func (g *Groupware) GetLatestEmailsSummaryForAllAccounts(w http.ResponseWriter, undesirable, ok, err := req.parseBoolParam(QueryParamUndesirable, false) if err != nil { - return errorResponse(joinAccountIds(allAccountIds), err) + return errorResponse(allAccountIds, err) } if ok { l = l.Bool(QueryParamUndesirable, undesirable) @@ -1674,7 +1674,7 @@ func (g *Groupware) GetLatestEmailsSummaryForAllAccounts(w http.ResponseWriter, 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(joinAccountIds(allAccountIds), jerr) + return req.errorResponseFromJmap(allAccountIds, jerr) } // sort in memory to respect the overall limit @@ -1698,7 +1698,7 @@ func (g *Groupware) GetLatestEmailsSummaryForAllAccounts(w http.ResponseWriter, summaries[i] = summarizeEmail(all[i].accountId, all[i].email) } - return etagResponse(joinAccountIds(allAccountIds), EmailSummaries{ + return etagResponse(allAccountIds, EmailSummaries{ Emails: summaries, Total: total, Limit: limit, diff --git a/services/groupware/pkg/groupware/groupware_api_identity.go b/services/groupware/pkg/groupware/groupware_api_identity.go index f97c91987..74d884489 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(accountId, err) + return errorResponse(single(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(accountId, jerr) + return req.errorResponseFromJmap(single(accountId), jerr) } - return etagResponse(accountId, res, sessionState, IdentityResponseObjectType, state, lang) + return etagResponse(single(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(accountId, err) + return errorResponse(single(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(accountId, jerr) + return req.errorResponseFromJmap(single(accountId), jerr) } if len(res) < 1 { - return notFoundResponse(accountId, sessionState) + return notFoundResponse(single(accountId), sessionState) } - return etagResponse(accountId, res[0], sessionState, IdentityResponseObjectType, state, lang) + return etagResponse(single(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(accountId, err) + return errorResponse(single(accountId), err) } logger := log.From(req.logger.With().Str(logAccountId, accountId)) var identity jmap.Identity err = req.body(&identity) if err != nil { - return errorResponse(accountId, err) + return errorResponse(single(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(accountId, jerr) + return req.errorResponseFromJmap(single(accountId), jerr) } - return etagResponse(accountId, created, sessionState, IdentityResponseObjectType, state, lang) + return etagResponse(single(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(accountId, err) + return errorResponse(single(accountId), err) } logger := log.From(req.logger.With().Str(logAccountId, accountId)) var identity jmap.Identity err = req.body(&identity) if err != nil { - return errorResponse(accountId, err) + return errorResponse(single(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(accountId, jerr) + return req.errorResponseFromJmap(single(accountId), jerr) } - return etagResponse(accountId, updated, sessionState, IdentityResponseObjectType, state, lang) + return etagResponse(single(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(accountId, err) + return errorResponse(single(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(accountId, UriParamEmailId, fmt.Sprintf("Invalid value for path parameter '%v': '%s': %s", UriParamIdentityId, log.SafeString(id), "empty list of identity ids")) + return req.parameterErrorResponse(single(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(accountId, jerr) + return req.errorResponseFromJmap(single(accountId), jerr) } notDeletedIds := structs.Missing(ids, deletion) if len(notDeletedIds) == 0 { - return noContentResponseWithEtag(accountId, sessionState, IdentityResponseObjectType, state) + return noContentResponseWithEtag(single(accountId), sessionState, IdentityResponseObjectType, state) } else { logger.Error().Array("not-deleted", log.SafeStringArray(notDeletedIds)).Msgf("failed to delete %d identities", len(notDeletedIds)) - return errorResponseWithSessionState(accountId, req.apiError(&ErrorFailedToDeleteSomeIdentities, + return errorResponseWithSessionState(single(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 1861344ca..9d2e97547 100644 --- a/services/groupware/pkg/groupware/groupware_api_index.go +++ b/services/groupware/pkg/groupware/groupware_api_index.go @@ -163,10 +163,10 @@ func (g *Groupware) Index(w http.ResponseWriter, r *http.Request) { boot, sessionState, state, lang, err := g.jmap.GetBootstrap(accountIds, req.session, req.ctx, req.logger, req.language()) if err != nil { - return req.errorResponseFromJmap(joinAccountIds(accountIds), err) + return req.errorResponseFromJmap(accountIds, err) } - return etagResponse(joinAccountIds(accountIds), IndexResponse{ + return etagResponse(accountIds, IndexResponse{ Version: Version, Capabilities: Capabilities, Limits: buildIndexLimits(req.session), diff --git a/services/groupware/pkg/groupware/groupware_api_mailbox.go b/services/groupware/pkg/groupware/groupware_api_mailbox.go index bde46f008..2e0ce731d 100644 --- a/services/groupware/pkg/groupware/groupware_api_mailbox.go +++ b/services/groupware/pkg/groupware/groupware_api_mailbox.go @@ -39,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(accountId, err) + return errorResponse(single(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(accountId, jerr) + return req.errorResponseFromJmap(single(accountId), jerr) } if len(mailboxes.Mailboxes) == 1 { - return etagResponse(accountId, mailboxes.Mailboxes[0], sessionState, MailboxResponseObjectType, state, lang) + return etagResponse(single(accountId), mailboxes.Mailboxes[0], sessionState, MailboxResponseObjectType, state, lang) } else { - return notFoundResponse(accountId, sessionState) + return notFoundResponse(single(accountId), sessionState) } }) } @@ -110,12 +110,12 @@ 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) + return errorResponse(single(accountId), err) } subscribed, set, err := req.parseBoolParam(QueryParamMailboxSearchSubscribed, false) if err != nil { - return errorResponse(accountId, err) + return errorResponse(single(accountId), err) } if set { filter.IsSubscribed = &subscribed @@ -125,25 +125,25 @@ func (g *Groupware) GetMailboxes(w http.ResponseWriter, r *http.Request) { 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) + mailboxesByAccountId, sessionState, state, lang, err := g.jmap.SearchMailboxes(single(accountId), req.session, req.ctx, logger, req.language(), filter) if err != nil { - return req.errorResponseFromJmap(accountId, err) + return req.errorResponseFromJmap(single(accountId), err) } if mailboxes, ok := mailboxesByAccountId[accountId]; ok { - return etagResponse(accountId, sortMailboxSlice(mailboxes), sessionState, MailboxResponseObjectType, state, lang) + return etagResponse(single(accountId), sortMailboxSlice(mailboxes), sessionState, MailboxResponseObjectType, state, lang) } else { - return notFoundResponse(accountId, sessionState) + return notFoundResponse(single(accountId), sessionState) } } else { - mailboxesByAccountId, sessionState, state, lang, err := g.jmap.GetAllMailboxes([]string{accountId}, req.session, req.ctx, logger, req.language()) + mailboxesByAccountId, sessionState, state, lang, err := g.jmap.GetAllMailboxes(single(accountId), req.session, req.ctx, logger, req.language()) if err != nil { - return req.errorResponseFromJmap(accountId, err) + return req.errorResponseFromJmap(single(accountId), err) } if mailboxes, ok := mailboxesByAccountId[accountId]; ok { - return etagResponse(accountId, sortMailboxSlice(mailboxes), sessionState, MailboxResponseObjectType, state, lang) + return etagResponse(single(accountId), sortMailboxSlice(mailboxes), sessionState, MailboxResponseObjectType, state, lang) } else { - return notFoundResponse(accountId, sessionState) + return notFoundResponse(single(accountId), sessionState) } } }) @@ -179,13 +179,13 @@ 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("", "") + return noContentResponse(nil, "") } logger := log.From(req.logger.With().Array(logAccountId, log.SafeStringArray(accountIds))) subscribed, set, err := req.parseBoolParam(QueryParamMailboxSearchSubscribed, false) if err != nil { - return errorResponse(joinAccountIds(accountIds), err) + return errorResponse(accountIds, err) } if set { filter.IsSubscribed = &subscribed @@ -195,15 +195,15 @@ func (g *Groupware) GetMailboxesForAllAccounts(w http.ResponseWriter, r *http.Re 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(joinAccountIds(accountIds), err) + return req.errorResponseFromJmap(accountIds, err) } - return etagResponse(joinAccountIds(accountIds), sortMailboxesMap(mailboxesByAccountId), sessionState, MailboxResponseObjectType, state, lang) + return etagResponse(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(joinAccountIds(accountIds), err) + return req.errorResponseFromJmap(accountIds, err) } - return etagResponse(joinAccountIds(accountIds), sortMailboxesMap(mailboxesByAccountId), sessionState, MailboxResponseObjectType, state, lang) + return etagResponse(accountIds, sortMailboxesMap(mailboxesByAccountId), sessionState, MailboxResponseObjectType, state, lang) } }) } @@ -213,7 +213,7 @@ func (g *Groupware) GetMailboxByRoleForAllAccounts(w http.ResponseWriter, r *htt g.respond(w, r, func(req Request) Response { accountIds := req.AllAccountIds() if len(accountIds) < 1 { - return noContentResponse("", "") + return noContentResponse(nil, "") } 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(joinAccountIds(accountIds), err) + return req.errorResponseFromJmap(accountIds, err) } - return etagResponse(joinAccountIds(accountIds), sortMailboxesMap(mailboxesByAccountId), sessionState, MailboxResponseObjectType, state, lang) + return etagResponse(accountIds, sortMailboxesMap(mailboxesByAccountId), sessionState, MailboxResponseObjectType, state, lang) }) } @@ -253,13 +253,13 @@ func (g *Groupware) GetMailboxChanges(w http.ResponseWriter, r *http.Request) { accountId, err := req.GetAccountIdForMail() if err != nil { - return errorResponse(accountId, err) + return errorResponse(single(accountId), err) } l = l.Str(logAccountId, accountId) maxChanges, ok, err := req.parseUIntParam(QueryParamMaxChanges, 0) if err != nil { - return errorResponse(accountId, err) + return errorResponse(single(accountId), err) } if ok { l = l.Uint(QueryParamMaxChanges, maxChanges) @@ -269,10 +269,10 @@ func (g *Groupware) GetMailboxChanges(w http.ResponseWriter, r *http.Request) { 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(accountId, jerr) + return req.errorResponseFromJmap(single(accountId), jerr) } - return etagResponse(accountId, changes, sessionState, MailboxResponseObjectType, state, lang) + return etagResponse(single(accountId), changes, sessionState, MailboxResponseObjectType, state, lang) }) } @@ -300,7 +300,7 @@ func (g *Groupware) GetMailboxChangesForAllAccounts(w http.ResponseWriter, r *ht sinceStateMap, ok, err := req.parseMapParam(QueryParamSince) if err != nil { - return errorResponse(joinAccountIds(allAccountIds), err) + return errorResponse(allAccountIds, err) } if ok { dict := zerolog.Dict() @@ -312,7 +312,7 @@ func (g *Groupware) GetMailboxChangesForAllAccounts(w http.ResponseWriter, r *ht maxChanges, ok, err := req.parseUIntParam(QueryParamMaxChanges, 0) if err != nil { - return errorResponse(joinAccountIds(allAccountIds), err) + return errorResponse(allAccountIds, err) } if ok { l = l.Uint(QueryParamMaxChanges, maxChanges) @@ -322,10 +322,10 @@ func (g *Groupware) GetMailboxChangesForAllAccounts(w http.ResponseWriter, r *ht 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(joinAccountIds(allAccountIds), jerr) + return req.errorResponseFromJmap(allAccountIds, jerr) } - return etagResponse(joinAccountIds(allAccountIds), changesByAccountId, sessionState, MailboxResponseObjectType, state, lang) + return etagResponse(allAccountIds, changesByAccountId, sessionState, MailboxResponseObjectType, state, lang) }) } @@ -338,10 +338,10 @@ func (g *Groupware) GetMailboxRoles(w http.ResponseWriter, r *http.Request) { rolesByAccountId, sessionState, state, lang, jerr := g.jmap.GetMailboxRolesForMultipleAccounts(allAccountIds, req.session, req.ctx, logger, req.language()) if jerr != nil { - return req.errorResponseFromJmap(joinAccountIds(allAccountIds), jerr) + return req.errorResponseFromJmap(allAccountIds, jerr) } - return etagResponse(joinAccountIds(allAccountIds), rolesByAccountId, sessionState, MailboxResponseObjectType, state, lang) + return etagResponse(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(accountId, err) + return errorResponse(single(accountId), err) } l = l.Str(logAccountId, accountId) var body jmap.MailboxChange err = req.body(&body) if err != nil { - return errorResponse(accountId, err) + return errorResponse(single(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(accountId, jerr) + return req.errorResponseFromJmap(single(accountId), jerr) } - return etagResponse(accountId, updated, sessionState, MailboxResponseObjectType, state, lang) + return etagResponse(single(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(accountId, err) + return errorResponse(single(accountId), err) } l = l.Str(logAccountId, accountId) var body jmap.MailboxChange err = req.body(&body) if err != nil { - return errorResponse(accountId, err) + return errorResponse(single(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(accountId, jerr) + return req.errorResponseFromJmap(single(accountId), jerr) } - return etagResponse(accountId, created, sessionState, MailboxResponseObjectType, state, lang) + return etagResponse(single(accountId), created, sessionState, MailboxResponseObjectType, state, lang) }) } @@ -406,12 +406,12 @@ func (g *Groupware) DeleteMailbox(w http.ResponseWriter, r *http.Request) { l := req.logger.With() accountId, err := req.GetAccountIdForMail() if err != nil { - return errorResponse(accountId, err) + return errorResponse(single(accountId), err) } l = l.Str(logAccountId, accountId) if len(mailboxIds) < 1 { - return noContentResponse(accountId, req.session.State) + return noContentResponse(single(accountId), req.session.State) } l = l.Array(UriParamMailboxId, log.SafeStringArray(mailboxIds)) @@ -419,10 +419,10 @@ func (g *Groupware) DeleteMailbox(w http.ResponseWriter, r *http.Request) { deleted, sessionState, state, lang, jerr := g.jmap.DeleteMailboxes(accountId, req.session, req.ctx, logger, req.language(), "", mailboxIds) if jerr != nil { - return req.errorResponseFromJmap(accountId, jerr) + return req.errorResponseFromJmap(single(accountId), jerr) } - return etagResponse(accountId, deleted, sessionState, MailboxResponseObjectType, state, lang) + return etagResponse(single(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 f6a6c6ae8..a57072bce 100644 --- a/services/groupware/pkg/groupware/groupware_api_quota.go +++ b/services/groupware/pkg/groupware/groupware_api_quota.go @@ -26,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(accountId, err) + return errorResponse(single(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()) + res, sessionState, state, lang, jerr := g.jmap.GetQuotas(single(accountId), req.session, req.ctx, logger, req.language()) if jerr != nil { - return req.errorResponseFromJmap(accountId, jerr) + return req.errorResponseFromJmap(single(accountId), jerr) } for _, v := range res { - return etagResponse(accountId, v.List, sessionState, QuotaResponseObjectType, state, lang) + return etagResponse(single(accountId), v.List, sessionState, QuotaResponseObjectType, state, lang) } - return notFoundResponse(accountId, sessionState) + return notFoundResponse(single(accountId), sessionState) }) } @@ -65,13 +65,13 @@ func (g *Groupware) GetQuotaForAllAccounts(w http.ResponseWriter, r *http.Reques g.respond(w, r, func(req Request) Response { accountIds := req.AllAccountIds() if len(accountIds) < 1 { - return noContentResponse(joinAccountIds(accountIds), "") + return noContentResponse(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(joinAccountIds(accountIds), jerr) + return req.errorResponseFromJmap(accountIds, jerr) } result := make(map[string]AccountQuota, len(res)) @@ -81,6 +81,6 @@ func (g *Groupware) GetQuotaForAllAccounts(w http.ResponseWriter, r *http.Reques Quotas: accountQuotas.List, } } - return etagResponse(joinAccountIds(accountIds), result, sessionState, QuotaResponseObjectType, state, lang) + return etagResponse(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 0a9239294..221d6184a 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 etagResponse(accountId, AllTaskLists, req.session.State, TaskListResponseObjectType, TaskListsState, "") + return etagResponse(single(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(accountId, tasklist, req.session.State, "") + return response(single(accountId), tasklist, req.session.State, "") } } - return etagNotFoundResponse(accountId, req.session.State, TaskListResponseObjectType, TaskListsState, "") + return etagNotFoundResponse(single(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(accountId, req.session.State) + return notFoundResponse(single(accountId), req.session.State) } - return etagResponse(accountId, tasks, req.session.State, TaskResponseObjectType, TaskState, "") + return etagResponse(single(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 de00e05b1..1e4db2938 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(accountId, err) + return errorResponse(single(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(accountId, jerr) + return req.errorResponseFromJmap(single(accountId), jerr) } - return etagResponse(accountId, res, sessionState, VacationResponseResponseObjectType, state, lang) + return etagResponse(single(accountId), res, sessionState, VacationResponseResponseObjectType, state, lang) }) } @@ -69,21 +69,21 @@ func (g *Groupware) SetVacation(w http.ResponseWriter, r *http.Request) { g.respond(w, r, func(req Request) Response { accountId, err := req.GetAccountIdForVacationResponse() if err != nil { - return errorResponse(accountId, err) + return errorResponse(single(accountId), err) } logger := log.From(req.logger.With().Str(logAccountId, accountId)) var body jmap.VacationResponsePayload err = req.body(&body) if err != nil { - return errorResponse(accountId, err) + return errorResponse(single(accountId), err) } 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 req.errorResponseFromJmap(single(accountId), jerr) } - return etagResponse(accountId, res, sessionState, VacationResponseResponseObjectType, state, lang) + return etagResponse(single(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 b4fa532c5..4db41492d 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(accountId string, err jmap.Error) Response { - return errorResponse(accountId, r.apiErrorFromJmap(r.observeJmapError(err))) +func (r Request) errorResponseFromJmap(accountIds []string, err jmap.Error) Response { + return errorResponse(accountIds, r.apiErrorFromJmap(r.observeJmapError(err))) } diff --git a/services/groupware/pkg/groupware/groupware_framework.go b/services/groupware/pkg/groupware/groupware_framework.go index 5197b5fce..455b1598b 100644 --- a/services/groupware/pkg/groupware/groupware_framework.go +++ b/services/groupware/pkg/groupware/groupware_framework.go @@ -207,42 +207,54 @@ func NewGroupware(config *config.Config, logger *log.Logger, mux *chi.Mux, prome m := metrics.New(prometheusRegistry, logger) - // TODO add timeouts and other meaningful configuration settings for the HTTP client - httpTransport := http.DefaultTransport.(*http.Transport).Clone() - httpTransport.ResponseHeaderTimeout = responseHeaderTimeout - if insecureTls { - tlsConfig := &tls.Config{InsecureSkipVerify: true} - httpTransport.TLSClientConfig = tlsConfig - } - httpClient := *http.DefaultClient - httpClient.Transport = httpTransport - userProvider := newRevaContextUsernameProvider() jmapMetricsAdapter := groupwareHttpJmapApiClientMetricsRecorder{m: m} - api := jmap.NewHttpJmapClient( - &httpClient, - masterUsername, - masterPassword, - jmapMetricsAdapter, - ) + var jmapClient jmap.Client + { + var api *jmap.HttpJmapClient + { + // TODO add timeouts and other meaningful configuration settings for the HTTP client + var httpClient http.Client + { + httpTransport := http.DefaultTransport.(*http.Transport).Clone() + httpTransport.ResponseHeaderTimeout = responseHeaderTimeout + if insecureTls { + tlsConfig := &tls.Config{InsecureSkipVerify: true} + httpTransport.TLSClientConfig = tlsConfig + } + httpClient = *http.DefaultClient + httpClient.Transport = httpTransport + } - wsDialer := &websocket.Dialer{ - HandshakeTimeout: wsHandshakeTimeout, - } - if insecureTls { - wsDialer.TLSClientConfig = &tls.Config{InsecureSkipVerify: true} - } + api = jmap.NewHttpJmapClient( + &httpClient, + masterUsername, + masterPassword, + jmapMetricsAdapter, + ) + } - wsf, err := jmap.NewHttpWsClientFactory(wsDialer, masterUsername, masterPassword, logger, jmapMetricsAdapter) - if err != nil { - logger.Error().Err(err).Msg("failed to create websocket client") - return nil, GroupwareInitializationError{Message: "failed to create websocket client", Err: err} - } + var wsf *jmap.HttpWsClientFactory + { + wsDialer := &websocket.Dialer{ + HandshakeTimeout: wsHandshakeTimeout, + } + if insecureTls { + wsDialer.TLSClientConfig = &tls.Config{InsecureSkipVerify: true} + } - // api implements all three interfaces: - jmapClient := jmap.NewClient(api, api, api, wsf) + wsf, err = jmap.NewHttpWsClientFactory(wsDialer, masterUsername, masterPassword, logger, jmapMetricsAdapter) + if err != nil { + logger.Error().Err(err).Msg("failed to create websocket client") + return nil, GroupwareInitializationError{Message: "failed to create websocket client", Err: err} + } + } + + // api implements all three interfaces: + jmapClient = jmap.NewClient(api, api, api, wsf) + } sessionCacheBuilder := newSessionCacheBuilder( sessionUrl, @@ -456,9 +468,20 @@ func (g *Groupware) ServeSSE(w http.ResponseWriter, r *http.Request) { }) } -// Provide a JMAP Session for the -func (g *Groupware) session(user user, _ *log.Logger) (jmap.Session, bool, *GroupwareError, time.Time) { - s := g.sessionCache.Get(user.GetUsername()) +// Provide a JMAP Session for the given user +func (g *Groupware) session(user user, logger *log.Logger) (jmap.Session, bool, *GroupwareError, time.Time) { + if user == nil { + logger.Warn().Msg("user is nil") + return jmap.Session{}, false, nil, time.Time{} + } + name := user.GetUsername() + if name == "" { + logger.Warn().Msg("user has an empty username") + return jmap.Session{}, false, nil, time.Time{} + } + + // first look into the session cache + s := g.sessionCache.Get(name) if s != nil { if s.Success() { return s.Get(), true, nil, s.Until() @@ -466,7 +489,8 @@ func (g *Groupware) session(user user, _ *log.Logger) (jmap.Session, bool, *Grou return jmap.Session{}, false, s.Error(), s.Until() } } - // not sure this should happen: + // not sure this should/could happen: + logger.Warn().Msg("session cache returned nil") return jmap.Session{}, false, nil, time.Time{} } @@ -521,44 +545,63 @@ func (g *Groupware) serveError(w http.ResponseWriter, r *http.Request, error *Er render.Render(w, r, errorResponses(*error)) } +// Execute a closure with a JMAP Session. +// +// Returns +// - a Response object +// - if an error occurs, after which timestamp a retry is allowed +// - whether the request was sent to the server or not func (g *Groupware) withSession(w http.ResponseWriter, r *http.Request, handler func(r Request) Response) (Response, time.Time, bool) { ctx := r.Context() sl := g.logger.SubloggerWithRequestID(ctx) logger := &sl - user, err := g.userProvider.GetUser(r, ctx, logger) - if err != nil { - g.metrics.AuthenticationFailureCounter.Inc() - g.serveError(w, r, apiError(errorId(r, ctx), ErrorInvalidAuthentication), time.Time{}) - return Response{}, time.Time{}, false - } - if user == nil { - g.metrics.AuthenticationFailureCounter.Inc() - g.serveError(w, r, apiError(errorId(r, ctx), ErrorMissingAuthentication), time.Time{}) - return Response{}, time.Time{}, false + // retrieve the current user from the inbound request + var user user + { + var err error + user, err = g.userProvider.GetUser(r, ctx, logger) + if err != nil { + g.metrics.AuthenticationFailureCounter.Inc() + g.serveError(w, r, apiError(errorId(r, ctx), ErrorInvalidAuthentication), time.Time{}) + return Response{}, time.Time{}, false + } + if user == nil { + g.metrics.AuthenticationFailureCounter.Inc() + g.serveError(w, r, apiError(errorId(r, ctx), ErrorMissingAuthentication), time.Time{}) + return Response{}, time.Time{}, false + } + + logger = log.From(logger.With().Str(logUserId, log.SafeString(user.GetId()))) } - logger = log.From(logger.With().Str(logUserId, log.SafeString(user.GetId()))) + // retrieve a JMAP Session for that user + var session jmap.Session + { + s, ok, gwerr, retryAfter := g.session(user, logger) + if gwerr != nil { + g.metrics.SessionFailureCounter.Inc() + errorId := errorId(r, ctx) + logger.Error().Str("code", gwerr.Code).Str("error", gwerr.Title).Str("detail", gwerr.Detail).Str(logErrorId, errorId).Msg("failed to determine JMAP session") + g.serveError(w, r, apiError(errorId, *gwerr), retryAfter) + return Response{}, retryAfter, false + } + if ok { + session = s + } else { + // no session = authentication failed + g.metrics.SessionFailureCounter.Inc() + errorId := errorId(r, ctx) + logger.Error().Str(logErrorId, errorId).Msg("could not authenticate, failed to find Session") + gwerr = &ErrorInvalidAuthentication + g.serveError(w, r, apiError(errorId, *gwerr), retryAfter) + return Response{}, retryAfter, false + } + } - session, ok, gwerr, retryAfter := g.session(user, logger) - if gwerr != nil { - g.metrics.SessionFailureCounter.Inc() - errorId := errorId(r, ctx) - logger.Error().Str("code", gwerr.Code).Str("error", gwerr.Title).Str("detail", gwerr.Detail).Str(logErrorId, errorId).Msg("failed to determine JMAP session") - g.serveError(w, r, apiError(errorId, *gwerr), retryAfter) - return Response{}, retryAfter, false - } - if !ok { - // no session = authentication failed - g.metrics.SessionFailureCounter.Inc() - errorId := errorId(r, ctx) - logger.Error().Str(logErrorId, errorId).Msg("could not authenticate, failed to find Session") - gwerr = &ErrorInvalidAuthentication - g.serveError(w, r, apiError(errorId, *gwerr), retryAfter) - return Response{}, retryAfter, false - } decoratedLogger := decorateLogger(logger, session) + // build the Request object req := Request{ g: g, user: user, @@ -568,7 +611,10 @@ func (g *Groupware) withSession(w http.ResponseWriter, r *http.Request, handler session: &session, } + // perform the actual request using the closure that was passed in response := handler(req) + + // return the result of that closure execution return response, time.Time{}, true } @@ -577,8 +623,10 @@ const ( StateResponseHeader = "State" ObjectTypeResponseHeader = "Object-Type" AccountIdResponseHeader = "Account-Id" + AccountIdsResponseHeader = "Account-Ids" ) +// Send the Response object as an HTTP response. func (g *Groupware) sendResponse(w http.ResponseWriter, r *http.Request, response Response) { if response.err != nil { g.log(response.err) @@ -600,9 +648,9 @@ func (g *Groupware) sendResponse(w http.ResponseWriter, r *http.Request, respons { etag := string(response.etag) if etag != "" { - challenge := r.Header.Get("if-none-match") - quotedEtag := "\"" + etag + "\"" - notModified = challenge != "" && (challenge == etag || challenge == quotedEtag) + challenge := r.Header.Get("if-none-match") // https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/If-None-Match + quotedEtag := "\"" + etag + "\"" // https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/ETag#etag_value + notModified = challenge != "" && (challenge == etag || challenge == quotedEtag) // be a bit flexible/permissive here with the quoting w.Header().Add("ETag", quotedEtag) w.Header().Add(StateResponseHeader, etag) } @@ -613,8 +661,17 @@ func (g *Groupware) sendResponse(w http.ResponseWriter, r *http.Request, respons w.Header().Add(ObjectTypeResponseHeader, ot) } } - if response.accountId != "" { - w.Header().Add(AccountIdResponseHeader, response.accountId) + switch len(response.accountIds) { + case 0: + break + case 1: + w.Header().Add(AccountIdResponseHeader, response.accountIds[0]) + default: + c := make([]string, len(response.accountIds)) + copy(c, response.accountIds) + slices.Sort(c) + value := strings.Join(c, ",") + w.Header().Add(AccountIdsResponseHeader, value) } if notModified { @@ -714,16 +771,6 @@ func (g *Groupware) MethodNotAllowed(w http.ResponseWriter, r *http.Request) { 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, ",") - } +func single[S any](s S) []S { + return []S{s} } diff --git a/services/groupware/pkg/groupware/groupware_request.go b/services/groupware/pkg/groupware/groupware_request.go index 37cd6b21b..beb5d8b4e 100644 --- a/services/groupware/pkg/groupware/groupware_request.go +++ b/services/groupware/pkg/groupware/groupware_request.go @@ -170,8 +170,8 @@ func (r Request) parameterError(param string, detail string) *Error { withSource(&ErrorSource{Parameter: param})) } -func (r Request) parameterErrorResponse(accountId string, param string, detail string) Response { - return errorResponse(accountId, r.parameterError(param, detail)) +func (r Request) parameterErrorResponse(accountIds []string, param string, detail string) Response { + return errorResponse(accountIds, r.parameterError(param, detail)) } func (r Request) getStringParam(param string, defaultValue string) (string, bool) { @@ -355,7 +355,7 @@ func (r Request) observeJmapError(jerr jmap.Error) jmap.Error { func (r Request) needTask(accountId string) (bool, Response) { if !IgnoreSessionCapabilityChecks { if r.session.Capabilities.Tasks == nil { - return false, errorResponseWithSessionState(accountId, r.apiError(&ErrorMissingTasksSessionCapability), r.session.State) + return false, errorResponseWithSessionState(single(accountId), r.apiError(&ErrorMissingTasksSessionCapability), r.session.State) } } return true, Response{} @@ -367,11 +367,11 @@ func (r Request) needTaskForAccount(accountId string) (bool, Response) { } account, ok := r.session.Accounts[accountId] if !ok { - return false, errorResponseWithSessionState(accountId, r.apiError(&ErrorAccountNotFound), r.session.State) + return false, errorResponseWithSessionState(single(accountId), r.apiError(&ErrorAccountNotFound), r.session.State) } if !IgnoreSessionCapabilityChecks { if account.AccountCapabilities.Tasks == nil { - return false, errorResponseWithSessionState(accountId, r.apiError(&ErrorMissingTasksAccountCapability), r.session.State) + return false, errorResponseWithSessionState(single(accountId), r.apiError(&ErrorMissingTasksAccountCapability), r.session.State) } } return true, Response{} @@ -380,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(accountId, err) + return false, "", errorResponse(single(accountId), err) } if !IgnoreSessionCapabilityChecks { if ok, resp := r.needTaskForAccount(accountId); !ok { @@ -393,7 +393,7 @@ func (r Request) needTaskWithAccount() (bool, string, Response) { func (r Request) needCalendar(accountId string) (bool, Response) { if !IgnoreSessionCapabilityChecks { if r.session.Capabilities.Calendars == nil { - return false, errorResponseWithSessionState(accountId, r.apiError(&ErrorMissingCalendarsSessionCapability), r.session.State) + return false, errorResponseWithSessionState(single(accountId), r.apiError(&ErrorMissingCalendarsSessionCapability), r.session.State) } } return true, Response{} @@ -405,11 +405,11 @@ func (r Request) needCalendarForAccount(accountId string) (bool, Response) { } account, ok := r.session.Accounts[accountId] if !ok { - return false, errorResponseWithSessionState(accountId, r.apiError(&ErrorAccountNotFound), r.session.State) + return false, errorResponseWithSessionState(single(accountId), r.apiError(&ErrorAccountNotFound), r.session.State) } if !IgnoreSessionCapabilityChecks { if account.AccountCapabilities.Calendars == nil { - return false, errorResponseWithSessionState(accountId, r.apiError(&ErrorMissingCalendarsAccountCapability), r.session.State) + return false, errorResponseWithSessionState(single(accountId), r.apiError(&ErrorMissingCalendarsAccountCapability), r.session.State) } } return true, Response{} @@ -418,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(accountId, err) + return false, "", errorResponse(single(accountId), err) } if !IgnoreSessionCapabilityChecks { if ok, resp := r.needCalendarForAccount(accountId); !ok { @@ -430,7 +430,7 @@ func (r Request) needCalendarWithAccount() (bool, string, Response) { func (r Request) needContact(accountId string) (bool, Response) { if r.session.Capabilities.Contacts == nil { - return false, errorResponseWithSessionState(accountId, r.apiError(&ErrorMissingContactsSessionCapability), r.session.State) + return false, errorResponseWithSessionState(single(accountId), r.apiError(&ErrorMissingContactsSessionCapability), r.session.State) } return true, Response{} } @@ -441,10 +441,10 @@ func (r Request) needContactForAccount(accountId string) (bool, Response) { } account, ok := r.session.Accounts[accountId] if !ok { - return false, errorResponseWithSessionState(accountId, r.apiError(&ErrorAccountNotFound), r.session.State) + return false, errorResponseWithSessionState(single(accountId), r.apiError(&ErrorAccountNotFound), r.session.State) } if account.AccountCapabilities.Contacts == nil { - return false, errorResponseWithSessionState(accountId, r.apiError(&ErrorMissingContactsAccountCapability), r.session.State) + return false, errorResponseWithSessionState(single(accountId), r.apiError(&ErrorMissingContactsAccountCapability), r.session.State) } return true, Response{} } @@ -452,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(accountId, err) + return false, "", errorResponse(single(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 8d3579c9b..b82fb63d8 100644 --- a/services/groupware/pkg/groupware/groupware_response.go +++ b/services/groupware/pkg/groupware/groupware_response.go @@ -31,14 +31,14 @@ type Response struct { err *Error etag jmap.State objectType ResponseObjectType - accountId string + accountIds []string sessionState jmap.SessionState contentLanguage jmap.Language } -func errorResponse(accountId string, err *Error) Response { +func errorResponse(accountIds []string, err *Error) Response { return Response{ - accountId: accountId, + accountIds: accountIds, body: nil, err: err, etag: "", @@ -46,9 +46,9 @@ func errorResponse(accountId string, err *Error) Response { } } -func errorResponseWithSessionState(accountId string, err *Error, sessionState jmap.SessionState) Response { +func errorResponseWithSessionState(accountIds []string, err *Error, sessionState jmap.SessionState) Response { return Response{ - accountId: accountId, + accountIds: accountIds, body: nil, err: err, etag: "", @@ -56,9 +56,9 @@ func errorResponseWithSessionState(accountId string, err *Error, sessionState jm } } -func response(accountId string, body any, sessionState jmap.SessionState, contentLanguage jmap.Language) Response { +func response(accountIds []string, body any, sessionState jmap.SessionState, contentLanguage jmap.Language) Response { return Response{ - accountId: accountId, + accountIds: accountIds, body: body, err: nil, etag: jmap.State(sessionState), @@ -67,9 +67,9 @@ func response(accountId string, body any, sessionState jmap.SessionState, conten } } -func etagResponse(accountId string, body any, sessionState jmap.SessionState, objectType ResponseObjectType, etag jmap.State, contentLanguage jmap.Language) Response { +func etagResponse(accountIds []string, body any, sessionState jmap.SessionState, objectType ResponseObjectType, etag jmap.State, contentLanguage jmap.Language) Response { return Response{ - accountId: accountId, + accountIds: accountIds, body: body, err: nil, etag: etag, @@ -92,9 +92,9 @@ func etagOnlyResponse(body any, etag jmap.State, objectType ResponseObjectType, } */ -func noContentResponse(accountId string, sessionState jmap.SessionState) Response { +func noContentResponse(accountIds []string, sessionState jmap.SessionState) Response { return Response{ - accountId: accountId, + accountIds: accountIds, body: nil, status: http.StatusNoContent, err: nil, @@ -103,9 +103,9 @@ func noContentResponse(accountId string, sessionState jmap.SessionState) Respons } } -func noContentResponseWithEtag(accountId string, sessionState jmap.SessionState, objectType ResponseObjectType, etag jmap.State) Response { +func noContentResponseWithEtag(accountIds []string, sessionState jmap.SessionState, objectType ResponseObjectType, etag jmap.State) Response { return Response{ - accountId: accountId, + accountIds: accountIds, body: nil, status: http.StatusNoContent, err: nil, @@ -139,9 +139,9 @@ func timeoutResponse(sessionState jmap.SessionState) Response { } */ -func notFoundResponse(accountId string, sessionState jmap.SessionState) Response { +func notFoundResponse(accountIds []string, sessionState jmap.SessionState) Response { return Response{ - accountId: accountId, + accountIds: accountIds, body: nil, status: http.StatusNotFound, err: nil, @@ -150,9 +150,9 @@ func notFoundResponse(accountId string, sessionState jmap.SessionState) Response } } -func etagNotFoundResponse(accountId string, sessionState jmap.SessionState, objectType ResponseObjectType, etag jmap.State, contentLanguage jmap.Language) Response { +func etagNotFoundResponse(accountIds []string, sessionState jmap.SessionState, objectType ResponseObjectType, etag jmap.State, contentLanguage jmap.Language) Response { return Response{ - accountId: accountId, + accountIds: accountIds, body: nil, status: http.StatusNotFound, err: nil,