Merge branch 'refactor-storages' into 'main'
First pass refactoring storages See merge request soapbox-pub/ditto!203
This commit is contained in:
commit
efd6e35065
|
@ -1,6 +1,5 @@
|
|||
{
|
||||
"$schema": "https://deno.land/x/deno@v1.41.0/cli/schemas/config-file.v1.json",
|
||||
"lock": false,
|
||||
"tasks": {
|
||||
"start": "deno run -A src/server.ts",
|
||||
"dev": "deno run -A --watch src/server.ts",
|
||||
|
|
|
@ -7,7 +7,7 @@ import { Conf } from '@/config.ts';
|
|||
import { getAuthor, getFollowedPubkeys } from '@/queries.ts';
|
||||
import { booleanParamSchema, fileSchema } from '@/schema.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 { uploadFile } from '@/upload.ts';
|
||||
import { nostrNow } from '@/utils.ts';
|
||||
|
@ -92,12 +92,12 @@ const accountSearchController: AppController = async (c) => {
|
|||
|
||||
const [event, events] = await Promise.all([
|
||||
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({
|
||||
events: event ? [event, ...events] : events,
|
||||
storage: eventsDB,
|
||||
storage: Storages.db,
|
||||
signal: c.req.raw.signal,
|
||||
});
|
||||
|
||||
|
@ -143,7 +143,7 @@ const accountStatusesController: AppController = async (c) => {
|
|||
const { signal } = c.req.raw;
|
||||
|
||||
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) {
|
||||
const pinnedEventIds = getTagSet(pinEvent.tags, 'e');
|
||||
return renderStatuses(c, [...pinnedEventIds].reverse());
|
||||
|
@ -164,8 +164,8 @@ const accountStatusesController: AppController = async (c) => {
|
|||
filter['#t'] = [tagged];
|
||||
}
|
||||
|
||||
const events = await eventsDB.query([filter], { signal })
|
||||
.then((events) => hydrateEvents({ events, storage: eventsDB, signal }))
|
||||
const events = await Storages.db.query([filter], { signal })
|
||||
.then((events) => hydrateEvents({ events, storage: Storages.db, signal }))
|
||||
.then((events) => {
|
||||
if (exclude_replies) {
|
||||
return events.filter((event) => !findReplyTag(event.tags));
|
||||
|
@ -306,7 +306,7 @@ const favouritesController: AppController = async (c) => {
|
|||
const params = paginationSchema.parse(c.req.query());
|
||||
const { signal } = c.req.raw;
|
||||
|
||||
const events7 = await eventsDB.query(
|
||||
const events7 = await Storages.db.query(
|
||||
[{ kinds: [7], authors: [pubkey], ...params }],
|
||||
{ signal },
|
||||
);
|
||||
|
@ -315,8 +315,8 @@ const favouritesController: AppController = async (c) => {
|
|||
.map((event) => event.tags.find((tag) => tag[0] === 'e')?.[1])
|
||||
.filter((id): id is string => !!id);
|
||||
|
||||
const events1 = await eventsDB.query([{ kinds: [1], ids }], { signal })
|
||||
.then((events) => hydrateEvents({ events, storage: eventsDB, signal }));
|
||||
const events1 = await Storages.db.query([{ kinds: [1], ids }], { signal })
|
||||
.then((events) => hydrateEvents({ events, storage: Storages.db, signal }));
|
||||
|
||||
const statuses = await Promise.all(events1.map((event) => renderStatus(event, { viewerPubkey: c.get('pubkey') })));
|
||||
return paginated(c, events1, statuses);
|
||||
|
|
|
@ -3,7 +3,7 @@ import { z } from 'zod';
|
|||
import { type AppController } from '@/app.ts';
|
||||
import { Conf } from '@/config.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 { 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 { 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 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) {
|
||||
const d = event.tags.find(([name]) => name === 'd')?.[1];
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { type AppController } from '@/app.ts';
|
||||
import { eventsDB } from '@/storages.ts';
|
||||
import { Storages } from '@/storages.ts';
|
||||
import { getTagSet } from '@/tags.ts';
|
||||
import { renderAccounts } from '@/views.ts';
|
||||
|
||||
|
@ -8,7 +8,7 @@ const blocksController: AppController = async (c) => {
|
|||
const pubkey = c.get('pubkey')!;
|
||||
const { signal } = c.req.raw;
|
||||
|
||||
const [event10000] = await eventsDB.query(
|
||||
const [event10000] = await Storages.db.query(
|
||||
[{ kinds: [10000], authors: [pubkey], limit: 1 }],
|
||||
{ signal },
|
||||
);
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { type AppController } from '@/app.ts';
|
||||
import { eventsDB } from '@/storages.ts';
|
||||
import { Storages } from '@/storages.ts';
|
||||
import { getTagSet } from '@/tags.ts';
|
||||
import { renderStatuses } from '@/views.ts';
|
||||
|
||||
|
@ -8,7 +8,7 @@ const bookmarksController: AppController = async (c) => {
|
|||
const pubkey = c.get('pubkey')!;
|
||||
const { signal } = c.req.raw;
|
||||
|
||||
const [event10003] = await eventsDB.query(
|
||||
const [event10003] = await Storages.db.query(
|
||||
[{ kinds: [10003], authors: [pubkey], limit: 1 }],
|
||||
{ signal },
|
||||
);
|
||||
|
|
|
@ -3,7 +3,7 @@ import { z } from 'zod';
|
|||
|
||||
import { AppController } from '@/app.ts';
|
||||
import { Conf } from '@/config.ts';
|
||||
import { eventsDB } from '@/storages.ts';
|
||||
import { Storages } from '@/storages.ts';
|
||||
import { AdminSigner } from '@/signers/AdminSigner.ts';
|
||||
|
||||
const relaySchema = z.object({
|
||||
|
@ -15,7 +15,7 @@ const relaySchema = z.object({
|
|||
type RelayEntity = z.infer<typeof relaySchema>;
|
||||
|
||||
export const adminRelaysController: AppController = async (c) => {
|
||||
const [event] = await eventsDB.query([
|
||||
const [event] = await Storages.db.query([
|
||||
{ kinds: [10002], authors: [Conf.pubkey], limit: 1 },
|
||||
]);
|
||||
|
||||
|
@ -36,7 +36,7 @@ export const adminSetRelaysController: AppController = async (c) => {
|
|||
created_at: Math.floor(Date.now() / 1000),
|
||||
});
|
||||
|
||||
await eventsDB.event(event);
|
||||
await Storages.db.event(event);
|
||||
|
||||
return c.json(renderRelays(event));
|
||||
};
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
import { type AppController } from '@/app.ts';
|
||||
import { Conf } from '@/config.ts';
|
||||
import { jsonServerMetaSchema } from '@/schemas/nostr.ts';
|
||||
import { eventsDB } from '@/storages.ts';
|
||||
import { Storages } from '@/storages.ts';
|
||||
|
||||
const instanceController: AppController = async (c) => {
|
||||
const { host, protocol } = Conf.url;
|
||||
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);
|
||||
|
||||
/** 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 { eventsDB } from '@/storages.ts';
|
||||
import { Storages } from '@/storages.ts';
|
||||
import { paginated, paginationSchema } from '@/utils/api.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 { signal } = c.req.raw;
|
||||
|
||||
const events = await eventsDB.query(
|
||||
const events = await Storages.db.query(
|
||||
[{ kinds: [1], '#p': [pubkey], since, until }],
|
||||
{ signal },
|
||||
);
|
||||
|
|
|
@ -4,7 +4,7 @@ import { type AppController } from '@/app.ts';
|
|||
import { Conf } from '@/config.ts';
|
||||
import { configSchema, elixirTupleSchema, type PleromaConfig } from '@/schemas/pleroma-api.ts';
|
||||
import { AdminSigner } from '@/signers/AdminSigner.ts';
|
||||
import { eventsDB } from '@/storages.ts';
|
||||
import { Storages } from '@/storages.ts';
|
||||
import { createAdminEvent } from '@/utils/api.ts';
|
||||
import { jsonSchema } from '@/schema.ts';
|
||||
|
||||
|
@ -66,7 +66,7 @@ const pleromaAdminDeleteStatusController: AppController = async (c) => {
|
|||
async function getConfigs(signal: AbortSignal): Promise<PleromaConfig[]> {
|
||||
const { pubkey } = Conf;
|
||||
|
||||
const [event] = await eventsDB.query([{
|
||||
const [event] = await Storages.db.query([{
|
||||
kinds: [30078],
|
||||
authors: [pubkey],
|
||||
'#d': ['pub.ditto.pleroma.config'],
|
||||
|
|
|
@ -5,7 +5,7 @@ import { z } from 'zod';
|
|||
import { AppController } from '@/app.ts';
|
||||
import { booleanParamSchema } from '@/schema.ts';
|
||||
import { nostrIdSchema } from '@/schemas/nostr.ts';
|
||||
import { searchStore } from '@/storages.ts';
|
||||
import { Storages } from '@/storages.ts';
|
||||
import { dedupeEvents } from '@/utils.ts';
|
||||
import { nip05Cache } from '@/utils/nip05.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];
|
||||
}
|
||||
|
||||
return searchStore.query([filter], { signal })
|
||||
.then((events) => hydrateEvents({ events, storage: searchStore, signal }));
|
||||
return Storages.search.query([filter], { signal })
|
||||
.then((events) => hydrateEvents({ events, storage: Storages.search, signal }));
|
||||
}
|
||||
|
||||
/** 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> {
|
||||
const filters = await getLookupFilters(query, signal);
|
||||
|
||||
return searchStore.query(filters, { limit: 1, signal })
|
||||
.then((events) => hydrateEvents({ events, storage: searchStore, signal }))
|
||||
return Storages.search.query(filters, { limit: 1, signal })
|
||||
.then((events) => hydrateEvents({ events, storage: Storages.search, signal }))
|
||||
.then(([event]) => event);
|
||||
}
|
||||
|
||||
|
|
|
@ -15,7 +15,7 @@ import { renderReblog, renderStatus } from '@/views/mastodon/statuses.ts';
|
|||
import { getLnurl } from '@/utils/lnurl.ts';
|
||||
import { nip05Cache } from '@/utils/nip05.ts';
|
||||
import { asyncReplaceAll } from '@/utils/text.ts';
|
||||
import { eventsDB } from '@/storages.ts';
|
||||
import { Storages } from '@/storages.ts';
|
||||
import { hydrateEvents } from '@/storages/hydrate.ts';
|
||||
|
||||
const createStatusSchema = z.object({
|
||||
|
@ -137,7 +137,7 @@ const createStatusController: AppController = async (c) => {
|
|||
if (data.quote_id) {
|
||||
await hydrateEvents({
|
||||
events: [event],
|
||||
storage: eventsDB,
|
||||
storage: Storages.db,
|
||||
signal: c.req.raw.signal,
|
||||
});
|
||||
}
|
||||
|
@ -242,7 +242,7 @@ const reblogStatusController: AppController = async (c) => {
|
|||
|
||||
await hydrateEvents({
|
||||
events: [reblogEvent],
|
||||
storage: eventsDB,
|
||||
storage: Storages.db,
|
||||
signal: signal,
|
||||
});
|
||||
|
||||
|
@ -262,7 +262,7 @@ const unreblogStatusController: AppController = async (c) => {
|
|||
if (!event) return c.json({ error: 'Event not found.' }, 404);
|
||||
|
||||
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);
|
||||
|
||||
await createEvent({
|
||||
|
|
|
@ -10,7 +10,6 @@ import { renderReblog, renderStatus } from '@/views/mastodon/statuses.ts';
|
|||
import { hydrateEvents } from '@/storages/hydrate.ts';
|
||||
import { Storages } from '@/storages.ts';
|
||||
import { UserStore } from '@/storages/UserStore.ts';
|
||||
import { getAdminStore } from '@/storages/adminStore.ts';
|
||||
|
||||
const debug = Debug('ditto:streaming');
|
||||
|
||||
|
@ -69,11 +68,11 @@ const streamingController: AppController = (c) => {
|
|||
const filter = await topicToFilter(stream, c.req.query(), pubkey);
|
||||
if (!filter) return;
|
||||
|
||||
const store = pubkey ? new UserStore(pubkey, Storages.admin) : Storages.admin;
|
||||
|
||||
try {
|
||||
for await (const msg of Storages.pubsub.req([filter], { signal: controller.signal })) {
|
||||
if (msg[0] === 'EVENT') {
|
||||
const store = new UserStore(pubkey as string, getAdminStore());
|
||||
|
||||
const [event] = await store.query([{ ids: [msg[2].id] }]);
|
||||
if (!event) continue;
|
||||
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
import { AppController } from '@/app.ts';
|
||||
import { Conf } from '@/config.ts';
|
||||
import { jsonServerMetaSchema } from '@/schemas/nostr.ts';
|
||||
import { eventsDB } from '@/storages.ts';
|
||||
import { Storages } from '@/storages.ts';
|
||||
|
||||
const relayInfoController: AppController = async (c) => {
|
||||
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);
|
||||
|
||||
return c.json({
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import { NostrEvent, NostrFilter, NSchema as n } from '@nostrify/nostrify';
|
||||
import { relayInfoController } from '@/controllers/nostr/relay-info.ts';
|
||||
import { eventsDB } from '@/storages.ts';
|
||||
import * as pipeline from '@/pipeline.ts';
|
||||
import {
|
||||
type ClientCLOSE,
|
||||
|
@ -71,7 +70,7 @@ function connectStream(socket: WebSocket) {
|
|||
controllers.get(subId)?.abort();
|
||||
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]);
|
||||
}
|
||||
|
||||
|
@ -115,7 +114,7 @@ function connectStream(socket: WebSocket) {
|
|||
|
||||
/** Handle COUNT. Return the number of events matching the filters. */
|
||||
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 }]);
|
||||
}
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@ import { Conf } from '@/config.ts';
|
|||
import { Debug } from '@/deps.ts';
|
||||
import * as pipeline from '@/pipeline.ts';
|
||||
import { AdminSigner } from '@/signers/AdminSigner.ts';
|
||||
import { eventsDB } from '@/storages.ts';
|
||||
import { Storages } from '@/storages.ts';
|
||||
|
||||
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) {
|
||||
return {
|
||||
|
|
|
@ -1,16 +1,16 @@
|
|||
import { AppMiddleware } from '@/app.ts';
|
||||
import { UserStore } from '@/storages/UserStore.ts';
|
||||
import { getAdminStore } from '@/storages/adminStore.ts';
|
||||
import { Storages } from '@/storages.ts';
|
||||
|
||||
/** Store middleware. */
|
||||
const storeMiddleware: AppMiddleware = async (c, next) => {
|
||||
const pubkey = c.get('pubkey');
|
||||
const adminStore = getAdminStore();
|
||||
|
||||
if (pubkey) {
|
||||
const store = new UserStore(pubkey, adminStore);
|
||||
const store = new UserStore(pubkey, Storages.admin);
|
||||
c.set('store', store);
|
||||
} else {
|
||||
c.set('store', adminStore);
|
||||
c.set('store', Storages.admin);
|
||||
}
|
||||
await next();
|
||||
};
|
||||
|
|
|
@ -12,7 +12,7 @@ import { isEphemeralKind } from '@/kinds.ts';
|
|||
import { DVM } from '@/pipeline/DVM.ts';
|
||||
import { updateStats } from '@/stats.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 { eventAge, isRelay, nostrDate, nostrNow, parseNip05, Time } from '@/utils.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. */
|
||||
async function encounterEvent(event: NostrEvent, signal: AbortSignal): Promise<boolean> {
|
||||
const [existing] = await cache.query([{ ids: [event.id], limit: 1 }]);
|
||||
cache.event(event);
|
||||
reqmeister.event(event, { signal });
|
||||
const [existing] = await Storages.cache.query([{ ids: [event.id], limit: 1 }]);
|
||||
Storages.cache.event(event);
|
||||
Storages.reqmeister.event(event, { signal });
|
||||
return !!existing;
|
||||
}
|
||||
|
||||
/** Hydrate the event with the user, if applicable. */
|
||||
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
|
||||
.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> {
|
||||
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 }],
|
||||
{ signal },
|
||||
);
|
||||
|
@ -102,7 +102,7 @@ async function storeEvent(event: DittoEvent, signal?: AbortSignal): Promise<void
|
|||
return Promise.reject(new RelayError('blocked', 'event was deleted'));
|
||||
} else {
|
||||
await Promise.all([
|
||||
eventsDB.event(event, { signal }).catch(debug),
|
||||
Storages.db.event(event, { signal }).catch(debug),
|
||||
updateStats(event).catch(debug),
|
||||
]);
|
||||
}
|
||||
|
@ -151,15 +151,15 @@ async function processDeletions(event: NostrEvent, signal: AbortSignal): Promise
|
|||
const ids = getTagSet(event.tags, 'e');
|
||||
|
||||
if (event.pubkey === Conf.pubkey) {
|
||||
await eventsDB.remove([{ ids: [...ids] }], { signal });
|
||||
await Storages.db.remove([{ ids: [...ids] }], { signal });
|
||||
} else {
|
||||
const events = await eventsDB.query(
|
||||
const events = await Storages.db.query(
|
||||
[{ ids: [...ids], authors: [event.pubkey] }],
|
||||
{ signal },
|
||||
);
|
||||
|
||||
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. */
|
||||
async function fetchRelatedEvents(event: DittoEvent, signal: AbortSignal) {
|
||||
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) {
|
||||
if (name === 'e') {
|
||||
const { count } = await cache.count([{ ids: [id] }]);
|
||||
const { count } = await Storages.cache.count([{ ids: [id] }]);
|
||||
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 * as pipeline from '@/pipeline.ts';
|
||||
import { AdminSigner } from '@/signers/AdminSigner.ts';
|
||||
import { eventsDB } from '@/storages.ts';
|
||||
import { Storages } from '@/storages.ts';
|
||||
|
||||
export class DVM {
|
||||
static async event(event: NostrEvent): Promise<void> {
|
||||
|
@ -34,7 +34,7 @@ export class DVM {
|
|||
return DVM.feedback(event, 'error', `Forbidden user: ${user}`);
|
||||
}
|
||||
|
||||
const [label] = await eventsDB.query([{
|
||||
const [label] = await Storages.db.query([{
|
||||
kinds: [1985],
|
||||
authors: [admin],
|
||||
'#L': ['nip05'],
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { NostrEvent, NostrFilter } from '@nostrify/nostrify';
|
||||
import { Conf } from '@/config.ts';
|
||||
import { eventsDB, optimizer } from '@/storages.ts';
|
||||
import { Storages } from '@/storages.ts';
|
||||
import { Debug } from '@/deps.ts';
|
||||
import { type DittoEvent } from '@/interfaces/DittoEvent.ts';
|
||||
import { type DittoRelation } from '@/interfaces/DittoFilter.ts';
|
||||
|
@ -31,8 +31,8 @@ const getEvent = async (
|
|||
filter.kinds = [kind];
|
||||
}
|
||||
|
||||
return await optimizer.query([filter], { limit: 1, signal })
|
||||
.then((events) => hydrateEvents({ events, storage: optimizer, signal }))
|
||||
return await Storages.optimizer.query([filter], { limit: 1, signal })
|
||||
.then((events) => hydrateEvents({ events, storage: Storages.optimizer, signal }))
|
||||
.then(([event]) => event);
|
||||
};
|
||||
|
||||
|
@ -40,14 +40,14 @@ const getEvent = async (
|
|||
const getAuthor = async (pubkey: string, opts: GetEventOpts = {}): Promise<NostrEvent | undefined> => {
|
||||
const { signal = AbortSignal.timeout(1000) } = opts;
|
||||
|
||||
return await optimizer.query([{ authors: [pubkey], kinds: [0], limit: 1 }], { limit: 1, signal })
|
||||
.then((events) => hydrateEvents({ events, storage: optimizer, signal }))
|
||||
return await Storages.optimizer.query([{ authors: [pubkey], kinds: [0], limit: 1 }], { limit: 1, signal })
|
||||
.then((events) => hydrateEvents({ events, storage: Storages.optimizer, signal }))
|
||||
.then(([event]) => event);
|
||||
};
|
||||
|
||||
/** Get users the given pubkey follows. */
|
||||
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;
|
||||
};
|
||||
|
||||
|
@ -83,15 +83,15 @@ async function getAncestors(event: NostrEvent, result: NostrEvent[] = []): Promi
|
|||
}
|
||||
|
||||
async function getDescendants(eventId: string, signal = AbortSignal.timeout(2000)): Promise<NostrEvent[]> {
|
||||
const events = await eventsDB.query([{ kinds: [1], '#e': [eventId] }], { limit: 200, signal });
|
||||
return hydrateEvents({ events, storage: eventsDB, signal });
|
||||
const events = await Storages.db.query([{ kinds: [1], '#e': [eventId] }], { limit: 200, signal });
|
||||
return hydrateEvents({ events, storage: Storages.db, signal });
|
||||
}
|
||||
|
||||
/** Returns whether the pubkey is followed by a local user. */
|
||||
async function isLocallyFollowed(pubkey: string): Promise<boolean> {
|
||||
const { host } = Conf.url;
|
||||
|
||||
const [event] = await eventsDB.query(
|
||||
const [event] = await Storages.db.query(
|
||||
[{ kinds: [3], '#p': [pubkey], search: `domain:${host}`, limit: 1 }],
|
||||
{ limit: 1 },
|
||||
);
|
||||
|
|
|
@ -4,7 +4,7 @@ import { InsertQueryBuilder } from 'kysely';
|
|||
import { db } from '@/db.ts';
|
||||
import { DittoTables } from '@/db/DittoTables.ts';
|
||||
import { Debug } from '@/deps.ts';
|
||||
import { eventsDB } from '@/storages.ts';
|
||||
import { Storages } from '@/storages.ts';
|
||||
import { findReplyTag } from '@/tags.ts';
|
||||
|
||||
type AuthorStat = keyof Omit<DittoTables['author_stats'], 'pubkey'>;
|
||||
|
@ -65,7 +65,7 @@ async function getStatsDiff(event: NostrEvent, prev: NostrEvent | undefined): Pr
|
|||
case 5: {
|
||||
if (!firstTaggedId) break;
|
||||
|
||||
const [repostedEvent] = await eventsDB.query(
|
||||
const [repostedEvent] = await Storages.db.query(
|
||||
[{ kinds: [6], ids: [firstTaggedId], authors: [event.pubkey] }],
|
||||
{ limit: 1 },
|
||||
);
|
||||
|
@ -77,7 +77,7 @@ async function getStatsDiff(event: NostrEvent, prev: NostrEvent | undefined): Pr
|
|||
const eventBeingRepostedPubkey = repostedEvent.tags.find(([name]) => name === 'p')?.[1];
|
||||
if (!eventBeingRepostedId || !eventBeingRepostedPubkey) break;
|
||||
|
||||
const [eventBeingReposted] = await eventsDB.query(
|
||||
const [eventBeingReposted] = await Storages.db.query(
|
||||
[{ kinds: [1], ids: [eventBeingRepostedId], authors: [eventBeingRepostedPubkey] }],
|
||||
{ limit: 1 },
|
||||
);
|
||||
|
@ -154,7 +154,7 @@ function eventStatsQuery(diffs: EventStatDiff[]) {
|
|||
|
||||
/** Get the last version of the event, if any. */
|
||||
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 },
|
||||
]);
|
||||
|
||||
|
|
118
src/storages.ts
118
src/storages.ts
|
@ -9,51 +9,95 @@ import { PoolStore } from '@/storages/pool-store.ts';
|
|||
import { Reqmeister } from '@/storages/reqmeister.ts';
|
||||
import { SearchStore } from '@/storages/search-store.ts';
|
||||
import { InternalRelay } from '@/storages/InternalRelay.ts';
|
||||
import { UserStore } from '@/storages/UserStore.ts';
|
||||
import { Time } from '@/utils/time.ts';
|
||||
|
||||
/** Relay pool storage. */
|
||||
const client = new PoolStore({
|
||||
pool,
|
||||
relays: activeRelays,
|
||||
publisher: pipeline,
|
||||
});
|
||||
|
||||
/** SQLite database to store events this Ditto server cares about. */
|
||||
const eventsDB = new EventsDB(db);
|
||||
|
||||
/** In-memory data store for cached events. */
|
||||
const cache = new NCache({ max: 3000 });
|
||||
|
||||
/** Batches requests for single events. */
|
||||
const reqmeister = new Reqmeister({
|
||||
client,
|
||||
delay: Time.seconds(1),
|
||||
timeout: Time.seconds(1),
|
||||
});
|
||||
|
||||
/** Main Ditto storage adapter */
|
||||
const optimizer = new Optimizer({
|
||||
db: eventsDB,
|
||||
cache,
|
||||
client: reqmeister,
|
||||
});
|
||||
|
||||
/** Storage to use for remote search. */
|
||||
const searchStore = new SearchStore({
|
||||
relay: Conf.searchRelay,
|
||||
fallback: optimizer,
|
||||
});
|
||||
|
||||
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;
|
||||
|
||||
static get pubsub(): InternalRelay {
|
||||
/** 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;
|
||||
}
|
||||
}
|
||||
|
||||
export { cache, client, eventsDB, optimizer, reqmeister, searchStore };
|
||||
/** Relay pool storage. */
|
||||
public static get client(): PoolStore {
|
||||
if (!this._client) {
|
||||
this._client = new PoolStore({
|
||||
pool,
|
||||
relays: activeRelays,
|
||||
publisher: pipeline,
|
||||
});
|
||||
}
|
||||
return this._client;
|
||||
}
|
||||
|
||||
/** In-memory data store for cached events. */
|
||||
public static get cache(): NCache {
|
||||
if (!this._cache) {
|
||||
this._cache = new NCache({ max: 3000 });
|
||||
}
|
||||
return this._cache;
|
||||
}
|
||||
|
||||
/** Batches requests for single events. */
|
||||
public static get reqmeister(): Reqmeister {
|
||||
if (!this._reqmeister) {
|
||||
this._reqmeister = new Reqmeister({
|
||||
client: this.client,
|
||||
delay: Time.seconds(1),
|
||||
timeout: Time.seconds(1),
|
||||
});
|
||||
}
|
||||
return this._reqmeister;
|
||||
}
|
||||
|
||||
/** Main Ditto storage adapter */
|
||||
public static get optimizer(): Optimizer {
|
||||
if (!this._optimizer) {
|
||||
this._optimizer = new Optimizer({
|
||||
db: this.db,
|
||||
cache: this.cache,
|
||||
client: this.reqmeister,
|
||||
});
|
||||
}
|
||||
return this._optimizer;
|
||||
}
|
||||
|
||||
/** Storage to use for remote search. */
|
||||
public static get search(): SearchStore {
|
||||
if (!this._search) {
|
||||
this._search = new SearchStore({
|
||||
relay: Conf.searchRelay,
|
||||
fallback: this.optimizer,
|
||||
});
|
||||
}
|
||||
return this._search;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 { AdminSigner } from '@/signers/AdminSigner.ts';
|
||||
import { APISigner } from '@/signers/APISigner.ts';
|
||||
import { client, eventsDB } from '@/storages.ts';
|
||||
import { Storages } from '@/storages.ts';
|
||||
import { nostrNow } from '@/utils.ts';
|
||||
|
||||
const debug = Debug('ditto:api');
|
||||
|
@ -43,7 +43,7 @@ async function updateEvent<E extends EventStub>(
|
|||
fn: (prev: NostrEvent | undefined) => E,
|
||||
c: AppContext,
|
||||
): 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);
|
||||
}
|
||||
|
||||
|
@ -80,7 +80,7 @@ async function publishEvent(event: NostrEvent, c: AppContext): Promise<NostrEven
|
|||
try {
|
||||
await Promise.all([
|
||||
pipeline.handleEvent(event, c.req.raw.signal),
|
||||
client.event(event),
|
||||
Storages.client.event(event),
|
||||
]);
|
||||
} catch (e) {
|
||||
if (e instanceof pipeline.RelayError) {
|
||||
|
|
|
@ -5,7 +5,7 @@ import { Conf } from '@/config.ts';
|
|||
import { Debug } from '@/deps.ts';
|
||||
import { SimpleLRU } from '@/utils/SimpleLRU.ts';
|
||||
import { Time } from '@/utils/time.ts';
|
||||
import { eventsDB } from '@/storages.ts';
|
||||
import { Storages } from '@/storages.ts';
|
||||
import { fetchWorker } from '@/workers/fetch.ts';
|
||||
|
||||
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> {
|
||||
const [label] = await eventsDB.query([{
|
||||
const [label] = await Storages.db.query([{
|
||||
kinds: [1985],
|
||||
authors: [Conf.pubkey],
|
||||
'#L': ['nip05'],
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import { Conf } from '@/config.ts';
|
||||
import { eventsDB } from '@/storages.ts';
|
||||
import { Storages } from '@/storages.ts';
|
||||
|
||||
export async function getRelays(pubkey: string): Promise<Set<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 },
|
||||
]);
|
||||
|
||||
|
|
16
src/views.ts
16
src/views.ts
|
@ -1,6 +1,6 @@
|
|||
import { NostrFilter } from '@nostrify/nostrify';
|
||||
import { AppContext } from '@/app.ts';
|
||||
import { eventsDB } from '@/storages.ts';
|
||||
import { Storages } from '@/storages.ts';
|
||||
import { renderAccount } from '@/views/mastodon/accounts.ts';
|
||||
import { renderStatus } from '@/views/mastodon/statuses.ts';
|
||||
import { paginated, paginationSchema } from '@/utils/api.ts';
|
||||
|
@ -12,15 +12,15 @@ async function renderEventAccounts(c: AppContext, filters: NostrFilter[], signal
|
|||
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));
|
||||
|
||||
if (!pubkeys.size) {
|
||||
return c.json([]);
|
||||
}
|
||||
|
||||
const authors = await eventsDB.query([{ kinds: [0], authors: [...pubkeys] }], { signal })
|
||||
.then((events) => hydrateEvents({ events, storage: eventsDB, signal }));
|
||||
const authors = await Storages.db.query([{ kinds: [0], authors: [...pubkeys] }], { signal })
|
||||
.then((events) => hydrateEvents({ events, storage: Storages.db, signal }));
|
||||
|
||||
const accounts = await Promise.all(
|
||||
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)) {
|
||||
const { since, until, limit } = paginationSchema.parse(c.req.query());
|
||||
|
||||
const events = await eventsDB.query([{ kinds: [0], authors, since, until, limit }], { signal })
|
||||
.then((events) => hydrateEvents({ events, storage: eventsDB, signal }));
|
||||
const events = await Storages.db.query([{ kinds: [0], authors, since, until, limit }], { signal })
|
||||
.then((events) => hydrateEvents({ events, storage: Storages.db, signal }));
|
||||
|
||||
const accounts = await Promise.all(
|
||||
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 events = await eventsDB.query([{ kinds: [1], ids, limit }], { signal })
|
||||
.then((events) => hydrateEvents({ events, storage: eventsDB, signal }));
|
||||
const events = await Storages.db.query([{ kinds: [1], ids, limit }], { signal })
|
||||
.then((events) => hydrateEvents({ events, storage: Storages.db, signal }));
|
||||
|
||||
if (!events.length) {
|
||||
return c.json([]);
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import { eventsDB } from '@/storages.ts';
|
||||
import { Storages } from '@/storages.ts';
|
||||
import { hasTag } from '@/tags.ts';
|
||||
|
||||
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: [targetPubkey], 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 { getMediaLinks, parseNoteContent } from '@/note.ts';
|
||||
import { jsonMediaDataSchema } from '@/schemas/nostr.ts';
|
||||
import { eventsDB, optimizer } from '@/storages.ts';
|
||||
import { Storages } from '@/storages.ts';
|
||||
import { findReplyTag } from '@/tags.ts';
|
||||
import { nostrDate } from '@/utils.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 }],
|
||||
);
|
||||
|
||||
|
@ -53,7 +53,7 @@ async function renderStatus(event: DittoEvent, opts: statusOpts): Promise<any> {
|
|||
),
|
||||
firstUrl ? unfurlCardCached(firstUrl) : null,
|
||||
viewerPubkey
|
||||
? await eventsDB.query([
|
||||
? await Storages.db.query([
|
||||
{ kinds: [6], '#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 },
|
||||
|
|
Loading…
Reference in New Issue