diff --git a/pkg/storage/cached/cached.go b/pkg/storage/cached/cached.go index 8a8b251b9..1ca4ad594 100644 --- a/pkg/storage/cached/cached.go +++ b/pkg/storage/cached/cached.go @@ -37,16 +37,19 @@ type Cache interface { // Get retrieves k value from the cache store. // If not present nil will be returned. - Get(ctx context.Context, k string) ([]byte, error) + Get(ctx context.Context, ns, key string) ([]byte, error) // Put stores a value into the cache store. - Put(ctx context.Context, k string, val []byte) error + Put(ctx context.Context, ns, key string, val []byte) error // Del removes k value from the cache store. - Del(ctx context.Context, k string) error + Del(ctx context.Context, ns string, keys ...string) error + + // DelNS removes all keys contained under a given namespace from the cache store. + DelNS(ctx context.Context, ns string) error // HasKey tells whether k is present in the cache store. - HasKey(ctx context.Context, k string) (bool, error) + HasKey(ctx context.Context, ns, key string) (bool, error) // Start starts Cache component. Start(ctx context.Context) error @@ -84,8 +87,8 @@ func New(cfg Config, rep repository.Repository, logger kitlog.Logger) (repositor User: &cachedUserRep{c: c, rep: rep}, Last: &cachedLastRep{c: c, rep: rep}, Capabilities: &cachedCapsRep{c: c, rep: rep}, + Private: &cachedPrivateRep{c: c, rep: rep}, BlockList: rep, - Private: rep, Roster: rep, VCard: &cachedVCardRep{c: c, rep: rep}, Offline: rep, diff --git a/pkg/storage/cached/capabilities.go b/pkg/storage/cached/capabilities.go index c32cbf3e0..a9477c8e8 100644 --- a/pkg/storage/cached/capabilities.go +++ b/pkg/storage/cached/capabilities.go @@ -23,6 +23,8 @@ import ( "github.com/ortuman/jackal/pkg/storage/repository" ) +const capsKey = "caps" + type capsCodec struct { val *capsmodel.Capabilities } @@ -51,8 +53,9 @@ type cachedCapsRep struct { func (c *cachedCapsRep) UpsertCapabilities(ctx context.Context, caps *capsmodel.Capabilities) error { op := updateOp{ - c: c.c, - key: capsKey(caps.Node, caps.Ver), + c: c.c, + namespace: capsNS(caps.Node, caps.Ver), + invalidKeys: []string{capsKey}, updateFn: func(ctx context.Context) error { return c.rep.UpsertCapabilities(ctx, caps) }, @@ -62,8 +65,9 @@ func (c *cachedCapsRep) UpsertCapabilities(ctx context.Context, caps *capsmodel. func (c *cachedCapsRep) CapabilitiesExist(ctx context.Context, node, ver string) (bool, error) { op := existsOp{ - c: c.c, - key: capsKey(node, ver), + c: c.c, + namespace: capsNS(node, ver), + key: capsKey, missFn: func(ctx context.Context) (bool, error) { return c.rep.CapabilitiesExist(ctx, node, ver) }, @@ -73,9 +77,10 @@ func (c *cachedCapsRep) CapabilitiesExist(ctx context.Context, node, ver string) func (c *cachedCapsRep) FetchCapabilities(ctx context.Context, node, ver string) (*capsmodel.Capabilities, error) { op := fetchOp{ - c: c.c, - key: capsKey(node, ver), - codec: &capsCodec{}, + c: c.c, + namespace: capsNS(node, ver), + key: capsKey, + codec: &capsCodec{}, missFn: func(ctx context.Context) (interface{}, error) { return c.rep.FetchCapabilities(ctx, node, ver) }, @@ -90,6 +95,6 @@ func (c *cachedCapsRep) FetchCapabilities(ctx context.Context, node, ver string) return nil, nil } -func capsKey(node, ver string) string { +func capsNS(node, ver string) string { return fmt.Sprintf("caps:%s:%s", node, ver) } diff --git a/pkg/storage/cached/capabilities_test.go b/pkg/storage/cached/capabilities_test.go index 334a16415..e3f9b5d5a 100644 --- a/pkg/storage/cached/capabilities_test.go +++ b/pkg/storage/cached/capabilities_test.go @@ -24,11 +24,12 @@ import ( func TestCachedCapsRep_UpsertCaps(t *testing.T) { // given - var cacheKey string + var cacheNS, cacheKey string cacheMock := &cacheMock{} - cacheMock.DelFunc = func(ctx context.Context, k string) error { - cacheKey = k + cacheMock.DelFunc = func(ctx context.Context, ns string, keys ...string) error { + cacheNS = ns + cacheKey = keys[0] return nil } @@ -50,15 +51,16 @@ func TestCachedCapsRep_UpsertCaps(t *testing.T) { // then require.NoError(t, err) - require.Equal(t, capsKey("n1", "v1"), cacheKey) + require.Equal(t, capsNS("n1", "v1"), cacheNS) + require.Equal(t, capsKey, cacheKey) require.Len(t, repMock.UpsertCapabilitiesCalls(), 1) } func TestCachedCapsRep_CapsExist(t *testing.T) { // given cacheMock := &cacheMock{} - cacheMock.HasKeyFunc = func(ctx context.Context, k string) (bool, error) { - if k == capsKey("n1", "v1") { + cacheMock.HasKeyFunc = func(ctx context.Context, ns, k string) (bool, error) { + if ns == capsNS("n1", "v1") && k == capsKey { return true, nil } return false, nil @@ -94,10 +96,10 @@ func TestCachedCapsRep_CapsExist(t *testing.T) { func TestCachedCapsRep_FetchCaps(t *testing.T) { // given cacheMock := &cacheMock{} - cacheMock.GetFunc = func(ctx context.Context, k string) ([]byte, error) { + cacheMock.GetFunc = func(ctx context.Context, ns, k string) ([]byte, error) { return nil, nil } - cacheMock.PutFunc = func(ctx context.Context, k string, val []byte) error { + cacheMock.PutFunc = func(ctx context.Context, ns, k string, val []byte) error { return nil } diff --git a/pkg/storage/cached/last.go b/pkg/storage/cached/last.go index 36c12ff13..08b1b46b2 100644 --- a/pkg/storage/cached/last.go +++ b/pkg/storage/cached/last.go @@ -23,6 +23,8 @@ import ( "github.com/ortuman/jackal/pkg/storage/repository" ) +const lastKey = "lst" + type lastCodec struct { val *lastmodel.Last } @@ -51,8 +53,9 @@ type cachedLastRep struct { func (c *cachedLastRep) UpsertLast(ctx context.Context, last *lastmodel.Last) error { op := updateOp{ - c: c.c, - key: lastKey(last.Username), + c: c.c, + namespace: lastNS(last.Username), + invalidKeys: []string{lastKey}, updateFn: func(ctx context.Context) error { return c.rep.UpsertLast(ctx, last) }, @@ -62,9 +65,10 @@ func (c *cachedLastRep) UpsertLast(ctx context.Context, last *lastmodel.Last) er func (c *cachedLastRep) FetchLast(ctx context.Context, username string) (*lastmodel.Last, error) { op := fetchOp{ - c: c.c, - key: lastKey(username), - codec: &lastCodec{}, + c: c.c, + namespace: lastNS(username), + key: lastKey, + codec: &lastCodec{}, missFn: func(ctx context.Context) (interface{}, error) { return c.rep.FetchLast(ctx, username) }, @@ -81,8 +85,9 @@ func (c *cachedLastRep) FetchLast(ctx context.Context, username string) (*lastmo func (c *cachedLastRep) DeleteLast(ctx context.Context, username string) error { op := updateOp{ - c: c.c, - key: lastKey(username), + c: c.c, + namespace: lastNS(username), + invalidKeys: []string{lastKey}, updateFn: func(ctx context.Context) error { return c.rep.DeleteLast(ctx, username) }, @@ -90,6 +95,6 @@ func (c *cachedLastRep) DeleteLast(ctx context.Context, username string) error { return op.do(ctx) } -func lastKey(username string) string { +func lastNS(username string) string { return fmt.Sprintf("lst:%s", username) } diff --git a/pkg/storage/cached/last_test.go b/pkg/storage/cached/last_test.go index c23b42804..eb467e832 100644 --- a/pkg/storage/cached/last_test.go +++ b/pkg/storage/cached/last_test.go @@ -25,11 +25,12 @@ import ( func TestCachedLastRep_UpsertLast(t *testing.T) { // given - var cacheKey string + var cacheNS, cacheKey string cacheMock := &cacheMock{} - cacheMock.DelFunc = func(ctx context.Context, k string) error { - cacheKey = k + cacheMock.DelFunc = func(ctx context.Context, ns string, keys ...string) error { + cacheNS = ns + cacheKey = keys[0] return nil } @@ -47,17 +48,19 @@ func TestCachedLastRep_UpsertLast(t *testing.T) { // then require.NoError(t, err) - require.Equal(t, lastKey("u1"), cacheKey) + require.Equal(t, lastNS("u1"), cacheNS) + require.Equal(t, lastKey, cacheKey) require.Len(t, repMock.UpsertLastCalls(), 1) } func TestCachedLastRep_DeleteLast(t *testing.T) { // given - var cacheKey string + var cacheNS, cacheKey string cacheMock := &cacheMock{} - cacheMock.DelFunc = func(ctx context.Context, k string) error { - cacheKey = k + cacheMock.DelFunc = func(ctx context.Context, ns string, keys ...string) error { + cacheNS = ns + cacheKey = keys[0] return nil } @@ -75,17 +78,18 @@ func TestCachedLastRep_DeleteLast(t *testing.T) { // then require.NoError(t, err) - require.Equal(t, lastKey("v1"), cacheKey) + require.Equal(t, lastNS("v1"), cacheNS) + require.Equal(t, lastKey, cacheKey) require.Len(t, repMock.DeleteLastCalls(), 1) } func TestCachedLastRep_FetchLast(t *testing.T) { // given cacheMock := &cacheMock{} - cacheMock.GetFunc = func(ctx context.Context, k string) ([]byte, error) { + cacheMock.GetFunc = func(ctx context.Context, ns, k string) ([]byte, error) { return nil, nil } - cacheMock.PutFunc = func(ctx context.Context, k string, val []byte) error { + cacheMock.PutFunc = func(ctx context.Context, ns, k string, val []byte) error { return nil } diff --git a/pkg/storage/cached/op.go b/pkg/storage/cached/op.go index 20457136b..111cba3ab 100644 --- a/pkg/storage/cached/op.go +++ b/pkg/storage/cached/op.go @@ -25,13 +25,14 @@ type codec interface { } type existsOp struct { - c Cache - key string - missFn func(context.Context) (bool, error) + c Cache + namespace string + key string + missFn func(context.Context) (bool, error) } func (op existsOp) do(ctx context.Context) (bool, error) { - ok, err := op.c.HasKey(ctx, op.key) + ok, err := op.c.HasKey(ctx, op.namespace, op.key) if err != nil { return false, err } @@ -42,27 +43,37 @@ func (op existsOp) do(ctx context.Context) (bool, error) { } type updateOp struct { - c Cache - key string - updateFn func(context.Context) error + c Cache + namespace string + invalidKeys []string + updateFn func(context.Context) error } func (op updateOp) do(ctx context.Context) error { - if err := op.c.Del(ctx, op.key); err != nil { - return err + switch { + case len(op.invalidKeys) > 0: + if err := op.c.Del(ctx, op.namespace, op.invalidKeys...); err != nil { + return err + } + + default: + if err := op.c.DelNS(ctx, op.namespace); err != nil { + return err + } } return op.updateFn(ctx) } type fetchOp struct { - c Cache - key string - codec codec - missFn func(context.Context) (interface{}, error) + c Cache + namespace string + key string + codec codec + missFn func(context.Context) (interface{}, error) } func (op fetchOp) do(ctx context.Context) (interface{}, error) { - b, err := op.c.Get(ctx, op.key) + b, err := op.c.Get(ctx, op.namespace, op.key) if err != nil { return nil, err } @@ -78,7 +89,7 @@ func (op fetchOp) do(ctx context.Context) (interface{}, error) { if err != nil { return nil, err } - if err := op.c.Put(ctx, op.key, b); err != nil { + if err := op.c.Put(ctx, op.namespace, op.key, b); err != nil { return nil, err } return obj, nil diff --git a/pkg/storage/cached/op_test.go b/pkg/storage/cached/op_test.go index 0d2a768b5..6225b59c6 100644 --- a/pkg/storage/cached/op_test.go +++ b/pkg/storage/cached/op_test.go @@ -26,8 +26,8 @@ import ( func TestCachedRepository_ExistsOp(t *testing.T) { // given cacheMock := &cacheMock{} - cacheMock.HasKeyFunc = func(ctx context.Context, k string) (bool, error) { - if k == "k0" { + cacheMock.HasKeyFunc = func(ctx context.Context, ns, k string) (bool, error) { + if ns == "n0" && k == "k0" { return true, nil } return false, nil @@ -37,8 +37,8 @@ func TestCachedRepository_ExistsOp(t *testing.T) { } // when - op0 := existsOp{c: cacheMock, key: "k0", missFn: missFn} - op1 := existsOp{c: cacheMock, key: "k1", missFn: missFn} + op0 := existsOp{c: cacheMock, namespace: "n0", key: "k0", missFn: missFn} + op1 := existsOp{c: cacheMock, namespace: "n1", key: "k1", missFn: missFn} ok0, _ := op0.do(context.Background()) ok1, _ := op1.do(context.Background()) @@ -53,7 +53,7 @@ func TestCachedRepository_UpdateOp(t *testing.T) { var output string cacheMock := &cacheMock{} - cacheMock.DelFunc = func(ctx context.Context, k string) error { + cacheMock.DelFunc = func(ctx context.Context, ns string, keys ...string) error { output += "del" return nil } @@ -63,7 +63,7 @@ func TestCachedRepository_UpdateOp(t *testing.T) { } // when - op := updateOp{c: cacheMock, key: "k0", updateFn: updateFn} + op := updateOp{c: cacheMock, invalidKeys: []string{"k0"}, updateFn: updateFn} _ = op.do(context.Background()) // then @@ -73,7 +73,7 @@ func TestCachedRepository_UpdateOp(t *testing.T) { func TestCachedRepository_FetchOpHit(t *testing.T) { // given cacheMock := &cacheMock{} - cacheMock.GetFunc = func(ctx context.Context, k string) ([]byte, error) { + cacheMock.GetFunc = func(ctx context.Context, ns, k string) ([]byte, error) { return []byte{255}, nil } @@ -107,10 +107,10 @@ func TestCachedRepository_FetchOpHit(t *testing.T) { func TestCachedRepository_FetchOpMiss(t *testing.T) { // given cacheMock := &cacheMock{} - cacheMock.GetFunc = func(ctx context.Context, k string) ([]byte, error) { + cacheMock.GetFunc = func(ctx context.Context, ns, k string) ([]byte, error) { return nil, nil } - cacheMock.PutFunc = func(ctx context.Context, k string, val []byte) error { + cacheMock.PutFunc = func(ctx context.Context, ns, k string, val []byte) error { return nil } diff --git a/pkg/storage/cached/private.go b/pkg/storage/cached/private.go new file mode 100644 index 000000000..502509e2a --- /dev/null +++ b/pkg/storage/cached/private.go @@ -0,0 +1,98 @@ +// Copyright 2021 The jackal Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package cachedrepository + +import ( + "context" + "fmt" + + "github.com/golang/protobuf/proto" + "github.com/jackal-xmpp/stravaganza/v2" + "github.com/ortuman/jackal/pkg/storage/repository" +) + +type privateCodec struct { + val stravaganza.Element +} + +func (c *privateCodec) encode(i interface{}) ([]byte, error) { + el := i.(stravaganza.Element) + return proto.Marshal(el.Proto()) +} + +func (c *privateCodec) decode(b []byte) error { + sb, err := stravaganza.NewBuilderFromBinary(b) + if err != nil { + return err + } + c.val = sb.Build() + return nil +} + +func (c *privateCodec) value() interface{} { + return c.val +} + +type cachedPrivateRep struct { + c Cache + rep repository.Private +} + +func (c *cachedPrivateRep) FetchPrivate(ctx context.Context, namespace, username string) (stravaganza.Element, error) { + op := fetchOp{ + c: c.c, + namespace: privateNS(username), + key: namespace, + codec: &privateCodec{}, + missFn: func(ctx context.Context) (interface{}, error) { + return c.rep.FetchPrivate(ctx, namespace, username) + }, + } + v, err := op.do(ctx) + switch { + case err != nil: + return nil, err + case v != nil: + return v.(stravaganza.Element), nil + } + return nil, nil +} + +func (c *cachedPrivateRep) UpsertPrivate(ctx context.Context, private stravaganza.Element, namespace, username string) error { + op := updateOp{ + c: c.c, + namespace: privateNS(username), + invalidKeys: []string{namespace}, + updateFn: func(ctx context.Context) error { + return c.rep.UpsertPrivate(ctx, private, namespace, username) + }, + } + return op.do(ctx) +} + +func (c *cachedPrivateRep) DeletePrivates(ctx context.Context, username string) error { + op := updateOp{ + c: c.c, + namespace: privateNS(username), + updateFn: func(ctx context.Context) error { + return c.rep.DeletePrivates(ctx, username) + }, + } + return op.do(ctx) +} + +func privateNS(username string) string { + return fmt.Sprintf("prv:%s", username) +} diff --git a/pkg/storage/cached/private_test.go b/pkg/storage/cached/private_test.go new file mode 100644 index 000000000..649daac05 --- /dev/null +++ b/pkg/storage/cached/private_test.go @@ -0,0 +1,117 @@ +// Copyright 2021 The jackal Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package cachedrepository + +import ( + "context" + "testing" + + "github.com/jackal-xmpp/stravaganza/v2" + "github.com/stretchr/testify/require" +) + +func TestCachedPrivateRep_UpsertPrivate(t *testing.T) { + // given + var cacheNS, cacheKey string + + cacheMock := &cacheMock{} + cacheMock.DelFunc = func(ctx context.Context, ns string, keys ...string) error { + cacheNS = ns + cacheKey = keys[0] + return nil + } + + repMock := &repositoryMock{} + repMock.UpsertPrivateFunc = func(ctx context.Context, private stravaganza.Element, namespace string, username string) error { + return nil + } + + // when + rep := cachedPrivateRep{ + c: cacheMock, + rep: repMock, + } + prv := stravaganza.NewBuilder("prv").Build() + + err := rep.UpsertPrivate(context.Background(), prv, "n0", "u1") + + // then + require.NoError(t, err) + require.Equal(t, privateNS("u1"), cacheNS) + require.Equal(t, "n0", cacheKey) + require.Len(t, repMock.UpsertPrivateCalls(), 1) +} + +func TestCachedPrivateRep_DeletePrivates(t *testing.T) { + // given + var cacheNS string + + cacheMock := &cacheMock{} + cacheMock.DelNSFunc = func(ctx context.Context, ns string) error { + cacheNS = ns + return nil + } + + repMock := &repositoryMock{} + repMock.DeletePrivatesFunc = func(ctx context.Context, username string) error { + return nil + } + + // when + rep := cachedPrivateRep{ + c: cacheMock, + rep: repMock, + } + err := rep.DeletePrivates(context.Background(), "u1") + + // then + require.NoError(t, err) + require.Equal(t, privateNS("u1"), cacheNS) + require.Len(t, repMock.DeletePrivatesCalls(), 1) +} + +func TestCachedPrivateRep_FetchPrivate(t *testing.T) { + // given + cacheMock := &cacheMock{} + cacheMock.GetFunc = func(ctx context.Context, ns, k string) ([]byte, error) { + return nil, nil + } + cacheMock.PutFunc = func(ctx context.Context, ns, k string, val []byte) error { + return nil + } + + repMock := &repositoryMock{} + repMock.FetchPrivateFunc = func(ctx context.Context, namespace string, username string) (stravaganza.Element, error) { + prv := stravaganza.NewBuilder("prv0").Build() + return prv, nil + } + + // when + rep := cachedPrivateRep{ + c: cacheMock, + rep: repMock, + } + prv, err := rep.FetchPrivate(context.Background(), "n0", "u1") + + // then + require.NotNil(t, prv) + require.NoError(t, err) + + require.Equal(t, "prv0", prv.Name()) + + require.Len(t, cacheMock.GetCalls(), 1) + require.Len(t, cacheMock.PutCalls(), 1) + require.Len(t, repMock.FetchPrivateCalls(), 1) +} diff --git a/pkg/storage/cached/redis/cache.go b/pkg/storage/cached/redis/cache.go index 84d4e8a6f..a531c7cf1 100644 --- a/pkg/storage/cached/redis/cache.go +++ b/pkg/storage/cached/redis/cache.go @@ -65,8 +65,9 @@ func New(cfg Config) *Cache { func (c *Cache) Type() string { return Type } // Get satisfies Cache interface. -func (c *Cache) Get(ctx context.Context, k string) ([]byte, error) { - val, err := c.pickClient(k).Get(ctx, k).Result() +func (c *Cache) Get(ctx context.Context, ns, key string) ([]byte, error) { + cl := c.pickClient(ns) + val, err := cl.HGet(ctx, ns, key).Result() if err != nil { if errors.Is(err, redis.Nil) { return nil, nil @@ -77,22 +78,34 @@ func (c *Cache) Get(ctx context.Context, k string) ([]byte, error) { } // Put satisfies Cache interface. -func (c *Cache) Put(ctx context.Context, k string, val []byte) error { - return c.pickClient(k).Set(ctx, k, val, c.ttl).Err() +func (c *Cache) Put(ctx context.Context, ns, key string, val []byte) error { + cl := c.pickClient(ns) + if err := cl.HSet(ctx, ns, key, val).Err(); err != nil { + return err + } + return cl.Expire(ctx, ns, c.ttl).Err() } // Del satisfies Cache interface. -func (c *Cache) Del(ctx context.Context, k string) error { - return c.pickClient(k).Del(ctx, k).Err() +func (c *Cache) Del(ctx context.Context, ns string, keys ...string) error { + cl := c.pickClient(ns) + return cl.HDel(ctx, ns, keys...).Err() +} + +// DelNS removes all keys contained under a given namespace from the cache store. +func (c *Cache) DelNS(ctx context.Context, ns string) error { + cl := c.pickClient(ns) + return cl.Del(ctx, ns).Err() } // HasKey satisfies Cache interface. -func (c *Cache) HasKey(ctx context.Context, k string) (bool, error) { - v, err := c.pickClient(k).Exists(ctx, k).Result() - if err != nil { +func (c *Cache) HasKey(ctx context.Context, ns, key string) (bool, error) { + cl := c.pickClient(ns) + res := cl.HExists(ctx, ns, key) + if err := res.Err(); err != nil { return false, err } - return v == 1, nil + return res.Val(), nil } // Start satisfies Cache interface. @@ -115,11 +128,11 @@ func (c *Cache) Stop(_ context.Context) error { return nil } -func (c *Cache) pickClient(k string) *redis.Client { +func (c *Cache) pickClient(ns string) *redis.Client { if len(c.clients) == 1 { return c.clients[0] } - cs := xxhash.Sum64String(k) + cs := xxhash.Sum64String(ns) idx := jumpHash(cs, len(c.clients)) return c.clients[idx] } diff --git a/pkg/storage/cached/tx.go b/pkg/storage/cached/tx.go index d605fdd05..5c526482e 100644 --- a/pkg/storage/cached/tx.go +++ b/pkg/storage/cached/tx.go @@ -35,8 +35,8 @@ func newCacheTx(c Cache, tx repository.Transaction) *cachedTx { User: &cachedUserRep{c: c, rep: tx}, Last: &cachedLastRep{c: c, rep: tx}, Capabilities: &cachedCapsRep{c: c, rep: tx}, + Private: &cachedPrivateRep{c: c, rep: tx}, BlockList: tx, - Private: tx, Roster: tx, VCard: &cachedVCardRep{c: c, rep: tx}, Offline: tx, diff --git a/pkg/storage/cached/user.go b/pkg/storage/cached/user.go index ff8efcf21..1ba9f5d2c 100644 --- a/pkg/storage/cached/user.go +++ b/pkg/storage/cached/user.go @@ -23,6 +23,8 @@ import ( "github.com/ortuman/jackal/pkg/storage/repository" ) +const userKey = "usr" + type userCodec struct { val *usermodel.User } @@ -51,8 +53,9 @@ type cachedUserRep struct { func (c *cachedUserRep) UpsertUser(ctx context.Context, user *usermodel.User) error { op := updateOp{ - c: c.c, - key: userKey(user.Username), + c: c.c, + namespace: userNS(user.Username), + invalidKeys: []string{userKey}, updateFn: func(ctx context.Context) error { return c.rep.UpsertUser(ctx, user) }, @@ -62,8 +65,9 @@ func (c *cachedUserRep) UpsertUser(ctx context.Context, user *usermodel.User) er func (c *cachedUserRep) DeleteUser(ctx context.Context, username string) error { op := updateOp{ - c: c.c, - key: userKey(username), + c: c.c, + namespace: userNS(username), + invalidKeys: []string{userKey}, updateFn: func(ctx context.Context) error { return c.rep.DeleteUser(ctx, username) }, @@ -73,9 +77,10 @@ func (c *cachedUserRep) DeleteUser(ctx context.Context, username string) error { func (c *cachedUserRep) FetchUser(ctx context.Context, username string) (*usermodel.User, error) { op := fetchOp{ - c: c.c, - key: userKey(username), - codec: &userCodec{}, + c: c.c, + namespace: userNS(username), + key: userKey, + codec: &userCodec{}, missFn: func(ctx context.Context) (interface{}, error) { return c.rep.FetchUser(ctx, username) }, @@ -92,8 +97,9 @@ func (c *cachedUserRep) FetchUser(ctx context.Context, username string) (*usermo func (c *cachedUserRep) UserExists(ctx context.Context, username string) (bool, error) { op := existsOp{ - c: c.c, - key: userKey(username), + c: c.c, + namespace: userNS(username), + key: userKey, missFn: func(ctx context.Context) (bool, error) { return c.rep.UserExists(ctx, username) }, @@ -101,6 +107,6 @@ func (c *cachedUserRep) UserExists(ctx context.Context, username string) (bool, return op.do(ctx) } -func userKey(username string) string { +func userNS(username string) string { return fmt.Sprintf("usr:%s", username) } diff --git a/pkg/storage/cached/user_test.go b/pkg/storage/cached/user_test.go index fcc69b0df..1de0fa614 100644 --- a/pkg/storage/cached/user_test.go +++ b/pkg/storage/cached/user_test.go @@ -24,11 +24,12 @@ import ( func TestCachedUserRep_UpsertUser(t *testing.T) { // given - var cacheKey string + var cacheNS, cacheKey string cacheMock := &cacheMock{} - cacheMock.DelFunc = func(ctx context.Context, k string) error { - cacheKey = k + cacheMock.DelFunc = func(ctx context.Context, ns string, keys ...string) error { + cacheNS = ns + cacheKey = keys[0] return nil } @@ -46,17 +47,19 @@ func TestCachedUserRep_UpsertUser(t *testing.T) { // then require.NoError(t, err) - require.Equal(t, userKey("u1"), cacheKey) + require.Equal(t, userNS("u1"), cacheNS) + require.Equal(t, userKey, cacheKey) require.Len(t, repMock.UpsertUserCalls(), 1) } func TestCachedUserRep_DeleteUser(t *testing.T) { // given - var cacheKey string + var cacheNS, cacheKey string cacheMock := &cacheMock{} - cacheMock.DelFunc = func(ctx context.Context, k string) error { - cacheKey = k + cacheMock.DelFunc = func(ctx context.Context, ns string, keys ...string) error { + cacheNS = ns + cacheKey = keys[0] return nil } @@ -74,17 +77,18 @@ func TestCachedUserRep_DeleteUser(t *testing.T) { // then require.NoError(t, err) - require.Equal(t, userKey("u1"), cacheKey) + require.Equal(t, userNS("u1"), cacheNS) + require.Equal(t, userKey, cacheKey) require.Len(t, repMock.DeleteUserCalls(), 1) } func TestCachedUserRep_FetchUser(t *testing.T) { // given cacheMock := &cacheMock{} - cacheMock.GetFunc = func(ctx context.Context, k string) ([]byte, error) { + cacheMock.GetFunc = func(ctx context.Context, ns, k string) ([]byte, error) { return nil, nil } - cacheMock.PutFunc = func(ctx context.Context, k string, val []byte) error { + cacheMock.PutFunc = func(ctx context.Context, ns, k string, val []byte) error { return nil } @@ -114,8 +118,8 @@ func TestCachedUserRep_FetchUser(t *testing.T) { func TestCachedUserRep_UserExists(t *testing.T) { // given cacheMock := &cacheMock{} - cacheMock.HasKeyFunc = func(ctx context.Context, k string) (bool, error) { - if k == userKey("u1") { + cacheMock.HasKeyFunc = func(ctx context.Context, ns, k string) (bool, error) { + if ns == userNS("u1") && k == userKey { return true, nil } return false, nil diff --git a/pkg/storage/cached/vcard.go b/pkg/storage/cached/vcard.go index 4a81c3e46..d8ce8f076 100644 --- a/pkg/storage/cached/vcard.go +++ b/pkg/storage/cached/vcard.go @@ -23,6 +23,8 @@ import ( "github.com/ortuman/jackal/pkg/storage/repository" ) +const vCardKey = "vc" + type vCardCodec struct { val stravaganza.Element } @@ -52,8 +54,9 @@ type cachedVCardRep struct { func (c *cachedVCardRep) UpsertVCard(ctx context.Context, vCard stravaganza.Element, username string) error { op := updateOp{ - c: c.c, - key: vCardKey(username), + c: c.c, + namespace: vCardNS(username), + invalidKeys: []string{vCardKey}, updateFn: func(ctx context.Context) error { return c.rep.UpsertVCard(ctx, vCard, username) }, @@ -63,9 +66,10 @@ func (c *cachedVCardRep) UpsertVCard(ctx context.Context, vCard stravaganza.Elem func (c *cachedVCardRep) FetchVCard(ctx context.Context, username string) (stravaganza.Element, error) { op := fetchOp{ - c: c.c, - key: vCardKey(username), - codec: &vCardCodec{}, + c: c.c, + namespace: vCardNS(username), + key: vCardKey, + codec: &vCardCodec{}, missFn: func(ctx context.Context) (interface{}, error) { return c.rep.FetchVCard(ctx, username) }, @@ -82,8 +86,9 @@ func (c *cachedVCardRep) FetchVCard(ctx context.Context, username string) (strav func (c *cachedVCardRep) DeleteVCard(ctx context.Context, username string) error { op := updateOp{ - c: c.c, - key: vCardKey(username), + c: c.c, + namespace: vCardNS(username), + invalidKeys: []string{vCardKey}, updateFn: func(ctx context.Context) error { return c.rep.DeleteVCard(ctx, username) }, @@ -91,6 +96,6 @@ func (c *cachedVCardRep) DeleteVCard(ctx context.Context, username string) error return op.do(ctx) } -func vCardKey(username string) string { +func vCardNS(username string) string { return fmt.Sprintf("vc:%s", username) } diff --git a/pkg/storage/cached/vcard_test.go b/pkg/storage/cached/vcard_test.go index a1b9a05f6..5f0a261eb 100644 --- a/pkg/storage/cached/vcard_test.go +++ b/pkg/storage/cached/vcard_test.go @@ -24,11 +24,12 @@ import ( func TestCachedVCardRep_UpsertVCard(t *testing.T) { // given - var cacheKey string + var cacheNS, cacheKey string cacheMock := &cacheMock{} - cacheMock.DelFunc = func(ctx context.Context, k string) error { - cacheKey = k + cacheMock.DelFunc = func(ctx context.Context, ns string, keys ...string) error { + cacheNS = ns + cacheKey = keys[0] return nil } @@ -48,17 +49,19 @@ func TestCachedVCardRep_UpsertVCard(t *testing.T) { // then require.NoError(t, err) - require.Equal(t, vCardKey("u1"), cacheKey) + require.Equal(t, vCardNS("u1"), cacheNS) + require.Equal(t, vCardKey, cacheKey) require.Len(t, repMock.UpsertVCardCalls(), 1) } func TestCachedVCardRep_DeleteVCard(t *testing.T) { // given - var cacheKey string + var cacheNS, cacheKey string cacheMock := &cacheMock{} - cacheMock.DelFunc = func(ctx context.Context, k string) error { - cacheKey = k + cacheMock.DelFunc = func(ctx context.Context, ns string, keys ...string) error { + cacheNS = ns + cacheKey = keys[0] return nil } @@ -76,17 +79,18 @@ func TestCachedVCardRep_DeleteVCard(t *testing.T) { // then require.NoError(t, err) - require.Equal(t, vCardKey("v1"), cacheKey) + require.Equal(t, vCardNS("v1"), cacheNS) + require.Equal(t, vCardKey, cacheKey) require.Len(t, repMock.DeleteVCardCalls(), 1) } func TestCachedVCardRep_FetchVCard(t *testing.T) { // given cacheMock := &cacheMock{} - cacheMock.GetFunc = func(ctx context.Context, k string) ([]byte, error) { + cacheMock.GetFunc = func(ctx context.Context, ns, k string) ([]byte, error) { return nil, nil } - cacheMock.PutFunc = func(ctx context.Context, k string, val []byte) error { + cacheMock.PutFunc = func(ctx context.Context, ns, k string, val []byte) error { return nil }