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