Support pagination with Link headers... but of course Soapbox doesn't do that
This commit is contained in:
parent
dcf6b69501
commit
1d6ebf6ba6
|
@ -1,10 +1,16 @@
|
||||||
|
import { z } from '@/deps.ts';
|
||||||
|
|
||||||
import { fetchFeed, fetchFollows } from '../client.ts';
|
import { fetchFeed, fetchFollows } from '../client.ts';
|
||||||
|
import { toStatus } from '../transmute.ts';
|
||||||
import { getKeys } from '../utils.ts';
|
import { getKeys } from '../utils.ts';
|
||||||
|
|
||||||
import type { Context } from '@/deps.ts';
|
import type { Context } from '@/deps.ts';
|
||||||
import { toStatus } from '../transmute.ts';
|
import { LOCAL_DOMAIN } from '../config.ts';
|
||||||
|
|
||||||
async function homeController(c: Context) {
|
async function homeController(c: Context) {
|
||||||
|
const since = paramSchema.parse(c.req.query('since'));
|
||||||
|
const until = paramSchema.parse(c.req.query('until'));
|
||||||
|
|
||||||
const keys = getKeys(c);
|
const keys = getKeys(c);
|
||||||
if (!keys) {
|
if (!keys) {
|
||||||
return c.json({ error: 'Unauthorized' }, 401);
|
return c.json({ error: 'Unauthorized' }, 401);
|
||||||
|
@ -15,10 +21,17 @@ async function homeController(c: Context) {
|
||||||
return c.json([]);
|
return c.json([]);
|
||||||
}
|
}
|
||||||
|
|
||||||
const events = await fetchFeed(follows);
|
const events = await fetchFeed(follows, { since, until });
|
||||||
const statuses = (await Promise.all(events.map(toStatus))).filter(Boolean);
|
const statuses = (await Promise.all(events.map(toStatus))).filter(Boolean);
|
||||||
|
|
||||||
return c.json(statuses);
|
const next = `${LOCAL_DOMAIN}/api/v1/timelines/home?until=${events[events.length - 1].created_at}`;
|
||||||
|
const prev = `${LOCAL_DOMAIN}/api/v1/timelines/home?since=${events[0].created_at}`;
|
||||||
|
|
||||||
|
return c.json(statuses, 200, {
|
||||||
|
link: `<${next}>; rel="next", <${prev}>; rel="prev"`,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const paramSchema = z.coerce.number().optional().catch(undefined);
|
||||||
|
|
||||||
export default homeController;
|
export default homeController;
|
||||||
|
|
|
@ -3,6 +3,7 @@ import { Author, RelayPool } from '@/deps.ts';
|
||||||
import { poolRelays } from './config.ts';
|
import { poolRelays } from './config.ts';
|
||||||
|
|
||||||
import type { Event, SignedEvent } from './event.ts';
|
import type { Event, SignedEvent } from './event.ts';
|
||||||
|
import { eventDateComparator } from './utils.ts';
|
||||||
|
|
||||||
const pool = new RelayPool(poolRelays);
|
const pool = new RelayPool(poolRelays);
|
||||||
|
|
||||||
|
@ -34,23 +35,40 @@ const fetchFollows = (pubkey: string): Promise<SignedEvent<3> | null> => {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
/** Fetch 20 events from people the user follows. */
|
interface PaginationParams {
|
||||||
function fetchFeed(event3: Event<3>): Promise<SignedEvent<1>[]> {
|
since?: number;
|
||||||
|
until?: number;
|
||||||
|
limit?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Fetch events from people the user follows. */
|
||||||
|
function fetchFeed(event3: Event<3>, params: PaginationParams = {}): Promise<SignedEvent<1>[]> {
|
||||||
|
const limit = params.limit ?? 20;
|
||||||
const authors = event3.tags.filter((tag) => tag[0] === 'p').map((tag) => tag[1]);
|
const authors = event3.tags.filter((tag) => tag[0] === 'p').map((tag) => tag[1]);
|
||||||
const results: SignedEvent<1>[] = [];
|
const results: SignedEvent<1>[] = [];
|
||||||
authors.push(event3.pubkey); // see own events in feed
|
authors.push(event3.pubkey); // see own events in feed
|
||||||
|
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
pool.subscribe(
|
pool.subscribe(
|
||||||
[{ authors, kinds: [1], limit: 20 }],
|
[{
|
||||||
|
authors,
|
||||||
|
kinds: [1],
|
||||||
|
since: params.since,
|
||||||
|
until: params.until,
|
||||||
|
limit,
|
||||||
|
}],
|
||||||
poolRelays,
|
poolRelays,
|
||||||
(event: SignedEvent<1> | null) => {
|
(event: SignedEvent<1> | null) => {
|
||||||
if (event) {
|
if (event) {
|
||||||
results.push(event);
|
results.push(event);
|
||||||
|
|
||||||
|
if (results.length >= limit) {
|
||||||
|
resolve(results.slice(0, limit).sort(eventDateComparator));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
void 0,
|
void 0,
|
||||||
() => resolve(results),
|
() => resolve(results.sort(eventDateComparator)),
|
||||||
{ unsubscribeOnEose: true },
|
{ unsubscribeOnEose: true },
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
10
src/utils.ts
10
src/utils.ts
|
@ -1,5 +1,13 @@
|
||||||
import { Context, getPublicKey } from '@/deps.ts';
|
import { Context, getPublicKey } from '@/deps.ts';
|
||||||
|
|
||||||
|
import type { Event } from './event.ts';
|
||||||
|
|
||||||
|
/** Get the current time in Nostr format. */
|
||||||
|
const nostrNow = () => Math.floor(new Date().getTime() / 1000);
|
||||||
|
|
||||||
|
/** Pass to sort() to sort events by date. */
|
||||||
|
const eventDateComparator = (a: Event, b: Event) => b.created_at - a.created_at;
|
||||||
|
|
||||||
function getKeys(c: Context) {
|
function getKeys(c: Context) {
|
||||||
const auth = c.req.headers.get('Authorization') || '';
|
const auth = c.req.headers.get('Authorization') || '';
|
||||||
|
|
||||||
|
@ -14,4 +22,4 @@ function getKeys(c: Context) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export { getKeys };
|
export { eventDateComparator, getKeys, nostrNow };
|
||||||
|
|
Loading…
Reference in New Issue