From e5100530da2cf0d02db2d226037fc4b00ddffc88 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Mon, 17 Feb 2025 15:16:43 -0600 Subject: [PATCH] Set up DittoApp, minor fixes to controllers --- packages/ditto/app.ts | 20 +++--------- packages/ditto/controllers/api/cashu.ts | 30 +++++++++--------- packages/ditto/controllers/api/timelines.ts | 34 ++++++++------------- 3 files changed, 30 insertions(+), 54 deletions(-) diff --git a/packages/ditto/app.ts b/packages/ditto/app.ts index 4083770a..0d66255e 100644 --- a/packages/ditto/app.ts +++ b/packages/ditto/app.ts @@ -1,6 +1,8 @@ +import { DittoApp } from '@ditto/api'; import { DittoConf } from '@ditto/conf'; import { DittoDatabase, DittoTables } from '@ditto/db'; -import { type Context, Env as HonoEnv, Handler, Hono, Input as HonoInput, MiddlewareHandler } from '@hono/hono'; +import { type DittoTranslator } from '@ditto/translators'; +import { type Context, Env as HonoEnv, Handler, Input as HonoInput, MiddlewareHandler } from '@hono/hono'; import { every } from '@hono/hono/combine'; import { cors } from '@hono/hono/cors'; import { serveStatic } from '@hono/hono/deno'; @@ -137,7 +139,6 @@ import { metricsController } from '@/controllers/metrics.ts'; import { manifestController } from '@/controllers/manifest.ts'; import { nodeInfoController, nodeInfoSchemaController } from '@/controllers/well-known/nodeinfo.ts'; import { nostrController } from '@/controllers/well-known/nostr.ts'; -import { DittoTranslator } from '@/interfaces/DittoTranslator.ts'; import { requireProof, requireRole } from '@/middleware/auth98Middleware.ts'; import { cacheControlMiddleware } from '@/middleware/cacheControlMiddleware.ts'; import { cspMiddleware } from '@/middleware/cspMiddleware.ts'; @@ -222,26 +223,13 @@ type AppMiddleware = MiddlewareHandler; // deno-lint-ignore no-explicit-any type AppController

