mirror of
https://github.com/opencloud-eu/opencloud.git
synced 2026-07-13 00:52:01 -04:00
groupware: add suppliers support for object changes
This commit is contained in:
@@ -397,3 +397,12 @@ func Flatten[T any](s [][]T) []T {
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func AllMatch[T any](s []T, predicate func(e T) bool) bool {
|
||||
for _, e := range s {
|
||||
if !predicate(e) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ func (g *Groupware) GetAddressbookById(w http.ResponseWriter, r *http.Request) {
|
||||
// Get the changes to Address Books since a certain State.
|
||||
// @api:tags addressbook,changes
|
||||
func (g *Groupware) GetAddressBookChanges(w http.ResponseWriter, r *http.Request) {
|
||||
changes(AddressBook, w, r, g, g.jmap.GetAddressbookChanges)
|
||||
changes(AddressBook, w, r, g, g.addressbooksChanges)
|
||||
}
|
||||
|
||||
func (g *Groupware) CreateAddressBook(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -39,3 +39,11 @@ func (g *Groupware) addressbooks(accountId jmap.AccountId, ids []string, ctx jma
|
||||
return jmap.AddressBookGetResponse{AccountId: accountId, State: state, NotFound: notFound, List: list}
|
||||
})
|
||||
}
|
||||
|
||||
func (g *Groupware) addressbooksChanges(accountId jmap.AccountId, sinceState jmap.State, maxChanges uint, ctx jmap.Context) (jmap.Result[jmap.AddressBookChanges], error) {
|
||||
return schanges(g.addressBookChangesSuppliers, accountId, sinceState, maxChanges, ctx,
|
||||
func(accountId jmap.AccountId, oldState, newState jmap.State, created, updated []jmap.AddressBook, destroyed []string, hasMoreChanges bool) jmap.AddressBookChanges {
|
||||
return jmap.AddressBookChanges{HasMoreChanges: hasMoreChanges, OldState: oldState, NewState: newState, Created: created, Updated: updated, Destroyed: destroyed}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@@ -67,7 +67,7 @@ func (g *Groupware) GetAllContacts(w http.ResponseWriter, r *http.Request) {
|
||||
// Get changes to Contacts since a given State
|
||||
// @api:tags contact,changes
|
||||
func (g *Groupware) GetContactsChanges(w http.ResponseWriter, r *http.Request) {
|
||||
changes(Contact, w, r, g, g.jmap.GetContactCardChanges)
|
||||
changes(Contact, w, r, g, g.contactsChanges)
|
||||
}
|
||||
|
||||
func (g *Groupware) CreateContact(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -83,9 +83,19 @@ func (g *Groupware) ModifyContact(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
func (g *Groupware) contacts(accountId jmap.AccountId, ids []string, ctx jmap.Context) (jmap.Result[jmap.ContactCardGetResponse], error) {
|
||||
return slist(g.contactCardListSuppliers, accountId, ids, ctx, func(accountId jmap.AccountId, state jmap.State, notFound []string, list []jmap.ContactCard) jmap.ContactCardGetResponse {
|
||||
return jmap.ContactCardGetResponse{AccountId: accountId, State: state, NotFound: notFound, List: list}
|
||||
})
|
||||
return slist(g.contactCardListSuppliers, accountId, ids, ctx,
|
||||
func(accountId jmap.AccountId, state jmap.State, notFound []string, list []jmap.ContactCard) jmap.ContactCardGetResponse {
|
||||
return jmap.ContactCardGetResponse{AccountId: accountId, State: state, NotFound: notFound, List: list}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
func (g *Groupware) contactsChanges(accountId jmap.AccountId, sinceState jmap.State, maxChanges uint, ctx jmap.Context) (jmap.Result[jmap.ContactCardChanges], error) {
|
||||
return schanges(g.contactCardChangesSuppliers, accountId, sinceState, maxChanges, ctx,
|
||||
func(accountId jmap.AccountId, oldState, newState jmap.State, created, updated []jmap.ContactCard, destroyed []string, hasMoreChanges bool) jmap.ContactCardChanges {
|
||||
return jmap.ContactCardChanges{HasMoreChanges: hasMoreChanges, OldState: oldState, NewState: newState, Created: created, Updated: updated, Destroyed: destroyed}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
func (g *Groupware) queryContacts(accountIds []jmap.AccountId, qps QueryParamsSupplier, limit *uint, //NOSONAR
|
||||
|
||||
@@ -117,9 +117,11 @@ type Groupware struct {
|
||||
// A threadsafe counter to generate the job IDs.
|
||||
jobCounter atomic.Uint64
|
||||
|
||||
addressBookListSuppliers []ListSupplier[jmap.AddressBook, jmap.AddressBookGetResponse]
|
||||
contactCardQuerySuppliers []QuerySupplier[jmap.ContactCard, *jmap.ContactCardSearchResults, jmap.ContactCardFilterElement, jmap.ContactCardComparator]
|
||||
contactCardListSuppliers []ListSupplier[jmap.ContactCard, jmap.ContactCardGetResponse]
|
||||
addressBookListSuppliers []ListSupplier[jmap.AddressBook, jmap.AddressBookGetResponse]
|
||||
addressBookChangesSuppliers []ChangesSupplier[jmap.AddressBook, jmap.AddressBookChanges]
|
||||
contactCardQuerySuppliers []QuerySupplier[jmap.ContactCard, *jmap.ContactCardSearchResults, jmap.ContactCardFilterElement, jmap.ContactCardComparator]
|
||||
contactCardListSuppliers []ListSupplier[jmap.ContactCard, jmap.ContactCardGetResponse]
|
||||
contactCardChangesSuppliers []ChangesSupplier[jmap.ContactCard, jmap.ContactCardChanges]
|
||||
}
|
||||
|
||||
// An error during the Groupware initialization.
|
||||
@@ -393,27 +395,33 @@ func NewGroupware(config *config.Config, logger *log.Logger, mux *chi.Mux, prome
|
||||
}
|
||||
|
||||
addressBookListSuppliers := []ListSupplier[jmap.AddressBook, jmap.AddressBookGetResponse]{}
|
||||
addressBookChangesSuppliers := []ChangesSupplier[jmap.AddressBook, jmap.AddressBookChanges]{}
|
||||
contactCardQuerySuppliers := []QuerySupplier[jmap.ContactCard, *jmap.ContactCardSearchResults, jmap.ContactCardFilterElement, jmap.ContactCardComparator]{}
|
||||
contactCardListSuppliers := []ListSupplier[jmap.ContactCard, jmap.ContactCardGetResponse]{}
|
||||
contactCardChangesSuppliers := []ChangesSupplier[jmap.ContactCard, jmap.ContactCardChanges]{}
|
||||
{
|
||||
{
|
||||
j := newJmapAddressBookSupplier(&jmapClient)
|
||||
addressBookListSuppliers = append(addressBookListSuppliers, j)
|
||||
addressBookChangesSuppliers = append(addressBookChangesSuppliers, j)
|
||||
}
|
||||
{
|
||||
j := newJmapContactCardSupplier(&jmapClient)
|
||||
contactCardQuerySuppliers = append(contactCardQuerySuppliers, j)
|
||||
contactCardListSuppliers = append(contactCardListSuppliers, j)
|
||||
contactCardChangesSuppliers = append(contactCardChangesSuppliers, j)
|
||||
}
|
||||
if config.EnableMockData {
|
||||
{
|
||||
m := newMockAddressBookSupplier()
|
||||
addressBookListSuppliers = append(addressBookListSuppliers, m)
|
||||
addressBookChangesSuppliers = append(addressBookChangesSuppliers, m)
|
||||
}
|
||||
{
|
||||
m := newMockContactCardSupplier()
|
||||
contactCardQuerySuppliers = append(contactCardQuerySuppliers, m)
|
||||
contactCardListSuppliers = append(contactCardListSuppliers, m)
|
||||
contactCardChangesSuppliers = append(contactCardChangesSuppliers, m)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -446,12 +454,14 @@ func NewGroupware(config *config.Config, logger *log.Logger, mux *chi.Mux, prome
|
||||
publicUrl: publicUrl,
|
||||
sendDurationsResponse: sendDurationsResponse,
|
||||
},
|
||||
eventChannel: eventChannel,
|
||||
jobsChannel: jobsChannel,
|
||||
jobCounter: atomic.Uint64{},
|
||||
addressBookListSuppliers: addressBookListSuppliers,
|
||||
contactCardQuerySuppliers: contactCardQuerySuppliers,
|
||||
contactCardListSuppliers: contactCardListSuppliers,
|
||||
eventChannel: eventChannel,
|
||||
jobsChannel: jobsChannel,
|
||||
jobCounter: atomic.Uint64{},
|
||||
addressBookListSuppliers: addressBookListSuppliers,
|
||||
addressBookChangesSuppliers: addressBookChangesSuppliers,
|
||||
contactCardQuerySuppliers: contactCardQuerySuppliers,
|
||||
contactCardListSuppliers: contactCardListSuppliers,
|
||||
contactCardChangesSuppliers: contactCardChangesSuppliers,
|
||||
}
|
||||
|
||||
for w := 1; w <= workerPoolSize; w++ {
|
||||
|
||||
@@ -11,6 +11,7 @@ type JmapAddressBookSupplier struct {
|
||||
}
|
||||
|
||||
var _ ListSupplier[jmap.AddressBook, jmap.AddressBookGetResponse] = &JmapAddressBookSupplier{}
|
||||
var _ ChangesSupplier[jmap.ContactCard, jmap.ContactCardChanges] = &JmapContactCardSupplier{}
|
||||
|
||||
func newJmapAddressBookSupplier(client *jmap.Client) *JmapAddressBookSupplier {
|
||||
return &JmapAddressBookSupplier{client: client}
|
||||
@@ -21,9 +22,15 @@ const jmapAddressBookSupplierId = SupplierId("jmap")
|
||||
func (c *JmapAddressBookSupplier) GetId() SupplierId {
|
||||
return jmapAddressBookSupplierId
|
||||
}
|
||||
|
||||
func (c *JmapAddressBookSupplier) IsMine(id string) bool {
|
||||
return id != "" && !strings.Contains(id, ":")
|
||||
}
|
||||
|
||||
func (c *JmapAddressBookSupplier) GetAll(accountId jmap.AccountId, ids []string, ctx jmap.Context) (jmap.Result[jmap.AddressBookGetResponse], error) {
|
||||
return c.client.GetAddressbooks(accountId, ids, ctx)
|
||||
}
|
||||
|
||||
func (c *JmapAddressBookSupplier) GetChanges(accountId jmap.AccountId, sinceState jmap.State, maxChanges uint, ctx jmap.Context) (jmap.Result[jmap.AddressBookChanges], error) {
|
||||
return c.client.GetAddressbookChanges(accountId, sinceState, maxChanges, ctx)
|
||||
}
|
||||
|
||||
@@ -9,11 +9,12 @@ import (
|
||||
)
|
||||
|
||||
type MockAddressBookSupplier struct {
|
||||
addressBook jmap.AddressBook
|
||||
addressBooks []jmap.AddressBook
|
||||
state jmap.State
|
||||
}
|
||||
|
||||
var MockAddressBookSupplierInstance *MockAddressBookSupplier = &MockAddressBookSupplier{
|
||||
addressBook: jmap.AddressBook{
|
||||
addressBooks: []jmap.AddressBook{{
|
||||
Id: "mock:1",
|
||||
Name: "Automatic Addressbook",
|
||||
Description: "Users",
|
||||
@@ -25,7 +26,8 @@ var MockAddressBookSupplierInstance *MockAddressBookSupplier = &MockAddressBookS
|
||||
MayAdmin: false,
|
||||
MayDelete: false,
|
||||
},
|
||||
},
|
||||
}},
|
||||
state: jmap.State("1"),
|
||||
}
|
||||
|
||||
var _ ListSupplier[jmap.AddressBook, jmap.AddressBookGetResponse] = &MockAddressBookSupplier{}
|
||||
@@ -41,7 +43,7 @@ func (c *MockAddressBookSupplier) IsMine(id string) bool {
|
||||
return strings.HasPrefix(id, "mock:")
|
||||
}
|
||||
func (c *MockAddressBookSupplier) GetAll(accountId jmap.AccountId, ids []string, ctx jmap.Context) (jmap.Result[jmap.AddressBookGetResponse], error) {
|
||||
abooks := []jmap.AddressBook{c.addressBook}
|
||||
abooks := c.addressBooks
|
||||
if len(ids) > 0 {
|
||||
abooks = structs.Filter(abooks, func(a jmap.AddressBook) bool { return slices.Contains(ids, a.Id) })
|
||||
}
|
||||
@@ -57,3 +59,22 @@ func (c *MockAddressBookSupplier) GetAll(accountId jmap.AccountId, ids []string,
|
||||
nil,
|
||||
), nil
|
||||
}
|
||||
|
||||
func (c *MockAddressBookSupplier) GetChanges(accountId jmap.AccountId, sinceState jmap.State, maxChanges uint, ctx jmap.Context) (jmap.Result[jmap.AddressBookChanges], error) {
|
||||
if sinceState == c.state {
|
||||
ch := jmap.AddressBookChanges{
|
||||
HasMoreChanges: false,
|
||||
OldState: c.state,
|
||||
NewState: c.state,
|
||||
}
|
||||
return jmap.NewResult(ch, jmap.EmptySessionState, c.state, jmap.NoLanguage, nil), nil
|
||||
} else {
|
||||
// what happens when the state is unknown?
|
||||
ch := jmap.AddressBookChanges{
|
||||
HasMoreChanges: false,
|
||||
NewState: c.state,
|
||||
Created: c.addressBooks,
|
||||
}
|
||||
return jmap.NewResult(ch, jmap.EmptySessionState, c.state, jmap.NoLanguage, nil), nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ type JmapContactCardSupplier struct {
|
||||
|
||||
var _ QuerySupplier[jmap.ContactCard, *jmap.ContactCardSearchResults, jmap.ContactCardFilterElement, jmap.ContactCardComparator] = &JmapContactCardSupplier{}
|
||||
var _ ListSupplier[jmap.ContactCard, jmap.ContactCardGetResponse] = &JmapContactCardSupplier{}
|
||||
var _ ChangesSupplier[jmap.ContactCard, jmap.ContactCardChanges] = &JmapContactCardSupplier{}
|
||||
|
||||
func newJmapContactCardSupplier(client *jmap.Client) *JmapContactCardSupplier {
|
||||
return &JmapContactCardSupplier{client: client}
|
||||
@@ -22,9 +23,11 @@ const jmapContactCardSupplierId = SupplierId("jmap")
|
||||
func (c *JmapContactCardSupplier) GetId() SupplierId {
|
||||
return jmapContactCardSupplierId
|
||||
}
|
||||
|
||||
func (c *JmapContactCardSupplier) IsMine(id string) bool {
|
||||
return id != "" && !strings.Contains(id, ":")
|
||||
}
|
||||
|
||||
func (c *JmapContactCardSupplier) Query(accountIds []jmap.AccountId, qps QueryParamsSupplier, limit *uint, filter jmap.ContactCardFilterElement, sortBy []jmap.ContactCardComparator, calculateTotal bool, ctx jmap.Context) (jmap.Result[map[jmap.AccountId]*jmap.ContactCardSearchResults], error) { //NOSONAR
|
||||
if m, err := mapQueryParams(c.GetId(), accountIds, qps); err != nil {
|
||||
return jmap.ZeroResultV[map[jmap.AccountId]*jmap.ContactCardSearchResults](), err
|
||||
@@ -32,6 +35,11 @@ func (c *JmapContactCardSupplier) Query(accountIds []jmap.AccountId, qps QueryPa
|
||||
return c.client.QueryContactCards(m, limit, filter, sortBy, calculateTotal, ctx)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *JmapContactCardSupplier) GetAll(accountId jmap.AccountId, ids []string, ctx jmap.Context) (jmap.Result[jmap.ContactCardGetResponse], error) {
|
||||
return c.client.GetContactCards(accountId, ids, ctx)
|
||||
}
|
||||
|
||||
func (c *JmapContactCardSupplier) GetChanges(accountId jmap.AccountId, sinceState jmap.State, maxChanges uint, ctx jmap.Context) (jmap.Result[jmap.ContactCardChanges], error) {
|
||||
return c.client.GetContactCardChanges(accountId, sinceState, maxChanges, ctx)
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
|
||||
type MockContactCardSupplier struct {
|
||||
contacts []jmap.ContactCard
|
||||
state jmap.State
|
||||
}
|
||||
|
||||
var MockContactCardSupplierInstance *MockContactCardSupplier = &MockContactCardSupplier{
|
||||
@@ -41,10 +42,12 @@ var MockContactCardSupplierInstance *MockContactCardSupplier = &MockContactCardS
|
||||
},
|
||||
},
|
||||
},
|
||||
state: jmap.State("1"),
|
||||
}
|
||||
|
||||
var _ QuerySupplier[jmap.ContactCard, *jmap.ContactCardSearchResults, jmap.ContactCardFilterElement, jmap.ContactCardComparator] = &MockContactCardSupplier{}
|
||||
var _ ListSupplier[jmap.ContactCard, jmap.ContactCardGetResponse] = &MockContactCardSupplier{}
|
||||
var _ ChangesSupplier[jmap.ContactCard, jmap.ContactCardChanges] = &MockContactCardSupplier{}
|
||||
|
||||
func newMockContactCardSupplier() *MockContactCardSupplier {
|
||||
return MockContactCardSupplierInstance
|
||||
@@ -128,5 +131,24 @@ func (c *MockContactCardSupplier) Query(accountIds []jmap.AccountId, qps QueryPa
|
||||
payload[accountId] = res
|
||||
}
|
||||
|
||||
return jmap.NewResult(payload, jmap.EmptySessionState, jmap.EmptyState, jmap.NoLanguage, nil), nil
|
||||
return jmap.NewResult(payload, jmap.EmptySessionState, c.state, jmap.NoLanguage, nil), nil
|
||||
}
|
||||
|
||||
func (c *MockContactCardSupplier) GetChanges(accountId jmap.AccountId, sinceState jmap.State, maxChanges uint, ctx jmap.Context) (jmap.Result[jmap.ContactCardChanges], error) {
|
||||
if sinceState == c.state {
|
||||
ch := jmap.ContactCardChanges{
|
||||
HasMoreChanges: false,
|
||||
OldState: c.state,
|
||||
NewState: c.state,
|
||||
}
|
||||
return jmap.NewResult(ch, jmap.EmptySessionState, c.state, jmap.NoLanguage, nil), nil
|
||||
} else {
|
||||
// what happens when the state is unknown?
|
||||
ch := jmap.ContactCardChanges{
|
||||
HasMoreChanges: false,
|
||||
NewState: c.state,
|
||||
Created: c.contacts,
|
||||
}
|
||||
return jmap.NewResult(ch, jmap.EmptySessionState, c.state, jmap.NoLanguage, nil), nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"slices"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/opencloud-eu/opencloud/pkg/jmap"
|
||||
@@ -15,6 +16,8 @@ type SupplierId string
|
||||
|
||||
const EmptySupplierId = ""
|
||||
|
||||
const DefaultSupplierId = SupplierId("jmap")
|
||||
|
||||
type Supplier[T jmap.Foo] interface {
|
||||
GetId() SupplierId
|
||||
IsMine(id string) bool
|
||||
@@ -30,6 +33,11 @@ type QuerySupplier[T jmap.Foo, R jmap.SearchResults[T], F jmap.FilterElement[T],
|
||||
Supplier[T]
|
||||
}
|
||||
|
||||
type ChangesSupplier[T jmap.Foo, C jmap.Changes[T]] interface {
|
||||
GetChanges(accountId jmap.AccountId, sinceState jmap.State, maxChanges uint, ctx jmap.Context) (jmap.Result[C], error)
|
||||
Supplier[T]
|
||||
}
|
||||
|
||||
// queryFunc func(req Request, accountIds []string, qps QueryParamsSupplier, limit *uint, filter FILTER, sortBy []COMP, ctx jmap.Context) (jmap.Result[SEARCHRESULTS], NextToken, error),
|
||||
func curryQueryFunc[SRES jmap.SearchResults[T], T jmap.Foo, FILTER any, COMP any](
|
||||
f func(accountIds []jmap.AccountId, qps QueryParamsSupplier, limit *uint, filter FILTER, sortBy []COMP, calculateTotal bool, ctx jmap.Context) (jmap.Result[SRES], NextToken, error),
|
||||
@@ -89,6 +97,86 @@ func agg[T jmap.Idable, R jmap.GetResponse[T]](accountId jmap.AccountId, supplie
|
||||
return ctor(accountId, state, notFounds, lists), nil
|
||||
}
|
||||
|
||||
func aggChanges[T jmap.Idable, R jmap.Changes[T]](accountId jmap.AccountId, supplierIds []SupplierId, responses []*R, //NOSONAR
|
||||
ctor func(accountId jmap.AccountId, oldState jmap.State, newState jmap.State, created []T, updated []T, destroyed []string, hasMoreChanges bool) R,
|
||||
) (R, error) {
|
||||
if len(responses) < 1 {
|
||||
var zero R
|
||||
return zero, fmt.Errorf("requires at least one response")
|
||||
}
|
||||
created := structs.Concat(structs.Map(responses, func(e *R) []T {
|
||||
if e != nil {
|
||||
return (*e).GetCreated()
|
||||
} else {
|
||||
return []T{}
|
||||
}
|
||||
})...)
|
||||
updated := structs.Concat(structs.Map(responses, func(e *R) []T {
|
||||
if e != nil {
|
||||
return (*e).GetUpdated()
|
||||
} else {
|
||||
return []T{}
|
||||
}
|
||||
})...)
|
||||
destroyed := structs.Concat(structs.Map(responses, func(e *R) []string {
|
||||
if e != nil {
|
||||
return (*e).GetDestroyed()
|
||||
} else {
|
||||
return []string{}
|
||||
}
|
||||
})...)
|
||||
oldStates, err := structs.MeshMap(supplierIds, responses, func(id SupplierId, e *R) (SupplierId, jmap.State, bool) {
|
||||
if e != nil {
|
||||
state := (*e).GetOldState()
|
||||
if state != jmap.EmptyState {
|
||||
return id, state, true
|
||||
} else {
|
||||
return id, jmap.EmptyState, false
|
||||
}
|
||||
} else {
|
||||
return "", jmap.EmptyState, false
|
||||
}
|
||||
})
|
||||
if err != nil {
|
||||
var zero R
|
||||
return zero, err
|
||||
}
|
||||
oldState, err := combineState(oldStates)
|
||||
if err != nil {
|
||||
var zero R
|
||||
return zero, err
|
||||
}
|
||||
newStates, err := structs.MeshMap(supplierIds, responses, func(id SupplierId, e *R) (SupplierId, jmap.State, bool) {
|
||||
if e != nil {
|
||||
state := (*e).GetNewState()
|
||||
if state != jmap.EmptyState {
|
||||
return id, state, true
|
||||
} else {
|
||||
return id, jmap.EmptyState, false
|
||||
}
|
||||
} else {
|
||||
return "", jmap.EmptyState, false
|
||||
}
|
||||
})
|
||||
if err != nil {
|
||||
var zero R
|
||||
return zero, err
|
||||
}
|
||||
newState, err := combineState(newStates)
|
||||
if err != nil {
|
||||
var zero R
|
||||
return zero, err
|
||||
}
|
||||
hasMoreChanges := structs.AllMatch(structs.Map(responses, func(e *R) bool {
|
||||
if e != nil {
|
||||
return (*e).GetHasMoreChanges()
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}), func(b bool) bool { return b })
|
||||
return ctor(accountId, oldState, newState, created, updated, destroyed, hasMoreChanges), nil
|
||||
}
|
||||
|
||||
func slist[T jmap.Idable, G jmap.GetResponse[T], S ListSupplier[T, G]](suppliers []S, accountId jmap.AccountId, ids []string, ctx jmap.Context, //NOSONAR
|
||||
ctor func(accountId jmap.AccountId, state jmap.State, notFound []string, list []T) G) (jmap.Result[G], error) {
|
||||
switch len(suppliers) {
|
||||
@@ -117,34 +205,143 @@ func slist[T jmap.Idable, G jmap.GetResponse[T], S ListSupplier[T, G]](suppliers
|
||||
}
|
||||
}
|
||||
|
||||
return jmap.RefineResultSlice(results, func(payloads []*G, sessionStates []*jmap.SessionState, states []*jmap.State, langs []*jmap.Language) (G, jmap.SessionState, jmap.State, jmap.Language, error) {
|
||||
resp, err := agg(accountId, supplierIds, payloads, ctor)
|
||||
if err != nil {
|
||||
return resp, jmap.EmptySessionState, jmap.EmptyState, jmap.NoLanguage, err
|
||||
}
|
||||
m, err := structs.MeshMap(supplierIds, sessionStates, func(id SupplierId, state *jmap.SessionState) (SupplierId, jmap.SessionState, bool) {
|
||||
if state != nil && *state != jmap.EmptySessionState {
|
||||
return id, *state, true
|
||||
} else {
|
||||
return id, jmap.EmptySessionState, false
|
||||
}
|
||||
})
|
||||
if err != nil {
|
||||
return resp, jmap.EmptySessionState, jmap.EmptyState, jmap.NoLanguage, err
|
||||
}
|
||||
sessionState, err := combineState(m)
|
||||
if err != nil {
|
||||
return resp, jmap.EmptySessionState, jmap.EmptyState, jmap.NoLanguage, err
|
||||
}
|
||||
lang := jmap.NoLanguage
|
||||
if f, ok := structs.First(langs, func(l *jmap.Language) bool { return l != nil && *l != jmap.NoLanguage }); ok {
|
||||
lang = *f
|
||||
}
|
||||
return resp, sessionState, resp.GetState(), lang, nil
|
||||
return smeld[T](supplierIds, results, func(payloads []*G) (G, error) {
|
||||
return agg(accountId, supplierIds, payloads, ctor)
|
||||
}, func(resp G) jmap.State {
|
||||
return resp.GetState()
|
||||
})
|
||||
|
||||
/*
|
||||
|
||||
return jmap.RefineResultSlice(results, func(payloads []*G, sessionStates []*jmap.SessionState, states []*jmap.State, langs []*jmap.Language) (G, jmap.SessionState, jmap.State, jmap.Language, error) {
|
||||
resp, err := agg(accountId, supplierIds, payloads, ctor)
|
||||
if err != nil {
|
||||
return resp, jmap.EmptySessionState, jmap.EmptyState, jmap.NoLanguage, err
|
||||
}
|
||||
m, err := structs.MeshMap(supplierIds, sessionStates, func(id SupplierId, state *jmap.SessionState) (SupplierId, jmap.SessionState, bool) {
|
||||
if state != nil && *state != jmap.EmptySessionState {
|
||||
return id, *state, true
|
||||
} else {
|
||||
return id, jmap.EmptySessionState, false
|
||||
}
|
||||
})
|
||||
if err != nil {
|
||||
return resp, jmap.EmptySessionState, jmap.EmptyState, jmap.NoLanguage, err
|
||||
}
|
||||
sessionState, err := combineState(m)
|
||||
if err != nil {
|
||||
return resp, jmap.EmptySessionState, jmap.EmptyState, jmap.NoLanguage, err
|
||||
}
|
||||
lang := jmap.NoLanguage
|
||||
if f, ok := structs.First(langs, func(l *jmap.Language) bool { return l != nil && *l != jmap.NoLanguage }); ok {
|
||||
lang = *f
|
||||
}
|
||||
return resp, sessionState, resp.GetState(), lang, nil
|
||||
})
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
||||
func schanges[T jmap.Idable, C jmap.Changes[T], S ChangesSupplier[T, C]](suppliers []S, accountId jmap.AccountId, sinceState jmap.State, maxChanges uint, ctx jmap.Context, //NOSONAR
|
||||
ctor func(accountId jmap.AccountId, oldState jmap.State, newState jmap.State, created []T, updated []T, destroyed []string, hasMoreChanges bool) C,
|
||||
) (jmap.Result[C], error) {
|
||||
switch len(suppliers) {
|
||||
case 0:
|
||||
return jmap.ZeroResultV[C](), nil
|
||||
case 1:
|
||||
return suppliers[0].GetChanges(accountId, sinceState, maxChanges, ctx)
|
||||
default:
|
||||
results := make([]*jmap.Result[C], len(suppliers))
|
||||
supplierIds := make([]SupplierId, len(suppliers))
|
||||
stateBySupplierId, err := splitState(sinceState, DefaultSupplierId)
|
||||
if err != nil {
|
||||
return jmap.ZeroResultV[C](), err
|
||||
}
|
||||
for i, supplier := range suppliers {
|
||||
supplierIds[i] = supplier.GetId()
|
||||
state := jmap.EmptyState
|
||||
if v, ok := stateBySupplierId[supplier.GetId()]; ok {
|
||||
state = v
|
||||
}
|
||||
// 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.GetChanges(accountId, state, maxChanges, ctx); err != nil {
|
||||
return result, err
|
||||
} else {
|
||||
results[i] = &result
|
||||
}
|
||||
}
|
||||
|
||||
// TODO need to reduce the maxChanges? this is quite complex to implement, if even possible, not doing that for now
|
||||
|
||||
return smeld[T](supplierIds, results, func(payloads []*C) (C, error) {
|
||||
return aggChanges(accountId, supplierIds, payloads, ctor)
|
||||
}, func(resp C) jmap.State {
|
||||
return resp.GetNewState()
|
||||
})
|
||||
|
||||
/*
|
||||
return jmap.RefineResultSlice(results, func(payloads []*C, sessionStates []*jmap.SessionState, states []*jmap.State, langs []*jmap.Language) (C, jmap.SessionState, jmap.State, jmap.Language, error) {
|
||||
resp, err := aggChanges(accountId, supplierIds, payloads, ctor)
|
||||
if err != nil {
|
||||
return resp, jmap.EmptySessionState, jmap.EmptyState, jmap.NoLanguage, err
|
||||
}
|
||||
m, err := structs.MeshMap(supplierIds, sessionStates, func(id SupplierId, state *jmap.SessionState) (SupplierId, jmap.SessionState, bool) {
|
||||
if state != nil && *state != jmap.EmptySessionState {
|
||||
return id, *state, true
|
||||
} else {
|
||||
return id, jmap.EmptySessionState, false
|
||||
}
|
||||
})
|
||||
if err != nil {
|
||||
return resp, jmap.EmptySessionState, jmap.EmptyState, jmap.NoLanguage, err
|
||||
}
|
||||
sessionState, err := combineState(m)
|
||||
if err != nil {
|
||||
return resp, jmap.EmptySessionState, jmap.EmptyState, jmap.NoLanguage, err
|
||||
}
|
||||
lang := jmap.NoLanguage
|
||||
if f, ok := structs.First(langs, func(l *jmap.Language) bool { return l != nil && *l != jmap.NoLanguage }); ok {
|
||||
lang = *f
|
||||
}
|
||||
return resp, sessionState, resp.GetNewState(), lang, nil
|
||||
})
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
||||
func smeld[T jmap.Idable, C any](
|
||||
supplierIds []SupplierId,
|
||||
results []*jmap.Result[C],
|
||||
aggregator func(payloads []*C) (C, error),
|
||||
stateSupplier func(resp C) jmap.State,
|
||||
) (jmap.Result[C], error) {
|
||||
return jmap.RefineResultSlice(results, func(payloads []*C, sessionStates []*jmap.SessionState, states []*jmap.State, langs []*jmap.Language) (C, jmap.SessionState, jmap.State, jmap.Language, error) {
|
||||
resp, err := aggregator(payloads)
|
||||
if err != nil {
|
||||
return resp, jmap.EmptySessionState, jmap.EmptyState, jmap.NoLanguage, err
|
||||
}
|
||||
m, err := structs.MeshMap(supplierIds, sessionStates, func(id SupplierId, state *jmap.SessionState) (SupplierId, jmap.SessionState, bool) {
|
||||
if state != nil && *state != jmap.EmptySessionState {
|
||||
return id, *state, true
|
||||
} else {
|
||||
return id, jmap.EmptySessionState, false
|
||||
}
|
||||
})
|
||||
if err != nil {
|
||||
return resp, jmap.EmptySessionState, jmap.EmptyState, jmap.NoLanguage, err
|
||||
}
|
||||
sessionState, err := combineState(m)
|
||||
if err != nil {
|
||||
return resp, jmap.EmptySessionState, jmap.EmptyState, jmap.NoLanguage, err
|
||||
}
|
||||
lang := jmap.NoLanguage
|
||||
if f, ok := structs.First(langs, func(l *jmap.Language) bool { return l != nil && *l != jmap.NoLanguage }); ok {
|
||||
lang = *f
|
||||
}
|
||||
return resp, sessionState, stateSupplier(resp), lang, nil
|
||||
})
|
||||
}
|
||||
|
||||
func fillMissingAccounts(qps QueryParamsSupplier, supplierId SupplierId, accountIds []jmap.AccountId, n map[jmap.AccountId]jmap.QueryParams) error {
|
||||
for _, accountId := range accountIds {
|
||||
if _, ok := n[accountId]; !ok {
|
||||
@@ -403,7 +600,7 @@ func squery[T jmap.Idable, R jmap.SearchResults[T], S QuerySupplier[T, R, F, C],
|
||||
|
||||
const combinedStateEncodingPrefix = "="
|
||||
|
||||
func combineState[K ~string, S jmap.State | jmap.SessionState](m map[K]S) (S, error) {
|
||||
func combineState[S jmap.State | jmap.SessionState](m map[SupplierId]S) (S, error) {
|
||||
if b, err := json.Marshal(m); err != nil {
|
||||
return "", err
|
||||
} else {
|
||||
@@ -412,14 +609,13 @@ func combineState[K ~string, S jmap.State | jmap.SessionState](m map[K]S) (S, er
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
func splitState[K ~string, S jmap.State | jmap.SessionState](state S) (map[K]S, error) {
|
||||
func splitState[S jmap.State | jmap.SessionState](state S, defaultSupplierId SupplierId) (map[SupplierId]S, error) {
|
||||
s := string(state)
|
||||
if strings.HasPrefix(s, combinedStateEncodingPrefix) {
|
||||
if b, err := base64.RawURLEncoding.DecodeString(s[len(combinedStateEncodingPrefix):]); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
m := map[K]S{}
|
||||
m := map[SupplierId]S{}
|
||||
if err := json.Unmarshal(b, &m); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
@@ -427,7 +623,6 @@ func splitState[K ~string, S jmap.State | jmap.SessionState](state S) (map[K]S,
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return map[K]S{K("jmap"): state}, nil
|
||||
return map[SupplierId]S{defaultSupplierId: state}, nil
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user