Don't let your memes be dreams
This commit is contained in:
parent
08c9ee0670
commit
68b5887ed0
|
@ -9,7 +9,7 @@ const actorController: AppController = async (c) => {
|
||||||
const username = c.req.param('username');
|
const username = c.req.param('username');
|
||||||
const { signal } = c.req.raw;
|
const { signal } = c.req.raw;
|
||||||
|
|
||||||
const pointer = await localNip05Lookup(username);
|
const pointer = await localNip05Lookup(c.get('store'), username);
|
||||||
if (!pointer) return notFound(c);
|
if (!pointer) return notFound(c);
|
||||||
|
|
||||||
const event = await getAuthor(pointer.pubkey, { signal });
|
const event = await getAuthor(pointer.pubkey, { signal });
|
||||||
|
|
|
@ -94,15 +94,16 @@ const accountSearchController: AppController = async (c) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const query = decodeURIComponent(q);
|
const query = decodeURIComponent(q);
|
||||||
|
const store = await Storages.search();
|
||||||
|
|
||||||
const [event, events] = await Promise.all([
|
const [event, events] = await Promise.all([
|
||||||
lookupAccount(query),
|
lookupAccount(query),
|
||||||
Storages.search.query([{ kinds: [0], search: query, limit: 20 }], { signal: c.req.raw.signal }),
|
store.query([{ kinds: [0], search: query, limit: 20 }], { signal: c.req.raw.signal }),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const results = await hydrateEvents({
|
const results = await hydrateEvents({
|
||||||
events: event ? [event, ...events] : events,
|
events: event ? [event, ...events] : events,
|
||||||
storage: Storages.db,
|
store,
|
||||||
signal: c.req.raw.signal,
|
signal: c.req.raw.signal,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -147,8 +148,10 @@ const accountStatusesController: AppController = async (c) => {
|
||||||
const { pinned, limit, exclude_replies, tagged } = accountStatusesQuerySchema.parse(c.req.query());
|
const { pinned, limit, exclude_replies, tagged } = accountStatusesQuerySchema.parse(c.req.query());
|
||||||
const { signal } = c.req.raw;
|
const { signal } = c.req.raw;
|
||||||
|
|
||||||
|
const store = await Storages.db();
|
||||||
|
|
||||||
if (pinned) {
|
if (pinned) {
|
||||||
const [pinEvent] = await Storages.db.query([{ kinds: [10001], authors: [pubkey], limit: 1 }], { signal });
|
const [pinEvent] = await store.query([{ kinds: [10001], authors: [pubkey], limit: 1 }], { signal });
|
||||||
if (pinEvent) {
|
if (pinEvent) {
|
||||||
const pinnedEventIds = getTagSet(pinEvent.tags, 'e');
|
const pinnedEventIds = getTagSet(pinEvent.tags, 'e');
|
||||||
return renderStatuses(c, [...pinnedEventIds].reverse());
|
return renderStatuses(c, [...pinnedEventIds].reverse());
|
||||||
|
@ -169,8 +172,8 @@ const accountStatusesController: AppController = async (c) => {
|
||||||
filter['#t'] = [tagged];
|
filter['#t'] = [tagged];
|
||||||
}
|
}
|
||||||
|
|
||||||
const events = await Storages.db.query([filter], { signal })
|
const events = await store.query([filter], { signal })
|
||||||
.then((events) => hydrateEvents({ events, storage: Storages.db, signal }))
|
.then((events) => hydrateEvents({ events, store, signal }))
|
||||||
.then((events) => {
|
.then((events) => {
|
||||||
if (exclude_replies) {
|
if (exclude_replies) {
|
||||||
return events.filter((event) => !findReplyTag(event.tags));
|
return events.filter((event) => !findReplyTag(event.tags));
|
||||||
|
@ -244,7 +247,7 @@ const followController: AppController = async (c) => {
|
||||||
const targetPubkey = c.req.param('pubkey');
|
const targetPubkey = c.req.param('pubkey');
|
||||||
|
|
||||||
await updateListEvent(
|
await updateListEvent(
|
||||||
{ kinds: [3], authors: [sourcePubkey] },
|
{ kinds: [3], authors: [sourcePubkey], limit: 1 },
|
||||||
(tags) => addTag(tags, ['p', targetPubkey]),
|
(tags) => addTag(tags, ['p', targetPubkey]),
|
||||||
c,
|
c,
|
||||||
);
|
);
|
||||||
|
@ -261,7 +264,7 @@ const unfollowController: AppController = async (c) => {
|
||||||
const targetPubkey = c.req.param('pubkey');
|
const targetPubkey = c.req.param('pubkey');
|
||||||
|
|
||||||
await updateListEvent(
|
await updateListEvent(
|
||||||
{ kinds: [3], authors: [sourcePubkey] },
|
{ kinds: [3], authors: [sourcePubkey], limit: 1 },
|
||||||
(tags) => deleteTag(tags, ['p', targetPubkey]),
|
(tags) => deleteTag(tags, ['p', targetPubkey]),
|
||||||
c,
|
c,
|
||||||
);
|
);
|
||||||
|
@ -298,7 +301,7 @@ const muteController: AppController = async (c) => {
|
||||||
const targetPubkey = c.req.param('pubkey');
|
const targetPubkey = c.req.param('pubkey');
|
||||||
|
|
||||||
await updateListEvent(
|
await updateListEvent(
|
||||||
{ kinds: [10000], authors: [sourcePubkey] },
|
{ kinds: [10000], authors: [sourcePubkey], limit: 1 },
|
||||||
(tags) => addTag(tags, ['p', targetPubkey]),
|
(tags) => addTag(tags, ['p', targetPubkey]),
|
||||||
c,
|
c,
|
||||||
);
|
);
|
||||||
|
@ -313,7 +316,7 @@ const unmuteController: AppController = async (c) => {
|
||||||
const targetPubkey = c.req.param('pubkey');
|
const targetPubkey = c.req.param('pubkey');
|
||||||
|
|
||||||
await updateListEvent(
|
await updateListEvent(
|
||||||
{ kinds: [10000], authors: [sourcePubkey] },
|
{ kinds: [10000], authors: [sourcePubkey], limit: 1 },
|
||||||
(tags) => deleteTag(tags, ['p', targetPubkey]),
|
(tags) => deleteTag(tags, ['p', targetPubkey]),
|
||||||
c,
|
c,
|
||||||
);
|
);
|
||||||
|
@ -327,7 +330,9 @@ const favouritesController: AppController = async (c) => {
|
||||||
const params = paginationSchema.parse(c.req.query());
|
const params = paginationSchema.parse(c.req.query());
|
||||||
const { signal } = c.req.raw;
|
const { signal } = c.req.raw;
|
||||||
|
|
||||||
const events7 = await Storages.db.query(
|
const store = await Storages.db();
|
||||||
|
|
||||||
|
const events7 = await store.query(
|
||||||
[{ kinds: [7], authors: [pubkey], ...params }],
|
[{ kinds: [7], authors: [pubkey], ...params }],
|
||||||
{ signal },
|
{ signal },
|
||||||
);
|
);
|
||||||
|
@ -336,8 +341,8 @@ const favouritesController: AppController = async (c) => {
|
||||||
.map((event) => event.tags.find((tag) => tag[0] === 'e')?.[1])
|
.map((event) => event.tags.find((tag) => tag[0] === 'e')?.[1])
|
||||||
.filter((id): id is string => !!id);
|
.filter((id): id is string => !!id);
|
||||||
|
|
||||||
const events1 = await Storages.db.query([{ kinds: [1], ids }], { signal })
|
const events1 = await store.query([{ kinds: [1], ids }], { signal })
|
||||||
.then((events) => hydrateEvents({ events, storage: Storages.db, signal }));
|
.then((events) => hydrateEvents({ events, store, signal }));
|
||||||
|
|
||||||
const viewerPubkey = await c.get('signer')?.getPublicKey();
|
const viewerPubkey = await c.get('signer')?.getPublicKey();
|
||||||
|
|
||||||
|
|
|
@ -39,12 +39,13 @@ const adminAccountsController: AppController = async (c) => {
|
||||||
return c.json([]);
|
return c.json([]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const store = await Storages.db();
|
||||||
const { since, until, limit } = paginationSchema.parse(c.req.query());
|
const { since, until, limit } = paginationSchema.parse(c.req.query());
|
||||||
const { signal } = c.req.raw;
|
const { signal } = c.req.raw;
|
||||||
|
|
||||||
const events = await Storages.db.query([{ kinds: [30361], authors: [Conf.pubkey], since, until, limit }], { signal });
|
const events = await store.query([{ kinds: [30361], authors: [Conf.pubkey], since, until, limit }], { signal });
|
||||||
const pubkeys = events.map((event) => event.tags.find(([name]) => name === 'd')?.[1]!);
|
const pubkeys = events.map((event) => event.tags.find(([name]) => name === 'd')?.[1]!);
|
||||||
const authors = await Storages.db.query([{ kinds: [0], authors: pubkeys }], { signal });
|
const authors = await store.query([{ kinds: [0], authors: pubkeys }], { signal });
|
||||||
|
|
||||||
for (const event of events) {
|
for (const event of events) {
|
||||||
const d = event.tags.find(([name]) => name === 'd')?.[1];
|
const d = event.tags.find(([name]) => name === 'd')?.[1];
|
||||||
|
@ -78,7 +79,7 @@ const adminAccountAction: AppController = async (c) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
await updateListAdminEvent(
|
await updateListAdminEvent(
|
||||||
{ kinds: [10000], authors: [Conf.pubkey] },
|
{ kinds: [10000], authors: [Conf.pubkey], limit: 1 },
|
||||||
(tags) => addTag(tags, ['p', authorId]),
|
(tags) => addTag(tags, ['p', authorId]),
|
||||||
c,
|
c,
|
||||||
);
|
);
|
||||||
|
|
|
@ -5,10 +5,11 @@ import { renderStatuses } from '@/views.ts';
|
||||||
|
|
||||||
/** https://docs.joinmastodon.org/methods/bookmarks/#get */
|
/** https://docs.joinmastodon.org/methods/bookmarks/#get */
|
||||||
const bookmarksController: AppController = async (c) => {
|
const bookmarksController: AppController = async (c) => {
|
||||||
|
const store = await Storages.db();
|
||||||
const pubkey = await c.get('signer')?.getPublicKey()!;
|
const pubkey = await c.get('signer')?.getPublicKey()!;
|
||||||
const { signal } = c.req.raw;
|
const { signal } = c.req.raw;
|
||||||
|
|
||||||
const [event10003] = await Storages.db.query(
|
const [event10003] = await store.query(
|
||||||
[{ kinds: [10003], authors: [pubkey], limit: 1 }],
|
[{ kinds: [10003], authors: [pubkey], limit: 1 }],
|
||||||
{ signal },
|
{ signal },
|
||||||
);
|
);
|
||||||
|
|
|
@ -16,7 +16,9 @@ const relaySchema = z.object({
|
||||||
type RelayEntity = z.infer<typeof relaySchema>;
|
type RelayEntity = z.infer<typeof relaySchema>;
|
||||||
|
|
||||||
export const adminRelaysController: AppController = async (c) => {
|
export const adminRelaysController: AppController = async (c) => {
|
||||||
const [event] = await Storages.db.query([
|
const store = await Storages.db();
|
||||||
|
|
||||||
|
const [event] = await store.query([
|
||||||
{ kinds: [10002], authors: [Conf.pubkey], limit: 1 },
|
{ kinds: [10002], authors: [Conf.pubkey], limit: 1 },
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
@ -28,6 +30,7 @@ export const adminRelaysController: AppController = async (c) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
export const adminSetRelaysController: AppController = async (c) => {
|
export const adminSetRelaysController: AppController = async (c) => {
|
||||||
|
const store = await Storages.db();
|
||||||
const relays = relaySchema.array().parse(await c.req.json());
|
const relays = relaySchema.array().parse(await c.req.json());
|
||||||
|
|
||||||
const event = await new AdminSigner().signEvent({
|
const event = await new AdminSigner().signEvent({
|
||||||
|
@ -37,7 +40,7 @@ export const adminSetRelaysController: AppController = async (c) => {
|
||||||
created_at: Math.floor(Date.now() / 1000),
|
created_at: Math.floor(Date.now() / 1000),
|
||||||
});
|
});
|
||||||
|
|
||||||
await Storages.db.event(event);
|
await store.event(event);
|
||||||
|
|
||||||
return c.json(renderRelays(event));
|
return c.json(renderRelays(event));
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
import { AppController } from '@/app.ts';
|
import { AppController } from '@/app.ts';
|
||||||
import { Conf } from '@/config.ts';
|
import { Conf } from '@/config.ts';
|
||||||
|
import { Storages } from '@/storages.ts';
|
||||||
import { getInstanceMetadata } from '@/utils/instance.ts';
|
import { getInstanceMetadata } from '@/utils/instance.ts';
|
||||||
|
|
||||||
const instanceController: AppController = async (c) => {
|
const instanceController: AppController = async (c) => {
|
||||||
const { host, protocol } = Conf.url;
|
const { host, protocol } = Conf.url;
|
||||||
const meta = await getInstanceMetadata(c.req.raw.signal);
|
const meta = await getInstanceMetadata(await Storages.db(), c.req.raw.signal);
|
||||||
|
|
||||||
/** Protocol to use for WebSocket URLs, depending on the protocol of the `LOCAL_DOMAIN`. */
|
/** Protocol to use for WebSocket URLs, depending on the protocol of the `LOCAL_DOMAIN`. */
|
||||||
const wsProtocol = protocol === 'http:' ? 'ws:' : 'wss:';
|
const wsProtocol = protocol === 'http:' ? 'ws:' : 'wss:';
|
||||||
|
|
|
@ -5,10 +5,11 @@ import { renderAccounts } from '@/views.ts';
|
||||||
|
|
||||||
/** https://docs.joinmastodon.org/methods/mutes/#get */
|
/** https://docs.joinmastodon.org/methods/mutes/#get */
|
||||||
const mutesController: AppController = async (c) => {
|
const mutesController: AppController = async (c) => {
|
||||||
|
const store = await Storages.db();
|
||||||
const pubkey = await c.get('signer')?.getPublicKey()!;
|
const pubkey = await c.get('signer')?.getPublicKey()!;
|
||||||
const { signal } = c.req.raw;
|
const { signal } = c.req.raw;
|
||||||
|
|
||||||
const [event10000] = await Storages.db.query(
|
const [event10000] = await store.query(
|
||||||
[{ kinds: [10000], authors: [pubkey], limit: 1 }],
|
[{ kinds: [10000], authors: [pubkey], limit: 1 }],
|
||||||
{ signal },
|
{ signal },
|
||||||
);
|
);
|
||||||
|
|
|
@ -20,7 +20,7 @@ async function renderNotifications(c: AppContext, filters: NostrFilter[]) {
|
||||||
const events = await store
|
const events = await store
|
||||||
.query(filters, { signal })
|
.query(filters, { signal })
|
||||||
.then((events) => events.filter((event) => event.pubkey !== pubkey))
|
.then((events) => events.filter((event) => event.pubkey !== pubkey))
|
||||||
.then((events) => hydrateEvents({ events, storage: store, signal }));
|
.then((events) => hydrateEvents({ events, store, signal }));
|
||||||
|
|
||||||
if (!events.length) {
|
if (!events.length) {
|
||||||
return c.json([]);
|
return c.json([]);
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { NSchema as n } from '@nostrify/nostrify';
|
import { NSchema as n, NStore } from '@nostrify/nostrify';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
import { type AppController } from '@/app.ts';
|
import { type AppController } from '@/app.ts';
|
||||||
|
@ -9,7 +9,8 @@ import { Storages } from '@/storages.ts';
|
||||||
import { createAdminEvent } from '@/utils/api.ts';
|
import { createAdminEvent } from '@/utils/api.ts';
|
||||||
|
|
||||||
const frontendConfigController: AppController = async (c) => {
|
const frontendConfigController: AppController = async (c) => {
|
||||||
const configs = await getConfigs(c.req.raw.signal);
|
const store = await Storages.db();
|
||||||
|
const configs = await getConfigs(store, c.req.raw.signal);
|
||||||
const frontendConfig = configs.find(({ group, key }) => group === ':pleroma' && key === ':frontend_configurations');
|
const frontendConfig = configs.find(({ group, key }) => group === ':pleroma' && key === ':frontend_configurations');
|
||||||
|
|
||||||
if (frontendConfig) {
|
if (frontendConfig) {
|
||||||
|
@ -25,7 +26,8 @@ const frontendConfigController: AppController = async (c) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const configController: AppController = async (c) => {
|
const configController: AppController = async (c) => {
|
||||||
const configs = await getConfigs(c.req.raw.signal);
|
const store = await Storages.db();
|
||||||
|
const configs = await getConfigs(store, c.req.raw.signal);
|
||||||
return c.json({ configs, need_reboot: false });
|
return c.json({ configs, need_reboot: false });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -33,7 +35,8 @@ const configController: AppController = async (c) => {
|
||||||
const updateConfigController: AppController = async (c) => {
|
const updateConfigController: AppController = async (c) => {
|
||||||
const { pubkey } = Conf;
|
const { pubkey } = Conf;
|
||||||
|
|
||||||
const configs = await getConfigs(c.req.raw.signal);
|
const store = await Storages.db();
|
||||||
|
const configs = await getConfigs(store, c.req.raw.signal);
|
||||||
const { configs: newConfigs } = z.object({ configs: z.array(configSchema) }).parse(await c.req.json());
|
const { configs: newConfigs } = z.object({ configs: z.array(configSchema) }).parse(await c.req.json());
|
||||||
|
|
||||||
for (const { group, key, value } of newConfigs) {
|
for (const { group, key, value } of newConfigs) {
|
||||||
|
@ -63,10 +66,10 @@ const pleromaAdminDeleteStatusController: AppController = async (c) => {
|
||||||
return c.json({});
|
return c.json({});
|
||||||
};
|
};
|
||||||
|
|
||||||
async function getConfigs(signal: AbortSignal): Promise<PleromaConfig[]> {
|
async function getConfigs(store: NStore, signal: AbortSignal): Promise<PleromaConfig[]> {
|
||||||
const { pubkey } = Conf;
|
const { pubkey } = Conf;
|
||||||
|
|
||||||
const [event] = await Storages.db.query([{
|
const [event] = await store.query([{
|
||||||
kinds: [30078],
|
kinds: [30078],
|
||||||
authors: [pubkey],
|
authors: [pubkey],
|
||||||
'#d': ['pub.ditto.pleroma.config'],
|
'#d': ['pub.ditto.pleroma.config'],
|
||||||
|
|
|
@ -48,7 +48,7 @@ const reportController: AppController = async (c) => {
|
||||||
tags,
|
tags,
|
||||||
}, c);
|
}, c);
|
||||||
|
|
||||||
await hydrateEvents({ events: [event], storage: store });
|
await hydrateEvents({ events: [event], store });
|
||||||
return c.json(await renderReport(event));
|
return c.json(await renderReport(event));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -58,7 +58,7 @@ const adminReportsController: AppController = async (c) => {
|
||||||
const viewerPubkey = await c.get('signer')?.getPublicKey();
|
const viewerPubkey = await c.get('signer')?.getPublicKey();
|
||||||
|
|
||||||
const reports = await store.query([{ kinds: [1984], '#P': [Conf.pubkey] }])
|
const reports = await store.query([{ kinds: [1984], '#P': [Conf.pubkey] }])
|
||||||
.then((events) => hydrateEvents({ storage: store, events: events, signal: c.req.raw.signal }))
|
.then((events) => hydrateEvents({ store, events: events, signal: c.req.raw.signal }))
|
||||||
.then((events) =>
|
.then((events) =>
|
||||||
Promise.all(
|
Promise.all(
|
||||||
events.map((event) => renderAdminReport(event, { viewerPubkey })),
|
events.map((event) => renderAdminReport(event, { viewerPubkey })),
|
||||||
|
@ -85,7 +85,7 @@ const adminReportController: AppController = async (c) => {
|
||||||
return c.json({ error: 'This action is not allowed' }, 403);
|
return c.json({ error: 'This action is not allowed' }, 403);
|
||||||
}
|
}
|
||||||
|
|
||||||
await hydrateEvents({ events: [event], storage: store, signal });
|
await hydrateEvents({ events: [event], store, signal });
|
||||||
|
|
||||||
return c.json(await renderAdminReport(event, { viewerPubkey: pubkey }));
|
return c.json(await renderAdminReport(event, { viewerPubkey: pubkey }));
|
||||||
};
|
};
|
||||||
|
@ -107,7 +107,7 @@ const adminReportResolveController: AppController = async (c) => {
|
||||||
return c.json({ error: 'This action is not allowed' }, 403);
|
return c.json({ error: 'This action is not allowed' }, 403);
|
||||||
}
|
}
|
||||||
|
|
||||||
await hydrateEvents({ events: [event], storage: store, signal });
|
await hydrateEvents({ events: [event], store, signal });
|
||||||
|
|
||||||
await createAdminEvent({
|
await createAdminEvent({
|
||||||
kind: 5,
|
kind: 5,
|
||||||
|
|
|
@ -78,7 +78,7 @@ const searchController: AppController = async (c) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
/** Get events for the search params. */
|
/** Get events for the search params. */
|
||||||
function searchEvents({ q, type, limit, account_id }: SearchQuery, signal: AbortSignal): Promise<NostrEvent[]> {
|
async function searchEvents({ q, type, limit, account_id }: SearchQuery, signal: AbortSignal): Promise<NostrEvent[]> {
|
||||||
if (type === 'hashtags') return Promise.resolve([]);
|
if (type === 'hashtags') return Promise.resolve([]);
|
||||||
|
|
||||||
const filter: NostrFilter = {
|
const filter: NostrFilter = {
|
||||||
|
@ -91,8 +91,10 @@ function searchEvents({ q, type, limit, account_id }: SearchQuery, signal: Abort
|
||||||
filter.authors = [account_id];
|
filter.authors = [account_id];
|
||||||
}
|
}
|
||||||
|
|
||||||
return Storages.search.query([filter], { signal })
|
const store = await Storages.search();
|
||||||
.then((events) => hydrateEvents({ events, storage: Storages.search, signal }));
|
|
||||||
|
return store.query([filter], { signal })
|
||||||
|
.then((events) => hydrateEvents({ events, store, signal }));
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Get event kinds to search from `type` query param. */
|
/** Get event kinds to search from `type` query param. */
|
||||||
|
@ -110,9 +112,10 @@ function typeToKinds(type: SearchQuery['type']): number[] {
|
||||||
/** Resolve a searched value into an event, if applicable. */
|
/** Resolve a searched value into an event, if applicable. */
|
||||||
async function lookupEvent(query: SearchQuery, signal: AbortSignal): Promise<NostrEvent | undefined> {
|
async function lookupEvent(query: SearchQuery, signal: AbortSignal): Promise<NostrEvent | undefined> {
|
||||||
const filters = await getLookupFilters(query, signal);
|
const filters = await getLookupFilters(query, signal);
|
||||||
|
const store = await Storages.search();
|
||||||
|
|
||||||
return Storages.search.query(filters, { limit: 1, signal })
|
return store.query(filters, { limit: 1, signal })
|
||||||
.then((events) => hydrateEvents({ events, storage: Storages.search, signal }))
|
.then((events) => hydrateEvents({ events, store, signal }))
|
||||||
.then(([event]) => event);
|
.then(([event]) => event);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -140,7 +140,7 @@ const createStatusController: AppController = async (c) => {
|
||||||
if (data.quote_id) {
|
if (data.quote_id) {
|
||||||
await hydrateEvents({
|
await hydrateEvents({
|
||||||
events: [event],
|
events: [event],
|
||||||
storage: Storages.db,
|
store: await Storages.db(),
|
||||||
signal: c.req.raw.signal,
|
signal: c.req.raw.signal,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -248,7 +248,7 @@ const reblogStatusController: AppController = async (c) => {
|
||||||
|
|
||||||
await hydrateEvents({
|
await hydrateEvents({
|
||||||
events: [reblogEvent],
|
events: [reblogEvent],
|
||||||
storage: Storages.db,
|
store: await Storages.db(),
|
||||||
signal: signal,
|
signal: signal,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -260,23 +260,30 @@ const reblogStatusController: AppController = async (c) => {
|
||||||
/** https://docs.joinmastodon.org/methods/statuses/#unreblog */
|
/** https://docs.joinmastodon.org/methods/statuses/#unreblog */
|
||||||
const unreblogStatusController: AppController = async (c) => {
|
const unreblogStatusController: AppController = async (c) => {
|
||||||
const eventId = c.req.param('id');
|
const eventId = c.req.param('id');
|
||||||
const pubkey = await c.get('signer')?.getPublicKey() as string;
|
const pubkey = await c.get('signer')?.getPublicKey()!;
|
||||||
|
|
||||||
const event = await getEvent(eventId, {
|
const event = await getEvent(eventId, { kind: 1 });
|
||||||
kind: 1,
|
|
||||||
});
|
|
||||||
if (!event) return c.json({ error: 'Event not found.' }, 404);
|
|
||||||
|
|
||||||
const filters: NostrFilter[] = [{ kinds: [6], authors: [pubkey], '#e': [event.id] }];
|
if (!event) {
|
||||||
const [repostedEvent] = await Storages.db.query(filters, { limit: 1 });
|
return c.json({ error: 'Event not found.' }, 404);
|
||||||
if (!repostedEvent) return c.json({ error: 'Event not found.' }, 404);
|
}
|
||||||
|
|
||||||
|
const store = await Storages.db();
|
||||||
|
|
||||||
|
const [repostedEvent] = await store.query(
|
||||||
|
[{ kinds: [6], authors: [pubkey], '#e': [event.id], limit: 1 }],
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!repostedEvent) {
|
||||||
|
return c.json({ error: 'Event not found.' }, 404);
|
||||||
|
}
|
||||||
|
|
||||||
await createEvent({
|
await createEvent({
|
||||||
kind: 5,
|
kind: 5,
|
||||||
tags: [['e', repostedEvent.id]],
|
tags: [['e', repostedEvent.id]],
|
||||||
}, c);
|
}, c);
|
||||||
|
|
||||||
return c.json(await renderStatus(event, {}));
|
return c.json(await renderStatus(event, { viewerPubkey: pubkey }));
|
||||||
};
|
};
|
||||||
|
|
||||||
const rebloggedByController: AppController = (c) => {
|
const rebloggedByController: AppController = (c) => {
|
||||||
|
@ -297,7 +304,7 @@ const bookmarkController: AppController = async (c) => {
|
||||||
|
|
||||||
if (event) {
|
if (event) {
|
||||||
await updateListEvent(
|
await updateListEvent(
|
||||||
{ kinds: [10003], authors: [pubkey] },
|
{ kinds: [10003], authors: [pubkey], limit: 1 },
|
||||||
(tags) => addTag(tags, ['e', eventId]),
|
(tags) => addTag(tags, ['e', eventId]),
|
||||||
c,
|
c,
|
||||||
);
|
);
|
||||||
|
@ -324,7 +331,7 @@ const unbookmarkController: AppController = async (c) => {
|
||||||
|
|
||||||
if (event) {
|
if (event) {
|
||||||
await updateListEvent(
|
await updateListEvent(
|
||||||
{ kinds: [10003], authors: [pubkey] },
|
{ kinds: [10003], authors: [pubkey], limit: 1 },
|
||||||
(tags) => deleteTag(tags, ['e', eventId]),
|
(tags) => deleteTag(tags, ['e', eventId]),
|
||||||
c,
|
c,
|
||||||
);
|
);
|
||||||
|
@ -351,7 +358,7 @@ const pinController: AppController = async (c) => {
|
||||||
|
|
||||||
if (event) {
|
if (event) {
|
||||||
await updateListEvent(
|
await updateListEvent(
|
||||||
{ kinds: [10001], authors: [pubkey] },
|
{ kinds: [10001], authors: [pubkey], limit: 1 },
|
||||||
(tags) => addTag(tags, ['e', eventId]),
|
(tags) => addTag(tags, ['e', eventId]),
|
||||||
c,
|
c,
|
||||||
);
|
);
|
||||||
|
@ -380,7 +387,7 @@ const unpinController: AppController = async (c) => {
|
||||||
|
|
||||||
if (event) {
|
if (event) {
|
||||||
await updateListEvent(
|
await updateListEvent(
|
||||||
{ kinds: [10001], authors: [pubkey] },
|
{ kinds: [10001], authors: [pubkey], limit: 1 },
|
||||||
(tags) => deleteTag(tags, ['e', eventId]),
|
(tags) => deleteTag(tags, ['e', eventId]),
|
||||||
c,
|
c,
|
||||||
);
|
);
|
||||||
|
|
|
@ -68,13 +68,15 @@ const streamingController: AppController = (c) => {
|
||||||
if (!filter) return;
|
if (!filter) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
for await (const msg of Storages.pubsub.req([filter], { signal: controller.signal })) {
|
const store = await Storages.pubsub();
|
||||||
|
|
||||||
|
for await (const msg of store.req([filter], { signal: controller.signal })) {
|
||||||
if (msg[0] === 'EVENT') {
|
if (msg[0] === 'EVENT') {
|
||||||
const event = msg[2];
|
const event = msg[2];
|
||||||
|
|
||||||
await hydrateEvents({
|
await hydrateEvents({
|
||||||
events: [event],
|
events: [event],
|
||||||
storage: Storages.admin,
|
store,
|
||||||
signal: AbortSignal.timeout(1000),
|
signal: AbortSignal.timeout(1000),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -40,7 +40,7 @@ async function renderSuggestedAccounts(store: NStore, signal?: AbortSignal) {
|
||||||
[{ kinds: [0], authors: pubkeys, limit: pubkeys.length }],
|
[{ kinds: [0], authors: pubkeys, limit: pubkeys.length }],
|
||||||
{ signal },
|
{ signal },
|
||||||
)
|
)
|
||||||
.then((events) => hydrateEvents({ events, storage: store, signal }));
|
.then((events) => hydrateEvents({ events, store, signal }));
|
||||||
|
|
||||||
const accounts = await Promise.all(pubkeys.map((pubkey) => {
|
const accounts = await Promise.all(pubkeys.map((pubkey) => {
|
||||||
const profile = profiles.find((event) => event.pubkey === pubkey);
|
const profile = profiles.find((event) => event.pubkey === pubkey);
|
||||||
|
|
|
@ -49,13 +49,7 @@ async function renderStatuses(c: AppContext, filters: NostrFilter[]) {
|
||||||
|
|
||||||
const events = await store
|
const events = await store
|
||||||
.query(filters, { signal })
|
.query(filters, { signal })
|
||||||
.then((events) =>
|
.then((events) => hydrateEvents({ events, store, signal }));
|
||||||
hydrateEvents({
|
|
||||||
events,
|
|
||||||
storage: store,
|
|
||||||
signal,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!events.length) {
|
if (!events.length) {
|
||||||
return c.json([]);
|
return c.json([]);
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
import { AppController } from '@/app.ts';
|
import { AppController } from '@/app.ts';
|
||||||
import { Conf } from '@/config.ts';
|
import { Conf } from '@/config.ts';
|
||||||
|
import { Storages } from '@/storages.ts';
|
||||||
import { getInstanceMetadata } from '@/utils/instance.ts';
|
import { getInstanceMetadata } from '@/utils/instance.ts';
|
||||||
|
|
||||||
const relayInfoController: AppController = async (c) => {
|
const relayInfoController: AppController = async (c) => {
|
||||||
const meta = await getInstanceMetadata(c.req.raw.signal);
|
const store = await Storages.db();
|
||||||
|
const meta = await getInstanceMetadata(store, c.req.raw.signal);
|
||||||
|
|
||||||
return c.json({
|
return c.json({
|
||||||
name: meta.name,
|
name: meta.name,
|
||||||
|
|
|
@ -72,14 +72,17 @@ function connectStream(socket: WebSocket) {
|
||||||
controllers.get(subId)?.abort();
|
controllers.get(subId)?.abort();
|
||||||
controllers.set(subId, controller);
|
controllers.set(subId, controller);
|
||||||
|
|
||||||
for (const event of await Storages.db.query(filters, { limit: FILTER_LIMIT })) {
|
const db = await Storages.db();
|
||||||
|
const pubsub = await Storages.pubsub();
|
||||||
|
|
||||||
|
for (const event of await db.query(filters, { limit: FILTER_LIMIT })) {
|
||||||
send(['EVENT', subId, event]);
|
send(['EVENT', subId, event]);
|
||||||
}
|
}
|
||||||
|
|
||||||
send(['EOSE', subId]);
|
send(['EOSE', subId]);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
for await (const msg of Storages.pubsub.req(filters, { signal: controller.signal })) {
|
for await (const msg of pubsub.req(filters, { signal: controller.signal })) {
|
||||||
if (msg[0] === 'EVENT') {
|
if (msg[0] === 'EVENT') {
|
||||||
send(['EVENT', subId, msg[2]]);
|
send(['EVENT', subId, msg[2]]);
|
||||||
}
|
}
|
||||||
|
@ -116,7 +119,8 @@ function connectStream(socket: WebSocket) {
|
||||||
|
|
||||||
/** Handle COUNT. Return the number of events matching the filters. */
|
/** Handle COUNT. Return the number of events matching the filters. */
|
||||||
async function handleCount([_, subId, ...rest]: NostrClientCOUNT): Promise<void> {
|
async function handleCount([_, subId, ...rest]: NostrClientCOUNT): Promise<void> {
|
||||||
const { count } = await Storages.db.count(prepareFilters(rest));
|
const store = await Storages.db();
|
||||||
|
const { count } = await store.count(prepareFilters(rest));
|
||||||
send(['COUNT', subId, { count, approximate: false }]);
|
send(['COUNT', subId, { count, approximate: false }]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -12,7 +12,7 @@ const nameSchema = z.string().min(1).regex(/^\w+$/);
|
||||||
const nostrController: AppController = async (c) => {
|
const nostrController: AppController = async (c) => {
|
||||||
const result = nameSchema.safeParse(c.req.query('name'));
|
const result = nameSchema.safeParse(c.req.query('name'));
|
||||||
const name = result.success ? result.data : undefined;
|
const name = result.success ? result.data : undefined;
|
||||||
const pointer = name ? await localNip05Lookup(name) : undefined;
|
const pointer = name ? await localNip05Lookup(c.get('store'), name) : undefined;
|
||||||
|
|
||||||
if (!name || !pointer) {
|
if (!name || !pointer) {
|
||||||
return c.json({ names: {}, relays: {} });
|
return c.json({ names: {}, relays: {} });
|
||||||
|
|
|
@ -45,7 +45,7 @@ async function handleAcct(c: AppContext, resource: URL): Promise<Response> {
|
||||||
}
|
}
|
||||||
|
|
||||||
const [username, host] = result.data;
|
const [username, host] = result.data;
|
||||||
const pointer = await localNip05Lookup(username);
|
const pointer = await localNip05Lookup(c.get('store'), username);
|
||||||
|
|
||||||
if (!pointer) {
|
if (!pointer) {
|
||||||
return c.json({ error: 'Not found' }, 404);
|
return c.json({ error: 'Not found' }, 404);
|
||||||
|
|
|
@ -60,7 +60,8 @@ async function findUser(user: Partial<User>, signal?: AbortSignal): Promise<User
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const [event] = await Storages.db.query([filter], { signal });
|
const store = await Storages.db();
|
||||||
|
const [event] = await store.query([filter], { signal });
|
||||||
|
|
||||||
if (event) {
|
if (event) {
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -7,10 +7,10 @@ export const storeMiddleware: AppMiddleware = async (c, next) => {
|
||||||
const pubkey = await c.get('signer')?.getPublicKey();
|
const pubkey = await c.get('signer')?.getPublicKey();
|
||||||
|
|
||||||
if (pubkey) {
|
if (pubkey) {
|
||||||
const store = new UserStore(pubkey, Storages.admin);
|
const store = new UserStore(pubkey, await Storages.admin());
|
||||||
c.set('store', store);
|
c.set('store', store);
|
||||||
} else {
|
} else {
|
||||||
c.set('store', Storages.admin);
|
c.set('store', await Storages.admin());
|
||||||
}
|
}
|
||||||
await next();
|
await next();
|
||||||
};
|
};
|
||||||
|
|
|
@ -57,7 +57,7 @@ async function handleEvent(event: DittoEvent, signal: AbortSignal): Promise<void
|
||||||
|
|
||||||
async function policyFilter(event: NostrEvent): Promise<void> {
|
async function policyFilter(event: NostrEvent): Promise<void> {
|
||||||
const policies: NPolicy[] = [
|
const policies: NPolicy[] = [
|
||||||
new MuteListPolicy(Conf.pubkey, Storages.admin),
|
new MuteListPolicy(Conf.pubkey, await Storages.admin()),
|
||||||
];
|
];
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -76,15 +76,20 @@ async function policyFilter(event: NostrEvent): Promise<void> {
|
||||||
|
|
||||||
/** Encounter the event, and return whether it has already been encountered. */
|
/** Encounter the event, and return whether it has already been encountered. */
|
||||||
async function encounterEvent(event: NostrEvent, signal: AbortSignal): Promise<boolean> {
|
async function encounterEvent(event: NostrEvent, signal: AbortSignal): Promise<boolean> {
|
||||||
const [existing] = await Storages.cache.query([{ ids: [event.id], limit: 1 }]);
|
const cache = await Storages.cache();
|
||||||
Storages.cache.event(event);
|
const reqmeister = await Storages.reqmeister();
|
||||||
Storages.reqmeister.event(event, { signal });
|
|
||||||
|
const [existing] = await cache.query([{ ids: [event.id], limit: 1 }]);
|
||||||
|
|
||||||
|
cache.event(event);
|
||||||
|
reqmeister.event(event, { signal });
|
||||||
|
|
||||||
return !!existing;
|
return !!existing;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Hydrate the event with the user, if applicable. */
|
/** Hydrate the event with the user, if applicable. */
|
||||||
async function hydrateEvent(event: DittoEvent, signal: AbortSignal): Promise<void> {
|
async function hydrateEvent(event: DittoEvent, signal: AbortSignal): Promise<void> {
|
||||||
await hydrateEvents({ events: [event], storage: Storages.db, signal });
|
await hydrateEvents({ events: [event], store: await Storages.db(), signal });
|
||||||
|
|
||||||
const domain = await db
|
const domain = await db
|
||||||
.selectFrom('pubkey_domains')
|
.selectFrom('pubkey_domains')
|
||||||
|
@ -98,8 +103,9 @@ async function hydrateEvent(event: DittoEvent, signal: AbortSignal): Promise<voi
|
||||||
/** Maybe store the event, if eligible. */
|
/** Maybe store the event, if eligible. */
|
||||||
async function storeEvent(event: DittoEvent, signal?: AbortSignal): Promise<void> {
|
async function storeEvent(event: DittoEvent, signal?: AbortSignal): Promise<void> {
|
||||||
if (isEphemeralKind(event.kind)) return;
|
if (isEphemeralKind(event.kind)) return;
|
||||||
|
const store = await Storages.db();
|
||||||
|
|
||||||
const [deletion] = await Storages.db.query(
|
const [deletion] = await store.query(
|
||||||
[{ kinds: [5], authors: [Conf.pubkey, event.pubkey], '#e': [event.id], limit: 1 }],
|
[{ kinds: [5], authors: [Conf.pubkey, event.pubkey], '#e': [event.id], limit: 1 }],
|
||||||
{ signal },
|
{ signal },
|
||||||
);
|
);
|
||||||
|
@ -108,7 +114,7 @@ async function storeEvent(event: DittoEvent, signal?: AbortSignal): Promise<void
|
||||||
return Promise.reject(new RelayError('blocked', 'event was deleted'));
|
return Promise.reject(new RelayError('blocked', 'event was deleted'));
|
||||||
} else {
|
} else {
|
||||||
await updateStats(event).catch(debug);
|
await updateStats(event).catch(debug);
|
||||||
await Storages.db.event(event, { signal }).catch(debug);
|
await store.event(event, { signal }).catch(debug);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -153,17 +159,18 @@ async function parseMetadata(event: NostrEvent, signal: AbortSignal): Promise<vo
|
||||||
async function processDeletions(event: NostrEvent, signal: AbortSignal): Promise<void> {
|
async function processDeletions(event: NostrEvent, signal: AbortSignal): Promise<void> {
|
||||||
if (event.kind === 5) {
|
if (event.kind === 5) {
|
||||||
const ids = getTagSet(event.tags, 'e');
|
const ids = getTagSet(event.tags, 'e');
|
||||||
|
const store = await Storages.db();
|
||||||
|
|
||||||
if (event.pubkey === Conf.pubkey) {
|
if (event.pubkey === Conf.pubkey) {
|
||||||
await Storages.db.remove([{ ids: [...ids] }], { signal });
|
await store.remove([{ ids: [...ids] }], { signal });
|
||||||
} else {
|
} else {
|
||||||
const events = await Storages.db.query(
|
const events = await store.query(
|
||||||
[{ ids: [...ids], authors: [event.pubkey] }],
|
[{ ids: [...ids], authors: [event.pubkey] }],
|
||||||
{ signal },
|
{ signal },
|
||||||
);
|
);
|
||||||
|
|
||||||
const deleteIds = events.map(({ id }) => id);
|
const deleteIds = events.map(({ id }) => id);
|
||||||
await Storages.db.remove([{ ids: deleteIds }], { signal });
|
await store.remove([{ ids: deleteIds }], { signal });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -189,19 +196,22 @@ async function trackHashtags(event: NostrEvent): Promise<void> {
|
||||||
|
|
||||||
/** Queue related events to fetch. */
|
/** Queue related events to fetch. */
|
||||||
async function fetchRelatedEvents(event: DittoEvent) {
|
async function fetchRelatedEvents(event: DittoEvent) {
|
||||||
|
const cache = await Storages.cache();
|
||||||
|
const reqmeister = await Storages.reqmeister();
|
||||||
|
|
||||||
if (!event.author) {
|
if (!event.author) {
|
||||||
const signal = AbortSignal.timeout(3000);
|
const signal = AbortSignal.timeout(3000);
|
||||||
Storages.reqmeister.query([{ kinds: [0], authors: [event.pubkey] }], { signal })
|
reqmeister.query([{ kinds: [0], authors: [event.pubkey] }], { signal })
|
||||||
.then((events) => Promise.allSettled(events.map((event) => handleEvent(event, signal))))
|
.then((events) => Promise.allSettled(events.map((event) => handleEvent(event, signal))))
|
||||||
.catch(() => {});
|
.catch(() => {});
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const [name, id] of event.tags) {
|
for (const [name, id] of event.tags) {
|
||||||
if (name === 'e') {
|
if (name === 'e') {
|
||||||
const { count } = await Storages.cache.count([{ ids: [id] }]);
|
const { count } = await cache.count([{ ids: [id] }]);
|
||||||
if (!count) {
|
if (!count) {
|
||||||
const signal = AbortSignal.timeout(3000);
|
const signal = AbortSignal.timeout(3000);
|
||||||
Storages.reqmeister.query([{ ids: [id] }], { signal })
|
reqmeister.query([{ ids: [id] }], { signal })
|
||||||
.then((events) => Promise.allSettled(events.map((event) => handleEvent(event, signal))))
|
.then((events) => Promise.allSettled(events.map((event) => handleEvent(event, signal))))
|
||||||
.catch(() => {});
|
.catch(() => {});
|
||||||
}
|
}
|
||||||
|
@ -272,7 +282,8 @@ function isFresh(event: NostrEvent): boolean {
|
||||||
/** Distribute the event through active subscriptions. */
|
/** Distribute the event through active subscriptions. */
|
||||||
async function streamOut(event: NostrEvent): Promise<void> {
|
async function streamOut(event: NostrEvent): Promise<void> {
|
||||||
if (isFresh(event)) {
|
if (isFresh(event)) {
|
||||||
await Storages.pubsub.event(event);
|
const pubsub = await Storages.pubsub();
|
||||||
|
await pubsub.event(event);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -34,7 +34,9 @@ export class DVM {
|
||||||
return DVM.feedback(event, 'error', `Forbidden user: ${user}`);
|
return DVM.feedback(event, 'error', `Forbidden user: ${user}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const [label] = await Storages.db.query([{
|
const store = await Storages.db();
|
||||||
|
|
||||||
|
const [label] = await store.query([{
|
||||||
kinds: [1985],
|
kinds: [1985],
|
||||||
authors: [admin],
|
authors: [admin],
|
||||||
'#L': ['nip05'],
|
'#L': ['nip05'],
|
||||||
|
|
|
@ -25,6 +25,7 @@ const getEvent = async (
|
||||||
opts: GetEventOpts = {},
|
opts: GetEventOpts = {},
|
||||||
): Promise<DittoEvent | undefined> => {
|
): Promise<DittoEvent | undefined> => {
|
||||||
debug(`getEvent: ${id}`);
|
debug(`getEvent: ${id}`);
|
||||||
|
const store = await Storages.optimizer();
|
||||||
const { kind, signal = AbortSignal.timeout(1000) } = opts;
|
const { kind, signal = AbortSignal.timeout(1000) } = opts;
|
||||||
|
|
||||||
const filter: NostrFilter = { ids: [id], limit: 1 };
|
const filter: NostrFilter = { ids: [id], limit: 1 };
|
||||||
|
@ -32,23 +33,25 @@ const getEvent = async (
|
||||||
filter.kinds = [kind];
|
filter.kinds = [kind];
|
||||||
}
|
}
|
||||||
|
|
||||||
return await Storages.optimizer.query([filter], { limit: 1, signal })
|
return await store.query([filter], { limit: 1, signal })
|
||||||
.then((events) => hydrateEvents({ events, storage: Storages.optimizer, signal }))
|
.then((events) => hydrateEvents({ events, store, signal }))
|
||||||
.then(([event]) => event);
|
.then(([event]) => event);
|
||||||
};
|
};
|
||||||
|
|
||||||
/** Get a Nostr `set_medatadata` event for a user's pubkey. */
|
/** Get a Nostr `set_medatadata` event for a user's pubkey. */
|
||||||
const getAuthor = async (pubkey: string, opts: GetEventOpts = {}): Promise<NostrEvent | undefined> => {
|
const getAuthor = async (pubkey: string, opts: GetEventOpts = {}): Promise<NostrEvent | undefined> => {
|
||||||
|
const store = await Storages.optimizer();
|
||||||
const { signal = AbortSignal.timeout(1000) } = opts;
|
const { signal = AbortSignal.timeout(1000) } = opts;
|
||||||
|
|
||||||
return await Storages.optimizer.query([{ authors: [pubkey], kinds: [0], limit: 1 }], { limit: 1, signal })
|
return await store.query([{ authors: [pubkey], kinds: [0], limit: 1 }], { limit: 1, signal })
|
||||||
.then((events) => hydrateEvents({ events, storage: Storages.optimizer, signal }))
|
.then((events) => hydrateEvents({ events, store, signal }))
|
||||||
.then(([event]) => event);
|
.then(([event]) => event);
|
||||||
};
|
};
|
||||||
|
|
||||||
/** Get users the given pubkey follows. */
|
/** Get users the given pubkey follows. */
|
||||||
const getFollows = async (pubkey: string, signal?: AbortSignal): Promise<NostrEvent | undefined> => {
|
const getFollows = async (pubkey: string, signal?: AbortSignal): Promise<NostrEvent | undefined> => {
|
||||||
const [event] = await Storages.db.query([{ authors: [pubkey], kinds: [3], limit: 1 }], { limit: 1, signal });
|
const store = await Storages.db();
|
||||||
|
const [event] = await store.query([{ authors: [pubkey], kinds: [3], limit: 1 }], { limit: 1, signal });
|
||||||
return event;
|
return event;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -84,15 +87,18 @@ async function getAncestors(event: NostrEvent, result: NostrEvent[] = []): Promi
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getDescendants(eventId: string, signal = AbortSignal.timeout(2000)): Promise<NostrEvent[]> {
|
async function getDescendants(eventId: string, signal = AbortSignal.timeout(2000)): Promise<NostrEvent[]> {
|
||||||
const events = await Storages.db.query([{ kinds: [1], '#e': [eventId] }], { limit: 200, signal });
|
const store = await Storages.db();
|
||||||
return hydrateEvents({ events, storage: Storages.db, signal });
|
const events = await store.query([{ kinds: [1], '#e': [eventId] }], { limit: 200, signal });
|
||||||
|
return hydrateEvents({ events, store, signal });
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Returns whether the pubkey is followed by a local user. */
|
/** Returns whether the pubkey is followed by a local user. */
|
||||||
async function isLocallyFollowed(pubkey: string): Promise<boolean> {
|
async function isLocallyFollowed(pubkey: string): Promise<boolean> {
|
||||||
const { host } = Conf.url;
|
const { host } = Conf.url;
|
||||||
|
|
||||||
const [event] = await Storages.db.query(
|
const store = await Storages.db();
|
||||||
|
|
||||||
|
const [event] = await store.query(
|
||||||
[{ kinds: [3], '#p': [pubkey], search: `domain:${host}`, limit: 1 }],
|
[{ kinds: [3], '#p': [pubkey], search: `domain:${host}`, limit: 1 }],
|
||||||
{ limit: 1 },
|
{ limit: 1 },
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
// deno-lint-ignore-file require-await
|
// deno-lint-ignore-file require-await
|
||||||
import { NConnectSigner } from '@nostrify/nostrify';
|
import { NConnectSigner, NostrEvent, NostrSigner } from '@nostrify/nostrify';
|
||||||
|
|
||||||
import { AdminSigner } from '@/signers/AdminSigner.ts';
|
import { AdminSigner } from '@/signers/AdminSigner.ts';
|
||||||
import { Storages } from '@/storages.ts';
|
import { Storages } from '@/storages.ts';
|
||||||
|
@ -9,24 +9,55 @@ import { Storages } from '@/storages.ts';
|
||||||
*
|
*
|
||||||
* Simple extension of nostrify's `NConnectSigner`, with our options to keep it DRY.
|
* Simple extension of nostrify's `NConnectSigner`, with our options to keep it DRY.
|
||||||
*/
|
*/
|
||||||
export class ConnectSigner extends NConnectSigner {
|
export class ConnectSigner implements NostrSigner {
|
||||||
private _pubkey: string;
|
private signer: Promise<NConnectSigner>;
|
||||||
|
|
||||||
constructor(pubkey: string, private relays?: string[]) {
|
constructor(private pubkey: string, private relays?: string[]) {
|
||||||
super({
|
this.signer = this.init();
|
||||||
pubkey,
|
}
|
||||||
|
|
||||||
|
async init(): Promise<NConnectSigner> {
|
||||||
|
return new NConnectSigner({
|
||||||
|
pubkey: this.pubkey,
|
||||||
// TODO: use a remote relay for `nprofile` signing (if present and `Conf.relay` isn't already in the list)
|
// TODO: use a remote relay for `nprofile` signing (if present and `Conf.relay` isn't already in the list)
|
||||||
relay: Storages.pubsub,
|
relay: await Storages.pubsub(),
|
||||||
signer: new AdminSigner(),
|
signer: new AdminSigner(),
|
||||||
timeout: 60000,
|
timeout: 60000,
|
||||||
});
|
});
|
||||||
|
|
||||||
this._pubkey = pubkey;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async signEvent(event: Omit<NostrEvent, 'id' | 'pubkey' | 'sig'>): Promise<NostrEvent> {
|
||||||
|
const signer = await this.signer;
|
||||||
|
return signer.signEvent(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
readonly nip04 = {
|
||||||
|
encrypt: async (pubkey: string, plaintext: string): Promise<string> => {
|
||||||
|
const signer = await this.signer;
|
||||||
|
return signer.nip04.encrypt(pubkey, plaintext);
|
||||||
|
},
|
||||||
|
|
||||||
|
decrypt: async (pubkey: string, ciphertext: string): Promise<string> => {
|
||||||
|
const signer = await this.signer;
|
||||||
|
return signer.nip04.decrypt(pubkey, ciphertext);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
readonly nip44 = {
|
||||||
|
encrypt: async (pubkey: string, plaintext: string): Promise<string> => {
|
||||||
|
const signer = await this.signer;
|
||||||
|
return signer.nip44.encrypt(pubkey, plaintext);
|
||||||
|
},
|
||||||
|
|
||||||
|
decrypt: async (pubkey: string, ciphertext: string): Promise<string> => {
|
||||||
|
const signer = await this.signer;
|
||||||
|
return signer.nip44.decrypt(pubkey, ciphertext);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
// Prevent unnecessary NIP-46 round-trips.
|
// Prevent unnecessary NIP-46 round-trips.
|
||||||
async getPublicKey(): Promise<string> {
|
async getPublicKey(): Promise<string> {
|
||||||
return this._pubkey;
|
return this.pubkey;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Get the user's relays if they passed in an `nprofile` auth token. */
|
/** Get the user's relays if they passed in an `nprofile` auth token. */
|
||||||
|
|
|
@ -4,13 +4,7 @@ import { DittoEvent } from '@/interfaces/DittoEvent.ts';
|
||||||
import { getTagSet } from '@/tags.ts';
|
import { getTagSet } from '@/tags.ts';
|
||||||
|
|
||||||
export class UserStore implements NStore {
|
export class UserStore implements NStore {
|
||||||
private store: NStore;
|
constructor(private pubkey: string, private store: NStore) {}
|
||||||
private pubkey: string;
|
|
||||||
|
|
||||||
constructor(pubkey: string, store: NStore) {
|
|
||||||
this.pubkey = pubkey;
|
|
||||||
this.store = store;
|
|
||||||
}
|
|
||||||
|
|
||||||
async event(event: NostrEvent, opts?: { signal?: AbortSignal }): Promise<void> {
|
async event(event: NostrEvent, opts?: { signal?: AbortSignal }): Promise<void> {
|
||||||
return await this.store.event(event, opts);
|
return await this.store.event(event, opts);
|
||||||
|
@ -21,12 +15,11 @@ export class UserStore implements NStore {
|
||||||
* https://github.com/nostr-protocol/nips/blob/master/51.md#standard-lists
|
* https://github.com/nostr-protocol/nips/blob/master/51.md#standard-lists
|
||||||
*/
|
*/
|
||||||
async query(filters: NostrFilter[], opts: { signal?: AbortSignal; limit?: number } = {}): Promise<DittoEvent[]> {
|
async query(filters: NostrFilter[], opts: { signal?: AbortSignal; limit?: number } = {}): Promise<DittoEvent[]> {
|
||||||
const allEvents = await this.store.query(filters, opts);
|
const events = await this.store.query(filters, opts);
|
||||||
|
const pubkeys = await this.getMutedPubkeys();
|
||||||
|
|
||||||
const mutedPubkeys = await this.getMutedPubkeys();
|
return events.filter((event) => {
|
||||||
|
return event.kind === 0 || !pubkeys.has(event.pubkey);
|
||||||
return allEvents.filter((event) => {
|
|
||||||
return event.kind === 0 || mutedPubkeys.has(event.pubkey) === false;
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -17,7 +17,7 @@ Deno.test('hydrateEvents(): author --- WITHOUT stats', async () => {
|
||||||
|
|
||||||
await hydrateEvents({
|
await hydrateEvents({
|
||||||
events: [event1],
|
events: [event1],
|
||||||
storage: db,
|
store: db,
|
||||||
});
|
});
|
||||||
|
|
||||||
const expectedEvent = { ...event1, author: event0 };
|
const expectedEvent = { ...event1, author: event0 };
|
||||||
|
@ -40,7 +40,7 @@ Deno.test('hydrateEvents(): repost --- WITHOUT stats', async () => {
|
||||||
|
|
||||||
await hydrateEvents({
|
await hydrateEvents({
|
||||||
events: [event6],
|
events: [event6],
|
||||||
storage: db,
|
store: db,
|
||||||
});
|
});
|
||||||
|
|
||||||
const expectedEvent6 = {
|
const expectedEvent6 = {
|
||||||
|
@ -67,7 +67,7 @@ Deno.test('hydrateEvents(): quote repost --- WITHOUT stats', async () => {
|
||||||
|
|
||||||
await hydrateEvents({
|
await hydrateEvents({
|
||||||
events: [event1quoteRepost],
|
events: [event1quoteRepost],
|
||||||
storage: db,
|
store: db,
|
||||||
});
|
});
|
||||||
|
|
||||||
const expectedEvent1quoteRepost = {
|
const expectedEvent1quoteRepost = {
|
||||||
|
@ -95,7 +95,7 @@ Deno.test('hydrateEvents(): repost of quote repost --- WITHOUT stats', async ()
|
||||||
|
|
||||||
await hydrateEvents({
|
await hydrateEvents({
|
||||||
events: [event6],
|
events: [event6],
|
||||||
storage: db,
|
store: db,
|
||||||
});
|
});
|
||||||
|
|
||||||
const expectedEvent6 = {
|
const expectedEvent6 = {
|
||||||
|
@ -122,7 +122,7 @@ Deno.test('hydrateEvents(): report pubkey and post // kind 1984 --- WITHOUT stat
|
||||||
|
|
||||||
await hydrateEvents({
|
await hydrateEvents({
|
||||||
events: [reportEvent],
|
events: [reportEvent],
|
||||||
storage: db,
|
store: db,
|
||||||
});
|
});
|
||||||
|
|
||||||
const expectedEvent: DittoEvent = {
|
const expectedEvent: DittoEvent = {
|
||||||
|
|
|
@ -8,13 +8,13 @@ import { Conf } from '@/config.ts';
|
||||||
|
|
||||||
interface HydrateOpts {
|
interface HydrateOpts {
|
||||||
events: DittoEvent[];
|
events: DittoEvent[];
|
||||||
storage: NStore;
|
store: NStore;
|
||||||
signal?: AbortSignal;
|
signal?: AbortSignal;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Hydrate events using the provided storage. */
|
/** Hydrate events using the provided storage. */
|
||||||
async function hydrateEvents(opts: HydrateOpts): Promise<DittoEvent[]> {
|
async function hydrateEvents(opts: HydrateOpts): Promise<DittoEvent[]> {
|
||||||
const { events, storage, signal } = opts;
|
const { events, store, signal } = opts;
|
||||||
|
|
||||||
if (!events.length) {
|
if (!events.length) {
|
||||||
return events;
|
return events;
|
||||||
|
@ -22,31 +22,31 @@ async function hydrateEvents(opts: HydrateOpts): Promise<DittoEvent[]> {
|
||||||
|
|
||||||
const cache = [...events];
|
const cache = [...events];
|
||||||
|
|
||||||
for (const event of await gatherReposts({ events: cache, storage, signal })) {
|
for (const event of await gatherReposts({ events: cache, store, signal })) {
|
||||||
cache.push(event);
|
cache.push(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const event of await gatherReacted({ events: cache, storage, signal })) {
|
for (const event of await gatherReacted({ events: cache, store, signal })) {
|
||||||
cache.push(event);
|
cache.push(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const event of await gatherQuotes({ events: cache, storage, signal })) {
|
for (const event of await gatherQuotes({ events: cache, store, signal })) {
|
||||||
cache.push(event);
|
cache.push(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const event of await gatherAuthors({ events: cache, storage, signal })) {
|
for (const event of await gatherAuthors({ events: cache, store, signal })) {
|
||||||
cache.push(event);
|
cache.push(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const event of await gatherUsers({ events: cache, storage, signal })) {
|
for (const event of await gatherUsers({ events: cache, store, signal })) {
|
||||||
cache.push(event);
|
cache.push(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const event of await gatherReportedProfiles({ events: cache, storage, signal })) {
|
for (const event of await gatherReportedProfiles({ events: cache, store, signal })) {
|
||||||
cache.push(event);
|
cache.push(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const event of await gatherReportedNotes({ events: cache, storage, signal })) {
|
for (const event of await gatherReportedNotes({ events: cache, store, signal })) {
|
||||||
cache.push(event);
|
cache.push(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -123,7 +123,7 @@ function assembleEvents(
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Collect reposts from the events. */
|
/** Collect reposts from the events. */
|
||||||
function gatherReposts({ events, storage, signal }: HydrateOpts): Promise<DittoEvent[]> {
|
function gatherReposts({ events, store, signal }: HydrateOpts): Promise<DittoEvent[]> {
|
||||||
const ids = new Set<string>();
|
const ids = new Set<string>();
|
||||||
|
|
||||||
for (const event of events) {
|
for (const event of events) {
|
||||||
|
@ -135,14 +135,14 @@ function gatherReposts({ events, storage, signal }: HydrateOpts): Promise<DittoE
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return storage.query(
|
return store.query(
|
||||||
[{ ids: [...ids], limit: ids.size }],
|
[{ ids: [...ids], limit: ids.size }],
|
||||||
{ signal },
|
{ signal },
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Collect events being reacted to by the events. */
|
/** Collect events being reacted to by the events. */
|
||||||
function gatherReacted({ events, storage, signal }: HydrateOpts): Promise<DittoEvent[]> {
|
function gatherReacted({ events, store, signal }: HydrateOpts): Promise<DittoEvent[]> {
|
||||||
const ids = new Set<string>();
|
const ids = new Set<string>();
|
||||||
|
|
||||||
for (const event of events) {
|
for (const event of events) {
|
||||||
|
@ -154,14 +154,14 @@ function gatherReacted({ events, storage, signal }: HydrateOpts): Promise<DittoE
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return storage.query(
|
return store.query(
|
||||||
[{ ids: [...ids], limit: ids.size }],
|
[{ ids: [...ids], limit: ids.size }],
|
||||||
{ signal },
|
{ signal },
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Collect quotes from the events. */
|
/** Collect quotes from the events. */
|
||||||
function gatherQuotes({ events, storage, signal }: HydrateOpts): Promise<DittoEvent[]> {
|
function gatherQuotes({ events, store, signal }: HydrateOpts): Promise<DittoEvent[]> {
|
||||||
const ids = new Set<string>();
|
const ids = new Set<string>();
|
||||||
|
|
||||||
for (const event of events) {
|
for (const event of events) {
|
||||||
|
@ -173,34 +173,34 @@ function gatherQuotes({ events, storage, signal }: HydrateOpts): Promise<DittoEv
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return storage.query(
|
return store.query(
|
||||||
[{ ids: [...ids], limit: ids.size }],
|
[{ ids: [...ids], limit: ids.size }],
|
||||||
{ signal },
|
{ signal },
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Collect authors from the events. */
|
/** Collect authors from the events. */
|
||||||
function gatherAuthors({ events, storage, signal }: HydrateOpts): Promise<DittoEvent[]> {
|
function gatherAuthors({ events, store, signal }: HydrateOpts): Promise<DittoEvent[]> {
|
||||||
const pubkeys = new Set(events.map((event) => event.pubkey));
|
const pubkeys = new Set(events.map((event) => event.pubkey));
|
||||||
|
|
||||||
return storage.query(
|
return store.query(
|
||||||
[{ kinds: [0], authors: [...pubkeys], limit: pubkeys.size }],
|
[{ kinds: [0], authors: [...pubkeys], limit: pubkeys.size }],
|
||||||
{ signal },
|
{ signal },
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Collect users from the events. */
|
/** Collect users from the events. */
|
||||||
function gatherUsers({ events, storage, signal }: HydrateOpts): Promise<DittoEvent[]> {
|
function gatherUsers({ events, store, signal }: HydrateOpts): Promise<DittoEvent[]> {
|
||||||
const pubkeys = new Set(events.map((event) => event.pubkey));
|
const pubkeys = new Set(events.map((event) => event.pubkey));
|
||||||
|
|
||||||
return storage.query(
|
return store.query(
|
||||||
[{ kinds: [30361], authors: [Conf.pubkey], '#d': [...pubkeys], limit: pubkeys.size }],
|
[{ kinds: [30361], authors: [Conf.pubkey], '#d': [...pubkeys], limit: pubkeys.size }],
|
||||||
{ signal },
|
{ signal },
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Collect reported notes from the events. */
|
/** Collect reported notes from the events. */
|
||||||
function gatherReportedNotes({ events, storage, signal }: HydrateOpts): Promise<DittoEvent[]> {
|
function gatherReportedNotes({ events, store, signal }: HydrateOpts): Promise<DittoEvent[]> {
|
||||||
const ids = new Set<string>();
|
const ids = new Set<string>();
|
||||||
for (const event of events) {
|
for (const event of events) {
|
||||||
if (event.kind === 1984) {
|
if (event.kind === 1984) {
|
||||||
|
@ -213,14 +213,14 @@ function gatherReportedNotes({ events, storage, signal }: HydrateOpts): Promise<
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return storage.query(
|
return store.query(
|
||||||
[{ kinds: [1], ids: [...ids], limit: ids.size }],
|
[{ kinds: [1], ids: [...ids], limit: ids.size }],
|
||||||
{ signal },
|
{ signal },
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Collect reported profiles from the events. */
|
/** Collect reported profiles from the events. */
|
||||||
function gatherReportedProfiles({ events, storage, signal }: HydrateOpts): Promise<DittoEvent[]> {
|
function gatherReportedProfiles({ events, store, signal }: HydrateOpts): Promise<DittoEvent[]> {
|
||||||
const pubkeys = new Set<string>();
|
const pubkeys = new Set<string>();
|
||||||
|
|
||||||
for (const event of events) {
|
for (const event of events) {
|
||||||
|
@ -232,7 +232,7 @@ function gatherReportedProfiles({ events, storage, signal }: HydrateOpts): Promi
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return storage.query(
|
return store.query(
|
||||||
[{ kinds: [0], authors: [...pubkeys], limit: pubkeys.size }],
|
[{ kinds: [0], authors: [...pubkeys], limit: pubkeys.size }],
|
||||||
{ signal },
|
{ signal },
|
||||||
);
|
);
|
||||||
|
|
|
@ -13,6 +13,7 @@ import { RelayPoolWorker } from 'nostr-relaypool';
|
||||||
import { getFilterLimit, matchFilters } from 'nostr-tools';
|
import { getFilterLimit, matchFilters } from 'nostr-tools';
|
||||||
|
|
||||||
import { Conf } from '@/config.ts';
|
import { Conf } from '@/config.ts';
|
||||||
|
import { Storages } from '@/storages.ts';
|
||||||
import { purifyEvent } from '@/storages/hydrate.ts';
|
import { purifyEvent } from '@/storages/hydrate.ts';
|
||||||
import { abortError } from '@/utils/abort.ts';
|
import { abortError } from '@/utils/abort.ts';
|
||||||
import { getRelays } from '@/utils/outbox.ts';
|
import { getRelays } from '@/utils/outbox.ts';
|
||||||
|
@ -35,7 +36,7 @@ class PoolStore implements NRelay {
|
||||||
async event(event: NostrEvent, opts: { signal?: AbortSignal } = {}): Promise<void> {
|
async event(event: NostrEvent, opts: { signal?: AbortSignal } = {}): Promise<void> {
|
||||||
if (opts.signal?.aborted) return Promise.reject(abortError());
|
if (opts.signal?.aborted) return Promise.reject(abortError());
|
||||||
|
|
||||||
const relaySet = await getRelays(event.pubkey);
|
const relaySet = await getRelays(await Storages.db(), event.pubkey);
|
||||||
relaySet.delete(Conf.relay);
|
relaySet.delete(Conf.relay);
|
||||||
|
|
||||||
const relays = [...relaySet].slice(0, 4);
|
const relays = [...relaySet].slice(0, 4);
|
||||||
|
|
|
@ -48,7 +48,7 @@ class SearchStore implements NStore {
|
||||||
|
|
||||||
return hydrateEvents({
|
return hydrateEvents({
|
||||||
events,
|
events,
|
||||||
storage: this.#hydrator,
|
store: this.#hydrator,
|
||||||
signal: opts?.signal,
|
signal: opts?.signal,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -42,7 +42,7 @@ async function createEvent(t: EventStub, c: AppContext): Promise<NostrEvent> {
|
||||||
/** Filter for fetching an existing event to update. */
|
/** Filter for fetching an existing event to update. */
|
||||||
interface UpdateEventFilter extends NostrFilter {
|
interface UpdateEventFilter extends NostrFilter {
|
||||||
kinds: [number];
|
kinds: [number];
|
||||||
limit?: 1;
|
limit: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Fetch existing event, update it, then publish the new event. */
|
/** Fetch existing event, update it, then publish the new event. */
|
||||||
|
@ -51,7 +51,8 @@ async function updateEvent<E extends EventStub>(
|
||||||
fn: (prev: NostrEvent | undefined) => E,
|
fn: (prev: NostrEvent | undefined) => E,
|
||||||
c: AppContext,
|
c: AppContext,
|
||||||
): Promise<NostrEvent> {
|
): Promise<NostrEvent> {
|
||||||
const [prev] = await Storages.db.query([filter], { limit: 1, signal: c.req.raw.signal });
|
const store = await Storages.db();
|
||||||
|
const [prev] = await store.query([filter], { signal: c.req.raw.signal });
|
||||||
return createEvent(fn(prev), c);
|
return createEvent(fn(prev), c);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -101,7 +102,8 @@ async function updateAdminEvent<E extends EventStub>(
|
||||||
fn: (prev: NostrEvent | undefined) => E,
|
fn: (prev: NostrEvent | undefined) => E,
|
||||||
c: AppContext,
|
c: AppContext,
|
||||||
): Promise<NostrEvent> {
|
): Promise<NostrEvent> {
|
||||||
const [prev] = await Storages.db.query([filter], { limit: 1, signal: c.req.raw.signal });
|
const store = await Storages.db();
|
||||||
|
const [prev] = await store.query([filter], { limit: 1, signal: c.req.raw.signal });
|
||||||
return createAdminEvent(fn(prev), c);
|
return createAdminEvent(fn(prev), c);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -110,7 +112,8 @@ async function publishEvent(event: NostrEvent, c: AppContext): Promise<NostrEven
|
||||||
debug('EVENT', event);
|
debug('EVENT', event);
|
||||||
try {
|
try {
|
||||||
await pipeline.handleEvent(event, c.req.raw.signal);
|
await pipeline.handleEvent(event, c.req.raw.signal);
|
||||||
await Storages.client.event(event);
|
const client = await Storages.client();
|
||||||
|
await client.event(event);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e instanceof RelayError) {
|
if (e instanceof RelayError) {
|
||||||
throw new HTTPException(422, {
|
throw new HTTPException(422, {
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import { Conf } from '@/config.ts';
|
import { Conf } from '@/config.ts';
|
||||||
|
import { Storages } from '@/storages.ts';
|
||||||
import { getInstanceMetadata } from '@/utils/instance.ts';
|
import { getInstanceMetadata } from '@/utils/instance.ts';
|
||||||
|
|
||||||
/** NIP-46 client-connect metadata. */
|
/** NIP-46 client-connect metadata. */
|
||||||
|
@ -11,7 +12,7 @@ interface ConnectMetadata {
|
||||||
/** Get NIP-46 `nostrconnect://` URI for the Ditto server. */
|
/** Get NIP-46 `nostrconnect://` URI for the Ditto server. */
|
||||||
export async function getClientConnectUri(signal?: AbortSignal): Promise<string> {
|
export async function getClientConnectUri(signal?: AbortSignal): Promise<string> {
|
||||||
const uri = new URL('nostrconnect://');
|
const uri = new URL('nostrconnect://');
|
||||||
const { name, tagline } = await getInstanceMetadata(signal);
|
const { name, tagline } = await getInstanceMetadata(await Storages.db(), signal);
|
||||||
|
|
||||||
const metadata: ConnectMetadata = {
|
const metadata: ConnectMetadata = {
|
||||||
name,
|
name,
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
import { NostrEvent, NostrMetadata, NSchema as n } from '@nostrify/nostrify';
|
import { NostrEvent, NostrMetadata, NSchema as n, NStore } from '@nostrify/nostrify';
|
||||||
|
|
||||||
import { Conf } from '@/config.ts';
|
import { Conf } from '@/config.ts';
|
||||||
import { serverMetaSchema } from '@/schemas/nostr.ts';
|
import { serverMetaSchema } from '@/schemas/nostr.ts';
|
||||||
import { Storages } from '@/storages.ts';
|
|
||||||
|
|
||||||
/** Like NostrMetadata, but some fields are required and also contains some extra fields. */
|
/** Like NostrMetadata, but some fields are required and also contains some extra fields. */
|
||||||
export interface InstanceMetadata extends NostrMetadata {
|
export interface InstanceMetadata extends NostrMetadata {
|
||||||
|
@ -14,8 +13,8 @@ export interface InstanceMetadata extends NostrMetadata {
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Get and parse instance metadata from the kind 0 of the admin user. */
|
/** Get and parse instance metadata from the kind 0 of the admin user. */
|
||||||
export async function getInstanceMetadata(signal?: AbortSignal): Promise<InstanceMetadata> {
|
export async function getInstanceMetadata(store: NStore, signal?: AbortSignal): Promise<InstanceMetadata> {
|
||||||
const [event] = await Storages.db.query(
|
const [event] = await store.query(
|
||||||
[{ kinds: [0], authors: [Conf.pubkey], limit: 1 }],
|
[{ kinds: [0], authors: [Conf.pubkey], limit: 1 }],
|
||||||
{ signal },
|
{ signal },
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { NIP05 } from '@nostrify/nostrify';
|
import { NIP05, NStore } from '@nostrify/nostrify';
|
||||||
import Debug from '@soapbox/stickynotes/debug';
|
import Debug from '@soapbox/stickynotes/debug';
|
||||||
import { nip19 } from 'nostr-tools';
|
import { nip19 } from 'nostr-tools';
|
||||||
|
|
||||||
|
@ -16,7 +16,8 @@ const nip05Cache = new SimpleLRU<string, nip19.ProfilePointer>(
|
||||||
const [name, domain] = key.split('@');
|
const [name, domain] = key.split('@');
|
||||||
try {
|
try {
|
||||||
if (domain === Conf.url.host) {
|
if (domain === Conf.url.host) {
|
||||||
const pointer = await localNip05Lookup(name);
|
const store = await Storages.db();
|
||||||
|
const pointer = await localNip05Lookup(store, name);
|
||||||
if (pointer) {
|
if (pointer) {
|
||||||
debug(`Found: ${key} is ${pointer.pubkey}`);
|
debug(`Found: ${key} is ${pointer.pubkey}`);
|
||||||
return pointer;
|
return pointer;
|
||||||
|
@ -36,8 +37,8 @@ const nip05Cache = new SimpleLRU<string, nip19.ProfilePointer>(
|
||||||
{ max: 500, ttl: Time.hours(1) },
|
{ max: 500, ttl: Time.hours(1) },
|
||||||
);
|
);
|
||||||
|
|
||||||
async function localNip05Lookup(name: string): Promise<nip19.ProfilePointer | undefined> {
|
async function localNip05Lookup(store: NStore, name: string): Promise<nip19.ProfilePointer | undefined> {
|
||||||
const [label] = await Storages.db.query([{
|
const [label] = await store.query([{
|
||||||
kinds: [1985],
|
kinds: [1985],
|
||||||
authors: [Conf.pubkey],
|
authors: [Conf.pubkey],
|
||||||
'#L': ['nip05'],
|
'#L': ['nip05'],
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
import { Conf } from '@/config.ts';
|
import { NStore } from '@nostrify/nostrify';
|
||||||
import { Storages } from '@/storages.ts';
|
|
||||||
|
|
||||||
export async function getRelays(pubkey: string): Promise<Set<string>> {
|
import { Conf } from '@/config.ts';
|
||||||
|
|
||||||
|
export async function getRelays(store: NStore, pubkey: string): Promise<Set<string>> {
|
||||||
const relays = new Set<`wss://${string}`>();
|
const relays = new Set<`wss://${string}`>();
|
||||||
|
|
||||||
const events = await Storages.db.query([
|
const events = await store.query([
|
||||||
{ kinds: [10002], authors: [pubkey, Conf.pubkey], limit: 2 },
|
{ kinds: [10002], authors: [pubkey, Conf.pubkey], limit: 2 },
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|
18
src/views.ts
18
src/views.ts
|
@ -12,15 +12,16 @@ async function renderEventAccounts(c: AppContext, filters: NostrFilter[], signal
|
||||||
return c.json([]);
|
return c.json([]);
|
||||||
}
|
}
|
||||||
|
|
||||||
const events = await Storages.db.query(filters, { signal });
|
const store = await Storages.db();
|
||||||
|
const events = await store.query(filters, { signal });
|
||||||
const pubkeys = new Set(events.map(({ pubkey }) => pubkey));
|
const pubkeys = new Set(events.map(({ pubkey }) => pubkey));
|
||||||
|
|
||||||
if (!pubkeys.size) {
|
if (!pubkeys.size) {
|
||||||
return c.json([]);
|
return c.json([]);
|
||||||
}
|
}
|
||||||
|
|
||||||
const authors = await Storages.db.query([{ kinds: [0], authors: [...pubkeys] }], { signal })
|
const authors = await store.query([{ kinds: [0], authors: [...pubkeys] }], { signal })
|
||||||
.then((events) => hydrateEvents({ events, storage: Storages.db, signal }));
|
.then((events) => hydrateEvents({ events, store, signal }));
|
||||||
|
|
||||||
const accounts = await Promise.all(
|
const accounts = await Promise.all(
|
||||||
authors.map((event) => renderAccount(event)),
|
authors.map((event) => renderAccount(event)),
|
||||||
|
@ -32,8 +33,10 @@ async function renderEventAccounts(c: AppContext, filters: NostrFilter[], signal
|
||||||
async function renderAccounts(c: AppContext, authors: string[], signal = AbortSignal.timeout(1000)) {
|
async function renderAccounts(c: AppContext, authors: string[], signal = AbortSignal.timeout(1000)) {
|
||||||
const { since, until, limit } = paginationSchema.parse(c.req.query());
|
const { since, until, limit } = paginationSchema.parse(c.req.query());
|
||||||
|
|
||||||
const events = await Storages.db.query([{ kinds: [0], authors, since, until, limit }], { signal })
|
const store = await Storages.db();
|
||||||
.then((events) => hydrateEvents({ events, storage: Storages.db, signal }));
|
|
||||||
|
const events = await store.query([{ kinds: [0], authors, since, until, limit }], { signal })
|
||||||
|
.then((events) => hydrateEvents({ events, store, signal }));
|
||||||
|
|
||||||
const accounts = await Promise.all(
|
const accounts = await Promise.all(
|
||||||
events.map((event) => renderAccount(event)),
|
events.map((event) => renderAccount(event)),
|
||||||
|
@ -48,10 +51,11 @@ async function renderStatuses(c: AppContext, ids: string[], signal = AbortSignal
|
||||||
return c.json([]);
|
return c.json([]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const store = await Storages.db();
|
||||||
const { limit } = paginationSchema.parse(c.req.query());
|
const { limit } = paginationSchema.parse(c.req.query());
|
||||||
|
|
||||||
const events = await Storages.db.query([{ kinds: [1], ids, limit }], { signal })
|
const events = await store.query([{ kinds: [1], ids, limit }], { signal })
|
||||||
.then((events) => hydrateEvents({ events, storage: Storages.db, signal }));
|
.then((events) => hydrateEvents({ events, store, signal }));
|
||||||
|
|
||||||
if (!events.length) {
|
if (!events.length) {
|
||||||
return c.json([]);
|
return c.json([]);
|
||||||
|
|
|
@ -2,7 +2,9 @@ import { Storages } from '@/storages.ts';
|
||||||
import { hasTag } from '@/tags.ts';
|
import { hasTag } from '@/tags.ts';
|
||||||
|
|
||||||
async function renderRelationship(sourcePubkey: string, targetPubkey: string) {
|
async function renderRelationship(sourcePubkey: string, targetPubkey: string) {
|
||||||
const events = await Storages.db.query([
|
const db = await Storages.db();
|
||||||
|
|
||||||
|
const events = await db.query([
|
||||||
{ kinds: [3], authors: [sourcePubkey], limit: 1 },
|
{ kinds: [3], authors: [sourcePubkey], limit: 1 },
|
||||||
{ kinds: [3], authors: [targetPubkey], limit: 1 },
|
{ kinds: [3], authors: [targetPubkey], limit: 1 },
|
||||||
{ kinds: [10000], authors: [sourcePubkey], limit: 1 },
|
{ kinds: [10000], authors: [sourcePubkey], limit: 1 },
|
||||||
|
|
|
@ -22,7 +22,7 @@ interface RenderStatusOpts {
|
||||||
async function renderStatus(event: DittoEvent, opts: RenderStatusOpts): Promise<any> {
|
async function renderStatus(event: DittoEvent, opts: RenderStatusOpts): Promise<any> {
|
||||||
const { viewerPubkey, depth = 1 } = opts;
|
const { viewerPubkey, depth = 1 } = opts;
|
||||||
|
|
||||||
if (depth > 2 || depth < 0) return null;
|
if (depth > 2 || depth < 0) return;
|
||||||
|
|
||||||
const note = nip19.noteEncode(event.id);
|
const note = nip19.noteEncode(event.id);
|
||||||
|
|
||||||
|
@ -40,7 +40,10 @@ async function renderStatus(event: DittoEvent, opts: RenderStatusOpts): Promise<
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
|
|
||||||
const mentionedProfiles = await Storages.optimizer.query(
|
const db = await Storages.db();
|
||||||
|
const optimizer = await Storages.optimizer();
|
||||||
|
|
||||||
|
const mentionedProfiles = await optimizer.query(
|
||||||
[{ kinds: [0], authors: mentionedPubkeys, limit: mentionedPubkeys.length }],
|
[{ kinds: [0], authors: mentionedPubkeys, limit: mentionedPubkeys.length }],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -53,7 +56,7 @@ async function renderStatus(event: DittoEvent, opts: RenderStatusOpts): Promise<
|
||||||
),
|
),
|
||||||
firstUrl ? unfurlCardCached(firstUrl) : null,
|
firstUrl ? unfurlCardCached(firstUrl) : null,
|
||||||
viewerPubkey
|
viewerPubkey
|
||||||
? await Storages.db.query([
|
? await db.query([
|
||||||
{ kinds: [6], '#e': [event.id], authors: [viewerPubkey], limit: 1 },
|
{ kinds: [6], '#e': [event.id], authors: [viewerPubkey], limit: 1 },
|
||||||
{ kinds: [7], '#e': [event.id], authors: [viewerPubkey], limit: 1 },
|
{ kinds: [7], '#e': [event.id], authors: [viewerPubkey], limit: 1 },
|
||||||
{ kinds: [9734], '#e': [event.id], authors: [viewerPubkey], limit: 1 },
|
{ kinds: [9734], '#e': [event.id], authors: [viewerPubkey], limit: 1 },
|
||||||
|
|
Loading…
Reference in New Issue