@atproto/bsky 0.0.15 → 0.0.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +18 -0
- package/dist/api/com/atproto/moderation/util.d.ts +4 -3
- package/dist/cache/read-through.d.ts +30 -0
- package/dist/config.d.ts +18 -0
- package/dist/context.d.ts +21 -6
- package/dist/daemon/config.d.ts +15 -0
- package/dist/daemon/context.d.ts +15 -0
- package/dist/daemon/index.d.ts +23 -0
- package/dist/daemon/logger.d.ts +3 -0
- package/dist/daemon/notifications.d.ts +18 -0
- package/dist/daemon/services.d.ts +11 -0
- package/dist/db/database-schema.d.ts +1 -2
- package/dist/db/index.js +41 -1
- package/dist/db/index.js.map +3 -3
- package/dist/db/migrations/20231003T202833377Z-create-moderation-subject-status.d.ts +3 -0
- package/dist/db/migrations/20231205T000257238Z-remove-did-cache.d.ts +3 -0
- package/dist/db/migrations/index.d.ts +2 -0
- package/dist/db/pagination.d.ts +2 -1
- package/dist/db/{periodic-moderation-action-reversal.d.ts → periodic-moderation-event-reversal.d.ts} +3 -5
- package/dist/db/tables/moderation.d.ts +24 -34
- package/dist/did-cache.d.ts +10 -7
- package/dist/feed-gen/types.d.ts +1 -1
- package/dist/index.d.ts +6 -1
- package/dist/index.js +4370 -2758
- package/dist/index.js.map +3 -3
- package/dist/indexer/context.d.ts +2 -0
- package/dist/indexer/index.d.ts +1 -0
- package/dist/lexicon/index.d.ts +23 -18
- package/dist/lexicon/lexicons.d.ts +561 -412
- package/dist/lexicon/types/app/bsky/feed/defs.d.ts +1 -7
- package/dist/lexicon/types/app/bsky/graph/defs.d.ts +1 -0
- package/dist/lexicon/types/com/atproto/admin/defs.d.ts +114 -48
- package/dist/lexicon/types/com/atproto/admin/{resolveModerationReports.d.ts → deleteAccount.d.ts} +2 -13
- package/dist/lexicon/types/com/atproto/admin/{takeModerationAction.d.ts → emitModerationEvent.d.ts} +5 -6
- package/dist/lexicon/types/com/atproto/admin/{getModerationAction.d.ts → getModerationEvent.d.ts} +1 -1
- package/dist/lexicon/types/com/atproto/admin/{getModerationActions.d.ts → queryModerationEvents.d.ts} +5 -1
- package/dist/lexicon/types/com/atproto/admin/{getModerationReports.d.ts → queryModerationStatuses.d.ts} +12 -6
- package/dist/lexicon/types/com/atproto/admin/sendEmail.d.ts +1 -0
- package/dist/lexicon/types/com/atproto/{admin/getModerationReport.d.ts → temp/importRepo.d.ts} +10 -7
- package/dist/lexicon/types/com/atproto/temp/pushBlob.d.ts +25 -0
- package/dist/lexicon/types/com/atproto/{admin/reverseModerationAction.d.ts → temp/transferAccount.d.ts} +11 -5
- package/dist/logger.d.ts +1 -0
- package/dist/migrate-moderation-data.d.ts +1 -0
- package/dist/redis.d.ts +10 -1
- package/dist/services/actor/index.d.ts +18 -4
- package/dist/services/actor/views.d.ts +6 -8
- package/dist/services/feed/index.d.ts +7 -4
- package/dist/services/feed/util.d.ts +9 -1
- package/dist/services/feed/views.d.ts +11 -21
- package/dist/services/graph/index.d.ts +5 -29
- package/dist/services/graph/types.d.ts +1 -0
- package/dist/services/index.d.ts +3 -7
- package/dist/services/label/index.d.ts +10 -4
- package/dist/services/moderation/index.d.ts +134 -72
- package/dist/services/moderation/pagination.d.ts +36 -0
- package/dist/services/moderation/status.d.ts +13 -0
- package/dist/services/moderation/types.d.ts +35 -0
- package/dist/services/moderation/views.d.ts +18 -14
- package/dist/services/types.d.ts +3 -0
- package/dist/services/util/notification.d.ts +5 -0
- package/dist/services/util/post.d.ts +6 -6
- package/dist/util/debug.d.ts +1 -1
- package/dist/util/retry.d.ts +1 -6
- package/package.json +11 -11
- package/src/api/app/bsky/feed/getActorFeeds.ts +2 -1
- package/src/api/app/bsky/feed/getActorLikes.ts +1 -3
- package/src/api/app/bsky/feed/getAuthorFeed.ts +1 -3
- package/src/api/app/bsky/feed/getFeed.ts +9 -9
- package/src/api/app/bsky/feed/getFeedGenerator.ts +3 -0
- package/src/api/app/bsky/feed/getFeedGenerators.ts +2 -1
- package/src/api/app/bsky/feed/getListFeed.ts +1 -3
- package/src/api/app/bsky/feed/getPostThread.ts +15 -54
- package/src/api/app/bsky/feed/getPosts.ts +21 -18
- package/src/api/app/bsky/feed/getSuggestedFeeds.ts +2 -1
- package/src/api/app/bsky/feed/getTimeline.ts +1 -3
- package/src/api/app/bsky/feed/searchPosts.ts +20 -17
- package/src/api/app/bsky/graph/getList.ts +6 -3
- package/src/api/app/bsky/graph/getListBlocks.ts +3 -2
- package/src/api/app/bsky/graph/getListMutes.ts +2 -1
- package/src/api/app/bsky/graph/getLists.ts +2 -1
- package/src/api/app/bsky/unspecced/getPopularFeedGenerators.ts +3 -1
- package/src/api/blob-resolver.ts +6 -11
- package/src/api/com/atproto/admin/emitModerationEvent.ts +220 -0
- package/src/api/com/atproto/admin/{getModerationActions.ts → getModerationEvent.ts} +5 -11
- package/src/api/com/atproto/admin/getRecord.ts +1 -0
- package/src/api/com/atproto/admin/{getModerationReports.ts → queryModerationEvents.ts} +13 -16
- package/src/api/com/atproto/admin/queryModerationStatuses.ts +55 -0
- package/src/api/com/atproto/moderation/createReport.ts +9 -7
- package/src/api/com/atproto/moderation/util.ts +38 -20
- package/src/api/index.ts +8 -14
- package/src/auth.ts +29 -21
- package/src/auto-moderator/index.ts +26 -19
- package/src/cache/read-through.ts +151 -0
- package/src/config.ts +90 -1
- package/src/context.ts +11 -7
- package/src/daemon/config.ts +60 -0
- package/src/daemon/context.ts +27 -0
- package/src/daemon/index.ts +78 -0
- package/src/daemon/logger.ts +6 -0
- package/src/daemon/notifications.ts +54 -0
- package/src/daemon/services.ts +22 -0
- package/src/db/database-schema.ts +0 -2
- package/src/db/migrations/20231003T202833377Z-create-moderation-subject-status.ts +123 -0
- package/src/db/migrations/20231205T000257238Z-remove-did-cache.ts +14 -0
- package/src/db/migrations/index.ts +2 -0
- package/src/db/pagination.ts +26 -3
- package/src/db/{periodic-moderation-action-reversal.ts → periodic-moderation-event-reversal.ts} +50 -46
- package/src/db/tables/moderation.ts +35 -52
- package/src/did-cache.ts +33 -56
- package/src/feed-gen/bsky-team.ts +1 -1
- package/src/feed-gen/hot-classic.ts +1 -1
- package/src/feed-gen/index.ts +0 -4
- package/src/feed-gen/mutuals.ts +6 -2
- package/src/feed-gen/types.ts +1 -1
- package/src/index.ts +57 -17
- package/src/indexer/context.ts +5 -0
- package/src/indexer/index.ts +10 -7
- package/src/lexicon/index.ts +80 -67
- package/src/lexicon/lexicons.ts +698 -507
- package/src/lexicon/types/app/bsky/feed/defs.ts +1 -18
- package/src/lexicon/types/app/bsky/graph/defs.ts +1 -0
- package/src/lexicon/types/com/atproto/admin/defs.ts +276 -84
- package/src/lexicon/types/com/atproto/admin/{resolveModerationReports.ts → deleteAccount.ts} +2 -13
- package/src/lexicon/types/com/atproto/admin/{takeModerationAction.ts → emitModerationEvent.ts} +13 -11
- package/src/lexicon/types/com/atproto/admin/{getModerationReport.ts → getModerationEvent.ts} +1 -1
- package/src/lexicon/types/com/atproto/admin/{getModerationActions.ts → queryModerationEvents.ts} +8 -1
- package/src/lexicon/types/com/atproto/admin/{getModerationReports.ts → queryModerationStatuses.ts} +21 -14
- package/src/lexicon/types/com/atproto/admin/sendEmail.ts +1 -0
- package/src/lexicon/types/com/atproto/{admin/getModerationAction.ts → temp/importRepo.ts} +11 -7
- package/src/lexicon/types/com/atproto/temp/pushBlob.ts +39 -0
- package/src/lexicon/types/com/atproto/{admin/reverseModerationAction.ts → temp/transferAccount.ts} +18 -5
- package/src/logger.ts +2 -0
- package/src/migrate-moderation-data.ts +414 -0
- package/src/redis.ts +43 -3
- package/src/services/actor/index.ts +55 -7
- package/src/services/actor/views.ts +18 -21
- package/src/services/feed/index.ts +52 -19
- package/src/services/feed/util.ts +47 -19
- package/src/services/feed/views.ts +87 -13
- package/src/services/graph/index.ts +21 -3
- package/src/services/graph/types.ts +1 -0
- package/src/services/index.ts +14 -14
- package/src/services/indexing/index.ts +7 -10
- package/src/services/indexing/plugins/block.ts +2 -3
- package/src/services/indexing/plugins/feed-generator.ts +2 -3
- package/src/services/indexing/plugins/follow.ts +2 -3
- package/src/services/indexing/plugins/like.ts +2 -3
- package/src/services/indexing/plugins/list-block.ts +2 -3
- package/src/services/indexing/plugins/list-item.ts +2 -3
- package/src/services/indexing/plugins/list.ts +2 -3
- package/src/services/indexing/plugins/post.ts +16 -4
- package/src/services/indexing/plugins/repost.ts +2 -3
- package/src/services/indexing/plugins/thread-gate.ts +2 -3
- package/src/services/label/index.ts +68 -25
- package/src/services/moderation/index.ts +380 -395
- package/src/services/moderation/pagination.ts +96 -0
- package/src/services/moderation/status.ts +241 -0
- package/src/services/moderation/types.ts +49 -0
- package/src/services/moderation/views.ts +278 -329
- package/src/services/types.ts +4 -0
- package/src/services/util/notification.ts +70 -0
- package/src/util/debug.ts +2 -2
- package/src/util/retry.ts +1 -44
- package/tests/__snapshots__/feed-generation.test.ts.snap +322 -6
- package/tests/__snapshots__/indexing.test.ts.snap +0 -6
- package/tests/admin/__snapshots__/get-record.test.ts.snap +30 -132
- package/tests/admin/__snapshots__/get-repo.test.ts.snap +14 -60
- package/tests/admin/__snapshots__/moderation-events.test.ts.snap +146 -0
- package/tests/admin/__snapshots__/moderation-statuses.test.ts.snap +64 -0
- package/tests/admin/__snapshots__/moderation.test.ts.snap +0 -125
- package/tests/admin/get-record.test.ts +5 -9
- package/tests/admin/get-repo.test.ts +10 -12
- package/tests/admin/moderation-events.test.ts +221 -0
- package/tests/admin/moderation-statuses.test.ts +145 -0
- package/tests/admin/moderation.test.ts +512 -860
- package/tests/admin/repo-search.test.ts +3 -3
- package/tests/algos/hot-classic.test.ts +1 -2
- package/tests/auth.test.ts +1 -1
- package/tests/auto-moderator/fuzzy-matcher.test.ts +2 -1
- package/tests/auto-moderator/labeler.test.ts +19 -20
- package/tests/auto-moderator/takedowns.test.ts +61 -28
- package/tests/blob-resolver.test.ts +4 -2
- package/tests/daemon.test.ts +191 -0
- package/tests/did-cache.test.ts +20 -5
- package/tests/feed-generation.test.ts +57 -9
- package/tests/handle-invalidation.test.ts +1 -5
- package/tests/indexing.test.ts +20 -13
- package/tests/redis-cache.test.ts +231 -0
- package/tests/seeds/basic.ts +3 -0
- package/tests/subscription/repo.test.ts +4 -7
- package/tests/views/__snapshots__/block-lists.test.ts.snap +3 -9
- package/tests/views/__snapshots__/blocks.test.ts.snap +0 -9
- package/tests/views/__snapshots__/mute-lists.test.ts.snap +5 -5
- package/tests/views/__snapshots__/mutes.test.ts.snap +0 -3
- package/tests/views/__snapshots__/thread.test.ts.snap +0 -30
- package/tests/views/actor-search.test.ts +2 -3
- package/tests/views/author-feed.test.ts +42 -36
- package/tests/views/follows.test.ts +40 -35
- package/tests/views/list-feed.test.ts +17 -9
- package/tests/views/notifications.test.ts +13 -9
- package/tests/views/profile.test.ts +20 -19
- package/tests/views/thread.test.ts +117 -94
- package/tests/views/threadgating.test.ts +89 -19
- package/tests/views/timeline.test.ts +21 -13
- package/dist/api/com/atproto/admin/resolveModerationReports.d.ts +0 -3
- package/dist/api/com/atproto/admin/reverseModerationAction.d.ts +0 -3
- package/dist/api/com/atproto/admin/takeModerationAction.d.ts +0 -3
- package/dist/db/tables/did-cache.d.ts +0 -10
- package/dist/feed-gen/best-of-follows.d.ts +0 -29
- package/dist/feed-gen/whats-hot.d.ts +0 -29
- package/dist/feed-gen/with-friends.d.ts +0 -3
- package/dist/label-cache.d.ts +0 -19
- package/src/api/com/atproto/admin/getModerationAction.ts +0 -44
- package/src/api/com/atproto/admin/getModerationReport.ts +0 -43
- package/src/api/com/atproto/admin/resolveModerationReports.ts +0 -24
- package/src/api/com/atproto/admin/reverseModerationAction.ts +0 -115
- package/src/api/com/atproto/admin/takeModerationAction.ts +0 -156
- package/src/db/tables/did-cache.ts +0 -13
- package/src/feed-gen/best-of-follows.ts +0 -74
- package/src/feed-gen/whats-hot.ts +0 -101
- package/src/feed-gen/with-friends.ts +0 -39
- package/src/label-cache.ts +0 -90
- package/tests/admin/__snapshots__/get-moderation-action.test.ts.snap +0 -172
- package/tests/admin/__snapshots__/get-moderation-actions.test.ts.snap +0 -178
- package/tests/admin/__snapshots__/get-moderation-report.test.ts.snap +0 -177
- package/tests/admin/__snapshots__/get-moderation-reports.test.ts.snap +0 -307
- package/tests/admin/get-moderation-action.test.ts +0 -100
- package/tests/admin/get-moderation-actions.test.ts +0 -164
- package/tests/admin/get-moderation-report.test.ts +0 -100
- package/tests/admin/get-moderation-reports.test.ts +0 -332
- package/tests/algos/whats-hot.test.ts +0 -118
- package/tests/algos/with-friends.test.ts +0 -145
- /package/dist/api/com/atproto/admin/{getModerationAction.d.ts → emitModerationEvent.d.ts} +0 -0
- /package/dist/api/com/atproto/admin/{getModerationActions.d.ts → getModerationEvent.d.ts} +0 -0
- /package/dist/api/com/atproto/admin/{getModerationReport.d.ts → queryModerationEvents.d.ts} +0 -0
- /package/dist/api/com/atproto/admin/{getModerationReports.d.ts → queryModerationStatuses.d.ts} +0 -0
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import assert from 'assert'
|
|
2
|
+
|
|
3
|
+
export interface DaemonConfigValues {
|
|
4
|
+
version: string
|
|
5
|
+
dbPostgresUrl: string
|
|
6
|
+
dbPostgresSchema?: string
|
|
7
|
+
notificationsDaemonFromDid?: string
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export class DaemonConfig {
|
|
11
|
+
constructor(private cfg: DaemonConfigValues) {}
|
|
12
|
+
|
|
13
|
+
static readEnv(overrides?: Partial<DaemonConfigValues>) {
|
|
14
|
+
const version = process.env.BSKY_VERSION || '0.0.0'
|
|
15
|
+
const dbPostgresUrl =
|
|
16
|
+
overrides?.dbPostgresUrl || process.env.DB_PRIMARY_POSTGRES_URL
|
|
17
|
+
const dbPostgresSchema =
|
|
18
|
+
overrides?.dbPostgresSchema || process.env.DB_POSTGRES_SCHEMA
|
|
19
|
+
const notificationsDaemonFromDid =
|
|
20
|
+
overrides?.notificationsDaemonFromDid ||
|
|
21
|
+
process.env.BSKY_NOTIFS_DAEMON_FROM_DID ||
|
|
22
|
+
undefined
|
|
23
|
+
assert(dbPostgresUrl)
|
|
24
|
+
return new DaemonConfig({
|
|
25
|
+
version,
|
|
26
|
+
dbPostgresUrl,
|
|
27
|
+
dbPostgresSchema,
|
|
28
|
+
notificationsDaemonFromDid,
|
|
29
|
+
...stripUndefineds(overrides ?? {}),
|
|
30
|
+
})
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
get version() {
|
|
34
|
+
return this.cfg.version
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
get dbPostgresUrl() {
|
|
38
|
+
return this.cfg.dbPostgresUrl
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
get dbPostgresSchema() {
|
|
42
|
+
return this.cfg.dbPostgresSchema
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
get notificationsDaemonFromDid() {
|
|
46
|
+
return this.cfg.notificationsDaemonFromDid
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function stripUndefineds(
|
|
51
|
+
obj: Record<string, unknown>,
|
|
52
|
+
): Record<string, unknown> {
|
|
53
|
+
const result = {}
|
|
54
|
+
Object.entries(obj).forEach(([key, val]) => {
|
|
55
|
+
if (val !== undefined) {
|
|
56
|
+
result[key] = val
|
|
57
|
+
}
|
|
58
|
+
})
|
|
59
|
+
return result
|
|
60
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { PrimaryDatabase } from '../db'
|
|
2
|
+
import { DaemonConfig } from './config'
|
|
3
|
+
import { Services } from './services'
|
|
4
|
+
|
|
5
|
+
export class DaemonContext {
|
|
6
|
+
constructor(
|
|
7
|
+
private opts: {
|
|
8
|
+
db: PrimaryDatabase
|
|
9
|
+
cfg: DaemonConfig
|
|
10
|
+
services: Services
|
|
11
|
+
},
|
|
12
|
+
) {}
|
|
13
|
+
|
|
14
|
+
get db(): PrimaryDatabase {
|
|
15
|
+
return this.opts.db
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
get cfg(): DaemonConfig {
|
|
19
|
+
return this.opts.cfg
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
get services(): Services {
|
|
23
|
+
return this.opts.services
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export default DaemonContext
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { PrimaryDatabase } from '../db'
|
|
2
|
+
import { dbLogger } from '../logger'
|
|
3
|
+
import { DaemonConfig } from './config'
|
|
4
|
+
import { DaemonContext } from './context'
|
|
5
|
+
import { createServices } from './services'
|
|
6
|
+
import { ImageUriBuilder } from '../image/uri'
|
|
7
|
+
import { NotificationsDaemon } from './notifications'
|
|
8
|
+
import logger from './logger'
|
|
9
|
+
|
|
10
|
+
export { DaemonConfig } from './config'
|
|
11
|
+
export type { DaemonConfigValues } from './config'
|
|
12
|
+
|
|
13
|
+
export class BskyDaemon {
|
|
14
|
+
public ctx: DaemonContext
|
|
15
|
+
public notifications: NotificationsDaemon
|
|
16
|
+
private dbStatsInterval: NodeJS.Timer
|
|
17
|
+
private notifStatsInterval: NodeJS.Timer
|
|
18
|
+
|
|
19
|
+
constructor(opts: {
|
|
20
|
+
ctx: DaemonContext
|
|
21
|
+
notifications: NotificationsDaemon
|
|
22
|
+
}) {
|
|
23
|
+
this.ctx = opts.ctx
|
|
24
|
+
this.notifications = opts.notifications
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
static create(opts: { db: PrimaryDatabase; cfg: DaemonConfig }): BskyDaemon {
|
|
28
|
+
const { db, cfg } = opts
|
|
29
|
+
const imgUriBuilder = new ImageUriBuilder('https://daemon.invalid') // will not be used by daemon
|
|
30
|
+
const services = createServices({
|
|
31
|
+
imgUriBuilder,
|
|
32
|
+
})
|
|
33
|
+
const ctx = new DaemonContext({
|
|
34
|
+
db,
|
|
35
|
+
cfg,
|
|
36
|
+
services,
|
|
37
|
+
})
|
|
38
|
+
const notifications = new NotificationsDaemon(ctx)
|
|
39
|
+
return new BskyDaemon({ ctx, notifications })
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async start() {
|
|
43
|
+
const { db, cfg } = this.ctx
|
|
44
|
+
const pool = db.pool
|
|
45
|
+
this.notifications.run({
|
|
46
|
+
startFromDid: cfg.notificationsDaemonFromDid,
|
|
47
|
+
})
|
|
48
|
+
this.dbStatsInterval = setInterval(() => {
|
|
49
|
+
dbLogger.info(
|
|
50
|
+
{
|
|
51
|
+
idleCount: pool.idleCount,
|
|
52
|
+
totalCount: pool.totalCount,
|
|
53
|
+
waitingCount: pool.waitingCount,
|
|
54
|
+
},
|
|
55
|
+
'db pool stats',
|
|
56
|
+
)
|
|
57
|
+
}, 10000)
|
|
58
|
+
this.notifStatsInterval = setInterval(() => {
|
|
59
|
+
logger.info(
|
|
60
|
+
{
|
|
61
|
+
count: this.notifications.count,
|
|
62
|
+
lastDid: this.notifications.lastDid,
|
|
63
|
+
},
|
|
64
|
+
'notifications daemon stats',
|
|
65
|
+
)
|
|
66
|
+
}, 10000)
|
|
67
|
+
return this
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async destroy(): Promise<void> {
|
|
71
|
+
await this.notifications.destroy()
|
|
72
|
+
await this.ctx.db.close()
|
|
73
|
+
clearInterval(this.dbStatsInterval)
|
|
74
|
+
clearInterval(this.notifStatsInterval)
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export default BskyDaemon
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { tidyNotifications } from '../services/util/notification'
|
|
2
|
+
import DaemonContext from './context'
|
|
3
|
+
import logger from './logger'
|
|
4
|
+
|
|
5
|
+
export class NotificationsDaemon {
|
|
6
|
+
ac = new AbortController()
|
|
7
|
+
running: Promise<void> | undefined
|
|
8
|
+
count = 0
|
|
9
|
+
lastDid: string | null = null
|
|
10
|
+
|
|
11
|
+
constructor(private ctx: DaemonContext) {}
|
|
12
|
+
|
|
13
|
+
run(opts?: RunOptions) {
|
|
14
|
+
if (this.running) return
|
|
15
|
+
this.count = 0
|
|
16
|
+
this.lastDid = null
|
|
17
|
+
this.ac = new AbortController()
|
|
18
|
+
this.running = this.tidyNotifications({
|
|
19
|
+
...opts,
|
|
20
|
+
forever: opts?.forever !== false, // run forever by default
|
|
21
|
+
})
|
|
22
|
+
.catch((err) => {
|
|
23
|
+
// allow this to cause an unhandled rejection, let deployment handle the crash.
|
|
24
|
+
logger.error({ err }, 'notifications daemon crashed')
|
|
25
|
+
throw err
|
|
26
|
+
})
|
|
27
|
+
.finally(() => (this.running = undefined))
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
private async tidyNotifications(opts: RunOptions) {
|
|
31
|
+
const actorService = this.ctx.services.actor(this.ctx.db)
|
|
32
|
+
for await (const { did } of actorService.all(opts)) {
|
|
33
|
+
if (this.ac.signal.aborted) return
|
|
34
|
+
try {
|
|
35
|
+
await tidyNotifications(this.ctx.db, did)
|
|
36
|
+
this.count++
|
|
37
|
+
this.lastDid = did
|
|
38
|
+
} catch (err) {
|
|
39
|
+
logger.warn({ err, did }, 'failed to tidy notifications for actor')
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async destroy() {
|
|
45
|
+
this.ac.abort()
|
|
46
|
+
await this.running
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
type RunOptions = {
|
|
51
|
+
forever?: boolean
|
|
52
|
+
batchSize?: number
|
|
53
|
+
startFromDid?: string
|
|
54
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { PrimaryDatabase } from '../db'
|
|
2
|
+
import { ActorService } from '../services/actor'
|
|
3
|
+
import { ImageUriBuilder } from '../image/uri'
|
|
4
|
+
import { GraphService } from '../services/graph'
|
|
5
|
+
import { LabelService } from '../services/label'
|
|
6
|
+
|
|
7
|
+
export function createServices(resources: {
|
|
8
|
+
imgUriBuilder: ImageUriBuilder
|
|
9
|
+
}): Services {
|
|
10
|
+
const { imgUriBuilder } = resources
|
|
11
|
+
const graph = GraphService.creator(imgUriBuilder)
|
|
12
|
+
const label = LabelService.creator(null)
|
|
13
|
+
return {
|
|
14
|
+
actor: ActorService.creator(imgUriBuilder, graph, label),
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export type Services = {
|
|
19
|
+
actor: FromDbPrimary<ActorService>
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
type FromDbPrimary<T> = (db: PrimaryDatabase) => T
|
|
@@ -24,7 +24,6 @@ import * as actorSync from './tables/actor-sync'
|
|
|
24
24
|
import * as record from './tables/record'
|
|
25
25
|
import * as notification from './tables/notification'
|
|
26
26
|
import * as notificationPushToken from './tables/notification-push-token'
|
|
27
|
-
import * as didCache from './tables/did-cache'
|
|
28
27
|
import * as moderation from './tables/moderation'
|
|
29
28
|
import * as label from './tables/label'
|
|
30
29
|
import * as algo from './tables/algo'
|
|
@@ -57,7 +56,6 @@ export type DatabaseSchemaType = duplicateRecord.PartialDB &
|
|
|
57
56
|
record.PartialDB &
|
|
58
57
|
notification.PartialDB &
|
|
59
58
|
notificationPushToken.PartialDB &
|
|
60
|
-
didCache.PartialDB &
|
|
61
59
|
moderation.PartialDB &
|
|
62
60
|
label.PartialDB &
|
|
63
61
|
algo.PartialDB &
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { Kysely } from 'kysely'
|
|
2
|
+
|
|
3
|
+
export async function up(db: Kysely<unknown>): Promise<void> {
|
|
4
|
+
await db.schema
|
|
5
|
+
.createTable('moderation_event')
|
|
6
|
+
.addColumn('id', 'serial', (col) => col.primaryKey())
|
|
7
|
+
.addColumn('action', 'varchar', (col) => col.notNull())
|
|
8
|
+
.addColumn('subjectType', 'varchar', (col) => col.notNull())
|
|
9
|
+
.addColumn('subjectDid', 'varchar', (col) => col.notNull())
|
|
10
|
+
.addColumn('subjectUri', 'varchar')
|
|
11
|
+
.addColumn('subjectCid', 'varchar')
|
|
12
|
+
.addColumn('comment', 'text')
|
|
13
|
+
.addColumn('meta', 'jsonb')
|
|
14
|
+
.addColumn('createdAt', 'varchar', (col) => col.notNull())
|
|
15
|
+
.addColumn('createdBy', 'varchar', (col) => col.notNull())
|
|
16
|
+
.addColumn('reversedAt', 'varchar')
|
|
17
|
+
.addColumn('reversedBy', 'varchar')
|
|
18
|
+
.addColumn('durationInHours', 'integer')
|
|
19
|
+
.addColumn('expiresAt', 'varchar')
|
|
20
|
+
.addColumn('reversedReason', 'text')
|
|
21
|
+
.addColumn('createLabelVals', 'varchar')
|
|
22
|
+
.addColumn('negateLabelVals', 'varchar')
|
|
23
|
+
.addColumn('legacyRefId', 'integer')
|
|
24
|
+
.execute()
|
|
25
|
+
await db.schema
|
|
26
|
+
.createTable('moderation_subject_status')
|
|
27
|
+
.addColumn('id', 'serial', (col) => col.primaryKey())
|
|
28
|
+
|
|
29
|
+
// Identifiers
|
|
30
|
+
.addColumn('did', 'varchar', (col) => col.notNull())
|
|
31
|
+
// Default to '' so that we can apply unique constraints on did and recordPath columns
|
|
32
|
+
.addColumn('recordPath', 'varchar', (col) => col.notNull().defaultTo(''))
|
|
33
|
+
.addColumn('blobCids', 'jsonb')
|
|
34
|
+
.addColumn('recordCid', 'varchar')
|
|
35
|
+
|
|
36
|
+
// human review team state
|
|
37
|
+
.addColumn('reviewState', 'varchar', (col) => col.notNull())
|
|
38
|
+
.addColumn('comment', 'varchar')
|
|
39
|
+
.addColumn('muteUntil', 'varchar')
|
|
40
|
+
.addColumn('lastReviewedAt', 'varchar')
|
|
41
|
+
.addColumn('lastReviewedBy', 'varchar')
|
|
42
|
+
|
|
43
|
+
// report state
|
|
44
|
+
.addColumn('lastReportedAt', 'varchar')
|
|
45
|
+
|
|
46
|
+
// visibility/intervention state
|
|
47
|
+
.addColumn('takendown', 'boolean', (col) => col.defaultTo(false).notNull())
|
|
48
|
+
.addColumn('suspendUntil', 'varchar')
|
|
49
|
+
|
|
50
|
+
// timestamps
|
|
51
|
+
.addColumn('createdAt', 'varchar', (col) => col.notNull())
|
|
52
|
+
.addColumn('updatedAt', 'varchar', (col) => col.notNull())
|
|
53
|
+
.addUniqueConstraint('moderation_status_unique_idx', ['did', 'recordPath'])
|
|
54
|
+
.execute()
|
|
55
|
+
|
|
56
|
+
await db.schema
|
|
57
|
+
.createIndex('moderation_subject_status_blob_cids_idx')
|
|
58
|
+
.on('moderation_subject_status')
|
|
59
|
+
.using('gin')
|
|
60
|
+
.column('blobCids')
|
|
61
|
+
.execute()
|
|
62
|
+
|
|
63
|
+
// Move foreign keys from moderation_action to moderation_event
|
|
64
|
+
await db.schema
|
|
65
|
+
.alterTable('record')
|
|
66
|
+
.dropConstraint('record_takedown_id_fkey')
|
|
67
|
+
.execute()
|
|
68
|
+
await db.schema
|
|
69
|
+
.alterTable('actor')
|
|
70
|
+
.dropConstraint('actor_takedown_id_fkey')
|
|
71
|
+
.execute()
|
|
72
|
+
await db.schema
|
|
73
|
+
.alterTable('actor')
|
|
74
|
+
.addForeignKeyConstraint(
|
|
75
|
+
'actor_takedown_id_fkey',
|
|
76
|
+
['takedownId'],
|
|
77
|
+
'moderation_event',
|
|
78
|
+
['id'],
|
|
79
|
+
)
|
|
80
|
+
.execute()
|
|
81
|
+
await db.schema
|
|
82
|
+
.alterTable('record')
|
|
83
|
+
.addForeignKeyConstraint(
|
|
84
|
+
'record_takedown_id_fkey',
|
|
85
|
+
['takedownId'],
|
|
86
|
+
'moderation_event',
|
|
87
|
+
['id'],
|
|
88
|
+
)
|
|
89
|
+
.execute()
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export async function down(db: Kysely<unknown>): Promise<void> {
|
|
93
|
+
await db.schema.dropTable('moderation_event').execute()
|
|
94
|
+
await db.schema.dropTable('moderation_subject_status').execute()
|
|
95
|
+
|
|
96
|
+
// Revert foreign key constraints
|
|
97
|
+
await db.schema
|
|
98
|
+
.alterTable('record')
|
|
99
|
+
.dropConstraint('record_takedown_id_fkey')
|
|
100
|
+
.execute()
|
|
101
|
+
await db.schema
|
|
102
|
+
.alterTable('actor')
|
|
103
|
+
.dropConstraint('actor_takedown_id_fkey')
|
|
104
|
+
.execute()
|
|
105
|
+
await db.schema
|
|
106
|
+
.alterTable('actor')
|
|
107
|
+
.addForeignKeyConstraint(
|
|
108
|
+
'actor_takedown_id_fkey',
|
|
109
|
+
['takedownId'],
|
|
110
|
+
'moderation_action',
|
|
111
|
+
['id'],
|
|
112
|
+
)
|
|
113
|
+
.execute()
|
|
114
|
+
await db.schema
|
|
115
|
+
.alterTable('record')
|
|
116
|
+
.addForeignKeyConstraint(
|
|
117
|
+
'record_takedown_id_fkey',
|
|
118
|
+
['takedownId'],
|
|
119
|
+
'moderation_action',
|
|
120
|
+
['id'],
|
|
121
|
+
)
|
|
122
|
+
.execute()
|
|
123
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { Kysely } from 'kysely'
|
|
2
|
+
|
|
3
|
+
export async function up(db: Kysely<unknown>): Promise<void> {
|
|
4
|
+
await db.schema.dropTable('did_cache').execute()
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export async function down(db: Kysely<unknown>): Promise<void> {
|
|
8
|
+
await db.schema
|
|
9
|
+
.createTable('did_cache')
|
|
10
|
+
.addColumn('did', 'varchar', (col) => col.primaryKey())
|
|
11
|
+
.addColumn('doc', 'jsonb', (col) => col.notNull())
|
|
12
|
+
.addColumn('updatedAt', 'bigint', (col) => col.notNull())
|
|
13
|
+
.execute()
|
|
14
|
+
}
|
|
@@ -30,3 +30,5 @@ export * as _20230904T211011773Z from './20230904T211011773Z-block-lists'
|
|
|
30
30
|
export * as _20230906T222220386Z from './20230906T222220386Z-thread-gating'
|
|
31
31
|
export * as _20230920T213858047Z from './20230920T213858047Z-add-tags-to-post'
|
|
32
32
|
export * as _20230929T192920807Z from './20230929T192920807Z-record-cursor-indexes'
|
|
33
|
+
export * as _20231003T202833377Z from './20231003T202833377Z-create-moderation-subject-status'
|
|
34
|
+
export * as _20231205T000257238Z from './20231205T000257238Z-remove-did-cache'
|
package/src/db/pagination.ts
CHANGED
|
@@ -117,13 +117,36 @@ export const paginate = <
|
|
|
117
117
|
direction?: 'asc' | 'desc'
|
|
118
118
|
keyset: K
|
|
119
119
|
tryIndex?: boolean
|
|
120
|
+
// By default, pg does nullsFirst
|
|
121
|
+
nullsLast?: boolean
|
|
120
122
|
},
|
|
121
123
|
): QB => {
|
|
122
|
-
const {
|
|
124
|
+
const {
|
|
125
|
+
limit,
|
|
126
|
+
cursor,
|
|
127
|
+
keyset,
|
|
128
|
+
direction = 'desc',
|
|
129
|
+
tryIndex,
|
|
130
|
+
nullsLast,
|
|
131
|
+
} = opts
|
|
123
132
|
const keysetSql = keyset.getSql(keyset.unpack(cursor), direction, tryIndex)
|
|
124
133
|
return qb
|
|
125
134
|
.if(!!limit, (q) => q.limit(limit as number))
|
|
126
|
-
.
|
|
127
|
-
|
|
135
|
+
.if(!nullsLast, (q) =>
|
|
136
|
+
q.orderBy(keyset.primary, direction).orderBy(keyset.secondary, direction),
|
|
137
|
+
)
|
|
138
|
+
.if(!!nullsLast, (q) =>
|
|
139
|
+
q
|
|
140
|
+
.orderBy(
|
|
141
|
+
direction === 'asc'
|
|
142
|
+
? sql`${keyset.primary} asc nulls last`
|
|
143
|
+
: sql`${keyset.primary} desc nulls last`,
|
|
144
|
+
)
|
|
145
|
+
.orderBy(
|
|
146
|
+
direction === 'asc'
|
|
147
|
+
? sql`${keyset.secondary} asc nulls last`
|
|
148
|
+
: sql`${keyset.secondary} desc nulls last`,
|
|
149
|
+
),
|
|
150
|
+
)
|
|
128
151
|
.if(!!keysetSql, (qb) => (keysetSql ? qb.where(keysetSql) : qb)) as QB
|
|
129
152
|
}
|
package/src/db/{periodic-moderation-action-reversal.ts → periodic-moderation-event-reversal.ts}
RENAMED
|
@@ -2,13 +2,15 @@ import { wait } from '@atproto/common'
|
|
|
2
2
|
import { Leader } from './leader'
|
|
3
3
|
import { dbLogger } from '../logger'
|
|
4
4
|
import AppContext from '../context'
|
|
5
|
+
import { AtUri } from '@atproto/api'
|
|
6
|
+
import { ModerationSubjectStatusRow } from '../services/moderation/types'
|
|
7
|
+
import { CID } from 'multiformats/cid'
|
|
5
8
|
import AtpAgent from '@atproto/api'
|
|
6
|
-
import {
|
|
7
|
-
import { ModerationActionRow } from '../services/moderation'
|
|
9
|
+
import { retryHttp } from '../util/retry'
|
|
8
10
|
|
|
9
11
|
export const MODERATION_ACTION_REVERSAL_ID = 1011
|
|
10
12
|
|
|
11
|
-
export class
|
|
13
|
+
export class PeriodicModerationEventReversal {
|
|
12
14
|
leader = new Leader(
|
|
13
15
|
MODERATION_ACTION_REVERSAL_ID,
|
|
14
16
|
this.appContext.db.getPrimary(),
|
|
@@ -20,48 +22,50 @@ export class PeriodicModerationActionReversal {
|
|
|
20
22
|
this.pushAgent = appContext.moderationPushAgent
|
|
21
23
|
}
|
|
22
24
|
|
|
23
|
-
|
|
24
|
-
async reverseLabels(labelTxn: LabelService, actionRow: ModerationActionRow) {
|
|
25
|
-
let uri: string
|
|
26
|
-
let cid: string | null = null
|
|
27
|
-
|
|
28
|
-
if (actionRow.subjectUri && actionRow.subjectCid) {
|
|
29
|
-
uri = actionRow.subjectUri
|
|
30
|
-
cid = actionRow.subjectCid
|
|
31
|
-
} else {
|
|
32
|
-
uri = actionRow.subjectDid
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
await labelTxn.formatAndCreate(this.appContext.cfg.labelerDid, uri, cid, {
|
|
36
|
-
create: actionRow.negateLabelVals
|
|
37
|
-
? actionRow.negateLabelVals.split(' ')
|
|
38
|
-
: undefined,
|
|
39
|
-
negate: actionRow.createLabelVals
|
|
40
|
-
? actionRow.createLabelVals.split(' ')
|
|
41
|
-
: undefined,
|
|
42
|
-
})
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
async revertAction(actionRow: ModerationActionRow) {
|
|
46
|
-
const reverseAction = {
|
|
47
|
-
id: actionRow.id,
|
|
48
|
-
createdBy: actionRow.createdBy,
|
|
49
|
-
createdAt: new Date(),
|
|
50
|
-
reason: `[SCHEDULED_REVERSAL] Reverting action as originally scheduled`,
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
if (this.pushAgent) {
|
|
54
|
-
await this.pushAgent.com.atproto.admin.reverseModerationAction(
|
|
55
|
-
reverseAction,
|
|
56
|
-
)
|
|
57
|
-
return
|
|
58
|
-
}
|
|
59
|
-
|
|
25
|
+
async revertState(eventRow: ModerationSubjectStatusRow) {
|
|
60
26
|
await this.appContext.db.getPrimary().transaction(async (dbTxn) => {
|
|
61
27
|
const moderationTxn = this.appContext.services.moderation(dbTxn)
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
28
|
+
const originalEvent =
|
|
29
|
+
await moderationTxn.getLastReversibleEventForSubject(eventRow)
|
|
30
|
+
if (originalEvent) {
|
|
31
|
+
const { restored } = await moderationTxn.revertState({
|
|
32
|
+
action: originalEvent.action,
|
|
33
|
+
createdBy: originalEvent.createdBy,
|
|
34
|
+
comment:
|
|
35
|
+
'[SCHEDULED_REVERSAL] Reverting action as originally scheduled',
|
|
36
|
+
subject:
|
|
37
|
+
eventRow.recordPath && eventRow.recordCid
|
|
38
|
+
? {
|
|
39
|
+
uri: AtUri.make(
|
|
40
|
+
eventRow.did,
|
|
41
|
+
...eventRow.recordPath.split('/'),
|
|
42
|
+
),
|
|
43
|
+
cid: CID.parse(eventRow.recordCid),
|
|
44
|
+
}
|
|
45
|
+
: { did: eventRow.did },
|
|
46
|
+
createdAt: new Date(),
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
const { pushAgent } = this
|
|
50
|
+
if (
|
|
51
|
+
originalEvent.action === 'com.atproto.admin.defs#modEventTakedown' &&
|
|
52
|
+
restored?.subjects?.length &&
|
|
53
|
+
pushAgent
|
|
54
|
+
) {
|
|
55
|
+
await Promise.allSettled(
|
|
56
|
+
restored.subjects.map((subject) =>
|
|
57
|
+
retryHttp(() =>
|
|
58
|
+
pushAgent.api.com.atproto.admin.updateSubjectStatus({
|
|
59
|
+
subject,
|
|
60
|
+
takedown: {
|
|
61
|
+
applied: false,
|
|
62
|
+
},
|
|
63
|
+
}),
|
|
64
|
+
),
|
|
65
|
+
),
|
|
66
|
+
)
|
|
67
|
+
}
|
|
68
|
+
}
|
|
65
69
|
})
|
|
66
70
|
}
|
|
67
71
|
|
|
@@ -69,12 +73,12 @@ export class PeriodicModerationActionReversal {
|
|
|
69
73
|
const moderationService = this.appContext.services.moderation(
|
|
70
74
|
this.appContext.db.getPrimary(),
|
|
71
75
|
)
|
|
72
|
-
const
|
|
73
|
-
await moderationService.
|
|
76
|
+
const subjectsDueForReversal =
|
|
77
|
+
await moderationService.getSubjectsDueForReversal()
|
|
74
78
|
|
|
75
79
|
// We shouldn't have too many actions due for reversal at any given time, so running in parallel is probably fine
|
|
76
80
|
// Internally, each reversal runs within its own transaction
|
|
77
|
-
await Promise.all(
|
|
81
|
+
await Promise.all(subjectsDueForReversal.map(this.revertState.bind(this)))
|
|
78
82
|
}
|
|
79
83
|
|
|
80
84
|
async run() {
|