mirror of
https://github.com/opencloud-eu/opencloud.git
synced 2026-05-01 04:33:11 -04:00
groupware: refactoring using function templates
* adds creating addressbooks, calendars, mailboxes * adds deleting mailbox, event, identity * adds modifying an email * introduce template functions for the Groupware API in templates.go, and use those in route function implementations whenever possible * add capability checking for mail, quota, blobs * adds Changes interface * adds JmapResponse interface
This commit is contained in:
@@ -14,7 +14,16 @@ func (j *Client) GetAddressbooks(accountId string, ids []string, ctx Context) (A
|
||||
)
|
||||
}
|
||||
|
||||
type AddressBookChanges = ChangesTemplate[AddressBook]
|
||||
type AddressBookChanges ChangesTemplate[AddressBook]
|
||||
|
||||
var _ Changes[AddressBook] = AddressBookChanges{}
|
||||
|
||||
func (c AddressBookChanges) GetHasMoreChanges() bool { return c.HasMoreChanges }
|
||||
func (c AddressBookChanges) GetOldState() State { return c.OldState }
|
||||
func (c AddressBookChanges) GetNewState() State { return c.NewState }
|
||||
func (c AddressBookChanges) GetCreated() []AddressBook { return c.Created }
|
||||
func (c AddressBookChanges) GetUpdated() []AddressBook { return c.Updated }
|
||||
func (c AddressBookChanges) GetDestroyed() []string { return c.Destroyed }
|
||||
|
||||
// Retrieve Address Book changes since a given state.
|
||||
// @apidoc addressbook,changes
|
||||
|
||||
@@ -10,10 +10,10 @@ import (
|
||||
|
||||
var NS_BLOB = ns(JmapBlob)
|
||||
|
||||
func (j *Client) GetBlobMetadata(accountId string, id string, ctx Context) (*Blob, SessionState, State, Language, Error) {
|
||||
func (j *Client) GetBlobMetadata(accountId string, ids []string, ctx Context) (BlobGetResponse, SessionState, State, Language, Error) {
|
||||
get := BlobGetCommand{
|
||||
AccountId: accountId,
|
||||
Ids: []string{id},
|
||||
Ids: ids,
|
||||
// add BlobPropertyData to retrieve the data
|
||||
Properties: []string{BlobPropertyDigestSha256, BlobPropertyDigestSha512, BlobPropertySize},
|
||||
}
|
||||
@@ -21,22 +21,16 @@ func (j *Client) GetBlobMetadata(accountId string, id string, ctx Context) (*Blo
|
||||
invocation(get, "0"),
|
||||
)
|
||||
if jerr != nil {
|
||||
return nil, "", "", "", jerr
|
||||
return bail[BlobGetResponse](jerr)
|
||||
}
|
||||
|
||||
return command(j, ctx, cmd, func(body *Response) (*Blob, State, Error) {
|
||||
return command(j, ctx, cmd, func(body *Response) (BlobGetResponse, State, Error) {
|
||||
var response BlobGetResponse
|
||||
err := retrieveGet(ctx, body, get, "0", &response)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
return BlobGetResponse{}, EmptyState, err
|
||||
}
|
||||
|
||||
if len(response.List) != 1 {
|
||||
ctx.Logger.Error().Msgf("%T.List has %v entries instead of 1", response, len(response.List))
|
||||
return nil, "", jmapError(err, JmapErrorInvalidJmapResponsePayload)
|
||||
}
|
||||
get := response.List[0]
|
||||
return &get, response.State, nil
|
||||
return response, response.State, nil
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -35,7 +35,16 @@ func (j *Client) GetCalendars(accountId string, ids []string, ctx Context) (Cale
|
||||
)
|
||||
}
|
||||
|
||||
type CalendarChanges = ChangesTemplate[Calendar]
|
||||
type CalendarChanges ChangesTemplate[Calendar]
|
||||
|
||||
var _ Changes[Calendar] = CalendarChanges{}
|
||||
|
||||
func (c CalendarChanges) GetHasMoreChanges() bool { return c.HasMoreChanges }
|
||||
func (c CalendarChanges) GetOldState() State { return c.OldState }
|
||||
func (c CalendarChanges) GetNewState() State { return c.NewState }
|
||||
func (c CalendarChanges) GetCreated() []Calendar { return c.Created }
|
||||
func (c CalendarChanges) GetUpdated() []Calendar { return c.Updated }
|
||||
func (c CalendarChanges) GetDestroyed() []string { return c.Destroyed }
|
||||
|
||||
// Retrieve Calendar changes since a given state.
|
||||
// @apidoc calendar,changes
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
|
||||
var NS_CHANGES = ns(JmapMail, JmapContacts, JmapCalendars) //, JmapQuota)
|
||||
|
||||
type Changes struct {
|
||||
type ObjectChanges struct {
|
||||
MaxChanges uint `json:"maxchanges,omitzero"`
|
||||
Mailboxes *MailboxChangesResponse `json:"mailboxes,omitempty"`
|
||||
Emails *EmailChangesResponse `json:"emails,omitempty"`
|
||||
@@ -74,7 +74,7 @@ func (s StateMap) MarshalZerologObject(e *zerolog.Event) {
|
||||
|
||||
// Retrieve the changes in any type of objects at once since a given State.
|
||||
// @api:tags changes
|
||||
func (j *Client) GetChanges(accountId string, stateMap StateMap, maxChanges uint, ctx Context) (Changes, SessionState, State, Language, Error) { //NOSONAR
|
||||
func (j *Client) GetChanges(accountId string, stateMap StateMap, maxChanges uint, ctx Context) (ObjectChanges, SessionState, State, Language, Error) { //NOSONAR
|
||||
logger := log.From(j.logger("GetChanges", ctx).With().Object("state", stateMap).Uint("maxChanges", maxChanges))
|
||||
ctx = ctx.WithLogger(logger)
|
||||
|
||||
@@ -107,18 +107,18 @@ func (j *Client) GetChanges(accountId string, stateMap StateMap, maxChanges uint
|
||||
|
||||
cmd, err := j.request(ctx, NS_CHANGES, methodCalls...)
|
||||
if err != nil {
|
||||
return Changes{}, "", "", "", err
|
||||
return ObjectChanges{}, "", "", "", err
|
||||
}
|
||||
|
||||
return command(j, ctx, cmd, func(body *Response) (Changes, State, Error) {
|
||||
changes := Changes{
|
||||
return command(j, ctx, cmd, func(body *Response) (ObjectChanges, State, Error) {
|
||||
changes := ObjectChanges{
|
||||
MaxChanges: maxChanges,
|
||||
}
|
||||
states := map[string]State{}
|
||||
|
||||
var mailboxes MailboxChangesResponse
|
||||
if ok, err := tryRetrieveResponseMatchParameters(ctx, body, CommandMailboxChanges, "mailboxes", &mailboxes); err != nil {
|
||||
return Changes{}, "", err
|
||||
return ObjectChanges{}, "", err
|
||||
} else if ok {
|
||||
changes.Mailboxes = &mailboxes
|
||||
states["mailbox"] = mailboxes.NewState
|
||||
@@ -126,7 +126,7 @@ func (j *Client) GetChanges(accountId string, stateMap StateMap, maxChanges uint
|
||||
|
||||
var emails EmailChangesResponse
|
||||
if ok, err := tryRetrieveResponseMatchParameters(ctx, body, CommandEmailChanges, "emails", &emails); err != nil {
|
||||
return Changes{}, "", err
|
||||
return ObjectChanges{}, "", err
|
||||
} else if ok {
|
||||
changes.Emails = &emails
|
||||
states["emails"] = emails.NewState
|
||||
@@ -134,7 +134,7 @@ func (j *Client) GetChanges(accountId string, stateMap StateMap, maxChanges uint
|
||||
|
||||
var calendars CalendarChangesResponse
|
||||
if ok, err := tryRetrieveResponseMatchParameters(ctx, body, CommandCalendarChanges, "calendars", &calendars); err != nil {
|
||||
return Changes{}, "", err
|
||||
return ObjectChanges{}, "", err
|
||||
} else if ok {
|
||||
changes.Calendars = &calendars
|
||||
states["calendars"] = calendars.NewState
|
||||
@@ -142,7 +142,7 @@ func (j *Client) GetChanges(accountId string, stateMap StateMap, maxChanges uint
|
||||
|
||||
var events CalendarEventChangesResponse
|
||||
if ok, err := tryRetrieveResponseMatchParameters(ctx, body, CommandCalendarEventChanges, "events", &events); err != nil {
|
||||
return Changes{}, "", err
|
||||
return ObjectChanges{}, "", err
|
||||
} else if ok {
|
||||
changes.Events = &events
|
||||
states["events"] = events.NewState
|
||||
@@ -150,7 +150,7 @@ func (j *Client) GetChanges(accountId string, stateMap StateMap, maxChanges uint
|
||||
|
||||
var addressbooks AddressBookChangesResponse
|
||||
if ok, err := tryRetrieveResponseMatchParameters(ctx, body, CommandAddressBookChanges, "addressbooks", &addressbooks); err != nil {
|
||||
return Changes{}, "", err
|
||||
return ObjectChanges{}, "", err
|
||||
} else if ok {
|
||||
changes.Addressbooks = &addressbooks
|
||||
states["addressbooks"] = addressbooks.NewState
|
||||
@@ -158,7 +158,7 @@ func (j *Client) GetChanges(accountId string, stateMap StateMap, maxChanges uint
|
||||
|
||||
var contacts ContactCardChangesResponse
|
||||
if ok, err := tryRetrieveResponseMatchParameters(ctx, body, CommandContactCardChanges, "contacts", &contacts); err != nil {
|
||||
return Changes{}, "", err
|
||||
return ObjectChanges{}, "", err
|
||||
} else if ok {
|
||||
changes.Contacts = &contacts
|
||||
states["contacts"] = contacts.NewState
|
||||
@@ -166,7 +166,7 @@ func (j *Client) GetChanges(accountId string, stateMap StateMap, maxChanges uint
|
||||
|
||||
var identities IdentityChangesResponse
|
||||
if ok, err := tryRetrieveResponseMatchParameters(ctx, body, CommandIdentityChanges, "identities", &identities); err != nil {
|
||||
return Changes{}, "", err
|
||||
return ObjectChanges{}, "", err
|
||||
} else if ok {
|
||||
changes.Identities = &identities
|
||||
states["identities"] = identities.NewState
|
||||
@@ -174,7 +174,7 @@ func (j *Client) GetChanges(accountId string, stateMap StateMap, maxChanges uint
|
||||
|
||||
var submissions EmailSubmissionChangesResponse
|
||||
if ok, err := tryRetrieveResponseMatchParameters(ctx, body, CommandEmailSubmissionChanges, "submissions", &submissions); err != nil {
|
||||
return Changes{}, "", err
|
||||
return ObjectChanges{}, "", err
|
||||
} else if ok {
|
||||
changes.EmailSubmissions = &submissions
|
||||
states["submissions"] = submissions.NewState
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
package jmap
|
||||
|
||||
import "github.com/opencloud-eu/opencloud/pkg/jscontact"
|
||||
|
||||
var NS_CONTACTS = ns(JmapContacts)
|
||||
|
||||
var DEFAULT_CONTACT_CARD_VERSION = jscontact.JSContactVersion_1_0
|
||||
|
||||
func (j *Client) GetContactCards(accountId string, contactIds []string, ctx Context) (ContactCardGetResponse, SessionState, State, Language, Error) {
|
||||
return get(j, "GetContactCards", ContactCardType,
|
||||
func(accountId string, ids []string) ContactCardGetCommand {
|
||||
@@ -14,7 +18,16 @@ func (j *Client) GetContactCards(accountId string, contactIds []string, ctx Cont
|
||||
)
|
||||
}
|
||||
|
||||
type ContactCardChanges = ChangesTemplate[ContactCard]
|
||||
type ContactCardChanges ChangesTemplate[ContactCard]
|
||||
|
||||
var _ Changes[ContactCard] = ContactCardChanges{}
|
||||
|
||||
func (c ContactCardChanges) GetHasMoreChanges() bool { return c.HasMoreChanges }
|
||||
func (c ContactCardChanges) GetOldState() State { return c.OldState }
|
||||
func (c ContactCardChanges) GetNewState() State { return c.NewState }
|
||||
func (c ContactCardChanges) GetCreated() []ContactCard { return c.Created }
|
||||
func (c ContactCardChanges) GetUpdated() []ContactCard { return c.Updated }
|
||||
func (c ContactCardChanges) GetDestroyed() []string { return c.Destroyed }
|
||||
|
||||
// Retrieve the changes in Contact Cards since a given State.
|
||||
// @api:tags contact,changes
|
||||
@@ -85,9 +98,12 @@ func (j *Client) QueryContactCards(accountIds []string,
|
||||
)
|
||||
}
|
||||
|
||||
func (j *Client) CreateContactCard(accountId string, contact ContactCard, ctx Context) (*ContactCard, SessionState, State, Language, Error) {
|
||||
func (j *Client) CreateContactCard(accountId string, contact ContactCardChange, ctx Context) (*ContactCard, SessionState, State, Language, Error) {
|
||||
if contact.Version == nil {
|
||||
contact.Version = &DEFAULT_CONTACT_CARD_VERSION
|
||||
}
|
||||
return create(j, "CreateContactCard", ContactCardType,
|
||||
func(accountId string, create map[string]ContactCard) ContactCardSetCommand {
|
||||
func(accountId string, create map[string]ContactCardChange) ContactCardSetCommand {
|
||||
return ContactCardSetCommand{AccountId: accountId, Create: create}
|
||||
},
|
||||
func(accountId string, ids string) ContactCardGetCommand {
|
||||
|
||||
@@ -34,9 +34,9 @@ func (j *Client) GetEmails(accountId string, ids []string, //NOSONAR
|
||||
methodCalls := []Invocation{invokeGet}
|
||||
var markEmails EmailSetCommand
|
||||
if markAsSeen {
|
||||
updates := make(map[string]EmailUpdate, len(ids))
|
||||
updates := make(map[string]PatchObject, len(ids))
|
||||
for _, id := range ids {
|
||||
updates[id] = EmailUpdate{EmailPropertyKeywords + "/" + JmapKeywordSeen: true}
|
||||
updates[id] = PatchObject{EmailPropertyKeywords + "/" + JmapKeywordSeen: true}
|
||||
}
|
||||
markEmails = EmailSetCommand{AccountId: accountId, Update: updates}
|
||||
methodCalls = []Invocation{invocation(markEmails, "0"), invokeGet}
|
||||
@@ -111,7 +111,15 @@ func (j *Client) GetEmailBlobId(accountId string, id string, ctx Context) (strin
|
||||
})
|
||||
}
|
||||
|
||||
type EmailSearchResults = SearchResultsTemplate[Email]
|
||||
type EmailSearchResults SearchResultsTemplate[Email]
|
||||
|
||||
var _ SearchResults[Email] = EmailSearchResults{}
|
||||
|
||||
func (r EmailSearchResults) GetResults() []Email { return r.Results }
|
||||
func (r EmailSearchResults) GetCanCalculateChanges() bool { return r.CanCalculateChanges }
|
||||
func (r EmailSearchResults) GetPosition() uint { return r.Position }
|
||||
func (r EmailSearchResults) GetLimit() uint { return r.Limit }
|
||||
func (r EmailSearchResults) GetTotal() *uint { return r.Total }
|
||||
|
||||
// Retrieve all the Emails in a given Mailbox by its id.
|
||||
func (j *Client) GetAllEmailsInMailbox(accountId string, mailboxId string, //NOSONAR
|
||||
@@ -200,14 +208,16 @@ func (j *Client) GetAllEmailsInMailbox(accountId string, mailboxId string, //NOS
|
||||
})
|
||||
}
|
||||
|
||||
type EmailChanges struct {
|
||||
HasMoreChanges bool `json:"hasMoreChanges"`
|
||||
OldState State `json:"oldState,omitempty"`
|
||||
NewState State `json:"newState"`
|
||||
Created []Email `json:"created,omitempty"`
|
||||
Updated []Email `json:"updated,omitempty"`
|
||||
Destroyed []string `json:"destroyed,omitempty"`
|
||||
}
|
||||
type EmailChanges ChangesTemplate[Email]
|
||||
|
||||
var _ Changes[Email] = EmailChanges{}
|
||||
|
||||
func (c EmailChanges) GetHasMoreChanges() bool { return c.HasMoreChanges }
|
||||
func (c EmailChanges) GetOldState() State { return c.OldState }
|
||||
func (c EmailChanges) GetNewState() State { return c.NewState }
|
||||
func (c EmailChanges) GetCreated() []Email { return c.Created }
|
||||
func (c EmailChanges) GetUpdated() []Email { return c.Updated }
|
||||
func (c EmailChanges) GetDestroyed() []string { return c.Destroyed }
|
||||
|
||||
// Retrieve the changes in Emails since a given State.
|
||||
// @api:tags email,changes
|
||||
@@ -495,13 +505,13 @@ type EmailWithSnippets struct {
|
||||
type EmailQueryWithSnippetsResult struct {
|
||||
Results []EmailWithSnippets `json:"results"`
|
||||
Total uint `json:"total"`
|
||||
Position uint `json:"position"`
|
||||
Limit uint `json:"limit,omitzero"`
|
||||
Position uint `json:"position,omitzero"`
|
||||
QueryState State `json:"queryState"`
|
||||
}
|
||||
|
||||
func (j *Client) QueryEmailsWithSnippets(accountIds []string, //NOSONAR
|
||||
filter EmailFilterElement, offset int, limit uint, fetchBodies bool, maxBodyValueBytes uint,
|
||||
filter EmailFilterElement, offset int, limit uint, collapseThreads bool, calculateTotal bool, fetchBodies bool, maxBodyValueBytes uint,
|
||||
ctx Context) (map[string]EmailQueryWithSnippetsResult, SessionState, State, Language, Error) {
|
||||
logger := j.loggerParams("QueryEmailsWithSnippets", ctx, func(z zerolog.Context) zerolog.Context {
|
||||
return z.Bool(logFetchBodies, fetchBodies)
|
||||
@@ -515,8 +525,8 @@ func (j *Client) QueryEmailsWithSnippets(accountIds []string, //NOSONAR
|
||||
AccountId: accountId,
|
||||
Filter: filter,
|
||||
Sort: []EmailComparator{{Property: EmailPropertyReceivedAt, IsAscending: false}},
|
||||
CollapseThreads: false,
|
||||
CalculateTotal: true,
|
||||
CollapseThreads: collapseThreads,
|
||||
CalculateTotal: calculateTotal,
|
||||
}
|
||||
if offset > 0 {
|
||||
query.Position = offset
|
||||
@@ -689,10 +699,10 @@ func (j *Client) ImportEmail(accountId string, data []byte, ctx Context) (Upload
|
||||
|
||||
}
|
||||
|
||||
func (j *Client) CreateEmail(accountId string, email EmailCreate, replaceId string, ctx Context) (*Email, SessionState, State, Language, Error) {
|
||||
func (j *Client) CreateEmail(accountId string, email EmailChange, replaceId string, ctx Context) (*Email, SessionState, State, Language, Error) {
|
||||
set := EmailSetCommand{
|
||||
AccountId: accountId,
|
||||
Create: map[string]EmailCreate{
|
||||
Create: map[string]EmailChange{
|
||||
"c": email,
|
||||
},
|
||||
}
|
||||
@@ -744,7 +754,7 @@ func (j *Client) CreateEmail(accountId string, email EmailCreate, replaceId stri
|
||||
// To create drafts, use the CreateEmail function instead.
|
||||
//
|
||||
// To delete mails, use the DeleteEmails function instead.
|
||||
func (j *Client) UpdateEmails(accountId string, updates map[string]EmailUpdate, ctx Context) (map[string]*Email, SessionState, State, Language, Error) {
|
||||
func (j *Client) UpdateEmails(accountId string, updates map[string]PatchObject, ctx Context) (map[string]*Email, SessionState, State, Language, Error) {
|
||||
set := EmailSetCommand{
|
||||
AccountId: accountId,
|
||||
Update: updates,
|
||||
@@ -770,6 +780,21 @@ func (j *Client) UpdateEmails(accountId string, updates map[string]EmailUpdate,
|
||||
})
|
||||
}
|
||||
|
||||
func (j *Client) UpdateEmail(accountId string, id string, changes EmailChange, ctx Context) (Email, SessionState, State, Language, Error) {
|
||||
return update(j, "UpdateEmail", EmailType,
|
||||
func(update map[string]PatchObject) EmailSetCommand {
|
||||
return EmailSetCommand{AccountId: accountId, Update: update}
|
||||
},
|
||||
func(id string) EmailGetCommand {
|
||||
return EmailGetCommand{AccountId: accountId, Ids: []string{id}}
|
||||
},
|
||||
func(resp EmailSetResponse) map[string]SetError { return resp.NotUpdated },
|
||||
func(resp EmailGetResponse) Email { return resp.List[0] },
|
||||
id, changes,
|
||||
ctx,
|
||||
)
|
||||
}
|
||||
|
||||
func (j *Client) DeleteEmails(accountId string, destroyIds []string, ctx Context) (map[string]SetError, SessionState, State, Language, Error) {
|
||||
return destroy(j, "DeleteEmails", EmailType,
|
||||
func(accountId string, destroy []string) EmailSetCommand {
|
||||
|
||||
@@ -12,6 +12,18 @@ func (r CalendarEventSearchResults) GetPosition() uint { return r.Pos
|
||||
func (r CalendarEventSearchResults) GetLimit() uint { return r.Limit }
|
||||
func (r CalendarEventSearchResults) GetTotal() *uint { return r.Total }
|
||||
|
||||
func (j *Client) GetCalendarEvents(accountId string, eventIds []string, ctx Context) (CalendarEventGetResponse, SessionState, State, Language, Error) {
|
||||
return get(j, "GetCalendarEvents", CalendarEventType,
|
||||
func(accountId string, ids []string) CalendarEventGetCommand {
|
||||
return CalendarEventGetCommand{AccountId: accountId, Ids: eventIds}
|
||||
},
|
||||
CalendarEventGetResponse{},
|
||||
identity1,
|
||||
accountId, eventIds,
|
||||
ctx,
|
||||
)
|
||||
}
|
||||
|
||||
func (j *Client) QueryCalendarEvents(accountIds []string, //NOSONAR
|
||||
filter CalendarEventFilterElement, sortBy []CalendarEventComparator,
|
||||
position int, limit uint, calculateTotal bool,
|
||||
@@ -38,7 +50,16 @@ func (j *Client) QueryCalendarEvents(accountIds []string, //NOSONAR
|
||||
)
|
||||
}
|
||||
|
||||
type CalendarEventChanges = ChangesTemplate[CalendarEvent]
|
||||
type CalendarEventChanges ChangesTemplate[CalendarEvent]
|
||||
|
||||
var _ Changes[CalendarEvent] = CalendarEventChanges{}
|
||||
|
||||
func (c CalendarEventChanges) GetHasMoreChanges() bool { return c.HasMoreChanges }
|
||||
func (c CalendarEventChanges) GetOldState() State { return c.OldState }
|
||||
func (c CalendarEventChanges) GetNewState() State { return c.NewState }
|
||||
func (c CalendarEventChanges) GetCreated() []CalendarEvent { return c.Created }
|
||||
func (c CalendarEventChanges) GetUpdated() []CalendarEvent { return c.Updated }
|
||||
func (c CalendarEventChanges) GetDestroyed() []string { return c.Destroyed }
|
||||
|
||||
// Retrieve the changes in Calendar Events since a given State.
|
||||
// @api:tags event,changes
|
||||
@@ -74,9 +95,9 @@ func (j *Client) GetCalendarEventChanges(accountId string, sinceState State, max
|
||||
)
|
||||
}
|
||||
|
||||
func (j *Client) CreateCalendarEvent(accountId string, event CalendarEvent, ctx Context) (*CalendarEvent, SessionState, State, Language, Error) {
|
||||
func (j *Client) CreateCalendarEvent(accountId string, event CalendarEventChange, ctx Context) (*CalendarEvent, SessionState, State, Language, Error) {
|
||||
return create(j, "CreateCalendarEvent", CalendarEventType,
|
||||
func(accountId string, create map[string]CalendarEvent) CalendarEventSetCommand {
|
||||
func(accountId string, create map[string]CalendarEventChange) CalendarEventSetCommand {
|
||||
return CalendarEventSetCommand{AccountId: accountId, Create: create}
|
||||
},
|
||||
func(accountId string, ref string) CalendarEventGetCommand {
|
||||
|
||||
@@ -8,23 +8,13 @@ import (
|
||||
|
||||
var NS_IDENTITY = ns(JmapMail)
|
||||
|
||||
func (j *Client) GetAllIdentities(accountId string, ctx Context) ([]Identity, SessionState, State, Language, Error) {
|
||||
return getA(j, "GetAllIdentities", IdentityType,
|
||||
func(accountId string, ids []string) IdentityGetCommand {
|
||||
return IdentityGetCommand{AccountId: accountId}
|
||||
},
|
||||
IdentityGetResponse{},
|
||||
accountId, []string{},
|
||||
ctx,
|
||||
)
|
||||
}
|
||||
|
||||
func (j *Client) GetIdentities(accountId string, identityIds []string, ctx Context) ([]Identity, SessionState, State, Language, Error) {
|
||||
return getA(j, "GetIdentities", IdentityType,
|
||||
func (j *Client) GetIdentities(accountId string, identityIds []string, ctx Context) (IdentityGetResponse, SessionState, State, Language, Error) {
|
||||
return get(j, "GetIdentities", IdentityType,
|
||||
func(accountId string, ids []string) IdentityGetCommand {
|
||||
return IdentityGetCommand{AccountId: accountId, Ids: ids}
|
||||
},
|
||||
IdentityGetResponse{},
|
||||
identity1,
|
||||
accountId, identityIds,
|
||||
ctx,
|
||||
)
|
||||
@@ -140,7 +130,16 @@ func (j *Client) DeleteIdentity(accountId string, destroyIds []string, ctx Conte
|
||||
)
|
||||
}
|
||||
|
||||
type IdentityChanges = ChangesTemplate[Identity]
|
||||
type IdentityChanges ChangesTemplate[Identity]
|
||||
|
||||
var _ Changes[Identity] = IdentityChanges{}
|
||||
|
||||
func (c IdentityChanges) GetHasMoreChanges() bool { return c.HasMoreChanges }
|
||||
func (c IdentityChanges) GetOldState() State { return c.OldState }
|
||||
func (c IdentityChanges) GetNewState() State { return c.NewState }
|
||||
func (c IdentityChanges) GetCreated() []Identity { return c.Created }
|
||||
func (c IdentityChanges) GetUpdated() []Identity { return c.Updated }
|
||||
func (c IdentityChanges) GetDestroyed() []string { return c.Destroyed }
|
||||
|
||||
// Retrieve the changes in Email Identities since a given State.
|
||||
// @api:tags email,changes
|
||||
|
||||
@@ -113,7 +113,16 @@ func (j *Client) SearchMailboxIdsPerRole(accountIds []string, roles []string, ct
|
||||
})
|
||||
}
|
||||
|
||||
type MailboxChanges = ChangesTemplate[Mailbox]
|
||||
type MailboxChanges ChangesTemplate[Mailbox]
|
||||
|
||||
var _ Changes[Mailbox] = MailboxChanges{}
|
||||
|
||||
func (c MailboxChanges) GetHasMoreChanges() bool { return c.HasMoreChanges }
|
||||
func (c MailboxChanges) GetOldState() State { return c.OldState }
|
||||
func (c MailboxChanges) GetNewState() State { return c.NewState }
|
||||
func (c MailboxChanges) GetCreated() []Mailbox { return c.Created }
|
||||
func (c MailboxChanges) GetUpdated() []Mailbox { return c.Updated }
|
||||
func (c MailboxChanges) GetDestroyed() []string { return c.Destroyed }
|
||||
|
||||
func newMailboxChanges(oldState, newState State, hasMoreChanges bool, created, updated []Mailbox, destroyed []string) MailboxChanges {
|
||||
return MailboxChanges{
|
||||
|
||||
@@ -15,7 +15,16 @@ func (j *Client) GetQuotas(accountIds []string, ctx Context) (map[string]QuotaGe
|
||||
)
|
||||
}
|
||||
|
||||
type QuotaChanges = ChangesTemplate[Quota]
|
||||
type QuotaChanges ChangesTemplate[Quota]
|
||||
|
||||
var _ Changes[Quota] = QuotaChanges{}
|
||||
|
||||
func (c QuotaChanges) GetHasMoreChanges() bool { return c.HasMoreChanges }
|
||||
func (c QuotaChanges) GetOldState() State { return c.OldState }
|
||||
func (c QuotaChanges) GetNewState() State { return c.NewState }
|
||||
func (c QuotaChanges) GetCreated() []Quota { return c.Created }
|
||||
func (c QuotaChanges) GetUpdated() []Quota { return c.Updated }
|
||||
func (c QuotaChanges) GetDestroyed() []string { return c.Destroyed }
|
||||
|
||||
// Retrieve the changes in Quotas since a given State.
|
||||
// @api:tags quota,changes
|
||||
|
||||
@@ -47,6 +47,23 @@ type VacationResponseChange struct {
|
||||
HtmlBody string `json:"htmlBody,omitempty"`
|
||||
}
|
||||
|
||||
var _ Change = VacationResponseChange{}
|
||||
|
||||
func (m VacationResponseChange) AsPatch() (PatchObject, error) {
|
||||
return toPatchObject(m)
|
||||
}
|
||||
|
||||
type VacationResponseChanges ChangesTemplate[VacationResponse]
|
||||
|
||||
var _ Changes[VacationResponse] = VacationResponseChanges{}
|
||||
|
||||
func (c VacationResponseChanges) GetHasMoreChanges() bool { return c.HasMoreChanges }
|
||||
func (c VacationResponseChanges) GetOldState() State { return c.OldState }
|
||||
func (c VacationResponseChanges) GetNewState() State { return c.NewState }
|
||||
func (c VacationResponseChanges) GetCreated() []VacationResponse { return c.Created }
|
||||
func (c VacationResponseChanges) GetUpdated() []VacationResponse { return c.Updated }
|
||||
func (c VacationResponseChanges) GetDestroyed() []string { return c.Destroyed }
|
||||
|
||||
func (j *Client) SetVacationResponse(accountId string, vacation VacationResponseChange,
|
||||
ctx Context) (VacationResponse, SessionState, State, Language, Error) {
|
||||
logger := j.logger("SetVacationResponse", ctx)
|
||||
|
||||
@@ -79,3 +79,11 @@ func parseConsts(pkgID string, suffix string, typeName string) (map[string]strin
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func firstKey[K comparable, V any](m map[K]V) (K, bool) {
|
||||
for k := range m {
|
||||
return k, true
|
||||
}
|
||||
var zero K
|
||||
return zero, false
|
||||
}
|
||||
|
||||
@@ -422,38 +422,37 @@ func (s *StalwartTest) fillEvents( //NOSONAR
|
||||
keywords := pickKeywords()
|
||||
categories := pickCategories()
|
||||
|
||||
sequence := 0
|
||||
sequence := uint(0)
|
||||
|
||||
alertId := id()
|
||||
alertOffset := pickRandom("-PT5M", "-PT10M", "-PT15M")
|
||||
|
||||
obj := CalendarEvent{
|
||||
Id: "",
|
||||
obj := CalendarEventChange{
|
||||
CalendarIds: toBoolMapS(calendarId),
|
||||
IsDraft: isDraft,
|
||||
Event: jscalendar.Event{
|
||||
IsDraft: &isDraft,
|
||||
EventChange: jscalendar.EventChange{
|
||||
Type: jscalendar.EventType,
|
||||
Start: jscalendar.LocalDateTime(start),
|
||||
Duration: jscalendar.Duration(duration),
|
||||
Status: status,
|
||||
Object: jscalendar.Object{
|
||||
CommonObject: jscalendar.CommonObject{
|
||||
Uid: uid,
|
||||
ProdId: productName,
|
||||
Title: title,
|
||||
Description: description,
|
||||
DescriptionContentType: descriptionFormat,
|
||||
Locale: locale,
|
||||
Color: color,
|
||||
Duration: ptr(jscalendar.Duration(duration)),
|
||||
Status: &status,
|
||||
ObjectChange: jscalendar.ObjectChange{
|
||||
CommonObjectChange: jscalendar.CommonObjectChange{
|
||||
Uid: &uid,
|
||||
ProdId: &productName,
|
||||
Title: &title,
|
||||
Description: &description,
|
||||
DescriptionContentType: &descriptionFormat,
|
||||
Locale: &locale,
|
||||
Color: &color,
|
||||
},
|
||||
Sequence: uint(sequence),
|
||||
ShowWithoutTime: false,
|
||||
FreeBusyStatus: freeBusy,
|
||||
Privacy: privacy,
|
||||
Sequence: uintPtr(sequence),
|
||||
ShowWithoutTime: boolPtr(false),
|
||||
FreeBusyStatus: &freeBusy,
|
||||
Privacy: &privacy,
|
||||
SentBy: organizerEmail,
|
||||
Participants: participantObjs,
|
||||
TimeZone: tz,
|
||||
HideAttendees: false,
|
||||
TimeZone: &tz,
|
||||
HideAttendees: boolPtr(false),
|
||||
ReplyTo: map[jscalendar.ReplyMethod]string{
|
||||
jscalendar.ReplyMethodImip: "mailto:" + organizerEmail, //NOSONAR
|
||||
},
|
||||
@@ -476,8 +475,8 @@ func (s *StalwartTest) fillEvents( //NOSONAR
|
||||
}
|
||||
|
||||
if EnableEventMayInviteFields {
|
||||
obj.MayInviteSelf = true
|
||||
obj.MayInviteOthers = true
|
||||
obj.MayInviteSelf = boolPtr(true)
|
||||
obj.MayInviteOthers = boolPtr(true)
|
||||
boxes.mayInvite = true
|
||||
}
|
||||
|
||||
@@ -492,7 +491,7 @@ func (s *StalwartTest) fillEvents( //NOSONAR
|
||||
}
|
||||
|
||||
if mainLocationId != "" {
|
||||
obj.MainLocationId = mainLocationId
|
||||
obj.MainLocationId = &mainLocationId
|
||||
}
|
||||
|
||||
err = propmap(i%2 == 0, 1, 1, &obj.Links, func(int, string) (jscalendar.Link, error) {
|
||||
|
||||
@@ -332,13 +332,13 @@ func (s *StalwartTest) fillContacts( //NOSONAR
|
||||
nameObj := createName(person)
|
||||
language := pickLanguage()
|
||||
|
||||
card := ContactCard{
|
||||
card := ContactCardChange{
|
||||
Type: jscontact.ContactCardType,
|
||||
Version: "1.0",
|
||||
Version: ptr(jscontact.JSContactVersion_1_0),
|
||||
AddressBookIds: toBoolMap([]string{addressbookId}),
|
||||
ProdId: productName,
|
||||
Language: language,
|
||||
Kind: jscontact.ContactCardKindIndividual,
|
||||
ProdId: &productName,
|
||||
Language: &language,
|
||||
Kind: ptr(jscontact.ContactCardKindIndividual),
|
||||
Name: &nameObj,
|
||||
}
|
||||
|
||||
|
||||
@@ -56,12 +56,12 @@ func TestEmails(t *testing.T) {
|
||||
|
||||
{
|
||||
{
|
||||
resp, sessionState, _, _, err := s.client.GetAllIdentities(accountId, ctx)
|
||||
resp, sessionState, _, _, err := s.client.GetIdentities(accountId, []string{}, ctx)
|
||||
require.NoError(err)
|
||||
require.Equal(session.State, sessionState)
|
||||
require.Len(resp, 1)
|
||||
require.Equal(user.email, resp[0].Email)
|
||||
require.Equal(user.description, resp[0].Name)
|
||||
require.Len(resp.List, 1)
|
||||
require.Equal(user.email, resp.List[0].Email)
|
||||
require.Equal(user.description, resp.List[0].Name)
|
||||
}
|
||||
|
||||
{
|
||||
@@ -188,13 +188,13 @@ func TestSendingEmails(t *testing.T) {
|
||||
{
|
||||
var identity Identity
|
||||
{
|
||||
identities, _, _, _, err := s.client.GetAllIdentities(accountId, ctx)
|
||||
resp, _, _, _, err := s.client.GetIdentities(accountId, []string{}, ctx)
|
||||
require.NoError(err)
|
||||
require.NotEmpty(identities)
|
||||
identity = identities[0]
|
||||
require.NotEmpty(resp.List)
|
||||
identity = resp.List[0]
|
||||
}
|
||||
|
||||
create := EmailCreate{
|
||||
create := EmailChange{
|
||||
Keywords: toBoolMapS("test"),
|
||||
Subject: subject,
|
||||
MailboxIds: toBoolMapS(mailboxPerRole[JmapMailboxRoleDrafts].Id),
|
||||
@@ -214,7 +214,7 @@ func TestSendingEmails(t *testing.T) {
|
||||
require.Contains(email.MailboxIds, mailboxPerRole[JmapMailboxRoleDrafts].Id)
|
||||
}
|
||||
|
||||
update := EmailCreate{
|
||||
update := EmailChange{
|
||||
From: []EmailAddress{{Name: fromName, Email: from.email}},
|
||||
To: []EmailAddress{{Name: to.description, Email: to.email}},
|
||||
Cc: []EmailAddress{{Name: cc.description, Email: cc.email}},
|
||||
@@ -240,7 +240,7 @@ func TestSendingEmails(t *testing.T) {
|
||||
require.Contains(email.MailboxIds, mailboxPerRole[JmapMailboxRoleDrafts].Id)
|
||||
require.Equal(notFound[0], created.Id)
|
||||
var ok bool
|
||||
updatedMailboxId, ok = structs.FirstKey(email.MailboxIds)
|
||||
updatedMailboxId, ok = firstKey(email.MailboxIds)
|
||||
require.True(ok)
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ package jmap
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"time"
|
||||
|
||||
@@ -1326,12 +1327,17 @@ type JmapCommand interface {
|
||||
GetObjectType() ObjectType
|
||||
}
|
||||
|
||||
type JmapResponse[T Foo] interface {
|
||||
GetMarker() T
|
||||
}
|
||||
|
||||
type GetCommand[T Foo] interface {
|
||||
JmapCommand
|
||||
GetResponse() GetResponse[T]
|
||||
}
|
||||
|
||||
type GetResponse[T Foo] interface {
|
||||
JmapResponse[T]
|
||||
GetState() State
|
||||
GetNotFound() []string
|
||||
GetList() []T
|
||||
@@ -1343,12 +1349,12 @@ type SetCommand[T Foo] interface {
|
||||
}
|
||||
|
||||
type SetResponse[T Foo] interface {
|
||||
JmapResponse[T]
|
||||
GetNotCreated() map[string]SetError
|
||||
GetNotUpdated() map[string]SetError
|
||||
GetNotDestroyed() map[string]SetError
|
||||
GetOldState() State
|
||||
GetNewState() State
|
||||
GetMarker() T
|
||||
}
|
||||
|
||||
type Change interface {
|
||||
@@ -1361,13 +1367,13 @@ type ChangesCommand[T Foo] interface {
|
||||
}
|
||||
|
||||
type ChangesResponse[T Foo] interface {
|
||||
JmapResponse[T]
|
||||
GetOldState() State
|
||||
GetNewState() State
|
||||
GetHasMoreChanges() bool
|
||||
GetCreated() []string
|
||||
GetUpdated() []string
|
||||
GetDestroyed() []string
|
||||
GetMarker() T
|
||||
}
|
||||
|
||||
type QueryCommand[T Foo] interface {
|
||||
@@ -1376,8 +1382,8 @@ type QueryCommand[T Foo] interface {
|
||||
}
|
||||
|
||||
type QueryResponse[T Foo] interface {
|
||||
JmapResponse[T]
|
||||
GetQueryState() State
|
||||
GetMarker() T
|
||||
}
|
||||
|
||||
type UploadCommand[T Foo] interface {
|
||||
@@ -1386,7 +1392,7 @@ type UploadCommand[T Foo] interface {
|
||||
}
|
||||
|
||||
type UploadResponse[T Foo] interface {
|
||||
GetMarker() T
|
||||
JmapResponse[T]
|
||||
}
|
||||
|
||||
type ParseCommand[T Foo] interface {
|
||||
@@ -1395,7 +1401,7 @@ type ParseCommand[T Foo] interface {
|
||||
}
|
||||
|
||||
type ParseResponse[T Foo] interface {
|
||||
GetMarker() T
|
||||
JmapResponse[T]
|
||||
}
|
||||
|
||||
type ChangesTemplate[T Foo] struct {
|
||||
@@ -1407,6 +1413,15 @@ type ChangesTemplate[T Foo] struct {
|
||||
Destroyed []string `json:"destroyed,omitempty"`
|
||||
}
|
||||
|
||||
type Changes[T Foo] interface {
|
||||
GetHasMoreChanges() bool
|
||||
GetOldState() State
|
||||
GetNewState() State
|
||||
GetCreated() []T
|
||||
GetUpdated() []T
|
||||
GetDestroyed() []string
|
||||
}
|
||||
|
||||
type SearchResultsTemplate[T Foo] struct {
|
||||
Results []T `json:"results"`
|
||||
CanCalculateChanges bool `json:"canCalculateChanges"`
|
||||
@@ -2965,6 +2980,7 @@ type EmailSubmissionGetResponse struct {
|
||||
|
||||
var _ GetResponse[EmailSubmission] = &EmailSubmissionGetResponse{}
|
||||
|
||||
func (r EmailSubmissionGetResponse) GetMarker() EmailSubmission { return EmailSubmission{} }
|
||||
func (r EmailSubmissionGetResponse) GetState() State { return r.State }
|
||||
func (r EmailSubmissionGetResponse) GetNotFound() []string { return r.NotFound }
|
||||
func (r EmailSubmissionGetResponse) GetList() []EmailSubmission { return r.List }
|
||||
@@ -3268,6 +3284,7 @@ type MailboxGetResponse struct {
|
||||
|
||||
var _ GetResponse[Mailbox] = &MailboxGetResponse{}
|
||||
|
||||
func (r MailboxGetResponse) GetMarker() Mailbox { return Mailbox{} }
|
||||
func (r MailboxGetResponse) GetState() State { return r.State }
|
||||
func (r MailboxGetResponse) GetNotFound() []string { return r.NotFound }
|
||||
func (r MailboxGetResponse) GetList() []Mailbox { return r.List }
|
||||
@@ -3396,7 +3413,7 @@ var _ QueryResponse[Mailbox] = &MailboxQueryResponse{}
|
||||
func (r MailboxQueryResponse) GetQueryState() State { return r.QueryState }
|
||||
func (r MailboxQueryResponse) GetMarker() Mailbox { return Mailbox{} }
|
||||
|
||||
type EmailCreate struct {
|
||||
type EmailChange struct {
|
||||
// The set of Mailbox ids this Email belongs to.
|
||||
//
|
||||
// An Email in the mail store MUST belong to one or more Mailboxes at all times
|
||||
@@ -3498,7 +3515,11 @@ type EmailCreate struct {
|
||||
Attachments []EmailBodyPart `json:"attachments,omitempty"`
|
||||
}
|
||||
|
||||
type EmailUpdate map[string]any
|
||||
var _ Change = EmailChange{}
|
||||
|
||||
func (e EmailChange) AsPatch() (PatchObject, error) {
|
||||
return toPatchObject(e)
|
||||
}
|
||||
|
||||
type EmailSetCommand struct {
|
||||
// The id of the account to use.
|
||||
@@ -3520,7 +3541,7 @@ type EmailSetCommand struct {
|
||||
// Any such property may be omitted by the client.
|
||||
//
|
||||
// The client MUST omit any properties that may only be set by the server.
|
||||
Create map[string]EmailCreate `json:"create,omitempty"`
|
||||
Create map[string]EmailChange `json:"create,omitempty"`
|
||||
|
||||
// A map of an id to a `Patch` object to apply to the current Email object with that id,
|
||||
// or null if no objects are to be updated.
|
||||
@@ -3547,7 +3568,7 @@ type EmailSetCommand struct {
|
||||
//
|
||||
// The client may choose to optimise network usage by just sending the diff or may send the whole object; the server
|
||||
// processes it the same either way.
|
||||
Update map[string]EmailUpdate `json:"update,omitempty"`
|
||||
Update map[string]PatchObject `json:"update,omitempty"`
|
||||
|
||||
// A list of ids for Email objects to permanently delete, or null if no objects are to be destroyed.
|
||||
Destroy []string `json:"destroy,omitempty"`
|
||||
@@ -3966,6 +3987,7 @@ type IdentityGetResponse struct {
|
||||
|
||||
var _ GetResponse[Identity] = &IdentityGetResponse{}
|
||||
|
||||
func (r IdentityGetResponse) GetMarker() Identity { return Identity{} }
|
||||
func (r IdentityGetResponse) GetState() State { return r.State }
|
||||
func (r IdentityGetResponse) GetNotFound() []string { return r.NotFound }
|
||||
func (r IdentityGetResponse) GetList() []Identity { return r.List }
|
||||
@@ -4050,6 +4072,7 @@ type VacationResponseGetResponse struct {
|
||||
|
||||
var _ GetResponse[VacationResponse] = &VacationResponseGetResponse{}
|
||||
|
||||
func (r VacationResponseGetResponse) GetMarker() VacationResponse { return VacationResponse{} }
|
||||
func (r VacationResponseGetResponse) GetState() State { return r.State }
|
||||
func (r VacationResponseGetResponse) GetNotFound() []string { return r.NotFound }
|
||||
func (r VacationResponseGetResponse) GetList() []VacationResponse { return r.List }
|
||||
@@ -4195,6 +4218,26 @@ var _ Idable = &Blob{}
|
||||
func (f Blob) GetObjectType() ObjectType { return BlobType }
|
||||
func (f Blob) GetId() string { return f.Id }
|
||||
|
||||
type BlobChange struct {
|
||||
}
|
||||
|
||||
var _ Change = BlobChange{}
|
||||
|
||||
func (m BlobChange) AsPatch() (PatchObject, error) {
|
||||
return nil, fmt.Errorf("BlobChange is unsupported")
|
||||
}
|
||||
|
||||
type BlobChanges ChangesTemplate[Blob]
|
||||
|
||||
var _ Changes[Blob] = BlobChanges{}
|
||||
|
||||
func (c BlobChanges) GetHasMoreChanges() bool { return c.HasMoreChanges }
|
||||
func (c BlobChanges) GetOldState() State { return c.OldState }
|
||||
func (c BlobChanges) GetNewState() State { return c.NewState }
|
||||
func (c BlobChanges) GetCreated() []Blob { return c.Created }
|
||||
func (c BlobChanges) GetUpdated() []Blob { return c.Updated }
|
||||
func (c BlobChanges) GetDestroyed() []string { return c.Destroyed }
|
||||
|
||||
type BlobGetCommand struct {
|
||||
AccountId string `json:"accountId"`
|
||||
Ids []string `json:"ids,omitempty"`
|
||||
@@ -4255,6 +4298,7 @@ type BlobGetResponse struct {
|
||||
|
||||
var _ GetResponse[Blob] = &BlobGetResponse{}
|
||||
|
||||
func (r BlobGetResponse) GetMarker() Blob { return Blob{} }
|
||||
func (r BlobGetResponse) GetState() State { return r.State }
|
||||
func (r BlobGetResponse) GetNotFound() []string { return r.NotFound }
|
||||
func (r BlobGetResponse) GetList() []Blob { return r.List }
|
||||
@@ -6370,6 +6414,15 @@ var _ Idable = &Quota{}
|
||||
func (f Quota) GetObjectType() ObjectType { return QuotaType }
|
||||
func (f Quota) GetId() string { return f.Id }
|
||||
|
||||
type QuotaChange struct {
|
||||
}
|
||||
|
||||
var _ Change = QuotaChange{}
|
||||
|
||||
func (m QuotaChange) AsPatch() (PatchObject, error) {
|
||||
return nil, fmt.Errorf("QuotaChange is unsupported")
|
||||
}
|
||||
|
||||
// See [RFC8098] for the exact meaning of these different fields.
|
||||
//
|
||||
// These fields are defined as case insensitive in [RFC8098] but are case sensitive in this RFC
|
||||
@@ -6492,6 +6545,7 @@ type QuotaGetResponse struct {
|
||||
|
||||
var _ GetResponse[Quota] = &QuotaGetResponse{}
|
||||
|
||||
func (r QuotaGetResponse) GetMarker() Quota { return Quota{} }
|
||||
func (r QuotaGetResponse) GetState() State { return r.State }
|
||||
func (r QuotaGetResponse) GetNotFound() []string { return r.NotFound }
|
||||
func (r QuotaGetResponse) GetList() []Quota { return r.List }
|
||||
@@ -6593,6 +6647,7 @@ type AddressBookGetResponse struct {
|
||||
|
||||
var _ GetResponse[AddressBook] = &AddressBookGetResponse{}
|
||||
|
||||
func (r AddressBookGetResponse) GetMarker() AddressBook { return AddressBook{} }
|
||||
func (r AddressBookGetResponse) GetState() State { return r.State }
|
||||
func (r AddressBookGetResponse) GetNotFound() []string { return r.NotFound }
|
||||
func (r AddressBookGetResponse) GetList() []AddressBook { return r.List }
|
||||
@@ -7168,6 +7223,7 @@ type ContactCardGetResponse struct {
|
||||
|
||||
var _ GetResponse[ContactCard] = &ContactCardGetResponse{}
|
||||
|
||||
func (r ContactCardGetResponse) GetMarker() ContactCard { return ContactCard{} }
|
||||
func (r ContactCardGetResponse) GetState() State { return r.State }
|
||||
func (r ContactCardGetResponse) GetNotFound() []string { return r.NotFound }
|
||||
func (r ContactCardGetResponse) GetList() []ContactCard { return r.List }
|
||||
@@ -7251,7 +7307,7 @@ type ContactCardSetCommand struct {
|
||||
// Any such property may be omitted by the client.
|
||||
//
|
||||
// The client MUST omit any properties that may only be set by the server.
|
||||
Create map[string]ContactCard `json:"create,omitempty"`
|
||||
Create map[string]ContactCardChange `json:"create,omitempty"`
|
||||
|
||||
// A map of an id to a `Patch` object to apply to the current Email object with that id,
|
||||
// or null if no objects are to be updated.
|
||||
@@ -7420,6 +7476,7 @@ type CalendarGetResponse struct {
|
||||
|
||||
var _ GetResponse[Calendar] = &CalendarGetResponse{}
|
||||
|
||||
func (r CalendarGetResponse) GetMarker() Calendar { return Calendar{} }
|
||||
func (r CalendarGetResponse) GetState() State { return r.State }
|
||||
func (r CalendarGetResponse) GetNotFound() []string { return r.NotFound }
|
||||
func (r CalendarGetResponse) GetList() []Calendar { return r.List }
|
||||
@@ -7896,6 +7953,7 @@ type CalendarEventGetResponse struct {
|
||||
|
||||
var _ GetResponse[CalendarEvent] = &CalendarEventGetResponse{}
|
||||
|
||||
func (r CalendarEventGetResponse) GetMarker() CalendarEvent { return CalendarEvent{} }
|
||||
func (r CalendarEventGetResponse) GetState() State { return r.State }
|
||||
func (r CalendarEventGetResponse) GetNotFound() []string { return r.NotFound }
|
||||
func (r CalendarEventGetResponse) GetList() []CalendarEvent { return r.List }
|
||||
@@ -7985,7 +8043,7 @@ type CalendarEventSetCommand struct {
|
||||
// Any such property may be omitted by the client.
|
||||
//
|
||||
// The client MUST omit any properties that may only be set by the server.
|
||||
Create map[string]CalendarEvent `json:"create,omitempty"`
|
||||
Create map[string]CalendarEventChange `json:"create,omitempty"`
|
||||
|
||||
// A map of an id to a `Patch` object to apply to the current Email object with that id,
|
||||
// or null if no objects are to be updated.
|
||||
|
||||
@@ -2058,8 +2058,8 @@ func (e Exemplar) EmailChanges() EmailChanges {
|
||||
}
|
||||
}
|
||||
|
||||
func (e Exemplar) Changes() (Changes, string, string) {
|
||||
return Changes{
|
||||
func (e Exemplar) Changes() (ObjectChanges, string, string) {
|
||||
return ObjectChanges{
|
||||
MaxChanges: 3,
|
||||
Mailboxes: &MailboxChangesResponse{
|
||||
AccountId: e.AccountId,
|
||||
|
||||
@@ -35,14 +35,6 @@ func get[T Foo, GETREQ GetCommand[T], GETRESP GetResponse[T], RESP any]( //NOSON
|
||||
})
|
||||
}
|
||||
|
||||
func getA[T Foo, GETREQ GetCommand[T], GETRESP GetResponse[T]]( //NOSONAR
|
||||
client *Client, name string, objType ObjectType,
|
||||
getCommandFactory func(string, []string) GETREQ,
|
||||
resp GETRESP,
|
||||
accountId string, ids []string, ctx Context) ([]T, SessionState, State, Language, Error) {
|
||||
return get(client, name, objType, getCommandFactory, resp, func(r GETRESP) []T { return r.GetList() }, accountId, ids, ctx)
|
||||
}
|
||||
|
||||
func getAN[T Foo, GETREQ GetCommand[T], GETRESP GetResponse[T], RESP any]( //NOSONAR
|
||||
client *Client, name string, objType ObjectType,
|
||||
getCommandFactory func(string, []string) GETREQ,
|
||||
|
||||
@@ -50,6 +50,11 @@ func mcid(accountId string, tag string) string {
|
||||
return accountId + ":" + tag
|
||||
}
|
||||
|
||||
func bail[R JmapResponse[T], T Foo](err Error) (R, SessionState, State, Language, Error) {
|
||||
var zero R
|
||||
return zero, EmptySessionState, EmptyState, NoLanguage, err
|
||||
}
|
||||
|
||||
type Cmdr interface {
|
||||
ApiSupplier
|
||||
Hooks
|
||||
|
||||
@@ -10,14 +10,14 @@ import (
|
||||
)
|
||||
|
||||
// Get attributes of a given account.
|
||||
func (g *Groupware) GetAccount(w http.ResponseWriter, r *http.Request) {
|
||||
func (g *Groupware) GetAccountById(w http.ResponseWriter, r *http.Request) {
|
||||
g.respond(w, r, func(req Request) Response {
|
||||
accountId, account, err := req.GetAccountForMail()
|
||||
if err != nil {
|
||||
return req.error(accountId, err)
|
||||
}
|
||||
var body jmap.Account = account
|
||||
return req.respond(accountId, body, req.session.State, AccountResponseObjectType, "")
|
||||
return req.respond(accountId, body, req.session.State, AccountResponseObjectType, jmap.EmptyState, jmap.NoLanguage)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ func (g *Groupware) GetAccounts(w http.ResponseWriter, r *http.Request) {
|
||||
// sort on accountId to have a stable order that remains the same with every query
|
||||
slices.SortFunc(list, func(a, b AccountWithId) int { return strings.Compare(a.AccountId, b.AccountId) })
|
||||
var RBODY []AccountWithId = list
|
||||
return req.respondN(structs.Map(list, func(a AccountWithId) string { return a.AccountId }), RBODY, req.session.State, AccountResponseObjectType, "")
|
||||
return req.respondN(structs.Map(list, func(a AccountWithId) string { return a.AccountId }), RBODY, req.session.State, AccountResponseObjectType, jmap.EmptyState, jmap.NoLanguage)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -66,7 +66,7 @@ func (g *Groupware) GetAccountsWithTheirIdentities(w http.ResponseWriter, r *htt
|
||||
// sort on accountId to have a stable order that remains the same with every query
|
||||
slices.SortFunc(list, func(a, b AccountWithIdAndIdentities) int { return strings.Compare(a.AccountId, b.AccountId) })
|
||||
var RBODY []AccountWithIdAndIdentities = list
|
||||
return req.respondN(structs.Map(list, func(a AccountWithIdAndIdentities) string { return a.AccountId }), RBODY, sessionState, AccountResponseObjectType, state)
|
||||
return req.respondN(structs.Map(list, func(a AccountWithIdAndIdentities) string { return a.AccountId }), RBODY, sessionState, AccountResponseObjectType, state, lang)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -76,7 +76,7 @@ type AccountWithId struct {
|
||||
}
|
||||
|
||||
type AccountWithIdAndIdentities struct {
|
||||
AccountId string `json:"accountId,omitempty"`
|
||||
jmap.Account
|
||||
AccountId string `json:"accountId,omitempty"`
|
||||
Identities []jmap.Identity `json:"identities,omitempty"`
|
||||
jmap.Account
|
||||
}
|
||||
|
||||
@@ -2,188 +2,32 @@ package groupware
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/opencloud-eu/opencloud/pkg/jmap"
|
||||
"github.com/opencloud-eu/opencloud/pkg/log"
|
||||
)
|
||||
|
||||
// Get all addressbooks of an account.
|
||||
func (g *Groupware) GetAddressbooks(w http.ResponseWriter, r *http.Request) {
|
||||
g.respond(w, r, func(req Request) Response {
|
||||
ok, accountId, resp := req.needContactWithAccount()
|
||||
if !ok {
|
||||
return resp
|
||||
}
|
||||
|
||||
addressbooks, sessionState, state, lang, jerr := g.jmap.GetAddressbooks(accountId, []string{}, req.ctx)
|
||||
if jerr != nil {
|
||||
return req.jmapError(accountId, jerr, sessionState, lang)
|
||||
}
|
||||
|
||||
var body jmap.AddressBookGetResponse = addressbooks
|
||||
return req.respond(accountId, body, sessionState, AddressBookResponseObjectType, state)
|
||||
})
|
||||
getall(AddressBook, w, r, g, g.jmap.GetAddressbooks)
|
||||
}
|
||||
|
||||
// Get an addressbook of an account by its identifier.
|
||||
func (g *Groupware) GetAddressbook(w http.ResponseWriter, r *http.Request) {
|
||||
g.respond(w, r, func(req Request) Response {
|
||||
ok, accountId, resp := req.needContactWithAccount()
|
||||
if !ok {
|
||||
return resp
|
||||
}
|
||||
|
||||
l := req.logger.With()
|
||||
|
||||
addressBookId, err := req.PathParam(UriParamAddressBookId)
|
||||
if err != nil {
|
||||
return req.error(accountId, err)
|
||||
}
|
||||
l = l.Str(UriParamAddressBookId, log.SafeString(addressBookId))
|
||||
|
||||
logger := log.From(l)
|
||||
addressbooks, sessionState, state, lang, jerr := g.jmap.GetAddressbooks(accountId, []string{addressBookId}, req.ctx)
|
||||
if jerr != nil {
|
||||
return req.jmapError(accountId, jerr, sessionState, lang)
|
||||
}
|
||||
|
||||
switch len(addressbooks.List) {
|
||||
case 0:
|
||||
return req.notFound(accountId, sessionState, ContactResponseObjectType, state)
|
||||
case 1:
|
||||
return req.respond(accountId, addressbooks.List[0], sessionState, ContactResponseObjectType, state)
|
||||
default:
|
||||
logger.Error().Msgf("found %d addressbooks matching '%s' instead of 1", len(addressbooks.List), addressBookId)
|
||||
return req.errorS(accountId, req.apiError(&ErrorMultipleIdMatches), sessionState)
|
||||
}
|
||||
})
|
||||
func (g *Groupware) GetAddressbookById(w http.ResponseWriter, r *http.Request) {
|
||||
get(AddressBook, w, r, g, g.jmap.GetAddressbooks)
|
||||
}
|
||||
|
||||
// Get the changes to Address Books since a certain State.
|
||||
// @api:tags addressbook,changes
|
||||
func (g *Groupware) GetAddressBookChanges(w http.ResponseWriter, r *http.Request) {
|
||||
g.respond(w, r, func(req Request) Response {
|
||||
ok, accountId, resp := req.needContactWithAccount()
|
||||
if !ok {
|
||||
return resp
|
||||
}
|
||||
|
||||
l := req.logger.With()
|
||||
|
||||
maxChanges, ok, err := req.parseUIntParam(QueryParamMaxChanges, 0)
|
||||
if err != nil {
|
||||
return req.error(accountId, err)
|
||||
}
|
||||
if ok {
|
||||
l = l.Uint(QueryParamMaxChanges, maxChanges)
|
||||
}
|
||||
|
||||
sinceState := jmap.State(req.OptHeaderParamDoc(HeaderParamSince, "Optionally specifies the state identifier from which on to list addressbook changes"))
|
||||
if sinceState != "" {
|
||||
l = l.Str(HeaderParamSince, log.SafeString(string(sinceState)))
|
||||
}
|
||||
|
||||
logger := log.From(l)
|
||||
ctx := req.ctx.WithLogger(logger)
|
||||
changes, sessionState, state, lang, jerr := g.jmap.GetAddressbookChanges(accountId, sinceState, maxChanges, ctx)
|
||||
if jerr != nil {
|
||||
return req.jmapError(accountId, jerr, sessionState, lang)
|
||||
}
|
||||
|
||||
return req.respond(accountId, changes, sessionState, AddressBookResponseObjectType, state)
|
||||
})
|
||||
changes(AddressBook, w, r, g, g.jmap.GetAddressbookChanges)
|
||||
}
|
||||
|
||||
func (g *Groupware) CreateAddressBook(w http.ResponseWriter, r *http.Request) {
|
||||
g.respond(w, r, func(req Request) Response {
|
||||
ok, accountId, resp := req.needContactWithAccount()
|
||||
if !ok {
|
||||
return resp
|
||||
}
|
||||
|
||||
l := req.logger.With()
|
||||
|
||||
var create jmap.AddressBookChange
|
||||
err := req.bodydoc(&create, "The address book to create")
|
||||
if err != nil {
|
||||
return req.error(accountId, err)
|
||||
}
|
||||
|
||||
logger := log.From(l)
|
||||
ctx := req.ctx.WithLogger(logger)
|
||||
created, sessionState, state, lang, jerr := g.jmap.CreateAddressBook(accountId, create, ctx)
|
||||
if jerr != nil {
|
||||
return req.jmapError(accountId, jerr, sessionState, lang)
|
||||
}
|
||||
return req.respond(accountId, created, sessionState, ContactResponseObjectType, state)
|
||||
})
|
||||
create(AddressBook, w, r, g, nil, g.jmap.CreateAddressBook)
|
||||
}
|
||||
|
||||
func (g *Groupware) DeleteAddressBook(w http.ResponseWriter, r *http.Request) {
|
||||
g.respond(w, r, func(req Request) Response {
|
||||
ok, accountId, resp := req.needContactWithAccount()
|
||||
if !ok {
|
||||
return resp
|
||||
}
|
||||
l := req.logger.With().Str(accountId, log.SafeString(accountId))
|
||||
|
||||
addressBookId, err := req.PathParam(UriParamAddressBookId)
|
||||
if err != nil {
|
||||
return req.error(accountId, err)
|
||||
}
|
||||
l.Str(UriParamAddressBookId, log.SafeString(addressBookId))
|
||||
|
||||
logger := log.From(l)
|
||||
ctx := req.ctx.WithLogger(logger)
|
||||
deleted, sessionState, state, lang, jerr := g.jmap.DeleteAddressBook(accountId, []string{addressBookId}, ctx)
|
||||
if jerr != nil {
|
||||
return req.jmapError(accountId, jerr, sessionState, lang)
|
||||
}
|
||||
|
||||
for _, e := range deleted {
|
||||
desc := e.Description
|
||||
if desc != "" {
|
||||
return req.error(accountId, apiError(
|
||||
req.errorId(),
|
||||
ErrorFailedToDeleteAddressBook,
|
||||
withDetail(e.Description),
|
||||
))
|
||||
} else {
|
||||
return req.error(accountId, apiError(
|
||||
req.errorId(),
|
||||
ErrorFailedToDeleteAddressBook,
|
||||
))
|
||||
}
|
||||
}
|
||||
return req.noContent(accountId, sessionState, AddressBookResponseObjectType, state)
|
||||
})
|
||||
delete(AddressBook, w, r, g, g.jmap.DeleteAddressBook)
|
||||
}
|
||||
|
||||
func (g *Groupware) ModifyAddressBook(w http.ResponseWriter, r *http.Request) {
|
||||
g.respond(w, r, func(req Request) Response {
|
||||
ok, accountId, resp := req.needContactWithAccount()
|
||||
if !ok {
|
||||
return resp
|
||||
}
|
||||
l := req.logger.With().Str(accountId, log.SafeString(accountId))
|
||||
id, err := req.PathParamDoc(UriParamAddressBookId, "The unique identifier of the AddressBook to modify")
|
||||
if err != nil {
|
||||
return req.error(accountId, err)
|
||||
}
|
||||
l.Str(UriParamAddressBookId, log.SafeString(id))
|
||||
|
||||
var change jmap.AddressBookChange
|
||||
err = req.body(&change)
|
||||
if err != nil {
|
||||
return req.error(accountId, err)
|
||||
}
|
||||
|
||||
logger := log.From(l)
|
||||
ctx := req.ctx.WithLogger(logger)
|
||||
updated, sessionState, state, lang, jerr := g.jmap.UpdateAddressBook(accountId, id, change, ctx)
|
||||
if jerr != nil {
|
||||
return req.jmapError(accountId, jerr, sessionState, lang)
|
||||
}
|
||||
return req.respond(accountId, updated, sessionState, AddressBookResponseObjectType, state)
|
||||
})
|
||||
modify(AddressBook, w, r, g, g.jmap.UpdateAddressBook)
|
||||
}
|
||||
|
||||
@@ -14,31 +14,7 @@ const (
|
||||
)
|
||||
|
||||
func (g *Groupware) GetBlobMeta(w http.ResponseWriter, r *http.Request) {
|
||||
g.respond(w, r, func(req Request) Response {
|
||||
accountId, err := req.GetAccountIdForBlob()
|
||||
if err != nil {
|
||||
return req.error(accountId, err)
|
||||
}
|
||||
l := req.logger.With().Str(logAccountId, accountId)
|
||||
|
||||
blobId, err := req.PathParam(UriParamBlobId)
|
||||
if err != nil {
|
||||
return req.error(accountId, err)
|
||||
}
|
||||
l = l.Str(UriParamBlobId, blobId)
|
||||
|
||||
logger := log.From(l)
|
||||
ctx := req.ctx.WithLogger(logger)
|
||||
|
||||
res, sessionState, state, lang, jerr := g.jmap.GetBlobMetadata(accountId, blobId, ctx)
|
||||
if jerr != nil {
|
||||
return req.jmapError(accountId, jerr, sessionState, lang)
|
||||
}
|
||||
if res == nil {
|
||||
return req.notFound(accountId, sessionState, BlobResponseObjectType, state)
|
||||
}
|
||||
return req.respond(accountId, res, sessionState, BlobResponseObjectType, state)
|
||||
})
|
||||
get(Blob, w, r, g, g.jmap.GetBlobMetadata)
|
||||
}
|
||||
|
||||
func (g *Groupware) UploadBlob(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
@@ -2,187 +2,32 @@ package groupware
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/opencloud-eu/opencloud/pkg/jmap"
|
||||
"github.com/opencloud-eu/opencloud/pkg/log"
|
||||
)
|
||||
|
||||
// Get all calendars of an account.
|
||||
func (g *Groupware) GetCalendars(w http.ResponseWriter, r *http.Request) {
|
||||
g.respond(w, r, func(req Request) Response {
|
||||
ok, accountId, resp := req.needCalendarWithAccount()
|
||||
if !ok {
|
||||
return resp
|
||||
}
|
||||
|
||||
calendars, sessionState, state, lang, jerr := g.jmap.GetCalendars(accountId, nil, req.ctx)
|
||||
if jerr != nil {
|
||||
return req.jmapError(accountId, jerr, sessionState, lang)
|
||||
}
|
||||
|
||||
return req.respond(accountId, calendars, sessionState, CalendarResponseObjectType, state)
|
||||
})
|
||||
getall(Calendar, w, r, g, g.jmap.GetCalendars)
|
||||
}
|
||||
|
||||
// Get a calendar of an account by its identifier.
|
||||
func (g *Groupware) GetCalendarById(w http.ResponseWriter, r *http.Request) {
|
||||
g.respond(w, r, func(req Request) Response {
|
||||
ok, accountId, resp := req.needCalendarWithAccount()
|
||||
if !ok {
|
||||
return resp
|
||||
}
|
||||
|
||||
l := req.logger.With()
|
||||
|
||||
calendarId, err := req.PathParam(UriParamCalendarId)
|
||||
if err != nil {
|
||||
return req.error(accountId, err)
|
||||
}
|
||||
l = l.Str(UriParamCalendarId, log.SafeString(calendarId))
|
||||
|
||||
logger := log.From(l)
|
||||
calendars, sessionState, state, lang, jerr := g.jmap.GetCalendars(accountId, single(calendarId), req.ctx)
|
||||
if jerr != nil {
|
||||
return req.jmapError(accountId, jerr, sessionState, lang)
|
||||
}
|
||||
|
||||
switch len(calendars.List) {
|
||||
case 0:
|
||||
return req.notFound(accountId, sessionState, ContactResponseObjectType, state)
|
||||
case 1:
|
||||
return req.respond(accountId, calendars.List[0], sessionState, ContactResponseObjectType, state)
|
||||
default:
|
||||
logger.Error().Msgf("found %d calendars matching '%s' instead of 1", len(calendars.List), calendarId)
|
||||
return req.errorS(accountId, req.apiError(&ErrorMultipleIdMatches), sessionState)
|
||||
}
|
||||
})
|
||||
get(Calendar, w, r, g, g.jmap.GetCalendars)
|
||||
}
|
||||
|
||||
// Get the changes to Calendars since a certain State.
|
||||
// @api:tags calendar,changes
|
||||
func (g *Groupware) GetCalendarChanges(w http.ResponseWriter, r *http.Request) {
|
||||
g.respond(w, r, func(req Request) Response {
|
||||
ok, accountId, resp := req.needCalendarWithAccount()
|
||||
if !ok {
|
||||
return resp
|
||||
}
|
||||
|
||||
l := req.logger.With()
|
||||
|
||||
maxChanges, ok, err := req.parseUIntParam(QueryParamMaxChanges, 0)
|
||||
if err != nil {
|
||||
return req.error(accountId, err)
|
||||
}
|
||||
if ok {
|
||||
l = l.Uint(QueryParamMaxChanges, maxChanges)
|
||||
}
|
||||
|
||||
sinceState := jmap.State(req.OptHeaderParamDoc(HeaderParamSince, "Optionally specifies the state identifier from which on to list calendar changes"))
|
||||
if sinceState != "" {
|
||||
l = l.Str(HeaderParamSince, log.SafeString(string(sinceState)))
|
||||
}
|
||||
|
||||
logger := log.From(l)
|
||||
ctx := req.ctx.WithLogger(logger)
|
||||
changes, sessionState, state, lang, jerr := g.jmap.GetCalendarChanges(accountId, sinceState, maxChanges, ctx)
|
||||
if jerr != nil {
|
||||
return req.jmapError(accountId, jerr, sessionState, lang)
|
||||
}
|
||||
|
||||
return req.respond(accountId, changes, sessionState, CalendarResponseObjectType, state)
|
||||
})
|
||||
changes(Calendar, w, r, g, g.jmap.GetCalendarChanges)
|
||||
}
|
||||
|
||||
func (g *Groupware) CreateCalendar(w http.ResponseWriter, r *http.Request) {
|
||||
g.respond(w, r, func(req Request) Response {
|
||||
ok, accountId, resp := req.needCalendarWithAccount()
|
||||
if !ok {
|
||||
return resp
|
||||
}
|
||||
|
||||
l := req.logger.With()
|
||||
|
||||
var create jmap.CalendarChange
|
||||
err := req.bodydoc(&create, "The calendar to create")
|
||||
if err != nil {
|
||||
return req.error(accountId, err)
|
||||
}
|
||||
|
||||
logger := log.From(l)
|
||||
ctx := req.ctx.WithLogger(logger)
|
||||
created, sessionState, state, lang, jerr := g.jmap.CreateCalendar(accountId, create, ctx)
|
||||
if jerr != nil {
|
||||
return req.jmapError(accountId, jerr, sessionState, lang)
|
||||
}
|
||||
return req.respond(accountId, created, sessionState, ContactResponseObjectType, state)
|
||||
})
|
||||
create(Calendar, w, r, g, nil, g.jmap.CreateCalendar)
|
||||
}
|
||||
|
||||
func (g *Groupware) DeleteCalendar(w http.ResponseWriter, r *http.Request) {
|
||||
g.respond(w, r, func(req Request) Response {
|
||||
ok, accountId, resp := req.needCalendarWithAccount()
|
||||
if !ok {
|
||||
return resp
|
||||
}
|
||||
l := req.logger.With().Str(accountId, log.SafeString(accountId))
|
||||
|
||||
calendarId, err := req.PathParam(UriParamCalendarId)
|
||||
if err != nil {
|
||||
return req.error(accountId, err)
|
||||
}
|
||||
l.Str(UriParamCalendarId, log.SafeString(calendarId))
|
||||
|
||||
logger := log.From(l)
|
||||
ctx := req.ctx.WithLogger(logger)
|
||||
deleted, sessionState, state, lang, jerr := g.jmap.DeleteCalendar(accountId, single(calendarId), ctx)
|
||||
if jerr != nil {
|
||||
return req.jmapError(accountId, jerr, sessionState, lang)
|
||||
}
|
||||
|
||||
for _, e := range deleted {
|
||||
desc := e.Description
|
||||
if desc != "" {
|
||||
return req.error(accountId, apiError(
|
||||
req.errorId(),
|
||||
ErrorFailedToDeleteCalendar,
|
||||
withDetail(e.Description),
|
||||
))
|
||||
} else {
|
||||
return req.error(accountId, apiError(
|
||||
req.errorId(),
|
||||
ErrorFailedToDeleteCalendar,
|
||||
))
|
||||
}
|
||||
}
|
||||
return req.noContent(accountId, sessionState, CalendarResponseObjectType, state)
|
||||
})
|
||||
delete(Calendar, w, r, g, g.jmap.DeleteCalendar)
|
||||
}
|
||||
|
||||
func (g *Groupware) ModifyCalendar(w http.ResponseWriter, r *http.Request) {
|
||||
g.respond(w, r, func(req Request) Response {
|
||||
ok, accountId, resp := req.needCalendarWithAccount()
|
||||
if !ok {
|
||||
return resp
|
||||
}
|
||||
l := req.logger.With().Str(accountId, log.SafeString(accountId))
|
||||
id, err := req.PathParamDoc(UriParamCalendarId, "The unique identifier of the Calendar to modify")
|
||||
if err != nil {
|
||||
return req.error(accountId, err)
|
||||
}
|
||||
l.Str(UriParamCalendarId, log.SafeString(id))
|
||||
|
||||
var change jmap.CalendarChange
|
||||
err = req.body(&change)
|
||||
if err != nil {
|
||||
return req.error(accountId, err)
|
||||
}
|
||||
|
||||
logger := log.From(l)
|
||||
ctx := req.ctx.WithLogger(logger)
|
||||
updated, sessionState, state, lang, jerr := g.jmap.UpdateCalendar(accountId, id, change, ctx)
|
||||
if jerr != nil {
|
||||
return req.jmapError(accountId, jerr, sessionState, lang)
|
||||
}
|
||||
return req.respond(accountId, updated, sessionState, CalendarResponseObjectType, state)
|
||||
})
|
||||
modify(Calendar, w, r, g, g.jmap.UpdateCalendar)
|
||||
}
|
||||
|
||||
@@ -82,8 +82,8 @@ func (g *Groupware) GetChanges(w http.ResponseWriter, r *http.Request) { //NOSON
|
||||
if jerr != nil {
|
||||
return req.jmapError(accountId, jerr, sessionState, lang)
|
||||
}
|
||||
var body jmap.Changes = changes
|
||||
var body jmap.ObjectChanges = changes
|
||||
|
||||
return req.respond(accountId, body, sessionState, "", state)
|
||||
return req.respond(accountId, body, sessionState, "", state, lang)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -91,7 +91,7 @@ func (g *Groupware) GetContactsInAddressbook(w http.ResponseWriter, r *http.Requ
|
||||
}
|
||||
|
||||
if contacts, ok := contactsByAccountId[accountId]; ok {
|
||||
return req.respondN(accountIds, contacts, sessionState, ContactResponseObjectType, state)
|
||||
return req.respondN(accountIds, contacts, sessionState, ContactResponseObjectType, state, lang)
|
||||
} else {
|
||||
return req.notFoundN(accountIds, sessionState, ContactResponseObjectType, state)
|
||||
}
|
||||
@@ -99,192 +99,36 @@ func (g *Groupware) GetContactsInAddressbook(w http.ResponseWriter, r *http.Requ
|
||||
}
|
||||
|
||||
func (g *Groupware) GetContactById(w http.ResponseWriter, r *http.Request) {
|
||||
g.respond(w, r, func(req Request) Response {
|
||||
ok, accountId, resp := req.needContactWithAccount()
|
||||
if !ok {
|
||||
return resp
|
||||
}
|
||||
|
||||
l := req.logger.With()
|
||||
|
||||
contactId, err := req.PathParam(UriParamContactId)
|
||||
if err != nil {
|
||||
return req.error(accountId, err)
|
||||
}
|
||||
l = l.Str(UriParamContactId, log.SafeString(contactId))
|
||||
|
||||
logger := log.From(l)
|
||||
ctx := req.ctx.WithLogger(logger)
|
||||
contacts, sessionState, state, lang, jerr := g.jmap.GetContactCards(accountId, single(contactId), ctx)
|
||||
if jerr != nil {
|
||||
return req.jmapError(accountId, jerr, sessionState, lang)
|
||||
}
|
||||
|
||||
switch len(contacts.List) {
|
||||
case 0:
|
||||
return req.notFound(accountId, sessionState, ContactResponseObjectType, state)
|
||||
case 1:
|
||||
return req.respond(accountId, contacts.List[0], sessionState, ContactResponseObjectType, state)
|
||||
default:
|
||||
logger.Error().Msgf("found %d contacts matching '%s' instead of 1", len(contacts.List), contactId)
|
||||
return req.errorS(accountId, req.apiError(&ErrorMultipleIdMatches), sessionState)
|
||||
}
|
||||
})
|
||||
get(Contact, w, r, g, g.jmap.GetContactCards)
|
||||
}
|
||||
|
||||
func (g *Groupware) GetAllContacts(w http.ResponseWriter, r *http.Request) {
|
||||
g.respond(w, r, func(req Request) Response {
|
||||
ok, accountId, resp := req.needContactWithAccount()
|
||||
if !ok {
|
||||
return resp
|
||||
}
|
||||
|
||||
l := req.logger.With()
|
||||
|
||||
logger := log.From(l)
|
||||
ctx := req.ctx.WithLogger(logger)
|
||||
contacts, sessionState, state, lang, jerr := g.jmap.GetContactCards(accountId, []string{}, ctx)
|
||||
if jerr != nil {
|
||||
return req.jmapError(accountId, jerr, sessionState, lang)
|
||||
}
|
||||
var body []jmap.ContactCard = contacts.List
|
||||
|
||||
return req.respond(accountId, body, sessionState, ContactResponseObjectType, state)
|
||||
})
|
||||
getallpaged(Contact, w, r, g,
|
||||
g.jmap.GetContactCards,
|
||||
func(cid string) jmap.ContactCardFilterElement {
|
||||
return jmap.ContactCardFilterCondition{InAddressBook: cid}
|
||||
},
|
||||
[]jmap.ContactCardComparator{{Property: jmap.ContactCardPropertyUpdated, IsAscending: true}},
|
||||
curryMapQuery(g.jmap.QueryContactCards),
|
||||
)
|
||||
}
|
||||
|
||||
// Get changes to Contacts since a given State
|
||||
// @api:tags contact,changes
|
||||
func (g *Groupware) GetContactsChanges(w http.ResponseWriter, r *http.Request) {
|
||||
g.respond(w, r, func(req Request) Response {
|
||||
ok, accountId, resp := req.needContactWithAccount()
|
||||
if !ok {
|
||||
return resp
|
||||
}
|
||||
|
||||
l := req.logger.With()
|
||||
|
||||
var maxChanges uint = 0
|
||||
if v, ok, err := req.parseUIntParam(QueryParamMaxChanges, 0); err != nil {
|
||||
return req.error(accountId, err)
|
||||
} else if ok {
|
||||
maxChanges = v
|
||||
l = l.Uint(QueryParamMaxChanges, v)
|
||||
}
|
||||
|
||||
sinceState := jmap.State(req.OptHeaderParamDoc(HeaderParamSince, "Specifies the state identifier from which on to list contact changes"))
|
||||
l = l.Str(HeaderParamSince, log.SafeString(string(sinceState)))
|
||||
|
||||
logger := log.From(l)
|
||||
ctx := req.ctx.WithLogger(logger)
|
||||
changes, sessionState, state, lang, jerr := g.jmap.GetContactCardChanges(accountId, sinceState, maxChanges, ctx)
|
||||
if jerr != nil {
|
||||
return req.jmapError(accountId, jerr, sessionState, lang)
|
||||
}
|
||||
var body jmap.ContactCardChanges = changes
|
||||
|
||||
return req.respond(accountId, body, sessionState, ContactResponseObjectType, state)
|
||||
})
|
||||
changes(Contact, w, r, g, g.jmap.GetContactCardChanges)
|
||||
}
|
||||
|
||||
func (g *Groupware) CreateContact(w http.ResponseWriter, r *http.Request) {
|
||||
g.respond(w, r, func(req Request) Response {
|
||||
ok, accountId, resp := req.needContactWithAccount()
|
||||
if !ok {
|
||||
return resp
|
||||
}
|
||||
|
||||
l := req.logger.With()
|
||||
|
||||
addressBookId, err := req.PathParam(UriParamAddressBookId)
|
||||
if err != nil {
|
||||
return req.error(accountId, err)
|
||||
}
|
||||
l = l.Str(UriParamAddressBookId, log.SafeString(addressBookId))
|
||||
|
||||
var create jmap.ContactCard
|
||||
err = req.bodydoc(&create, "The contact to create, which may not have its id attribute set")
|
||||
if err != nil {
|
||||
return req.error(accountId, err)
|
||||
}
|
||||
|
||||
logger := log.From(l)
|
||||
ctx := req.ctx.WithLogger(logger)
|
||||
created, sessionState, state, lang, jerr := g.jmap.CreateContactCard(accountId, create, ctx)
|
||||
if jerr != nil {
|
||||
return req.jmapError(accountId, jerr, sessionState, lang)
|
||||
}
|
||||
return req.respond(accountId, created, sessionState, ContactResponseObjectType, state)
|
||||
})
|
||||
create(Contact, w, r, g, nil, g.jmap.CreateContactCard)
|
||||
}
|
||||
|
||||
func (g *Groupware) DeleteContact(w http.ResponseWriter, r *http.Request) {
|
||||
g.respond(w, r, func(req Request) Response {
|
||||
ok, accountId, resp := req.needContactWithAccount()
|
||||
if !ok {
|
||||
return resp
|
||||
}
|
||||
l := req.logger.With().Str(accountId, log.SafeString(accountId))
|
||||
|
||||
contactId, err := req.PathParam(UriParamContactId)
|
||||
if err != nil {
|
||||
return req.error(accountId, err)
|
||||
}
|
||||
l.Str(UriParamContactId, log.SafeString(contactId))
|
||||
|
||||
logger := log.From(l)
|
||||
ctx := req.ctx.WithLogger(logger)
|
||||
deleted, sessionState, state, lang, jerr := g.jmap.DeleteContactCard(accountId, single(contactId), ctx)
|
||||
if jerr != nil {
|
||||
return req.jmapError(accountId, jerr, sessionState, lang)
|
||||
}
|
||||
|
||||
for _, e := range deleted {
|
||||
desc := e.Description
|
||||
if desc != "" {
|
||||
return req.error(accountId, apiError(
|
||||
req.errorId(),
|
||||
ErrorFailedToDeleteContact,
|
||||
withDetail(e.Description),
|
||||
))
|
||||
} else {
|
||||
return req.error(accountId, apiError(
|
||||
req.errorId(),
|
||||
ErrorFailedToDeleteContact,
|
||||
))
|
||||
}
|
||||
}
|
||||
return req.noContent(accountId, sessionState, ContactResponseObjectType, state)
|
||||
})
|
||||
delete(Contact, w, r, g, g.jmap.DeleteContactCard)
|
||||
}
|
||||
|
||||
func (g *Groupware) ModifyContact(w http.ResponseWriter, r *http.Request) {
|
||||
g.respond(w, r, func(req Request) Response {
|
||||
ok, accountId, resp := req.needContactWithAccount()
|
||||
if !ok {
|
||||
return resp
|
||||
}
|
||||
l := req.logger.With().Str(accountId, log.SafeString(accountId))
|
||||
id, err := req.PathParamDoc(UriParamContactId, "The unique identifier of the Contact to modify")
|
||||
if err != nil {
|
||||
return req.error(accountId, err)
|
||||
}
|
||||
l.Str(UriParamContactId, log.SafeString(id))
|
||||
|
||||
var change jmap.ContactCardChange
|
||||
err = req.body(&change)
|
||||
if err != nil {
|
||||
return req.error(accountId, err)
|
||||
}
|
||||
|
||||
logger := log.From(l)
|
||||
ctx := req.ctx.WithLogger(logger)
|
||||
updated, sessionState, state, lang, jerr := g.jmap.UpdateContactCard(accountId, id, change, ctx)
|
||||
if jerr != nil {
|
||||
return req.jmapError(accountId, jerr, sessionState, lang)
|
||||
}
|
||||
return req.respond(accountId, updated, sessionState, ContactResponseObjectType, state)
|
||||
})
|
||||
modify(Contact, w, r, g, g.jmap.UpdateContactCard)
|
||||
}
|
||||
|
||||
func mapContactCardSort(s SortCrit) jmap.ContactCardComparator {
|
||||
|
||||
@@ -24,37 +24,8 @@ import (
|
||||
// Get the changes tp Emails since a certain State.
|
||||
// @api:tags email,changes
|
||||
func (g *Groupware) GetEmailChanges(w http.ResponseWriter, r *http.Request) {
|
||||
g.respond(w, r, func(req Request) Response {
|
||||
l := req.logger.With()
|
||||
|
||||
accountId, err := req.GetAccountIdForMail()
|
||||
if err != nil {
|
||||
return req.error(accountId, err)
|
||||
}
|
||||
l = l.Str(logAccountId, accountId)
|
||||
|
||||
maxChanges, ok, err := req.parseUIntParam(QueryParamMaxChanges, 0)
|
||||
if err != nil {
|
||||
return req.error(accountId, err)
|
||||
}
|
||||
if ok {
|
||||
l = l.Uint(QueryParamMaxChanges, maxChanges)
|
||||
}
|
||||
|
||||
sinceState := jmap.EmptyState
|
||||
if s := req.OptHeaderParamDoc(HeaderParamSince, "Optionally specifies the state identifier from which on to list email changes"); s != "" {
|
||||
l = l.Str(HeaderParamSince, log.SafeString(s))
|
||||
sinceState = jmap.State(s)
|
||||
}
|
||||
|
||||
logger := log.From(l)
|
||||
ctx := req.ctx.WithLogger(logger)
|
||||
changes, sessionState, state, lang, jerr := g.jmap.GetEmailChanges(accountId, sinceState, true, g.config.maxBodyValueBytes, maxChanges, ctx)
|
||||
if jerr != nil {
|
||||
return req.jmapError(accountId, jerr, sessionState, lang)
|
||||
}
|
||||
|
||||
return req.respond(accountId, changes, sessionState, MailboxResponseObjectType, state)
|
||||
changes(Email, w, r, g, func(accountId string, sinceState jmap.State, maxChanges uint, ctx jmap.Context) (jmap.EmailChanges, jmap.SessionState, jmap.State, jmap.Language, jmap.Error) {
|
||||
return g.jmap.GetEmailChanges(accountId, sinceState, true, g.config.maxBodyValueBytes, maxChanges, ctx)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -67,63 +38,31 @@ func (g *Groupware) GetEmailChanges(w http.ResponseWriter, r *http.Request) {
|
||||
// A limit and an offset may be specified using the query parameters 'limit' and 'offset',
|
||||
// respectively.
|
||||
func (g *Groupware) GetAllEmailsInMailbox(w http.ResponseWriter, r *http.Request) { //NOSONAR
|
||||
g.respond(w, r, func(req Request) Response {
|
||||
l := req.logger.With()
|
||||
collapseThreads := false
|
||||
fetchBodies := false
|
||||
withThreads := true
|
||||
query(Email, w, r, g, g.defaults.emailLimit,
|
||||
func(req Request, accountId, containerId string, offset int, limit uint, ctx jmap.Context) (jmap.EmailSearchResults, jmap.SessionState, jmap.State, jmap.Language, *Error) {
|
||||
emails, sessionState, state, lang, jerr := g.jmap.GetAllEmailsInMailbox(accountId, containerId, offset, limit, collapseThreads, fetchBodies, g.config.maxBodyValueBytes, withThreads, ctx)
|
||||
if jerr != nil {
|
||||
return emails, sessionState, state, lang, req.apiErrorFromJmap(req.observeJmapError(jerr))
|
||||
}
|
||||
|
||||
accountId, err := req.GetAccountIdForMail()
|
||||
if err != nil {
|
||||
return req.error(accountId, err)
|
||||
}
|
||||
l = l.Str(logAccountId, accountId)
|
||||
sanitized, err := req.sanitizeEmails(emails.Results)
|
||||
if err != nil {
|
||||
return emails, sessionState, state, lang, err
|
||||
}
|
||||
|
||||
mailboxId, err := req.PathParam(UriParamMailboxId)
|
||||
if err != nil {
|
||||
return req.error(accountId, err)
|
||||
}
|
||||
|
||||
offset, ok, err := req.parseIntParam(QueryParamOffset, 0)
|
||||
if err != nil {
|
||||
return req.error(accountId, err)
|
||||
}
|
||||
if ok {
|
||||
l = l.Int(QueryParamOffset, offset)
|
||||
}
|
||||
|
||||
limit, ok, err := req.parseUIntParam(QueryParamLimit, g.defaults.emailLimit)
|
||||
if err != nil {
|
||||
return req.error(accountId, err)
|
||||
}
|
||||
if ok {
|
||||
l = l.Uint(QueryParamLimit, limit)
|
||||
}
|
||||
|
||||
logger := log.From(l)
|
||||
ctx := req.ctx.WithLogger(logger)
|
||||
|
||||
collapseThreads := false
|
||||
fetchBodies := false
|
||||
withThreads := true
|
||||
|
||||
emails, sessionState, state, lang, jerr := g.jmap.GetAllEmailsInMailbox(accountId, mailboxId, offset, limit, collapseThreads, fetchBodies, g.config.maxBodyValueBytes, withThreads, ctx)
|
||||
if jerr != nil {
|
||||
return req.jmapError(accountId, jerr, sessionState, lang)
|
||||
}
|
||||
|
||||
sanitized, err := req.sanitizeEmails(emails.Results)
|
||||
if err != nil {
|
||||
return req.error(accountId, err)
|
||||
}
|
||||
|
||||
safe := jmap.EmailSearchResults{
|
||||
Results: sanitized,
|
||||
Total: emails.Total,
|
||||
Limit: emails.Limit,
|
||||
Position: emails.Position,
|
||||
CanCalculateChanges: emails.CanCalculateChanges,
|
||||
}
|
||||
|
||||
return req.respond(accountId, safe, sessionState, EmailResponseObjectType, state)
|
||||
})
|
||||
safe := jmap.EmailSearchResults{
|
||||
Results: sanitized,
|
||||
Total: emails.Total,
|
||||
Limit: emails.Limit,
|
||||
Position: emails.Position,
|
||||
CanCalculateChanges: emails.CanCalculateChanges,
|
||||
}
|
||||
return safe, sessionState, state, lang, nil
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
func (g *Groupware) GetEmailsById(w http.ResponseWriter, r *http.Request) { //NOSONAR
|
||||
@@ -196,7 +135,7 @@ func (g *Groupware) GetEmailsById(w http.ResponseWriter, r *http.Request) { //NO
|
||||
}
|
||||
|
||||
if len(ids) == 1 {
|
||||
logger := log.From(l.Str("id", log.SafeString(id)))
|
||||
logger := log.From(l.Str(UriParamEmailId, log.SafeString(id)))
|
||||
ctx := req.ctx.WithLogger(logger)
|
||||
emails, _, sessionState, state, lang, jerr := g.jmap.GetEmails(accountId, ids, true, g.config.maxBodyValueBytes, markAsSeen, true, ctx)
|
||||
if jerr != nil {
|
||||
@@ -209,10 +148,10 @@ func (g *Groupware) GetEmailsById(w http.ResponseWriter, r *http.Request) { //NO
|
||||
if err != nil {
|
||||
return req.error(accountId, err)
|
||||
}
|
||||
return req.respond(accountId, sanitized, sessionState, EmailResponseObjectType, state)
|
||||
return req.respond(accountId, sanitized, sessionState, EmailResponseObjectType, state, lang)
|
||||
}
|
||||
} else {
|
||||
logger := log.From(l.Array("ids", log.SafeStringArray(ids)))
|
||||
logger := log.From(l.Array(UriParamEmailId, log.SafeStringArray(ids)))
|
||||
ctx := req.ctx.WithLogger(logger)
|
||||
emails, _, sessionState, state, lang, jerr := g.jmap.GetEmails(accountId, ids, true, g.config.maxBodyValueBytes, markAsSeen, false, ctx)
|
||||
if jerr != nil {
|
||||
@@ -225,7 +164,7 @@ func (g *Groupware) GetEmailsById(w http.ResponseWriter, r *http.Request) { //NO
|
||||
if err != nil {
|
||||
return req.error(accountId, err)
|
||||
}
|
||||
return req.respond(accountId, sanitized, sessionState, EmailResponseObjectType, state)
|
||||
return req.respond(accountId, sanitized, sessionState, EmailResponseObjectType, state, lang)
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -284,7 +223,7 @@ func (g *Groupware) GetEmailAttachments(w http.ResponseWriter, r *http.Request)
|
||||
return req.error(accountId, err)
|
||||
}
|
||||
var body []jmap.EmailBodyPart = email.Attachments
|
||||
return req.respond(accountId, body, sessionState, EmailResponseObjectType, state)
|
||||
return req.respond(accountId, body, sessionState, EmailResponseObjectType, state, lang)
|
||||
})
|
||||
} else {
|
||||
g.stream(w, r, func(req Request, w http.ResponseWriter) *Error {
|
||||
@@ -399,7 +338,7 @@ func (g *Groupware) getEmailsSince(w http.ResponseWriter, r *http.Request, since
|
||||
return req.jmapError(accountId, jerr, sessionState, lang)
|
||||
}
|
||||
|
||||
return req.respond(accountId, changes, sessionState, EmailResponseObjectType, state)
|
||||
return req.respond(accountId, changes, sessionState, EmailResponseObjectType, state, lang)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -428,7 +367,8 @@ type SnippetWithoutEmailId struct {
|
||||
|
||||
type EmailWithSnippetsSearchResults struct {
|
||||
Results []EmailWithSnippets `json:"results"`
|
||||
Total uint `json:"total,omitzero"`
|
||||
Total *uint `json:"total,omitzero"`
|
||||
Position uint `json:"position"`
|
||||
Limit uint `json:"limit,omitzero"`
|
||||
QueryState jmap.State `json:"queryState,omitempty"`
|
||||
}
|
||||
@@ -610,20 +550,32 @@ func (g *Groupware) GetEmails(w http.ResponseWriter, r *http.Request) { //NOSONA
|
||||
return req.error(accountId, err)
|
||||
}
|
||||
|
||||
l := req.logger.With().Str(logAccountId, log.SafeString(accountId))
|
||||
|
||||
ok, filter, makesSnippets, offset, limit, logger, err := g.buildEmailFilter(req)
|
||||
if !ok {
|
||||
return req.error(accountId, err)
|
||||
}
|
||||
logger = log.From(req.logger.With().Str(logAccountId, log.SafeString(accountId)))
|
||||
ctx := req.ctx.WithLogger(logger)
|
||||
|
||||
if !filter.IsNotEmpty() {
|
||||
filter = nil
|
||||
}
|
||||
|
||||
fetchBodies := false
|
||||
calculateTotal := true
|
||||
if b, ok, err := req.parseBoolParam(QueryParamCalculateTotal, true); err != nil {
|
||||
return req.error(accountId, err)
|
||||
} else if ok {
|
||||
calculateTotal = b
|
||||
l = l.Bool(QueryParamCalculateTotal, calculateTotal)
|
||||
}
|
||||
|
||||
resultsByAccount, sessionState, state, lang, jerr := g.jmap.QueryEmailsWithSnippets(single(accountId), filter, offset, limit, fetchBodies, g.config.maxBodyValueBytes, ctx)
|
||||
fetchBodies := false
|
||||
collapseThreads := false
|
||||
|
||||
logger = log.From(l)
|
||||
ctx := req.ctx.WithLogger(logger)
|
||||
|
||||
resultsByAccount, sessionState, state, lang, jerr := g.jmap.QueryEmailsWithSnippets(single(accountId), filter, offset, limit, collapseThreads, calculateTotal, fetchBodies, g.config.maxBodyValueBytes, ctx)
|
||||
if jerr != nil {
|
||||
return req.jmapError(accountId, jerr, sessionState, lang)
|
||||
}
|
||||
@@ -653,12 +605,18 @@ func (g *Groupware) GetEmails(w http.ResponseWriter, r *http.Request) { //NOSONA
|
||||
}
|
||||
}
|
||||
|
||||
var total *uint = nil
|
||||
if calculateTotal {
|
||||
total = &results.Total
|
||||
}
|
||||
|
||||
return req.respond(accountId, EmailWithSnippetsSearchResults{
|
||||
Results: flattened,
|
||||
Total: results.Total,
|
||||
Total: total,
|
||||
Position: results.Position,
|
||||
Limit: results.Limit,
|
||||
QueryState: results.QueryState,
|
||||
}, sessionState, EmailResponseObjectType, state)
|
||||
}, sessionState, EmailResponseObjectType, state, lang)
|
||||
} else {
|
||||
return req.notFound(accountId, sessionState, EmailResponseObjectType, state)
|
||||
}
|
||||
@@ -720,7 +678,7 @@ func (g *Groupware) GetEmailsForAllAccounts(w http.ResponseWriter, r *http.Reque
|
||||
QueryState: state,
|
||||
}
|
||||
|
||||
return req.respondN(allAccountIds, body, sessionState, EmailResponseObjectType, state)
|
||||
return req.respondN(allAccountIds, body, sessionState, EmailResponseObjectType, state, lang)
|
||||
} else {
|
||||
withThreads := true
|
||||
|
||||
@@ -759,7 +717,7 @@ func (g *Groupware) GetEmailsForAllAccounts(w http.ResponseWriter, r *http.Reque
|
||||
QueryState: state,
|
||||
}
|
||||
|
||||
return req.respondN(allAccountIds, body, sessionState, EmailResponseObjectType, state)
|
||||
return req.respondN(allAccountIds, body, sessionState, EmailResponseObjectType, state, lang)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -822,127 +780,53 @@ func findSentMailboxId(j *jmap.Client, accountId string, req Request, ctx jmap.C
|
||||
}
|
||||
|
||||
func (g *Groupware) CreateEmail(w http.ResponseWriter, r *http.Request) {
|
||||
g.respond(w, r, func(req Request) Response {
|
||||
logger := req.logger
|
||||
|
||||
accountId, gwerr := req.GetAccountIdForMail()
|
||||
if gwerr != nil {
|
||||
return req.error(accountId, gwerr)
|
||||
}
|
||||
logger = log.From(logger.With().Str(logAccountId, log.SafeString(accountId)))
|
||||
ctx := req.ctx.WithLogger(logger)
|
||||
|
||||
var body jmap.EmailCreate
|
||||
err := req.body(&body)
|
||||
if err != nil {
|
||||
return req.error(accountId, err)
|
||||
}
|
||||
|
||||
if len(body.MailboxIds) < 1 {
|
||||
mailboxId, resp := findDraftsMailboxId(g.jmap, accountId, req, ctx)
|
||||
if mailboxId != "" {
|
||||
body.MailboxIds[mailboxId] = true
|
||||
} else {
|
||||
return resp
|
||||
create(Email, w, r, g,
|
||||
func(r Request, accountId string, body *jmap.EmailChange, ctx jmap.Context) (bool, Response) {
|
||||
if len(body.MailboxIds) < 1 {
|
||||
mailboxId, resp := findDraftsMailboxId(g.jmap, accountId, r, ctx)
|
||||
if mailboxId != "" {
|
||||
body.MailboxIds[mailboxId] = true
|
||||
} else {
|
||||
return false, resp
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
created, sessionState, state, lang, jerr := g.jmap.CreateEmail(accountId, body, "", ctx)
|
||||
if jerr != nil {
|
||||
return req.jmapError(accountId, jerr, sessionState, lang)
|
||||
}
|
||||
|
||||
return req.respond(accountId, created, sessionState, EmailResponseObjectType, state)
|
||||
})
|
||||
return true, Response{}
|
||||
},
|
||||
func(accountId string, body jmap.EmailChange, ctx jmap.Context) (*jmap.Email, jmap.SessionState, jmap.State, jmap.Language, jmap.Error) {
|
||||
return g.jmap.CreateEmail(accountId, body, "", ctx)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
func (g *Groupware) ReplaceEmail(w http.ResponseWriter, r *http.Request) {
|
||||
g.respond(w, r, func(req Request) Response {
|
||||
logger := req.logger
|
||||
|
||||
accountId, gwerr := req.GetAccountIdForMail()
|
||||
if gwerr != nil {
|
||||
return req.error(accountId, gwerr)
|
||||
}
|
||||
|
||||
replaceId, err := req.PathParam(UriParamEmailId)
|
||||
if err != nil {
|
||||
return req.error(accountId, err)
|
||||
}
|
||||
|
||||
logger = log.From(logger.With().Str(logAccountId, log.SafeString(accountId)))
|
||||
ctx := req.ctx.WithLogger(logger)
|
||||
|
||||
var body jmap.EmailCreate
|
||||
err = req.body(&body)
|
||||
if err != nil {
|
||||
return req.error(accountId, err)
|
||||
}
|
||||
|
||||
if len(body.MailboxIds) < 1 {
|
||||
mailboxId, resp := findDraftsMailboxId(g.jmap, accountId, req, ctx)
|
||||
if mailboxId != "" {
|
||||
body.MailboxIds[mailboxId] = true
|
||||
} else {
|
||||
return resp
|
||||
replaceId := ""
|
||||
create(Email, w, r, g,
|
||||
func(r Request, accountId string, body *jmap.EmailChange, ctx jmap.Context) (bool, Response) {
|
||||
if len(body.MailboxIds) < 1 {
|
||||
mailboxId, resp := findDraftsMailboxId(g.jmap, accountId, r, ctx)
|
||||
if mailboxId != "" {
|
||||
body.MailboxIds[mailboxId] = true
|
||||
} else {
|
||||
return false, resp
|
||||
}
|
||||
}
|
||||
var err *Error
|
||||
replaceId, err = r.PathParam(UriParamEmailId)
|
||||
if err != nil {
|
||||
return false, r.error(accountId, err)
|
||||
}
|
||||
}
|
||||
|
||||
created, sessionState, state, lang, jerr := g.jmap.CreateEmail(accountId, body, replaceId, ctx)
|
||||
if jerr != nil {
|
||||
return req.jmapError(accountId, jerr, sessionState, lang)
|
||||
}
|
||||
|
||||
return req.respond(accountId, created, sessionState, EmailResponseObjectType, state)
|
||||
})
|
||||
return true, Response{}
|
||||
},
|
||||
func(accountId string, body jmap.EmailChange, ctx jmap.Context) (*jmap.Email, jmap.SessionState, jmap.State, jmap.Language, jmap.Error) {
|
||||
ctx = ctx.WithLogger(log.From(ctx.Logger.With().Str("replaceId", replaceId)))
|
||||
return g.jmap.CreateEmail(accountId, body, replaceId, ctx)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
func (g *Groupware) UpdateEmail(w http.ResponseWriter, r *http.Request) {
|
||||
g.respond(w, r, func(req Request) Response {
|
||||
l := req.logger.With()
|
||||
|
||||
accountId, gwerr := req.GetAccountIdForMail()
|
||||
if gwerr != nil {
|
||||
return req.error(accountId, gwerr)
|
||||
}
|
||||
l.Str(logAccountId, accountId)
|
||||
|
||||
emailId, err := req.PathParam(UriParamEmailId)
|
||||
if err != nil {
|
||||
return req.error(accountId, err)
|
||||
}
|
||||
l.Str(UriParamEmailId, log.SafeString(emailId))
|
||||
|
||||
logger := log.From(l)
|
||||
ctx := req.ctx.WithLogger(logger)
|
||||
|
||||
var body map[string]any
|
||||
err = req.body(&body)
|
||||
if err != nil {
|
||||
return req.error(accountId, err)
|
||||
}
|
||||
|
||||
updates := map[string]jmap.EmailUpdate{
|
||||
emailId: body,
|
||||
}
|
||||
|
||||
result, sessionState, state, lang, jerr := g.jmap.UpdateEmails(accountId, updates, ctx)
|
||||
if jerr != nil {
|
||||
return req.jmapError(accountId, jerr, sessionState, lang)
|
||||
}
|
||||
|
||||
if result == nil {
|
||||
return req.error(accountId, apiError(req.errorId(), ErrorApiInconsistency, withTitle("API Inconsistency: Missing Email Update Response", //NOSONAR
|
||||
"An internal API behaved unexpectedly: missing Email update response from JMAP endpoint"))) //NOSONAR
|
||||
}
|
||||
updatedEmail, ok := result[emailId]
|
||||
if !ok {
|
||||
return req.error(accountId, apiError(req.errorId(), ErrorApiInconsistency, withTitle("API Inconsistency: Wrong Email Update Response ID", //NOSONAR
|
||||
"An internal API behaved unexpectedly: wrong Email update ID response from JMAP endpoint"))) //NOSONAR
|
||||
}
|
||||
|
||||
return req.respond(accountId, updatedEmail, sessionState, EmailResponseObjectType, state)
|
||||
})
|
||||
modify(Email, w, r, g, g.jmap.UpdateEmail)
|
||||
}
|
||||
|
||||
type emailKeywordUpdates struct {
|
||||
@@ -986,14 +870,14 @@ func (g *Groupware) UpdateEmailKeywords(w http.ResponseWriter, r *http.Request)
|
||||
return req.noop(accountId)
|
||||
}
|
||||
|
||||
patch := jmap.EmailUpdate{}
|
||||
patch := jmap.PatchObject{}
|
||||
for _, keyword := range body.Add {
|
||||
patch["keywords/"+keyword] = true //NOSONAR
|
||||
}
|
||||
for _, keyword := range body.Remove {
|
||||
patch["keywords/"+keyword] = nil //NOSONAR
|
||||
}
|
||||
patches := map[string]jmap.EmailUpdate{
|
||||
patches := map[string]jmap.PatchObject{
|
||||
emailId: patch,
|
||||
}
|
||||
|
||||
@@ -1003,16 +887,16 @@ func (g *Groupware) UpdateEmailKeywords(w http.ResponseWriter, r *http.Request)
|
||||
}
|
||||
|
||||
if result == nil {
|
||||
return req.error(accountId, apiError(req.errorId(), ErrorApiInconsistency, withTitle("API Inconsistency: Missing Email Update Response",
|
||||
"An internal API behaved unexpectedly: missing Email update response from JMAP endpoint")))
|
||||
return req.error(accountId, apiError(req.errorId(), ErrorApiInconsistency, withTitle("API Inconsistency: Missing Email Update Response", //NOSONAR
|
||||
"An internal API behaved unexpectedly: missing Email update response from JMAP endpoint"))) //NOSONAR
|
||||
}
|
||||
updatedEmail, ok := result[emailId]
|
||||
if !ok {
|
||||
return req.error(accountId, apiError(req.errorId(), ErrorApiInconsistency, withTitle("API Inconsistency: Wrong Email Update Response ID",
|
||||
"An internal API behaved unexpectedly: wrong Email update ID response from JMAP endpoint")))
|
||||
return req.error(accountId, apiError(req.errorId(), ErrorApiInconsistency, withTitle("API Inconsistency: Wrong Email Update Response ID", //NOSONAR
|
||||
"An internal API behaved unexpectedly: wrong Email update ID response from JMAP endpoint"))) //NOSONAR
|
||||
}
|
||||
|
||||
return req.respond(accountId, updatedEmail, sessionState, EmailResponseObjectType, state)
|
||||
return req.respond(accountId, updatedEmail, sessionState, EmailResponseObjectType, state, lang)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1048,11 +932,11 @@ func (g *Groupware) AddEmailKeywords(w http.ResponseWriter, r *http.Request) { /
|
||||
return req.noop(accountId)
|
||||
}
|
||||
|
||||
patch := jmap.EmailUpdate{}
|
||||
patch := jmap.PatchObject{}
|
||||
for _, keyword := range body {
|
||||
patch["keywords/"+keyword] = true
|
||||
}
|
||||
patches := map[string]jmap.EmailUpdate{
|
||||
patches := map[string]jmap.PatchObject{
|
||||
emailId: patch,
|
||||
}
|
||||
|
||||
@@ -1074,7 +958,7 @@ func (g *Groupware) AddEmailKeywords(w http.ResponseWriter, r *http.Request) { /
|
||||
if updatedEmail == nil {
|
||||
return req.noContent(accountId, sessionState, EmailResponseObjectType, state)
|
||||
} else {
|
||||
return req.respond(accountId, updatedEmail, sessionState, EmailResponseObjectType, state)
|
||||
return req.respond(accountId, updatedEmail, sessionState, EmailResponseObjectType, state, lang)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -1111,11 +995,11 @@ func (g *Groupware) RemoveEmailKeywords(w http.ResponseWriter, r *http.Request)
|
||||
return req.noop(accountId)
|
||||
}
|
||||
|
||||
patch := jmap.EmailUpdate{}
|
||||
patch := jmap.PatchObject{}
|
||||
for _, keyword := range body {
|
||||
patch["keywords/"+keyword] = nil
|
||||
}
|
||||
patches := map[string]jmap.EmailUpdate{
|
||||
patches := map[string]jmap.PatchObject{
|
||||
emailId: patch,
|
||||
}
|
||||
|
||||
@@ -1137,52 +1021,14 @@ func (g *Groupware) RemoveEmailKeywords(w http.ResponseWriter, r *http.Request)
|
||||
if updatedEmail == nil {
|
||||
return req.noContent(accountId, sessionState, EmailResponseObjectType, state)
|
||||
} else {
|
||||
return req.respond(accountId, updatedEmail, sessionState, EmailResponseObjectType, state)
|
||||
return req.respond(accountId, updatedEmail, sessionState, EmailResponseObjectType, state, lang)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Delete an email by its unique identifier.
|
||||
func (g *Groupware) DeleteEmail(w http.ResponseWriter, r *http.Request) {
|
||||
g.respond(w, r, func(req Request) Response {
|
||||
l := req.logger.With()
|
||||
|
||||
accountId, gwerr := req.GetAccountIdForMail()
|
||||
if gwerr != nil {
|
||||
return req.error(accountId, gwerr)
|
||||
}
|
||||
l.Str(logAccountId, log.SafeString(accountId))
|
||||
|
||||
emailId, err := req.PathParam(UriParamEmailId)
|
||||
if err != nil {
|
||||
return req.error(accountId, err)
|
||||
}
|
||||
l.Str(UriParamEmailId, log.SafeString(emailId))
|
||||
|
||||
logger := log.From(l)
|
||||
ctx := req.ctx.WithLogger(logger)
|
||||
resp, sessionState, state, lang, jerr := g.jmap.DeleteEmails(accountId, single(emailId), ctx)
|
||||
if jerr != nil {
|
||||
return req.jmapError(accountId, jerr, sessionState, lang)
|
||||
}
|
||||
|
||||
for _, e := range resp {
|
||||
desc := e.Description
|
||||
if desc != "" {
|
||||
return req.error(accountId, apiError(
|
||||
req.errorId(),
|
||||
ErrorFailedToDeleteEmail,
|
||||
withDetail(e.Description),
|
||||
))
|
||||
} else {
|
||||
return req.error(accountId, apiError(
|
||||
req.errorId(),
|
||||
ErrorFailedToDeleteEmail,
|
||||
))
|
||||
}
|
||||
}
|
||||
return req.noContent(accountId, sessionState, EmailResponseObjectType, state)
|
||||
})
|
||||
delete(Email, w, r, g, g.jmap.DeleteEmails)
|
||||
}
|
||||
|
||||
// Delete a set of emails by their unique identifiers.
|
||||
@@ -1190,44 +1036,7 @@ func (g *Groupware) DeleteEmail(w http.ResponseWriter, r *http.Request) {
|
||||
// The identifiers of the emails to delete are specified as part of the request
|
||||
// body, as an array of strings.
|
||||
func (g *Groupware) DeleteEmails(w http.ResponseWriter, r *http.Request) {
|
||||
/// @api body
|
||||
g.respond(w, r, func(req Request) Response {
|
||||
l := req.logger.With()
|
||||
|
||||
accountId, gwerr := req.GetAccountIdForMail()
|
||||
if gwerr != nil {
|
||||
return req.error(accountId, gwerr)
|
||||
}
|
||||
l.Str(logAccountId, accountId)
|
||||
|
||||
var emailIds []string
|
||||
err := req.body(&emailIds)
|
||||
if err != nil {
|
||||
return req.error(accountId, err)
|
||||
}
|
||||
|
||||
l.Array("emailIds", log.SafeStringArray(emailIds))
|
||||
|
||||
logger := log.From(l)
|
||||
ctx := req.ctx.WithLogger(logger)
|
||||
resp, sessionState, state, lang, jerr := g.jmap.DeleteEmails(accountId, emailIds, ctx)
|
||||
if jerr != nil {
|
||||
return req.jmapError(accountId, jerr, sessionState, lang)
|
||||
}
|
||||
|
||||
if len(resp) > 0 {
|
||||
meta := make(map[string]any, len(resp))
|
||||
for emailId, e := range resp {
|
||||
meta[emailId] = e.Description
|
||||
}
|
||||
return req.error(accountId, apiError(
|
||||
req.errorId(),
|
||||
ErrorFailedToDeleteEmail,
|
||||
withMeta(meta),
|
||||
))
|
||||
}
|
||||
return req.noContent(accountId, sessionState, EmailResponseObjectType, state)
|
||||
})
|
||||
deleteMany(Email, w, r, g, g.jmap.DeleteEmails)
|
||||
}
|
||||
|
||||
func (g *Groupware) SendEmail(w http.ResponseWriter, r *http.Request) { //NOSONAR
|
||||
@@ -1285,7 +1094,7 @@ func (g *Groupware) SendEmail(w http.ResponseWriter, r *http.Request) { //NOSONA
|
||||
return req.jmapError(accountId, jerr, sessionState, lang)
|
||||
}
|
||||
|
||||
return req.respond(accountId, resp, sessionState, EmailResponseObjectType, state)
|
||||
return req.respond(accountId, resp, sessionState, EmailResponseObjectType, state, lang)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1456,7 +1265,7 @@ func (g *Groupware) RelatedToEmail(w http.ResponseWriter, r *http.Request) { //N
|
||||
return req.respond(accountId, AboutEmailResponse{
|
||||
Email: sanitized,
|
||||
RequestId: reqId,
|
||||
}, sessionState, EmailResponseObjectType, state)
|
||||
}, sessionState, EmailResponseObjectType, state, lang)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1733,7 +1542,7 @@ func (g *Groupware) GetLatestEmailsSummaryForAllAccounts(w http.ResponseWriter,
|
||||
Total: total,
|
||||
Limit: limit,
|
||||
Offset: offset,
|
||||
}, sessionState, EmailResponseObjectType, state)
|
||||
}, sessionState, EmailResponseObjectType, state, lang)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -43,7 +43,7 @@ func (g *Groupware) GetEventsInCalendar(w http.ResponseWriter, r *http.Request)
|
||||
filter := jmap.CalendarEventFilterCondition{
|
||||
InCalendar: calendarId,
|
||||
}
|
||||
sortBy := []jmap.CalendarEventComparator{{Property: jmap.CalendarEventPropertyUpdated, IsAscending: false}}
|
||||
sortBy := []jmap.CalendarEventComparator{{Property: jmap.CalendarEventPropertyStart, IsAscending: false}}
|
||||
|
||||
logger := log.From(l)
|
||||
ctx := req.ctx.WithLogger(logger)
|
||||
@@ -53,139 +53,55 @@ func (g *Groupware) GetEventsInCalendar(w http.ResponseWriter, r *http.Request)
|
||||
}
|
||||
|
||||
if events, ok := eventsByAccountId[accountId]; ok {
|
||||
return req.respond(accountId, events, sessionState, EventResponseObjectType, state)
|
||||
return req.respond(accountId, events, sessionState, EventResponseObjectType, state, lang)
|
||||
} else {
|
||||
return req.notFound(accountId, sessionState, EventResponseObjectType, state)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Get changes to Contacts since a given State
|
||||
//func query[T jmap.Foo, CHANGE jmap.Change, CHANGES jmap.Changes[T], SEARCHRESULTS jmap.SearchResults[T]]( //NOSONAR
|
||||
|
||||
func curryMapQuery[SRES jmap.SearchResults[T], T jmap.Foo, FILTER any, COMP any](
|
||||
f func(accountIds []string, filter FILTER, sortBy []COMP, position int, limit uint, calculateTotal bool, ctx jmap.Context) (map[string]SRES, jmap.SessionState, jmap.State, jmap.Language, jmap.Error),
|
||||
) func(req Request, accountId string, filter FILTER, sortBy []COMP, offset int, limit uint, ctx jmap.Context) (SRES, jmap.SessionState, jmap.State, jmap.Language, jmap.Error) {
|
||||
return func(req Request, accountId string, filter FILTER, sortBy []COMP, offset int, limit uint, ctx jmap.Context) (SRES, jmap.SessionState, jmap.State, jmap.Language, jmap.Error) {
|
||||
m, sessionState, state, lang, err := f(single(accountId), filter, sortBy, offset, limit, true, ctx)
|
||||
return m[accountId], sessionState, state, lang, err
|
||||
}
|
||||
}
|
||||
|
||||
func (g *Groupware) GetAllEvents(w http.ResponseWriter, r *http.Request) {
|
||||
getallpaged(Event, w, r, g,
|
||||
g.jmap.GetCalendarEvents,
|
||||
func(cid string) jmap.CalendarEventFilterElement {
|
||||
return jmap.CalendarEventFilterCondition{InCalendar: cid}
|
||||
},
|
||||
[]jmap.CalendarEventComparator{{Property: jmap.CalendarEventPropertyStart, IsAscending: true}},
|
||||
curryMapQuery(g.jmap.QueryCalendarEvents),
|
||||
)
|
||||
}
|
||||
|
||||
func (g *Groupware) GetEventById(w http.ResponseWriter, r *http.Request) {
|
||||
get(Event, w, r, g, g.jmap.GetCalendarEvents)
|
||||
}
|
||||
|
||||
// Get changes to Calendar Events since a given State
|
||||
// @api:tags event,changes
|
||||
func (g *Groupware) GetEventChanges(w http.ResponseWriter, r *http.Request) {
|
||||
g.respond(w, r, func(req Request) Response {
|
||||
ok, accountId, resp := req.needCalendarWithAccount()
|
||||
if !ok {
|
||||
return resp
|
||||
}
|
||||
|
||||
l := req.logger.With()
|
||||
|
||||
var maxChanges uint = 0
|
||||
if v, ok, err := req.parseUIntParam(QueryParamMaxChanges, 0); err != nil {
|
||||
return req.error(accountId, err)
|
||||
} else if ok {
|
||||
maxChanges = v
|
||||
l = l.Uint(QueryParamMaxChanges, v)
|
||||
}
|
||||
|
||||
sinceState := jmap.State(req.OptHeaderParamDoc(HeaderParamSince, "Specifies the state identifier from which on to list event changes"))
|
||||
l = l.Str(HeaderParamSince, log.SafeString(string(sinceState)))
|
||||
|
||||
logger := log.From(l)
|
||||
ctx := req.ctx.WithLogger(logger)
|
||||
changes, sessionState, state, lang, jerr := g.jmap.GetCalendarEventChanges(accountId, sinceState, maxChanges, ctx)
|
||||
if jerr != nil {
|
||||
return req.jmapError(accountId, jerr, sessionState, lang)
|
||||
}
|
||||
var body jmap.CalendarEventChanges = changes
|
||||
|
||||
return req.respond(accountId, body, sessionState, ContactResponseObjectType, state)
|
||||
})
|
||||
changes(Event, w, r, g, g.jmap.GetCalendarEventChanges)
|
||||
}
|
||||
|
||||
func (g *Groupware) CreateCalendarEvent(w http.ResponseWriter, r *http.Request) {
|
||||
g.respond(w, r, func(req Request) Response {
|
||||
ok, accountId, resp := req.needCalendarWithAccount()
|
||||
if !ok {
|
||||
return resp
|
||||
}
|
||||
|
||||
l := req.logger.With()
|
||||
|
||||
var create jmap.CalendarEvent
|
||||
err := req.body(&create)
|
||||
if err != nil {
|
||||
return req.error(accountId, err)
|
||||
}
|
||||
|
||||
logger := log.From(l)
|
||||
ctx := req.ctx.WithLogger(logger)
|
||||
created, sessionState, state, lang, jerr := g.jmap.CreateCalendarEvent(accountId, create, ctx)
|
||||
if jerr != nil {
|
||||
return req.jmapError(accountId, jerr, sessionState, lang)
|
||||
}
|
||||
return req.respond(accountId, created, sessionState, EventResponseObjectType, state)
|
||||
})
|
||||
func (g *Groupware) CreateEvent(w http.ResponseWriter, r *http.Request) {
|
||||
create(Event, w, r, g, nil, g.jmap.CreateCalendarEvent)
|
||||
}
|
||||
|
||||
func (g *Groupware) DeleteCalendarEvent(w http.ResponseWriter, r *http.Request) {
|
||||
g.respond(w, r, func(req Request) Response {
|
||||
ok, accountId, resp := req.needCalendarWithAccount()
|
||||
if !ok {
|
||||
return resp
|
||||
}
|
||||
l := req.logger.With().Str(accountId, log.SafeString(accountId))
|
||||
|
||||
eventId, err := req.PathParam(UriParamEventId)
|
||||
if err != nil {
|
||||
return req.error(accountId, err)
|
||||
}
|
||||
l.Str(UriParamEventId, log.SafeString(eventId))
|
||||
|
||||
logger := log.From(l)
|
||||
ctx := req.ctx.WithLogger(logger)
|
||||
deleted, sessionState, state, lang, jerr := g.jmap.DeleteCalendarEvent(accountId, single(eventId), ctx)
|
||||
if jerr != nil {
|
||||
return req.jmapError(accountId, jerr, sessionState, lang)
|
||||
}
|
||||
|
||||
for _, e := range deleted {
|
||||
desc := e.Description
|
||||
if desc != "" {
|
||||
return req.errorS(accountId, apiError(
|
||||
req.errorId(),
|
||||
ErrorFailedToDeleteContact,
|
||||
withDetail(e.Description),
|
||||
), sessionState)
|
||||
} else {
|
||||
return req.errorS(accountId, apiError(
|
||||
req.errorId(),
|
||||
ErrorFailedToDeleteContact,
|
||||
), sessionState)
|
||||
}
|
||||
}
|
||||
return req.noContent(accountId, sessionState, EventResponseObjectType, state)
|
||||
})
|
||||
func (g *Groupware) DeleteEvent(w http.ResponseWriter, r *http.Request) {
|
||||
delete(Event, w, r, g, g.jmap.DeleteCalendarEvent)
|
||||
}
|
||||
|
||||
func (g *Groupware) ModifyCalendarEvent(w http.ResponseWriter, r *http.Request) {
|
||||
g.respond(w, r, func(req Request) Response {
|
||||
ok, accountId, resp := req.needCalendarWithAccount()
|
||||
if !ok {
|
||||
return resp
|
||||
}
|
||||
l := req.logger.With().Str(accountId, log.SafeString(accountId))
|
||||
id, err := req.PathParamDoc(UriParamEventId, "The unique identifier of the Calendar Event to modify")
|
||||
if err != nil {
|
||||
return req.error(accountId, err)
|
||||
}
|
||||
l.Str(UriParamEventId, log.SafeString(id))
|
||||
|
||||
var change jmap.CalendarEventChange
|
||||
err = req.body(&change)
|
||||
if err != nil {
|
||||
return req.error(accountId, err)
|
||||
}
|
||||
|
||||
logger := log.From(l)
|
||||
ctx := req.ctx.WithLogger(logger)
|
||||
updated, sessionState, state, lang, jerr := g.jmap.UpdateCalendarEvent(accountId, id, change, ctx)
|
||||
if jerr != nil {
|
||||
return req.jmapError(accountId, jerr, sessionState, lang)
|
||||
}
|
||||
return req.respond(accountId, updated, sessionState, EventResponseObjectType, state)
|
||||
})
|
||||
func (g *Groupware) ModifyEvent(w http.ResponseWriter, r *http.Request) {
|
||||
modify(Event, w, r, g, g.jmap.UpdateCalendarEvent)
|
||||
}
|
||||
|
||||
// Parse a blob that contains an iCal file and return it as JSCalendar.
|
||||
@@ -211,6 +127,6 @@ func (g *Groupware) ParseIcalBlob(w http.ResponseWriter, r *http.Request) {
|
||||
if jerr != nil {
|
||||
return req.jmapError(accountId, jerr, sessionState, lang)
|
||||
}
|
||||
return req.respond(accountId, resp, sessionState, EventResponseObjectType, state)
|
||||
return req.respond(accountId, resp, sessionState, EventResponseObjectType, state, lang)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,170 +1,33 @@
|
||||
package groupware
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/opencloud-eu/opencloud/pkg/jmap"
|
||||
"github.com/opencloud-eu/opencloud/pkg/log"
|
||||
)
|
||||
|
||||
// Get the list of identities that are associated with an account.
|
||||
func (g *Groupware) GetIdentities(w http.ResponseWriter, r *http.Request) {
|
||||
g.respond(w, r, func(req Request) Response {
|
||||
accountId, err := req.GetAccountIdForMail()
|
||||
if err != nil {
|
||||
return req.error(accountId, err)
|
||||
}
|
||||
logger := log.From(req.logger.With().Str(logAccountId, accountId))
|
||||
ctx := req.ctx.WithLogger(logger)
|
||||
res, sessionState, state, lang, jerr := g.jmap.GetAllIdentities(accountId, ctx)
|
||||
if jerr != nil {
|
||||
return req.jmapError(accountId, jerr, sessionState, lang)
|
||||
}
|
||||
return req.respond(accountId, res, sessionState, IdentityResponseObjectType, state)
|
||||
})
|
||||
getall(Identity, w, r, g, g.jmap.GetIdentities)
|
||||
}
|
||||
|
||||
func (g *Groupware) GetIdentityById(w http.ResponseWriter, r *http.Request) {
|
||||
g.respond(w, r, func(req Request) Response {
|
||||
accountId, err := req.GetAccountIdForMail()
|
||||
if err != nil {
|
||||
return req.error(accountId, err)
|
||||
}
|
||||
id, err := req.PathParam(UriParamIdentityId)
|
||||
if err != nil {
|
||||
return req.error(accountId, err)
|
||||
}
|
||||
logger := log.From(req.logger.With().Str(logAccountId, accountId).Str(logIdentityId, id))
|
||||
ctx := req.ctx.WithLogger(logger)
|
||||
res, sessionState, state, lang, jerr := g.jmap.GetIdentities(accountId, single(id), ctx)
|
||||
if jerr != nil {
|
||||
return req.jmapError(accountId, jerr, sessionState, lang)
|
||||
}
|
||||
if len(res) < 1 {
|
||||
return req.notFound(accountId, sessionState, IdentityResponseObjectType, state)
|
||||
}
|
||||
var body jmap.Identity = res[0]
|
||||
return req.respond(accountId, body, sessionState, IdentityResponseObjectType, state)
|
||||
})
|
||||
get(Identity, w, r, g, g.jmap.GetIdentities)
|
||||
}
|
||||
|
||||
func (g *Groupware) AddIdentity(w http.ResponseWriter, r *http.Request) {
|
||||
g.respond(w, r, func(req Request) Response {
|
||||
accountId, err := req.GetAccountIdForMail()
|
||||
if err != nil {
|
||||
return req.error(accountId, err)
|
||||
}
|
||||
logger := log.From(req.logger.With().Str(logAccountId, accountId))
|
||||
ctx := req.ctx.WithLogger(logger)
|
||||
|
||||
var identity jmap.IdentityChange
|
||||
err = req.body(&identity)
|
||||
if err != nil {
|
||||
return req.error(accountId, err)
|
||||
}
|
||||
|
||||
created, sessionState, state, lang, jerr := g.jmap.CreateIdentity(accountId, identity, ctx)
|
||||
if jerr != nil {
|
||||
return req.jmapError(accountId, jerr, sessionState, lang)
|
||||
}
|
||||
return req.respond(accountId, created, sessionState, IdentityResponseObjectType, state)
|
||||
})
|
||||
func (g *Groupware) CreateIdentity(w http.ResponseWriter, r *http.Request) {
|
||||
create(Identity, w, r, g, nil, g.jmap.CreateIdentity)
|
||||
}
|
||||
|
||||
func (g *Groupware) ModifyIdentity(w http.ResponseWriter, r *http.Request) {
|
||||
g.respond(w, r, func(req Request) Response {
|
||||
accountId, err := req.GetAccountIdForMail()
|
||||
if err != nil {
|
||||
return req.error(accountId, err)
|
||||
}
|
||||
id, err := req.PathParamDoc(UriParamIdentityId, "The unique identifier of the Identity to modify")
|
||||
if err != nil {
|
||||
return req.error(accountId, err)
|
||||
}
|
||||
|
||||
logger := log.From(req.logger.With().Str(logAccountId, accountId).Str(UriParamIdentityId, log.SafeString(id)))
|
||||
ctx := req.ctx.WithLogger(logger)
|
||||
|
||||
var identity jmap.IdentityChange
|
||||
err = req.body(&identity)
|
||||
if err != nil {
|
||||
return req.error(accountId, err)
|
||||
}
|
||||
|
||||
updated, sessionState, state, lang, jerr := g.jmap.UpdateIdentity(accountId, id, identity, ctx)
|
||||
if jerr != nil {
|
||||
return req.jmapError(accountId, jerr, sessionState, lang)
|
||||
}
|
||||
return req.respond(accountId, updated, sessionState, IdentityResponseObjectType, state)
|
||||
})
|
||||
modify(Identity, w, r, g, g.jmap.UpdateIdentity)
|
||||
}
|
||||
|
||||
// Delete an identity.
|
||||
func (g *Groupware) DeleteIdentity(w http.ResponseWriter, r *http.Request) {
|
||||
g.respond(w, r, func(req Request) Response {
|
||||
accountId, err := req.GetAccountIdForMail()
|
||||
if err != nil {
|
||||
return req.error(accountId, err)
|
||||
}
|
||||
|
||||
id, err := req.PathParam(UriParamIdentityId)
|
||||
if err != nil {
|
||||
return req.error(accountId, err)
|
||||
}
|
||||
ids := strings.Split(id, ",")
|
||||
if len(ids) < 1 {
|
||||
return req.parameterErrorResponse(single(accountId), UriParamIdentityId, fmt.Sprintf("Invalid value for path parameter '%v': '%s': %s", UriParamIdentityId, log.SafeString(id), "empty list of identity ids"))
|
||||
}
|
||||
|
||||
logger := log.From(req.logger.With().Str(logAccountId, accountId).Array(UriParamIdentityId, log.SafeStringArray(ids)))
|
||||
ctx := req.ctx.WithLogger(logger)
|
||||
|
||||
notDeleted, sessionState, state, lang, jerr := g.jmap.DeleteIdentity(accountId, ids, ctx)
|
||||
if jerr != nil {
|
||||
return req.jmapError(accountId, jerr, sessionState, lang)
|
||||
}
|
||||
|
||||
if len(notDeleted) == 0 {
|
||||
return req.noContent(accountId, sessionState, IdentityResponseObjectType, state)
|
||||
} else {
|
||||
logger.Error().Msgf("failed to delete %d identities", len(notDeleted))
|
||||
return req.errorS(accountId, req.apiError(&ErrorFailedToDeleteSomeIdentities), sessionState)
|
||||
}
|
||||
})
|
||||
delete(Identity, w, r, g, g.jmap.DeleteIdentity)
|
||||
}
|
||||
|
||||
// Get changes to Identities since a given State
|
||||
// @api:tags identity,changes
|
||||
func (g *Groupware) GetIdentityChanges(w http.ResponseWriter, r *http.Request) {
|
||||
g.respond(w, r, func(req Request) Response {
|
||||
accountId, err := req.GetAccountIdForMail()
|
||||
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 identity changes"))
|
||||
l = l.Str(HeaderParamSince, log.SafeString(string(sinceState)))
|
||||
|
||||
logger := log.From(l)
|
||||
ctx := req.ctx.WithLogger(logger)
|
||||
changes, sessionState, state, lang, jerr := g.jmap.GetIdentityChanges(accountId, sinceState, maxChanges, ctx)
|
||||
if jerr != nil {
|
||||
return req.jmapError(accountId, jerr, sessionState, lang)
|
||||
}
|
||||
var body jmap.IdentityChanges = changes
|
||||
|
||||
return req.respond(accountId, body, sessionState, IdentityResponseObjectType, state)
|
||||
})
|
||||
changes(Identity, w, r, g, g.jmap.GetIdentityChanges)
|
||||
}
|
||||
|
||||
@@ -160,7 +160,7 @@ func (g *Groupware) Index(w http.ResponseWriter, r *http.Request) {
|
||||
Accounts: buildIndexAccounts(req.session, boot),
|
||||
PrimaryAccounts: buildIndexPrimaryAccounts(req.session),
|
||||
}
|
||||
return req.respondN(accountIds, body, sessionState, IndexResponseObjectType, state)
|
||||
return req.respondN(accountIds, body, sessionState, IndexResponseObjectType, state, lang)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -18,29 +18,12 @@ import (
|
||||
//
|
||||
// This is the primary mechanism for organising Emails within an account.
|
||||
// It is analogous to a folder or a label in other systems.
|
||||
func (g *Groupware) GetMailbox(w http.ResponseWriter, r *http.Request) {
|
||||
g.respond(w, r, func(req Request) Response {
|
||||
accountId, err := req.GetAccountIdForMail()
|
||||
if err != nil {
|
||||
return req.error(accountId, err)
|
||||
}
|
||||
func (g *Groupware) GetMailboxById(w http.ResponseWriter, r *http.Request) {
|
||||
get(Mailbox, w, r, g, g.jmap.GetMailbox)
|
||||
}
|
||||
|
||||
mailboxId, err := req.PathParam(UriParamMailboxId)
|
||||
if err != nil {
|
||||
return req.error(accountId, err)
|
||||
}
|
||||
|
||||
mailboxes, sessionState, state, lang, jerr := g.jmap.GetMailbox(accountId, single(mailboxId), req.ctx)
|
||||
if jerr != nil {
|
||||
return req.jmapError(accountId, jerr, sessionState, lang)
|
||||
}
|
||||
|
||||
if len(mailboxes.List) == 1 {
|
||||
return req.respond(accountId, mailboxes.List[0], sessionState, MailboxResponseObjectType, state)
|
||||
} else {
|
||||
return req.notFound(accountId, sessionState, MailboxResponseObjectType, state)
|
||||
}
|
||||
})
|
||||
func (g *Groupware) ModifyMailbox(w http.ResponseWriter, r *http.Request) {
|
||||
modify(Mailbox, w, r, g, g.jmap.UpdateMailbox)
|
||||
}
|
||||
|
||||
// Get the list of all the mailboxes of an account, potentially filtering on the
|
||||
@@ -92,7 +75,7 @@ func (g *Groupware) GetMailboxes(w http.ResponseWriter, r *http.Request) { //NOS
|
||||
}
|
||||
|
||||
if mailboxes, ok := mailboxesByAccountId[accountId]; ok {
|
||||
return req.respond(accountId, sortMailboxSlice(mailboxes), sessionState, MailboxResponseObjectType, state)
|
||||
return req.respond(accountId, sortMailboxSlice(mailboxes), sessionState, MailboxResponseObjectType, state, lang)
|
||||
} else {
|
||||
return req.notFound(accountId, sessionState, MailboxResponseObjectType, state)
|
||||
}
|
||||
@@ -102,7 +85,7 @@ func (g *Groupware) GetMailboxes(w http.ResponseWriter, r *http.Request) { //NOS
|
||||
return req.jmapError(accountId, err, sessionState, lang)
|
||||
}
|
||||
if mailboxes, ok := mailboxesByAccountId[accountId]; ok {
|
||||
return req.respond(accountId, sortMailboxSlice(mailboxes), sessionState, MailboxResponseObjectType, state)
|
||||
return req.respond(accountId, sortMailboxSlice(mailboxes), sessionState, MailboxResponseObjectType, state, lang)
|
||||
} else {
|
||||
return req.notFound(accountId, sessionState, MailboxResponseObjectType, state)
|
||||
}
|
||||
@@ -140,13 +123,13 @@ func (g *Groupware) GetMailboxesForAllAccounts(w http.ResponseWriter, r *http.Re
|
||||
if err != nil {
|
||||
return req.jmapErrorN(accountIds, err, sessionState, lang)
|
||||
}
|
||||
return req.respondN(accountIds, sortMailboxesMap(mailboxesByAccountId), sessionState, MailboxResponseObjectType, state)
|
||||
return req.respondN(accountIds, sortMailboxesMap(mailboxesByAccountId), sessionState, MailboxResponseObjectType, state, lang)
|
||||
} else {
|
||||
mailboxesByAccountId, sessionState, state, lang, err := g.jmap.GetAllMailboxes(accountIds, ctx)
|
||||
if err != nil {
|
||||
return req.jmapErrorN(accountIds, err, sessionState, lang)
|
||||
}
|
||||
return req.respondN(accountIds, sortMailboxesMap(mailboxesByAccountId), sessionState, MailboxResponseObjectType, state)
|
||||
return req.respondN(accountIds, sortMailboxesMap(mailboxesByAccountId), sessionState, MailboxResponseObjectType, state, lang)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -175,58 +158,14 @@ func (g *Groupware) GetMailboxByRoleForAllAccounts(w http.ResponseWriter, r *htt
|
||||
if jerr != nil {
|
||||
return req.jmapErrorN(accountIds, jerr, sessionState, lang)
|
||||
}
|
||||
return req.respondN(accountIds, sortMailboxesMap(mailboxesByAccountId), sessionState, MailboxResponseObjectType, state)
|
||||
return req.respondN(accountIds, sortMailboxesMap(mailboxesByAccountId), sessionState, MailboxResponseObjectType, state, lang)
|
||||
})
|
||||
}
|
||||
|
||||
// Get the changes tp Mailboxes since a certain State.
|
||||
// @api:tags mailbox,changes
|
||||
func (g *Groupware) GetMailboxChanges(w http.ResponseWriter, r *http.Request) {
|
||||
g.respond(w, r, func(req Request) Response {
|
||||
l := req.logger.With()
|
||||
|
||||
accountId, err := req.GetAccountIdForMail()
|
||||
if err != nil {
|
||||
return req.error(accountId, err)
|
||||
}
|
||||
l = l.Str(logAccountId, accountId)
|
||||
|
||||
maxChanges, ok, err := req.parseUIntParam(QueryParamMaxChanges, 0)
|
||||
if err != nil {
|
||||
return req.error(accountId, err)
|
||||
}
|
||||
if ok {
|
||||
l = l.Uint(QueryParamMaxChanges, maxChanges)
|
||||
}
|
||||
|
||||
sinceState := jmap.State(req.OptHeaderParamDoc(HeaderParamSince, "Optionally specifies the state identifier from which on to list mailbox changes"))
|
||||
if sinceState != "" {
|
||||
l = l.Str(HeaderParamSince, log.SafeString(string(sinceState)))
|
||||
}
|
||||
|
||||
logger := log.From(l)
|
||||
ctx := req.ctx.WithLogger(logger)
|
||||
|
||||
// As for Emails and Contacts, one would expect this request without any prior state to
|
||||
// be usable to list all the objects that currently exist, but that is not the case for
|
||||
// Mailbox, at least in combination with Stalwart, as those are initial objects that
|
||||
// "always existed", even with the initial State, and this the Mailbox/changes operation
|
||||
// returns nothing.
|
||||
// For this reason, when the "since" state is empty, we respond with an error.
|
||||
if sinceState == "" {
|
||||
return req.error(accountId, req.apiError(&ErrorInvalidUserRequest, withTitle(
|
||||
"Mailbox changes without state is unsupported",
|
||||
"Requesting Mailbox changes without an initial state is an unsupported operation",
|
||||
)))
|
||||
}
|
||||
|
||||
changes, sessionState, state, lang, jerr := g.jmap.GetMailboxChanges(accountId, sinceState, maxChanges, ctx)
|
||||
if jerr != nil {
|
||||
return req.jmapError(accountId, jerr, sessionState, lang)
|
||||
}
|
||||
|
||||
return req.respond(accountId, changes, sessionState, MailboxResponseObjectType, state)
|
||||
})
|
||||
changes(Mailbox, w, r, g, g.jmap.GetMailboxChanges)
|
||||
}
|
||||
|
||||
// Get the changes that occured in all the mailboxes of all accounts.
|
||||
@@ -266,7 +205,7 @@ func (g *Groupware) GetMailboxChangesForAllAccounts(w http.ResponseWriter, r *ht
|
||||
return req.jmapErrorN(allAccountIds, jerr, sessionState, lang)
|
||||
}
|
||||
|
||||
return req.respondN(allAccountIds, changesByAccountId, sessionState, MailboxResponseObjectType, state)
|
||||
return req.respondN(allAccountIds, changesByAccountId, sessionState, MailboxResponseObjectType, state, lang)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -285,67 +224,12 @@ func (g *Groupware) GetMailboxRoles(w http.ResponseWriter, r *http.Request) {
|
||||
return req.jmapErrorN(allAccountIds, jerr, sessionState, lang)
|
||||
}
|
||||
|
||||
return req.respondN(allAccountIds, rolesByAccountId, sessionState, MailboxResponseObjectType, state)
|
||||
})
|
||||
}
|
||||
|
||||
func (g *Groupware) UpdateMailbox(w http.ResponseWriter, r *http.Request) {
|
||||
g.respond(w, r, func(req Request) Response {
|
||||
l := req.logger.With()
|
||||
|
||||
accountId, err := req.GetAccountIdForMail()
|
||||
if err != nil {
|
||||
return req.error(accountId, err)
|
||||
}
|
||||
l = l.Str(logAccountId, accountId)
|
||||
|
||||
mailboxId, err := req.PathParamDoc(UriParamMailboxId, "the identifier of the mailbox to update")
|
||||
if err != nil {
|
||||
return req.error(accountId, err)
|
||||
}
|
||||
l = l.Str(UriParamMailboxId, log.SafeString(mailboxId))
|
||||
|
||||
var body jmap.MailboxChange
|
||||
err = req.body(&body)
|
||||
if err != nil {
|
||||
return req.error(accountId, err)
|
||||
}
|
||||
logger := log.From(l)
|
||||
ctx := req.ctx.WithLogger(logger)
|
||||
|
||||
updated, sessionState, state, lang, jerr := g.jmap.UpdateMailbox(accountId, mailboxId, body, ctx)
|
||||
if jerr != nil {
|
||||
return req.jmapError(accountId, jerr, sessionState, lang)
|
||||
}
|
||||
|
||||
return req.respond(accountId, updated, sessionState, MailboxResponseObjectType, state)
|
||||
return req.respondN(allAccountIds, rolesByAccountId, sessionState, MailboxResponseObjectType, state, lang)
|
||||
})
|
||||
}
|
||||
|
||||
func (g *Groupware) CreateMailbox(w http.ResponseWriter, r *http.Request) {
|
||||
g.respond(w, r, func(req Request) Response {
|
||||
l := req.logger.With()
|
||||
accountId, err := req.GetAccountIdForMail()
|
||||
if err != nil {
|
||||
return req.error(accountId, err)
|
||||
}
|
||||
l = l.Str(logAccountId, accountId)
|
||||
|
||||
var body jmap.MailboxChange
|
||||
err = req.body(&body)
|
||||
if err != nil {
|
||||
return req.error(accountId, err)
|
||||
}
|
||||
logger := log.From(l)
|
||||
ctx := req.ctx.WithLogger(logger)
|
||||
|
||||
created, sessionState, state, lang, jerr := g.jmap.CreateMailbox(accountId, body, ctx)
|
||||
if jerr != nil {
|
||||
return req.jmapError(accountId, jerr, sessionState, lang)
|
||||
}
|
||||
|
||||
return req.respond(accountId, created, sessionState, MailboxResponseObjectType, state)
|
||||
})
|
||||
create(Mailbox, w, r, g, nil, g.jmap.CreateMailbox)
|
||||
}
|
||||
|
||||
// Delete Mailboxes by their unique identifiers.
|
||||
@@ -354,34 +238,7 @@ func (g *Groupware) CreateMailbox(w http.ResponseWriter, r *http.Request) {
|
||||
//
|
||||
// @api:example deletedmailboxes
|
||||
func (g *Groupware) DeleteMailbox(w http.ResponseWriter, r *http.Request) {
|
||||
g.respond(w, r, func(req Request) Response {
|
||||
l := req.logger.With()
|
||||
accountId, err := req.GetAccountIdForMail()
|
||||
if err != nil {
|
||||
return req.error(accountId, err)
|
||||
}
|
||||
l = l.Str(logAccountId, accountId)
|
||||
|
||||
mailboxIds, err := req.PathListParamDoc(UriParamMailboxId, "the identifier of the mailbox to delete")
|
||||
if err != nil {
|
||||
return req.error(accountId, err)
|
||||
}
|
||||
l = l.Array(UriParamMailboxId, log.SafeStringArray(mailboxIds))
|
||||
|
||||
if len(mailboxIds) < 1 {
|
||||
return req.noop(accountId) // no mailbox identifiers were mentioned in the request
|
||||
}
|
||||
|
||||
logger := log.From(l)
|
||||
ctx := req.ctx.WithLogger(logger)
|
||||
|
||||
deleted, sessionState, state, lang, jerr := g.jmap.DeleteMailboxes(accountId, mailboxIds, ctx)
|
||||
if jerr != nil {
|
||||
return req.jmapError(accountId, jerr, sessionState, lang)
|
||||
}
|
||||
|
||||
return req.respond(accountId, deleted, sessionState, MailboxResponseObjectType, state)
|
||||
})
|
||||
delete(Mailbox, w, r, g, g.jmap.DeleteMailboxes)
|
||||
}
|
||||
|
||||
var mailboxRoleSortOrderScore = map[string]int{
|
||||
|
||||
@@ -130,6 +130,6 @@ func (g *Groupware) GetObjects(w http.ResponseWriter, r *http.Request) { //NOSON
|
||||
}
|
||||
var body jmap.Objects = objs
|
||||
|
||||
return req.respond(accountId, body, sessionState, "", state)
|
||||
return req.respond(accountId, body, sessionState, "", state, lang)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -13,22 +13,8 @@ import (
|
||||
//
|
||||
// Note that there may be multiple Quota objects for different resource types.
|
||||
func (g *Groupware) GetQuota(w http.ResponseWriter, r *http.Request) {
|
||||
g.respond(w, r, func(req Request) Response {
|
||||
accountId, err := req.GetAccountIdForQuota()
|
||||
if err != nil {
|
||||
return req.error(accountId, err)
|
||||
}
|
||||
logger := log.From(req.logger.With().Str(logAccountId, accountId))
|
||||
ctx := req.ctx.WithLogger(logger)
|
||||
|
||||
res, sessionState, state, lang, jerr := g.jmap.GetQuotas(single(accountId), ctx)
|
||||
if jerr != nil {
|
||||
return req.jmapError(accountId, jerr, sessionState, lang)
|
||||
}
|
||||
for _, quotas := range res {
|
||||
return req.respond(accountId, quotas, sessionState, QuotaResponseObjectType, state)
|
||||
}
|
||||
return req.notFound(accountId, sessionState, QuotaResponseObjectType, state)
|
||||
getFromMap(Quota, w, r, g, func(accountIds, _ []string, ctx jmap.Context) (map[string]jmap.QuotaGetResponse, jmap.SessionState, jmap.State, jmap.Language, jmap.Error) {
|
||||
return g.jmap.GetQuotas(accountIds, ctx)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -62,42 +48,15 @@ func (g *Groupware) GetQuotaForAllAccounts(w http.ResponseWriter, r *http.Reques
|
||||
Quotas: accountQuotas.List,
|
||||
}
|
||||
}
|
||||
return req.respondN(accountIds, result, sessionState, QuotaResponseObjectType, state)
|
||||
return req.respondN(accountIds, result, sessionState, QuotaResponseObjectType, state, lang)
|
||||
})
|
||||
}
|
||||
|
||||
// currently unsupported in Stalwart:
|
||||
/*
|
||||
// Get changes to Quotas since a given State
|
||||
//
|
||||
// Currently unsupported in Stalwart.
|
||||
// @api:tags contact,changes
|
||||
// @api:ignore
|
||||
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)
|
||||
})
|
||||
changes(Quota, w, r, g, g.jmap.GetQuotaChanges)
|
||||
}
|
||||
*/
|
||||
|
||||
@@ -16,7 +16,7 @@ func (g *Groupware) GetTaskLists(w http.ResponseWriter, r *http.Request) {
|
||||
var _ string = accountId
|
||||
|
||||
var body []jmap.TaskList = AllTaskLists
|
||||
return req.respond(accountId, body, req.session.State, TaskListResponseObjectType, TaskListsState)
|
||||
return req.respond(accountId, body, req.session.State, TaskListResponseObjectType, TaskListsState, jmap.NoLanguage)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ func (g *Groupware) GetTaskListById(w http.ResponseWriter, r *http.Request) {
|
||||
// TODO replace with proper implementation
|
||||
for _, tasklist := range AllTaskLists {
|
||||
if tasklist.Id == tasklistId {
|
||||
return req.respond(accountId, tasklist, req.session.State, TaskListResponseObjectType, TaskListsState)
|
||||
return req.respond(accountId, tasklist, req.session.State, TaskListResponseObjectType, TaskListsState, jmap.NoLanguage)
|
||||
}
|
||||
}
|
||||
return req.etaggedNotFound(accountId, req.session.State, TaskListResponseObjectType, TaskListsState)
|
||||
@@ -61,6 +61,6 @@ func (g *Groupware) GetTasksInTaskList(w http.ResponseWriter, r *http.Request) {
|
||||
if !ok {
|
||||
return req.notFound(accountId, req.session.State, TaskResponseObjectType, TaskState)
|
||||
}
|
||||
return req.respond(accountId, tasks, req.session.State, TaskResponseObjectType, TaskState)
|
||||
return req.respond(accountId, tasks, req.session.State, TaskResponseObjectType, TaskState, jmap.NoLanguage)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ import (
|
||||
"net/http"
|
||||
|
||||
"github.com/opencloud-eu/opencloud/pkg/jmap"
|
||||
"github.com/opencloud-eu/opencloud/pkg/log"
|
||||
)
|
||||
|
||||
// Get vacation notice information.
|
||||
@@ -14,19 +13,8 @@ import (
|
||||
//
|
||||
// The VacationResponse object represents the state of vacation-response-related settings for an account.
|
||||
func (g *Groupware) GetVacation(w http.ResponseWriter, r *http.Request) {
|
||||
g.respond(w, r, func(req Request) Response {
|
||||
accountId, err := req.GetAccountIdForVacationResponse()
|
||||
if err != nil {
|
||||
return req.error(accountId, err)
|
||||
}
|
||||
logger := log.From(req.logger.With().Str(logAccountId, accountId))
|
||||
ctx := req.ctx.WithLogger(logger)
|
||||
|
||||
res, sessionState, state, lang, jerr := g.jmap.GetVacationResponse(accountId, ctx)
|
||||
if jerr != nil {
|
||||
return req.jmapError(accountId, jerr, sessionState, lang)
|
||||
}
|
||||
return req.respond(accountId, res, sessionState, VacationResponseResponseObjectType, state)
|
||||
get(VacationResponse, w, r, g, func(accountId string, ids []string, ctx jmap.Context) (jmap.VacationResponseGetResponse, jmap.SessionState, jmap.State, jmap.Language, jmap.Error) {
|
||||
return g.jmap.GetVacationResponse(accountId, ctx)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -35,25 +23,7 @@ func (g *Groupware) GetVacation(w http.ResponseWriter, r *http.Request) {
|
||||
// A vacation response sends an automatic reply when a message is delivered to the mail store, informing the original
|
||||
// sender that their message may not be read for some time.
|
||||
func (g *Groupware) SetVacation(w http.ResponseWriter, r *http.Request) {
|
||||
g.respond(w, r, func(req Request) Response {
|
||||
accountId, err := req.GetAccountIdForVacationResponse()
|
||||
if err != nil {
|
||||
return req.error(accountId, err)
|
||||
}
|
||||
logger := log.From(req.logger.With().Str(logAccountId, accountId))
|
||||
ctx := req.ctx.WithLogger(logger)
|
||||
|
||||
var body jmap.VacationResponseChange
|
||||
err = req.body(&body)
|
||||
if err != nil {
|
||||
return req.error(accountId, err)
|
||||
}
|
||||
|
||||
res, sessionState, state, lang, jerr := g.jmap.SetVacationResponse(accountId, body, ctx)
|
||||
if jerr != nil {
|
||||
return req.jmapError(accountId, jerr, sessionState, lang)
|
||||
}
|
||||
|
||||
return req.respond(accountId, res, sessionState, VacationResponseResponseObjectType, state)
|
||||
modify(VacationResponse, w, r, g, func(accountId string, id string, change jmap.VacationResponseChange, ctx jmap.Context) (jmap.VacationResponse, jmap.SessionState, jmap.State, jmap.Language, jmap.Error) {
|
||||
return g.jmap.SetVacationResponse(accountId, change, ctx)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -191,18 +191,27 @@ const (
|
||||
ErrorCodeAccountNotFound = "ACCNFD"
|
||||
ErrorCodeAccountNotSupportedByMethod = "ACCNSM"
|
||||
ErrorCodeAccountReadOnly = "ACCRDO"
|
||||
ErrorCodeMissingBlobSessionCapability = "MSCBLO"
|
||||
ErrorCodeMissingBlobAccountCapability = "MACBLO"
|
||||
ErrorCodeMissingMailsSessionCapability = "MSCMAI"
|
||||
ErrorCodeMissingMailsAccountCapability = "MACMAI"
|
||||
ErrorCodeMissingCalendarsSessionCapability = "MSCCAL"
|
||||
ErrorCodeMissingCalendarsAccountCapability = "MACCAL"
|
||||
ErrorCodeMissingContactsSessionCapability = "MSCCON"
|
||||
ErrorCodeMissingContactsAccountCapability = "MACCON"
|
||||
ErrorCodeMissingTasksSessionCapability = "MSCTSK"
|
||||
ErrorCodeMissingTasksAccountCapability = "MACTSK"
|
||||
ErrorCodeMissingQuotaSessionCapability = "MSCMAI"
|
||||
ErrorCodeMissingQuotaAccountCapability = "MACMAI"
|
||||
ErrorCodeFailedToDeleteEmail = "DELEML"
|
||||
ErrorCodeFailedToDeleteSomeIdentities = "DELSID"
|
||||
ErrorCodeFailedToSanitizeEmail = "FSANEM"
|
||||
ErrorCodeFailedToDeleteAddressBook = "DELABK"
|
||||
ErrorCodeFailedToDeleteCalendar = "DELCAL"
|
||||
ErrorCodeFailedToDeleteMailbox = "DELMBX"
|
||||
ErrorCodeFailedToDeleteContact = "DELCNT"
|
||||
ErrorCodeFailedToDeleteCalendar = "DELCAL"
|
||||
ErrorCodeFailedToDeleteEvent = "DELEVT"
|
||||
ErrorCodeFailedToDeleteIdentity = "DELIDN"
|
||||
ErrorCodeNoMailboxWithDraftRole = "NMBXDR"
|
||||
ErrorCodeNoMailboxWithSentRole = "NMBXSE"
|
||||
ErrorCodeInvalidSortSpecification = "INVSSP"
|
||||
@@ -392,6 +401,30 @@ var (
|
||||
Title: "The referenced Account is read-only",
|
||||
Detail: "The Account that was referenced in the request only supports read-only operations.",
|
||||
}
|
||||
ErrorMissingBlobSessionCapability = GroupwareError{
|
||||
Status: http.StatusExpectationFailed,
|
||||
Code: ErrorCodeMissingBlobAccountCapability,
|
||||
Title: "Session is missing the blob session capability",
|
||||
Detail: "The JMAP Session of the user does not have the required capability for blobs.",
|
||||
}
|
||||
ErrorMissingBlobAccountCapability = GroupwareError{
|
||||
Status: http.StatusExpectationFailed,
|
||||
Code: ErrorCodeMissingBlobSessionCapability,
|
||||
Title: "Account is missing the blob capability",
|
||||
Detail: "The JMAP Account of the user does not have the required capability for blobs.",
|
||||
}
|
||||
ErrorMissingMailsSessionCapability = GroupwareError{
|
||||
Status: http.StatusExpectationFailed,
|
||||
Code: ErrorCodeMissingMailsAccountCapability,
|
||||
Title: "Session is missing the mails session capability",
|
||||
Detail: "The JMAP Session of the user does not have the required capability for mails.",
|
||||
}
|
||||
ErrorMissingMailsAccountCapability = GroupwareError{
|
||||
Status: http.StatusExpectationFailed,
|
||||
Code: ErrorCodeMissingMailsSessionCapability,
|
||||
Title: "Account is missing the mails capability",
|
||||
Detail: "The JMAP Account of the user does not have the required capability for mails.",
|
||||
}
|
||||
ErrorMissingCalendarsSessionCapability = GroupwareError{
|
||||
Status: http.StatusExpectationFailed,
|
||||
Code: ErrorCodeMissingCalendarsAccountCapability,
|
||||
@@ -428,6 +461,18 @@ var (
|
||||
Title: "Account is missing the tasks capability",
|
||||
Detail: "The JMAP Account of the user does not have the required capability for tasks",
|
||||
}
|
||||
ErrorMissingQuotaSessionCapability = GroupwareError{
|
||||
Status: http.StatusExpectationFailed,
|
||||
Code: ErrorCodeMissingQuotaSessionCapability,
|
||||
Title: "Session is missing the quota session capability",
|
||||
Detail: "The JMAP Session of the user does not have the required capability for quotas.",
|
||||
}
|
||||
ErrorMissingQuotaAccountCapability = GroupwareError{
|
||||
Status: http.StatusExpectationFailed,
|
||||
Code: ErrorCodeMissingQuotaAccountCapability,
|
||||
Title: "Account is missing the quota capability",
|
||||
Detail: "The JMAP Account of the user does not have the required capability for quotas.",
|
||||
}
|
||||
ErrorFailedToDeleteEmail = GroupwareError{
|
||||
Status: http.StatusInternalServerError,
|
||||
Code: ErrorCodeFailedToDeleteEmail,
|
||||
@@ -452,11 +497,17 @@ var (
|
||||
Title: "Failed to delete address books",
|
||||
Detail: "One or more address books could not be deleted.",
|
||||
}
|
||||
ErrorFailedToDeleteContact = GroupwareError{
|
||||
ErrorFailedToDeleteMailbox = GroupwareError{
|
||||
Status: http.StatusInternalServerError,
|
||||
Code: ErrorCodeFailedToDeleteContact,
|
||||
Title: "Failed to delete contacts",
|
||||
Detail: "One or more contacts could not be deleted.",
|
||||
Code: ErrorCodeFailedToDeleteMailbox,
|
||||
Title: "Failed to delete mailboxes",
|
||||
Detail: "One or more mailboxes could not be deleted.",
|
||||
}
|
||||
ErrorFailedToDeleteEvent = GroupwareError{
|
||||
Status: http.StatusInternalServerError,
|
||||
Code: ErrorCodeFailedToDeleteEvent,
|
||||
Title: "Failed to delete events",
|
||||
Detail: "One or more events could not be deleted.",
|
||||
}
|
||||
ErrorFailedToDeleteCalendar = GroupwareError{
|
||||
Status: http.StatusInternalServerError,
|
||||
@@ -464,6 +515,18 @@ var (
|
||||
Title: "Failed to delete calendar",
|
||||
Detail: "One or more calendars could not be deleted.",
|
||||
}
|
||||
ErrorFailedToDeleteContact = GroupwareError{
|
||||
Status: http.StatusInternalServerError,
|
||||
Code: ErrorCodeFailedToDeleteContact,
|
||||
Title: "Failed to delete contacts",
|
||||
Detail: "One or more contacts could not be deleted.",
|
||||
}
|
||||
ErrorFailedToDeleteIdentity = GroupwareError{
|
||||
Status: http.StatusInternalServerError,
|
||||
Code: ErrorCodeFailedToDeleteIdentity,
|
||||
Title: "Failed to delete identities",
|
||||
Detail: "One or more identities could not be deleted.",
|
||||
}
|
||||
ErrorNoMailboxWithDraftRole = GroupwareError{
|
||||
Status: http.StatusExpectationFailed,
|
||||
Code: ErrorCodeNoMailboxWithDraftRole,
|
||||
|
||||
@@ -43,7 +43,6 @@ const (
|
||||
logErrorSourceHeader = "source-header"
|
||||
logErrorSourceParameter = "source-parameter"
|
||||
logErrorSourcePointer = "source-pointer"
|
||||
logIdentityId = "identity-id"
|
||||
logEmailId = "email-id"
|
||||
logJobDescription = "job"
|
||||
logJobId = "job-id"
|
||||
@@ -108,7 +107,7 @@ type Groupware struct {
|
||||
jmap *jmap.Client
|
||||
userProvider userProvider
|
||||
// SSE events that need to be pushed to clients.
|
||||
eventChannel chan Event
|
||||
eventChannel chan SSEvent
|
||||
// Background jobs that need to be executed.
|
||||
jobsChannel chan Job
|
||||
// A threadsafe counter to generate the job IDs.
|
||||
@@ -132,8 +131,8 @@ func (e GroupwareInitializationError) Unwrap() error {
|
||||
return e.Err
|
||||
}
|
||||
|
||||
// SSE Event.
|
||||
type Event struct {
|
||||
// Ssrver Sent Event.
|
||||
type SSEvent struct {
|
||||
// The type of event, will be sent as the "type" attribute.
|
||||
Type string
|
||||
// The ID of the stream to push the event to, typically the username.
|
||||
@@ -314,7 +313,7 @@ func NewGroupware(config *config.Config, logger *log.Logger, mux *chi.Mux, prome
|
||||
jmapClient.AddSessionEventListener(sessionCache)
|
||||
|
||||
// A channel to process SSE Events with a single worker.
|
||||
eventChannel := make(chan Event, eventChannelSize)
|
||||
eventChannel := make(chan SSEvent, eventChannelSize)
|
||||
{
|
||||
eventBufferSizeMetric, err := prometheus.NewConstMetric(m.EventBufferSizeDesc, prometheus.GaugeValue, float64(eventChannelSize))
|
||||
if err != nil {
|
||||
@@ -465,7 +464,7 @@ func (g *Groupware) listenForEvents() {
|
||||
|
||||
func (g *Groupware) push(user user, typ string, body any) {
|
||||
g.metrics.SSEEventsCounter.WithLabelValues(typ).Inc()
|
||||
g.eventChannel <- Event{Type: typ, Stream: user.GetUsername(), Body: body}
|
||||
g.eventChannel <- SSEvent{Type: typ, Stream: user.GetUsername(), Body: body}
|
||||
}
|
||||
|
||||
func (g *Groupware) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
106
services/groupware/pkg/groupware/objtypes.go
Normal file
106
services/groupware/pkg/groupware/objtypes.go
Normal file
@@ -0,0 +1,106 @@
|
||||
package groupware
|
||||
|
||||
import (
|
||||
"github.com/opencloud-eu/opencloud/pkg/jmap"
|
||||
)
|
||||
|
||||
type ObjectType[T jmap.Foo, CH jmap.Change, CHS jmap.Changes[T]] struct {
|
||||
name string
|
||||
responseType ResponseObjectType
|
||||
uriParamName string
|
||||
containerUriParamName string
|
||||
accountFunc func(r *Request) (bool, string, Response)
|
||||
failedToDeleteError GroupwareError
|
||||
}
|
||||
|
||||
var (
|
||||
Blob = ObjectType[jmap.Blob, jmap.BlobChange, jmap.BlobChanges]{
|
||||
name: "blob",
|
||||
responseType: BlobResponseObjectType,
|
||||
uriParamName: UriParamBlobId,
|
||||
containerUriParamName: "",
|
||||
accountFunc: (*Request).needBloblWithAccount,
|
||||
failedToDeleteError: ErrorServerUnavailable,
|
||||
}
|
||||
|
||||
AddressBook = ObjectType[jmap.AddressBook, jmap.AddressBookChange, jmap.AddressBookChanges]{
|
||||
name: "address book",
|
||||
responseType: AddressBookResponseObjectType,
|
||||
uriParamName: UriParamAddressBookId,
|
||||
containerUriParamName: "",
|
||||
accountFunc: (*Request).needContactWithAccount,
|
||||
failedToDeleteError: ErrorFailedToDeleteAddressBook,
|
||||
}
|
||||
|
||||
Calendar = ObjectType[jmap.Calendar, jmap.CalendarChange, jmap.CalendarChanges]{
|
||||
name: "calendar",
|
||||
responseType: CalendarResponseObjectType,
|
||||
uriParamName: UriParamCalendarId,
|
||||
containerUriParamName: "",
|
||||
accountFunc: (*Request).needCalendarWithAccount,
|
||||
failedToDeleteError: ErrorFailedToDeleteCalendar,
|
||||
}
|
||||
|
||||
Contact = ObjectType[jmap.ContactCard, jmap.ContactCardChange, jmap.ContactCardChanges]{
|
||||
name: "contact",
|
||||
responseType: ContactResponseObjectType,
|
||||
uriParamName: UriParamContactId,
|
||||
containerUriParamName: UriParamCalendarId,
|
||||
accountFunc: (*Request).needCalendarWithAccount,
|
||||
failedToDeleteError: ErrorFailedToDeleteContact,
|
||||
}
|
||||
|
||||
Email = ObjectType[jmap.Email, jmap.EmailChange, jmap.EmailChanges]{
|
||||
name: "email",
|
||||
responseType: EmailResponseObjectType,
|
||||
uriParamName: UriParamEmailId,
|
||||
containerUriParamName: UriParamMailboxId,
|
||||
accountFunc: (*Request).needMailWithAccount,
|
||||
failedToDeleteError: ErrorFailedToDeleteEmail,
|
||||
}
|
||||
|
||||
Event = ObjectType[jmap.CalendarEvent, jmap.CalendarEventChange, jmap.CalendarEventChanges]{
|
||||
name: "event",
|
||||
responseType: EventResponseObjectType,
|
||||
uriParamName: UriParamEventId,
|
||||
containerUriParamName: UriParamCalendarId,
|
||||
accountFunc: (*Request).needCalendarWithAccount,
|
||||
failedToDeleteError: ErrorFailedToDeleteEvent,
|
||||
}
|
||||
|
||||
Identity = ObjectType[jmap.Identity, jmap.IdentityChange, jmap.IdentityChanges]{
|
||||
name: "identity",
|
||||
responseType: IdentityResponseObjectType,
|
||||
uriParamName: UriParamIdentityId,
|
||||
containerUriParamName: "",
|
||||
accountFunc: (*Request).needMailWithAccount,
|
||||
failedToDeleteError: ErrorFailedToDeleteIdentity,
|
||||
}
|
||||
|
||||
Mailbox = ObjectType[jmap.Mailbox, jmap.MailboxChange, jmap.MailboxChanges]{
|
||||
name: "mailbox",
|
||||
responseType: MailboxResponseObjectType,
|
||||
uriParamName: UriParamMailboxId,
|
||||
containerUriParamName: "",
|
||||
accountFunc: (*Request).needMailWithAccount,
|
||||
failedToDeleteError: ErrorFailedToDeleteMailbox,
|
||||
}
|
||||
|
||||
Quota = ObjectType[jmap.Quota, jmap.QuotaChange, jmap.QuotaChanges]{
|
||||
name: "quota",
|
||||
responseType: QuotaResponseObjectType,
|
||||
uriParamName: "",
|
||||
containerUriParamName: "",
|
||||
accountFunc: (*Request).needQuotaWithAccount,
|
||||
failedToDeleteError: ErrorServerUnavailable,
|
||||
}
|
||||
|
||||
VacationResponse = ObjectType[jmap.VacationResponse, jmap.VacationResponseChange, jmap.VacationResponseChanges]{
|
||||
name: "vacation response",
|
||||
responseType: VacationResponseResponseObjectType,
|
||||
uriParamName: "",
|
||||
containerUriParamName: "",
|
||||
accountFunc: (*Request).needMailWithAccount,
|
||||
failedToDeleteError: ErrorServerUnavailable,
|
||||
}
|
||||
)
|
||||
@@ -227,6 +227,16 @@ func (r *Request) parameterErrorResponse(accountIds []string, param string, deta
|
||||
return r.errorN(accountIds, r.parameterError(param, detail))
|
||||
}
|
||||
|
||||
func (r *Request) unsupportedParams(accountIds []string, params ...string) (bool, Response) {
|
||||
q := r.r.URL.Query()
|
||||
for _, p := range params {
|
||||
if q.Has(p) {
|
||||
return true, r.parameterErrorResponse(accountIds, p, "Unsupported query parameter")
|
||||
}
|
||||
}
|
||||
return false, Response{}
|
||||
}
|
||||
|
||||
func (r *Request) getStringParam(param string, defaultValue string) (string, bool) {
|
||||
q := r.r.URL.Query()
|
||||
if !q.Has(param) {
|
||||
@@ -373,10 +383,8 @@ func (r *Request) parseOptStringListParam(param string) ([]string, bool, *Error)
|
||||
return nil, false, nil
|
||||
}
|
||||
for _, value := range q[param] {
|
||||
for _, v := range strings.Split(value, ",") {
|
||||
if strings.TrimSpace(v) != "" {
|
||||
result = append(result, v)
|
||||
}
|
||||
for v := range notEmptyString(trimmed(strings.SplitSeq(value, ","))) {
|
||||
result = append(result, v)
|
||||
}
|
||||
}
|
||||
return result, true, nil
|
||||
@@ -445,6 +453,70 @@ func (r *Request) observeJmapError(jerr jmap.Error) jmap.Error {
|
||||
return jerr
|
||||
}
|
||||
|
||||
func (r *Request) needBlob(accountId string) (bool, Response) {
|
||||
if r.session.Capabilities.Blob == nil {
|
||||
return false, errorResponse(single(accountId), r.apiError(&ErrorMissingBlobSessionCapability), r.session.State, jmap.NoLanguage)
|
||||
}
|
||||
return true, Response{}
|
||||
}
|
||||
|
||||
func (r *Request) needBlobForAccount(accountId string) (bool, Response) {
|
||||
if ok, resp := r.needBlob(accountId); !ok {
|
||||
return ok, resp
|
||||
}
|
||||
account, ok := r.session.Accounts[accountId]
|
||||
if !ok {
|
||||
return false, errorResponse(single(accountId), r.apiError(&ErrorAccountNotFound), r.session.State, jmap.NoLanguage)
|
||||
}
|
||||
if account.AccountCapabilities.Blob == nil {
|
||||
return false, errorResponse(single(accountId), r.apiError(&ErrorMissingBlobAccountCapability), r.session.State, jmap.NoLanguage)
|
||||
}
|
||||
return true, Response{}
|
||||
}
|
||||
|
||||
func (r *Request) needBloblWithAccount() (bool, string, Response) {
|
||||
accountId, err := r.GetAccountIdForBlob()
|
||||
if err != nil {
|
||||
return false, "", r.error(accountId, err)
|
||||
}
|
||||
if ok, resp := r.needBlobForAccount(accountId); !ok {
|
||||
return false, accountId, resp
|
||||
}
|
||||
return true, accountId, Response{}
|
||||
}
|
||||
|
||||
func (r *Request) needMail(accountId string) (bool, Response) {
|
||||
if r.session.Capabilities.Mail == nil {
|
||||
return false, errorResponse(single(accountId), r.apiError(&ErrorMissingMailsSessionCapability), r.session.State, jmap.NoLanguage)
|
||||
}
|
||||
return true, Response{}
|
||||
}
|
||||
|
||||
func (r *Request) needMailForAccount(accountId string) (bool, Response) {
|
||||
if ok, resp := r.needMail(accountId); !ok {
|
||||
return ok, resp
|
||||
}
|
||||
account, ok := r.session.Accounts[accountId]
|
||||
if !ok {
|
||||
return false, errorResponse(single(accountId), r.apiError(&ErrorAccountNotFound), r.session.State, jmap.NoLanguage)
|
||||
}
|
||||
if account.AccountCapabilities.Contacts == nil {
|
||||
return false, errorResponse(single(accountId), r.apiError(&ErrorMissingMailsAccountCapability), r.session.State, jmap.NoLanguage)
|
||||
}
|
||||
return true, Response{}
|
||||
}
|
||||
|
||||
func (r *Request) needMailWithAccount() (bool, string, Response) {
|
||||
accountId, err := r.GetAccountIdForMail()
|
||||
if err != nil {
|
||||
return false, "", r.error(accountId, err)
|
||||
}
|
||||
if ok, resp := r.needMailForAccount(accountId); !ok {
|
||||
return false, accountId, resp
|
||||
}
|
||||
return true, accountId, Response{}
|
||||
}
|
||||
|
||||
func (r *Request) needTask(accountId string) (bool, Response) {
|
||||
if !IgnoreSessionCapabilityChecksForTasks {
|
||||
if r.session.Capabilities.Tasks == nil {
|
||||
@@ -543,6 +615,38 @@ func (r *Request) needContactWithAccount() (bool, string, Response) {
|
||||
return true, accountId, Response{}
|
||||
}
|
||||
|
||||
func (r *Request) needQuota(accountId string) (bool, Response) {
|
||||
if r.session.Capabilities.Quota == nil {
|
||||
return false, errorResponse(single(accountId), r.apiError(&ErrorMissingQuotaSessionCapability), r.session.State, jmap.NoLanguage)
|
||||
}
|
||||
return true, Response{}
|
||||
}
|
||||
|
||||
func (r *Request) needQuotaForAccount(accountId string) (bool, Response) {
|
||||
if ok, resp := r.needQuota(accountId); !ok {
|
||||
return ok, resp
|
||||
}
|
||||
account, ok := r.session.Accounts[accountId]
|
||||
if !ok {
|
||||
return false, errorResponse(single(accountId), r.apiError(&ErrorAccountNotFound), r.session.State, jmap.NoLanguage)
|
||||
}
|
||||
if account.AccountCapabilities.Quota == nil {
|
||||
return false, errorResponse(single(accountId), r.apiError(&ErrorMissingQuotaAccountCapability), r.session.State, jmap.NoLanguage)
|
||||
}
|
||||
return true, Response{}
|
||||
}
|
||||
|
||||
func (r *Request) needQuotaWithAccount() (bool, string, Response) {
|
||||
accountId, err := r.GetAccountIdForQuota()
|
||||
if err != nil {
|
||||
return false, "", r.error(accountId, err)
|
||||
}
|
||||
if ok, resp := r.needQuotaForAccount(accountId); !ok {
|
||||
return false, accountId, resp
|
||||
}
|
||||
return true, accountId, Response{}
|
||||
}
|
||||
|
||||
type SortCrit struct {
|
||||
Attribute string
|
||||
Ascending bool
|
||||
@@ -598,7 +702,3 @@ func mapSort[T any](accountIds []string, req *Request, defaultSort []T, props []
|
||||
func toState(s string) jmap.State {
|
||||
return jmap.State(s)
|
||||
}
|
||||
|
||||
func ptr[T any](t T) *T {
|
||||
return &t
|
||||
}
|
||||
|
||||
@@ -74,12 +74,12 @@ func etaggedResponse(accountIds []string, body any, sessionState jmap.SessionSta
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Request) respond(accountId string, body any, sessionState jmap.SessionState, objectType ResponseObjectType, etag jmap.State) Response {
|
||||
return etaggedResponse(single(accountId), body, sessionState, objectType, etag, jmap.Language(r.language()))
|
||||
func (r *Request) respond(accountId string, body any, sessionState jmap.SessionState, objectType ResponseObjectType, etag jmap.State, lang jmap.Language) Response {
|
||||
return etaggedResponse(single(accountId), body, sessionState, objectType, etag, lang)
|
||||
}
|
||||
|
||||
func (r *Request) respondN(accountIds []string, body any, sessionState jmap.SessionState, objectType ResponseObjectType, etag jmap.State) Response {
|
||||
return etaggedResponse(accountIds, body, sessionState, objectType, etag, jmap.Language(r.language()))
|
||||
func (r *Request) respondN(accountIds []string, body any, sessionState jmap.SessionState, objectType ResponseObjectType, etag jmap.State, lang jmap.Language) Response {
|
||||
return etaggedResponse(accountIds, body, sessionState, objectType, etag, lang)
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -67,6 +67,8 @@ const (
|
||||
QueryParamQuotas = "quotas"
|
||||
QueryParamIdentities = "identities"
|
||||
QueryParamEmailSubmissions = "submissions"
|
||||
QueryParamId = "id"
|
||||
QueryParamCalculateTotal = "total"
|
||||
HeaderParamSince = "if-none-match"
|
||||
)
|
||||
|
||||
@@ -91,27 +93,31 @@ func (g *Groupware) Route(r chi.Router) {
|
||||
})
|
||||
})
|
||||
r.Route("/{accountid}", func(r chi.Router) {
|
||||
r.Get("/", g.GetAccount)
|
||||
r.Get("/", g.GetAccountById)
|
||||
r.Route("/identities", func(r chi.Router) {
|
||||
r.Get("/", g.GetIdentities)
|
||||
r.Post("/", g.AddIdentity)
|
||||
r.Post("/", g.CreateIdentity)
|
||||
r.Route("/{identityid}", func(r chi.Router) {
|
||||
r.Get("/", g.GetIdentityById)
|
||||
r.Patch("/", g.ModifyIdentity)
|
||||
r.Delete("/", g.DeleteIdentity)
|
||||
})
|
||||
})
|
||||
r.Get("/vacation", g.GetVacation)
|
||||
r.Put("/vacation", g.SetVacation)
|
||||
r.Get("/quota", g.GetQuota)
|
||||
r.Route("/vacation", func(r chi.Router) {
|
||||
r.Get("/", g.GetVacation)
|
||||
r.Put("/", g.SetVacation)
|
||||
})
|
||||
r.Route("/quota", func(r chi.Router) {
|
||||
r.Get("/", g.GetQuota)
|
||||
})
|
||||
r.Route("/mailboxes", func(r chi.Router) {
|
||||
r.Get("/", g.GetMailboxes) // ?name=&role=&subcribed=
|
||||
r.Post("/", g.CreateMailbox)
|
||||
r.Route("/{mailboxid}", func(r chi.Router) {
|
||||
r.Get("/", g.GetMailbox)
|
||||
r.Get("/emails", g.GetAllEmailsInMailbox)
|
||||
r.Patch("/", g.UpdateMailbox)
|
||||
r.Get("/", g.GetMailboxById)
|
||||
r.Patch("/", g.ModifyMailbox)
|
||||
r.Delete("/", g.DeleteMailbox)
|
||||
r.Get("/emails", g.GetAllEmailsInMailbox)
|
||||
})
|
||||
})
|
||||
r.Route("/emails", func(r chi.Router) {
|
||||
@@ -150,7 +156,7 @@ func (g *Groupware) Route(r chi.Router) {
|
||||
r.Get("/", g.GetAddressbooks)
|
||||
r.Post("/", g.CreateAddressBook)
|
||||
r.Route("/{addressbookid}", func(r chi.Router) {
|
||||
r.Get("/", g.GetAddressbook)
|
||||
r.Get("/", g.GetAddressbookById)
|
||||
r.Patch("/", g.ModifyAddressBook)
|
||||
r.Delete("/", g.DeleteAddressBook)
|
||||
r.Get("/contacts", g.GetContactsInAddressbook) //NOSONAR
|
||||
@@ -176,9 +182,13 @@ func (g *Groupware) Route(r chi.Router) {
|
||||
})
|
||||
})
|
||||
r.Route("/events", func(r chi.Router) {
|
||||
r.Post("/", g.CreateCalendarEvent)
|
||||
r.Patch("/", g.ModifyCalendarEvent)
|
||||
r.Delete("/{eventid}", g.DeleteCalendarEvent)
|
||||
r.Get("/", g.GetAllEvents)
|
||||
r.Post("/", g.CreateEvent)
|
||||
r.Route("/{eventid}", func(r chi.Router) {
|
||||
r.Get("/", g.GetEventById)
|
||||
r.Patch("/", g.ModifyEvent)
|
||||
r.Delete("/", g.DeleteEvent)
|
||||
})
|
||||
})
|
||||
r.Route("/tasklists", func(r chi.Router) {
|
||||
r.Get("/", g.GetTaskLists)
|
||||
|
||||
469
services/groupware/pkg/groupware/templates.go
Normal file
469
services/groupware/pkg/groupware/templates.go
Normal file
@@ -0,0 +1,469 @@
|
||||
package groupware
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/opencloud-eu/opencloud/pkg/jmap"
|
||||
"github.com/opencloud-eu/opencloud/pkg/log"
|
||||
)
|
||||
|
||||
// Create a new {{.Name}} using the JSON payload in the body if the `{{.Method}}` operation.
|
||||
//
|
||||
// When successful, it returns a `200 OK` with the {{.ObjType}} that was just created in the response.
|
||||
func create[T jmap.Foo, CHANGE jmap.Change, CHANGES jmap.Changes[T]](
|
||||
o ObjectType[T, CHANGE, CHANGES],
|
||||
w http.ResponseWriter, r *http.Request,
|
||||
g *Groupware,
|
||||
bodyFunc func(r Request, accountId string, body *CHANGE, ctx jmap.Context) (bool, Response),
|
||||
createFunc func(accountId string, change CHANGE, ctx jmap.Context) (*T, jmap.SessionState, jmap.State, jmap.Language, jmap.Error),
|
||||
) {
|
||||
g.respond(w, r, func(req Request) Response {
|
||||
ok, accountId, resp := o.accountFunc(&req)
|
||||
if !ok {
|
||||
return resp
|
||||
}
|
||||
l := req.logger.With().Str(accountId, log.SafeString(accountId))
|
||||
|
||||
var create CHANGE
|
||||
err := req.body(&create)
|
||||
if err != nil {
|
||||
return req.error(accountId, err)
|
||||
}
|
||||
|
||||
logger := log.From(l)
|
||||
ctx := req.ctx.WithLogger(logger)
|
||||
|
||||
if bodyFunc != nil {
|
||||
if ok, resp := bodyFunc(req, accountId, &create, ctx); !ok {
|
||||
return resp
|
||||
}
|
||||
}
|
||||
|
||||
created, sessionState, state, lang, jerr := createFunc(accountId, create, ctx)
|
||||
if jerr != nil {
|
||||
return req.jmapError(accountId, jerr, sessionState, lang)
|
||||
}
|
||||
return req.respond(accountId, created, sessionState, o.responseType, state, lang)
|
||||
})
|
||||
}
|
||||
|
||||
func getall[T jmap.Foo, CHANGE jmap.Change, CHANGES jmap.Changes[T], RESP jmap.GetResponse[T]]( //NOSONAR
|
||||
o ObjectType[T, CHANGE, CHANGES],
|
||||
w http.ResponseWriter, r *http.Request,
|
||||
g *Groupware,
|
||||
getFunc func(accountId string, ids []string, ctx jmap.Context) (RESP, jmap.SessionState, jmap.State, jmap.Language, jmap.Error),
|
||||
) {
|
||||
g.respond(w, r, func(req Request) Response {
|
||||
ok, accountId, resp := o.accountFunc(&req)
|
||||
if !ok {
|
||||
return resp
|
||||
}
|
||||
l := req.logger.With().Str(accountId, log.SafeString(accountId))
|
||||
|
||||
if notok, resp := req.unsupportedParams(single(accountId), QueryParamOffset, QueryParamLimit); notok {
|
||||
return resp
|
||||
}
|
||||
|
||||
logger := log.From(l)
|
||||
ctx := req.ctx.WithLogger(logger)
|
||||
objs, sessionState, state, lang, jerr := getFunc(accountId, []string{}, ctx)
|
||||
if jerr != nil {
|
||||
return req.jmapError(accountId, jerr, sessionState, lang)
|
||||
}
|
||||
return req.respond(accountId, objs, sessionState, o.responseType, state, lang)
|
||||
})
|
||||
}
|
||||
|
||||
func getallpaged[T jmap.Foo, CHANGE jmap.Change, CHANGES jmap.Changes[T], RESP jmap.GetResponse[T], FILTER any, COMP any, SEARCHRESULTS jmap.SearchResults[T]]( //NOSONAR
|
||||
o ObjectType[T, CHANGE, CHANGES],
|
||||
w http.ResponseWriter, r *http.Request,
|
||||
g *Groupware,
|
||||
getFunc func(accountId string, ids []string, ctx jmap.Context) (RESP, jmap.SessionState, jmap.State, jmap.Language, jmap.Error),
|
||||
filterFunc func(containerId string) FILTER,
|
||||
sortBy []COMP,
|
||||
queryFunc func(req Request, accountId string, filter FILTER, sortBy []COMP, offset int, limit uint, ctx jmap.Context) (SEARCHRESULTS, jmap.SessionState, jmap.State, jmap.Language, jmap.Error),
|
||||
) {
|
||||
g.respond(w, r, func(req Request) Response {
|
||||
ok, accountId, resp := o.accountFunc(&req)
|
||||
if !ok {
|
||||
return resp
|
||||
}
|
||||
l := req.logger.With().Str(accountId, log.SafeString(accountId))
|
||||
|
||||
search := false
|
||||
offset, ok, err := req.parseIntParam(QueryParamOffset, 0)
|
||||
if err != nil {
|
||||
return req.error(accountId, err)
|
||||
}
|
||||
if ok {
|
||||
search = true
|
||||
l = l.Int(QueryParamOffset, offset)
|
||||
}
|
||||
|
||||
limit, ok, err := req.parseUIntParam(QueryParamLimit, uint(0))
|
||||
if err != nil {
|
||||
return req.error(accountId, err)
|
||||
}
|
||||
if ok {
|
||||
search = true
|
||||
l = l.Uint(QueryParamLimit, limit)
|
||||
}
|
||||
|
||||
if search {
|
||||
containerId := ""
|
||||
if o.containerUriParamName != "" {
|
||||
var err *Error
|
||||
containerId, err = req.PathParam(o.containerUriParamName)
|
||||
if err != nil {
|
||||
return req.error(accountId, err)
|
||||
}
|
||||
l = l.Str(o.containerUriParamName, log.SafeString(containerId))
|
||||
}
|
||||
|
||||
filter := filterFunc(containerId)
|
||||
|
||||
logger := log.From(l)
|
||||
ctx := req.ctx.WithLogger(logger)
|
||||
results, sessionState, state, lang, jerr := queryFunc(req, accountId, filter, sortBy, offset, limit, ctx)
|
||||
if jerr != nil {
|
||||
return req.jmapError(accountId, jerr, sessionState, lang)
|
||||
}
|
||||
return req.respond(accountId, results, sessionState, o.responseType, state, lang)
|
||||
} else {
|
||||
logger := log.From(l)
|
||||
ctx := req.ctx.WithLogger(logger)
|
||||
objs, sessionState, state, lang, jerr := getFunc(accountId, []string{}, ctx)
|
||||
if jerr != nil {
|
||||
return req.jmapError(accountId, jerr, sessionState, lang)
|
||||
}
|
||||
return req.respond(accountId, objs, sessionState, o.responseType, state, lang)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func query[T jmap.Foo, CHANGE jmap.Change, CHANGES jmap.Changes[T], SEARCHRESULTS jmap.SearchResults[T]]( //NOSONAR
|
||||
o ObjectType[T, CHANGE, CHANGES],
|
||||
w http.ResponseWriter, r *http.Request,
|
||||
g *Groupware,
|
||||
defaultLimit uint,
|
||||
queryFunc func(req Request, accountId string, containerId string, offset int, limit uint, ctx jmap.Context) (SEARCHRESULTS, jmap.SessionState, jmap.State, jmap.Language, *Error),
|
||||
) {
|
||||
g.respond(w, r, func(req Request) Response {
|
||||
ok, accountId, resp := o.accountFunc(&req)
|
||||
if !ok {
|
||||
return resp
|
||||
}
|
||||
l := req.logger.With().Str(accountId, log.SafeString(accountId))
|
||||
|
||||
containerId := ""
|
||||
if o.containerUriParamName != "" {
|
||||
var err *Error
|
||||
containerId, err = req.PathParam(o.containerUriParamName)
|
||||
if err != nil {
|
||||
return req.error(accountId, err)
|
||||
}
|
||||
l = l.Str(o.containerUriParamName, log.SafeString(containerId))
|
||||
}
|
||||
|
||||
offset, ok, err := req.parseIntParam(QueryParamOffset, 0)
|
||||
if err != nil {
|
||||
return req.error(accountId, err)
|
||||
}
|
||||
if ok {
|
||||
l = l.Int(QueryParamOffset, offset)
|
||||
}
|
||||
|
||||
limit, ok, err := req.parseUIntParam(QueryParamLimit, defaultLimit)
|
||||
if err != nil {
|
||||
return req.error(accountId, err)
|
||||
}
|
||||
if ok {
|
||||
l = l.Uint(QueryParamLimit, limit)
|
||||
}
|
||||
|
||||
logger := log.From(l)
|
||||
ctx := req.ctx.WithLogger(logger)
|
||||
|
||||
results, sessionState, state, lang, err := queryFunc(req, accountId, containerId, offset, limit, ctx)
|
||||
if err != nil {
|
||||
return req.error(accountId, err)
|
||||
}
|
||||
|
||||
return req.respond(accountId, results, sessionState, o.responseType, state, lang)
|
||||
})
|
||||
}
|
||||
|
||||
func get[T jmap.Foo, CHANGE jmap.Change, CHANGES jmap.Changes[T], RESP jmap.GetResponse[T]](
|
||||
o ObjectType[T, CHANGE, CHANGES],
|
||||
w http.ResponseWriter, r *http.Request,
|
||||
g *Groupware,
|
||||
getFunc func(accountId string, ids []string, ctx jmap.Context) (RESP, jmap.SessionState, jmap.State, jmap.Language, jmap.Error),
|
||||
) {
|
||||
g.respond(w, r, func(req Request) Response {
|
||||
ok, accountId, resp := o.accountFunc(&req)
|
||||
if !ok {
|
||||
return resp
|
||||
}
|
||||
l := req.logger.With().Str(accountId, log.SafeString(accountId))
|
||||
ids := []string{}
|
||||
if o.uriParamName != "" {
|
||||
id, err := req.PathParamDoc(o.uriParamName, "The unique identifier of the object to retrieve")
|
||||
if err != nil {
|
||||
return req.error(accountId, err)
|
||||
}
|
||||
l.Str(o.uriParamName, log.SafeString(id))
|
||||
ids = single(id)
|
||||
}
|
||||
|
||||
logger := log.From(l)
|
||||
ctx := req.ctx.WithLogger(logger)
|
||||
objs, sessionState, state, lang, jerr := getFunc(accountId, ids, ctx)
|
||||
if jerr != nil {
|
||||
return req.jmapError(accountId, jerr, sessionState, lang)
|
||||
}
|
||||
|
||||
n := len(objs.GetList())
|
||||
switch n {
|
||||
case 0:
|
||||
return req.notFound(accountId, sessionState, ContactResponseObjectType, state)
|
||||
case 1:
|
||||
return req.respond(accountId, objs.GetList()[0], sessionState, ContactResponseObjectType, state, lang)
|
||||
default:
|
||||
logger.Error().Msgf("found %d %s matching '%s' instead of 1", n, o.responseType, ids)
|
||||
return req.errorS(accountId, req.apiError(&ErrorMultipleIdMatches), sessionState)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func getFromMap[T jmap.Foo, CHANGE jmap.Change, CHANGES jmap.Changes[T], RESP jmap.GetResponse[T]](
|
||||
o ObjectType[T, CHANGE, CHANGES],
|
||||
w http.ResponseWriter, r *http.Request,
|
||||
g *Groupware,
|
||||
getFunc func(accountIds []string, ids []string, ctx jmap.Context) (map[string]RESP, jmap.SessionState, jmap.State, jmap.Language, jmap.Error),
|
||||
) {
|
||||
g.respond(w, r, func(req Request) Response {
|
||||
ok, accountId, resp := o.accountFunc(&req)
|
||||
if !ok {
|
||||
return resp
|
||||
}
|
||||
l := req.logger.With().Str(accountId, log.SafeString(accountId))
|
||||
id, err := req.PathParamDoc(o.uriParamName, "The unique identifier of the object to retrieve")
|
||||
// TODO add id splitting
|
||||
if err != nil {
|
||||
return req.error(accountId, err)
|
||||
}
|
||||
l.Str(o.uriParamName, log.SafeString(id))
|
||||
|
||||
logger := log.From(l)
|
||||
ctx := req.ctx.WithLogger(logger)
|
||||
objMap, sessionState, state, lang, jerr := getFunc(single(accountId), single(id), ctx)
|
||||
if jerr != nil {
|
||||
return req.jmapError(accountId, jerr, sessionState, lang)
|
||||
}
|
||||
|
||||
if objs, ok := objMap[accountId]; ok {
|
||||
n := len(objs.GetList())
|
||||
switch n {
|
||||
case 0:
|
||||
return req.notFound(accountId, sessionState, ContactResponseObjectType, state)
|
||||
case 1:
|
||||
return req.respond(accountId, objs.GetList()[0], sessionState, ContactResponseObjectType, state, lang)
|
||||
default:
|
||||
logger.Error().Msgf("found %d %s matching '%s' instead of 1", n, o.responseType, id)
|
||||
return req.errorS(accountId, req.apiError(&ErrorMultipleIdMatches), sessionState)
|
||||
}
|
||||
} else {
|
||||
return req.notFound(accountId, sessionState, ContactResponseObjectType, state)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func changes[T jmap.Foo, CHANGE jmap.Change, CHANGES jmap.Changes[T]](
|
||||
o ObjectType[T, CHANGE, CHANGES],
|
||||
w http.ResponseWriter, r *http.Request,
|
||||
g *Groupware,
|
||||
changesFunc func(accountId string, sinceState jmap.State, maxChanges uint, ctx jmap.Context) (CHANGES, jmap.SessionState, jmap.State, jmap.Language, jmap.Error),
|
||||
) {
|
||||
g.respond(w, r, func(req Request) Response {
|
||||
ok, accountId, resp := o.accountFunc(&req)
|
||||
if !ok {
|
||||
return resp
|
||||
}
|
||||
l := req.logger.With().Str(accountId, log.SafeString(accountId))
|
||||
|
||||
maxChanges, ok, err := req.parseUIntParam(QueryParamMaxChanges, 0)
|
||||
if err != nil {
|
||||
return req.error(accountId, err)
|
||||
}
|
||||
if ok {
|
||||
l = l.Uint(QueryParamMaxChanges, maxChanges)
|
||||
}
|
||||
|
||||
sinceState := jmap.State(req.OptHeaderParamDoc(HeaderParamSince, "Optionally specifies the state identifier from which on to list changes"))
|
||||
if sinceState != "" {
|
||||
l = l.Str(HeaderParamSince, log.SafeString(string(sinceState)))
|
||||
}
|
||||
|
||||
logger := log.From(l)
|
||||
ctx := req.ctx.WithLogger(logger)
|
||||
changes, sessionState, state, lang, jerr := changesFunc(accountId, sinceState, maxChanges, ctx)
|
||||
if jerr != nil {
|
||||
return req.jmapError(accountId, jerr, sessionState, lang)
|
||||
}
|
||||
|
||||
return req.respond(accountId, changes, sessionState, o.responseType, state, lang)
|
||||
})
|
||||
}
|
||||
|
||||
func delete[T jmap.Foo, CHANGE jmap.Change, CHANGES jmap.Changes[T]]( //NOSONAR
|
||||
o ObjectType[T, CHANGE, CHANGES],
|
||||
w http.ResponseWriter, r *http.Request,
|
||||
g *Groupware,
|
||||
deleteFunc func(accountId string, ids []string, ctx jmap.Context) (map[string]jmap.SetError, jmap.SessionState, jmap.State, jmap.Language, jmap.Error),
|
||||
) {
|
||||
g.respond(w, r, func(req Request) Response {
|
||||
ok, accountId, resp := o.accountFunc(&req)
|
||||
if !ok {
|
||||
return resp
|
||||
}
|
||||
l := req.logger.With().Str(accountId, log.SafeString(accountId))
|
||||
id, err := req.PathParamDoc(o.uriParamName, "The unique identifier of the object to delete")
|
||||
if err != nil {
|
||||
return req.error(accountId, err)
|
||||
}
|
||||
l.Str(o.uriParamName, log.SafeString(id))
|
||||
|
||||
logger := log.From(l)
|
||||
ctx := req.ctx.WithLogger(logger)
|
||||
setErrors, sessionState, state, lang, jerr := deleteFunc(accountId, single(id), ctx)
|
||||
if jerr != nil {
|
||||
return req.jmapError(accountId, jerr, sessionState, lang)
|
||||
}
|
||||
|
||||
for _, e := range setErrors {
|
||||
desc := e.Description
|
||||
if desc != "" {
|
||||
return req.error(accountId, apiError(
|
||||
req.errorId(),
|
||||
o.failedToDeleteError,
|
||||
withDetail(e.Description),
|
||||
))
|
||||
} else {
|
||||
return req.error(accountId, apiError(
|
||||
req.errorId(),
|
||||
o.failedToDeleteError,
|
||||
))
|
||||
}
|
||||
}
|
||||
return req.noContent(accountId, sessionState, o.responseType, state)
|
||||
})
|
||||
}
|
||||
|
||||
func deleteMany[T jmap.Foo, CHANGE jmap.Change, CHANGES jmap.Changes[T]]( //NOSONAR
|
||||
o ObjectType[T, CHANGE, CHANGES],
|
||||
w http.ResponseWriter, r *http.Request,
|
||||
g *Groupware,
|
||||
deleteFunc func(accountId string, ids []string, ctx jmap.Context) (map[string]jmap.SetError, jmap.SessionState, jmap.State, jmap.Language, jmap.Error),
|
||||
) {
|
||||
g.respond(w, r, func(req Request) Response {
|
||||
ok, accountId, resp := o.accountFunc(&req)
|
||||
if !ok {
|
||||
return resp
|
||||
}
|
||||
l := req.logger.With().Str(accountId, log.SafeString(accountId))
|
||||
|
||||
ids := []string{}
|
||||
if o.uriParamName != "" {
|
||||
pathId, err := req.PathParam(o.uriParamName)
|
||||
if err != nil {
|
||||
return req.error(accountId, err)
|
||||
}
|
||||
if ok {
|
||||
ids = append(ids, pathId)
|
||||
}
|
||||
}
|
||||
{
|
||||
queryIds, ok, err := req.parseOptStringListParam(QueryParamId)
|
||||
if err != nil {
|
||||
return req.error(accountId, err)
|
||||
}
|
||||
if ok {
|
||||
ids = append(ids, queryIds...)
|
||||
}
|
||||
}
|
||||
{
|
||||
var bodyIds []string
|
||||
err := req.body(&bodyIds)
|
||||
if err != nil {
|
||||
return req.error(accountId, err)
|
||||
}
|
||||
ids = append(ids, bodyIds...)
|
||||
}
|
||||
switch len(ids) {
|
||||
case 0:
|
||||
return req.noop(accountId)
|
||||
case 1:
|
||||
l.Str("id", log.SafeString(ids[0]))
|
||||
default:
|
||||
l.Array("ids", log.SafeStringArray(ids))
|
||||
}
|
||||
|
||||
logger := log.From(l)
|
||||
ctx := req.ctx.WithLogger(logger)
|
||||
setErrors, sessionState, state, lang, jerr := deleteFunc(accountId, ids, ctx)
|
||||
if jerr != nil {
|
||||
return req.jmapError(accountId, jerr, sessionState, lang)
|
||||
}
|
||||
|
||||
for _, e := range setErrors {
|
||||
desc := e.Description
|
||||
if desc != "" {
|
||||
return req.error(accountId, apiError(
|
||||
req.errorId(),
|
||||
o.failedToDeleteError,
|
||||
withDetail(e.Description),
|
||||
))
|
||||
} else {
|
||||
return req.error(accountId, apiError(
|
||||
req.errorId(),
|
||||
o.failedToDeleteError,
|
||||
))
|
||||
}
|
||||
}
|
||||
return req.noContent(accountId, sessionState, o.responseType, state)
|
||||
})
|
||||
}
|
||||
|
||||
func modify[T jmap.Foo, CHANGE jmap.Change, CHANGES jmap.Changes[T]](
|
||||
o ObjectType[T, CHANGE, CHANGES],
|
||||
w http.ResponseWriter, r *http.Request,
|
||||
g *Groupware,
|
||||
updateFunc func(accountId string, id string, change CHANGE, ctx jmap.Context) (T, jmap.SessionState, jmap.State, jmap.Language, jmap.Error),
|
||||
) {
|
||||
g.respond(w, r, func(req Request) Response {
|
||||
ok, accountId, resp := o.accountFunc(&req)
|
||||
if !ok {
|
||||
return resp
|
||||
}
|
||||
l := req.logger.With().Str(accountId, log.SafeString(accountId))
|
||||
id, err := req.PathParamDoc(o.uriParamName, "The unique identifier of the object to modify")
|
||||
if err != nil {
|
||||
return req.error(accountId, err)
|
||||
}
|
||||
l.Str(o.uriParamName, log.SafeString(id))
|
||||
|
||||
var change CHANGE
|
||||
err = req.body(&change)
|
||||
if err != nil {
|
||||
return req.error(accountId, err)
|
||||
}
|
||||
|
||||
logger := log.From(l)
|
||||
ctx := req.ctx.WithLogger(logger)
|
||||
updated, sessionState, state, lang, jerr := updateFunc(accountId, id, change, ctx)
|
||||
if jerr != nil {
|
||||
return req.jmapError(accountId, jerr, sessionState, lang)
|
||||
}
|
||||
return req.respond(accountId, updated, sessionState, o.responseType, state, lang)
|
||||
})
|
||||
}
|
||||
20
services/groupware/pkg/groupware/tools.go
Normal file
20
services/groupware/pkg/groupware/tools.go
Normal file
@@ -0,0 +1,20 @@
|
||||
package groupware
|
||||
|
||||
import (
|
||||
"iter"
|
||||
"strings"
|
||||
|
||||
"github.com/opencloud-eu/opencloud/pkg/structs"
|
||||
)
|
||||
|
||||
func ptr[T any](t T) *T {
|
||||
return &t
|
||||
}
|
||||
|
||||
func trimmed(it iter.Seq[string]) iter.Seq[string] {
|
||||
return structs.MapSeq(it, strings.TrimSpace)
|
||||
}
|
||||
|
||||
func notEmptyString(it iter.Seq[string]) iter.Seq[string] {
|
||||
return structs.FilterSeq(it, func(s string) bool { return s != "" })
|
||||
}
|
||||
Reference in New Issue
Block a user