mirror of
https://gitlab.com/soapbox-pub/ditto.git
synced 2025-12-06 11:29:46 +00:00
Merge branch 'store-policy' into 'main'
Expose db to Policy script See merge request soapbox-pub/ditto!486
This commit is contained in:
commit
a1585d81aa
24 changed files with 142 additions and 95 deletions
|
|
@ -1,5 +1,4 @@
|
||||||
{
|
{
|
||||||
"$schema": "https://deno.land/x/deno@v1.41.0/cli/schemas/config-file.v1.json",
|
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"tasks": {
|
"tasks": {
|
||||||
"start": "deno run -A src/server.ts",
|
"start": "deno run -A src/server.ts",
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,13 @@
|
||||||
import { JsonParseStream } from '@std/json/json-parse-stream';
|
import { JsonParseStream } from '@std/json/json-parse-stream';
|
||||||
import { TextLineStream } from '@std/streams/text-line-stream';
|
import { TextLineStream } from '@std/streams/text-line-stream';
|
||||||
|
|
||||||
import { DittoDB } from '@/db/DittoDB.ts';
|
|
||||||
import { AdminSigner } from '@/signers/AdminSigner.ts';
|
import { AdminSigner } from '@/signers/AdminSigner.ts';
|
||||||
import { EventsDB } from '@/storages/EventsDB.ts';
|
import { Storages } from '@/storages.ts';
|
||||||
import { type EventStub } from '@/utils/api.ts';
|
import { type EventStub } from '@/utils/api.ts';
|
||||||
import { nostrNow } from '@/utils.ts';
|
import { nostrNow } from '@/utils.ts';
|
||||||
|
|
||||||
const signer = new AdminSigner();
|
const signer = new AdminSigner();
|
||||||
|
const store = await Storages.db();
|
||||||
const { kysely } = await DittoDB.getInstance();
|
|
||||||
const eventsDB = new EventsDB(kysely);
|
|
||||||
|
|
||||||
const readable = Deno.stdin.readable
|
const readable = Deno.stdin.readable
|
||||||
.pipeThrough(new TextDecoderStream())
|
.pipeThrough(new TextDecoderStream())
|
||||||
|
|
@ -25,7 +22,7 @@ for await (const t of readable) {
|
||||||
...t as EventStub,
|
...t as EventStub,
|
||||||
});
|
});
|
||||||
|
|
||||||
await eventsDB.event(event);
|
await store.event(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
Deno.exit(0);
|
Deno.exit(0);
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,11 @@
|
||||||
import { NSchema } from '@nostrify/nostrify';
|
import { NSchema } from '@nostrify/nostrify';
|
||||||
import { nip19 } from 'nostr-tools';
|
import { nip19 } from 'nostr-tools';
|
||||||
|
|
||||||
import { DittoDB } from '@/db/DittoDB.ts';
|
|
||||||
import { AdminSigner } from '@/signers/AdminSigner.ts';
|
import { AdminSigner } from '@/signers/AdminSigner.ts';
|
||||||
import { EventsDB } from '@/storages/EventsDB.ts';
|
import { Storages } from '@/storages.ts';
|
||||||
import { nostrNow } from '@/utils.ts';
|
import { nostrNow } from '@/utils.ts';
|
||||||
|
|
||||||
const { kysely } = await DittoDB.getInstance();
|
const store = await Storages.db();
|
||||||
const eventsDB = new EventsDB(kysely);
|
|
||||||
|
|
||||||
const [pubkeyOrNpub, role] = Deno.args;
|
const [pubkeyOrNpub, role] = Deno.args;
|
||||||
const pubkey = pubkeyOrNpub.startsWith('npub1') ? nip19.decode(pubkeyOrNpub as `npub1${string}`).data : pubkeyOrNpub;
|
const pubkey = pubkeyOrNpub.startsWith('npub1') ? nip19.decode(pubkeyOrNpub as `npub1${string}`).data : pubkeyOrNpub;
|
||||||
|
|
@ -25,7 +23,7 @@ if (!['admin', 'user'].includes(role)) {
|
||||||
const signer = new AdminSigner();
|
const signer = new AdminSigner();
|
||||||
const admin = await signer.getPublicKey();
|
const admin = await signer.getPublicKey();
|
||||||
|
|
||||||
const [existing] = await eventsDB.query([{
|
const [existing] = await store.query([{
|
||||||
kinds: [30382],
|
kinds: [30382],
|
||||||
authors: [admin],
|
authors: [admin],
|
||||||
'#d': [pubkey],
|
'#d': [pubkey],
|
||||||
|
|
@ -59,6 +57,6 @@ const event = await signer.signEvent({
|
||||||
created_at: nostrNow(),
|
created_at: nostrNow(),
|
||||||
});
|
});
|
||||||
|
|
||||||
await eventsDB.event(event);
|
await store.event(event);
|
||||||
|
|
||||||
Deno.exit(0);
|
Deno.exit(0);
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import { DittoDB } from '@/db/DittoDB.ts';
|
import { Storages } from '@/storages.ts';
|
||||||
|
|
||||||
// This migrates kysely internally.
|
// This migrates kysely internally.
|
||||||
const { kysely } = await DittoDB.getInstance();
|
const kysely = await Storages.kysely();
|
||||||
|
|
||||||
// Close the connection before exiting.
|
// Close the connection before exiting.
|
||||||
await kysely.destroy();
|
await kysely.destroy();
|
||||||
|
|
|
||||||
|
|
@ -6,11 +6,9 @@
|
||||||
import { NostrEvent, NRelay1, NSchema } from '@nostrify/nostrify';
|
import { NostrEvent, NRelay1, NSchema } from '@nostrify/nostrify';
|
||||||
import { nip19 } from 'nostr-tools';
|
import { nip19 } from 'nostr-tools';
|
||||||
|
|
||||||
import { DittoDB } from '@/db/DittoDB.ts';
|
import { Storages } from '@/storages.ts';
|
||||||
import { EventsDB } from '@/storages/EventsDB.ts';
|
|
||||||
|
|
||||||
const { kysely } = await DittoDB.getInstance();
|
const store = await Storages.db();
|
||||||
const eventsDB = new EventsDB(kysely);
|
|
||||||
|
|
||||||
interface ImportEventsOpts {
|
interface ImportEventsOpts {
|
||||||
profilesOnly: boolean;
|
profilesOnly: boolean;
|
||||||
|
|
@ -21,7 +19,7 @@ const importUsers = async (
|
||||||
authors: string[],
|
authors: string[],
|
||||||
relays: string[],
|
relays: string[],
|
||||||
opts?: Partial<ImportEventsOpts>,
|
opts?: Partial<ImportEventsOpts>,
|
||||||
doEvent: DoEvent = async (event: NostrEvent) => await eventsDB.event(event),
|
doEvent: DoEvent = async (event: NostrEvent) => await store.event(event),
|
||||||
) => {
|
) => {
|
||||||
// Kind 0s + follow lists.
|
// Kind 0s + follow lists.
|
||||||
const profiles: Record<string, Record<number, NostrEvent>> = {};
|
const profiles: Record<string, Record<number, NostrEvent>> = {};
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
import { nip19 } from 'nostr-tools';
|
import { nip19 } from 'nostr-tools';
|
||||||
|
|
||||||
import { DittoDB } from '@/db/DittoDB.ts';
|
|
||||||
import { Storages } from '@/storages.ts';
|
import { Storages } from '@/storages.ts';
|
||||||
import { refreshAuthorStats } from '@/utils/stats.ts';
|
import { refreshAuthorStats } from '@/utils/stats.ts';
|
||||||
|
|
||||||
|
|
@ -18,6 +17,6 @@ try {
|
||||||
}
|
}
|
||||||
|
|
||||||
const store = await Storages.db();
|
const store = await Storages.db();
|
||||||
const { kysely } = await DittoDB.getInstance();
|
const kysely = await Storages.kysely();
|
||||||
|
|
||||||
await refreshAuthorStats({ pubkey, kysely, store });
|
await refreshAuthorStats({ pubkey, kysely, store });
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import os from 'node:os';
|
||||||
import * as dotenv from '@std/dotenv';
|
import * as dotenv from '@std/dotenv';
|
||||||
import { getPublicKey, nip19 } from 'nostr-tools';
|
import { getPublicKey, nip19 } from 'nostr-tools';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
@ -240,6 +241,14 @@ class Conf {
|
||||||
static get policy(): string {
|
static get policy(): string {
|
||||||
return Deno.env.get('DITTO_POLICY') || new URL('../data/policy.ts', import.meta.url).pathname;
|
return Deno.env.get('DITTO_POLICY') || new URL('../data/policy.ts', import.meta.url).pathname;
|
||||||
}
|
}
|
||||||
|
/** Absolute path to the data directory used by Ditto. */
|
||||||
|
static get dataDir(): string {
|
||||||
|
return Deno.env.get('DITTO_DATA_DIR') || new URL('../data', import.meta.url).pathname;
|
||||||
|
}
|
||||||
|
/** Absolute path of the Deno directory. */
|
||||||
|
static get denoDir(): string {
|
||||||
|
return Deno.env.get('DENO_DIR') || `${os.userInfo().homedir}/.cache/deno`;
|
||||||
|
}
|
||||||
/** Whether zap splits should be enabled. */
|
/** Whether zap splits should be enabled. */
|
||||||
static get zapSplitsEnabled(): boolean {
|
static get zapSplitsEnabled(): boolean {
|
||||||
return optionalBooleanSchema.parse(Deno.env.get('ZAP_SPLITS_ENABLED')) ?? false;
|
return optionalBooleanSchema.parse(Deno.env.get('ZAP_SPLITS_ENABLED')) ?? false;
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,6 @@ import { z } from 'zod';
|
||||||
|
|
||||||
import { AppController } from '@/app.ts';
|
import { AppController } from '@/app.ts';
|
||||||
import { Conf } from '@/config.ts';
|
import { Conf } from '@/config.ts';
|
||||||
import { DittoDB } from '@/db/DittoDB.ts';
|
|
||||||
import { nostrNow } from '@/utils.ts';
|
import { nostrNow } from '@/utils.ts';
|
||||||
import { parseBody } from '@/utils/api.ts';
|
import { parseBody } from '@/utils/api.ts';
|
||||||
import { Storages } from '@/storages.ts';
|
import { Storages } from '@/storages.ts';
|
||||||
|
|
@ -82,7 +81,7 @@ const createTokenController: AppController = async (c) => {
|
||||||
async function getToken(
|
async function getToken(
|
||||||
{ pubkey, secret, relays = [] }: { pubkey: string; secret?: string; relays?: string[] },
|
{ pubkey, secret, relays = [] }: { pubkey: string; secret?: string; relays?: string[] },
|
||||||
): Promise<`token1${string}`> {
|
): Promise<`token1${string}`> {
|
||||||
const { kysely } = await DittoDB.getInstance();
|
const kysely = await Storages.kysely();
|
||||||
const token = generateToken();
|
const token = generateToken();
|
||||||
|
|
||||||
const serverSeckey = generateSecretKey();
|
const serverSeckey = generateSecretKey();
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,6 @@ import { z } from 'zod';
|
||||||
|
|
||||||
import { type AppController } from '@/app.ts';
|
import { type AppController } from '@/app.ts';
|
||||||
import { Conf } from '@/config.ts';
|
import { Conf } from '@/config.ts';
|
||||||
import { DittoDB } from '@/db/DittoDB.ts';
|
|
||||||
import { DittoUpload, dittoUploads } from '@/DittoUploads.ts';
|
import { DittoUpload, dittoUploads } from '@/DittoUploads.ts';
|
||||||
import { DittoEvent } from '@/interfaces/DittoEvent.ts';
|
import { DittoEvent } from '@/interfaces/DittoEvent.ts';
|
||||||
import { getAncestors, getAuthor, getDescendants, getEvent } from '@/queries.ts';
|
import { getAncestors, getAuthor, getDescendants, getEvent } from '@/queries.ts';
|
||||||
|
|
@ -16,9 +15,10 @@ import { addTag, deleteTag } from '@/utils/tags.ts';
|
||||||
import { asyncReplaceAll } from '@/utils/text.ts';
|
import { asyncReplaceAll } from '@/utils/text.ts';
|
||||||
import { lookupPubkey } from '@/utils/lookup.ts';
|
import { lookupPubkey } from '@/utils/lookup.ts';
|
||||||
import { Storages } from '@/storages.ts';
|
import { Storages } from '@/storages.ts';
|
||||||
import { hydrateEvents, purifyEvent } from '@/storages/hydrate.ts';
|
import { hydrateEvents } from '@/storages/hydrate.ts';
|
||||||
import { createEvent, paginated, paginatedList, parseBody, updateListEvent } from '@/utils/api.ts';
|
import { createEvent, paginated, paginatedList, parseBody, updateListEvent } from '@/utils/api.ts';
|
||||||
import { getInvoice, getLnurl } from '@/utils/lnurl.ts';
|
import { getInvoice, getLnurl } from '@/utils/lnurl.ts';
|
||||||
|
import { purifyEvent } from '@/utils/purify.ts';
|
||||||
import { getZapSplits } from '@/utils/zap-split.ts';
|
import { getZapSplits } from '@/utils/zap-split.ts';
|
||||||
import { renderEventAccounts } from '@/views.ts';
|
import { renderEventAccounts } from '@/views.ts';
|
||||||
import { accountFromPubkey, renderAccount } from '@/views/mastodon/accounts.ts';
|
import { accountFromPubkey, renderAccount } from '@/views/mastodon/accounts.ts';
|
||||||
|
|
@ -578,7 +578,7 @@ const zappedByController: AppController = async (c) => {
|
||||||
const id = c.req.param('id');
|
const id = c.req.param('id');
|
||||||
const params = c.get('listPagination');
|
const params = c.get('listPagination');
|
||||||
const store = await Storages.db();
|
const store = await Storages.db();
|
||||||
const { kysely } = await DittoDB.getInstance();
|
const kysely = await Storages.kysely();
|
||||||
|
|
||||||
const zaps = await kysely.selectFrom('event_zaps')
|
const zaps = await kysely.selectFrom('event_zaps')
|
||||||
.selectAll()
|
.selectAll()
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,6 @@ import { z } from 'zod';
|
||||||
|
|
||||||
import { type AppController } from '@/app.ts';
|
import { type AppController } from '@/app.ts';
|
||||||
import { Conf } from '@/config.ts';
|
import { Conf } from '@/config.ts';
|
||||||
import { DittoDB } from '@/db/DittoDB.ts';
|
|
||||||
import { streamingConnectionsGauge } from '@/metrics.ts';
|
import { streamingConnectionsGauge } from '@/metrics.ts';
|
||||||
import { MuteListPolicy } from '@/policies/MuteListPolicy.ts';
|
import { MuteListPolicy } from '@/policies/MuteListPolicy.ts';
|
||||||
import { getFeedPubkeys } from '@/queries.ts';
|
import { getFeedPubkeys } from '@/queries.ts';
|
||||||
|
|
@ -222,7 +221,7 @@ async function topicToFilter(
|
||||||
|
|
||||||
async function getTokenPubkey(token: string): Promise<string | undefined> {
|
async function getTokenPubkey(token: string): Promise<string | undefined> {
|
||||||
if (token.startsWith('token1')) {
|
if (token.startsWith('token1')) {
|
||||||
const { kysely } = await DittoDB.getInstance();
|
const kysely = await Storages.kysely();
|
||||||
|
|
||||||
const { user_pubkey } = await kysely
|
const { user_pubkey } = await kysely
|
||||||
.selectFrom('nip46_tokens')
|
.selectFrom('nip46_tokens')
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,12 @@
|
||||||
import { register } from 'prom-client';
|
import { register } from 'prom-client';
|
||||||
|
|
||||||
import { AppController } from '@/app.ts';
|
import { AppController } from '@/app.ts';
|
||||||
import { DittoDB } from '@/db/DittoDB.ts';
|
|
||||||
import { dbAvailableConnectionsGauge, dbPoolSizeGauge } from '@/metrics.ts';
|
import { dbAvailableConnectionsGauge, dbPoolSizeGauge } from '@/metrics.ts';
|
||||||
|
import { Storages } from '@/storages.ts';
|
||||||
|
|
||||||
/** Prometheus/OpenMetrics controller. */
|
/** Prometheus/OpenMetrics controller. */
|
||||||
export const metricsController: AppController = async (c) => {
|
export const metricsController: AppController = async (c) => {
|
||||||
const db = await DittoDB.getInstance();
|
const db = await Storages.database();
|
||||||
|
|
||||||
// Update some metrics at request time.
|
// Update some metrics at request time.
|
||||||
dbPoolSizeGauge.set(db.poolSize);
|
dbPoolSizeGauge.set(db.poolSize);
|
||||||
|
|
|
||||||
|
|
@ -3,24 +3,12 @@ import path from 'node:path';
|
||||||
|
|
||||||
import { FileMigrationProvider, Kysely, Migrator } from 'kysely';
|
import { FileMigrationProvider, Kysely, Migrator } from 'kysely';
|
||||||
|
|
||||||
import { Conf } from '@/config.ts';
|
|
||||||
import { DittoPglite } from '@/db/adapters/DittoPglite.ts';
|
import { DittoPglite } from '@/db/adapters/DittoPglite.ts';
|
||||||
import { DittoPostgres } from '@/db/adapters/DittoPostgres.ts';
|
import { DittoPostgres } from '@/db/adapters/DittoPostgres.ts';
|
||||||
import { DittoDatabase, DittoDatabaseOpts } from '@/db/DittoDatabase.ts';
|
import { DittoDatabase, DittoDatabaseOpts } from '@/db/DittoDatabase.ts';
|
||||||
import { DittoTables } from '@/db/DittoTables.ts';
|
import { DittoTables } from '@/db/DittoTables.ts';
|
||||||
|
|
||||||
export class DittoDB {
|
export class DittoDB {
|
||||||
private static db: DittoDatabase | undefined;
|
|
||||||
|
|
||||||
/** Create (and migrate) the database if it isn't been already, or return the existing connection. */
|
|
||||||
static async getInstance(): Promise<DittoDatabase> {
|
|
||||||
if (!this.db) {
|
|
||||||
this.db = this.create(Conf.databaseUrl, { poolSize: Conf.pg.poolSize });
|
|
||||||
await this.migrate(this.db.kysely);
|
|
||||||
}
|
|
||||||
return this.db;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Open a new database connection. */
|
/** Open a new database connection. */
|
||||||
static create(databaseUrl: string, opts?: DittoDatabaseOpts): DittoDatabase {
|
static create(databaseUrl: string, opts?: DittoDatabaseOpts): DittoDatabase {
|
||||||
const { protocol } = new URL(databaseUrl);
|
const { protocol } = new URL(databaseUrl);
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ import { nip19 } from 'nostr-tools';
|
||||||
import { AppMiddleware } from '@/app.ts';
|
import { AppMiddleware } from '@/app.ts';
|
||||||
import { ConnectSigner } from '@/signers/ConnectSigner.ts';
|
import { ConnectSigner } from '@/signers/ConnectSigner.ts';
|
||||||
import { ReadOnlySigner } from '@/signers/ReadOnlySigner.ts';
|
import { ReadOnlySigner } from '@/signers/ReadOnlySigner.ts';
|
||||||
import { DittoDB } from '@/db/DittoDB.ts';
|
import { Storages } from '@/storages.ts';
|
||||||
|
|
||||||
/** We only accept "Bearer" type. */
|
/** We only accept "Bearer" type. */
|
||||||
const BEARER_REGEX = new RegExp(`^Bearer (${nip19.BECH32_REGEX.source})$`);
|
const BEARER_REGEX = new RegExp(`^Bearer (${nip19.BECH32_REGEX.source})$`);
|
||||||
|
|
@ -20,7 +20,7 @@ export const signerMiddleware: AppMiddleware = async (c, next) => {
|
||||||
|
|
||||||
if (bech32.startsWith('token1')) {
|
if (bech32.startsWith('token1')) {
|
||||||
try {
|
try {
|
||||||
const { kysely } = await DittoDB.getInstance();
|
const kysely = await Storages.kysely();
|
||||||
|
|
||||||
const { user_pubkey, server_seckey, relays } = await kysely
|
const { user_pubkey, server_seckey, relays } = await kysely
|
||||||
.selectFrom('nip46_tokens')
|
.selectFrom('nip46_tokens')
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,6 @@ import { LRUCache } from 'lru-cache';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
import { Conf } from '@/config.ts';
|
import { Conf } from '@/config.ts';
|
||||||
import { DittoDB } from '@/db/DittoDB.ts';
|
|
||||||
import { DittoEvent } from '@/interfaces/DittoEvent.ts';
|
import { DittoEvent } from '@/interfaces/DittoEvent.ts';
|
||||||
import { pipelineEventsCounter, policyEventsCounter } from '@/metrics.ts';
|
import { pipelineEventsCounter, policyEventsCounter } from '@/metrics.ts';
|
||||||
import { RelayError } from '@/RelayError.ts';
|
import { RelayError } from '@/RelayError.ts';
|
||||||
|
|
@ -53,7 +52,7 @@ async function handleEvent(event: DittoEvent, signal: AbortSignal): Promise<void
|
||||||
throw new RelayError('blocked', 'user is disabled');
|
throw new RelayError('blocked', 'user is disabled');
|
||||||
}
|
}
|
||||||
|
|
||||||
const { kysely } = await DittoDB.getInstance();
|
const kysely = await Storages.kysely();
|
||||||
|
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
storeEvent(event, signal),
|
storeEvent(event, signal),
|
||||||
|
|
@ -104,7 +103,7 @@ async function existsInDB(event: DittoEvent): Promise<boolean> {
|
||||||
async function hydrateEvent(event: DittoEvent, signal: AbortSignal): Promise<void> {
|
async function hydrateEvent(event: DittoEvent, signal: AbortSignal): Promise<void> {
|
||||||
await hydrateEvents({ events: [event], store: await Storages.db(), signal });
|
await hydrateEvents({ events: [event], store: await Storages.db(), signal });
|
||||||
|
|
||||||
const { kysely } = await DittoDB.getInstance();
|
const kysely = await Storages.kysely();
|
||||||
const domain = await kysely
|
const domain = await kysely
|
||||||
.selectFrom('pubkey_domains')
|
.selectFrom('pubkey_domains')
|
||||||
.select('domain')
|
.select('domain')
|
||||||
|
|
@ -118,7 +117,7 @@ async function hydrateEvent(event: DittoEvent, signal: AbortSignal): Promise<voi
|
||||||
async function storeEvent(event: DittoEvent, signal?: AbortSignal): Promise<undefined> {
|
async function storeEvent(event: DittoEvent, signal?: AbortSignal): Promise<undefined> {
|
||||||
if (NKinds.ephemeral(event.kind)) return;
|
if (NKinds.ephemeral(event.kind)) return;
|
||||||
const store = await Storages.db();
|
const store = await Storages.db();
|
||||||
const { kysely } = await DittoDB.getInstance();
|
const kysely = await Storages.kysely();
|
||||||
|
|
||||||
await updateStats({ event, store, kysely }).catch(debug);
|
await updateStats({ event, store, kysely }).catch(debug);
|
||||||
await store.event(event, { signal });
|
await store.event(event, { signal });
|
||||||
|
|
@ -146,7 +145,7 @@ async function parseMetadata(event: NostrEvent, signal: AbortSignal): Promise<vo
|
||||||
|
|
||||||
// Track pubkey domain.
|
// Track pubkey domain.
|
||||||
try {
|
try {
|
||||||
const { kysely } = await DittoDB.getInstance();
|
const kysely = await Storages.kysely();
|
||||||
const { domain } = parseNip05(nip05);
|
const { domain } = parseNip05(nip05);
|
||||||
|
|
||||||
await sql`
|
await sql`
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
// deno-lint-ignore-file require-await
|
// deno-lint-ignore-file require-await
|
||||||
import { Conf } from '@/config.ts';
|
import { Conf } from '@/config.ts';
|
||||||
|
import { DittoDatabase } from '@/db/DittoDatabase.ts';
|
||||||
import { DittoDB } from '@/db/DittoDB.ts';
|
import { DittoDB } from '@/db/DittoDB.ts';
|
||||||
import { AdminStore } from '@/storages/AdminStore.ts';
|
import { AdminStore } from '@/storages/AdminStore.ts';
|
||||||
import { EventsDB } from '@/storages/EventsDB.ts';
|
import { EventsDB } from '@/storages/EventsDB.ts';
|
||||||
|
|
@ -11,17 +12,31 @@ import { seedZapSplits } from '@/utils/zap-split.ts';
|
||||||
|
|
||||||
export class Storages {
|
export class Storages {
|
||||||
private static _db: Promise<EventsDB> | undefined;
|
private static _db: Promise<EventsDB> | undefined;
|
||||||
|
private static _database: DittoDatabase | undefined;
|
||||||
private static _admin: Promise<AdminStore> | undefined;
|
private static _admin: Promise<AdminStore> | undefined;
|
||||||
private static _client: Promise<NPool> | undefined;
|
private static _client: Promise<NPool> | undefined;
|
||||||
private static _pubsub: Promise<InternalRelay> | undefined;
|
private static _pubsub: Promise<InternalRelay> | undefined;
|
||||||
private static _search: Promise<SearchStore> | undefined;
|
private static _search: Promise<SearchStore> | undefined;
|
||||||
|
|
||||||
|
public static async database(): Promise<DittoDatabase> {
|
||||||
|
if (!this._database) {
|
||||||
|
this._database = DittoDB.create(Conf.databaseUrl, { poolSize: Conf.pg.poolSize });
|
||||||
|
await DittoDB.migrate(this._database.kysely);
|
||||||
|
}
|
||||||
|
return this._database;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async kysely(): Promise<DittoDatabase['kysely']> {
|
||||||
|
const { kysely } = await this.database();
|
||||||
|
return kysely;
|
||||||
|
}
|
||||||
|
|
||||||
/** SQL database to store events this Ditto server cares about. */
|
/** SQL database to store events this Ditto server cares about. */
|
||||||
public static async db(): Promise<EventsDB> {
|
public static async db(): Promise<EventsDB> {
|
||||||
if (!this._db) {
|
if (!this._db) {
|
||||||
this._db = (async () => {
|
this._db = (async () => {
|
||||||
const { kysely } = await DittoDB.getInstance();
|
const { kysely } = await this.database();
|
||||||
const store = new EventsDB(kysely);
|
const store = new EventsDB({ kysely, pubkey: Conf.pubkey, timeout: Conf.db.timeouts.default });
|
||||||
await seedZapSplits(store);
|
await seedZapSplits(store);
|
||||||
return store;
|
return store;
|
||||||
})();
|
})();
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
// deno-lint-ignore-file require-await
|
// deno-lint-ignore-file require-await
|
||||||
|
|
||||||
import { NDatabase, NPostgres } from '@nostrify/db';
|
import { NPostgres } from '@nostrify/db';
|
||||||
import {
|
import {
|
||||||
NIP50,
|
NIP50,
|
||||||
NKinds,
|
NKinds,
|
||||||
|
|
@ -16,13 +16,12 @@ import { Stickynotes } from '@soapbox/stickynotes';
|
||||||
import { Kysely } from 'kysely';
|
import { Kysely } from 'kysely';
|
||||||
import { nip27 } from 'nostr-tools';
|
import { nip27 } from 'nostr-tools';
|
||||||
|
|
||||||
import { Conf } from '@/config.ts';
|
|
||||||
import { DittoTables } from '@/db/DittoTables.ts';
|
import { DittoTables } from '@/db/DittoTables.ts';
|
||||||
import { dbEventsCounter } from '@/metrics.ts';
|
import { dbEventsCounter } from '@/metrics.ts';
|
||||||
import { RelayError } from '@/RelayError.ts';
|
import { RelayError } from '@/RelayError.ts';
|
||||||
import { purifyEvent } from '@/storages/hydrate.ts';
|
|
||||||
import { isNostrId, isURL } from '@/utils.ts';
|
import { isNostrId, isURL } from '@/utils.ts';
|
||||||
import { abortError } from '@/utils/abort.ts';
|
import { abortError } from '@/utils/abort.ts';
|
||||||
|
import { purifyEvent } from '@/utils/purify.ts';
|
||||||
|
|
||||||
/** Function to decide whether or not to index a tag. */
|
/** Function to decide whether or not to index a tag. */
|
||||||
type TagCondition = ({ event, count, value }: {
|
type TagCondition = ({ event, count, value }: {
|
||||||
|
|
@ -31,9 +30,19 @@ type TagCondition = ({ event, count, value }: {
|
||||||
value: string;
|
value: string;
|
||||||
}) => boolean;
|
}) => boolean;
|
||||||
|
|
||||||
|
/** Options for the EventsDB store. */
|
||||||
|
interface EventsDBOpts {
|
||||||
|
/** Kysely instance to use. */
|
||||||
|
kysely: Kysely<DittoTables>;
|
||||||
|
/** Pubkey of the admin account. */
|
||||||
|
pubkey: string;
|
||||||
|
/** Timeout in milliseconds for database queries. */
|
||||||
|
timeout: number;
|
||||||
|
}
|
||||||
|
|
||||||
/** SQL database storage adapter for Nostr events. */
|
/** SQL database storage adapter for Nostr events. */
|
||||||
class EventsDB implements NStore {
|
class EventsDB implements NStore {
|
||||||
private store: NDatabase | NPostgres;
|
private store: NPostgres;
|
||||||
private console = new Stickynotes('ditto:db:events');
|
private console = new Stickynotes('ditto:db:events');
|
||||||
|
|
||||||
/** Conditions for when to index certain tags. */
|
/** Conditions for when to index certain tags. */
|
||||||
|
|
@ -53,8 +62,8 @@ class EventsDB implements NStore {
|
||||||
't': ({ event, count, value }) => (event.kind === 1985 ? count < 20 : count < 5) && value.length < 50,
|
't': ({ event, count, value }) => (event.kind === 1985 ? count < 20 : count < 5) && value.length < 50,
|
||||||
};
|
};
|
||||||
|
|
||||||
constructor(private kysely: Kysely<DittoTables>) {
|
constructor(private opts: EventsDBOpts) {
|
||||||
this.store = new NPostgres(kysely, {
|
this.store = new NPostgres(opts.kysely, {
|
||||||
indexTags: EventsDB.indexTags,
|
indexTags: EventsDB.indexTags,
|
||||||
indexSearch: EventsDB.searchText,
|
indexSearch: EventsDB.searchText,
|
||||||
});
|
});
|
||||||
|
|
@ -73,7 +82,7 @@ class EventsDB implements NStore {
|
||||||
await this.deleteEventsAdmin(event);
|
await this.deleteEventsAdmin(event);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await this.store.event(event, { ...opts, timeout: opts.timeout ?? Conf.db.timeouts.default });
|
await this.store.event(event, { ...opts, timeout: opts.timeout ?? this.opts.timeout });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e.message === 'Cannot add a deleted event') {
|
if (e.message === 'Cannot add a deleted event') {
|
||||||
throw new RelayError('blocked', 'event deleted by user');
|
throw new RelayError('blocked', 'event deleted by user');
|
||||||
|
|
@ -88,7 +97,7 @@ class EventsDB implements NStore {
|
||||||
/** Check if an event has been deleted by the admin. */
|
/** Check if an event has been deleted by the admin. */
|
||||||
private async isDeletedAdmin(event: NostrEvent): Promise<boolean> {
|
private async isDeletedAdmin(event: NostrEvent): Promise<boolean> {
|
||||||
const filters: NostrFilter[] = [
|
const filters: NostrFilter[] = [
|
||||||
{ kinds: [5], authors: [Conf.pubkey], '#e': [event.id], limit: 1 },
|
{ kinds: [5], authors: [this.opts.pubkey], '#e': [event.id], limit: 1 },
|
||||||
];
|
];
|
||||||
|
|
||||||
if (NKinds.replaceable(event.kind) || NKinds.parameterizedReplaceable(event.kind)) {
|
if (NKinds.replaceable(event.kind) || NKinds.parameterizedReplaceable(event.kind)) {
|
||||||
|
|
@ -96,7 +105,7 @@ class EventsDB implements NStore {
|
||||||
|
|
||||||
filters.push({
|
filters.push({
|
||||||
kinds: [5],
|
kinds: [5],
|
||||||
authors: [Conf.pubkey],
|
authors: [this.opts.pubkey],
|
||||||
'#a': [`${event.kind}:${event.pubkey}:${d}`],
|
'#a': [`${event.kind}:${event.pubkey}:${d}`],
|
||||||
since: event.created_at,
|
since: event.created_at,
|
||||||
limit: 1,
|
limit: 1,
|
||||||
|
|
@ -109,7 +118,7 @@ class EventsDB implements NStore {
|
||||||
|
|
||||||
/** The DITTO_NSEC can delete any event from the database. NDatabase already handles user deletions. */
|
/** The DITTO_NSEC can delete any event from the database. NDatabase already handles user deletions. */
|
||||||
private async deleteEventsAdmin(event: NostrEvent): Promise<void> {
|
private async deleteEventsAdmin(event: NostrEvent): Promise<void> {
|
||||||
if (event.kind === 5 && event.pubkey === Conf.pubkey) {
|
if (event.kind === 5 && event.pubkey === this.opts.pubkey) {
|
||||||
const ids = new Set(event.tags.filter(([name]) => name === 'e').map(([_name, value]) => value));
|
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));
|
const addrs = new Set(event.tags.filter(([name]) => name === 'a').map(([_name, value]) => value));
|
||||||
|
|
||||||
|
|
@ -180,7 +189,7 @@ class EventsDB implements NStore {
|
||||||
|
|
||||||
this.console.debug('REQ', JSON.stringify(filters));
|
this.console.debug('REQ', JSON.stringify(filters));
|
||||||
|
|
||||||
return this.store.query(filters, { ...opts, timeout: opts.timeout ?? Conf.db.timeouts.default });
|
return this.store.query(filters, { ...opts, timeout: opts.timeout ?? this.opts.timeout });
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Delete events based on filters from the database. */
|
/** Delete events based on filters from the database. */
|
||||||
|
|
@ -188,7 +197,7 @@ class EventsDB implements NStore {
|
||||||
if (!filters.length) return Promise.resolve();
|
if (!filters.length) return Promise.resolve();
|
||||||
this.console.debug('DELETE', JSON.stringify(filters));
|
this.console.debug('DELETE', JSON.stringify(filters));
|
||||||
|
|
||||||
return this.store.remove(filters, { ...opts, timeout: opts.timeout ?? Conf.db.timeouts.default });
|
return this.store.remove(filters, { ...opts, timeout: opts.timeout ?? this.opts.timeout });
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Get number of events that would be returned by filters. */
|
/** Get number of events that would be returned by filters. */
|
||||||
|
|
@ -201,7 +210,7 @@ class EventsDB implements NStore {
|
||||||
|
|
||||||
this.console.debug('COUNT', JSON.stringify(filters));
|
this.console.debug('COUNT', JSON.stringify(filters));
|
||||||
|
|
||||||
return this.store.count(filters, { ...opts, timeout: opts.timeout ?? Conf.db.timeouts.default });
|
return this.store.count(filters, { ...opts, timeout: opts.timeout ?? this.opts.timeout });
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Return only the tags that should be indexed. */
|
/** Return only the tags that should be indexed. */
|
||||||
|
|
@ -277,7 +286,7 @@ class EventsDB implements NStore {
|
||||||
) as { key: 'domain'; value: string } | undefined)?.value;
|
) as { key: 'domain'; value: string } | undefined)?.value;
|
||||||
|
|
||||||
if (domain) {
|
if (domain) {
|
||||||
const query = this.kysely
|
const query = this.opts.kysely
|
||||||
.selectFrom('pubkey_domains')
|
.selectFrom('pubkey_domains')
|
||||||
.select('pubkey')
|
.select('pubkey')
|
||||||
.where('domain', '=', domain);
|
.where('domain', '=', domain);
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ import { Machina } from '@nostrify/nostrify/utils';
|
||||||
import { matchFilter } from 'nostr-tools';
|
import { matchFilter } from 'nostr-tools';
|
||||||
|
|
||||||
import { DittoEvent } from '@/interfaces/DittoEvent.ts';
|
import { DittoEvent } from '@/interfaces/DittoEvent.ts';
|
||||||
import { purifyEvent } from '@/storages/hydrate.ts';
|
import { purifyEvent } from '@/utils/purify.ts';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* PubSub event store for streaming events within the application.
|
* PubSub event store for streaming events within the application.
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,13 @@
|
||||||
import { NostrEvent, NStore } from '@nostrify/nostrify';
|
import { NStore } from '@nostrify/nostrify';
|
||||||
|
import { Kysely } from 'kysely';
|
||||||
import { matchFilter } from 'nostr-tools';
|
import { matchFilter } from 'nostr-tools';
|
||||||
|
|
||||||
import { DittoDB } from '@/db/DittoDB.ts';
|
|
||||||
import { DittoTables } from '@/db/DittoTables.ts';
|
import { DittoTables } from '@/db/DittoTables.ts';
|
||||||
import { Conf } from '@/config.ts';
|
import { Conf } from '@/config.ts';
|
||||||
import { type DittoEvent } from '@/interfaces/DittoEvent.ts';
|
import { type DittoEvent } from '@/interfaces/DittoEvent.ts';
|
||||||
import { findQuoteTag } from '@/utils/tags.ts';
|
import { findQuoteTag } from '@/utils/tags.ts';
|
||||||
import { findQuoteInContent } from '@/utils/note.ts';
|
import { findQuoteInContent } from '@/utils/note.ts';
|
||||||
import { Kysely } from 'kysely';
|
import { Storages } from '@/storages.ts';
|
||||||
|
|
||||||
interface HydrateOpts {
|
interface HydrateOpts {
|
||||||
events: DittoEvent[];
|
events: DittoEvent[];
|
||||||
|
|
@ -18,7 +18,7 @@ interface HydrateOpts {
|
||||||
|
|
||||||
/** Hydrate events using the provided storage. */
|
/** Hydrate events using the provided storage. */
|
||||||
async function hydrateEvents(opts: HydrateOpts): Promise<DittoEvent[]> {
|
async function hydrateEvents(opts: HydrateOpts): Promise<DittoEvent[]> {
|
||||||
const { events, store, signal, kysely = (await DittoDB.getInstance()).kysely } = opts;
|
const { events, store, signal, kysely = await Storages.kysely() } = opts;
|
||||||
|
|
||||||
if (!events.length) {
|
if (!events.length) {
|
||||||
return events;
|
return events;
|
||||||
|
|
@ -338,17 +338,4 @@ async function gatherEventStats(
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Return a normalized event without any non-standard keys. */
|
export { hydrateEvents };
|
||||||
function purifyEvent(event: NostrEvent): NostrEvent {
|
|
||||||
return {
|
|
||||||
id: event.id,
|
|
||||||
pubkey: event.pubkey,
|
|
||||||
kind: event.kind,
|
|
||||||
content: event.content,
|
|
||||||
tags: event.tags,
|
|
||||||
sig: event.sig,
|
|
||||||
created_at: event.created_at,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export { hydrateEvents, purifyEvent };
|
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,8 @@ import { finalizeEvent, generateSecretKey } from 'nostr-tools';
|
||||||
|
|
||||||
import { Conf } from '@/config.ts';
|
import { Conf } from '@/config.ts';
|
||||||
import { DittoDB } from '@/db/DittoDB.ts';
|
import { DittoDB } from '@/db/DittoDB.ts';
|
||||||
import { purifyEvent } from '@/storages/hydrate.ts';
|
|
||||||
import { EventsDB } from '@/storages/EventsDB.ts';
|
import { EventsDB } from '@/storages/EventsDB.ts';
|
||||||
|
import { purifyEvent } from '@/utils/purify.ts';
|
||||||
|
|
||||||
/** Import an event fixture by name in tests. */
|
/** Import an event fixture by name in tests. */
|
||||||
export async function eventFixture(name: string): Promise<NostrEvent> {
|
export async function eventFixture(name: string): Promise<NostrEvent> {
|
||||||
|
|
@ -38,7 +38,12 @@ export async function createTestDB() {
|
||||||
const { kysely } = DittoDB.create(testDatabaseUrl, { poolSize: 1 });
|
const { kysely } = DittoDB.create(testDatabaseUrl, { poolSize: 1 });
|
||||||
|
|
||||||
await DittoDB.migrate(kysely);
|
await DittoDB.migrate(kysely);
|
||||||
const store = new EventsDB(kysely);
|
|
||||||
|
const store = new EventsDB({
|
||||||
|
kysely,
|
||||||
|
timeout: Conf.db.timeouts.default,
|
||||||
|
pubkey: Conf.pubkey,
|
||||||
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
store,
|
store,
|
||||||
|
|
|
||||||
|
|
@ -3,10 +3,10 @@ import { Stickynotes } from '@soapbox/stickynotes';
|
||||||
import { Kysely, sql } from 'kysely';
|
import { Kysely, sql } from 'kysely';
|
||||||
|
|
||||||
import { Conf } from '@/config.ts';
|
import { Conf } from '@/config.ts';
|
||||||
import { DittoDB } from '@/db/DittoDB.ts';
|
|
||||||
import { DittoTables } from '@/db/DittoTables.ts';
|
import { DittoTables } from '@/db/DittoTables.ts';
|
||||||
import { handleEvent } from '@/pipeline.ts';
|
import { handleEvent } from '@/pipeline.ts';
|
||||||
import { AdminSigner } from '@/signers/AdminSigner.ts';
|
import { AdminSigner } from '@/signers/AdminSigner.ts';
|
||||||
|
import { Storages } from '@/storages.ts';
|
||||||
import { Time } from '@/utils/time.ts';
|
import { Time } from '@/utils/time.ts';
|
||||||
|
|
||||||
const console = new Stickynotes('ditto:trends');
|
const console = new Stickynotes('ditto:trends');
|
||||||
|
|
@ -70,7 +70,7 @@ export async function updateTrendingTags(
|
||||||
aliases?: string[],
|
aliases?: string[],
|
||||||
) {
|
) {
|
||||||
console.info(`Updating trending ${l}...`);
|
console.info(`Updating trending ${l}...`);
|
||||||
const { kysely } = await DittoDB.getInstance();
|
const kysely = await Storages.kysely();
|
||||||
const signal = AbortSignal.timeout(1000);
|
const signal = AbortSignal.timeout(1000);
|
||||||
|
|
||||||
const yesterday = Math.floor((Date.now() - Time.days(1)) / 1000);
|
const yesterday = Math.floor((Date.now() - Time.days(1)) / 1000);
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ import { RelayError } from '@/RelayError.ts';
|
||||||
import { AdminSigner } from '@/signers/AdminSigner.ts';
|
import { AdminSigner } from '@/signers/AdminSigner.ts';
|
||||||
import { Storages } from '@/storages.ts';
|
import { Storages } from '@/storages.ts';
|
||||||
import { nostrNow } from '@/utils.ts';
|
import { nostrNow } from '@/utils.ts';
|
||||||
import { purifyEvent } from '@/storages/hydrate.ts';
|
import { purifyEvent } from '@/utils/purify.ts';
|
||||||
|
|
||||||
const debug = Debug('ditto:api');
|
const debug = Debug('ditto:api');
|
||||||
|
|
||||||
|
|
|
||||||
14
src/utils/purify.ts
Normal file
14
src/utils/purify.ts
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
import { NostrEvent } from '@nostrify/nostrify';
|
||||||
|
|
||||||
|
/** Return a normalized event without any non-standard keys. */
|
||||||
|
export function purifyEvent(event: NostrEvent): NostrEvent {
|
||||||
|
return {
|
||||||
|
id: event.id,
|
||||||
|
pubkey: event.pubkey,
|
||||||
|
kind: event.kind,
|
||||||
|
content: event.content,
|
||||||
|
tags: event.tags,
|
||||||
|
sig: event.sig,
|
||||||
|
created_at: event.created_at,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
@ -13,8 +13,8 @@ export const policyWorker = Comlink.wrap<CustomPolicy>(
|
||||||
type: 'module',
|
type: 'module',
|
||||||
deno: {
|
deno: {
|
||||||
permissions: {
|
permissions: {
|
||||||
read: [Conf.policy],
|
read: [Conf.denoDir, Conf.policy, Conf.dataDir],
|
||||||
write: false,
|
write: [Conf.dataDir],
|
||||||
net: 'inherit',
|
net: 'inherit',
|
||||||
env: false,
|
env: false,
|
||||||
},
|
},
|
||||||
|
|
@ -24,7 +24,12 @@ export const policyWorker = Comlink.wrap<CustomPolicy>(
|
||||||
);
|
);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await policyWorker.import(Conf.policy);
|
await policyWorker.init({
|
||||||
|
path: Conf.policy,
|
||||||
|
cwd: Deno.cwd(),
|
||||||
|
databaseUrl: Conf.databaseUrl,
|
||||||
|
adminPubkey: Conf.pubkey,
|
||||||
|
});
|
||||||
console.debug(`Using custom policy: ${Conf.policy}`);
|
console.debug(`Using custom policy: ${Conf.policy}`);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e.message.includes('Module not found')) {
|
if (e.message.includes('Module not found')) {
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,21 @@ import { NostrEvent, NostrRelayOK, NPolicy } from '@nostrify/nostrify';
|
||||||
import { NoOpPolicy, ReadOnlyPolicy } from '@nostrify/nostrify/policies';
|
import { NoOpPolicy, ReadOnlyPolicy } from '@nostrify/nostrify/policies';
|
||||||
import * as Comlink from 'comlink';
|
import * as Comlink from 'comlink';
|
||||||
|
|
||||||
|
import { DittoDB } from '@/db/DittoDB.ts';
|
||||||
|
import { EventsDB } from '@/storages/EventsDB.ts';
|
||||||
|
|
||||||
|
/** Serializable object the worker can use to set up the state. */
|
||||||
|
interface PolicyInit {
|
||||||
|
/** Path to the policy module (https, jsr, file, etc) */
|
||||||
|
path: string;
|
||||||
|
/** Current working directory. */
|
||||||
|
cwd: string;
|
||||||
|
/** Database URL to connect to. */
|
||||||
|
databaseUrl: string;
|
||||||
|
/** Admin pubkey to use for EventsDB checks. */
|
||||||
|
adminPubkey: string;
|
||||||
|
}
|
||||||
|
|
||||||
export class CustomPolicy implements NPolicy {
|
export class CustomPolicy implements NPolicy {
|
||||||
private policy: NPolicy = new ReadOnlyPolicy();
|
private policy: NPolicy = new ReadOnlyPolicy();
|
||||||
|
|
||||||
|
|
@ -11,10 +26,22 @@ export class CustomPolicy implements NPolicy {
|
||||||
return this.policy.call(event);
|
return this.policy.call(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
async import(path: string): Promise<void> {
|
async init({ path, cwd, databaseUrl, adminPubkey }: PolicyInit): Promise<void> {
|
||||||
|
// HACK: PGlite uses `path.resolve`, which requires read permission on Deno (which we don't want to give).
|
||||||
|
// We can work around this getting the cwd from the caller and overwriting `Deno.cwd`.
|
||||||
|
Deno.cwd = () => cwd;
|
||||||
|
|
||||||
|
const { kysely } = DittoDB.create(databaseUrl, { poolSize: 1 });
|
||||||
|
|
||||||
|
const store = new EventsDB({
|
||||||
|
kysely,
|
||||||
|
pubkey: adminPubkey,
|
||||||
|
timeout: 1_000,
|
||||||
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const Policy = (await import(path)).default;
|
const Policy = (await import(path)).default;
|
||||||
this.policy = new Policy();
|
this.policy = new Policy({ store });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e.message.includes('Module not found')) {
|
if (e.message.includes('Module not found')) {
|
||||||
this.policy = new NoOpPolicy();
|
this.policy = new NoOpPolicy();
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue