From 335f7dc2814d78a3b7e7816e7c7f156fb8e66979 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Sun, 31 Dec 2023 14:05:04 -0600 Subject: [PATCH 1/8] Add tags module --- src/tags.test.ts | 25 +++++++++++++++++++++++++ src/tags.ts | 17 ++++++++++++++++- 2 files changed, 41 insertions(+), 1 deletion(-) create mode 100644 src/tags.test.ts diff --git a/src/tags.test.ts b/src/tags.test.ts new file mode 100644 index 00000000..9c7e938a --- /dev/null +++ b/src/tags.test.ts @@ -0,0 +1,25 @@ +import { assertEquals } from '@/deps-test.ts'; + +import { deleteTag, getTagSet, setTag } from './tags.ts'; + +Deno.test('getTagSet', () => { + assertEquals(getTagSet([], 'p'), new Set()); + assertEquals(getTagSet([['p', '123']], 'p'), new Set(['123'])); + assertEquals(getTagSet([['p', '123'], ['p', '456']], 'p'), new Set(['123', '456'])); + assertEquals(getTagSet([['p', '123'], ['p', '456'], ['q', '789']], 'p'), new Set(['123', '456'])); +}); + +Deno.test('setTag', () => { + assertEquals(setTag([], ['p', '123']), [['p', '123']]); + assertEquals(setTag([['p', '123']], ['p', '123']), [['p', '123']]); + assertEquals(setTag([['p', '123'], ['p', '456']], ['p', '123']), [['p', '123'], ['p', '456']]); + assertEquals(setTag([['p', '123'], ['p', '456']], ['p', '789']), [['p', '123'], ['p', '456'], ['p', '789']]); +}); + +Deno.test('deleteTag', () => { + assertEquals(deleteTag([], ['p', '123']), []); + assertEquals(deleteTag([['p', '123']], ['p', '123']), []); + assertEquals(deleteTag([['p', '123']], ['p', '456']), [['p', '123']]); + assertEquals(deleteTag([['p', '123'], ['p', '123']], ['p', '123']), []); + assertEquals(deleteTag([['p', '123'], ['p', '456']], ['p', '456']), [['p', '123']]); +}); diff --git a/src/tags.ts b/src/tags.ts index 98efc7d2..a55560c3 100644 --- a/src/tags.ts +++ b/src/tags.ts @@ -11,4 +11,19 @@ function getTagSet(tags: string[][], tagName: string): Set { return set; } -export { getTagSet }; +/** Delete all occurences of the tag by its name/value pair. */ +function deleteTag(tags: readonly string[][], tag: string[]): string[][] { + return tags.filter(([name, value]) => !(name === tag[0] && value === tag[1])); +} + +/** Add a tag to the list, replacing the name/value pair if it already exists. */ +function setTag(tags: readonly string[][], tag: string[]): string[][] { + const tagIndex = tags.findIndex(([name, value]) => name === tag[0] && value === tag[1]); + if (tagIndex === -1) { + return [...tags, tag]; + } else { + return [...tags.slice(0, tagIndex), tag, ...tags.slice(tagIndex + 1)]; + } +} + +export { deleteTag, getTagSet, setTag }; From dc27ee05d4b6431e94bdcf0adb49cc17385501ec Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Sun, 31 Dec 2023 20:13:30 -0600 Subject: [PATCH 2/8] Create `updateListEvent` helper function --- src/controllers/api/accounts.ts | 17 ++++++++--------- src/tags.ts | 7 ++++++- src/utils.ts | 5 ++--- src/utils/web.ts | 22 +++++++++++++++++++--- 4 files changed, 35 insertions(+), 16 deletions(-) diff --git a/src/controllers/api/accounts.ts b/src/controllers/api/accounts.ts index 5e345ae4..412fa530 100644 --- a/src/controllers/api/accounts.ts +++ b/src/controllers/api/accounts.ts @@ -7,9 +7,10 @@ import { type DittoFilter } from '@/filter.ts'; import { getAuthor, getFollowedPubkeys, getFollows } from '@/queries.ts'; import { booleanParamSchema, fileSchema } from '@/schema.ts'; import { jsonMetaContentSchema } from '@/schemas/nostr.ts'; +import { setTag } from '@/tags.ts'; import { uploadFile } from '@/upload.ts'; import { isFollowing, lookupAccount, nostrNow } from '@/utils.ts'; -import { paginated, paginationSchema, parseBody } from '@/utils/web.ts'; +import { paginated, paginationSchema, parseBody, updateListEvent } from '@/utils/web.ts'; import { createEvent } from '@/utils/web.ts'; import { renderEventAccounts } from '@/views.ts'; import { accountFromPubkey, renderAccount } from '@/views/mastodon/accounts.ts'; @@ -219,14 +220,12 @@ const followController: AppController = async (c) => { const source = await getFollows(sourcePubkey); if (!source || !isFollowing(source, targetPubkey)) { - await createEvent({ - kind: 3, - content: '', - tags: [ - ...(source?.tags ?? []), - ['p', targetPubkey], - ], - }, c); + await updateListEvent( + source ?? { kind: 3 }, + ['p', targetPubkey], + setTag, + c, + ); } const relationship = await renderRelationship(sourcePubkey, targetPubkey); diff --git a/src/tags.ts b/src/tags.ts index a55560c3..4909b3ff 100644 --- a/src/tags.ts +++ b/src/tags.ts @@ -11,6 +11,11 @@ function getTagSet(tags: string[][], tagName: string): Set { return set; } +/** Check if the tag exists by its name and value. */ +function hasTag(tags: string[][], tag: string[]): boolean { + return tags.some(([name, value]) => name === tag[0] && value === tag[1]); +} + /** Delete all occurences of the tag by its name/value pair. */ function deleteTag(tags: readonly string[][], tag: string[]): string[][] { return tags.filter(([name, value]) => !(name === tag[0] && value === tag[1])); @@ -26,4 +31,4 @@ function setTag(tags: readonly string[][], tag: string[]): string[][] { } } -export { deleteTag, getTagSet, setTag }; +export { deleteTag, getTagSet, hasTag, setTag }; diff --git a/src/utils.ts b/src/utils.ts index 1f10cd3b..27c61364 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,5 +1,6 @@ import { type Event, type EventTemplate, getEventHash, nip19, z } from '@/deps.ts'; import { getAuthor } from '@/queries.ts'; +import { hasTag } from '@/tags.ts'; import { lookupNip05Cached } from '@/utils/nip05.ts'; import { nostrIdSchema } from '@/schemas/nostr.ts'; @@ -97,9 +98,7 @@ const isRelay = (relay: string): relay is `wss://${string}` => relaySchema.safeP /** Check whether source is following target. */ function isFollowing(source: Event<3>, targetPubkey: string): boolean { - return Boolean( - source.tags.find(([tagName, tagValue]) => tagName === 'p' && tagValue === targetPubkey), - ); + return hasTag(source.tags, ['p', targetPubkey]); } /** Deduplicate events by ID. */ diff --git a/src/utils/web.ts b/src/utils/web.ts index b02209f1..0e559c63 100644 --- a/src/utils/web.ts +++ b/src/utils/web.ts @@ -1,13 +1,12 @@ +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 * as pipeline from '@/pipeline.ts'; import { signAdminEvent, signEvent } from '@/sign.ts'; import { nostrNow } from '@/utils.ts'; -import type { AppContext } from '@/app.ts'; - /** EventTemplate with defaults. */ -type EventStub = TypeFest.SetOptional, 'created_at' | 'tags'>; +type EventStub = TypeFest.SetOptional, 'content' | 'created_at' | 'tags'>; /** Publish an event through the pipeline. */ async function createEvent(t: EventStub, c: AppContext): Promise> { @@ -18,6 +17,7 @@ async function createEvent(t: EventStub, c: AppContext): Pr } const event = await signEvent({ + content: '', created_at: nostrNow(), tags: [], ...t, @@ -26,9 +26,24 @@ 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, + ); +} + /** Publish an admin event through the pipeline. */ async function createAdminEvent(t: EventStub, c: AppContext): Promise> { const event = await signAdminEvent({ + content: '', created_at: nostrNow(), tags: [], ...t, @@ -139,4 +154,5 @@ export { type PaginationParams, paginationSchema, parseBody, + updateListEvent, }; From f665c5f825b126c64e0bd7a0978c68c68702e77c Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Sun, 31 Dec 2023 20:30:41 -0600 Subject: [PATCH 3/8] Remove isFollowing util --- src/controllers/api/accounts.ts | 14 +++++--------- src/utils.ts | 7 ------- src/views/mastodon/relationships.ts | 6 +++--- 3 files changed, 8 insertions(+), 19 deletions(-) diff --git a/src/controllers/api/accounts.ts b/src/controllers/api/accounts.ts index 412fa530..d10509e5 100644 --- a/src/controllers/api/accounts.ts +++ b/src/controllers/api/accounts.ts @@ -7,9 +7,9 @@ import { type DittoFilter } from '@/filter.ts'; import { getAuthor, getFollowedPubkeys, getFollows } from '@/queries.ts'; import { booleanParamSchema, fileSchema } from '@/schema.ts'; import { jsonMetaContentSchema } from '@/schemas/nostr.ts'; -import { setTag } from '@/tags.ts'; +import { hasTag, setTag } from '@/tags.ts'; import { uploadFile } from '@/upload.ts'; -import { isFollowing, lookupAccount, nostrNow } from '@/utils.ts'; +import { lookupAccount, nostrNow } from '@/utils.ts'; import { paginated, paginationSchema, parseBody, updateListEvent } from '@/utils/web.ts'; import { createEvent } from '@/utils/web.ts'; import { renderEventAccounts } from '@/views.ts'; @@ -218,14 +218,10 @@ const followController: AppController = async (c) => { const targetPubkey = c.req.param('pubkey'); const source = await getFollows(sourcePubkey); + const tag = ['p', targetPubkey]; - if (!source || !isFollowing(source, targetPubkey)) { - await updateListEvent( - source ?? { kind: 3 }, - ['p', targetPubkey], - setTag, - c, - ); + if (!source || !hasTag(source.tags, tag)) { + await updateListEvent(source ?? { kind: 3 }, tag, setTag, c); } const relationship = await renderRelationship(sourcePubkey, targetPubkey); diff --git a/src/utils.ts b/src/utils.ts index 27c61364..1294e007 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,6 +1,5 @@ import { type Event, type EventTemplate, getEventHash, nip19, z } from '@/deps.ts'; import { getAuthor } from '@/queries.ts'; -import { hasTag } from '@/tags.ts'; import { lookupNip05Cached } from '@/utils/nip05.ts'; import { nostrIdSchema } from '@/schemas/nostr.ts'; @@ -96,11 +95,6 @@ const relaySchema = z.string().max(255).startsWith('wss://').url(); /** Check whether the value is a valid relay URL. */ const isRelay = (relay: string): relay is `wss://${string}` => relaySchema.safeParse(relay).success; -/** Check whether source is following target. */ -function isFollowing(source: Event<3>, targetPubkey: string): boolean { - return hasTag(source.tags, ['p', targetPubkey]); -} - /** Deduplicate events by ID. */ function dedupeEvents(events: Event[]): Event[] { return [...new Map(events.map((event) => [event.id, event])).values()]; @@ -155,7 +149,6 @@ export { eventDateComparator, eventMatchesTemplate, findTag, - isFollowing, isNostrId, isRelay, isURL, diff --git a/src/views/mastodon/relationships.ts b/src/views/mastodon/relationships.ts index 91d33ea7..57a17df8 100644 --- a/src/views/mastodon/relationships.ts +++ b/src/views/mastodon/relationships.ts @@ -1,5 +1,5 @@ import { getFollows } from '@/queries.ts'; -import { isFollowing } from '@/utils.ts'; +import { hasTag } from '@/tags.ts'; async function renderRelationship(sourcePubkey: string, targetPubkey: string) { const [source, target] = await Promise.all([ @@ -9,10 +9,10 @@ async function renderRelationship(sourcePubkey: string, targetPubkey: string) { return { id: targetPubkey, - following: source ? isFollowing(source, targetPubkey) : false, + following: source ? hasTag(source.tags, ['p', targetPubkey]) : false, showing_reblogs: true, notifying: false, - followed_by: target ? isFollowing(target, sourcePubkey) : false, + followed_by: target ? hasTag(target.tags, ['p', sourcePubkey]) : false, blocking: false, blocked_by: false, muting: false, From e341ec7b36d062113b89d2409e4ab6316f00d906 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Sun, 31 Dec 2023 21:01:30 -0600 Subject: [PATCH 4/8] db/events: respect the signal --- src/db/events.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/db/events.ts b/src/db/events.ts index a20c6509..79f670f0 100644 --- a/src/db/events.ts +++ b/src/db/events.ts @@ -257,6 +257,7 @@ async function getEvents( filters: DittoFilter[], opts: GetEventsOpts = {}, ): Promise[]> { + if (opts.signal?.aborted) return Promise.resolve([]); if (!filters.length) return Promise.resolve([]); debug('REQ', JSON.stringify(filters)); let query = getEventsQuery(filters); From 8023cfa7b24f5f241d9fffb804f365a199b5fbfc Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Sun, 31 Dec 2023 22:01:57 -0600 Subject: [PATCH 5/8] 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 d10509e5..0e0ee16d 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 92500d36..95020d0e 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 0e559c63..e5437d2d 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, }; From 63fb934220525bf6748aa90b44ef6ce64ebe7ebc Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Sun, 31 Dec 2023 22:04:11 -0600 Subject: [PATCH 6/8] setTag -> addTag --- src/controllers/api/accounts.ts | 4 ++-- src/tags.test.ts | 12 ++++++------ src/tags.ts | 4 ++-- src/utils/web.ts | 2 +- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/controllers/api/accounts.ts b/src/controllers/api/accounts.ts index 0e0ee16d..5609fbef 100644 --- a/src/controllers/api/accounts.ts +++ b/src/controllers/api/accounts.ts @@ -7,7 +7,7 @@ import { type DittoFilter } from '@/filter.ts'; import { getAuthor, getFollowedPubkeys } from '@/queries.ts'; import { booleanParamSchema, fileSchema } from '@/schema.ts'; import { jsonMetaContentSchema } from '@/schemas/nostr.ts'; -import { setTag } from '@/tags.ts'; +import { addTag } from '@/tags.ts'; import { uploadFile } from '@/upload.ts'; import { lookupAccount, nostrNow } from '@/utils.ts'; import { paginated, paginationSchema, parseBody, updateListEvent } from '@/utils/web.ts'; @@ -219,7 +219,7 @@ const followController: AppController = async (c) => { await updateListEvent( { kinds: [3], authors: [sourcePubkey] }, - (tags) => setTag(tags, ['p', targetPubkey]), + (tags) => addTag(tags, ['p', targetPubkey]), c, ); diff --git a/src/tags.test.ts b/src/tags.test.ts index 9c7e938a..c4d32143 100644 --- a/src/tags.test.ts +++ b/src/tags.test.ts @@ -1,6 +1,6 @@ import { assertEquals } from '@/deps-test.ts'; -import { deleteTag, getTagSet, setTag } from './tags.ts'; +import { addTag, deleteTag, getTagSet } from './tags.ts'; Deno.test('getTagSet', () => { assertEquals(getTagSet([], 'p'), new Set()); @@ -9,11 +9,11 @@ Deno.test('getTagSet', () => { assertEquals(getTagSet([['p', '123'], ['p', '456'], ['q', '789']], 'p'), new Set(['123', '456'])); }); -Deno.test('setTag', () => { - assertEquals(setTag([], ['p', '123']), [['p', '123']]); - assertEquals(setTag([['p', '123']], ['p', '123']), [['p', '123']]); - assertEquals(setTag([['p', '123'], ['p', '456']], ['p', '123']), [['p', '123'], ['p', '456']]); - assertEquals(setTag([['p', '123'], ['p', '456']], ['p', '789']), [['p', '123'], ['p', '456'], ['p', '789']]); +Deno.test('addTag', () => { + assertEquals(addTag([], ['p', '123']), [['p', '123']]); + assertEquals(addTag([['p', '123']], ['p', '123']), [['p', '123']]); + assertEquals(addTag([['p', '123'], ['p', '456']], ['p', '123']), [['p', '123'], ['p', '456']]); + assertEquals(addTag([['p', '123'], ['p', '456']], ['p', '789']), [['p', '123'], ['p', '456'], ['p', '789']]); }); Deno.test('deleteTag', () => { diff --git a/src/tags.ts b/src/tags.ts index 4909b3ff..60278080 100644 --- a/src/tags.ts +++ b/src/tags.ts @@ -22,7 +22,7 @@ function deleteTag(tags: readonly string[][], tag: string[]): string[][] { } /** Add a tag to the list, replacing the name/value pair if it already exists. */ -function setTag(tags: readonly string[][], tag: string[]): string[][] { +function addTag(tags: readonly string[][], tag: string[]): string[][] { const tagIndex = tags.findIndex(([name, value]) => name === tag[0] && value === tag[1]); if (tagIndex === -1) { return [...tags, tag]; @@ -31,4 +31,4 @@ function setTag(tags: readonly string[][], tag: string[]): string[][] { } } -export { deleteTag, getTagSet, hasTag, setTag }; +export { addTag, deleteTag, getTagSet, hasTag }; diff --git a/src/utils/web.ts b/src/utils/web.ts index e5437d2d..3687a652 100644 --- a/src/utils/web.ts +++ b/src/utils/web.ts @@ -53,7 +53,7 @@ async function updateEvent>( } /** Fetch existing event, update its tags, then publish the new event. */ -function updateListEvent>( +function updateListEvent( filter: UpdateEventFilter, fn: (tags: string[][]) => string[][], c: AppContext, From 6d1375ba5908399406b826e66aa9d279342816a6 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Sun, 31 Dec 2023 22:56:21 -0600 Subject: [PATCH 7/8] Optimize the relationships controller, support block relationships --- src/views/mastodon/relationships.ts | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/views/mastodon/relationships.ts b/src/views/mastodon/relationships.ts index 57a17df8..97fa0f13 100644 --- a/src/views/mastodon/relationships.ts +++ b/src/views/mastodon/relationships.ts @@ -1,20 +1,22 @@ -import { getFollows } from '@/queries.ts'; +import { eventsDB } from '@/db/events.ts'; import { hasTag } from '@/tags.ts'; async function renderRelationship(sourcePubkey: string, targetPubkey: string) { - const [source, target] = await Promise.all([ - getFollows(sourcePubkey), - getFollows(targetPubkey), + const [event3, target3, event10000, target10000] = await eventsDB.getEvents([ + { kinds: [3], authors: [sourcePubkey], limit: 1 }, + { kinds: [3], authors: [targetPubkey], limit: 1 }, + { kinds: [10000], authors: [sourcePubkey], limit: 1 }, + { kinds: [10000], authors: [targetPubkey], limit: 1 }, ]); return { id: targetPubkey, - following: source ? hasTag(source.tags, ['p', targetPubkey]) : false, + following: event3 ? hasTag(event3.tags, ['p', targetPubkey]) : false, showing_reblogs: true, notifying: false, - followed_by: target ? hasTag(target.tags, ['p', sourcePubkey]) : false, - blocking: false, - blocked_by: false, + followed_by: target3 ? hasTag(target3?.tags, ['p', sourcePubkey]) : false, + blocking: event10000 ? hasTag(target10000.tags, ['p', targetPubkey]) : false, + blocked_by: target10000 ? hasTag(target10000.tags, ['p', sourcePubkey]) : false, muting: false, muting_notifications: false, requested: false, From 84eb4cec2e74240e11fef7f4d96df392d8117812 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Sun, 31 Dec 2023 23:35:37 -0600 Subject: [PATCH 8/8] Add blockController, fix bugs --- src/app.ts | 2 ++ src/client.ts | 1 + src/controllers/api/accounts.ts | 15 +++++++++++++++ src/utils/web.ts | 2 +- src/views/mastodon/relationships.ts | 2 +- 5 files changed, 20 insertions(+), 2 deletions(-) diff --git a/src/app.ts b/src/app.ts index c3212f8f..e637cb4f 100644 --- a/src/app.ts +++ b/src/app.ts @@ -23,6 +23,7 @@ import { accountLookupController, accountSearchController, accountStatusesController, + blockController, createAccountController, favouritesController, followController, @@ -135,6 +136,7 @@ app.patch( app.get('/api/v1/accounts/search', accountSearchController); app.get('/api/v1/accounts/lookup', accountLookupController); app.get('/api/v1/accounts/relationships', relationshipsController); +app.post('/api/v1/accounts/:pubkey{[0-9a-f]{64}}/block', blockController); app.post('/api/v1/accounts/:pubkey{[0-9a-f]{64}}/follow', followController); app.get('/api/v1/accounts/:pubkey{[0-9a-f]{64}}/followers', followersController); app.get('/api/v1/accounts/:pubkey{[0-9a-f]{64}}/following', followingController); diff --git a/src/client.ts b/src/client.ts index fbcfaa5f..511cbc75 100644 --- a/src/client.ts +++ b/src/client.ts @@ -52,6 +52,7 @@ function getEvents(filters: Filter[], opts: GetEventsOpts = /** Publish an event to the given relays, or the entire pool. */ function storeEvent(event: Event, opts: StoreEventOpts = {}): Promise { const { relays = activeRelays } = opts; + const debug = Debug('ditto:client:publish'); debug('EVENT', event); pool.publish(event, relays); return Promise.resolve(); diff --git a/src/controllers/api/accounts.ts b/src/controllers/api/accounts.ts index 5609fbef..9e1acc80 100644 --- a/src/controllers/api/accounts.ts +++ b/src/controllers/api/accounts.ts @@ -246,6 +246,20 @@ const followingController: AppController = async (c) => { return c.json(accounts.filter(Boolean)); }; +const blockController: AppController = async (c) => { + const sourcePubkey = c.get('pubkey')!; + const targetPubkey = c.req.param('pubkey'); + + await updateListEvent( + { kinds: [10000], authors: [sourcePubkey] }, + (tags) => addTag(tags, ['p', targetPubkey]), + c, + ); + + const relationship = await renderRelationship(sourcePubkey, targetPubkey); + return c.json(relationship); +}; + const favouritesController: AppController = async (c) => { const pubkey = c.get('pubkey')!; const params = paginationSchema.parse(c.req.query()); @@ -275,6 +289,7 @@ export { accountLookupController, accountSearchController, accountStatusesController, + blockController, createAccountController, favouritesController, followController, diff --git a/src/utils/web.ts b/src/utils/web.ts index 3687a652..1e7be076 100644 --- a/src/utils/web.ts +++ b/src/utils/web.ts @@ -60,7 +60,7 @@ function updateListEvent( ): Promise> { return updateEvent(filter, (prev) => ({ kind: filter.kinds[0], - content: prev?.content, + content: prev?.content ?? '', tags: fn(prev?.tags ?? []), }), c); } diff --git a/src/views/mastodon/relationships.ts b/src/views/mastodon/relationships.ts index 97fa0f13..e5ce2807 100644 --- a/src/views/mastodon/relationships.ts +++ b/src/views/mastodon/relationships.ts @@ -15,7 +15,7 @@ async function renderRelationship(sourcePubkey: string, targetPubkey: string) { showing_reblogs: true, notifying: false, followed_by: target3 ? hasTag(target3?.tags, ['p', sourcePubkey]) : false, - blocking: event10000 ? hasTag(target10000.tags, ['p', targetPubkey]) : false, + blocking: event10000 ? hasTag(event10000.tags, ['p', targetPubkey]) : false, blocked_by: target10000 ? hasTag(target10000.tags, ['p', sourcePubkey]) : false, muting: false, muting_notifications: false,