From 98e1701c0a569b61c2fbb7e5f8d2c9a9db78a3af Mon Sep 17 00:00:00 2001
From: Pascal Bleser
Date: Fri, 27 Mar 2026 15:54:25 +0100
Subject: [PATCH] groupware: add changes support for quotas, identities,
submissions
---
pkg/jmap/api_blob.go | 4 +-
pkg/jmap/api_bootstrap.go | 4 +-
pkg/jmap/api_calendar.go | 16 +-
pkg/jmap/api_changes.go | 83 +++-
pkg/jmap/api_contact.go | 18 +-
pkg/jmap/api_email.go | 78 +++-
pkg/jmap/api_identity.go | 61 ++-
pkg/jmap/api_mailbox.go | 109 +----
pkg/jmap/api_objects.go | 72 +++-
pkg/jmap/api_quota.go | 88 +++-
pkg/jmap/api_vacation.go | 6 +-
pkg/jmap/client.go | 12 +-
pkg/jmap/integration_test.go | 8 +-
pkg/jmap/model.go | 406 +++++++++++++-----
pkg/jmap/templates.go | 77 +++-
pkg/jmap/tools.go | 9 +
.../groupware/pkg/groupware/api_changes.go | 7 +
.../groupware/pkg/groupware/api_objects.go | 48 ++-
services/groupware/pkg/groupware/api_quota.go | 33 ++
services/groupware/pkg/groupware/route.go | 3 +
20 files changed, 835 insertions(+), 307 deletions(-)
diff --git a/pkg/jmap/api_blob.go b/pkg/jmap/api_blob.go
index c44a5bf904..40b6402f07 100644
--- a/pkg/jmap/api_blob.go
+++ b/pkg/jmap/api_blob.go
@@ -10,7 +10,7 @@ import (
)
func (j *Client) GetBlobMetadata(accountId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, id string) (*Blob, SessionState, State, Language, Error) {
- cmd, jerr := j.request(session, logger,
+ cmd, jerr := j.request(session, logger, ns(JmapBlob),
invocation(CommandBlobGet, BlobGetCommand{
AccountId: accountId,
Ids: []string{id},
@@ -89,7 +89,7 @@ func (j *Client) UploadBlob(accountId string, session *Session, ctx context.Cont
Properties: []string{BlobPropertyDigestSha512},
}
- cmd, jerr := j.request(session, logger,
+ cmd, jerr := j.request(session, logger, ns(JmapBlob),
invocation(CommandBlobUpload, upload, "0"),
invocation(CommandBlobGet, getHash, "1"),
)
diff --git a/pkg/jmap/api_bootstrap.go b/pkg/jmap/api_bootstrap.go
index 532a8a4001..31c44e6e42 100644
--- a/pkg/jmap/api_bootstrap.go
+++ b/pkg/jmap/api_bootstrap.go
@@ -12,6 +12,8 @@ type AccountBootstrapResult struct {
Quotas []Quota `json:"quotas,omitempty"`
}
+var NS_MAIL_QUOTA = ns(JmapMail, JmapQuota)
+
func (j *Client) GetBootstrap(accountIds []string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string) (map[string]AccountBootstrapResult, SessionState, State, Language, Error) { //NOSONAR
uniqueAccountIds := structs.Uniq(accountIds)
@@ -23,7 +25,7 @@ func (j *Client) GetBootstrap(accountIds []string, session *Session, ctx context
calls[i*2+1] = invocation(CommandQuotaGet, QuotaGetCommand{AccountId: accountId}, mcid(accountId, "Q"))
}
- cmd, err := j.request(session, logger, calls...)
+ cmd, err := j.request(session, logger, NS_MAIL_QUOTA, calls...)
if err != nil {
return nil, "", "", "", err
}
diff --git a/pkg/jmap/api_calendar.go b/pkg/jmap/api_calendar.go
index 3de6d55925..47d895f34c 100644
--- a/pkg/jmap/api_calendar.go
+++ b/pkg/jmap/api_calendar.go
@@ -7,10 +7,12 @@ import (
"github.com/opencloud-eu/opencloud/pkg/structs"
)
+var NS_CALENDARS = ns(JmapCalendars)
+
func (j *Client) ParseICalendarBlob(accountId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, blobIds []string) (CalendarEventParseResponse, SessionState, State, Language, Error) {
logger = j.logger("ParseICalendarBlob", session, logger)
- cmd, err := j.request(session, logger,
+ cmd, err := j.request(session, logger, NS_CALENDARS,
invocation(CommandCalendarEventParse, CalendarEventParseCommand{AccountId: accountId, BlobIds: blobIds}, "0"),
)
if err != nil {
@@ -33,7 +35,7 @@ type CalendarsResponse struct {
}
func (j *Client) GetCalendars(accountId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, ids []string) (CalendarsResponse, SessionState, State, Language, Error) {
- return getTemplate(j, "GetCalendars", CommandCalendarGet,
+ return getTemplate(j, "GetCalendars", NS_CALENDARS, CommandCalendarGet,
func(accountId string, ids []string) CalendarGetCommand {
return CalendarGetCommand{AccountId: accountId, Ids: ids}
},
@@ -57,7 +59,7 @@ type CalendarChanges struct {
// Retrieve Calendar changes since a given state.
// @apidoc calendar,changes
func (j *Client) GetCalendarChanges(accountId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, sinceState State, maxChanges uint) (CalendarChanges, SessionState, State, Language, Error) {
- return changesTemplate(j, "GetCalendarChanges",
+ return changesTemplate(j, "GetCalendarChanges", NS_CALENDARS,
CommandCalendarChanges, CommandCalendarGet,
func() CalendarChangesCommand {
return CalendarChangesCommand{AccountId: accountId, SinceState: sinceState, MaxChanges: posUIntPtr(maxChanges)}
@@ -126,7 +128,7 @@ func (j *Client) QueryCalendarEvents(accountIds []string, session *Session, ctx
// Properties: CalendarEventProperties, // to also retrieve UTCStart and UTCEnd
}, mcid(accountId, "1"))
}
- cmd, err := j.request(session, logger, invocations...)
+ cmd, err := j.request(session, logger, NS_CALENDARS, invocations...)
if err != nil {
return nil, "", "", "", err
}
@@ -161,7 +163,7 @@ type CalendarEventChanges struct {
func (j *Client) GetCalendarEventChanges(accountId string, session *Session, ctx context.Context, logger *log.Logger,
acceptLanguage string, sinceState State, maxChanges uint) (CalendarEventChanges, SessionState, State, Language, Error) {
- return changesTemplate(j, "GetCalendarEventChanges",
+ return changesTemplate(j, "GetCalendarEventChanges", NS_CALENDARS,
CommandCalendarEventChanges, CommandCalendarEventGet,
func() CalendarEventChangesCommand {
return CalendarEventChangesCommand{AccountId: accountId, SinceState: sinceState, MaxChanges: posUIntPtr(maxChanges)}
@@ -196,7 +198,7 @@ func (j *Client) GetCalendarEventChanges(accountId string, session *Session, ctx
}
func (j *Client) CreateCalendarEvent(accountId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, create CalendarEvent) (*CalendarEvent, SessionState, State, Language, Error) {
- return createTemplate(j, "CreateCalendarEvent", CalendarEventType, CommandCalendarEventSet, CommandCalendarEventGet,
+ return createTemplate(j, "CreateCalendarEvent", NS_CALENDARS, CalendarEventType, CommandCalendarEventSet, CommandCalendarEventGet,
func(accountId string, create map[string]CalendarEvent) CalendarEventSetCommand {
return CalendarEventSetCommand{AccountId: accountId, Create: create}
},
@@ -219,7 +221,7 @@ func (j *Client) CreateCalendarEvent(accountId string, session *Session, ctx con
}
func (j *Client) DeleteCalendarEvent(accountId string, destroy []string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string) (map[string]SetError, SessionState, State, Language, Error) {
- return deleteTemplate(j, "DeleteCalendarEvent", CommandCalendarEventSet,
+ return deleteTemplate(j, "DeleteCalendarEvent", NS_CALENDARS, CommandCalendarEventSet,
func(accountId string, destroy []string) CalendarEventSetCommand {
return CalendarEventSetCommand{AccountId: accountId, Destroy: destroy}
},
diff --git a/pkg/jmap/api_changes.go b/pkg/jmap/api_changes.go
index da319b2ede..7c763e7216 100644
--- a/pkg/jmap/api_changes.go
+++ b/pkg/jmap/api_changes.go
@@ -7,30 +7,43 @@ import (
"github.com/rs/zerolog"
)
+// Note that Quota/changes is currently not supported in Stalwart, as it always gives a
+// cannotCalculateChanges error back.
+
+var NS_CHANGES = ns(JmapMail, JmapContacts, JmapCalendars) //, JmapQuota)
+
type Changes struct {
- MaxChanges uint `json:"maxchanges,omitzero"`
- Mailboxes *MailboxChangesResponse `json:"mailboxes,omitempty"`
- Emails *EmailChangesResponse `json:"emails,omitempty"`
- Calendars *CalendarChangesResponse `json:"calendars,omitempty"`
- Events *CalendarEventChangesResponse `json:"events,omitempty"`
- Addressbooks *AddressBookChangesResponse `json:"addressbooks,omitempty"`
- Contacts *ContactCardChangesResponse `json:"contacts,omitempty"`
+ MaxChanges uint `json:"maxchanges,omitzero"`
+ Mailboxes *MailboxChangesResponse `json:"mailboxes,omitempty"`
+ Emails *EmailChangesResponse `json:"emails,omitempty"`
+ Calendars *CalendarChangesResponse `json:"calendars,omitempty"`
+ Events *CalendarEventChangesResponse `json:"events,omitempty"`
+ Addressbooks *AddressBookChangesResponse `json:"addressbooks,omitempty"`
+ Contacts *ContactCardChangesResponse `json:"contacts,omitempty"`
+ Identities *IdentityChangesResponse `json:"identities,omitempty"`
+ EmailSubmissions *EmailSubmissionChangesResponse `json:"submissions,omitempty"`
+ // Quotas *QuotaChangesResponse `json:"quotas,omitempty"`
}
type StateMap struct {
- Mailboxes *State `json:"mailboxes,omitempty"`
- Emails *State `json:"emails,omitempty"`
- Calendars *State `json:"calendars,omitempty"`
- Events *State `json:"events,omitempty"`
- Addressbooks *State `json:"addressbooks,omitempty"`
- Contacts *State `json:"contacts,omitempty"`
+ Mailboxes *State `json:"mailboxes,omitempty"`
+ Emails *State `json:"emails,omitempty"`
+ Calendars *State `json:"calendars,omitempty"`
+ Events *State `json:"events,omitempty"`
+ Addressbooks *State `json:"addressbooks,omitempty"`
+ Contacts *State `json:"contacts,omitempty"`
+ Identities *State `json:"identities,omitempty"`
+ EmailSubmissions *State `json:"submissions,omitempty"`
+ // Quotas *State `json:"quotas,omitempty"`
}
var _ zerolog.LogObjectMarshaler = StateMap{}
func (s StateMap) IsZero() bool {
return s.Mailboxes == nil && s.Emails == nil && s.Calendars == nil &&
- s.Events == nil && s.Addressbooks == nil && s.Contacts == nil
+ s.Events == nil && s.Addressbooks == nil && s.Contacts == nil &&
+ s.Identities == nil && s.EmailSubmissions == nil
+ //s.Quotas == nil
}
func (s StateMap) MarshalZerologObject(e *zerolog.Event) {
@@ -52,6 +65,13 @@ func (s StateMap) MarshalZerologObject(e *zerolog.Event) {
if s.Contacts != nil {
e.Str("contacts", string(*s.Contacts))
}
+ if s.Identities != nil {
+ e.Str("identities", string(*s.Identities))
+ }
+ if s.EmailSubmissions != nil {
+ e.Str("submissions", string(*s.EmailSubmissions))
+ }
+ // if s.Quotas != nil { e.Str("quotas", string(*s.Quotas)) }
}
func (j *Client) GetChanges(accountId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, stateMap StateMap, maxChanges uint) (Changes, SessionState, State, Language, Error) { //NOSONAR
@@ -79,8 +99,15 @@ func (j *Client) GetChanges(accountId string, session *Session, ctx context.Cont
if stateMap.Contacts != nil {
methodCalls = append(methodCalls, invocation(CommandContactCardChanges, ContactCardChangesCommand{AccountId: accountId, SinceState: *stateMap.Contacts, MaxChanges: posUIntPtr(maxChanges)}, "contacts"))
}
+ if stateMap.Identities != nil {
+ methodCalls = append(methodCalls, invocation(CommandIdentityChanges, IdentityChangesCommand{AccountId: accountId, SinceState: *stateMap.Identities, MaxChanges: posUIntPtr(maxChanges)}, "identities"))
+ }
+ if stateMap.EmailSubmissions != nil {
+ methodCalls = append(methodCalls, invocation(CommandEmailSubmissionChanges, EmailSubmissionChangesCommand{AccountId: accountId, SinceState: *stateMap.EmailSubmissions, MaxChanges: posUIntPtr(maxChanges)}, "submissions"))
+ }
+ // if stateMap.Quotas != nil { methodCalls = append(methodCalls, invocation(CommandQuotaChanges, QuotaChangesCommand{AccountId: accountId, SinceState: *stateMap.Quotas, MaxChanges: posUIntPtr(maxChanges)}, "quotas")) }
- cmd, err := j.request(session, logger, methodCalls...)
+ cmd, err := j.request(session, logger, NS_CHANGES, methodCalls...)
if err != nil {
return Changes{}, "", "", "", err
}
@@ -139,6 +166,32 @@ func (j *Client) GetChanges(accountId string, session *Session, ctx context.Cont
states["contacts"] = contacts.NewState
}
+ var identities IdentityChangesResponse
+ if ok, err := tryRetrieveResponseMatchParameters(logger, body, CommandIdentityChanges, "identities", &identities); err != nil {
+ return Changes{}, "", err
+ } else if ok {
+ changes.Identities = &identities
+ states["identities"] = identities.NewState
+ }
+
+ var submissions EmailSubmissionChangesResponse
+ if ok, err := tryRetrieveResponseMatchParameters(logger, body, CommandEmailSubmissionChanges, "submissions", &submissions); err != nil {
+ return Changes{}, "", err
+ } else if ok {
+ changes.EmailSubmissions = &submissions
+ states["submissions"] = submissions.NewState
+ }
+
+ /*
+ var quotas QuotaChangesResponse
+ if ok, err := tryRetrieveResponseMatchParameters(logger, body, CommandQuotaChanges, "quotas", "as); err != nil {
+ return Changes{}, "", err
+ } else if ok {
+ changes.Quotas = "as
+ states["quotas"] = quotas.NewState
+ }
+ */
+
return changes, squashKeyedStates(states), nil
})
}
diff --git a/pkg/jmap/api_contact.go b/pkg/jmap/api_contact.go
index 92ddd47fa1..0095a9c171 100644
--- a/pkg/jmap/api_contact.go
+++ b/pkg/jmap/api_contact.go
@@ -9,6 +9,8 @@ import (
"github.com/opencloud-eu/opencloud/pkg/structs"
)
+var NS_CONTACTS = ns(JmapContacts)
+
type AddressBooksResponse struct {
AddressBooks []AddressBook `json:"addressbooks"`
NotFound []string `json:"notFound,omitempty"`
@@ -17,7 +19,7 @@ type AddressBooksResponse struct {
func (j *Client) GetAddressbooks(accountId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, ids []string) (AddressBooksResponse, SessionState, State, Language, Error) {
logger = j.logger("GetAddressbooks", session, logger)
- cmd, err := j.request(session, logger,
+ cmd, err := j.request(session, logger, NS_CONTACTS,
invocation(CommandAddressBookGet, AddressBookGetCommand{AccountId: accountId, Ids: ids}, "0"),
)
if err != nil {
@@ -49,7 +51,7 @@ type AddressBookChanges struct {
// Retrieve Address Book changes since a given state.
// @apidoc addressbook,changes
func (j *Client) GetAddressbookChanges(accountId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, sinceState State, maxChanges uint) (AddressBookChanges, SessionState, State, Language, Error) {
- return changesTemplate(j, "GetAddressbookChanges",
+ return changesTemplate(j, "GetAddressbookChanges", NS_CONTACTS,
CommandAddressBookChanges, CommandAddressBookGet,
func() AddressBookChangesCommand {
return AddressBookChangesCommand{AccountId: accountId, SinceState: sinceState, MaxChanges: posUIntPtr(maxChanges)}
@@ -87,7 +89,7 @@ func (j *Client) GetContactCardsById(accountId string, session *Session, ctx con
acceptLanguage string, contactIds []string) (map[string]jscontact.ContactCard, SessionState, State, Language, Error) {
logger = j.logger("GetContactCardsById", session, logger)
- cmd, err := j.request(session, logger, invocation(CommandContactCardGet, ContactCardGetCommand{
+ cmd, err := j.request(session, logger, NS_CONTACTS, invocation(CommandContactCardGet, ContactCardGetCommand{
Ids: contactIds,
AccountId: accountId,
}, "0"))
@@ -111,7 +113,7 @@ 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) {
- return getTemplate(j, "GetContactCards", CommandContactCardGet,
+ return getTemplate(j, "GetContactCards", NS_CONTACTS, CommandContactCardGet,
func(accountId string, ids []string) ContactCardGetCommand {
return ContactCardGetCommand{AccountId: accountId, Ids: contactIds}
},
@@ -132,7 +134,7 @@ type ContactCardChanges struct {
func (j *Client) GetContactCardChanges(accountId string, session *Session, ctx context.Context, logger *log.Logger,
acceptLanguage string, sinceState State, maxChanges uint) (ContactCardChanges, SessionState, State, Language, Error) {
- return changesTemplate(j, "GetContactCardChanges",
+ return changesTemplate(j, "GetContactCardChanges", NS_CONTACTS,
CommandContactCardChanges, CommandContactCardGet,
func() ContactCardChangesCommand {
return ContactCardChangesCommand{AccountId: accountId, SinceState: sinceState, MaxChanges: posUIntPtr(maxChanges)}
@@ -200,7 +202,7 @@ func (j *Client) QueryContactCards(accountIds []string, session *Session, ctx co
},
}, mcid(accountId, "1"))
}
- cmd, err := j.request(session, logger, invocations...)
+ cmd, err := j.request(session, logger, NS_CONTACTS, invocations...)
if err != nil {
return nil, "", "", "", err
}
@@ -227,7 +229,7 @@ func (j *Client) QueryContactCards(accountIds []string, session *Session, ctx co
func (j *Client) CreateContactCard(accountId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, create jscontact.ContactCard) (*jscontact.ContactCard, SessionState, State, Language, Error) {
logger = j.logger("CreateContactCard", session, logger)
- cmd, err := j.request(session, logger,
+ cmd, err := j.request(session, logger, NS_CONTACTS,
invocation(CommandContactCardSet, ContactCardSetCommand{
AccountId: accountId,
Create: map[string]jscontact.ContactCard{
@@ -281,7 +283,7 @@ func (j *Client) CreateContactCard(accountId string, session *Session, ctx conte
func (j *Client) DeleteContactCard(accountId string, destroy []string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string) (map[string]SetError, SessionState, State, Language, Error) {
logger = j.logger("DeleteContactCard", session, logger)
- cmd, err := j.request(session, logger,
+ cmd, err := j.request(session, logger, NS_CONTACTS,
invocation(CommandContactCardSet, ContactCardSetCommand{
AccountId: accountId,
Destroy: destroy,
diff --git a/pkg/jmap/api_email.go b/pkg/jmap/api_email.go
index d421fd77bf..bc8b2a960a 100644
--- a/pkg/jmap/api_email.go
+++ b/pkg/jmap/api_email.go
@@ -11,6 +11,9 @@ import (
"github.com/rs/zerolog"
)
+var NS_MAIL = ns(JmapMail)
+var NS_MAIL_SUBMISSION = ns(JmapMail, JmapSubmission)
+
type Emails struct {
Emails []Email `json:"emails,omitempty"`
Total uint `json:"total,omitzero"`
@@ -54,7 +57,7 @@ func (j *Client) GetEmails(accountId string, session *Session, ctx context.Conte
methodCalls = append(methodCalls, invocation(CommandThreadGet, threads, "2"))
}
- cmd, err := j.request(session, logger, methodCalls...)
+ cmd, err := j.request(session, logger, NS_MAIL, methodCalls...)
if err != nil {
return nil, nil, "", "", "", err
}
@@ -92,7 +95,7 @@ func (j *Client) GetEmailBlobId(accountId string, session *Session, ctx context.
logger = j.logger("GetEmailBlobId", session, logger)
get := EmailGetCommand{AccountId: accountId, Ids: []string{id}, FetchAllBodyValues: false, Properties: []string{"blobId"}}
- cmd, err := j.request(session, logger, invocation(CommandEmailGet, get, "0"))
+ cmd, err := j.request(session, logger, NS_MAIL, invocation(CommandEmailGet, get, "0"))
if err != nil {
return "", "", "", "", err
}
@@ -156,7 +159,7 @@ func (j *Client) GetAllEmailsInMailbox(accountId string, session *Session, ctx c
invocations = append(invocations, invocation(CommandThreadGet, threads, "2"))
}
- cmd, err := j.request(session, logger, invocations...)
+ cmd, err := j.request(session, logger, NS_MAIL, invocations...)
if err != nil {
return Emails{}, "", "", "", err
}
@@ -232,7 +235,7 @@ func (j *Client) GetEmailChanges(accountId string, session *Session, ctx context
getUpdated.MaxBodyValueBytes = maxBodyValueBytes
}
- cmd, err := j.request(session, logger,
+ cmd, err := j.request(session, logger, NS_MAIL,
invocation(CommandEmailChanges, changes, "0"),
invocation(CommandEmailGet, getCreated, "1"),
invocation(CommandEmailGet, getUpdated, "2"),
@@ -336,7 +339,7 @@ func (j *Client) QueryEmailSnippets(accountIds []string, filter EmailFilterEleme
invocations[i*3+2] = invocation(CommandSearchSnippetGet, snippet, mcid(accountId, "2"))
}
- cmd, err := j.request(session, logger, invocations...)
+ cmd, err := j.request(session, logger, NS_MAIL, invocations...)
if err != nil {
return nil, "", "", "", err
}
@@ -440,7 +443,7 @@ func (j *Client) QueryEmails(accountIds []string, filter EmailFilterElement, ses
invocations[i*2+1] = invocation(CommandEmailGet, mails, mcid(accountId, "1"))
}
- cmd, err := j.request(session, logger, invocations...)
+ cmd, err := j.request(session, logger, NS_MAIL, invocations...)
if err != nil {
return nil, "", "", "", err
}
@@ -532,7 +535,7 @@ func (j *Client) QueryEmailsWithSnippets(accountIds []string, filter EmailFilter
invocations[i*3+2] = invocation(CommandEmailGet, mails, mcid(accountId, "2"))
}
- cmd, err := j.request(session, logger, invocations...)
+ cmd, err := j.request(session, logger, NS_MAIL, invocations...)
if err != nil {
return nil, "", "", "", err
}
@@ -623,7 +626,7 @@ func (j *Client) ImportEmail(accountId string, session *Session, ctx context.Con
Properties: []string{BlobPropertyDigestSha512},
}
- cmd, err := j.request(session, logger,
+ cmd, err := j.request(session, logger, NS_MAIL,
invocation(CommandBlobUpload, upload, "0"),
invocation(CommandBlobGet, getHash, "1"),
)
@@ -682,7 +685,7 @@ func (j *Client) CreateEmail(accountId string, email EmailCreate, replaceId stri
set.Destroy = []string{replaceId}
}
- cmd, err := j.request(session, logger,
+ cmd, err := j.request(session, logger, NS_MAIL,
invocation(CommandEmailSet, set, "0"),
)
if err != nil {
@@ -727,7 +730,7 @@ func (j *Client) CreateEmail(accountId string, email EmailCreate, replaceId stri
//
// To delete mails, use the DeleteEmails function instead.
func (j *Client) UpdateEmails(accountId string, updates map[string]EmailUpdate, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string) (map[string]*Email, SessionState, State, Language, Error) {
- cmd, err := j.request(session, logger,
+ cmd, err := j.request(session, logger, NS_MAIL,
invocation(CommandEmailSet, EmailSetCommand{
AccountId: accountId,
Update: updates,
@@ -754,7 +757,7 @@ func (j *Client) UpdateEmails(accountId string, updates map[string]EmailUpdate,
}
func (j *Client) DeleteEmails(accountId string, destroy []string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string) (map[string]SetError, SessionState, State, Language, Error) {
- cmd, err := j.request(session, logger,
+ cmd, err := j.request(session, logger, NS_MAIL,
invocation(CommandEmailSet, EmailSetCommand{
AccountId: accountId,
Destroy: destroy,
@@ -836,7 +839,7 @@ func (j *Client) SubmitEmail(accountId string, identityId string, emailId string
Ids: []string{"#" + id},
}
- cmd, err := j.request(session, logger,
+ cmd, err := j.request(session, logger, NS_MAIL_SUBMISSION,
invocation(CommandEmailSubmissionSet, set, "0"),
invocation(CommandEmailSubmissionGet, get, "1"),
)
@@ -897,7 +900,7 @@ type emailSubmissionResult struct {
func (j *Client) GetEmailSubmissionStatus(accountId string, submissionIds []string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string) (map[string]EmailSubmission, []string, SessionState, State, Language, Error) {
logger = j.logger("GetEmailSubmissionStatus", session, logger)
- cmd, err := j.request(session, logger, invocation(CommandEmailSubmissionGet, EmailSubmissionGetCommand{
+ cmd, err := j.request(session, logger, NS_MAIL_SUBMISSION, invocation(CommandEmailSubmissionGet, EmailSubmissionGetCommand{
AccountId: accountId,
Ids: submissionIds,
}, "0"))
@@ -926,7 +929,7 @@ func (j *Client) EmailsInThread(accountId string, threadId string, session *Sess
return z.Bool(logFetchBodies, fetchBodies).Str("threadId", log.SafeString(threadId))
})
- cmd, err := j.request(session, logger,
+ cmd, err := j.request(session, logger, NS_MAIL,
invocation(CommandThreadGet, ThreadGetCommand{
AccountId: accountId,
Ids: []string{threadId},
@@ -1025,7 +1028,7 @@ func (j *Client) QueryEmailSummaries(accountIds []string, session *Session, ctx
}, mcid(accountId, "2"))
}
}
- cmd, err := j.request(session, logger, invocations...)
+ cmd, err := j.request(session, logger, NS_MAIL, invocations...)
if err != nil {
return nil, "", "", "", err
}
@@ -1068,6 +1071,51 @@ func (j *Client) QueryEmailSummaries(accountIds []string, session *Session, ctx
})
}
+type EmailSubmissionChanges struct {
+ OldState State `json:"oldState,omitempty"`
+ NewState State `json:"newState"`
+ HasMoreChanges bool `json:"hasMoreChanges"`
+ Created []EmailSubmission `json:"created,omitempty"`
+ Updated []EmailSubmission `json:"updated,omitempty"`
+ Destroyed []string `json:"destroyed,omitempty"`
+}
+
+func (j *Client) GetEmailSubmissionChanges(accountId string, session *Session, ctx context.Context, logger *log.Logger,
+ acceptLanguage string, sinceState State, maxChanges uint) (EmailSubmissionChanges, SessionState, State, Language, Error) {
+ return changesTemplate(j, "GetEmailSubmissionChanges", NS_MAIL_SUBMISSION,
+ CommandEmailSubmissionChanges, CommandEmailSubmissionGet,
+ func() EmailSubmissionChangesCommand {
+ return EmailSubmissionChangesCommand{AccountId: accountId, SinceState: sinceState, MaxChanges: posUIntPtr(maxChanges)}
+ },
+ func(path string, rof string) EmailSubmissionGetRefCommand {
+ return EmailSubmissionGetRefCommand{
+ AccountId: accountId,
+ IdsRef: &ResultReference{
+ Name: CommandEmailSubmissionChanges,
+ Path: path,
+ ResultOf: rof,
+ },
+ }
+ },
+ func(resp EmailSubmissionChangesResponse) (State, State, bool, []string) {
+ return resp.OldState, resp.NewState, resp.HasMoreChanges, resp.Destroyed
+ },
+ func(resp EmailSubmissionGetResponse) []EmailSubmission { return resp.List },
+ func(oldState, newState State, hasMoreChanges bool, created, updated []EmailSubmission, destroyed []string) EmailSubmissionChanges {
+ return EmailSubmissionChanges{
+ OldState: oldState,
+ NewState: newState,
+ HasMoreChanges: hasMoreChanges,
+ Created: created,
+ Updated: updated,
+ Destroyed: destroyed,
+ }
+ },
+ func(resp EmailSubmissionGetResponse) State { return resp.State },
+ session, ctx, logger, acceptLanguage,
+ )
+}
+
func setThreadSize(threads *ThreadGetResponse, emails []Email) {
threadSizeById := make(map[string]int, len(threads.List))
for _, thread := range threads.List {
diff --git a/pkg/jmap/api_identity.go b/pkg/jmap/api_identity.go
index 65cf3ef8f1..3387daa1ce 100644
--- a/pkg/jmap/api_identity.go
+++ b/pkg/jmap/api_identity.go
@@ -8,8 +8,10 @@ import (
"github.com/opencloud-eu/opencloud/pkg/structs"
)
+var NS_IDENTITY = ns(JmapMail)
+
func (j *Client) GetAllIdentities(accountId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string) ([]Identity, SessionState, State, Language, Error) {
- return getTemplate(j, "GetAllIdentities", CommandIdentityGet,
+ return getTemplate(j, "GetAllIdentities", NS_IDENTITY, CommandIdentityGet,
func(accountId string, ids []string) IdentityGetCommand {
return IdentityGetCommand{AccountId: accountId}
},
@@ -20,7 +22,7 @@ func (j *Client) GetAllIdentities(accountId string, session *Session, ctx contex
}
func (j *Client) GetIdentities(accountId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, identityIds []string) ([]Identity, SessionState, State, Language, Error) {
- return getTemplate(j, "GetIdentities", CommandIdentityGet,
+ return getTemplate(j, "GetIdentities", NS_IDENTITY, CommandIdentityGet,
func(accountId string, ids []string) IdentityGetCommand {
return IdentityGetCommand{AccountId: accountId, Ids: ids}
},
@@ -31,7 +33,7 @@ func (j *Client) GetIdentities(accountId string, session *Session, ctx context.C
}
func (j *Client) GetIdentitiesForAllAccounts(accountIds []string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string) (map[string][]Identity, SessionState, State, Language, Error) {
- return getTemplateN(j, "GetIdentitiesForAllAccounts", CommandIdentityGet,
+ return getTemplateN(j, "GetIdentitiesForAllAccounts", NS_IDENTITY, CommandIdentityGet,
func(accountId string, ids []string) IdentityGetCommand {
return IdentityGetCommand{AccountId: accountId}
},
@@ -59,7 +61,7 @@ func (j *Client) GetIdentitiesAndMailboxes(mailboxAccountId string, accountIds [
calls[i+1] = invocation(CommandIdentityGet, IdentityGetCommand{AccountId: accountId}, strconv.Itoa(i+1))
}
- cmd, err := j.request(session, logger, calls...)
+ cmd, err := j.request(session, logger, NS_IDENTITY, calls...)
if err != nil {
return IdentitiesAndMailboxesGetResponse{}, "", "", "", err
}
@@ -95,7 +97,7 @@ func (j *Client) GetIdentitiesAndMailboxes(mailboxAccountId string, accountIds [
func (j *Client) CreateIdentity(accountId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, identity Identity) (Identity, SessionState, State, Language, Error) {
logger = j.logger("CreateIdentity", session, logger)
- cmd, err := j.request(session, logger, invocation(CommandIdentitySet, IdentitySetCommand{
+ cmd, err := j.request(session, logger, NS_IDENTITY, invocation(CommandIdentitySet, IdentitySetCommand{
AccountId: accountId,
Create: map[string]Identity{
"c": identity,
@@ -121,7 +123,7 @@ func (j *Client) CreateIdentity(accountId string, session *Session, ctx context.
func (j *Client) UpdateIdentity(accountId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, identity Identity) (Identity, SessionState, State, Language, Error) {
logger = j.logger("UpdateIdentity", session, logger)
- cmd, err := j.request(session, logger, invocation(CommandIdentitySet, IdentitySetCommand{
+ cmd, err := j.request(session, logger, NS_IDENTITY, invocation(CommandIdentitySet, IdentitySetCommand{
AccountId: accountId,
Update: map[string]PatchObject{
"c": identity.AsPatch(),
@@ -147,7 +149,7 @@ func (j *Client) UpdateIdentity(accountId string, session *Session, ctx context.
func (j *Client) DeleteIdentity(accountId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, ids []string) ([]string, SessionState, State, Language, Error) {
logger = j.logger("DeleteIdentity", session, logger)
- cmd, err := j.request(session, logger, invocation(CommandIdentitySet, IdentitySetCommand{
+ cmd, err := j.request(session, logger, NS_IDENTITY, invocation(CommandIdentitySet, IdentitySetCommand{
AccountId: accountId,
Destroy: ids,
}, "0"))
@@ -168,3 +170,48 @@ func (j *Client) DeleteIdentity(accountId string, session *Session, ctx context.
return response.Destroyed, response.NewState, nil
})
}
+
+type IdentityChanges struct {
+ OldState State `json:"oldState,omitempty"`
+ NewState State `json:"newState"`
+ HasMoreChanges bool `json:"hasMoreChanges"`
+ Created []Identity `json:"created,omitempty"`
+ Updated []Identity `json:"updated,omitempty"`
+ Destroyed []string `json:"destroyed,omitempty"`
+}
+
+func (j *Client) GetIdentityChanges(accountId string, session *Session, ctx context.Context, logger *log.Logger,
+ acceptLanguage string, sinceState State, maxChanges uint) (IdentityChanges, SessionState, State, Language, Error) {
+ return changesTemplate(j, "GetIdentityChanges", NS_IDENTITY,
+ CommandIdentityChanges, CommandIdentityGet,
+ func() IdentityChangesCommand {
+ return IdentityChangesCommand{AccountId: accountId, SinceState: sinceState, MaxChanges: posUIntPtr(maxChanges)}
+ },
+ func(path string, rof string) IdentityGetRefCommand {
+ return IdentityGetRefCommand{
+ AccountId: accountId,
+ IdsRef: &ResultReference{
+ Name: CommandIdentityChanges,
+ Path: path,
+ ResultOf: rof,
+ },
+ }
+ },
+ func(resp IdentityChangesResponse) (State, State, bool, []string) {
+ return resp.OldState, resp.NewState, resp.HasMoreChanges, resp.Destroyed
+ },
+ func(resp IdentityGetResponse) []Identity { return resp.List },
+ func(oldState, newState State, hasMoreChanges bool, created, updated []Identity, destroyed []string) IdentityChanges {
+ return IdentityChanges{
+ OldState: oldState,
+ NewState: newState,
+ HasMoreChanges: hasMoreChanges,
+ Created: created,
+ Updated: updated,
+ Destroyed: destroyed,
+ }
+ },
+ func(resp IdentityGetResponse) State { return resp.State },
+ session, ctx, logger, acceptLanguage,
+ )
+}
diff --git a/pkg/jmap/api_mailbox.go b/pkg/jmap/api_mailbox.go
index 867bdd2521..58426179c4 100644
--- a/pkg/jmap/api_mailbox.go
+++ b/pkg/jmap/api_mailbox.go
@@ -9,13 +9,15 @@ import (
"github.com/opencloud-eu/opencloud/pkg/structs"
)
+var NS_MAILBOX = ns(JmapMail)
+
type MailboxesResponse struct {
Mailboxes []Mailbox `json:"mailboxes"`
NotFound []any `json:"notFound"`
}
func (j *Client) GetMailbox(accountId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, ids []string) (MailboxesResponse, SessionState, State, Language, Error) {
- return getTemplate(j, "GetMailbox", CommandCalendarGet,
+ return getTemplate(j, "GetMailbox", NS_MAILBOX, CommandCalendarGet,
func(accountId string, ids []string) MailboxGetCommand {
return MailboxGetCommand{AccountId: accountId, Ids: ids}
},
@@ -31,7 +33,7 @@ func (j *Client) GetMailbox(accountId string, session *Session, ctx context.Cont
}
func (j *Client) GetAllMailboxes(accountIds []string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string) (map[string][]Mailbox, SessionState, State, Language, Error) {
- return getTemplateN(j, "GetAllMailboxes", CommandCalendarGet,
+ return getTemplateN(j, "GetAllMailboxes", NS_MAILBOX, CommandCalendarGet,
func(accountId string, ids []string) MailboxGetCommand {
return MailboxGetCommand{AccountId: accountId}
},
@@ -59,7 +61,7 @@ func (j *Client) SearchMailboxes(accountIds []string, session *Session, ctx cont
},
}, mcid(accountId, "1"))
}
- cmd, err := j.request(session, logger, invocations...)
+ cmd, err := j.request(session, logger, NS_MAILBOX, invocations...)
if err != nil {
return nil, "", "", "", err
}
@@ -92,7 +94,7 @@ func (j *Client) SearchMailboxIdsPerRole(accountIds []string, session *Session,
invocations[i*len(roles)+j] = invocation(CommandMailboxQuery, MailboxQueryCommand{AccountId: accountId, Filter: MailboxFilterCondition{Role: role}}, mcid(accountId, role))
}
}
- cmd, err := j.request(session, logger, invocations...)
+ cmd, err := j.request(session, logger, NS_MAILBOX, invocations...)
if err != nil {
return nil, "", "", "", err
}
@@ -144,7 +146,7 @@ func newMailboxChanges(oldState, newState State, hasMoreChanges bool, created, u
// 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 State, maxChanges uint) (MailboxChanges, SessionState, State, Language, Error) {
- return changesTemplate(j, "GetMailboxChanges",
+ return changesTemplate(j, "GetMailboxChanges", NS_MAILBOX,
CommandMailboxChanges, CommandMailboxGet,
func() MailboxChangesCommand {
return MailboxChangesCommand{AccountId: accountId, SinceState: sinceState, MaxChanges: posUIntPtr(maxChanges)}
@@ -171,7 +173,7 @@ func (j *Client) GetMailboxChanges(accountId string, session *Session, ctx conte
// Retrieve Mailbox changes of multiple Accounts.
func (j *Client) GetMailboxChangesForMultipleAccounts(accountIds []string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, sinceStateMap map[string]State, maxChanges uint) (map[string]MailboxChanges, SessionState, State, Language, Error) { //NOSONAR
- return changesTemplateN(j, "GetMailboxChangesForMultipleAccounts",
+ return changesTemplateN(j, "GetMailboxChangesForMultipleAccounts", NS_MAILBOX,
accountIds, sinceStateMap, CommandMailboxChanges, CommandMailboxGet,
func(accountId string, state State) MailboxChangesCommand {
return MailboxChangesCommand{AccountId: accountId, SinceState: state, MaxChanges: posUIntPtr(maxChanges)}
@@ -188,91 +190,6 @@ func (j *Client) GetMailboxChangesForMultipleAccounts(accountIds []string, sessi
func(resp MailboxGetResponse) State { return resp.State },
session, ctx, logger, acceptLanguage,
)
-
- /*
- 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.Dict(logSinceState, sinceStateLogDict)
- })
-
- uniqueAccountIds := structs.Uniq(accountIds)
- n := len(uniqueAccountIds)
- if n < 1 {
- return map[string]MailboxChanges{}, "", "", "", nil
- }
-
- invocations := make([]Invocation, n*3)
- for i, accountId := range uniqueAccountIds {
- changes := MailboxChangesCommand{
- AccountId: accountId,
- }
-
- sinceState, ok := sinceStateMap[accountId]
- if ok {
- changes.SinceState = sinceState
- }
-
- if maxChanges > 0 {
- changes.MaxChanges = &maxChanges
- }
-
- getCreated := MailboxGetRefCommand{
- AccountId: accountId,
- IdsRef: &ResultReference{Name: CommandMailboxChanges, Path: "/created", ResultOf: mcid(accountId, "0")},
- }
- 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(CommandMailboxGet, getCreated, mcid(accountId, "1"))
- invocations[i*3+2] = invocation(CommandMailboxGet, getUpdated, mcid(accountId, "2"))
- }
-
- cmd, err := j.request(session, logger, invocations...)
- if err != nil {
- return nil, "", "", "", err
- }
-
- return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (map[string]MailboxChanges, State, Error) {
- resp := make(map[string]MailboxChanges, n)
- stateByAccountId := make(map[string]State, n)
- for _, accountId := range uniqueAccountIds {
- var mailboxResponse MailboxChangesResponse
- err = retrieveResponseMatchParameters(logger, body, CommandMailboxChanges, mcid(accountId, "0"), &mailboxResponse)
- if err != nil {
- return nil, "", err
- }
-
- var createdResponse MailboxGetResponse
- err = retrieveResponseMatchParameters(logger, body, CommandMailboxGet, mcid(accountId, "1"), &createdResponse)
- if err != nil {
- return nil, "", err
- }
-
- var updatedResponse MailboxGetResponse
- err = retrieveResponseMatchParameters(logger, body, CommandMailboxGet, mcid(accountId, "2"), &updatedResponse)
- if err != nil {
- return nil, "", err
- }
-
- resp[accountId] = MailboxChanges{
- Destroyed: mailboxResponse.Destroyed,
- HasMoreChanges: mailboxResponse.HasMoreChanges,
- NewState: mailboxResponse.NewState,
- Created: createdResponse.List,
- Updated: updatedResponse.List,
- }
- stateByAccountId[accountId] = createdResponse.State
- }
-
- return resp, squashState(stateByAccountId), nil
- })
- */
}
func (j *Client) GetMailboxRolesForMultipleAccounts(accountIds []string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string) (map[string][]string, SessionState, State, Language, Error) {
@@ -304,7 +221,7 @@ func (j *Client) GetMailboxRolesForMultipleAccounts(accountIds []string, session
}, mcid(accountId, "1"))
}
- cmd, err := j.request(session, logger, invocations...)
+ cmd, err := j.request(session, logger, NS_MAILBOX, invocations...)
if err != nil {
return nil, "", "", "", err
}
@@ -349,7 +266,7 @@ func (j *Client) GetInboxNameForMultipleAccounts(accountIds []string, session *S
}, mcid(accountId, "0"))
}
- cmd, err := j.request(session, logger, invocations...)
+ cmd, err := j.request(session, logger, NS_MAILBOX, invocations...)
if err != nil {
return nil, "", "", "", err
}
@@ -381,7 +298,7 @@ func (j *Client) GetInboxNameForMultipleAccounts(accountIds []string, session *S
func (j *Client) UpdateMailbox(accountId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, mailboxId string, ifInState string, update MailboxChange) (Mailbox, SessionState, State, Language, Error) { //NOSONAR
logger = j.logger("UpdateMailbox", session, logger)
- cmd, err := j.request(session, logger, invocation(CommandMailboxSet, MailboxSetCommand{
+ cmd, err := j.request(session, logger, NS_MAILBOX, invocation(CommandMailboxSet, MailboxSetCommand{
AccountId: accountId,
IfInState: ifInState,
Update: map[string]PatchObject{
@@ -409,7 +326,7 @@ func (j *Client) UpdateMailbox(accountId string, session *Session, ctx context.C
func (j *Client) CreateMailbox(accountId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, ifInState string, create MailboxChange) (Mailbox, SessionState, State, Language, Error) {
logger = j.logger("CreateMailbox", session, logger)
- cmd, err := j.request(session, logger, invocation(CommandMailboxSet, MailboxSetCommand{
+ cmd, err := j.request(session, logger, NS_MAILBOX, invocation(CommandMailboxSet, MailboxSetCommand{
AccountId: accountId,
IfInState: ifInState,
Create: map[string]MailboxChange{
@@ -441,7 +358,7 @@ func (j *Client) CreateMailbox(accountId string, session *Session, ctx context.C
func (j *Client) DeleteMailboxes(accountId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, ifInState string, mailboxIds []string) ([]string, SessionState, State, Language, Error) {
logger = j.logger("DeleteMailbox", session, logger)
- cmd, err := j.request(session, logger, invocation(CommandMailboxSet, MailboxSetCommand{
+ cmd, err := j.request(session, logger, NS_MAILBOX, invocation(CommandMailboxSet, MailboxSetCommand{
AccountId: accountId,
IfInState: ifInState,
Destroy: mailboxIds,
diff --git a/pkg/jmap/api_objects.go b/pkg/jmap/api_objects.go
index 6ab76a0cec..9f507256da 100644
--- a/pkg/jmap/api_objects.go
+++ b/pkg/jmap/api_objects.go
@@ -6,19 +6,26 @@ import (
"github.com/opencloud-eu/opencloud/pkg/log"
)
+var NS_OBJECTS = ns(JmapMail, JmapSubmission, JmapContacts, JmapCalendars, JmapQuota)
+
type Objects struct {
- Mailboxes *MailboxGetResponse `json:"mailboxes,omitempty"`
- Emails *EmailGetResponse `json:"emails,omitempty"`
- Calendars *CalendarGetResponse `json:"calendars,omitempty"`
- Events *CalendarEventGetResponse `json:"events,omitempty"`
- Addressbooks *AddressBookGetResponse `json:"addressbooks,omitempty"`
- Contacts *ContactCardGetResponse `json:"contacts,omitempty"`
+ Mailboxes *MailboxGetResponse `json:"mailboxes,omitempty"`
+ Emails *EmailGetResponse `json:"emails,omitempty"`
+ Calendars *CalendarGetResponse `json:"calendars,omitempty"`
+ Events *CalendarEventGetResponse `json:"events,omitempty"`
+ Addressbooks *AddressBookGetResponse `json:"addressbooks,omitempty"`
+ Contacts *ContactCardGetResponse `json:"contacts,omitempty"`
+ Quotas *QuotaGetResponse `json:"quotas,omitempty"`
+ Identities *IdentityGetResponse `json:"identities,omitempty"`
+ EmailSubmissions *EmailSubmissionGetResponse `json:"submissions,omitempty"`
}
func (j *Client) GetObjects(accountId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, //NOSONAR
mailboxIds []string, emailIds []string,
addressbookIds []string, contactIds []string,
calendarIds []string, eventIds []string,
+ quotaIds []string, identityIds []string,
+ emailSubmissionIds []string,
) (Objects, SessionState, State, Language, Error) {
l := j.logger("GetObjects", session, logger).With()
if len(mailboxIds) > 0 {
@@ -39,6 +46,15 @@ func (j *Client) GetObjects(accountId string, session *Session, ctx context.Cont
if len(eventIds) > 0 {
l = l.Array("eventIds", log.SafeStringArray(eventIds))
}
+ if len(quotaIds) > 0 {
+ l = l.Array("quotaIds", log.SafeStringArray(quotaIds))
+ }
+ if len(identityIds) > 0 {
+ l = l.Array("identityIds", log.SafeStringArray(identityIds))
+ }
+ if len(emailSubmissionIds) > 0 {
+ l = l.Array("emailSubmissionIds", log.SafeStringArray(emailSubmissionIds))
+ }
logger = log.From(l)
methodCalls := []Invocation{}
@@ -60,20 +76,31 @@ func (j *Client) GetObjects(accountId string, session *Session, ctx context.Cont
if len(eventIds) > 0 {
methodCalls = append(methodCalls, invocation(CommandCalendarEventGet, CalendarEventGetCommand{AccountId: accountId, Ids: eventIds}, "events"))
}
+ if len(quotaIds) > 0 {
+ methodCalls = append(methodCalls, invocation(CommandQuotaGet, QuotaGetCommand{AccountId: accountId, Ids: quotaIds}, "quotas"))
+ }
+ if len(identityIds) > 0 {
+ methodCalls = append(methodCalls, invocation(CommandIdentityGet, IdentityGetCommand{AccountId: accountId, Ids: identityIds}, "identities"))
+ }
+ if len(emailSubmissionIds) > 0 {
+ methodCalls = append(methodCalls, invocation(CommandEmailSubmissionGet, EmailSubmissionGetCommand{AccountId: accountId, Ids: emailSubmissionIds}, "emailSubmissionIds"))
+ }
- cmd, err := j.request(session, logger, methodCalls...)
+ cmd, err := j.request(session, logger, NS_OBJECTS, methodCalls...)
if err != nil {
return Objects{}, "", "", "", err
}
return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (Objects, State, Error) {
objs := Objects{}
+ states := map[string]State{}
var mailboxes MailboxGetResponse
if ok, err := tryRetrieveResponseMatchParameters(logger, body, CommandMailboxGet, "mailboxes", &mailboxes); err != nil {
return Objects{}, "", err
} else if ok {
objs.Mailboxes = &mailboxes
+ states["mailbox"] = mailboxes.State
}
var emails EmailGetResponse
@@ -81,6 +108,7 @@ func (j *Client) GetObjects(accountId string, session *Session, ctx context.Cont
return Objects{}, "", err
} else if ok {
objs.Emails = &emails
+ states["email"] = emails.State
}
var calendars CalendarGetResponse
@@ -88,6 +116,7 @@ func (j *Client) GetObjects(accountId string, session *Session, ctx context.Cont
return Objects{}, "", err
} else if ok {
objs.Calendars = &calendars
+ states["calendar"] = calendars.State
}
var events CalendarEventGetResponse
@@ -95,6 +124,7 @@ func (j *Client) GetObjects(accountId string, session *Session, ctx context.Cont
return Objects{}, "", err
} else if ok {
objs.Events = &events
+ states["event"] = events.State
}
var addressbooks AddressBookGetResponse
@@ -102,6 +132,7 @@ func (j *Client) GetObjects(accountId string, session *Session, ctx context.Cont
return Objects{}, "", err
} else if ok {
objs.Addressbooks = &addressbooks
+ states["addressbook"] = addressbooks.State
}
var contacts ContactCardGetResponse
@@ -109,10 +140,33 @@ func (j *Client) GetObjects(accountId string, session *Session, ctx context.Cont
return Objects{}, "", err
} else if ok {
objs.Contacts = &contacts
+ states["contact"] = contacts.State
}
- state := squashStates(mailboxes.State, emails.State, calendars.State, events.State, addressbooks.State, contacts.State)
+ var quotas QuotaGetResponse
+ if ok, err := tryRetrieveResponseMatchParameters(logger, body, CommandQuotaGet, "quotas", "as); err != nil {
+ return Objects{}, "", err
+ } else if ok {
+ objs.Quotas = "as
+ states["quota"] = quotas.State
+ }
- return objs, state, nil
+ var identities IdentityGetResponse
+ if ok, err := tryRetrieveResponseMatchParameters(logger, body, CommandIdentityGet, "identities", &identities); err != nil {
+ return Objects{}, "", err
+ } else if ok {
+ objs.Identities = &identities
+ states["identity"] = identities.State
+ }
+
+ var submissions EmailSubmissionGetResponse
+ if ok, err := tryRetrieveResponseMatchParameters(logger, body, CommandEmailSubmissionGet, "submissions", &submissions); err != nil {
+ return Objects{}, "", err
+ } else if ok {
+ objs.EmailSubmissions = &submissions
+ states["submissions"] = submissions.State
+ }
+
+ return objs, squashKeyedStates(states), nil
})
}
diff --git a/pkg/jmap/api_quota.go b/pkg/jmap/api_quota.go
index 0f137650ef..367d152384 100644
--- a/pkg/jmap/api_quota.go
+++ b/pkg/jmap/api_quota.go
@@ -6,8 +6,10 @@ import (
"github.com/opencloud-eu/opencloud/pkg/log"
)
+var NS_QUOTA = ns(JmapQuota)
+
func (j *Client) GetQuotas(accountIds []string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string) (map[string]QuotaGetResponse, SessionState, State, Language, Error) {
- return getTemplateN(j, "GetQuotas", CommandQuotaGet,
+ return getTemplateN(j, "GetQuotas", NS_QUOTA, CommandQuotaGet,
func(accountId string, ids []string) QuotaGetCommand {
return QuotaGetCommand{AccountId: accountId}
},
@@ -17,3 +19,87 @@ func (j *Client) GetQuotas(accountIds []string, session *Session, ctx context.Co
accountIds, session, ctx, logger, acceptLanguage, []string{},
)
}
+
+type QuotaChanges struct {
+ OldState State `json:"oldState,omitempty"`
+ NewState State `json:"newState"`
+ HasMoreChanges bool `json:"hasMoreChanges"`
+ Created []Quota `json:"created,omitempty"`
+ Updated []Quota `json:"updated,omitempty"`
+ Destroyed []string `json:"destroyed,omitempty"`
+}
+
+func (j *Client) GetQuotaChanges(accountId string, session *Session, ctx context.Context, logger *log.Logger,
+ acceptLanguage string, sinceState State, maxChanges uint) (QuotaChanges, SessionState, State, Language, Error) {
+ return changesTemplate(j, "GetQuotaChanges", NS_QUOTA,
+ CommandQuotaChanges, CommandQuotaGet,
+ func() QuotaChangesCommand {
+ return QuotaChangesCommand{AccountId: accountId, SinceState: sinceState, MaxChanges: posUIntPtr(maxChanges)}
+ },
+ func(path string, rof string) QuotaGetRefCommand {
+ return QuotaGetRefCommand{
+ AccountId: accountId,
+ IdsRef: &ResultReference{
+ Name: CommandQuotaChanges,
+ Path: path,
+ ResultOf: rof,
+ },
+ }
+ },
+ func(resp QuotaChangesResponse) (State, State, bool, []string) {
+ return resp.OldState, resp.NewState, resp.HasMoreChanges, resp.Destroyed
+ },
+ func(resp QuotaGetResponse) []Quota { return resp.List },
+ func(oldState, newState State, hasMoreChanges bool, created, updated []Quota, destroyed []string) QuotaChanges {
+ return QuotaChanges{
+ OldState: oldState,
+ NewState: newState,
+ HasMoreChanges: hasMoreChanges,
+ Created: created,
+ Updated: updated,
+ Destroyed: destroyed,
+ }
+ },
+ func(resp QuotaGetResponse) State { return resp.State },
+ session, ctx, logger, acceptLanguage,
+ )
+}
+
+func (j *Client) GetQuotaUsageChanges(accountId string, session *Session, ctx context.Context, logger *log.Logger,
+ acceptLanguage string, sinceState State, maxChanges uint) (QuotaChanges, SessionState, State, Language, Error) {
+ return updatedTemplate(j, "GetQuotaUsageChanges", NS_QUOTA,
+ CommandQuotaChanges, CommandQuotaGet,
+ func() QuotaChangesCommand {
+ return QuotaChangesCommand{AccountId: accountId, SinceState: sinceState, MaxChanges: posUIntPtr(maxChanges)}
+ },
+ func(path string, rof string) QuotaGetRefCommand {
+ return QuotaGetRefCommand{
+ AccountId: accountId,
+ IdsRef: &ResultReference{
+ Name: CommandQuotaChanges,
+ Path: path,
+ ResultOf: rof,
+ },
+ PropertiesRef: &ResultReference{
+ Name: CommandQuotaChanges,
+ Path: "/updatedProperties",
+ ResultOf: rof,
+ },
+ }
+ },
+ func(resp QuotaChangesResponse) (State, State, bool) {
+ return resp.OldState, resp.NewState, resp.HasMoreChanges
+ },
+ func(resp QuotaGetResponse) []Quota { return resp.List },
+ func(oldState, newState State, hasMoreChanges bool, updated []Quota) QuotaChanges {
+ return QuotaChanges{
+ OldState: oldState,
+ NewState: newState,
+ HasMoreChanges: hasMoreChanges,
+ Updated: updated,
+ }
+ },
+ func(resp QuotaGetResponse) State { return resp.State },
+ session, ctx, logger, acceptLanguage,
+ )
+}
diff --git a/pkg/jmap/api_vacation.go b/pkg/jmap/api_vacation.go
index 8b0073c373..8eeeae75f4 100644
--- a/pkg/jmap/api_vacation.go
+++ b/pkg/jmap/api_vacation.go
@@ -8,12 +8,14 @@ import (
"github.com/opencloud-eu/opencloud/pkg/log"
)
+var NS_VACATION = ns(JmapVacationResponse)
+
const (
vacationResponseId = "singleton"
)
func (j *Client) GetVacationResponse(accountId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string) (VacationResponseGetResponse, SessionState, State, Language, Error) {
- return getTemplate(j, "GetVacationResponse", CommandVacationResponseGet,
+ return getTemplate(j, "GetVacationResponse", NS_VACATION, CommandVacationResponseGet,
func(accountId string, ids []string) VacationResponseGetCommand {
return VacationResponseGetCommand{AccountId: accountId}
},
@@ -50,7 +52,7 @@ type VacationResponsePayload struct {
func (j *Client) SetVacationResponse(accountId string, vacation VacationResponsePayload, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string) (VacationResponse, SessionState, State, Language, Error) {
logger = j.logger("SetVacationResponse", session, logger)
- cmd, err := j.request(session, logger,
+ cmd, err := j.request(session, logger, NS_VACATION,
invocation(CommandVacationResponseSet, VacationResponseSetCommand{
AccountId: accountId,
Create: map[string]VacationResponse{
diff --git a/pkg/jmap/client.go b/pkg/jmap/client.go
index d3814c0077..81b05bfccf 100644
--- a/pkg/jmap/client.go
+++ b/pkg/jmap/client.go
@@ -5,6 +5,7 @@ import (
"errors"
"io"
"net/url"
+ "slices"
"github.com/opencloud-eu/opencloud/pkg/log"
"github.com/rs/zerolog"
@@ -91,13 +92,20 @@ func (j *Client) maxCallsCheck(calls int, session *Session, logger *log.Logger)
// Construct a Request from the given list of Invocation objects.
//
// If an issue occurs, then it is logged prior to returning it.
-func (j *Client) request(session *Session, logger *log.Logger, methodCalls ...Invocation) (Request, Error) {
+func (j *Client) request(session *Session, logger *log.Logger, using []JmapNamespace, methodCalls ...Invocation) (Request, Error) {
err := j.maxCallsCheck(len(methodCalls), session, logger)
if err != nil {
return Request{}, err
}
+
+ if using == nil {
+ using = JmapNamespaces
+ }
+ if !slices.Contains(using, JmapCore) {
+ using = slices.Insert(using, 0, JmapCore)
+ }
return Request{
- Using: []string{JmapCore, JmapMail, JmapContacts},
+ Using: using,
MethodCalls: methodCalls,
CreatedIds: nil,
}, nil
diff --git a/pkg/jmap/integration_test.go b/pkg/jmap/integration_test.go
index ae720325ed..73fb53fffa 100644
--- a/pkg/jmap/integration_test.go
+++ b/pkg/jmap/integration_test.go
@@ -708,9 +708,9 @@ func (j *TestJmapClient) create(id string, objectType ObjectType, body map[strin
}).command(body)
}
-func (j *TestJmapClient) create1(accountId string, objectType ObjectType, ns string, obj map[string]any) (string, error) {
+func (j *TestJmapClient) create1(accountId string, objectType ObjectType, ns JmapNamespace, obj map[string]any) (string, error) {
body := map[string]any{
- "using": []string{JmapCore, ns},
+ "using": []string{string(JmapCore), string(ns)},
"methodCalls": []any{
[]any{
objectType + "/set",
@@ -727,11 +727,11 @@ func (j *TestJmapClient) create1(accountId string, objectType ObjectType, ns str
return j.create("c", objectType, body)
}
-func (j *TestJmapClient) objectsById(accountId string, objectType ObjectType, scope string) (map[string]map[string]any, error) {
+func (j *TestJmapClient) objectsById(accountId string, objectType ObjectType, scope JmapNamespace) (map[string]map[string]any, error) {
m := map[string]map[string]any{}
{
body := map[string]any{
- "using": []string{JmapCore, scope},
+ "using": []string{string(JmapCore), string(scope)},
"methodCalls": []any{
[]any{
objectType + "/get",
diff --git a/pkg/jmap/model.go b/pkg/jmap/model.go
index a85bda3378..7a9efaf715 100644
--- a/pkg/jmap/model.go
+++ b/pkg/jmap/model.go
@@ -89,26 +89,28 @@ type DispositionTypeOption string
type Duration string
+type JmapNamespace string
+
const (
- JmapCore = "urn:ietf:params:jmap:core"
- JmapMail = "urn:ietf:params:jmap:mail"
- JmapMDN = "urn:ietf:params:jmap:mdn" // https://datatracker.ietf.org/doc/rfc9007/
- JmapSubmission = "urn:ietf:params:jmap:submission"
- JmapVacationResponse = "urn:ietf:params:jmap:vacationresponse"
- JmapCalendars = "urn:ietf:params:jmap:calendars"
- JmapContacts = "urn:ietf:params:jmap:contacts"
- JmapSieve = "urn:ietf:params:jmap:sieve"
- JmapBlob = "urn:ietf:params:jmap:blob"
- JmapQuota = "urn:ietf:params:jmap:quota"
- JmapWebsocket = "urn:ietf:params:jmap:websocket" // #nosec G101 false positive: these are not credentials
- JmapPrincipals = "urn:ietf:params:jmap:principals"
- JmapPrincipalsOwner = "urn:ietf:params:jmap:principals:owner"
- JmapTasks = "urn:ietf:params:jmap:tasks"
- JmapTasksRecurrences = "urn:ietf:params:jmap:tasks:recurrences"
- JmapTasksAssignees = "urn:ietf:params:jmap:tasks:assignees"
- JmapTasksAlerts = "urn:ietf:params:jmap:tasks:alerts"
- JmapTasksMultilingual = "urn:ietf:params:jmap:tasks:multilingual"
- JmapTasksCustomTimezones = "urn:ietf:params:jmap:tasks:customtimezones"
+ JmapCore = JmapNamespace("urn:ietf:params:jmap:core")
+ JmapMail = JmapNamespace("urn:ietf:params:jmap:mail")
+ JmapMDN = JmapNamespace("urn:ietf:params:jmap:mdn") // https://datatracker.ietf.org/doc/rfc9007/
+ JmapSubmission = JmapNamespace("urn:ietf:params:jmap:submission")
+ JmapVacationResponse = JmapNamespace("urn:ietf:params:jmap:vacationresponse")
+ JmapCalendars = JmapNamespace("urn:ietf:params:jmap:calendars")
+ JmapContacts = JmapNamespace("urn:ietf:params:jmap:contacts")
+ JmapSieve = JmapNamespace("urn:ietf:params:jmap:sieve")
+ JmapBlob = JmapNamespace("urn:ietf:params:jmap:blob")
+ JmapQuota = JmapNamespace("urn:ietf:params:jmap:quota")
+ JmapWebsocket = JmapNamespace("urn:ietf:params:jmap:websocket") // #nosec G101 false positive: these are not credentials
+ JmapPrincipals = JmapNamespace("urn:ietf:params:jmap:principals")
+ JmapPrincipalsOwner = JmapNamespace("urn:ietf:params:jmap:principals:owner")
+ JmapTasks = JmapNamespace("urn:ietf:params:jmap:tasks")
+ JmapTasksRecurrences = JmapNamespace("urn:ietf:params:jmap:tasks:recurrences")
+ JmapTasksAssignees = JmapNamespace("urn:ietf:params:jmap:tasks:assignees")
+ JmapTasksAlerts = JmapNamespace("urn:ietf:params:jmap:tasks:alerts")
+ JmapTasksMultilingual = JmapNamespace("urn:ietf:params:jmap:tasks:multilingual")
+ JmapTasksCustomTimezones = JmapNamespace("urn:ietf:params:jmap:tasks:customtimezones")
CoreType = ObjectType("Core")
PushSubscriptionType = ObjectType("PushSubscription")
@@ -260,6 +262,28 @@ const (
)
var (
+ JmapNamespaces = []JmapNamespace{
+ JmapCore,
+ JmapMail,
+ JmapMDN,
+ JmapSubmission,
+ JmapVacationResponse,
+ JmapCalendars,
+ JmapContacts,
+ JmapSieve,
+ JmapBlob,
+ JmapQuota,
+ JmapWebsocket,
+ JmapPrincipals,
+ JmapPrincipalsOwner,
+ JmapTasks,
+ JmapTasksRecurrences,
+ JmapTasksAssignees,
+ JmapTasksAlerts,
+ JmapTasksMultilingual,
+ JmapTasksCustomTimezones,
+ }
+
ObjectTypes = []ObjectType{
CoreType,
PushSubscriptionType,
@@ -1334,29 +1358,6 @@ type MailboxSetResponse struct {
NotDestroyed map[string]SetError `json:"notDestroyed,omitempty"`
}
-type MailboxChangesCommand 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 Mailbox/get response.
- //
- // The server will return the changes that have occurred since this state.
- SinceState State `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,omitzero"`
-}
-
type MailboxFilterElement interface {
_isAMailboxFilterElement() // marker method
}
@@ -2490,7 +2491,7 @@ type EmailSubmissionGetRefCommand struct {
//
// If null, then all records of the data type are returned, if this is supported for that data
// type and the number of records does not exceed the maxObjectsInGet limit.
- IdRef *ResultReference `json:"#ids,omitempty"`
+ IdsRef *ResultReference `json:"#ids,omitempty"`
// If supplied, only the properties listed in the array are returned for each EmailSubmission object.
//
@@ -2532,6 +2533,54 @@ type EmailSubmissionGetResponse struct {
NotFound []string `json:"notFound,omitempty"`
}
+type EmailSubmissionChangesCommand 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 EmailSubmission/get response.
+ //
+ // The server will return the changes that have occurred since this state.
+ SinceState State `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,omitzero"`
+}
+
+type EmailSubmissionChangesResponse 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 EmailSubmission/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"`
+}
+
// Patch Object.
//
// Example:
@@ -2643,7 +2692,7 @@ type Request struct {
// The client MAY include capability identifiers even if the method calls it makes do not utilise those capabilities.
// The server advertises the set of specifications it supports in the Session object
// (see [Section 2](https://jmap.io/spec-core.html#the-jmap-session-resource)), as keys on the capabilities property.
- Using []string `json:"using"`
+ Using []JmapNamespace `json:"using"`
// An array of method calls to process on the server.
//
@@ -2815,6 +2864,29 @@ type MailboxGetResponse struct {
NotFound []any `json:"notFound"`
}
+type MailboxChangesCommand 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 Mailbox/get response.
+ //
+ // The server will return the changes that have occurred since this state.
+ SinceState State `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,omitzero"`
+}
+
type MailboxChangesResponse struct {
// The id of the account used for the call.
AccountId string `json:"accountId"`
@@ -3209,6 +3281,60 @@ type IdentityGetCommand struct {
Ids []string `json:"ids,omitempty"`
}
+type IdentityGetRefCommand struct {
+ AccountId string `json:"accountId"`
+ IdsRef *ResultReference `json:"#ids,omitempty"`
+ PropertiesRef *ResultReference `json:"#properties,omitempty"`
+}
+
+type IdentityChangesCommand 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 Mailbox/get response.
+ //
+ // The server will return the changes that have occurred since this state.
+ SinceState State `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,omitzero"`
+}
+
+type IdentityChangesResponse 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 Mailbox/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 IdentitySetCommand struct {
AccountId string `json:"accountId"`
IfInState string `json:"ifInState,omitempty"`
@@ -4948,6 +5074,12 @@ type QuotaGetCommand struct {
Ids []string `json:"ids,omitempty"`
}
+type QuotaGetRefCommand struct {
+ AccountId string `json:"accountId"`
+ IdsRef *ResultReference `json:"#ids,omitempty"`
+ PropertiesRef *ResultReference `json:"#properties,omitempty"`
+}
+
type QuotaGetResponse struct {
AccountId string `json:"accountId"`
State State `json:"state,omitempty"`
@@ -4955,6 +5087,52 @@ type QuotaGetResponse struct {
NotFound []string `json:"notFound,omitempty"`
}
+type QuotaChangesCommand 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 "Quota/get" response.
+ // The server will return the changes that have occurred since this state.
+ SinceState State `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"`
+
+ // If only the "used" Quota property has changed since the old state, this will be a list containing only that property.
+ //
+ // If the server is unable to tell if only "used" has changed, it MUST be null.
+ UpdatedProperties []string `json:"updatedProperties,omitempty"`
+}
+
+type QuotaChangesResponse 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 "Quota/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 AddressBookGetCommand struct {
AccountId string `json:"accountId"`
Ids []string `json:"ids,omitempty"`
@@ -6067,74 +6245,80 @@ type ErrorResponse struct {
}
const (
- ErrorCommand Command = "error" // only occurs in responses
- CommandBlobGet Command = "Blob/get"
- CommandBlobUpload Command = "Blob/upload"
- CommandEmailGet Command = "Email/get"
- CommandEmailQuery Command = "Email/query"
- CommandEmailChanges Command = "Email/changes"
- CommandEmailSet Command = "Email/set"
- CommandEmailImport Command = "Email/import"
- CommandEmailSubmissionGet Command = "EmailSubmission/get"
- CommandEmailSubmissionSet Command = "EmailSubmission/set"
- CommandThreadGet Command = "Thread/get"
- CommandMailboxGet Command = "Mailbox/get"
- CommandMailboxSet Command = "Mailbox/set"
- CommandMailboxQuery Command = "Mailbox/query"
- CommandMailboxChanges Command = "Mailbox/changes"
- CommandIdentityGet Command = "Identity/get"
- CommandIdentitySet Command = "Identity/set"
- CommandVacationResponseGet Command = "VacationResponse/get"
- CommandVacationResponseSet Command = "VacationResponse/set"
- CommandSearchSnippetGet Command = "SearchSnippet/get"
- CommandQuotaGet Command = "Quota/get"
- CommandAddressBookGet Command = "AddressBook/get"
- CommandAddressBookChanges Command = "AddressBook/changes"
- CommandContactCardQuery Command = "ContactCard/query"
- CommandContactCardGet Command = "ContactCard/get"
- CommandContactCardChanges Command = "ContactCard/changes"
- CommandContactCardSet Command = "ContactCard/set"
- CommandCalendarEventParse Command = "CalendarEvent/parse"
- CommandCalendarGet Command = "Calendar/get"
- CommandCalendarChanges Command = "Calendar/changes"
- CommandCalendarEventQuery Command = "CalendarEvent/query"
- CommandCalendarEventGet Command = "CalendarEvent/get"
- CommandCalendarEventSet Command = "CalendarEvent/set"
- CommandCalendarEventChanges Command = "CalendarEvent/changes"
+ ErrorCommand Command = "error" // only occurs in responses
+ CommandBlobGet Command = "Blob/get"
+ CommandBlobUpload Command = "Blob/upload"
+ CommandEmailGet Command = "Email/get"
+ CommandEmailQuery Command = "Email/query"
+ CommandEmailChanges Command = "Email/changes"
+ CommandEmailSet Command = "Email/set"
+ CommandEmailImport Command = "Email/import"
+ CommandEmailSubmissionGet Command = "EmailSubmission/get"
+ CommandEmailSubmissionSet Command = "EmailSubmission/set"
+ CommandEmailSubmissionChanges Command = "EmailSubmission/changes"
+ CommandThreadGet Command = "Thread/get"
+ CommandMailboxGet Command = "Mailbox/get"
+ CommandMailboxSet Command = "Mailbox/set"
+ CommandMailboxQuery Command = "Mailbox/query"
+ CommandMailboxChanges Command = "Mailbox/changes"
+ CommandIdentityGet Command = "Identity/get"
+ CommandIdentitySet Command = "Identity/set"
+ CommandIdentityChanges Command = "Identity/changes"
+ CommandVacationResponseGet Command = "VacationResponse/get"
+ CommandVacationResponseSet Command = "VacationResponse/set"
+ CommandSearchSnippetGet Command = "SearchSnippet/get"
+ CommandQuotaGet Command = "Quota/get"
+ CommandQuotaChanges Command = "Quota/changes"
+ CommandAddressBookGet Command = "AddressBook/get"
+ CommandAddressBookChanges Command = "AddressBook/changes"
+ CommandContactCardQuery Command = "ContactCard/query"
+ CommandContactCardGet Command = "ContactCard/get"
+ CommandContactCardChanges Command = "ContactCard/changes"
+ CommandContactCardSet Command = "ContactCard/set"
+ CommandCalendarEventParse Command = "CalendarEvent/parse"
+ CommandCalendarGet Command = "Calendar/get"
+ CommandCalendarChanges Command = "Calendar/changes"
+ CommandCalendarEventQuery Command = "CalendarEvent/query"
+ CommandCalendarEventGet Command = "CalendarEvent/get"
+ CommandCalendarEventSet Command = "CalendarEvent/set"
+ CommandCalendarEventChanges Command = "CalendarEvent/changes"
)
var CommandResponseTypeMap = map[Command]func() any{
- ErrorCommand: func() any { return ErrorResponse{} },
- CommandBlobGet: func() any { return BlobGetResponse{} },
- CommandBlobUpload: func() any { return BlobUploadResponse{} },
- CommandMailboxQuery: func() any { return MailboxQueryResponse{} },
- CommandMailboxGet: func() any { return MailboxGetResponse{} },
- CommandMailboxSet: func() any { return MailboxSetResponse{} },
- CommandMailboxChanges: func() any { return MailboxChangesResponse{} },
- CommandEmailQuery: func() any { return EmailQueryResponse{} },
- CommandEmailChanges: func() any { return EmailChangesResponse{} },
- CommandEmailGet: func() any { return EmailGetResponse{} },
- CommandEmailSet: func() any { return EmailSetResponse{} },
- CommandEmailSubmissionGet: func() any { return EmailSubmissionGetResponse{} },
- CommandEmailSubmissionSet: func() any { return EmailSubmissionSetResponse{} },
- CommandThreadGet: func() any { return ThreadGetResponse{} },
- CommandIdentityGet: func() any { return IdentityGetResponse{} },
- CommandIdentitySet: func() any { return IdentitySetResponse{} },
- CommandVacationResponseGet: func() any { return VacationResponseGetResponse{} },
- CommandVacationResponseSet: func() any { return VacationResponseSetResponse{} },
- CommandSearchSnippetGet: func() any { return SearchSnippetGetResponse{} },
- CommandQuotaGet: func() any { return QuotaGetResponse{} },
- CommandAddressBookGet: func() any { return AddressBookGetResponse{} },
- CommandAddressBookChanges: func() any { return AddressBookChangesResponse{} },
- 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{} },
- CommandCalendarChanges: func() any { return CalendarChangesResponse{} },
- CommandCalendarEventQuery: func() any { return CalendarEventQueryResponse{} },
- CommandCalendarEventGet: func() any { return CalendarEventGetResponse{} },
- CommandCalendarEventSet: func() any { return CalendarEventSetResponse{} },
- CommandCalendarEventChanges: func() any { return CalendarEventChangesResponse{} },
+ ErrorCommand: func() any { return ErrorResponse{} },
+ CommandBlobGet: func() any { return BlobGetResponse{} },
+ CommandBlobUpload: func() any { return BlobUploadResponse{} },
+ CommandMailboxQuery: func() any { return MailboxQueryResponse{} },
+ CommandMailboxGet: func() any { return MailboxGetResponse{} },
+ CommandMailboxSet: func() any { return MailboxSetResponse{} },
+ CommandMailboxChanges: func() any { return MailboxChangesResponse{} },
+ CommandEmailQuery: func() any { return EmailQueryResponse{} },
+ CommandEmailChanges: func() any { return EmailChangesResponse{} },
+ CommandEmailGet: func() any { return EmailGetResponse{} },
+ CommandEmailSet: func() any { return EmailSetResponse{} },
+ CommandEmailSubmissionGet: func() any { return EmailSubmissionGetResponse{} },
+ CommandEmailSubmissionSet: func() any { return EmailSubmissionSetResponse{} },
+ CommandEmailSubmissionChanges: func() any { return EmailSubmissionChangesResponse{} },
+ CommandThreadGet: func() any { return ThreadGetResponse{} },
+ CommandIdentityGet: func() any { return IdentityGetResponse{} },
+ CommandIdentityChanges: func() any { return IdentityChangesResponse{} },
+ CommandIdentitySet: func() any { return IdentitySetResponse{} },
+ CommandVacationResponseGet: func() any { return VacationResponseGetResponse{} },
+ CommandVacationResponseSet: func() any { return VacationResponseSetResponse{} },
+ CommandSearchSnippetGet: func() any { return SearchSnippetGetResponse{} },
+ CommandQuotaGet: func() any { return QuotaGetResponse{} },
+ CommandQuotaChanges: func() any { return QuotaChangesResponse{} },
+ CommandAddressBookGet: func() any { return AddressBookGetResponse{} },
+ CommandAddressBookChanges: func() any { return AddressBookChangesResponse{} },
+ 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{} },
+ CommandCalendarChanges: func() any { return CalendarChangesResponse{} },
+ CommandCalendarEventQuery: func() any { return CalendarEventQueryResponse{} },
+ CommandCalendarEventGet: func() any { return CalendarEventGetResponse{} },
+ CommandCalendarEventSet: func() any { return CalendarEventSetResponse{} },
+ CommandCalendarEventChanges: func() any { return CalendarEventChangesResponse{} },
}
diff --git a/pkg/jmap/templates.go b/pkg/jmap/templates.go
index 802f54ffd5..3fb02805c3 100644
--- a/pkg/jmap/templates.go
+++ b/pkg/jmap/templates.go
@@ -10,8 +10,8 @@ import (
)
func getTemplate[GETREQ any, GETRESP any, RESP any]( //NOSONAR
- client *Client, name string, getCommand Command,
- getCommandFactory func(string, []string) GETREQ,
+ client *Client, name string, using []JmapNamespace,
+ getCommand Command, getCommandFactory func(string, []string) GETREQ,
mapper func(GETRESP) RESP,
stateMapper func(GETRESP) State,
accountId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, ids []string) (RESP, SessionState, State, Language, Error) {
@@ -19,7 +19,7 @@ func getTemplate[GETREQ any, GETRESP any, RESP any]( //NOSONAR
var zero RESP
- cmd, err := client.request(session, logger,
+ cmd, err := client.request(session, logger, using,
invocation(getCommand, getCommandFactory(accountId, ids), "0"),
)
if err != nil {
@@ -38,8 +38,8 @@ func getTemplate[GETREQ any, GETRESP any, RESP any]( //NOSONAR
}
func getTemplateN[GETREQ any, GETRESP any, ITEM any, RESP any]( //NOSONAR
- client *Client, name string, getCommand Command,
- getCommandFactory func(string, []string) GETREQ,
+ client *Client, name string, using []JmapNamespace,
+ getCommand Command, getCommandFactory func(string, []string) GETREQ,
itemMapper func(GETRESP) ITEM,
respMapper func(map[string]ITEM) RESP,
stateMapper func(GETRESP) State,
@@ -55,7 +55,7 @@ func getTemplateN[GETREQ any, GETRESP any, ITEM any, RESP any]( //NOSONAR
invocations[i] = invocation(getCommand, getCommandFactory(accountId, ids), mcid(accountId, "0"))
}
- cmd, err := client.request(session, logger, invocations...)
+ cmd, err := client.request(session, logger, using, invocations...)
if err != nil {
return zero, "", "", "", err
}
@@ -77,7 +77,8 @@ func getTemplateN[GETREQ any, GETRESP any, ITEM any, RESP any]( //NOSONAR
}
func createTemplate[T any, SETREQ any, GETREQ any, SETRESP any, GETRESP any]( //NOSONAR
- client *Client, name string, t ObjectType, setCommand Command, getCommand Command,
+ client *Client, name string, using []JmapNamespace, t ObjectType,
+ setCommand Command, getCommand Command,
setCommandFactory func(string, map[string]T) SETREQ,
getCommandFactory func(string, string) GETREQ,
createdMapper func(SETRESP) map[string]*T,
@@ -88,7 +89,7 @@ func createTemplate[T any, SETREQ any, GETREQ any, SETRESP any, GETRESP any]( //
logger = client.logger(name, session, logger)
createMap := map[string]T{"c": create}
- cmd, err := client.request(session, logger,
+ cmd, err := client.request(session, logger, using,
invocation(setCommand, setCommandFactory(accountId, createMap), "0"),
invocation(getCommand, getCommandFactory(accountId, "#c"), "1"),
)
@@ -135,14 +136,14 @@ func createTemplate[T any, SETREQ any, GETREQ any, SETRESP any, GETRESP any]( //
})
}
-func deleteTemplate[REQ any, RESP any](client *Client, name string, c Command, //NOSONAR
- commandFactory func(string, []string) REQ,
+func deleteTemplate[REQ any, RESP any](client *Client, name string, using []JmapNamespace, //NOSONAR
+ c Command, commandFactory func(string, []string) REQ,
notDestroyedMapper func(RESP) map[string]SetError,
stateMapper func(RESP) State,
accountId string, destroy []string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string) (map[string]SetError, SessionState, State, Language, Error) {
logger = client.logger(name, session, logger)
- cmd, err := client.request(session, logger,
+ cmd, err := client.request(session, logger, using,
invocation(c, commandFactory(accountId, destroy), "0"),
)
if err != nil {
@@ -160,7 +161,7 @@ func deleteTemplate[REQ any, RESP any](client *Client, name string, c Command, /
}
func changesTemplate[CHANGESREQ any, GETREQ any, CHANGESRESP any, GETRESP any, ITEM any, RESP any]( //NOSONAR
- client *Client, name string,
+ client *Client, name string, using []JmapNamespace,
changesCommand Command, getCommand Command,
changesCommandFactory func() CHANGESREQ,
getCommandFactory func(string, string) GETREQ,
@@ -175,7 +176,7 @@ func changesTemplate[CHANGESREQ any, GETREQ any, CHANGESRESP any, GETRESP any, I
changes := changesCommandFactory()
getCreated := getCommandFactory("/created", "0") //NOSONAR
getUpdated := getCommandFactory("/updated", "0") //NOSONAR
- cmd, err := client.request(session, logger,
+ cmd, err := client.request(session, logger, using,
invocation(changesCommand, changes, "0"),
invocation(getCommand, getCreated, "1"),
invocation(getCommand, getUpdated, "2"),
@@ -215,8 +216,54 @@ func changesTemplate[CHANGESREQ any, GETREQ any, CHANGESRESP any, GETRESP any, I
})
}
+func updatedTemplate[CHANGESREQ any, GETREQ any, CHANGESRESP any, GETRESP any, ITEM any, RESP any]( //NOSONAR
+ client *Client, name string, using []JmapNamespace,
+ changesCommand Command, getCommand Command,
+ changesCommandFactory func() CHANGESREQ,
+ getCommandFactory func(string, string) GETREQ,
+ changesMapper func(CHANGESRESP) (State, State, bool),
+ getMapper func(GETRESP) []ITEM,
+ respMapper func(State, State, bool, []ITEM) RESP,
+ stateMapper func(GETRESP) State,
+ session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string) (RESP, SessionState, State, Language, Error) {
+ logger = client.logger(name, session, logger)
+ var zero RESP
+
+ changes := changesCommandFactory()
+ getUpdated := getCommandFactory("/updated", "0") //NOSONAR
+ cmd, err := client.request(session, logger, using,
+ invocation(changesCommand, changes, "0"),
+ invocation(getCommand, getUpdated, "1"),
+ )
+ if err != nil {
+ return zero, "", "", "", err
+ }
+
+ return command(client.api, logger, ctx, session, client.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (RESP, State, Error) {
+ var changesResponse CHANGESRESP
+ err = retrieveResponseMatchParameters(logger, body, changesCommand, "0", &changesResponse)
+ if err != nil {
+ return zero, "", err
+ }
+
+ var updatedResponse GETRESP
+ err = retrieveResponseMatchParameters(logger, body, getCommand, "1", &updatedResponse)
+ if err != nil {
+ logger.Error().Err(err).Send()
+ return zero, "", err
+ }
+
+ oldState, newState, hasMoreChanges := changesMapper(changesResponse)
+ updated := getMapper(updatedResponse)
+
+ result := respMapper(oldState, newState, hasMoreChanges, updated)
+
+ return result, stateMapper(updatedResponse), nil
+ })
+}
+
func changesTemplateN[CHANGESREQ any, GETREQ any, CHANGESRESP any, GETRESP any, ITEM any, CHANGESITEM any, RESP any]( //NOSONAR
- client *Client, name string,
+ client *Client, name string, using []JmapNamespace,
accountIds []string, sinceStateMap map[string]State,
changesCommand Command, getCommand Command,
changesCommandFactory func(string, State) CHANGESREQ,
@@ -260,7 +307,7 @@ func changesTemplateN[CHANGESREQ any, GETREQ any, CHANGESRESP any, GETRESP any,
invocations[i*3+2] = invocation(getCommand, getUpdated, mcid(accountId, "2"))
}
- cmd, err := client.request(session, logger, invocations...)
+ cmd, err := client.request(session, logger, using, invocations...)
if err != nil {
return zero, "", "", "", err
}
diff --git a/pkg/jmap/tools.go b/pkg/jmap/tools.go
index 7f527412ee..284f789221 100644
--- a/pkg/jmap/tools.go
+++ b/pkg/jmap/tools.go
@@ -382,3 +382,12 @@ func posUIntPtr(i uint) *uint {
return nil
}
}
+
+func ns(namespaces ...JmapNamespace) []JmapNamespace {
+ result := make([]JmapNamespace, len(namespaces)+1)
+ result[0] = JmapCore
+ for i, n := range namespaces {
+ result[i+1] = n
+ }
+ return result
+}
diff --git a/services/groupware/pkg/groupware/api_changes.go b/services/groupware/pkg/groupware/api_changes.go
index 986d7dce0b..cec3d3f6a7 100644
--- a/services/groupware/pkg/groupware/api_changes.go
+++ b/services/groupware/pkg/groupware/api_changes.go
@@ -61,6 +61,13 @@ func (g *Groupware) GetChanges(w http.ResponseWriter, r *http.Request) { //NOSON
if state, ok := req.getStringParam(QueryParamEvents, ""); ok {
sinceState.Events = ptr(toState(state))
}
+ if state, ok := req.getStringParam(QueryParamIdentities, ""); ok {
+ sinceState.Identities = ptr(toState(state))
+ }
+ if state, ok := req.getStringParam(QueryParamEmailSubmissions, ""); ok {
+ sinceState.EmailSubmissions = ptr(toState(state))
+ }
+ //if state, ok := req.getStringParam(QueryParamQuotas, ""); ok { sinceState.Quotas = ptr(toState(state)) }
if sinceState.IsZero() {
return req.noop(accountId)
}
diff --git a/services/groupware/pkg/groupware/api_objects.go b/services/groupware/pkg/groupware/api_objects.go
index 0c35e95cbb..c93d1b3a40 100644
--- a/services/groupware/pkg/groupware/api_objects.go
+++ b/services/groupware/pkg/groupware/api_objects.go
@@ -8,12 +8,15 @@ import (
)
type ObjectsRequest struct {
- Mailboxes []string `json:"mailboxes,omitempty"`
- Emails []string `json:"emails,omitempty"`
- Addressbooks []string `json:"addressbooks,omitempty"`
- Contacts []string `json:"contacts,omitempty"`
- Calendars []string `json:"calendars,omitempty"`
- Events []string `json:"events,omitempty"`
+ Mailboxes []string `json:"mailboxes,omitempty"`
+ Emails []string `json:"emails,omitempty"`
+ Addressbooks []string `json:"addressbooks,omitempty"`
+ Contacts []string `json:"contacts,omitempty"`
+ Calendars []string `json:"calendars,omitempty"`
+ Events []string `json:"events,omitempty"`
+ Quotas []string `json:"quotas,omitempty"`
+ Identities []string `json:"identities,omitempty"`
+ EmailSubmissions []string `json:"submissions,omitempty"`
}
// Retrieve changes for multiple or all Groupware objects, based on their respective state token.
@@ -48,17 +51,23 @@ func (g *Groupware) GetObjects(w http.ResponseWriter, r *http.Request) { //NOSON
contactIds := []string{}
calendarIds := []string{}
eventIds := []string{}
+ quotaIds := []string{}
+ identityIds := []string{}
+ emailSubmissionIds := []string{}
{
var objects ObjectsRequest
if ok, err := req.optBody(&objects); err != nil {
return req.error(accountId, err)
} else if ok {
mailboxIds = append(mailboxIds, objects.Mailboxes...)
- emailIds = append(mailboxIds, objects.Emails...)
- addressbookIds = append(mailboxIds, objects.Addressbooks...)
- contactIds = append(mailboxIds, objects.Contacts...)
- calendarIds = append(mailboxIds, objects.Calendars...)
- eventIds = append(mailboxIds, objects.Events...)
+ emailIds = append(emailIds, objects.Emails...)
+ addressbookIds = append(addressbookIds, objects.Addressbooks...)
+ contactIds = append(contactIds, objects.Contacts...)
+ calendarIds = append(calendarIds, objects.Calendars...)
+ eventIds = append(eventIds, objects.Events...)
+ quotaIds = append(quotaIds, objects.Quotas...)
+ identityIds = append(identityIds, objects.Identities...)
+ emailSubmissionIds = append(emailSubmissionIds, objects.EmailSubmissions...)
}
}
@@ -92,10 +101,25 @@ func (g *Groupware) GetObjects(w http.ResponseWriter, r *http.Request) { //NOSON
} else if ok {
eventIds = append(eventIds, list...)
}
+ if list, ok, err := req.parseOptStringListParam(QueryParamQuotas); err != nil {
+ return req.error(accountId, err)
+ } else if ok {
+ quotaIds = append(quotaIds, list...)
+ }
+ if list, ok, err := req.parseOptStringListParam(QueryParamIdentities); err != nil {
+ return req.error(accountId, err)
+ } else if ok {
+ identityIds = append(identityIds, list...)
+ }
+ if list, ok, err := req.parseOptStringListParam(QueryParamEmailSubmissions); err != nil {
+ return req.error(accountId, err)
+ } else if ok {
+ emailSubmissionIds = append(emailSubmissionIds, list...)
+ }
logger := log.From(l)
objs, sessionState, state, lang, jerr := g.jmap.GetObjects(accountId, req.session, req.ctx, logger, req.language(),
- mailboxIds, emailIds, addressbookIds, contactIds, calendarIds, eventIds)
+ mailboxIds, emailIds, addressbookIds, contactIds, calendarIds, eventIds, quotaIds, identityIds, emailSubmissionIds)
if jerr != nil {
return req.jmapError(accountId, jerr, sessionState, lang)
}
diff --git a/services/groupware/pkg/groupware/api_quota.go b/services/groupware/pkg/groupware/api_quota.go
index bfdbdeeabd..bbf6cc7947 100644
--- a/services/groupware/pkg/groupware/api_quota.go
+++ b/services/groupware/pkg/groupware/api_quota.go
@@ -64,3 +64,36 @@ func (g *Groupware) GetQuotaForAllAccounts(w http.ResponseWriter, r *http.Reques
return req.respondN(accountIds, result, sessionState, QuotaResponseObjectType, state)
})
}
+
+// Get changes to Contacts since a given State
+// @api:tags contact,changes
+func (g *Groupware) GetQuotaChanges(w http.ResponseWriter, r *http.Request) {
+ g.respond(w, r, func(req Request) Response {
+ accountId, err := req.GetAccountIdForQuota()
+ if err != nil {
+ return req.error(accountId, err)
+ }
+
+ l := req.logger.With().Str(logAccountId, accountId)
+
+ 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 := jmap.State(req.OptHeaderParamDoc(HeaderParamSince, "Specifies the state identifier from which on to list quota changes"))
+ l = l.Str(HeaderParamSince, log.SafeString(string(sinceState)))
+
+ logger := log.From(l)
+ changes, sessionState, state, lang, jerr := g.jmap.GetQuotaChanges(accountId, req.session, req.ctx, logger, req.language(), sinceState, maxChanges)
+ if jerr != nil {
+ return req.jmapError(accountId, jerr, sessionState, lang)
+ }
+ var body jmap.QuotaChanges = changes
+
+ return req.respond(accountId, body, sessionState, QuotaResponseObjectType, state)
+ })
+}
diff --git a/services/groupware/pkg/groupware/route.go b/services/groupware/pkg/groupware/route.go
index 0275705cfe..57bca79502 100644
--- a/services/groupware/pkg/groupware/route.go
+++ b/services/groupware/pkg/groupware/route.go
@@ -64,6 +64,9 @@ const (
QueryParamContacts = "contacts"
QueryParamCalendars = "calendars"
QueryParamEvents = "events"
+ QueryParamQuotas = "quotas"
+ QueryParamIdentities = "identities"
+ QueryParamEmailSubmissions = "submissions"
HeaderParamSince = "if-none-match"
)