From 502af2cd487aadb108d083bc4e8509be9d8b69a5 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Wed, 3 May 2023 15:22:24 -0500 Subject: [PATCH] Return first page of statuses in profile --- src/app.ts | 3 ++- src/client.ts | 23 +++++++++++++++++------ src/controllers/api/accounts.ts | 27 ++++++++++++++++++++++++--- 3 files changed, 43 insertions(+), 10 deletions(-) diff --git a/src/app.ts b/src/app.ts index 62a10ec..ca7512d 100644 --- a/src/app.ts +++ b/src/app.ts @@ -4,6 +4,7 @@ import { accountController, accountLookupController, accountSearchController, + accountStatusesController, credentialsController, relationshipsController, } from './controllers/api/accounts.ts'; @@ -45,6 +46,7 @@ app.get('/api/v1/accounts/verify_credentials', requireAuth, credentialsControlle app.get('/api/v1/accounts/search', accountSearchController); app.get('/api/v1/accounts/lookup', accountLookupController); app.get('/api/v1/accounts/relationships', relationshipsController); +app.get('/api/v1/accounts/:pubkey{[0-9a-f]{64}}/statuses', accountStatusesController); app.get('/api/v1/accounts/:pubkey{[0-9a-f]{64}}', accountController); app.get('/api/v1/statuses/:id{[0-9a-f]{64}}/context', contextController); @@ -55,7 +57,6 @@ app.get('/api/v1/timelines/home', requireAuth, homeController); // Not (yet) implemented. app.get('/api/v1/notifications', emptyArrayController); -app.get('/api/v1/accounts/:id/statuses', emptyArrayController); app.get('/api/v1/bookmarks', emptyArrayController); app.get('/api/v1/custom_emojis', emptyArrayController); app.get('/api/v1/accounts/search', emptyArrayController); diff --git a/src/client.ts b/src/client.ts index db85f92..ba19f44 100644 --- a/src/client.ts +++ b/src/client.ts @@ -1,4 +1,4 @@ -import { Author, type Filter, findReplyTag, matchFilter, RelayPool } from '@/deps.ts'; +import { Author, findReplyTag, matchFilter, RelayPool } from '@/deps.ts'; import { type Event, type SignedEvent } from '@/event.ts'; import { poolRelays } from './config.ts'; @@ -7,12 +7,23 @@ import { eventDateComparator, nostrNow } from './utils.ts'; const pool = new RelayPool(poolRelays); +type Filter = { + ids?: string[]; + kinds?: K[]; + authors?: string[]; + since?: number; + until?: number; + limit?: number; + search?: string; + [key: `#${string}`]: string[]; +}; + interface GetFilterOpts { timeout?: number; } /** Get events from a NIP-01 filter. */ -function getFilter(filter: Filter, opts: GetFilterOpts = {}): Promise { +function getFilter(filter: Filter, opts: GetFilterOpts = {}): Promise[]> { return new Promise((resolve) => { let tid: number; const results: SignedEvent[] = []; @@ -27,21 +38,21 @@ function getFilter(filter: Filter, opts: GetFilterOpts = {}): Promise= filter.limit) { unsub(); clearTimeout(tid); - resolve(results); + resolve(results as SignedEvent[]); } }, undefined, () => { unsub(); clearTimeout(tid); - resolve(results); + resolve(results as SignedEvent[]); }, ); if (typeof opts.timeout === 'number') { tid = setTimeout(() => { unsub(); - resolve(results); + resolve(results as SignedEvent[]); }, opts.timeout); } }); @@ -121,4 +132,4 @@ function getDescendants(eventId: string): Promise[]> { return getFilter({ kinds: [1], '#e': [eventId], limit: 200 }, { timeout: 2000 }) as Promise[]>; } -export { getAncestors, getAuthor, getDescendants, getEvent, getFeed, getFollows, pool }; +export { getAncestors, getAuthor, getDescendants, getEvent, getFeed, getFilter, getFollows, pool }; diff --git a/src/controllers/api/accounts.ts b/src/controllers/api/accounts.ts index ddf2175..cf0556a 100644 --- a/src/controllers/api/accounts.ts +++ b/src/controllers/api/accounts.ts @@ -1,7 +1,7 @@ import { type AppController } from '@/app.ts'; -import { nip05 } from '@/deps.ts'; -import { getAuthor } from '@/client.ts'; -import { toAccount } from '@/transmute.ts'; +import { nip05, z } from '@/deps.ts'; +import { getAuthor, getFilter } from '@/client.ts'; +import { toAccount, toStatus } from '@/transmute.ts'; import { bech32ToPubkey } from '@/utils.ts'; import type { Event } from '@/event.ts'; @@ -83,6 +83,26 @@ const relationshipsController: AppController = (c) => { return c.json(result); }; +const accountStatusesQuerySchema = z.object({ + pinned: z.coerce.boolean(), + limit: z.coerce.number().positive().transform((v) => Math.min(v, 40)).catch(20), +}); + +const accountStatusesController: AppController = async (c) => { + const pubkey = c.req.param('pubkey'); + const { pinned, limit } = accountStatusesQuerySchema.parse(c.req.query()); + + // Nostr doesn't support pinned statuses. + if (pinned) { + return c.json([]); + } + + const events = await getFilter({ authors: [pubkey], kinds: [1], limit }); + const statuses = await Promise.all(events.map((event) => toStatus(event))); + + return c.json(statuses); +}; + /** Resolve a bech32 or NIP-05 identifier to an account. */ async function lookupAccount(value: string): Promise | undefined> { console.log(`Looking up ${value}`); @@ -98,6 +118,7 @@ export { accountController, accountLookupController, accountSearchController, + accountStatusesController, credentialsController, relationshipsController, };