Merge remote-tracking branch 'origin/main' into use-postgres-js

This commit is contained in:
Alex Gleason 2024-07-15 21:35:29 -05:00
commit e208d7ef56
No known key found for this signature in database
GPG key ID: 7211D1F99744FBB7
11 changed files with 43 additions and 13 deletions

View file

@ -1,4 +1,4 @@
image: denoland/deno:1.45.0 image: denoland/deno:1.45.2
default: default:
interruptible: true interruptible: true

View file

@ -1 +1 @@
deno 1.45.0 deno 1.45.2

View file

@ -7,9 +7,23 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased] ## [Unreleased]
## [1.1.0] - 2024-07-15
### Added
- Prometheus support (`/metrics` endpoint).
- Sort zaps by amount; add pagination.
### Fixed
- Added IP rate-limiting of HTTP requests and WebSocket messages.
- Added database query timeouts.
- Fixed nos2x compatibility.
## [1.0.0] - 2024-06-14 ## [1.0.0] - 2024-06-14
- Initial release - Initial release
[unreleased]: https://gitlab.com/soapbox-pub/ditto/-/compare/v1.0.0...HEAD [unreleased]: https://gitlab.com/soapbox-pub/ditto/-/compare/v1.1.0...HEAD
[1.1.0]: https://gitlab.com/soapbox-pub/ditto/-/compare/v1.0.0...v1.1.0
[1.0.0]: https://gitlab.com/soapbox-pub/ditto/-/tags/v1.0.0 [1.0.0]: https://gitlab.com/soapbox-pub/ditto/-/tags/v1.0.0

View file

