Remove @/config.ts import from utils/stats.ts

This commit is contained in:
Alex Gleason 2025-02-27 16:35:06 -06:00
parent ddf1a9d6dc
commit 5f5d0bc324
No known key found for this signature in database
GPG key ID: 7211D1F99744FBB7
18 changed files with 86 additions and 50 deletions

View file

@ -194,7 +194,7 @@ await db.migrate();
const pgstore = new DittoPgStore({
db,
pubkey: await conf.signer.getPublicKey(),
conf,
timeout: conf.db.timeouts.default,
notify: conf.notifyEnabled,
});

View file

@ -1,5 +1,6 @@
// deno-lint-ignore-file require-await
import { type DittoConf } from '@ditto/conf';
import { type DittoDB, type DittoTables } from '@ditto/db';
import { detectLanguage } from '@ditto/lang';
import { NPostgres, NPostgresSchema } from '@nostrify/db';
@ -52,8 +53,8 @@ interface TagConditionOpts {
interface DittoPgStoreOpts {
/** Kysely instance to use. */
db: DittoDB;
/** Pubkey of the admin account. */
pubkey: string;
/** Ditto configuration. */
conf: DittoConf;
/** Timeout in milliseconds for database queries. */
timeout?: number;
/** Whether the event returned should be a Nostr event or a Ditto event. Defaults to false. */
@ -171,7 +172,7 @@ export class DittoPgStore extends NPostgres {
): Promise<undefined> {
try {
await super.transaction(async (relay, kysely) => {
await updateStats({ event, relay, kysely: kysely as unknown as Kysely<DittoTables> });
await updateStats({ conf: this.opts.conf, relay, kysely: kysely as unknown as Kysely<DittoTables>, event });
await relay.event(event, opts);
});
} catch (e) {
@ -229,8 +230,11 @@ export class DittoPgStore extends NPostgres {
/** Check if an event has been deleted by the admin. */
private async isDeletedAdmin(event: NostrEvent): Promise<boolean> {
const { conf } = this.opts;
const adminPubkey = await conf.signer.getPublicKey();
const filters: NostrFilter[] = [
{ kinds: [5], authors: [this.opts.pubkey], '#e': [event.id], limit: 1 },
{ kinds: [5], authors: [adminPubkey], '#e': [event.id], limit: 1 },
];
if (NKinds.replaceable(event.kind) || NKinds.parameterizedReplaceable(event.kind)) {
@ -238,7 +242,7 @@ export class DittoPgStore extends NPostgres {
filters.push({
kinds: [5],
authors: [this.opts.pubkey],
authors: [adminPubkey],
'#a': [`${event.kind}:${event.pubkey}:${d}`],
since: event.created_at,
limit: 1,
@ -251,7 +255,10 @@ export class DittoPgStore extends NPostgres {
/** The DITTO_NSEC can delete any event from the database. NDatabase already handles user deletions. */
private async deleteEventsAdmin(event: NostrEvent): Promise<void> {
if (event.kind === 5 && event.pubkey === this.opts.pubkey) {
const { conf } = this.opts;
const adminPubkey = await conf.signer.getPublicKey();
if (event.kind === 5 && event.pubkey === adminPubkey) {
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));

View file

@ -19,8 +19,8 @@ export async function createTestDB(opts?: { pure?: boolean }) {
const store = new DittoPgStore({
db,
conf,
timeout: conf.db.timeouts.default,
pubkey: await conf.signer.getPublicKey(),
pure: opts?.pure ?? false,
notify: true,
});

View file

@ -23,7 +23,7 @@ Deno.test('updateStats with kind 1 increments notes count', async () => {
Deno.test('updateStats with kind 1 increments replies count', async () => {
await using test = await setupTest();
const { relay, kysely } = test;
const { kysely, relay } = test;
const sk = generateSecretKey();
@ -42,7 +42,7 @@ Deno.test('updateStats with kind 1 increments replies count', async () => {
Deno.test('updateStats with kind 5 decrements notes count', async () => {
await using test = await setupTest();
const { relay, kysely } = test;
const { kysely, relay } = test;
const sk = generateSecretKey();
const pubkey = getPublicKey(sk);
@ -74,7 +74,7 @@ Deno.test('updateStats with kind 3 increments followers count', async () => {
Deno.test('updateStats with kind 3 decrements followers count', async () => {
await using test = await setupTest();
const { relay, kysely } = test;
const { kysely, relay } = test;
const sk = generateSecretKey();
const follow = genEvent({ kind: 3, tags: [['p', 'alex']], created_at: 0 }, sk);
@ -101,7 +101,7 @@ Deno.test('getFollowDiff returns added and removed followers', () => {
Deno.test('updateStats with kind 6 increments reposts count', async () => {
await using test = await setupTest();
const { relay, kysely } = test;
const { kysely, relay } = test;
const note = genEvent({ kind: 1 });
await updateStats({ ...test, event: note });
@ -118,7 +118,7 @@ Deno.test('updateStats with kind 6 increments reposts count', async () => {
Deno.test('updateStats with kind 5 decrements reposts count', async () => {
await using test = await setupTest();
const { relay, kysely } = test;
const { kysely, relay } = test;
const note = genEvent({ kind: 1 });
await updateStats({ ...test, event: note });
@ -138,7 +138,7 @@ Deno.test('updateStats with kind 5 decrements reposts count', async () => {
Deno.test('updateStats with kind 7 increments reactions count', async () => {
await using test = await setupTest();
const { relay, kysely } = test;
const { kysely, relay } = test;
const note = genEvent({ kind: 1 });
await updateStats({ ...test, event: note });
@ -155,7 +155,7 @@ Deno.test('updateStats with kind 7 increments reactions count', async () => {
Deno.test('updateStats with kind 5 decrements reactions count', async () => {
await using test = await setupTest();
const { relay, kysely } = test;
const { kysely, relay } = test;
const note = genEvent({ kind: 1 });
await updateStats({ ...test, event: note });
@ -175,7 +175,7 @@ Deno.test('updateStats with kind 5 decrements reactions count', async () => {
Deno.test('countAuthorStats counts author stats from the database', async () => {
await using test = await setupTest();
const { relay } = test;
const { kysely, relay } = test;
const sk = generateSecretKey();
const pubkey = getPublicKey(sk);
@ -184,7 +184,7 @@ Deno.test('countAuthorStats counts author stats from the database', async () =>
await relay.event(genEvent({ kind: 1, content: 'yolo' }, sk));
await relay.event(genEvent({ kind: 3, tags: [['p', pubkey]] }));
await test.kysely.insertInto('author_stats').values({
await kysely.insertInto('author_stats').values({
pubkey,
search: 'Yolo Lolo',
notes_count: 0,
@ -193,7 +193,7 @@ Deno.test('countAuthorStats counts author stats from the database', async () =>
}).onConflict((oc) => oc.column('pubkey').doUpdateSet({ 'search': 'baka' }))
.execute();
const stats = await countAuthorStats({ ...test, pubkey });
const stats = await countAuthorStats({ ...test, kysely, pubkey });
assertEquals(stats!.notes_count, 2);
assertEquals(stats!.followers_count, 1);
@ -206,9 +206,10 @@ async function setupTest() {
await db.migrate();
const { kysely } = db;
const relay = new NPostgres(kysely);
const relay = new NPostgres(db.kysely);
return {
conf,
relay,
kysely,
[Symbol.asyncDispose]: async () => {

View file

@ -4,40 +4,46 @@ import { Insertable, Kysely, UpdateObject } from 'kysely';
import { SetRequired } from 'type-fest';
import { z } from 'zod';
import { Conf } from '@/config.ts';
import { findQuoteTag, findReplyTag, getTagSet } from '@/utils/tags.ts';
import type { DittoConf } from '@ditto/conf';
interface UpdateStatsOpts {
kysely: Kysely<DittoTables>;
conf: DittoConf;
relay: NStore;
kysely: Kysely<DittoTables>;
event: NostrEvent;
x?: 1 | -1;
}
/** Handle one event at a time and update relevant stats for it. */
// deno-lint-ignore require-await
export async function updateStats({ event, kysely, relay, x = 1 }: UpdateStatsOpts): Promise<void> {
export async function updateStats(opts: UpdateStatsOpts): Promise<void> {
const { event } = opts;
switch (event.kind) {
case 1:
case 20:
case 1111:
case 30023:
return handleEvent1(kysely, event, x);
return handleEvent1(opts);
case 3:
return handleEvent3(kysely, event, x, relay);
return handleEvent3(opts);
case 5:
return handleEvent5(kysely, event, -1, relay);
return handleEvent5(opts);
case 6:
return handleEvent6(kysely, event, x);
return handleEvent6(opts);
case 7:
return handleEvent7(kysely, event, x);
return handleEvent7(opts);
case 9735:
return handleEvent9735(kysely, event);
return handleEvent9735(opts);
}
}
/** Update stats for kind 1 event. */
async function handleEvent1(kysely: Kysely<DittoTables>, event: NostrEvent, x: number): Promise<void> {
async function handleEvent1(opts: UpdateStatsOpts): Promise<void> {
const { conf, kysely, event, x = 1 } = opts;
await updateAuthorStats(kysely, event.pubkey, (prev) => {
const now = event.created_at;
@ -47,7 +53,7 @@ async function handleEvent1(kysely: Kysely<DittoTables>, event: NostrEvent, x: n
if (start && end) { // Streak exists.
if (now <= end) {
// Streak cannot go backwards in time. Skip it.
} else if (now - end > Conf.streakWindow) {
} else if (now - end > conf.streakWindow) {
// Streak is broken. Start a new streak.
start = now;
end = now;
@ -88,7 +94,9 @@ async function handleEvent1(kysely: Kysely<DittoTables>, event: NostrEvent, x: n
}
/** Update stats for kind 3 event. */
async function handleEvent3(kysely: Kysely<DittoTables>, event: NostrEvent, x: number, relay: NStore): Promise<void> {
async function handleEvent3(opts: UpdateStatsOpts): Promise<void> {
const { relay, kysely, event, x = 1 } = opts;
const following = getTagSet(event.tags, 'p');
await updateAuthorStats(kysely, event.pubkey, () => ({ following_count: following.size }));
@ -117,26 +125,34 @@ async function handleEvent3(kysely: Kysely<DittoTables>, event: NostrEvent, x: n
}
/** Update stats for kind 5 event. */
async function handleEvent5(kysely: Kysely<DittoTables>, event: NostrEvent, x: -1, relay: NStore): Promise<void> {
async function handleEvent5(opts: UpdateStatsOpts): Promise<void> {
const { relay, event, x = -1 } = opts;
const id = event.tags.find(([name]) => name === 'e')?.[1];
if (id) {
const [target] = await relay.query([{ ids: [id], authors: [event.pubkey], limit: 1 }]);
if (target) {
await updateStats({ event: target, kysely, relay, x });
await updateStats({ ...opts, event: target, x });
}
}
}
/** Update stats for kind 6 event. */
async function handleEvent6(kysely: Kysely<DittoTables>, event: NostrEvent, x: number): Promise<void> {
async function handleEvent6(opts: UpdateStatsOpts): Promise<void> {
const { kysely, event, x = 1 } = opts;
const id = event.tags.find(([name]) => name === 'e')?.[1];
if (id) {
await updateEventStats(kysely, id, ({ reposts_count }) => ({ reposts_count: Math.max(0, reposts_count + x) }));
}
}
/** Update stats for kind 7 event. */
async function handleEvent7(kysely: Kysely<DittoTables>, event: NostrEvent, x: number): Promise<void> {
async function handleEvent7(opts: UpdateStatsOpts): Promise<void> {
const { kysely, event, x = 1 } = opts;
const id = event.tags.findLast(([name]) => name === 'e')?.[1];
const emoji = event.content;
@ -166,12 +182,15 @@ async function handleEvent7(kysely: Kysely<DittoTables>, event: NostrEvent, x: n
}
/** Update stats for kind 9735 event. */
async function handleEvent9735(kysely: Kysely<DittoTables>, event: NostrEvent): Promise<void> {
async function handleEvent9735(opts: UpdateStatsOpts): Promise<void> {
const { kysely, event } = opts;
// https://github.com/nostr-protocol/nips/blob/master/57.md#appendix-f-validating-zap-receipts
const id = event.tags.find(([name]) => name === 'e')?.[1];
if (!id) return;
const amountSchema = z.coerce.number().int().nonnegative().catch(0);
let amount = 0;
try {
const zapRequest = n.json().pipe(n.event()).parse(event.tags.find(([name]) => name === 'description')?.[1]);

View file

@ -1,11 +1,13 @@
import './deno-env.ts'; // HACK should be removed when `@/config.ts` is removed.
import { DittoConf } from '@ditto/conf';
import { DittoPolyPg } from '@ditto/db';
import '@soapbox/safe-fetch/load';
import { NostrEvent, NostrRelayOK, NPolicy } from '@nostrify/nostrify';
import { ReadOnlyPolicy } from '@nostrify/policies';
import * as Comlink from 'comlink';
import { ReadOnlySigner } from '@/signers/ReadOnlySigner.ts';
import { DittoPgStore } from '@/storages/DittoPgStore.ts';
/** Serializable object the worker can use to set up the state. */
@ -31,9 +33,18 @@ export class CustomPolicy implements NPolicy {
const db = new DittoPolyPg(databaseUrl, { poolSize: 1 });
const conf = new Proxy(new DittoConf(new Map()), {
get(target, prop) {
if (prop === 'signer') {
return new ReadOnlySigner(pubkey);
}
return Reflect.get(target, prop);
},
});
const store = new DittoPgStore({
db,
pubkey,
conf,
timeout: 5_000,
});

View file

@ -72,8 +72,6 @@ export class LibreTranslateTranslator implements DittoTranslator {
const response = await this.fetch(request);
const json = await response.json();
console.log(json);
if (!response.ok) {
const result = LibreTranslateTranslator.errorSchema().safeParse(json);

View file

@ -9,7 +9,7 @@ import { nostrNow } from '../packages/ditto/utils.ts';
const conf = new DittoConf(Deno.env);
const db = new DittoPolyPg(conf.databaseUrl);
const relay = new DittoPgStore({ db, pubkey: await conf.signer.getPublicKey() });
const relay = new DittoPgStore({ db, conf });
const { signer } = conf;

View file

@ -8,7 +8,7 @@ import { nostrNow } from '../packages/ditto/utils.ts';
const conf = new DittoConf(Deno.env);
const db = new DittoPolyPg(conf.databaseUrl);
const relay = new DittoPgStore({ db, pubkey: await conf.signer.getPublicKey() });
const relay = new DittoPgStore({ db, conf });
const [pubkeyOrNpub, role] = Deno.args;
const pubkey = pubkeyOrNpub.startsWith('npub1') ? nip19.decode(pubkeyOrNpub as `npub1${string}`).data : pubkeyOrNpub;

View file

@ -7,7 +7,7 @@ import { DittoPgStore } from '../packages/ditto/storages/DittoPgStore.ts';
const conf = new DittoConf(Deno.env);
const db = new DittoPolyPg(conf.databaseUrl);
const relay = new DittoPgStore({ db, pubkey: await conf.signer.getPublicKey() });
const relay = new DittoPgStore({ db, conf });
interface ExportFilter {
authors?: string[];

View file

@ -9,7 +9,7 @@ import { DittoPgStore } from '../packages/ditto/storages/DittoPgStore.ts';
const conf = new DittoConf(Deno.env);
const db = new DittoPolyPg(conf.databaseUrl);
const relay = new DittoPgStore({ db, pubkey: await conf.signer.getPublicKey() });
const relay = new DittoPgStore({ db, conf });
const sem = new Semaphore(conf.pg.poolSize);
console.warn('Importing events...');

View file

@ -6,7 +6,7 @@ import { PolicyWorker } from '../packages/ditto/workers/policy.ts';
const conf = new DittoConf(Deno.env);
const db = new DittoPolyPg(conf.databaseUrl);
const relay = new DittoPgStore({ db, pubkey: await conf.signer.getPublicKey() });
const relay = new DittoPgStore({ db, conf });
const policyWorker = new PolicyWorker(conf);
let count = 0;

View file

@ -10,7 +10,7 @@ import { DittoRelayStore } from '../packages/ditto/storages/DittoRelayStore.ts';
const conf = new DittoConf(Deno.env);
const db = new DittoPolyPg(conf.databaseUrl);
const pgstore = new DittoPgStore({ db, pubkey: await conf.signer.getPublicKey() });
const pgstore = new DittoPgStore({ db, conf });
const relaystore = new DittoRelayStore({ conf, db, relay: pgstore });
const sem = new Semaphore(5);

View file

@ -6,7 +6,7 @@ import { DittoPgStore } from '../packages/ditto/storages/DittoPgStore.ts';
const conf = new DittoConf(Deno.env);
const db = new DittoPolyPg(conf.databaseUrl);
const relay = new DittoPgStore({ db, pubkey: await conf.signer.getPublicKey() });
const relay = new DittoPgStore({ db, conf });
for await (const msg of relay.req([{ kinds: [0] }])) {
if (msg[0] === 'EVENT') {

View file

@ -12,7 +12,7 @@ import { DittoPgStore } from '../packages/ditto/storages/DittoPgStore.ts';
const conf = new DittoConf(Deno.env);
const db = new DittoPolyPg(conf.databaseUrl);
const relay = new DittoPgStore({ db, pubkey: await conf.signer.getPublicKey() });
const relay = new DittoPgStore({ db, conf });
interface ImportEventsOpts {
profilesOnly: boolean;

View file

@ -7,7 +7,7 @@ import { DittoPgStore } from '../packages/ditto/storages/DittoPgStore.ts';
const conf = new DittoConf(Deno.env);
const db = new DittoPolyPg(conf.databaseUrl);
const relay = new DittoPgStore({ db, pubkey: await conf.signer.getPublicKey() });
const relay = new DittoPgStore({ db, conf });
function die(code: number, ...args: unknown[]) {
console.error(...args);

View file

@ -7,7 +7,7 @@ import { refreshAuthorStats } from '../packages/ditto/utils/stats.ts';
const conf = new DittoConf(Deno.env);
const db = new DittoPolyPg(conf.databaseUrl);
const relay = new DittoPgStore({ db, pubkey: await conf.signer.getPublicKey() });
const relay = new DittoPgStore({ db, conf });
const { kysely } = db;

View file

@ -13,7 +13,7 @@ import {
const conf = new DittoConf(Deno.env);
const db = new DittoPolyPg(conf.databaseUrl);
const relay = new DittoPgStore({ db, pubkey: await conf.signer.getPublicKey() });
const relay = new DittoPgStore({ db, conf });
const ctx = { conf, db, relay };
const trendSchema = z.enum(['pubkeys', 'zapped_events', 'events', 'hashtags', 'links']);