Merge branch 'rm-relays' into 'main'

Remove `relays` table from the database, track them with a NIP-65 admin event

See merge request soapbox-pub/ditto!207
This commit is contained in:
Alex Gleason 2024-05-02 00:37:02 +00:00
commit 79177cd6c3
23 changed files with 44 additions and 113 deletions

View File

@ -7,7 +7,6 @@
"debug": "deno run -A --inspect src/server.ts", "debug": "deno run -A --inspect src/server.ts",
"test": "DATABASE_URL=\"sqlite://:memory:\" deno test -A", "test": "DATABASE_URL=\"sqlite://:memory:\" deno test -A",
"check": "deno check src/server.ts", "check": "deno check src/server.ts",
"relays:sync": "deno run -A scripts/relays.ts sync",
"nsec": "deno run scripts/nsec.ts", "nsec": "deno run scripts/nsec.ts",
"admin:event": "deno run -A scripts/admin-event.ts", "admin:event": "deno run -A scripts/admin-event.ts",
"admin:role": "deno run -A scripts/admin-role.ts" "admin:role": "deno run -A scripts/admin-role.ts"

View File

@ -1,23 +0,0 @@
import { addRelays } from '@/db/relays.ts';
import { filteredArray } from '@/schema.ts';
import { relaySchema } from '@/utils.ts';
switch (Deno.args[0]) {
case 'sync':
await sync(Deno.args.slice(1));
break;
default:
console.log('Usage: deno run -A scripts/relays.ts sync <url>');
}
async function sync([url]: string[]) {
if (!url) {
console.error('Error: please provide a URL');
Deno.exit(1);
}
const response = await fetch(url);
const data = await response.json();
const values = filteredArray(relaySchema).parse(data) as `wss://${string}`[];
await addRelays(values, { active: true });
console.log(`Done: added ${values.length} relays.`);
}

View File

