Consolidate AdminStore and UserStore

This commit is contained in:
Alex Gleason 2025-02-20 20:03:31 -06:00
parent 8a978b088b
commit 8f49b99935
No known key found for this signature in database
GPG key ID: 7211D1F99744FBB7
4 changed files with 60 additions and 76 deletions

View file

@ -1,43 +0,0 @@
import { NostrEvent, NostrFilter, NStore } from '@nostrify/nostrify';
import { Conf } from '@/config.ts';
import { DittoEvent } from '@/interfaces/DittoEvent.ts';
import { getTagSet } from '@/utils/tags.ts';
/** A store that prevents banned users from being displayed. */
export class AdminStore implements NStore {
constructor(private store: NStore) {}
async event(event: NostrEvent, opts?: { signal?: AbortSignal }): Promise<void> {
return await this.store.event(event, opts);
}
async query(filters: NostrFilter[], opts: { signal?: AbortSignal; limit?: number } = {}): Promise<DittoEvent[]> {
const events = await this.store.query(filters, opts);
const pubkeys = new Set(events.map((event) => event.pubkey));
const users = await this.store.query([{
kinds: [30382],
authors: [await Conf.signer.getPublicKey()],
'#d': [...pubkeys],
limit: pubkeys.size,
}]);
const adminPubkey = await Conf.signer.getPublicKey();
return events.filter((event) => {
const user = users.find(
({ kind, pubkey, tags }) =>
kind === 30382 && pubkey === adminPubkey && tags.find(([name]) => name === 'd')?.[1] === event.pubkey,
);
const n = getTagSet(user?.tags ?? [], 'n');
if (n.has('disabled')) {
return false;
}
return true;
});
}
}

View file

@ -14,9 +14,8 @@ Deno.test('query events of users that are not muted', async () => {
const blockEventCopy = structuredClone(blockEvent); const blockEventCopy = structuredClone(blockEvent);
const event1authorUserMeCopy = structuredClone(event1authorUserMe); const event1authorUserMeCopy = structuredClone(event1authorUserMe);
const db = new MockRelay(); const relay = new MockRelay();
const store = new UserStore({ relay, userPubkey: userBlackCopy.pubkey });
const store = new UserStore(userBlackCopy.pubkey, db);
await store.event(blockEventCopy); await store.event(blockEventCopy);
await store.event(userBlackCopy); await store.event(userBlackCopy);
@ -30,9 +29,8 @@ Deno.test('user never muted anyone', async () => {
const userBlackCopy = structuredClone(userBlack); const userBlackCopy = structuredClone(userBlack);
const userMeCopy = structuredClone(userMe); const userMeCopy = structuredClone(userMe);
const db = new MockRelay(); const relay = new MockRelay();
const store = new UserStore({ relay, userPubkey: userBlackCopy.pubkey });
const store = new UserStore(userBlackCopy.pubkey, db);
await store.event(userBlackCopy); await store.event(userBlackCopy);
await store.event(userMeCopy); await store.event(userMeCopy);

View file

@ -1,43 +1,66 @@
import { NostrEvent, NostrFilter, NStore } from '@nostrify/nostrify'; import { NostrEvent, NostrFilter, NostrRelayCLOSED, NostrRelayEOSE, NostrRelayEVENT, NRelay } from '@nostrify/nostrify';
import { DittoEvent } from '@/interfaces/DittoEvent.ts'; interface UserStoreOpts {
import { getTagSet } from '@/utils/tags.ts'; relay: NRelay;
userPubkey: string;
adminPubkey?: string;
}
export class UserStore implements NStore { export class UserStore implements NRelay {
private promise: Promise<DittoEvent[]> | undefined; constructor(private opts: UserStoreOpts) {}
constructor(private pubkey: string, private store: NStore) {} req(
filters: NostrFilter[],
opts?: { signal?: AbortSignal },
): AsyncIterable<NostrRelayEVENT | NostrRelayEOSE | NostrRelayCLOSED> {
// TODO: support req maybe? It would be inefficient.
return this.opts.relay.req(filters, opts);
}
async event(event: NostrEvent, opts?: { signal?: AbortSignal }): Promise<void> { async event(event: NostrEvent, opts?: { signal?: AbortSignal }): Promise<void> {
return await this.store.event(event, opts); return await this.opts.relay.event(event, opts);
} }
/** /**
* Query events that `pubkey` did not mute * Query events that `pubkey` did not mute
* https://github.com/nostr-protocol/nips/blob/master/51.md#standard-lists * https://github.com/nostr-protocol/nips/blob/master/51.md#standard-lists
*/ */
async query(filters: NostrFilter[], opts: { signal?: AbortSignal; limit?: number } = {}): Promise<DittoEvent[]> { async query(filters: NostrFilter[], opts: { signal?: AbortSignal; limit?: number } = {}): Promise<NostrEvent[]> {
const events = await this.store.query(filters, opts); const { relay, userPubkey, adminPubkey } = this.opts;
const pubkeys = await this.getMutedPubkeys();
const mutes = new Set<string>();
const [muteList] = await this.opts.relay.query([{ authors: [userPubkey], kinds: [10000], limit: 1 }]);
for (const [name, value] of muteList?.tags ?? []) {
if (name === 'p') {
mutes.add(value);
}
}
const events = await relay.query(filters, opts);
const users = adminPubkey
? await relay.query([{
kinds: [30382],
authors: [adminPubkey],
'#d': [...events.map(({ pubkey }) => pubkey)],
}])
: [];
return events.filter((event) => { return events.filter((event) => {
return event.kind === 0 || !pubkeys.has(event.pubkey); const user = users.find((user) => user.tags.find(([name]) => name === 'd')?.[1] === event.pubkey);
for (const [name, value] of user?.tags ?? []) {
if (name === 'n' && value === 'disabled') {
return false;
}
}
return event.kind === 0 || !mutes.has(event.pubkey);
}); });
} }
private async getMuteList(): Promise<DittoEvent | undefined> { close(): Promise<void> {
if (!this.promise) { return this.opts.relay.close();
this.promise = this.store.query([{ authors: [this.pubkey], kinds: [10000], limit: 1 }]);
}
const [muteList] = await this.promise;
return muteList;
}
private async getMutedPubkeys(): Promise<Set<string>> {
const mutedPubkeysEvent = await this.getMuteList();
if (!mutedPubkeysEvent) {
return new Set();
}
return getTagSet(mutedPubkeysEvent.tags, 'p');
} }
} }

View file

@ -35,9 +35,15 @@ export function userMiddleware(opts: { privileged: boolean; required?: boolean }
const header = c.req.header('authorization'); const header = c.req.header('authorization');
if (header) { if (header) {
const { relay, conf } = c.var;
const signer = await getSigner(header, c.var);
const userPubkey = await signer.getPublicKey();
const adminPubkey = await conf.signer.getPublicKey();
const user: User = { const user: User = {
signer: await getSigner(header, c.var), signer,
relay: c.var.relay, // TODO: set user's relay relay: new UserStore({ relay, userPubkey, adminPubkey }),
}; };
c.set('user', user); c.set('user', user);