diff --git a/pkg/jmap/api.go b/pkg/jmap/api.go index 7b049e1f68..e53db6c758 100644 --- a/pkg/jmap/api.go +++ b/pkg/jmap/api.go @@ -8,8 +8,23 @@ import ( "github.com/opencloud-eu/opencloud/pkg/log" ) +type Context struct { + Session *Session + Context context.Context + Logger *log.Logger + AcceptLanguage string +} + +func (c Context) WithLogger(newLogger *log.Logger) Context { + return Context{Session: c.Session, Context: c.Context, AcceptLanguage: c.AcceptLanguage, Logger: newLogger} +} + +func (c Context) WithContext(newContext context.Context) Context { + return Context{Session: c.Session, Context: newContext, AcceptLanguage: c.AcceptLanguage, Logger: c.Logger} +} + type ApiClient interface { - Command(ctx context.Context, logger *log.Logger, session *Session, request Request, acceptLanguage string) ([]byte, Language, Error) + Command(request Request, ctx Context) ([]byte, Language, Error) io.Closer } @@ -33,8 +48,8 @@ type SessionClient interface { } type BlobClient interface { - UploadBinary(ctx context.Context, logger *log.Logger, session *Session, uploadUrl string, endpoint string, contentType string, acceptLanguage string, content io.Reader) (UploadedBlob, Language, Error) - DownloadBinary(ctx context.Context, logger *log.Logger, session *Session, downloadUrl string, endpoint string, acceptLanguage string) (*BlobDownload, Language, Error) + UploadBinary(uploadUrl string, endpoint string, contentType string, content io.Reader, ctx Context) (UploadedBlob, Language, Error) + DownloadBinary(downloadUrl string, endpoint string, ctx Context) (*BlobDownload, Language, Error) io.Closer } diff --git a/pkg/jmap/api_addressbook.go b/pkg/jmap/api_addressbook.go index bcd55ecaa4..299b5ab944 100644 --- a/pkg/jmap/api_addressbook.go +++ b/pkg/jmap/api_addressbook.go @@ -1,21 +1,16 @@ package jmap -import ( - "context" - - "github.com/opencloud-eu/opencloud/pkg/log" -) - var NS_ADDRESSBOOKS = ns(JmapContacts) -func (j *Client) GetAddressbooks(accountId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, ids []string) (AddressBookGetResponse, SessionState, State, Language, Error) { +func (j *Client) GetAddressbooks(accountId string, ids []string, ctx Context) (AddressBookGetResponse, SessionState, State, Language, Error) { return get(j, "GetAddressbooks", NS_ADDRESSBOOKS, func(accountId string, ids []string) AddressBookGetCommand { return AddressBookGetCommand{AccountId: accountId, Ids: ids} }, AddressBookGetResponse{}, identity1, - accountId, session, ctx, logger, acceptLanguage, ids, + accountId, ids, + ctx, ) } @@ -23,10 +18,10 @@ type AddressBookChanges = ChangesTemplate[AddressBook] // Retrieve Address Book changes since a given state. // @apidoc addressbook,changes -func (j *Client) GetAddressbookChanges(accountId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, sinceState State, maxChanges uint) (AddressBookChanges, SessionState, State, Language, Error) { +func (j *Client) GetAddressbookChanges(accountId string, sinceState State, maxChanges uint, ctx Context) (AddressBookChanges, SessionState, State, Language, Error) { return changesA(j, "GetAddressbookChanges", NS_ADDRESSBOOKS, func() AddressBookChangesCommand { - return AddressBookChangesCommand{AccountId: accountId, SinceState: sinceState, MaxChanges: posUIntPtr(maxChanges)} + return AddressBookChangesCommand{AccountId: accountId, SinceState: sinceState, MaxChanges: uintPtr(maxChanges)} }, AddressBookChangesResponse{}, AddressBookGetResponse{}, @@ -50,11 +45,11 @@ func (j *Client) GetAddressbookChanges(accountId string, session *Session, ctx c Destroyed: destroyed, } }, - session, ctx, logger, acceptLanguage, + ctx, ) } -func (j *Client) CreateAddressBook(accountId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, addressbook AddressBookChange) (*AddressBook, SessionState, State, Language, Error) { +func (j *Client) CreateAddressBook(accountId string, addressbook AddressBookChange, ctx Context) (*AddressBook, SessionState, State, Language, Error) { return create(j, "CreateAddressBook", NS_ADDRESSBOOKS, func(accountId string, create map[string]AddressBookChange) AddressBookSetCommand { return AddressBookSetCommand{AccountId: accountId, Create: create} @@ -68,21 +63,23 @@ func (j *Client) CreateAddressBook(accountId string, session *Session, ctx conte func(resp AddressBookGetResponse) []AddressBook { return resp.List }, - accountId, session, ctx, logger, acceptLanguage, addressbook, + accountId, addressbook, + ctx, ) } -func (j *Client) DeleteAddressBook(accountId string, destroyIds []string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string) (map[string]SetError, SessionState, State, Language, Error) { +func (j *Client) DeleteAddressBook(accountId string, destroyIds []string, ctx Context) (map[string]SetError, SessionState, State, Language, Error) { return destroy(j, "DeleteAddressBook", NS_ADDRESSBOOKS, func(accountId string, destroy []string) AddressBookSetCommand { return AddressBookSetCommand{AccountId: accountId, Destroy: destroy} }, AddressBookSetResponse{}, - accountId, destroyIds, session, ctx, logger, acceptLanguage, + accountId, destroyIds, + ctx, ) } -func (j *Client) UpdateAddressBook(accountId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, id string, changes AddressBookChange) (AddressBook, SessionState, State, Language, Error) { +func (j *Client) UpdateAddressBook(accountId string, id string, changes AddressBookChange, ctx Context) (AddressBook, SessionState, State, Language, Error) { return update(j, "UpdateAddressBook", NS_ADDRESSBOOKS, func(update map[string]PatchObject) AddressBookSetCommand { return AddressBookSetCommand{AccountId: accountId, Update: update} @@ -92,6 +89,7 @@ func (j *Client) UpdateAddressBook(accountId string, session *Session, ctx conte }, func(resp AddressBookSetResponse) map[string]SetError { return resp.NotUpdated }, func(resp AddressBookGetResponse) AddressBook { return resp.List[0] }, - id, changes, session, ctx, logger, acceptLanguage, + id, changes, + ctx, ) } diff --git a/pkg/jmap/api_blob.go b/pkg/jmap/api_blob.go index 27b16cdb49..4024686422 100644 --- a/pkg/jmap/api_blob.go +++ b/pkg/jmap/api_blob.go @@ -1,7 +1,6 @@ package jmap import ( - "context" "encoding/base64" "io" "strings" @@ -9,28 +8,31 @@ import ( "github.com/opencloud-eu/opencloud/pkg/log" ) -func (j *Client) GetBlobMetadata(accountId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, id string) (*Blob, SessionState, State, Language, Error) { - cmd, jerr := j.request(session, logger, ns(JmapBlob), - invocation(BlobGetCommand{ - AccountId: accountId, - Ids: []string{id}, - // add BlobPropertyData to retrieve the data - Properties: []string{BlobPropertyDigestSha256, BlobPropertyDigestSha512, BlobPropertySize}, - }, "0"), +var NS_BLOB = ns(JmapBlob) + +func (j *Client) GetBlobMetadata(accountId string, id string, ctx Context) (*Blob, SessionState, State, Language, Error) { + get := BlobGetCommand{ + AccountId: accountId, + Ids: []string{id}, + // add BlobPropertyData to retrieve the data + Properties: []string{BlobPropertyDigestSha256, BlobPropertyDigestSha512, BlobPropertySize}, + } + cmd, jerr := j.request(ctx, NS_BLOB, + invocation(get, "0"), ) if jerr != nil { return nil, "", "", "", jerr } - return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (*Blob, State, Error) { + return command(j, ctx, cmd, func(body *Response) (*Blob, State, Error) { var response BlobGetResponse - err := retrieveResponseMatchParameters(logger, body, CommandBlobGet, "0", &response) + err := retrieveGet(ctx, body, get, "0", &response) if err != nil { return nil, "", err } if len(response.List) != 1 { - logger.Error().Msgf("%T.List has %v entries instead of 1", response, len(response.List)) + 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] @@ -45,26 +47,28 @@ type UploadedBlobWithHash struct { Sha512 string `json:"sha:512,omitempty"` } -func (j *Client) UploadBlobStream(accountId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, contentType string, body io.Reader) (UploadedBlob, Language, Error) { - logger = log.From(logger.With().Str(logEndpoint, session.UploadEndpoint)) +func (j *Client) UploadBlobStream(accountId string, contentType string, body io.Reader, ctx Context) (UploadedBlob, Language, Error) { + logger := log.From(ctx.Logger.With().Str(logEndpoint, ctx.Session.UploadEndpoint)) + ctx = ctx.WithLogger(logger) // TODO(pbleser-oc) use a library for proper URL template parsing - uploadUrl := strings.ReplaceAll(session.UploadUrlTemplate, "{accountId}", accountId) - return j.blob.UploadBinary(ctx, logger, session, uploadUrl, session.UploadEndpoint, contentType, acceptLanguage, body) + uploadUrl := strings.ReplaceAll(ctx.Session.UploadUrlTemplate, "{accountId}", accountId) + return j.blob.UploadBinary(uploadUrl, ctx.Session.UploadEndpoint, contentType, body, ctx) } -func (j *Client) DownloadBlobStream(accountId string, blobId string, name string, typ string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string) (*BlobDownload, Language, Error) { //NOSONAR - logger = log.From(logger.With().Str(logEndpoint, session.DownloadEndpoint)) +func (j *Client) DownloadBlobStream(accountId string, blobId string, name string, typ string, ctx Context) (*BlobDownload, Language, Error) { //NOSONAR + logger := log.From(ctx.Logger.With().Str(logEndpoint, ctx.Session.DownloadEndpoint)) + ctx = ctx.WithLogger(logger) // TODO(pbleser-oc) use a library for proper URL template parsing - downloadUrl := session.DownloadUrlTemplate + downloadUrl := ctx.Session.DownloadUrlTemplate downloadUrl = strings.ReplaceAll(downloadUrl, "{accountId}", accountId) downloadUrl = strings.ReplaceAll(downloadUrl, "{blobId}", blobId) downloadUrl = strings.ReplaceAll(downloadUrl, "{name}", name) downloadUrl = strings.ReplaceAll(downloadUrl, "{type}", typ) logger = log.From(logger.With().Str(logDownloadUrl, downloadUrl).Str(logBlobId, blobId)) - return j.blob.DownloadBinary(ctx, logger, session, downloadUrl, session.DownloadEndpoint, acceptLanguage) + return j.blob.DownloadBinary(downloadUrl, ctx.Session.DownloadEndpoint, ctx) } -func (j *Client) UploadBlob(accountId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, data []byte, contentType string) (UploadedBlobWithHash, SessionState, State, Language, Error) { +func (j *Client) UploadBlob(accountId string, data []byte, contentType string, ctx Context) (UploadedBlobWithHash, SessionState, State, Language, Error) { encoded := base64.StdEncoding.EncodeToString(data) upload := BlobUploadCommand{ @@ -89,7 +93,7 @@ func (j *Client) UploadBlob(accountId string, session *Session, ctx context.Cont Properties: []string{BlobPropertyDigestSha512}, } - cmd, jerr := j.request(session, logger, ns(JmapBlob), + cmd, jerr := j.request(ctx, ns(JmapBlob), invocation(upload, "0"), invocation(getHash, "1"), ) @@ -97,31 +101,31 @@ func (j *Client) UploadBlob(accountId string, session *Session, ctx context.Cont return UploadedBlobWithHash{}, "", "", "", jerr } - return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (UploadedBlobWithHash, State, Error) { + return command(j, ctx, cmd, func(body *Response) (UploadedBlobWithHash, State, Error) { var uploadResponse BlobUploadResponse - err := retrieveResponseMatchParameters(logger, body, upload.GetCommand(), "0", &uploadResponse) + err := retrieveUpload(ctx, body, upload, "0", &uploadResponse) if err != nil { return UploadedBlobWithHash{}, "", err } var getResponse BlobGetResponse - err = retrieveResponseMatchParameters(logger, body, getHash.GetCommand(), "1", &getResponse) + err = retrieveGet(ctx, body, getHash, "1", &getResponse) if err != nil { return UploadedBlobWithHash{}, "", err } if len(uploadResponse.Created) != 1 { - logger.Error().Msgf("%T.Created has %v entries instead of 1", uploadResponse, len(uploadResponse.Created)) + ctx.Logger.Error().Msgf("%T.Created has %v entries instead of 1", uploadResponse, len(uploadResponse.Created)) return UploadedBlobWithHash{}, "", jmapError(err, JmapErrorInvalidJmapResponsePayload) } upload, ok := uploadResponse.Created["0"] if !ok { - logger.Error().Msgf("%T.Created has no item '0'", uploadResponse) + ctx.Logger.Error().Msgf("%T.Created has no item '0'", uploadResponse) return UploadedBlobWithHash{}, "", jmapError(err, JmapErrorInvalidJmapResponsePayload) } if len(getResponse.List) != 1 { - logger.Error().Msgf("%T.List has %v entries instead of 1", getResponse, len(getResponse.List)) + ctx.Logger.Error().Msgf("%T.List has %v entries instead of 1", getResponse, len(getResponse.List)) return UploadedBlobWithHash{}, "", jmapError(err, JmapErrorInvalidJmapResponsePayload) } get := getResponse.List[0] diff --git a/pkg/jmap/api_bootstrap.go b/pkg/jmap/api_bootstrap.go index 2101863af6..27767ca309 100644 --- a/pkg/jmap/api_bootstrap.go +++ b/pkg/jmap/api_bootstrap.go @@ -1,9 +1,6 @@ package jmap import ( - "context" - - "github.com/opencloud-eu/opencloud/pkg/log" "github.com/opencloud-eu/opencloud/pkg/structs" ) @@ -14,10 +11,11 @@ type AccountBootstrapResult struct { var NS_MAIL_QUOTA = ns(JmapMail, JmapQuota) -func (j *Client) GetBootstrap(accountIds []string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string) (map[string]AccountBootstrapResult, SessionState, State, Language, Error) { //NOSONAR +func (j *Client) GetBootstrap(accountIds []string, ctx Context) (map[string]AccountBootstrapResult, SessionState, State, Language, Error) { //NOSONAR uniqueAccountIds := structs.Uniq(accountIds) - logger = j.logger("GetBootstrap", session, logger) + logger := j.logger("GetBootstrap", ctx) + ctx = ctx.WithLogger(logger) calls := make([]Invocation, len(uniqueAccountIds)*2) for i, accountId := range uniqueAccountIds { @@ -25,18 +23,18 @@ func (j *Client) GetBootstrap(accountIds []string, session *Session, ctx context calls[i*2+1] = invocation(QuotaGetCommand{AccountId: accountId}, mcid(accountId, "Q")) } - cmd, err := j.request(session, logger, NS_MAIL_QUOTA, calls...) + cmd, err := j.request(ctx, NS_MAIL_QUOTA, calls...) if err != nil { return nil, "", "", "", err } - return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (map[string]AccountBootstrapResult, State, Error) { + return command(j, ctx, cmd, func(body *Response) (map[string]AccountBootstrapResult, State, Error) { identityPerAccount := map[string][]Identity{} quotaPerAccount := map[string][]Quota{} identityStatesPerAccount := map[string]State{} quotaStatesPerAccount := map[string]State{} for _, accountId := range uniqueAccountIds { var identityResponse IdentityGetResponse - err = retrieveResponseMatchParameters(logger, body, CommandIdentityGet, mcid(accountId, "I"), &identityResponse) + err = retrieveResponseMatchParameters(ctx, body, CommandIdentityGet, mcid(accountId, "I"), &identityResponse) if err != nil { return nil, "", err } else { @@ -45,7 +43,7 @@ func (j *Client) GetBootstrap(accountIds []string, session *Session, ctx context } var quotaResponse QuotaGetResponse - err = retrieveResponseMatchParameters(logger, body, CommandQuotaGet, mcid(accountId, "Q"), "aResponse) + err = retrieveResponseMatchParameters(ctx, body, CommandQuotaGet, mcid(accountId, "Q"), "aResponse) if err != nil { return nil, "", err } else { diff --git a/pkg/jmap/api_calendar.go b/pkg/jmap/api_calendar.go index bb5e49c539..6f13cf7683 100644 --- a/pkg/jmap/api_calendar.go +++ b/pkg/jmap/api_calendar.go @@ -1,27 +1,21 @@ package jmap -import ( - "context" - - "github.com/opencloud-eu/opencloud/pkg/log" - "github.com/opencloud-eu/opencloud/pkg/structs" -) - var NS_CALENDARS = ns(JmapCalendars) -func (j *Client) ParseICalendarBlob(accountId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, blobIds []string) (CalendarEventParseResponse, SessionState, State, Language, Error) { - logger = j.logger("ParseICalendarBlob", session, logger) +func (j *Client) ParseICalendarBlob(accountId string, blobIds []string, ctx Context) (CalendarEventParseResponse, SessionState, State, Language, Error) { + logger := j.logger("ParseICalendarBlob", ctx) - cmd, err := j.request(session, logger, NS_CALENDARS, - invocation(CalendarEventParseCommand{AccountId: accountId, BlobIds: blobIds}, "0"), + parse := CalendarEventParseCommand{AccountId: accountId, BlobIds: blobIds} + cmd, err := j.request(ctx.WithLogger(logger), NS_CALENDARS, + invocation(parse, "0"), ) if err != nil { return CalendarEventParseResponse{}, "", "", "", err } - return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (CalendarEventParseResponse, State, Error) { + return command(j, ctx, cmd, func(body *Response) (CalendarEventParseResponse, State, Error) { var response CalendarEventParseResponse - err = retrieveResponseMatchParameters(logger, body, CommandCalendarEventParse, "0", &response) + err = retrieveParse(ctx, body, parse, "0", &response) if err != nil { return CalendarEventParseResponse{}, "", err } @@ -29,14 +23,15 @@ func (j *Client) ParseICalendarBlob(accountId string, session *Session, ctx cont }) } -func (j *Client) GetCalendars(accountId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, ids []string) (CalendarGetResponse, SessionState, State, Language, Error) { +func (j *Client) GetCalendars(accountId string, ids []string, ctx Context) (CalendarGetResponse, SessionState, State, Language, Error) { return get(j, "GetCalendars", NS_CALENDARS, func(accountId string, ids []string) CalendarGetCommand { return CalendarGetCommand{AccountId: accountId, Ids: ids} }, CalendarGetResponse{}, identity1, - accountId, session, ctx, logger, acceptLanguage, ids, + accountId, ids, + ctx, ) } @@ -44,10 +39,10 @@ type CalendarChanges = ChangesTemplate[Calendar] // Retrieve Calendar changes since a given state. // @apidoc calendar,changes -func (j *Client) GetCalendarChanges(accountId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, sinceState State, maxChanges uint) (CalendarChanges, SessionState, State, Language, Error) { +func (j *Client) GetCalendarChanges(accountId string, sinceState State, maxChanges uint, ctx Context) (CalendarChanges, SessionState, State, Language, Error) { return changes(j, "GetCalendarChanges", NS_CALENDARS, func() CalendarChangesCommand { - return CalendarChangesCommand{AccountId: accountId, SinceState: sinceState, MaxChanges: posUIntPtr(maxChanges)} + return CalendarChangesCommand{AccountId: accountId, SinceState: sinceState, MaxChanges: uintPtr(maxChanges)} }, CalendarChangesResponse{}, func(path string, rof string) CalendarGetRefCommand { @@ -71,78 +66,55 @@ func (j *Client) GetCalendarChanges(accountId string, session *Session, ctx cont Destroyed: destroyed, } }, - session, ctx, logger, acceptLanguage, + ctx, ) } -func (j *Client) QueryCalendarEvents(accountIds []string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, //NOSONAR +type CalendarEventSearchResults SearchResultsTemplate[CalendarEvent] + +var _ SearchResults[CalendarEvent] = CalendarEventSearchResults{} + +func (r CalendarEventSearchResults) GetResults() []CalendarEvent { return r.Results } +func (r CalendarEventSearchResults) GetCanCalculateChanges() bool { return r.CanCalculateChanges } +func (r CalendarEventSearchResults) GetPosition() uint { return r.Position } +func (r CalendarEventSearchResults) GetLimit() uint { return r.Limit } +func (r CalendarEventSearchResults) GetTotal() *uint { return r.Total } + +func (j *Client) QueryCalendarEvents(accountIds []string, //NOSONAR filter CalendarEventFilterElement, sortBy []CalendarEventComparator, - position uint, limit uint) (map[string][]CalendarEvent, SessionState, State, Language, Error) { - logger = j.logger("QueryCalendarEvents", session, logger) - - uniqueAccountIds := structs.Uniq(accountIds) - - if sortBy == nil { - sortBy = []CalendarEventComparator{{Property: CalendarEventPropertyStart, IsAscending: false}} - } - - invocations := make([]Invocation, len(uniqueAccountIds)*2) - for i, accountId := range uniqueAccountIds { - query := CalendarEventQueryCommand{ - AccountId: accountId, - Filter: filter, - Sort: sortBy, - } - if limit > 0 { - query.Limit = limit - } - if position > 0 { - query.Position = position - } - invocations[i*2+0] = invocation(query, mcid(accountId, "0")) - invocations[i*2+1] = invocation(CalendarEventGetRefCommand{ - AccountId: accountId, - IdsRef: &ResultReference{ - Name: CommandCalendarEventQuery, - Path: "/ids/*", - ResultOf: mcid(accountId, "0"), - }, - // Properties: CalendarEventProperties, // to also retrieve UTCStart and UTCEnd - }, mcid(accountId, "1")) - } - cmd, err := j.request(session, logger, NS_CALENDARS, invocations...) - if err != nil { - return nil, "", "", "", err - } - - return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (map[string][]CalendarEvent, State, Error) { - resp := map[string][]CalendarEvent{} - stateByAccountId := map[string]State{} - for _, accountId := range uniqueAccountIds { - var response CalendarEventGetResponse - err = retrieveResponseMatchParameters(logger, body, CommandCalendarEventGet, mcid(accountId, "1"), &response) - if err != nil { - return nil, "", err + position int, limit uint, calculateTotal bool, + ctx Context) (map[string]CalendarEventSearchResults, SessionState, State, Language, Error) { + return queryN(j, "QueryCalendarEvents", NS_CALENDARS, + []CalendarEventComparator{{Property: CalendarEventPropertyStart, IsAscending: false}}, + func(accountId string, filter CalendarEventFilterElement, sortBy []CalendarEventComparator, position int, limit uint) CalendarEventQueryCommand { + return CalendarEventQueryCommand{AccountId: accountId, Filter: filter, Sort: sortBy, Position: position, Limit: uintPtr(limit), CalculateTotal: calculateTotal} + }, + func(accountId string, cmd Command, path string, rof string) CalendarEventGetRefCommand { + return CalendarEventGetRefCommand{AccountId: accountId, IdsRef: &ResultReference{Name: cmd, Path: path, ResultOf: rof}} + }, + func(query CalendarEventQueryResponse, get CalendarEventGetResponse) CalendarEventSearchResults { + return CalendarEventSearchResults{ + Results: get.List, + CanCalculateChanges: query.CanCalculateChanges, + Position: query.Position, + Total: uintPtrIf(query.Total, calculateTotal), + Limit: query.Limit, } - if len(response.NotFound) > 0 { - // TODO what to do when there are not-found calendarevents here? potentially nothing, they could have been deleted between query and get? - } - resp[accountId] = response.List - stateByAccountId[accountId] = response.State - } - return resp, squashState(stateByAccountId), nil - }) + }, + accountIds, + filter, sortBy, limit, position, ctx, + ) } type CalendarEventChanges = ChangesTemplate[CalendarEvent] // Retrieve the changes in Calendar Events since a given State. // @api:tags event,changes -func (j *Client) GetCalendarEventChanges(accountId string, session *Session, ctx context.Context, logger *log.Logger, - acceptLanguage string, sinceState State, maxChanges uint) (CalendarEventChanges, SessionState, State, Language, Error) { +func (j *Client) GetCalendarEventChanges(accountId string, sinceState State, maxChanges uint, + ctx Context) (CalendarEventChanges, SessionState, State, Language, Error) { return changes(j, "GetCalendarEventChanges", NS_CALENDARS, func() CalendarEventChangesCommand { - return CalendarEventChangesCommand{AccountId: accountId, SinceState: sinceState, MaxChanges: posUIntPtr(maxChanges)} + return CalendarEventChangesCommand{AccountId: accountId, SinceState: sinceState, MaxChanges: uintPtr(maxChanges)} }, CalendarEventChangesResponse{}, func(path string, rof string) CalendarEventGetRefCommand { @@ -166,11 +138,11 @@ func (j *Client) GetCalendarEventChanges(accountId string, session *Session, ctx Destroyed: destroyed, } }, - session, ctx, logger, acceptLanguage, + ctx, ) } -func (j *Client) CreateCalendarEvent(accountId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, event CalendarEvent) (*CalendarEvent, SessionState, State, Language, Error) { +func (j *Client) CreateCalendarEvent(accountId string, event CalendarEvent, ctx Context) (*CalendarEvent, SessionState, State, Language, Error) { return create(j, "CreateCalendarEvent", NS_CALENDARS, func(accountId string, create map[string]CalendarEvent) CalendarEventSetCommand { return CalendarEventSetCommand{AccountId: accountId, Create: create} @@ -184,19 +156,23 @@ func (j *Client) CreateCalendarEvent(accountId string, session *Session, ctx con func(resp CalendarEventGetResponse) []CalendarEvent { return resp.List }, - accountId, session, ctx, logger, acceptLanguage, event) + accountId, event, + ctx, + ) } -func (j *Client) DeleteCalendarEvent(accountId string, destroyIds []string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string) (map[string]SetError, SessionState, State, Language, Error) { +func (j *Client) DeleteCalendarEvent(accountId string, destroyIds []string, ctx Context) (map[string]SetError, SessionState, State, Language, Error) { return destroy(j, "DeleteCalendarEvent", NS_CALENDARS, func(accountId string, destroy []string) CalendarEventSetCommand { return CalendarEventSetCommand{AccountId: accountId, Destroy: destroy} }, CalendarEventSetResponse{}, - accountId, destroyIds, session, ctx, logger, acceptLanguage) + accountId, destroyIds, + ctx, + ) } -func (j *Client) CreateCalendar(accountId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, calendar CalendarChange) (*Calendar, SessionState, State, Language, Error) { +func (j *Client) CreateCalendar(accountId string, calendar CalendarChange, ctx Context) (*Calendar, SessionState, State, Language, Error) { return create(j, "CreateCalendar", NS_CALENDARS, func(accountId string, create map[string]CalendarChange) CalendarSetCommand { return CalendarSetCommand{AccountId: accountId, Create: create} @@ -210,21 +186,23 @@ func (j *Client) CreateCalendar(accountId string, session *Session, ctx context. func(resp CalendarGetResponse) []Calendar { return resp.List }, - accountId, session, ctx, logger, acceptLanguage, calendar, + accountId, calendar, + ctx, ) } -func (j *Client) DeleteCalendar(accountId string, destroyIds []string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string) (map[string]SetError, SessionState, State, Language, Error) { +func (j *Client) DeleteCalendar(accountId string, destroyIds []string, ctx Context) (map[string]SetError, SessionState, State, Language, Error) { return destroy(j, "DeleteCalendar", NS_CALENDARS, func(accountId string, destroy []string) CalendarSetCommand { return CalendarSetCommand{AccountId: accountId, Destroy: destroy} }, CalendarSetResponse{}, - accountId, destroyIds, session, ctx, logger, acceptLanguage, + accountId, destroyIds, + ctx, ) } -func (j *Client) UpdateCalendar(accountId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, id string, changes CalendarChange) (Calendar, SessionState, State, Language, Error) { +func (j *Client) UpdateCalendar(accountId string, id string, changes CalendarChange, ctx Context) (Calendar, SessionState, State, Language, Error) { return update(j, "UpdateCalendar", NS_CALENDARS, func(update map[string]PatchObject) CalendarSetCommand { return CalendarSetCommand{AccountId: accountId, Update: update} @@ -234,6 +212,7 @@ func (j *Client) UpdateCalendar(accountId string, session *Session, ctx context. }, func(resp CalendarSetResponse) map[string]SetError { return resp.NotUpdated }, func(resp CalendarGetResponse) Calendar { return resp.List[0] }, - id, changes, session, ctx, logger, acceptLanguage, + id, changes, + ctx, ) } diff --git a/pkg/jmap/api_changes.go b/pkg/jmap/api_changes.go index 234b692141..db2d715038 100644 --- a/pkg/jmap/api_changes.go +++ b/pkg/jmap/api_changes.go @@ -1,8 +1,6 @@ package jmap import ( - "context" - "github.com/opencloud-eu/opencloud/pkg/log" "github.com/rs/zerolog" ) @@ -76,49 +74,50 @@ 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, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, stateMap StateMap, maxChanges uint) (Changes, SessionState, State, Language, Error) { //NOSONAR - logger = log.From(j.logger("GetChanges", session, logger).With().Object("state", stateMap).Uint("maxChanges", maxChanges)) +func (j *Client) GetChanges(accountId string, stateMap StateMap, maxChanges uint, ctx Context) (Changes, SessionState, State, Language, Error) { //NOSONAR + logger := log.From(j.logger("GetChanges", ctx).With().Object("state", stateMap).Uint("maxChanges", maxChanges)) + ctx = ctx.WithLogger(logger) methodCalls := []Invocation{} if stateMap.Mailboxes != nil { - methodCalls = append(methodCalls, invocation(MailboxChangesCommand{AccountId: accountId, SinceState: *stateMap.Mailboxes, MaxChanges: posUIntPtr(maxChanges)}, "mailboxes")) + methodCalls = append(methodCalls, invocation(MailboxChangesCommand{AccountId: accountId, SinceState: *stateMap.Mailboxes, MaxChanges: uintPtr(maxChanges)}, "mailboxes")) } if stateMap.Emails != nil { - methodCalls = append(methodCalls, invocation(EmailChangesCommand{AccountId: accountId, SinceState: *stateMap.Emails, MaxChanges: posUIntPtr(maxChanges)}, "emails")) + methodCalls = append(methodCalls, invocation(EmailChangesCommand{AccountId: accountId, SinceState: *stateMap.Emails, MaxChanges: uintPtr(maxChanges)}, "emails")) } if stateMap.Calendars != nil { - methodCalls = append(methodCalls, invocation(CalendarChangesCommand{AccountId: accountId, SinceState: *stateMap.Calendars, MaxChanges: posUIntPtr(maxChanges)}, "calendars")) + methodCalls = append(methodCalls, invocation(CalendarChangesCommand{AccountId: accountId, SinceState: *stateMap.Calendars, MaxChanges: uintPtr(maxChanges)}, "calendars")) } if stateMap.Events != nil { - methodCalls = append(methodCalls, invocation(CalendarEventChangesCommand{AccountId: accountId, SinceState: *stateMap.Events, MaxChanges: posUIntPtr(maxChanges)}, "events")) + methodCalls = append(methodCalls, invocation(CalendarEventChangesCommand{AccountId: accountId, SinceState: *stateMap.Events, MaxChanges: uintPtr(maxChanges)}, "events")) } if stateMap.Addressbooks != nil { - methodCalls = append(methodCalls, invocation(AddressBookChangesCommand{AccountId: accountId, SinceState: *stateMap.Addressbooks, MaxChanges: posUIntPtr(maxChanges)}, "addressbooks")) + methodCalls = append(methodCalls, invocation(AddressBookChangesCommand{AccountId: accountId, SinceState: *stateMap.Addressbooks, MaxChanges: uintPtr(maxChanges)}, "addressbooks")) } if stateMap.Contacts != nil { - methodCalls = append(methodCalls, invocation(ContactCardChangesCommand{AccountId: accountId, SinceState: *stateMap.Contacts, MaxChanges: posUIntPtr(maxChanges)}, "contacts")) + methodCalls = append(methodCalls, invocation(ContactCardChangesCommand{AccountId: accountId, SinceState: *stateMap.Contacts, MaxChanges: uintPtr(maxChanges)}, "contacts")) } if stateMap.Identities != nil { - methodCalls = append(methodCalls, invocation(IdentityChangesCommand{AccountId: accountId, SinceState: *stateMap.Identities, MaxChanges: posUIntPtr(maxChanges)}, "identities")) + methodCalls = append(methodCalls, invocation(IdentityChangesCommand{AccountId: accountId, SinceState: *stateMap.Identities, MaxChanges: uintPtr(maxChanges)}, "identities")) } if stateMap.EmailSubmissions != nil { - methodCalls = append(methodCalls, invocation(EmailSubmissionChangesCommand{AccountId: accountId, SinceState: *stateMap.EmailSubmissions, MaxChanges: posUIntPtr(maxChanges)}, "submissions")) + methodCalls = append(methodCalls, invocation(EmailSubmissionChangesCommand{AccountId: accountId, SinceState: *stateMap.EmailSubmissions, MaxChanges: uintPtr(maxChanges)}, "submissions")) } // if stateMap.Quotas != nil { methodCalls = append(methodCalls, invocation(CommandQuotaChanges, QuotaChangesCommand{AccountId: accountId, SinceState: *stateMap.Quotas, MaxChanges: posUIntPtr(maxChanges)}, "quotas")) } - cmd, err := j.request(session, logger, NS_CHANGES, methodCalls...) + cmd, err := j.request(ctx, NS_CHANGES, methodCalls...) if err != nil { return Changes{}, "", "", "", err } - return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (Changes, State, Error) { + return command(j, ctx, cmd, func(body *Response) (Changes, State, Error) { changes := Changes{ MaxChanges: maxChanges, } states := map[string]State{} var mailboxes MailboxChangesResponse - if ok, err := tryRetrieveResponseMatchParameters(logger, body, CommandMailboxChanges, "mailboxes", &mailboxes); err != nil { + if ok, err := tryRetrieveResponseMatchParameters(ctx, body, CommandMailboxChanges, "mailboxes", &mailboxes); err != nil { return Changes{}, "", err } else if ok { changes.Mailboxes = &mailboxes @@ -126,7 +125,7 @@ func (j *Client) GetChanges(accountId string, session *Session, ctx context.Cont } var emails EmailChangesResponse - if ok, err := tryRetrieveResponseMatchParameters(logger, body, CommandEmailChanges, "emails", &emails); err != nil { + if ok, err := tryRetrieveResponseMatchParameters(ctx, body, CommandEmailChanges, "emails", &emails); err != nil { return Changes{}, "", err } else if ok { changes.Emails = &emails @@ -134,7 +133,7 @@ func (j *Client) GetChanges(accountId string, session *Session, ctx context.Cont } var calendars CalendarChangesResponse - if ok, err := tryRetrieveResponseMatchParameters(logger, body, CommandCalendarChanges, "calendars", &calendars); err != nil { + if ok, err := tryRetrieveResponseMatchParameters(ctx, body, CommandCalendarChanges, "calendars", &calendars); err != nil { return Changes{}, "", err } else if ok { changes.Calendars = &calendars @@ -142,7 +141,7 @@ func (j *Client) GetChanges(accountId string, session *Session, ctx context.Cont } var events CalendarEventChangesResponse - if ok, err := tryRetrieveResponseMatchParameters(logger, body, CommandCalendarEventChanges, "events", &events); err != nil { + if ok, err := tryRetrieveResponseMatchParameters(ctx, body, CommandCalendarEventChanges, "events", &events); err != nil { return Changes{}, "", err } else if ok { changes.Events = &events @@ -150,7 +149,7 @@ func (j *Client) GetChanges(accountId string, session *Session, ctx context.Cont } var addressbooks AddressBookChangesResponse - if ok, err := tryRetrieveResponseMatchParameters(logger, body, CommandAddressBookChanges, "addressbooks", &addressbooks); err != nil { + if ok, err := tryRetrieveResponseMatchParameters(ctx, body, CommandAddressBookChanges, "addressbooks", &addressbooks); err != nil { return Changes{}, "", err } else if ok { changes.Addressbooks = &addressbooks @@ -158,7 +157,7 @@ func (j *Client) GetChanges(accountId string, session *Session, ctx context.Cont } var contacts ContactCardChangesResponse - if ok, err := tryRetrieveResponseMatchParameters(logger, body, CommandContactCardChanges, "contacts", &contacts); err != nil { + if ok, err := tryRetrieveResponseMatchParameters(ctx, body, CommandContactCardChanges, "contacts", &contacts); err != nil { return Changes{}, "", err } else if ok { changes.Contacts = &contacts @@ -166,7 +165,7 @@ func (j *Client) GetChanges(accountId string, session *Session, ctx context.Cont } var identities IdentityChangesResponse - if ok, err := tryRetrieveResponseMatchParameters(logger, body, CommandIdentityChanges, "identities", &identities); err != nil { + if ok, err := tryRetrieveResponseMatchParameters(ctx, body, CommandIdentityChanges, "identities", &identities); err != nil { return Changes{}, "", err } else if ok { changes.Identities = &identities @@ -174,7 +173,7 @@ func (j *Client) GetChanges(accountId string, session *Session, ctx context.Cont } var submissions EmailSubmissionChangesResponse - if ok, err := tryRetrieveResponseMatchParameters(logger, body, CommandEmailSubmissionChanges, "submissions", &submissions); err != nil { + if ok, err := tryRetrieveResponseMatchParameters(ctx, body, CommandEmailSubmissionChanges, "submissions", &submissions); err != nil { return Changes{}, "", err } else if ok { changes.EmailSubmissions = &submissions diff --git a/pkg/jmap/api_contact.go b/pkg/jmap/api_contact.go index bc658f3f33..1ae7e2f9e8 100644 --- a/pkg/jmap/api_contact.go +++ b/pkg/jmap/api_contact.go @@ -1,24 +1,16 @@ package jmap -import ( - "context" - "fmt" - - "github.com/opencloud-eu/opencloud/pkg/log" - "github.com/opencloud-eu/opencloud/pkg/structs" -) - var NS_CONTACTS = ns(JmapContacts) -func (j *Client) GetContactCards(accountId string, session *Session, ctx context.Context, logger *log.Logger, - acceptLanguage string, contactIds []string) (ContactCardGetResponse, SessionState, State, Language, Error) { +func (j *Client) GetContactCards(accountId string, contactIds []string, ctx Context) (ContactCardGetResponse, SessionState, State, Language, Error) { return get(j, "GetContactCards", NS_CONTACTS, func(accountId string, ids []string) ContactCardGetCommand { return ContactCardGetCommand{AccountId: accountId, Ids: contactIds} }, ContactCardGetResponse{}, identity1, - accountId, session, ctx, logger, acceptLanguage, contactIds, + accountId, contactIds, + ctx, ) } @@ -26,11 +18,10 @@ type ContactCardChanges = ChangesTemplate[ContactCard] // Retrieve the changes in Contact Cards since a given State. // @api:tags contact,changes -func (j *Client) GetContactCardChanges(accountId string, session *Session, ctx context.Context, logger *log.Logger, - acceptLanguage string, sinceState State, maxChanges uint) (ContactCardChanges, SessionState, State, Language, Error) { +func (j *Client) GetContactCardChanges(accountId string, sinceState State, maxChanges uint, ctx Context) (ContactCardChanges, SessionState, State, Language, Error) { return changes(j, "GetContactCardChanges", NS_CONTACTS, func() ContactCardChangesCommand { - return ContactCardChangesCommand{AccountId: accountId, SinceState: sinceState, MaxChanges: posUIntPtr(maxChanges)} + return ContactCardChangesCommand{AccountId: accountId, SinceState: sinceState, MaxChanges: uintPtr(maxChanges)} }, ContactCardChangesResponse{}, func(path string, rof string) ContactCardGetRefCommand { @@ -54,128 +45,72 @@ func (j *Client) GetContactCardChanges(accountId string, session *Session, ctx c Destroyed: destroyed, } }, - session, ctx, logger, acceptLanguage, + ctx, ) } -func (j *Client) QueryContactCards(accountIds []string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, //NOSONAR +type ContactCardSearchResults SearchResultsTemplate[ContactCard] + +var _ SearchResults[ContactCard] = ContactCardSearchResults{} + +func (r ContactCardSearchResults) GetResults() []ContactCard { return r.Results } +func (r ContactCardSearchResults) GetCanCalculateChanges() bool { return r.CanCalculateChanges } +func (r ContactCardSearchResults) GetPosition() uint { return r.Position } +func (r ContactCardSearchResults) GetLimit() uint { return r.Limit } +func (r ContactCardSearchResults) GetTotal() *uint { return r.Total } + +func (j *Client) QueryContactCards(accountIds []string, filter ContactCardFilterElement, sortBy []ContactCardComparator, - position uint, limit uint) (map[string][]ContactCard, SessionState, State, Language, Error) { - logger = j.logger("QueryContactCards", session, logger) - - uniqueAccountIds := structs.Uniq(accountIds) - - if sortBy == nil { - sortBy = []ContactCardComparator{{Property: ContactCardPropertyUpdated, IsAscending: false}} - } - - invocations := make([]Invocation, len(uniqueAccountIds)*2) - for i, accountId := range uniqueAccountIds { - query := ContactCardQueryCommand{ - AccountId: accountId, - Filter: filter, - Sort: sortBy, - } - if limit > 0 { - query.Limit = limit - } - if position > 0 { - query.Position = position - } - invocations[i*2+0] = invocation(query, mcid(accountId, "0")) - invocations[i*2+1] = invocation(ContactCardGetRefCommand{ - AccountId: accountId, - IdsRef: &ResultReference{ - Name: CommandContactCardQuery, - Path: "/ids/*", - ResultOf: mcid(accountId, "0"), - }, - }, mcid(accountId, "1")) - } - cmd, err := j.request(session, logger, NS_CONTACTS, invocations...) - if err != nil { - return nil, "", "", "", err - } - - return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (map[string][]ContactCard, State, Error) { - resp := map[string][]ContactCard{} - stateByAccountId := map[string]State{} - for _, accountId := range uniqueAccountIds { - var response ContactCardGetResponse - err = retrieveResponseMatchParameters(logger, body, CommandContactCardGet, mcid(accountId, "1"), &response) - if err != nil { - return nil, "", err + position int, limit uint, calculateTotal bool, + ctx Context) (map[string]ContactCardSearchResults, SessionState, State, Language, Error) { + return queryN(j, "QueryContactCards", NS_CONTACTS, + []ContactCardComparator{{Property: ContactCardPropertyUpdated, IsAscending: false}}, + func(accountId string, filter ContactCardFilterElement, sortBy []ContactCardComparator, position int, limit uint) ContactCardQueryCommand { + return ContactCardQueryCommand{AccountId: accountId, Filter: filter, Sort: sortBy, Position: position, Limit: uintPtr(limit), CalculateTotal: calculateTotal} + }, + func(accountId string, cmd Command, path string, rof string) ContactCardGetRefCommand { + return ContactCardGetRefCommand{AccountId: accountId, IdsRef: &ResultReference{Name: cmd, Path: path, ResultOf: rof}} + }, + func(query ContactCardQueryResponse, get ContactCardGetResponse) ContactCardSearchResults { + return ContactCardSearchResults{ + Results: get.List, + CanCalculateChanges: query.CanCalculateChanges, + Position: query.Position, + Total: uintPtrIf(query.Total, calculateTotal), + Limit: query.Limit, } - if len(response.NotFound) > 0 { - // TODO what to do when there are not-found emails here? potentially nothing, they could have been deleted between query and get? - } - resp[accountId] = response.List - stateByAccountId[accountId] = response.State - } - return resp, squashState(stateByAccountId), nil - }) -} - -func (j *Client) CreateContactCard(accountId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, create ContactCard) (*ContactCard, SessionState, State, Language, Error) { - logger = j.logger("CreateContactCard", session, logger) - - cmd, err := j.request(session, logger, NS_CONTACTS, - invocation(ContactCardSetCommand{ - AccountId: accountId, - Create: map[string]ContactCard{ - "c": create, - }, - }, "0"), - invocation(ContactCardGetCommand{ - AccountId: accountId, - Ids: []string{"#c"}, - }, "1"), + }, + accountIds, + filter, sortBy, limit, position, ctx, ) - if err != nil { - return nil, "", "", "", err - } - - return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (*ContactCard, State, Error) { - var setResponse ContactCardSetResponse - err = retrieveResponseMatchParameters(logger, body, CommandContactCardSet, "0", &setResponse) - if err != nil { - return nil, "", err - } - - setErr, notok := setResponse.NotCreated["c"] - if notok { - logger.Error().Msgf("%T.NotCreated returned an error %v", setResponse, setErr) - return nil, "", setErrorError(setErr, EmailType) - } - - if created, ok := setResponse.Created["c"]; !ok || created == nil { - berr := fmt.Errorf("failed to find %s in %s response", ContactCardType, string(CommandContactCardSet)) - logger.Error().Err(berr) - return nil, "", jmapError(berr, JmapErrorInvalidJmapResponsePayload) - } - - var getResponse ContactCardGetResponse - err = retrieveResponseMatchParameters(logger, body, CommandContactCardGet, "1", &getResponse) - if err != nil { - return nil, "", err - } - - if len(getResponse.List) < 1 { - berr := fmt.Errorf("failed to find %s in %s response", ContactCardType, string(CommandContactCardSet)) - logger.Error().Err(berr) - return nil, "", jmapError(berr, JmapErrorInvalidJmapResponsePayload) - } - - return &getResponse.List[0], setResponse.NewState, nil - }) } -func (j *Client) DeleteContactCard(accountId string, destroyIds []string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string) (map[string]SetError, SessionState, State, Language, Error) { +func (j *Client) CreateContactCard(accountId string, contact ContactCard, ctx Context) (*ContactCard, SessionState, State, Language, Error) { + return create(j, "CreateContactCard", NS_CONTACTS, + func(accountId string, create map[string]ContactCard) ContactCardSetCommand { + return ContactCardSetCommand{AccountId: accountId, Create: create} + }, + func(accountId string, ids string) ContactCardGetCommand { + return ContactCardGetCommand{AccountId: accountId, Ids: []string{ids}} + }, + func(resp ContactCardSetResponse) map[string]*ContactCard { + return resp.Created + }, + func(resp ContactCardGetResponse) []ContactCard { + return resp.List + }, + accountId, contact, + ctx, + ) +} + +func (j *Client) DeleteContactCard(accountId string, destroyIds []string, ctx Context) (map[string]SetError, SessionState, State, Language, Error) { return destroy(j, "DeleteContactCard", NS_CONTACTS, func(accountId string, destroy []string) ContactCardSetCommand { return ContactCardSetCommand{AccountId: accountId, Destroy: destroy} }, ContactCardSetResponse{}, - accountId, destroyIds, session, ctx, logger, acceptLanguage, + accountId, destroyIds, + ctx, ) } diff --git a/pkg/jmap/api_email.go b/pkg/jmap/api_email.go index c7cda8fc82..d46da23d8f 100644 --- a/pkg/jmap/api_email.go +++ b/pkg/jmap/api_email.go @@ -1,7 +1,6 @@ package jmap import ( - "context" "encoding/base64" "fmt" "time" @@ -27,8 +26,11 @@ type getEmailsResult struct { } // Retrieve specific Emails by their id. -func (j *Client) GetEmails(accountId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, ids []string, fetchBodies bool, maxBodyValueBytes uint, markAsSeen bool, withThreads bool) ([]Email, []string, SessionState, State, Language, Error) { //NOSONAR - logger = j.logger("GetEmails", session, logger) +func (j *Client) GetEmails(accountId string, ids []string, //NOSONAR + fetchBodies bool, maxBodyValueBytes uint, markAsSeen bool, withThreads bool, + ctx Context) ([]Email, []string, SessionState, State, Language, Error) { + logger := j.logger("GetEmails", ctx) + ctx = ctx.WithLogger(logger) get := EmailGetCommand{AccountId: accountId, Ids: ids, FetchAllBodyValues: fetchBodies} if maxBodyValueBytes > 0 { @@ -57,14 +59,14 @@ func (j *Client) GetEmails(accountId string, session *Session, ctx context.Conte methodCalls = append(methodCalls, invocation(threads, "2")) } - cmd, err := j.request(session, logger, NS_MAIL, methodCalls...) + cmd, err := j.request(ctx, NS_MAIL, methodCalls...) if err != nil { return nil, nil, "", "", "", err } - result, sessionState, state, language, gwerr := command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (getEmailsResult, State, Error) { + result, sessionState, state, language, gwerr := command(j, ctx, cmd, func(body *Response) (getEmailsResult, State, Error) { if markAsSeen { var markResponse EmailSetResponse - err = retrieveResponseMatchParameters(logger, body, CommandEmailSet, "0", &markResponse) + err = retrieveResponseMatchParameters(ctx, body, CommandEmailSet, "0", &markResponse) if err != nil { return getEmailsResult{}, "", err } @@ -74,13 +76,13 @@ func (j *Client) GetEmails(accountId string, session *Session, ctx context.Conte } } var response EmailGetResponse - err = retrieveResponseMatchParameters(logger, body, CommandEmailGet, "1", &response) + err = retrieveResponseMatchParameters(ctx, body, CommandEmailGet, "1", &response) if err != nil { return getEmailsResult{}, "", err } if withThreads { var threads ThreadGetResponse - err = retrieveResponseMatchParameters(logger, body, CommandThreadGet, "2", &threads) + err = retrieveResponseMatchParameters(ctx, body, CommandThreadGet, "2", &threads) if err != nil { return getEmailsResult{}, "", err } @@ -91,17 +93,18 @@ func (j *Client) GetEmails(accountId string, session *Session, ctx context.Conte return result.emails, result.notFound, sessionState, state, language, gwerr } -func (j *Client) GetEmailBlobId(accountId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, id string) (string, SessionState, State, Language, Error) { - logger = j.logger("GetEmailBlobId", session, logger) +func (j *Client) GetEmailBlobId(accountId string, id string, ctx Context) (string, SessionState, State, Language, Error) { + logger := j.logger("GetEmailBlobId", ctx) + ctx = ctx.WithLogger(logger) get := EmailGetCommand{AccountId: accountId, Ids: []string{id}, FetchAllBodyValues: false, Properties: []string{"blobId"}} - cmd, err := j.request(session, logger, NS_MAIL, invocation(get, "0")) + cmd, err := j.request(ctx, NS_MAIL, invocation(get, "0")) if err != nil { return "", "", "", "", err } - return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (string, State, Error) { + return command(j, ctx, cmd, func(body *Response) (string, State, Error) { var response EmailGetResponse - err = retrieveResponseMatchParameters(logger, body, CommandEmailGet, "0", &response) + err = retrieveGet(ctx, body, get, "0", &response) if err != nil { return "", "", err } @@ -114,10 +117,13 @@ func (j *Client) GetEmailBlobId(accountId string, session *Session, ctx context. } // Retrieve all the Emails in a given Mailbox by its id. -func (j *Client) GetAllEmailsInMailbox(accountId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, mailboxId string, offset int, limit uint, collapseThreads bool, fetchBodies bool, maxBodyValueBytes uint, withThreads bool) (Emails, SessionState, State, Language, Error) { //NOSONAR - logger = j.loggerParams("GetAllEmailsInMailbox", session, logger, func(z zerolog.Context) zerolog.Context { +func (j *Client) GetAllEmailsInMailbox(accountId string, mailboxId string, //NOSONAR + offset int, limit uint, collapseThreads bool, fetchBodies bool, maxBodyValueBytes uint, withThreads bool, + ctx Context) (Emails, SessionState, State, Language, Error) { + logger := j.loggerParams("GetAllEmailsInMailbox", ctx, func(z zerolog.Context) zerolog.Context { return z.Bool(logFetchBodies, fetchBodies).Int(logOffset, offset).Uint(logLimit, limit) }) + ctx = ctx.WithLogger(logger) query := EmailQueryCommand{ AccountId: accountId, @@ -147,8 +153,9 @@ func (j *Client) GetAllEmailsInMailbox(accountId string, session *Session, ctx c invocation(get, "1"), } + threads := ThreadGetRefCommand{} if withThreads { - threads := ThreadGetRefCommand{ + threads = ThreadGetRefCommand{ AccountId: accountId, IdsRef: &ResultReference{ ResultOf: "1", @@ -159,19 +166,19 @@ func (j *Client) GetAllEmailsInMailbox(accountId string, session *Session, ctx c invocations = append(invocations, invocation(threads, "2")) } - cmd, err := j.request(session, logger, NS_MAIL, invocations...) + cmd, err := j.request(ctx, NS_MAIL, invocations...) if err != nil { return Emails{}, "", "", "", err } - return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (Emails, State, Error) { + return command(j, ctx, cmd, func(body *Response) (Emails, State, Error) { var queryResponse EmailQueryResponse - err = retrieveResponseMatchParameters(logger, body, CommandEmailQuery, "0", &queryResponse) + err = retrieveQuery(ctx, body, query, "0", &queryResponse) if err != nil { return Emails{}, "", err } var getResponse EmailGetResponse - err = retrieveResponseMatchParameters(logger, body, CommandEmailGet, "1", &getResponse) + err = retrieveGet(ctx, body, get, "1", &getResponse) if err != nil { logger.Error().Err(err).Send() return Emails{}, "", err @@ -179,7 +186,7 @@ func (j *Client) GetAllEmailsInMailbox(accountId string, session *Session, ctx c if withThreads { var thread ThreadGetResponse - err = retrieveResponseMatchParameters(logger, body, CommandThreadGet, "2", &thread) + err = retrieveGet(ctx, body, threads, "2", &thread) if err != nil { return Emails{}, "", err } @@ -206,10 +213,13 @@ type EmailChanges struct { // Retrieve the changes in Emails since a given State. // @api:tags email,changes -func (j *Client) GetEmailChanges(accountId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, sinceState State, fetchBodies bool, maxBodyValueBytes uint, maxChanges uint) (EmailChanges, SessionState, State, Language, Error) { //NOSONAR - logger = j.loggerParams("GetEmailChanges", session, logger, func(z zerolog.Context) zerolog.Context { +func (j *Client) GetEmailChanges(accountId string, + sinceState State, fetchBodies bool, maxBodyValueBytes uint, maxChanges uint, + ctx Context) (EmailChanges, SessionState, State, Language, Error) { //NOSONAR + logger := j.loggerParams("GetEmailChanges", ctx, func(z zerolog.Context) zerolog.Context { return z.Bool(logFetchBodies, fetchBodies).Str(logSinceState, string(sinceState)) }) + ctx = ctx.WithLogger(logger) changes := EmailChangesCommand{ AccountId: accountId, @@ -236,7 +246,7 @@ func (j *Client) GetEmailChanges(accountId string, session *Session, ctx context getUpdated.MaxBodyValueBytes = maxBodyValueBytes } - cmd, err := j.request(session, logger, NS_MAIL, + cmd, err := j.request(ctx, NS_MAIL, invocation(changes, "0"), invocation(getCreated, "1"), invocation(getUpdated, "2"), @@ -245,22 +255,22 @@ func (j *Client) GetEmailChanges(accountId string, session *Session, ctx context return EmailChanges{}, "", "", "", err } - return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (EmailChanges, State, Error) { + return command(j, ctx, cmd, func(body *Response) (EmailChanges, State, Error) { var changesResponse EmailChangesResponse - err = retrieveResponseMatchParameters(logger, body, CommandEmailChanges, "0", &changesResponse) + err = retrieveChanges(ctx, body, changes, "0", &changesResponse) if err != nil { return EmailChanges{}, "", err } var createdResponse EmailGetResponse - err = retrieveResponseMatchParameters(logger, body, CommandEmailGet, "1", &createdResponse) + err = retrieveGet(ctx, body, getCreated, "1", &createdResponse) if err != nil { logger.Error().Err(err).Send() return EmailChanges{}, "", err } var updatedResponse EmailGetResponse - err = retrieveResponseMatchParameters(logger, body, CommandEmailGet, "2", &updatedResponse) + err = retrieveGet(ctx, body, getUpdated, "2", &updatedResponse) if err != nil { logger.Error().Err(err).Send() return EmailChanges{}, "", err @@ -291,10 +301,13 @@ type EmailSnippetQueryResult struct { QueryState State `json:"queryState"` } -func (j *Client) QueryEmailSnippets(accountIds []string, filter EmailFilterElement, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, offset int, limit uint) (map[string]EmailSnippetQueryResult, SessionState, State, Language, Error) { //NOSONAR - logger = j.loggerParams("QueryEmailSnippets", session, logger, func(z zerolog.Context) zerolog.Context { +func (j *Client) QueryEmailSnippets(accountIds []string, //NOSONAR + filter EmailFilterElement, offset int, limit uint, + ctx Context) (map[string]EmailSnippetQueryResult, SessionState, State, Language, Error) { + logger := j.loggerParams("QueryEmailSnippets", ctx, func(z zerolog.Context) zerolog.Context { return z.Uint(logLimit, limit).Int(logOffset, offset) }) + ctx = ctx.WithLogger(logger) uniqueAccountIds := structs.Uniq(accountIds) invocations := make([]Invocation, len(uniqueAccountIds)*3) @@ -340,28 +353,28 @@ func (j *Client) QueryEmailSnippets(accountIds []string, filter EmailFilterEleme invocations[i*3+2] = invocation(snippet, mcid(accountId, "2")) } - cmd, err := j.request(session, logger, NS_MAIL, invocations...) + cmd, err := j.request(ctx, NS_MAIL, invocations...) if err != nil { return nil, "", "", "", err } - return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (map[string]EmailSnippetQueryResult, State, Error) { + return command(j, ctx, cmd, func(body *Response) (map[string]EmailSnippetQueryResult, State, Error) { results := make(map[string]EmailSnippetQueryResult, len(uniqueAccountIds)) for _, accountId := range uniqueAccountIds { var queryResponse EmailQueryResponse - err = retrieveResponseMatchParameters(logger, body, CommandEmailQuery, mcid(accountId, "0"), &queryResponse) + err = retrieveResponseMatchParameters(ctx, body, CommandEmailQuery, mcid(accountId, "0"), &queryResponse) if err != nil { return nil, "", err } var mailResponse EmailGetResponse - err = retrieveResponseMatchParameters(logger, body, CommandEmailGet, mcid(accountId, "1"), &mailResponse) + err = retrieveResponseMatchParameters(ctx, body, CommandEmailGet, mcid(accountId, "1"), &mailResponse) if err != nil { return nil, "", err } var snippetResponse SearchSnippetGetResponse - err = retrieveResponseMatchParameters(logger, body, CommandSearchSnippetGet, mcid(accountId, "2"), &snippetResponse) + err = retrieveResponseMatchParameters(ctx, body, CommandSearchSnippetGet, mcid(accountId, "2"), &snippetResponse) if err != nil { return nil, "", err } @@ -407,10 +420,13 @@ type EmailQueryResult struct { QueryState State `json:"queryState"` } -func (j *Client) QueryEmails(accountIds []string, filter EmailFilterElement, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, offset int, limit uint, fetchBodies bool, maxBodyValueBytes uint) (map[string]EmailQueryResult, SessionState, State, Language, Error) { //NOSONAR - logger = j.loggerParams("QueryEmails", session, logger, func(z zerolog.Context) zerolog.Context { +func (j *Client) QueryEmails(accountIds []string, + filter EmailFilterElement, offset int, limit uint, fetchBodies bool, maxBodyValueBytes uint, + ctx Context) (map[string]EmailQueryResult, SessionState, State, Language, Error) { //NOSONAR + logger := j.loggerParams("QueryEmails", ctx, func(z zerolog.Context) zerolog.Context { return z.Bool(logFetchBodies, fetchBodies) }) + ctx = ctx.WithLogger(logger) uniqueAccountIds := structs.Uniq(accountIds) invocations := make([]Invocation, len(uniqueAccountIds)*2) @@ -444,22 +460,22 @@ func (j *Client) QueryEmails(accountIds []string, filter EmailFilterElement, ses invocations[i*2+1] = invocation(mails, mcid(accountId, "1")) } - cmd, err := j.request(session, logger, NS_MAIL, invocations...) + cmd, err := j.request(ctx, NS_MAIL, invocations...) if err != nil { return nil, "", "", "", err } - return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (map[string]EmailQueryResult, State, Error) { + return command(j, ctx, cmd, func(body *Response) (map[string]EmailQueryResult, State, Error) { results := make(map[string]EmailQueryResult, len(uniqueAccountIds)) for _, accountId := range uniqueAccountIds { var queryResponse EmailQueryResponse - err = retrieveResponseMatchParameters(logger, body, CommandEmailQuery, mcid(accountId, "0"), &queryResponse) + err = retrieveResponseMatchParameters(ctx, body, CommandEmailQuery, mcid(accountId, "0"), &queryResponse) if err != nil { return nil, "", err } var emailsResponse EmailGetResponse - err = retrieveResponseMatchParameters(logger, body, CommandEmailGet, mcid(accountId, "1"), &emailsResponse) + err = retrieveResponseMatchParameters(ctx, body, CommandEmailGet, mcid(accountId, "1"), &emailsResponse) if err != nil { return nil, "", err } @@ -489,10 +505,13 @@ type EmailQueryWithSnippetsResult struct { QueryState State `json:"queryState"` } -func (j *Client) QueryEmailsWithSnippets(accountIds []string, filter EmailFilterElement, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, offset int, limit uint, fetchBodies bool, maxBodyValueBytes uint) (map[string]EmailQueryWithSnippetsResult, SessionState, State, Language, Error) { //NOSONAR - logger = j.loggerParams("QueryEmailsWithSnippets", session, logger, func(z zerolog.Context) zerolog.Context { +func (j *Client) QueryEmailsWithSnippets(accountIds []string, //NOSONAR + filter EmailFilterElement, offset int, limit uint, 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) }) + ctx = ctx.WithLogger(logger) uniqueAccountIds := structs.Uniq(accountIds) invocations := make([]Invocation, len(uniqueAccountIds)*3) @@ -536,28 +555,28 @@ func (j *Client) QueryEmailsWithSnippets(accountIds []string, filter EmailFilter invocations[i*3+2] = invocation(mails, mcid(accountId, "2")) } - cmd, err := j.request(session, logger, NS_MAIL, invocations...) + cmd, err := j.request(ctx, NS_MAIL, invocations...) if err != nil { return nil, "", "", "", err } - return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (map[string]EmailQueryWithSnippetsResult, State, Error) { + return command(j, ctx, cmd, func(body *Response) (map[string]EmailQueryWithSnippetsResult, State, Error) { result := make(map[string]EmailQueryWithSnippetsResult, len(uniqueAccountIds)) for _, accountId := range uniqueAccountIds { var queryResponse EmailQueryResponse - err = retrieveResponseMatchParameters(logger, body, CommandEmailQuery, mcid(accountId, "0"), &queryResponse) + err = retrieveResponseMatchParameters(ctx, body, CommandEmailQuery, mcid(accountId, "0"), &queryResponse) if err != nil { return nil, "", err } var snippetResponse SearchSnippetGetResponse - err = retrieveResponseMatchParameters(logger, body, CommandSearchSnippetGet, mcid(accountId, "1"), &snippetResponse) + err = retrieveResponseMatchParameters(ctx, body, CommandSearchSnippetGet, mcid(accountId, "1"), &snippetResponse) if err != nil { return nil, "", err } var emailsResponse EmailGetResponse - err = retrieveResponseMatchParameters(logger, body, CommandEmailGet, mcid(accountId, "2"), &emailsResponse) + err = retrieveResponseMatchParameters(ctx, body, CommandEmailGet, mcid(accountId, "2"), &emailsResponse) if err != nil { return nil, "", err } @@ -602,7 +621,7 @@ type UploadedEmail struct { Sha512 string `json:"sha:512"` } -func (j *Client) ImportEmail(accountId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, data []byte) (UploadedEmail, SessionState, State, Language, Error) { +func (j *Client) ImportEmail(accountId string, data []byte, ctx Context) (UploadedEmail, SessionState, State, Language, Error) { encoded := base64.StdEncoding.EncodeToString(data) upload := BlobUploadCommand{ @@ -627,7 +646,7 @@ func (j *Client) ImportEmail(accountId string, session *Session, ctx context.Con Properties: []string{BlobPropertyDigestSha512}, } - cmd, err := j.request(session, logger, NS_MAIL, + cmd, err := j.request(ctx, NS_MAIL, invocation(upload, "0"), invocation(getHash, "1"), ) @@ -635,32 +654,32 @@ func (j *Client) ImportEmail(accountId string, session *Session, ctx context.Con return UploadedEmail{}, "", "", "", err } - return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (UploadedEmail, State, Error) { + return command(j, ctx, cmd, func(body *Response) (UploadedEmail, State, Error) { var uploadResponse BlobUploadResponse - err = retrieveResponseMatchParameters(logger, body, CommandBlobUpload, "0", &uploadResponse) + err = retrieveResponseMatchParameters(ctx, body, CommandBlobUpload, "0", &uploadResponse) if err != nil { return UploadedEmail{}, "", err } var getResponse BlobGetResponse - err = retrieveResponseMatchParameters(logger, body, CommandBlobGet, "1", &getResponse) + err = retrieveResponseMatchParameters(ctx, body, CommandBlobGet, "1", &getResponse) if err != nil { - logger.Error().Err(err).Send() + ctx.Logger.Error().Err(err).Send() return UploadedEmail{}, "", err } if len(uploadResponse.Created) != 1 { - logger.Error().Msgf("%T.Created has %v elements instead of 1", uploadResponse, len(uploadResponse.Created)) + ctx.Logger.Error().Msgf("%T.Created has %v elements instead of 1", uploadResponse, len(uploadResponse.Created)) return UploadedEmail{}, "", jmapError(err, JmapErrorInvalidJmapResponsePayload) } upload, ok := uploadResponse.Created["0"] if !ok { - logger.Error().Msgf("%T.Created has no element '0'", uploadResponse) + ctx.Logger.Error().Msgf("%T.Created has no element '0'", uploadResponse) return UploadedEmail{}, "", jmapError(err, JmapErrorInvalidJmapResponsePayload) } if len(getResponse.List) != 1 { - logger.Error().Msgf("%T.List has %v elements instead of 1", getResponse, len(getResponse.List)) + ctx.Logger.Error().Msgf("%T.List has %v elements instead of 1", getResponse, len(getResponse.List)) return UploadedEmail{}, "", jmapError(err, JmapErrorInvalidJmapResponsePayload) } get := getResponse.List[0] @@ -675,7 +694,7 @@ func (j *Client) ImportEmail(accountId string, session *Session, ctx context.Con } -func (j *Client) CreateEmail(accountId string, email EmailCreate, replaceId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string) (*Email, SessionState, State, Language, Error) { +func (j *Client) CreateEmail(accountId string, email EmailCreate, replaceId string, ctx Context) (*Email, SessionState, State, Language, Error) { set := EmailSetCommand{ AccountId: accountId, Create: map[string]EmailCreate{ @@ -686,16 +705,16 @@ func (j *Client) CreateEmail(accountId string, email EmailCreate, replaceId stri set.Destroy = []string{replaceId} } - cmd, err := j.request(session, logger, NS_MAIL, + cmd, err := j.request(ctx, NS_MAIL, invocation(set, "0"), ) if err != nil { return nil, "", "", "", err } - return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (*Email, State, Error) { + return command(j, ctx, cmd, func(body *Response) (*Email, State, Error) { var setResponse EmailSetResponse - err = retrieveResponseMatchParameters(logger, body, CommandEmailSet, "0", &setResponse) + err = retrieveResponseMatchParameters(ctx, body, CommandEmailSet, "0", &setResponse) if err != nil { return nil, "", err } @@ -707,14 +726,14 @@ func (j *Client) CreateEmail(accountId string, email EmailCreate, replaceId stri setErr, notok := setResponse.NotCreated["c"] if notok { - logger.Error().Msgf("%T.NotCreated returned an error %v", setResponse, setErr) + ctx.Logger.Error().Msgf("%T.NotCreated returned an error %v", setResponse, setErr) return nil, "", setErrorError(setErr, EmailType) } created, ok := setResponse.Created["c"] if !ok { berr := fmt.Errorf("failed to find %s in %s response", EmailType, string(CommandEmailSet)) - logger.Error().Err(berr) + ctx.Logger.Error().Err(berr) return nil, "", jmapError(berr, JmapErrorInvalidJmapResponsePayload) } @@ -730,20 +749,19 @@ 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, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string) (map[string]*Email, SessionState, State, Language, Error) { - cmd, err := j.request(session, logger, NS_MAIL, - invocation(EmailSetCommand{ - AccountId: accountId, - Update: updates, - }, "0"), - ) +func (j *Client) UpdateEmails(accountId string, updates map[string]EmailUpdate, ctx Context) (map[string]*Email, SessionState, State, Language, Error) { + set := EmailSetCommand{ + AccountId: accountId, + Update: updates, + } + cmd, err := j.request(ctx, NS_MAIL, invocation(set, "0")) if err != nil { return nil, "", "", "", err } - return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (map[string]*Email, State, Error) { + return command(j, ctx, cmd, func(body *Response) (map[string]*Email, State, Error) { var setResponse EmailSetResponse - err = retrieveResponseMatchParameters(logger, body, CommandEmailSet, "0", &setResponse) + err = retrieveSet(ctx, body, set, "0", &setResponse) if err != nil { return nil, "", err } @@ -757,20 +775,19 @@ func (j *Client) UpdateEmails(accountId string, updates map[string]EmailUpdate, }) } -func (j *Client) DeleteEmails(accountId string, destroy []string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string) (map[string]SetError, SessionState, State, Language, Error) { - cmd, err := j.request(session, logger, NS_MAIL, - invocation(EmailSetCommand{ - AccountId: accountId, - Destroy: destroy, - }, "0"), - ) +func (j *Client) DeleteEmails(accountId string, destroy []string, ctx Context) (map[string]SetError, SessionState, State, Language, Error) { + set := EmailSetCommand{ + AccountId: accountId, + Destroy: destroy, + } + cmd, err := j.request(ctx, NS_MAIL, invocation(set, "0")) if err != nil { return nil, "", "", "", err } - return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (map[string]SetError, State, Error) { + return command(j, ctx, cmd, func(body *Response) (map[string]SetError, State, Error) { var setResponse EmailSetResponse - err = retrieveResponseMatchParameters(logger, body, CommandEmailSet, "0", &setResponse) + err = retrieveSet(ctx, body, set, "0", &setResponse) if err != nil { return nil, "", err } @@ -807,8 +824,10 @@ type MoveMail struct { ToMailboxId string } -func (j *Client) SubmitEmail(accountId string, identityId string, emailId string, move *MoveMail, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string) (EmailSubmission, SessionState, State, Language, Error) { //NOSONAR - logger = j.logger("SubmitEmail", session, logger) +func (j *Client) SubmitEmail(accountId string, identityId string, emailId string, move *MoveMail, //NOSONAR + ctx Context) (EmailSubmission, SessionState, State, Language, Error) { + logger := j.logger("SubmitEmail", ctx) + ctx = ctx.WithLogger(logger) update := map[string]any{ EmailPropertyKeywords + "/" + JmapKeywordDraft: nil, // unmark as draft @@ -821,7 +840,7 @@ func (j *Client) SubmitEmail(accountId string, identityId string, emailId string id := "s0" - set := EmailSubmissionSetCommand{ + submit := EmailSubmissionSetCommand{ AccountId: accountId, Create: map[string]EmailSubmissionCreate{ id: { @@ -840,17 +859,17 @@ func (j *Client) SubmitEmail(accountId string, identityId string, emailId string Ids: []string{"#" + id}, } - cmd, err := j.request(session, logger, NS_MAIL_SUBMISSION, - invocation(set, "0"), + cmd, err := j.request(ctx, NS_MAIL_SUBMISSION, + invocation(submit, "0"), invocation(get, "1"), ) if err != nil { return EmailSubmission{}, "", "", "", err } - return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (EmailSubmission, State, Error) { + return command(j, ctx, cmd, func(body *Response) (EmailSubmission, State, Error) { var submissionResponse EmailSubmissionSetResponse - err = retrieveResponseMatchParameters(logger, body, CommandEmailSubmissionSet, "0", &submissionResponse) + err = retrieveSet(ctx, body, submit, "0", &submissionResponse) if err != nil { return EmailSubmission{}, "", err } @@ -866,14 +885,14 @@ func (j *Client) SubmitEmail(accountId string, identityId string, emailId string // The response to this MUST be returned after the EmailSubmission/set response." // from an example in the spec, it has the same tag as the EmailSubmission/set command ("0" in this case) var setResponse EmailSetResponse - err = retrieveResponseMatchParameters(logger, body, CommandEmailSet, "0", &setResponse) + err = retrieveResponseMatchParameters(ctx, body, CommandEmailSet, "0", &setResponse) if err != nil { return EmailSubmission{}, "", err } if len(setResponse.Updated) == 1 { var getResponse EmailSubmissionGetResponse - err = retrieveResponseMatchParameters(logger, body, CommandEmailSubmissionGet, "1", &getResponse) + err = retrieveGet(ctx, body, get, "1", &getResponse) if err != nil { return EmailSubmission{}, "", err } @@ -898,20 +917,22 @@ type emailSubmissionResult struct { notFound []string } -func (j *Client) GetEmailSubmissionStatus(accountId string, submissionIds []string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string) (map[string]EmailSubmission, []string, SessionState, State, Language, Error) { - logger = j.logger("GetEmailSubmissionStatus", session, logger) +func (j *Client) GetEmailSubmissionStatus(accountId string, submissionIds []string, ctx Context) (map[string]EmailSubmission, []string, SessionState, State, Language, Error) { + logger := j.logger("GetEmailSubmissionStatus", ctx) + ctx = ctx.WithLogger(logger) - cmd, err := j.request(session, logger, NS_MAIL_SUBMISSION, invocation(EmailSubmissionGetCommand{ + get := EmailSubmissionGetCommand{ AccountId: accountId, Ids: submissionIds, - }, "0")) + } + cmd, err := j.request(ctx, NS_MAIL_SUBMISSION, invocation(get, "0")) if err != nil { return nil, nil, "", "", "", err } - result, sessionState, state, lang, err := command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (emailSubmissionResult, State, Error) { + result, sessionState, state, lang, err := command(j, ctx, cmd, func(body *Response) (emailSubmissionResult, State, Error) { var response EmailSubmissionGetResponse - err = retrieveResponseMatchParameters(logger, body, CommandEmailSubmissionGet, "0", &response) + err = retrieveGet(ctx, body, get, "0", &response) if err != nil { return emailSubmissionResult{}, "", err } @@ -925,34 +946,41 @@ func (j *Client) GetEmailSubmissionStatus(accountId string, submissionIds []stri return result.submissions, result.notFound, sessionState, state, lang, err } -func (j *Client) EmailsInThread(accountId string, threadId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, fetchBodies bool, maxBodyValueBytes uint) ([]Email, SessionState, State, Language, Error) { //NOSONAR - logger = j.loggerParams("EmailsInThread", session, logger, func(z zerolog.Context) zerolog.Context { +func (j *Client) EmailsInThread(accountId string, threadId string, + fetchBodies bool, maxBodyValueBytes uint, + ctx Context) ([]Email, SessionState, State, Language, Error) { //NOSONAR + logger := j.loggerParams("EmailsInThread", ctx, func(z zerolog.Context) zerolog.Context { return z.Bool(logFetchBodies, fetchBodies).Str("threadId", log.SafeString(threadId)) }) + ctx = ctx.WithLogger(logger) - cmd, err := j.request(session, logger, NS_MAIL, - invocation(ThreadGetCommand{ - AccountId: accountId, - Ids: []string{threadId}, - }, "0"), - invocation(EmailGetRefCommand{ - AccountId: accountId, - IdsRef: &ResultReference{ - ResultOf: "0", - Name: CommandThreadGet, - Path: "/list/*/emailIds", - }, - FetchAllBodyValues: fetchBodies, - MaxBodyValueBytes: maxBodyValueBytes, - }, "1"), + thread := ThreadGetCommand{ + AccountId: accountId, + Ids: []string{threadId}, + } + + get := EmailGetRefCommand{ + AccountId: accountId, + IdsRef: &ResultReference{ + ResultOf: "0", + Name: CommandThreadGet, + Path: "/list/*/emailIds", + }, + FetchAllBodyValues: fetchBodies, + MaxBodyValueBytes: maxBodyValueBytes, + } + + cmd, err := j.request(ctx, NS_MAIL, + invocation(thread, "0"), + invocation(get, "1"), ) if err != nil { return nil, "", "", "", err } - return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) ([]Email, State, Error) { + return command(j, ctx, cmd, func(body *Response) ([]Email, State, Error) { var emailsResponse EmailGetResponse - err = retrieveResponseMatchParameters(logger, body, CommandEmailGet, "1", &emailsResponse) + err = retrieveGet(ctx, body, get, "1", &emailsResponse) if err != nil { return nil, "", err } @@ -987,8 +1015,11 @@ var EmailSummaryProperties = []string{ EmailPropertyPreview, } -func (j *Client) QueryEmailSummaries(accountIds []string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, filter EmailFilterElement, limit uint, withThreads bool) (map[string]EmailsSummary, SessionState, State, Language, Error) { //NOSONAR - logger = j.logger("QueryEmailSummaries", session, logger) +func (j *Client) QueryEmailSummaries(accountIds []string, //NOSONAR + filter EmailFilterElement, limit uint, withThreads bool, + ctx Context) (map[string]EmailsSummary, SessionState, State, Language, Error) { + logger := j.logger("QueryEmailSummaries", ctx) + ctx = ctx.WithLogger(logger) uniqueAccountIds := structs.Uniq(accountIds) @@ -1029,22 +1060,22 @@ func (j *Client) QueryEmailSummaries(accountIds []string, session *Session, ctx }, mcid(accountId, "2")) } } - cmd, err := j.request(session, logger, NS_MAIL, invocations...) + cmd, err := j.request(ctx, NS_MAIL, invocations...) if err != nil { return nil, "", "", "", err } - return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (map[string]EmailsSummary, State, Error) { + return command(j, ctx, cmd, func(body *Response) (map[string]EmailsSummary, State, Error) { resp := map[string]EmailsSummary{} for _, accountId := range uniqueAccountIds { var queryResponse EmailQueryResponse - err = retrieveResponseMatchParameters(logger, body, CommandEmailQuery, mcid(accountId, "0"), &queryResponse) + err = retrieveResponseMatchParameters(ctx, body, CommandEmailQuery, mcid(accountId, "0"), &queryResponse) if err != nil { return nil, "", err } var response EmailGetResponse - err = retrieveResponseMatchParameters(logger, body, CommandEmailGet, mcid(accountId, "1"), &response) + err = retrieveResponseMatchParameters(ctx, body, CommandEmailGet, mcid(accountId, "1"), &response) if err != nil { return nil, "", err } @@ -1053,7 +1084,7 @@ func (j *Client) QueryEmailSummaries(accountIds []string, session *Session, ctx } if withThreads { var thread ThreadGetResponse - err = retrieveResponseMatchParameters(logger, body, CommandThreadGet, mcid(accountId, "2"), &thread) + err = retrieveResponseMatchParameters(ctx, body, CommandThreadGet, mcid(accountId, "2"), &thread) if err != nil { return nil, "", err } @@ -1076,11 +1107,11 @@ type EmailSubmissionChanges = ChangesTemplate[EmailSubmission] // Retrieve the changes in Email Submissions since a given State. // @api:tags email,changes -func (j *Client) GetEmailSubmissionChanges(accountId string, session *Session, ctx context.Context, logger *log.Logger, - acceptLanguage string, sinceState State, maxChanges uint) (EmailSubmissionChanges, SessionState, State, Language, Error) { +func (j *Client) GetEmailSubmissionChanges(accountId string, sinceState State, maxChanges uint, + ctx Context) (EmailSubmissionChanges, SessionState, State, Language, Error) { return changes(j, "GetEmailSubmissionChanges", NS_MAIL_SUBMISSION, func() EmailSubmissionChangesCommand { - return EmailSubmissionChangesCommand{AccountId: accountId, SinceState: sinceState, MaxChanges: posUIntPtr(maxChanges)} + return EmailSubmissionChangesCommand{AccountId: accountId, SinceState: sinceState, MaxChanges: uintPtr(maxChanges)} }, EmailSubmissionChangesResponse{}, func(path string, rof string) EmailSubmissionGetRefCommand { @@ -1104,7 +1135,7 @@ func (j *Client) GetEmailSubmissionChanges(accountId string, session *Session, c Destroyed: destroyed, } }, - session, ctx, logger, acceptLanguage, + ctx, ) } diff --git a/pkg/jmap/api_identity.go b/pkg/jmap/api_identity.go index 0ce515658c..12df2eb1a3 100644 --- a/pkg/jmap/api_identity.go +++ b/pkg/jmap/api_identity.go @@ -1,36 +1,36 @@ package jmap import ( - "context" "strconv" - "github.com/opencloud-eu/opencloud/pkg/log" "github.com/opencloud-eu/opencloud/pkg/structs" ) var NS_IDENTITY = ns(JmapMail) -func (j *Client) GetAllIdentities(accountId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string) ([]Identity, SessionState, State, Language, Error) { +func (j *Client) GetAllIdentities(accountId string, ctx Context) ([]Identity, SessionState, State, Language, Error) { return getA(j, "GetAllIdentities", NS_IDENTITY, func(accountId string, ids []string) IdentityGetCommand { return IdentityGetCommand{AccountId: accountId} }, IdentityGetResponse{}, - accountId, session, ctx, logger, acceptLanguage, []string{}, + accountId, []string{}, + ctx, ) } -func (j *Client) GetIdentities(accountId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, identityIds []string) ([]Identity, SessionState, State, Language, Error) { +func (j *Client) GetIdentities(accountId string, identityIds []string, ctx Context) ([]Identity, SessionState, State, Language, Error) { return getA(j, "GetIdentities", NS_IDENTITY, func(accountId string, ids []string) IdentityGetCommand { return IdentityGetCommand{AccountId: accountId, Ids: ids} }, IdentityGetResponse{}, - accountId, session, ctx, logger, acceptLanguage, identityIds, + accountId, identityIds, + ctx, ) } -func (j *Client) GetIdentitiesForAllAccounts(accountIds []string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string) (map[string][]Identity, SessionState, State, Language, Error) { +func (j *Client) GetIdentitiesForAllAccounts(accountIds []string, ctx Context) (map[string][]Identity, SessionState, State, Language, Error) { return getN(j, "GetIdentitiesForAllAccounts", NS_IDENTITY, func(accountId string, ids []string) IdentityGetCommand { return IdentityGetCommand{AccountId: accountId} @@ -38,7 +38,8 @@ func (j *Client) GetIdentitiesForAllAccounts(accountIds []string, session *Sessi IdentityGetResponse{}, func(resp IdentityGetResponse) []Identity { return resp.List }, identity1, - accountIds, session, ctx, logger, acceptLanguage, []string{}, + accountIds, []string{}, + ctx, ) } @@ -48,10 +49,11 @@ type IdentitiesAndMailboxesGetResponse struct { Mailboxes []Mailbox `json:"mailboxes"` } -func (j *Client) GetIdentitiesAndMailboxes(mailboxAccountId string, accountIds []string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string) (IdentitiesAndMailboxesGetResponse, SessionState, State, Language, Error) { +func (j *Client) GetIdentitiesAndMailboxes(mailboxAccountId string, accountIds []string, ctx Context) (IdentitiesAndMailboxesGetResponse, SessionState, State, Language, Error) { uniqueAccountIds := structs.Uniq(accountIds) - logger = j.logger("GetIdentitiesAndMailboxes", session, logger) + logger := j.logger("GetIdentitiesAndMailboxes", ctx) + ctx = ctx.WithLogger(logger) calls := make([]Invocation, len(uniqueAccountIds)+1) calls[0] = invocation(MailboxGetCommand{AccountId: mailboxAccountId}, "0") @@ -59,17 +61,17 @@ func (j *Client) GetIdentitiesAndMailboxes(mailboxAccountId string, accountIds [ calls[i+1] = invocation(IdentityGetCommand{AccountId: accountId}, strconv.Itoa(i+1)) } - cmd, err := j.request(session, logger, NS_IDENTITY, calls...) + cmd, err := j.request(ctx, NS_IDENTITY, calls...) if err != nil { return IdentitiesAndMailboxesGetResponse{}, "", "", "", err } - return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (IdentitiesAndMailboxesGetResponse, State, Error) { + return command(j, ctx, cmd, func(body *Response) (IdentitiesAndMailboxesGetResponse, State, Error) { identities := make(map[string][]Identity, len(uniqueAccountIds)) stateByAccountId := make(map[string]State, len(uniqueAccountIds)) notFound := []string{} for i, accountId := range uniqueAccountIds { var response IdentityGetResponse - err = retrieveResponseMatchParameters(logger, body, CommandIdentityGet, strconv.Itoa(i+1), &response) + err = retrieveResponseMatchParameters(ctx, body, CommandIdentityGet, strconv.Itoa(i+1), &response) if err != nil { return IdentitiesAndMailboxesGetResponse{}, "", err } else { @@ -80,7 +82,7 @@ func (j *Client) GetIdentitiesAndMailboxes(mailboxAccountId string, accountIds [ } var mailboxResponse MailboxGetResponse - err = retrieveResponseMatchParameters(logger, body, CommandMailboxGet, "0", &mailboxResponse) + err = retrieveResponseMatchParameters(ctx, body, CommandMailboxGet, "0", &mailboxResponse) if err != nil { return IdentitiesAndMailboxesGetResponse{}, "", err } @@ -93,91 +95,60 @@ func (j *Client) GetIdentitiesAndMailboxes(mailboxAccountId string, accountIds [ }) } -func (j *Client) CreateIdentity(accountId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, identity Identity) (Identity, SessionState, State, Language, Error) { - logger = j.logger("CreateIdentity", session, logger) - cmd, err := j.request(session, logger, NS_IDENTITY, invocation(IdentitySetCommand{ - AccountId: accountId, - Create: map[string]Identity{ - "c": identity, +func (j *Client) CreateIdentity(accountId string, identity IdentityChange, ctx Context) (*Identity, SessionState, State, Language, Error) { + return create(j, "CreateIdentity", NS_IDENTITY, + func(accountId string, create map[string]IdentityChange) IdentitySetCommand { + return IdentitySetCommand{AccountId: accountId, Create: create} }, - }, "0")) - if err != nil { - return Identity{}, "", "", "", err - } - return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (Identity, State, Error) { - var response IdentitySetResponse - err = retrieveResponseMatchParameters(logger, body, CommandIdentitySet, "0", &response) - if err != nil { - return Identity{}, response.NewState, err - } - setErr, notok := response.NotCreated["c"] - if notok { - logger.Error().Msgf("%T.NotCreated returned an error %v", response, setErr) //NOSONAR - return Identity{}, "", setErrorError(setErr, IdentityType) - } - return response.Created["c"], response.NewState, nil - }) + func(accountId string, ids string) IdentityGetCommand { + return IdentityGetCommand{AccountId: accountId, Ids: []string{ids}} + }, + func(resp IdentitySetResponse) map[string]*Identity { + return resp.Created + }, + func(resp IdentityGetResponse) []Identity { + return resp.List + }, + accountId, identity, + ctx, + ) } -func (j *Client) UpdateIdentity(accountId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, identity Identity) (Identity, SessionState, State, Language, Error) { - logger = j.logger("UpdateIdentity", session, logger) - cmd, err := j.request(session, logger, NS_IDENTITY, invocation(IdentitySetCommand{ - AccountId: accountId, - Update: map[string]PatchObject{ - "c": identity.AsPatch(), +func (j *Client) UpdateIdentity(accountId string, id string, changes IdentityChange, ctx Context) (Identity, SessionState, State, Language, Error) { + return update(j, "UpdateIdentity", NS_IDENTITY, + func(update map[string]PatchObject) IdentitySetCommand { + return IdentitySetCommand{AccountId: accountId, Update: update} }, - }, "0")) - if err != nil { - return Identity{}, "", "", "", err - } - return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (Identity, State, Error) { - var response IdentitySetResponse - err = retrieveResponseMatchParameters(logger, body, CommandIdentitySet, "0", &response) - if err != nil { - return Identity{}, response.NewState, err - } - setErr, notok := response.NotCreated["c"] - if notok { - logger.Error().Msgf("%T.NotCreated returned an error %v", response, setErr) - return Identity{}, "", setErrorError(setErr, IdentityType) - } - return response.Created["c"], response.NewState, nil - }) + func(id string) IdentityGetCommand { + return IdentityGetCommand{AccountId: accountId, Ids: []string{id}} + }, + func(resp IdentitySetResponse) map[string]SetError { return resp.NotUpdated }, + func(resp IdentityGetResponse) Identity { return resp.List[0] }, + id, changes, + ctx, + ) } -func (j *Client) DeleteIdentity(accountId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, ids []string) ([]string, SessionState, State, Language, Error) { - logger = j.logger("DeleteIdentity", session, logger) - cmd, err := j.request(session, logger, NS_IDENTITY, invocation(IdentitySetCommand{ - AccountId: accountId, - Destroy: ids, - }, "0")) - if err != nil { - return nil, "", "", "", err - } - return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) ([]string, State, Error) { - var response IdentitySetResponse - err = retrieveResponseMatchParameters(logger, body, CommandIdentitySet, "0", &response) - if err != nil { - return nil, "", err - } - for _, setErr := range response.NotDestroyed { - // TODO only returning the first error here, we should probably aggregate them instead - logger.Error().Msgf("%T.NotCreated returned an error %v", response, setErr) - return nil, "", setErrorError(setErr, IdentityType) - } - return response.Destroyed, response.NewState, nil - }) +func (j *Client) DeleteIdentity(accountId string, destroyIds []string, ctx Context) (map[string]SetError, SessionState, State, Language, Error) { + return destroy(j, "DeleteIdentity", NS_IDENTITY, + func(accountId string, destroy []string) IdentitySetCommand { + return IdentitySetCommand{AccountId: accountId, Destroy: destroyIds} + }, + IdentitySetResponse{}, + accountId, destroyIds, + ctx, + ) } type IdentityChanges = ChangesTemplate[Identity] // Retrieve the changes in Email Identities since a given State. // @api:tags email,changes -func (j *Client) GetIdentityChanges(accountId string, session *Session, ctx context.Context, logger *log.Logger, - acceptLanguage string, sinceState State, maxChanges uint) (IdentityChanges, SessionState, State, Language, Error) { +func (j *Client) GetIdentityChanges(accountId string, sinceState State, maxChanges uint, + ctx Context) (IdentityChanges, SessionState, State, Language, Error) { return changes(j, "GetIdentityChanges", NS_IDENTITY, func() IdentityChangesCommand { - return IdentityChangesCommand{AccountId: accountId, SinceState: sinceState, MaxChanges: posUIntPtr(maxChanges)} + return IdentityChangesCommand{AccountId: accountId, SinceState: sinceState, MaxChanges: uintPtr(maxChanges)} }, IdentityChangesResponse{}, func(path string, rof string) IdentityGetRefCommand { @@ -201,6 +172,6 @@ func (j *Client) GetIdentityChanges(accountId string, session *Session, ctx cont Destroyed: destroyed, } }, - session, ctx, logger, acceptLanguage, + ctx, ) } diff --git a/pkg/jmap/api_mailbox.go b/pkg/jmap/api_mailbox.go index e96cbc1cea..7e845cf9c7 100644 --- a/pkg/jmap/api_mailbox.go +++ b/pkg/jmap/api_mailbox.go @@ -1,17 +1,14 @@ package jmap import ( - "context" - "fmt" "slices" - "github.com/opencloud-eu/opencloud/pkg/log" "github.com/opencloud-eu/opencloud/pkg/structs" ) var NS_MAILBOX = ns(JmapMail) -func (j *Client) GetMailbox(accountId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, ids []string) (MailboxGetResponse, SessionState, State, Language, Error) { +func (j *Client) GetMailbox(accountId string, ids []string, ctx Context) (MailboxGetResponse, SessionState, State, Language, Error) { /* return get(j, "GetMailbox", NS_MAILBOX, func(accountId string, ids []string) MailboxGetCommand { @@ -23,10 +20,10 @@ func (j *Client) GetMailbox(accountId string, session *Session, ctx context.Cont ) */ - return fget[Mailboxes](MAILBOX, j, "GetMailbox", accountId, ids, session, ctx, logger, acceptLanguage) + return fget[Mailboxes](MAILBOX, j, "GetMailbox", accountId, ids, ctx) } -func (j *Client) GetAllMailboxes(accountIds []string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string) (map[string][]Mailbox, SessionState, State, Language, Error) { +func (j *Client) GetAllMailboxes(accountIds []string, ctx Context) (map[string][]Mailbox, SessionState, State, Language, Error) { /* return getAN(j, "GetAllMailboxes", NS_MAILBOX, func(accountId string, ids []string) MailboxGetCommand { @@ -37,11 +34,12 @@ func (j *Client) GetAllMailboxes(accountIds []string, session *Session, ctx cont accountIds, session, ctx, logger, acceptLanguage, []string{}, ) */ - return fgetAN[Mailboxes](MAILBOX, j, "GetAllMailboxes", identity1, accountIds, []string{}, session, ctx, logger, acceptLanguage) + return fgetAN[Mailboxes](MAILBOX, j, "GetAllMailboxes", identity1, accountIds, []string{}, ctx) } -func (j *Client) SearchMailboxes(accountIds []string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, filter MailboxFilterElement) (map[string][]Mailbox, SessionState, State, Language, Error) { - logger = j.logger("SearchMailboxes", session, logger) +func (j *Client) SearchMailboxes(accountIds []string, filter MailboxFilterElement, ctx Context) (map[string][]Mailbox, SessionState, State, Language, Error) { + logger := j.logger("SearchMailboxes", ctx) + ctx = ctx.WithLogger(logger) uniqueAccountIds := structs.Uniq(accountIds) @@ -57,17 +55,17 @@ func (j *Client) SearchMailboxes(accountIds []string, session *Session, ctx cont }, }, mcid(accountId, "1")) } - cmd, err := j.request(session, logger, NS_MAILBOX, invocations...) + cmd, err := j.request(ctx, NS_MAILBOX, invocations...) if err != nil { return nil, "", "", "", err } - return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (map[string][]Mailbox, State, Error) { + return command(j, ctx, cmd, func(body *Response) (map[string][]Mailbox, State, Error) { resp := map[string][]Mailbox{} stateByAccountid := map[string]State{} for _, accountId := range uniqueAccountIds { var response MailboxGetResponse - err = retrieveResponseMatchParameters(logger, body, CommandMailboxGet, mcid(accountId, "1"), &response) + err = retrieveResponseMatchParameters(ctx, body, CommandMailboxGet, mcid(accountId, "1"), &response) if err != nil { return nil, "", err } @@ -79,8 +77,9 @@ func (j *Client) SearchMailboxes(accountIds []string, session *Session, ctx cont }) } -func (j *Client) SearchMailboxIdsPerRole(accountIds []string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, roles []string) (map[string]map[string]string, SessionState, State, Language, Error) { //NOSONAR - logger = j.logger("SearchMailboxIdsPerRole", session, logger) +func (j *Client) SearchMailboxIdsPerRole(accountIds []string, roles []string, ctx Context) (map[string]map[string]string, SessionState, State, Language, Error) { //NOSONAR + logger := j.logger("SearchMailboxIdsPerRole", ctx) + ctx = ctx.WithLogger(logger) uniqueAccountIds := structs.Uniq(accountIds) @@ -90,19 +89,19 @@ func (j *Client) SearchMailboxIdsPerRole(accountIds []string, session *Session, invocations[i*len(roles)+j] = invocation(MailboxQueryCommand{AccountId: accountId, Filter: MailboxFilterCondition{Role: role}}, mcid(accountId, role)) } } - cmd, err := j.request(session, logger, NS_MAILBOX, invocations...) + cmd, err := j.request(ctx, NS_MAILBOX, invocations...) if err != nil { return nil, "", "", "", err } - return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (map[string]map[string]string, State, Error) { + return command(j, ctx, cmd, func(body *Response) (map[string]map[string]string, State, Error) { resp := map[string]map[string]string{} stateByAccountid := map[string]State{} for _, accountId := range uniqueAccountIds { mailboxIdsByRole := map[string]string{} for _, role := range roles { var response MailboxQueryResponse - err = retrieveResponseMatchParameters(logger, body, CommandMailboxQuery, mcid(accountId, role), &response) + err = retrieveResponseMatchParameters(ctx, body, CommandMailboxQuery, mcid(accountId, role), &response) if err != nil { return nil, "", err } @@ -134,10 +133,11 @@ func newMailboxChanges(oldState, newState State, hasMoreChanges bool, created, u // Retrieve Mailbox changes since a given state. // @apidoc mailboxes,changes -func (j *Client) GetMailboxChanges(accountId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, sinceState State, maxChanges uint) (MailboxChanges, SessionState, State, Language, Error) { +func (j *Client) GetMailboxChanges(accountId string, sinceState State, maxChanges uint, + ctx Context) (MailboxChanges, SessionState, State, Language, Error) { return changesA(j, "GetMailboxChanges", NS_MAILBOX, func() MailboxChangesCommand { - return MailboxChangesCommand{AccountId: accountId, SinceState: sinceState, MaxChanges: posUIntPtr(maxChanges)} + return MailboxChangesCommand{AccountId: accountId, SinceState: sinceState, MaxChanges: uintPtr(maxChanges)} }, MailboxChangesResponse{}, MailboxGetResponse{}, @@ -152,17 +152,19 @@ func (j *Client) GetMailboxChanges(accountId string, session *Session, ctx conte } }, newMailboxChanges, - session, ctx, logger, acceptLanguage, + ctx, ) } // Retrieve Mailbox changes of multiple Accounts. // @api:tags email,changes -func (j *Client) GetMailboxChangesForMultipleAccounts(accountIds []string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, sinceStateMap map[string]State, maxChanges uint) (map[string]MailboxChanges, SessionState, State, Language, Error) { //NOSONAR +func (j *Client) GetMailboxChangesForMultipleAccounts(accountIds []string, //NOSONAR + sinceStateMap map[string]State, maxChanges uint, + ctx Context) (map[string]MailboxChanges, SessionState, State, Language, Error) { return changesN(j, "GetMailboxChangesForMultipleAccounts", NS_MAILBOX, accountIds, sinceStateMap, func(accountId string, state State) MailboxChangesCommand { - return MailboxChangesCommand{AccountId: accountId, SinceState: state, MaxChanges: posUIntPtr(maxChanges)} + return MailboxChangesCommand{AccountId: accountId, SinceState: state, MaxChanges: uintPtr(maxChanges)} }, MailboxChangesResponse{}, func(accountId string, path string, ref string) MailboxGetRefCommand { @@ -172,12 +174,13 @@ func (j *Client) GetMailboxChangesForMultipleAccounts(accountIds []string, sessi newMailboxChanges, identity1, func(resp MailboxGetResponse) State { return resp.State }, - session, ctx, logger, acceptLanguage, + ctx, ) } -func (j *Client) GetMailboxRolesForMultipleAccounts(accountIds []string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string) (map[string][]string, SessionState, State, Language, Error) { - logger = j.logger("GetMailboxRolesForMultipleAccounts", session, logger) +func (j *Client) GetMailboxRolesForMultipleAccounts(accountIds []string, ctx Context) (map[string][]string, SessionState, State, Language, Error) { + logger := j.logger("GetMailboxRolesForMultipleAccounts", ctx) + ctx = ctx.WithLogger(logger) uniqueAccountIds := structs.Uniq(accountIds) n := len(uniqueAccountIds) @@ -205,17 +208,17 @@ func (j *Client) GetMailboxRolesForMultipleAccounts(accountIds []string, session }, mcid(accountId, "1")) } - cmd, err := j.request(session, logger, NS_MAILBOX, invocations...) + cmd, err := j.request(ctx, NS_MAILBOX, invocations...) if err != nil { return nil, "", "", "", err } - return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (map[string][]string, State, Error) { + return command(j, ctx, cmd, func(body *Response) (map[string][]string, State, Error) { resp := make(map[string][]string, n) stateByAccountId := make(map[string]State, n) for _, accountId := range uniqueAccountIds { var getResponse MailboxGetResponse - err = retrieveResponseMatchParameters(logger, body, CommandMailboxGet, mcid(accountId, "1"), &getResponse) + err = retrieveResponseMatchParameters(ctx, body, CommandMailboxGet, mcid(accountId, "1"), &getResponse) if err != nil { return nil, "", err } @@ -231,8 +234,9 @@ func (j *Client) GetMailboxRolesForMultipleAccounts(accountIds []string, session }) } -func (j *Client) GetInboxNameForMultipleAccounts(accountIds []string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string) (map[string]string, SessionState, State, Language, Error) { - logger = j.logger("GetInboxNameForMultipleAccounts", session, logger) +func (j *Client) GetInboxNameForMultipleAccounts(accountIds []string, ctx Context) (map[string]string, SessionState, State, Language, Error) { + logger := j.logger("GetInboxNameForMultipleAccounts", ctx) + ctx = ctx.WithLogger(logger) uniqueAccountIds := structs.Uniq(accountIds) n := len(uniqueAccountIds) @@ -250,17 +254,17 @@ func (j *Client) GetInboxNameForMultipleAccounts(accountIds []string, session *S }, mcid(accountId, "0")) } - cmd, err := j.request(session, logger, NS_MAILBOX, invocations...) + cmd, err := j.request(ctx, NS_MAILBOX, invocations...) if err != nil { return nil, "", "", "", err } - return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (map[string]string, State, Error) { + return command(j, ctx, cmd, func(body *Response) (map[string]string, State, Error) { resp := make(map[string]string, n) stateByAccountId := make(map[string]State, n) for _, accountId := range uniqueAccountIds { var r MailboxQueryResponse - err = retrieveResponseMatchParameters(logger, body, CommandMailboxGet, mcid(accountId, "0"), &r) + err = retrieveResponseMatchParameters(ctx, body, CommandMailboxGet, mcid(accountId, "0"), &r) if err != nil { return nil, "", err } @@ -280,89 +284,48 @@ func (j *Client) GetInboxNameForMultipleAccounts(accountIds []string, session *S }) } -func (j *Client) UpdateMailbox(accountId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, mailboxId string, ifInState string, update MailboxChange) (Mailbox, SessionState, State, Language, Error) { //NOSONAR - logger = j.logger("UpdateMailbox", session, logger) - cmd, err := j.request(session, logger, NS_MAILBOX, invocation(MailboxSetCommand{ - AccountId: accountId, - IfInState: ifInState, - Update: map[string]PatchObject{ - mailboxId: update.AsPatch(), +func (j *Client) UpdateMailbox(accountId string, mailboxId string, change MailboxChange, //NOSONAR + ctx Context) (Mailbox, SessionState, State, Language, Error) { + return update(j, "UpdateMailbox", NS_MAILBOX, + func(update map[string]PatchObject) MailboxSetCommand { + return MailboxSetCommand{AccountId: accountId, Update: update} }, - }, "0")) - if err != nil { - return Mailbox{}, "", "", "", err - } - - return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (Mailbox, State, Error) { - var setResp MailboxSetResponse - err = retrieveResponseMatchParameters(logger, body, CommandMailboxSet, "0", &setResp) - if err != nil { - return Mailbox{}, "", err - } - setErr, notok := setResp.NotUpdated["u"] - if notok { - logger.Error().Msgf("%T.NotUpdated returned an error %v", setResp, setErr) - return Mailbox{}, "", setErrorError(setErr, MailboxType) - } - return setResp.Updated["c"], setResp.NewState, nil - }) -} - -func (j *Client) CreateMailbox(accountId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, ifInState string, create MailboxChange) (Mailbox, SessionState, State, Language, Error) { - logger = j.logger("CreateMailbox", session, logger) - cmd, err := j.request(session, logger, NS_MAILBOX, invocation(MailboxSetCommand{ - AccountId: accountId, - IfInState: ifInState, - Create: map[string]MailboxChange{ - "c": create, + func(id string) MailboxGetCommand { + return MailboxGetCommand{AccountId: accountId, Ids: []string{id}} }, - }, "0")) - if err != nil { - return Mailbox{}, "", "", "", err - } - - return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (Mailbox, State, Error) { - var setResp MailboxSetResponse - err = retrieveResponseMatchParameters(logger, body, CommandMailboxSet, "0", &setResp) - if err != nil { - return Mailbox{}, "", err - } - setErr, notok := setResp.NotCreated["c"] - if notok { - logger.Error().Msgf("%T.NotCreated returned an error %v", setResp, setErr) - return Mailbox{}, "", setErrorError(setErr, MailboxType) - } - if mailbox, ok := setResp.Created["c"]; ok { - return mailbox, setResp.NewState, nil - } else { - return Mailbox{}, "", jmapError(fmt.Errorf("failed to find created %T in response", Mailbox{}), JmapErrorMissingCreatedObject) - } - }) + func(resp MailboxSetResponse) map[string]SetError { return resp.NotUpdated }, + func(resp MailboxGetResponse) Mailbox { return resp.List[0] }, + mailboxId, change, + ctx, + ) } -func (j *Client) DeleteMailboxes(accountId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, ifInState string, mailboxIds []string) ([]string, SessionState, State, Language, Error) { - logger = j.logger("DeleteMailbox", session, logger) - set := MailboxSetCommand{ - AccountId: accountId, - IfInState: ifInState, - Destroy: mailboxIds, - } - cmd, err := j.request(session, logger, NS_MAILBOX, invocation(set, "0")) - if err != nil { - return nil, "", "", "", err - } - - return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) ([]string, State, Error) { - var setResp MailboxSetResponse - err = retrieveSet(logger, body, set, "0", &setResp) - if err != nil { - return nil, "", err - } - setErr, notok := setResp.NotDestroyed["u"] - if notok { - logger.Error().Msgf("%T.NotDestroyed returned an error %v", setResp, setErr) - return nil, "", setErrorError(setErr, MailboxType) - } - return setResp.Destroyed, setResp.NewState, nil - }) +func (j *Client) CreateMailbox(accountId string, mailbox MailboxChange, ctx Context) (*Mailbox, SessionState, State, Language, Error) { + return create(j, "CreateMailbox", NS_MAILBOX, + func(accountId string, create map[string]MailboxChange) MailboxSetCommand { + return MailboxSetCommand{AccountId: accountId, Create: create} + }, + func(accountId string, ids string) MailboxGetCommand { + return MailboxGetCommand{AccountId: accountId, Ids: []string{ids}} + }, + func(resp MailboxSetResponse) map[string]*Mailbox { + return resp.Created + }, + func(resp MailboxGetResponse) []Mailbox { + return resp.List + }, + accountId, mailbox, + ctx, + ) +} + +func (j *Client) DeleteMailboxes(accountId string, destroyIds []string, ctx Context) (map[string]SetError, SessionState, State, Language, Error) { + return destroy(j, "DeleteMailboxes", NS_MAILBOX, + func(accountId string, destroy []string) MailboxSetCommand { + return MailboxSetCommand{AccountId: accountId, Destroy: destroyIds} + }, + MailboxSetResponse{}, + accountId, destroyIds, + ctx, + ) } diff --git a/pkg/jmap/api_objects.go b/pkg/jmap/api_objects.go index 6e07bb1b6a..1d8c7a0d1a 100644 --- a/pkg/jmap/api_objects.go +++ b/pkg/jmap/api_objects.go @@ -1,8 +1,6 @@ package jmap import ( - "context" - "github.com/opencloud-eu/opencloud/pkg/log" ) @@ -22,14 +20,15 @@ type Objects struct { // Retrieve objects of all types by their identifiers in a single batch. // @api:tags changes -func (j *Client) GetObjects(accountId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, //NOSONAR +func (j *Client) GetObjects(accountId string, //NOSONAR mailboxIds []string, emailIds []string, addressbookIds []string, contactIds []string, calendarIds []string, eventIds []string, quotaIds []string, identityIds []string, emailSubmissionIds []string, + ctx Context, ) (Objects, SessionState, State, Language, Error) { - l := j.logger("GetObjects", session, logger).With() + l := j.logger("GetObjects", ctx).With() if len(mailboxIds) > 0 { l = l.Array("mailboxIds", log.SafeStringArray(mailboxIds)) } @@ -57,7 +56,8 @@ func (j *Client) GetObjects(accountId string, session *Session, ctx context.Cont if len(emailSubmissionIds) > 0 { l = l.Array("emailSubmissionIds", log.SafeStringArray(emailSubmissionIds)) } - logger = log.From(l) + logger := log.From(l) + ctx = ctx.WithLogger(logger) methodCalls := []Invocation{} if len(mailboxIds) > 0 { @@ -88,17 +88,17 @@ func (j *Client) GetObjects(accountId string, session *Session, ctx context.Cont methodCalls = append(methodCalls, invocation(EmailSubmissionGetCommand{AccountId: accountId, Ids: emailSubmissionIds}, "emailSubmissionIds")) } - cmd, err := j.request(session, logger, NS_OBJECTS, methodCalls...) + cmd, err := j.request(ctx, NS_OBJECTS, methodCalls...) if err != nil { return Objects{}, "", "", "", err } - return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (Objects, State, Error) { + return command(j, ctx, cmd, func(body *Response) (Objects, State, Error) { objs := Objects{} states := map[string]State{} var mailboxes MailboxGetResponse - if ok, err := tryRetrieveResponseMatchParameters(logger, body, CommandMailboxGet, "mailboxes", &mailboxes); err != nil { + if ok, err := tryRetrieveResponseMatchParameters(ctx, body, CommandMailboxGet, "mailboxes", &mailboxes); err != nil { return Objects{}, "", err } else if ok { objs.Mailboxes = &mailboxes @@ -106,7 +106,7 @@ func (j *Client) GetObjects(accountId string, session *Session, ctx context.Cont } var emails EmailGetResponse - if ok, err := tryRetrieveResponseMatchParameters(logger, body, CommandEmailGet, "emails", &emails); err != nil { + if ok, err := tryRetrieveResponseMatchParameters(ctx, body, CommandEmailGet, "emails", &emails); err != nil { return Objects{}, "", err } else if ok { objs.Emails = &emails @@ -114,7 +114,7 @@ func (j *Client) GetObjects(accountId string, session *Session, ctx context.Cont } var calendars CalendarGetResponse - if ok, err := tryRetrieveResponseMatchParameters(logger, body, CommandCalendarGet, "calendars", &calendars); err != nil { + if ok, err := tryRetrieveResponseMatchParameters(ctx, body, CommandCalendarGet, "calendars", &calendars); err != nil { return Objects{}, "", err } else if ok { objs.Calendars = &calendars @@ -122,7 +122,7 @@ func (j *Client) GetObjects(accountId string, session *Session, ctx context.Cont } var events CalendarEventGetResponse - if ok, err := tryRetrieveResponseMatchParameters(logger, body, CommandCalendarEventGet, "events", &events); err != nil { + if ok, err := tryRetrieveResponseMatchParameters(ctx, body, CommandCalendarEventGet, "events", &events); err != nil { return Objects{}, "", err } else if ok { objs.Events = &events @@ -130,7 +130,7 @@ func (j *Client) GetObjects(accountId string, session *Session, ctx context.Cont } var addressbooks AddressBookGetResponse - if ok, err := tryRetrieveResponseMatchParameters(logger, body, CommandAddressBookGet, "addressbooks", &addressbooks); err != nil { + if ok, err := tryRetrieveResponseMatchParameters(ctx, body, CommandAddressBookGet, "addressbooks", &addressbooks); err != nil { return Objects{}, "", err } else if ok { objs.Addressbooks = &addressbooks @@ -138,7 +138,7 @@ func (j *Client) GetObjects(accountId string, session *Session, ctx context.Cont } var contacts ContactCardGetResponse - if ok, err := tryRetrieveResponseMatchParameters(logger, body, CommandContactCardGet, "contacts", &contacts); err != nil { + if ok, err := tryRetrieveResponseMatchParameters(ctx, body, CommandContactCardGet, "contacts", &contacts); err != nil { return Objects{}, "", err } else if ok { objs.Contacts = &contacts @@ -146,7 +146,7 @@ func (j *Client) GetObjects(accountId string, session *Session, ctx context.Cont } var quotas QuotaGetResponse - if ok, err := tryRetrieveResponseMatchParameters(logger, body, CommandQuotaGet, "quotas", "as); err != nil { + if ok, err := tryRetrieveResponseMatchParameters(ctx, body, CommandQuotaGet, "quotas", "as); err != nil { return Objects{}, "", err } else if ok { objs.Quotas = "as @@ -154,7 +154,7 @@ func (j *Client) GetObjects(accountId string, session *Session, ctx context.Cont } var identities IdentityGetResponse - if ok, err := tryRetrieveResponseMatchParameters(logger, body, CommandIdentityGet, "identities", &identities); err != nil { + if ok, err := tryRetrieveResponseMatchParameters(ctx, body, CommandIdentityGet, "identities", &identities); err != nil { return Objects{}, "", err } else if ok { objs.Identities = &identities @@ -162,7 +162,7 @@ func (j *Client) GetObjects(accountId string, session *Session, ctx context.Cont } var submissions EmailSubmissionGetResponse - if ok, err := tryRetrieveResponseMatchParameters(logger, body, CommandEmailSubmissionGet, "submissions", &submissions); err != nil { + if ok, err := tryRetrieveResponseMatchParameters(ctx, body, CommandEmailSubmissionGet, "submissions", &submissions); err != nil { return Objects{}, "", err } else if ok { objs.EmailSubmissions = &submissions diff --git a/pkg/jmap/api_principal.go b/pkg/jmap/api_principal.go index 9eb42df7e0..512c0559df 100644 --- a/pkg/jmap/api_principal.go +++ b/pkg/jmap/api_principal.go @@ -1,37 +1,50 @@ package jmap -import ( - "context" - - "github.com/opencloud-eu/opencloud/pkg/log" -) - var NS_PRINCIPALS = ns(JmapPrincipals) -type PrincipalsResponse struct { - Principals []Principal `json:"principals"` - NotFound []string `json:"notFound,omitempty"` -} - -func (j *Client) GetPrincipals(accountId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, ids []string) (PrincipalsResponse, SessionState, State, Language, Error) { - logger = j.logger("GetPrincipals", session, logger) - - cmd, err := j.request(session, logger, NS_PRINCIPALS, - invocation(PrincipalGetCommand{AccountId: accountId, Ids: ids}, "0"), +func (j *Client) GetPrincipals(accountId string, ids []string, ctx Context) (PrincipalGetResponse, SessionState, State, Language, Error) { + return get(j, "GetPrincipals", NS_PRINCIPALS, + func(accountId string, ids []string) PrincipalGetCommand { + return PrincipalGetCommand{AccountId: accountId, Ids: ids} + }, + PrincipalGetResponse{}, + identity1, + accountId, ids, + ctx, + ) +} + +type PrincipalSearchResults SearchResultsTemplate[Principal] + +var _ SearchResults[Principal] = PrincipalSearchResults{} + +func (r PrincipalSearchResults) GetResults() []Principal { return r.Results } +func (r PrincipalSearchResults) GetCanCalculateChanges() bool { return r.CanCalculateChanges } +func (r PrincipalSearchResults) GetPosition() uint { return r.Position } +func (r PrincipalSearchResults) GetLimit() uint { return r.Limit } +func (r PrincipalSearchResults) GetTotal() *uint { return r.Total } + +func (j *Client) QueryPrincipals(accountId string, + filter PrincipalFilterElement, sortBy []PrincipalComparator, + position uint, limit uint, calculateTotal bool, + ctx Context) (PrincipalSearchResults, SessionState, State, Language, Error) { + return query(j, "QueryPrincipals", NS_PRINCIPALS, + []PrincipalComparator{{Property: PrincipalPropertyName, IsAscending: true}}, + func(filter PrincipalFilterElement, sortBy []PrincipalComparator, position uint, limit uint) PrincipalQueryCommand { + return PrincipalQueryCommand{AccountId: accountId, Filter: filter, Sort: sortBy, Position: position, Limit: limit, CalculateTotal: calculateTotal} + }, + func(cmd Command, path string, rof string) PrincipalGetRefCommand { + return PrincipalGetRefCommand{AccountId: accountId, IdsRef: &ResultReference{Name: cmd, Path: path, ResultOf: rof}} + }, + func(query PrincipalQueryResponse, get PrincipalGetResponse) PrincipalSearchResults { + return PrincipalSearchResults{ + Results: get.List, + CanCalculateChanges: query.CanCalculateChanges, + Position: query.Position, + Total: uintPtrIf(query.Total, calculateTotal), + Limit: query.Limit, + } + }, + filter, sortBy, limit, position, ctx, ) - if err != nil { - return PrincipalsResponse{}, "", "", "", err - } - - return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (PrincipalsResponse, State, Error) { - var response PrincipalGetResponse - err = retrieveResponseMatchParameters(logger, body, CommandPrincipalGet, "0", &response) - if err != nil { - return PrincipalsResponse{}, response.State, err - } - return PrincipalsResponse{ - Principals: response.List, - NotFound: response.NotFound, - }, response.State, nil - }) } diff --git a/pkg/jmap/api_quota.go b/pkg/jmap/api_quota.go index 1f8c8deba2..4aca032636 100644 --- a/pkg/jmap/api_quota.go +++ b/pkg/jmap/api_quota.go @@ -1,14 +1,8 @@ package jmap -import ( - "context" - - "github.com/opencloud-eu/opencloud/pkg/log" -) - var NS_QUOTA = ns(JmapQuota) -func (j *Client) GetQuotas(accountIds []string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string) (map[string]QuotaGetResponse, SessionState, State, Language, Error) { +func (j *Client) GetQuotas(accountIds []string, ctx Context) (map[string]QuotaGetResponse, SessionState, State, Language, Error) { return getN(j, "GetQuotas", NS_QUOTA, func(accountId string, ids []string) QuotaGetCommand { return QuotaGetCommand{AccountId: accountId} @@ -16,7 +10,8 @@ func (j *Client) GetQuotas(accountIds []string, session *Session, ctx context.Co QuotaGetResponse{}, identity1, identity1, - accountIds, session, ctx, logger, acceptLanguage, []string{}, + accountIds, []string{}, + ctx, ) } @@ -24,11 +19,11 @@ type QuotaChanges = ChangesTemplate[Quota] // Retrieve the changes in Quotas since a given State. // @api:tags quota,changes -func (j *Client) GetQuotaChanges(accountId string, session *Session, ctx context.Context, logger *log.Logger, - acceptLanguage string, sinceState State, maxChanges uint) (QuotaChanges, SessionState, State, Language, Error) { +func (j *Client) GetQuotaChanges(accountId string, sinceState State, maxChanges uint, + ctx Context) (QuotaChanges, SessionState, State, Language, Error) { return changesA(j, "GetQuotaChanges", NS_QUOTA, func() QuotaChangesCommand { - return QuotaChangesCommand{AccountId: accountId, SinceState: sinceState, MaxChanges: posUIntPtr(maxChanges)} + return QuotaChangesCommand{AccountId: accountId, SinceState: sinceState, MaxChanges: uintPtr(maxChanges)} }, QuotaChangesResponse{}, QuotaGetResponse{}, @@ -52,15 +47,15 @@ func (j *Client) GetQuotaChanges(accountId string, session *Session, ctx context Destroyed: destroyed, } }, - session, ctx, logger, acceptLanguage, + ctx, ) } -func (j *Client) GetQuotaUsageChanges(accountId string, session *Session, ctx context.Context, logger *log.Logger, - acceptLanguage string, sinceState State, maxChanges uint) (QuotaChanges, SessionState, State, Language, Error) { +func (j *Client) GetQuotaUsageChanges(accountId string, sinceState State, maxChanges uint, + ctx Context) (QuotaChanges, SessionState, State, Language, Error) { return updates(j, "GetQuotaUsageChanges", NS_QUOTA, func() QuotaChangesCommand { - return QuotaChangesCommand{AccountId: accountId, SinceState: sinceState, MaxChanges: posUIntPtr(maxChanges)} + return QuotaChangesCommand{AccountId: accountId, SinceState: sinceState, MaxChanges: uintPtr(maxChanges)} }, QuotaChangesResponse{}, func(path string, rof string) QuotaGetRefCommand { @@ -87,6 +82,6 @@ func (j *Client) GetQuotaUsageChanges(accountId string, session *Session, ctx co Updated: updated, } }, - session, ctx, logger, acceptLanguage, + ctx, ) } diff --git a/pkg/jmap/api_vacation.go b/pkg/jmap/api_vacation.go index f513ec0ff6..6b1ae9acaf 100644 --- a/pkg/jmap/api_vacation.go +++ b/pkg/jmap/api_vacation.go @@ -1,11 +1,8 @@ package jmap import ( - "context" "fmt" "time" - - "github.com/opencloud-eu/opencloud/pkg/log" ) var NS_VACATION = ns(JmapVacationResponse) @@ -14,19 +11,20 @@ const ( vacationResponseId = "singleton" ) -func (j *Client) GetVacationResponse(accountId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string) (VacationResponseGetResponse, SessionState, State, Language, Error) { +func (j *Client) GetVacationResponse(accountId string, ctx Context) (VacationResponseGetResponse, SessionState, State, Language, Error) { return get(j, "GetVacationResponse", NS_VACATION, func(accountId string, ids []string) VacationResponseGetCommand { return VacationResponseGetCommand{AccountId: accountId} }, VacationResponseGetResponse{}, identity1, - accountId, session, ctx, logger, acceptLanguage, []string{}, + accountId, []string{}, + ctx, ) } // Same as VacationResponse but without the id. -type VacationResponsePayload struct { +type VacationResponseChange struct { // Should a vacation response be sent if a message arrives between the "fromDate" and "toDate"? IsEnabled bool `json:"isEnabled"` // If "isEnabled" is true, messages that arrive on or after this date-time (but before the "toDate" if defined) should receive the @@ -49,33 +47,39 @@ type VacationResponsePayload struct { HtmlBody string `json:"htmlBody,omitempty"` } -func (j *Client) SetVacationResponse(accountId string, vacation VacationResponsePayload, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string) (VacationResponse, SessionState, State, Language, Error) { - logger = j.logger("SetVacationResponse", session, logger) +func (j *Client) SetVacationResponse(accountId string, vacation VacationResponseChange, + ctx Context) (VacationResponse, SessionState, State, Language, Error) { + logger := j.logger("SetVacationResponse", ctx) + ctx = ctx.WithLogger(logger) - cmd, err := j.request(session, logger, NS_VACATION, - invocation(VacationResponseSetCommand{ - AccountId: accountId, - Create: map[string]VacationResponse{ - vacationResponseId: { - IsEnabled: vacation.IsEnabled, - FromDate: vacation.FromDate, - ToDate: vacation.ToDate, - Subject: vacation.Subject, - TextBody: vacation.TextBody, - HtmlBody: vacation.HtmlBody, - }, + set := VacationResponseSetCommand{ + AccountId: accountId, + Create: map[string]VacationResponse{ + vacationResponseId: { + IsEnabled: vacation.IsEnabled, + FromDate: vacation.FromDate, + ToDate: vacation.ToDate, + Subject: vacation.Subject, + TextBody: vacation.TextBody, + HtmlBody: vacation.HtmlBody, }, - }, "0"), + }, + } + + get := VacationResponseGetCommand{AccountId: accountId} + + cmd, err := j.request(ctx, NS_VACATION, + invocation(set, "0"), // chain a second request to get the current complete VacationResponse object // after performing the changes, as that makes for a better API - invocation(VacationResponseGetCommand{AccountId: accountId}, "1"), + invocation(get, "1"), ) if err != nil { return VacationResponse{}, "", "", "", err } - return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (VacationResponse, State, Error) { + return command(j, ctx, cmd, func(body *Response) (VacationResponse, State, Error) { var setResponse VacationResponseSetResponse - err = retrieveResponseMatchParameters(logger, body, CommandVacationResponseSet, "0", &setResponse) + err = retrieveSet(ctx, body, set, "0", &setResponse) if err != nil { return VacationResponse{}, "", err } @@ -88,7 +92,7 @@ func (j *Client) SetVacationResponse(accountId string, vacation VacationResponse } var getResponse VacationResponseGetResponse - err = retrieveResponseMatchParameters(logger, body, CommandVacationResponseGet, "1", &getResponse) + err = retrieveGet(ctx, body, get, "1", &getResponse) if err != nil { return VacationResponse{}, "", err } diff --git a/pkg/jmap/client.go b/pkg/jmap/client.go index 81b05bfccf..e7380b8139 100644 --- a/pkg/jmap/client.go +++ b/pkg/jmap/client.go @@ -22,13 +22,27 @@ type Client struct { WsPushListener } +type ApiSupplier interface { + Api() ApiClient +} + +type Hooks interface { + OnSessionOutdated(session *Session, newState SessionState) +} + var _ io.Closer = &Client{} var _ WsPushListener = &Client{} +var _ ApiSupplier = &Client{} +var _ Hooks = &Client{} func (j *Client) Close() error { return errors.Join(j.api.Close(), j.session.Close(), j.blob.Close(), j.ws.Close()) } +func (j *Client) Api() ApiClient { + return j.api +} + func NewClient(session SessionClient, api ApiClient, blob BlobClient, ws WsClientFactory) Client { return Client{ session: session, @@ -44,7 +58,7 @@ func (j *Client) AddSessionEventListener(listener SessionEventListener) { j.sessionEventListeners.add(listener) } -func (j *Client) onSessionOutdated(session *Session, newSessionState SessionState) { +func (j *Client) OnSessionOutdated(session *Session, newSessionState SessionState) { j.sessionEventListeners.signal(func(listener SessionEventListener) { listener.OnSessionOutdated(session, newSessionState) }) @@ -65,25 +79,25 @@ func (j *Client) FetchSession(ctx context.Context, sessionUrl *url.URL, username return newSession(wk) } -func (j *Client) logger(operation string, _ *Session, logger *log.Logger) *log.Logger { - l := logger.With().Str(logOperation, operation) +func (j *Client) logger(operation string, ctx Context) *log.Logger { + l := ctx.Logger.With().Str(logOperation, operation) return log.From(l) } -func (j *Client) loggerParams(operation string, _ *Session, logger *log.Logger, params func(zerolog.Context) zerolog.Context) *log.Logger { - l := logger.With().Str(logOperation, operation) +func (j *Client) loggerParams(operation string, ctx Context, params func(zerolog.Context) zerolog.Context) *log.Logger { + l := ctx.Logger.With().Str(logOperation, operation) if params != nil { l = params(l) } return log.From(l) } -func (j *Client) maxCallsCheck(calls int, session *Session, logger *log.Logger) Error { - if calls > session.Capabilities.Core.MaxCallsInRequest { - logger.Error(). - Int("max-calls-in-request", session.Capabilities.Core.MaxCallsInRequest). +func (j *Client) maxCallsCheck(calls int, ctx Context) Error { + if calls > ctx.Session.Capabilities.Core.MaxCallsInRequest { + ctx.Logger.Error(). + Int("max-calls-in-request", ctx.Session.Capabilities.Core.MaxCallsInRequest). Int("calls-in-request", calls). - Msgf("number of calls in request payload (%d) exceeds the allowed maximum (%d)", session.Capabilities.Core.MaxCallsInRequest, calls) + Msgf("number of calls in request payload (%d) exceeds the allowed maximum (%d)", ctx.Session.Capabilities.Core.MaxCallsInRequest, calls) return jmapError(errTooManyMethodCalls, JmapErrorTooManyMethodCalls) } return nil @@ -92,8 +106,8 @@ func (j *Client) maxCallsCheck(calls int, session *Session, logger *log.Logger) // Construct a Request from the given list of Invocation objects. // // If an issue occurs, then it is logged prior to returning it. -func (j *Client) request(session *Session, logger *log.Logger, using []JmapNamespace, methodCalls ...Invocation) (Request, Error) { - err := j.maxCallsCheck(len(methodCalls), session, logger) +func (j *Client) request(ctx Context, using []JmapNamespace, methodCalls ...Invocation) (Request, Error) { + err := j.maxCallsCheck(len(methodCalls), ctx) if err != nil { return Request{}, err } diff --git a/pkg/jmap/http.go b/pkg/jmap/http.go index 89b3a8086b..ec337291a8 100644 --- a/pkg/jmap/http.go +++ b/pkg/jmap/http.go @@ -217,7 +217,12 @@ func (h *HttpJmapClient) GetSession(ctx context.Context, sessionUrl *url.URL, us return data, nil } -func (h *HttpJmapClient) Command(ctx context.Context, logger *log.Logger, session *Session, request Request, acceptLanguage string) ([]byte, Language, Error) { //NOSONAR +func (h *HttpJmapClient) Command(request Request, ctx Context) ([]byte, Language, Error) { //NOSONAR + session := ctx.Session + logger := ctx.Logger + acceptLanguage := ctx.AcceptLanguage + cotx := ctx.Context + jmapUrl := session.JmapUrl.String() endpoint := session.JmapEndpoint logger = log.From(logger.With().Str(logEndpoint, endpoint)) @@ -228,7 +233,7 @@ func (h *HttpJmapClient) Command(ctx context.Context, logger *log.Logger, sessio return nil, "", jmapError(err, JmapErrorEncodingRequestBody) } - req, err := http.NewRequestWithContext(ctx, http.MethodPost, jmapUrl, bytes.NewBuffer(bodyBytes)) + req, err := http.NewRequestWithContext(cotx, http.MethodPost, jmapUrl, bytes.NewBuffer(bodyBytes)) if err != nil { logger.Error().Err(err).Msgf("failed to create POST request for %v", jmapUrl) return nil, "", jmapError(err, JmapErrorCreatingRequest) @@ -249,7 +254,7 @@ func (h *HttpJmapClient) Command(ctx context.Context, logger *log.Logger, sessio logger.Trace().Str(logEndpoint, endpoint).Str(logProto, logProtoJmap).Str(logType, logTypeRequest).Msg(string(requestBytes)) } } - if err := h.auth(ctx, session.Username, logger, req); err != nil { + if err := h.auth(cotx, session.Username, logger, req); err != nil { return nil, "", err } @@ -295,10 +300,15 @@ func (h *HttpJmapClient) Command(ctx context.Context, logger *log.Logger, sessio return body, language, nil } -func (h *HttpJmapClient) UploadBinary(ctx context.Context, logger *log.Logger, session *Session, uploadUrl string, endpoint string, contentType string, acceptLanguage string, body io.Reader) (UploadedBlob, Language, Error) { //NOSONAR +func (h *HttpJmapClient) UploadBinary(uploadUrl string, endpoint string, contentType string, body io.Reader, ctx Context) (UploadedBlob, Language, Error) { //NOSONAR + session := ctx.Session + logger := ctx.Logger + acceptLanguage := ctx.AcceptLanguage + cotx := ctx.Context + logger = log.From(logger.With().Str(logEndpoint, endpoint)) - req, err := http.NewRequestWithContext(ctx, http.MethodPost, uploadUrl, body) + req, err := http.NewRequestWithContext(cotx, http.MethodPost, uploadUrl, body) if err != nil { logger.Error().Err(err).Msgf("failed to create POST request for %v", uploadUrl) return UploadedBlob{}, "", jmapError(err, JmapErrorCreatingRequest) @@ -315,7 +325,7 @@ func (h *HttpJmapClient) UploadBinary(ctx context.Context, logger *log.Logger, s } } - if err := h.auth(ctx, session.Username, logger, req); err != nil { + if err := h.auth(cotx, session.Username, logger, req); err != nil { return UploadedBlob{}, "", err } @@ -357,8 +367,6 @@ func (h *HttpJmapClient) UploadBinary(ctx context.Context, logger *log.Logger, s return UploadedBlob{}, language, jmapError(err, JmapErrorServerResponse) } - logger.Trace() - var result UploadedBlob err = json.Unmarshal(responseBody, &result) if err != nil { @@ -370,10 +378,15 @@ func (h *HttpJmapClient) UploadBinary(ctx context.Context, logger *log.Logger, s return result, language, nil } -func (h *HttpJmapClient) DownloadBinary(ctx context.Context, logger *log.Logger, session *Session, downloadUrl string, endpoint string, acceptLanguage string) (*BlobDownload, Language, Error) { //NOSONAR +func (h *HttpJmapClient) DownloadBinary(downloadUrl string, endpoint string, ctx Context) (*BlobDownload, Language, Error) { //NOSONAR + session := ctx.Session + logger := ctx.Logger + acceptLanguage := ctx.AcceptLanguage + cotx := ctx.Context + logger = log.From(logger.With().Str(logEndpoint, endpoint)) - req, err := http.NewRequestWithContext(ctx, http.MethodGet, downloadUrl, nil) + req, err := http.NewRequestWithContext(cotx, http.MethodGet, downloadUrl, nil) if err != nil { logger.Error().Err(err).Msgf("failed to create GET request for %v", downloadUrl) return nil, "", jmapError(err, JmapErrorCreatingRequest) @@ -389,7 +402,7 @@ func (h *HttpJmapClient) DownloadBinary(ctx context.Context, logger *log.Logger, } } - if err := h.auth(ctx, session.Username, logger, req); err != nil { + if err := h.auth(cotx, session.Username, logger, req); err != nil { return nil, "", err } diff --git a/pkg/jmap/integration_contact_test.go b/pkg/jmap/integration_contact_test.go index 37d60bef65..ef9cef8f6e 100644 --- a/pkg/jmap/integration_contact_test.go +++ b/pkg/jmap/integration_contact_test.go @@ -1,7 +1,6 @@ package jmap import ( - "context" golog "log" "math/rand" "regexp" @@ -20,7 +19,6 @@ import ( "github.com/ProtonMail/go-crypto/openpgp" "github.com/brianvoe/gofakeit/v7" "github.com/opencloud-eu/opencloud/pkg/jscontact" - "github.com/opencloud-eu/opencloud/pkg/log" "github.com/opencloud-eu/opencloud/pkg/structs" ) @@ -45,17 +43,17 @@ func TestAddressBooks(t *testing.T) { func(session *Session) string { return session.PrimaryAccounts.Contacts }, list, getid, - func(s *StalwartTest, accountId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, ids []string) (AddressBookGetResponse, SessionState, State, Language, Error) { - return s.client.GetAddressbooks(accountId, session, ctx, logger, acceptLanguage, ids) + func(s *StalwartTest, accountId string, ids []string, ctx Context) (AddressBookGetResponse, SessionState, State, Language, Error) { + return s.client.GetAddressbooks(accountId, ids, ctx) }, - func(s *StalwartTest, accountId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, id string, change AddressBookChange) (AddressBook, SessionState, State, Language, Error) { //NOSONAR - return s.client.UpdateAddressBook(accountId, session, ctx, logger, acceptLanguage, id, change) + func(s *StalwartTest, accountId string, id string, change AddressBookChange, ctx Context) (AddressBook, SessionState, State, Language, Error) { //NOSONAR + return s.client.UpdateAddressBook(accountId, id, change, ctx) }, - func(s *StalwartTest, accountId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, ids []string) (map[string]SetError, SessionState, State, Language, Error) { //NOSONAR - return s.client.DeleteAddressBook(accountId, ids, session, ctx, logger, acceptLanguage) + func(s *StalwartTest, accountId string, ids []string, ctx Context) (map[string]SetError, SessionState, State, Language, Error) { //NOSONAR + return s.client.DeleteAddressBook(accountId, ids, ctx) }, - func(s *StalwartTest, t *testing.T, accountId string, count uint, session *Session, user User, principalIds []string) (AddressBookBoxes, []AddressBook, SessionState, State, error) { - return s.fillAddressBook(t, accountId, count, session, user, principalIds) + func(s *StalwartTest, t *testing.T, accountId string, count uint, ctx Context, user User, principalIds []string) (AddressBookBoxes, []AddressBook, SessionState, State, error) { + return s.fillAddressBook(t, accountId, count, ctx, user, principalIds) }, func(orig AddressBook) AddressBookChange { return AddressBookChange{ @@ -86,6 +84,7 @@ func TestContacts(t *testing.T) { user := pickUser() session := s.Session(user.name) + ctx := s.Context(session) accountId, addressbookId, expectedContactCardsById, boxes, err := s.fillContacts(t, count, session, user) require.NoError(err) @@ -99,15 +98,19 @@ func TestContacts(t *testing.T) { {Property: ContactCardPropertyCreated, IsAscending: true}, } - contactsByAccount, _, _, _, err := s.client.QueryContactCards([]string{accountId}, session, t.Context(), s.logger, "", filter, sortBy, 0, 0) + contactsByAccount, _, _, _, err := s.client.QueryContactCards([]string{accountId}, filter, sortBy, 0, 0, true, ctx) require.NoError(err) require.Len(contactsByAccount, 1) require.Contains(contactsByAccount, accountId) - contacts := contactsByAccount[accountId] - require.Len(contacts, int(count)) + results := contactsByAccount[accountId] + require.Len(results.Results, int(count)) + require.Equal(uint(0), results.Limit) + require.Equal(uint(0), results.Position) + require.Equal(uint(0), results.Total) + require.Equal(true, results.CanCalculateChanges) - for _, actual := range contacts { + for _, actual := range results.Results { expected, ok := expectedContactCardsById[actual.Id] require.True(ok, "failed to find created contact by its id") matchContact(t, actual, expected) @@ -115,13 +118,13 @@ func TestContacts(t *testing.T) { // retrieve all objects at once { - ids := structs.Map(contacts, func(c ContactCard) string { return c.Id }) - fetched, _, _, _, err := s.client.GetContactCards(accountId, session, t.Context(), s.logger, "", ids) + ids := structs.Map(results.Results, func(c ContactCard) string { return c.Id }) + fetched, _, _, _, err := s.client.GetContactCards(accountId, ids, ctx) require.NoError(err) require.Empty(fetched.NotFound) require.Len(fetched.List, len(ids)) byId := structs.Index(fetched.List, func(r ContactCard) string { return r.Id }) - for _, actual := range contacts { + for _, actual := range results.Results { expected, ok := byId[actual.Id] require.True(ok, "failed to find created contact by its id") matchContact(t, actual, expected) @@ -129,8 +132,8 @@ func TestContacts(t *testing.T) { } // retrieve each object one by one - for _, actual := range contacts { - fetched, _, _, _, err := s.client.GetContactCards(accountId, session, t.Context(), s.logger, "", []string{actual.Id}) + for _, actual := range results.Results { + fetched, _, _, _, err := s.client.GetContactCards(accountId, []string{actual.Id}, ctx) require.NoError(err) require.Len(fetched.List, 1) matchContact(t, fetched.List[0], actual) @@ -169,7 +172,7 @@ func (s *StalwartTest) fillAddressBook( //NOSONAR t *testing.T, accountId string, count uint, - session *Session, + ctx Context, _ User, principalIds []string, ) (AddressBookBoxes, []AddressBook, SessionState, State, error) { @@ -192,7 +195,7 @@ func (s *StalwartTest) fillAddressBook( //NOSONAR IsSubscribed: &subscribed, } if i%2 == 0 { - abook.SortOrder = posUIntPtr(gofakeit.Uint()) + abook.SortOrder = uintPtr(gofakeit.Uint()) boxes.sortOrdered = true } var sharing *AddressBookRights = nil @@ -218,7 +221,7 @@ func (s *StalwartTest) fillAddressBook( //NOSONAR abook.ShareWith = m } - a, sessionState, state, _, err := s.client.CreateAddressBook(accountId, session, s.ctx, s.logger, "", abook) + a, sessionState, state, _, err := s.client.CreateAddressBook(accountId, abook, ctx) if err != nil { return boxes, created, ss, as, err } diff --git a/pkg/jmap/integration_email_test.go b/pkg/jmap/integration_email_test.go index 1a8c992703..3e14d1a356 100644 --- a/pkg/jmap/integration_email_test.go +++ b/pkg/jmap/integration_email_test.go @@ -40,10 +40,11 @@ func TestEmails(t *testing.T) { user := pickUser() session := s.Session(user.name) + ctx := s.Context(session) accountId := session.PrimaryAccounts.Mail - inboxId, inboxFolder := s.findInbox(t, accountId, session) + inboxId, inboxFolder := s.findInbox(t, accountId, ctx) var threads int = 0 var mails []filledMail = nil @@ -55,7 +56,7 @@ func TestEmails(t *testing.T) { { { - resp, sessionState, _, _, err := s.client.GetAllIdentities(accountId, session, s.ctx, s.logger, "") + resp, sessionState, _, _, err := s.client.GetAllIdentities(accountId, ctx) require.NoError(err) require.Equal(session.State, sessionState) require.Len(resp, 1) @@ -64,7 +65,7 @@ func TestEmails(t *testing.T) { } { - respByAccountId, sessionState, _, _, err := s.client.GetAllMailboxes([]string{accountId}, session, s.ctx, s.logger, "") + respByAccountId, sessionState, _, _, err := s.client.GetAllMailboxes([]string{accountId}, ctx) require.NoError(err) require.Equal(session.State, sessionState) require.Len(respByAccountId, 1) @@ -80,7 +81,7 @@ func TestEmails(t *testing.T) { } { - resp, sessionState, _, _, err := s.client.GetAllEmailsInMailbox(accountId, session, s.ctx, s.logger, "", inboxId, 0, 0, true, false, 0, true) + resp, sessionState, _, _, err := s.client.GetAllEmailsInMailbox(accountId, inboxId, 0, 0, true, false, 0, true, ctx) require.NoError(err) require.Equal(session.State, sessionState) @@ -94,7 +95,7 @@ func TestEmails(t *testing.T) { } { - resp, sessionState, _, _, err := s.client.GetAllEmailsInMailbox(accountId, session, s.ctx, s.logger, "", inboxId, 0, 0, false, false, 0, true) + resp, sessionState, _, _, err := s.client.GetAllEmailsInMailbox(accountId, inboxId, 0, 0, false, false, 0, true, ctx) require.NoError(err) require.Equal(session.State, sessionState) @@ -122,6 +123,7 @@ func TestSendingEmails(t *testing.T) { from := pickUser() session := s.Session(from.name) + ctx := s.Context(session) accountId := session.PrimaryAccounts.Mail var to User @@ -142,7 +144,7 @@ func TestSendingEmails(t *testing.T) { var mailboxPerRole map[string]Mailbox { - mailboxes, _, _, _, err := s.client.GetAllMailboxes([]string{accountId}, session, s.ctx, s.logger, "") + mailboxes, _, _, _, err := s.client.GetAllMailboxes([]string{accountId}, ctx) require.NoError(err) mailboxPerRole = structs.Index(mailboxes[accountId], func(m Mailbox) string { return m.Role }) require.Contains(mailboxPerRole, JmapMailboxRoleInbox) @@ -152,7 +154,7 @@ func TestSendingEmails(t *testing.T) { } { roles := []string{JmapMailboxRoleDrafts, JmapMailboxRoleSent, JmapMailboxRoleInbox} - m, _, _, _, err := s.client.SearchMailboxIdsPerRole([]string{accountId}, session, s.ctx, s.logger, "", roles) + m, _, _, _, err := s.client.SearchMailboxIdsPerRole([]string{accountId}, roles, ctx) require.NoError(err) require.Contains(m, accountId) a := m[accountId] @@ -166,7 +168,7 @@ func TestSendingEmails(t *testing.T) { accountId string session *Session }{{toAccountId, toSession}, {ccAccountId, ccSession}} { - mailboxes, _, _, _, err := s.client.GetAllMailboxes([]string{u.accountId}, u.session, s.ctx, s.logger, "") + mailboxes, _, _, _, err := s.client.GetAllMailboxes([]string{u.accountId}, ctx) require.NoError(err) for _, mailbox := range mailboxes[u.accountId] { require.Equal(0, mailbox.TotalEmails) @@ -180,7 +182,7 @@ func TestSendingEmails(t *testing.T) { { var identity Identity { - identities, _, _, _, err := s.client.GetAllIdentities(accountId, session, s.ctx, s.logger, "") + identities, _, _, _, err := s.client.GetAllIdentities(accountId, ctx) require.NoError(err) require.NotEmpty(identities) identity = identities[0] @@ -191,12 +193,12 @@ func TestSendingEmails(t *testing.T) { Subject: subject, MailboxIds: toBoolMapS(mailboxPerRole[JmapMailboxRoleDrafts].Id), } - created, _, _, _, err := s.client.CreateEmail(accountId, create, "", session, s.ctx, s.logger, "") + created, _, _, _, err := s.client.CreateEmail(accountId, create, "", ctx) require.NoError(err) require.NotEmpty(created.Id) { - emails, notFound, _, _, _, err := s.client.GetEmails(accountId, session, s.ctx, s.logger, "", []string{created.Id}, true, 0, false, false) + emails, notFound, _, _, _, err := s.client.GetEmails(accountId, []string{created.Id}, true, 0, false, false, ctx) require.NoError(err) require.Len(emails, 1) require.Empty(notFound) @@ -215,14 +217,14 @@ func TestSendingEmails(t *testing.T) { Subject: subject, MailboxIds: toBoolMapS(mailboxPerRole[JmapMailboxRoleDrafts].Id), } - updated, _, _, _, err := s.client.CreateEmail(accountId, update, created.Id, session, s.ctx, s.logger, "") + updated, _, _, _, err := s.client.CreateEmail(accountId, update, created.Id, ctx) require.NoError(err) require.NotEmpty(updated.Id) require.NotEqual(created.Id, updated.Id) var updatedMailboxId string { - emails, notFound, _, _, _, err := s.client.GetEmails(accountId, session, s.ctx, s.logger, "", []string{created.Id, updated.Id}, true, 0, false, false) + emails, notFound, _, _, _, err := s.client.GetEmails(accountId, []string{created.Id, updated.Id}, true, 0, false, false, ctx) require.NoError(err) require.Len(emails, 1) require.Len(notFound, 1) @@ -241,7 +243,7 @@ func TestSendingEmails(t *testing.T) { ToMailboxId: mailboxPerRole[JmapMailboxRoleSent].Id, } - sub, _, _, _, err := s.client.SubmitEmail(accountId, identity.Id, updated.Id, &move, session, s.ctx, s.logger, "") + sub, _, _, _, err := s.client.SubmitEmail(accountId, identity.Id, updated.Id, &move, ctx) require.NoError(err) require.NotEmpty(sub.Id) require.NotEmpty(sub.ThreadId) @@ -272,7 +274,7 @@ func TestSendingEmails(t *testing.T) { } time.Sleep(1 * time.Second) - subs, notFound, _, _, _, err := s.client.GetEmailSubmissionStatus(accountId, []string{sub.Id}, session, s.ctx, s.logger, "") + subs, notFound, _, _, _, err := s.client.GetEmailSubmissionStatus(accountId, []string{sub.Id}, ctx) require.NoError(err) require.Empty(notFound) require.Contains(subs, sub.Id) @@ -286,7 +288,7 @@ func TestSendingEmails(t *testing.T) { accountId string session *Session }{{to, toAccountId, toSession}, {cc, ccAccountId, ccSession}} { - mailboxes, _, _, _, err := s.client.GetAllMailboxes([]string{r.accountId}, r.session, s.ctx, s.logger, "") + mailboxes, _, _, _, err := s.client.GetAllMailboxes([]string{r.accountId}, ctx) require.NoError(err) inboxId := "" for _, mailbox := range mailboxes[r.accountId] { @@ -297,7 +299,7 @@ func TestSendingEmails(t *testing.T) { } require.NotEmpty(inboxId, "failed to find the Mailbox with the 'inbox' role for %v", r.user.name) - emails, _, _, _, err := s.client.QueryEmails([]string{r.accountId}, EmailFilterCondition{InMailbox: inboxId}, r.session, s.ctx, s.logger, "", 0, 0, true, 0) + emails, _, _, _, err := s.client.QueryEmails([]string{r.accountId}, EmailFilterCondition{InMailbox: inboxId}, 0, 0, true, 0, ctx) require.NoError(err) require.Contains(emails, r.accountId) require.Len(emails[r.accountId].Emails, 1) @@ -354,11 +356,11 @@ func matchEmail(t *testing.T, actual Email, expected filledMail, hasBodies bool) } } -func (s *StalwartTest) findInbox(t *testing.T, accountId string, session *Session) (string, string) { +func (s *StalwartTest) findInbox(t *testing.T, accountId string, ctx Context) (string, string) { require := require.New(t) - respByAccountId, sessionState, _, _, err := s.client.GetAllMailboxes([]string{accountId}, session, s.ctx, s.logger, "") + respByAccountId, sessionState, _, _, err := s.client.GetAllMailboxes([]string{accountId}, ctx) require.NoError(err) - require.Equal(session.State, sessionState) + require.Equal(ctx.Session.State, sessionState) require.Len(respByAccountId, 1) require.Contains(respByAccountId, accountId) resp := respByAccountId[accountId] diff --git a/pkg/jmap/integration_event_test.go b/pkg/jmap/integration_event_test.go index dc40cac92f..47a8a1623e 100644 --- a/pkg/jmap/integration_event_test.go +++ b/pkg/jmap/integration_event_test.go @@ -1,7 +1,6 @@ package jmap import ( - "context" "encoding/base64" "encoding/json" "fmt" @@ -17,7 +16,6 @@ import ( "github.com/stretchr/testify/require" "github.com/opencloud-eu/opencloud/pkg/jscalendar" - "github.com/opencloud-eu/opencloud/pkg/log" "github.com/opencloud-eu/opencloud/pkg/structs" ) @@ -36,17 +34,17 @@ func TestCalendars(t *testing.T) { //NOSONAR func(session *Session) string { return session.PrimaryAccounts.Calendars }, func(resp CalendarGetResponse) []Calendar { return resp.List }, func(obj Calendar) string { return obj.Id }, - func(s *StalwartTest, accountId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, ids []string) (CalendarGetResponse, SessionState, State, Language, Error) { - return s.client.GetCalendars(accountId, session, ctx, logger, acceptLanguage, ids) + func(s *StalwartTest, accountId string, ids []string, ctx Context) (CalendarGetResponse, SessionState, State, Language, Error) { + return s.client.GetCalendars(accountId, ids, ctx) }, - func(s *StalwartTest, accountId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, id string, change CalendarChange) (Calendar, SessionState, State, Language, Error) { //NOSONAR - return s.client.UpdateCalendar(accountId, session, ctx, logger, acceptLanguage, id, change) + func(s *StalwartTest, accountId string, id string, change CalendarChange, ctx Context) (Calendar, SessionState, State, Language, Error) { //NOSONAR + return s.client.UpdateCalendar(accountId, id, change, ctx) }, - func(s *StalwartTest, accountId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, ids []string) (map[string]SetError, SessionState, State, Language, Error) { //NOSONAR - return s.client.DeleteCalendar(accountId, ids, session, ctx, logger, acceptLanguage) + func(s *StalwartTest, accountId string, ids []string, ctx Context) (map[string]SetError, SessionState, State, Language, Error) { //NOSONAR + return s.client.DeleteCalendar(accountId, ids, ctx) }, - func(s *StalwartTest, t *testing.T, accountId string, count uint, session *Session, user User, principalIds []string) (CalendarBoxes, []Calendar, SessionState, State, error) { - return s.fillCalendar(t, accountId, count, session, user, principalIds) + func(s *StalwartTest, t *testing.T, accountId string, count uint, ctx Context, user User, principalIds []string) (CalendarBoxes, []Calendar, SessionState, State, error) { + return s.fillCalendar(t, accountId, count, ctx, user, principalIds) }, func(orig Calendar) CalendarChange { return CalendarChange{ @@ -77,6 +75,7 @@ func TestEvents(t *testing.T) { user := pickUser() session := s.Session(user.name) + ctx := s.Context(session) accountId, calendarId, expectedEventsById, boxes, err := s.fillEvents(t, count, session, user) require.NoError(err) @@ -90,18 +89,49 @@ func TestEvents(t *testing.T) { {Property: CalendarEventPropertyStart, IsAscending: true}, } - contactsByAccount, _, _, _, err := s.client.QueryCalendarEvents([]string{accountId}, session, t.Context(), s.logger, "", filter, sortBy, 0, 0) - require.NoError(err) + { + resultsByAccount, _, _, _, err := s.client.QueryCalendarEvents([]string{accountId}, filter, sortBy, 0, 0, true, ctx) + require.NoError(err) - require.Len(contactsByAccount, 1) - require.Contains(contactsByAccount, accountId) - contacts := contactsByAccount[accountId] - require.Len(contacts, int(count)) + require.Len(resultsByAccount, 1) + require.Contains(resultsByAccount, accountId) + results := resultsByAccount[accountId] + require.Len(results.Results, int(count)) + require.Equal(uint(0), results.Limit) + require.Equal(uint(0), results.Position) + require.Equal(true, results.CanCalculateChanges) + require.NotNil(results.Total) + require.Equal(count, *results.Total) - for _, actual := range contacts { - expected, ok := expectedEventsById[actual.Id] - require.True(ok, "failed to find created contact by its id") - matchEvent(t, actual, expected) + for _, actual := range results.Results { + expected, ok := expectedEventsById[actual.Id] + require.True(ok, "failed to find created contact by its id") + matchEvent(t, actual, expected) + } + } + + { + limit := uint(10) + slices := count / limit + remainder := count + require.Greater(slices, uint(1), "we need to have more than 10 objects in order to test the pagination of search results") + for i := range slices { + position := int(i * limit) + page := min(remainder, limit) + m, _, _, _, err := s.client.QueryCalendarEvents([]string{accountId}, filter, sortBy, position, limit, true, ctx) + fmt.Printf("=== i=%d | limit=%d | remainder=%d | position=%d | limit=%d | results=%d\n", i, limit, remainder, position, limit, len(m[accountId].Results)) + require.NoError(err) + require.Len(m, 1) + require.Contains(m, accountId) + results := m[accountId] + require.Equal(len(results.Results), int(page)) + require.Equal(limit, results.Limit) + require.Equal(position, results.Position) + require.Equal(true, results.CanCalculateChanges) + require.NotNil(results.Total) + require.Equal(count, *results.Total) + remainder -= uint(len(results.Results)) + } } exceptions := []string{} @@ -127,7 +157,7 @@ func (s *StalwartTest) fillCalendar( //NOSONAR t *testing.T, accountId string, count uint, - session *Session, + ctx Context, _ User, principalIds []string, ) (CalendarBoxes, []Calendar, SessionState, State, error) { @@ -180,7 +210,7 @@ func (s *StalwartTest) fillCalendar( //NOSONAR }, } if i%2 == 0 { - cal.SortOrder = posUIntPtr(gofakeit.Uint()) + cal.SortOrder = uintPtr(gofakeit.Uint()) boxes.sortOrdered = true } var sharing *CalendarRights = nil @@ -233,7 +263,7 @@ func (s *StalwartTest) fillCalendar( //NOSONAR cal.ShareWith = m } - a, sessionState, state, _, err := s.client.CreateCalendar(accountId, session, s.ctx, s.logger, "", cal) + a, sessionState, state, _, err := s.client.CreateCalendar(accountId, cal, ctx) if err != nil { return boxes, created, ss, as, err } diff --git a/pkg/jmap/integration_test.go b/pkg/jmap/integration_test.go index 64ffc030a6..f277b44f91 100644 --- a/pkg/jmap/integration_test.go +++ b/pkg/jmap/integration_test.go @@ -175,6 +175,15 @@ func (s *StalwartTest) Close() error { return nil } +func (s *StalwartTest) Context(session *Session) Context { + return Context{ + Session: session, + Context: s.ctx, + Logger: s.logger, + AcceptLanguage: "", + } +} + func (s *StalwartTest) Session(username string) *Session { session, jerr := s.client.FetchSession(s.ctx, s.sessionUrl, username, s.logger) require.NoError(s.t, jerr) @@ -1218,10 +1227,10 @@ func containerTest[OBJ Idable, RESP GetResponse[OBJ], BOXES any, CHANGE Change]( acc func(session *Session) string, obj func(RESP) []OBJ, id func(OBJ) string, - get func(s *StalwartTest, accountId string, session *Session, ctx context.Context, logger *clog.Logger, acceptLanguage string, ids []string) (RESP, SessionState, State, Language, Error), - update func(s *StalwartTest, accountId string, session *Session, ctx context.Context, logger *clog.Logger, acceptLanguage string, id string, change CHANGE) (OBJ, SessionState, State, Language, Error), - destroy func(s *StalwartTest, accountId string, session *Session, ctx context.Context, logger *clog.Logger, acceptLanguage string, ids []string) (map[string]SetError, SessionState, State, Language, Error), - fill func(s *StalwartTest, t *testing.T, accountId string, count uint, session *Session, _ User, principalIds []string) (BOXES, []OBJ, SessionState, State, error), + get func(s *StalwartTest, accountId string, ids []string, ctx Context) (RESP, SessionState, State, Language, Error), + update func(s *StalwartTest, accountId string, id string, change CHANGE, ctx Context) (OBJ, SessionState, State, Language, Error), + destroy func(s *StalwartTest, accountId string, ids []string, ctx Context) (map[string]SetError, SessionState, State, Language, Error), + fill func(s *StalwartTest, t *testing.T, accountId string, count uint, ctx Context, _ User, principalIds []string) (BOXES, []OBJ, SessionState, State, error), change func(OBJ) CHANGE, checkChanged func(t *testing.T, orig OBJ, change CHANGE, changed OBJ), ) { @@ -1233,16 +1242,17 @@ func containerTest[OBJ Idable, RESP GetResponse[OBJ], BOXES any, CHANGE Change]( user := pickUser() session := s.Session(user.name) + ctx := s.Context(session) accountId := acc(session) // we first need to retrieve the list of all the Principals in order to be able to use and test sharing principalIds := []string{} { - principals, _, _, _, err := s.client.GetPrincipals(accountId, session, s.ctx, s.logger, "", []string{}) + principals, _, _, _, err := s.client.GetPrincipals(accountId, []string{}, ctx) require.NoError(err) - require.NotEmpty(principals.Principals) - principalIds = structs.Map(principals.Principals, func(p Principal) string { return p.Id }) + require.NotEmpty(principals.List) + principalIds = structs.Map(principals.List, func(p Principal) string { return p.Id }) } ss := SessionState("") @@ -1252,7 +1262,7 @@ func containerTest[OBJ Idable, RESP GetResponse[OBJ], BOXES any, CHANGE Change]( // from the tests below defaultContainerId := "" { - resp, sessionState, state, _, err := get(s, accountId, session, s.ctx, s.logger, "", []string{}) + resp, sessionState, state, _, err := get(s, accountId, []string{}, ctx) require.NoError(err) require.Empty(resp.GetNotFound()) objs := obj(resp) @@ -1265,7 +1275,7 @@ func containerTest[OBJ Idable, RESP GetResponse[OBJ], BOXES any, CHANGE Change]( // we are going to create a random amount of objects num := uint(5 + rand.Intn(30)) { - boxes, all, sessionState, state, err := fill(s, t, accountId, num, session, user, principalIds) + boxes, all, sessionState, state, err := fill(s, t, accountId, num, ctx, user, principalIds) require.NoError(err) require.Len(all, int(num)) ss = sessionState @@ -1273,7 +1283,7 @@ func containerTest[OBJ Idable, RESP GetResponse[OBJ], BOXES any, CHANGE Change]( { // lets retrieve all the existing objects by passing an empty ID slice - resp, sessionState, state, _, err := get(s, accountId, session, s.ctx, s.logger, "", []string{}) + resp, sessionState, state, _, err := get(s, accountId, []string{}, ctx) require.NoError(err) require.Empty(resp.GetNotFound()) objs := obj(resp) @@ -1300,7 +1310,7 @@ func containerTest[OBJ Idable, RESP GetResponse[OBJ], BOXES any, CHANGE Change]( // lets retrieve every object we created by its ID for _, a := range all { i := id(a) - resp, sessionState, state, _, err := get(s, accountId, session, s.ctx, s.logger, "", []string{i}) + resp, sessionState, state, _, err := get(s, accountId, []string{i}, ctx) require.NoError(err) require.Empty(resp.GetNotFound()) objs := obj(resp) @@ -1314,7 +1324,7 @@ func containerTest[OBJ Idable, RESP GetResponse[OBJ], BOXES any, CHANGE Change]( for _, a := range all { i := id(a) ch := change(a) - changed, sessionState, state, _, err := update(s, accountId, session, s.ctx, s.logger, "", i, ch) + changed, sessionState, state, _, err := update(s, accountId, i, ch, ctx) require.NoError(err) require.NotEqual(a, changed) require.Equal(sessionState, ss) @@ -1325,7 +1335,7 @@ func containerTest[OBJ Idable, RESP GetResponse[OBJ], BOXES any, CHANGE Change]( // now lets delete each object that we created, all at once ids := structs.Map(all, id) { - errMap, sessionState, state, _, err := destroy(s, accountId, session, s.ctx, s.logger, "", ids) + errMap, sessionState, state, _, err := destroy(s, accountId, ids, ctx) require.NoError(err) require.Empty(errMap) require.Equal(sessionState, ss) diff --git a/pkg/jmap/integration_ws_test.go b/pkg/jmap/integration_ws_test.go index 9177d2152c..1a1825d1fb 100644 --- a/pkg/jmap/integration_ws_test.go +++ b/pkg/jmap/integration_ws_test.go @@ -1,7 +1,6 @@ package jmap import ( - "context" "sync" "sync/atomic" "testing" @@ -61,7 +60,7 @@ func TestWs(t *testing.T) { require := require.New(t) - ctx := context.Background() + cotx := t.Context() s, err := newStalwartTest(t) require.NoError(err) @@ -69,11 +68,12 @@ func TestWs(t *testing.T) { user := pickUser() session := s.Session(user.name) + ctx := s.Context(session) mailAccountId := session.PrimaryAccounts.Mail inboxFolder := "" { - _, inboxFolder = s.findInbox(t, mailAccountId, session) + _, inboxFolder = s.findInbox(t, mailAccountId, ctx) } l := &testWsPushListener{t: t, username: user.name, logger: s.logger, mailAccountId: mailAccountId} @@ -90,7 +90,7 @@ func TestWs(t *testing.T) { var initialState State { - changes, sessionState, state, _, err := s.client.GetEmailChanges(mailAccountId, session, s.ctx, s.logger, "", State(""), true, 0, 0) + changes, sessionState, state, _, err := s.client.GetEmailChanges(mailAccountId, EmptyState, true, 0, 0, ctx) require.NoError(err) require.Equal(session.State, sessionState) require.NotEmpty(state) @@ -104,7 +104,7 @@ func TestWs(t *testing.T) { require.NotEmpty(initialState) { - changes, sessionState, state, _, err := s.client.GetEmailChanges(mailAccountId, session, s.ctx, s.logger, "", initialState, true, 0, 0) + changes, sessionState, state, _, err := s.client.GetEmailChanges(mailAccountId, initialState, true, 0, 0, ctx) require.NoError(err) require.Equal(session.State, sessionState) require.Equal(initialState, state) @@ -114,7 +114,7 @@ func TestWs(t *testing.T) { require.Empty(changes.Updated) } - wsc, err := s.client.EnablePushNotifications(ctx, initialState, func() (*Session, error) { return session, nil }) + wsc, err := s.client.EnablePushNotifications(cotx, initialState, func() (*Session, error) { return session, nil }) require.NoError(err) defer wsc.Close() @@ -147,7 +147,7 @@ func TestWs(t *testing.T) { } var lastState State { - changes, sessionState, state, _, err := s.client.GetEmailChanges(mailAccountId, session, s.ctx, s.logger, "", initialState, true, 0, 0) + changes, sessionState, state, _, err := s.client.GetEmailChanges(mailAccountId, initialState, true, 0, 0, ctx) require.NoError(err) require.Equal(session.State, sessionState) require.NotEqual(initialState, state) @@ -181,7 +181,7 @@ func TestWs(t *testing.T) { l.m.Unlock() } { - changes, sessionState, state, _, err := s.client.GetEmailChanges(mailAccountId, session, s.ctx, s.logger, "", lastState, true, 0, 0) + changes, sessionState, state, _, err := s.client.GetEmailChanges(mailAccountId, lastState, true, 0, 0, ctx) require.NoError(err) require.Equal(session.State, sessionState) require.NotEqual(lastState, state) @@ -215,7 +215,7 @@ func TestWs(t *testing.T) { l.m.Unlock() } { - changes, sessionState, state, _, err := s.client.GetEmailChanges(mailAccountId, session, s.ctx, s.logger, "", lastState, true, 0, 0) + changes, sessionState, state, _, err := s.client.GetEmailChanges(mailAccountId, lastState, true, 0, 0, ctx) require.NoError(err) require.Equal(session.State, sessionState) require.NotEqual(lastState, state) diff --git a/pkg/jmap/model.go b/pkg/jmap/model.go index a09e59ffce..4b28edba03 100644 --- a/pkg/jmap/model.go +++ b/pkg/jmap/model.go @@ -1354,13 +1354,32 @@ type ChangesResponse[T Foo] interface { GetMarker() T } -type QueryCommand interface { +type QueryCommand[T Foo] interface { JmapCommand - GetResponse() QueryResponse + GetResponse() QueryResponse[T] } -type QueryResponse interface { +type QueryResponse[T Foo] interface { GetQueryState() State + GetMarker() T +} + +type UploadCommand[T Foo] interface { + JmapCommand + GetResponse() UploadResponse[T] +} + +type UploadResponse[T Foo] interface { + GetMarker() T +} + +type ParseCommand[T Foo] interface { + JmapCommand + GetResponse() ParseResponse[T] +} + +type ParseResponse[T Foo] interface { + GetMarker() T } type ChangesTemplate[T Foo] struct { @@ -1372,6 +1391,22 @@ type ChangesTemplate[T Foo] struct { Destroyed []string `json:"destroyed,omitempty"` } +type SearchResultsTemplate[T Foo] struct { + Results []T `json:"results"` + CanCalculateChanges bool `json:"canCalculateChanges"` + Position uint `json:"position"` + Limit uint `json:"limit,omitzero"` + Total *uint `json:"total,omitzero"` +} + +type SearchResults[T Foo] interface { + GetResults() []T + GetCanCalculateChanges() bool + GetPosition() uint + GetTotal() *uint + GetLimit() uint +} + type FilterOperatorTerm string const ( @@ -1656,8 +1691,8 @@ type MailboxSetResponse struct { AccountId string `json:"accountId"` OldState State `json:"oldState,omitempty"` NewState State `json:"newState,omitempty"` - Created map[string]Mailbox `json:"created,omitempty"` - Updated map[string]Mailbox `json:"updated,omitempty"` + Created map[string]*Mailbox `json:"created,omitempty"` + Updated map[string]*Mailbox `json:"updated,omitempty"` Destroyed []string `json:"destroyed,omitempty"` NotCreated map[string]SetError `json:"notCreated,omitempty"` NotUpdated map[string]SetError `json:"notUpdated,omitempty"` @@ -1717,11 +1752,11 @@ type MailboxQueryCommand struct { FilterAsTree bool `json:"filterAsTree,omitempty"` } -var _ QueryCommand = &MailboxQueryCommand{} +var _ QueryCommand[Mailbox] = &MailboxQueryCommand{} -func (c MailboxQueryCommand) GetCommand() Command { return CommandMailboxQuery } -func (c MailboxQueryCommand) GetObjectType() ObjectType { return MailboxType } -func (c MailboxQueryCommand) GetResponse() QueryResponse { return MailboxQueryResponse{} } +func (c MailboxQueryCommand) GetCommand() Command { return CommandMailboxQuery } +func (c MailboxQueryCommand) GetObjectType() ObjectType { return MailboxType } +func (c MailboxQueryCommand) GetResponse() QueryResponse[Mailbox] { return MailboxQueryResponse{} } type EmailFilterElement interface { _isAnEmailFilterElement() // marker method @@ -2013,11 +2048,11 @@ type EmailQueryCommand struct { CalculateTotal bool `json:"calculateTotal,omitempty"` } -var _ QueryCommand = &EmailQueryCommand{} +var _ QueryCommand[Email] = &EmailQueryCommand{} -func (c EmailQueryCommand) GetCommand() Command { return CommandEmailQuery } -func (c EmailQueryCommand) GetObjectType() ObjectType { return MailboxType } -func (c EmailQueryCommand) GetResponse() QueryResponse { return EmailQueryResponse{} } +func (c EmailQueryCommand) GetCommand() Command { return CommandEmailQuery } +func (c EmailQueryCommand) GetObjectType() ObjectType { return MailboxType } +func (c EmailQueryCommand) GetResponse() QueryResponse[Email] { return EmailQueryResponse{} } type EmailGetCommand struct { // The ids of the Email objects to return. @@ -3063,9 +3098,10 @@ type EmailQueryResponse struct { Limit uint `json:"limit,omitempty,omitzero"` } -var _ QueryResponse = &EmailQueryResponse{} +var _ QueryResponse[Email] = &EmailQueryResponse{} func (r EmailQueryResponse) GetQueryState() State { return r.QueryState } +func (r EmailQueryResponse) GetMarker() Email { return Email{} } type EmailGetResponse struct { // The id of the account used for the call. @@ -3284,9 +3320,10 @@ type MailboxQueryResponse struct { Limit int `json:"limit,omitzero"` } -var _ QueryResponse = &MailboxQueryResponse{} +var _ QueryResponse[Mailbox] = &MailboxQueryResponse{} func (r MailboxQueryResponse) GetQueryState() State { return r.QueryState } +func (r MailboxQueryResponse) GetMarker() Mailbox { return Mailbox{} } type EmailCreate struct { // The set of Mailbox ids this Email belongs to. @@ -3727,11 +3764,11 @@ func (r IdentityChangesResponse) GetDestroyed() []string { return r.Destroyed } func (r IdentityChangesResponse) GetMarker() Identity { return Identity{} } type IdentitySetCommand struct { - AccountId string `json:"accountId"` - IfInState string `json:"ifInState,omitempty"` - Create map[string]Identity `json:"create,omitempty"` - Update map[string]PatchObject `json:"update,omitempty"` - Destroy []string `json:"destroy,omitempty"` + AccountId string `json:"accountId"` + IfInState string `json:"ifInState,omitempty"` + Create map[string]IdentityChange `json:"create,omitempty"` + Update map[string]PatchObject `json:"update,omitempty"` + Destroy []string `json:"destroy,omitempty"` } var _ SetCommand[Identity] = &IdentitySetCommand{} @@ -3741,15 +3778,15 @@ func (c IdentitySetCommand) GetObjectType() ObjectType { return Identit func (c IdentitySetCommand) GetResponse() SetResponse[Identity] { return IdentitySetResponse{} } type IdentitySetResponse struct { - AccountId string `json:"accountId"` - OldState State `json:"oldState,omitempty"` - NewState State `json:"newState,omitempty"` - Created map[string]Identity `json:"created,omitempty"` - Updated map[string]Identity `json:"updated,omitempty"` - Destroyed []string `json:"destroyed,omitempty"` - NotCreated map[string]SetError `json:"notCreated,omitempty"` - NotUpdated map[string]SetError `json:"notUpdated,omitempty"` - NotDestroyed map[string]SetError `json:"notDestroyed,omitempty"` + AccountId string `json:"accountId"` + OldState State `json:"oldState,omitempty"` + NewState State `json:"newState,omitempty"` + Created map[string]*Identity `json:"created,omitempty"` + Updated map[string]*Identity `json:"updated,omitempty"` + Destroyed []string `json:"destroyed,omitempty"` + NotCreated map[string]SetError `json:"notCreated,omitempty"` + NotUpdated map[string]SetError `json:"notUpdated,omitempty"` + NotDestroyed map[string]SetError `json:"notDestroyed,omitempty"` } var _ SetResponse[Identity] = &IdentitySetResponse{} @@ -3810,9 +3847,41 @@ var _ Idable = &Identity{} func (f Identity) GetObjectType() ObjectType { return IdentityType } func (f Identity) GetId() string { return f.Id } -var _ Change = Identity{} +type IdentityChange struct { + // The “From” name the client SHOULD use when creating a new Email from this Identity. + Name string `json:"name,omitempty" doc:"req"` -func (i Identity) AsPatch() PatchObject { + // The “From” email address the client MUST use when creating a new Email from this Identity. + // + // If the mailbox part of the address (the section before the “@”) is the single character + // * (e.g., *@example.com) then the client may use any valid address ending in that domain + // (e.g., foo@example.com). + Email string `json:"email,omitempty"` + + // The Reply-To value the client SHOULD set when creating a new Email from this Identity. + ReplyTo string `json:"replyTo,omitempty"` + + // The Bcc value the client SHOULD set when creating a new Email from this Identity. + Bcc *[]EmailAddress `json:"bcc,omitempty"` + + // A signature the client SHOULD insert into new plaintext messages that will be sent from + // this Identity. + // + // Clients MAY ignore this and/or combine this with a client-specific signature preference. + TextSignature *string `json:"textSignature,omitempty"` + + // A signature the client SHOULD insert into new HTML messages that will be sent from this + // Identity. + // + // This text MUST be an HTML snippet to be inserted into the section of the HTML. + // + // Clients MAY ignore this and/or combine this with a client-specific signature preference. + HtmlSignature *string `json:"htmlSignature,omitempty"` +} + +var _ Change = IdentityChange{} + +func (i IdentityChange) AsPatch() PatchObject { p := PatchObject{} if i.Name != "" { p["name"] = i.Name @@ -3985,10 +4054,11 @@ type BlobUploadCommand struct { Create map[string]UploadObject `json:"create"` } -var _ JmapCommand = &BlobUploadCommand{} +var _ UploadCommand[Blob] = &BlobUploadCommand{} -func (c BlobUploadCommand) GetCommand() Command { return CommandBlobUpload } -func (c BlobUploadCommand) GetObjectType() ObjectType { return BlobType } +func (c BlobUploadCommand) GetCommand() Command { return CommandBlobUpload } +func (c BlobUploadCommand) GetObjectType() ObjectType { return BlobType } +func (c BlobUploadCommand) GetResponse() UploadResponse[Blob] { return BlobUploadResponse{} } type BlobUploadCreateResult struct { Id string `json:"id"` @@ -4001,6 +4071,10 @@ type BlobUploadResponse struct { Created map[string]BlobUploadCreateResult `json:"created"` } +var _ UploadResponse[Blob] = &BlobUploadResponse{} + +func (r BlobUploadResponse) GetMarker() Blob { return Blob{} } + const ( BlobPropertyDataAsText = "data:asText" BlobPropertyDataAsBase64 = "data:asBase64" @@ -5837,6 +5911,28 @@ var _ Idable = &Principal{} func (f Principal) GetObjectType() ObjectType { return PrincipalType } func (f Principal) GetId() string { return f.Id } +const ( + PrincipalPropertyId = "id" + PrincipalPropertyType = "type" + PrincipalPropertyName = "name" + PrincipalPropertyDescription = "description" + PrincipalPropertyEmail = "email" + PrincipalPropertyTimeZone = "timeZone" + PrincipalPropertyCapabilities = "capabilites" + PrincipalPropertyAccounts = "accounts" +) + +var PrincipalProperties = []string{ + PrincipalPropertyId, + PrincipalPropertyType, + PrincipalPropertyName, + PrincipalPropertyDescription, + PrincipalPropertyEmail, + PrincipalPropertyTimeZone, + PrincipalPropertyCapabilities, + PrincipalPropertyAccounts, +} + type ShareChangePerson struct { // The name of the person who made the change. Name string `json:"name"` @@ -6589,7 +6685,7 @@ type ContactCardQueryCommand struct { // // If the index is greater than or equal to the total number of objects in the results // list, then the ids array in the response will be empty, but this is not an error. - Position uint `json:"position,omitzero" default:"0" doc:"opt"` + Position int `json:"position,omitzero" default:"0" doc:"opt"` // An Email id. // @@ -6615,7 +6711,7 @@ type ContactCardQueryCommand struct { // to the maximum; the new limit is returned with the response so the client is aware. // // If a negative value is given, the call MUST be rejected with an invalidArguments error. - Limit uint `json:"limit,omitzero" doc:"opt"` + Limit *uint `json:"limit,omitzero" doc:"opt"` // Does the client wish to know the total number of results in the query? // @@ -6624,11 +6720,13 @@ type ContactCardQueryCommand struct { CalculateTotal bool `json:"calculateTotal,omitzero"` } -var _ QueryCommand = &ContactCardQueryCommand{} +var _ QueryCommand[ContactCard] = &ContactCardQueryCommand{} -func (c ContactCardQueryCommand) GetCommand() Command { return CommandContactCardQuery } -func (c ContactCardQueryCommand) GetObjectType() ObjectType { return ContactCardType } -func (c ContactCardQueryCommand) GetResponse() QueryResponse { return ContactCardQueryResponse{} } +func (c ContactCardQueryCommand) GetCommand() Command { return CommandContactCardQuery } +func (c ContactCardQueryCommand) GetObjectType() ObjectType { return ContactCardType } +func (c ContactCardQueryCommand) GetResponse() QueryResponse[ContactCard] { + return ContactCardQueryResponse{} +} type ContactCardQueryResponse struct { // The id of the account used for the call. @@ -6680,9 +6778,10 @@ type ContactCardQueryResponse struct { Limit uint `json:"limit,omitempty,omitzero"` } -var _ QueryResponse = &ContactCardQueryResponse{} +var _ QueryResponse[ContactCard] = &ContactCardQueryResponse{} -func (r ContactCardQueryResponse) GetQueryState() State { return r.QueryState } +func (r ContactCardQueryResponse) GetQueryState() State { return r.QueryState } +func (r ContactCardQueryResponse) GetMarker() ContactCard { return ContactCard{} } type ContactCardGetCommand struct { // The ids of the ContactCard objects to return. @@ -6960,10 +7059,13 @@ type CalendarEventParseCommand struct { Properties []string `json:"properties,omitempty"` } -var _ JmapCommand = &CalendarEventParseCommand{} +var _ ParseCommand[CalendarEvent] = &CalendarEventParseCommand{} func (c CalendarEventParseCommand) GetCommand() Command { return CommandCalendarEventParse } func (c CalendarEventParseCommand) GetObjectType() ObjectType { return CalendarEventType } +func (c CalendarEventParseCommand) GetResponse() ParseResponse[CalendarEvent] { + return CalendarEventParseResponse{} +} type CalendarEventParseResponse struct { // The id of the account used for the call. @@ -6981,6 +7083,10 @@ type CalendarEventParseResponse struct { NotParsable []string `json:"notParsable,omitempty"` } +var _ ParseResponse[CalendarEvent] = &CalendarEventParseResponse{} + +func (r CalendarEventParseResponse) GetMarker() CalendarEvent { return CalendarEvent{} } + type CalendarGetCommand struct { AccountId string `json:"accountId"` Ids []string `json:"ids,omitempty"` @@ -7309,7 +7415,7 @@ type CalendarEventQueryCommand struct { // // If the index is greater than or equal to the total number of objects in the results // list, then the ids array in the response will be empty, but this is not an error. - Position uint `json:"position,omitempty" doc:"opt"` + Position int `json:"position,omitempty" doc:"opt"` // An Email id. // @@ -7335,7 +7441,7 @@ type CalendarEventQueryCommand struct { // to the maximum; the new limit is returned with the response so the client is aware. // // If a negative value is given, the call MUST be rejected with an invalidArguments error. - Limit uint `json:"limit,omitempty" doc:"opt" default:"0"` + Limit *uint `json:"limit,omitempty" doc:"opt" default:"0"` // Does the client wish to know the total number of results in the query? // @@ -7344,11 +7450,13 @@ type CalendarEventQueryCommand struct { CalculateTotal bool `json:"calculateTotal,omitempty" doc:"opt" default:"false"` } -var _ QueryCommand = &CalendarEventQueryCommand{} +var _ QueryCommand[CalendarEvent] = &CalendarEventQueryCommand{} -func (c CalendarEventQueryCommand) GetCommand() Command { return CommandCalendarEventQuery } -func (c CalendarEventQueryCommand) GetObjectType() ObjectType { return CalendarEventType } -func (c CalendarEventQueryCommand) GetResponse() QueryResponse { return CalendarEventQueryResponse{} } +func (c CalendarEventQueryCommand) GetCommand() Command { return CommandCalendarEventQuery } +func (c CalendarEventQueryCommand) GetObjectType() ObjectType { return CalendarEventType } +func (c CalendarEventQueryCommand) GetResponse() QueryResponse[CalendarEvent] { + return CalendarEventQueryResponse{} +} type CalendarEventQueryResponse struct { // The id of the account used for the call. @@ -7400,9 +7508,10 @@ type CalendarEventQueryResponse struct { Limit uint `json:"limit,omitempty,omitzero"` } -var _ QueryResponse = &CalendarEventQueryResponse{} +var _ QueryResponse[CalendarEvent] = &CalendarEventQueryResponse{} -func (r CalendarEventQueryResponse) GetQueryState() State { return r.QueryState } +func (r CalendarEventQueryResponse) GetQueryState() State { return r.QueryState } +func (r CalendarEventQueryResponse) GetMarker() CalendarEvent { return CalendarEvent{} } type CalendarEventGetCommand struct { // The ids of the CalendarEvent objects to return. @@ -7771,11 +7880,60 @@ type PrincipalComparator struct { } type PrincipalQueryCommand struct { - AccountId string `json:"accountId"` - Filter PrincipalFilterElement `json:"filter,omitempty"` - Sort []PrincipalComparator `json:"sort,omitempty"` - SortAsTree bool `json:"sortAsTree,omitempty"` - FilterAsTree bool `json:"filterAsTree,omitempty"` + AccountId string `json:"accountId"` + Filter PrincipalFilterElement `json:"filter,omitempty"` + Sort []PrincipalComparator `json:"sort,omitempty"` + + // The zero-based index of the first id in the full list of results to return. + // + // If a negative value is given, it is an offset from the end of the list. + // Specifically, the negative value MUST be added to the total number of results given + // the filter, and if still negative, it’s clamped to 0. This is now the zero-based + // index of the first id to return. + // + // If the index is greater than or equal to the total number of objects in the results + // list, then the ids array in the response will be empty, but this is not an error. + Position uint `json:"position,omitzero" default:"0" doc:"opt"` + + // An Email id. + // + // If supplied, the position argument is ignored. + // The index of this id in the results will be used in combination with the anchorOffset + // argument to determine the index of the first result to return. + Anchor string `json:"anchor,omitempty" doc:"opt"` + + // The index of the first result to return relative to the index of the anchor, + // if an anchor is given. + // + // This MAY be negative. + // + // For example, -1 means the Principal immediately preceding the anchor is the first result in + // the list returned. + AnchorOffset int `json:"anchorOffset,omitzero" default:"0" doc:"opt"` + + // The maximum number of results to return. + // + // If null, no limit presumed. + // The server MAY choose to enforce a maximum limit argument. + // In this case, if a greater value is given (or if it is null), the limit is clamped + // to the maximum; the new limit is returned with the response so the client is aware. + // + // If a negative value is given, the call MUST be rejected with an invalidArguments error. + Limit uint `json:"limit,omitzero" doc:"opt"` + + // Does the client wish to know the total number of results in the query? + // + // This may be slow and expensive for servers to calculate, particularly with complex filters, + // so clients should take care to only request the total when needed. + CalculateTotal bool `json:"calculateTotal,omitzero"` +} + +var _ QueryCommand[Principal] = PrincipalQueryCommand{} + +func (c PrincipalQueryCommand) GetCommand() Command { return CommandPrincipalQuery } +func (c PrincipalQueryCommand) GetObjectType() ObjectType { return PrincipalType } +func (c PrincipalQueryCommand) GetResponse() QueryResponse[Principal] { + return PrincipalQueryResponse{} } type PrincipalQueryResponse struct { @@ -7796,38 +7954,39 @@ type PrincipalQueryResponse struct { // // Should a client receive back a response with a different queryState string to a previous call, it MUST either // throw away the currently cached query and fetch it again (note, this does not require fetching the records - // again, just the list of ids) or call Mailbox/queryChanges to get the difference. + // again, just the list of ids) or call Principal/queryChanges to get the difference. QueryState State `json:"queryState"` - // This is true if the server supports calling Mailbox/queryChanges with these filter/sort parameters. + // This is true if the server supports calling Principal/queryChanges with these filter/sort parameters. // - // Note, this does not guarantee that the Mailbox/queryChanges call will succeed, as it may only be possible for + // Note, this does not guarantee that the Principal/queryChanges call will succeed, as it may only be possible for // a limited time afterwards due to server internal implementation details. CanCalculateChanges bool `json:"canCalculateChanges"` // The zero-based index of the first result in the ids array within the complete list of query results. - Position int `json:"position"` + Position uint `json:"position"` - // The list of ids for each Mailbox in the query results, starting at the index given by the position argument + // The list of ids for each Principal in the query results, starting at the index given by the position argument // of this response and continuing until it hits the end of the results or reaches the limit number of ids. // // If position is >= total, this MUST be the empty list. Ids []string `json:"ids"` - // The total number of Mailbox in the results (given the filter) (only if requested). + // The total number of Principal in the results (given the filter) (only if requested). // // This argument MUST be omitted if the calculateTotal request argument is not true. - Total int `json:"total,omitzero"` + Total uint `json:"total,omitzero"` // The limit enforced by the server on the maximum number of results to return (if set by the server). // // This is only returned if the server set a limit or used a different limit than that given in the request. - Limit int `json:"limit,omitzero"` + Limit uint `json:"limit,omitzero"` } -var _ QueryResponse = &PrincipalQueryResponse{} +var _ QueryResponse[Principal] = &PrincipalQueryResponse{} func (r PrincipalQueryResponse) GetQueryState() State { return r.QueryState } +func (r PrincipalQueryResponse) GetMarker() Principal { return Principal{} } type ErrorResponse struct { Type string `json:"type"` diff --git a/pkg/jmap/templates.go b/pkg/jmap/templates.go index 94cacfe42b..345a005443 100644 --- a/pkg/jmap/templates.go +++ b/pkg/jmap/templates.go @@ -1,7 +1,6 @@ package jmap import ( - "context" "fmt" "github.com/opencloud-eu/opencloud/pkg/log" @@ -48,25 +47,28 @@ func (f Mailboxes) MapChanges(oldState, newState State, hasMoreChanges bool, cre func fget[F Factory[T, GETREQ, GETRESP, CHANGES], T Foo, GETREQ GetCommand[T], GETRESP GetResponse[T], CHANGES any](f Factory[T, GETREQ, GETRESP, CHANGES], //NOSONAR client *Client, name string, accountId string, ids []string, - session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string) (GETRESP, SessionState, State, Language, Error) { + ctx Context) (GETRESP, SessionState, State, Language, Error) { var getresp GETRESP return get(client, name, f.Namespaces(), f.CreateGetCommand, getresp, identity1, - accountId, session, ctx, logger, acceptLanguage, ids, + accountId, ids, + ctx, ) } func fgetA[F Factory[T, GETREQ, GETRESP, CHANGES], T Foo, GETREQ GetCommand[T], GETRESP GetResponse[T], CHANGES any](f Factory[T, GETREQ, GETRESP, CHANGES], //NOSONAR client *Client, name string, accountId string, ids []string, - session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string) ([]T, SessionState, State, Language, Error) { + ctx Context) ([]T, SessionState, State, Language, Error) { var getresp GETRESP return getA(client, name, f.Namespaces(), f.CreateGetCommand, getresp, - accountId, session, ctx, logger, acceptLanguage, ids, + accountId, + ids, + ctx, ) } @@ -75,22 +77,20 @@ func get[T Foo, GETREQ GetCommand[T], GETRESP GetResponse[T], RESP any]( //NOSON getCommandFactory func(string, []string) GETREQ, _ GETRESP, mapper func(GETRESP) RESP, - accountId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, ids []string) (RESP, SessionState, State, Language, Error) { - logger = client.logger(name, session, logger) + accountId string, ids []string, ctx Context) (RESP, SessionState, State, Language, Error) { + ctx = ctx.WithLogger(client.logger(name, ctx)) var zero RESP get := getCommandFactory(accountId, ids) - cmd, err := client.request(session, logger, using, - invocation(get, "0"), - ) + cmd, err := client.request(ctx, using, invocation(get, "0")) if err != nil { return zero, "", "", "", err } - return command(client.api, logger, ctx, session, client.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (RESP, State, Error) { + return command(client, ctx, cmd, func(body *Response) (RESP, State, Error) { var response GETRESP - err = retrieveGet(logger, body, get, "0", &response) + err = retrieveGet(ctx, body, get, "0", &response) if err != nil { return zero, "", err } @@ -103,21 +103,22 @@ func getA[T Foo, GETREQ GetCommand[T], GETRESP GetResponse[T]]( //NOSONAR client *Client, name string, using []JmapNamespace, getCommandFactory func(string, []string) GETREQ, resp GETRESP, - accountId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, ids []string) ([]T, SessionState, State, Language, Error) { - return get(client, name, using, getCommandFactory, resp, func(r GETRESP) []T { return r.GetList() }, accountId, session, ctx, logger, acceptLanguage, ids) + accountId string, ids []string, ctx Context) ([]T, SessionState, State, Language, Error) { + return get(client, name, using, getCommandFactory, resp, func(r GETRESP) []T { return r.GetList() }, accountId, ids, ctx) } func fgetAN[F Factory[T, GETREQ, GETRESP, CHANGES], T Foo, GETREQ GetCommand[T], GETRESP GetResponse[T], RESP any, CHANGES any](f Factory[T, GETREQ, GETRESP, CHANGES], //NOSONAR client *Client, name string, respMapper func(map[string][]T) RESP, accountIds []string, ids []string, - session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string) (RESP, SessionState, State, Language, Error) { + ctx Context) (RESP, SessionState, State, Language, Error) { var getresp GETRESP return getAN(client, name, f.Namespaces(), f.CreateGetCommand, getresp, respMapper, - accountIds, session, ctx, logger, acceptLanguage, ids, + accountIds, ids, + ctx, ) } @@ -126,11 +127,12 @@ func getAN[T Foo, GETREQ GetCommand[T], GETRESP GetResponse[T], RESP any]( //NOS getCommandFactory func(string, []string) GETREQ, resp GETRESP, respMapper func(map[string][]T) RESP, - accountIds []string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, ids []string) (RESP, SessionState, State, Language, Error) { + accountIds []string, ids []string, ctx Context) (RESP, SessionState, State, Language, Error) { return getN(client, name, using, getCommandFactory, resp, func(r GETRESP) []T { return r.GetList() }, respMapper, - accountIds, session, ctx, logger, acceptLanguage, ids, + accountIds, ids, + ctx, ) } @@ -140,8 +142,9 @@ func getN[T Foo, ITEM any, GETREQ GetCommand[T], GETRESP GetResponse[T], RESP an _ GETRESP, itemMapper func(GETRESP) ITEM, respMapper func(map[string]ITEM) RESP, - accountIds []string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, ids []string) (RESP, SessionState, State, Language, Error) { - logger = client.logger(name, session, logger) + accountIds []string, ids []string, ctx Context) (RESP, SessionState, State, Language, Error) { + logger := client.logger(name, ctx) + ctx = ctx.WithLogger(logger) var zero RESP @@ -155,17 +158,17 @@ func getN[T Foo, ITEM any, GETREQ GetCommand[T], GETRESP GetResponse[T], RESP an invocations[i] = invocation(get, mcid(accountId, "0")) } - cmd, err := client.request(session, logger, using, invocations...) + cmd, err := client.request(ctx, using, invocations...) if err != nil { return zero, "", "", "", err } - return command(client.api, logger, ctx, session, client.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (RESP, State, Error) { + return command(client, ctx, cmd, func(body *Response) (RESP, State, Error) { result := map[string]ITEM{} responses := map[string]GETRESP{} for _, accountId := range uniqueAccountIds { var resp GETRESP - err = retrieveResponseMatchParameters(logger, body, c, mcid(accountId, "0"), &resp) + err = retrieveResponseMatchParameters(ctx, body, c, mcid(accountId, "0"), &resp) if err != nil { return zero, "", err } @@ -182,13 +185,15 @@ func create[T Foo, C any, SETREQ SetCommand[T], GETREQ GetCommand[T], SETRESP Se getCommandFactory func(string, string) GETREQ, createdMapper func(SETRESP) map[string]*T, listMapper func(GETRESP) []T, - accountId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, create C) (*T, SessionState, State, Language, Error) { - logger = client.logger(name, session, logger) + accountId string, create C, + ctx Context) (*T, SessionState, State, Language, Error) { + logger := client.logger(name, ctx) + ctx = ctx.WithLogger(logger) createMap := map[string]C{"c": create} get := getCommandFactory(accountId, "#c") set := setCommandFactory(accountId, createMap) - cmd, err := client.request(session, logger, using, + cmd, err := client.request(ctx, using, invocation(set, "0"), invocation(get, "1"), ) @@ -196,9 +201,9 @@ func create[T Foo, C any, SETREQ SetCommand[T], GETREQ GetCommand[T], SETRESP Se return nil, "", "", "", err } - return command(client.api, logger, ctx, session, client.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (*T, State, Error) { + return command(client, ctx, cmd, func(body *Response) (*T, State, Error) { var setResponse SETRESP - err = retrieveSet(logger, body, set, "0", &setResponse) + err = retrieveSet(ctx, body, set, "0", &setResponse) if err != nil { return nil, "", err } @@ -218,7 +223,7 @@ func create[T Foo, C any, SETREQ SetCommand[T], GETREQ GetCommand[T], SETRESP Se } var getResponse GETRESP - err = retrieveGet(logger, body, get, "1", &getResponse) + err = retrieveGet(ctx, body, get, "1", &getResponse) if err != nil { return nil, "", err } @@ -237,20 +242,21 @@ func create[T Foo, C any, SETREQ SetCommand[T], GETREQ GetCommand[T], SETRESP Se func destroy[T Foo, REQ SetCommand[T], RESP SetResponse[T]](client *Client, name string, using []JmapNamespace, //NOSONAR setCommandFactory func(string, []string) REQ, _ RESP, - accountId string, destroy []string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string) (map[string]SetError, SessionState, State, Language, Error) { - logger = client.logger(name, session, logger) + accountId string, destroy []string, ctx Context) (map[string]SetError, SessionState, State, Language, Error) { + logger := client.logger(name, ctx) + ctx = ctx.WithLogger(logger) set := setCommandFactory(accountId, destroy) - cmd, err := client.request(session, logger, using, + cmd, err := client.request(ctx, using, invocation(set, "0"), ) if err != nil { return nil, "", "", "", err } - return command(client.api, logger, ctx, session, client.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (map[string]SetError, State, Error) { + return command(client, ctx, cmd, func(body *Response) (map[string]SetError, State, Error) { var setResponse RESP - err = retrieveSet(logger, body, set, "0", &setResponse) + err = retrieveSet(ctx, body, set, "0", &setResponse) if err != nil { return nil, "", err } @@ -265,12 +271,12 @@ func changesA[T Foo, CHANGESREQ ChangesCommand[T], GETREQ GetCommand[T], CHANGES _ GETRESP, getCommandFactory func(string, string) GETREQ, respMapper func(State, State, bool, []T, []T, []string) RESP, - session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string) (RESP, SessionState, State, Language, Error) { + ctx Context) (RESP, SessionState, State, Language, Error) { return changes(client, name, using, changesCommandFactory, changesResp, getCommandFactory, func(r GETRESP) []T { return r.GetList() }, respMapper, - session, ctx, logger, acceptLanguage, + ctx, ) } @@ -281,15 +287,15 @@ func changes[T Foo, CHANGESREQ ChangesCommand[T], GETREQ GetCommand[T], CHANGESR getCommandFactory func(string, string) GETREQ, getMapper func(GETRESP) []ITEM, respMapper func(State, State, bool, []ITEM, []ITEM, []string) RESP, - session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string) (RESP, SessionState, State, Language, Error) { - logger = client.logger(name, session, logger) + ctx Context) (RESP, SessionState, State, Language, Error) { + logger := client.logger(name, ctx) var zero RESP changes := changesCommandFactory() getCreated := getCommandFactory("/created", "0") //NOSONAR getUpdated := getCommandFactory("/updated", "0") //NOSONAR - cmd, err := client.request(session, logger, using, + cmd, err := client.request(ctx.WithLogger(logger), using, invocation(changes, "0"), invocation(getCreated, "1"), invocation(getUpdated, "2"), @@ -298,22 +304,22 @@ func changes[T Foo, CHANGESREQ ChangesCommand[T], GETREQ GetCommand[T], CHANGESR return zero, "", "", "", err } - return command(client.api, logger, ctx, session, client.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (RESP, State, Error) { + return command(client, ctx, cmd, func(body *Response) (RESP, State, Error) { var changesResponse CHANGESRESP - err = retrieveChanges(logger, body, changes, "0", &changesResponse) + err = retrieveChanges(ctx, body, changes, "0", &changesResponse) if err != nil { return zero, "", err } var createdResponse GETRESP - err = retrieveGet(logger, body, getCreated, "1", &createdResponse) + err = retrieveGet(ctx, body, getCreated, "1", &createdResponse) if err != nil { logger.Error().Err(err).Send() return zero, "", err } var updatedResponse GETRESP - err = retrieveGet(logger, body, getUpdated, "2", &updatedResponse) + err = retrieveGet(ctx, body, getUpdated, "2", &updatedResponse) if err != nil { logger.Error().Err(err).Send() return zero, "", err @@ -338,8 +344,8 @@ func changesN[T Foo, CHANGESREQ ChangesCommand[T], GETREQ GetCommand[T], CHANGES changesItemMapper func(State, State, bool, []ITEM, []ITEM, []string) CHANGESITEM, respMapper func(map[string]CHANGESITEM) RESP, stateMapper func(GETRESP) State, - session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string) (RESP, SessionState, State, Language, Error) { - logger = client.loggerParams(name, session, logger, func(z zerolog.Context) zerolog.Context { + ctx Context) (RESP, SessionState, State, Language, Error) { + logger := client.loggerParams(name, ctx, func(z zerolog.Context) zerolog.Context { sinceStateLogDict := zerolog.Dict() for k, v := range sinceStateMap { sinceStateLogDict.Str(log.SafeString(k), log.SafeString(string(v))) @@ -377,29 +383,31 @@ func changesN[T Foo, CHANGESREQ ChangesCommand[T], GETREQ GetCommand[T], CHANGES getCommand = getCreated.GetCommand() } - cmd, err := client.request(session, logger, using, invocations...) + ctx = ctx.WithLogger(logger) + + cmd, err := client.request(ctx, using, invocations...) if err != nil { return zero, "", "", "", err } - return command(client.api, logger, ctx, session, client.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (RESP, State, Error) { + return command(client, ctx, cmd, func(body *Response) (RESP, State, Error) { changesItemByAccount := make(map[string]CHANGESITEM, n) stateByAccountId := make(map[string]State, n) for _, accountId := range uniqueAccountIds { var changesResponse CHANGESRESP - err = retrieveResponseMatchParameters(logger, body, changesCommand, mcid(accountId, "0"), &changesResponse) + err = retrieveResponseMatchParameters(ctx, body, changesCommand, mcid(accountId, "0"), &changesResponse) if err != nil { return zero, "", err } var createdResponse GETRESP - err = retrieveResponseMatchParameters(logger, body, getCommand, mcid(accountId, "1"), &createdResponse) + err = retrieveResponseMatchParameters(ctx, body, getCommand, mcid(accountId, "1"), &createdResponse) if err != nil { return zero, "", err } var updatedResponse GETRESP - err = retrieveResponseMatchParameters(logger, body, getCommand, mcid(accountId, "2"), &updatedResponse) + err = retrieveResponseMatchParameters(ctx, body, getCommand, mcid(accountId, "2"), &updatedResponse) if err != nil { return zero, "", err } @@ -420,13 +428,14 @@ func updates[T Foo, CHANGESREQ ChangesCommand[T], GETREQ GetCommand[T], CHANGESR getCommandFactory func(string, string) GETREQ, getMapper func(GETRESP) []ITEM, respMapper func(State, State, bool, []ITEM) RESP, - session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string) (RESP, SessionState, State, Language, Error) { - logger = client.logger(name, session, logger) + ctx Context) (RESP, SessionState, State, Language, Error) { + logger := client.logger(name, ctx) + ctx = ctx.WithLogger(logger) var zero RESP changes := changesCommandFactory() getUpdated := getCommandFactory("/updated", "0") //NOSONAR - cmd, err := client.request(session, logger, using, + cmd, err := client.request(ctx, using, invocation(changes, "0"), invocation(getUpdated, "1"), ) @@ -434,15 +443,15 @@ func updates[T Foo, CHANGESREQ ChangesCommand[T], GETREQ GetCommand[T], CHANGESR return zero, "", "", "", err } - return command(client.api, logger, ctx, session, client.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (RESP, State, Error) { + return command(client, ctx, cmd, func(body *Response) (RESP, State, Error) { var changesResponse CHANGESRESP - err = retrieveChanges(logger, body, changes, "0", &changesResponse) + err = retrieveChanges(ctx, body, changes, "0", &changesResponse) if err != nil { return zero, "", err } var updatedResponse GETRESP - err = retrieveGet(logger, body, getUpdated, "1", &updatedResponse) + err = retrieveGet(ctx, body, getUpdated, "1", &updatedResponse) if err != nil { logger.Error().Err(err).Send() return zero, "", err @@ -461,19 +470,20 @@ func update[T Foo, CHANGES Change, SET SetCommand[T], GET GetCommand[T], RESP an notUpdatedExtractor func(SETRESP) map[string]SetError, objExtractor func(GETRESP) RESP, id string, changes CHANGES, - session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string) (RESP, SessionState, State, Language, Error) { - logger = client.logger(name, session, logger) + ctx Context) (RESP, SessionState, State, Language, Error) { + logger := client.logger(name, ctx) + ctx = ctx.WithLogger(logger) update := setCommandFactory(map[string]PatchObject{id: changes.AsPatch()}) get := getCommandFactory(id) - cmd, err := client.request(session, logger, using, invocation(update, "0"), invocation(get, "1")) + cmd, err := client.request(ctx, using, invocation(update, "0"), invocation(get, "1")) var zero RESP if err != nil { return zero, "", "", "", err } - return command(client.api, logger, ctx, session, client.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (RESP, State, Error) { + return command(client, ctx, cmd, func(body *Response) (RESP, State, Error) { var setResponse SETRESP - err = retrieveSet(logger, body, update, "0", &setResponse) + err = retrieveSet(ctx, body, update, "0", &setResponse) if err != nil { return zero, setResponse.GetNewState(), err } @@ -484,10 +494,107 @@ func update[T Foo, CHANGES Change, SET SetCommand[T], GET GetCommand[T], RESP an return zero, "", setErrorError(setErr, update.GetObjectType()) } var getResponse GETRESP - err = retrieveGet(logger, body, get, "1", &getResponse) + err = retrieveGet(ctx, body, get, "1", &getResponse) if err != nil { return zero, setResponse.GetNewState(), err } return objExtractor(getResponse), setResponse.GetNewState(), nil }) } + +func query[T Foo, FILTER any, SORT any, QUERY QueryCommand[T], GET GetCommand[T], QUERYRESP QueryResponse[T], GETRESP GetResponse[T], RESP any]( //NOSONAR + client *Client, name string, using []JmapNamespace, + defaultSortBy []SORT, + queryCommandFactory func(filter FILTER, sortBy []SORT, limit uint, position uint) QUERY, + getCommandFactory func(cmd Command, path string, rof string) GET, + respMapper func(query QUERYRESP, get GETRESP) RESP, + filter FILTER, sortBy []SORT, limit uint, position uint, + ctx Context) (RESP, SessionState, State, Language, Error) { + + logger := client.logger(name, ctx) + ctx = ctx.WithLogger(logger) + + if sortBy == nil { + sortBy = defaultSortBy + } + + query := queryCommandFactory(filter, sortBy, limit, position) + get := getCommandFactory(query.GetCommand(), "/ids/*", "0") + + var zero RESP + + cmd, err := client.request(ctx, using, invocation(query, "0"), invocation(get, "1")) + if err != nil { + return zero, "", "", "", err + } + + return command(client, ctx, cmd, func(body *Response) (RESP, State, Error) { + var queryResponse QUERYRESP + err = retrieveQuery(ctx, body, query, "0", &queryResponse) + if err != nil { + return zero, EmptyState, err + } + var getResponse GETRESP + err = retrieveGet(ctx, body, get, "1", &getResponse) + if err != nil { + return zero, EmptyState, err + } + return respMapper(queryResponse, getResponse), queryResponse.GetQueryState(), nil + }) +} + +func queryN[T Foo, FILTER any, SORT any, QUERY QueryCommand[T], GET GetCommand[T], QUERYRESP QueryResponse[T], GETRESP GetResponse[T], RESP any]( //NOSONAR + client *Client, name string, using []JmapNamespace, + defaultSortBy []SORT, + queryCommandFactory func(accountId string, filter FILTER, sortBy []SORT, position int, limit uint) QUERY, + getCommandFactory func(accountId string, cmd Command, path string, rof string) GET, + respMapper func(query QUERYRESP, get GETRESP) RESP, + accountIds []string, + filter FILTER, sortBy []SORT, limit uint, position int, + ctx Context) (map[string]RESP, SessionState, State, Language, Error) { + uniqueAccountIds := structs.Uniq(accountIds) + + if sortBy == nil { + sortBy = defaultSortBy + } + + invocations := make([]Invocation, len(uniqueAccountIds)*2) + var g GET + var q QUERY + for i, accountId := range uniqueAccountIds { + query := queryCommandFactory(accountId, filter, sortBy, position, limit) + get := getCommandFactory(accountId, query.GetCommand(), "/ids/*", mcid(accountId, "0")) + invocations[i*2+0] = invocation(query, mcid(accountId, "0")) + invocations[i*2+1] = invocation(get, mcid(accountId, "1")) + q = query + g = get + } + + cmd, err := client.request(ctx, NS_CALENDARS, invocations...) + if err != nil { + return nil, "", "", "", err + } + + return command(client, ctx, cmd, func(body *Response) (map[string]RESP, State, Error) { + resp := map[string]RESP{} + stateByAccountId := map[string]State{} + for _, accountId := range uniqueAccountIds { + var queryResponse QUERYRESP + err = retrieveQuery(ctx, body, q, mcid(accountId, "0"), &queryResponse) + if err != nil { + return nil, "", err + } + var getResponse GETRESP + err = retrieveGet(ctx, body, g, mcid(accountId, "1"), &getResponse) + if err != nil { + return nil, "", err + } + if len(getResponse.GetNotFound()) > 0 { + // TODO what to do when there are not-found calendarevents here? potentially nothing, they could have been deleted between query and get? + } + resp[accountId] = respMapper(queryResponse, getResponse) + stateByAccountId[accountId] = getResponse.GetState() + } + return resp, squashState(stateByAccountId), nil + }) +} diff --git a/pkg/jmap/tools.go b/pkg/jmap/tools.go index 08790a8c3e..bf17907daa 100644 --- a/pkg/jmap/tools.go +++ b/pkg/jmap/tools.go @@ -1,7 +1,6 @@ package jmap import ( - "context" "encoding/json" "errors" "fmt" @@ -13,7 +12,6 @@ import ( "github.com/mitchellh/mapstructure" "github.com/opencloud-eu/opencloud/pkg/jscalendar" - "github.com/opencloud-eu/opencloud/pkg/log" ) type eventListeners[T any] struct { @@ -52,16 +50,19 @@ func mcid(accountId string, tag string) string { return accountId + ":" + tag } -func command[T any](api ApiClient, //NOSONAR - logger *log.Logger, - ctx context.Context, - session *Session, - sessionOutdatedHandler func(session *Session, newState SessionState), +type Cmdr interface { + ApiSupplier + Hooks +} + +func command[T any](client Cmdr, //NOSONAR + ctx Context, request Request, - acceptLanguage string, mapper func(body *Response) (T, State, Error)) (T, SessionState, State, Language, Error) { - responseBody, language, jmapErr := api.Command(ctx, logger, session, request, acceptLanguage) + logger := ctx.Logger + + responseBody, language, jmapErr := client.Api().Command(request, ctx) if jmapErr != nil { var zero T return zero, "", "", language, jmapErr @@ -75,10 +76,8 @@ func command[T any](api ApiClient, //NOSONAR return zero, "", "", language, jmapError(err, JmapErrorDecodingResponseBody) } - if response.SessionState != session.State { - if sessionOutdatedHandler != nil { - sessionOutdatedHandler(session, response.SessionState) - } + if response.SessionState != ctx.Session.State { + client.OnSessionOutdated(ctx.Session, response.SessionState) } // search for an "error" response @@ -199,25 +198,25 @@ func retrieveResponseMatch(data *Response, command Command, tag string) (Invocat return Invocation{}, false } -func retrieveResponseMatchParameters[T any](logger *log.Logger, data *Response, command Command, tag string, target *T) Error { +func retrieveResponseMatchParameters[T any](ctx Context, data *Response, command Command, tag string, target *T) Error { match, ok := retrieveResponseMatch(data, command, tag) if !ok { err := fmt.Errorf("failed to find JMAP response invocation match for command '%v' and tag '%v'", command, tag) // NOSONAR - logger.Error().Msg(err.Error()) + ctx.Logger.Error().Msg(err.Error()) return jmapError(err, JmapErrorInvalidJmapResponsePayload) } params := match.Parameters typedParams, ok := params.(T) if !ok { err := fmt.Errorf("JMAP response invocation matches command '%v' and tag '%v' but the type %T does not match the expected %T", command, tag, params, *target) // NOSONAR - logger.Error().Msg(err.Error()) + ctx.Logger.Error().Msg(err.Error()) return jmapError(err, JmapErrorInvalidJmapResponsePayload) } *target = typedParams return nil } -func tryRetrieveResponseMatchParameters[T any](logger *log.Logger, data *Response, command Command, tag string, target *T) (bool, Error) { +func tryRetrieveResponseMatchParameters[T any](ctx Context, data *Response, command Command, tag string, target *T) (bool, Error) { match, ok := retrieveResponseMatch(data, command, tag) if !ok { return false, nil @@ -226,23 +225,35 @@ func tryRetrieveResponseMatchParameters[T any](logger *log.Logger, data *Respons typedParams, ok := params.(T) if !ok { err := fmt.Errorf("JMAP response invocation matches command '%v' and tag '%v' but the type %T does not match the expected %T", command, tag, params, *target) - logger.Error().Msg(err.Error()) + ctx.Logger.Error().Msg(err.Error()) return true, jmapError(err, JmapErrorInvalidJmapResponsePayload) } *target = typedParams return true, nil } -func retrieveGet[T Foo, C GetCommand[T], R GetResponse[T]](logger *log.Logger, data *Response, command C, tag string, target *R) Error { - return retrieveResponseMatchParameters(logger, data, command.GetCommand(), tag, target) +func retrieveGet[T Foo, C GetCommand[T], R GetResponse[T]](ctx Context, data *Response, command C, tag string, target *R) Error { + return retrieveResponseMatchParameters(ctx, data, command.GetCommand(), tag, target) } -func retrieveSet[T Foo, C SetCommand[T], R SetResponse[T]](logger *log.Logger, data *Response, command C, tag string, target *R) Error { - return retrieveResponseMatchParameters(logger, data, command.GetCommand(), tag, target) +func retrieveSet[T Foo, C SetCommand[T], R SetResponse[T]](ctx Context, data *Response, command C, tag string, target *R) Error { + return retrieveResponseMatchParameters(ctx, data, command.GetCommand(), tag, target) } -func retrieveChanges[T Foo, C ChangesCommand[T], R ChangesResponse[T]](logger *log.Logger, data *Response, command C, tag string, target *R) Error { - return retrieveResponseMatchParameters(logger, data, command.GetCommand(), tag, target) +func retrieveQuery[T Foo, C QueryCommand[T], R QueryResponse[T]](ctx Context, data *Response, command C, tag string, target *R) Error { + return retrieveResponseMatchParameters(ctx, data, command.GetCommand(), tag, target) +} + +func retrieveChanges[T Foo, C ChangesCommand[T], R ChangesResponse[T]](ctx Context, data *Response, command C, tag string, target *R) Error { + return retrieveResponseMatchParameters(ctx, data, command.GetCommand(), tag, target) +} + +func retrieveUpload[T Foo, C UploadCommand[T], R UploadResponse[T]](ctx Context, data *Response, command C, tag string, target *R) Error { + return retrieveResponseMatchParameters(ctx, data, command.GetCommand(), tag, target) +} + +func retrieveParse[T Foo, C ParseCommand[T], R ParseResponse[T]](ctx Context, data *Response, command C, tag string, target *R) Error { + return retrieveResponseMatchParameters(ctx, data, command.GetCommand(), tag, target) } func (i *Invocation) MarshalJSON() ([]byte, error) { @@ -396,7 +407,7 @@ func identity1[T any](t T) T { func list[T Foo, GETRESP GetResponse[T]](r GETRESP) []T { return r.GetList() } func getid[T Idable](r T) string { return r.GetId() } -func posUIntPtr(i uint) *uint { +func uintPtr(i uint) *uint { if i > 0 { return &i } else { @@ -404,6 +415,14 @@ func posUIntPtr(i uint) *uint { } } +func uintPtrIf(i uint, condition bool) *uint { + if condition { + return uintPtr(i) + } else { + return nil + } +} + func ns(namespaces ...JmapNamespace) []JmapNamespace { result := make([]JmapNamespace, len(namespaces)+1) result[0] = JmapCore diff --git a/pkg/jscontact/model.go b/pkg/jscontact/model.go index 3b7b5e0976..43046e5445 100644 --- a/pkg/jscontact/model.go +++ b/pkg/jscontact/model.go @@ -2012,324 +2012,3 @@ type PersonalInfo struct { // Note that succinct labels are best for proper display on small graphical interfaces and screens. Label string `json:"label,omitempty"` } - -// A ContactCard object contains information about a person, company, or other entity, or represents a group of such entities. -// -// It is a JSCard (JSContact) object, as defined in [RFC9553], with two additional properties. -// -// A contact card with a `kind` property equal to `group` represents a group of contacts. -// Clients often present these separately from other contact cards. -// -// The `members` property, as defined in RFC XXX, Section XXX, contains a set of UIDs for other contacts that are the members -// of this group. -// Clients should consider the group to contain any `ContactCard` with a matching UID, from any account they have access to with -// support for the `urn:ietf:params:jmap:contacts` capability. -// UIDs that cannot be found SHOULD be ignored but preserved. -// -// For example, suppose a user adds contacts from a shared address book to their private group, then temporarily loses access to -// this address book. -// The UIDs cannot be resolved so the contacts will disappear from the group. -// However, if they are given permission to access the data again the UIDs will be found and the contacts will reappear. -type ContactCard struct { - // The id of the Card (immutable; server-set). - // - // The id uniquely identifies a Card with a particular “uid” within a particular account. - // - // This is a JMAP extension and not part of [RFC9553]. - Id string `json:"id,omitempty" doc:"!request,req"` - - // The set of AddressBook ids this Card belongs to. - // - // A card MUST belong to at least one AddressBook at all times (until it is destroyed). - // - // The set is represented as an object, with each key being an AddressBook id. - // - // The value for each key in the object MUST be true. - // - // This is a JMAP extension and not part of [RFC9553]. - AddressBookIds map[string]bool `json:"addressBookIds,omitempty"` - - // The JSContact type of the Card object: the value MUST be "Card". - Type TypeOfContactCard `json:"@type,omitempty"` - - // The JSContact version of this Card. - // - // The value MUST be one of the IANA-registered JSContact Version values for the version property. - Version JSContactVersion `json:"version"` - - // The date and time when the Card was created (UTCDateTime). - Created time.Time `json:"created,omitzero"` - - // The kind of the entity the Card represents (default: `individual`). - // - // Values are: - // * `individual`: a single person - // * `group`: a group of people or entities - // * `org`: an organization - // * `location`: a named location - // * `device`: a device such as an appliance, a computer, or a network element - // * `application`: a software application - Kind ContactCardKind `json:"kind,omitempty"` - - // The language tag, as defined in [RFC5646]. - // - // The language tag that best describes the language used for text in the Card, optionally including - // additional information such as the script. - // - // Note that values MAY be localized in the `localizations` property. - Language string `json:"language,omitempty"` - - // The set of Cards that are members of this group Card. - // - // Each key in the set is the uid property value of the member, and each boolean value MUST be `true`. - // If this property is set, then the value of the kind property MUST be `group`. - // - // The opposite is not true. A group Card will usually contain the members property to specify the members - // of the group, but it is not required to. - // - // A group Card without the members property can be considered an abstract grouping or one whose members - // are known empirically (e.g., `IETF Participants`). - Members map[string]bool `json:"members,omitempty"` - - // The identifier for the product that created the Card. - // - // If set, the value MUST be at least one character long. - ProdId string `json:"prodId,omitempty"` - - // The set of Card objects that relate to the Card. - // - // The value is a map, where each key is the uid property value of the related Card, and the value - // defines the relation - // - // ```json - // { - // "relatedTo": { - // "urn:uuid:f81d4fae-7dec-11d0-a765-00a0c91e6bf6": { - // "relation": {"friend": true} - // }, - // "8cacdfb7d1ffdb59@example.com": { - // "relation": {} - // } - // } - // } - // ``` - RelatedTo map[string]Relation `json:"relatedTo,omitempty"` - - // An identifier that associates the object as the same across different systems, address books, and views. - // - // The value SHOULD be a URN [RFC8141], but for compatibility with [RFC6350], it MAY also be a URI [RFC3986] - // or free-text value. - // - // The value of the URN SHOULD be in the "uuid" namespace [RFC9562]. - // - // [RFC9562] describes multiple versions of Universally Unique IDentifiers (UUIDs); UUID version 4 is RECOMMENDED. - Uid string `json:"uid,omitempty"` - - // The date and time when the data in the Card was last modified (UTCDateTime). - Updated time.Time `json:"updated,omitzero"` - - // The name of the entity represented by the Card. - // - // This can be any type of name, e.g., it can, but need not, be the legal name of a person. - Name *Name `json:"name,omitempty"` - - // The nicknames of the entity represented by the Card. - Nicknames map[string]Nickname `json:"nicknames,omitempty"` - - // The company or organization names and units associated with the Card. - Organizations map[string]Organization `json:"organizations,omitempty"` - - // The information that directs how to address, speak to, or refer to the entity that is represented by the Card. - SpeakToAs *SpeakToAs `json:"speakToAs,omitempty"` - - // The job titles or functional positions of the entity represented by the Card. - Titles map[string]Title `json:"titles,omitempty"` - - // The email addresses in which to contact the entity represented by the Card. - Emails map[string]EmailAddress `json:"emails,omitempty"` - - // The online services that are associated with the entity represented by the Card. - // - // This can be messaging services, social media profiles, and other. - OnlineServices map[string]OnlineService `json:"onlineServices,omitempty"` - - // The phone numbers by which to contact the entity represented by the Card. - Phones map[string]Phone `json:"phones,omitempty"` - - // The preferred languages for contacting the entity associated with the Card. - PreferredLanguages map[string]LanguagePref `json:"preferredLanguages,omitempty"` - - // The calendaring resources of the entity represented by the Card, such as to look up free-busy information. - // - // A Calendar object has all properties of the Resource data type, with the following additional definitions: - // * The `@type` property value MUST be `Calendar`, if set - // * The `kind` property is mandatory. Its enumerated values are: - // * `calendar`: The resource is a calendar that contains entries such as calendar events or tasks - // * `freeBusy`: The resource allows for free-busy lookups, for example, to schedule group events - Calendars map[string]Calendar `json:"calendars,omitempty"` - - // The scheduling addresses by which the entity may receive calendar scheduling invitations. - SchedulingAddresses map[string]SchedulingAddress `json:"schedulingAddresses,omitempty"` - - // The addresses of the entity represented by the Card, such as postal addresses or geographic locations. - Addresses map[string]Address `json:"addresses,omitempty"` - - // The cryptographic resources such as public keys and certificates associated with the entity represented by the Card. - // - // A CryptoKey object has all properties of the `Resource` data type, with the following additional definition: - // the `@type` property value MUST be `CryptoKey`, if set. - // - // The following example shows how to refer to an external cryptographic resource: - // ```json - // "cryptoKeys": { - // "mykey1": { - // "uri": "https://www.example.com/keys/jdoe.cer" - // } - // } - // ``` - CryptoKeys map[string]CryptoKey `json:"cryptoKeys,omitempty"` - - // The directories containing information about the entity represented by the Card. - // - // A Directory object has all properties of the `Resource` data type, with the following additional definitions: - // * The `@type` property value MUST be `Directory`, if set - // * The `kind` property is mandatory; tts enumerated values are: - // * `directory`: the resource is a directory service that the entity represented by the Card is a part of; this - // typically is an organizational directory that also contains associated entities, e.g., co-workers and management - // in a company directory - // * `entry`: the resource is a directory entry of the entity represented by the Card; in contrast to the `directory` - // type, this is the specific URI for the entity within a directory - Directories map[string]Directory `json:"directories,omitempty"` - - // The links to resources that do not fit any of the other use-case-specific resource properties. - // - // A Link object has all properties of the `Resource` data type, with the following additional definitions: - // * The `@type` property value MUST be `Link`, if set - // * The `kind` property is optional; tts enumerated values are: - // * `contact`: the resource is a URI by which the entity represented by the Card may be contacted; - // this includes web forms or other media that require user interaction - Links map[string]Link `json:"links,omitempty"` - - // The media resources such as photographs, avatars, or sounds that are associated with the entity represented by the Card. - // - // A Media object has all properties of the Resource data type, with the following additional definitions: - // * the `@type` property value MUST be `Media`, if set - // * the `kind` property is mandatory; its enumerated values are: - // * `photo`: the resource is a photograph or avatar - // * `sound`: the resource is audio media, e.g., to specify the proper pronunciation of the name property contents - // * `logo`: the resource is a graphic image or logo associated with the entity represented by the Card - Media map[string]Media `json:"media,omitempty"` - - // The property values localized to languages other than the main `language` of the Card. - // - // Localizations provide language-specific alternatives for existing property values and SHOULD NOT add new properties. - // - // The keys in the localizations property value are language tags [RFC5646]; the values are of type `PatchObject` and - // localize the Card in that language tag. - // - // The paths in the `PatchObject` are relative to the Card that includes the localizations property. - // - // A patch MUST NOT target the localizations property. - // - // Conceptually, a Card is localized as follows: - // * Determine the language tag in which the Card should be localized. - // * If the localizations property includes a key for that language, obtain the PatchObject value; - // if there is no such key, stop. - // * Create a copy of the Card, but do not copy the localizations property. - // * Apply all patches in the PatchObject to the copy of the Card. - // * Optionally, set the language property in the copy of the Card. - // * Use the patched copy of the Card as the localized variant of the original Card. - // - // A patch in the `PatchObject` may contain any value type. - // - // Its value MUST be a valid value according to the definition of the patched property. - Localizations map[string]PatchObject `json:"localizations,omitempty"` - - // The memorable dates and events for the entity represented by the Card. - Anniversaries map[string]Anniversary `json:"anniversaries,omitempty"` - - // The set of free-text keywords, also known as tags. - // - // Each key in the set is a keyword, and each boolean value MUST be `true`. - Keywords map[string]bool `json:"keywords,omitempty"` - - // The free-text notes that are associated with the Card. - Notes map[string]Note `json:"notes,omitempty"` - - // The personal information of the entity represented by the Card. - PersonalInfo map[string]PersonalInfo `json:"personalInfo,omitempty"` -} - -func (f ContactCard) GetId() string { return f.Id } - -const ( - ContactCardPropertyId = "id" - ContactCardPropertyAddressBookIds = "addressBookIds" - ContactCardPropertyType = "@type" - ContactCardPropertyVersion = "version" - ContactCardPropertyCreated = "created" - ContactCardPropertyKind = "kind" - ContactCardPropertyLanguage = "language" - ContactCardPropertyMembers = "members" - ContactCardPropertyProdId = "prodId" - ContactCardPropertyRelatedTo = "relatedTo" - ContactCardPropertyUid = "uid" - ContactCardPropertyUpdated = "updated" - ContactCardPropertyName = "name" - ContactCardPropertyNicknames = "nicknames" - ContactCardPropertyOrganizations = "organizations" - ContactCardPropertySpeakToAs = "speakToAs" - ContactCardPropertyTitles = "titles" - ContactCardPropertyEmails = "emails" - ContactCardPropertyOnlineServices = "onlineServices" - ContactCardPropertyPhones = "phones" - ContactCardPropertyPreferredLanguages = "preferredLanguages" - ContactCardPropertyCalendars = "calendars" - ContactCardPropertySchedulingAddresses = "schedulingAddresses" - ContactCardPropertyAddresses = "addresses" - ContactCardPropertyCryptoKeys = "cryptoKeys" - ContactCardPropertyDirectories = "directories" - ContactCardPropertyLinks = "links" - ContactCardPropertyMedia = "media" - ContactCardPropertyLocalizations = "localizations" - ContactCardPropertyAnniversaries = "anniversaries" - ContactCardPropertyKeywords = "keywords" - ContactCardPropertyNotes = "notes" - ContactCardPropertyPersonalInfo = "personalInfo" -) - -var ContactCardProperties = []string{ - ContactCardPropertyId, - ContactCardPropertyAddressBookIds, - ContactCardPropertyType, - ContactCardPropertyVersion, - ContactCardPropertyCreated, - ContactCardPropertyKind, - ContactCardPropertyLanguage, - ContactCardPropertyMembers, - ContactCardPropertyProdId, - ContactCardPropertyRelatedTo, - ContactCardPropertyUid, - ContactCardPropertyUpdated, - ContactCardPropertyName, - ContactCardPropertyNicknames, - ContactCardPropertyOrganizations, - ContactCardPropertySpeakToAs, - ContactCardPropertyTitles, - ContactCardPropertyEmails, - ContactCardPropertyOnlineServices, - ContactCardPropertyPhones, - ContactCardPropertyPreferredLanguages, - ContactCardPropertyCalendars, - ContactCardPropertySchedulingAddresses, - ContactCardPropertyAddresses, - ContactCardPropertyCryptoKeys, - ContactCardPropertyDirectories, - ContactCardPropertyLinks, - ContactCardPropertyMedia, - ContactCardPropertyLocalizations, - ContactCardPropertyAnniversaries, - ContactCardPropertyKeywords, - ContactCardPropertyNotes, - ContactCardPropertyPersonalInfo, -} diff --git a/services/groupware/pkg/groupware/api_account.go b/services/groupware/pkg/groupware/api_account.go index 96f5665c9c..0314bf1881 100644 --- a/services/groupware/pkg/groupware/api_account.go +++ b/services/groupware/pkg/groupware/api_account.go @@ -44,7 +44,7 @@ func (g *Groupware) GetAccounts(w http.ResponseWriter, r *http.Request) { func (g *Groupware) GetAccountsWithTheirIdentities(w http.ResponseWriter, r *http.Request) { g.respond(w, r, func(req Request) Response { allAccountIds := req.AllAccountIds() - resp, sessionState, state, lang, err := g.jmap.GetIdentitiesForAllAccounts(allAccountIds, req.session, req.ctx, req.logger, req.language()) + resp, sessionState, state, lang, err := g.jmap.GetIdentitiesForAllAccounts(allAccountIds, req.ctx) if err != nil { return req.jmapErrorN(allAccountIds, err, sessionState, lang) } diff --git a/services/groupware/pkg/groupware/api_addressbooks.go b/services/groupware/pkg/groupware/api_addressbooks.go index d98b20fa8d..6f169b81aa 100644 --- a/services/groupware/pkg/groupware/api_addressbooks.go +++ b/services/groupware/pkg/groupware/api_addressbooks.go @@ -15,7 +15,7 @@ func (g *Groupware) GetAddressbooks(w http.ResponseWriter, r *http.Request) { return resp } - addressbooks, sessionState, state, lang, jerr := g.jmap.GetAddressbooks(accountId, req.session, req.ctx, req.logger, req.language(), nil) + addressbooks, sessionState, state, lang, jerr := g.jmap.GetAddressbooks(accountId, []string{}, req.ctx) if jerr != nil { return req.jmapError(accountId, jerr, sessionState, lang) } @@ -42,7 +42,7 @@ func (g *Groupware) GetAddressbook(w http.ResponseWriter, r *http.Request) { l = l.Str(UriParamAddressBookId, log.SafeString(addressBookId)) logger := log.From(l) - addressbooks, sessionState, state, lang, jerr := g.jmap.GetAddressbooks(accountId, req.session, req.ctx, logger, req.language(), []string{addressBookId}) + addressbooks, sessionState, state, lang, jerr := g.jmap.GetAddressbooks(accountId, []string{addressBookId}, req.ctx) if jerr != nil { return req.jmapError(accountId, jerr, sessionState, lang) } @@ -84,8 +84,8 @@ func (g *Groupware) GetAddressBookChanges(w http.ResponseWriter, r *http.Request } logger := log.From(l) - - changes, sessionState, state, lang, jerr := g.jmap.GetAddressbookChanges(accountId, req.session, req.ctx, logger, req.language(), sinceState, maxChanges) + 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) } @@ -110,7 +110,8 @@ func (g *Groupware) CreateAddressBook(w http.ResponseWriter, r *http.Request) { } logger := log.From(l) - created, sessionState, state, lang, jerr := g.jmap.CreateAddressBook(accountId, req.session, req.ctx, logger, req.language(), create) + 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) } @@ -133,8 +134,8 @@ func (g *Groupware) DeleteAddressBook(w http.ResponseWriter, r *http.Request) { l.Str(UriParamAddressBookId, log.SafeString(addressBookId)) logger := log.From(l) - - deleted, sessionState, state, lang, jerr := g.jmap.DeleteAddressBook(accountId, []string{addressBookId}, req.session, req.ctx, logger, req.language()) + 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) } diff --git a/services/groupware/pkg/groupware/api_blob.go b/services/groupware/pkg/groupware/api_blob.go index 556e496684..133cd9c5ec 100644 --- a/services/groupware/pkg/groupware/api_blob.go +++ b/services/groupware/pkg/groupware/api_blob.go @@ -5,6 +5,7 @@ import ( "net/http" "strconv" + "github.com/opencloud-eu/opencloud/pkg/jmap" "github.com/opencloud-eu/opencloud/pkg/log" ) @@ -27,8 +28,9 @@ func (g *Groupware) GetBlobMeta(w http.ResponseWriter, r *http.Request) { l = l.Str(UriParamBlobId, blobId) logger := log.From(l) + ctx := req.ctx.WithLogger(logger) - res, sessionState, state, lang, jerr := g.jmap.GetBlobMetadata(accountId, req.session, req.ctx, logger, req.language(), blobId) + res, sessionState, state, lang, jerr := g.jmap.GetBlobMetadata(accountId, blobId, ctx) if jerr != nil { return req.jmapError(accountId, jerr, sessionState, lang) } @@ -57,8 +59,8 @@ func (g *Groupware) UploadBlob(w http.ResponseWriter, r *http.Request) { return req.error(accountId, err) } logger := log.From(req.logger.With().Str(logAccountId, accountId)) - - resp, lang, jerr := g.jmap.UploadBlobStream(accountId, req.session, req.ctx, logger, req.language(), contentType, body) + ctx := req.ctx.WithLogger(logger) + resp, lang, jerr := g.jmap.UploadBlobStream(accountId, contentType, body, ctx) if jerr != nil { return req.jmapError(accountId, jerr, req.session.State, lang) } @@ -85,21 +87,22 @@ func (g *Groupware) DownloadBlob(w http.ResponseWriter, r *http.Request) { return gwerr } logger := log.From(req.logger.With().Str(logAccountId, accountId).Str(UriParamBlobId, blobId)) + ctx := req.ctx.WithLogger(logger) - return req.serveBlob(blobId, name, typ, logger, accountId, w) + return req.serveBlob(blobId, name, typ, ctx, accountId, w) }) } -func (r *Request) serveBlob(blobId string, name string, typ string, logger *log.Logger, accountId string, w http.ResponseWriter) *Error { +func (r *Request) serveBlob(blobId string, name string, typ string, ctx jmap.Context, accountId string, w http.ResponseWriter) *Error { if typ == "" { typ = DefaultBlobDownloadType } - blob, lang, jerr := r.g.jmap.DownloadBlobStream(accountId, blobId, name, typ, r.session, r.ctx, logger, r.language()) + blob, lang, jerr := r.g.jmap.DownloadBlobStream(accountId, blobId, name, typ, ctx) if blob != nil && blob.Body != nil { defer func(Body io.ReadCloser) { err := Body.Close() if err != nil { - logger.Error().Err(err).Msg("failed to close response body") + ctx.Logger.Error().Err(err).Msg("failed to close response body") } }(blob.Body) } diff --git a/services/groupware/pkg/groupware/api_calendars.go b/services/groupware/pkg/groupware/api_calendars.go index 251dd46f24..d2a2af3dbd 100644 --- a/services/groupware/pkg/groupware/api_calendars.go +++ b/services/groupware/pkg/groupware/api_calendars.go @@ -15,7 +15,7 @@ func (g *Groupware) GetCalendars(w http.ResponseWriter, r *http.Request) { return resp } - calendars, sessionState, state, lang, jerr := g.jmap.GetCalendars(accountId, req.session, req.ctx, req.logger, req.language(), nil) + calendars, sessionState, state, lang, jerr := g.jmap.GetCalendars(accountId, nil, req.ctx) if jerr != nil { return req.jmapError(accountId, jerr, sessionState, lang) } @@ -41,7 +41,7 @@ func (g *Groupware) GetCalendarById(w http.ResponseWriter, r *http.Request) { l = l.Str(UriParamCalendarId, log.SafeString(calendarId)) logger := log.From(l) - calendars, sessionState, state, lang, jerr := g.jmap.GetCalendars(accountId, req.session, req.ctx, logger, req.language(), []string{calendarId}) + calendars, sessionState, state, lang, jerr := g.jmap.GetCalendars(accountId, single(calendarId), req.ctx) if jerr != nil { return req.jmapError(accountId, jerr, sessionState, lang) } @@ -83,8 +83,8 @@ func (g *Groupware) GetCalendarChanges(w http.ResponseWriter, r *http.Request) { } logger := log.From(l) - - changes, sessionState, state, lang, jerr := g.jmap.GetCalendarChanges(accountId, req.session, req.ctx, logger, req.language(), sinceState, maxChanges) + 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) } @@ -109,7 +109,8 @@ func (g *Groupware) CreateCalendar(w http.ResponseWriter, r *http.Request) { } logger := log.From(l) - created, sessionState, state, lang, jerr := g.jmap.CreateCalendar(accountId, req.session, req.ctx, logger, req.language(), create) + 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) } @@ -132,8 +133,8 @@ func (g *Groupware) DeleteCalendar(w http.ResponseWriter, r *http.Request) { l.Str(UriParamCalendarId, log.SafeString(calendarId)) logger := log.From(l) - - deleted, sessionState, state, lang, jerr := g.jmap.DeleteCalendar(accountId, []string{calendarId}, req.session, req.ctx, logger, req.language()) + 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) } diff --git a/services/groupware/pkg/groupware/api_changes.go b/services/groupware/pkg/groupware/api_changes.go index 83d0ab8770..07e4b88335 100644 --- a/services/groupware/pkg/groupware/api_changes.go +++ b/services/groupware/pkg/groupware/api_changes.go @@ -77,7 +77,8 @@ func (g *Groupware) GetChanges(w http.ResponseWriter, r *http.Request) { //NOSON } logger := log.From(l) - changes, sessionState, state, lang, jerr := g.jmap.GetChanges(accountId, req.session, req.ctx, logger, req.language(), sinceState, maxChanges) + ctx := req.ctx.WithLogger(logger) + changes, sessionState, state, lang, jerr := g.jmap.GetChanges(accountId, sinceState, maxChanges, ctx) if jerr != nil { return req.jmapError(accountId, jerr, sessionState, lang) } diff --git a/services/groupware/pkg/groupware/api_contacts.go b/services/groupware/pkg/groupware/api_contacts.go index 8bbf61a4ad..b7aeedc1ca 100644 --- a/services/groupware/pkg/groupware/api_contacts.go +++ b/services/groupware/pkg/groupware/api_contacts.go @@ -57,12 +57,12 @@ func (g *Groupware) GetContactsInAddressbook(w http.ResponseWriter, r *http.Requ } l = l.Str(UriParamAddressBookId, log.SafeString(addressBookId)) - offset, ok, err := req.parseUIntParam(QueryParamOffset, 0) + offset, ok, err := req.parseIntParam(QueryParamOffset, 0) if err != nil { return req.errorN(accountIds, err) } if ok { - l = l.Uint(QueryParamOffset, offset) + l = l.Int(QueryParamOffset, offset) } limit, ok, err := req.parseUIntParam(QueryParamLimit, g.defaults.contactLimit) @@ -84,7 +84,8 @@ func (g *Groupware) GetContactsInAddressbook(w http.ResponseWriter, r *http.Requ } logger := log.From(l) - contactsByAccountId, sessionState, state, lang, jerr := g.jmap.QueryContactCards(accountIds, req.session, req.ctx, logger, req.language(), filter, sortBy, offset, limit) + ctx := req.ctx.WithLogger(logger) + contactsByAccountId, sessionState, state, lang, jerr := g.jmap.QueryContactCards(accountIds, filter, sortBy, offset, limit, true, ctx) if jerr != nil { return req.jmapErrorN(accountIds, jerr, sessionState, lang) } @@ -113,7 +114,8 @@ func (g *Groupware) GetContactById(w http.ResponseWriter, r *http.Request) { l = l.Str(UriParamContactId, log.SafeString(contactId)) logger := log.From(l) - contacts, sessionState, state, lang, jerr := g.jmap.GetContactCards(accountId, req.session, req.ctx, logger, req.language(), []string{contactId}) + 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) } @@ -140,7 +142,8 @@ func (g *Groupware) GetAllContacts(w http.ResponseWriter, r *http.Request) { l := req.logger.With() logger := log.From(l) - contacts, sessionState, state, lang, jerr := g.jmap.GetContactCards(accountId, req.session, req.ctx, logger, req.language(), []string{}) + 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) } @@ -173,7 +176,8 @@ func (g *Groupware) GetContactsChanges(w http.ResponseWriter, r *http.Request) { l = l.Str(HeaderParamSince, log.SafeString(string(sinceState))) logger := log.From(l) - changes, sessionState, state, lang, jerr := g.jmap.GetContactCardChanges(accountId, req.session, req.ctx, logger, req.language(), sinceState, maxChanges) + 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) } @@ -205,7 +209,8 @@ func (g *Groupware) CreateContact(w http.ResponseWriter, r *http.Request) { } logger := log.From(l) - created, sessionState, state, lang, jerr := g.jmap.CreateContactCard(accountId, req.session, req.ctx, logger, req.language(), create) + 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) } @@ -228,8 +233,8 @@ func (g *Groupware) DeleteContact(w http.ResponseWriter, r *http.Request) { l.Str(UriParamContactId, log.SafeString(contactId)) logger := log.From(l) - - deleted, sessionState, state, lang, jerr := g.jmap.DeleteContactCard(accountId, []string{contactId}, req.session, req.ctx, logger, req.language()) + 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) } diff --git a/services/groupware/pkg/groupware/api_emails.go b/services/groupware/pkg/groupware/api_emails.go index 47bf6d25f2..de781c39c3 100644 --- a/services/groupware/pkg/groupware/api_emails.go +++ b/services/groupware/pkg/groupware/api_emails.go @@ -48,8 +48,8 @@ func (g *Groupware) GetEmailChanges(w http.ResponseWriter, r *http.Request) { } logger := log.From(l) - - changes, sessionState, state, lang, jerr := g.jmap.GetEmailChanges(accountId, req.session, req.ctx, logger, req.language(), sinceState, true, g.config.maxBodyValueBytes, maxChanges) + 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) } @@ -98,12 +98,13 @@ func (g *Groupware) GetAllEmailsInMailbox(w http.ResponseWriter, r *http.Request } logger := log.From(l) + ctx := req.ctx.WithLogger(logger) collapseThreads := false fetchBodies := false withThreads := true - emails, sessionState, state, lang, jerr := g.jmap.GetAllEmailsInMailbox(accountId, req.session, req.ctx, logger, req.language(), mailboxId, offset, limit, collapseThreads, fetchBodies, g.config.maxBodyValueBytes, withThreads) + 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) } @@ -151,8 +152,8 @@ func (g *Groupware) GetEmailsById(w http.ResponseWriter, r *http.Request) { //NO } logger := log.From(req.logger.With().Str(logAccountId, log.SafeString(accountId)).Str("id", log.SafeString(id)).Str("accept", log.SafeString(accept))) - - blobId, _, _, _, jerr := g.jmap.GetEmailBlobId(accountId, req.session, req.ctx, logger, req.language(), id) + ctx := req.ctx.WithLogger(logger) + blobId, _, _, _, jerr := g.jmap.GetEmailBlobId(accountId, id, ctx) if jerr != nil { return req.apiErrorFromJmap(req.observeJmapError(jerr)) } @@ -165,7 +166,7 @@ func (g *Groupware) GetEmailsById(w http.ResponseWriter, r *http.Request) { //NO if gwerr != nil { return gwerr } - return req.serveBlob(blobId, name, typ, logger, accountId, w) + return req.serveBlob(blobId, name, typ, ctx, accountId, w) } }) } else { @@ -195,8 +196,8 @@ func (g *Groupware) GetEmailsById(w http.ResponseWriter, r *http.Request) { //NO if len(ids) == 1 { logger := log.From(l.Str("id", log.SafeString(id))) - - emails, _, sessionState, state, lang, jerr := g.jmap.GetEmails(accountId, req.session, req.ctx, logger, req.language(), ids, true, g.config.maxBodyValueBytes, markAsSeen, true) + 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 { return req.jmapError(accountId, jerr, sessionState, lang) } @@ -211,8 +212,8 @@ func (g *Groupware) GetEmailsById(w http.ResponseWriter, r *http.Request) { //NO } } else { logger := log.From(l.Array("ids", log.SafeStringArray(ids))) - - emails, _, sessionState, state, lang, jerr := g.jmap.GetEmails(accountId, req.session, req.ctx, logger, req.language(), ids, true, g.config.maxBodyValueBytes, markAsSeen, false) + 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 { return req.jmapError(accountId, jerr, sessionState, lang) } @@ -269,7 +270,8 @@ func (g *Groupware) GetEmailAttachments(w http.ResponseWriter, r *http.Request) } logger := log.From(l) - emails, _, sessionState, state, lang, jerr := g.jmap.GetEmails(accountId, req.session, req.ctx, logger, req.language(), []string{id}, false, 0, false, false) + ctx := req.ctx.WithLogger(logger) + emails, _, sessionState, state, lang, jerr := g.jmap.GetEmails(accountId, single(id), false, 0, false, false, ctx) if jerr != nil { return req.jmapError(accountId, jerr, sessionState, lang) } @@ -305,8 +307,8 @@ func (g *Groupware) GetEmailAttachments(w http.ResponseWriter, r *http.Request) Str(UriParamEmailId, log.SafeString(id)) l = contextAppender(l) logger := log.From(l) - - emails, _, _, _, lang, jerr := g.jmap.GetEmails(mailAccountId, req.session, req.ctx, logger, req.language(), []string{id}, false, 0, false, false) + ctx := req.ctx.WithLogger(logger) + emails, _, _, _, lang, jerr := g.jmap.GetEmails(mailAccountId, single(id), false, 0, false, false, ctx) if jerr != nil { return req.apiErrorFromJmap(req.observeJmapError(jerr)) } @@ -329,7 +331,7 @@ func (g *Groupware) GetEmailAttachments(w http.ResponseWriter, r *http.Request) return nil } - blob, lang, jerr := g.jmap.DownloadBlobStream(blobAccountId, attachment.BlobId, attachment.Name, attachment.Type, req.session, req.ctx, logger, req.language()) + blob, lang, jerr := g.jmap.DownloadBlobStream(blobAccountId, attachment.BlobId, attachment.Name, attachment.Type, ctx) if blob != nil && blob.Body != nil { defer func(Body io.ReadCloser) { err := Body.Close() @@ -390,8 +392,8 @@ func (g *Groupware) getEmailsSince(w http.ResponseWriter, r *http.Request, since } logger := log.From(l) - - changes, sessionState, state, lang, jerr := g.jmap.GetEmailChanges(accountId, req.session, req.ctx, logger, req.language(), since, true, g.config.maxBodyValueBytes, maxChanges) + ctx := req.ctx.WithLogger(logger) + changes, sessionState, state, lang, jerr := g.jmap.GetEmailChanges(accountId, since, true, g.config.maxBodyValueBytes, maxChanges, ctx) if jerr != nil { return req.jmapError(accountId, jerr, sessionState, lang) } @@ -612,6 +614,7 @@ func (g *Groupware) GetEmails(w http.ResponseWriter, r *http.Request) { //NOSONA 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 @@ -619,7 +622,7 @@ func (g *Groupware) GetEmails(w http.ResponseWriter, r *http.Request) { //NOSONA fetchBodies := false - resultsByAccount, sessionState, state, lang, jerr := g.jmap.QueryEmailsWithSnippets(single(accountId), filter, req.session, req.ctx, logger, req.language(), offset, limit, fetchBodies, g.config.maxBodyValueBytes) + resultsByAccount, sessionState, state, lang, jerr := g.jmap.QueryEmailsWithSnippets(single(accountId), filter, offset, limit, fetchBodies, g.config.maxBodyValueBytes, ctx) if jerr != nil { return req.jmapError(accountId, jerr, sessionState, lang) } @@ -671,13 +674,14 @@ func (g *Groupware) GetEmailsForAllAccounts(w http.ResponseWriter, r *http.Reque return req.errorN(allAccountIds, err) } logger = log.From(req.logger.With().Array(logAccountId, log.SafeStringArray(allAccountIds))) + ctx := req.ctx.WithLogger(logger) if !filter.IsNotEmpty() { filter = nil } if makesSnippets { - resultsByAccountId, sessionState, state, lang, jerr := g.jmap.QueryEmailSnippets(allAccountIds, filter, req.session, req.ctx, logger, req.language(), offset, limit) + resultsByAccountId, sessionState, state, lang, jerr := g.jmap.QueryEmailSnippets(allAccountIds, filter, offset, limit, ctx) if jerr != nil { return req.jmapErrorN(allAccountIds, jerr, sessionState, lang) } @@ -717,7 +721,7 @@ func (g *Groupware) GetEmailsForAllAccounts(w http.ResponseWriter, r *http.Reque } else { withThreads := true - resultsByAccountId, sessionState, state, lang, jerr := g.jmap.QueryEmailSummaries(allAccountIds, req.session, req.ctx, logger, req.language(), filter, limit, withThreads) + resultsByAccountId, sessionState, state, lang, jerr := g.jmap.QueryEmailSummaries(allAccountIds, filter, limit, withThreads, ctx) if jerr != nil { return req.jmapErrorN(allAccountIds, jerr, sessionState, lang) } @@ -762,8 +766,8 @@ var draftEmailAutoMailboxRolePrecedence = []string{ jmap.JmapMailboxRoleInbox, // but if there is none, we will use the Mailbox with the inbox role instead } -func findDraftsMailboxId(j *jmap.Client, accountId string, req Request, logger *log.Logger) (string, Response) { - mailboxIdsPerAccountIds, sessionState, _, lang, jerr := j.SearchMailboxIdsPerRole(single(accountId), req.session, req.ctx, logger, req.language(), draftEmailAutoMailboxRolePrecedence) +func findDraftsMailboxId(j *jmap.Client, accountId string, req Request, ctx jmap.Context) (string, Response) { + mailboxIdsPerAccountIds, sessionState, _, lang, jerr := j.SearchMailboxIdsPerRole(single(accountId), draftEmailAutoMailboxRolePrecedence, ctx) if jerr != nil { return "", req.jmapError(accountId, jerr, sessionState, lang) } else { @@ -785,8 +789,8 @@ var sentEmailAutoMailboxRolePrecedence = []string{ var draftAndSentMailboxRoles = structs.Uniq(structs.Concat(draftEmailAutoMailboxRolePrecedence, sentEmailAutoMailboxRolePrecedence)) -func findSentMailboxId(j *jmap.Client, accountId string, req Request, logger *log.Logger) (string, string, Response) { //NOSONAR - mailboxIdsPerAccountIds, sessionState, _, lang, jerr := j.SearchMailboxIdsPerRole(single(accountId), req.session, req.ctx, logger, req.language(), draftAndSentMailboxRoles) +func findSentMailboxId(j *jmap.Client, accountId string, req Request, ctx jmap.Context) (string, string, Response) { //NOSONAR + mailboxIdsPerAccountIds, sessionState, _, lang, jerr := j.SearchMailboxIdsPerRole(single(accountId), draftAndSentMailboxRoles, ctx) if jerr != nil { return "", "", req.jmapError(accountId, jerr, sessionState, lang) } else { @@ -823,6 +827,7 @@ func (g *Groupware) CreateEmail(w http.ResponseWriter, r *http.Request) { 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) @@ -831,7 +836,7 @@ func (g *Groupware) CreateEmail(w http.ResponseWriter, r *http.Request) { } if len(body.MailboxIds) < 1 { - mailboxId, resp := findDraftsMailboxId(g.jmap, accountId, req, logger) + mailboxId, resp := findDraftsMailboxId(g.jmap, accountId, req, ctx) if mailboxId != "" { body.MailboxIds[mailboxId] = true } else { @@ -839,7 +844,7 @@ func (g *Groupware) CreateEmail(w http.ResponseWriter, r *http.Request) { } } - created, sessionState, state, lang, jerr := g.jmap.CreateEmail(accountId, body, "", req.session, req.ctx, logger, req.language()) + created, sessionState, state, lang, jerr := g.jmap.CreateEmail(accountId, body, "", ctx) if jerr != nil { return req.jmapError(accountId, jerr, sessionState, lang) } @@ -863,6 +868,7 @@ func (g *Groupware) ReplaceEmail(w http.ResponseWriter, r *http.Request) { } logger = log.From(logger.With().Str(logAccountId, log.SafeString(accountId))) + ctx := req.ctx.WithLogger(logger) var body jmap.EmailCreate err = req.body(&body) @@ -871,7 +877,7 @@ func (g *Groupware) ReplaceEmail(w http.ResponseWriter, r *http.Request) { } if len(body.MailboxIds) < 1 { - mailboxId, resp := findDraftsMailboxId(g.jmap, accountId, req, logger) + mailboxId, resp := findDraftsMailboxId(g.jmap, accountId, req, ctx) if mailboxId != "" { body.MailboxIds[mailboxId] = true } else { @@ -879,7 +885,7 @@ func (g *Groupware) ReplaceEmail(w http.ResponseWriter, r *http.Request) { } } - created, sessionState, state, lang, jerr := g.jmap.CreateEmail(accountId, body, replaceId, req.session, req.ctx, logger, req.language()) + created, sessionState, state, lang, jerr := g.jmap.CreateEmail(accountId, body, replaceId, ctx) if jerr != nil { return req.jmapError(accountId, jerr, sessionState, lang) } @@ -905,6 +911,7 @@ func (g *Groupware) UpdateEmail(w http.ResponseWriter, r *http.Request) { l.Str(UriParamEmailId, log.SafeString(emailId)) logger := log.From(l) + ctx := req.ctx.WithLogger(logger) var body map[string]any err = req.body(&body) @@ -916,7 +923,7 @@ func (g *Groupware) UpdateEmail(w http.ResponseWriter, r *http.Request) { emailId: body, } - result, sessionState, state, lang, jerr := g.jmap.UpdateEmails(accountId, updates, req.session, req.ctx, logger, req.language()) + result, sessionState, state, lang, jerr := g.jmap.UpdateEmails(accountId, updates, ctx) if jerr != nil { return req.jmapError(accountId, jerr, sessionState, lang) } @@ -964,6 +971,7 @@ func (g *Groupware) UpdateEmailKeywords(w http.ResponseWriter, r *http.Request) l.Str(UriParamEmailId, log.SafeString(emailId)) logger := log.From(l) + ctx := req.ctx.WithLogger(logger) var body emailKeywordUpdates err = req.body(&body) @@ -986,7 +994,7 @@ func (g *Groupware) UpdateEmailKeywords(w http.ResponseWriter, r *http.Request) emailId: patch, } - result, sessionState, state, lang, jerr := g.jmap.UpdateEmails(accountId, patches, req.session, req.ctx, logger, req.language()) + result, sessionState, state, lang, jerr := g.jmap.UpdateEmails(accountId, patches, ctx) if jerr != nil { return req.jmapError(accountId, jerr, sessionState, lang) } @@ -1025,6 +1033,7 @@ func (g *Groupware) AddEmailKeywords(w http.ResponseWriter, r *http.Request) { / l.Str(UriParamEmailId, log.SafeString(emailId)) logger := log.From(l) + ctx := req.ctx.WithLogger(logger) var body []string err = req.body(&body) @@ -1044,7 +1053,7 @@ func (g *Groupware) AddEmailKeywords(w http.ResponseWriter, r *http.Request) { / emailId: patch, } - result, sessionState, state, lang, jerr := g.jmap.UpdateEmails(accountId, patches, req.session, req.ctx, logger, req.language()) + result, sessionState, state, lang, jerr := g.jmap.UpdateEmails(accountId, patches, ctx) if jerr != nil { return req.jmapError(accountId, jerr, sessionState, lang) } @@ -1087,6 +1096,7 @@ func (g *Groupware) RemoveEmailKeywords(w http.ResponseWriter, r *http.Request) l.Str(UriParamEmailId, log.SafeString(emailId)) logger := log.From(l) + ctx := req.ctx.WithLogger(logger) var body []string err = req.body(&body) @@ -1106,7 +1116,7 @@ func (g *Groupware) RemoveEmailKeywords(w http.ResponseWriter, r *http.Request) emailId: patch, } - result, sessionState, state, lang, jerr := g.jmap.UpdateEmails(accountId, patches, req.session, req.ctx, logger, req.language()) + result, sessionState, state, lang, jerr := g.jmap.UpdateEmails(accountId, patches, ctx) if jerr != nil { return req.jmapError(accountId, jerr, sessionState, lang) } @@ -1147,8 +1157,8 @@ func (g *Groupware) DeleteEmail(w http.ResponseWriter, r *http.Request) { l.Str(UriParamEmailId, log.SafeString(emailId)) logger := log.From(l) - - resp, sessionState, state, lang, jerr := g.jmap.DeleteEmails(accountId, []string{emailId}, req.session, req.ctx, logger, req.language()) + 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) } @@ -1196,8 +1206,8 @@ func (g *Groupware) DeleteEmails(w http.ResponseWriter, r *http.Request) { l.Array("emailIds", log.SafeStringArray(emailIds)) logger := log.From(l) - - resp, sessionState, state, lang, jerr := g.jmap.DeleteEmails(accountId, emailIds, req.session, req.ctx, logger, req.language()) + 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) } @@ -1245,7 +1255,8 @@ func (g *Groupware) SendEmail(w http.ResponseWriter, r *http.Request) { //NOSONA moveToMailboxId, _ := req.getStringParam(QueryParamMoveToMailboxId, "") if moveFromMailboxId == "" || moveToMailboxId == "" { - draftsMailboxId, sentMailboxId, resp := findSentMailboxId(g.jmap, accountId, req, req.logger) + ctx := req.ctx.WithLogger(log.From(l)) + draftsMailboxId, sentMailboxId, resp := findSentMailboxId(g.jmap, accountId, req, ctx) if draftsMailboxId != "" && sentMailboxId != "" { if moveFromMailboxId == "" { moveFromMailboxId = draftsMailboxId @@ -1265,8 +1276,8 @@ func (g *Groupware) SendEmail(w http.ResponseWriter, r *http.Request) { //NOSONA } logger := log.From(l) - - resp, sessionState, state, lang, jerr := g.jmap.SubmitEmail(accountId, identityId, emailId, move, req.session, req.ctx, logger, req.language()) + ctx := req.ctx.WithLogger(logger) + resp, sessionState, state, lang, jerr := g.jmap.SubmitEmail(accountId, identityId, emailId, move, ctx) if jerr != nil { return req.jmapError(accountId, jerr, sessionState, lang) } @@ -1366,10 +1377,11 @@ func (g *Groupware) RelatedToEmail(w http.ResponseWriter, r *http.Request) { //N } logger := log.From(l) + ctx := req.ctx.WithLogger(logger) reqId := req.GetRequestId() getEmailsBefore := time.Now() - emails, _, sessionState, state, lang, jerr := g.jmap.GetEmails(accountId, req.session, req.ctx, logger, req.language(), []string{id}, true, g.config.maxBodyValueBytes, false, false) + emails, _, sessionState, state, lang, jerr := g.jmap.GetEmails(accountId, single(id), true, g.config.maxBodyValueBytes, false, false, ctx) getEmailsDuration := time.Since(getEmailsBefore) if jerr != nil { return req.jmapError(accountId, jerr, sessionState, lang) @@ -1394,7 +1406,8 @@ func (g *Groupware) RelatedToEmail(w http.ResponseWriter, r *http.Request) { //N g.job(logger, RelationTypeSameSender, func(jobId uint64, l *log.Logger) { before := time.Now() - resultsByAccountId, _, _, lang, jerr := g.jmap.QueryEmails(single(accountId), filter, req.session, bgctx, l, req.language(), 0, limit, false, g.config.maxBodyValueBytes) + ctx = ctx.WithLogger(logger).WithContext(bgctx) + resultsByAccountId, _, _, lang, jerr := g.jmap.QueryEmails(single(accountId), filter, 0, limit, false, g.config.maxBodyValueBytes, ctx) if results, ok := resultsByAccountId[accountId]; ok { duration := time.Since(before) if jerr != nil { @@ -1415,7 +1428,8 @@ func (g *Groupware) RelatedToEmail(w http.ResponseWriter, r *http.Request) { //N g.job(logger, RelationTypeSameThread, func(jobId uint64, l *log.Logger) { before := time.Now() - emails, _, _, lang, jerr := g.jmap.EmailsInThread(accountId, email.ThreadId, req.session, bgctx, l, req.language(), false, g.config.maxBodyValueBytes) + ctx = ctx.WithLogger(logger).WithContext(bgctx) + emails, _, _, lang, jerr := g.jmap.EmailsInThread(accountId, email.ThreadId, false, g.config.maxBodyValueBytes, ctx) duration := time.Since(before) if jerr != nil { _ = req.observeJmapError(jerr) @@ -1683,8 +1697,9 @@ func (g *Groupware) GetLatestEmailsSummaryForAllAccounts(w http.ResponseWriter, } logger := log.From(l) + ctx := req.ctx.WithLogger(logger) - emailsSummariesByAccount, sessionState, state, lang, jerr := g.jmap.QueryEmailSummaries(allAccountIds, req.session, req.ctx, logger, req.language(), filter, limit, true) + emailsSummariesByAccount, sessionState, state, lang, jerr := g.jmap.QueryEmailSummaries(allAccountIds, filter, limit, true, ctx) if jerr != nil { return req.jmapErrorN(allAccountIds, jerr, sessionState, lang) } diff --git a/services/groupware/pkg/groupware/api_events.go b/services/groupware/pkg/groupware/api_events.go index fd4feb9301..431febd46b 100644 --- a/services/groupware/pkg/groupware/api_events.go +++ b/services/groupware/pkg/groupware/api_events.go @@ -24,12 +24,12 @@ func (g *Groupware) GetEventsInCalendar(w http.ResponseWriter, r *http.Request) } l = l.Str(UriParamCalendarId, log.SafeString(calendarId)) - offset, ok, err := req.parseUIntParam(QueryParamOffset, 0) + offset, ok, err := req.parseIntParam(QueryParamOffset, 0) if err != nil { return req.error(accountId, err) } if ok { - l = l.Uint(QueryParamOffset, offset) + l = l.Int(QueryParamOffset, offset) } limit, ok, err := req.parseUIntParam(QueryParamLimit, g.defaults.contactLimit) @@ -46,7 +46,8 @@ func (g *Groupware) GetEventsInCalendar(w http.ResponseWriter, r *http.Request) sortBy := []jmap.CalendarEventComparator{{Property: jmap.CalendarEventPropertyUpdated, IsAscending: false}} logger := log.From(l) - eventsByAccountId, sessionState, state, lang, jerr := g.jmap.QueryCalendarEvents(single(accountId), req.session, req.ctx, logger, req.language(), filter, sortBy, offset, limit) + ctx := req.ctx.WithLogger(logger) + eventsByAccountId, sessionState, state, lang, jerr := g.jmap.QueryCalendarEvents(single(accountId), filter, sortBy, offset, limit, true, ctx) if jerr != nil { return req.jmapError(accountId, jerr, sessionState, lang) } @@ -82,7 +83,8 @@ func (g *Groupware) GetEventChanges(w http.ResponseWriter, r *http.Request) { l = l.Str(HeaderParamSince, log.SafeString(string(sinceState))) logger := log.From(l) - changes, sessionState, state, lang, jerr := g.jmap.GetCalendarEventChanges(accountId, req.session, req.ctx, logger, req.language(), sinceState, maxChanges) + 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) } @@ -108,7 +110,8 @@ func (g *Groupware) CreateCalendarEvent(w http.ResponseWriter, r *http.Request) } logger := log.From(l) - created, sessionState, state, lang, jerr := g.jmap.CreateCalendarEvent(accountId, req.session, req.ctx, logger, req.language(), create) + 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) } @@ -131,8 +134,8 @@ func (g *Groupware) DeleteCalendarEvent(w http.ResponseWriter, r *http.Request) l.Str(UriParamEventId, log.SafeString(eventId)) logger := log.From(l) - - deleted, sessionState, state, lang, jerr := g.jmap.DeleteCalendarEvent(accountId, []string{eventId}, req.session, req.ctx, logger, req.language()) + 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) } @@ -174,8 +177,8 @@ func (g *Groupware) ParseIcalBlob(w http.ResponseWriter, r *http.Request) { blobIds := strings.Split(blobId, ",") l := req.logger.With().Array(UriParamBlobId, log.SafeStringArray(blobIds)) logger := log.From(l) - - resp, sessionState, state, lang, jerr := g.jmap.ParseICalendarBlob(accountId, req.session, req.ctx, logger, req.language(), blobIds) + ctx := req.ctx.WithLogger(logger) + resp, sessionState, state, lang, jerr := g.jmap.ParseICalendarBlob(accountId, blobIds, ctx) if jerr != nil { return req.jmapError(accountId, jerr, sessionState, lang) } diff --git a/services/groupware/pkg/groupware/api_identity.go b/services/groupware/pkg/groupware/api_identity.go index 0ef0a2be6f..bdbff5fa39 100644 --- a/services/groupware/pkg/groupware/api_identity.go +++ b/services/groupware/pkg/groupware/api_identity.go @@ -7,7 +7,6 @@ import ( "github.com/opencloud-eu/opencloud/pkg/jmap" "github.com/opencloud-eu/opencloud/pkg/log" - "github.com/opencloud-eu/opencloud/pkg/structs" ) // Get the list of identities that are associated with an account. @@ -18,7 +17,8 @@ func (g *Groupware) GetIdentities(w http.ResponseWriter, r *http.Request) { return req.error(accountId, err) } logger := log.From(req.logger.With().Str(logAccountId, accountId)) - res, sessionState, state, lang, jerr := g.jmap.GetAllIdentities(accountId, req.session, req.ctx, logger, req.language()) + 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) } @@ -37,7 +37,8 @@ func (g *Groupware) GetIdentityById(w http.ResponseWriter, r *http.Request) { return req.error(accountId, err) } logger := log.From(req.logger.With().Str(logAccountId, accountId).Str(logIdentityId, id)) - res, sessionState, state, lang, jerr := g.jmap.GetIdentities(accountId, req.session, req.ctx, logger, req.language(), []string{id}) + 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) } @@ -56,14 +57,15 @@ func (g *Groupware) AddIdentity(w http.ResponseWriter, r *http.Request) { return req.error(accountId, err) } logger := log.From(req.logger.With().Str(logAccountId, accountId)) + ctx := req.ctx.WithLogger(logger) - var identity jmap.Identity + 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, req.session, req.ctx, logger, req.language(), identity) + created, sessionState, state, lang, jerr := g.jmap.CreateIdentity(accountId, identity, ctx) if jerr != nil { return req.jmapError(accountId, jerr, sessionState, lang) } @@ -77,15 +79,21 @@ func (g *Groupware) ModifyIdentity(w http.ResponseWriter, r *http.Request) { if err != nil { return req.error(accountId, err) } - logger := log.From(req.logger.With().Str(logAccountId, accountId)) + id, err := req.PathParamDoc(UriParamIdentityId, "The unique identifier of the Identity to modify") + if err != nil { + return req.error(accountId, err) + } - var identity jmap.Identity + 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, req.session, req.ctx, logger, req.language(), identity) + updated, sessionState, state, lang, jerr := g.jmap.UpdateIdentity(accountId, id, identity, ctx) if jerr != nil { return req.jmapError(accountId, jerr, sessionState, lang) } @@ -100,7 +108,6 @@ func (g *Groupware) DeleteIdentity(w http.ResponseWriter, r *http.Request) { if err != nil { return req.error(accountId, err) } - logger := log.From(req.logger.With().Str(logAccountId, accountId)) id, err := req.PathParam(UriParamIdentityId) if err != nil { @@ -111,18 +118,19 @@ func (g *Groupware) DeleteIdentity(w http.ResponseWriter, r *http.Request) { return req.parameterErrorResponse(single(accountId), UriParamIdentityId, fmt.Sprintf("Invalid value for path parameter '%v': '%s': %s", UriParamIdentityId, log.SafeString(id), "empty list of identity ids")) } - deletion, sessionState, state, lang, jerr := g.jmap.DeleteIdentity(accountId, req.session, req.ctx, logger, req.language(), 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) } - notDeletedIds := structs.Missing(ids, deletion) - if len(notDeletedIds) == 0 { + if len(notDeleted) == 0 { return req.noContent(accountId, sessionState, IdentityResponseObjectType, state) } else { - logger.Error().Array("not-deleted", log.SafeStringArray(notDeletedIds)).Msgf("failed to delete %d identities", len(notDeletedIds)) - return req.errorS(accountId, req.apiError(&ErrorFailedToDeleteSomeIdentities, - withMeta(map[string]any{"ids": notDeletedIds})), sessionState) + logger.Error().Msgf("failed to delete %d identities", len(notDeleted)) + return req.errorS(accountId, req.apiError(&ErrorFailedToDeleteSomeIdentities), sessionState) } }) } @@ -150,7 +158,8 @@ func (g *Groupware) GetIdentityChanges(w http.ResponseWriter, r *http.Request) { l = l.Str(HeaderParamSince, log.SafeString(string(sinceState))) logger := log.From(l) - changes, sessionState, state, lang, jerr := g.jmap.GetIdentityChanges(accountId, req.session, req.ctx, logger, req.language(), sinceState, maxChanges) + 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) } diff --git a/services/groupware/pkg/groupware/api_index.go b/services/groupware/pkg/groupware/api_index.go index 015152054d..b17b0718b9 100644 --- a/services/groupware/pkg/groupware/api_index.go +++ b/services/groupware/pkg/groupware/api_index.go @@ -148,7 +148,7 @@ func (g *Groupware) Index(w http.ResponseWriter, r *http.Request) { g.respond(w, r, func(req Request) Response { accountIds := req.AllAccountIds() - boot, sessionState, state, lang, err := g.jmap.GetBootstrap(accountIds, req.session, req.ctx, req.logger, req.language()) + boot, sessionState, state, lang, err := g.jmap.GetBootstrap(accountIds, req.ctx) if err != nil { return req.jmapErrorN(accountIds, err, sessionState, lang) } diff --git a/services/groupware/pkg/groupware/api_mailbox.go b/services/groupware/pkg/groupware/api_mailbox.go index ed30ce3e84..13801cdf53 100644 --- a/services/groupware/pkg/groupware/api_mailbox.go +++ b/services/groupware/pkg/groupware/api_mailbox.go @@ -30,7 +30,7 @@ func (g *Groupware) GetMailbox(w http.ResponseWriter, r *http.Request) { return req.error(accountId, err) } - mailboxes, sessionState, state, lang, jerr := g.jmap.GetMailbox(accountId, req.session, req.ctx, req.logger, req.language(), []string{mailboxId}) + mailboxes, sessionState, state, lang, jerr := g.jmap.GetMailbox(accountId, single(mailboxId), req.ctx) if jerr != nil { return req.jmapError(accountId, jerr, sessionState, lang) } @@ -83,9 +83,10 @@ func (g *Groupware) GetMailboxes(w http.ResponseWriter, r *http.Request) { //NOS } logger := log.From(req.logger.With().Str(logAccountId, accountId)) + ctx := req.ctx.WithLogger(logger) if hasCriteria { - mailboxesByAccountId, sessionState, state, lang, err := g.jmap.SearchMailboxes(single(accountId), req.session, req.ctx, logger, req.language(), filter) + mailboxesByAccountId, sessionState, state, lang, err := g.jmap.SearchMailboxes(single(accountId), filter, ctx) if err != nil { return req.jmapError(accountId, err, sessionState, lang) } @@ -96,7 +97,7 @@ func (g *Groupware) GetMailboxes(w http.ResponseWriter, r *http.Request) { //NOS return req.notFound(accountId, sessionState, MailboxResponseObjectType, state) } } else { - mailboxesByAccountId, sessionState, state, lang, err := g.jmap.GetAllMailboxes(single(accountId), req.session, req.ctx, logger, req.language()) + mailboxesByAccountId, sessionState, state, lang, err := g.jmap.GetAllMailboxes(single(accountId), ctx) if err != nil { return req.jmapError(accountId, err, sessionState, lang) } @@ -117,6 +118,7 @@ func (g *Groupware) GetMailboxesForAllAccounts(w http.ResponseWriter, r *http.Re return req.noopN(nil) // when the user has no accounts } logger := log.From(req.logger.With().Array(logAccountId, log.SafeStringArray(accountIds))) + ctx := req.ctx.WithLogger(logger) var filter jmap.MailboxFilterCondition hasCriteria := false @@ -134,13 +136,13 @@ func (g *Groupware) GetMailboxesForAllAccounts(w http.ResponseWriter, r *http.Re } if hasCriteria { - mailboxesByAccountId, sessionState, state, lang, err := g.jmap.SearchMailboxes(accountIds, req.session, req.ctx, logger, req.language(), filter) + mailboxesByAccountId, sessionState, state, lang, err := g.jmap.SearchMailboxes(accountIds, filter, ctx) if err != nil { return req.jmapErrorN(accountIds, err, sessionState, lang) } return req.respondN(accountIds, sortMailboxesMap(mailboxesByAccountId), sessionState, MailboxResponseObjectType, state) } else { - mailboxesByAccountId, sessionState, state, lang, err := g.jmap.GetAllMailboxes(accountIds, req.session, req.ctx, logger, req.language()) + mailboxesByAccountId, sessionState, state, lang, err := g.jmap.GetAllMailboxes(accountIds, ctx) if err != nil { return req.jmapErrorN(accountIds, err, sessionState, lang) } @@ -163,12 +165,13 @@ func (g *Groupware) GetMailboxByRoleForAllAccounts(w http.ResponseWriter, r *htt } logger := log.From(req.logger.With().Array(logAccountId, log.SafeStringArray(accountIds)).Str("role", role)) + ctx := req.ctx.WithLogger(logger) filter := jmap.MailboxFilterCondition{ Role: role, } - mailboxesByAccountId, sessionState, state, lang, jerr := g.jmap.SearchMailboxes(accountIds, req.session, req.ctx, logger, req.language(), filter) + mailboxesByAccountId, sessionState, state, lang, jerr := g.jmap.SearchMailboxes(accountIds, filter, ctx) if jerr != nil { return req.jmapErrorN(accountIds, jerr, sessionState, lang) } @@ -202,6 +205,7 @@ func (g *Groupware) GetMailboxChanges(w http.ResponseWriter, r *http.Request) { } 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 @@ -216,7 +220,7 @@ func (g *Groupware) GetMailboxChanges(w http.ResponseWriter, r *http.Request) { ))) } - changes, sessionState, state, lang, jerr := g.jmap.GetMailboxChanges(accountId, req.session, req.ctx, logger, req.language(), sinceState, maxChanges) + changes, sessionState, state, lang, jerr := g.jmap.GetMailboxChanges(accountId, sinceState, maxChanges, ctx) if jerr != nil { return req.jmapError(accountId, jerr, sessionState, lang) } @@ -254,9 +258,10 @@ func (g *Groupware) GetMailboxChangesForAllAccounts(w http.ResponseWriter, r *ht } logger := log.From(l) + ctx := req.ctx.WithLogger(logger) sinceStateMap := structs.MapValues(sinceStateStrMap, toState) - changesByAccountId, sessionState, state, lang, jerr := g.jmap.GetMailboxChangesForMultipleAccounts(allAccountIds, req.session, req.ctx, logger, req.language(), sinceStateMap, maxChanges) + changesByAccountId, sessionState, state, lang, jerr := g.jmap.GetMailboxChangesForMultipleAccounts(allAccountIds, sinceStateMap, maxChanges, ctx) if jerr != nil { return req.jmapErrorN(allAccountIds, jerr, sessionState, lang) } @@ -273,8 +278,9 @@ func (g *Groupware) GetMailboxRoles(w http.ResponseWriter, r *http.Request) { allAccountIds := req.AllAccountIds() l.Array(logAccountId, log.SafeStringArray(allAccountIds)) logger := log.From(l) + ctx := req.ctx.WithLogger(logger) - rolesByAccountId, sessionState, state, lang, jerr := g.jmap.GetMailboxRolesForMultipleAccounts(allAccountIds, req.session, req.ctx, logger, req.language()) + rolesByAccountId, sessionState, state, lang, jerr := g.jmap.GetMailboxRolesForMultipleAccounts(allAccountIds, ctx) if jerr != nil { return req.jmapErrorN(allAccountIds, jerr, sessionState, lang) } @@ -305,8 +311,9 @@ func (g *Groupware) UpdateMailbox(w http.ResponseWriter, r *http.Request) { return req.error(accountId, err) } logger := log.From(l) + ctx := req.ctx.WithLogger(logger) - updated, sessionState, state, lang, jerr := g.jmap.UpdateMailbox(accountId, req.session, req.ctx, logger, req.language(), mailboxId, "", body) + updated, sessionState, state, lang, jerr := g.jmap.UpdateMailbox(accountId, mailboxId, body, ctx) if jerr != nil { return req.jmapError(accountId, jerr, sessionState, lang) } @@ -330,8 +337,9 @@ func (g *Groupware) CreateMailbox(w http.ResponseWriter, r *http.Request) { return req.error(accountId, err) } logger := log.From(l) + ctx := req.ctx.WithLogger(logger) - created, sessionState, state, lang, jerr := g.jmap.CreateMailbox(accountId, req.session, req.ctx, logger, req.language(), "", body) + created, sessionState, state, lang, jerr := g.jmap.CreateMailbox(accountId, body, ctx) if jerr != nil { return req.jmapError(accountId, jerr, sessionState, lang) } @@ -365,8 +373,9 @@ func (g *Groupware) DeleteMailbox(w http.ResponseWriter, r *http.Request) { } logger := log.From(l) + ctx := req.ctx.WithLogger(logger) - deleted, sessionState, state, lang, jerr := g.jmap.DeleteMailboxes(accountId, req.session, req.ctx, logger, req.language(), "", mailboxIds) + deleted, sessionState, state, lang, jerr := g.jmap.DeleteMailboxes(accountId, mailboxIds, ctx) if jerr != nil { return req.jmapError(accountId, jerr, sessionState, lang) } diff --git a/services/groupware/pkg/groupware/api_objects.go b/services/groupware/pkg/groupware/api_objects.go index cead96685b..7af25a0f39 100644 --- a/services/groupware/pkg/groupware/api_objects.go +++ b/services/groupware/pkg/groupware/api_objects.go @@ -120,8 +120,11 @@ func (g *Groupware) GetObjects(w http.ResponseWriter, r *http.Request) { //NOSON } logger := log.From(l) - objs, sessionState, state, lang, jerr := g.jmap.GetObjects(accountId, req.session, req.ctx, logger, req.language(), - mailboxIds, emailIds, addressbookIds, contactIds, calendarIds, eventIds, quotaIds, identityIds, emailSubmissionIds) + ctx := req.ctx.WithLogger(logger) + objs, sessionState, state, lang, jerr := g.jmap.GetObjects(accountId, + mailboxIds, emailIds, addressbookIds, contactIds, calendarIds, eventIds, quotaIds, identityIds, emailSubmissionIds, + ctx, + ) if jerr != nil { return req.jmapError(accountId, jerr, sessionState, lang) } diff --git a/services/groupware/pkg/groupware/api_quota.go b/services/groupware/pkg/groupware/api_quota.go index f1ac84c169..5772ba1888 100644 --- a/services/groupware/pkg/groupware/api_quota.go +++ b/services/groupware/pkg/groupware/api_quota.go @@ -19,8 +19,9 @@ func (g *Groupware) GetQuota(w http.ResponseWriter, r *http.Request) { 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), req.session, req.ctx, logger, req.language()) + res, sessionState, state, lang, jerr := g.jmap.GetQuotas(single(accountId), ctx) if jerr != nil { return req.jmapError(accountId, jerr, sessionState, lang) } @@ -47,8 +48,9 @@ func (g *Groupware) GetQuotaForAllAccounts(w http.ResponseWriter, r *http.Reques return req.noopN(accountIds) // user has no accounts } logger := log.From(req.logger.With().Array(logAccountId, log.SafeStringArray(accountIds))) + ctx := req.ctx.WithLogger(logger) - res, sessionState, state, lang, jerr := g.jmap.GetQuotas(accountIds, req.session, req.ctx, logger, req.language()) + res, sessionState, state, lang, jerr := g.jmap.GetQuotas(accountIds, ctx) if jerr != nil { return req.jmapErrorN(accountIds, jerr, sessionState, lang) } diff --git a/services/groupware/pkg/groupware/api_vacation.go b/services/groupware/pkg/groupware/api_vacation.go index 10627e2f9c..1cd99307d0 100644 --- a/services/groupware/pkg/groupware/api_vacation.go +++ b/services/groupware/pkg/groupware/api_vacation.go @@ -20,8 +20,9 @@ func (g *Groupware) GetVacation(w http.ResponseWriter, r *http.Request) { 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, req.session, req.ctx, logger, req.language()) + res, sessionState, state, lang, jerr := g.jmap.GetVacationResponse(accountId, ctx) if jerr != nil { return req.jmapError(accountId, jerr, sessionState, lang) } @@ -40,14 +41,15 @@ func (g *Groupware) SetVacation(w http.ResponseWriter, r *http.Request) { return req.error(accountId, err) } logger := log.From(req.logger.With().Str(logAccountId, accountId)) + ctx := req.ctx.WithLogger(logger) - var body jmap.VacationResponsePayload + 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, req.session, req.ctx, logger, req.language()) + res, sessionState, state, lang, jerr := g.jmap.SetVacationResponse(accountId, body, ctx) if jerr != nil { return req.jmapError(accountId, jerr, sessionState, lang) } diff --git a/services/groupware/pkg/groupware/error.go b/services/groupware/pkg/groupware/error.go index 199a0d6631..9a45694164 100644 --- a/services/groupware/pkg/groupware/error.go +++ b/services/groupware/pkg/groupware/error.go @@ -632,7 +632,7 @@ func errorId(r *http.Request, ctx context.Context) string { } func (r *Request) errorId() string { - return errorId(r.r, r.ctx) + return errorId(r.r, r.cotx) } func apiError(id string, gwerr GroupwareError, options ...ErrorOpt) *Error { diff --git a/services/groupware/pkg/groupware/framework.go b/services/groupware/pkg/groupware/framework.go index 1869759098..703a35fd8b 100644 --- a/services/groupware/pkg/groupware/framework.go +++ b/services/groupware/pkg/groupware/framework.go @@ -582,6 +582,15 @@ func (g *Groupware) serveError(w http.ResponseWriter, r *http.Request, error *Er } } +func newContext(session *jmap.Session, cotx context.Context, logger *log.Logger, acceptLanguage string) jmap.Context { + return jmap.Context{ + Session: session, + Context: cotx, + Logger: logger, + AcceptLanguage: acceptLanguage, + } +} + // Execute a closure with a JMAP Session. // // Returns @@ -589,23 +598,23 @@ func (g *Groupware) serveError(w http.ResponseWriter, r *http.Request, error *Er // - if an error occurs, after which timestamp a retry is allowed // - whether the request was sent to the server or not func (g *Groupware) withSession(w http.ResponseWriter, r *http.Request, handler func(r Request) Response) (Response, time.Time, bool) { - ctx := r.Context() - sl := g.logger.SubloggerWithRequestID(ctx) + cotx := r.Context() + sl := g.logger.SubloggerWithRequestID(cotx) logger := &sl // retrieve the current user from the inbound request var user user { var err error - user, err = g.userProvider.GetUser(r, ctx, logger) + user, err = g.userProvider.GetUser(r, cotx, logger) if err != nil { g.metrics.AuthenticationFailureCounter.Inc() - g.serveError(w, r, apiError(errorId(r, ctx), ErrorInvalidAuthentication), time.Time{}) + g.serveError(w, r, apiError(errorId(r, cotx), ErrorInvalidAuthentication), time.Time{}) return Response{}, time.Time{}, false } if user == nil { g.metrics.AuthenticationFailureCounter.Inc() - g.serveError(w, r, apiError(errorId(r, ctx), ErrorMissingAuthentication), time.Time{}) + g.serveError(w, r, apiError(errorId(r, cotx), ErrorMissingAuthentication), time.Time{}) return Response{}, time.Time{}, false } @@ -615,10 +624,10 @@ func (g *Groupware) withSession(w http.ResponseWriter, r *http.Request, handler // retrieve a JMAP Session for that user var session jmap.Session { - s, ok, gwerr, retryAfter := g.session(ctx, user, logger) + s, ok, gwerr, retryAfter := g.session(cotx, user, logger) if gwerr != nil { g.metrics.SessionFailureCounter.Inc() - errorId := errorId(r, ctx) + errorId := errorId(r, cotx) logger.Error().Str("code", gwerr.Code).Str("error", gwerr.Title).Str("detail", gwerr.Detail).Str(logErrorId, errorId).Msg("failed to determine JMAP session") g.serveError(w, r, apiError(errorId, *gwerr), retryAfter) return Response{}, retryAfter, false @@ -628,7 +637,7 @@ func (g *Groupware) withSession(w http.ResponseWriter, r *http.Request, handler } else { // no session = authentication failed g.metrics.SessionFailureCounter.Inc() - errorId := errorId(r, ctx) + errorId := errorId(r, cotx) logger.Error().Str(logErrorId, errorId).Msg("could not authenticate, failed to find Session") gwerr = &ErrorInvalidAuthentication g.serveError(w, r, apiError(errorId, *gwerr), retryAfter) @@ -638,11 +647,16 @@ func (g *Groupware) withSession(w http.ResponseWriter, r *http.Request, handler decoratedLogger := decorateLogger(logger, session) + language := r.Header.Get("Accept-Language") + + ctx := newContext(&session, cotx, decoratedLogger, language) + // build the Request object req := Request{ g: g, user: user, r: r, + cotx: cotx, ctx: ctx, logger: decoratedLogger, session: &session, @@ -735,32 +749,32 @@ func (g *Groupware) respond(w http.ResponseWriter, r *http.Request, handler func } func (g *Groupware) stream(w http.ResponseWriter, r *http.Request, handler func(r Request, w http.ResponseWriter) *Error) { - ctx := r.Context() - sl := g.logger.SubloggerWithRequestID(ctx) + cotx := r.Context() + sl := g.logger.SubloggerWithRequestID(cotx) logger := &sl - user, err := g.userProvider.GetUser(r, ctx, logger) + user, err := g.userProvider.GetUser(r, cotx, logger) if err != nil { - g.serveError(w, r, apiError(errorId(r, ctx), ErrorInvalidAuthentication), time.Time{}) + g.serveError(w, r, apiError(errorId(r, cotx), ErrorInvalidAuthentication), time.Time{}) return } if user == nil { - g.serveError(w, r, apiError(errorId(r, ctx), ErrorMissingAuthentication), time.Time{}) + g.serveError(w, r, apiError(errorId(r, cotx), ErrorMissingAuthentication), time.Time{}) return } logger = log.From(logger.With().Str(logUserId, log.SafeString(user.GetId()))) - session, ok, gwerr, retryAfter := g.session(ctx, user, logger) + session, ok, gwerr, retryAfter := g.session(cotx, user, logger) if gwerr != nil { - errorId := errorId(r, ctx) + errorId := errorId(r, cotx) logger.Error().Str("code", gwerr.Code).Str("error", gwerr.Title).Str("detail", gwerr.Detail).Str(logErrorId, errorId).Msg("failed to determine JMAP session") g.serveError(w, r, apiError(errorId, *gwerr), retryAfter) return } if !ok { // no session = authentication failed - errorId := errorId(r, ctx) + errorId := errorId(r, cotx) logger.Error().Str(logErrorId, errorId).Msg("could not authenticate, failed to find Session") gwerr = &ErrorInvalidAuthentication g.serveError(w, r, apiError(errorId, *gwerr), retryAfter) @@ -769,13 +783,18 @@ func (g *Groupware) stream(w http.ResponseWriter, r *http.Request, handler func( decoratedLogger := decorateLogger(logger, session) + language := r.Header.Get("Accept-Language") + + ctx := newContext(&session, cotx, decoratedLogger, language) + req := Request{ g: g, user: user, r: r, - ctx: ctx, + cotx: cotx, logger: decoratedLogger, session: &session, + ctx: ctx, } apierr := handler(req, w) diff --git a/services/groupware/pkg/groupware/request.go b/services/groupware/pkg/groupware/request.go index b648fd17c4..9fd18ae55f 100644 --- a/services/groupware/pkg/groupware/request.go +++ b/services/groupware/pkg/groupware/request.go @@ -39,9 +39,10 @@ type Request struct { g *Groupware user user r *http.Request - ctx context.Context + cotx context.Context logger *log.Logger session *jmap.Session + ctx jmap.Context } func isDefaultAccountId(accountId string) bool { @@ -57,11 +58,11 @@ func (r *Request) GetUser() user { } func (r *Request) GetRequestId() string { - return chimiddleware.GetReqID(r.ctx) + return chimiddleware.GetReqID(r.cotx) } func (r *Request) GetTraceId() string { - return groupwaremiddleware.GetTraceID(r.ctx) + return groupwaremiddleware.GetTraceID(r.cotx) } var ( diff --git a/services/groupware/pkg/groupware/request_test.go b/services/groupware/pkg/groupware/request_test.go index 277e379b3c..69d40cb51f 100644 --- a/services/groupware/pkg/groupware/request_test.go +++ b/services/groupware/pkg/groupware/request_test.go @@ -1,7 +1,6 @@ package groupware import ( - "context" "fmt" "net/http" "net/url" @@ -12,8 +11,8 @@ import ( func TestParseSort(t *testing.T) { req := Request{ - r: &http.Request{}, - ctx: context.Background(), + r: &http.Request{}, + cotx: t.Context(), } require := require.New(t) { @@ -84,7 +83,7 @@ func TestParseMap(t *testing.T) { { u, err := url.Parse(tt.uri) require.NoError(err) - req = Request{r: &http.Request{URL: u}, ctx: context.Background()} + req = Request{r: &http.Request{URL: u}, cotx: t.Context()} } res, ok, err := req.parseMapParam("map") require.Nil(err)