groupware: add changes support for quotas, identities, submissions

This commit is contained in:
Pascal Bleser
2026-03-27 15:54:25 +01:00
parent 0b984a8e2f
commit 98e1701c0a
20 changed files with 835 additions and 307 deletions

View File

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

View File

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

View File

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

View File

@@ -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", &quotas); err != nil {
return Changes{}, "", err
} else if ok {
changes.Quotas = &quotas
states["quotas"] = quotas.NewState
}
*/
return changes, squashKeyedStates(states), nil
})
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -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", &quotas); err != nil {
return Objects{}, "", err
} else if ok {
objs.Quotas = &quotas
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
})
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -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; its 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; its 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{} },
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -64,6 +64,9 @@ const (
QueryParamContacts = "contacts"
QueryParamCalendars = "calendars"
QueryParamEvents = "events"
QueryParamQuotas = "quotas"
QueryParamIdentities = "identities"
QueryParamEmailSubmissions = "submissions"
HeaderParamSince = "if-none-match"
)