fix: save event from mirrors (#376)

* fix: save event from mirrors

* docs: add config for mirroring event limits

* chore: add skip admission check (thanks YEGHRO)

* 2.0.1

* chore: bump some deps
This commit is contained in:
Ricardo Arturo Cabral Mejía 2024-10-22 09:09:34 -04:00 committed by GitHub
parent c2345efba0
commit 64972290d6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 259 additions and 47 deletions

5
.gitignore vendored
View File

@ -35,4 +35,7 @@ dist
*.env *.env
# Nostr data folder # Nostr data folder
.nostr .nostr
# Docker Compose overrides
docker-compose.overrides.yml

View File

@ -87,6 +87,8 @@ Running `nostream` for the first time creates the settings file in `<project_roo
| paymentProcessors.lnurl.invoiceURL | [LUD-06 Pay Request](https://github.com/lnurl/luds/blob/luds/06.md) provider URL. (e.g. https://getalby.com/lnurlp/your-username) | | paymentProcessors.lnurl.invoiceURL | [LUD-06 Pay Request](https://github.com/lnurl/luds/blob/luds/06.md) provider URL. (e.g. https://getalby.com/lnurlp/your-username) |
| mirroring.static[].address | Address of mirrored relay. (e.g. ws://100.100.100.100:8008) | | mirroring.static[].address | Address of mirrored relay. (e.g. ws://100.100.100.100:8008) |
| mirroring.static[].filters | Subscription filters used to mirror. | | mirroring.static[].filters | Subscription filters used to mirror. |
| mirroring.static[].limits.event | Event limit overrides for this mirror. See configurations under limits.event. |
| mirroring.static[].skipAdmissionCheck | Disable the admission fee check for events coming from this mirror. |
| mirroring.static[].secret | Secret to pass to relays. Nostream relays only. Optional. | | mirroring.static[].secret | Secret to pass to relays. Nostream relays only. Optional. |
| workers.count | Number of workers to spin up to handle incoming connections. | | workers.count | Number of workers to spin up to handle incoming connections. |
| | Spin workers as many CPUs are available when set to zero. Defaults to zero. | | | Spin workers as many CPUs are available when set to zero. Defaults to zero. |

View File

@ -1,4 +1,4 @@
FROM node:18-alpine3.16 as build FROM node:18-alpine3.16 AS build
WORKDIR /build WORKDIR /build

View File

@ -75,6 +75,7 @@ services:
restart: on-failure restart: on-failure
networks: networks:
default: default:
nostream-db: nostream-db:
image: postgres image: postgres
container_name: nostream-db container_name: nostream-db
@ -96,6 +97,7 @@ services:
timeout: 5s timeout: 5s
retries: 5 retries: 5
start_period: 360s start_period: 360s
nostream-cache: nostream-cache:
image: redis:7.0.5-alpine3.16 image: redis:7.0.5-alpine3.16
container_name: nostream-cache container_name: nostream-cache
@ -110,6 +112,7 @@ services:
interval: 1s interval: 1s
timeout: 5s timeout: 5s
retries: 5 retries: 5
nostream-migrate: nostream-migrate:
image: node:18-alpine3.16 image: node:18-alpine3.16
container_name: nostream-migrate container_name: nostream-migrate
@ -131,7 +134,6 @@ services:
condition: service_healthy condition: service_healthy
networks: networks:
default: default:
ipv4_address: 10.10.10.254
networks: networks:
default: default:

62
package-lock.json generated
View File

@ -1,16 +1,16 @@
{ {
"name": "nostream", "name": "nostream",
"version": "2.0.0", "version": "2.0.1",
"lockfileVersion": 2, "lockfileVersion": 2,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "nostream", "name": "nostream",
"version": "2.0.0", "version": "2.0.1",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@noble/secp256k1": "1.7.1", "@noble/secp256k1": "1.7.1",
"axios": "1.6.5", "axios": "^1.7.7",
"bech32": "2.0.0", "bech32": "2.0.0",
"body-parser": "1.20.1", "body-parser": "1.20.1",
"debug": "4.3.4", "debug": "4.3.4",
@ -27,7 +27,7 @@
"redis": "4.5.1", "redis": "4.5.1",
"rxjs": "7.8.0", "rxjs": "7.8.0",
"tor-control-ts": "^1.0.0", "tor-control-ts": "^1.0.0",
"ws": "8.12.0" "ws": "^8.18.0"
}, },
"devDependencies": { "devDependencies": {
"@commitlint/cli": "17.2.0", "@commitlint/cli": "17.2.0",
@ -50,7 +50,7 @@
"@types/ramda": "^0.28.13", "@types/ramda": "^0.28.13",
"@types/sinon": "^10.0.11", "@types/sinon": "^10.0.11",
"@types/sinon-chai": "^3.2.8", "@types/sinon-chai": "^3.2.8",
"@types/ws": "^8.5.3", "@types/ws": "^8.5.12",
"@typescript-eslint/eslint-plugin": "^5.19.0", "@typescript-eslint/eslint-plugin": "^5.19.0",
"@typescript-eslint/parser": "^5.19.0", "@typescript-eslint/parser": "^5.19.0",
"chai": "^4.3.6", "chai": "^4.3.6",
@ -2385,9 +2385,9 @@
"dev": true "dev": true
}, },
"node_modules/@types/ws": { "node_modules/@types/ws": {
"version": "8.5.3", "version": "8.5.12",
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.3.tgz", "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.12.tgz",
"integrity": "sha512-6YOoWjruKj1uLf3INHH7D3qTXwFfEsg1kf3c0uDdSBJwfa/llkwIjrAGV7j7mVgGNbzTQ3HiHKKDXl6bJPD97w==", "integrity": "sha512-3tPRkv1EtkDpzlgyKyI8pGsGZAGPEaXeu0DOj5DI25Ja91bdAYddYHbADRYVrZMRbfW+1l5YwXVDKohDJNQxkQ==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@types/node": "*" "@types/node": "*"
@ -2909,11 +2909,11 @@
} }
}, },
"node_modules/axios": { "node_modules/axios": {
"version": "1.6.5", "version": "1.7.7",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.6.5.tgz", "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.7.tgz",
"integrity": "sha512-Ii012v05KEVuUoFWmMW/UQv9aRIc3ZwkWDcM+h5Il8izZCtRVpDUfwpoFf7eOtajT3QiGR4yDUx7lPqHJULgbg==", "integrity": "sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q==",
"dependencies": { "dependencies": {
"follow-redirects": "^1.15.4", "follow-redirects": "^1.15.6",
"form-data": "^4.0.0", "form-data": "^4.0.0",
"proxy-from-env": "^1.1.0" "proxy-from-env": "^1.1.0"
} }
@ -5095,9 +5095,9 @@
"dev": true "dev": true
}, },
"node_modules/follow-redirects": { "node_modules/follow-redirects": {
"version": "1.15.4", "version": "1.15.9",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.4.tgz", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz",
"integrity": "sha512-Cr4D/5wlrb0z9dgERpUL3LrmPKVDsETIJhaCMeDfuFYcqa5bldGV6wBsAN6X/vxlXQtFBMrXdXxdL8CbDTGniw==", "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==",
"funding": [ "funding": [
{ {
"type": "individual", "type": "individual",
@ -13444,9 +13444,9 @@
} }
}, },
"node_modules/ws": { "node_modules/ws": {
"version": "8.12.0", "version": "8.18.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.12.0.tgz", "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz",
"integrity": "sha512-kU62emKIdKVeEIOIKVegvqpXMSTAMLJozpHZaJNDYqBjzlSYXQGviYwN1osDLJ9av68qHd4a2oSjd7yD4pacig==", "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==",
"engines": { "engines": {
"node": ">=10.0.0" "node": ">=10.0.0"
}, },
@ -15477,9 +15477,9 @@
"dev": true "dev": true
}, },
"@types/ws": { "@types/ws": {
"version": "8.5.3", "version": "8.5.12",
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.3.tgz", "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.12.tgz",
"integrity": "sha512-6YOoWjruKj1uLf3INHH7D3qTXwFfEsg1kf3c0uDdSBJwfa/llkwIjrAGV7j7mVgGNbzTQ3HiHKKDXl6bJPD97w==", "integrity": "sha512-3tPRkv1EtkDpzlgyKyI8pGsGZAGPEaXeu0DOj5DI25Ja91bdAYddYHbADRYVrZMRbfW+1l5YwXVDKohDJNQxkQ==",
"dev": true, "dev": true,
"requires": { "requires": {
"@types/node": "*" "@types/node": "*"
@ -15841,11 +15841,11 @@
"dev": true "dev": true
}, },
"axios": { "axios": {
"version": "1.6.5", "version": "1.7.7",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.6.5.tgz", "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.7.tgz",
"integrity": "sha512-Ii012v05KEVuUoFWmMW/UQv9aRIc3ZwkWDcM+h5Il8izZCtRVpDUfwpoFf7eOtajT3QiGR4yDUx7lPqHJULgbg==", "integrity": "sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q==",
"requires": { "requires": {
"follow-redirects": "^1.15.4", "follow-redirects": "^1.15.6",
"form-data": "^4.0.0", "form-data": "^4.0.0",
"proxy-from-env": "^1.1.0" "proxy-from-env": "^1.1.0"
} }
@ -17529,9 +17529,9 @@
"dev": true "dev": true
}, },
"follow-redirects": { "follow-redirects": {
"version": "1.15.4", "version": "1.15.9",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.4.tgz", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz",
"integrity": "sha512-Cr4D/5wlrb0z9dgERpUL3LrmPKVDsETIJhaCMeDfuFYcqa5bldGV6wBsAN6X/vxlXQtFBMrXdXxdL8CbDTGniw==" "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ=="
}, },
"foreground-child": { "foreground-child": {
"version": "2.0.0", "version": "2.0.0",
@ -23673,9 +23673,9 @@
} }
}, },
"ws": { "ws": {
"version": "8.12.0", "version": "8.18.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.12.0.tgz", "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz",
"integrity": "sha512-kU62emKIdKVeEIOIKVegvqpXMSTAMLJozpHZaJNDYqBjzlSYXQGviYwN1osDLJ9av68qHd4a2oSjd7yD4pacig==", "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==",
"requires": {} "requires": {}
}, },
"xmlbuilder": { "xmlbuilder": {

View File

@ -1,6 +1,6 @@
{ {
"name": "nostream", "name": "nostream",
"version": "2.0.0", "version": "2.0.1",
"description": "A Nostr relay written in Typescript.", "description": "A Nostr relay written in Typescript.",
"supportedNips": [ "supportedNips": [
1, 1,
@ -90,7 +90,7 @@
"@types/ramda": "^0.28.13", "@types/ramda": "^0.28.13",
"@types/sinon": "^10.0.11", "@types/sinon": "^10.0.11",
"@types/sinon-chai": "^3.2.8", "@types/sinon-chai": "^3.2.8",
"@types/ws": "^8.5.3", "@types/ws": "^8.5.12",
"@typescript-eslint/eslint-plugin": "^5.19.0", "@typescript-eslint/eslint-plugin": "^5.19.0",
"@typescript-eslint/parser": "^5.19.0", "@typescript-eslint/parser": "^5.19.0",
"chai": "^4.3.6", "chai": "^4.3.6",
@ -115,7 +115,7 @@
}, },
"dependencies": { "dependencies": {
"@noble/secp256k1": "1.7.1", "@noble/secp256k1": "1.7.1",
"axios": "1.6.5", "axios": "^1.7.7",
"bech32": "2.0.0", "bech32": "2.0.0",
"body-parser": "1.20.1", "body-parser": "1.20.1",
"debug": "4.3.4", "debug": "4.3.4",
@ -132,7 +132,7 @@
"redis": "4.5.1", "redis": "4.5.1",
"rxjs": "7.8.0", "rxjs": "7.8.0",
"tor-control-ts": "^1.0.0", "tor-control-ts": "^1.0.0",
"ws": "8.12.0" "ws": "^8.18.0"
}, },
"config": { "config": {
"commitizen": { "commitizen": {

View File

@ -203,6 +203,10 @@ export interface Mirror {
address: string address: string
filters?: SubscriptionFilter[] filters?: SubscriptionFilter[]
secret?: Secret secret?: Secret
limits?: {
event?: EventLimits
}
skipAdmissionCheck?: boolean
} }
export interface Mirroring { export interface Mirroring {

View File

@ -1,12 +1,15 @@
import { anyPass, map, path } from 'ramda' import { anyPass, map, mergeDeepRight, path } from 'ramda'
import { RawData, WebSocket } from 'ws' import { RawData, WebSocket } from 'ws'
import cluster from 'cluster' import cluster from 'cluster'
import { randomUUID } from 'crypto' import { randomUUID } from 'crypto'
import { createRelayedEventMessage, createSubscriptionMessage } from '../utils/messages' import { createRelayedEventMessage, createSubscriptionMessage } from '../utils/messages'
import { isEventIdValid, isEventMatchingFilter, isEventSignatureValid } from '../utils/event' import { EventLimits, FeeSchedule, Mirror, Settings } from '../@types/settings'
import { Mirror, Settings } from '../@types/settings' import { getEventExpiration, getEventProofOfWork, getPubkeyProofOfWork, getPublicKey, getRelayPrivateKey, isEventIdValid, isEventKindOrRangeMatch, isEventMatchingFilter, isEventSignatureValid, isExpiredEvent } from '../utils/event'
import { IEventRepository, IUserRepository } from '../@types/repositories'
import { createLogger } from '../factories/logger-factory' import { createLogger } from '../factories/logger-factory'
import { Event } from '../@types/event'
import { EventExpirationTimeMetadataKey } from '../constants/base'
import { IRunnable } from '../@types/base' import { IRunnable } from '../@types/base'
import { OutgoingEventMessage } from '../@types/messages' import { OutgoingEventMessage } from '../@types/messages'
import { RelayedEvent } from '../@types/event' import { RelayedEvent } from '../@types/event'
@ -19,6 +22,8 @@ export class StaticMirroringWorker implements IRunnable {
private config: Mirror private config: Mirror
public constructor( public constructor(
private readonly eventRepository: IEventRepository,
private readonly userRepository: IUserRepository,
private readonly process: NodeJS.Process, private readonly process: NodeJS.Process,
private readonly settings: () => Settings, private readonly settings: () => Settings,
) { ) {
@ -57,7 +62,7 @@ export class StaticMirroringWorker implements IRunnable {
this.send(JSON.stringify(createSubscriptionMessage(subscriptionId, filters))) this.send(JSON.stringify(createSubscriptionMessage(subscriptionId, filters)))
} }
}) })
.on('message', async function (raw: RawData) { .on('message', async (raw: RawData) => {
try { try {
const message = JSON.parse(raw.toString('utf8')) as OutgoingEventMessage const message = JSON.parse(raw.toString('utf8')) as OutgoingEventMessage
@ -70,7 +75,7 @@ export class StaticMirroringWorker implements IRunnable {
return return
} }
const event = message[2] let event = message[2]
if (!anyPass(map(isEventMatchingFilter, config.filters))(event)) { if (!anyPass(map(isEventMatchingFilter, config.filters))(event)) {
return return
@ -80,10 +85,34 @@ export class StaticMirroringWorker implements IRunnable {
return return
} }
if (isExpiredEvent(event)) {
return
}
const eventExpiration = getEventExpiration(event)
if (eventExpiration) {
event = {
...event,
[EventExpirationTimeMetadataKey]: eventExpiration,
} as any
}
if (!this.canAcceptEvent(event)) {
return
}
if (!await this.isUserAdmitted(event)) {
return
}
since = Math.floor(Date.now() / 1000) - 30 since = Math.floor(Date.now() / 1000) - 30
if (cluster.isWorker && typeof process.send === 'function') { debug('%s >> local: %s', config.address, event.id)
debug('%s >> local: %s', config.address, event.id)
const inserted = await this.eventRepository.create(event)
if (inserted && cluster.isWorker && typeof process.send === 'function') {
process.send({ process.send({
eventName: WebSocketServerAdapterEvent.Broadcast, eventName: WebSocketServerAdapterEvent.Broadcast,
event, event,
@ -110,6 +139,165 @@ export class StaticMirroringWorker implements IRunnable {
this.client = createMirror(this.config) this.client = createMirror(this.config)
} }
private getRelayPublicKey(): string {
const relayPrivkey = getRelayPrivateKey(this.settings().info.relay_url)
return getPublicKey(relayPrivkey)
}
private canAcceptEvent(event: Event): boolean {
if (this.getRelayPublicKey() === event.pubkey) {
debug(`event ${event.id} not accepted: pubkey is relay pubkey`)
return false
}
const now = Math.floor(Date.now() / 1000)
const eventLimits = this.settings().limits?.event ?? {}
const eventLimitOverrides = this.config.limits.event ?? {}
const limits = mergeDeepRight(eventLimits, eventLimitOverrides) as EventLimits
if (Array.isArray(limits.content)) {
for (const limit of limits.content) {
if (
typeof limit.maxLength !== 'undefined'
&& limit.maxLength > 0
&& event.content.length > limit.maxLength
&& (
!Array.isArray(limit.kinds)
|| limit.kinds.some(isEventKindOrRangeMatch(event))
)
) {
debug(`event ${event.id} not accepted: content is longer than ${limit.maxLength} bytes`)
return false
}
}
} else if (
typeof limits.content?.maxLength !== 'undefined'
&& limits.content?.maxLength > 0
&& event.content.length > limits.content.maxLength
&& (
!Array.isArray(limits.content.kinds)
|| limits.content.kinds.some(isEventKindOrRangeMatch(event))
)
) {
debug(`event ${event.id} not accepted: content is longer than ${limits.content.maxLength} bytes`)
return false
}
if (
typeof limits.createdAt?.maxPositiveDelta !== 'undefined'
&& limits.createdAt.maxPositiveDelta > 0
&& event.created_at > now + limits.createdAt.maxPositiveDelta) {
debug(`event ${event.id} not accepted: created_at is more than ${limits.createdAt.maxPositiveDelta} seconds in the future`)
return false
}
if (
typeof limits.createdAt?.maxNegativeDelta !== 'undefined'
&& limits.createdAt.maxNegativeDelta > 0
&& event.created_at < now - limits.createdAt.maxNegativeDelta) {
debug(`event ${event.id} not accepted: created_at is more than ${limits.createdAt.maxNegativeDelta} seconds in the past`)
return false
}
if (
typeof limits.eventId?.minLeadingZeroBits !== 'undefined'
&& limits.eventId.minLeadingZeroBits > 0
) {
const pow = getEventProofOfWork(event.id)
if (pow < limits.eventId.minLeadingZeroBits) {
debug(`event ${event.id} not accepted: pow difficulty ${pow}<${limits.eventId.minLeadingZeroBits}`)
return false
}
}
if (
typeof limits.pubkey?.minLeadingZeroBits !== 'undefined'
&& limits.pubkey.minLeadingZeroBits > 0
) {
const pow = getPubkeyProofOfWork(event.pubkey)
if (pow < limits.pubkey.minLeadingZeroBits) {
debug(`event ${event.id} not accepted: pow pubkey difficulty ${pow}<${limits.pubkey.minLeadingZeroBits}`)
return false
}
}
if (
typeof limits.pubkey?.whitelist !== 'undefined'
&& limits.pubkey.whitelist.length > 0
&& !limits.pubkey.whitelist.some((prefix) => event.pubkey.startsWith(prefix))
) {
debug(`event ${event.id} not accepted: pubkey not allowed: ${event.pubkey}`)
return false
}
if (
typeof limits.pubkey?.blacklist !== 'undefined'
&& limits.pubkey.blacklist.length > 0
&& limits.pubkey.blacklist.some((prefix) => event.pubkey.startsWith(prefix))
) {
debug(`event ${event.id} not accepted: pubkey not allowed: ${event.pubkey}`)
return false
}
if (
typeof limits.kind?.whitelist !== 'undefined'
&& limits.kind.whitelist.length > 0
&& !limits.kind.whitelist.some(isEventKindOrRangeMatch(event))) {
debug(`blocked: event kind ${event.kind} not allowed`)
return false
}
if (
typeof limits.kind?.blacklist !== 'undefined'
&& limits.kind.blacklist.length > 0
&& limits.kind.blacklist.some(isEventKindOrRangeMatch(event))) {
debug(`blocked: event kind ${event.kind} not allowed`)
return false
}
return true
}
protected async isUserAdmitted(event: Event): Promise<boolean> {
const currentSettings = this.settings()
if (this.config.skipAdmissionCheck === true) {
return true
}
if (currentSettings.payments?.enabled !== true) {
return true
}
const isApplicableFee = (feeSchedule: FeeSchedule) =>
feeSchedule.enabled
&& !feeSchedule.whitelists?.pubkeys?.some((prefix) => event.pubkey.startsWith(prefix))
&& !feeSchedule.whitelists?.event_kinds?.some(isEventKindOrRangeMatch(event))
const feeSchedules = currentSettings.payments?.feeSchedules?.admission?.filter(isApplicableFee)
if (!Array.isArray(feeSchedules) || !feeSchedules.length) {
return true
}
const user = await this.userRepository.findByPubkey(event.pubkey)
if (user?.isAdmitted !== true) {
debug(`user not admitted: ${event.pubkey}`)
return false
}
const minBalance = currentSettings.limits?.event?.pubkey?.minBalance
if (minBalance && user.balance < minBalance) {
debug(`user not admitted: user balance ${user.balance} < ${minBalance}`)
return false
}
return true
}
private onMessage(message: { eventName: string, event: unknown, source: string }): void { private onMessage(message: { eventName: string, event: unknown, source: string }): void {
if ( if (
message.eventName !== WebSocketServerAdapterEvent.Broadcast message.eventName !== WebSocketServerAdapterEvent.Broadcast

View File

@ -1,6 +1,19 @@
import { getMasterDbClient, getReadReplicaDbClient } from '../database/client'
import { createSettings } from './settings-factory' import { createSettings } from './settings-factory'
import { EventRepository } from '../repositories/event-repository'
import { StaticMirroringWorker } from '../app/static-mirroring-worker' import { StaticMirroringWorker } from '../app/static-mirroring-worker'
import { UserRepository } from '../repositories/user-repository'
export const staticMirroringWorkerFactory = () => { export const staticMirroringWorkerFactory = () => {
return new StaticMirroringWorker(process, createSettings) const dbClient = getMasterDbClient()
const readReplicaDbClient = getReadReplicaDbClient()
const eventRepository = new EventRepository(dbClient, readReplicaDbClient)
const userRepository = new UserRepository(dbClient)
return new StaticMirroringWorker(
eventRepository,
userRepository,
process,
createSettings,
)
} }

View File

@ -125,7 +125,7 @@ export class SettingsStatic {
const settingsFilePath = join(basePath, `settings.${fileType}`) const settingsFilePath = join(basePath, `settings.${fileType}`)
const reload = () => { const reload = () => {
console.log('reloading settings') debug('reloading settings')
SettingsStatic._settings = undefined SettingsStatic._settings = undefined
SettingsStatic.createSettings() SettingsStatic.createSettings()
} }