From b81da2c0d7a029736a3108dfb26524e7cff660d3 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Wed, 26 Feb 2025 17:58:17 -0600 Subject: [PATCH 01/17] Move ditto/interfaces to @ditto/mastoapi/types --- packages/ditto/caches/translationCache.ts | 2 +- packages/ditto/controllers/api/accounts.ts | 3 ++- packages/ditto/controllers/api/translate.ts | 2 +- packages/ditto/controllers/api/trends.ts | 6 ++++-- packages/ditto/utils/note.ts | 3 ++- packages/ditto/utils/og-metadata.ts | 4 ++-- packages/ditto/utils/unfurl.ts | 11 +++++------ packages/ditto/views/mastodon/accounts.ts | 3 ++- packages/ditto/views/mastodon/attachments.ts | 3 ++- packages/ditto/views/mastodon/statuses.ts | 5 ++--- packages/mastoapi/deno.json | 3 ++- .../entities => mastoapi/types}/MastodonAccount.ts | 0 .../entities => mastoapi/types}/MastodonAttachment.ts | 0 .../entities => mastoapi/types}/MastodonMention.ts | 0 .../types/MastodonPreviewCard.ts} | 2 +- .../entities => mastoapi/types}/MastodonStatus.ts | 8 ++++---- .../types}/MastodonTranslation.ts | 2 +- packages/mastoapi/types/mod.ts | 6 ++++++ 18 files changed, 37 insertions(+), 26 deletions(-) rename packages/{ditto/entities => mastoapi/types}/MastodonAccount.ts (100%) rename packages/{ditto/entities => mastoapi/types}/MastodonAttachment.ts (100%) rename packages/{ditto/entities => mastoapi/types}/MastodonMention.ts (100%) rename packages/{ditto/entities/PreviewCard.ts => mastoapi/types/MastodonPreviewCard.ts} (89%) rename packages/{ditto/entities => mastoapi/types}/MastodonStatus.ts (80%) rename packages/{ditto/entities => mastoapi/types}/MastodonTranslation.ts (93%) create mode 100644 packages/mastoapi/types/mod.ts diff --git a/packages/ditto/caches/translationCache.ts b/packages/ditto/caches/translationCache.ts index 7bd27946..2196d22a 100644 --- a/packages/ditto/caches/translationCache.ts +++ b/packages/ditto/caches/translationCache.ts @@ -1,8 +1,8 @@ +import { MastodonTranslation } from '@ditto/mastoapi/types'; import { LanguageCode } from 'iso-639-1'; import { LRUCache } from 'lru-cache'; import { Conf } from '@/config.ts'; -import { MastodonTranslation } from '@/entities/MastodonTranslation.ts'; /** Translations LRU cache. */ export const translationCache = new LRUCache<`${LanguageCode}-${string}`, MastodonTranslation>({ diff --git a/packages/ditto/controllers/api/accounts.ts b/packages/ditto/controllers/api/accounts.ts index ad9dde19..a01c0038 100644 --- a/packages/ditto/controllers/api/accounts.ts +++ b/packages/ditto/controllers/api/accounts.ts @@ -19,7 +19,8 @@ import { hydrateEvents } from '@/storages/hydrate.ts'; import { bech32ToPubkey } from '@/utils.ts'; import { addTag, deleteTag, findReplyTag, getTagSet } from '@/utils/tags.ts'; import { getPubkeysBySearch } from '@/utils/search.ts'; -import { MastodonAccount } from '@/entities/MastodonAccount.ts'; + +import type { MastodonAccount } from '@ditto/mastoapi/types'; const createAccountSchema = z.object({ username: z.string().min(1).max(30).regex(/^[a-z0-9_]+$/i), diff --git a/packages/ditto/controllers/api/translate.ts b/packages/ditto/controllers/api/translate.ts index 7a0f7731..65a95a26 100644 --- a/packages/ditto/controllers/api/translate.ts +++ b/packages/ditto/controllers/api/translate.ts @@ -1,11 +1,11 @@ import { cachedTranslationsSizeGauge } from '@ditto/metrics'; +import { MastodonTranslation } from '@ditto/mastoapi/types'; import { logi } from '@soapbox/logi'; import { LanguageCode } from 'iso-639-1'; import { z } from 'zod'; import { AppController } from '@/app.ts'; import { translationCache } from '@/caches/translationCache.ts'; -import { MastodonTranslation } from '@/entities/MastodonTranslation.ts'; import { getEvent } from '@/queries.ts'; import { localeSchema } from '@/schema.ts'; import { parseBody } from '@/utils/api.ts'; diff --git a/packages/ditto/controllers/api/trends.ts b/packages/ditto/controllers/api/trends.ts index a687c2cc..f7a09b5f 100644 --- a/packages/ditto/controllers/api/trends.ts +++ b/packages/ditto/controllers/api/trends.ts @@ -7,10 +7,12 @@ import { z } from 'zod'; import { AppController } from '@/app.ts'; import { hydrateEvents } from '@/storages/hydrate.ts'; import { generateDateRange, Time } from '@/utils/time.ts'; -import { PreviewCard, unfurlCardCached } from '@/utils/unfurl.ts'; +import { unfurlCardCached } from '@/utils/unfurl.ts'; import { errorJson } from '@/utils/log.ts'; import { renderStatus } from '@/views/mastodon/statuses.ts'; +import type { MastodonPreviewCard } from '@ditto/mastoapi/types'; + interface TrendHistory { day: string; accounts: string; @@ -23,7 +25,7 @@ interface TrendingHashtag { history: TrendHistory[]; } -interface TrendingLink extends PreviewCard { +interface TrendingLink extends MastodonPreviewCard { history: TrendHistory[]; } diff --git a/packages/ditto/utils/note.ts b/packages/ditto/utils/note.ts index 45fcf94a..a17a0f15 100644 --- a/packages/ditto/utils/note.ts +++ b/packages/ditto/utils/note.ts @@ -4,10 +4,11 @@ import linkify from 'linkifyjs'; import { nip19, nip27 } from 'nostr-tools'; import { Conf } from '@/config.ts'; -import { MastodonMention } from '@/entities/MastodonMention.ts'; import { html } from '@/utils/html.ts'; import { getUrlMediaType, isPermittedMediaType } from '@/utils/media.ts'; +import { MastodonMention } from '@ditto/mastoapi/types'; + linkify.registerCustomProtocol('nostr', true); linkify.registerCustomProtocol('wss'); diff --git a/packages/ditto/utils/og-metadata.ts b/packages/ditto/utils/og-metadata.ts index c0e5756c..9dc7d125 100644 --- a/packages/ditto/utils/og-metadata.ts +++ b/packages/ditto/utils/og-metadata.ts @@ -1,10 +1,10 @@ import { nip19 } from 'nostr-tools'; import { match } from 'path-to-regexp'; -import { MastodonAccount } from '@/entities/MastodonAccount.ts'; -import { MastodonStatus } from '@/entities/MastodonStatus.ts'; import { InstanceMetadata } from '@/utils/instance.ts'; +import type { MastodonAccount, MastodonStatus } from '@ditto/mastoapi/types'; + export interface MetadataEntities { status?: MastodonStatus; account?: MastodonAccount; diff --git a/packages/ditto/utils/unfurl.ts b/packages/ditto/utils/unfurl.ts index e2d4f855..31905a04 100644 --- a/packages/ditto/utils/unfurl.ts +++ b/packages/ditto/utils/unfurl.ts @@ -6,10 +6,11 @@ import DOMPurify from 'isomorphic-dompurify'; import { unfurl } from 'unfurl.js'; import { Conf } from '@/config.ts'; -import { PreviewCard } from '@/entities/PreviewCard.ts'; import { errorJson } from '@/utils/log.ts'; -async function unfurlCard(url: string, signal: AbortSignal): Promise { +import type { MastodonPreviewCard } from '@ditto/mastoapi/types'; + +async function unfurlCard(url: string, signal: AbortSignal): Promise { try { const result = await unfurl(url, { fetch: (url) => @@ -55,10 +56,10 @@ async function unfurlCard(url: string, signal: AbortSignal): Promise>(Conf.caches.linkPreview); +const previewCardCache = new TTLCache>(Conf.caches.linkPreview); /** Unfurl card from cache if available, otherwise fetch it. */ -function unfurlCardCached(url: string, signal = AbortSignal.timeout(1000)): Promise { +export function unfurlCardCached(url: string, signal = AbortSignal.timeout(1000)): Promise { const cached = previewCardCache.get(url); if (cached !== undefined) { return cached; @@ -69,5 +70,3 @@ function unfurlCardCached(url: string, signal = AbortSignal.timeout(1000)): Prom return card; } } - -export { type PreviewCard, unfurlCardCached }; diff --git a/packages/ditto/views/mastodon/accounts.ts b/packages/ditto/views/mastodon/accounts.ts index d541e633..827b7921 100644 --- a/packages/ditto/views/mastodon/accounts.ts +++ b/packages/ditto/views/mastodon/accounts.ts @@ -2,7 +2,6 @@ import { NSchema as n } from '@nostrify/nostrify'; import { nip19, UnsignedEvent } from 'nostr-tools'; import { Conf } from '@/config.ts'; -import { MastodonAccount } from '@/entities/MastodonAccount.ts'; import { type DittoEvent } from '@/interfaces/DittoEvent.ts'; import { metadataSchema } from '@/schemas/nostr.ts'; import { getLnurl } from '@/utils/lnurl.ts'; @@ -11,6 +10,8 @@ import { getTagSet } from '@/utils/tags.ts'; import { nostrDate, nostrNow, parseNip05 } from '@/utils.ts'; import { renderEmojis } from '@/views/mastodon/emojis.ts'; +import type { MastodonAccount } from '@ditto/mastoapi/types'; + type ToAccountOpts = { withSource: true; settingsStore: Record | undefined; diff --git a/packages/ditto/views/mastodon/attachments.ts b/packages/ditto/views/mastodon/attachments.ts index 4e9401fd..b0d2e49c 100644 --- a/packages/ditto/views/mastodon/attachments.ts +++ b/packages/ditto/views/mastodon/attachments.ts @@ -1,4 +1,5 @@ -import { MastodonAttachment } from '@/entities/MastodonAttachment.ts'; +import { MastodonAttachment } from '@ditto/mastoapi/types'; + import { getUrlMediaType } from '@/utils/media.ts'; /** Render Mastodon media attachment. */ diff --git a/packages/ditto/views/mastodon/statuses.ts b/packages/ditto/views/mastodon/statuses.ts index 5957356e..55aa0808 100644 --- a/packages/ditto/views/mastodon/statuses.ts +++ b/packages/ditto/views/mastodon/statuses.ts @@ -2,9 +2,6 @@ import { NostrEvent, NStore } from '@nostrify/nostrify'; import { nip19 } from 'nostr-tools'; import { Conf } from '@/config.ts'; -import { MastodonAttachment } from '@/entities/MastodonAttachment.ts'; -import { MastodonMention } from '@/entities/MastodonMention.ts'; -import { MastodonStatus } from '@/entities/MastodonStatus.ts'; import { type DittoEvent } from '@/interfaces/DittoEvent.ts'; import { nostrDate } from '@/utils.ts'; import { getMediaLinks, parseNoteContent, stripimeta } from '@/utils/note.ts'; @@ -14,6 +11,8 @@ import { accountFromPubkey, renderAccount } from '@/views/mastodon/accounts.ts'; import { renderAttachment } from '@/views/mastodon/attachments.ts'; import { renderEmojis } from '@/views/mastodon/emojis.ts'; +import { MastodonAttachment, MastodonMention, MastodonStatus } from '@ditto/mastoapi/types'; + interface RenderStatusOpts { viewerPubkey?: string; depth?: number; diff --git a/packages/mastoapi/deno.json b/packages/mastoapi/deno.json index b9626b3e..fc976655 100644 --- a/packages/mastoapi/deno.json +++ b/packages/mastoapi/deno.json @@ -5,6 +5,7 @@ "./middleware": "./middleware/mod.ts", "./pagination": "./pagination/mod.ts", "./router": "./router/mod.ts", - "./test": "./test.ts" + "./test": "./test.ts", + "./types": "./types/mod.ts" } } diff --git a/packages/ditto/entities/MastodonAccount.ts b/packages/mastoapi/types/MastodonAccount.ts similarity index 100% rename from packages/ditto/entities/MastodonAccount.ts rename to packages/mastoapi/types/MastodonAccount.ts diff --git a/packages/ditto/entities/MastodonAttachment.ts b/packages/mastoapi/types/MastodonAttachment.ts similarity index 100% rename from packages/ditto/entities/MastodonAttachment.ts rename to packages/mastoapi/types/MastodonAttachment.ts diff --git a/packages/ditto/entities/MastodonMention.ts b/packages/mastoapi/types/MastodonMention.ts similarity index 100% rename from packages/ditto/entities/MastodonMention.ts rename to packages/mastoapi/types/MastodonMention.ts diff --git a/packages/ditto/entities/PreviewCard.ts b/packages/mastoapi/types/MastodonPreviewCard.ts similarity index 89% rename from packages/ditto/entities/PreviewCard.ts rename to packages/mastoapi/types/MastodonPreviewCard.ts index 2decf926..9d625cca 100644 --- a/packages/ditto/entities/PreviewCard.ts +++ b/packages/mastoapi/types/MastodonPreviewCard.ts @@ -1,4 +1,4 @@ -export interface PreviewCard { +export interface MastodonPreviewCard { url: string; title: string; description: string; diff --git a/packages/ditto/entities/MastodonStatus.ts b/packages/mastoapi/types/MastodonStatus.ts similarity index 80% rename from packages/ditto/entities/MastodonStatus.ts rename to packages/mastoapi/types/MastodonStatus.ts index 3bc15f55..019e5a7b 100644 --- a/packages/ditto/entities/MastodonStatus.ts +++ b/packages/mastoapi/types/MastodonStatus.ts @@ -1,11 +1,11 @@ -import { MastodonAccount } from '@/entities/MastodonAccount.ts'; -import { MastodonAttachment } from '@/entities/MastodonAttachment.ts'; -import { PreviewCard } from '@/entities/PreviewCard.ts'; +import type { MastodonAccount } from './MastodonAccount.ts'; +import type { MastodonAttachment } from './MastodonAttachment.ts'; +import type { MastodonPreviewCard } from './MastodonPreviewCard.ts'; export interface MastodonStatus { id: string; account: MastodonAccount; - card: PreviewCard | null; + card: MastodonPreviewCard | null; content: string; created_at: string; in_reply_to_id: string | null; diff --git a/packages/ditto/entities/MastodonTranslation.ts b/packages/mastoapi/types/MastodonTranslation.ts similarity index 93% rename from packages/ditto/entities/MastodonTranslation.ts rename to packages/mastoapi/types/MastodonTranslation.ts index d59b9aad..fa8b99e8 100644 --- a/packages/ditto/entities/MastodonTranslation.ts +++ b/packages/mastoapi/types/MastodonTranslation.ts @@ -1,4 +1,4 @@ -import { LanguageCode } from 'iso-639-1'; +import type { LanguageCode } from 'iso-639-1'; /** https://docs.joinmastodon.org/entities/Translation/ */ export interface MastodonTranslation { diff --git a/packages/mastoapi/types/mod.ts b/packages/mastoapi/types/mod.ts new file mode 100644 index 00000000..48e92474 --- /dev/null +++ b/packages/mastoapi/types/mod.ts @@ -0,0 +1,6 @@ +export type { MastodonAccount } from './MastodonAccount.ts'; +export type { MastodonAttachment } from './MastodonAttachment.ts'; +export type { MastodonMention } from './MastodonMention.ts'; +export type { MastodonPreviewCard } from './MastodonPreviewCard.ts'; +export type { MastodonStatus } from './MastodonStatus.ts'; +export type { MastodonTranslation } from './MastodonTranslation.ts'; From 55115d3592d9a3d4d3b900e3c457866a3bce7127 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Wed, 26 Feb 2025 18:14:42 -0600 Subject: [PATCH 02/17] PolicyWorker: move Deno.env set above imports --- packages/ditto/workers/policy.worker.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/ditto/workers/policy.worker.ts b/packages/ditto/workers/policy.worker.ts index 539830a5..6b7bbeef 100644 --- a/packages/ditto/workers/policy.worker.ts +++ b/packages/ditto/workers/policy.worker.ts @@ -1,3 +1,6 @@ +// @ts-ignore Don't try to access the env from this worker. +Deno.env = new Map(); + import { DittoPolyPg } from '@ditto/db'; import '@soapbox/safe-fetch/load'; import { NostrEvent, NostrRelayOK, NPolicy } from '@nostrify/nostrify'; @@ -6,9 +9,6 @@ import * as Comlink from 'comlink'; import { DittoPgStore } from '@/storages/DittoPgStore.ts'; -// @ts-ignore Don't try to access the env from this worker. -Deno.env = new Map(); - /** Serializable object the worker can use to set up the state. */ interface PolicyInit { /** Path to the policy module (https, jsr, file, etc) */ From 92da5e6ac3f7a08cb3f0024fe5a4d901c0895227 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Wed, 26 Feb 2025 18:19:30 -0600 Subject: [PATCH 03/17] Actually fix policy.worker import (hack) --- packages/ditto/workers/deno-env.ts | 2 ++ packages/ditto/workers/policy.worker.ts | 3 +-- 2 files changed, 3 insertions(+), 2 deletions(-) create mode 100644 packages/ditto/workers/deno-env.ts diff --git a/packages/ditto/workers/deno-env.ts b/packages/ditto/workers/deno-env.ts new file mode 100644 index 00000000..1500eb57 --- /dev/null +++ b/packages/ditto/workers/deno-env.ts @@ -0,0 +1,2 @@ +// @ts-ignore Don't try to access the env from this worker. +Deno.env = new Map(); diff --git a/packages/ditto/workers/policy.worker.ts b/packages/ditto/workers/policy.worker.ts index 6b7bbeef..b2ca3720 100644 --- a/packages/ditto/workers/policy.worker.ts +++ b/packages/ditto/workers/policy.worker.ts @@ -1,5 +1,4 @@ -// @ts-ignore Don't try to access the env from this worker. -Deno.env = new Map(); +import './deno-env.ts'; // HACK should be removed when `@/config.ts` is removed. import { DittoPolyPg } from '@ditto/db'; import '@soapbox/safe-fetch/load'; From 432f65ff6115e9959310d6eecfc94318d9be3600 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Thu, 27 Feb 2025 13:51:57 -0600 Subject: [PATCH 04/17] Remove `@/config.ts` import when starting Sentry --- packages/ditto/app.ts | 3 +++ packages/ditto/sentry.ts | 19 +++++++++---------- packages/ditto/server.ts | 8 ++++---- packages/ditto/workers/verify.worker.ts | 1 - 4 files changed, 16 insertions(+), 15 deletions(-) diff --git a/packages/ditto/app.ts b/packages/ditto/app.ts index f89448c4..f417b1b0 100644 --- a/packages/ditto/app.ts +++ b/packages/ditto/app.ts @@ -12,6 +12,7 @@ import { NostrEvent, NostrSigner, NRelay, NUploader } from '@nostrify/nostrify'; import { cron } from '@/cron.ts'; import { startFirehose } from '@/firehose.ts'; +import { startSentry } from '@/sentry.ts'; import { DittoAPIStore } from '@/storages/DittoAPIStore.ts'; import { DittoPgStore } from '@/storages/DittoPgStore.ts'; import { DittoPool } from '@/storages/DittoPool.ts'; @@ -182,6 +183,8 @@ type AppController

= Handler Date: Thu, 27 Feb 2025 13:56:02 -0600 Subject: [PATCH 05/17] Remove `@/config.ts` imports from tests --- packages/ditto/storages/DittoPgStore.test.ts | 9 ++++----- packages/ditto/test.ts | 10 ++++++---- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/packages/ditto/storages/DittoPgStore.test.ts b/packages/ditto/storages/DittoPgStore.test.ts index 405229dd..d243d0e4 100644 --- a/packages/ditto/storages/DittoPgStore.test.ts +++ b/packages/ditto/storages/DittoPgStore.test.ts @@ -5,7 +5,6 @@ import { generateSecretKey } from 'nostr-tools'; import { RelayError } from '@/RelayError.ts'; import { eventFixture } from '@/test.ts'; -import { Conf } from '@/config.ts'; import { DittoPgStore } from '@/storages/DittoPgStore.ts'; import { createTestDB } from '@/test.ts'; @@ -152,7 +151,7 @@ Deno.test("user cannot delete another user's event", async () => { Deno.test('admin can delete any event', async () => { await using db = await createTestDB({ pure: true }); - const { store } = db; + const { conf, store } = db; const sk = generateSecretKey(); @@ -168,7 +167,7 @@ Deno.test('admin can delete any event', async () => { assertEquals(await store.query([{ kinds: [1] }]), [two, one]); await store.event( - genEvent({ kind: 5, tags: [['e', one.id]] }, Conf.seckey), // admin sk + genEvent({ kind: 5, tags: [['e', one.id]] }, conf.seckey), // admin sk ); assertEquals(await store.query([{ kinds: [1] }]), [two]); @@ -176,12 +175,12 @@ Deno.test('admin can delete any event', async () => { Deno.test('throws a RelayError when inserting an event deleted by the admin', async () => { await using db = await createTestDB({ pure: true }); - const { store } = db; + const { conf, store } = db; const event = genEvent(); await store.event(event); - const deletion = genEvent({ kind: 5, tags: [['e', event.id]] }, Conf.seckey); + const deletion = genEvent({ kind: 5, tags: [['e', event.id]] }, conf.seckey); await store.event(deletion); await assertRejects( diff --git a/packages/ditto/test.ts b/packages/ditto/test.ts index f8fd08d8..d2ea5ec7 100644 --- a/packages/ditto/test.ts +++ b/packages/ditto/test.ts @@ -1,7 +1,7 @@ +import { DittoConf } from '@ditto/conf'; import { DittoPolyPg } from '@ditto/db'; import { NostrEvent } from '@nostrify/nostrify'; -import { Conf } from '@/config.ts'; import { DittoPgStore } from '@/storages/DittoPgStore.ts'; import { sql } from 'kysely'; @@ -13,13 +13,14 @@ export async function eventFixture(name: string): Promise { /** Create a database for testing. It uses `DATABASE_URL`, or creates an in-memory database by default. */ export async function createTestDB(opts?: { pure?: boolean }) { - const db = new DittoPolyPg(Conf.databaseUrl, { poolSize: 1 }); + const conf = new DittoConf(Deno.env); + const db = new DittoPolyPg(conf.databaseUrl, { poolSize: 1 }); await db.migrate(); const store = new DittoPgStore({ db, - timeout: Conf.db.timeouts.default, - pubkey: await Conf.signer.getPublicKey(), + timeout: conf.db.timeouts.default, + pubkey: await conf.signer.getPublicKey(), pure: opts?.pure ?? false, notify: true, }); @@ -28,6 +29,7 @@ export async function createTestDB(opts?: { pure?: boolean }) { db, ...db, store, + conf, kysely: db.kysely, [Symbol.asyncDispose]: async () => { const { rows } = await sql< From 2266152df3ccc06d8eb43218f47501d84fa18abf Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Thu, 27 Feb 2025 14:02:44 -0600 Subject: [PATCH 06/17] Remove `@/config.ts` import from utils/zap-split.ts --- packages/ditto/app.ts | 2 +- packages/ditto/controllers/api/ditto.ts | 14 +++++++----- packages/ditto/controllers/api/statuses.ts | 2 +- packages/ditto/utils/zap-split.ts | 26 +++++++++++++++------- 4 files changed, 28 insertions(+), 16 deletions(-) diff --git a/packages/ditto/app.ts b/packages/ditto/app.ts index f417b1b0..8e818552 100644 --- a/packages/ditto/app.ts +++ b/packages/ditto/app.ts @@ -202,7 +202,7 @@ const pgstore = new DittoPgStore({ const pool = new DittoPool({ conf, relay: pgstore }); const relay = new DittoRelayStore({ db, conf, relay: pgstore }); -await seedZapSplits(relay); +await seedZapSplits({ conf, relay }); if (conf.firehoseEnabled) { startFirehose({ diff --git a/packages/ditto/controllers/api/ditto.ts b/packages/ditto/controllers/api/ditto.ts index 38c72eb4..be0a2c85 100644 --- a/packages/ditto/controllers/api/ditto.ts +++ b/packages/ditto/controllers/api/ditto.ts @@ -186,7 +186,8 @@ const zapSplitSchema = z.record( ); export const updateZapSplitsController: AppController = async (c) => { - const { conf, relay } = c.var; + const { conf } = c.var; + const body = await parseBody(c.req.raw); const result = zapSplitSchema.safeParse(body); @@ -196,7 +197,7 @@ export const updateZapSplitsController: AppController = async (c) => { const adminPubkey = await conf.signer.getPublicKey(); - const dittoZapSplit = await getZapSplits(relay, adminPubkey); + const dittoZapSplit = await getZapSplits(adminPubkey, c.var); if (!dittoZapSplit) { return c.json({ error: 'Zap split not activated, restart the server.' }, 404); } @@ -223,7 +224,8 @@ export const updateZapSplitsController: AppController = async (c) => { const deleteZapSplitSchema = z.array(n.id()).min(1); export const deleteZapSplitsController: AppController = async (c) => { - const { conf, relay } = c.var; + const { conf } = c.var; + const body = await parseBody(c.req.raw); const result = deleteZapSplitSchema.safeParse(body); @@ -233,7 +235,7 @@ export const deleteZapSplitsController: AppController = async (c) => { const adminPubkey = await conf.signer.getPublicKey(); - const dittoZapSplit = await getZapSplits(relay, adminPubkey); + const dittoZapSplit = await getZapSplits(adminPubkey, c.var); if (!dittoZapSplit) { return c.json({ error: 'Zap split not activated, restart the server.' }, 404); } @@ -253,9 +255,9 @@ export const deleteZapSplitsController: AppController = async (c) => { }; export const getZapSplitsController: AppController = async (c) => { - const { conf, relay } = c.var; + const { conf } = c.var; - const dittoZapSplit: DittoZapSplits | undefined = await getZapSplits(relay, await conf.signer.getPublicKey()) ?? {}; + const dittoZapSplit: DittoZapSplits | undefined = await getZapSplits(await conf.signer.getPublicKey(), c.var) ?? {}; if (!dittoZapSplit) { return c.json({ error: 'Zap split not activated, restart the server.' }, 404); } diff --git a/packages/ditto/controllers/api/statuses.ts b/packages/ditto/controllers/api/statuses.ts index 8bc04151..53fab57a 100644 --- a/packages/ditto/controllers/api/statuses.ts +++ b/packages/ditto/controllers/api/statuses.ts @@ -196,7 +196,7 @@ const createStatusController: AppController = async (c) => { if (conf.zapSplitsEnabled) { const meta = n.json().pipe(n.metadata()).catch({}).parse(author?.content); const lnurl = getLnurl(meta); - const dittoZapSplit = await getZapSplits(relay, await conf.signer.getPublicKey()); + const dittoZapSplit = await getZapSplits(await conf.signer.getPublicKey(), c.var); if (lnurl && dittoZapSplit) { const totalSplit = Object.values(dittoZapSplit).reduce((total, { weight }) => total + weight, 0); for (const zapPubkey in dittoZapSplit) { diff --git a/packages/ditto/utils/zap-split.ts b/packages/ditto/utils/zap-split.ts index 85b6f056..a9d5ba2b 100644 --- a/packages/ditto/utils/zap-split.ts +++ b/packages/ditto/utils/zap-split.ts @@ -1,8 +1,9 @@ -import { Conf } from '@/config.ts'; import { NSchema as n, NStore } from '@nostrify/nostrify'; import { nostrNow } from '@/utils.ts'; import { percentageSchema } from '@/schema.ts'; +import type { DittoConf } from '@ditto/conf'; + type Pubkey = string; type ExtraMessage = string; /** Number from 1 to 100, stringified. */ @@ -12,11 +13,18 @@ export type DittoZapSplits = { [key: Pubkey]: { weight: splitPercentages; message: ExtraMessage }; }; +interface GetZapSplitsOpts { + conf: DittoConf; + relay: NStore; +} + /** Gets zap splits from NIP-78 in DittoZapSplits format. */ -export async function getZapSplits(store: NStore, pubkey: string): Promise { +export async function getZapSplits(pubkey: string, opts: GetZapSplitsOpts): Promise { + const { relay } = opts; + const zapSplits: DittoZapSplits = {}; - const [event] = await store.query([{ + const [event] = await relay.query([{ authors: [pubkey], kinds: [30078], '#d': ['pub.ditto.zapSplits'], @@ -36,15 +44,17 @@ export async function getZapSplits(store: NStore, pubkey: string): Promise { + const { conf, relay } = opts; + + const pubkey = await conf.signer.getPublicKey(); + const zapSplit: DittoZapSplits | undefined = await getZapSplits(pubkey, opts); if (!zapSplit) { const dittoPubkey = '781a1527055f74c1f70230f10384609b34548f8ab6a0a6caa74025827f9fdae5'; const dittoMsg = 'Official Ditto Account'; - const signer = Conf.signer; - const event = await signer.signEvent({ + const event = await conf.signer.signEvent({ content: '', created_at: nostrNow(), kind: 30078, @@ -54,6 +64,6 @@ export async function seedZapSplits(store: NStore) { ], }); - await store.event(event); + await relay.event(event); } } From 0e667995c15da5c4023e98818384df298808d6e0 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Thu, 27 Feb 2025 14:15:02 -0600 Subject: [PATCH 07/17] Remove `@/config.ts` import from utils/note.ts --- packages/ditto/storages/DittoRelayStore.ts | 3 +- packages/ditto/utils/note.test.ts | 32 +++++++++++++++++++--- packages/ditto/utils/note.ts | 16 +++++++---- packages/ditto/views/mastodon/accounts.ts | 4 +-- packages/ditto/views/mastodon/statuses.ts | 2 +- 5 files changed, 44 insertions(+), 13 deletions(-) diff --git a/packages/ditto/storages/DittoRelayStore.ts b/packages/ditto/storages/DittoRelayStore.ts index 7b935d96..b3938c9d 100644 --- a/packages/ditto/storages/DittoRelayStore.ts +++ b/packages/ditto/storages/DittoRelayStore.ts @@ -324,7 +324,8 @@ export class DittoRelayStore implements NRelay { } private async prewarmLinkPreview(event: NostrEvent, signal?: AbortSignal): Promise { - const { firstUrl } = parseNoteContent(stripimeta(event.content, event.tags), []); + const { firstUrl } = parseNoteContent(stripimeta(event.content, event.tags), [], this.opts); + if (firstUrl) { await unfurlCardCached(firstUrl, signal); } diff --git a/packages/ditto/utils/note.test.ts b/packages/ditto/utils/note.test.ts index 699c4c5e..cdf29314 100644 --- a/packages/ditto/utils/note.test.ts +++ b/packages/ditto/utils/note.test.ts @@ -1,26 +1,35 @@ +import { DittoConf } from '@ditto/conf'; import { assertEquals } from '@std/assert'; import { eventFixture } from '@/test.ts'; import { getMediaLinks, parseNoteContent, stripimeta } from '@/utils/note.ts'; Deno.test('parseNoteContent', () => { - const { html, links, firstUrl } = parseNoteContent('Hello, world!', []); + const conf = new DittoConf(new Map()); + const { html, links, firstUrl } = parseNoteContent('Hello, world!', [], { conf }); + assertEquals(html, 'Hello, world!'); assertEquals(links, []); assertEquals(firstUrl, undefined); }); Deno.test('parseNoteContent parses URLs', () => { - const { html } = parseNoteContent('check out my website: https://alexgleason.me', []); + const conf = new DittoConf(new Map()); + const { html } = parseNoteContent('check out my website: https://alexgleason.me', [], { conf }); + assertEquals(html, 'check out my website: https://alexgleason.me'); }); Deno.test('parseNoteContent parses bare URLs', () => { - const { html } = parseNoteContent('have you seen ditto.pub?', []); + const conf = new DittoConf(new Map()); + const { html } = parseNoteContent('have you seen ditto.pub?', [], { conf }); + assertEquals(html, 'have you seen ditto.pub?'); }); Deno.test('parseNoteContent parses mentions with apostrophes', () => { + const conf = new DittoConf(new Map()); + const { html } = parseNoteContent( `did you see nostr:nprofile1qqsqgc0uhmxycvm5gwvn944c7yfxnnxm0nyh8tt62zhrvtd3xkj8fhgprdmhxue69uhkwmr9v9ek7mnpw3hhytnyv4mz7un9d3shjqgcwaehxw309ahx7umywf5hvefwv9c8qtmjv4kxz7gpzemhxue69uhhyetvv9ujumt0wd68ytnsw43z7s3al0v's speech?`, [{ @@ -29,7 +38,9 @@ Deno.test('parseNoteContent parses mentions with apostrophes', () => { acct: 'alex@gleasonator.dev', url: 'https://gleasonator.dev/@alex', }], + { conf }, ); + assertEquals( html, 'did you see @alex@gleasonator.dev's speech?', @@ -37,6 +48,8 @@ Deno.test('parseNoteContent parses mentions with apostrophes', () => { }); Deno.test('parseNoteContent parses mentions with commas', () => { + const conf = new DittoConf(new Map()); + const { html } = parseNoteContent( `Sim. Hi nostr:npub1q3sle0kvfsehgsuexttt3ugjd8xdklxfwwkh559wxckmzddywnws6cd26p and nostr:npub1gujeqakgt7fyp6zjggxhyy7ft623qtcaay5lkc8n8gkry4cvnrzqd3f67z, any chance to have Cobrafuma as PWA?`, [{ @@ -50,7 +63,9 @@ Deno.test('parseNoteContent parses mentions with commas', () => { acct: 'patrick@patrickdosreis.com', url: 'https://gleasonator.dev/@patrick@patrickdosreis.com', }], + { conf }, ); + assertEquals( html, 'Sim. Hi @alex@gleasonator.dev and @patrick@patrickdosreis.com, any chance to have Cobrafuma as PWA?', @@ -58,19 +73,26 @@ Deno.test('parseNoteContent parses mentions with commas', () => { }); Deno.test("parseNoteContent doesn't parse invalid nostr URIs", () => { - const { html } = parseNoteContent('nip19 has URIs like nostr:npub and nostr:nevent, etc.', []); + const conf = new DittoConf(new Map()); + const { html } = parseNoteContent('nip19 has URIs like nostr:npub and nostr:nevent, etc.', [], { conf }); assertEquals(html, 'nip19 has URIs like nostr:npub and nostr:nevent, etc.'); }); Deno.test('parseNoteContent renders empty for non-profile nostr URIs', () => { + const conf = new DittoConf(new Map()); + const { html } = parseNoteContent( 'nostr:nevent1qgsr9cvzwc652r4m83d86ykplrnm9dg5gwdvzzn8ameanlvut35wy3gpz3mhxue69uhhztnnwashymtnw3ezucm0d5qzqru8mkz2q4gzsxg99q7pdneyx7n8p5u0afe3ntapj4sryxxmg4gpcdvgce', [], + { conf }, ); + assertEquals(html, ''); }); Deno.test("parseNoteContent doesn't fuck up links to my own post", () => { + const conf = new DittoConf(new Map()); + const { html } = parseNoteContent( 'Check this post: https://gleasonator.dev/@alex@gleasonator.dev/posts/a8badb480d88f9e7b6a090342279ef47ed0e0a3989ed85f898dfedc6be94225f', [{ @@ -79,7 +101,9 @@ Deno.test("parseNoteContent doesn't fuck up links to my own post", () => { acct: 'alex@gleasonator.dev', url: 'https://gleasonator.dev/@alex', }], + { conf }, ); + assertEquals( html, 'Check this post: https://gleasonator.dev/@alex@gleasonator.dev/posts/a8badb480d88f9e7b6a090342279ef47ed0e0a3989ed85f898dfedc6be94225f', diff --git a/packages/ditto/utils/note.ts b/packages/ditto/utils/note.ts index a17a0f15..c51595f1 100644 --- a/packages/ditto/utils/note.ts +++ b/packages/ditto/utils/note.ts @@ -3,11 +3,11 @@ import linkifyStr from 'linkify-string'; import linkify from 'linkifyjs'; import { nip19, nip27 } from 'nostr-tools'; -import { Conf } from '@/config.ts'; import { html } from '@/utils/html.ts'; import { getUrlMediaType, isPermittedMediaType } from '@/utils/media.ts'; -import { MastodonMention } from '@ditto/mastoapi/types'; +import type { DittoConf } from '@ditto/conf'; +import type { MastodonMention } from '@ditto/mastoapi/types'; linkify.registerCustomProtocol('nostr', true); linkify.registerCustomProtocol('wss'); @@ -21,8 +21,14 @@ interface ParsedNoteContent { firstUrl: string | undefined; } +interface ParseNoteContentOpts { + conf: DittoConf; +} + /** Convert Nostr content to Mastodon API HTML. Also return parsed data. */ -function parseNoteContent(content: string, mentions: MastodonMention[]): ParsedNoteContent { +function parseNoteContent(content: string, mentions: MastodonMention[], opts: ParseNoteContentOpts): ParsedNoteContent { + const { conf } = opts; + const links = linkify.find(content).filter(({ type }) => type === 'url'); const firstUrl = links.find(isNonMediaLink)?.href; @@ -30,7 +36,7 @@ function parseNoteContent(content: string, mentions: MastodonMention[]): ParsedN render: { hashtag: ({ content }) => { const tag = content.replace(/^#/, ''); - const href = Conf.local(`/tags/${tag}`); + const href = conf.local(`/tags/${tag}`); return html``; }, url: ({ attributes, content }) => { @@ -49,7 +55,7 @@ function parseNoteContent(content: string, mentions: MastodonMention[]): ParsedN const npub = nip19.npubEncode(pubkey); const acct = mention?.acct ?? npub; const name = mention?.acct ?? npub.substring(0, 8); - const href = mention?.url ?? Conf.local(`/@${acct}`); + const href = mention?.url ?? conf.local(`/@${acct}`); return html`@${name}${extra}`; } else { return ''; diff --git a/packages/ditto/views/mastodon/accounts.ts b/packages/ditto/views/mastodon/accounts.ts index 827b7921..4639ade3 100644 --- a/packages/ditto/views/mastodon/accounts.ts +++ b/packages/ditto/views/mastodon/accounts.ts @@ -48,7 +48,7 @@ function renderAccount(event: Omit, opts: ToAccountOpt const parsed05 = stats?.nip05 ? parseNip05(stats.nip05) : undefined; const acct = parsed05?.handle || npub; - const { html } = parseNoteContent(about || '', []); + const { html } = parseNoteContent(about || '', [], { conf: Conf }); const fields = _fields ?.slice(0, Conf.profileFields.maxFields) @@ -84,7 +84,7 @@ function renderAccount(event: Omit, opts: ToAccountOpt discoverable: true, display_name: name ?? '', emojis: renderEmojis(event), - fields: fields.map((field) => ({ ...field, value: parseNoteContent(field.value, []).html })), + fields: fields.map((field) => ({ ...field, value: parseNoteContent(field.value, [], { conf: Conf }).html })), follow_requests_count: 0, followers_count: stats?.followers_count ?? 0, following_count: stats?.following_count ?? 0, diff --git a/packages/ditto/views/mastodon/statuses.ts b/packages/ditto/views/mastodon/statuses.ts index 55aa0808..065ac798 100644 --- a/packages/ditto/views/mastodon/statuses.ts +++ b/packages/ditto/views/mastodon/statuses.ts @@ -42,7 +42,7 @@ async function renderStatus( const mentions = event.mentions?.map((event) => renderMention(event)) ?? []; - const { html, links, firstUrl } = parseNoteContent(stripimeta(event.content, event.tags), mentions); + const { html, links, firstUrl } = parseNoteContent(stripimeta(event.content, event.tags), mentions, { conf: Conf }); const [card, relatedEvents] = await Promise .all([ From 7fe06753aac5aece91907fa7fe2224b2b7eb9aa4 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Thu, 27 Feb 2025 14:25:09 -0600 Subject: [PATCH 08/17] Remove `@/config.ts` import from utils/pleroma.ts --- packages/ditto/controllers/api/pleroma.ts | 12 ++++-------- packages/ditto/middleware/cspMiddleware.ts | 2 +- packages/ditto/utils/pleroma.ts | 17 +++++++++++++---- 3 files changed, 18 insertions(+), 13 deletions(-) diff --git a/packages/ditto/controllers/api/pleroma.ts b/packages/ditto/controllers/api/pleroma.ts index ef27696d..b4458c6c 100644 --- a/packages/ditto/controllers/api/pleroma.ts +++ b/packages/ditto/controllers/api/pleroma.ts @@ -7,9 +7,7 @@ import { lookupPubkey } from '@/utils/lookup.ts'; import { getPleromaConfigs } from '@/utils/pleroma.ts'; const frontendConfigController: AppController = async (c) => { - const { relay, signal } = c.var; - - const configDB = await getPleromaConfigs(relay, signal); + const configDB = await getPleromaConfigs(c.var); const frontendConfig = configDB.get(':pleroma', ':frontend_configurations'); if (frontendConfig) { @@ -25,17 +23,15 @@ const frontendConfigController: AppController = async (c) => { }; const configController: AppController = async (c) => { - const { relay, signal } = c.var; - - const configs = await getPleromaConfigs(relay, signal); + const configs = await getPleromaConfigs(c.var); return c.json({ configs, need_reboot: false }); }; /** Pleroma admin config controller. */ const updateConfigController: AppController = async (c) => { - const { conf, relay, signal } = c.var; + const { conf } = c.var; - const configs = await getPleromaConfigs(relay, signal); + const configs = await getPleromaConfigs(c.var); const { configs: newConfigs } = z.object({ configs: z.array(configSchema) }).parse(await c.req.json()); configs.merge(newConfigs); diff --git a/packages/ditto/middleware/cspMiddleware.ts b/packages/ditto/middleware/cspMiddleware.ts index 8e890101..4417a3a1 100644 --- a/packages/ditto/middleware/cspMiddleware.ts +++ b/packages/ditto/middleware/cspMiddleware.ts @@ -9,7 +9,7 @@ export const cspMiddleware = (): AppMiddleware => { const { conf, relay } = c.var; if (!configDBCache) { - configDBCache = getPleromaConfigs(relay); + configDBCache = getPleromaConfigs({ conf, relay }); } const { host, protocol, origin } = conf.url; diff --git a/packages/ditto/utils/pleroma.ts b/packages/ditto/utils/pleroma.ts index db3ca6a1..ae7588e3 100644 --- a/packages/ditto/utils/pleroma.ts +++ b/packages/ditto/utils/pleroma.ts @@ -1,14 +1,23 @@ import { NSchema as n, NStore } from '@nostrify/nostrify'; -import { Conf } from '@/config.ts'; import { configSchema } from '@/schemas/pleroma-api.ts'; import { PleromaConfigDB } from '@/utils/PleromaConfigDB.ts'; -export async function getPleromaConfigs(store: NStore, signal?: AbortSignal): Promise { - const signer = Conf.signer; +import type { DittoConf } from '@ditto/conf'; + +interface GetPleromaConfigsOpts { + conf: DittoConf; + relay: NStore; + signal?: AbortSignal; +} + +export async function getPleromaConfigs(opts: GetPleromaConfigsOpts): Promise { + const { conf, relay, signal } = opts; + + const signer = conf.signer; const pubkey = await signer.getPublicKey(); - const [event] = await store.query([{ + const [event] = await relay.query([{ kinds: [30078], authors: [pubkey], '#d': ['pub.ditto.pleroma.config'], From 5f5d0bc324af830475b69c331bf8870a99f431a1 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Thu, 27 Feb 2025 16:35:06 -0600 Subject: [PATCH 09/17] Remove `@/config.ts` import from utils/stats.ts --- packages/ditto/app.ts | 2 +- packages/ditto/storages/DittoPgStore.ts | 19 ++++--- packages/ditto/test.ts | 2 +- packages/ditto/utils/stats.test.ts | 23 ++++---- packages/ditto/utils/stats.ts | 53 +++++++++++++------ packages/ditto/workers/policy.worker.ts | 13 ++++- .../translators/LibreTranslateTranslator.ts | 2 - scripts/admin-event.ts | 2 +- scripts/admin-role.ts | 2 +- scripts/db-export.ts | 2 +- scripts/db-import.ts | 2 +- scripts/db-policy.ts | 2 +- scripts/db-populate-nip05.ts | 2 +- scripts/db-populate-search.ts | 2 +- scripts/nostr-pull.ts | 2 +- scripts/setup-kind0.ts | 2 +- scripts/stats-recompute.ts | 2 +- scripts/trends.ts | 2 +- 18 files changed, 86 insertions(+), 50 deletions(-) diff --git a/packages/ditto/app.ts b/packages/ditto/app.ts index 8e818552..5f3a12e4 100644 --- a/packages/ditto/app.ts +++ b/packages/ditto/app.ts @@ -194,7 +194,7 @@ await db.migrate(); const pgstore = new DittoPgStore({ db, - pubkey: await conf.signer.getPublicKey(), + conf, timeout: conf.db.timeouts.default, notify: conf.notifyEnabled, }); diff --git a/packages/ditto/storages/DittoPgStore.ts b/packages/ditto/storages/DittoPgStore.ts index ea3e864c..34625fbc 100644 --- a/packages/ditto/storages/DittoPgStore.ts +++ b/packages/ditto/storages/DittoPgStore.ts @@ -1,5 +1,6 @@ // deno-lint-ignore-file require-await +import { type DittoConf } from '@ditto/conf'; import { type DittoDB, type DittoTables } from '@ditto/db'; import { detectLanguage } from '@ditto/lang'; import { NPostgres, NPostgresSchema } from '@nostrify/db'; @@ -52,8 +53,8 @@ interface TagConditionOpts { interface DittoPgStoreOpts { /** Kysely instance to use. */ db: DittoDB; - /** Pubkey of the admin account. */ - pubkey: string; + /** Ditto configuration. */ + conf: DittoConf; /** Timeout in milliseconds for database queries. */ timeout?: number; /** Whether the event returned should be a Nostr event or a Ditto event. Defaults to false. */ @@ -171,7 +172,7 @@ export class DittoPgStore extends NPostgres { ): Promise { try { await super.transaction(async (relay, kysely) => { - await updateStats({ event, relay, kysely: kysely as unknown as Kysely }); + await updateStats({ conf: this.opts.conf, relay, kysely: kysely as unknown as Kysely, event }); await relay.event(event, opts); }); } catch (e) { @@ -229,8 +230,11 @@ export class DittoPgStore extends NPostgres { /** Check if an event has been deleted by the admin. */ private async isDeletedAdmin(event: NostrEvent): Promise { + const { conf } = this.opts; + const adminPubkey = await conf.signer.getPublicKey(); + const filters: NostrFilter[] = [ - { kinds: [5], authors: [this.opts.pubkey], '#e': [event.id], limit: 1 }, + { kinds: [5], authors: [adminPubkey], '#e': [event.id], limit: 1 }, ]; if (NKinds.replaceable(event.kind) || NKinds.parameterizedReplaceable(event.kind)) { @@ -238,7 +242,7 @@ export class DittoPgStore extends NPostgres { filters.push({ kinds: [5], - authors: [this.opts.pubkey], + authors: [adminPubkey], '#a': [`${event.kind}:${event.pubkey}:${d}`], since: event.created_at, limit: 1, @@ -251,7 +255,10 @@ export class DittoPgStore extends NPostgres { /** The DITTO_NSEC can delete any event from the database. NDatabase already handles user deletions. */ private async deleteEventsAdmin(event: NostrEvent): Promise { - if (event.kind === 5 && event.pubkey === this.opts.pubkey) { + const { conf } = this.opts; + const adminPubkey = await conf.signer.getPublicKey(); + + if (event.kind === 5 && event.pubkey === adminPubkey) { const ids = new Set(event.tags.filter(([name]) => name === 'e').map(([_name, value]) => value)); const addrs = new Set(event.tags.filter(([name]) => name === 'a').map(([_name, value]) => value)); diff --git a/packages/ditto/test.ts b/packages/ditto/test.ts index d2ea5ec7..31b31e37 100644 --- a/packages/ditto/test.ts +++ b/packages/ditto/test.ts @@ -19,8 +19,8 @@ export async function createTestDB(opts?: { pure?: boolean }) { const store = new DittoPgStore({ db, + conf, timeout: conf.db.timeouts.default, - pubkey: await conf.signer.getPublicKey(), pure: opts?.pure ?? false, notify: true, }); diff --git a/packages/ditto/utils/stats.test.ts b/packages/ditto/utils/stats.test.ts index 043e6f13..2ebcab94 100644 --- a/packages/ditto/utils/stats.test.ts +++ b/packages/ditto/utils/stats.test.ts @@ -23,7 +23,7 @@ Deno.test('updateStats with kind 1 increments notes count', async () => { Deno.test('updateStats with kind 1 increments replies count', async () => { await using test = await setupTest(); - const { relay, kysely } = test; + const { kysely, relay } = test; const sk = generateSecretKey(); @@ -42,7 +42,7 @@ Deno.test('updateStats with kind 1 increments replies count', async () => { Deno.test('updateStats with kind 5 decrements notes count', async () => { await using test = await setupTest(); - const { relay, kysely } = test; + const { kysely, relay } = test; const sk = generateSecretKey(); const pubkey = getPublicKey(sk); @@ -74,7 +74,7 @@ Deno.test('updateStats with kind 3 increments followers count', async () => { Deno.test('updateStats with kind 3 decrements followers count', async () => { await using test = await setupTest(); - const { relay, kysely } = test; + const { kysely, relay } = test; const sk = generateSecretKey(); const follow = genEvent({ kind: 3, tags: [['p', 'alex']], created_at: 0 }, sk); @@ -101,7 +101,7 @@ Deno.test('getFollowDiff returns added and removed followers', () => { Deno.test('updateStats with kind 6 increments reposts count', async () => { await using test = await setupTest(); - const { relay, kysely } = test; + const { kysely, relay } = test; const note = genEvent({ kind: 1 }); await updateStats({ ...test, event: note }); @@ -118,7 +118,7 @@ Deno.test('updateStats with kind 6 increments reposts count', async () => { Deno.test('updateStats with kind 5 decrements reposts count', async () => { await using test = await setupTest(); - const { relay, kysely } = test; + const { kysely, relay } = test; const note = genEvent({ kind: 1 }); await updateStats({ ...test, event: note }); @@ -138,7 +138,7 @@ Deno.test('updateStats with kind 5 decrements reposts count', async () => { Deno.test('updateStats with kind 7 increments reactions count', async () => { await using test = await setupTest(); - const { relay, kysely } = test; + const { kysely, relay } = test; const note = genEvent({ kind: 1 }); await updateStats({ ...test, event: note }); @@ -155,7 +155,7 @@ Deno.test('updateStats with kind 7 increments reactions count', async () => { Deno.test('updateStats with kind 5 decrements reactions count', async () => { await using test = await setupTest(); - const { relay, kysely } = test; + const { kysely, relay } = test; const note = genEvent({ kind: 1 }); await updateStats({ ...test, event: note }); @@ -175,7 +175,7 @@ Deno.test('updateStats with kind 5 decrements reactions count', async () => { Deno.test('countAuthorStats counts author stats from the database', async () => { await using test = await setupTest(); - const { relay } = test; + const { kysely, relay } = test; const sk = generateSecretKey(); const pubkey = getPublicKey(sk); @@ -184,7 +184,7 @@ Deno.test('countAuthorStats counts author stats from the database', async () => await relay.event(genEvent({ kind: 1, content: 'yolo' }, sk)); await relay.event(genEvent({ kind: 3, tags: [['p', pubkey]] })); - await test.kysely.insertInto('author_stats').values({ + await kysely.insertInto('author_stats').values({ pubkey, search: 'Yolo Lolo', notes_count: 0, @@ -193,7 +193,7 @@ Deno.test('countAuthorStats counts author stats from the database', async () => }).onConflict((oc) => oc.column('pubkey').doUpdateSet({ 'search': 'baka' })) .execute(); - const stats = await countAuthorStats({ ...test, pubkey }); + const stats = await countAuthorStats({ ...test, kysely, pubkey }); assertEquals(stats!.notes_count, 2); assertEquals(stats!.followers_count, 1); @@ -206,9 +206,10 @@ async function setupTest() { await db.migrate(); const { kysely } = db; - const relay = new NPostgres(kysely); + const relay = new NPostgres(db.kysely); return { + conf, relay, kysely, [Symbol.asyncDispose]: async () => { diff --git a/packages/ditto/utils/stats.ts b/packages/ditto/utils/stats.ts index 448ba241..922d5dca 100644 --- a/packages/ditto/utils/stats.ts +++ b/packages/ditto/utils/stats.ts @@ -4,40 +4,46 @@ import { Insertable, Kysely, UpdateObject } from 'kysely'; import { SetRequired } from 'type-fest'; import { z } from 'zod'; -import { Conf } from '@/config.ts'; import { findQuoteTag, findReplyTag, getTagSet } from '@/utils/tags.ts'; +import type { DittoConf } from '@ditto/conf'; + interface UpdateStatsOpts { - kysely: Kysely; + conf: DittoConf; relay: NStore; + kysely: Kysely; event: NostrEvent; x?: 1 | -1; } /** Handle one event at a time and update relevant stats for it. */ // deno-lint-ignore require-await -export async function updateStats({ event, kysely, relay, x = 1 }: UpdateStatsOpts): Promise { +export async function updateStats(opts: UpdateStatsOpts): Promise { + const { event } = opts; + switch (event.kind) { case 1: case 20: case 1111: case 30023: - return handleEvent1(kysely, event, x); + return handleEvent1(opts); case 3: - return handleEvent3(kysely, event, x, relay); + return handleEvent3(opts); case 5: - return handleEvent5(kysely, event, -1, relay); + return handleEvent5(opts); case 6: - return handleEvent6(kysely, event, x); + return handleEvent6(opts); case 7: - return handleEvent7(kysely, event, x); + return handleEvent7(opts); case 9735: - return handleEvent9735(kysely, event); + return handleEvent9735(opts); } } /** Update stats for kind 1 event. */ -async function handleEvent1(kysely: Kysely, event: NostrEvent, x: number): Promise { +async function handleEvent1(opts: UpdateStatsOpts): Promise { + const { conf, kysely, event, x = 1 } = opts; + await updateAuthorStats(kysely, event.pubkey, (prev) => { const now = event.created_at; @@ -47,7 +53,7 @@ async function handleEvent1(kysely: Kysely, event: NostrEvent, x: n if (start && end) { // Streak exists. if (now <= end) { // Streak cannot go backwards in time. Skip it. - } else if (now - end > Conf.streakWindow) { + } else if (now - end > conf.streakWindow) { // Streak is broken. Start a new streak. start = now; end = now; @@ -88,7 +94,9 @@ async function handleEvent1(kysely: Kysely, event: NostrEvent, x: n } /** Update stats for kind 3 event. */ -async function handleEvent3(kysely: Kysely, event: NostrEvent, x: number, relay: NStore): Promise { +async function handleEvent3(opts: UpdateStatsOpts): Promise { + const { relay, kysely, event, x = 1 } = opts; + const following = getTagSet(event.tags, 'p'); await updateAuthorStats(kysely, event.pubkey, () => ({ following_count: following.size })); @@ -117,26 +125,34 @@ async function handleEvent3(kysely: Kysely, event: NostrEvent, x: n } /** Update stats for kind 5 event. */ -async function handleEvent5(kysely: Kysely, event: NostrEvent, x: -1, relay: NStore): Promise { +async function handleEvent5(opts: UpdateStatsOpts): Promise { + const { relay, event, x = -1 } = opts; + const id = event.tags.find(([name]) => name === 'e')?.[1]; + if (id) { const [target] = await relay.query([{ ids: [id], authors: [event.pubkey], limit: 1 }]); if (target) { - await updateStats({ event: target, kysely, relay, x }); + await updateStats({ ...opts, event: target, x }); } } } /** Update stats for kind 6 event. */ -async function handleEvent6(kysely: Kysely, event: NostrEvent, x: number): Promise { +async function handleEvent6(opts: UpdateStatsOpts): Promise { + const { kysely, event, x = 1 } = opts; + const id = event.tags.find(([name]) => name === 'e')?.[1]; + if (id) { await updateEventStats(kysely, id, ({ reposts_count }) => ({ reposts_count: Math.max(0, reposts_count + x) })); } } /** Update stats for kind 7 event. */ -async function handleEvent7(kysely: Kysely, event: NostrEvent, x: number): Promise { +async function handleEvent7(opts: UpdateStatsOpts): Promise { + const { kysely, event, x = 1 } = opts; + const id = event.tags.findLast(([name]) => name === 'e')?.[1]; const emoji = event.content; @@ -166,12 +182,15 @@ async function handleEvent7(kysely: Kysely, event: NostrEvent, x: n } /** Update stats for kind 9735 event. */ -async function handleEvent9735(kysely: Kysely, event: NostrEvent): Promise { +async function handleEvent9735(opts: UpdateStatsOpts): Promise { + const { kysely, event } = opts; + // https://github.com/nostr-protocol/nips/blob/master/57.md#appendix-f-validating-zap-receipts const id = event.tags.find(([name]) => name === 'e')?.[1]; if (!id) return; const amountSchema = z.coerce.number().int().nonnegative().catch(0); + let amount = 0; try { const zapRequest = n.json().pipe(n.event()).parse(event.tags.find(([name]) => name === 'description')?.[1]); diff --git a/packages/ditto/workers/policy.worker.ts b/packages/ditto/workers/policy.worker.ts index b2ca3720..6ea39f1a 100644 --- a/packages/ditto/workers/policy.worker.ts +++ b/packages/ditto/workers/policy.worker.ts @@ -1,11 +1,13 @@ import './deno-env.ts'; // HACK should be removed when `@/config.ts` is removed. +import { DittoConf } from '@ditto/conf'; import { DittoPolyPg } from '@ditto/db'; import '@soapbox/safe-fetch/load'; import { NostrEvent, NostrRelayOK, NPolicy } from '@nostrify/nostrify'; import { ReadOnlyPolicy } from '@nostrify/policies'; import * as Comlink from 'comlink'; +import { ReadOnlySigner } from '@/signers/ReadOnlySigner.ts'; import { DittoPgStore } from '@/storages/DittoPgStore.ts'; /** Serializable object the worker can use to set up the state. */ @@ -31,9 +33,18 @@ export class CustomPolicy implements NPolicy { const db = new DittoPolyPg(databaseUrl, { poolSize: 1 }); + const conf = new Proxy(new DittoConf(new Map()), { + get(target, prop) { + if (prop === 'signer') { + return new ReadOnlySigner(pubkey); + } + return Reflect.get(target, prop); + }, + }); + const store = new DittoPgStore({ db, - pubkey, + conf, timeout: 5_000, }); diff --git a/packages/translators/LibreTranslateTranslator.ts b/packages/translators/LibreTranslateTranslator.ts index cc978e90..62c39e1c 100644 --- a/packages/translators/LibreTranslateTranslator.ts +++ b/packages/translators/LibreTranslateTranslator.ts @@ -72,8 +72,6 @@ export class LibreTranslateTranslator implements DittoTranslator { const response = await this.fetch(request); const json = await response.json(); - console.log(json); - if (!response.ok) { const result = LibreTranslateTranslator.errorSchema().safeParse(json); diff --git a/scripts/admin-event.ts b/scripts/admin-event.ts index bec49460..750057ec 100644 --- a/scripts/admin-event.ts +++ b/scripts/admin-event.ts @@ -9,7 +9,7 @@ import { nostrNow } from '../packages/ditto/utils.ts'; const conf = new DittoConf(Deno.env); const db = new DittoPolyPg(conf.databaseUrl); -const relay = new DittoPgStore({ db, pubkey: await conf.signer.getPublicKey() }); +const relay = new DittoPgStore({ db, conf }); const { signer } = conf; diff --git a/scripts/admin-role.ts b/scripts/admin-role.ts index 59b95878..ba2059f6 100644 --- a/scripts/admin-role.ts +++ b/scripts/admin-role.ts @@ -8,7 +8,7 @@ import { nostrNow } from '../packages/ditto/utils.ts'; const conf = new DittoConf(Deno.env); const db = new DittoPolyPg(conf.databaseUrl); -const relay = new DittoPgStore({ db, pubkey: await conf.signer.getPublicKey() }); +const relay = new DittoPgStore({ db, conf }); const [pubkeyOrNpub, role] = Deno.args; const pubkey = pubkeyOrNpub.startsWith('npub1') ? nip19.decode(pubkeyOrNpub as `npub1${string}`).data : pubkeyOrNpub; diff --git a/scripts/db-export.ts b/scripts/db-export.ts index d9295420..215e9d6f 100644 --- a/scripts/db-export.ts +++ b/scripts/db-export.ts @@ -7,7 +7,7 @@ import { DittoPgStore } from '../packages/ditto/storages/DittoPgStore.ts'; const conf = new DittoConf(Deno.env); const db = new DittoPolyPg(conf.databaseUrl); -const relay = new DittoPgStore({ db, pubkey: await conf.signer.getPublicKey() }); +const relay = new DittoPgStore({ db, conf }); interface ExportFilter { authors?: string[]; diff --git a/scripts/db-import.ts b/scripts/db-import.ts index 4d27e54a..3a51ddb0 100644 --- a/scripts/db-import.ts +++ b/scripts/db-import.ts @@ -9,7 +9,7 @@ import { DittoPgStore } from '../packages/ditto/storages/DittoPgStore.ts'; const conf = new DittoConf(Deno.env); const db = new DittoPolyPg(conf.databaseUrl); -const relay = new DittoPgStore({ db, pubkey: await conf.signer.getPublicKey() }); +const relay = new DittoPgStore({ db, conf }); const sem = new Semaphore(conf.pg.poolSize); console.warn('Importing events...'); diff --git a/scripts/db-policy.ts b/scripts/db-policy.ts index b7ceee96..9dcf8986 100644 --- a/scripts/db-policy.ts +++ b/scripts/db-policy.ts @@ -6,7 +6,7 @@ import { PolicyWorker } from '../packages/ditto/workers/policy.ts'; const conf = new DittoConf(Deno.env); const db = new DittoPolyPg(conf.databaseUrl); -const relay = new DittoPgStore({ db, pubkey: await conf.signer.getPublicKey() }); +const relay = new DittoPgStore({ db, conf }); const policyWorker = new PolicyWorker(conf); let count = 0; diff --git a/scripts/db-populate-nip05.ts b/scripts/db-populate-nip05.ts index c1015f9f..49866579 100644 --- a/scripts/db-populate-nip05.ts +++ b/scripts/db-populate-nip05.ts @@ -10,7 +10,7 @@ import { DittoRelayStore } from '../packages/ditto/storages/DittoRelayStore.ts'; const conf = new DittoConf(Deno.env); const db = new DittoPolyPg(conf.databaseUrl); -const pgstore = new DittoPgStore({ db, pubkey: await conf.signer.getPublicKey() }); +const pgstore = new DittoPgStore({ db, conf }); const relaystore = new DittoRelayStore({ conf, db, relay: pgstore }); const sem = new Semaphore(5); diff --git a/scripts/db-populate-search.ts b/scripts/db-populate-search.ts index 7189b30c..54b28737 100644 --- a/scripts/db-populate-search.ts +++ b/scripts/db-populate-search.ts @@ -6,7 +6,7 @@ import { DittoPgStore } from '../packages/ditto/storages/DittoPgStore.ts'; const conf = new DittoConf(Deno.env); const db = new DittoPolyPg(conf.databaseUrl); -const relay = new DittoPgStore({ db, pubkey: await conf.signer.getPublicKey() }); +const relay = new DittoPgStore({ db, conf }); for await (const msg of relay.req([{ kinds: [0] }])) { if (msg[0] === 'EVENT') { diff --git a/scripts/nostr-pull.ts b/scripts/nostr-pull.ts index d8a4513a..b8204f94 100644 --- a/scripts/nostr-pull.ts +++ b/scripts/nostr-pull.ts @@ -12,7 +12,7 @@ import { DittoPgStore } from '../packages/ditto/storages/DittoPgStore.ts'; const conf = new DittoConf(Deno.env); const db = new DittoPolyPg(conf.databaseUrl); -const relay = new DittoPgStore({ db, pubkey: await conf.signer.getPublicKey() }); +const relay = new DittoPgStore({ db, conf }); interface ImportEventsOpts { profilesOnly: boolean; diff --git a/scripts/setup-kind0.ts b/scripts/setup-kind0.ts index b3dd0682..5428fdcd 100644 --- a/scripts/setup-kind0.ts +++ b/scripts/setup-kind0.ts @@ -7,7 +7,7 @@ import { DittoPgStore } from '../packages/ditto/storages/DittoPgStore.ts'; const conf = new DittoConf(Deno.env); const db = new DittoPolyPg(conf.databaseUrl); -const relay = new DittoPgStore({ db, pubkey: await conf.signer.getPublicKey() }); +const relay = new DittoPgStore({ db, conf }); function die(code: number, ...args: unknown[]) { console.error(...args); diff --git a/scripts/stats-recompute.ts b/scripts/stats-recompute.ts index 16614e45..432e6f92 100644 --- a/scripts/stats-recompute.ts +++ b/scripts/stats-recompute.ts @@ -7,7 +7,7 @@ import { refreshAuthorStats } from '../packages/ditto/utils/stats.ts'; const conf = new DittoConf(Deno.env); const db = new DittoPolyPg(conf.databaseUrl); -const relay = new DittoPgStore({ db, pubkey: await conf.signer.getPublicKey() }); +const relay = new DittoPgStore({ db, conf }); const { kysely } = db; diff --git a/scripts/trends.ts b/scripts/trends.ts index 2a878a12..067643d9 100644 --- a/scripts/trends.ts +++ b/scripts/trends.ts @@ -13,7 +13,7 @@ import { const conf = new DittoConf(Deno.env); const db = new DittoPolyPg(conf.databaseUrl); -const relay = new DittoPgStore({ db, pubkey: await conf.signer.getPublicKey() }); +const relay = new DittoPgStore({ db, conf }); const ctx = { conf, db, relay }; const trendSchema = z.enum(['pubkeys', 'zapped_events', 'events', 'hashtags', 'links']); From 7e00b9f9a2c473d442da802351d57101bc730919 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Thu, 27 Feb 2025 16:52:09 -0600 Subject: [PATCH 10/17] Add PolicyWorker test --- packages/ditto/workers/deno-env.ts | 2 -- packages/ditto/workers/policy.test.ts | 14 ++++++++++++++ packages/ditto/workers/policy.ts | 11 +++++++++++ packages/ditto/workers/policy.worker.ts | 5 +++-- 4 files changed, 28 insertions(+), 4 deletions(-) delete mode 100644 packages/ditto/workers/deno-env.ts create mode 100644 packages/ditto/workers/policy.test.ts diff --git a/packages/ditto/workers/deno-env.ts b/packages/ditto/workers/deno-env.ts deleted file mode 100644 index 1500eb57..00000000 --- a/packages/ditto/workers/deno-env.ts +++ /dev/null @@ -1,2 +0,0 @@ -// @ts-ignore Don't try to access the env from this worker. -Deno.env = new Map(); diff --git a/packages/ditto/workers/policy.test.ts b/packages/ditto/workers/policy.test.ts new file mode 100644 index 00000000..623e7c98 --- /dev/null +++ b/packages/ditto/workers/policy.test.ts @@ -0,0 +1,14 @@ +import { DittoConf } from '@ditto/conf'; +import { generateSecretKey, nip19 } from 'nostr-tools'; + +import { PolicyWorker } from './policy.ts'; + +Deno.test('PolicyWorker', () => { + const conf = new DittoConf( + new Map([ + ['DITTO_NSEC', nip19.nsecEncode(generateSecretKey())], + ]), + ); + + new PolicyWorker(conf); +}); diff --git a/packages/ditto/workers/policy.ts b/packages/ditto/workers/policy.ts index e2617f72..6cddd0a0 100644 --- a/packages/ditto/workers/policy.ts +++ b/packages/ditto/workers/policy.ts @@ -3,6 +3,8 @@ import { NostrEvent, NostrRelayOK, NPolicy } from '@nostrify/nostrify'; import { logi } from '@soapbox/logi'; import * as Comlink from 'comlink'; +import { errorJson } from '@/utils/log.ts'; + import type { CustomPolicy } from '@/workers/policy.worker.ts'; export class PolicyWorker implements NPolicy { @@ -85,6 +87,15 @@ export class PolicyWorker implements NPolicy { return; } + logi({ + level: 'error', + ns: 'ditto.system.policy', + msg: 'Failed to load custom policy', + path: conf.policy, + error: errorJson(e), + enabled: false, + }); + throw new Error(`DITTO_POLICY (error importing policy): ${conf.policy}`); } } diff --git a/packages/ditto/workers/policy.worker.ts b/packages/ditto/workers/policy.worker.ts index 6ea39f1a..80966c2e 100644 --- a/packages/ditto/workers/policy.worker.ts +++ b/packages/ditto/workers/policy.worker.ts @@ -1,5 +1,3 @@ -import './deno-env.ts'; // HACK should be removed when `@/config.ts` is removed. - import { DittoConf } from '@ditto/conf'; import { DittoPolyPg } from '@ditto/db'; import '@soapbox/safe-fetch/load'; @@ -10,6 +8,9 @@ import * as Comlink from 'comlink'; import { ReadOnlySigner } from '@/signers/ReadOnlySigner.ts'; import { DittoPgStore } from '@/storages/DittoPgStore.ts'; +// @ts-ignore Don't try to access the env from this worker. +Deno.env = new Map(); + /** Serializable object the worker can use to set up the state. */ interface PolicyInit { /** Path to the policy module (https, jsr, file, etc) */ From 70d8c2ed594eb5e9871881ce927c80fd5bbf0bf3 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Thu, 27 Feb 2025 16:54:12 -0600 Subject: [PATCH 11/17] DittoPgStore: destructure conf --- packages/ditto/storages/DittoPgStore.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/ditto/storages/DittoPgStore.ts b/packages/ditto/storages/DittoPgStore.ts index 34625fbc..f73d1703 100644 --- a/packages/ditto/storages/DittoPgStore.ts +++ b/packages/ditto/storages/DittoPgStore.ts @@ -170,9 +170,10 @@ export class DittoPgStore extends NPostgres { event: NostrEvent, opts: { signal?: AbortSignal; timeout?: number } = {}, ): Promise { + const { conf } = this.opts; try { await super.transaction(async (relay, kysely) => { - await updateStats({ conf: this.opts.conf, relay, kysely: kysely as unknown as Kysely, event }); + await updateStats({ conf, relay, kysely: kysely as unknown as Kysely, event }); await relay.event(event, opts); }); } catch (e) { From 2acd23ebbe88def5b604c194e713985b12e7b682 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Thu, 27 Feb 2025 17:32:54 -0600 Subject: [PATCH 12/17] Remove `@/config.ts` import from utils/instance.ts --- packages/ditto/DittoPush.ts | 4 ++-- packages/ditto/controllers/api/ditto.ts | 4 ++-- packages/ditto/controllers/api/instance.ts | 12 +++++------ packages/ditto/controllers/frontend.ts | 2 +- packages/ditto/controllers/manifest.ts | 4 +--- .../ditto/controllers/nostr/relay-info.ts | 4 ++-- packages/ditto/utils/instance.ts | 21 +++++++++++++------ 7 files changed, 28 insertions(+), 23 deletions(-) diff --git a/packages/ditto/DittoPush.ts b/packages/ditto/DittoPush.ts index 3a378300..c1dded38 100644 --- a/packages/ditto/DittoPush.ts +++ b/packages/ditto/DittoPush.ts @@ -14,10 +14,10 @@ export class DittoPush { private server: Promise; constructor(opts: DittoPushOpts) { - const { conf, relay } = opts; + const { conf } = opts; this.server = (async () => { - const meta = await getInstanceMetadata(relay); + const meta = await getInstanceMetadata(opts); const keys = await conf.vapidKeys; if (keys) { diff --git a/packages/ditto/controllers/api/ditto.ts b/packages/ditto/controllers/api/ditto.ts index be0a2c85..7493085a 100644 --- a/packages/ditto/controllers/api/ditto.ts +++ b/packages/ditto/controllers/api/ditto.ts @@ -327,7 +327,7 @@ const updateInstanceSchema = z.object({ }); export const updateInstanceController: AppController = async (c) => { - const { conf, relay, signal } = c.var; + const { conf } = c.var; const body = await parseBody(c.req.raw); const result = updateInstanceSchema.safeParse(body); @@ -337,7 +337,7 @@ export const updateInstanceController: AppController = async (c) => { return c.json(result.error, 422); } - const meta = await getInstanceMetadata(relay, signal); + const meta = await getInstanceMetadata(c.var); await updateAdminEvent( { kinds: [0], authors: [pubkey], limit: 1 }, diff --git a/packages/ditto/controllers/api/instance.ts b/packages/ditto/controllers/api/instance.ts index 1fb742e5..a887fe4d 100644 --- a/packages/ditto/controllers/api/instance.ts +++ b/packages/ditto/controllers/api/instance.ts @@ -15,9 +15,9 @@ const features = [ ]; const instanceV1Controller: AppController = async (c) => { - const { conf, relay, signal } = c.var; + const { conf } = c.var; const { host, protocol } = conf.url; - const meta = await getInstanceMetadata(relay, signal); + const meta = await getInstanceMetadata(c.var); /** Protocol to use for WebSocket URLs, depending on the protocol of the `LOCAL_DOMAIN`. */ const wsProtocol = protocol === 'http:' ? 'ws:' : 'wss:'; @@ -75,9 +75,9 @@ const instanceV1Controller: AppController = async (c) => { }; const instanceV2Controller: AppController = async (c) => { - const { conf, relay, signal } = c.var; + const { conf } = c.var; const { host, protocol } = conf.url; - const meta = await getInstanceMetadata(relay, signal); + const meta = await getInstanceMetadata(c.var); /** Protocol to use for WebSocket URLs, depending on the protocol of the `LOCAL_DOMAIN`. */ const wsProtocol = protocol === 'http:' ? 'ws:' : 'wss:'; @@ -164,9 +164,7 @@ const instanceV2Controller: AppController = async (c) => { }; const instanceDescriptionController: AppController = async (c) => { - const { relay, signal } = c.var; - - const meta = await getInstanceMetadata(relay, signal); + const meta = await getInstanceMetadata(c.var); return c.json({ content: meta.about, diff --git a/packages/ditto/controllers/frontend.ts b/packages/ditto/controllers/frontend.ts index ad98a9aa..c0a6848f 100644 --- a/packages/ditto/controllers/frontend.ts +++ b/packages/ditto/controllers/frontend.ts @@ -40,7 +40,7 @@ async function getEntities(c: AppContext, params: { acct?: string; statusId?: st const { relay } = c.var; const entities: MetadataEntities = { - instance: await getInstanceMetadata(relay), + instance: await getInstanceMetadata(c.var), }; if (params.statusId) { diff --git a/packages/ditto/controllers/manifest.ts b/packages/ditto/controllers/manifest.ts index 70d42dea..7d49ba1e 100644 --- a/packages/ditto/controllers/manifest.ts +++ b/packages/ditto/controllers/manifest.ts @@ -3,9 +3,7 @@ import { WebManifestCombined } from '@/types/webmanifest.ts'; import { getInstanceMetadata } from '@/utils/instance.ts'; export const manifestController: AppController = async (c) => { - const { relay, signal } = c.var; - - const meta = await getInstanceMetadata(relay, signal); + const meta = await getInstanceMetadata(c.var); const manifest: WebManifestCombined = { description: meta.about, diff --git a/packages/ditto/controllers/nostr/relay-info.ts b/packages/ditto/controllers/nostr/relay-info.ts index 50702c23..945e311b 100644 --- a/packages/ditto/controllers/nostr/relay-info.ts +++ b/packages/ditto/controllers/nostr/relay-info.ts @@ -4,9 +4,9 @@ import { AppController } from '@/app.ts'; import { getInstanceMetadata } from '@/utils/instance.ts'; const relayInfoController: AppController = async (c) => { - const { conf, relay, signal } = c.var; + const { conf } = c.var; - const meta = await getInstanceMetadata(relay, signal); + const meta = await getInstanceMetadata(c.var); c.res.headers.set('access-control-allow-origin', '*'); diff --git a/packages/ditto/utils/instance.ts b/packages/ditto/utils/instance.ts index 3f746e07..52fe7358 100644 --- a/packages/ditto/utils/instance.ts +++ b/packages/ditto/utils/instance.ts @@ -1,9 +1,10 @@ import { NostrEvent, NostrMetadata, NSchema as n, NStore } from '@nostrify/nostrify'; import { z } from 'zod'; -import { Conf } from '@/config.ts'; import { screenshotsSchema, serverMetaSchema } from '@/schemas/nostr.ts'; +import type { DittoConf } from '@ditto/conf'; + /** Like NostrMetadata, but some fields are required and also contains some extra fields. */ export interface InstanceMetadata extends NostrMetadata { about: string; @@ -15,10 +16,18 @@ export interface InstanceMetadata extends NostrMetadata { screenshots: z.infer; } +interface GetInstanceMetadataOpts { + conf: DittoConf; + relay: NStore; + signal?: AbortSignal; +} + /** Get and parse instance metadata from the kind 0 of the admin user. */ -export async function getInstanceMetadata(store: NStore, signal?: AbortSignal): Promise { - const [event] = await store.query( - [{ kinds: [0], authors: [await Conf.signer.getPublicKey()], limit: 1 }], +export async function getInstanceMetadata(opts: GetInstanceMetadataOpts): Promise { + const { conf, relay, signal } = opts; + + const [event] = await relay.query( + [{ kinds: [0], authors: [await conf.signer.getPublicKey()], limit: 1 }], { signal }, ); @@ -33,8 +42,8 @@ export async function getInstanceMetadata(store: NStore, signal?: AbortSignal): name: meta.name ?? 'Ditto', about: meta.about ?? 'Nostr community server', tagline: meta.tagline ?? meta.about ?? 'Nostr community server', - email: meta.email ?? `postmaster@${Conf.url.host}`, - picture: meta.picture ?? Conf.local('/images/thumbnail.png'), + email: meta.email ?? `postmaster@${conf.url.host}`, + picture: meta.picture ?? conf.local('/images/thumbnail.png'), event, screenshots: meta.screenshots ?? [], }; From 1bce9e2982d1e868e8e8fea0b0f1190ce5b57b3f Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Thu, 27 Feb 2025 17:34:35 -0600 Subject: [PATCH 13/17] Remove `@/config.ts` import from utils/upload.ts --- packages/ditto/utils/upload.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/ditto/utils/upload.ts b/packages/ditto/utils/upload.ts index 6c160bb4..1dcce807 100644 --- a/packages/ditto/utils/upload.ts +++ b/packages/ditto/utils/upload.ts @@ -6,7 +6,6 @@ import { encode } from 'blurhash'; import sharp from 'sharp'; import { AppContext } from '@/app.ts'; -import { Conf } from '@/config.ts'; import { DittoUpload, dittoUploads } from '@/DittoUploads.ts'; import { errorJson } from '@/utils/log.ts'; @@ -22,7 +21,8 @@ export async function uploadFile( meta: FileMeta, signal?: AbortSignal, ): Promise { - const uploader = c.get('uploader'); + const { conf, uploader } = c.var; + if (!uploader) { throw new HTTPException(500, { res: c.json({ error: 'No uploader configured.' }), @@ -31,7 +31,7 @@ export async function uploadFile( const { pubkey, description } = meta; - if (file.size > Conf.maxUploadSize) { + if (file.size > conf.maxUploadSize) { throw new Error('File size is too large.'); } @@ -63,7 +63,7 @@ export async function uploadFile( // If the uploader didn't already, try to get a blurhash and media dimensions. // This requires `MEDIA_ANALYZE=true` to be configured because it comes with security tradeoffs. - if (Conf.mediaAnalyze && (!blurhash || !dim)) { + if (conf.mediaAnalyze && (!blurhash || !dim)) { try { const bytes = await new Response(file.stream()).bytes(); const img = sharp(bytes); From 0cdb7b8cd514227b37a8a4aa0a632ae88a07f084 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Thu, 27 Feb 2025 17:57:22 -0600 Subject: [PATCH 14/17] Add requestId middleware --- packages/ditto/middleware/logiMiddleware.ts | 10 ++++++---- packages/mastoapi/router/DittoApp.ts | 1 + packages/mastoapi/router/DittoEnv.ts | 2 ++ packages/mastoapi/router/DittoMiddleware.ts | 2 +- packages/mastoapi/router/DittoRoute.ts | 2 ++ 5 files changed, 12 insertions(+), 5 deletions(-) diff --git a/packages/ditto/middleware/logiMiddleware.ts b/packages/ditto/middleware/logiMiddleware.ts index be17e3bb..7db2fa87 100644 --- a/packages/ditto/middleware/logiMiddleware.ts +++ b/packages/ditto/middleware/logiMiddleware.ts @@ -1,11 +1,13 @@ -import { MiddlewareHandler } from '@hono/hono'; import { logi } from '@soapbox/logi'; -export const logiMiddleware: MiddlewareHandler = async (c, next) => { +import type { DittoMiddleware } from '@ditto/mastoapi/router'; + +export const logiMiddleware: DittoMiddleware = async (c, next) => { + const { requestId } = c.var; const { method } = c.req; const { pathname } = new URL(c.req.url); - logi({ level: 'info', ns: 'ditto.http.request', method, pathname }); + logi({ level: 'info', ns: 'ditto.http.request', method, pathname, requestId }); const start = new Date(); @@ -15,5 +17,5 @@ export const logiMiddleware: MiddlewareHandler = async (c, next) => { const duration = (end.getTime() - start.getTime()) / 1000; const level = c.res.status >= 500 ? 'error' : 'info'; - logi({ level, ns: 'ditto.http.response', method, pathname, status: c.res.status, duration }); + logi({ level, ns: 'ditto.http.response', method, pathname, status: c.res.status, duration, requestId }); }; diff --git a/packages/mastoapi/router/DittoApp.ts b/packages/mastoapi/router/DittoApp.ts index 2d3c0107..866965b7 100644 --- a/packages/mastoapi/router/DittoApp.ts +++ b/packages/mastoapi/router/DittoApp.ts @@ -15,6 +15,7 @@ export class DittoApp extends Hono { c.set('conf', opts.conf); c.set('relay', opts.relay); c.set('signal', c.req.raw.signal); + c.set('requestId', c.req.header('X-Request-Id') ?? crypto.randomUUID()); return next(); }); } diff --git a/packages/mastoapi/router/DittoEnv.ts b/packages/mastoapi/router/DittoEnv.ts index 7f399e62..35fb0118 100644 --- a/packages/mastoapi/router/DittoEnv.ts +++ b/packages/mastoapi/router/DittoEnv.ts @@ -16,5 +16,7 @@ export interface DittoEnv extends Env { db: DittoDB; /** Abort signal for the request. */ signal: AbortSignal; + /** Unique ID for the request. */ + requestId: string; }; } diff --git a/packages/mastoapi/router/DittoMiddleware.ts b/packages/mastoapi/router/DittoMiddleware.ts index 1483ca90..91afd533 100644 --- a/packages/mastoapi/router/DittoMiddleware.ts +++ b/packages/mastoapi/router/DittoMiddleware.ts @@ -2,4 +2,4 @@ import type { MiddlewareHandler } from '@hono/hono'; import type { DittoEnv } from './DittoEnv.ts'; // deno-lint-ignore ban-types -export type DittoMiddleware = MiddlewareHandler; +export type DittoMiddleware = MiddlewareHandler; diff --git a/packages/mastoapi/router/DittoRoute.ts b/packages/mastoapi/router/DittoRoute.ts index 369fb858..53d2109b 100644 --- a/packages/mastoapi/router/DittoRoute.ts +++ b/packages/mastoapi/router/DittoRoute.ts @@ -25,6 +25,7 @@ export class DittoRoute extends Hono { if (!vars.conf) this.throwMissingVar('conf'); if (!vars.relay) this.throwMissingVar('relay'); if (!vars.signal) this.throwMissingVar('signal'); + if (!vars.requestId) this.throwMissingVar('requestId'); return { ...vars, @@ -32,6 +33,7 @@ export class DittoRoute extends Hono { conf: vars.conf, relay: vars.relay, signal: vars.signal, + requestId: vars.requestId, }; } From 368bf91b1fb45a5bcd3747c27fa820038c2ea6f5 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Thu, 27 Feb 2025 18:08:55 -0600 Subject: [PATCH 15/17] Log requestId in some convenient spots --- packages/ditto/app.ts | 10 ++-------- packages/ditto/controllers/api/admin.ts | 6 +++--- packages/ditto/controllers/api/cashu.ts | 4 ++-- packages/ditto/controllers/api/media.ts | 4 ++-- packages/ditto/controllers/api/streaming.ts | 5 +++-- packages/ditto/controllers/api/translate.ts | 4 ++-- packages/ditto/controllers/error.ts | 15 +++++++++++++-- packages/ditto/controllers/frontend.ts | 4 +++- packages/ditto/controllers/nostr/relay.ts | 21 ++++++++++++++++----- packages/mastoapi/router/DittoApp.ts | 2 +- 10 files changed, 47 insertions(+), 28 deletions(-) diff --git a/packages/ditto/app.ts b/packages/ditto/app.ts index 5f3a12e4..44b0b9b4 100644 --- a/packages/ditto/app.ts +++ b/packages/ditto/app.ts @@ -1,5 +1,5 @@ import { DittoConf } from '@ditto/conf'; -import { DittoDB, DittoPolyPg } from '@ditto/db'; +import { DittoPolyPg } from '@ditto/db'; import { paginationMiddleware, tokenMiddleware, userMiddleware } from '@ditto/mastoapi/middleware'; import { DittoApp, type DittoEnv } from '@ditto/mastoapi/router'; import { relayPoolRelaysSizeGauge, relayPoolSubscriptionsSizeGauge } from '@ditto/metrics'; @@ -152,21 +152,15 @@ import { logiMiddleware } from '@/middleware/logiMiddleware.ts'; import { DittoRelayStore } from '@/storages/DittoRelayStore.ts'; export interface AppEnv extends DittoEnv { - Variables: { - conf: DittoConf; + Variables: DittoEnv['Variables'] & { /** Uploader for the user to upload files. */ uploader?: NUploader; /** NIP-98 signed event proving the pubkey is owned by the user. */ proof?: NostrEvent; - /** Kysely instance for the database. */ - db: DittoDB; - /** Base database store. No content filtering. */ - relay: NRelay; /** Normalized pagination params. */ pagination: { since?: number; until?: number; limit: number }; /** Translation service. */ translator?: DittoTranslator; - signal: AbortSignal; user?: { /** Signer to get the logged-in user's pubkey, relays, and to sign events, or `undefined` if the user isn't logged in. */ signer: NostrSigner; diff --git a/packages/ditto/controllers/api/admin.ts b/packages/ditto/controllers/api/admin.ts index f3611035..124e0f88 100644 --- a/packages/ditto/controllers/api/admin.ts +++ b/packages/ditto/controllers/api/admin.ts @@ -128,7 +128,7 @@ const adminAccountActionSchema = z.object({ }); const adminActionController: AppController = async (c) => { - const { conf, relay } = c.var; + const { conf, relay, requestId } = c.var; const body = await parseBody(c.req.raw); const result = adminAccountActionSchema.safeParse(body); @@ -155,7 +155,7 @@ const adminActionController: AppController = async (c) => { n.disabled = true; n.suspended = true; relay.remove!([{ authors: [authorId] }]).catch((e: unknown) => { - logi({ level: 'error', ns: 'ditto.api.admin.account.action', type: data.type, error: errorJson(e) }); + logi({ level: 'error', ns: 'ditto.api.admin.account.action', type: data.type, requestId, error: errorJson(e) }); }); } if (data.type === 'revoke_name') { @@ -163,7 +163,7 @@ const adminActionController: AppController = async (c) => { try { await relay.remove!([{ kinds: [30360], authors: [await conf.signer.getPublicKey()], '#p': [authorId] }]); } catch (e) { - logi({ level: 'error', ns: 'ditto.api.admin.account.action', type: data.type, error: errorJson(e) }); + logi({ level: 'error', ns: 'ditto.api.admin.account.action', type: data.type, requestId, error: errorJson(e) }); return c.json({ error: 'Unexpected runtime error' }, 500); } } diff --git a/packages/ditto/controllers/api/cashu.ts b/packages/ditto/controllers/api/cashu.ts index 4546dda3..1989a569 100644 --- a/packages/ditto/controllers/api/cashu.ts +++ b/packages/ditto/controllers/api/cashu.ts @@ -103,7 +103,7 @@ route.put('/wallet', userMiddleware({ enc: 'nip44' }), async (c) => { /** Gets a wallet, if it exists. */ route.get('/wallet', userMiddleware({ enc: 'nip44' }), swapNutzapsMiddleware, async (c) => { - const { conf, relay, user, signal } = c.var; + const { conf, relay, user, signal, requestId } = c.var; const pubkey = await user.signer.getPublicKey(); @@ -139,7 +139,7 @@ route.get('/wallet', userMiddleware({ enc: 'nip44' }), swapNutzapsMiddleware, as return accumulator + current.amount; }, 0); } catch (e) { - logi({ level: 'error', ns: 'ditto.api.cashu.wallet.swap', error: errorJson(e) }); + logi({ level: 'error', ns: 'ditto.api.cashu.wallet.swap', requestId, error: errorJson(e) }); } } diff --git a/packages/ditto/controllers/api/media.ts b/packages/ditto/controllers/api/media.ts index c6c6b062..65660e99 100644 --- a/packages/ditto/controllers/api/media.ts +++ b/packages/ditto/controllers/api/media.ts @@ -21,7 +21,7 @@ const mediaUpdateSchema = z.object({ }); const mediaController: AppController = async (c) => { - const { user, signal } = c.var; + const { user, signal, requestId } = c.var; const pubkey = await user!.signer.getPublicKey(); const result = mediaBodySchema.safeParse(await parseBody(c.req.raw)); @@ -35,7 +35,7 @@ const mediaController: AppController = async (c) => { const media = await uploadFile(c, file, { pubkey, description }, signal); return c.json(renderAttachment(media)); } catch (e) { - logi({ level: 'error', ns: 'ditto.api.media', error: errorJson(e) }); + logi({ level: 'error', ns: 'ditto.api.media', requestId, error: errorJson(e) }); return c.json({ error: 'Failed to upload file.' }, 500); } }; diff --git a/packages/ditto/controllers/api/streaming.ts b/packages/ditto/controllers/api/streaming.ts index e6924641..4eebe6b3 100644 --- a/packages/ditto/controllers/api/streaming.ts +++ b/packages/ditto/controllers/api/streaming.ts @@ -65,7 +65,8 @@ const limiter = new TTLCache(); const connections = new Set(); const streamingController: AppController = async (c) => { - const { conf, relay, user } = c.var; + const { conf, relay, user, requestId } = c.var; + const upgrade = c.req.header('upgrade'); const token = c.req.header('sec-websocket-protocol'); const stream = streamSchema.optional().catch(undefined).parse(c.req.query('stream')); @@ -122,7 +123,7 @@ const streamingController: AppController = async (c) => { } } } catch (e) { - logi({ level: 'error', ns: 'ditto.streaming', msg: 'Error in streaming', error: errorJson(e) }); + logi({ level: 'error', ns: 'ditto.streaming', msg: 'Error in streaming', requestId, error: errorJson(e) }); } } diff --git a/packages/ditto/controllers/api/translate.ts b/packages/ditto/controllers/api/translate.ts index 65a95a26..ac62ddbe 100644 --- a/packages/ditto/controllers/api/translate.ts +++ b/packages/ditto/controllers/api/translate.ts @@ -17,7 +17,7 @@ const translateSchema = z.object({ }); const translateController: AppController = async (c) => { - const { relay, user, signal } = c.var; + const { relay, user, signal, requestId } = c.var; const result = translateSchema.safeParse(await parseBody(c.req.raw)); @@ -143,7 +143,7 @@ const translateController: AppController = async (c) => { if (e instanceof Error && e.message.includes('not supported')) { return c.json({ error: `Translation of source language '${event.language}' not supported` }, 422); } - logi({ level: 'error', ns: 'ditto.translate', error: errorJson(e) }); + logi({ level: 'error', ns: 'ditto.translate', requestId, error: errorJson(e) }); return c.json({ error: 'Service Unavailable' }, 503); } }; diff --git a/packages/ditto/controllers/error.ts b/packages/ditto/controllers/error.ts index a00a530b..42c2088f 100644 --- a/packages/ditto/controllers/error.ts +++ b/packages/ditto/controllers/error.ts @@ -4,7 +4,10 @@ import { logi } from '@soapbox/logi'; import { errorJson } from '@/utils/log.ts'; -export const errorHandler: ErrorHandler = (err, c) => { +import type { DittoEnv } from '@ditto/mastoapi/router'; + +export const errorHandler: ErrorHandler = (err, c) => { + const { requestId } = c.var; const { method } = c.req; const { pathname } = new URL(c.req.url); @@ -22,7 +25,15 @@ export const errorHandler: ErrorHandler = (err, c) => { return c.json({ error: 'The server was unable to respond in a timely manner' }, 500); } - logi({ level: 'error', ns: 'ditto.http', msg: 'Unhandled error', method, pathname, error: errorJson(err) }); + logi({ + level: 'error', + ns: 'ditto.http', + msg: 'Unhandled error', + method, + pathname, + requestId, + error: errorJson(err), + }); return c.json({ error: 'Something went wrong' }, 500); }; diff --git a/packages/ditto/controllers/frontend.ts b/packages/ditto/controllers/frontend.ts index c0a6848f..bd2b4de3 100644 --- a/packages/ditto/controllers/frontend.ts +++ b/packages/ditto/controllers/frontend.ts @@ -14,6 +14,8 @@ import { renderAccount } from '@/views/mastodon/accounts.ts'; const META_PLACEHOLDER = '' as const; export const frontendController: AppMiddleware = async (c) => { + const { requestId } = c.var; + c.header('Cache-Control', 'max-age=86400, s-maxage=30, public, stale-if-error=604800'); try { @@ -26,7 +28,7 @@ export const frontendController: AppMiddleware = async (c) => { const meta = renderMetadata(c.req.url, entities); return c.html(content.replace(META_PLACEHOLDER, meta)); } catch (e) { - logi({ level: 'error', ns: 'ditto.frontend', msg: 'Error building meta tags', error: errorJson(e) }); + logi({ level: 'error', ns: 'ditto.frontend', msg: 'Error building meta tags', requestId, error: errorJson(e) }); return c.html(content); } } diff --git a/packages/ditto/controllers/nostr/relay.ts b/packages/ditto/controllers/nostr/relay.ts index f6641549..dea341e3 100644 --- a/packages/ditto/controllers/nostr/relay.ts +++ b/packages/ditto/controllers/nostr/relay.ts @@ -11,6 +11,7 @@ import { NostrClientMsg, NostrClientREQ, NostrRelayMsg, + NRelay, NSchema as n, } from '@nostrify/nostrify'; @@ -40,8 +41,17 @@ const limiters = { /** Connections for metrics purposes. */ const connections = new Set(); +interface ConnectStreamOpts { + conf: DittoConf; + relay: NRelay; + requestId: string; +} + /** Set up the Websocket connection. */ -function connectStream(conf: DittoConf, relay: DittoPgStore, socket: WebSocket, ip: string | undefined) { +function connectStream(socket: WebSocket, ip: string | undefined, opts: ConnectStreamOpts): void { + const { conf, requestId } = opts; + const relay = opts.relay as DittoPgStore; + const controllers = new Map(); if (ip) { @@ -74,7 +84,7 @@ function connectStream(conf: DittoConf, relay: DittoPgStore, socket: WebSocket, const msg = result.data; const verb = msg[0]; - logi({ level: 'trace', ns: 'ditto.relay.msg', verb, msg: msg as JsonValue, ip }); + logi({ level: 'trace', ns: 'ditto.relay.msg', verb, msg: msg as JsonValue, ip, requestId }); relayMessagesCounter.inc({ verb }); handleMsg(result.data); @@ -165,7 +175,7 @@ function connectStream(conf: DittoConf, relay: DittoPgStore, socket: WebSocket, send(['OK', event.id, false, e.message]); } else { send(['OK', event.id, false, 'error: something went wrong']); - logi({ level: 'error', ns: 'ditto.relay', msg: 'Error in relay', error: errorJson(e), ip }); + logi({ level: 'error', ns: 'ditto.relay', msg: 'Error in relay', error: errorJson(e), ip, requestId }); } } } @@ -195,7 +205,8 @@ function connectStream(conf: DittoConf, relay: DittoPgStore, socket: WebSocket, } const relayController: AppController = (c, next) => { - const { conf, relay } = c.var; + const { conf } = c.var; + const upgrade = c.req.header('upgrade'); // NIP-11: https://github.com/nostr-protocol/nips/blob/master/11.md @@ -214,7 +225,7 @@ const relayController: AppController = (c, next) => { } const { socket, response } = Deno.upgradeWebSocket(c.req.raw); - connectStream(conf, relay as DittoPgStore, socket, ip); + connectStream(socket, ip, c.var); return response; }; diff --git a/packages/mastoapi/router/DittoApp.ts b/packages/mastoapi/router/DittoApp.ts index 866965b7..f16ca61e 100644 --- a/packages/mastoapi/router/DittoApp.ts +++ b/packages/mastoapi/router/DittoApp.ts @@ -7,7 +7,7 @@ export class DittoApp extends Hono { // @ts-ignore Require a DittoRoute for type safety. declare route: (path: string, app: Hono) => Hono; - constructor(opts: Omit & HonoOptions) { + constructor(opts: Omit & HonoOptions) { super(opts); this.use((c, next) => { From 381cbfd36ffcf371071bcee61eac4bd6db2f79fb Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Thu, 27 Feb 2025 18:24:05 -0600 Subject: [PATCH 16/17] Add ip to logiMiddleware --- packages/ditto/middleware/logiMiddleware.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/ditto/middleware/logiMiddleware.ts b/packages/ditto/middleware/logiMiddleware.ts index 7db2fa87..ad683022 100644 --- a/packages/ditto/middleware/logiMiddleware.ts +++ b/packages/ditto/middleware/logiMiddleware.ts @@ -7,7 +7,9 @@ export const logiMiddleware: DittoMiddleware = async (c, next) => { const { method } = c.req; const { pathname } = new URL(c.req.url); - logi({ level: 'info', ns: 'ditto.http.request', method, pathname, requestId }); + const ip = c.req.header('x-real-ip'); + + logi({ level: 'info', ns: 'ditto.http.request', method, pathname, ip, requestId }); const start = new Date(); @@ -17,5 +19,5 @@ export const logiMiddleware: DittoMiddleware = async (c, next) => { const duration = (end.getTime() - start.getTime()) / 1000; const level = c.res.status >= 500 ? 'error' : 'info'; - logi({ level, ns: 'ditto.http.response', method, pathname, status: c.res.status, duration, requestId }); + logi({ level, ns: 'ditto.http.response', method, pathname, status: c.res.status, duration, ip, requestId }); }; From 96c845205beafededd290e94d389d51af86b2aaa Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Thu, 27 Feb 2025 19:19:33 -0600 Subject: [PATCH 17/17] Upgrade Deno to v2.2.2 --- .gitlab-ci.yml | 2 +- .tool-versions | 2 +- Dockerfile | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index b754ff1e..68d0f790 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,4 +1,4 @@ -image: denoland/deno:2.2.0 +image: denoland/deno:2.2.2 default: interruptible: true diff --git a/.tool-versions b/.tool-versions index f9adf79b..9cafcaae 100644 --- a/.tool-versions +++ b/.tool-versions @@ -1 +1 @@ -deno 2.2.0 \ No newline at end of file +deno 2.2.2 \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 0b8724a0..fc50c763 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM denoland/deno:2.2.0 +FROM denoland/deno:2.2.2 ENV PORT 5000 WORKDIR /app