groupware: refactor contactcard changes, and Request framework

* implement ContactCard retrieval endpoint for syncing

 * re-implement that endpoint for Email too

 * fix the Mailbox changes endpoint to actually return changes about
   Mailboxes, and not about Emails

 * when querying the diff of Mailboxes without any prior state, return
   an error since the result is not what one would expect

 * introduce the 'changes' API tag and group

 * refactor the successful response functions to consistently return an
   object type and object state whenever possible

 * move the syncing endpoints under /accounts/*/changes/ for better
   clarity, e.g. /changes/emails instead of /emails/mailbox/*/changes
This commit is contained in:
Pascal Bleser
2026-03-24 09:57:45 +01:00
parent 68a4a5a83c
commit 0202bb43af
22 changed files with 732 additions and 466 deletions

View File

@@ -63,6 +63,103 @@ func (j *Client) GetContactCardsById(accountId string, session *Session, ctx con
})
}
func (j *Client) GetContactCards(accountId string, session *Session, ctx context.Context, logger *log.Logger,
acceptLanguage string, contactIds []string) ([]jscontact.ContactCard, SessionState, State, Language, Error) {
logger = j.logger("GetContactCards", session, logger)
cmd, err := j.request(session, logger, invocation(CommandContactCardGet, ContactCardGetCommand{
Ids: contactIds,
AccountId: accountId,
}, "0"))
if err != nil {
return nil, "", "", "", err
}
return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) ([]jscontact.ContactCard, State, Error) {
var response ContactCardGetResponse
err = retrieveResponseMatchParameters(logger, body, CommandContactCardGet, "0", &response)
if err != nil {
return nil, "", err
}
return response.List, response.State, nil
})
}
type ContactCardChanges struct {
OldState State `json:"oldState,omitempty"`
NewState State `json:"newState"`
HasMoreChanges bool `json:"hasMoreChanges"`
Created []jscontact.ContactCard `json:"created,omitempty"`
Updated []jscontact.ContactCard `json:"updated,omitempty"`
Destroyed []string `json:"destroyed,omitempty"`
}
func (j *Client) GetContactCardsSince(accountId string, session *Session, ctx context.Context, logger *log.Logger,
acceptLanguage string, sinceState string, maxChanges uint) (ContactCardChanges, SessionState, State, Language, Error) {
logger = j.logger("GetContactCards", session, logger)
maxChangesPtr := &maxChanges
if maxChanges < 1 {
maxChangesPtr = nil
}
cmd, err := j.request(session, logger,
invocation(CommandContactCardChanges, ContactCardChangesCommand{
AccountId: accountId,
SinceState: sinceState,
MaxChanges: maxChangesPtr,
}, "0"),
invocation(CommandContactCardGet, ContactCardGetRefCommand{
AccountId: accountId,
IdsRef: &ResultReference{
ResultOf: "0",
Name: CommandContactCardChanges,
Path: "/created",
},
}, "1"),
invocation(CommandContactCardGet, ContactCardGetRefCommand{
AccountId: accountId,
IdsRef: &ResultReference{
ResultOf: "0",
Name: CommandContactCardChanges,
Path: "/updated",
},
}, "2"),
)
if err != nil {
return ContactCardChanges{}, "", "", "", err
}
return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (ContactCardChanges, State, Error) {
result := ContactCardChanges{}
var changes ContactCardChangesResponse
err = retrieveResponseMatchParameters(logger, body, CommandContactCardChanges, "0", &changes)
if err != nil {
return ContactCardChanges{}, "", err
}
result.NewState = changes.NewState
result.OldState = changes.OldState
result.HasMoreChanges = changes.HasMoreChanges
result.Destroyed = changes.Destroyed
var created ContactCardGetResponse
err = retrieveResponseMatchParameters(logger, body, CommandContactCardGet, "1", &created)
if err != nil {
return ContactCardChanges{}, "", err
}
result.Created = created.List
var updated ContactCardGetResponse
err = retrieveResponseMatchParameters(logger, body, CommandContactCardGet, "2", &updated)
if err != nil {
return ContactCardChanges{}, "", err
}
result.Updated = updated.List
return result, changes.NewState, nil
})
}
func (j *Client) QueryContactCards(accountIds []string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string,
filter ContactCardFilterElement, sortBy []ContactCardComparator,
position uint, limit uint) (map[string][]jscontact.ContactCard, SessionState, State, Language, Error) {

View File

@@ -194,38 +194,18 @@ func (j *Client) GetAllEmailsInMailbox(accountId string, session *Session, ctx c
})
}
func (j *Client) GetEmailChanges(accountId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, sinceState State, maxChanges uint) (EmailChangesResponse, SessionState, State, Language, Error) {
logger = j.loggerParams("GetEmailChanges", session, logger, func(z zerolog.Context) zerolog.Context {
return z.Str(logSinceState, string(sinceState))
})
changes := EmailChangesCommand{
AccountId: accountId,
SinceState: sinceState,
}
if maxChanges > 0 {
changes.MaxChanges = maxChanges
}
cmd, err := j.request(session, logger, invocation(CommandEmailChanges, changes, "0"))
if err != nil {
return EmailChangesResponse{}, "", "", "", simpleError(err, JmapErrorInvalidJmapRequestPayload)
}
return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (EmailChangesResponse, State, Error) {
var changesResponse EmailChangesResponse
err = retrieveResponseMatchParameters(logger, body, CommandEmailChanges, "0", &changesResponse)
if err != nil {
return EmailChangesResponse{}, "", err
}
return changesResponse, changesResponse.NewState, nil
})
type EmailChanges struct {
HasMoreChanges bool `json:"hasMoreChanges"`
OldState State `json:"oldState,omitempty"`
NewState State `json:"newState"`
Created []Email `json:"created,omitempty"`
Updated []Email `json:"updated,omitempty"`
Destroyed []string `json:"destroyed,omitempty"`
}
// Get all the Emails that have been created, updated or deleted since a given state.
func (j *Client) GetEmailsSince(accountId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, sinceState State, fetchBodies bool, maxBodyValueBytes uint, maxChanges uint) (MailboxChanges, SessionState, State, Language, Error) {
logger = j.loggerParams("GetEmailsSince", session, logger, func(z zerolog.Context) zerolog.Context {
func (j *Client) GetEmailChanges(accountId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, sinceState State, fetchBodies bool, maxBodyValueBytes uint, maxChanges uint) (EmailChanges, SessionState, State, Language, Error) {
logger = j.loggerParams("GetEmailChanges", session, logger, func(z zerolog.Context) zerolog.Context {
return z.Bool(logFetchBodies, fetchBodies).Str(logSinceState, string(sinceState))
})
@@ -234,7 +214,7 @@ func (j *Client) GetEmailsSince(accountId string, session *Session, ctx context.
SinceState: sinceState,
}
if maxChanges > 0 {
changes.MaxChanges = maxChanges
changes.MaxChanges = &maxChanges
}
getCreated := EmailGetRefCommand{
@@ -260,33 +240,34 @@ func (j *Client) GetEmailsSince(accountId string, session *Session, ctx context.
invocation(CommandEmailGet, getUpdated, "2"),
)
if err != nil {
return MailboxChanges{}, "", "", "", simpleError(err, JmapErrorInvalidJmapRequestPayload)
return EmailChanges{}, "", "", "", simpleError(err, JmapErrorInvalidJmapRequestPayload)
}
return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (MailboxChanges, State, Error) {
return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (EmailChanges, State, Error) {
var changesResponse EmailChangesResponse
err = retrieveResponseMatchParameters(logger, body, CommandEmailChanges, "0", &changesResponse)
if err != nil {
return MailboxChanges{}, "", err
return EmailChanges{}, "", err
}
var createdResponse EmailGetResponse
err = retrieveResponseMatchParameters(logger, body, CommandEmailGet, "1", &createdResponse)
if err != nil {
logger.Error().Err(err).Send()
return MailboxChanges{}, "", err
return EmailChanges{}, "", err
}
var updatedResponse EmailGetResponse
err = retrieveResponseMatchParameters(logger, body, CommandEmailGet, "2", &updatedResponse)
if err != nil {
logger.Error().Err(err).Send()
return MailboxChanges{}, "", err
return EmailChanges{}, "", err
}
return MailboxChanges{
return EmailChanges{
Destroyed: changesResponse.Destroyed,
HasMoreChanges: changesResponse.HasMoreChanges,
OldState: changesResponse.OldState,
NewState: changesResponse.NewState,
Created: createdResponse.List,
Updated: updatedResponse.List,

View File

@@ -155,48 +155,41 @@ func (j *Client) SearchMailboxIdsPerRole(accountIds []string, session *Session,
}
type MailboxChanges struct {
Destroyed []string `json:"destroyed,omitzero"`
HasMoreChanges bool `json:"hasMoreChanges,omitzero"`
NewState State `json:"newState"`
Created []Email `json:"created,omitempty"`
Updated []Email `json:"updated,omitempty"`
HasMoreChanges bool `json:"hasMoreChanges"`
OldState State `json:"oldState,omitempty"`
NewState State `json:"newState"`
Created []Mailbox `json:"created,omitempty"`
Updated []Mailbox `json:"updated,omitempty"`
Destroyed []string `json:"destroyed,omitempty"`
}
// Retrieve Email changes in a given Mailbox since a given state.
func (j *Client) GetMailboxChanges(accountId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, mailboxId string, sinceState string, fetchBodies bool, maxBodyValueBytes uint, maxChanges uint) (MailboxChanges, SessionState, State, Language, Error) {
logger = j.loggerParams("GetMailboxChanges", session, logger, func(z zerolog.Context) zerolog.Context {
return z.Bool(logFetchBodies, fetchBodies).Str(logSinceState, sinceState)
})
// Retrieve Mailbox changes since a given state.
// @apidoc mailboxes,changes
func (j *Client) GetMailboxChanges(accountId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, sinceState string, maxChanges uint) (MailboxChanges, SessionState, State, Language, Error) {
logger = j.logger("GetMailboxChanges", session, logger)
changes := MailboxChangesCommand{
AccountId: accountId,
SinceState: sinceState,
MaxChanges: nil,
}
if maxChanges > 0 {
changes.MaxChanges = maxChanges
changes.MaxChanges = &maxChanges
}
getCreated := EmailGetRefCommand{
AccountId: accountId,
FetchAllBodyValues: fetchBodies,
IdsRef: &ResultReference{Name: CommandMailboxChanges, Path: "/created", ResultOf: "0"},
getCreated := MailboxGetRefCommand{
AccountId: accountId,
IdsRef: &ResultReference{Name: CommandMailboxChanges, Path: "/created", ResultOf: "0"},
}
if maxBodyValueBytes > 0 {
getCreated.MaxBodyValueBytes = maxBodyValueBytes
}
getUpdated := EmailGetRefCommand{
AccountId: accountId,
FetchAllBodyValues: fetchBodies,
IdsRef: &ResultReference{Name: CommandMailboxChanges, Path: "/updated", ResultOf: "0"},
}
if maxBodyValueBytes > 0 {
getUpdated.MaxBodyValueBytes = maxBodyValueBytes
getUpdated := MailboxGetRefCommand{
AccountId: accountId,
IdsRef: &ResultReference{Name: CommandMailboxChanges, Path: "/updated", ResultOf: "0"},
}
cmd, err := j.request(session, logger,
invocation(CommandMailboxChanges, changes, "0"),
invocation(CommandEmailGet, getCreated, "1"),
invocation(CommandEmailGet, getUpdated, "2"),
invocation(CommandMailboxGet, getCreated, "1"),
invocation(CommandMailboxGet, getUpdated, "2"),
)
if err != nil {
return MailboxChanges{}, "", "", "", err
@@ -209,15 +202,15 @@ func (j *Client) GetMailboxChanges(accountId string, session *Session, ctx conte
return MailboxChanges{}, "", err
}
var createdResponse EmailGetResponse
err = retrieveResponseMatchParameters(logger, body, CommandEmailGet, "1", &createdResponse)
var createdResponse MailboxGetResponse
err = retrieveResponseMatchParameters(logger, body, CommandMailboxGet, "1", &createdResponse)
if err != nil {
logger.Error().Err(err).Send()
return MailboxChanges{}, "", err
}
var updatedResponse EmailGetResponse
err = retrieveResponseMatchParameters(logger, body, CommandEmailGet, "2", &updatedResponse)
var updatedResponse MailboxGetResponse
err = retrieveResponseMatchParameters(logger, body, CommandMailboxGet, "2", &updatedResponse)
if err != nil {
logger.Error().Err(err).Send()
return MailboxChanges{}, "", err
@@ -226,6 +219,7 @@ func (j *Client) GetMailboxChanges(accountId string, session *Session, ctx conte
return MailboxChanges{
Destroyed: mailboxResponse.Destroyed,
HasMoreChanges: mailboxResponse.HasMoreChanges,
OldState: mailboxResponse.OldState,
NewState: mailboxResponse.NewState,
Created: createdResponse.List,
Updated: createdResponse.List,
@@ -234,13 +228,13 @@ func (j *Client) GetMailboxChanges(accountId string, session *Session, ctx conte
}
// Retrieve Email changes in Mailboxes of multiple Accounts.
func (j *Client) GetMailboxChangesForMultipleAccounts(accountIds []string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, sinceStateMap map[string]string, fetchBodies bool, maxBodyValueBytes uint, maxChanges uint) (map[string]MailboxChanges, SessionState, State, Language, Error) {
func (j *Client) GetMailboxChangesForMultipleAccounts(accountIds []string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, sinceStateMap map[string]string, maxChanges uint) (map[string]MailboxChanges, SessionState, State, Language, Error) {
logger = j.loggerParams("GetMailboxChangesForMultipleAccounts", session, logger, func(z zerolog.Context) zerolog.Context {
sinceStateLogDict := zerolog.Dict()
for k, v := range sinceStateMap {
sinceStateLogDict.Str(log.SafeString(k), log.SafeString(v))
}
return z.Bool(logFetchBodies, fetchBodies).Dict(logSinceState, sinceStateLogDict)
return z.Dict(logSinceState, sinceStateLogDict)
})
uniqueAccountIds := structs.Uniq(accountIds)
@@ -261,29 +255,21 @@ func (j *Client) GetMailboxChangesForMultipleAccounts(accountIds []string, sessi
}
if maxChanges > 0 {
changes.MaxChanges = maxChanges
changes.MaxChanges = &maxChanges
}
getCreated := EmailGetRefCommand{
AccountId: accountId,
FetchAllBodyValues: fetchBodies,
IdsRef: &ResultReference{Name: CommandMailboxChanges, Path: "/created", ResultOf: mcid(accountId, "0")},
getCreated := MailboxGetRefCommand{
AccountId: accountId,
IdsRef: &ResultReference{Name: CommandMailboxChanges, Path: "/created", ResultOf: mcid(accountId, "0")},
}
if maxBodyValueBytes > 0 {
getCreated.MaxBodyValueBytes = maxBodyValueBytes
}
getUpdated := EmailGetRefCommand{
AccountId: accountId,
FetchAllBodyValues: fetchBodies,
IdsRef: &ResultReference{Name: CommandMailboxChanges, Path: "/updated", ResultOf: mcid(accountId, "0")},
}
if maxBodyValueBytes > 0 {
getUpdated.MaxBodyValueBytes = maxBodyValueBytes
getUpdated := MailboxGetRefCommand{
AccountId: accountId,
IdsRef: &ResultReference{Name: CommandMailboxChanges, Path: "/updated", ResultOf: mcid(accountId, "0")},
}
invocations[i*3+0] = invocation(CommandMailboxChanges, changes, mcid(accountId, "0"))
invocations[i*3+1] = invocation(CommandEmailGet, getCreated, mcid(accountId, "1"))
invocations[i*3+2] = invocation(CommandEmailGet, getUpdated, mcid(accountId, "2"))
invocations[i*3+1] = invocation(CommandMailboxGet, getCreated, mcid(accountId, "1"))
invocations[i*3+2] = invocation(CommandMailboxGet, getUpdated, mcid(accountId, "2"))
}
cmd, err := j.request(session, logger, invocations...)
@@ -301,14 +287,14 @@ func (j *Client) GetMailboxChangesForMultipleAccounts(accountIds []string, sessi
return nil, "", err
}
var createdResponse EmailGetResponse
err = retrieveResponseMatchParameters(logger, body, CommandEmailGet, mcid(accountId, "1"), &createdResponse)
var createdResponse MailboxGetResponse
err = retrieveResponseMatchParameters(logger, body, CommandMailboxGet, mcid(accountId, "1"), &createdResponse)
if err != nil {
return nil, "", err
}
var updatedResponse EmailGetResponse
err = retrieveResponseMatchParameters(logger, body, CommandEmailGet, mcid(accountId, "2"), &updatedResponse)
var updatedResponse MailboxGetResponse
err = retrieveResponseMatchParameters(logger, body, CommandMailboxGet, mcid(accountId, "2"), &updatedResponse)
if err != nil {
return nil, "", err
}

View File

@@ -90,7 +90,7 @@ func TestWs(t *testing.T) {
var initialState State
{
changes, sessionState, state, _, err := s.client.GetEmailChanges(mailAccountId, session, s.ctx, s.logger, "", "", 0)
changes, sessionState, state, _, err := s.client.GetEmailChanges(mailAccountId, session, s.ctx, s.logger, "", State(""), true, 0, 0)
require.NoError(err)
require.Equal(session.State, sessionState)
require.NotEmpty(state)
@@ -104,7 +104,7 @@ func TestWs(t *testing.T) {
require.NotEmpty(initialState)
{
changes, sessionState, state, _, err := s.client.GetEmailChanges(mailAccountId, session, s.ctx, s.logger, "", initialState, 0)
changes, sessionState, state, _, err := s.client.GetEmailChanges(mailAccountId, session, s.ctx, s.logger, "", initialState, true, 0, 0)
require.NoError(err)
require.Equal(session.State, sessionState)
require.Equal(initialState, state)
@@ -147,7 +147,7 @@ func TestWs(t *testing.T) {
}
var lastState State
{
changes, sessionState, state, _, err := s.client.GetEmailChanges(mailAccountId, session, s.ctx, s.logger, "", initialState, 0)
changes, sessionState, state, _, err := s.client.GetEmailChanges(mailAccountId, session, s.ctx, s.logger, "", initialState, true, 0, 0)
require.NoError(err)
require.Equal(session.State, sessionState)
require.NotEqual(initialState, state)
@@ -158,7 +158,7 @@ func TestWs(t *testing.T) {
require.Empty(changes.Updated)
lastState = state
emailIds = append(emailIds, changes.Created...)
emailIds = append(emailIds, structs.Map(changes.Created, func(e Email) string { return e.Id })...)
}
{
@@ -181,7 +181,7 @@ func TestWs(t *testing.T) {
l.m.Unlock()
}
{
changes, sessionState, state, _, err := s.client.GetEmailChanges(mailAccountId, session, s.ctx, s.logger, "", lastState, 0)
changes, sessionState, state, _, err := s.client.GetEmailChanges(mailAccountId, session, s.ctx, s.logger, "", lastState, true, 0, 0)
require.NoError(err)
require.Equal(session.State, sessionState)
require.NotEqual(lastState, state)
@@ -192,7 +192,7 @@ func TestWs(t *testing.T) {
require.Empty(changes.Updated)
lastState = state
emailIds = append(emailIds, changes.Created...)
emailIds = append(emailIds, structs.Map(changes.Created, func(e Email) string { return e.Id })...)
}
{
@@ -215,7 +215,7 @@ func TestWs(t *testing.T) {
l.m.Unlock()
}
{
changes, sessionState, state, _, err := s.client.GetEmailChanges(mailAccountId, session, s.ctx, s.logger, "", lastState, 0)
changes, sessionState, state, _, err := s.client.GetEmailChanges(mailAccountId, session, s.ctx, s.logger, "", lastState, true, 0, 0)
require.NoError(err)
require.Equal(session.State, sessionState)
require.NotEqual(lastState, state)

View File

@@ -842,8 +842,12 @@ type SessionState string
type State string
const EmptyState = State("")
type Language string
const NoLanguage = Language("")
type SessionResponse struct {
Capabilities SessionCapabilities `json:"capabilities"`
@@ -1350,7 +1354,7 @@ type MailboxChangesCommand struct {
// If supplied by the client, the value MUST be a positive integer greater than 0.
//
// If a value outside of this range is given, the server MUST reject the call with an invalidArguments error.
MaxChanges uint `json:"maxChanges,omitzero"`
MaxChanges *uint `json:"maxChanges,omitzero"`
}
type MailboxFilterElement interface {
@@ -1859,7 +1863,7 @@ type EmailChangesCommand struct {
// The server MAY choose to return fewer than this value but MUST NOT return more.
// If not given by the client, the server may choose how many to return.
// If supplied by the client, the value MUST be a positive integer greater than 0.
MaxChanges uint `json:"maxChanges,omitzero"`
MaxChanges *uint `json:"maxChanges,omitempty"`
}
type EmailAddress struct {
@@ -5307,6 +5311,47 @@ type ContactCardGetResponse struct {
NotFound []any `json:"notFound"`
}
type ContactCardChangesCommand struct {
// The id of the account to use.
AccountId string `json:"accountId"`
// The current state of the client.
// This is the string that was returned as the "state" argument in the "ContactCard/get" response.
// The server will return the changes that have occurred since this state.
SinceState string `json:"sinceState,omitempty"`
// The maximum number of ids to return in the response.
// The server MAY choose to return fewer than this value but MUST NOT return more.
// If not given by the client, the server may choose how many to return.
// If supplied by the client, the value MUST be a positive integer greater than 0.
// If a value outside of this range is given, the server MUST reject the call with an `invalidArguments` error.
MaxChanges *uint `json:"maxChanges,omitempty"`
}
type ContactCardChangesResponse struct {
// The id of the account used for the call.
AccountId string `json:"accountId"`
// This is the "sinceState" argument echoed back; it's the state from which the server is returning changes.
OldState State `json:"oldState"`
// This is the state the client will be in after applying the set of changes to the old state.
NewState State `json:"newState"`
// If true, the client may call "ContactCard/changes" again with the "newState" returned to get further updates.
// If false, "newState" is the current server state.
HasMoreChanges bool `json:"hasMoreChanges"`
// An array of ids for records that have been created since the old state.
Created []string `json:"created,omitempty"`
// An array of ids for records that have been updated since the old state.
Updated []string `json:"updated,omitempty"`
// An array of ids for records that have been destroyed since the old state.
Destroyed []string `json:"destroyed,omitempty"`
}
type ContactCardUpdate map[string]any
type ContactCardSetCommand struct {
@@ -5884,6 +5929,7 @@ const (
CommandAddressBookGet Command = "AddressBook/get"
CommandContactCardQuery Command = "ContactCard/query"
CommandContactCardGet Command = "ContactCard/get"
CommandContactCardChanges Command = "ContactCard/changes"
CommandContactCardSet Command = "ContactCard/set"
CommandCalendarEventParse Command = "CalendarEvent/parse"
CommandCalendarGet Command = "Calendar/get"
@@ -5916,6 +5962,7 @@ var CommandResponseTypeMap = map[Command]func() any{
CommandAddressBookGet: func() any { return AddressBookGetResponse{} },
CommandContactCardQuery: func() any { return ContactCardQueryResponse{} },
CommandContactCardGet: func() any { return ContactCardGetResponse{} },
CommandContactCardChanges: func() any { return ContactCardChangesResponse{} },
CommandContactCardSet: func() any { return ContactCardSetResponse{} },
CommandCalendarEventParse: func() any { return CalendarEventParseResponse{} },
CommandCalendarGet: func() any { return CalendarGetResponse{} },

View File

@@ -10,6 +10,7 @@ import (
"strings"
"time"
"github.com/opencloud-eu/opencloud/pkg/jscontact"
c "github.com/opencloud-eu/opencloud/pkg/jscontact"
)
@@ -697,9 +698,10 @@ func (e Exemplar) Mailboxes() []Mailbox {
}
func (e Exemplar) MailboxChanges() MailboxChanges {
a, _, _ := e.MailboxInbox()
return MailboxChanges{
NewState: "aesh2ahj",
Created: []Email{e.Email()},
Created: []Mailbox{a},
Destroyed: []string{"baingow4"},
}
}
@@ -1783,3 +1785,35 @@ func (e Exemplar) ContactCard() c.ContactCard {
},
}
}
func (e Exemplar) ContactCardChanges() (ContactCardChanges, string, string) {
c := e.ContactCard()
return ContactCardChanges{
OldState: "xai3iiraipoo",
NewState: "ni7thah7eeY4",
HasMoreChanges: true,
Created: []jscontact.ContactCard{c},
Destroyed: []string{"eaae", "bcba"},
}, "A created ContactCard and two deleted ones", "created"
}
func (e Exemplar) OtherContactCardChanges() (ContactCardChanges, string, string) {
c := e.ContactCard()
return ContactCardChanges{
OldState: "xai3iiraipoo",
NewState: "ni7thah7eeY4",
HasMoreChanges: false,
Updated: []jscontact.ContactCard{c},
}, "An updated ContactCard", "updated"
}
func (e Exemplar) EmailChanges() EmailChanges {
emails := []Email{e.Email()}
return EmailChanges{
OldState: "xai3iiraipoo",
NewState: "ni7thah7eeY4",
HasMoreChanges: true,
Created: emails,
Destroyed: []string{"mmnan", "moxzz"},
}
}

View File

@@ -58,6 +58,9 @@ tags:
- name: vacation
x-displayName: Vacation Responses
description: APIs about vacation responses
- name: changes
x-displayName: Changes
description: APIs for retrieving changes to objects
x-tagGroups:
- name: Bootstrapping
tags:
@@ -86,6 +89,9 @@ x-tagGroups:
- name: Quotas
tags:
- quota
- name: changes
tags:
- changes
- name: Uncategorized
tags:
- untagged

View File

@@ -14,10 +14,10 @@ func (g *Groupware) GetAccount(w http.ResponseWriter, r *http.Request) {
g.respond(w, r, func(req Request) Response {
accountId, account, err := req.GetAccountForMail()
if err != nil {
return errorResponse(single(accountId), err)
return req.error(accountId, err)
}
var body jmap.Account = account
return etagResponse(single(accountId), body, req.session.State, AccountResponseObjectType, jmap.State(req.session.State), "")
return req.respond(accountId, body, req.session.State, AccountResponseObjectType, "")
})
}
@@ -36,7 +36,7 @@ func (g *Groupware) GetAccounts(w http.ResponseWriter, r *http.Request) {
// sort on accountId to have a stable order that remains the same with every query
slices.SortFunc(list, func(a, b AccountWithId) int { return strings.Compare(a.AccountId, b.AccountId) })
var RBODY []AccountWithId = list
return etagResponse(structs.Map(list, func(a AccountWithId) string { return a.AccountId }), RBODY, req.session.State, AccountResponseObjectType, jmap.State(req.session.State), "")
return req.respondN(structs.Map(list, func(a AccountWithId) string { return a.AccountId }), RBODY, req.session.State, AccountResponseObjectType, "")
})
}
@@ -46,7 +46,7 @@ func (g *Groupware) GetAccountsWithTheirIdentities(w http.ResponseWriter, r *htt
allAccountIds := req.AllAccountIds()
resp, sessionState, state, lang, err := g.jmap.GetIdentitiesForAllAccounts(allAccountIds, req.session, req.ctx, req.logger, req.language())
if err != nil {
return req.errorResponseFromJmap(allAccountIds, err)
return req.jmapErrorN(allAccountIds, err, sessionState, lang)
}
list := make([]AccountWithIdAndIdentities, len(req.session.Accounts))
i := 0
@@ -66,7 +66,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) })
var RBODY []AccountWithIdAndIdentities = list
return etagResponse(structs.Map(list, func(a AccountWithIdAndIdentities) string { return a.AccountId }), RBODY, sessionState, AccountResponseObjectType, state, lang)
return req.respondN(structs.Map(list, func(a AccountWithIdAndIdentities) string { return a.AccountId }), RBODY, sessionState, AccountResponseObjectType, state)
})
}

View File

@@ -16,13 +16,13 @@ func (g *Groupware) GetBlobMeta(w http.ResponseWriter, r *http.Request) {
g.respond(w, r, func(req Request) Response {
accountId, err := req.GetAccountIdForBlob()
if err != nil {
return errorResponse(single(accountId), err)
return req.error(accountId, err)
}
l := req.logger.With().Str(logAccountId, accountId)
blobId, err := req.PathParam(UriParamBlobId)
if err != nil {
return errorResponse(single(accountId), err)
return req.error(accountId, err)
}
l = l.Str(UriParamBlobId, blobId)
@@ -30,12 +30,12 @@ func (g *Groupware) GetBlobMeta(w http.ResponseWriter, r *http.Request) {
res, sessionState, state, lang, jerr := g.jmap.GetBlobMetadata(accountId, req.session, req.ctx, logger, req.language(), blobId)
if jerr != nil {
return req.errorResponseFromJmap(single(accountId), jerr)
return req.jmapError(accountId, jerr, sessionState, lang)
}
if res == nil {
return notFoundResponse(single(accountId), sessionState)
return req.notFound(accountId, sessionState, BlobResponseObjectType, state)
}
return etagResponse(single(accountId), res, sessionState, BlobResponseObjectType, state, lang)
return req.respond(accountId, res, sessionState, BlobResponseObjectType, state)
})
}
@@ -54,16 +54,16 @@ func (g *Groupware) UploadBlob(w http.ResponseWriter, r *http.Request) {
accountId, err := req.GetAccountIdForBlob()
if err != nil {
return errorResponse(single(accountId), err)
return req.error(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(single(accountId), jerr)
return req.jmapError(accountId, jerr, req.session.State, lang)
}
return response(single(accountId), resp, req.session.State, lang)
return req.respondWithoutStatus(accountId, resp)
})
}

View File

@@ -18,10 +18,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(single(accountId), jerr)
return req.jmapError(accountId, jerr, sessionState, lang)
}
return etagResponse(single(accountId), calendars, sessionState, CalendarResponseObjectType, state, lang)
return req.respond(accountId, calendars, sessionState, CalendarResponseObjectType, state)
})
}
@@ -37,20 +37,20 @@ func (g *Groupware) GetCalendarById(w http.ResponseWriter, r *http.Request) {
calendarId, err := req.PathParam(UriParamCalendarId)
if err != nil {
return errorResponse(single(accountId), err)
return req.error(accountId, err)
}
l = l.Str(UriParamCalendarId, log.SafeString(calendarId))
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(single(accountId), jerr)
return req.jmapError(accountId, jerr, sessionState, lang)
}
if len(calendars.NotFound) > 0 {
return notFoundResponse(single(accountId), sessionState)
return req.notFound(accountId, sessionState, CalendarResponseObjectType, state)
} else {
return etagResponse(single(accountId), calendars.Calendars[0], sessionState, CalendarResponseObjectType, state, lang)
return req.respond(accountId, calendars.Calendars[0], sessionState, CalendarResponseObjectType, state)
}
})
}
@@ -67,13 +67,13 @@ func (g *Groupware) GetEventsInCalendar(w http.ResponseWriter, r *http.Request)
calendarId, err := req.PathParam(UriParamCalendarId)
if err != nil {
return errorResponse(single(accountId), err)
return req.error(accountId, err)
}
l = l.Str(UriParamCalendarId, log.SafeString(calendarId))
offset, ok, err := req.parseUIntParam(QueryParamOffset, 0)
if err != nil {
return errorResponse(single(accountId), err)
return req.error(accountId, err)
}
if ok {
l = l.Uint(QueryParamOffset, offset)
@@ -81,7 +81,7 @@ func (g *Groupware) GetEventsInCalendar(w http.ResponseWriter, r *http.Request)
limit, ok, err := req.parseUIntParam(QueryParamLimit, g.defaults.contactLimit)
if err != nil {
return errorResponse(single(accountId), err)
return req.error(accountId, err)
}
if ok {
l = l.Uint(QueryParamLimit, limit)
@@ -95,13 +95,13 @@ func (g *Groupware) GetEventsInCalendar(w http.ResponseWriter, r *http.Request)
logger := log.From(l)
eventsByAccountId, sessionState, state, lang, jerr := g.jmap.QueryCalendarEvents(single(accountId), req.session, req.ctx, logger, req.language(), filter, sortBy, offset, limit)
if jerr != nil {
return req.errorResponseFromJmap(single(accountId), jerr)
return req.jmapError(accountId, jerr, sessionState, lang)
}
if events, ok := eventsByAccountId[accountId]; ok {
return etagResponse(single(accountId), events, sessionState, EventResponseObjectType, state, lang)
return req.respond(accountId, events, sessionState, EventResponseObjectType, state)
} else {
return notFoundResponse(single(accountId), sessionState)
return req.notFound(accountId, sessionState, EventResponseObjectType, state)
}
})
}
@@ -118,15 +118,15 @@ func (g *Groupware) CreateCalendarEvent(w http.ResponseWriter, r *http.Request)
var create jmap.CalendarEvent
err := req.body(&create)
if err != nil {
return errorResponse(single(accountId), err)
return req.error(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(single(accountId), jerr)
return req.jmapError(accountId, jerr, sessionState, lang)
}
return etagResponse(single(accountId), created, sessionState, EventResponseObjectType, state, lang)
return req.respond(accountId, created, sessionState, EventResponseObjectType, state)
})
}
@@ -140,33 +140,33 @@ func (g *Groupware) DeleteCalendarEvent(w http.ResponseWriter, r *http.Request)
eventId, err := req.PathParam(UriParamEventId)
if err != nil {
return errorResponse(single(accountId), err)
return req.error(accountId, err)
}
l.Str(UriParamEventId, log.SafeString(eventId))
logger := log.From(l)
deleted, sessionState, state, _, jerr := g.jmap.DeleteCalendarEvent(accountId, []string{eventId}, req.session, req.ctx, logger, req.language())
deleted, sessionState, state, lang, jerr := g.jmap.DeleteCalendarEvent(accountId, []string{eventId}, req.session, req.ctx, logger, req.language())
if jerr != nil {
return req.errorResponseFromJmap(single(accountId), jerr)
return req.jmapError(accountId, jerr, sessionState, lang)
}
for _, e := range deleted {
desc := e.Description
if desc != "" {
return errorResponseWithSessionState(single(accountId), apiError(
return req.errorS(accountId, apiError(
req.errorId(),
ErrorFailedToDeleteContact,
withDetail(e.Description),
), sessionState)
} else {
return errorResponseWithSessionState(single(accountId), apiError(
return req.errorS(accountId, apiError(
req.errorId(),
ErrorFailedToDeleteContact,
), sessionState)
}
}
return noContentResponseWithEtag(single(accountId), sessionState, EventResponseObjectType, state)
return req.noContent(accountId, sessionState, EventResponseObjectType, state)
})
}
@@ -174,12 +174,12 @@ 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(single(accountId), err)
return req.error(accountId, err)
}
blobId, err := req.PathParam(UriParamBlobId)
if err != nil {
return errorResponse(single(accountId), err)
return req.error(accountId, err)
}
blobIds := strings.Split(blobId, ",")
@@ -188,8 +188,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(single(accountId), jerr)
return req.jmapError(accountId, jerr, sessionState, lang)
}
return etagResponse(single(accountId), resp, sessionState, EventResponseObjectType, state, lang)
return req.respond(accountId, resp, sessionState, EventResponseObjectType, state)
})
}

View File

@@ -51,11 +51,11 @@ 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(single(accountId), jerr)
return req.jmapError(accountId, jerr, sessionState, lang)
}
var body jmap.AddressBooksResponse = addressbooks
return etagResponse(single(accountId), body, sessionState, AddressBookResponseObjectType, state, lang)
return req.respond(accountId, body, sessionState, AddressBookResponseObjectType, state)
})
}
@@ -71,20 +71,20 @@ func (g *Groupware) GetAddressbook(w http.ResponseWriter, r *http.Request) {
addressBookId, err := req.PathParam(UriParamAddressBookId)
if err != nil {
return errorResponse(single(accountId), err)
return req.error(accountId, err)
}
l = l.Str(UriParamAddressBookId, log.SafeString(addressBookId))
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(single(accountId), jerr)
return req.jmapError(accountId, jerr, sessionState, lang)
}
if len(addressbooks.NotFound) > 0 {
return notFoundResponse(single(accountId), sessionState)
return req.notFound(accountId, sessionState, AddressBookResponseObjectType, state)
} else {
return etagResponse(single(accountId), addressbooks.AddressBooks[0], sessionState, AddressBookResponseObjectType, state, lang)
return req.respond(accountId, addressbooks.AddressBooks[0], sessionState, AddressBookResponseObjectType, state)
}
})
}
@@ -102,13 +102,13 @@ func (g *Groupware) GetContactsInAddressbook(w http.ResponseWriter, r *http.Requ
addressBookId, err := req.PathParam(UriParamAddressBookId)
if err != nil {
return errorResponseWithSessionState(accountIds, err, req.session.State)
return req.errorN(accountIds, err)
}
l = l.Str(UriParamAddressBookId, log.SafeString(addressBookId))
offset, ok, err := req.parseUIntParam(QueryParamOffset, 0)
if err != nil {
return errorResponseWithSessionState(accountIds, err, req.session.State)
return req.errorN(accountIds, err)
}
if ok {
l = l.Uint(QueryParamOffset, offset)
@@ -116,7 +116,7 @@ func (g *Groupware) GetContactsInAddressbook(w http.ResponseWriter, r *http.Requ
limit, ok, err := req.parseUIntParam(QueryParamLimit, g.defaults.contactLimit)
if err != nil {
return errorResponseWithSessionState(accountIds, err, req.session.State)
return req.errorN(accountIds, err)
}
if ok {
l = l.Uint(QueryParamLimit, limit)
@@ -135,13 +135,13 @@ func (g *Groupware) GetContactsInAddressbook(w http.ResponseWriter, r *http.Requ
logger := log.From(l)
contactsByAccountId, sessionState, state, lang, jerr := g.jmap.QueryContactCards(accountIds, req.session, req.ctx, logger, req.language(), filter, sortBy, offset, limit)
if jerr != nil {
return req.errorResponseFromJmap(accountIds, jerr)
return req.jmapErrorN(accountIds, jerr, sessionState, lang)
}
if contacts, ok := contactsByAccountId[accountId]; ok {
return etagResponse(accountIds, contacts, sessionState, ContactResponseObjectType, state, lang)
return req.respondN(accountIds, contacts, sessionState, ContactResponseObjectType, state)
} else {
return etagNotFoundResponse(accountIds, sessionState, ContactResponseObjectType, state, lang)
return req.notFoundN(accountIds, sessionState, ContactResponseObjectType, state)
}
})
}
@@ -157,24 +157,80 @@ func (g *Groupware) GetContactById(w http.ResponseWriter, r *http.Request) {
contactId, err := req.PathParam(UriParamContactId)
if err != nil {
return errorResponse(single(accountId), err)
return req.error(accountId, err)
}
l = l.Str(UriParamContactId, log.SafeString(contactId))
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(single(accountId), jerr)
return req.jmapError(accountId, jerr, sessionState, lang)
}
if contact, ok := contactsById[contactId]; ok {
return etagResponse(single(accountId), contact, sessionState, ContactResponseObjectType, state, lang)
return req.respond(accountId, contact, sessionState, ContactResponseObjectType, state)
} else {
return etagNotFoundResponse(single(accountId), sessionState, ContactResponseObjectType, state, lang)
return req.notFound(accountId, sessionState, ContactResponseObjectType, state)
}
})
}
func (g *Groupware) GetAllContacts(w http.ResponseWriter, r *http.Request) {
g.respond(w, r, func(req Request) Response {
ok, accountId, resp := req.needContactWithAccount()
if !ok {
return resp
}
l := req.logger.With()
logger := log.From(l)
contacts, sessionState, state, lang, jerr := g.jmap.GetContactCards(accountId, req.session, req.ctx, logger, req.language(), []string{})
if jerr != nil {
return req.jmapError(accountId, jerr, sessionState, lang)
}
var body []jscontact.ContactCard = contacts
return req.respond(accountId, body, sessionState, ContactResponseObjectType, state)
})
}
// Get changes to Contacts since a given State
// @api:tags contact,changes
func (g *Groupware) GetContactsChanges(w http.ResponseWriter, r *http.Request) {
g.respond(w, r, func(req Request) Response {
ok, accountId, resp := req.needContactWithAccount()
if !ok {
return resp
}
l := req.logger.With()
var maxChanges uint = 0
if v, ok, err := req.parseUIntParam(QueryParamMaxChanges, 0); err != nil {
return req.error(accountId, err)
} else if ok {
maxChanges = v
l = l.Uint(QueryParamMaxChanges, v)
}
sinceState, err := req.HeaderParamDoc(HeaderParamSince, "Specifies the state identifier from which on to list mailbox changes")
if err != nil {
return req.error(accountId, err)
}
l = l.Str(HeaderParamSince, log.SafeString(sinceState))
logger := log.From(l)
changes, sessionState, state, lang, jerr := g.jmap.GetContactCardsSince(accountId, req.session, req.ctx, logger, req.language(), sinceState, maxChanges)
if jerr != nil {
return req.jmapError(accountId, jerr, sessionState, lang)
}
var body jmap.ContactCardChanges = changes
return req.respond(accountId, body, sessionState, ContactResponseObjectType, state)
})
}
func (g *Groupware) CreateContact(w http.ResponseWriter, r *http.Request) {
g.respond(w, r, func(req Request) Response {
ok, accountId, resp := req.needContactWithAccount()
@@ -186,22 +242,22 @@ func (g *Groupware) CreateContact(w http.ResponseWriter, r *http.Request) {
addressBookId, err := req.PathParam(UriParamAddressBookId)
if err != nil {
return errorResponse(single(accountId), err)
return req.error(accountId, err)
}
l = l.Str(UriParamAddressBookId, log.SafeString(addressBookId))
var create jscontact.ContactCard
err = req.bodydoc(&create, "The contact to create, which may not have its id attribute set")
if err != nil {
return errorResponse(single(accountId), err)
return req.error(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(single(accountId), jerr)
return req.jmapError(accountId, jerr, sessionState, lang)
}
return etagResponse(single(accountId), created, sessionState, ContactResponseObjectType, state, lang)
return req.respond(accountId, created, sessionState, ContactResponseObjectType, state)
})
}
@@ -215,33 +271,33 @@ func (g *Groupware) DeleteContact(w http.ResponseWriter, r *http.Request) {
contactId, err := req.PathParam(UriParamContactId)
if err != nil {
return errorResponse(single(accountId), err)
return req.error(accountId, err)
}
l.Str(UriParamContactId, log.SafeString(contactId))
logger := log.From(l)
deleted, sessionState, state, _, jerr := g.jmap.DeleteContactCard(accountId, []string{contactId}, req.session, req.ctx, logger, req.language())
deleted, sessionState, state, lang, jerr := g.jmap.DeleteContactCard(accountId, []string{contactId}, req.session, req.ctx, logger, req.language())
if jerr != nil {
return req.errorResponseFromJmap(single(accountId), jerr)
return req.jmapError(accountId, jerr, sessionState, lang)
}
for _, e := range deleted {
desc := e.Description
if desc != "" {
return errorResponseWithSessionState(single(accountId), apiError(
return req.error(accountId, apiError(
req.errorId(),
ErrorFailedToDeleteContact,
withDetail(e.Description),
), sessionState)
))
} else {
return errorResponseWithSessionState(single(accountId), apiError(
return req.error(accountId, apiError(
req.errorId(),
ErrorFailedToDeleteContact,
), sessionState)
))
}
}
return noContentResponseWithEtag(single(accountId), sessionState, ContactResponseObjectType, state)
return req.noContent(accountId, sessionState, ContactResponseObjectType, state)
})
}

View File

@@ -21,44 +21,41 @@ import (
"github.com/opencloud-eu/opencloud/services/groupware/pkg/metrics"
)
// Get all the emails in a mailbox since a given state.
//
// Retrieve the list of all the emails that are in a given mailbox since a given state.
//
// The mailbox must be specified by its id, as part of the request URL path.
//
// A limit and an offset may be specified using the query parameters 'limit' and 'offset',
// respectively.
func (g *Groupware) GetAllEmailsInMailboxSince(w http.ResponseWriter, r *http.Request) {
maxChanges := uint(0)
// Get the changes that occured in a given mailbox since a certain state.
// @api:tags mailbox,changes
func (g *Groupware) GetEmailChanges(w http.ResponseWriter, r *http.Request) {
g.respond(w, r, func(req Request) Response {
l := req.logger.With()
accountId, err := req.GetAccountIdForMail()
if err != nil {
return errorResponse(single(accountId), err)
return req.error(accountId, err)
}
l = l.Str(logAccountId, accountId)
mailboxId, err := req.PathParam(UriParamMailboxId)
maxChanges, ok, err := req.parseUIntParam(QueryParamMaxChanges, 0)
if err != nil {
return errorResponse(single(accountId), err)
return req.error(accountId, err)
}
if ok {
l = l.Uint(QueryParamMaxChanges, maxChanges)
}
since, err := req.PathParamDoc(UriParamSince, "State identifier that indicates the coordinate from whence on to list mailbox changes")
if err != nil {
return errorResponse(single(accountId), err)
sinceState := jmap.EmptyState
if s := req.OptHeaderParamDoc(HeaderParamSince, "Optionally specifies the state identifier from which on to list email changes"); s != "" {
l = l.Str(HeaderParamSince, log.SafeString(s))
sinceState = jmap.State(s)
}
logger := log.From(req.logger.With().Str(HeaderParamSince, log.SafeString(since)).Str(logAccountId, log.SafeString(accountId)))
logger := log.From(l)
changes, sessionState, state, lang, jerr := g.jmap.GetMailboxChanges(accountId, req.session, req.ctx, logger, req.language(), mailboxId, since, true, g.config.maxBodyValueBytes, maxChanges)
changes, sessionState, state, lang, jerr := g.jmap.GetEmailChanges(accountId, req.session, req.ctx, logger, req.language(), sinceState, true, g.config.maxBodyValueBytes, maxChanges)
if jerr != nil {
return req.errorResponseFromJmap(single(accountId), jerr)
return req.jmapError(accountId, jerr, sessionState, lang)
}
return etagResponse(single(accountId), changes, sessionState, EmailResponseObjectType, state, lang)
return req.respond(accountId, changes, sessionState, MailboxResponseObjectType, state)
})
}
// Get all the emails in a mailbox.
@@ -75,18 +72,18 @@ func (g *Groupware) GetAllEmailsInMailbox(w http.ResponseWriter, r *http.Request
accountId, err := req.GetAccountIdForMail()
if err != nil {
return errorResponse(single(accountId), err)
return req.error(accountId, err)
}
l = l.Str(logAccountId, accountId)
mailboxId, err := req.PathParam(UriParamMailboxId)
if err != nil {
return errorResponse(single(accountId), err)
return req.error(accountId, err)
}
offset, ok, err := req.parseIntParam(QueryParamOffset, 0)
if err != nil {
return errorResponse(single(accountId), err)
return req.error(accountId, err)
}
if ok {
l = l.Int(QueryParamOffset, offset)
@@ -94,7 +91,7 @@ func (g *Groupware) GetAllEmailsInMailbox(w http.ResponseWriter, r *http.Request
limit, ok, err := req.parseUIntParam(QueryParamLimit, g.defaults.emailLimit)
if err != nil {
return errorResponse(single(accountId), err)
return req.error(accountId, err)
}
if ok {
l = l.Uint(QueryParamLimit, limit)
@@ -108,12 +105,12 @@ func (g *Groupware) GetAllEmailsInMailbox(w http.ResponseWriter, r *http.Request
emails, sessionState, state, lang, jerr := g.jmap.GetAllEmailsInMailbox(accountId, req.session, req.ctx, logger, req.language(), mailboxId, offset, limit, collapseThreads, fetchBodies, g.config.maxBodyValueBytes, withThreads)
if jerr != nil {
return req.errorResponseFromJmap(single(accountId), jerr)
return req.jmapError(accountId, jerr, sessionState, lang)
}
sanitized, err := req.sanitizeEmails(emails.Emails)
if err != nil {
return errorResponseWithSessionState(single(accountId), err, sessionState)
return req.error(accountId, err)
}
safe := jmap.Emails{
@@ -123,7 +120,7 @@ func (g *Groupware) GetAllEmailsInMailbox(w http.ResponseWriter, r *http.Request
Offset: emails.Offset,
}
return etagResponse(single(accountId), safe, sessionState, EmailResponseObjectType, state, lang)
return req.respond(accountId, safe, sessionState, EmailResponseObjectType, state)
})
}
@@ -175,13 +172,13 @@ func (g *Groupware) GetEmailsById(w http.ResponseWriter, r *http.Request) {
g.respond(w, r, func(req Request) Response {
accountId, err := req.GetAccountIdForMail()
if err != nil {
return errorResponse(single(accountId), err)
return req.error(accountId, err)
}
l := req.logger.With().Str(logAccountId, log.SafeString(accountId))
id, err := req.PathParam(UriParamEmailId)
if err != nil {
return errorResponse(single(accountId), err)
return req.error(accountId, err)
}
ids := strings.Split(id, ",")
if len(ids) < 1 {
@@ -190,7 +187,7 @@ func (g *Groupware) GetEmailsById(w http.ResponseWriter, r *http.Request) {
markAsSeen, ok, err := req.parseBoolParam(QueryParamMarkAsSeen, false)
if err != nil {
return errorResponse(single(accountId), err)
return req.error(accountId, err)
}
if ok {
l = l.Bool(QueryParamMarkAsSeen, markAsSeen)
@@ -201,32 +198,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.config.maxBodyValueBytes, markAsSeen, true)
if jerr != nil {
return req.errorResponseFromJmap(single(accountId), jerr)
return req.jmapError(accountId, jerr, sessionState, lang)
}
if len(emails) < 1 {
return notFoundResponse(single(accountId), sessionState)
return req.notFound(accountId, sessionState, EmailResponseObjectType, state)
} else {
sanitized, err := req.sanitizeEmail(emails[0])
if err != nil {
return errorResponseWithSessionState(single(accountId), err, sessionState)
return req.error(accountId, err)
}
return etagResponse(single(accountId), sanitized, sessionState, EmailResponseObjectType, state, lang)
return req.respond(accountId, sanitized, sessionState, EmailResponseObjectType, state)
}
} 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.config.maxBodyValueBytes, markAsSeen, false)
if jerr != nil {
return req.errorResponseFromJmap(single(accountId), jerr)
return req.jmapError(accountId, jerr, sessionState, lang)
}
if len(emails) < 1 {
return notFoundResponse(single(accountId), sessionState)
return req.notFound(accountId, sessionState, EmailResponseObjectType, state)
} else {
sanitized, err := req.sanitizeEmails(emails)
if err != nil {
return errorResponseWithSessionState(single(accountId), err, sessionState)
return req.error(accountId, err)
}
return etagResponse(single(accountId), sanitized, sessionState, EmailResponseObjectType, state, lang)
return req.respond(accountId, sanitized, sessionState, EmailResponseObjectType, state)
}
}
})
@@ -259,29 +256,29 @@ 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(single(accountId), err)
return req.error(accountId, err)
}
l := req.logger.With().Str(logAccountId, log.SafeString(accountId))
id, err := req.PathParam(UriParamEmailId)
if err != nil {
return errorResponse(single(accountId), err)
return req.error(accountId, err)
}
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(single(accountId), jerr)
return req.jmapError(accountId, jerr, sessionState, lang)
}
if len(emails) < 1 {
return notFoundResponse(single(accountId), sessionState)
return req.notFound(accountId, sessionState, EmailResponseObjectType, state)
}
email, err := req.sanitizeEmail(emails[0])
if err != nil {
return errorResponseWithSessionState(single(accountId), err, sessionState)
return req.error(accountId, err)
}
var body []jmap.EmailBodyPart = email.Attachments
return etagResponse(single(accountId), body, sessionState, EmailResponseObjectType, state, lang)
return req.respond(accountId, body, sessionState, EmailResponseObjectType, state)
})
} else {
g.stream(w, r, func(req Request, w http.ResponseWriter) *Error {
@@ -377,13 +374,13 @@ func (g *Groupware) getEmailsSince(w http.ResponseWriter, r *http.Request, since
accountId, err := req.GetAccountIdForMail()
if err != nil {
return errorResponse(single(accountId), err)
return req.error(accountId, err)
}
l = l.Str(logAccountId, log.SafeString(accountId))
maxChanges, ok, err := req.parseUIntParam(QueryParamMaxChanges, 0)
if err != nil {
return errorResponse(single(accountId), err)
return req.error(accountId, err)
}
if ok {
l = l.Uint(QueryParamMaxChanges, maxChanges)
@@ -391,12 +388,12 @@ func (g *Groupware) getEmailsSince(w http.ResponseWriter, r *http.Request, since
logger := log.From(l)
changes, sessionState, state, lang, jerr := g.jmap.GetEmailsSince(accountId, req.session, req.ctx, logger, req.language(), since, true, g.config.maxBodyValueBytes, maxChanges)
changes, sessionState, state, lang, jerr := g.jmap.GetEmailChanges(accountId, req.session, req.ctx, logger, req.language(), since, true, g.config.maxBodyValueBytes, maxChanges)
if jerr != nil {
return req.errorResponseFromJmap(single(accountId), jerr)
return req.jmapError(accountId, jerr, sessionState, lang)
}
return etagResponse(single(accountId), changes, sessionState, EmailResponseObjectType, state, lang)
return req.respond(accountId, changes, sessionState, EmailResponseObjectType, state)
})
}
@@ -604,12 +601,12 @@ func (g *Groupware) GetEmails(w http.ResponseWriter, r *http.Request) {
g.respond(w, r, func(req Request) Response {
accountId, err := req.GetAccountIdForMail()
if err != nil {
return errorResponse(single(accountId), err)
return req.error(accountId, err)
}
ok, filter, makesSnippets, offset, limit, logger, err := g.buildEmailFilter(req)
if !ok {
return errorResponse(single(accountId), err)
return req.error(accountId, err)
}
logger = log.From(req.logger.With().Str(logAccountId, log.SafeString(accountId)))
@@ -621,7 +618,7 @@ func (g *Groupware) GetEmails(w http.ResponseWriter, r *http.Request) {
resultsByAccount, sessionState, state, lang, jerr := g.jmap.QueryEmailsWithSnippets(single(accountId), filter, req.session, req.ctx, logger, req.language(), offset, limit, fetchBodies, g.config.maxBodyValueBytes)
if jerr != nil {
return req.errorResponseFromJmap(single(accountId), jerr)
return req.jmapError(accountId, jerr, sessionState, lang)
}
if results, ok := resultsByAccount[accountId]; ok {
@@ -641,7 +638,7 @@ func (g *Groupware) GetEmails(w http.ResponseWriter, r *http.Request) {
}
sanitized, err := req.sanitizeEmail(result.Email)
if err != nil {
return errorResponseWithSessionState(single(accountId), err, sessionState)
return req.error(accountId, err)
}
flattened[i] = EmailWithSnippets{
Email: sanitized,
@@ -649,14 +646,14 @@ func (g *Groupware) GetEmails(w http.ResponseWriter, r *http.Request) {
}
}
return etagResponse(single(accountId), EmailWithSnippetsSearchResults{
return req.respond(accountId, EmailWithSnippetsSearchResults{
Results: flattened,
Total: results.Total,
Limit: results.Limit,
QueryState: results.QueryState,
}, sessionState, EmailResponseObjectType, state, lang)
}, sessionState, EmailResponseObjectType, state)
} else {
return notFoundResponse(single(accountId), sessionState)
return req.notFound(accountId, sessionState, EmailResponseObjectType, state)
}
})
}
@@ -668,7 +665,7 @@ func (g *Groupware) GetEmailsForAllAccounts(w http.ResponseWriter, r *http.Reque
ok, filter, makesSnippets, offset, limit, logger, err := g.buildEmailFilter(req)
if !ok {
return errorResponse(allAccountIds, err)
return req.errorN(allAccountIds, err)
}
logger = log.From(req.logger.With().Array(logAccountId, log.SafeStringArray(allAccountIds)))
@@ -679,7 +676,7 @@ func (g *Groupware) GetEmailsForAllAccounts(w http.ResponseWriter, r *http.Reque
if makesSnippets {
resultsByAccountId, sessionState, state, lang, jerr := g.jmap.QueryEmailSnippets(allAccountIds, filter, req.session, req.ctx, logger, req.language(), offset, limit)
if jerr != nil {
return req.errorResponseFromJmap(allAccountIds, jerr)
return req.jmapErrorN(allAccountIds, jerr, sessionState, lang)
}
var totalOverAllAccounts uint = 0
@@ -713,13 +710,13 @@ func (g *Groupware) GetEmailsForAllAccounts(w http.ResponseWriter, r *http.Reque
QueryState: state,
}
return etagResponse(allAccountIds, body, sessionState, EmailResponseObjectType, state, lang)
return req.respondN(allAccountIds, body, sessionState, EmailResponseObjectType, state)
} else {
withThreads := true
resultsByAccountId, sessionState, state, lang, jerr := g.jmap.QueryEmailSummaries(allAccountIds, req.session, req.ctx, logger, req.language(), filter, limit, withThreads)
if jerr != nil {
return req.errorResponseFromJmap(allAccountIds, jerr)
return req.jmapErrorN(allAccountIds, jerr, sessionState, lang)
}
var totalAcrossAllAccounts uint = 0
@@ -752,7 +749,7 @@ func (g *Groupware) GetEmailsForAllAccounts(w http.ResponseWriter, r *http.Reque
QueryState: state,
}
return etagResponse(allAccountIds, body, sessionState, EmailResponseObjectType, state, lang)
return req.respondN(allAccountIds, body, sessionState, EmailResponseObjectType, state)
}
})
}
@@ -763,9 +760,9 @@ var draftEmailAutoMailboxRolePrecedence = []string{
}
func findDraftsMailboxId(j *jmap.Client, accountId string, req Request, logger *log.Logger) (string, Response) {
mailboxIdsPerAccountIds, _, _, _, jerr := j.SearchMailboxIdsPerRole(single(accountId), req.session, req.ctx, logger, req.language(), draftEmailAutoMailboxRolePrecedence)
mailboxIdsPerAccountIds, sessionState, _, lang, jerr := j.SearchMailboxIdsPerRole(single(accountId), req.session, req.ctx, logger, req.language(), draftEmailAutoMailboxRolePrecedence)
if jerr != nil {
return "", req.errorResponseFromJmap(single(accountId), jerr)
return "", req.jmapError(accountId, jerr, sessionState, lang)
} else {
for _, role := range draftEmailAutoMailboxRolePrecedence {
if mailboxId, ok := mailboxIdsPerAccountIds[accountId][role]; ok {
@@ -774,7 +771,7 @@ func findDraftsMailboxId(j *jmap.Client, accountId string, req Request, logger *
}
// couldn't find a Mailbox with the drafts role for that account,
// we have to return an error... ?
return "", errorResponse(single(accountId), apiError(req.errorId(), ErrorNoMailboxWithDraftRole))
return "", req.error(accountId, apiError(req.errorId(), ErrorNoMailboxWithDraftRole))
}
}
@@ -786,9 +783,9 @@ var sentEmailAutoMailboxRolePrecedence = []string{
var draftAndSentMailboxRoles = structs.Uniq(structs.Concat(draftEmailAutoMailboxRolePrecedence, sentEmailAutoMailboxRolePrecedence))
func findSentMailboxId(j *jmap.Client, accountId string, req Request, logger *log.Logger) (string, string, Response) {
mailboxIdsPerAccountIds, _, _, _, jerr := j.SearchMailboxIdsPerRole(single(accountId), req.session, req.ctx, logger, req.language(), draftAndSentMailboxRoles)
mailboxIdsPerAccountIds, sessionState, _, lang, jerr := j.SearchMailboxIdsPerRole(single(accountId), req.session, req.ctx, logger, req.language(), draftAndSentMailboxRoles)
if jerr != nil {
return "", "", req.errorResponseFromJmap(single(accountId), jerr)
return "", "", req.jmapError(accountId, jerr, sessionState, lang)
} else {
sentMailboxId := ""
for _, role := range sentEmailAutoMailboxRolePrecedence {
@@ -798,7 +795,7 @@ func findSentMailboxId(j *jmap.Client, accountId string, req Request, logger *lo
}
}
if sentMailboxId == "" {
return "", "", errorResponse(single(accountId), apiError(req.errorId(), ErrorNoMailboxWithSentRole))
return "", "", req.error(accountId, apiError(req.errorId(), ErrorNoMailboxWithSentRole))
}
draftsMailboxId := ""
for _, role := range draftEmailAutoMailboxRolePrecedence {
@@ -808,7 +805,7 @@ func findSentMailboxId(j *jmap.Client, accountId string, req Request, logger *lo
}
}
if draftsMailboxId == "" {
return "", "", errorResponse(single(accountId), apiError(req.errorId(), ErrorNoMailboxWithDraftRole))
return "", "", req.error(accountId, apiError(req.errorId(), ErrorNoMailboxWithDraftRole))
}
return draftsMailboxId, sentMailboxId, Response{}
}
@@ -820,14 +817,14 @@ func (g *Groupware) CreateEmail(w http.ResponseWriter, r *http.Request) {
accountId, gwerr := req.GetAccountIdForMail()
if gwerr != nil {
return errorResponse(single(accountId), gwerr)
return req.error(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(single(accountId), err)
return req.error(accountId, err)
}
if len(body.MailboxIds) < 1 {
@@ -841,10 +838,10 @@ func (g *Groupware) CreateEmail(w http.ResponseWriter, r *http.Request) {
created, sessionState, state, lang, jerr := g.jmap.CreateEmail(accountId, body, "", req.session, req.ctx, logger, req.language())
if jerr != nil {
return req.errorResponseFromJmap(single(accountId), jerr)
return req.jmapError(accountId, jerr, sessionState, lang)
}
return etagResponse(single(accountId), created, sessionState, EmailResponseObjectType, state, lang)
return req.respond(accountId, created, sessionState, EmailResponseObjectType, state)
})
}
@@ -854,12 +851,12 @@ func (g *Groupware) ReplaceEmail(w http.ResponseWriter, r *http.Request) {
accountId, gwerr := req.GetAccountIdForMail()
if gwerr != nil {
return errorResponse(single(accountId), gwerr)
return req.error(accountId, gwerr)
}
replaceId, err := req.PathParam(UriParamEmailId)
if err != nil {
return errorResponse(single(accountId), err)
return req.error(accountId, err)
}
logger = log.From(logger.With().Str(logAccountId, log.SafeString(accountId)))
@@ -867,7 +864,7 @@ func (g *Groupware) ReplaceEmail(w http.ResponseWriter, r *http.Request) {
var body jmap.EmailCreate
err = req.body(&body)
if err != nil {
return errorResponse(single(accountId), err)
return req.error(accountId, err)
}
if len(body.MailboxIds) < 1 {
@@ -881,10 +878,10 @@ func (g *Groupware) ReplaceEmail(w http.ResponseWriter, r *http.Request) {
created, sessionState, state, lang, jerr := g.jmap.CreateEmail(accountId, body, replaceId, req.session, req.ctx, logger, req.language())
if jerr != nil {
return req.errorResponseFromJmap(single(accountId), jerr)
return req.jmapError(accountId, jerr, sessionState, lang)
}
return etagResponse(single(accountId), created, sessionState, EmailResponseObjectType, state, lang)
return req.respond(accountId, created, sessionState, EmailResponseObjectType, state)
})
}
@@ -894,13 +891,13 @@ func (g *Groupware) UpdateEmail(w http.ResponseWriter, r *http.Request) {
accountId, gwerr := req.GetAccountIdForMail()
if gwerr != nil {
return errorResponse(single(accountId), gwerr)
return req.error(accountId, gwerr)
}
l.Str(logAccountId, accountId)
emailId, err := req.PathParam(UriParamEmailId)
if err != nil {
return errorResponse(single(accountId), err)
return req.error(accountId, err)
}
l.Str(UriParamEmailId, log.SafeString(emailId))
@@ -909,7 +906,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(single(accountId), err)
return req.error(accountId, err)
}
updates := map[string]jmap.EmailUpdate{
@@ -918,20 +915,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(single(accountId), jerr)
return req.jmapError(accountId, jerr, sessionState, lang)
}
if result == nil {
return errorResponse(single(accountId), apiError(req.errorId(), ErrorApiInconsistency, withTitle("API Inconsistency: Missing Email Update Response",
return req.error(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(single(accountId), apiError(req.errorId(), ErrorApiInconsistency, withTitle("API Inconsistency: Wrong Email Update Response ID",
return req.error(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(single(accountId), updatedEmail, sessionState, EmailResponseObjectType, state, lang)
return req.respond(accountId, updatedEmail, sessionState, EmailResponseObjectType, state)
})
}
@@ -950,13 +947,13 @@ func (g *Groupware) UpdateEmailKeywords(w http.ResponseWriter, r *http.Request)
accountId, gwerr := req.GetAccountIdForMail()
if gwerr != nil {
return errorResponse(single(accountId), gwerr)
return req.error(accountId, gwerr)
}
l.Str(logAccountId, accountId)
emailId, err := req.PathParam(UriParamEmailId)
if err != nil {
return errorResponse(single(accountId), err)
return req.error(accountId, err)
}
l.Str(UriParamEmailId, log.SafeString(emailId))
@@ -965,11 +962,11 @@ func (g *Groupware) UpdateEmailKeywords(w http.ResponseWriter, r *http.Request)
var body emailKeywordUpdates
err = req.body(&body)
if err != nil {
return errorResponse(single(accountId), err)
return req.error(accountId, err)
}
if body.IsEmpty() {
return noContentResponse(single(accountId), req.session.State)
return req.noop(accountId)
}
patch := jmap.EmailUpdate{}
@@ -985,20 +982,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(single(accountId), jerr)
return req.jmapError(accountId, jerr, sessionState, lang)
}
if result == nil {
return errorResponse(single(accountId), apiError(req.errorId(), ErrorApiInconsistency, withTitle("API Inconsistency: Missing Email Update Response",
return req.error(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(single(accountId), apiError(req.errorId(), ErrorApiInconsistency, withTitle("API Inconsistency: Wrong Email Update Response ID",
return req.error(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(single(accountId), updatedEmail, sessionState, EmailResponseObjectType, state, lang)
return req.respond(accountId, updatedEmail, sessionState, EmailResponseObjectType, state)
})
}
@@ -1009,13 +1006,13 @@ func (g *Groupware) AddEmailKeywords(w http.ResponseWriter, r *http.Request) {
accountId, gwerr := req.GetAccountIdForMail()
if gwerr != nil {
return errorResponse(single(accountId), gwerr)
return req.error(accountId, gwerr)
}
l.Str(logAccountId, accountId)
emailId, err := req.PathParam(UriParamEmailId)
if err != nil {
return errorResponse(single(accountId), err)
return req.error(accountId, err)
}
l.Str(UriParamEmailId, log.SafeString(emailId))
@@ -1024,11 +1021,11 @@ func (g *Groupware) AddEmailKeywords(w http.ResponseWriter, r *http.Request) {
var body []string
err = req.body(&body)
if err != nil {
return errorResponse(single(accountId), err)
return req.error(accountId, err)
}
if len(body) < 1 {
return noContentResponse(single(accountId), req.session.State)
return req.noop(accountId)
}
patch := jmap.EmailUpdate{}
@@ -1041,23 +1038,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(single(accountId), jerr)
return req.jmapError(accountId, jerr, sessionState, lang)
}
if result == nil {
return errorResponse(single(accountId), apiError(req.errorId(), ErrorApiInconsistency, withTitle("API Inconsistency: Missing Email Update Response",
return req.error(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(single(accountId), apiError(req.errorId(), ErrorApiInconsistency, withTitle("API Inconsistency: Wrong Email Update Response ID",
return req.error(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(single(accountId), sessionState, EmailResponseObjectType, state)
return req.noContent(accountId, sessionState, EmailResponseObjectType, state)
} else {
return etagResponse(single(accountId), updatedEmail, sessionState, EmailResponseObjectType, state, lang)
return req.respond(accountId, updatedEmail, sessionState, EmailResponseObjectType, state)
}
})
}
@@ -1069,13 +1066,13 @@ func (g *Groupware) RemoveEmailKeywords(w http.ResponseWriter, r *http.Request)
accountId, err := req.GetAccountIdForMail()
if err != nil {
return errorResponse(single(accountId), err)
return req.error(accountId, err)
}
l.Str(logAccountId, accountId)
emailId, err := req.PathParam(UriParamEmailId)
if err != nil {
return errorResponse(single(accountId), err)
return req.error(accountId, err)
}
l.Str(UriParamEmailId, log.SafeString(emailId))
@@ -1084,11 +1081,11 @@ func (g *Groupware) RemoveEmailKeywords(w http.ResponseWriter, r *http.Request)
var body []string
err = req.body(&body)
if err != nil {
return errorResponse(single(accountId), err)
return req.error(accountId, err)
}
if len(body) < 1 {
return noContentResponse(single(accountId), req.session.State)
return req.noop(accountId)
}
patch := jmap.EmailUpdate{}
@@ -1101,23 +1098,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(single(accountId), jerr)
return req.jmapError(accountId, jerr, sessionState, lang)
}
if result == nil {
return errorResponse(single(accountId), apiError(req.errorId(), ErrorApiInconsistency, withTitle("API Inconsistency: Missing Email Update Response",
return req.error(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(single(accountId), apiError(req.errorId(), ErrorApiInconsistency, withTitle("API Inconsistency: Wrong Email Update Response ID",
return req.error(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(single(accountId), sessionState, EmailResponseObjectType, state)
return req.noContent(accountId, sessionState, EmailResponseObjectType, state)
} else {
return etagResponse(single(accountId), updatedEmail, sessionState, EmailResponseObjectType, state, lang)
return req.respond(accountId, updatedEmail, sessionState, EmailResponseObjectType, state)
}
})
}
@@ -1129,39 +1126,39 @@ func (g *Groupware) DeleteEmail(w http.ResponseWriter, r *http.Request) {
accountId, gwerr := req.GetAccountIdForMail()
if gwerr != nil {
return errorResponse(single(accountId), gwerr)
return req.error(accountId, gwerr)
}
l.Str(logAccountId, log.SafeString(accountId))
emailId, err := req.PathParam(UriParamEmailId)
if err != nil {
return errorResponse(single(accountId), err)
return req.error(accountId, err)
}
l.Str(UriParamEmailId, log.SafeString(emailId))
logger := log.From(l)
resp, sessionState, state, _, jerr := g.jmap.DeleteEmails(accountId, []string{emailId}, req.session, req.ctx, logger, req.language())
resp, sessionState, state, lang, jerr := g.jmap.DeleteEmails(accountId, []string{emailId}, req.session, req.ctx, logger, req.language())
if jerr != nil {
return req.errorResponseFromJmap(single(accountId), jerr)
return req.jmapError(accountId, jerr, sessionState, lang)
}
for _, e := range resp {
desc := e.Description
if desc != "" {
return errorResponseWithSessionState(single(accountId), apiError(
return req.error(accountId, apiError(
req.errorId(),
ErrorFailedToDeleteEmail,
withDetail(e.Description),
), sessionState)
))
} else {
return errorResponseWithSessionState(single(accountId), apiError(
return req.error(accountId, apiError(
req.errorId(),
ErrorFailedToDeleteEmail,
), sessionState)
))
}
}
return noContentResponseWithEtag(single(accountId), sessionState, EmailResponseObjectType, state)
return req.noContent(accountId, sessionState, EmailResponseObjectType, state)
})
}
@@ -1176,23 +1173,23 @@ func (g *Groupware) DeleteEmails(w http.ResponseWriter, r *http.Request) {
accountId, gwerr := req.GetAccountIdForMail()
if gwerr != nil {
return errorResponse(single(accountId), gwerr)
return req.error(accountId, gwerr)
}
l.Str(logAccountId, accountId)
var emailIds []string
err := req.body(&emailIds)
if err != nil {
return errorResponse(single(accountId), err)
return req.error(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())
resp, sessionState, state, lang, jerr := g.jmap.DeleteEmails(accountId, emailIds, req.session, req.ctx, logger, req.language())
if jerr != nil {
return req.errorResponseFromJmap(single(accountId), jerr)
return req.jmapError(accountId, jerr, sessionState, lang)
}
if len(resp) > 0 {
@@ -1200,13 +1197,13 @@ func (g *Groupware) DeleteEmails(w http.ResponseWriter, r *http.Request) {
for emailId, e := range resp {
meta[emailId] = e.Description
}
return errorResponseWithSessionState(single(accountId), apiError(
return req.error(accountId, apiError(
req.errorId(),
ErrorFailedToDeleteEmail,
withMeta(meta),
), sessionState)
))
}
return noContentResponseWithEtag(single(accountId), sessionState, EmailResponseObjectType, state)
return req.noContent(accountId, sessionState, EmailResponseObjectType, state)
})
}
@@ -1216,19 +1213,19 @@ func (g *Groupware) SendEmail(w http.ResponseWriter, r *http.Request) {
accountId, gwerr := req.GetAccountIdForMail()
if gwerr != nil {
return errorResponse(single(accountId), gwerr)
return req.error(accountId, gwerr)
}
l.Str(logAccountId, accountId)
emailId, err := req.PathParam(UriParamEmailId)
if err != nil {
return errorResponse(single(accountId), err)
return req.error(accountId, err)
}
l.Str(UriParamEmailId, log.SafeString(emailId))
identityId, err := req.getMandatoryStringParam(QueryParamIdentityId)
if err != nil {
return errorResponse(single(accountId), err)
return req.error(accountId, err)
}
l.Str(QueryParamIdentityId, log.SafeString(identityId))
@@ -1261,10 +1258,10 @@ func (g *Groupware) SendEmail(w http.ResponseWriter, r *http.Request) {
resp, sessionState, state, lang, jerr := g.jmap.SubmitEmail(accountId, identityId, emailId, move, req.session, req.ctx, logger, req.language())
if jerr != nil {
return req.errorResponseFromJmap(single(accountId), jerr)
return req.jmapError(accountId, jerr, sessionState, lang)
}
return etagResponse(single(accountId), resp, sessionState, EmailResponseObjectType, state, lang)
return req.respond(accountId, resp, sessionState, EmailResponseObjectType, state)
})
}
@@ -1330,21 +1327,21 @@ func (g *Groupware) RelatedToEmail(w http.ResponseWriter, r *http.Request) {
g.respond(w, r, func(req Request) Response {
l := req.logger.With()
accountId, gwerr := req.GetAccountIdForMail()
if gwerr != nil {
return errorResponse(single(accountId), gwerr)
accountId, err := req.GetAccountIdForMail()
if err != nil {
return req.error(accountId, err)
}
l = l.Str(logAccountId, log.SafeString(accountId))
id, err := req.PathParam(UriParamEmailId)
if err != nil {
return errorResponse(single(accountId), err)
return req.error(accountId, err)
}
l = l.Str(logEmailId, log.SafeString(id))
limit, ok, err := req.parseUIntParam(QueryParamLimit, 10) // TODO configurable default limit
if err != nil {
return errorResponse(single(accountId), err)
return req.error(accountId, err)
}
if ok {
l = l.Uint("limit", limit)
@@ -1352,7 +1349,7 @@ func (g *Groupware) RelatedToEmail(w http.ResponseWriter, r *http.Request) {
days, ok, err := req.parseUIntParam(QueryParamDays, 5) // TODO configurable default days
if err != nil {
return errorResponse(single(accountId), err)
return req.error(accountId, err)
}
if ok {
l = l.Uint("days", days)
@@ -1365,13 +1362,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.config.maxBodyValueBytes, false, false)
getEmailsDuration := time.Since(getEmailsBefore)
if jerr != nil {
return req.errorResponseFromJmap(single(accountId), jerr)
return req.jmapError(accountId, jerr, sessionState, lang)
}
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(single(accountId), sessionState)
return req.notFound(accountId, sessionState, EmailResponseObjectType, state)
} else {
req.observe(g.metrics.EmailByIdDuration.WithLabelValues(req.session.JmapEndpoint, metrics.Values.Result.Found), getEmailsDuration.Seconds())
}
@@ -1408,7 +1405,7 @@ func (g *Groupware) RelatedToEmail(w http.ResponseWriter, r *http.Request) {
g.job(logger, RelationTypeSameThread, func(jobId uint64, l *log.Logger) {
before := time.Now()
emails, _, _, _, jerr := g.jmap.EmailsInThread(accountId, email.ThreadId, req.session, bgctx, l, req.language(), false, g.config.maxBodyValueBytes)
emails, _, _, lang, jerr := g.jmap.EmailsInThread(accountId, email.ThreadId, req.session, bgctx, l, req.language(), false, g.config.maxBodyValueBytes)
duration := time.Since(before)
if jerr != nil {
_ = req.observeJmapError(jerr)
@@ -1427,12 +1424,12 @@ func (g *Groupware) RelatedToEmail(w http.ResponseWriter, r *http.Request) {
sanitized, err := req.sanitizeEmail(email)
if err != nil {
return errorResponseWithSessionState(single(accountId), err, sessionState)
return req.error(accountId, err)
}
return etagResponse(single(accountId), AboutEmailResponse{
return req.respond(accountId, AboutEmailResponse{
Email: sanitized,
RequestId: reqId,
}, sessionState, EmailResponseObjectType, state, lang)
}, sessionState, EmailResponseObjectType, state)
})
}
@@ -1630,7 +1627,7 @@ func (g *Groupware) GetLatestEmailsSummaryForAllAccounts(w http.ResponseWriter,
limit, ok, err := req.parseUIntParam(QueryParamLimit, 10) // TODO from configuration
if err != nil {
return errorResponse(allAccountIds, err)
return req.errorN(allAccountIds, err)
}
if ok {
l = l.Uint(QueryParamLimit, limit)
@@ -1638,10 +1635,10 @@ func (g *Groupware) GetLatestEmailsSummaryForAllAccounts(w http.ResponseWriter,
offset, ok, err := req.parseUIntParam(QueryParamOffset, 0)
if err != nil {
return errorResponse(allAccountIds, err)
return req.errorN(allAccountIds, err)
}
if offset > 0 {
return notImplementedResponse()
return req.notImplementedN(allAccountIds, EmailResponseObjectType)
}
if ok {
l = l.Uint(QueryParamOffset, limit)
@@ -1649,7 +1646,7 @@ func (g *Groupware) GetLatestEmailsSummaryForAllAccounts(w http.ResponseWriter,
seen, ok, err := req.parseBoolParam(QueryParamSeen, false)
if err != nil {
return errorResponse(allAccountIds, err)
return req.errorN(allAccountIds, err)
}
if ok {
l = l.Bool(QueryParamSeen, seen)
@@ -1657,7 +1654,7 @@ func (g *Groupware) GetLatestEmailsSummaryForAllAccounts(w http.ResponseWriter,
undesirable, ok, err := req.parseBoolParam(QueryParamUndesirable, false)
if err != nil {
return errorResponse(allAccountIds, err)
return req.errorN(allAccountIds, err)
}
if ok {
l = l.Bool(QueryParamUndesirable, undesirable)
@@ -1679,7 +1676,7 @@ func (g *Groupware) GetLatestEmailsSummaryForAllAccounts(w http.ResponseWriter,
emailsSummariesByAccount, sessionState, state, lang, jerr := g.jmap.QueryEmailSummaries(allAccountIds, req.session, req.ctx, logger, req.language(), filter, limit, true)
if jerr != nil {
return req.errorResponseFromJmap(allAccountIds, jerr)
return req.jmapErrorN(allAccountIds, jerr, sessionState, lang)
}
// sort in memory to respect the overall limit
@@ -1703,12 +1700,12 @@ func (g *Groupware) GetLatestEmailsSummaryForAllAccounts(w http.ResponseWriter,
summaries[i] = summarizeEmail(all[i].accountId, all[i].email)
}
return etagResponse(allAccountIds, EmailSummaries{
return req.respondN(allAccountIds, EmailSummaries{
Emails: summaries,
Total: total,
Limit: limit,
Offset: offset,
}, sessionState, EmailResponseObjectType, state, lang)
}, sessionState, EmailResponseObjectType, state)
})
}

View File

@@ -15,14 +15,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(single(accountId), err)
return req.error(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(single(accountId), jerr)
return req.jmapError(accountId, jerr, sessionState, lang)
}
return etagResponse(single(accountId), res, sessionState, IdentityResponseObjectType, state, lang)
return req.respond(accountId, res, sessionState, IdentityResponseObjectType, state)
})
}
@@ -30,22 +30,22 @@ 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(single(accountId), err)
return req.error(accountId, err)
}
id, err := req.PathParam(UriParamIdentityId)
if err != nil {
return errorResponse(single(accountId), err)
return req.error(accountId, err)
}
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(single(accountId), jerr)
return req.jmapError(accountId, jerr, sessionState, lang)
}
if len(res) < 1 {
return notFoundResponse(single(accountId), sessionState)
return req.notFound(accountId, sessionState, IdentityResponseObjectType, state)
}
var body jmap.Identity = res[0]
return etagResponse(single(accountId), body, sessionState, IdentityResponseObjectType, state, lang)
return req.respond(accountId, body, sessionState, IdentityResponseObjectType, state)
})
}
@@ -53,21 +53,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(single(accountId), err)
return req.error(accountId, err)
}
logger := log.From(req.logger.With().Str(logAccountId, accountId))
var identity jmap.Identity
err = req.body(&identity)
if err != nil {
return errorResponse(single(accountId), err)
return req.error(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(single(accountId), jerr)
return req.jmapError(accountId, jerr, sessionState, lang)
}
return etagResponse(single(accountId), created, sessionState, IdentityResponseObjectType, state, lang)
return req.respond(accountId, created, sessionState, IdentityResponseObjectType, state)
})
}
@@ -75,21 +75,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(single(accountId), err)
return req.error(accountId, err)
}
logger := log.From(req.logger.With().Str(logAccountId, accountId))
var identity jmap.Identity
err = req.body(&identity)
if err != nil {
return errorResponse(single(accountId), err)
return req.error(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(single(accountId), jerr)
return req.jmapError(accountId, jerr, sessionState, lang)
}
return etagResponse(single(accountId), updated, sessionState, IdentityResponseObjectType, state, lang)
return req.respond(accountId, updated, sessionState, IdentityResponseObjectType, state)
})
}
@@ -98,30 +98,30 @@ 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(single(accountId), err)
return req.error(accountId, err)
}
logger := log.From(req.logger.With().Str(logAccountId, accountId))
id, err := req.PathParam(UriParamIdentityId)
if err != nil {
return errorResponse(single(accountId), err)
return req.error(accountId, err)
}
ids := strings.Split(id, ",")
if len(ids) < 1 {
return req.parameterErrorResponse(single(accountId), UriParamIdentityId, 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)
deletion, sessionState, state, lang, jerr := g.jmap.DeleteIdentity(accountId, req.session, req.ctx, logger, req.language(), ids)
if jerr != nil {
return req.errorResponseFromJmap(single(accountId), jerr)
return req.jmapError(accountId, jerr, sessionState, lang)
}
notDeletedIds := structs.Missing(ids, deletion)
if len(notDeletedIds) == 0 {
return noContentResponseWithEtag(single(accountId), sessionState, IdentityResponseObjectType, state)
return req.noContent(accountId, sessionState, IdentityResponseObjectType, state)
} else {
logger.Error().Array("not-deleted", log.SafeStringArray(notDeletedIds)).Msgf("failed to delete %d identities", len(notDeletedIds))
return errorResponseWithSessionState(single(accountId), req.apiError(&ErrorFailedToDeleteSomeIdentities,
return req.errorS(accountId, req.apiError(&ErrorFailedToDeleteSomeIdentities,
withMeta(map[string]any{"ids": notDeletedIds})), sessionState)
}
})

View File

@@ -150,7 +150,7 @@ func (g *Groupware) Index(w http.ResponseWriter, r *http.Request) {
boot, sessionState, state, lang, err := g.jmap.GetBootstrap(accountIds, req.session, req.ctx, req.logger, req.language())
if err != nil {
return req.errorResponseFromJmap(accountIds, err)
return req.jmapErrorN(accountIds, err, sessionState, lang)
}
var body IndexResponse = IndexResponse{
@@ -160,7 +160,7 @@ func (g *Groupware) Index(w http.ResponseWriter, r *http.Request) {
Accounts: buildIndexAccounts(req.session, boot),
PrimaryAccounts: buildIndexPrimaryAccounts(req.session),
}
return etagResponse(accountIds, body, sessionState, IndexResponseObjectType, state, lang)
return req.respondN(accountIds, body, sessionState, IndexResponseObjectType, state)
})
}

View File

@@ -21,23 +21,23 @@ 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(single(accountId), err)
return req.error(accountId, err)
}
mailboxId, err := req.PathParam(UriParamMailboxId)
if err != nil {
return errorResponse(single(accountId), err)
return req.error(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(single(accountId), jerr)
return req.jmapError(accountId, jerr, sessionState, lang)
}
if len(mailboxes.Mailboxes) == 1 {
return etagResponse(single(accountId), mailboxes.Mailboxes[0], sessionState, MailboxResponseObjectType, state, lang)
return req.respond(accountId, mailboxes.Mailboxes[0], sessionState, MailboxResponseObjectType, state)
} else {
return notFoundResponse(single(accountId), sessionState)
return req.notFound(accountId, sessionState, MailboxResponseObjectType, state)
}
})
}
@@ -69,12 +69,12 @@ func (g *Groupware) GetMailboxes(w http.ResponseWriter, r *http.Request) {
accountId, err := req.GetAccountIdForMail()
if err != nil {
return errorResponse(single(accountId), err)
return req.error(accountId, err)
}
subscribed, set, err := req.parseBoolParam(QueryParamMailboxSearchSubscribed, false)
if err != nil {
return errorResponse(single(accountId), err)
return req.error(accountId, err)
}
if set {
filter.IsSubscribed = &subscribed
@@ -86,23 +86,23 @@ func (g *Groupware) GetMailboxes(w http.ResponseWriter, r *http.Request) {
if hasCriteria {
mailboxesByAccountId, sessionState, state, lang, err := g.jmap.SearchMailboxes(single(accountId), req.session, req.ctx, logger, req.language(), filter)
if err != nil {
return req.errorResponseFromJmap(single(accountId), err)
return req.jmapError(accountId, err, sessionState, lang)
}
if mailboxes, ok := mailboxesByAccountId[accountId]; ok {
return etagResponse(single(accountId), sortMailboxSlice(mailboxes), sessionState, MailboxResponseObjectType, state, lang)
return req.respond(accountId, sortMailboxSlice(mailboxes), sessionState, MailboxResponseObjectType, state)
} else {
return notFoundResponse(single(accountId), sessionState)
return req.notFound(accountId, sessionState, MailboxResponseObjectType, state)
}
} else {
mailboxesByAccountId, sessionState, state, lang, err := g.jmap.GetAllMailboxes(single(accountId), req.session, req.ctx, logger, req.language())
if err != nil {
return req.errorResponseFromJmap(single(accountId), err)
return req.jmapError(accountId, err, sessionState, lang)
}
if mailboxes, ok := mailboxesByAccountId[accountId]; ok {
return etagResponse(single(accountId), sortMailboxSlice(mailboxes), sessionState, MailboxResponseObjectType, state, lang)
return req.respond(accountId, sortMailboxSlice(mailboxes), sessionState, MailboxResponseObjectType, state)
} else {
return notFoundResponse(single(accountId), sessionState)
return req.notFound(accountId, sessionState, MailboxResponseObjectType, state)
}
}
})
@@ -113,7 +113,7 @@ 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(nil, "") // when the user has no accounts
return req.noopN(nil) // when the user has no accounts
}
logger := log.From(req.logger.With().Array(logAccountId, log.SafeStringArray(accountIds)))
@@ -126,7 +126,7 @@ func (g *Groupware) GetMailboxesForAllAccounts(w http.ResponseWriter, r *http.Re
}
if subscribed, set, err := req.parseBoolParam(QueryParamMailboxSearchSubscribed, false); err != nil {
return errorResponse(accountIds, err)
return req.errorN(accountIds, err)
} else if set {
filter.IsSubscribed = &subscribed
hasCriteria = true
@@ -135,15 +135,15 @@ func (g *Groupware) GetMailboxesForAllAccounts(w http.ResponseWriter, r *http.Re
if hasCriteria {
mailboxesByAccountId, sessionState, state, lang, err := g.jmap.SearchMailboxes(accountIds, req.session, req.ctx, logger, req.language(), filter)
if err != nil {
return req.errorResponseFromJmap(accountIds, err)
return req.jmapErrorN(accountIds, err, sessionState, lang)
}
return etagResponse(accountIds, sortMailboxesMap(mailboxesByAccountId), sessionState, MailboxResponseObjectType, state, lang)
return req.respondN(accountIds, sortMailboxesMap(mailboxesByAccountId), sessionState, MailboxResponseObjectType, state)
} else {
mailboxesByAccountId, sessionState, state, lang, err := g.jmap.GetAllMailboxes(accountIds, req.session, req.ctx, logger, req.language())
if err != nil {
return req.errorResponseFromJmap(accountIds, err)
return req.jmapErrorN(accountIds, err, sessionState, lang)
}
return etagResponse(accountIds, sortMailboxesMap(mailboxesByAccountId), sessionState, MailboxResponseObjectType, state, lang)
return req.respondN(accountIds, sortMailboxesMap(mailboxesByAccountId), sessionState, MailboxResponseObjectType, state)
}
})
}
@@ -153,12 +153,12 @@ func (g *Groupware) GetMailboxByRoleForAllAccounts(w http.ResponseWriter, r *htt
g.respond(w, r, func(req Request) Response {
accountIds := req.AllAccountIds()
if len(accountIds) < 1 {
return noContentResponse(nil, "") // when the user has no accounts
return req.noopN(accountIds) // when the user has no accounts
}
role, err := req.PathParamDoc(UriParamRole, "Role of the mailboxes to retrieve across all accounts")
if err != nil {
return errorResponse(accountIds, err)
return req.errorN(accountIds, err)
}
logger := log.From(req.logger.With().Array(logAccountId, log.SafeStringArray(accountIds)).Str("role", role))
@@ -169,50 +169,58 @@ func (g *Groupware) GetMailboxByRoleForAllAccounts(w http.ResponseWriter, r *htt
mailboxesByAccountId, sessionState, state, lang, jerr := g.jmap.SearchMailboxes(accountIds, req.session, req.ctx, logger, req.language(), filter)
if jerr != nil {
return req.errorResponseFromJmap(accountIds, jerr)
return req.jmapErrorN(accountIds, jerr, sessionState, lang)
}
return etagResponse(accountIds, sortMailboxesMap(mailboxesByAccountId), sessionState, MailboxResponseObjectType, state, lang)
return req.respondN(accountIds, sortMailboxesMap(mailboxesByAccountId), sessionState, MailboxResponseObjectType, state)
})
}
// Get the changes that occured in a given mailbox since a certain state.
// @api:tags mailbox,changes
func (g *Groupware) GetMailboxChanges(w http.ResponseWriter, r *http.Request) {
g.respond(w, r, func(req Request) Response {
l := req.logger.With()
accountId, err := req.GetAccountIdForMail()
if err != nil {
return errorResponse(single(accountId), err)
return req.error(accountId, err)
}
l = l.Str(logAccountId, accountId)
mailboxId, err := req.PathParam(UriParamMailboxId)
if err != nil {
return errorResponse(single(accountId), err)
}
maxChanges, ok, err := req.parseUIntParam(QueryParamMaxChanges, 0)
if err != nil {
return errorResponse(single(accountId), err)
return req.error(accountId, err)
}
if ok {
l = l.Uint(QueryParamMaxChanges, maxChanges)
}
sinceState, err := req.HeaderParamDoc(HeaderParamSince, "Specifies the state identifier from which on to list mailbox changes")
if err != nil {
return errorResponse(single(accountId), err)
sinceState := req.OptHeaderParamDoc(HeaderParamSince, "Optionally specifies the state identifier from which on to list mailbox changes")
if sinceState != "" {
l = l.Str(HeaderParamSince, log.SafeString(sinceState))
}
l = l.Str(HeaderParamSince, log.SafeString(sinceState))
logger := log.From(l)
changes, sessionState, state, lang, jerr := g.jmap.GetMailboxChanges(accountId, req.session, req.ctx, logger, req.language(), mailboxId, sinceState, true, g.config.maxBodyValueBytes, maxChanges)
if jerr != nil {
return req.errorResponseFromJmap(single(accountId), jerr)
// As for Emails and Contacts, one would expect this request without any prior state to
// be usable to list all the objects that currently exist, but that is not the case for
// Mailbox, at least in combination with Stalwart, as those are initial objects that
// "always existed", even with the initial State, and this the Mailbox/changes operation
// returns nothing.
// For this reason, when the "since" state is empty, we respond with an error.
if sinceState == "" {
return req.error(accountId, req.apiError(&ErrorInvalidUserRequest, withTitle(
"Mailbox changes without state is unsupported",
"Requesting Mailbox changes without an initial state is an unsupported operation",
)))
}
return etagResponse(single(accountId), changes, sessionState, MailboxResponseObjectType, state, lang)
changes, sessionState, state, lang, jerr := g.jmap.GetMailboxChanges(accountId, req.session, req.ctx, logger, req.language(), sinceState, maxChanges)
if jerr != nil {
return req.jmapError(accountId, jerr, sessionState, lang)
}
return req.respond(accountId, changes, sessionState, MailboxResponseObjectType, state)
})
}
@@ -226,7 +234,7 @@ func (g *Groupware) GetMailboxChangesForAllAccounts(w http.ResponseWriter, r *ht
sinceStateMap, ok, err := req.parseMapParam(QueryParamSince)
if err != nil {
return errorResponse(allAccountIds, err)
return req.errorN(allAccountIds, err)
}
if ok {
dict := zerolog.Dict()
@@ -238,7 +246,7 @@ func (g *Groupware) GetMailboxChangesForAllAccounts(w http.ResponseWriter, r *ht
maxChanges, ok, err := req.parseUIntParam(QueryParamMaxChanges, 0)
if err != nil {
return errorResponse(allAccountIds, err)
return req.errorN(allAccountIds, err)
}
if ok {
l = l.Uint(QueryParamMaxChanges, maxChanges)
@@ -246,12 +254,12 @@ func (g *Groupware) GetMailboxChangesForAllAccounts(w http.ResponseWriter, r *ht
logger := log.From(l)
changesByAccountId, sessionState, state, lang, jerr := g.jmap.GetMailboxChangesForMultipleAccounts(allAccountIds, req.session, req.ctx, logger, req.language(), sinceStateMap, true, g.config.maxBodyValueBytes, maxChanges)
changesByAccountId, sessionState, state, lang, jerr := g.jmap.GetMailboxChangesForMultipleAccounts(allAccountIds, req.session, req.ctx, logger, req.language(), sinceStateMap, maxChanges)
if jerr != nil {
return req.errorResponseFromJmap(allAccountIds, jerr)
return req.jmapErrorN(allAccountIds, jerr, sessionState, lang)
}
return etagResponse(allAccountIds, changesByAccountId, sessionState, MailboxResponseObjectType, state, lang)
return req.respondN(allAccountIds, changesByAccountId, sessionState, MailboxResponseObjectType, state)
})
}
@@ -266,10 +274,10 @@ func (g *Groupware) GetMailboxRoles(w http.ResponseWriter, r *http.Request) {
rolesByAccountId, sessionState, state, lang, jerr := g.jmap.GetMailboxRolesForMultipleAccounts(allAccountIds, req.session, req.ctx, logger, req.language())
if jerr != nil {
return req.errorResponseFromJmap(allAccountIds, jerr)
return req.jmapErrorN(allAccountIds, jerr, sessionState, lang)
}
return etagResponse(allAccountIds, rolesByAccountId, sessionState, MailboxResponseObjectType, state, lang)
return req.respondN(allAccountIds, rolesByAccountId, sessionState, MailboxResponseObjectType, state)
})
}
@@ -279,29 +287,29 @@ func (g *Groupware) UpdateMailbox(w http.ResponseWriter, r *http.Request) {
accountId, err := req.GetAccountIdForMail()
if err != nil {
return errorResponse(single(accountId), err)
return req.error(accountId, err)
}
l = l.Str(logAccountId, accountId)
mailboxId, err := req.PathParamDoc(UriParamMailboxId, "the identifier of the mailbox to update")
if err != nil {
return errorResponse(single(accountId), err)
return req.error(accountId, err)
}
l = l.Str(UriParamMailboxId, log.SafeString(mailboxId))
var body jmap.MailboxChange
err = req.body(&body)
if err != nil {
return errorResponse(single(accountId), err)
return req.error(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(single(accountId), jerr)
return req.jmapError(accountId, jerr, sessionState, lang)
}
return etagResponse(single(accountId), updated, sessionState, MailboxResponseObjectType, state, lang)
return req.respond(accountId, updated, sessionState, MailboxResponseObjectType, state)
})
}
@@ -310,23 +318,23 @@ func (g *Groupware) CreateMailbox(w http.ResponseWriter, r *http.Request) {
l := req.logger.With()
accountId, err := req.GetAccountIdForMail()
if err != nil {
return errorResponse(single(accountId), err)
return req.error(accountId, err)
}
l = l.Str(logAccountId, accountId)
var body jmap.MailboxChange
err = req.body(&body)
if err != nil {
return errorResponse(single(accountId), err)
return req.error(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(single(accountId), jerr)
return req.jmapError(accountId, jerr, sessionState, lang)
}
return etagResponse(single(accountId), created, sessionState, MailboxResponseObjectType, state, lang)
return req.respond(accountId, created, sessionState, MailboxResponseObjectType, state)
})
}
@@ -340,28 +348,28 @@ func (g *Groupware) DeleteMailbox(w http.ResponseWriter, r *http.Request) {
l := req.logger.With()
accountId, err := req.GetAccountIdForMail()
if err != nil {
return errorResponse(single(accountId), err)
return req.error(accountId, err)
}
l = l.Str(logAccountId, accountId)
mailboxIds, err := req.PathListParamDoc(UriParamMailboxId, "the identifier of the mailbox to delete")
if err != nil {
return errorResponse(single(accountId), err)
return req.error(accountId, err)
}
l = l.Array(UriParamMailboxId, log.SafeStringArray(mailboxIds))
if len(mailboxIds) < 1 {
return noContentResponse(single(accountId), req.session.State) // no mailbox identifiers were mentioned in the request
return req.noop(accountId) // no mailbox identifiers were mentioned in the request
}
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(single(accountId), jerr)
return req.jmapError(accountId, jerr, sessionState, lang)
}
return etagResponse(single(accountId), deleted, sessionState, MailboxResponseObjectType, state, lang)
return req.respond(accountId, deleted, sessionState, MailboxResponseObjectType, state)
})
}

View File

@@ -16,19 +16,19 @@ 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(single(accountId), err)
return req.error(accountId, err)
}
logger := log.From(req.logger.With().Str(logAccountId, accountId))
res, sessionState, state, lang, jerr := g.jmap.GetQuotas(single(accountId), req.session, req.ctx, logger, req.language())
if jerr != nil {
return req.errorResponseFromJmap(single(accountId), jerr)
return req.jmapError(accountId, jerr, sessionState, lang)
}
for _, v := range res {
body := v.List
return etagResponse(single(accountId), body, sessionState, QuotaResponseObjectType, state, lang)
return req.respond(accountId, body, sessionState, QuotaResponseObjectType, state)
}
return notFoundResponse(single(accountId), sessionState)
return req.notFound(accountId, sessionState, QuotaResponseObjectType, state)
})
}
@@ -45,13 +45,13 @@ func (g *Groupware) GetQuotaForAllAccounts(w http.ResponseWriter, r *http.Reques
g.respond(w, r, func(req Request) Response {
accountIds := req.AllAccountIds()
if len(accountIds) < 1 {
return noContentResponse(accountIds, "") // user has no accounts
return req.noopN(accountIds) // user has no accounts
}
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(accountIds, jerr)
return req.jmapErrorN(accountIds, jerr, sessionState, lang)
}
result := make(map[string]AccountQuota, len(res))
@@ -61,6 +61,6 @@ func (g *Groupware) GetQuotaForAllAccounts(w http.ResponseWriter, r *http.Reques
Quotas: accountQuotas.List,
}
}
return etagResponse(accountIds, result, sessionState, QuotaResponseObjectType, state, lang)
return req.respondN(accountIds, result, sessionState, QuotaResponseObjectType, state)
})
}

View File

@@ -16,7 +16,7 @@ func (g *Groupware) GetTaskLists(w http.ResponseWriter, r *http.Request) {
var _ string = accountId
var body []jmap.TaskList = AllTaskLists
return etagResponse(single(accountId), body, req.session.State, TaskListResponseObjectType, TaskListsState, "")
return req.respond(accountId, body, req.session.State, TaskListResponseObjectType, TaskListsState)
})
}
@@ -31,15 +31,15 @@ func (g *Groupware) GetTaskListById(w http.ResponseWriter, r *http.Request) {
tasklistId, err := req.PathParam(UriParamTaskListId)
if err != nil {
return errorResponse(single(accountId), err)
return req.error(accountId, err)
}
// TODO replace with proper implementation
for _, tasklist := range AllTaskLists {
if tasklist.Id == tasklistId {
return response(single(accountId), tasklist, req.session.State, "")
return req.respond(accountId, tasklist, req.session.State, TaskListResponseObjectType, TaskListsState)
}
}
return etagNotFoundResponse(single(accountId), req.session.State, TaskListResponseObjectType, TaskListsState, "")
return req.etaggedNotFound(accountId, req.session.State, TaskListResponseObjectType, TaskListsState)
})
}
@@ -54,13 +54,13 @@ func (g *Groupware) GetTasksInTaskList(w http.ResponseWriter, r *http.Request) {
tasklistId, err := req.PathParam(UriParamTaskListId)
if err != nil {
return errorResponse(single(accountId), err)
return req.error(accountId, err)
}
// TODO replace with proper implementation
tasks, ok := TaskMapByTaskListId[tasklistId]
if !ok {
return notFoundResponse(single(accountId), req.session.State)
return req.notFound(accountId, req.session.State, TaskResponseObjectType, TaskState)
}
return etagResponse(single(accountId), tasks, req.session.State, TaskResponseObjectType, TaskState, "")
return req.respond(accountId, tasks, req.session.State, TaskResponseObjectType, TaskState)
})
}

View File

@@ -17,15 +17,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(single(accountId), err)
return req.error(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(single(accountId), jerr)
return req.jmapError(accountId, jerr, sessionState, lang)
}
return etagResponse(single(accountId), res, sessionState, VacationResponseResponseObjectType, state, lang)
return req.respond(accountId, res, sessionState, VacationResponseResponseObjectType, state)
})
}
@@ -37,21 +37,21 @@ func (g *Groupware) SetVacation(w http.ResponseWriter, r *http.Request) {
g.respond(w, r, func(req Request) Response {
accountId, err := req.GetAccountIdForVacationResponse()
if err != nil {
return errorResponse(single(accountId), err)
return req.error(accountId, err)
}
logger := log.From(req.logger.With().Str(logAccountId, accountId))
var body jmap.VacationResponsePayload
err = req.body(&body)
if err != nil {
return errorResponse(single(accountId), err)
return req.error(accountId, err)
}
res, sessionState, state, lang, jerr := g.jmap.SetVacationResponse(accountId, body, req.session, req.ctx, logger, req.language())
if jerr != nil {
return req.errorResponseFromJmap(single(accountId), jerr)
return req.jmapError(accountId, jerr, sessionState, lang)
}
return etagResponse(single(accountId), res, sessionState, VacationResponseResponseObjectType, state, lang)
return req.respond(accountId, res, sessionState, VacationResponseResponseObjectType, state)
})
}

View File

@@ -651,6 +651,22 @@ func errorResponses(errors ...Error) ErrorResponse {
return ErrorResponse{Errors: errors}
}
func (r *Request) errorResponseFromJmap(accountIds []string, err jmap.Error) Response {
return errorResponseWithSessionState(accountIds, r.apiErrorFromJmap(r.observeJmapError(err)), r.session.State)
func (r *Request) error(accountId string, err *Error) Response {
return errorResponse(single(accountId), err, r.session.State, jmap.NoLanguage)
}
func (r *Request) errorS(accountId string, err *Error, sessionState jmap.SessionState) Response {
return errorResponse(single(accountId), err, sessionState, jmap.NoLanguage)
}
func (r *Request) errorN(accountIds []string, err *Error) Response {
return errorResponse(accountIds, err, r.session.State, jmap.NoLanguage)
}
func (r *Request) jmapError(accountId string, err jmap.Error, sessionState jmap.SessionState, lang jmap.Language) Response {
return errorResponse(single(accountId), r.apiErrorFromJmap(r.observeJmapError(err)), sessionState, lang)
}
func (r *Request) jmapErrorN(accountIds []string, err jmap.Error, sessionState jmap.SessionState, lang jmap.Language) Response {
return errorResponse(accountIds, r.apiErrorFromJmap(r.observeJmapError(err)), sessionState, lang)
}

View File

@@ -223,7 +223,7 @@ func (r *Request) parameterError(param string, detail string) *Error {
}
func (r *Request) parameterErrorResponse(accountIds []string, param string, detail string) Response {
return errorResponse(accountIds, r.parameterError(param, detail))
return r.errorN(accountIds, r.parameterError(param, detail))
}
func (r *Request) getStringParam(param string, defaultValue string) (string, bool) {
@@ -427,7 +427,7 @@ func (r *Request) observeJmapError(jerr jmap.Error) jmap.Error {
func (r *Request) needTask(accountId string) (bool, Response) {
if !IgnoreSessionCapabilityChecksForTasks {
if r.session.Capabilities.Tasks == nil {
return false, errorResponseWithSessionState(single(accountId), r.apiError(&ErrorMissingTasksSessionCapability), r.session.State)
return false, errorResponse(single(accountId), r.apiError(&ErrorMissingTasksSessionCapability), r.session.State, jmap.Language(r.language()))
}
}
return true, Response{}
@@ -439,10 +439,10 @@ func (r *Request) needTaskForAccount(accountId string) (bool, Response) {
}
account, ok := r.session.Accounts[accountId]
if !ok {
return false, errorResponseWithSessionState(single(accountId), r.apiError(&ErrorAccountNotFound), r.session.State)
return false, errorResponse(single(accountId), r.apiError(&ErrorAccountNotFound), r.session.State, jmap.NoLanguage)
}
if account.AccountCapabilities.Tasks == nil {
return false, errorResponseWithSessionState(single(accountId), r.apiError(&ErrorMissingTasksAccountCapability), r.session.State)
return false, errorResponse(single(accountId), r.apiError(&ErrorMissingTasksAccountCapability), r.session.State, jmap.NoLanguage)
}
return true, Response{}
}
@@ -450,7 +450,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(single(accountId), err)
return false, "", r.error(accountId, err)
}
if ok, resp := r.needTaskForAccount(accountId); !ok {
return false, accountId, resp
@@ -460,7 +460,7 @@ func (r *Request) needTaskWithAccount() (bool, string, Response) {
func (r *Request) needCalendar(accountId string) (bool, Response) {
if r.session.Capabilities.Calendars == nil {
return false, errorResponseWithSessionState(single(accountId), r.apiError(&ErrorMissingCalendarsSessionCapability), r.session.State)
return false, errorResponse(single(accountId), r.apiError(&ErrorMissingCalendarsSessionCapability), r.session.State, jmap.NoLanguage)
}
return true, Response{}
}
@@ -471,10 +471,10 @@ func (r *Request) needCalendarForAccount(accountId string) (bool, Response) {
}
account, ok := r.session.Accounts[accountId]
if !ok {
return false, errorResponseWithSessionState(single(accountId), r.apiError(&ErrorAccountNotFound), r.session.State)
return false, errorResponse(single(accountId), r.apiError(&ErrorAccountNotFound), r.session.State, jmap.NoLanguage)
}
if account.AccountCapabilities.Calendars == nil {
return false, errorResponseWithSessionState(single(accountId), r.apiError(&ErrorMissingCalendarsAccountCapability), r.session.State)
return false, errorResponse(single(accountId), r.apiError(&ErrorMissingCalendarsAccountCapability), r.session.State, jmap.NoLanguage)
}
return true, Response{}
}
@@ -482,7 +482,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(single(accountId), err)
return false, "", r.error(accountId, err)
}
if ok, resp := r.needCalendarForAccount(accountId); !ok {
return false, accountId, resp
@@ -492,7 +492,7 @@ func (r *Request) needCalendarWithAccount() (bool, string, Response) {
func (r *Request) needContact(accountId string) (bool, Response) {
if r.session.Capabilities.Contacts == nil {
return false, errorResponseWithSessionState(single(accountId), r.apiError(&ErrorMissingContactsSessionCapability), r.session.State)
return false, errorResponse(single(accountId), r.apiError(&ErrorMissingContactsSessionCapability), r.session.State, jmap.NoLanguage)
}
return true, Response{}
}
@@ -503,10 +503,10 @@ func (r *Request) needContactForAccount(accountId string) (bool, Response) {
}
account, ok := r.session.Accounts[accountId]
if !ok {
return false, errorResponseWithSessionState(single(accountId), r.apiError(&ErrorAccountNotFound), r.session.State)
return false, errorResponse(single(accountId), r.apiError(&ErrorAccountNotFound), r.session.State, jmap.NoLanguage)
}
if account.AccountCapabilities.Contacts == nil {
return false, errorResponseWithSessionState(single(accountId), r.apiError(&ErrorMissingContactsAccountCapability), r.session.State)
return false, errorResponse(single(accountId), r.apiError(&ErrorMissingContactsAccountCapability), r.session.State, jmap.NoLanguage)
}
return true, Response{}
}
@@ -514,7 +514,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(single(accountId), err)
return false, "", r.error(accountId, err)
}
if ok, resp := r.needContactForAccount(accountId); !ok {
return false, accountId, resp
@@ -565,7 +565,7 @@ func (r *Request) parseSort(s string, props []string) ([]SortCrit, *Error) {
func mapSort[T any](accountIds []string, req *Request, defaultSort []T, props []string, mapper func(SortCrit) T) ([]T, bool, Response) {
if sortSpec, ok := req.getStringParam(QueryParamSort, ""); ok && strings.TrimSpace(sortSpec) != "" {
if sort, err := req.parseSort(sortSpec, props); err != nil {
return nil, false, errorResponseWithSessionState(accountIds, err, req.session.State)
return nil, false, errorResponse(accountIds, err, req.session.State, jmap.NoLanguage)
} else {
return structs.Map(sort, mapper), true, Response{}
}

View File

@@ -36,23 +36,14 @@ type Response struct {
contentLanguage jmap.Language
}
func errorResponse(accountIds []string, err *Error) Response {
func errorResponse(accountIds []string, err *Error, sessionState jmap.SessionState, contentLanguage jmap.Language) Response {
return Response{
accountIds: accountIds,
body: nil,
err: err,
etag: "",
sessionState: "",
}
}
func errorResponseWithSessionState(accountIds []string, err *Error, sessionState jmap.SessionState) Response {
return Response{
accountIds: accountIds,
body: nil,
err: err,
etag: "",
sessionState: sessionState,
accountIds: accountIds,
body: nil,
err: err,
etag: "",
sessionState: sessionState,
contentLanguage: contentLanguage,
}
}
@@ -67,7 +58,11 @@ func response(accountIds []string, body any, sessionState jmap.SessionState, con
}
}
func etagResponse(accountIds []string, body any, sessionState jmap.SessionState, objectType ResponseObjectType, etag jmap.State, contentLanguage jmap.Language) Response {
func (r *Request) respondWithoutStatus(accountId string, body any) Response {
return response(single(accountId), body, r.session.State, jmap.Language(r.language()))
}
func etaggedResponse(accountIds []string, body any, sessionState jmap.SessionState, objectType ResponseObjectType, etag jmap.State, contentLanguage jmap.Language) Response {
return Response{
accountIds: accountIds,
body: body,
@@ -79,6 +74,14 @@ func etagResponse(accountIds []string, body any, sessionState jmap.SessionState,
}
}
func (r *Request) respond(accountId string, body any, sessionState jmap.SessionState, objectType ResponseObjectType, etag jmap.State) Response {
return etaggedResponse(single(accountId), body, sessionState, objectType, etag, jmap.Language(r.language()))
}
func (r *Request) respondN(accountIds []string, body any, sessionState jmap.SessionState, objectType ResponseObjectType, etag jmap.State) Response {
return etaggedResponse(accountIds, body, sessionState, objectType, etag, jmap.Language(r.language()))
}
/*
func etagOnlyResponse(body any, etag jmap.State, objectType ResponseObjectType, contentLanguage jmap.Language) Response {
return Response{
@@ -103,6 +106,14 @@ func noContentResponse(accountIds []string, sessionState jmap.SessionState) Resp
}
}
func (r *Request) noop(accountId string) Response {
return noContentResponse(single(accountId), r.session.State)
}
func (r *Request) noopN(accountIds []string) Response {
return noContentResponse(accountIds, r.session.State)
}
func noContentResponseWithEtag(accountIds []string, sessionState jmap.SessionState, objectType ResponseObjectType, etag jmap.State) Response {
return Response{
accountIds: accountIds,
@@ -115,6 +126,10 @@ func noContentResponseWithEtag(accountIds []string, sessionState jmap.SessionSta
}
}
func (r *Request) noContent(accountId string, sessionState jmap.SessionState, objectType ResponseObjectType, etag jmap.State) Response {
return noContentResponseWithEtag(single(accountId), sessionState, objectType, etag)
}
/*
func acceptedResponse(sessionState jmap.SessionState) Response {
return Response{
@@ -139,18 +154,27 @@ func timeoutResponse(sessionState jmap.SessionState) Response {
}
*/
func notFoundResponse(accountIds []string, sessionState jmap.SessionState) Response {
func notFoundResponse(accountIds []string, sessionState jmap.SessionState, objectType ResponseObjectType, etag jmap.State) Response {
return Response{
accountIds: accountIds,
body: nil,
status: http.StatusNotFound,
err: nil,
etag: "",
objectType: objectType,
etag: etag,
sessionState: sessionState,
}
}
func etagNotFoundResponse(accountIds []string, sessionState jmap.SessionState, objectType ResponseObjectType, etag jmap.State, contentLanguage jmap.Language) Response {
func (r *Request) notFound(accountId string, sessionState jmap.SessionState, objectType ResponseObjectType, etag jmap.State) Response {
return notFoundResponse(single(accountId), sessionState, objectType, etag)
}
func (r *Request) notFoundN(accountIds []string, sessionState jmap.SessionState, objectType ResponseObjectType, etag jmap.State) Response {
return notFoundResponse(accountIds, sessionState, objectType, etag)
}
func etaggedNotFoundResponse(accountIds []string, sessionState jmap.SessionState, objectType ResponseObjectType, etag jmap.State, contentLanguage jmap.Language) Response {
return Response{
accountIds: accountIds,
body: nil,
@@ -163,10 +187,21 @@ func etagNotFoundResponse(accountIds []string, sessionState jmap.SessionState, o
}
}
func notImplementedResponse() Response {
func (r *Request) etaggedNotFound(accountId string, sessionState jmap.SessionState, objectType ResponseObjectType, etag jmap.State) Response {
return etaggedNotFoundResponse(single(accountId), sessionState, objectType, etag, jmap.Language(r.language()))
}
func notImplementedResponse(accountIds []string, sessionState jmap.SessionState, objectType ResponseObjectType) Response {
return Response{
body: nil,
status: http.StatusNotImplemented,
err: nil,
accountIds: accountIds,
body: nil,
status: http.StatusNotImplemented,
err: nil,
objectType: objectType,
sessionState: sessionState,
}
}
func (r *Request) notImplementedN(accountIds []string, objectType ResponseObjectType) Response {
return notImplementedResponse(accountIds, r.session.State, objectType)
}

View File

@@ -23,7 +23,6 @@ const (
UriParamContactId = "contactid" // Identifier of the contact
UriParamEventId = "eventid" // Idenfitier of the event
UriParamBlobName = "blobname"
UriParamSince = "since"
UriParamRole = "role"
QueryParamMailboxSearchName = "name"
QueryParamMailboxSearchRole = "role"
@@ -102,8 +101,6 @@ func (g *Groupware) Route(r chi.Router) {
r.Route("/{mailboxid}", func(r chi.Router) {
r.Get("/", g.GetMailbox)
r.Get("/emails", g.GetAllEmailsInMailbox)
r.Get("/emails/since/{since}", g.GetAllEmailsInMailboxSince)
r.Get("/changes", g.GetMailboxChanges)
r.Patch("/", g.UpdateMailbox)
r.Delete("/", g.DeleteMailbox)
})
@@ -148,6 +145,7 @@ func (g *Groupware) Route(r chi.Router) {
})
})
r.Route("/contacts", func(r chi.Router) {
r.Get("/", g.GetAllContacts)
r.Post("/", g.CreateContact)
r.Delete("/{contactid}", g.DeleteContact)
r.Get("/{contactid}", g.GetContactById)
@@ -170,6 +168,11 @@ func (g *Groupware) Route(r chi.Router) {
r.Get("/tasks", g.GetTasksInTaskList)
})
})
r.Route("/changes", func(r chi.Router) {
r.Get("/contacts", g.GetContactsChanges)
r.Get("/mailboxes", g.GetMailboxChanges)
r.Get("/emails", g.GetEmailChanges)
})
})
})