@ -2,7 +2,6 @@ export interface DittoTables {
events: EventRow; events: EventRow;
events_fts: EventFTSRow; events_fts: EventFTSRow;
tags: TagRow; tags: TagRow;
relays: RelayRow;
unattached_media: UnattachedMediaRow; unattached_media: UnattachedMediaRow;
author_stats: AuthorStatsRow; author_stats: AuthorStatsRow;
event_stats: EventStatsRow; event_stats: EventStatsRow;
@ -45,12 +44,6 @@ interface TagRow {
event_id: string; event_id: string;
} }
interface RelayRow {
url: string;
domain: string;
active: boolean;
}
interface UnattachedMediaRow { interface UnattachedMediaRow {
id: string; id: string;
pubkey: string; pubkey: string;

View File

@ -1,4 +1,4 @@
import { Kysely } from '@/deps.ts'; import { Kysely } from 'kysely';
export async function up(db: Kysely<any>): Promise<void> { export async function up(db: Kysely<any>): Promise<void> {
await db.schema await db.schema

View File

@ -1,4 +1,4 @@
import { Kysely } from '@/deps.ts'; import { Kysely } from 'kysely';
export async function up(db: Kysely<any>): Promise<void> { export async function up(db: Kysely<any>): Promise<void> {
await db.schema await db.schema

View File

@ -1,4 +1,4 @@
import { Kysely } from '@/deps.ts'; import { Kysely } from 'kysely';
export async function up(_db: Kysely<any>): Promise<void> { export async function up(_db: Kysely<any>): Promise<void> {
} }

View File

@ -1,4 +1,4 @@
import { Kysely } from '@/deps.ts'; import { Kysely } from 'kysely';
export async function up(_db: Kysely<any>): Promise<void> { export async function up(_db: Kysely<any>): Promise<void> {
} }

View File

@ -1,4 +1,4 @@
import { Kysely } from '@/deps.ts'; import { Kysely } from 'kysely';
export async function up(_db: Kysely<any>): Promise<void> { export async function up(_db: Kysely<any>): Promise<void> {
} }

View File

@ -1,4 +1,4 @@
import { Kysely } from '@/deps.ts'; import { Kysely } from 'kysely';
export async function up(db: Kysely<any>): Promise<void> { export async function up(db: Kysely<any>): Promise<void> {
await db.schema await db.schema

View File

@ -1,4 +1,4 @@
import { Kysely } from '@/deps.ts'; import { Kysely } from 'kysely';
export async function up(_db: Kysely<any>): Promise<void> { export async function up(_db: Kysely<any>): Promise<void> {
} }

View File

@ -1,4 +1,4 @@
import { Kysely } from '@/deps.ts'; import { Kysely } from 'kysely';
export async function up(db: Kysely<any>): Promise<void> { export async function up(db: Kysely<any>): Promise<void> {
await db.schema await db.schema

View File

@ -1,4 +1,4 @@
import { Kysely } from '@/deps.ts'; import { Kysely } from 'kysely';
export async function up(db: Kysely<any>): Promise<void> { export async function up(db: Kysely<any>): Promise<void> {
await db.schema.dropTable('users').ifExists().execute(); await db.schema.dropTable('users').ifExists().execute();

View File

@ -1,4 +1,4 @@
import { Kysely } from '@/deps.ts'; import { Kysely } from 'kysely';
export async function up(db: Kysely<any>): Promise<void> { export async function up(db: Kysely<any>): Promise<void> {
await db.schema await db.schema

View File

@ -1,4 +1,4 @@
import { Kysely } from '@/deps.ts'; import { Kysely } from 'kysely';
export async function up(db: Kysely<any>): Promise<void> { export async function up(db: Kysely<any>): Promise<void> {
await db.schema.dropIndex('idx_tags_tag').execute(); await db.schema.dropIndex('idx_tags_tag').execute();

View File

@ -1,4 +1,4 @@
import { Kysely } from '@/deps.ts'; import { Kysely } from 'kysely';
export async function up(db: Kysely<any>): Promise<void> { export async function up(db: Kysely<any>): Promise<void> {
await db.schema.alterTable('events').addColumn('deleted_at', 'integer').execute(); await db.schema.alterTable('events').addColumn('deleted_at', 'integer').execute();

View File

@ -1,4 +1,4 @@
import { Kysely } from '@/deps.ts'; import { Kysely } from 'kysely';
export async function up(db: Kysely<any>): Promise<void> { export async function up(db: Kysely<any>): Promise<void> {
await db.schema.createIndex('idx_author_stats_pubkey').on('author_stats').column('pubkey').execute(); await db.schema.createIndex('idx_author_stats_pubkey').on('author_stats').column('pubkey').execute();

View File

@ -1,4 +1,4 @@
import { Kysely } from '@/deps.ts'; import { Kysely } from 'kysely';
export async function up(db: Kysely<any>): Promise<void> { export async function up(db: Kysely<any>): Promise<void> {
await db.schema await db.schema

View File

@ -1,4 +1,4 @@
import { Kysely } from '@/deps.ts'; import { Kysely } from 'kysely';
export async function up(db: Kysely<any>): Promise<void> { export async function up(db: Kysely<any>): Promise<void> {
await db.schema await db.schema

View File

@ -0,0 +1,14 @@
import { Kysely } from 'kysely';
export async function up(db: Kysely<any>): Promise<void> {
await db.schema.dropTable('relays').execute();
}
export async function down(db: Kysely<any>): Promise<void> {
await db.schema
.createTable('relays')
.addColumn('url', 'text', (col) => col.primaryKey())
.addColumn('domain', 'text', (col) => col.notNull())
.addColumn('active', 'boolean', (col) => col.notNull())
.execute();
}

View File

@ -1,37 +0,0 @@
import tldts from 'tldts';
import { db } from '@/db.ts';
interface AddRelaysOpts {
active?: boolean;
}
/** Inserts relays into the database, skipping duplicates. */
function addRelays(relays: `wss://${string}`[], opts: AddRelaysOpts = {}) {
if (!relays.length) return Promise.resolve();
const { active = false } = opts;
const values = relays.map((url) => ({
url: new URL(url).toString(),
domain: tldts.getDomain(url)!,
active,
}));
return db.insertInto('relays')
.values(values)
.onConflict((oc) => oc.column('url').doNothing())
.execute();
}
/** Get a list of all known active relay URLs. */
async function getActiveRelays(): Promise<string[]> {
const rows = await db
.selectFrom('relays')
.select('relays.url')
.where('relays.active', '=', true)
.execute();
return rows.map((row) => row.url);
}
export { addRelays, getActiveRelays };

View File

@ -5,7 +5,6 @@ import { sql } from 'kysely';
import { Conf } from '@/config.ts'; import { Conf } from '@/config.ts';
import { db } from '@/db.ts'; import { db } from '@/db.ts';
import { addRelays } from '@/db/relays.ts';
import { deleteAttachedMedia } from '@/db/unattached-media.ts'; import { deleteAttachedMedia } from '@/db/unattached-media.ts';
import { DittoEvent } from '@/interfaces/DittoEvent.ts'; import { DittoEvent } from '@/interfaces/DittoEvent.ts';
import { isEphemeralKind } from '@/kinds.ts'; import { isEphemeralKind } from '@/kinds.ts';
@ -14,7 +13,7 @@ import { updateStats } from '@/stats.ts';
import { hydrateEvents, purifyEvent } from '@/storages/hydrate.ts'; import { hydrateEvents, purifyEvent } from '@/storages/hydrate.ts';
import { Storages } from '@/storages.ts'; import { Storages } from '@/storages.ts';
import { getTagSet } from '@/tags.ts'; import { getTagSet } from '@/tags.ts';
import { eventAge, isRelay, nostrDate, nostrNow, parseNip05, Time } from '@/utils.ts'; import { eventAge, nostrDate, nostrNow, parseNip05, Time } from '@/utils.ts';
import { fetchWorker } from '@/workers/fetch.ts'; import { fetchWorker } from '@/workers/fetch.ts';
import { TrendsWorker } from '@/workers/trends.ts'; import { TrendsWorker } from '@/workers/trends.ts';
import { verifyEventWorker } from '@/workers/verify.ts'; import { verifyEventWorker } from '@/workers/verify.ts';
@ -59,7 +58,6 @@ async function handleEvent(event: DittoEvent, signal: AbortSignal): Promise<void
parseMetadata(event, signal), parseMetadata(event, signal),
processDeletions(event, signal), processDeletions(event, signal),
DVM.event(event), DVM.event(event),
trackRelays(event),
trackHashtags(event), trackHashtags(event),
fetchRelatedEvents(event, signal), fetchRelatedEvents(event, signal),
processMedia(event), processMedia(event),
@ -183,22 +181,6 @@ async function trackHashtags(event: NostrEvent): Promise<void> {
} }
} }
/** Tracks known relays in the database. */
function trackRelays(event: NostrEvent) {
const relays = new Set<`wss://${string}`>();
event.tags.forEach((tag) => {
if (['p', 'e', 'a'].includes(tag[0]) && isRelay(tag[2])) {
relays.add(tag[2]);
}
if (event.kind === 10002 && tag[0] === 'r' && isRelay(tag[1])) {
relays.add(tag[1]);
}
});
return addRelays([...relays]);
}
/** Queue related events to fetch. */ /** Queue related events to fetch. */
async function fetchRelatedEvents(event: DittoEvent, signal: AbortSignal) { async function fetchRelatedEvents(event: DittoEvent, signal: AbortSignal) {
if (!event.user) { if (!event.user) {

View File

@ -1,8 +1,20 @@
import { RelayPoolWorker } from 'nostr-relaypool'; import { RelayPoolWorker } from 'nostr-relaypool';
import { getActiveRelays } from '@/db/relays.ts'; import { Storages } from '@/storages.ts';
import { Conf } from '@/config.ts';
const activeRelays = await getActiveRelays(); const [relayList] = await Storages.db.query([
{ kinds: [10002], authors: [Conf.pubkey], limit: 1 },
]);
const tags = relayList?.tags ?? [];
const activeRelays = tags.reduce((acc, [name, url, marker]) => {
if (name === 'r' && !marker) {
acc.push(url);
}
return acc;
}, []);
console.log(`pool: connecting to ${activeRelays.length} relays.`); console.log(`pool: connecting to ${activeRelays.length} relays.`);

View File

@ -1,6 +1,5 @@
import { NostrEvent } from '@nostrify/nostrify'; import { NostrEvent } from '@nostrify/nostrify';
import { EventTemplate, getEventHash, nip19 } from 'nostr-tools'; import { EventTemplate, getEventHash, nip19 } from 'nostr-tools';
import { z } from 'zod';
import { nostrIdSchema } from '@/schemas/nostr.ts'; import { nostrIdSchema } from '@/schemas/nostr.ts';
@ -82,12 +81,6 @@ async function sha256(message: string): Promise<string> {
return hashHex; return hashHex;
} }
/** Schema to parse a relay URL. */
const relaySchema = z.string().max(255).startsWith('wss://').url();
/** Check whether the value is a valid relay URL. */
const isRelay = (relay: string): relay is `wss://${string}` => relaySchema.safeParse(relay).success;
/** Deduplicate events by ID. */ /** Deduplicate events by ID. */
function dedupeEvents(events: NostrEvent[]): NostrEvent[] { function dedupeEvents(events: NostrEvent[]): NostrEvent[] {
return [...new Map(events.map((event) => [event.id, event])).values()]; return [...new Map(events.map((event) => [event.id, event])).values()];
@ -143,13 +136,11 @@ export {
eventMatchesTemplate, eventMatchesTemplate,
findTag, findTag,
isNostrId, isNostrId,
isRelay,
isURL, isURL,
type Nip05, type Nip05,
nostrDate, nostrDate,
nostrNow, nostrNow,
parseNip05, parseNip05,
relaySchema,
sha256, sha256,
}; };