From 8023cfa7b24f5f241d9fffb804f365a199b5fbfc Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Sun, 31 Dec 2023 22:01:57 -0600 Subject: [PATCH] Use a cleaner API for updating lists --- src/controllers/api/accounts.ts | 15 +++++----- src/queries.ts | 2 +- src/utils/web.ts | 51 ++++++++++++++++++++++++--------- 3 files changed, 46 insertions(+), 22 deletions(-) diff --git a/src/controllers/api/accounts.ts b/src/controllers/api/accounts.ts index d10509e..0e0ee16 100644 --- a/src/controllers/api/accounts.ts +++ b/src/controllers/api/accounts.ts @@ -4,10 +4,10 @@ import { eventsDB } from '@/db/events.ts'; import { insertUser } from '@/db/users.ts'; import { findReplyTag, nip19, z } from '@/deps.ts'; import { type DittoFilter } from '@/filter.ts'; -import { getAuthor, getFollowedPubkeys, getFollows } from '@/queries.ts'; +import { getAuthor, getFollowedPubkeys } from '@/queries.ts'; import { booleanParamSchema, fileSchema } from '@/schema.ts'; import { jsonMetaContentSchema } from '@/schemas/nostr.ts'; -import { hasTag, setTag } from '@/tags.ts'; +import { setTag } from '@/tags.ts'; import { uploadFile } from '@/upload.ts'; import { lookupAccount, nostrNow } from '@/utils.ts'; import { paginated, paginationSchema, parseBody, updateListEvent } from '@/utils/web.ts'; @@ -217,12 +217,11 @@ const followController: AppController = async (c) => { const sourcePubkey = c.get('pubkey')!; const targetPubkey = c.req.param('pubkey'); - const source = await getFollows(sourcePubkey); - const tag = ['p', targetPubkey]; - - if (!source || !hasTag(source.tags, tag)) { - await updateListEvent(source ?? { kind: 3 }, tag, setTag, c); - } + await updateListEvent( + { kinds: [3], authors: [sourcePubkey] }, + (tags) => setTag(tags, ['p', targetPubkey]), + c, + ); const relationship = await renderRelationship(sourcePubkey, targetPubkey); return c.json(relationship); diff --git a/src/queries.ts b/src/queries.ts index 92500d3..95020d0 100644 --- a/src/queries.ts +++ b/src/queries.ts @@ -78,7 +78,7 @@ const getAuthor = async (pubkey: string, opts: GetEventOpts<0> = {}): Promise | undefined> => { +const getFollows = async (pubkey: string, signal?: AbortSignal): Promise | undefined> => { const [event] = await eventsDB.getEvents([{ authors: [pubkey], kinds: [3], limit: 1 }], { limit: 1, signal }); return event; }; diff --git a/src/utils/web.ts b/src/utils/web.ts index 0e559c6..e5437d2 100644 --- a/src/utils/web.ts +++ b/src/utils/web.ts @@ -1,9 +1,19 @@ import { type AppContext } from '@/app.ts'; import { Conf } from '@/config.ts'; -import { type Context, type Event, EventTemplate, HTTPException, parseFormData, type TypeFest, z } from '@/deps.ts'; +import { + type Context, + type Event, + EventTemplate, + Filter, + HTTPException, + parseFormData, + type TypeFest, + z, +} from '@/deps.ts'; import * as pipeline from '@/pipeline.ts'; import { signAdminEvent, signEvent } from '@/sign.ts'; import { nostrNow } from '@/utils.ts'; +import { eventsDB } from '@/db/events.ts'; /** EventTemplate with defaults. */ type EventStub = TypeFest.SetOptional, 'content' | 'created_at' | 'tags'>; @@ -26,20 +36,34 @@ async function createEvent(t: EventStub, c: AppContext): Pr return publishEvent(event, c); } -/** Add the tag to the list and then publish the new list, or throw if the tag already exists. */ -function updateListEvent>( - t: E, - tag: string[], - fn: (tags: string[][], tag: string[]) => string[][], - c: AppContext, -): Promise> { - const { kind, content, tags = [] } = t; - return createEvent( - { kind, content, tags: fn(tags, tag) }, - c, - ); +/** Filter for fetching an existing event to update. */ +interface UpdateEventFilter extends Filter { + kinds: [K]; + limit?: 1; } +/** Fetch existing event, update it, then publish the new event. */ +async function updateEvent>( + filter: UpdateEventFilter, + fn: (prev: Event | undefined) => E, + c: AppContext, +): Promise> { + const [prev] = await eventsDB.getEvents([filter], { limit: 1 }); + return createEvent(fn(prev), c); +} + +/** Fetch existing event, update its tags, then publish the new event. */ +function updateListEvent>( + filter: UpdateEventFilter, + fn: (tags: string[][]) => string[][], + c: AppContext, +): Promise> { + return updateEvent(filter, (prev) => ({ + kind: filter.kinds[0], + content: prev?.content, + tags: fn(prev?.tags ?? []), + }), c); +} /** Publish an admin event through the pipeline. */ async function createAdminEvent(t: EventStub, c: AppContext): Promise> { const event = await signAdminEvent({ @@ -154,5 +178,6 @@ export { type PaginationParams, paginationSchema, parseBody, + updateEvent, updateListEvent, };