@ -1,6 +1,6 @@
{ {
"$schema": "https://deno.land/x/deno@v1.41.0/cli/schemas/config-file.v1.json", "$schema": "https://deno.land/x/deno@v1.41.0/cli/schemas/config-file.v1.json",
"version": "1.0.0", "version": "1.1.0",
"tasks": { "tasks": {
"start": "deno run -A src/server.ts", "start": "deno run -A src/server.ts",
"dev": "deno run -A --watch src/server.ts", "dev": "deno run -A --watch src/server.ts",

View file

@ -98,6 +98,21 @@ class Conf {
} }
return undefined; return undefined;
}, },
/** Database query timeout configurations. */
timeouts: {
/** Default query timeout when another setting isn't more specific. */
get default(): number {
return Number(Deno.env.get('DB_TIMEOUT_DEFAULT') || 5_000);
},
/** Timeout used for queries made through the Nostr relay. */
get relay(): number {
return Number(Deno.env.get('DB_TIMEOUT_RELAY') || 1_000);
},
/** Timeout used for timelines such as home, notifications, hashtag, etc. */
get timelines(): number {
return Number(Deno.env.get('DB_TIMEOUT_TIMELINES') || 15_000);
},
},
}; };
/** Character limit to enforce for posts made through Mastodon API. */ /** Character limit to enforce for posts made through Mastodon API. */
static get postCharLimit(): number { static get postCharLimit(): number {

View file

@ -209,7 +209,7 @@ const accountStatusesController: AppController = async (c) => {
filter['#t'] = [tagged]; filter['#t'] = [tagged];
} }
const opts = { signal, limit, timeout: 10_000 }; const opts = { signal, limit, timeout: Conf.db.timeouts.timelines };
const events = await store.query([filter], opts) const events = await store.query([filter], opts)
.then((events) => hydrateEvents({ events, store, signal })) .then((events) => hydrateEvents({ events, store, signal }))

View file

@ -78,7 +78,7 @@ async function renderNotifications(
const store = c.get('store'); const store = c.get('store');
const pubkey = await c.get('signer')?.getPublicKey()!; const pubkey = await c.get('signer')?.getPublicKey()!;
const { signal } = c.req.raw; const { signal } = c.req.raw;
const opts = { signal, limit: params.limit, timeout: 15_000 }; const opts = { signal, limit: params.limit, timeout: Conf.db.timeouts.timelines };
const events = await store const events = await store
.query(filters, opts) .query(filters, opts)

View file

@ -60,7 +60,7 @@ const suggestedTimelineController: AppController = async (c) => {
async function renderStatuses(c: AppContext, filters: NostrFilter[]) { async function renderStatuses(c: AppContext, filters: NostrFilter[]) {
const { signal } = c.req.raw; const { signal } = c.req.raw;
const store = c.get('store'); const store = c.get('store');
const opts = { signal, timeout: 10_000 }; const opts = { signal, timeout: Conf.db.timeouts.timelines };
const events = await store const events = await store
.query(filters, opts) .query(filters, opts)

View file

@ -10,6 +10,7 @@ import {
} from '@nostrify/nostrify'; } from '@nostrify/nostrify';
import { AppController } from '@/app.ts'; import { AppController } from '@/app.ts';
import { Conf } from '@/config.ts';
import { relayInfoController } from '@/controllers/nostr/relay-info.ts'; import { relayInfoController } from '@/controllers/nostr/relay-info.ts';
import { relayConnectionsGauge, relayEventCounter, relayMessageCounter } from '@/metrics.ts'; import { relayConnectionsGauge, relayEventCounter, relayMessageCounter } from '@/metrics.ts';
import * as pipeline from '@/pipeline.ts'; import * as pipeline from '@/pipeline.ts';
@ -95,7 +96,7 @@ function connectStream(socket: WebSocket, ip: string | undefined) {
const pubsub = await Storages.pubsub(); const pubsub = await Storages.pubsub();
try { try {
for (const event of await store.query(filters, { limit: FILTER_LIMIT, timeout: 1000 })) { for (const event of await store.query(filters, { limit: FILTER_LIMIT, timeout: Conf.db.timeouts.relay })) {
send(['EVENT', subId, event]); send(['EVENT', subId, event]);
} }
} catch (e) { } catch (e) {
@ -150,7 +151,7 @@ function connectStream(socket: WebSocket, ip: string | undefined) {
/** Handle COUNT. Return the number of events matching the filters. */ /** Handle COUNT. Return the number of events matching the filters. */
async function handleCount([_, subId, ...filters]: NostrClientCOUNT): Promise<void> { async function handleCount([_, subId, ...filters]: NostrClientCOUNT): Promise<void> {
const store = await Storages.db(); const store = await Storages.db();
const { count } = await store.count(filters, { timeout: 100 }); const { count } = await store.count(filters, { timeout: Conf.db.timeouts.relay });
send(['COUNT', subId, { count, approximate: false }]); send(['COUNT', subId, { count, approximate: false }]);
} }

View file

@ -64,7 +64,7 @@ class EventsDB implements NStore {
await this.deleteEventsAdmin(event); await this.deleteEventsAdmin(event);
try { try {
await this.store.event(event, { ...opts, timeout: opts.timeout ?? 1000 }); await this.store.event(event, { ...opts, timeout: opts.timeout ?? Conf.db.timeouts.default });
} 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');
@ -164,7 +164,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 ?? 1000 }); return this.store.query(filters, { ...opts, timeout: opts.timeout ?? Conf.db.timeouts.default });
} }
/** Delete events based on filters from the database. */ /** Delete events based on filters from the database. */
@ -172,7 +172,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 ?? 3000 }); return this.store.remove(filters, { ...opts, timeout: opts.timeout ?? Conf.db.timeouts.default });
} }
/** Get number of events that would be returned by filters. */ /** Get number of events that would be returned by filters. */
@ -185,7 +185,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 ?? 500 }); return this.store.count(filters, { ...opts, timeout: opts.timeout ?? Conf.db.timeouts.default });
} }
/** Return only the tags that should be indexed. */ /** Return only the tags that should be indexed. */