Use a cleaner API for updating lists

This commit is contained in:
Alex Gleason 2023-12-31 22:01:57 -06:00
parent e341ec7b36
commit 8023cfa7b2
No known key found for this signature in database
GPG Key ID: 7211D1F99744FBB7
3 changed files with 46 additions and 22 deletions

View File

@ -4,10 +4,10 @@ import { eventsDB } from '@/db/events.ts';
import { insertUser } from '@/db/users.ts'; import { insertUser } from '@/db/users.ts';
import { findReplyTag, nip19, z } from '@/deps.ts'; import { findReplyTag, nip19, z } from '@/deps.ts';
import { type DittoFilter } from '@/filter.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 { booleanParamSchema, fileSchema } from '@/schema.ts';
import { jsonMetaContentSchema } from '@/schemas/nostr.ts'; import { jsonMetaContentSchema } from '@/schemas/nostr.ts';
import { hasTag, setTag } from '@/tags.ts'; import { setTag } from '@/tags.ts';
import { uploadFile } from '@/upload.ts'; import { uploadFile } from '@/upload.ts';
import { lookupAccount, nostrNow } from '@/utils.ts'; import { lookupAccount, nostrNow } from '@/utils.ts';
import { paginated, paginationSchema, parseBody, updateListEvent } from '@/utils/web.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 sourcePubkey = c.get('pubkey')!;
const targetPubkey = c.req.param('pubkey'); const targetPubkey = c.req.param('pubkey');
const source = await getFollows(sourcePubkey); await updateListEvent(
const tag = ['p', targetPubkey]; { kinds: [3], authors: [sourcePubkey] },
(tags) => setTag(tags, ['p', targetPubkey]),
if (!source || !hasTag(source.tags, tag)) { c,
await updateListEvent(source ?? { kind: 3 }, tag, setTag, c); );
}
const relationship = await renderRelationship(sourcePubkey, targetPubkey); const relationship = await renderRelationship(sourcePubkey, targetPubkey);
return c.json(relationship); return c.json(relationship);

View File

@ -78,7 +78,7 @@ const getAuthor = async (pubkey: string, opts: GetEventOpts<0> = {}): Promise<Ev
}; };
/** Get users the given pubkey follows. */ /** Get users the given pubkey follows. */
const getFollows = async (pubkey: string, signal = AbortSignal.timeout(1000)): Promise<Event<3> | undefined> => { const getFollows = async (pubkey: string, signal?: AbortSignal): Promise<Event<3> | undefined> => {
const [event] = await eventsDB.getEvents([{ authors: [pubkey], kinds: [3], limit: 1 }], { limit: 1, signal }); const [event] = await eventsDB.getEvents([{ authors: [pubkey], kinds: [3], limit: 1 }], { limit: 1, signal });
return event; return event;
}; };

View File

@ -1,9 +1,19 @@
import { type AppContext } from '@/app.ts'; import { type AppContext } from '@/app.ts';
import { Conf } from '@/config.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 * as pipeline from '@/pipeline.ts';
import { signAdminEvent, signEvent } from '@/sign.ts'; import { signAdminEvent, signEvent } from '@/sign.ts';
import { nostrNow } from '@/utils.ts'; import { nostrNow } from '@/utils.ts';
import { eventsDB } from '@/db/events.ts';
/** EventTemplate with defaults. */ /** EventTemplate with defaults. */
type EventStub<K extends number = number> = TypeFest.SetOptional<EventTemplate<K>, 'content' | 'created_at' | 'tags'>; type EventStub<K extends number = number> = TypeFest.SetOptional<EventTemplate<K>, 'content' | 'created_at' | 'tags'>;
@ -26,20 +36,34 @@ async function createEvent<K extends number>(t: EventStub<K>, c: AppContext): Pr
return publishEvent(event, c); return publishEvent(event, c);
} }
/** Add the tag to the list and then publish the new list, or throw if the tag already exists. */ /** Filter for fetching an existing event to update. */
function updateListEvent<K extends number, E extends EventStub<K>>( interface UpdateEventFilter<K extends number> extends Filter<K> {
t: E, kinds: [K];
tag: string[], limit?: 1;
fn: (tags: string[][], tag: string[]) => string[][],
c: AppContext,
): Promise<Event<K>> {
const { kind, content, tags = [] } = t;
return createEvent(
{ kind, content, tags: fn(tags, tag) },
c,
);
} }
/** Fetch existing event, update it, then publish the new event. */
async function updateEvent<K extends number, E extends EventStub<K>>(
filter: UpdateEventFilter<K>,
fn: (prev: Event<K> | undefined) => E,
c: AppContext,
): Promise<Event<K>> {
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<K extends number, E extends EventStub<K>>(
filter: UpdateEventFilter<K>,
fn: (tags: string[][]) => string[][],
c: AppContext,
): Promise<Event<K>> {
return updateEvent(filter, (prev) => ({
kind: filter.kinds[0],
content: prev?.content,
tags: fn(prev?.tags ?? []),
}), c);
}
/** Publish an admin event through the pipeline. */ /** Publish an admin event through the pipeline. */
async function createAdminEvent<K extends number>(t: EventStub<K>, c: AppContext): Promise<Event<K>> { async function createAdminEvent<K extends number>(t: EventStub<K>, c: AppContext): Promise<Event<K>> {
const event = await signAdminEvent({ const event = await signAdminEvent({
@ -154,5 +178,6 @@ export {
type PaginationParams, type PaginationParams,
paginationSchema, paginationSchema,
parseBody, parseBody,
updateEvent,
updateListEvent, updateListEvent,
}; };