mirror of
https://github.com/opencloud-eu/opencloud.git
synced 2026-05-24 14:09:21 -05:00
groupware: fix use of ?limit=0
* JMAP query limit of 0 is synonymous with "no limit", but we actually want to be able to perform queries without any results, for cases where we only want to count the total number of objects, and also because it makes more sense semantically * introduce query parameter validation checks, in order to only allow query parameters that are actually supported, which is going to be useful during development of clients
This commit is contained in:
+16
-14
@@ -64,33 +64,35 @@ func (j *Client) GetContactCardChanges(accountId string, sinceState State, maxCh
|
||||
|
||||
type ContactCardSearchResults SearchResultsTemplate[ContactCard]
|
||||
|
||||
var _ SearchResults[ContactCard] = ContactCardSearchResults{}
|
||||
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 (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 (r *ContactCardSearchResults) RemoveResults() { r.Results = nil }
|
||||
func (r *ContactCardSearchResults) SetLimit(limit *uint) { r.Limit = limit }
|
||||
|
||||
func (j *Client) QueryContactCards(accountIds []string,
|
||||
filter ContactCardFilterElement, sortBy []ContactCardComparator,
|
||||
position int, limit uint, calculateTotal bool,
|
||||
ctx Context) (map[string]ContactCardSearchResults, SessionState, State, Language, Error) {
|
||||
position int, limit *uint, calculateTotal bool,
|
||||
ctx Context) (map[string]*ContactCardSearchResults, SessionState, State, Language, Error) {
|
||||
return queryN(j, "QueryContactCards", ContactCardType,
|
||||
[]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, filter ContactCardFilterElement, sortBy []ContactCardComparator, position int, limit *uint) ContactCardQueryCommand {
|
||||
return ContactCardQueryCommand{AccountId: accountId, Filter: filter, Sort: sortBy, Position: position, Limit: 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{
|
||||
func(query ContactCardQueryResponse, get ContactCardGetResponse) *ContactCardSearchResults {
|
||||
return &ContactCardSearchResults{
|
||||
Results: get.List,
|
||||
CanCalculateChanges: query.CanCalculateChanges,
|
||||
Position: query.Position,
|
||||
Total: uintPtrIfPtr(query.Total, calculateTotal),
|
||||
Limit: query.Limit,
|
||||
Total: valueIf(query.Total, calculateTotal),
|
||||
Limit: ptrIf(query.Limit, limit != nil),
|
||||
}
|
||||
},
|
||||
accountIds,
|
||||
|
||||
+42
-45
@@ -113,20 +113,26 @@ func (j *Client) GetEmailBlobId(accountId string, id string, ctx Context) (strin
|
||||
|
||||
type EmailSearchResults SearchResultsTemplate[Email]
|
||||
|
||||
var _ SearchResults[Email] = EmailSearchResults{}
|
||||
var _ SearchResults[Email] = &EmailSearchResults{}
|
||||
|
||||
func (r EmailSearchResults) GetResults() []Email { return r.Results }
|
||||
func (r EmailSearchResults) GetCanCalculateChanges() bool { return r.CanCalculateChanges }
|
||||
func (r EmailSearchResults) GetPosition() uint { return r.Position }
|
||||
func (r EmailSearchResults) GetLimit() uint { return r.Limit }
|
||||
func (r EmailSearchResults) GetTotal() *uint { return r.Total }
|
||||
func (r *EmailSearchResults) GetResults() []Email { return r.Results }
|
||||
func (r *EmailSearchResults) GetCanCalculateChanges() bool { return r.CanCalculateChanges }
|
||||
func (r *EmailSearchResults) GetPosition() uint { return r.Position }
|
||||
func (r *EmailSearchResults) GetLimit() *uint { return r.Limit }
|
||||
func (r *EmailSearchResults) GetTotal() *uint { return r.Total }
|
||||
func (r *EmailSearchResults) RemoveResults() { r.Results = nil }
|
||||
func (r *EmailSearchResults) SetLimit(limit *uint) { r.Limit = limit }
|
||||
|
||||
// Retrieve all the Emails in a given Mailbox by its id.
|
||||
func (j *Client) GetAllEmailsInMailbox(accountId string, mailboxId string, //NOSONAR
|
||||
position int, limit uint, collapseThreads bool, fetchBodies bool, maxBodyValueBytes uint, withThreads bool,
|
||||
ctx Context) (EmailSearchResults, SessionState, State, Language, Error) {
|
||||
position int, limit *uint, collapseThreads bool, fetchBodies bool, maxBodyValueBytes uint, withThreads bool,
|
||||
ctx Context) (*EmailSearchResults, SessionState, State, Language, Error) {
|
||||
logger := j.loggerParams("GetAllEmailsInMailbox", ctx, func(z zerolog.Context) zerolog.Context {
|
||||
return z.Bool(logFetchBodies, fetchBodies).Int(logPosition, position).Uint(logLimit, limit)
|
||||
l := z.Bool(logFetchBodies, fetchBodies).Int(logPosition, position)
|
||||
if limit != nil {
|
||||
l = l.Uint(logLimit, *limit)
|
||||
}
|
||||
return l
|
||||
})
|
||||
ctx = ctx.WithLogger(logger)
|
||||
|
||||
@@ -136,12 +142,8 @@ func (j *Client) GetAllEmailsInMailbox(accountId string, mailboxId string, //NOS
|
||||
Sort: []EmailComparator{{Property: EmailPropertyReceivedAt, IsAscending: false}},
|
||||
CollapseThreads: collapseThreads,
|
||||
CalculateTotal: true,
|
||||
}
|
||||
if position > 0 {
|
||||
query.Position = position
|
||||
}
|
||||
if limit > 0 {
|
||||
query.Limit = &limit
|
||||
Position: position,
|
||||
Limit: limit,
|
||||
}
|
||||
|
||||
get := EmailGetRefCommand{
|
||||
@@ -173,36 +175,36 @@ func (j *Client) GetAllEmailsInMailbox(accountId string, mailboxId string, //NOS
|
||||
|
||||
cmd, err := j.request(ctx, NS_MAIL, invocations...)
|
||||
if err != nil {
|
||||
return EmailSearchResults{}, "", "", "", err
|
||||
return nil, "", "", "", err
|
||||
}
|
||||
|
||||
return command(j, ctx, cmd, func(body *Response) (EmailSearchResults, State, Error) {
|
||||
return command(j, ctx, cmd, func(body *Response) (*EmailSearchResults, State, Error) {
|
||||
var queryResponse EmailQueryResponse
|
||||
err = retrieveQuery(ctx, body, query, "0", &queryResponse)
|
||||
if err != nil {
|
||||
return EmailSearchResults{}, "", err
|
||||
return nil, "", err
|
||||
}
|
||||
var getResponse EmailGetResponse
|
||||
err = retrieveGet(ctx, body, get, "1", &getResponse)
|
||||
if err != nil {
|
||||
logger.Error().Err(err).Send()
|
||||
return EmailSearchResults{}, "", err
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
if withThreads {
|
||||
var thread ThreadGetResponse
|
||||
err = retrieveGet(ctx, body, threads, "2", &thread)
|
||||
if err != nil {
|
||||
return EmailSearchResults{}, "", err
|
||||
return nil, "", err
|
||||
}
|
||||
setThreadSize(&thread, getResponse.List)
|
||||
}
|
||||
|
||||
return EmailSearchResults{
|
||||
return &EmailSearchResults{
|
||||
Results: getResponse.List,
|
||||
CanCalculateChanges: queryResponse.CanCalculateChanges,
|
||||
Position: queryResponse.Position,
|
||||
Limit: queryResponse.Limit,
|
||||
Limit: ptrIf(queryResponse.Limit, limit != nil),
|
||||
Total: uintPtr(queryResponse.Total),
|
||||
}, queryResponse.QueryState, nil
|
||||
})
|
||||
@@ -304,10 +306,14 @@ type SearchSnippetWithMeta struct {
|
||||
type EmailSnippetSearchResults SearchResultsTemplate[SearchSnippetWithMeta]
|
||||
|
||||
func (j *Client) QueryEmailSnippets(accountIds []string, //NOSONAR
|
||||
filter EmailFilterElement, position int, limit uint,
|
||||
filter EmailFilterElement, position int, limit *uint,
|
||||
ctx Context) (map[string]EmailSnippetSearchResults, SessionState, State, Language, Error) {
|
||||
logger := j.loggerParams("QueryEmailSnippets", ctx, func(z zerolog.Context) zerolog.Context {
|
||||
return z.Uint(logLimit, limit).Int(logPosition, position)
|
||||
l := z.Int(logPosition, position)
|
||||
if limit != nil {
|
||||
l = l.Uint(logLimit, *limit)
|
||||
}
|
||||
return l
|
||||
})
|
||||
ctx = ctx.WithLogger(logger)
|
||||
|
||||
@@ -320,12 +326,8 @@ func (j *Client) QueryEmailSnippets(accountIds []string, //NOSONAR
|
||||
Sort: []EmailComparator{{Property: EmailPropertyReceivedAt, IsAscending: false}},
|
||||
CollapseThreads: true,
|
||||
CalculateTotal: true,
|
||||
}
|
||||
if position > 0 {
|
||||
query.Position = position
|
||||
}
|
||||
if limit > 0 {
|
||||
query.Limit = &limit
|
||||
Position: position,
|
||||
Limit: limit,
|
||||
}
|
||||
|
||||
mails := EmailGetRefCommand{
|
||||
@@ -409,7 +411,7 @@ func (j *Client) QueryEmailSnippets(accountIds []string, //NOSONAR
|
||||
Results: snippets,
|
||||
CanCalculateChanges: queryResponse.CanCalculateChanges,
|
||||
Total: uintPtr(queryResponse.Total),
|
||||
Limit: queryResponse.Limit,
|
||||
Limit: ptrIf(queryResponse.Limit, limit != nil),
|
||||
Position: queryResponse.Position,
|
||||
}
|
||||
}
|
||||
@@ -511,7 +513,7 @@ type EmailQueryWithSnippetsResult struct {
|
||||
}
|
||||
|
||||
func (j *Client) QueryEmailsWithSnippets(accountIds []string, //NOSONAR
|
||||
filter EmailFilterElement, position int, limit uint, collapseThreads bool, calculateTotal bool, fetchBodies bool, maxBodyValueBytes uint,
|
||||
filter EmailFilterElement, position int, limit *uint, collapseThreads bool, calculateTotal bool, fetchBodies bool, maxBodyValueBytes uint,
|
||||
ctx Context) (map[string]EmailQueryWithSnippetsResult, SessionState, State, Language, Error) {
|
||||
logger := j.loggerParams("QueryEmailsWithSnippets", ctx, func(z zerolog.Context) zerolog.Context {
|
||||
return z.Bool(logFetchBodies, fetchBodies)
|
||||
@@ -527,12 +529,8 @@ func (j *Client) QueryEmailsWithSnippets(accountIds []string, //NOSONAR
|
||||
Sort: []EmailComparator{{Property: EmailPropertyReceivedAt, IsAscending: false}},
|
||||
CollapseThreads: collapseThreads,
|
||||
CalculateTotal: calculateTotal,
|
||||
}
|
||||
if position > 0 {
|
||||
query.Position = position
|
||||
}
|
||||
if limit > 0 {
|
||||
query.Limit = &limit
|
||||
Position: position,
|
||||
Limit: limit,
|
||||
}
|
||||
|
||||
snippet := SearchSnippetGetRefCommand{
|
||||
@@ -1027,7 +1025,7 @@ var EmailSummaryProperties = []string{
|
||||
}
|
||||
|
||||
func (j *Client) QueryEmailSummaries(accountIds []string, //NOSONAR
|
||||
filter EmailFilterElement, limit uint, withThreads bool,
|
||||
filter EmailFilterElement, limit *uint, withThreads bool, calculateTotal bool,
|
||||
ctx Context) (map[string]EmailsSummary, SessionState, State, Language, Error) {
|
||||
logger := j.logger("QueryEmailSummaries", ctx)
|
||||
ctx = ctx.WithLogger(logger)
|
||||
@@ -1042,12 +1040,11 @@ func (j *Client) QueryEmailSummaries(accountIds []string, //NOSONAR
|
||||
invocations := make([]Invocation, len(uniqueAccountIds)*factor)
|
||||
for i, accountId := range uniqueAccountIds {
|
||||
get := EmailQueryCommand{
|
||||
AccountId: accountId,
|
||||
Filter: filter,
|
||||
Sort: []EmailComparator{{Property: EmailPropertyReceivedAt, IsAscending: false}},
|
||||
}
|
||||
if limit > 0 {
|
||||
get.Limit = &limit
|
||||
AccountId: accountId,
|
||||
Filter: filter,
|
||||
Sort: []EmailComparator{{Property: EmailPropertyReceivedAt, IsAscending: false}},
|
||||
CalculateTotal: calculateTotal,
|
||||
Limit: limit,
|
||||
}
|
||||
invocations[i*factor+0] = invocation(get, mcid(accountId, "0"))
|
||||
|
||||
|
||||
+16
-14
@@ -4,13 +4,15 @@ var NS_CALENDAR_EVENTS = ns(JmapCalendars)
|
||||
|
||||
type CalendarEventSearchResults SearchResultsTemplate[CalendarEvent]
|
||||
|
||||
var _ SearchResults[CalendarEvent] = CalendarEventSearchResults{}
|
||||
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 (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 (r *CalendarEventSearchResults) RemoveResults() { r.Results = nil }
|
||||
func (r *CalendarEventSearchResults) SetLimit(limit *uint) { r.Limit = limit }
|
||||
|
||||
func (j *Client) GetCalendarEvents(accountId string, eventIds []string, ctx Context) (CalendarEventGetResponse, SessionState, State, Language, Error) {
|
||||
return get(j, "GetCalendarEvents", CalendarEventType,
|
||||
@@ -26,23 +28,23 @@ func (j *Client) GetCalendarEvents(accountId string, eventIds []string, ctx Cont
|
||||
|
||||
func (j *Client) QueryCalendarEvents(accountIds []string, //NOSONAR
|
||||
filter CalendarEventFilterElement, sortBy []CalendarEventComparator,
|
||||
position int, limit uint, calculateTotal bool,
|
||||
ctx Context) (map[string]CalendarEventSearchResults, SessionState, State, Language, Error) {
|
||||
position int, limit *uint, calculateTotal bool,
|
||||
ctx Context) (map[string]*CalendarEventSearchResults, SessionState, State, Language, Error) {
|
||||
return queryN(j, "QueryCalendarEvents", CalendarEventType,
|
||||
[]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, filter CalendarEventFilterElement, sortBy []CalendarEventComparator, position int, limit *uint) CalendarEventQueryCommand {
|
||||
return CalendarEventQueryCommand{AccountId: accountId, Filter: filter, Sort: sortBy, Position: position, Limit: 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{
|
||||
func(query CalendarEventQueryResponse, get CalendarEventGetResponse) *CalendarEventSearchResults {
|
||||
return &CalendarEventSearchResults{
|
||||
Results: get.List,
|
||||
CanCalculateChanges: query.CanCalculateChanges,
|
||||
Position: query.Position,
|
||||
Total: uintPtrIfPtr(query.Total, calculateTotal),
|
||||
Limit: query.Limit,
|
||||
Total: valueIf(query.Total, calculateTotal),
|
||||
Limit: ptrIf(query.Limit, limit != nil),
|
||||
}
|
||||
},
|
||||
accountIds,
|
||||
|
||||
@@ -182,21 +182,21 @@ func (j *Client) GetMailboxChangesForMultipleAccounts(accountIds []string, //NOS
|
||||
)
|
||||
}
|
||||
|
||||
func (j *Client) GetMailboxRolesForMultipleAccounts(accountIds []string, ctx Context) (map[string][]string, SessionState, State, Language, Error) {
|
||||
func (j *Client) GetMailboxRolesForMultipleAccounts(accountIds []string, ctx Context) (map[string]*[]string, SessionState, State, Language, Error) {
|
||||
return queryN(j, "GetMailboxRolesForMultipleAccounts", MailboxType,
|
||||
[]MailboxComparator{{Property: MailboxPropertySortOrder, IsAscending: true}},
|
||||
func(accountId string, filter MailboxFilterCondition, sortBy []MailboxComparator, _ int, _ uint) MailboxQueryCommand {
|
||||
func(accountId string, filter MailboxFilterCondition, sortBy []MailboxComparator, _ int, _ *uint) MailboxQueryCommand {
|
||||
return MailboxQueryCommand{AccountId: accountId, Filter: filter, Sort: sortBy, SortAsTree: false, FilterAsTree: false, Position: 0, Limit: nil, CalculateTotal: false}
|
||||
},
|
||||
func(accountId string, cmd Command, path, rof string) MailboxGetRefCommand {
|
||||
return MailboxGetRefCommand{AccountId: accountId, IdsRef: &ResultReference{Name: cmd, Path: path, ResultOf: rof}}
|
||||
},
|
||||
func(_ MailboxQueryResponse, get MailboxGetResponse) []string {
|
||||
func(_ MailboxQueryResponse, get MailboxGetResponse) *[]string {
|
||||
roles := structs.Map(get.List, func(m Mailbox) string { return m.Role })
|
||||
slices.Sort(roles)
|
||||
return roles
|
||||
return &roles
|
||||
},
|
||||
accountIds, MailboxFilterCondition{HasAnyRole: boolPtr(true)}, nil, 0, 0,
|
||||
accountIds, MailboxFilterCondition{HasAnyRole: truep}, nil, nil, 0,
|
||||
ctx,
|
||||
)
|
||||
}
|
||||
|
||||
+15
-13
@@ -16,33 +16,35 @@ func (j *Client) GetPrincipals(accountId string, ids []string, ctx Context) (Pri
|
||||
|
||||
type PrincipalSearchResults SearchResultsTemplate[Principal]
|
||||
|
||||
var _ SearchResults[Principal] = PrincipalSearchResults{}
|
||||
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 (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 (r *PrincipalSearchResults) RemoveResults() { r.Results = nil }
|
||||
func (r *PrincipalSearchResults) SetLimit(limit *uint) { r.Limit = limit }
|
||||
|
||||
func (j *Client) QueryPrincipals(accountId string,
|
||||
filter PrincipalFilterElement, sortBy []PrincipalComparator,
|
||||
position uint, limit uint, calculateTotal bool,
|
||||
ctx Context) (PrincipalSearchResults, SessionState, State, Language, Error) {
|
||||
position uint, limit *uint, calculateTotal bool,
|
||||
ctx Context) (*PrincipalSearchResults, SessionState, State, Language, Error) {
|
||||
return query(j, "QueryPrincipals", PrincipalType,
|
||||
[]PrincipalComparator{{Property: PrincipalPropertyName, IsAscending: true}},
|
||||
func(filter PrincipalFilterElement, sortBy []PrincipalComparator, position uint, limit uint) PrincipalQueryCommand {
|
||||
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{
|
||||
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,
|
||||
Total: ptrIf(query.Total, calculateTotal),
|
||||
Limit: ptrIf(query.Limit, limit != nil),
|
||||
}
|
||||
},
|
||||
filter, sortBy, limit, position, ctx,
|
||||
|
||||
@@ -49,8 +49,8 @@ func TestCalendars(t *testing.T) { //NOSONAR
|
||||
},
|
||||
func(orig Calendar) CalendarChange {
|
||||
return CalendarChange{
|
||||
Description: strPtr(orig.Description + " (changed)"),
|
||||
IsSubscribed: boolPtr(!orig.IsSubscribed),
|
||||
Description: ptr(orig.Description + " (changed)"),
|
||||
IsSubscribed: ptr(!orig.IsSubscribed),
|
||||
}
|
||||
},
|
||||
func(t *testing.T, orig Calendar, _ CalendarChange, changed Calendar) {
|
||||
@@ -93,7 +93,7 @@ func TestEvents(t *testing.T) {
|
||||
ss := EmptySessionState
|
||||
os := EmptyState
|
||||
{
|
||||
resultsByAccount, sessionState, state, _, err := s.client.QueryCalendarEvents([]string{accountId}, filter, sortBy, 0, 0, true, ctx)
|
||||
resultsByAccount, sessionState, state, _, err := s.client.QueryCalendarEvents([]string{accountId}, filter, sortBy, 0, nil, true, ctx)
|
||||
require.NoError(err)
|
||||
|
||||
require.Len(resultsByAccount, 1)
|
||||
@@ -124,7 +124,7 @@ func TestEvents(t *testing.T) {
|
||||
for i := range slices {
|
||||
position := int(i * limit)
|
||||
page := min(remainder, limit)
|
||||
m, sessionState, _, _, err := s.client.QueryCalendarEvents([]string{accountId}, filter, sortBy, position, limit, true, ctx)
|
||||
m, sessionState, _, _, err := s.client.QueryCalendarEvents([]string{accountId}, filter, sortBy, position, &limit, true, ctx)
|
||||
require.NoError(err)
|
||||
require.Len(m, 1)
|
||||
require.Contains(m, accountId)
|
||||
@@ -147,7 +147,7 @@ func TestEvents(t *testing.T) {
|
||||
Status: ptr(jscalendar.StatusCancelled),
|
||||
ObjectChange: jscalendar.ObjectChange{
|
||||
Sequence: uintPtr(99),
|
||||
ShowWithoutTime: boolPtr(true),
|
||||
ShowWithoutTime: truep,
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -173,7 +173,7 @@ func TestEvents(t *testing.T) {
|
||||
}
|
||||
|
||||
{
|
||||
shouldBeEmpty, sessionState, state, _, err := s.client.QueryCalendarEvents([]string{accountId}, filter, sortBy, 0, 0, true, ctx)
|
||||
shouldBeEmpty, sessionState, state, _, err := s.client.QueryCalendarEvents([]string{accountId}, filter, sortBy, 0, nil, true, ctx)
|
||||
require.NoError(err)
|
||||
require.Contains(shouldBeEmpty, accountId)
|
||||
resp := shouldBeEmpty[accountId]
|
||||
@@ -446,13 +446,13 @@ func (s *StalwartTest) fillEvents( //NOSONAR
|
||||
Color: &color,
|
||||
},
|
||||
Sequence: uintPtr(sequence),
|
||||
ShowWithoutTime: boolPtr(false),
|
||||
ShowWithoutTime: falsep,
|
||||
FreeBusyStatus: &freeBusy,
|
||||
Privacy: &privacy,
|
||||
SentBy: organizerEmail,
|
||||
Participants: participantObjs,
|
||||
TimeZone: &tz,
|
||||
HideAttendees: boolPtr(false),
|
||||
HideAttendees: falsep,
|
||||
ReplyTo: map[jscalendar.ReplyMethod]string{
|
||||
jscalendar.ReplyMethodImip: "mailto:" + organizerEmail, //NOSONAR
|
||||
},
|
||||
@@ -475,8 +475,8 @@ func (s *StalwartTest) fillEvents( //NOSONAR
|
||||
}
|
||||
|
||||
if EnableEventMayInviteFields {
|
||||
obj.MayInviteSelf = boolPtr(true)
|
||||
obj.MayInviteOthers = boolPtr(true)
|
||||
obj.MayInviteSelf = truep
|
||||
obj.MayInviteOthers = truep
|
||||
boxes.mayInvite = true
|
||||
}
|
||||
|
||||
|
||||
@@ -59,8 +59,8 @@ func TestAddressBooks(t *testing.T) {
|
||||
},
|
||||
func(orig AddressBook) AddressBookChange {
|
||||
return AddressBookChange{
|
||||
Description: strPtr(orig.Description + " (changed)"),
|
||||
IsSubscribed: boolPtr(!orig.IsSubscribed),
|
||||
Description: ptr(orig.Description + " (changed)"),
|
||||
IsSubscribed: ptr(!orig.IsSubscribed),
|
||||
}
|
||||
},
|
||||
func(t *testing.T, orig AddressBook, _ AddressBookChange, changed AddressBook) {
|
||||
@@ -100,7 +100,7 @@ func TestContacts(t *testing.T) {
|
||||
{Property: ContactCardPropertyCreated, IsAscending: true},
|
||||
}
|
||||
|
||||
contactsByAccount, ss, os, _, err := s.client.QueryContactCards([]string{accountId}, filter, sortBy, 0, 0, true, ctx)
|
||||
contactsByAccount, ss, os, _, err := s.client.QueryContactCards([]string{accountId}, filter, sortBy, 0, nil, true, ctx)
|
||||
require.NoError(err)
|
||||
|
||||
require.Len(contactsByAccount, 1)
|
||||
@@ -146,7 +146,7 @@ func TestContacts(t *testing.T) {
|
||||
now := time.Now().Truncate(time.Duration(1) * time.Second).UTC()
|
||||
for _, event := range expectedContactCardsById {
|
||||
change := ContactCardChange{
|
||||
Language: strPtr("xyz"),
|
||||
Language: ptr("xyz"),
|
||||
Updated: ptr(now),
|
||||
}
|
||||
changed, sessionState, state, _, err := s.client.UpdateContactCard(accountId, event.Id, change, ctx)
|
||||
@@ -169,7 +169,7 @@ func TestContacts(t *testing.T) {
|
||||
os = state
|
||||
}
|
||||
{
|
||||
shouldBeEmpty, sessionState, state, _, err := s.client.QueryContactCards([]string{accountId}, filter, sortBy, 0, 0, true, ctx)
|
||||
shouldBeEmpty, sessionState, state, _, err := s.client.QueryContactCards([]string{accountId}, filter, sortBy, 0, nil, true, ctx)
|
||||
require.NoError(err)
|
||||
require.Contains(shouldBeEmpty, accountId)
|
||||
resp := shouldBeEmpty[accountId]
|
||||
|
||||
@@ -81,7 +81,7 @@ func TestEmails(t *testing.T) {
|
||||
}
|
||||
|
||||
{
|
||||
resp, sessionState, _, _, err := s.client.GetAllEmailsInMailbox(accountId, inboxId, 0, 0, true, false, 0, true, ctx)
|
||||
resp, sessionState, _, _, err := s.client.GetAllEmailsInMailbox(accountId, inboxId, 0, nil, true, false, 0, true, ctx)
|
||||
require.NoError(err)
|
||||
require.Equal(session.State, sessionState)
|
||||
|
||||
@@ -95,7 +95,7 @@ func TestEmails(t *testing.T) {
|
||||
}
|
||||
|
||||
{
|
||||
resp, sessionState, _, _, err := s.client.GetAllEmailsInMailbox(accountId, inboxId, 0, 0, false, false, 0, true, ctx)
|
||||
resp, sessionState, _, _, err := s.client.GetAllEmailsInMailbox(accountId, inboxId, 0, nil, false, false, 0, true, ctx)
|
||||
require.NoError(err)
|
||||
require.Equal(session.State, sessionState)
|
||||
|
||||
|
||||
@@ -1058,7 +1058,7 @@ func toBoolMap[K comparable](s []K) map[K]bool {
|
||||
func toBoolPtrMap[K comparable](s []K) map[K]*bool {
|
||||
m := make(map[K]*bool, len(s))
|
||||
for _, e := range s {
|
||||
m[e] = ptr(true)
|
||||
m[e] = truep
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
+9
-7
@@ -1424,7 +1424,7 @@ type Changes[T Foo] interface {
|
||||
|
||||
type SearchResultsTemplate[T Foo] struct {
|
||||
// The list of objects that resulted from the query.
|
||||
Results []T `json:"results"`
|
||||
Results []T `json:"results,omitempty"`
|
||||
|
||||
// This is true if the server supports calling queryChanges with these filter/sort parameters.
|
||||
//
|
||||
@@ -1436,10 +1436,10 @@ type SearchResultsTemplate[T Foo] struct {
|
||||
Position uint `json:"position"`
|
||||
|
||||
// The maximum amount of results to return, as requested using the `limit` query parameter.
|
||||
Limit uint `json:"limit,omitzero"`
|
||||
Limit *uint `json:"limit,omitempty"`
|
||||
|
||||
// The total amount of results that exist for the query.
|
||||
Total *uint `json:"total,omitzero"`
|
||||
Total *uint `json:"total,omitempty"`
|
||||
}
|
||||
|
||||
type SearchResults[T Foo] interface {
|
||||
@@ -1447,7 +1447,9 @@ type SearchResults[T Foo] interface {
|
||||
GetCanCalculateChanges() bool
|
||||
GetPosition() uint
|
||||
GetTotal() *uint
|
||||
GetLimit() uint
|
||||
GetLimit() *uint
|
||||
RemoveResults()
|
||||
SetLimit(*uint)
|
||||
}
|
||||
|
||||
type FilterOperatorTerm string
|
||||
@@ -6386,7 +6388,7 @@ type Quota struct {
|
||||
// in the `using` section of the request.
|
||||
//
|
||||
// Further, the server MUST NOT return Quota objects for which there are no types recognized by the client.
|
||||
Types []ObjectType `json:"types,omitempty"`
|
||||
Types []ObjectTypeName `json:"types,omitempty"`
|
||||
|
||||
// The warn limit set by this quota, using the `resourceType` defined as unit of measure.
|
||||
//
|
||||
@@ -7082,7 +7084,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,omitempty" doc:"opt"`
|
||||
|
||||
// Does the client wish to know the total number of results in the query?
|
||||
//
|
||||
@@ -8289,7 +8291,7 @@ type PrincipalQueryCommand 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,omitempty" doc:"opt"`
|
||||
|
||||
// Does the client wish to know the total number of results in the query?
|
||||
//
|
||||
|
||||
+26
-26
@@ -454,12 +454,12 @@ func (e Exemplar) Quota() Quota {
|
||||
Used: 11696865,
|
||||
HardLimit: 20000000000,
|
||||
Name: e.Username,
|
||||
Types: []ObjectType{
|
||||
EmailType,
|
||||
SieveScriptType,
|
||||
FileNodeType,
|
||||
CalendarEventType,
|
||||
ContactCardType,
|
||||
Types: []ObjectTypeName{
|
||||
EmailName,
|
||||
SieveScriptName,
|
||||
FileNodeName,
|
||||
CalendarEventName,
|
||||
ContactCardName,
|
||||
},
|
||||
Description: e.IdentityName,
|
||||
SoftLimit: 19000000000,
|
||||
@@ -477,12 +477,12 @@ func (e Exemplar) Quotas() []Quota {
|
||||
Used: 29102918,
|
||||
HardLimit: 50000000000,
|
||||
Name: e.SharedAccountId,
|
||||
Types: []ObjectType{
|
||||
EmailType,
|
||||
SieveScriptType,
|
||||
FileNodeType,
|
||||
CalendarEventType,
|
||||
ContactCardType,
|
||||
Types: []ObjectTypeName{
|
||||
EmailName,
|
||||
SieveScriptName,
|
||||
FileNodeName,
|
||||
CalendarEventName,
|
||||
ContactCardName,
|
||||
},
|
||||
Description: e.SharedAccountName,
|
||||
SoftLimit: 90000000000,
|
||||
@@ -562,7 +562,7 @@ func (e Exemplar) MailboxInbox() (Mailbox, string, string) {
|
||||
Id: e.MailboxInboxId,
|
||||
Name: "Inbox",
|
||||
Role: JmapMailboxRoleInbox,
|
||||
SortOrder: intPtr(0),
|
||||
SortOrder: ptr(0),
|
||||
TotalEmails: 1291,
|
||||
UnreadEmails: 82,
|
||||
TotalThreads: 891,
|
||||
@@ -578,7 +578,7 @@ func (e Exemplar) MailboxInbox() (Mailbox, string, string) {
|
||||
MayDelete: true,
|
||||
MaySubmit: true,
|
||||
},
|
||||
IsSubscribed: boolPtr(true),
|
||||
IsSubscribed: truep,
|
||||
}, "An Inbox Mailbox", "inbox"
|
||||
}
|
||||
|
||||
@@ -587,7 +587,7 @@ func (e Exemplar) MailboxInboxProjects() (Mailbox, string, string) {
|
||||
Id: e.MailboxProjectId,
|
||||
ParentId: e.MailboxInboxId,
|
||||
Name: "Projects",
|
||||
SortOrder: intPtr(0),
|
||||
SortOrder: ptr(0),
|
||||
TotalEmails: 112,
|
||||
UnreadEmails: 3,
|
||||
TotalThreads: 85,
|
||||
@@ -603,7 +603,7 @@ func (e Exemplar) MailboxInboxProjects() (Mailbox, string, string) {
|
||||
MayDelete: true,
|
||||
MaySubmit: true,
|
||||
},
|
||||
IsSubscribed: boolPtr(true),
|
||||
IsSubscribed: truep,
|
||||
}, "A Projects Mailbox under the Inbox", "projects"
|
||||
}
|
||||
|
||||
@@ -612,7 +612,7 @@ func (e Exemplar) MailboxDrafts() (Mailbox, string, string) {
|
||||
Id: e.MailboxDraftsId,
|
||||
Name: "Drafts",
|
||||
Role: JmapMailboxRoleDrafts,
|
||||
SortOrder: intPtr(0),
|
||||
SortOrder: ptr(0),
|
||||
TotalEmails: 12,
|
||||
UnreadEmails: 1,
|
||||
TotalThreads: 12,
|
||||
@@ -628,7 +628,7 @@ func (e Exemplar) MailboxDrafts() (Mailbox, string, string) {
|
||||
MayDelete: true,
|
||||
MaySubmit: true,
|
||||
},
|
||||
IsSubscribed: boolPtr(true),
|
||||
IsSubscribed: truep,
|
||||
}, "A Drafts Mailbox", "drafts"
|
||||
}
|
||||
|
||||
@@ -637,7 +637,7 @@ func (e Exemplar) MailboxSent() (Mailbox, string, string) {
|
||||
Id: e.MailboxSentId,
|
||||
Name: "Sent Items",
|
||||
Role: JmapMailboxRoleSent,
|
||||
SortOrder: intPtr(0),
|
||||
SortOrder: ptr(0),
|
||||
TotalEmails: 1621,
|
||||
UnreadEmails: 0,
|
||||
TotalThreads: 1621,
|
||||
@@ -653,7 +653,7 @@ func (e Exemplar) MailboxSent() (Mailbox, string, string) {
|
||||
MayDelete: true,
|
||||
MaySubmit: true,
|
||||
},
|
||||
IsSubscribed: boolPtr(true),
|
||||
IsSubscribed: truep,
|
||||
}, "A Sent Mailbox", "sent"
|
||||
}
|
||||
|
||||
@@ -662,7 +662,7 @@ func (e Exemplar) MailboxJunk() (Mailbox, string, string) {
|
||||
Id: e.MailboxJunkId,
|
||||
Name: "Junk Mail",
|
||||
Role: JmapMailboxRoleJunk,
|
||||
SortOrder: intPtr(0),
|
||||
SortOrder: ptr(0),
|
||||
TotalEmails: 251,
|
||||
UnreadEmails: 0,
|
||||
TotalThreads: 251,
|
||||
@@ -678,7 +678,7 @@ func (e Exemplar) MailboxJunk() (Mailbox, string, string) {
|
||||
MayDelete: true,
|
||||
MaySubmit: true,
|
||||
},
|
||||
IsSubscribed: boolPtr(true),
|
||||
IsSubscribed: truep,
|
||||
}, "A Junk Mailbox", "junk"
|
||||
}
|
||||
|
||||
@@ -687,7 +687,7 @@ func (e Exemplar) MailboxDeleted() (Mailbox, string, string) {
|
||||
Id: e.MailboxDeletedId,
|
||||
Name: "Deleted Items",
|
||||
Role: JmapMailboxRoleTrash,
|
||||
SortOrder: intPtr(0),
|
||||
SortOrder: ptr(0),
|
||||
TotalEmails: 99,
|
||||
UnreadEmails: 0,
|
||||
TotalThreads: 91,
|
||||
@@ -703,7 +703,7 @@ func (e Exemplar) MailboxDeleted() (Mailbox, string, string) {
|
||||
MayDelete: true,
|
||||
MaySubmit: true,
|
||||
},
|
||||
IsSubscribed: boolPtr(true),
|
||||
IsSubscribed: truep,
|
||||
}, "A Trash Mailbox", "deleted"
|
||||
}
|
||||
|
||||
@@ -815,7 +815,7 @@ func (e Exemplar) Emails() EmailSearchResults {
|
||||
return EmailSearchResults{
|
||||
Results: []Email{e.Email()},
|
||||
Total: uintPtr(132),
|
||||
Limit: 1,
|
||||
Limit: uintPtr(1),
|
||||
Position: 5,
|
||||
CanCalculateChanges: true,
|
||||
}
|
||||
@@ -1991,7 +1991,7 @@ func (e Exemplar) ContactCardChangeForUpdate() (ContactCardChange, string, strin
|
||||
return ContactCardChange{
|
||||
AddressBookIds: map[string]*bool{
|
||||
"c34c2bb4-4e8e-4579-b35d-6f6739a11146": nil,
|
||||
"02b6977f-bb60-4511-949e-37f47a930382": boolPtr(true),
|
||||
"02b6977f-bb60-4511-949e-37f47a930382": truep,
|
||||
},
|
||||
Nicknames: map[string]c.Nickname{
|
||||
"a": {
|
||||
|
||||
+15
-17
@@ -430,11 +430,11 @@ func update[T Foo, CHANGES Change, SET SetCommand[T], GET GetCommand[T], RESP an
|
||||
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, objType ObjectType,
|
||||
defaultSortBy []SORT,
|
||||
queryCommandFactory func(filter FILTER, sortBy []SORT, limit uint, position uint) QUERY,
|
||||
queryCommandFactory func(filter FILTER, sortBy []SORT, position uint, limit *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) {
|
||||
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)
|
||||
@@ -443,26 +443,24 @@ func query[T Foo, FILTER any, SORT any, QUERY QueryCommand[T], GET GetCommand[T]
|
||||
sortBy = defaultSortBy
|
||||
}
|
||||
|
||||
query := queryCommandFactory(filter, sortBy, limit, position)
|
||||
query := queryCommandFactory(filter, sortBy, position, limit)
|
||||
get := getCommandFactory(query.GetCommand(), "/ids/*", "0")
|
||||
|
||||
var zero RESP
|
||||
|
||||
cmd, err := client.request(ctx, objType.Namespaces, invocation(query, "0"), invocation(get, "1"))
|
||||
if err != nil {
|
||||
return zero, "", "", "", err
|
||||
return nil, "", "", "", err
|
||||
}
|
||||
|
||||
return command(client, ctx, cmd, func(body *Response) (RESP, State, Error) {
|
||||
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
|
||||
return nil, EmptyState, err
|
||||
}
|
||||
var getResponse GETRESP
|
||||
err = retrieveGet(ctx, body, get, "1", &getResponse)
|
||||
if err != nil {
|
||||
return zero, EmptyState, err
|
||||
return nil, EmptyState, err
|
||||
}
|
||||
return respMapper(queryResponse, getResponse), queryResponse.GetQueryState(), nil
|
||||
})
|
||||
@@ -471,12 +469,12 @@ func query[T Foo, FILTER any, SORT any, QUERY QueryCommand[T], GET GetCommand[T]
|
||||
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, objType ObjectType,
|
||||
defaultSortBy []SORT,
|
||||
queryCommandFactory func(accountId string, filter FILTER, sortBy []SORT, position int, limit uint) QUERY,
|
||||
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,
|
||||
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) {
|
||||
filter FILTER, sortBy []SORT, limit *uint, position int,
|
||||
ctx Context) (map[string]*RESP, SessionState, State, Language, Error) {
|
||||
logger := client.logger(name, ctx)
|
||||
ctx = ctx.WithLogger(logger)
|
||||
|
||||
@@ -503,8 +501,8 @@ func queryN[T Foo, FILTER any, SORT any, QUERY QueryCommand[T], GET GetCommand[T
|
||||
return nil, "", "", "", err
|
||||
}
|
||||
|
||||
return command(client, ctx, cmd, func(body *Response) (map[string]RESP, State, Error) {
|
||||
resp := map[string]RESP{}
|
||||
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
|
||||
|
||||
+14
-25
@@ -393,22 +393,15 @@ func mapPairs[K comparable, L, R any](left map[K]L, right map[K]R) map[K]pair[L,
|
||||
return result
|
||||
}
|
||||
|
||||
func ptr[T any](t T) *T {
|
||||
var (
|
||||
truep = ptr(true)
|
||||
falsep = ptr(false)
|
||||
)
|
||||
|
||||
func ptr[T any | string | int | uint | bool](t T) *T {
|
||||
return &t
|
||||
}
|
||||
|
||||
func strPtr(s string) *string {
|
||||
return &s
|
||||
}
|
||||
|
||||
func intPtr(i int) *int {
|
||||
return &i
|
||||
}
|
||||
|
||||
func boolPtr(b bool) *bool {
|
||||
return &b
|
||||
}
|
||||
|
||||
func identity1[T any](t T) T {
|
||||
return t
|
||||
}
|
||||
@@ -417,24 +410,20 @@ func list[T Foo, GETRESP GetResponse[T]](r GETRESP) []T { return r.GetList() }
|
||||
func getid[T Idable](r T) string { return r.GetId() }
|
||||
|
||||
func uintPtr(i uint) *uint {
|
||||
if i > 0 {
|
||||
return &i
|
||||
return ptr(i)
|
||||
}
|
||||
|
||||
func valueIf[T any | uint | int | bool](value *T, condition bool) *T {
|
||||
if condition {
|
||||
return value
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func uintPtrIf(i uint, condition bool) *uint {
|
||||
func ptrIf[T any | uint | int | bool](value T, condition bool) *T {
|
||||
if condition {
|
||||
return uintPtr(i)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func uintPtrIfPtr(i *uint, condition bool) *uint {
|
||||
if condition {
|
||||
return i
|
||||
return &value
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -82,6 +82,18 @@ func Index[K comparable, V any](source []V, indexer func(V) K) map[K]V {
|
||||
return result
|
||||
}
|
||||
|
||||
func Set[V comparable](source []V) map[V]struct{} {
|
||||
if source == nil {
|
||||
var zero map[V]struct{}
|
||||
return zero
|
||||
}
|
||||
result := map[V]struct{}{}
|
||||
for _, v := range source {
|
||||
result[v] = struct{}{}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// Creates a slice from a slice, putting each value from the source slice through the
|
||||
// mapper function to determine the value to store into the resulting slice.
|
||||
func Map[E any, R any](source []E, mapper func(E) R) []R {
|
||||
|
||||
@@ -248,3 +248,12 @@ func TestFilterSeq(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSet(t *testing.T) {
|
||||
s := Set([]string{"a", "b", "c", "b", "d"})
|
||||
assert.Len(t, s, 4)
|
||||
for _, e := range []string{"a", "b", "c", "d"} {
|
||||
_, ok := s[e]
|
||||
assert.True(t, ok)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ import (
|
||||
"net/http"
|
||||
|
||||
"github.com/opencloud-eu/opencloud/pkg/jmap"
|
||||
"github.com/opencloud-eu/opencloud/pkg/log"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -42,60 +41,70 @@ var (
|
||||
|
||||
// Get all the contacts in an addressbook of an account by its identifier.
|
||||
func (g *Groupware) GetContactsInAddressbook(w http.ResponseWriter, r *http.Request) { //NOSONAR
|
||||
g.respond(w, r, func(req Request) Response {
|
||||
ok, accountId, resp := req.needContactWithAccount()
|
||||
if !ok {
|
||||
return resp
|
||||
}
|
||||
accountIds := single(accountId)
|
||||
getallpaged(Contact, w, r, g, true,
|
||||
func(addressbookId string) jmap.ContactCardFilterElement {
|
||||
return jmap.ContactCardFilterCondition{InAddressBook: addressbookId}
|
||||
},
|
||||
[]jmap.ContactCardComparator{{Property: jmap.ContactCardPropertyUpdated, IsAscending: true}},
|
||||
curryMapQuery(g.jmap.QueryContactCards),
|
||||
)
|
||||
|
||||
l := req.logger.With()
|
||||
/*
|
||||
g.respond(w, r, func(req Request) Response {
|
||||
ok, accountId, resp := req.needContactWithAccount()
|
||||
if !ok {
|
||||
return resp
|
||||
}
|
||||
accountIds := single(accountId)
|
||||
|
||||
addressBookId, err := req.PathParam(UriParamAddressBookId)
|
||||
if err != nil {
|
||||
return req.errorN(accountIds, err)
|
||||
}
|
||||
l = l.Str(UriParamAddressBookId, log.SafeString(addressBookId))
|
||||
l := req.logger.With()
|
||||
|
||||
position, ok, err := req.parseIntParam(QueryParamPosition, 0)
|
||||
if err != nil {
|
||||
return req.errorN(accountIds, err)
|
||||
}
|
||||
if ok {
|
||||
l = l.Int(QueryParamPosition, position)
|
||||
}
|
||||
addressBookId, err := req.PathParam(UriParamAddressBookId)
|
||||
if err != nil {
|
||||
return req.errorN(accountIds, err)
|
||||
}
|
||||
l = l.Str(UriParamAddressBookId, log.SafeString(addressBookId))
|
||||
|
||||
limit, ok, err := req.parseUIntParam(QueryParamLimit, g.defaults.contactLimit)
|
||||
if err != nil {
|
||||
return req.errorN(accountIds, err)
|
||||
}
|
||||
if ok {
|
||||
l = l.Uint(QueryParamLimit, limit)
|
||||
}
|
||||
position, ok, err := req.parseIntParam(QueryParamPosition, 0)
|
||||
if err != nil {
|
||||
return req.errorN(accountIds, err)
|
||||
}
|
||||
if ok {
|
||||
l = l.Int(QueryParamPosition, position)
|
||||
}
|
||||
|
||||
filter := jmap.ContactCardFilterCondition{
|
||||
InAddressBook: addressBookId,
|
||||
}
|
||||
var sortBy []jmap.ContactCardComparator
|
||||
if sort, ok, resp := mapSort(accountIds, &req, DefaultContactSort, SupportedContactSortingProperties, mapContactCardSort); !ok {
|
||||
return resp
|
||||
} else {
|
||||
sortBy = sort
|
||||
}
|
||||
limit, ok, err := req.parseUIntParam(QueryParamLimit, g.defaults.contactLimit)
|
||||
if err != nil {
|
||||
return req.errorN(accountIds, err)
|
||||
}
|
||||
if ok {
|
||||
l = l.Uint(QueryParamLimit, limit)
|
||||
}
|
||||
|
||||
logger := log.From(l)
|
||||
ctx := req.ctx.WithLogger(logger)
|
||||
contactsByAccountId, sessionState, state, lang, jerr := g.jmap.QueryContactCards(accountIds, filter, sortBy, position, limit, true, ctx)
|
||||
if jerr != nil {
|
||||
return req.jmapErrorN(accountIds, jerr, sessionState, lang)
|
||||
}
|
||||
filter := jmap.ContactCardFilterCondition{
|
||||
InAddressBook: addressBookId,
|
||||
}
|
||||
var sortBy []jmap.ContactCardComparator
|
||||
if sort, ok, resp := mapSort(accountIds, &req, DefaultContactSort, SupportedContactSortingProperties, mapContactCardSort); !ok {
|
||||
return resp
|
||||
} else {
|
||||
sortBy = sort
|
||||
}
|
||||
|
||||
if contacts, ok := contactsByAccountId[accountId]; ok {
|
||||
return req.respondN(accountIds, contacts, sessionState, ContactResponseObjectType, state, lang)
|
||||
} else {
|
||||
return req.notFoundN(accountIds, sessionState, ContactResponseObjectType, state)
|
||||
}
|
||||
})
|
||||
logger := log.From(l)
|
||||
ctx := req.ctx.WithLogger(logger)
|
||||
contactsByAccountId, sessionState, state, lang, jerr := g.jmap.QueryContactCards(accountIds, filter, sortBy, position, limit, true, ctx)
|
||||
if jerr != nil {
|
||||
return req.jmapErrorN(accountIds, jerr, sessionState, lang)
|
||||
}
|
||||
|
||||
if contacts, ok := contactsByAccountId[accountId]; ok {
|
||||
return req.respondN(accountIds, contacts, sessionState, ContactResponseObjectType, state, lang)
|
||||
} else {
|
||||
return req.notFoundN(accountIds, sessionState, ContactResponseObjectType, state)
|
||||
}
|
||||
})
|
||||
*/
|
||||
}
|
||||
|
||||
func (g *Groupware) GetContactById(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -103,9 +112,9 @@ func (g *Groupware) GetContactById(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
func (g *Groupware) GetAllContacts(w http.ResponseWriter, r *http.Request) {
|
||||
getallpaged(Contact, w, r, g,
|
||||
func(cid string) jmap.ContactCardFilterElement {
|
||||
return jmap.ContactCardFilterCondition{InAddressBook: cid}
|
||||
getallpaged(Contact, w, r, g, false,
|
||||
func(_ string) jmap.ContactCardFilterElement {
|
||||
return jmap.ContactCardFilterCondition{}
|
||||
},
|
||||
[]jmap.ContactCardComparator{{Property: jmap.ContactCardPropertyUpdated, IsAscending: true}},
|
||||
curryMapQuery(g.jmap.QueryContactCards),
|
||||
@@ -129,11 +138,3 @@ func (g *Groupware) DeleteContact(w http.ResponseWriter, r *http.Request) {
|
||||
func (g *Groupware) ModifyContact(w http.ResponseWriter, r *http.Request) {
|
||||
modify(Contact, w, r, g, g.jmap.UpdateContactCard)
|
||||
}
|
||||
|
||||
func mapContactCardSort(s SortCrit) jmap.ContactCardComparator {
|
||||
attr := s.Attribute
|
||||
if mapped, ok := ContactSortingPropertyMapping[s.Attribute]; ok {
|
||||
attr = mapped
|
||||
}
|
||||
return jmap.ContactCardComparator{Property: attr, IsAscending: s.Ascending}
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@ func (g *Groupware) GetAllEmailsInMailbox(w http.ResponseWriter, r *http.Request
|
||||
fetchBodies := false
|
||||
withThreads := true
|
||||
query(Email, w, r, g, g.defaults.emailLimit,
|
||||
func(req Request, accountId, containerId string, position int, limit uint, ctx jmap.Context) (jmap.EmailSearchResults, jmap.SessionState, jmap.State, jmap.Language, *Error) {
|
||||
func(req Request, accountId, containerId string, position int, limit *uint, ctx jmap.Context) (*jmap.EmailSearchResults, jmap.SessionState, jmap.State, jmap.Language, *Error) {
|
||||
emails, sessionState, state, lang, jerr := g.jmap.GetAllEmailsInMailbox(accountId, containerId, position, limit, collapseThreads, fetchBodies, g.config.maxBodyValueBytes, withThreads, ctx)
|
||||
if jerr != nil {
|
||||
return emails, sessionState, state, lang, req.apiErrorFromJmap(req.observeJmapError(jerr))
|
||||
@@ -53,7 +53,7 @@ func (g *Groupware) GetAllEmailsInMailbox(w http.ResponseWriter, r *http.Request
|
||||
return emails, sessionState, state, lang, err
|
||||
}
|
||||
|
||||
safe := jmap.EmailSearchResults{
|
||||
safe := &jmap.EmailSearchResults{
|
||||
Results: sanitized,
|
||||
Total: emails.Total,
|
||||
Limit: emails.Limit,
|
||||
@@ -342,17 +342,12 @@ func (g *Groupware) getEmailsSince(w http.ResponseWriter, r *http.Request, since
|
||||
})
|
||||
}
|
||||
|
||||
type EmailSearchSnippetsResults struct {
|
||||
Results []Snippet `json:"results,omitempty"`
|
||||
Total uint `json:"total,omitzero"`
|
||||
Limit uint `json:"limit,omitzero"`
|
||||
QueryState jmap.State `json:"queryState,omitempty"`
|
||||
}
|
||||
type EmailSearchSnippetsResults jmap.SearchResultsTemplate[Snippet]
|
||||
|
||||
type EmailWithSnippets struct {
|
||||
AccountId string `json:"accountId,omitempty"`
|
||||
AccountId string `json:"accountId,omitempty"`
|
||||
Snippets []SnippetWithoutEmailId `json:"snippets,omitempty"`
|
||||
jmap.Email
|
||||
Snippets []SnippetWithoutEmailId `json:"snippets,omitempty"`
|
||||
}
|
||||
|
||||
type Snippet struct {
|
||||
@@ -365,22 +360,9 @@ type SnippetWithoutEmailId struct {
|
||||
Preview string `json:"preview,omitempty"`
|
||||
}
|
||||
|
||||
type EmailWithSnippetsSearchResults struct {
|
||||
Results []EmailWithSnippets `json:"results"`
|
||||
Total *uint `json:"total,omitzero"`
|
||||
Position uint `json:"position"`
|
||||
Limit uint `json:"limit,omitzero"`
|
||||
QueryState jmap.State `json:"queryState,omitempty"`
|
||||
}
|
||||
type EmailWithSnippetsSearchResults jmap.SearchResultsTemplate[EmailWithSnippets]
|
||||
|
||||
type EmailSearchResults struct {
|
||||
Results []jmap.Email `json:"results"`
|
||||
Total uint `json:"total,omitzero"`
|
||||
Limit uint `json:"limit,omitzero"`
|
||||
QueryState jmap.State `json:"queryState,omitempty"`
|
||||
}
|
||||
|
||||
func (g *Groupware) buildEmailFilter(req Request) (bool, jmap.EmailFilterElement, bool, int, uint, *log.Logger, *Error) { //NOSONAR
|
||||
func (g *Groupware) buildEmailFilter(req Request) (bool, jmap.EmailFilterElement, bool, int, *uint, *log.Logger, *Error) { //NOSONAR
|
||||
mailboxId, _ := req.getStringParam(QueryParamMailboxId, "") // the identifier of the Mailbox to which to restrict the search
|
||||
text, _ := req.getStringParam(QueryParamSearchText, "") // text that must be included in the Email, specifically in From, To, Cc, Bcc, Subject and any text/* body part
|
||||
from, _ := req.getStringParam(QueryParamSearchFrom, "") // text that must be included in the From header of the Email
|
||||
@@ -392,11 +374,11 @@ func (g *Groupware) buildEmailFilter(req Request) (bool, jmap.EmailFilterElement
|
||||
messageId, _ := req.getStringParam(QueryParamSearchMessageId, "") // value of the Message-ID header of the Email
|
||||
notInMailboxIds, _, err := req.parseOptStringListParam(QueryParamNotInMailboxId) // a comma-separated list of identifiers of Mailboxes the Email must *not* be in
|
||||
if err != nil {
|
||||
return false, nil, false, 0, 0, nil, err
|
||||
return false, nil, false, 0, nil, nil, err
|
||||
}
|
||||
keywords, _, err := req.parseOptStringListParam(QueryParamSearchKeyword) // the Email must have all those keywords
|
||||
if err != nil {
|
||||
return false, nil, false, 0, 0, nil, err
|
||||
return false, nil, false, 0, nil, nil, err
|
||||
}
|
||||
|
||||
snippets := false
|
||||
@@ -405,23 +387,27 @@ func (g *Groupware) buildEmailFilter(req Request) (bool, jmap.EmailFilterElement
|
||||
|
||||
position, ok, err := req.parseIntParam(QueryParamPosition, 0) // pagination element position (offset)
|
||||
if err != nil {
|
||||
return false, nil, snippets, 0, 0, nil, err
|
||||
return false, nil, snippets, 0, nil, nil, err
|
||||
}
|
||||
if ok {
|
||||
l = l.Int(QueryParamPosition, position)
|
||||
}
|
||||
|
||||
limit, ok, err := req.parseUIntParam(QueryParamLimit, g.defaults.emailLimit) // maximum number of results (size of a page)
|
||||
if err != nil {
|
||||
return false, nil, snippets, 0, 0, nil, err
|
||||
}
|
||||
if ok {
|
||||
l = l.Uint(QueryParamLimit, limit)
|
||||
var limit *uint = nil
|
||||
{
|
||||
v, ok, err := req.parseUIntParam(QueryParamLimit, g.defaults.emailLimit) // maximum number of results (size of a page)
|
||||
if err != nil {
|
||||
return false, nil, snippets, 0, nil, nil, err
|
||||
}
|
||||
if ok {
|
||||
l = l.Uint(QueryParamLimit, v)
|
||||
limit = &v
|
||||
}
|
||||
}
|
||||
|
||||
before, ok, err := req.parseDateParam(QueryParamSearchBefore) // the Email must have been received before this date-time
|
||||
if err != nil {
|
||||
return false, nil, snippets, 0, 0, nil, err
|
||||
return false, nil, snippets, 0, nil, nil, err
|
||||
}
|
||||
if ok {
|
||||
l = l.Time(QueryParamSearchBefore, before)
|
||||
@@ -429,7 +415,7 @@ func (g *Groupware) buildEmailFilter(req Request) (bool, jmap.EmailFilterElement
|
||||
|
||||
after, ok, err := req.parseDateParam(QueryParamSearchAfter) // the Email must have been received after this date-time
|
||||
if err != nil {
|
||||
return false, nil, snippets, 0, 0, nil, err
|
||||
return false, nil, snippets, 0, nil, nil, err
|
||||
}
|
||||
if ok {
|
||||
l = l.Time(QueryParamSearchAfter, after)
|
||||
@@ -468,7 +454,7 @@ func (g *Groupware) buildEmailFilter(req Request) (bool, jmap.EmailFilterElement
|
||||
|
||||
minSize, ok, err := req.parseIntParam(QueryParamSearchMinSize, 0) // the minimum size of the Email
|
||||
if err != nil {
|
||||
return false, nil, snippets, 0, 0, nil, err
|
||||
return false, nil, snippets, 0, nil, nil, err
|
||||
}
|
||||
if ok {
|
||||
l = l.Int(QueryParamSearchMinSize, minSize)
|
||||
@@ -476,7 +462,7 @@ func (g *Groupware) buildEmailFilter(req Request) (bool, jmap.EmailFilterElement
|
||||
|
||||
maxSize, ok, err := req.parseIntParam(QueryParamSearchMaxSize, 0) // the maximum size of the Email
|
||||
if err != nil {
|
||||
return false, nil, snippets, 0, 0, nil, err
|
||||
return false, nil, snippets, 0, nil, nil, err
|
||||
}
|
||||
if ok {
|
||||
l = l.Int(QueryParamSearchMaxSize, maxSize)
|
||||
@@ -575,47 +561,56 @@ func (g *Groupware) GetEmails(w http.ResponseWriter, r *http.Request) { //NOSONA
|
||||
logger = log.From(l)
|
||||
ctx := req.ctx.WithLogger(logger)
|
||||
|
||||
resultsByAccount, sessionState, state, lang, jerr := g.jmap.QueryEmailsWithSnippets(single(accountId), filter, position, limit, collapseThreads, calculateTotal, fetchBodies, g.config.maxBodyValueBytes, ctx)
|
||||
jmaplimit := limit
|
||||
if limit != nil && *limit == 0 {
|
||||
jmaplimit = UintPtrOne
|
||||
}
|
||||
|
||||
resultsByAccount, sessionState, state, lang, jerr := g.jmap.QueryEmailsWithSnippets(single(accountId), filter, position, jmaplimit, collapseThreads, calculateTotal, fetchBodies, g.config.maxBodyValueBytes, ctx)
|
||||
if jerr != nil {
|
||||
return req.jmapError(accountId, jerr, sessionState, lang)
|
||||
}
|
||||
|
||||
if results, ok := resultsByAccount[accountId]; ok {
|
||||
flattened := make([]EmailWithSnippets, len(results.Results))
|
||||
for i, result := range results.Results {
|
||||
var snippets []SnippetWithoutEmailId
|
||||
if makesSnippets {
|
||||
snippets := make([]SnippetWithoutEmailId, len(result.Snippets))
|
||||
for j, snippet := range result.Snippets {
|
||||
snippets[j] = SnippetWithoutEmailId{
|
||||
Subject: snippet.Subject,
|
||||
Preview: snippet.Preview,
|
||||
var flattened []EmailWithSnippets
|
||||
if limit != nil && *limit == 0 {
|
||||
flattened = nil
|
||||
} else {
|
||||
flattened = make([]EmailWithSnippets, len(results.Results))
|
||||
for i, result := range results.Results {
|
||||
var snippets []SnippetWithoutEmailId
|
||||
if makesSnippets {
|
||||
snippets := make([]SnippetWithoutEmailId, len(result.Snippets))
|
||||
for j, snippet := range result.Snippets {
|
||||
snippets[j] = SnippetWithoutEmailId{
|
||||
Subject: snippet.Subject,
|
||||
Preview: snippet.Preview,
|
||||
}
|
||||
}
|
||||
} else {
|
||||
snippets = nil
|
||||
}
|
||||
sanitized, err := req.sanitizeEmail(result.Email)
|
||||
if err != nil {
|
||||
return req.error(accountId, err)
|
||||
}
|
||||
flattened[i] = EmailWithSnippets{
|
||||
Email: sanitized,
|
||||
Snippets: snippets,
|
||||
}
|
||||
} else {
|
||||
snippets = nil
|
||||
}
|
||||
sanitized, err := req.sanitizeEmail(result.Email)
|
||||
if err != nil {
|
||||
return req.error(accountId, err)
|
||||
}
|
||||
flattened[i] = EmailWithSnippets{
|
||||
Email: sanitized,
|
||||
Snippets: snippets,
|
||||
}
|
||||
}
|
||||
|
||||
var total *uint = nil
|
||||
if calculateTotal {
|
||||
total = &results.Total
|
||||
rlimit := &results.Limit
|
||||
if limit != nil && *limit == 0 {
|
||||
rlimit = UintPtrZero
|
||||
}
|
||||
|
||||
return req.respond(accountId, EmailWithSnippetsSearchResults{
|
||||
Results: flattened,
|
||||
Total: total,
|
||||
Position: results.Position,
|
||||
Limit: results.Limit,
|
||||
QueryState: results.QueryState,
|
||||
Results: flattened,
|
||||
Total: ptrIf(results.Total, calculateTotal),
|
||||
Position: results.Position,
|
||||
Limit: rlimit,
|
||||
}, sessionState, EmailResponseObjectType, state, lang)
|
||||
} else {
|
||||
return req.notFound(accountId, sessionState, EmailResponseObjectType, state)
|
||||
@@ -639,8 +634,13 @@ func (g *Groupware) GetEmailsForAllAccounts(w http.ResponseWriter, r *http.Reque
|
||||
filter = nil
|
||||
}
|
||||
|
||||
jmaplimit := limit
|
||||
if limit != nil && *limit == 0 {
|
||||
jmaplimit = UintPtrOne
|
||||
}
|
||||
|
||||
if makesSnippets {
|
||||
resultsByAccountId, sessionState, state, lang, jerr := g.jmap.QueryEmailSnippets(allAccountIds, filter, position, limit, ctx)
|
||||
resultsByAccountId, sessionState, state, lang, jerr := g.jmap.QueryEmailSnippets(allAccountIds, filter, position, jmaplimit, ctx)
|
||||
if jerr != nil {
|
||||
return req.jmapErrorN(allAccountIds, jerr, sessionState, lang)
|
||||
}
|
||||
@@ -654,35 +654,40 @@ func (g *Groupware) GetEmailsForAllAccounts(w http.ResponseWriter, r *http.Reque
|
||||
total += len(results.Results)
|
||||
}
|
||||
|
||||
flattened := make([]Snippet, total)
|
||||
{
|
||||
i := 0
|
||||
for accountId, results := range resultsByAccountId {
|
||||
for _, result := range results.Results {
|
||||
flattened[i] = Snippet{
|
||||
AccountId: accountId,
|
||||
SearchSnippetWithMeta: result,
|
||||
var flattened []Snippet
|
||||
if limit != nil && *limit == 0 {
|
||||
flattened = []Snippet{}
|
||||
} else {
|
||||
flattened = make([]Snippet, total)
|
||||
{
|
||||
i := 0
|
||||
for accountId, results := range resultsByAccountId {
|
||||
for _, result := range results.Results {
|
||||
flattened[i] = Snippet{
|
||||
AccountId: accountId,
|
||||
SearchSnippetWithMeta: result,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
slices.SortFunc(flattened, func(a, b Snippet) int { return a.ReceivedAt.Compare(b.ReceivedAt) })
|
||||
slices.SortFunc(flattened, func(a, b Snippet) int { return a.ReceivedAt.Compare(b.ReceivedAt) })
|
||||
}
|
||||
|
||||
// TODO position and limit over the aggregated results by account
|
||||
|
||||
body := EmailSearchSnippetsResults{
|
||||
Results: flattened,
|
||||
Total: totalOverAllAccounts,
|
||||
Limit: limit,
|
||||
QueryState: state,
|
||||
Results: flattened,
|
||||
Total: &totalOverAllAccounts,
|
||||
Limit: limit,
|
||||
}
|
||||
|
||||
return req.respondN(allAccountIds, body, sessionState, EmailResponseObjectType, state, lang)
|
||||
} else {
|
||||
withThreads := true
|
||||
calculateTotal := true
|
||||
|
||||
resultsByAccountId, sessionState, state, lang, jerr := g.jmap.QueryEmailSummaries(allAccountIds, filter, limit, withThreads, ctx)
|
||||
resultsByAccountId, sessionState, state, lang, jerr := g.jmap.QueryEmailSummaries(allAccountIds, filter, jmaplimit, withThreads, calculateTotal, ctx)
|
||||
if jerr != nil {
|
||||
return req.jmapErrorN(allAccountIds, jerr, sessionState, lang)
|
||||
}
|
||||
@@ -694,27 +699,31 @@ func (g *Groupware) GetEmailsForAllAccounts(w http.ResponseWriter, r *http.Reque
|
||||
total += len(results.Emails)
|
||||
}
|
||||
|
||||
flattened := make([]jmap.Email, total)
|
||||
{
|
||||
i := 0
|
||||
for accountId, results := range resultsByAccountId {
|
||||
for _, result := range results.Emails {
|
||||
result.AccountId = accountId
|
||||
flattened[i] = result
|
||||
i++
|
||||
var flattened []jmap.Email
|
||||
if limit != nil && *limit == 0 {
|
||||
flattened = []jmap.Email{}
|
||||
} else {
|
||||
flattened = make([]jmap.Email, total)
|
||||
{
|
||||
i := 0
|
||||
for accountId, results := range resultsByAccountId {
|
||||
for _, result := range results.Emails {
|
||||
result.AccountId = accountId
|
||||
flattened[i] = result
|
||||
i++
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
slices.SortFunc(flattened, func(a, b jmap.Email) int { return a.ReceivedAt.Compare(b.ReceivedAt) })
|
||||
slices.SortFunc(flattened, func(a, b jmap.Email) int { return a.ReceivedAt.Compare(b.ReceivedAt) })
|
||||
}
|
||||
|
||||
// TODO position and limit over the aggregated results by account
|
||||
|
||||
body := EmailSearchResults{
|
||||
Results: flattened,
|
||||
Total: totalAcrossAllAccounts,
|
||||
Limit: limit,
|
||||
QueryState: state,
|
||||
body := jmap.EmailSearchResults{
|
||||
Results: flattened,
|
||||
Total: &totalAcrossAllAccounts,
|
||||
Limit: limit,
|
||||
}
|
||||
|
||||
return req.respondN(allAccountIds, body, sessionState, EmailResponseObjectType, state, lang)
|
||||
@@ -1410,6 +1419,10 @@ type EmailSummary struct {
|
||||
Preview string `json:"preview,omitempty"`
|
||||
}
|
||||
|
||||
var _ jmap.Foo = EmailSummary{}
|
||||
|
||||
func (e EmailSummary) GetObjectType() jmap.ObjectType { return jmap.EmailType }
|
||||
|
||||
func summarizeEmail(accountId string, email jmap.Email) EmailSummary {
|
||||
return EmailSummary{
|
||||
AccountId: accountId,
|
||||
@@ -1438,13 +1451,7 @@ type emailWithAccountId struct {
|
||||
email jmap.Email
|
||||
}
|
||||
|
||||
type EmailSummaries struct {
|
||||
Emails []EmailSummary `json:"emails,omitempty"`
|
||||
Total uint `json:"total,omitzero"`
|
||||
Limit uint `json:"limit,omitzero"`
|
||||
Position uint `json:"position,omitzero"`
|
||||
State jmap.State `json:"state,omitempty"`
|
||||
}
|
||||
type EmailSummaries jmap.SearchResultsTemplate[EmailSummary]
|
||||
|
||||
// Get a summary of the latest emails across all the mailboxes, across all of a user's accounts.
|
||||
//
|
||||
@@ -1513,7 +1520,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, filter, limit, true, ctx)
|
||||
calculateTotal := true
|
||||
withThreads := true
|
||||
emailsSummariesByAccount, sessionState, state, lang, jerr := g.jmap.QueryEmailSummaries(allAccountIds, filter, &limit, withThreads, calculateTotal, ctx)
|
||||
if jerr != nil {
|
||||
return req.jmapErrorN(allAccountIds, jerr, sessionState, lang)
|
||||
}
|
||||
@@ -1539,12 +1548,16 @@ func (g *Groupware) GetLatestEmailsSummaryForAllAccounts(w http.ResponseWriter,
|
||||
summaries[i] = summarizeEmail(all[i].accountId, all[i].email)
|
||||
}
|
||||
|
||||
return req.respondN(allAccountIds, EmailSummaries{
|
||||
Emails: summaries,
|
||||
Total: total,
|
||||
Limit: limit,
|
||||
body := EmailSummaries{
|
||||
Results: summaries,
|
||||
Limit: &limit,
|
||||
Position: position,
|
||||
}, sessionState, EmailResponseObjectType, state, lang)
|
||||
}
|
||||
if calculateTotal {
|
||||
body.Total = &total
|
||||
}
|
||||
|
||||
return req.respondN(allAccountIds, body, sessionState, EmailResponseObjectType, state, lang)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -10,60 +10,71 @@ import (
|
||||
|
||||
// Get all the events in a calendar of an account by its identifier.
|
||||
func (g *Groupware) GetEventsInCalendar(w http.ResponseWriter, r *http.Request) { //NOSONAR
|
||||
g.respond(w, r, func(req Request) Response {
|
||||
ok, accountId, resp := req.needCalendarWithAccount()
|
||||
if !ok {
|
||||
return resp
|
||||
}
|
||||
getallpaged(Event, w, r, g,
|
||||
true,
|
||||
func(calendarId string) jmap.CalendarEventFilterElement {
|
||||
return jmap.CalendarEventFilterCondition{InCalendar: calendarId}
|
||||
},
|
||||
[]jmap.CalendarEventComparator{{Property: jmap.CalendarEventPropertyStart, IsAscending: true}},
|
||||
curryMapQuery(g.jmap.QueryCalendarEvents),
|
||||
)
|
||||
|
||||
l := req.logger.With()
|
||||
/*
|
||||
g.respond(w, r, func(req Request) Response {
|
||||
ok, accountId, resp := req.needCalendarWithAccount()
|
||||
if !ok {
|
||||
return resp
|
||||
}
|
||||
|
||||
calendarId, err := req.PathParam(UriParamCalendarId)
|
||||
if err != nil {
|
||||
return req.error(accountId, err)
|
||||
}
|
||||
l = l.Str(UriParamCalendarId, log.SafeString(calendarId))
|
||||
l := req.logger.With()
|
||||
|
||||
position, ok, err := req.parseIntParam(QueryParamPosition, 0)
|
||||
if err != nil {
|
||||
return req.error(accountId, err)
|
||||
}
|
||||
if ok {
|
||||
l = l.Int(QueryParamPosition, position)
|
||||
}
|
||||
calendarId, err := req.PathParam(UriParamCalendarId)
|
||||
if err != nil {
|
||||
return req.error(accountId, err)
|
||||
}
|
||||
l = l.Str(UriParamCalendarId, log.SafeString(calendarId))
|
||||
|
||||
limit, ok, err := req.parseUIntParam(QueryParamLimit, g.defaults.contactLimit)
|
||||
if err != nil {
|
||||
return req.error(accountId, err)
|
||||
}
|
||||
if ok {
|
||||
l = l.Uint(QueryParamLimit, limit)
|
||||
}
|
||||
position, ok, err := req.parseIntParam(QueryParamPosition, 0)
|
||||
if err != nil {
|
||||
return req.error(accountId, err)
|
||||
}
|
||||
if ok {
|
||||
l = l.Int(QueryParamPosition, position)
|
||||
}
|
||||
|
||||
filter := jmap.CalendarEventFilterCondition{
|
||||
InCalendar: calendarId,
|
||||
}
|
||||
sortBy := []jmap.CalendarEventComparator{{Property: jmap.CalendarEventPropertyStart, IsAscending: false}}
|
||||
limit, ok, err := req.parseUIntParam(QueryParamLimit, g.defaults.contactLimit)
|
||||
if err != nil {
|
||||
return req.error(accountId, err)
|
||||
}
|
||||
if ok {
|
||||
l = l.Uint(QueryParamLimit, limit)
|
||||
}
|
||||
|
||||
logger := log.From(l)
|
||||
ctx := req.ctx.WithLogger(logger)
|
||||
eventsByAccountId, sessionState, state, lang, jerr := g.jmap.QueryCalendarEvents(single(accountId), filter, sortBy, position, limit, true, ctx)
|
||||
if jerr != nil {
|
||||
return req.jmapError(accountId, jerr, sessionState, lang)
|
||||
}
|
||||
filter := jmap.CalendarEventFilterCondition{
|
||||
InCalendar: calendarId,
|
||||
}
|
||||
sortBy := []jmap.CalendarEventComparator{{Property: jmap.CalendarEventPropertyStart, IsAscending: false}}
|
||||
|
||||
if events, ok := eventsByAccountId[accountId]; ok {
|
||||
return req.respond(accountId, events, sessionState, EventResponseObjectType, state, lang)
|
||||
} else {
|
||||
return req.notFound(accountId, sessionState, EventResponseObjectType, state)
|
||||
}
|
||||
})
|
||||
logger := log.From(l)
|
||||
ctx := req.ctx.WithLogger(logger)
|
||||
eventsByAccountId, sessionState, state, lang, jerr := g.jmap.QueryCalendarEvents(single(accountId), filter, sortBy, position, limit, true, ctx)
|
||||
if jerr != nil {
|
||||
return req.jmapError(accountId, jerr, sessionState, lang)
|
||||
}
|
||||
|
||||
if events, ok := eventsByAccountId[accountId]; ok {
|
||||
return req.respond(accountId, events, sessionState, EventResponseObjectType, state, lang)
|
||||
} else {
|
||||
return req.notFound(accountId, sessionState, EventResponseObjectType, state)
|
||||
}
|
||||
})
|
||||
*/
|
||||
}
|
||||
|
||||
func curryMapQuery[SRES jmap.SearchResults[T], T jmap.Foo, FILTER any, COMP any](
|
||||
f func(accountIds []string, filter FILTER, sortBy []COMP, position int, limit uint, calculateTotal bool, ctx jmap.Context) (map[string]SRES, jmap.SessionState, jmap.State, jmap.Language, jmap.Error),
|
||||
) func(req Request, accountId string, filter FILTER, sortBy []COMP, position int, limit uint, ctx jmap.Context) (SRES, jmap.SessionState, jmap.State, jmap.Language, jmap.Error) {
|
||||
return func(req Request, accountId string, filter FILTER, sortBy []COMP, position int, limit uint, ctx jmap.Context) (SRES, jmap.SessionState, jmap.State, jmap.Language, jmap.Error) {
|
||||
f func(accountIds []string, filter FILTER, sortBy []COMP, position int, limit *uint, calculateTotal bool, ctx jmap.Context) (map[string]SRES, jmap.SessionState, jmap.State, jmap.Language, jmap.Error),
|
||||
) func(req Request, accountId string, filter FILTER, sortBy []COMP, position int, limit *uint, ctx jmap.Context) (SRES, jmap.SessionState, jmap.State, jmap.Language, jmap.Error) {
|
||||
return func(req Request, accountId string, filter FILTER, sortBy []COMP, position int, limit *uint, ctx jmap.Context) (SRES, jmap.SessionState, jmap.State, jmap.Language, jmap.Error) {
|
||||
m, sessionState, state, lang, err := f(single(accountId), filter, sortBy, position, limit, true, ctx)
|
||||
return m[accountId], sessionState, state, lang, err
|
||||
}
|
||||
@@ -71,9 +82,8 @@ func curryMapQuery[SRES jmap.SearchResults[T], T jmap.Foo, FILTER any, COMP any]
|
||||
|
||||
func (g *Groupware) GetAllEvents(w http.ResponseWriter, r *http.Request) {
|
||||
getallpaged(Event, w, r, g,
|
||||
func(cid string) jmap.CalendarEventFilterElement {
|
||||
return jmap.CalendarEventFilterCondition{InCalendar: cid}
|
||||
},
|
||||
false,
|
||||
func(_ string) jmap.CalendarEventFilterElement { return jmap.CalendarEventFilterCondition{} },
|
||||
[]jmap.CalendarEventComparator{{Property: jmap.CalendarEventPropertyStart, IsAscending: true}},
|
||||
curryMapQuery(g.jmap.QueryCalendarEvents),
|
||||
)
|
||||
|
||||
@@ -26,6 +26,8 @@ func (g *Groupware) ModifyMailbox(w http.ResponseWriter, r *http.Request) {
|
||||
modify(Mailbox, w, r, g, g.jmap.UpdateMailbox)
|
||||
}
|
||||
|
||||
var GetMailboxesParams = toSupportedQueryParams(QueryParamMailboxSearchName, QueryParamMailboxSearchRole, QueryParamMailboxSearchSubscribed)
|
||||
|
||||
// Get the list of all the mailboxes of an account, potentially filtering on the
|
||||
// name and/or role of the mailbox.
|
||||
//
|
||||
@@ -65,6 +67,10 @@ func (g *Groupware) GetMailboxes(w http.ResponseWriter, r *http.Request) { //NOS
|
||||
hasCriteria = true
|
||||
}
|
||||
|
||||
if notok, resp := req.unsupportedQueryParams(single(accountId), GetMailboxesParams); notok {
|
||||
return resp
|
||||
}
|
||||
|
||||
logger := log.From(req.logger.With().Str(logAccountId, accountId))
|
||||
ctx := req.ctx.WithLogger(logger)
|
||||
|
||||
|
||||
@@ -162,19 +162,6 @@ func (e Exemplar) MailboxesByAccountIdFilteredOnInboxRole() (map[string][]jmap.M
|
||||
}, "All mailboxes for all accounts, filtered on the 'inbox' role", "inboxrole"
|
||||
}
|
||||
|
||||
func (e Exemplar) EmailSearchResults() EmailSearchResults {
|
||||
j := jmap.ExemplarInstance
|
||||
email := j.Email()
|
||||
email.BodyStructure = nil
|
||||
email.BodyValues = nil
|
||||
return EmailSearchResults{
|
||||
Results: []jmap.Email{email},
|
||||
Total: 132,
|
||||
Limit: 1,
|
||||
QueryState: "seehug3p",
|
||||
}
|
||||
}
|
||||
|
||||
func (e Exemplar) MailboxRolesByAccounts() (map[string][]string, string, string, string) {
|
||||
j := jmap.ExemplarInstance
|
||||
return map[string][]string{
|
||||
|
||||
@@ -50,7 +50,7 @@ var (
|
||||
plural: "contacts",
|
||||
responseType: ContactResponseObjectType,
|
||||
uriParamName: UriParamContactId,
|
||||
containerUriParamName: UriParamCalendarId,
|
||||
containerUriParamName: UriParamAddressBookId,
|
||||
accountFunc: (*Request).needCalendarWithAccount,
|
||||
failedToDeleteError: ErrorFailedToDeleteContact,
|
||||
}
|
||||
|
||||
@@ -227,11 +227,19 @@ func (r *Request) parameterErrorResponse(accountIds []string, param string, deta
|
||||
return r.errorN(accountIds, r.parameterError(param, detail))
|
||||
}
|
||||
|
||||
func (r *Request) unsupportedParams(accountIds []string, params ...string) (bool, Response) {
|
||||
type supportedQueryParams map[string]struct{}
|
||||
|
||||
func toSupportedQueryParams(params ...string) supportedQueryParams {
|
||||
return structs.Set(params)
|
||||
}
|
||||
|
||||
var noSupportedQueryParams supportedQueryParams = toSupportedQueryParams()
|
||||
|
||||
func (r *Request) unsupportedQueryParams(accountIds []string, allowed supportedQueryParams) (bool, Response) {
|
||||
q := r.r.URL.Query()
|
||||
for _, p := range params {
|
||||
if q.Has(p) {
|
||||
return true, r.parameterErrorResponse(accountIds, p, "Unsupported query parameter")
|
||||
for n := range q {
|
||||
if _, ok := allowed[n]; !ok {
|
||||
return true, r.parameterErrorResponse(accountIds, n, "Unsupported query parameter")
|
||||
}
|
||||
}
|
||||
return false, Response{}
|
||||
@@ -390,9 +398,11 @@ func (r *Request) parseOptStringListParam(param string) ([]string, bool, *Error)
|
||||
return result, true, nil
|
||||
}
|
||||
|
||||
/*
|
||||
func (r *Request) bodydoc(target any, _ string) *Error {
|
||||
return r.body(target)
|
||||
}
|
||||
*/
|
||||
|
||||
func (r *Request) body(target any) *Error {
|
||||
body := r.r.Body
|
||||
@@ -687,18 +697,6 @@ func (r *Request) parseSort(s string, props []string) ([]SortCrit, *Error) {
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func mapSort[T any](accountIds []string, req *Request, defaultSort []T, props []string, mapper func(SortCrit) T) ([]T, bool, Response) {
|
||||
if sortSpec, ok := req.getStringParam(QueryParamSort, ""); ok && strings.TrimSpace(sortSpec) != "" {
|
||||
if sort, err := req.parseSort(sortSpec, props); err != nil {
|
||||
return nil, false, errorResponse(accountIds, err, req.session.State, jmap.NoLanguage)
|
||||
} else {
|
||||
return structs.Map(sort, mapper), true, Response{}
|
||||
}
|
||||
} else {
|
||||
return defaultSort, true, Response{}
|
||||
}
|
||||
}
|
||||
|
||||
func toState(s string) jmap.State {
|
||||
return jmap.State(s)
|
||||
}
|
||||
|
||||
@@ -170,10 +170,6 @@ func (r *Request) notFound(accountId string, sessionState jmap.SessionState, obj
|
||||
return notFoundResponse(single(accountId), sessionState, objectType, etag)
|
||||
}
|
||||
|
||||
func (r *Request) notFoundN(accountIds []string, sessionState jmap.SessionState, objectType ResponseObjectType, etag jmap.State) Response {
|
||||
return notFoundResponse(accountIds, sessionState, objectType, etag)
|
||||
}
|
||||
|
||||
func etaggedNotFoundResponse(accountIds []string, sessionState jmap.SessionState, objectType ResponseObjectType, etag jmap.State, contentLanguage jmap.Language) Response {
|
||||
return Response{
|
||||
accountIds: accountIds,
|
||||
|
||||
@@ -24,6 +24,10 @@ func create[T jmap.Foo, CHANGE jmap.Change, CHANGES jmap.Changes[T]](
|
||||
}
|
||||
l := req.logger.With().Str(accountId, log.SafeString(accountId))
|
||||
|
||||
if notok, resp := req.unsupportedQueryParams(single(accountId), noSupportedQueryParams); notok {
|
||||
return resp
|
||||
}
|
||||
|
||||
var create CHANGE
|
||||
err := req.body(&create)
|
||||
if err != nil {
|
||||
@@ -62,7 +66,7 @@ func getall[T jmap.Foo, CHANGE jmap.Change, CHANGES jmap.Changes[T], RESP jmap.G
|
||||
}
|
||||
l := req.logger.With().Str(accountId, log.SafeString(accountId))
|
||||
|
||||
if notok, resp := req.unsupportedParams(single(accountId), QueryParamPosition, QueryParamLimit); notok {
|
||||
if notok, resp := req.unsupportedQueryParams(single(accountId), noSupportedQueryParams); notok {
|
||||
return resp
|
||||
}
|
||||
|
||||
@@ -76,15 +80,18 @@ func getall[T jmap.Foo, CHANGE jmap.Change, CHANGES jmap.Changes[T], RESP jmap.G
|
||||
})
|
||||
}
|
||||
|
||||
var paginationQueryParams = toSupportedQueryParams(QueryParamPosition, QueryParamLimit)
|
||||
|
||||
// Retrieve all the {{.Name}} with support for paging using the {{.QueryParam.QueryParamPosition.Name}} and {{.QueryParam.QueryParamLimit.Name}} query parameters.
|
||||
// @api:response 200:SEARCHRESULTS returns the {{.Names}} within the requested range, as well as the total amount of {{.Names}}
|
||||
func getallpaged[T jmap.Foo, CHANGE jmap.Change, CHANGES jmap.Changes[T], FILTER any, COMP any, SEARCHRESULTS jmap.SearchResults[T]]( //NOSONAR
|
||||
o ObjectType[T, CHANGE, CHANGES],
|
||||
w http.ResponseWriter, r *http.Request,
|
||||
g *Groupware,
|
||||
withContainerId bool,
|
||||
filterFunc func(containerId string) FILTER,
|
||||
sortBy []COMP,
|
||||
queryFunc func(req Request, accountId string, filter FILTER, sortBy []COMP, position int, limit uint, ctx jmap.Context) (SEARCHRESULTS, jmap.SessionState, jmap.State, jmap.Language, jmap.Error),
|
||||
queryFunc func(req Request, accountId string, filter FILTER, sortBy []COMP, position int, limit *uint, ctx jmap.Context) (SEARCHRESULTS, jmap.SessionState, jmap.State, jmap.Language, jmap.Error),
|
||||
) {
|
||||
g.respond(w, r, func(req Request) Response {
|
||||
ok, accountId, resp := o.accountFunc(&req)
|
||||
@@ -101,16 +108,20 @@ func getallpaged[T jmap.Foo, CHANGE jmap.Change, CHANGES jmap.Changes[T], FILTER
|
||||
l = l.Int(QueryParamPosition, position)
|
||||
}
|
||||
|
||||
limit, ok, err := req.parseUIntParam(QueryParamLimit, uint(0))
|
||||
if err != nil {
|
||||
return req.error(accountId, err)
|
||||
}
|
||||
if ok {
|
||||
l = l.Uint(QueryParamLimit, limit)
|
||||
var limit *uint = nil
|
||||
{
|
||||
v, ok, err := req.parseUIntParam(QueryParamLimit, uint(0))
|
||||
if err != nil {
|
||||
return req.error(accountId, err)
|
||||
}
|
||||
if ok {
|
||||
l = l.Uint(QueryParamLimit, v)
|
||||
limit = &v
|
||||
}
|
||||
}
|
||||
|
||||
containerId := ""
|
||||
if o.containerUriParamName != "" {
|
||||
if withContainerId && o.containerUriParamName != "" {
|
||||
var err *Error
|
||||
containerId, err = req.PathParam(o.containerUriParamName)
|
||||
if err != nil {
|
||||
@@ -119,14 +130,29 @@ func getallpaged[T jmap.Foo, CHANGE jmap.Change, CHANGES jmap.Changes[T], FILTER
|
||||
l = l.Str(o.containerUriParamName, log.SafeString(containerId))
|
||||
}
|
||||
|
||||
if notok, resp := req.unsupportedQueryParams(single(accountId), paginationQueryParams); notok {
|
||||
return resp
|
||||
}
|
||||
|
||||
filter := filterFunc(containerId)
|
||||
|
||||
jmaplimit := limit
|
||||
if limit != nil && *limit == 0 {
|
||||
jmaplimit = UintPtrOne
|
||||
}
|
||||
|
||||
logger := log.From(l)
|
||||
ctx := req.ctx.WithLogger(logger)
|
||||
results, sessionState, state, lang, jerr := queryFunc(req, accountId, filter, sortBy, position, limit, ctx)
|
||||
results, sessionState, state, lang, jerr := queryFunc(req, accountId, filter, sortBy, position, jmaplimit, ctx)
|
||||
if jerr != nil {
|
||||
return req.jmapError(accountId, jerr, sessionState, lang)
|
||||
}
|
||||
|
||||
if limit != nil && *limit == 0 {
|
||||
results.RemoveResults()
|
||||
results.SetLimit(UintPtrZero)
|
||||
}
|
||||
|
||||
return req.respond(accountId, results, sessionState, o.responseType, state, lang)
|
||||
})
|
||||
}
|
||||
@@ -138,7 +164,7 @@ func query[T jmap.Foo, CHANGE jmap.Change, CHANGES jmap.Changes[T], SEARCHRESULT
|
||||
w http.ResponseWriter, r *http.Request,
|
||||
g *Groupware,
|
||||
defaultLimit uint,
|
||||
queryFunc func(req Request, accountId string, containerId string, position int, limit uint, ctx jmap.Context) (SEARCHRESULTS, jmap.SessionState, jmap.State, jmap.Language, *Error),
|
||||
queryFunc func(req Request, accountId string, containerId string, position int, limit *uint, ctx jmap.Context) (SEARCHRESULTS, jmap.SessionState, jmap.State, jmap.Language, *Error),
|
||||
) {
|
||||
g.respond(w, r, func(req Request) Response {
|
||||
ok, accountId, resp := o.accountFunc(&req)
|
||||
@@ -165,22 +191,38 @@ func query[T jmap.Foo, CHANGE jmap.Change, CHANGES jmap.Changes[T], SEARCHRESULT
|
||||
l = l.Int(QueryParamPosition, position)
|
||||
}
|
||||
|
||||
limit, ok, err := req.parseUIntParam(QueryParamLimit, defaultLimit)
|
||||
if err != nil {
|
||||
return req.error(accountId, err)
|
||||
}
|
||||
if ok {
|
||||
l = l.Uint(QueryParamLimit, limit)
|
||||
var limit *uint = nil
|
||||
{
|
||||
v, ok, err := req.parseUIntParam(QueryParamLimit, defaultLimit)
|
||||
if err != nil {
|
||||
return req.error(accountId, err)
|
||||
}
|
||||
if ok {
|
||||
l = l.Uint(QueryParamLimit, v)
|
||||
limit = &v
|
||||
} else if defaultLimit > 0 {
|
||||
limit = &defaultLimit
|
||||
}
|
||||
}
|
||||
|
||||
logger := log.From(l)
|
||||
ctx := req.ctx.WithLogger(logger)
|
||||
|
||||
results, sessionState, state, lang, err := queryFunc(req, accountId, containerId, position, limit, ctx)
|
||||
jmaplimit := limit
|
||||
if limit != nil && *limit == 0 {
|
||||
jmaplimit = UintPtrOne
|
||||
}
|
||||
|
||||
results, sessionState, state, lang, err := queryFunc(req, accountId, containerId, position, jmaplimit, ctx)
|
||||
if err != nil {
|
||||
return req.error(accountId, err)
|
||||
}
|
||||
|
||||
if limit != nil && *limit == 0 {
|
||||
results.RemoveResults()
|
||||
results.SetLimit(UintPtrZero)
|
||||
}
|
||||
|
||||
return req.respond(accountId, results, sessionState, o.responseType, state, lang)
|
||||
})
|
||||
}
|
||||
@@ -210,6 +252,10 @@ func get[T jmap.Foo, CHANGE jmap.Change, CHANGES jmap.Changes[T], RESP jmap.GetR
|
||||
ids = single(id)
|
||||
}
|
||||
|
||||
if notok, resp := req.unsupportedQueryParams(single(accountId), noSupportedQueryParams); notok {
|
||||
return resp
|
||||
}
|
||||
|
||||
logger := log.From(l)
|
||||
ctx := req.ctx.WithLogger(logger)
|
||||
objs, sessionState, state, lang, jerr := getFunc(accountId, ids, ctx)
|
||||
@@ -251,6 +297,10 @@ func getFromMap[T jmap.Foo, CHANGE jmap.Change, CHANGES jmap.Changes[T], RESP jm
|
||||
}
|
||||
l.Str(o.uriParamName, log.SafeString(id))
|
||||
|
||||
if notok, resp := req.unsupportedQueryParams(single(accountId), noSupportedQueryParams); notok {
|
||||
return resp
|
||||
}
|
||||
|
||||
logger := log.From(l)
|
||||
ctx := req.ctx.WithLogger(logger)
|
||||
objMap, sessionState, state, lang, jerr := getFunc(single(accountId), single(id), ctx)
|
||||
@@ -275,6 +325,8 @@ func getFromMap[T jmap.Foo, CHANGE jmap.Change, CHANGES jmap.Changes[T], RESP jm
|
||||
})
|
||||
}
|
||||
|
||||
var changesSupportedQueryParams = toSupportedQueryParams(QueryParamMaxChanges)
|
||||
|
||||
// Retrieve the changes that occured for {{.Name}}, optionally since an opaque state specified using the header `{{.HeaderParam.HeaderParamSince}}`,
|
||||
// optionally bounded by the query parameter `{{.QueryParam.QueryParamMaxChanges}}`.
|
||||
// @api:response 200:CHANGES returns the changes to {{.Names}}: created, updated, and identifiers of destroyed {{.Names}}
|
||||
@@ -304,6 +356,10 @@ func changes[T jmap.Foo, CHANGE jmap.Change, CHANGES jmap.Changes[T]](
|
||||
l = l.Str(HeaderParamSince, log.SafeString(string(sinceState)))
|
||||
}
|
||||
|
||||
if notok, resp := req.unsupportedQueryParams(single(accountId), changesSupportedQueryParams); notok {
|
||||
return resp
|
||||
}
|
||||
|
||||
logger := log.From(l)
|
||||
ctx := req.ctx.WithLogger(logger)
|
||||
changes, sessionState, state, lang, jerr := changesFunc(accountId, sinceState, maxChanges, ctx)
|
||||
@@ -336,6 +392,10 @@ func delete[T jmap.Foo, CHANGE jmap.Change, CHANGES jmap.Changes[T]]( //NOSONAR
|
||||
}
|
||||
l.Str(o.uriParamName, log.SafeString(id))
|
||||
|
||||
if notok, resp := req.unsupportedQueryParams(single(accountId), noSupportedQueryParams); notok {
|
||||
return resp
|
||||
}
|
||||
|
||||
logger := log.From(l)
|
||||
ctx := req.ctx.WithLogger(logger)
|
||||
setErrors, sessionState, state, lang, jerr := deleteFunc(accountId, single(id), ctx)
|
||||
@@ -362,6 +422,8 @@ func delete[T jmap.Foo, CHANGE jmap.Change, CHANGES jmap.Changes[T]]( //NOSONAR
|
||||
})
|
||||
}
|
||||
|
||||
var deleteManySupportedQueryParams = toSupportedQueryParams(QueryParamId)
|
||||
|
||||
// Delete several {{.Name}} objects referenced by their unique identifiers as specified as an array in the body,
|
||||
// or using the query parameter `{{.QueryParam.QueryParamId}}`.
|
||||
// @api:response 204 when the referenced {{.Names}} have all been deleted successfully
|
||||
@@ -415,6 +477,10 @@ func deleteMany[T jmap.Foo, CHANGE jmap.Change, CHANGES jmap.Changes[T]]( //NOSO
|
||||
l.Array("ids", log.SafeStringArray(ids))
|
||||
}
|
||||
|
||||
if notok, resp := req.unsupportedQueryParams(single(accountId), deleteManySupportedQueryParams); notok {
|
||||
return resp
|
||||
}
|
||||
|
||||
logger := log.From(l)
|
||||
ctx := req.ctx.WithLogger(logger)
|
||||
setErrors, sessionState, state, lang, jerr := deleteFunc(accountId, ids, ctx)
|
||||
@@ -461,6 +527,10 @@ func modify[T jmap.Foo, CHANGE jmap.Change, CHANGES jmap.Changes[T]](
|
||||
}
|
||||
l.Str(o.uriParamName, log.SafeString(id))
|
||||
|
||||
if notok, resp := req.unsupportedQueryParams(single(accountId), noSupportedQueryParams); notok {
|
||||
return resp
|
||||
}
|
||||
|
||||
var change CHANGE
|
||||
err = req.body(&change)
|
||||
if err != nil {
|
||||
|
||||
@@ -18,3 +18,18 @@ func trimmed(it iter.Seq[string]) iter.Seq[string] {
|
||||
func notEmptyString(it iter.Seq[string]) iter.Seq[string] {
|
||||
return structs.FilterSeq(it, func(s string) bool { return s != "" })
|
||||
}
|
||||
|
||||
func uintPtr(v uint) *uint {
|
||||
return &v
|
||||
}
|
||||
|
||||
var UintPtrOne *uint = uintPtr(1)
|
||||
var UintPtrZero *uint = uintPtr(0)
|
||||
|
||||
func ptrIf[T any | uint | int | bool](t T, predicate bool) *T {
|
||||
if predicate {
|
||||
return &t
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user