From 344b5211dc6bed34a1807b966fe63127d46986f3 Mon Sep 17 00:00:00 2001 From: Jim Toth Date: Tue, 19 Sep 2023 20:54:47 -0400 Subject: [PATCH 1/2] WIP --- src/legacy/legacy.ts | 23 ++++- test/e2e/artbycity.e2e.ts | 202 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 221 insertions(+), 4 deletions(-) diff --git a/src/legacy/legacy.ts b/src/legacy/legacy.ts index 65582d9..95014db 100644 --- a/src/legacy/legacy.ts +++ b/src/legacy/legacy.ts @@ -27,11 +27,13 @@ export default class ArtByCityLegacy { slugs: LegacyMemcache profiles: LegacyMemcache avatars: LegacyMemcache + likes: LegacyMemcache } = { /* eslint-disable indent */ publications: new LegacyMemcache(), slugs: new LegacyMemcache(), profiles: new LegacyMemcache(), - avatars: new LegacyMemcache() + avatars: new LegacyMemcache(), + likes: new LegacyMemcache() } /* eslint-enable indent */ constructor(arweave: Arweave, private readonly config: ArtByCityConfig) { @@ -398,9 +400,16 @@ export default class ArtByCityLegacy { async queryLikes( address: string, receivedOrSent: 'received' | 'sent', - limit: number | 'all' = 100, - cursor?: string + limit: number | 'all' = 'all', + cursor?: string, + useCache: boolean = true ): Promise { + const cacheKey = `${receivedOrSent}-by-${address}` + if (this.cacheEnabled && useCache && limit === 'all') { + const cached = this.caches.likes.get(cacheKey) + if (cached) { return cached } + } + const receivedOrSentOpts = receivedOrSent === 'received' ? { to: address } : { from: address } @@ -414,10 +423,16 @@ export default class ArtByCityLegacy { cursor }) - return { + const feed = { cursor: nextCursor, likes: transactions.map(mapLegacyLikeFromTransaction) } + + if (this.cacheEnabled && useCache && limit === 'all') { + this.caches.likes.put(cacheKey, feed) + } + + return feed } async queryLikesForPublication( diff --git a/test/e2e/artbycity.e2e.ts b/test/e2e/artbycity.e2e.ts index 786a9c8..c798fee 100644 --- a/test/e2e/artbycity.e2e.ts +++ b/test/e2e/artbycity.e2e.ts @@ -1201,6 +1201,208 @@ describe(`ArtByCity (web)`, () => { expect(putSpy).to.not.have.been.called }) }) + + context('Likes', () => { + context('sent by address', () => { + it('gets queries for all likes from memcache', async () => { + const address = LIKER_ADDRESS + const cacheKey = `sent-by-${address}` + const abc = new ArtByCity(arweave) + + const getSpy = sandbox.spy(abc.legacy.caches.likes, 'get') + const putSpy = sandbox.spy(abc.legacy.caches.likes, 'put') + + const likesNoCache = await abc.legacy.queryLikes(address, 'sent') + const likesFromCache = await abc.legacy.queryLikes(address, 'sent') + + expect(likesNoCache).to.deep.equal(likesFromCache) + expect(getSpy).to.have.been.calledTwice + expect(putSpy).to.have.been.calledOnce + expect(getSpy.firstCall).to.have.been.calledWith(cacheKey) + expect(getSpy.firstCall).to.have.returned(null) + expect(putSpy.firstCall).to.have.been.calledWith(cacheKey) + expect(getSpy.secondCall).to.have.been.calledWith(cacheKey) + expect(getSpy.secondCall).to.have.returned(likesNoCache) + }) + + it('allows force override of cache', async () => { + const address = LIKER_ADDRESS + const abc = new ArtByCity(arweave) + + const getSpy = sandbox.spy(abc.legacy.caches.likes, 'get') + const putSpy = sandbox.spy(abc.legacy.caches.likes, 'put') + + const likesNoCache = await abc.legacy.queryLikes(address, 'sent') + const likesCacheBust = await abc.legacy + .queryLikes(address, 'sent', 'all', undefined, false) + + expect(likesNoCache).to.deep.equal(likesCacheBust) + expect(getSpy).to.have.been.calledOnce.and.returned(null) + expect(putSpy).to.have.been.calledOnce + }) + + it('does not use cache when it is disabled', async () => { + const address = LIKER_ADDRESS + const abc = new ArtByCity(arweave, { cache: { type: 'disabled' } }) + + const getSpy = sandbox.spy(abc.legacy.caches.likes, 'get') + const putSpy = sandbox.spy(abc.legacy.caches.likes, 'put') + + const likesNoCache = await abc.legacy.queryLikes(address, 'sent') + const likesAlsoNoCache = await abc.legacy + .queryLikes(address, 'sent') + + expect(likesNoCache).to.deep.equal(likesAlsoNoCache) + expect(getSpy).to.not.have.been.called + expect(putSpy).to.not.have.been.called + }) + + it('does not use cache when not querying for all', async () => { + const address = LIKER_ADDRESS + const abc = new ArtByCity(arweave) + + const getSpy = sandbox.spy(abc.legacy.caches.likes, 'get') + const putSpy = sandbox.spy(abc.legacy.caches.likes, 'put') + + const threeLikes = await abc.legacy.queryLikes(address, 'sent', 3) + const alsoThreeLikes = await abc.legacy + .queryLikes(address, 'sent', 3) + + expect(threeLikes.likes).to.deep.equal(alsoThreeLikes.likes) + expect(getSpy).to.not.have.been.called + expect(putSpy).to.not.have.been.called + }) + }) + + context('received by address', () => { + it('gets queries for all likes from memcache', async () => { + const address = ARTIST_ADDRESS + const cacheKey = `received-by-${address}` + const abc = new ArtByCity(arweave) + + const getSpy = sandbox.spy(abc.legacy.caches.likes, 'get') + const putSpy = sandbox.spy(abc.legacy.caches.likes, 'put') + + const likesNoCache = await abc.legacy + .queryLikes(address, 'received') + const likesFromCache = await abc.legacy + .queryLikes(address, 'received') + + expect(likesNoCache).to.deep.equal(likesFromCache) + expect(getSpy).to.have.been.calledTwice + expect(putSpy).to.have.been.calledOnce + expect(getSpy.firstCall).to.have.been.calledWith(cacheKey) + expect(getSpy.firstCall).to.have.returned(null) + expect(putSpy.firstCall).to.have.been.calledWith(cacheKey) + expect(getSpy.secondCall).to.have.been.calledWith(cacheKey) + expect(getSpy.secondCall).to.have.returned(likesNoCache) + }) + + it('allows force override of cache', async () => { + const address = ARTIST_ADDRESS + const abc = new ArtByCity(arweave) + + const getSpy = sandbox.spy(abc.legacy.caches.likes, 'get') + const putSpy = sandbox.spy(abc.legacy.caches.likes, 'put') + + const likesNoCache = await abc.legacy + .queryLikes(address, 'received') + const likesCacheBust = await abc.legacy + .queryLikes(address, 'received', 'all', undefined, false) + + expect(likesNoCache).to.deep.equal(likesCacheBust) + expect(getSpy).to.have.been.calledOnce.and.returned(null) + expect(putSpy).to.have.been.calledOnce + }) + + it('does not use cache when it is disabled', async () => { + const address = ARTIST_ADDRESS + const abc = new ArtByCity(arweave, { cache: { type: 'disabled' } }) + + const getSpy = sandbox.spy(abc.legacy.caches.likes, 'get') + const putSpy = sandbox.spy(abc.legacy.caches.likes, 'put') + + const likesNoCache = await abc.legacy + .queryLikes(address, 'received') + const likesAlsoNoCache = await abc.legacy + .queryLikes(address, 'received') + + expect(likesNoCache).to.deep.equal(likesAlsoNoCache) + expect(getSpy).to.not.have.been.called + expect(putSpy).to.not.have.been.called + }) + + it('does not use cache when not querying for all', async () => { + const address = ARTIST_ADDRESS + const abc = new ArtByCity(arweave) + + const getSpy = sandbox.spy(abc.legacy.caches.likes, 'get') + const putSpy = sandbox.spy(abc.legacy.caches.likes, 'put') + + const threeLikes = await abc.legacy + .queryLikes(address, 'received', 3) + const alsoThreeLikes = await abc.legacy + .queryLikes(address, 'received', 3) + + expect(threeLikes.likes).to.deep.equal(alsoThreeLikes.likes) + expect(getSpy).to.not.have.been.called + expect(putSpy).to.not.have.been.called + }) + }) + + context('by publication', () => { + it('gets queries for all likes from memcache', async () => { + const id = LIKED_PUBLICATION + const cacheKey = `likes-for-${id}` + const abc = new ArtByCity(arweave) + + const getSpy = sandbox.spy(abc.legacy.caches.likes, 'get') + const putSpy = sandbox.spy(abc.legacy.caches.likes, 'put') + + const likesNoCache = await abc.legacy + .queryLikesForPublication(id) + const likesFromCache = await abc.legacy + .queryLikesForPublication(id) + + expect(likesNoCache).to.deep.equal(likesFromCache) + expect(getSpy).to.have.been.calledTwice + expect(putSpy).to.have.been.calledOnce + expect(getSpy.firstCall).to.have.been.calledWith(cacheKey) + expect(getSpy.firstCall).to.have.returned(null) + expect(putSpy.firstCall).to.have.been.calledWith(cacheKey) + expect(getSpy.secondCall).to.have.been.calledWith(cacheKey) + expect(getSpy.secondCall).to.have.returned(likesNoCache) + }) + + it('allows force override of cache') + + it('does not use cache when it is disabled') + + it('does not use cache when not querying for all') + }) + }) + + context('Tips', () => { + context('sent by address', () => { + it('gets queries for all tips from memcache') + + it('allows force override of cache') + + it('does not use cache when it is disabled') + + it('does not use cache when not querying for all') + }) + + context('received by address', () => { + it('gets queries for all tips from memcache') + + it('allows force override of cache') + + it('does not use cache when it is disabled') + + it('does not use cache when not querying for all') + }) + }) }) }) }) From 32340e78b25c60085e86127d72641a587fc6b23e Mon Sep 17 00:00:00 2001 From: Jim Toth Date: Wed, 20 Sep 2023 16:37:46 -0400 Subject: [PATCH 2/2] Adds legacy memcache when querying for 'all' likes or tips --- src/legacy/legacy.ts | 42 +++++++-- test/e2e/artbycity.e2e.ts | 189 +++++++++++++++++++++++++++++++++++--- 2 files changed, 213 insertions(+), 18 deletions(-) diff --git a/src/legacy/legacy.ts b/src/legacy/legacy.ts index 95014db..5bc244f 100644 --- a/src/legacy/legacy.ts +++ b/src/legacy/legacy.ts @@ -28,12 +28,14 @@ export default class ArtByCityLegacy { profiles: LegacyMemcache avatars: LegacyMemcache likes: LegacyMemcache + tips: LegacyMemcache } = { /* eslint-disable indent */ publications: new LegacyMemcache(), slugs: new LegacyMemcache(), profiles: new LegacyMemcache(), avatars: new LegacyMemcache(), - likes: new LegacyMemcache() + likes: new LegacyMemcache(), + tips: new LegacyMemcache() } /* eslint-enable indent */ constructor(arweave: Arweave, private readonly config: ArtByCityConfig) { @@ -437,9 +439,16 @@ export default class ArtByCityLegacy { async queryLikesForPublication( id: string, - limit: number | 'all' = 100, - cursor?: string + limit: number | 'all' = 'all', + cursor?: string, + useCache: boolean = true ): Promise { + const cacheKey = `likes-for-${id}` + if (this.cacheEnabled && useCache && limit === 'all') { + const cached = this.caches.likes.get(cacheKey) + if (cached) { return cached } + } + const { transactions, cursor: nextCursor @@ -449,18 +458,31 @@ export default class ArtByCityLegacy { tags: [ { name: 'liked-entity', value: id } ] }) - return { + const feed = { cursor: nextCursor, likes: transactions.map(mapLegacyLikeFromTransaction) } + + if (this.cacheEnabled && useCache && limit === 'all') { + this.caches.likes.put(cacheKey, feed) + } + + return feed } async queryTips( address: string, receivedOrSent: 'received' | 'sent', - limit: number | 'all' = 100, - cursor?: string + limit: number | 'all' = 'all', + cursor?: string, + useCache: boolean = true ): Promise { + const cacheKey = `${receivedOrSent}-by-${address}` + if (this.cacheEnabled && useCache && limit === 'all') { + const cached = this.caches.tips.get(cacheKey) + if (cached) { return cached } + } + const receivedOrSentOpts = receivedOrSent === 'received' ? { to: address } : { from: address } @@ -474,7 +496,7 @@ export default class ArtByCityLegacy { cursor }) - return { + const feed = { cursor: nextCursor, tips: transactions.map(tx => { return { @@ -486,5 +508,11 @@ export default class ArtByCityLegacy { } }) } + + if (this.cacheEnabled && useCache && limit === 'all') { + this.caches.tips.put(cacheKey, feed) + } + + return feed } } diff --git a/test/e2e/artbycity.e2e.ts b/test/e2e/artbycity.e2e.ts index c798fee..ad7138b 100644 --- a/test/e2e/artbycity.e2e.ts +++ b/test/e2e/artbycity.e2e.ts @@ -1374,33 +1374,200 @@ describe(`ArtByCity (web)`, () => { expect(getSpy.secondCall).to.have.returned(likesNoCache) }) - it('allows force override of cache') + it('allows force override of cache', async () => { + const id = LIKED_PUBLICATION + const abc = new ArtByCity(arweave) + + const getSpy = sandbox.spy(abc.legacy.caches.likes, 'get') + const putSpy = sandbox.spy(abc.legacy.caches.likes, 'put') + + const likesNoCache = await abc.legacy + .queryLikesForPublication(id) + const likesCacheBust = await abc.legacy + .queryLikesForPublication(id, 'all', undefined, false) + + expect(likesNoCache.likes).to.deep.equal(likesCacheBust.likes) + expect(getSpy).to.have.been.calledOnce.and.returned(null) + expect(putSpy).to.have.been.calledOnce + }) + + it('does not use cache when it is disabled', async () => { + const id = LIKED_PUBLICATION + const abc = new ArtByCity(arweave, { cache: { type: 'disabled' } }) + + const getSpy = sandbox.spy(abc.legacy.caches.likes, 'get') + const putSpy = sandbox.spy(abc.legacy.caches.likes, 'put') + + const likesNoCache = await abc.legacy + .queryLikesForPublication(id) + const likesAlsoNoCache = await abc.legacy + .queryLikesForPublication(id) + + expect(likesNoCache).to.deep.equal(likesAlsoNoCache) + expect(getSpy).to.not.have.been.called + expect(putSpy).to.not.have.been.called + }) + + it('does not use cache when not querying for all', async () => { + const id = LIKED_PUBLICATION + const abc = new ArtByCity(arweave) - it('does not use cache when it is disabled') + const getSpy = sandbox.spy(abc.legacy.caches.likes, 'get') + const putSpy = sandbox.spy(abc.legacy.caches.likes, 'put') - it('does not use cache when not querying for all') + const threeLikes = await abc.legacy + .queryLikesForPublication(id, 3) + const alsoThreeLikes = await abc.legacy + .queryLikesForPublication(id, 3) + + expect(threeLikes.likes).to.deep.equal(alsoThreeLikes.likes) + expect(getSpy).to.not.have.been.called + expect(putSpy).to.not.have.been.called + }) }) }) context('Tips', () => { context('sent by address', () => { - it('gets queries for all tips from memcache') + it('gets queries for all tips from memcache', async () => { + const address = TIPPER_ADDRESS + const cacheKey = `sent-by-${address}` + const abc = new ArtByCity(arweave) + + const getSpy = sandbox.spy(abc.legacy.caches.tips, 'get') + const putSpy = sandbox.spy(abc.legacy.caches.tips, 'put') + + const tipsNoCache = await abc.legacy.queryTips(address, 'sent') + const tipsFromCache = await abc.legacy.queryTips(address, 'sent') + + expect(tipsNoCache).to.deep.equal(tipsFromCache) + expect(getSpy).to.have.been.calledTwice + expect(putSpy).to.have.been.calledOnce + expect(getSpy.firstCall).to.have.been.calledWith(cacheKey) + expect(getSpy.firstCall).to.have.returned(null) + expect(putSpy.firstCall).to.have.been.calledWith(cacheKey) + expect(getSpy.secondCall).to.have.been.calledWith(cacheKey) + expect(getSpy.secondCall).to.have.returned(tipsNoCache) + }) + + it('allows force override of cache', async () => { + const address = TIPPER_ADDRESS + const abc = new ArtByCity(arweave) + + const getSpy = sandbox.spy(abc.legacy.caches.tips, 'get') + const putSpy = sandbox.spy(abc.legacy.caches.tips, 'put') - it('allows force override of cache') + const tipsNoCache = await abc.legacy.queryTips(address, 'sent') + const tipsCacheBust = await abc.legacy + .queryTips(address, 'sent', 'all', undefined, false) + + expect(tipsNoCache).to.deep.equal(tipsCacheBust) + expect(getSpy).to.have.been.calledOnce.and.returned(null) + expect(putSpy).to.have.been.calledOnce + }) - it('does not use cache when it is disabled') + it('does not use cache when it is disabled', async () => { + const address = TIPPER_ADDRESS + const abc = new ArtByCity(arweave, { cache: { type: 'disabled' } }) - it('does not use cache when not querying for all') + const getSpy = sandbox.spy(abc.legacy.caches.tips, 'get') + const putSpy = sandbox.spy(abc.legacy.caches.tips, 'put') + + const tipsNoCache = await abc.legacy.queryTips(address, 'sent') + const tipsAlsoNoCache = await abc.legacy.queryTips(address, 'sent') + + expect(tipsNoCache).to.deep.equal(tipsAlsoNoCache) + expect(getSpy).to.not.have.been.called + expect(putSpy).to.not.have.been.called + }) + + it('does not use cache when not querying for all', async () => { + const address = TIPPER_ADDRESS + const abc = new ArtByCity(arweave) + + const getSpy = sandbox.spy(abc.legacy.caches.tips, 'get') + const putSpy = sandbox.spy(abc.legacy.caches.tips, 'put') + + const threeTips = await abc.legacy.queryTips(address, 'sent', 3) + const alsoThreeTips = await abc.legacy.queryTips(address, 'sent', 3) + + expect(threeTips.tips).to.deep.equal(alsoThreeTips.tips) + expect(getSpy).to.not.have.been.called + expect(putSpy).to.not.have.been.called + }) }) context('received by address', () => { - it('gets queries for all tips from memcache') + it('gets queries for all tips from memcache', async () => { + const address = TIPPEE_ADDRESS + const cacheKey = `received-by-${address}` + const abc = new ArtByCity(arweave) + + const getSpy = sandbox.spy(abc.legacy.caches.tips, 'get') + const putSpy = sandbox.spy(abc.legacy.caches.tips, 'put') + + const tipsNoCache = await abc.legacy + .queryTips(address, 'received') + const tipsFromCache = await abc.legacy + .queryTips(address, 'received') + + expect(tipsNoCache).to.deep.equal(tipsFromCache) + expect(getSpy).to.have.been.calledTwice + expect(putSpy).to.have.been.calledOnce + expect(getSpy.firstCall).to.have.been.calledWith(cacheKey) + expect(getSpy.firstCall).to.have.returned(null) + expect(putSpy.firstCall).to.have.been.calledWith(cacheKey) + expect(getSpy.secondCall).to.have.been.calledWith(cacheKey) + expect(getSpy.secondCall).to.have.returned(tipsNoCache) + }) + + it('allows force override of cache', async () => { + const address = TIPPEE_ADDRESS + const abc = new ArtByCity(arweave) - it('allows force override of cache') + const getSpy = sandbox.spy(abc.legacy.caches.tips, 'get') + const putSpy = sandbox.spy(abc.legacy.caches.tips, 'put') - it('does not use cache when it is disabled') + const tipsNoCache = await abc.legacy.queryTips(address, 'received') + const tipsCacheBust = await abc.legacy + .queryTips(address, 'received', 'all', undefined, false) + + expect(tipsNoCache).to.deep.equal(tipsCacheBust) + expect(getSpy).to.have.been.calledOnce.and.returned(null) + expect(putSpy).to.have.been.calledOnce + }) + + it('does not use cache when it is disabled', async () => { + const address = TIPPEE_ADDRESS + const abc = new ArtByCity(arweave, { cache: { type: 'disabled' } }) - it('does not use cache when not querying for all') + const getSpy = sandbox.spy(abc.legacy.caches.tips, 'get') + const putSpy = sandbox.spy(abc.legacy.caches.tips, 'put') + + const tipsNoCache = await abc.legacy.queryTips(address, 'received') + const tipsAlsoNoCache = await abc.legacy + .queryTips(address, 'received') + + expect(tipsNoCache).to.deep.equal(tipsAlsoNoCache) + expect(getSpy).to.not.have.been.called + expect(putSpy).to.not.have.been.called + }) + + it('does not use cache when not querying for all', async () => { + const address = TIPPEE_ADDRESS + const abc = new ArtByCity(arweave) + + const getSpy = sandbox.spy(abc.legacy.caches.tips, 'get') + const putSpy = sandbox.spy(abc.legacy.caches.tips, 'put') + + const threeTips = await abc.legacy.queryTips(address, 'received', 3) + const alsoThreeTips = await abc.legacy + .queryTips(address, 'received', 3) + + expect(threeTips.tips).to.deep.equal(alsoThreeTips.tips) + expect(getSpy).to.not.have.been.called + expect(putSpy).to.not.have.been.called + }) }) }) })