groupware: add Object-Type and Account-Id response headers

* implement Request.AllAccountIds() to generalize the fetching (and
   uniqifying) of all account IDs, which will allow us to implement
   things such as "subscribed" accounts, or limiting the number of
   accounts in one request

 * add Account-Id response header

 * add Object-Type response header
This commit is contained in:
Pascal Bleser
2025-11-26 11:29:55 +01:00
parent db5c2a18c6
commit 865da8b83f
16 changed files with 491 additions and 425 deletions

View File

@@ -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)
})
}

View File

@@ -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)
})

View File

@@ -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)
})
}

View File

@@ -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)
})
}

View File

@@ -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)
})
}

View File

@@ -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)
}
})

View File

@@ -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)
})
}

View File

@@ -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)
})
}

View File

@@ -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)
})
}

View File

@@ -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, "")
})
}

View File

@@ -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)
})
}

View File

@@ -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)))
}

View File

@@ -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, ",")
}
}

View File

@@ -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 {

View File

@@ -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

View File

@@ -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,