Merge branch 'paginated-list' into 'main'
Paginate list events (kind 3, 10000) See merge request soapbox-pub/ditto!343
This commit is contained in:
commit
97d629cf07
|
@ -16,7 +16,7 @@ const mutesController: AppController = async (c) => {
|
||||||
|
|
||||||
if (event10000) {
|
if (event10000) {
|
||||||
const pubkeys = getTagSet(event10000.tags, 'p');
|
const pubkeys = getTagSet(event10000.tags, 'p');
|
||||||
return renderAccounts(c, [...pubkeys].reverse());
|
return renderAccounts(c, [...pubkeys]);
|
||||||
} else {
|
} else {
|
||||||
return c.json([]);
|
return c.json([]);
|
||||||
}
|
}
|
||||||
|
|
|
@ -138,8 +138,8 @@ async function parseBody(req: Request): Promise<unknown> {
|
||||||
|
|
||||||
/** Schema to parse pagination query params. */
|
/** Schema to parse pagination query params. */
|
||||||
const paginationSchema = z.object({
|
const paginationSchema = z.object({
|
||||||
since: z.coerce.number().optional().catch(undefined),
|
since: z.coerce.number().nonnegative().optional().catch(undefined),
|
||||||
until: z.coerce.number().optional().catch(undefined),
|
until: z.coerce.number().nonnegative().optional().catch(undefined),
|
||||||
limit: z.coerce.number().catch(20).transform((value) => Math.min(Math.max(value, 0), 40)),
|
limit: z.coerce.number().catch(20).transform((value) => Math.min(Math.max(value, 0), 40)),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -179,6 +179,47 @@ function paginated(c: AppContext, events: NostrEvent[], entities: (Entity | unde
|
||||||
return c.json(results, 200, headers);
|
return c.json(results, 200, headers);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Query params for paginating a list. */
|
||||||
|
const listPaginationSchema = z.object({
|
||||||
|
offset: z.coerce.number().nonnegative().catch(0),
|
||||||
|
limit: z.coerce.number().catch(20).transform((value) => Math.min(Math.max(value, 0), 40)),
|
||||||
|
});
|
||||||
|
|
||||||
|
/** Build HTTP Link header for paginating Nostr lists. */
|
||||||
|
function buildListLinkHeader(url: string, params: { offset: number; limit: number }): string | undefined {
|
||||||
|
const { origin } = Conf.url;
|
||||||
|
const { pathname, search } = new URL(url);
|
||||||
|
const { offset, limit } = params;
|
||||||
|
const next = new URL(pathname + search, origin);
|
||||||
|
const prev = new URL(pathname + search, origin);
|
||||||
|
|
||||||
|
next.searchParams.set('offset', String(offset + limit));
|
||||||
|
prev.searchParams.set('offset', String(Math.max(offset - limit, 0)));
|
||||||
|
|
||||||
|
next.searchParams.set('limit', String(limit));
|
||||||
|
prev.searchParams.set('limit', String(limit));
|
||||||
|
|
||||||
|
return `<${next}>; rel="next", <${prev}>; rel="prev"`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** paginate a list of tags. */
|
||||||
|
function paginatedList(
|
||||||
|
c: AppContext,
|
||||||
|
params: { offset: number; limit: number },
|
||||||
|
entities: (Entity | undefined)[],
|
||||||
|
headers: HeaderRecord = {},
|
||||||
|
) {
|
||||||
|
const link = buildListLinkHeader(c.req.url, params);
|
||||||
|
|
||||||
|
if (link) {
|
||||||
|
headers.link = link;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filter out undefined entities.
|
||||||
|
const results = entities.filter((entity): entity is Entity => Boolean(entity));
|
||||||
|
return c.json(results, 200, headers);
|
||||||
|
}
|
||||||
|
|
||||||
/** JSON-LD context. */
|
/** JSON-LD context. */
|
||||||
type LDContext = (string | Record<string, string | Record<string, string>>)[];
|
type LDContext = (string | Record<string, string | Record<string, string>>)[];
|
||||||
|
|
||||||
|
@ -209,8 +250,10 @@ export {
|
||||||
createAdminEvent,
|
createAdminEvent,
|
||||||
createEvent,
|
createEvent,
|
||||||
type EventStub,
|
type EventStub,
|
||||||
|
listPaginationSchema,
|
||||||
localRequest,
|
localRequest,
|
||||||
paginated,
|
paginated,
|
||||||
|
paginatedList,
|
||||||
type PaginationParams,
|
type PaginationParams,
|
||||||
paginationSchema,
|
paginationSchema,
|
||||||
parseBody,
|
parseBody,
|
||||||
|
|
12
src/views.ts
12
src/views.ts
|
@ -4,7 +4,7 @@ import { AppContext } from '@/app.ts';
|
||||||
import { Storages } from '@/storages.ts';
|
import { Storages } from '@/storages.ts';
|
||||||
import { renderAccount } from '@/views/mastodon/accounts.ts';
|
import { renderAccount } from '@/views/mastodon/accounts.ts';
|
||||||
import { renderStatus } from '@/views/mastodon/statuses.ts';
|
import { renderStatus } from '@/views/mastodon/statuses.ts';
|
||||||
import { paginated, paginationSchema } from '@/utils/api.ts';
|
import { listPaginationSchema, paginated, paginatedList, paginationSchema } from '@/utils/api.ts';
|
||||||
import { hydrateEvents } from '@/storages/hydrate.ts';
|
import { hydrateEvents } from '@/storages/hydrate.ts';
|
||||||
import { accountFromPubkey } from '@/views/mastodon/accounts.ts';
|
import { accountFromPubkey } from '@/views/mastodon/accounts.ts';
|
||||||
|
|
||||||
|
@ -42,12 +42,14 @@ async function renderEventAccounts(c: AppContext, filters: NostrFilter[], opts?:
|
||||||
return paginated(c, events, accounts);
|
return paginated(c, events, accounts);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function renderAccounts(c: AppContext, authors: string[], signal = AbortSignal.timeout(1000)) {
|
async function renderAccounts(c: AppContext, pubkeys: string[]) {
|
||||||
const { since, until, limit } = paginationSchema.parse(c.req.query());
|
const { offset, limit } = listPaginationSchema.parse(c.req.query());
|
||||||
|
const authors = pubkeys.reverse().slice(offset, offset + limit);
|
||||||
|
|
||||||
const store = await Storages.db();
|
const store = await Storages.db();
|
||||||
|
const signal = c.req.raw.signal;
|
||||||
|
|
||||||
const events = await store.query([{ kinds: [0], authors, since, until, limit }], { signal })
|
const events = await store.query([{ kinds: [0], authors }], { signal })
|
||||||
.then((events) => hydrateEvents({ events, store, signal }));
|
.then((events) => hydrateEvents({ events, store, signal }));
|
||||||
|
|
||||||
const accounts = await Promise.all(
|
const accounts = await Promise.all(
|
||||||
|
@ -61,7 +63,7 @@ async function renderAccounts(c: AppContext, authors: string[], signal = AbortSi
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
return paginated(c, events, accounts);
|
return paginatedList(c, { offset, limit }, accounts);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Render statuses by event IDs. */
|
/** Render statuses by event IDs. */
|
||||||
|
|
Loading…
Reference in New Issue