Set up DittoApp, minor fixes to controllers

This commit is contained in:
Alex Gleason 2025-02-17 15:16:43 -06:00
parent d3730284de
commit e5100530da
No known key found for this signature in database
GPG key ID: 7211D1F99744FBB7
3 changed files with 30 additions and 54 deletions

View file

@ -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<AppEnv>;
// deno-lint-ignore no-explicit-any
type AppController<P extends string = any> = Handler<AppEnv, P, HonoInput, Response | Promise<Response>>;
const app = new Hono<AppEnv>({ 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(

View file

@ -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<typeof walletSchema>;
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)) {

View file

@ -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([]);