mirror of
https://gitlab.com/soapbox-pub/ditto.git
synced 2025-12-06 11:29:46 +00:00
api: add DittoApp and DittoRoute classes
This commit is contained in:
parent
28360e0ea8
commit
30f4d45fca
7 changed files with 104 additions and 40 deletions
16
packages/api/DittoApp.test.ts
Normal file
16
packages/api/DittoApp.test.ts
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
import { Hono } from '@hono/hono';
|
||||||
|
|
||||||
|
import { DittoApp } from './DittoApp.ts';
|
||||||
|
import { DittoRoute } from './DittoRoute.ts';
|
||||||
|
|
||||||
|
Deno.test('DittoApp', () => {
|
||||||
|
const app = new DittoApp();
|
||||||
|
|
||||||
|
const hono = new Hono();
|
||||||
|
const route = new DittoRoute();
|
||||||
|
|
||||||
|
app.route('/', route);
|
||||||
|
|
||||||
|
// @ts-expect-error Passing a non-DittoRoute to route.
|
||||||
|
app.route('/', hono);
|
||||||
|
});
|
||||||
8
packages/api/DittoApp.ts
Normal file
8
packages/api/DittoApp.ts
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
import { Hono } from '@hono/hono';
|
||||||
|
|
||||||
|
import type { DittoEnv } from './DittoEnv.ts';
|
||||||
|
|
||||||
|
export class DittoApp extends Hono<DittoEnv> {
|
||||||
|
// @ts-ignore Require a DittoRoute for type safety.
|
||||||
|
declare route: (path: string, app: Hono<DittoEnv>) => Hono<DittoEnv>;
|
||||||
|
}
|
||||||
17
packages/api/DittoEnv.ts
Normal file
17
packages/api/DittoEnv.ts
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
import type { DittoConf } from '@ditto/conf';
|
||||||
|
import type { DittoDatabase } from '@ditto/db';
|
||||||
|
import type { Env } from '@hono/hono';
|
||||||
|
import type { NRelay } from '@nostrify/nostrify';
|
||||||
|
|
||||||
|
export interface DittoEnv extends Env {
|
||||||
|
Variables: {
|
||||||
|
/** Ditto site configuration. */
|
||||||
|
conf: DittoConf;
|
||||||
|
/** Main database. */
|
||||||
|
store: NRelay;
|
||||||
|
/** Database object. */
|
||||||
|
db: DittoDatabase;
|
||||||
|
/** Abort signal for the request. */
|
||||||
|
signal: AbortSignal;
|
||||||
|
};
|
||||||
|
}
|
||||||
12
packages/api/DittoRoute.test.ts
Normal file
12
packages/api/DittoRoute.test.ts
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
import { assertEquals } from '@std/assert';
|
||||||
|
|
||||||
|
import { DittoRoute } from './DittoRoute.ts';
|
||||||
|
|
||||||
|
Deno.test('DittoRoute', async () => {
|
||||||
|
const route = new DittoRoute();
|
||||||
|
const response = await route.request('/');
|
||||||
|
const body = await response.json();
|
||||||
|
|
||||||
|
assertEquals(response.status, 500);
|
||||||
|
assertEquals(body, { error: 'Missing required variable: db' });
|
||||||
|
});
|
||||||
|
|
@ -1,52 +1,59 @@
|
||||||
import { type Env, Hono } from '@hono/hono';
|
import { type Context, type ErrorHandler, Hono } from '@hono/hono';
|
||||||
|
import { HTTPException } from '@hono/hono/http-exception';
|
||||||
|
|
||||||
import type { DittoConf } from '@ditto/conf';
|
|
||||||
import type { DittoDatabase, DittoTables } from '@ditto/db';
|
|
||||||
import type { HonoOptions } from '@hono/hono/hono-base';
|
import type { HonoOptions } from '@hono/hono/hono-base';
|
||||||
import type { NostrSigner, NPool, NRelay, NStore, NUploader } from '@nostrify/nostrify';
|
import type { DittoEnv } from './DittoEnv.ts';
|
||||||
import type { Kysely } from 'kysely';
|
|
||||||
|
|
||||||
interface DittoEnv extends Env {
|
|
||||||
Variables: {
|
|
||||||
conf: DittoConf;
|
|
||||||
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;
|
|
||||||
/** Storage for the user, might filter out unwanted content. */
|
|
||||||
store: NStore;
|
|
||||||
};
|
|
||||||
/** Uploader for the user to upload files. */
|
|
||||||
uploader?: NUploader;
|
|
||||||
/** Kysely instance for the database. */
|
|
||||||
kysely: Kysely<DittoTables>;
|
|
||||||
/** Main database. */
|
|
||||||
store: NRelay;
|
|
||||||
/** Internal Nostr relay for realtime subscriptions. */
|
|
||||||
pubsub: NRelay;
|
|
||||||
/** Nostr relay pool. */
|
|
||||||
pool: NPool<NRelay>;
|
|
||||||
/** Database object. */
|
|
||||||
db: DittoDatabase;
|
|
||||||
/** Normalized pagination params. */
|
|
||||||
pagination: { since?: number; until?: number; limit: number };
|
|
||||||
/** Normalized list pagination params. */
|
|
||||||
listPagination: { offset: number; limit: number };
|
|
||||||
/** Translation service. */
|
|
||||||
translator?: DittoTranslator;
|
|
||||||
signal: AbortSignal;
|
|
||||||
pipeline: Pick<NStore, 'event'>;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ditto base route class.
|
||||||
|
* Ensures that required variables are set for type safety.
|
||||||
|
*/
|
||||||
export class DittoRoute extends Hono<DittoEnv> {
|
export class DittoRoute extends Hono<DittoEnv> {
|
||||||
constructor(opts: HonoOptions<DittoEnv> = {}) {
|
constructor(opts: HonoOptions<DittoEnv> = {}) {
|
||||||
super(opts);
|
super(opts);
|
||||||
this.init();
|
|
||||||
}
|
|
||||||
|
|
||||||
init(): void {
|
|
||||||
this.use((c, next) => {
|
this.use((c, next) => {
|
||||||
|
this.setSignal(c);
|
||||||
|
this.assertVars(c.var);
|
||||||
return next();
|
return next();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.onError(this._errorHandler);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private setSignal(c: Context<DittoEnv>): void {
|
||||||
|
if (!c.var.signal) {
|
||||||
|
c.set('signal', c.req.raw.signal);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private assertVars(vars: Partial<DittoEnv['Variables']>): DittoEnv['Variables'] {
|
||||||
|
if (!vars.db) this.throwMissingVar('db');
|
||||||
|
if (!vars.conf) this.throwMissingVar('conf');
|
||||||
|
if (!vars.store) this.throwMissingVar('store');
|
||||||
|
if (!vars.signal) this.throwMissingVar('signal');
|
||||||
|
|
||||||
|
return {
|
||||||
|
db: vars.db,
|
||||||
|
conf: vars.conf,
|
||||||
|
store: vars.store,
|
||||||
|
signal: vars.signal,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private throwMissingVar(name: string): never {
|
||||||
|
throw new HTTPException(500, { message: `Missing required variable: ${name}` });
|
||||||
|
}
|
||||||
|
|
||||||
|
private _errorHandler: ErrorHandler = (error, c) => {
|
||||||
|
if (error instanceof HTTPException) {
|
||||||
|
if (error.res) {
|
||||||
|
return error.res;
|
||||||
|
} else {
|
||||||
|
return c.json({ error: error.message }, error.status);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.json({ error: 'Something went wrong' }, 500);
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
"name": "@ditto/api",
|
"name": "@ditto/api",
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"exports": {
|
"exports": {
|
||||||
|
".": "./mod.ts",
|
||||||
"./middleware": "./middleware/mod.ts"
|
"./middleware": "./middleware/mod.ts"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1 +1,4 @@
|
||||||
|
export { DittoApp } from './DittoApp.ts';
|
||||||
export { DittoRoute } from './DittoRoute.ts';
|
export { DittoRoute } from './DittoRoute.ts';
|
||||||
|
|
||||||
|
export type { DittoEnv } from './DittoEnv.ts';
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue