mirror of
https://gitlab.com/soapbox-pub/ditto.git
synced 2025-12-06 11:29:46 +00:00
Refactor Storages to get lazy-loaded only when they are used
This commit is contained in:
parent
a6681a97d9
commit
c190d2c8ce
27 changed files with 175 additions and 140 deletions
|
|
@ -7,7 +7,7 @@ import { Conf } from '@/config.ts';
|
||||||
import { getAuthor, getFollowedPubkeys } from '@/queries.ts';
|
import { getAuthor, getFollowedPubkeys } from '@/queries.ts';
|
||||||
import { booleanParamSchema, fileSchema } from '@/schema.ts';
|
import { booleanParamSchema, fileSchema } from '@/schema.ts';
|
||||||
import { jsonMetaContentSchema } from '@/schemas/nostr.ts';
|
import { jsonMetaContentSchema } from '@/schemas/nostr.ts';
|
||||||
import { eventsDB, searchStore } from '@/storages.ts';
|
import { Storages } from '@/storages.ts';
|
||||||
import { addTag, deleteTag, findReplyTag, getTagSet } from '@/tags.ts';
|
import { addTag, deleteTag, findReplyTag, getTagSet } from '@/tags.ts';
|
||||||
import { uploadFile } from '@/upload.ts';
|
import { uploadFile } from '@/upload.ts';
|
||||||
import { nostrNow } from '@/utils.ts';
|
import { nostrNow } from '@/utils.ts';
|
||||||
|
|
@ -92,12 +92,12 @@ const accountSearchController: AppController = async (c) => {
|
||||||
|
|
||||||
const [event, events] = await Promise.all([
|
const [event, events] = await Promise.all([
|
||||||
lookupAccount(query),
|
lookupAccount(query),
|
||||||
searchStore.query([{ kinds: [0], search: query, limit: 20 }], { signal: c.req.raw.signal }),
|
Storages.search.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: eventsDB,
|
storage: Storages.db,
|
||||||
signal: c.req.raw.signal,
|
signal: c.req.raw.signal,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -143,7 +143,7 @@ const accountStatusesController: AppController = async (c) => {
|
||||||
const { signal } = c.req.raw;
|
const { signal } = c.req.raw;
|
||||||
|
|
||||||
if (pinned) {
|
if (pinned) {
|
||||||
const [pinEvent] = await eventsDB.query([{ kinds: [10001], authors: [pubkey], limit: 1 }], { signal });
|
const [pinEvent] = await Storages.db.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());
|
||||||
|
|
@ -164,8 +164,8 @@ const accountStatusesController: AppController = async (c) => {
|
||||||
filter['#t'] = [tagged];
|
filter['#t'] = [tagged];
|
||||||
}
|
}
|
||||||
|
|
||||||
const events = await eventsDB.query([filter], { signal })
|
const events = await Storages.db.query([filter], { signal })
|
||||||
.then((events) => hydrateEvents({ events, storage: eventsDB, signal }))
|
.then((events) => hydrateEvents({ events, storage: Storages.db, 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));
|
||||||
|
|
@ -306,7 +306,7 @@ 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 eventsDB.query(
|
const events7 = await Storages.db.query(
|
||||||
[{ kinds: [7], authors: [pubkey], ...params }],
|
[{ kinds: [7], authors: [pubkey], ...params }],
|
||||||
{ signal },
|
{ signal },
|
||||||
);
|
);
|
||||||
|
|
@ -315,8 +315,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 eventsDB.query([{ kinds: [1], ids }], { signal })
|
const events1 = await Storages.db.query([{ kinds: [1], ids }], { signal })
|
||||||
.then((events) => hydrateEvents({ events, storage: eventsDB, signal }));
|
.then((events) => hydrateEvents({ events, storage: Storages.db, signal }));
|
||||||
|
|
||||||
const statuses = await Promise.all(events1.map((event) => renderStatus(event, { viewerPubkey: c.get('pubkey') })));
|
const statuses = await Promise.all(events1.map((event) => renderStatus(event, { viewerPubkey: c.get('pubkey') })));
|
||||||
return paginated(c, events1, statuses);
|
return paginated(c, events1, statuses);
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ import { z } from 'zod';
|
||||||
import { type AppController } from '@/app.ts';
|
import { type AppController } from '@/app.ts';
|
||||||
import { Conf } from '@/config.ts';
|
import { Conf } from '@/config.ts';
|
||||||
import { booleanParamSchema } from '@/schema.ts';
|
import { booleanParamSchema } from '@/schema.ts';
|
||||||
import { eventsDB } from '@/storages.ts';
|
import { Storages } from '@/storages.ts';
|
||||||
import { renderAdminAccount } from '@/views/mastodon/admin-accounts.ts';
|
import { renderAdminAccount } from '@/views/mastodon/admin-accounts.ts';
|
||||||
import { paginated, paginationSchema } from '@/utils/api.ts';
|
import { paginated, paginationSchema } from '@/utils/api.ts';
|
||||||
|
|
||||||
|
|
@ -41,9 +41,9 @@ const adminAccountsController: AppController = async (c) => {
|
||||||
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 eventsDB.query([{ kinds: [30361], authors: [Conf.pubkey], since, until, limit }], { signal });
|
const events = await Storages.db.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 eventsDB.query([{ kinds: [0], authors: pubkeys }], { signal });
|
const authors = await Storages.db.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];
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { type AppController } from '@/app.ts';
|
import { type AppController } from '@/app.ts';
|
||||||
import { eventsDB } from '@/storages.ts';
|
import { Storages } from '@/storages.ts';
|
||||||
import { getTagSet } from '@/tags.ts';
|
import { getTagSet } from '@/tags.ts';
|
||||||
import { renderAccounts } from '@/views.ts';
|
import { renderAccounts } from '@/views.ts';
|
||||||
|
|
||||||
|
|
@ -8,7 +8,7 @@ const blocksController: AppController = async (c) => {
|
||||||
const pubkey = c.get('pubkey')!;
|
const pubkey = c.get('pubkey')!;
|
||||||
const { signal } = c.req.raw;
|
const { signal } = c.req.raw;
|
||||||
|
|
||||||
const [event10000] = await eventsDB.query(
|
const [event10000] = await Storages.db.query(
|
||||||
[{ kinds: [10000], authors: [pubkey], limit: 1 }],
|
[{ kinds: [10000], authors: [pubkey], limit: 1 }],
|
||||||
{ signal },
|
{ signal },
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { type AppController } from '@/app.ts';
|
import { type AppController } from '@/app.ts';
|
||||||
import { eventsDB } from '@/storages.ts';
|
import { Storages } from '@/storages.ts';
|
||||||
import { getTagSet } from '@/tags.ts';
|
import { getTagSet } from '@/tags.ts';
|
||||||
import { renderStatuses } from '@/views.ts';
|
import { renderStatuses } from '@/views.ts';
|
||||||
|
|
||||||
|
|
@ -8,7 +8,7 @@ const bookmarksController: AppController = async (c) => {
|
||||||
const pubkey = c.get('pubkey')!;
|
const pubkey = c.get('pubkey')!;
|
||||||
const { signal } = c.req.raw;
|
const { signal } = c.req.raw;
|
||||||
|
|
||||||
const [event10003] = await eventsDB.query(
|
const [event10003] = await Storages.db.query(
|
||||||
[{ kinds: [10003], authors: [pubkey], limit: 1 }],
|
[{ kinds: [10003], authors: [pubkey], limit: 1 }],
|
||||||
{ signal },
|
{ signal },
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ import { z } from 'zod';
|
||||||
|
|
||||||
import { AppController } from '@/app.ts';
|
import { AppController } from '@/app.ts';
|
||||||
import { Conf } from '@/config.ts';
|
import { Conf } from '@/config.ts';
|
||||||
import { eventsDB } from '@/storages.ts';
|
import { Storages } from '@/storages.ts';
|
||||||
import { AdminSigner } from '@/signers/AdminSigner.ts';
|
import { AdminSigner } from '@/signers/AdminSigner.ts';
|
||||||
|
|
||||||
const relaySchema = z.object({
|
const relaySchema = z.object({
|
||||||
|
|
@ -15,7 +15,7 @@ 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 eventsDB.query([
|
const [event] = await Storages.db.query([
|
||||||
{ kinds: [10002], authors: [Conf.pubkey], limit: 1 },
|
{ kinds: [10002], authors: [Conf.pubkey], limit: 1 },
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|
@ -36,7 +36,7 @@ export const adminSetRelaysController: AppController = async (c) => {
|
||||||
created_at: Math.floor(Date.now() / 1000),
|
created_at: Math.floor(Date.now() / 1000),
|
||||||
});
|
});
|
||||||
|
|
||||||
await eventsDB.event(event);
|
await Storages.db.event(event);
|
||||||
|
|
||||||
return c.json(renderRelays(event));
|
return c.json(renderRelays(event));
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,13 @@
|
||||||
import { type AppController } from '@/app.ts';
|
import { type AppController } from '@/app.ts';
|
||||||
import { Conf } from '@/config.ts';
|
import { Conf } from '@/config.ts';
|
||||||
import { jsonServerMetaSchema } from '@/schemas/nostr.ts';
|
import { jsonServerMetaSchema } from '@/schemas/nostr.ts';
|
||||||
import { eventsDB } from '@/storages.ts';
|
import { Storages } from '@/storages.ts';
|
||||||
|
|
||||||
const instanceController: AppController = async (c) => {
|
const instanceController: AppController = async (c) => {
|
||||||
const { host, protocol } = Conf.url;
|
const { host, protocol } = Conf.url;
|
||||||
const { signal } = c.req.raw;
|
const { signal } = c.req.raw;
|
||||||
|
|
||||||
const [event] = await eventsDB.query([{ kinds: [0], authors: [Conf.pubkey], limit: 1 }], { signal });
|
const [event] = await Storages.db.query([{ kinds: [0], authors: [Conf.pubkey], limit: 1 }], { signal });
|
||||||
const meta = jsonServerMetaSchema.parse(event?.content);
|
const meta = jsonServerMetaSchema.parse(event?.content);
|
||||||
|
|
||||||
/** 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`. */
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { type AppController } from '@/app.ts';
|
import { type AppController } from '@/app.ts';
|
||||||
import { eventsDB } from '@/storages.ts';
|
import { Storages } from '@/storages.ts';
|
||||||
import { paginated, paginationSchema } from '@/utils/api.ts';
|
import { paginated, paginationSchema } from '@/utils/api.ts';
|
||||||
import { renderNotification } from '@/views/mastodon/notifications.ts';
|
import { renderNotification } from '@/views/mastodon/notifications.ts';
|
||||||
|
|
||||||
|
|
@ -8,7 +8,7 @@ const notificationsController: AppController = async (c) => {
|
||||||
const { since, until } = paginationSchema.parse(c.req.query());
|
const { since, until } = paginationSchema.parse(c.req.query());
|
||||||
const { signal } = c.req.raw;
|
const { signal } = c.req.raw;
|
||||||
|
|
||||||
const events = await eventsDB.query(
|
const events = await Storages.db.query(
|
||||||
[{ kinds: [1], '#p': [pubkey], since, until }],
|
[{ kinds: [1], '#p': [pubkey], since, until }],
|
||||||
{ signal },
|
{ signal },
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ import { type AppController } from '@/app.ts';
|
||||||
import { Conf } from '@/config.ts';
|
import { Conf } from '@/config.ts';
|
||||||
import { configSchema, elixirTupleSchema, type PleromaConfig } from '@/schemas/pleroma-api.ts';
|
import { configSchema, elixirTupleSchema, type PleromaConfig } from '@/schemas/pleroma-api.ts';
|
||||||
import { AdminSigner } from '@/signers/AdminSigner.ts';
|
import { AdminSigner } from '@/signers/AdminSigner.ts';
|
||||||
import { eventsDB } from '@/storages.ts';
|
import { Storages } from '@/storages.ts';
|
||||||
import { createAdminEvent } from '@/utils/api.ts';
|
import { createAdminEvent } from '@/utils/api.ts';
|
||||||
import { jsonSchema } from '@/schema.ts';
|
import { jsonSchema } from '@/schema.ts';
|
||||||
|
|
||||||
|
|
@ -66,7 +66,7 @@ const pleromaAdminDeleteStatusController: AppController = async (c) => {
|
||||||
async function getConfigs(signal: AbortSignal): Promise<PleromaConfig[]> {
|
async function getConfigs(signal: AbortSignal): Promise<PleromaConfig[]> {
|
||||||
const { pubkey } = Conf;
|
const { pubkey } = Conf;
|
||||||
|
|
||||||
const [event] = await eventsDB.query([{
|
const [event] = await Storages.db.query([{
|
||||||
kinds: [30078],
|
kinds: [30078],
|
||||||
authors: [pubkey],
|
authors: [pubkey],
|
||||||
'#d': ['pub.ditto.pleroma.config'],
|
'#d': ['pub.ditto.pleroma.config'],
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ import { z } from 'zod';
|
||||||
import { AppController } from '@/app.ts';
|
import { AppController } from '@/app.ts';
|
||||||
import { booleanParamSchema } from '@/schema.ts';
|
import { booleanParamSchema } from '@/schema.ts';
|
||||||
import { nostrIdSchema } from '@/schemas/nostr.ts';
|
import { nostrIdSchema } from '@/schemas/nostr.ts';
|
||||||
import { searchStore } from '@/storages.ts';
|
import { Storages } from '@/storages.ts';
|
||||||
import { dedupeEvents } from '@/utils.ts';
|
import { dedupeEvents } from '@/utils.ts';
|
||||||
import { nip05Cache } from '@/utils/nip05.ts';
|
import { nip05Cache } from '@/utils/nip05.ts';
|
||||||
import { accountFromPubkey, renderAccount } from '@/views/mastodon/accounts.ts';
|
import { accountFromPubkey, renderAccount } from '@/views/mastodon/accounts.ts';
|
||||||
|
|
@ -91,8 +91,8 @@ function searchEvents({ q, type, limit, account_id }: SearchQuery, signal: Abort
|
||||||
filter.authors = [account_id];
|
filter.authors = [account_id];
|
||||||
}
|
}
|
||||||
|
|
||||||
return searchStore.query([filter], { signal })
|
return Storages.search.query([filter], { signal })
|
||||||
.then((events) => hydrateEvents({ events, storage: searchStore, signal }));
|
.then((events) => hydrateEvents({ events, storage: Storages.search, signal }));
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Get event kinds to search from `type` query param. */
|
/** Get event kinds to search from `type` query param. */
|
||||||
|
|
@ -111,8 +111,8 @@ function typeToKinds(type: SearchQuery['type']): number[] {
|
||||||
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);
|
||||||
|
|
||||||
return searchStore.query(filters, { limit: 1, signal })
|
return Storages.search.query(filters, { limit: 1, signal })
|
||||||
.then((events) => hydrateEvents({ events, storage: searchStore, signal }))
|
.then((events) => hydrateEvents({ events, storage: Storages.search, signal }))
|
||||||
.then(([event]) => event);
|
.then(([event]) => event);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ import { renderReblog, renderStatus } from '@/views/mastodon/statuses.ts';
|
||||||
import { getLnurl } from '@/utils/lnurl.ts';
|
import { getLnurl } from '@/utils/lnurl.ts';
|
||||||
import { nip05Cache } from '@/utils/nip05.ts';
|
import { nip05Cache } from '@/utils/nip05.ts';
|
||||||
import { asyncReplaceAll } from '@/utils/text.ts';
|
import { asyncReplaceAll } from '@/utils/text.ts';
|
||||||
import { eventsDB } from '@/storages.ts';
|
import { Storages } from '@/storages.ts';
|
||||||
import { hydrateEvents } from '@/storages/hydrate.ts';
|
import { hydrateEvents } from '@/storages/hydrate.ts';
|
||||||
|
|
||||||
const createStatusSchema = z.object({
|
const createStatusSchema = z.object({
|
||||||
|
|
@ -137,7 +137,7 @@ const createStatusController: AppController = async (c) => {
|
||||||
if (data.quote_id) {
|
if (data.quote_id) {
|
||||||
await hydrateEvents({
|
await hydrateEvents({
|
||||||
events: [event],
|
events: [event],
|
||||||
storage: eventsDB,
|
storage: Storages.db,
|
||||||
signal: c.req.raw.signal,
|
signal: c.req.raw.signal,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -242,7 +242,7 @@ const reblogStatusController: AppController = async (c) => {
|
||||||
|
|
||||||
await hydrateEvents({
|
await hydrateEvents({
|
||||||
events: [reblogEvent],
|
events: [reblogEvent],
|
||||||
storage: eventsDB,
|
storage: Storages.db,
|
||||||
signal: signal,
|
signal: signal,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -262,7 +262,7 @@ const unreblogStatusController: AppController = async (c) => {
|
||||||
if (!event) return c.json({ error: 'Event not found.' }, 404);
|
if (!event) return c.json({ error: 'Event not found.' }, 404);
|
||||||
|
|
||||||
const filters: NostrFilter[] = [{ kinds: [6], authors: [pubkey], '#e': [event.id] }];
|
const filters: NostrFilter[] = [{ kinds: [6], authors: [pubkey], '#e': [event.id] }];
|
||||||
const [repostedEvent] = await eventsDB.query(filters, { limit: 1 });
|
const [repostedEvent] = await Storages.db.query(filters, { limit: 1 });
|
||||||
if (!repostedEvent) return c.json({ error: 'Event not found.' }, 404);
|
if (!repostedEvent) return c.json({ error: 'Event not found.' }, 404);
|
||||||
|
|
||||||
await createEvent({
|
await createEvent({
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,6 @@ import { renderReblog, renderStatus } from '@/views/mastodon/statuses.ts';
|
||||||
import { hydrateEvents } from '@/storages/hydrate.ts';
|
import { hydrateEvents } from '@/storages/hydrate.ts';
|
||||||
import { Storages } from '@/storages.ts';
|
import { Storages } from '@/storages.ts';
|
||||||
import { UserStore } from '@/storages/UserStore.ts';
|
import { UserStore } from '@/storages/UserStore.ts';
|
||||||
import { getAdminStore } from '@/storages/adminStore.ts';
|
|
||||||
|
|
||||||
const debug = Debug('ditto:streaming');
|
const debug = Debug('ditto:streaming');
|
||||||
|
|
||||||
|
|
@ -69,11 +68,11 @@ const streamingController: AppController = (c) => {
|
||||||
const filter = await topicToFilter(stream, c.req.query(), pubkey);
|
const filter = await topicToFilter(stream, c.req.query(), pubkey);
|
||||||
if (!filter) return;
|
if (!filter) return;
|
||||||
|
|
||||||
|
const store = pubkey ? new UserStore(pubkey, Storages.admin) : Storages.admin;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
for await (const msg of Storages.pubsub.req([filter], { signal: controller.signal })) {
|
for await (const msg of Storages.pubsub.req([filter], { signal: controller.signal })) {
|
||||||
if (msg[0] === 'EVENT') {
|
if (msg[0] === 'EVENT') {
|
||||||
const store = new UserStore(pubkey as string, getAdminStore());
|
|
||||||
|
|
||||||
const [event] = await store.query([{ ids: [msg[2].id] }]);
|
const [event] = await store.query([{ ids: [msg[2].id] }]);
|
||||||
if (!event) continue;
|
if (!event) continue;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
import { AppController } from '@/app.ts';
|
import { AppController } from '@/app.ts';
|
||||||
import { Conf } from '@/config.ts';
|
import { Conf } from '@/config.ts';
|
||||||
import { jsonServerMetaSchema } from '@/schemas/nostr.ts';
|
import { jsonServerMetaSchema } from '@/schemas/nostr.ts';
|
||||||
import { eventsDB } from '@/storages.ts';
|
import { Storages } from '@/storages.ts';
|
||||||
|
|
||||||
const relayInfoController: AppController = async (c) => {
|
const relayInfoController: AppController = async (c) => {
|
||||||
const { signal } = c.req.raw;
|
const { signal } = c.req.raw;
|
||||||
const [event] = await eventsDB.query([{ kinds: [0], authors: [Conf.pubkey], limit: 1 }], { signal });
|
const [event] = await Storages.db.query([{ kinds: [0], authors: [Conf.pubkey], limit: 1 }], { signal });
|
||||||
const meta = jsonServerMetaSchema.parse(event?.content);
|
const meta = jsonServerMetaSchema.parse(event?.content);
|
||||||
|
|
||||||
return c.json({
|
return c.json({
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
import { NostrEvent, NostrFilter, NSchema as n } from '@nostrify/nostrify';
|
import { NostrEvent, NostrFilter, NSchema as n } from '@nostrify/nostrify';
|
||||||
import { relayInfoController } from '@/controllers/nostr/relay-info.ts';
|
import { relayInfoController } from '@/controllers/nostr/relay-info.ts';
|
||||||
import { eventsDB } from '@/storages.ts';
|
|
||||||
import * as pipeline from '@/pipeline.ts';
|
import * as pipeline from '@/pipeline.ts';
|
||||||
import {
|
import {
|
||||||
type ClientCLOSE,
|
type ClientCLOSE,
|
||||||
|
|
@ -71,7 +70,7 @@ 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 eventsDB.query(filters, { limit: FILTER_LIMIT })) {
|
for (const event of await Storages.db.query(filters, { limit: FILTER_LIMIT })) {
|
||||||
send(['EVENT', subId, event]);
|
send(['EVENT', subId, event]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -115,7 +114,7 @@ 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]: ClientCOUNT): Promise<void> {
|
async function handleCount([_, subId, ...rest]: ClientCOUNT): Promise<void> {
|
||||||
const { count } = await eventsDB.count(prepareFilters(rest));
|
const { count } = await Storages.db.count(prepareFilters(rest));
|
||||||
send(['COUNT', subId, { count, approximate: false }]);
|
send(['COUNT', subId, { count, approximate: false }]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ import { Conf } from '@/config.ts';
|
||||||
import { Debug } from '@/deps.ts';
|
import { Debug } from '@/deps.ts';
|
||||||
import * as pipeline from '@/pipeline.ts';
|
import * as pipeline from '@/pipeline.ts';
|
||||||
import { AdminSigner } from '@/signers/AdminSigner.ts';
|
import { AdminSigner } from '@/signers/AdminSigner.ts';
|
||||||
import { eventsDB } from '@/storages.ts';
|
import { Storages } from '@/storages.ts';
|
||||||
|
|
||||||
const debug = Debug('ditto:users');
|
const debug = Debug('ditto:users');
|
||||||
|
|
||||||
|
|
@ -59,7 +59,7 @@ async function findUser(user: Partial<User>, signal?: AbortSignal): Promise<User
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const [event] = await eventsDB.query([filter], { signal });
|
const [event] = await Storages.db.query([filter], { signal });
|
||||||
|
|
||||||
if (event) {
|
if (event) {
|
||||||
return {
|
return {
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,16 @@
|
||||||
import { AppMiddleware } from '@/app.ts';
|
import { AppMiddleware } from '@/app.ts';
|
||||||
import { UserStore } from '@/storages/UserStore.ts';
|
import { UserStore } from '@/storages/UserStore.ts';
|
||||||
import { getAdminStore } from '@/storages/adminStore.ts';
|
import { Storages } from '@/storages.ts';
|
||||||
|
|
||||||
/** Store middleware. */
|
/** Store middleware. */
|
||||||
const storeMiddleware: AppMiddleware = async (c, next) => {
|
const storeMiddleware: AppMiddleware = async (c, next) => {
|
||||||
const pubkey = c.get('pubkey');
|
const pubkey = c.get('pubkey');
|
||||||
const adminStore = getAdminStore();
|
|
||||||
if (pubkey) {
|
if (pubkey) {
|
||||||
const store = new UserStore(pubkey, adminStore);
|
const store = new UserStore(pubkey, Storages.admin);
|
||||||
c.set('store', store);
|
c.set('store', store);
|
||||||
} else {
|
} else {
|
||||||
c.set('store', adminStore);
|
c.set('store', Storages.admin);
|
||||||
}
|
}
|
||||||
await next();
|
await next();
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ import { isEphemeralKind } from '@/kinds.ts';
|
||||||
import { DVM } from '@/pipeline/DVM.ts';
|
import { DVM } from '@/pipeline/DVM.ts';
|
||||||
import { updateStats } from '@/stats.ts';
|
import { updateStats } from '@/stats.ts';
|
||||||
import { hydrateEvents, purifyEvent } from '@/storages/hydrate.ts';
|
import { hydrateEvents, purifyEvent } from '@/storages/hydrate.ts';
|
||||||
import { cache, eventsDB, reqmeister, Storages } from '@/storages.ts';
|
import { Storages } from '@/storages.ts';
|
||||||
import { getTagSet } from '@/tags.ts';
|
import { getTagSet } from '@/tags.ts';
|
||||||
import { eventAge, isRelay, nostrDate, nostrNow, parseNip05, Time } from '@/utils.ts';
|
import { eventAge, isRelay, nostrDate, nostrNow, parseNip05, Time } from '@/utils.ts';
|
||||||
import { fetchWorker } from '@/workers/fetch.ts';
|
import { fetchWorker } from '@/workers/fetch.ts';
|
||||||
|
|
@ -70,15 +70,15 @@ async function handleEvent(event: DittoEvent, signal: AbortSignal): 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 cache.query([{ ids: [event.id], limit: 1 }]);
|
const [existing] = await Storages.cache.query([{ ids: [event.id], limit: 1 }]);
|
||||||
cache.event(event);
|
Storages.cache.event(event);
|
||||||
reqmeister.event(event, { signal });
|
Storages.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: eventsDB, signal });
|
await hydrateEvents({ events: [event], storage: Storages.db, signal });
|
||||||
|
|
||||||
const domain = await db
|
const domain = await db
|
||||||
.selectFrom('pubkey_domains')
|
.selectFrom('pubkey_domains')
|
||||||
|
|
@ -93,7 +93,7 @@ async function hydrateEvent(event: DittoEvent, signal: AbortSignal): Promise<voi
|
||||||
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 [deletion] = await eventsDB.query(
|
const [deletion] = await Storages.db.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 },
|
||||||
);
|
);
|
||||||
|
|
@ -102,7 +102,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 Promise.all([
|
await Promise.all([
|
||||||
eventsDB.event(event, { signal }).catch(debug),
|
Storages.db.event(event, { signal }).catch(debug),
|
||||||
updateStats(event).catch(debug),
|
updateStats(event).catch(debug),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
@ -151,15 +151,15 @@ async function processDeletions(event: NostrEvent, signal: AbortSignal): Promise
|
||||||
const ids = getTagSet(event.tags, 'e');
|
const ids = getTagSet(event.tags, 'e');
|
||||||
|
|
||||||
if (event.pubkey === Conf.pubkey) {
|
if (event.pubkey === Conf.pubkey) {
|
||||||
await eventsDB.remove([{ ids: [...ids] }], { signal });
|
await Storages.db.remove([{ ids: [...ids] }], { signal });
|
||||||
} else {
|
} else {
|
||||||
const events = await eventsDB.query(
|
const events = await Storages.db.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 eventsDB.remove([{ ids: deleteIds }], { signal });
|
await Storages.db.remove([{ ids: deleteIds }], { signal });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -202,14 +202,14 @@ function trackRelays(event: NostrEvent) {
|
||||||
/** Queue related events to fetch. */
|
/** Queue related events to fetch. */
|
||||||
async function fetchRelatedEvents(event: DittoEvent, signal: AbortSignal) {
|
async function fetchRelatedEvents(event: DittoEvent, signal: AbortSignal) {
|
||||||
if (!event.user) {
|
if (!event.user) {
|
||||||
reqmeister.req({ kinds: [0], authors: [event.pubkey] }, { signal }).catch(() => {});
|
Storages.reqmeister.req({ kinds: [0], authors: [event.pubkey] }, { signal }).catch(() => {});
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const [name, id, relay] of event.tags) {
|
for (const [name, id, relay] of event.tags) {
|
||||||
if (name === 'e') {
|
if (name === 'e') {
|
||||||
const { count } = await cache.count([{ ids: [id] }]);
|
const { count } = await Storages.cache.count([{ ids: [id] }]);
|
||||||
if (!count) {
|
if (!count) {
|
||||||
reqmeister.req({ ids: [id] }, { relays: [relay] }).catch(() => {});
|
Storages.reqmeister.req({ ids: [id] }, { relays: [relay] }).catch(() => {});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ import { NIP05, NostrEvent } from '@nostrify/nostrify';
|
||||||
import { Conf } from '@/config.ts';
|
import { Conf } from '@/config.ts';
|
||||||
import * as pipeline from '@/pipeline.ts';
|
import * as pipeline from '@/pipeline.ts';
|
||||||
import { AdminSigner } from '@/signers/AdminSigner.ts';
|
import { AdminSigner } from '@/signers/AdminSigner.ts';
|
||||||
import { eventsDB } from '@/storages.ts';
|
import { Storages } from '@/storages.ts';
|
||||||
|
|
||||||
export class DVM {
|
export class DVM {
|
||||||
static async event(event: NostrEvent): Promise<void> {
|
static async event(event: NostrEvent): Promise<void> {
|
||||||
|
|
@ -34,7 +34,7 @@ export class DVM {
|
||||||
return DVM.feedback(event, 'error', `Forbidden user: ${user}`);
|
return DVM.feedback(event, 'error', `Forbidden user: ${user}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const [label] = await eventsDB.query([{
|
const [label] = await Storages.db.query([{
|
||||||
kinds: [1985],
|
kinds: [1985],
|
||||||
authors: [admin],
|
authors: [admin],
|
||||||
'#L': ['nip05'],
|
'#L': ['nip05'],
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { NostrEvent, NostrFilter } from '@nostrify/nostrify';
|
import { NostrEvent, NostrFilter } from '@nostrify/nostrify';
|
||||||
import { Conf } from '@/config.ts';
|
import { Conf } from '@/config.ts';
|
||||||
import { eventsDB, optimizer } from '@/storages.ts';
|
import { Storages } from '@/storages.ts';
|
||||||
import { Debug } from '@/deps.ts';
|
import { Debug } from '@/deps.ts';
|
||||||
import { type DittoEvent } from '@/interfaces/DittoEvent.ts';
|
import { type DittoEvent } from '@/interfaces/DittoEvent.ts';
|
||||||
import { type DittoRelation } from '@/interfaces/DittoFilter.ts';
|
import { type DittoRelation } from '@/interfaces/DittoFilter.ts';
|
||||||
|
|
@ -31,8 +31,8 @@ const getEvent = async (
|
||||||
filter.kinds = [kind];
|
filter.kinds = [kind];
|
||||||
}
|
}
|
||||||
|
|
||||||
return await optimizer.query([filter], { limit: 1, signal })
|
return await Storages.optimizer.query([filter], { limit: 1, signal })
|
||||||
.then((events) => hydrateEvents({ events, storage: optimizer, signal }))
|
.then((events) => hydrateEvents({ events, storage: Storages.optimizer, signal }))
|
||||||
.then(([event]) => event);
|
.then(([event]) => event);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -40,14 +40,14 @@ const getEvent = async (
|
||||||
const getAuthor = async (pubkey: string, opts: GetEventOpts = {}): Promise<NostrEvent | undefined> => {
|
const getAuthor = async (pubkey: string, opts: GetEventOpts = {}): Promise<NostrEvent | undefined> => {
|
||||||
const { signal = AbortSignal.timeout(1000) } = opts;
|
const { signal = AbortSignal.timeout(1000) } = opts;
|
||||||
|
|
||||||
return await optimizer.query([{ authors: [pubkey], kinds: [0], limit: 1 }], { limit: 1, signal })
|
return await Storages.optimizer.query([{ authors: [pubkey], kinds: [0], limit: 1 }], { limit: 1, signal })
|
||||||
.then((events) => hydrateEvents({ events, storage: optimizer, signal }))
|
.then((events) => hydrateEvents({ events, storage: Storages.optimizer, 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 eventsDB.query([{ authors: [pubkey], kinds: [3], limit: 1 }], { limit: 1, signal });
|
const [event] = await Storages.db.query([{ authors: [pubkey], kinds: [3], limit: 1 }], { limit: 1, signal });
|
||||||
return event;
|
return event;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -83,15 +83,15 @@ 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 eventsDB.query([{ kinds: [1], '#e': [eventId] }], { limit: 200, signal });
|
const events = await Storages.db.query([{ kinds: [1], '#e': [eventId] }], { limit: 200, signal });
|
||||||
return hydrateEvents({ events, storage: eventsDB, signal });
|
return hydrateEvents({ events, storage: Storages.db, 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 eventsDB.query(
|
const [event] = await Storages.db.query(
|
||||||
[{ kinds: [3], '#p': [pubkey], search: `domain:${host}`, limit: 1 }],
|
[{ kinds: [3], '#p': [pubkey], search: `domain:${host}`, limit: 1 }],
|
||||||
{ limit: 1 },
|
{ limit: 1 },
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ import { InsertQueryBuilder } from 'kysely';
|
||||||
import { db } from '@/db.ts';
|
import { db } from '@/db.ts';
|
||||||
import { DittoTables } from '@/db/DittoTables.ts';
|
import { DittoTables } from '@/db/DittoTables.ts';
|
||||||
import { Debug } from '@/deps.ts';
|
import { Debug } from '@/deps.ts';
|
||||||
import { eventsDB } from '@/storages.ts';
|
import { Storages } from '@/storages.ts';
|
||||||
import { findReplyTag } from '@/tags.ts';
|
import { findReplyTag } from '@/tags.ts';
|
||||||
|
|
||||||
type AuthorStat = keyof Omit<DittoTables['author_stats'], 'pubkey'>;
|
type AuthorStat = keyof Omit<DittoTables['author_stats'], 'pubkey'>;
|
||||||
|
|
@ -65,7 +65,7 @@ async function getStatsDiff(event: NostrEvent, prev: NostrEvent | undefined): Pr
|
||||||
case 5: {
|
case 5: {
|
||||||
if (!firstTaggedId) break;
|
if (!firstTaggedId) break;
|
||||||
|
|
||||||
const [repostedEvent] = await eventsDB.query(
|
const [repostedEvent] = await Storages.db.query(
|
||||||
[{ kinds: [6], ids: [firstTaggedId], authors: [event.pubkey] }],
|
[{ kinds: [6], ids: [firstTaggedId], authors: [event.pubkey] }],
|
||||||
{ limit: 1 },
|
{ limit: 1 },
|
||||||
);
|
);
|
||||||
|
|
@ -77,7 +77,7 @@ async function getStatsDiff(event: NostrEvent, prev: NostrEvent | undefined): Pr
|
||||||
const eventBeingRepostedPubkey = repostedEvent.tags.find(([name]) => name === 'p')?.[1];
|
const eventBeingRepostedPubkey = repostedEvent.tags.find(([name]) => name === 'p')?.[1];
|
||||||
if (!eventBeingRepostedId || !eventBeingRepostedPubkey) break;
|
if (!eventBeingRepostedId || !eventBeingRepostedPubkey) break;
|
||||||
|
|
||||||
const [eventBeingReposted] = await eventsDB.query(
|
const [eventBeingReposted] = await Storages.db.query(
|
||||||
[{ kinds: [1], ids: [eventBeingRepostedId], authors: [eventBeingRepostedPubkey] }],
|
[{ kinds: [1], ids: [eventBeingRepostedId], authors: [eventBeingRepostedPubkey] }],
|
||||||
{ limit: 1 },
|
{ limit: 1 },
|
||||||
);
|
);
|
||||||
|
|
@ -154,7 +154,7 @@ function eventStatsQuery(diffs: EventStatDiff[]) {
|
||||||
|
|
||||||
/** Get the last version of the event, if any. */
|
/** Get the last version of the event, if any. */
|
||||||
async function maybeGetPrev(event: NostrEvent): Promise<NostrEvent> {
|
async function maybeGetPrev(event: NostrEvent): Promise<NostrEvent> {
|
||||||
const [prev] = await eventsDB.query([
|
const [prev] = await Storages.db.query([
|
||||||
{ kinds: [event.kind], authors: [event.pubkey], limit: 1 },
|
{ kinds: [event.kind], authors: [event.pubkey], limit: 1 },
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,51 +9,95 @@ import { PoolStore } from '@/storages/pool-store.ts';
|
||||||
import { Reqmeister } from '@/storages/reqmeister.ts';
|
import { Reqmeister } from '@/storages/reqmeister.ts';
|
||||||
import { SearchStore } from '@/storages/search-store.ts';
|
import { SearchStore } from '@/storages/search-store.ts';
|
||||||
import { InternalRelay } from '@/storages/InternalRelay.ts';
|
import { InternalRelay } from '@/storages/InternalRelay.ts';
|
||||||
|
import { UserStore } from '@/storages/UserStore.ts';
|
||||||
import { Time } from '@/utils/time.ts';
|
import { Time } from '@/utils/time.ts';
|
||||||
|
|
||||||
|
export class Storages {
|
||||||
|
private static _db: EventsDB | undefined;
|
||||||
|
private static _admin: UserStore | undefined;
|
||||||
|
private static _cache: NCache | undefined;
|
||||||
|
private static _client: PoolStore | undefined;
|
||||||
|
private static _optimizer: Optimizer | undefined;
|
||||||
|
private static _reqmeister: Reqmeister | undefined;
|
||||||
|
private static _pubsub: InternalRelay | undefined;
|
||||||
|
private static _search: SearchStore | undefined;
|
||||||
|
|
||||||
|
/** SQLite database to store events this Ditto server cares about. */
|
||||||
|
public static get db(): EventsDB {
|
||||||
|
if (!this._db) {
|
||||||
|
this._db = new EventsDB(db);
|
||||||
|
}
|
||||||
|
return this._db;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Admin user storage. */
|
||||||
|
public static get admin(): UserStore {
|
||||||
|
if (!this._admin) {
|
||||||
|
this._admin = new UserStore(Conf.pubkey, this.db);
|
||||||
|
}
|
||||||
|
return this._admin;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Internal pubsub relay between controllers and the pipeline. */
|
||||||
|
public static get pubsub(): InternalRelay {
|
||||||
|
if (!this._pubsub) {
|
||||||
|
this._pubsub = new InternalRelay();
|
||||||
|
}
|
||||||
|
return this._pubsub;
|
||||||
|
}
|
||||||
|
|
||||||
/** Relay pool storage. */
|
/** Relay pool storage. */
|
||||||
const client = new PoolStore({
|
public static get client(): PoolStore {
|
||||||
|
if (!this._client) {
|
||||||
|
this._client = new PoolStore({
|
||||||
pool,
|
pool,
|
||||||
relays: activeRelays,
|
relays: activeRelays,
|
||||||
publisher: pipeline,
|
publisher: pipeline,
|
||||||
});
|
});
|
||||||
|
}
|
||||||
/** SQLite database to store events this Ditto server cares about. */
|
return this._client;
|
||||||
const eventsDB = new EventsDB(db);
|
}
|
||||||
|
|
||||||
/** In-memory data store for cached events. */
|
/** In-memory data store for cached events. */
|
||||||
const cache = new NCache({ max: 3000 });
|
public static get cache(): NCache {
|
||||||
|
if (!this._cache) {
|
||||||
|
this._cache = new NCache({ max: 3000 });
|
||||||
|
}
|
||||||
|
return this._cache;
|
||||||
|
}
|
||||||
|
|
||||||
/** Batches requests for single events. */
|
/** Batches requests for single events. */
|
||||||
const reqmeister = new Reqmeister({
|
public static get reqmeister(): Reqmeister {
|
||||||
client,
|
if (!this._reqmeister) {
|
||||||
|
this._reqmeister = new Reqmeister({
|
||||||
|
client: this.client,
|
||||||
delay: Time.seconds(1),
|
delay: Time.seconds(1),
|
||||||
timeout: Time.seconds(1),
|
timeout: Time.seconds(1),
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
return this._reqmeister;
|
||||||
|
}
|
||||||
|
|
||||||
/** Main Ditto storage adapter */
|
/** Main Ditto storage adapter */
|
||||||
const optimizer = new Optimizer({
|
public static get optimizer(): Optimizer {
|
||||||
db: eventsDB,
|
if (!this._optimizer) {
|
||||||
cache,
|
this._optimizer = new Optimizer({
|
||||||
client: reqmeister,
|
db: this.db,
|
||||||
|
cache: this.cache,
|
||||||
|
client: this.reqmeister,
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
return this._optimizer;
|
||||||
|
}
|
||||||
|
|
||||||
/** Storage to use for remote search. */
|
/** Storage to use for remote search. */
|
||||||
const searchStore = new SearchStore({
|
public static get search(): SearchStore {
|
||||||
|
if (!this._search) {
|
||||||
|
this._search = new SearchStore({
|
||||||
relay: Conf.searchRelay,
|
relay: Conf.searchRelay,
|
||||||
fallback: optimizer,
|
fallback: this.optimizer,
|
||||||
});
|
});
|
||||||
|
|
||||||
export class Storages {
|
|
||||||
private static _pubsub: InternalRelay | undefined;
|
|
||||||
|
|
||||||
static get pubsub(): InternalRelay {
|
|
||||||
if (!this._pubsub) {
|
|
||||||
this._pubsub = new InternalRelay();
|
|
||||||
}
|
}
|
||||||
|
return this._search;
|
||||||
return this._pubsub;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export { cache, client, eventsDB, optimizer, reqmeister, searchStore };
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
import { UserStore } from '@/storages/UserStore.ts';
|
|
||||||
import { Conf } from '@/config.ts';
|
|
||||||
import { eventsDB } from '@/storages.ts';
|
|
||||||
|
|
||||||
export function getAdminStore() {
|
|
||||||
return new UserStore(Conf.pubkey, eventsDB);
|
|
||||||
}
|
|
||||||
|
|
@ -9,7 +9,7 @@ import { Debug, parseFormData, type TypeFest } from '@/deps.ts';
|
||||||
import * as pipeline from '@/pipeline.ts';
|
import * as pipeline from '@/pipeline.ts';
|
||||||
import { AdminSigner } from '@/signers/AdminSigner.ts';
|
import { AdminSigner } from '@/signers/AdminSigner.ts';
|
||||||
import { APISigner } from '@/signers/APISigner.ts';
|
import { APISigner } from '@/signers/APISigner.ts';
|
||||||
import { client, eventsDB } from '@/storages.ts';
|
import { Storages } from '@/storages.ts';
|
||||||
import { nostrNow } from '@/utils.ts';
|
import { nostrNow } from '@/utils.ts';
|
||||||
|
|
||||||
const debug = Debug('ditto:api');
|
const debug = Debug('ditto:api');
|
||||||
|
|
@ -43,7 +43,7 @@ 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 eventsDB.query([filter], { limit: 1, signal: c.req.raw.signal });
|
const [prev] = await Storages.db.query([filter], { limit: 1, signal: c.req.raw.signal });
|
||||||
return createEvent(fn(prev), c);
|
return createEvent(fn(prev), c);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -80,7 +80,7 @@ async function publishEvent(event: NostrEvent, c: AppContext): Promise<NostrEven
|
||||||
try {
|
try {
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
pipeline.handleEvent(event, c.req.raw.signal),
|
pipeline.handleEvent(event, c.req.raw.signal),
|
||||||
client.event(event),
|
Storages.client.event(event),
|
||||||
]);
|
]);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e instanceof pipeline.RelayError) {
|
if (e instanceof pipeline.RelayError) {
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ import { Conf } from '@/config.ts';
|
||||||
import { Debug } from '@/deps.ts';
|
import { Debug } from '@/deps.ts';
|
||||||
import { SimpleLRU } from '@/utils/SimpleLRU.ts';
|
import { SimpleLRU } from '@/utils/SimpleLRU.ts';
|
||||||
import { Time } from '@/utils/time.ts';
|
import { Time } from '@/utils/time.ts';
|
||||||
import { eventsDB } from '@/storages.ts';
|
import { Storages } from '@/storages.ts';
|
||||||
import { fetchWorker } from '@/workers/fetch.ts';
|
import { fetchWorker } from '@/workers/fetch.ts';
|
||||||
|
|
||||||
const debug = Debug('ditto:nip05');
|
const debug = Debug('ditto:nip05');
|
||||||
|
|
@ -37,7 +37,7 @@ const nip05Cache = new SimpleLRU<string, nip19.ProfilePointer>(
|
||||||
);
|
);
|
||||||
|
|
||||||
async function localNip05Lookup(name: string): Promise<nip19.ProfilePointer | undefined> {
|
async function localNip05Lookup(name: string): Promise<nip19.ProfilePointer | undefined> {
|
||||||
const [label] = await eventsDB.query([{
|
const [label] = await Storages.db.query([{
|
||||||
kinds: [1985],
|
kinds: [1985],
|
||||||
authors: [Conf.pubkey],
|
authors: [Conf.pubkey],
|
||||||
'#L': ['nip05'],
|
'#L': ['nip05'],
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
import { Conf } from '@/config.ts';
|
import { Conf } from '@/config.ts';
|
||||||
import { eventsDB } from '@/storages.ts';
|
import { Storages } from '@/storages.ts';
|
||||||
|
|
||||||
export async function getRelays(pubkey: string): Promise<Set<string>> {
|
export async function getRelays(pubkey: string): Promise<Set<string>> {
|
||||||
const relays = new Set<`wss://${string}`>();
|
const relays = new Set<`wss://${string}`>();
|
||||||
|
|
||||||
const events = await eventsDB.query([
|
const events = await Storages.db.query([
|
||||||
{ kinds: [10002], authors: [pubkey, Conf.pubkey], limit: 2 },
|
{ kinds: [10002], authors: [pubkey, Conf.pubkey], limit: 2 },
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|
|
||||||
16
src/views.ts
16
src/views.ts
|
|
@ -1,6 +1,6 @@
|
||||||
import { NostrFilter } from '@nostrify/nostrify';
|
import { NostrFilter } from '@nostrify/nostrify';
|
||||||
import { AppContext } from '@/app.ts';
|
import { AppContext } from '@/app.ts';
|
||||||
import { eventsDB } 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 { paginated, paginationSchema } from '@/utils/api.ts';
|
||||||
|
|
@ -12,15 +12,15 @@ async function renderEventAccounts(c: AppContext, filters: NostrFilter[], signal
|
||||||
return c.json([]);
|
return c.json([]);
|
||||||
}
|
}
|
||||||
|
|
||||||
const events = await eventsDB.query(filters, { signal });
|
const events = await Storages.db.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 eventsDB.query([{ kinds: [0], authors: [...pubkeys] }], { signal })
|
const authors = await Storages.db.query([{ kinds: [0], authors: [...pubkeys] }], { signal })
|
||||||
.then((events) => hydrateEvents({ events, storage: eventsDB, signal }));
|
.then((events) => hydrateEvents({ events, storage: Storages.db, signal }));
|
||||||
|
|
||||||
const accounts = await Promise.all(
|
const accounts = await Promise.all(
|
||||||
authors.map((event) => renderAccount(event)),
|
authors.map((event) => renderAccount(event)),
|
||||||
|
|
@ -32,8 +32,8 @@ 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 eventsDB.query([{ kinds: [0], authors, since, until, limit }], { signal })
|
const events = await Storages.db.query([{ kinds: [0], authors, since, until, limit }], { signal })
|
||||||
.then((events) => hydrateEvents({ events, storage: eventsDB, signal }));
|
.then((events) => hydrateEvents({ events, storage: Storages.db, signal }));
|
||||||
|
|
||||||
const accounts = await Promise.all(
|
const accounts = await Promise.all(
|
||||||
events.map((event) => renderAccount(event)),
|
events.map((event) => renderAccount(event)),
|
||||||
|
|
@ -50,8 +50,8 @@ async function renderStatuses(c: AppContext, ids: string[], signal = AbortSignal
|
||||||
|
|
||||||
const { limit } = paginationSchema.parse(c.req.query());
|
const { limit } = paginationSchema.parse(c.req.query());
|
||||||
|
|
||||||
const events = await eventsDB.query([{ kinds: [1], ids, limit }], { signal })
|
const events = await Storages.db.query([{ kinds: [1], ids, limit }], { signal })
|
||||||
.then((events) => hydrateEvents({ events, storage: eventsDB, signal }));
|
.then((events) => hydrateEvents({ events, storage: Storages.db, signal }));
|
||||||
|
|
||||||
if (!events.length) {
|
if (!events.length) {
|
||||||
return c.json([]);
|
return c.json([]);
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
import { eventsDB } from '@/storages.ts';
|
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 eventsDB.query([
|
const events = await Storages.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 },
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ import { Conf } from '@/config.ts';
|
||||||
import { type DittoEvent } from '@/interfaces/DittoEvent.ts';
|
import { type DittoEvent } from '@/interfaces/DittoEvent.ts';
|
||||||
import { getMediaLinks, parseNoteContent } from '@/note.ts';
|
import { getMediaLinks, parseNoteContent } from '@/note.ts';
|
||||||
import { jsonMediaDataSchema } from '@/schemas/nostr.ts';
|
import { jsonMediaDataSchema } from '@/schemas/nostr.ts';
|
||||||
import { eventsDB, optimizer } from '@/storages.ts';
|
import { Storages } from '@/storages.ts';
|
||||||
import { findReplyTag } from '@/tags.ts';
|
import { findReplyTag } from '@/tags.ts';
|
||||||
import { nostrDate } from '@/utils.ts';
|
import { nostrDate } from '@/utils.ts';
|
||||||
import { unfurlCardCached } from '@/utils/unfurl.ts';
|
import { unfurlCardCached } from '@/utils/unfurl.ts';
|
||||||
|
|
@ -40,7 +40,7 @@ async function renderStatus(event: DittoEvent, opts: statusOpts): Promise<any> {
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
|
|
||||||
const mentionedProfiles = await optimizer.query(
|
const mentionedProfiles = await Storages.optimizer.query(
|
||||||
[{ authors: mentionedPubkeys, limit: mentionedPubkeys.length }],
|
[{ authors: mentionedPubkeys, limit: mentionedPubkeys.length }],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -53,7 +53,7 @@ async function renderStatus(event: DittoEvent, opts: statusOpts): Promise<any> {
|
||||||
),
|
),
|
||||||
firstUrl ? unfurlCardCached(firstUrl) : null,
|
firstUrl ? unfurlCardCached(firstUrl) : null,
|
||||||
viewerPubkey
|
viewerPubkey
|
||||||
? await eventsDB.query([
|
? await Storages.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…
Add table
Reference in a new issue