From c85f31f63f1d4b89cf07afe9a14cdf76ee046432 Mon Sep 17 00:00:00 2001 From: "P. Reis" Date: Fri, 10 May 2024 10:19:15 -0300 Subject: [PATCH 1/9] feat: create MuteListPolicy class --- src/policies/MuteListPolicy.ts | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 src/policies/MuteListPolicy.ts diff --git a/src/policies/MuteListPolicy.ts b/src/policies/MuteListPolicy.ts new file mode 100644 index 0000000..e234219 --- /dev/null +++ b/src/policies/MuteListPolicy.ts @@ -0,0 +1,23 @@ +import { NostrEvent, NostrRelayOK, NPolicy, NStore } from '@nostrify/nostrify'; + +import { getTagSet } from '@/tags.ts'; + +export class MuteListPolicy implements NPolicy { + constructor(private pubkey: string, private store: NStore) { + this.store = store; + this.pubkey = pubkey; + } + + async call(event: NostrEvent): Promise { + const allowEvent = ['OK', event.id, true, ''] as NostrRelayOK; + const blockEvent = ['OK', event.id, false, 'You are banned in this server.'] as NostrRelayOK; + + const [muteList] = await this.store.query([{ authors: [this.pubkey], kinds: [10000], limit: 1 }]); + if (!muteList) return allowEvent; + + const mutedPubkeys = getTagSet(muteList.tags, 'p'); + if (mutedPubkeys.has(event.pubkey)) return blockEvent; + + return allowEvent; + } +} From 0c0465f131a8c1a92f083d26f5d964cc31080708 Mon Sep 17 00:00:00 2001 From: "P. Reis" Date: Fri, 10 May 2024 10:31:34 -0300 Subject: [PATCH 2/9] refactor(UserStore): move mute logic to separate function & create isMuted() function --- src/storages/UserStore.ts | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/src/storages/UserStore.ts b/src/storages/UserStore.ts index a3f0726..b13df42 100644 --- a/src/storages/UserStore.ts +++ b/src/storages/UserStore.ts @@ -1,4 +1,5 @@ import { NostrEvent, NostrFilter, NStore } from '@nostrify/nostrify'; + import { DittoEvent } from '@/interfaces/DittoEvent.ts'; import { getTagSet } from '@/tags.ts'; @@ -16,25 +17,34 @@ export class UserStore implements NStore { } /** - * Query events that `pubkey` did not block + * Query events that `pubkey` did not mute * https://github.com/nostr-protocol/nips/blob/master/51.md#standard-lists */ async query(filters: NostrFilter[], opts: { signal?: AbortSignal; limit?: number } = {}): Promise { const allEvents = await this.store.query(filters, opts); - const mutedPubkeysEvent = await this.getMuteList(); - if (!mutedPubkeysEvent) { - return allEvents; - } - const mutedPubkeys = getTagSet(mutedPubkeysEvent.tags, 'p'); + const mutedPubkeys = await this.getMutedPubkeys(); return allEvents.filter((event) => { return event.kind === 0 || mutedPubkeys.has(event.pubkey) === false; }); } + async isMuted(pubkey: string): Promise { + const mutedPubkeys = await this.getMutedPubkeys(); + return mutedPubkeys.has(pubkey); + } + private async getMuteList(): Promise { const [muteList] = await this.store.query([{ authors: [this.pubkey], kinds: [10000], limit: 1 }]); return muteList; } + + private async getMutedPubkeys(): Promise> { + const mutedPubkeysEvent = await this.getMuteList(); + if (!mutedPubkeysEvent) { + return new Set(); + } + return getTagSet(mutedPubkeysEvent.tags, 'p'); + } } From 26dd4606ed26c7d907a9f0348862829713047af9 Mon Sep 17 00:00:00 2001 From: "P. Reis" Date: Fri, 10 May 2024 10:35:04 -0300 Subject: [PATCH 3/9] test: UserStore with 100.00% code coverage --- src/storages/UserStore.test.ts | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/storages/UserStore.test.ts b/src/storages/UserStore.test.ts index 11f96cb..42c0439 100644 --- a/src/storages/UserStore.test.ts +++ b/src/storages/UserStore.test.ts @@ -8,7 +8,7 @@ import userMe from '~/fixtures/events/event-0-makes-repost-with-quote-repost.jso import blockEvent from '~/fixtures/events/kind-10000-black-blocks-user-me.json' with { type: 'json' }; import event1authorUserMe from '~/fixtures/events/event-1-quote-repost-will-be-reposted.json' with { type: 'json' }; -Deno.test('query events of users that are not blocked', async () => { +Deno.test('query events of users that are not muted', async () => { const userBlackCopy = structuredClone(userBlack); const userMeCopy = structuredClone(userMe); const blockEventCopy = structuredClone(blockEvent); @@ -24,4 +24,19 @@ Deno.test('query events of users that are not blocked', async () => { await store.event(event1authorUserMeCopy); assertEquals(await store.query([{ kinds: [1] }], { limit: 1 }), []); + assertEquals(await store.isMuted(userMeCopy.pubkey), true); +}); + +Deno.test('user never muted anyone', async () => { + const userBlackCopy = structuredClone(userBlack); + const userMeCopy = structuredClone(userMe); + + const db = new MockRelay(); + + const store = new UserStore(userBlackCopy.pubkey, db); + + await store.event(userBlackCopy); + await store.event(userMeCopy); + + assertEquals(await store.isMuted(userMeCopy.pubkey), false); }); From ebeec2ccba1ab18c6b62a3ac0aeb6758df4604ff Mon Sep 17 00:00:00 2001 From: "P. Reis" Date: Fri, 10 May 2024 11:26:15 -0300 Subject: [PATCH 4/9] test: MuteListPolicy with 100.00% code coverage --- src/policies/MuteListPolicy.test.ts | 72 +++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 src/policies/MuteListPolicy.test.ts diff --git a/src/policies/MuteListPolicy.test.ts b/src/policies/MuteListPolicy.test.ts new file mode 100644 index 0000000..69561f8 --- /dev/null +++ b/src/policies/MuteListPolicy.test.ts @@ -0,0 +1,72 @@ +import { MockRelay } from '@nostrify/nostrify/test'; + +import { assertEquals } from '@/deps-test.ts'; +import { UserStore } from '@/storages/UserStore.ts'; +import { MuteListPolicy } from '@/policies/MuteListPolicy.ts'; + +import userBlack from '~/fixtures/events/kind-0-black.json' with { type: 'json' }; +import userMe from '~/fixtures/events/event-0-makes-repost-with-quote-repost.json' with { type: 'json' }; +import blockEvent from '~/fixtures/events/kind-10000-black-blocks-user-me.json' with { type: 'json' }; +import event1authorUserMe from '~/fixtures/events/event-1-quote-repost-will-be-reposted.json' with { type: 'json' }; +import event1 from '~/fixtures/events/event-1.json' with { type: 'json' }; + +Deno.test('block event: muted user cannot post', async () => { + const userBlackCopy = structuredClone(userBlack); + const userMeCopy = structuredClone(userMe); + const blockEventCopy = structuredClone(blockEvent); + const event1authorUserMeCopy = structuredClone(event1authorUserMe); + + const db = new MockRelay(); + + const store = new UserStore(userBlackCopy.pubkey, db); + const policy = new MuteListPolicy(userBlack.pubkey, db); + + await store.event(blockEventCopy); + await store.event(userBlackCopy); + await store.event(userMeCopy); + + const ok = await policy.call(event1authorUserMeCopy); + + assertEquals(ok, ['OK', event1authorUserMeCopy.id, false, 'You are banned in this server.']); +}); + +Deno.test('allow event: user is NOT muted because there is no muted event', async () => { + const userBlackCopy = structuredClone(userBlack); + const userMeCopy = structuredClone(userMe); + const event1authorUserMeCopy = structuredClone(event1authorUserMe); + + const db = new MockRelay(); + + const store = new UserStore(userBlackCopy.pubkey, db); + const policy = new MuteListPolicy(userBlack.pubkey, db); + + await store.event(userBlackCopy); + await store.event(userMeCopy); + + const ok = await policy.call(event1authorUserMeCopy); + + assertEquals(ok, ['OK', event1authorUserMeCopy.id, true, '']); +}); + +Deno.test('allow event: user is NOT muted because he is not in mute event', async () => { + const userBlackCopy = structuredClone(userBlack); + const userMeCopy = structuredClone(userMe); + const event1authorUserMeCopy = structuredClone(event1authorUserMe); + const blockEventCopy = structuredClone(blockEvent); + const event1copy = structuredClone(event1); + + const db = new MockRelay(); + + const store = new UserStore(userBlackCopy.pubkey, db); + const policy = new MuteListPolicy(userBlack.pubkey, db); + + await store.event(userBlackCopy); + await store.event(blockEventCopy); + await store.event(userMeCopy); + await store.event(event1copy); + await store.event(event1authorUserMeCopy); + + const ok = await policy.call(event1copy); + + assertEquals(ok, ['OK', event1.id, true, '']); +}); From 86518dbac5af56799dc015227f0b19c225cf8536 Mon Sep 17 00:00:00 2001 From: "P. Reis" Date: Fri, 10 May 2024 14:34:48 -0300 Subject: [PATCH 5/9] refactor(MuteListPolicy): shorthand private constructor --- src/policies/MuteListPolicy.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/policies/MuteListPolicy.ts b/src/policies/MuteListPolicy.ts index e234219..c069565 100644 --- a/src/policies/MuteListPolicy.ts +++ b/src/policies/MuteListPolicy.ts @@ -3,10 +3,7 @@ import { NostrEvent, NostrRelayOK, NPolicy, NStore } from '@nostrify/nostrify'; import { getTagSet } from '@/tags.ts'; export class MuteListPolicy implements NPolicy { - constructor(private pubkey: string, private store: NStore) { - this.store = store; - this.pubkey = pubkey; - } + constructor(private pubkey: string, private store: NStore) {} async call(event: NostrEvent): Promise { const allowEvent = ['OK', event.id, true, ''] as NostrRelayOK; From 4069ddc02c82d2f53df49671a4e0a8febfda3755 Mon Sep 17 00:00:00 2001 From: "P. Reis" Date: Fri, 10 May 2024 14:37:02 -0300 Subject: [PATCH 6/9] refactor(MuteListPolicy): human lint preference --- src/policies/MuteListPolicy.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/policies/MuteListPolicy.ts b/src/policies/MuteListPolicy.ts index c069565..f9f8de2 100644 --- a/src/policies/MuteListPolicy.ts +++ b/src/policies/MuteListPolicy.ts @@ -6,8 +6,8 @@ export class MuteListPolicy implements NPolicy { constructor(private pubkey: string, private store: NStore) {} async call(event: NostrEvent): Promise { - const allowEvent = ['OK', event.id, true, ''] as NostrRelayOK; - const blockEvent = ['OK', event.id, false, 'You are banned in this server.'] as NostrRelayOK; + const allowEvent: NostrRelayOK = ['OK', event.id, true, '']; + const blockEvent: NostrRelayOK = ['OK', event.id, false, 'You are banned in this server.']; const [muteList] = await this.store.query([{ authors: [this.pubkey], kinds: [10000], limit: 1 }]); if (!muteList) return allowEvent; From 3970bb81f76853dec4a1dda4c2fd6876dae2ec26 Mon Sep 17 00:00:00 2001 From: "P. Reis" Date: Fri, 10 May 2024 18:03:35 -0300 Subject: [PATCH 7/9] refactor(MuteListPolicy): simplify condition --- src/policies/MuteListPolicy.ts | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/policies/MuteListPolicy.ts b/src/policies/MuteListPolicy.ts index f9f8de2..1db8556 100644 --- a/src/policies/MuteListPolicy.ts +++ b/src/policies/MuteListPolicy.ts @@ -6,15 +6,13 @@ export class MuteListPolicy implements NPolicy { constructor(private pubkey: string, private store: NStore) {} async call(event: NostrEvent): Promise { - const allowEvent: NostrRelayOK = ['OK', event.id, true, '']; - const blockEvent: NostrRelayOK = ['OK', event.id, false, 'You are banned in this server.']; - const [muteList] = await this.store.query([{ authors: [this.pubkey], kinds: [10000], limit: 1 }]); - if (!muteList) return allowEvent; + const pubkeys = getTagSet(muteList?.tags ?? [], 'p'); - const mutedPubkeys = getTagSet(muteList.tags, 'p'); - if (mutedPubkeys.has(event.pubkey)) return blockEvent; + if (pubkeys.has(event.pubkey)) { + return ['OK', event.id, false, 'You are banned in this server.']; + } - return allowEvent; + return ['OK', event.id, true, '']; } } From 4fdf15761c1e5570df4e5a18e8b5e5d6074c0ff6 Mon Sep 17 00:00:00 2001 From: "P. Reis" Date: Fri, 10 May 2024 18:18:55 -0300 Subject: [PATCH 8/9] refactor(UserStore): remove isMuted function --- src/storages/UserStore.ts | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/storages/UserStore.ts b/src/storages/UserStore.ts index b13df42..1c7aaee 100644 --- a/src/storages/UserStore.ts +++ b/src/storages/UserStore.ts @@ -30,11 +30,6 @@ export class UserStore implements NStore { }); } - async isMuted(pubkey: string): Promise { - const mutedPubkeys = await this.getMutedPubkeys(); - return mutedPubkeys.has(pubkey); - } - private async getMuteList(): Promise { const [muteList] = await this.store.query([{ authors: [this.pubkey], kinds: [10000], limit: 1 }]); return muteList; From 732cb45b1eb60b868e046c843ca8f176470d40c5 Mon Sep 17 00:00:00 2001 From: "P. Reis" Date: Fri, 10 May 2024 18:20:00 -0300 Subject: [PATCH 9/9] test(UserStore): update with 100.00% code coverage --- src/storages/UserStore.test.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/storages/UserStore.test.ts b/src/storages/UserStore.test.ts index 42c0439..b1955bd 100644 --- a/src/storages/UserStore.test.ts +++ b/src/storages/UserStore.test.ts @@ -24,7 +24,6 @@ Deno.test('query events of users that are not muted', async () => { await store.event(event1authorUserMeCopy); assertEquals(await store.query([{ kinds: [1] }], { limit: 1 }), []); - assertEquals(await store.isMuted(userMeCopy.pubkey), true); }); Deno.test('user never muted anyone', async () => { @@ -38,5 +37,5 @@ Deno.test('user never muted anyone', async () => { await store.event(userBlackCopy); await store.event(userMeCopy); - assertEquals(await store.isMuted(userMeCopy.pubkey), false); + assertEquals(await store.query([{ kinds: [0], authors: [userMeCopy.pubkey] }], { limit: 1 }), [userMeCopy]); });