From 7ed065ddfeb346743d78eb5250de119a023249d3 Mon Sep 17 00:00:00 2001
From: Pascal Bleser
Date: Wed, 24 Jun 2026 15:42:09 +0200
Subject: [PATCH] groupware: fix query objects in a container with multiple
suppliers
* by adding a predicate that can analyze a filter and inform the
template methods whether the filter is applicable for that supplier
or not
* this fixed /groupware/accounts/.../addressbooks/.../contacts
---
services/groupware/pkg/groupware/api_contacts.go | 11 +++++++++++
services/groupware/pkg/groupware/suppliers.go | 8 ++++++++
services/groupware/pkg/groupware/suppliers_test.go | 3 +++
3 files changed, 22 insertions(+)
diff --git a/services/groupware/pkg/groupware/api_contacts.go b/services/groupware/pkg/groupware/api_contacts.go
index cfe96db940..dc460459c9 100644
--- a/services/groupware/pkg/groupware/api_contacts.go
+++ b/services/groupware/pkg/groupware/api_contacts.go
@@ -86,6 +86,17 @@ func (g *Groupware) contacts(accountIds []jmap.AccountId, qps QueryParamsSupplie
filter jmap.ContactCardFilterElement, sortBy []jmap.ContactCardComparator, calculateTotal bool,
ctx jmap.Context) (jmap.Result[*jmap.ContactCardSearchResults], NextToken, error) {
return squery(g.contactCardQuerySuppliers, accountIds, qps, limit, filter, sortBy, calculateTotal, ctx,
+ func(supplier QuerySupplier[jmap.ContactCard, *jmap.ContactCardSearchResults, jmap.ContactCardFilterElement, jmap.ContactCardComparator], filter jmap.ContactCardFilterElement) bool {
+ switch c := filter.(type) {
+ case jmap.ContactCardFilterCondition:
+ if c.InAddressBook != "" {
+ if !supplier.IsMine(c.InAddressBook) {
+ return false
+ }
+ }
+ }
+ return true
+ },
func(a, b jmap.ContactCard) int { return a.Created.Compare(b.Created) },
func(canCalculateChanges jmap.ChangeCalculation, position, limit, total *uint, results []jmap.ContactCard) *jmap.ContactCardSearchResults {
return &jmap.ContactCardSearchResults{
diff --git a/services/groupware/pkg/groupware/suppliers.go b/services/groupware/pkg/groupware/suppliers.go
index 47ec77fa0f..d7535f465b 100644
--- a/services/groupware/pkg/groupware/suppliers.go
+++ b/services/groupware/pkg/groupware/suppliers.go
@@ -13,6 +13,8 @@ import (
type SupplierId string
+const EmptySupplierId = ""
+
type Supplier[T jmap.Foo] interface {
GetId() SupplierId
IsMine(id string) bool
@@ -171,6 +173,7 @@ func fillMissingAccounts(qps QueryParamsSupplier, supplierId SupplierId, account
func squery[T jmap.Idable, R jmap.SearchResults[T], S QuerySupplier[T, R, F, C], F jmap.FilterElement[T], C jmap.Comparator[T]]( //NOSONAR
suppliers []S, accountIds []jmap.AccountId, qps QueryParamsSupplier, limit *uint, filter F, sortBy []C,
calculateTotal bool, ctx jmap.Context,
+ filterSupplierPredicate func(supplier S, filter F) bool,
sorter func(T, T) int,
searchResultCtor func(canCalculateChanges jmap.ChangeCalculation, position *uint, limit *uint, total *uint, results []T) R) (
jmap.Result[R], NextToken, error,
@@ -296,6 +299,11 @@ func squery[T jmap.Idable, R jmap.SearchResults[T], S QuerySupplier[T, R, F, C],
lang := jmap.NoLanguage
durations := make([][]time.Duration, len(suppliers))
for i, supplier := range suppliers {
+ if !filterSupplierPredicate(supplier, filter) {
+ // the filter is not applicable for this supplier => skip
+ continue
+ }
+
// we are not injecting id prefixes here for all the objects, as each supplier is responsible for doing that if necessary
if result, err := supplier.Query(accountIds, qps, limit, filter, sortBy, calculateTotal, ctx); err != nil {
return jmap.ZeroResult[R](result.Durations), NoNextToken, err
diff --git a/services/groupware/pkg/groupware/suppliers_test.go b/services/groupware/pkg/groupware/suppliers_test.go
index 9972314585..e08f198f9a 100644
--- a/services/groupware/pkg/groupware/suppliers_test.go
+++ b/services/groupware/pkg/groupware/suppliers_test.go
@@ -101,6 +101,9 @@ func pets(
calculateTotal bool,
ctx jmap.Context) (jmap.Result[*PetSearchResults], NextToken, error) {
return squery(suppliers, accountIds, qps, limit, filter, sortBy, calculateTotal, ctx,
+ func(supplier QuerySupplier[Pet, *PetSearchResults, PetFilterElement, PetComparator], filter PetFilterElement) bool {
+ return true
+ },
func(a, b Pet) int { return strings.Compare(a.name, b.name) },
func(canCalculateChanges jmap.ChangeCalculation, position, limit, total *uint, results []Pet) *PetSearchResults {
return &PetSearchResults{