= Handler>; -const app = new Hono({ strict: false }); +const app = new DittoApp({ conf, db, store }, { strict: false }); /** User-provided files in the gitignored `public/` directory. */ const publicFiles = serveStatic({ root: conf.publicDir }); /** Static files provided by the Ditto repo, checked into git. */ const staticFiles = serveStatic({ root: new URL('./static', import.meta.url).pathname }); -// Set up the base context. -app.use((c, next) => { - c.set('db', db); - c.set('conf', conf); - c.set('kysely', kysely); - c.set('pool', pool); - c.set('store', store); - c.set('pubsub', pubsub); - c.set('signal', c.req.raw.signal); - c.set('pipeline', pipeline); - return next(); -}); - app.use(cacheControlMiddleware({ noStore: true })); const ratelimit = every( diff --git a/packages/ditto/controllers/api/cashu.ts b/packages/ditto/controllers/api/cashu.ts index 20c5e747..ab36e476 100644 --- a/packages/ditto/controllers/api/cashu.ts +++ b/packages/ditto/controllers/api/cashu.ts @@ -1,8 +1,7 @@ import { Proof } from '@cashu/cashu-ts'; -import { confRequiredMw } from '@ditto/api/middleware'; -import { Hono } from '@hono/hono'; -import { generateSecretKey, getPublicKey } from 'nostr-tools'; +import { DittoRoute } from '@ditto/api'; import { bytesToString, stringToBytes } from '@scure/base'; +import { generateSecretKey, getPublicKey } from 'nostr-tools'; import { z } from 'zod'; import { createEvent, parseBody } from '@/utils/api.ts'; @@ -15,7 +14,7 @@ import { errorJson } from '@/utils/log.ts'; type Wallet = z.infer; -const app = new Hono().use('*', confRequiredMw, requireStore); +const app = new DittoRoute(); // app.delete('/wallet') -> 204 @@ -44,9 +43,8 @@ const createCashuWalletAndNutzapInfoSchema = z.object({ * https://github.com/nostr-protocol/nips/blob/master/61.md#nutzap-informational-event */ app.put('/wallet', requireNip44Signer, async (c) => { - const { conf, signer } = c.var; - const store = c.get('store'); - const pubkey = await signer.getPublicKey(); + const { conf, store, user } = c.var; + const pubkey = await user.signer.getPublicKey(); const body = await parseBody(c.req.raw); const { signal } = c.req.raw; const result = createCashuWalletAndNutzapInfoSchema.safeParse(body); @@ -74,23 +72,23 @@ app.put('/wallet', requireNip44Signer, async (c) => { walletContentTags.push(['mint', mint]); } - const encryptedWalletContentTags = await signer.nip44.encrypt(pubkey, JSON.stringify(walletContentTags)); + const encryptedWalletContentTags = await user.signer.nip44.encrypt(pubkey, JSON.stringify(walletContentTags)); // Wallet - await createEvent({ + await createEvent(c.var, { kind: 17375, content: encryptedWalletContentTags, - }, c); + }); // Nutzap information - await createEvent({ + await createEvent(c.var, { kind: 10019, tags: [ ...mints.map((mint) => ['mint', mint, 'sat']), ['relay', conf.relay], // TODO: add more relays once things get more stable ['pubkey', p2pk], ], - }, c); + }); // TODO: hydrate wallet and add a 'balance' field when a 'renderWallet' view function is created const walletEntity: Wallet = { @@ -105,9 +103,9 @@ app.put('/wallet', requireNip44Signer, async (c) => { /** Gets a wallet, if it exists. */ app.get('/wallet', requireNip44Signer, swapNutzapsMiddleware, async (c) => { - const { conf, signer } = c.var; + const { conf, user } = c.var; const store = c.get('store'); - const pubkey = await signer.getPublicKey(); + const pubkey = await user.signer.getPublicKey(); const { signal } = c.req.raw; const [event] = await store.query([{ authors: [pubkey], kinds: [17375] }], { signal }); @@ -115,7 +113,7 @@ app.get('/wallet', requireNip44Signer, swapNutzapsMiddleware, async (c) => { return c.json({ error: 'Wallet not found' }, 404); } - const decryptedContent: string[][] = JSON.parse(await signer.nip44.decrypt(pubkey, event.content)); + const decryptedContent: string[][] = JSON.parse(await user.signer.nip44.decrypt(pubkey, event.content)); const privkey = decryptedContent.find(([value]) => value === 'privkey')?.[1]; if (!privkey || !isNostrId(privkey)) { @@ -131,7 +129,7 @@ app.get('/wallet', requireNip44Signer, swapNutzapsMiddleware, async (c) => { for (const token of tokens) { try { const decryptedContent: { mint: string; proofs: Proof[] } = JSON.parse( - await signer.nip44.decrypt(pubkey, token.content), + await user.signer.nip44.decrypt(pubkey, token.content), ); if (!mints.includes(decryptedContent.mint)) { diff --git a/packages/ditto/controllers/api/timelines.ts b/packages/ditto/controllers/api/timelines.ts index d3c9dd0b..bcf88a9a 100644 --- a/packages/ditto/controllers/api/timelines.ts +++ b/packages/ditto/controllers/api/timelines.ts @@ -29,18 +29,17 @@ export class TimelineRoute { const homeTimelineController: AppController = async (c) => { const { user, pagination } = c.var; - const pubkey = await user.signer.getPublicKey()!; + const pubkey = await user!.signer.getPublicKey()!; const result = homeQuerySchema.safeParse(c.req.query()); - if (!result.success) {const homeTimelineController: AppController = async (c) => { - + if (!result.success) { return c.json({ error: 'Bad request', schema: result.error }, 400); } const { exclude_replies, only_media } = result.data; - const authors = [...await getFeedPubkeys(pubkey)]; - const filter: NostrFilter = { authors, kinds: [1, 6, 20], ...params }; + const authors = [...await getFeedPubkeys(c.var, pubkey)]; + const filter: NostrFilter = { authors, kinds: [1, 6, 20], ...pagination }; const search: string[] = []; @@ -66,8 +65,7 @@ const publicQuerySchema = z.object({ }); const publicTimelineController: AppController = (c) => { - const { conf } = c.var; - const params = c.get('pagination'); + const { conf, pagination } = c.var; const result = publicQuerySchema.safeParse(c.req.query()); if (!result.success) { @@ -76,7 +74,7 @@ const publicTimelineController: AppController = (c) => { const { local, instance, language } = result.data; - const filter: NostrFilter = { kinds: [1, 20], ...params }; + const filter: NostrFilter = { kinds: [1, 20], ...pagination }; const search: `${string}:${string}`[] = []; @@ -98,9 +96,9 @@ const publicTimelineController: AppController = (c) => { }; const hashtagTimelineController: AppController = (c) => { + const { pagination } = c.var; const hashtag = c.req.param('hashtag')!.toLowerCase(); - const params = c.get('pagination'); - return renderStatuses(c, [{ kinds: [1, 20], '#t': [hashtag], ...params }]); + return renderStatuses(c, [{ kinds: [1, 20], '#t': [hashtag], ...pagination }]); }; const suggestedTimelineController: AppController = async (c) => { @@ -119,27 +117,19 @@ const suggestedTimelineController: AppController = async (c) => { /** Render statuses for timelines. */ async function renderStatuses(c: AppContext, filters: NostrFilter[]) { - const { conf } = c.var; - const { signal } = c.req.raw; - const store = c.get('store'); + const { conf, store, user, signal } = c.var; const opts = { signal, timeout: conf.db.timeouts.timelines }; const events = await store .query(filters, opts) - .then((events) => hydrateEvents({ events, store, signal })); + .then((events) => hydrateEvents(c.var, events)); if (!events.length) { return c.json([]); } - const viewerPubkey = await c.get('signer')?.getPublicKey(); - - const statuses = (await Promise.all(events.map((event) => { - if (event.kind === 6) { - return renderReblog(event, { viewerPubkey }); - } - return renderStatus(event, { viewerPubkey }); - }))).filter(Boolean); + const view = new StatusView(c.var); + const statuses = (await Promise.all(events.map((event) => view.render(event)))).filter(Boolean); if (!statuses.length) { return c.json([]);