@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
|
@@ -2,24 +2,28 @@
|
|
|
2
2
|
* GENERATED CODE - DO NOT MODIFY
|
|
3
3
|
*/
|
|
4
4
|
import express from 'express'
|
|
5
|
+
import stream from 'stream'
|
|
5
6
|
import { ValidationResult, BlobRef } from '@atproto/lexicon'
|
|
6
7
|
import { lexicons } from '../../../../lexicons'
|
|
7
8
|
import { isObj, hasProp } from '../../../../util'
|
|
8
9
|
import { CID } from 'multiformats/cid'
|
|
9
10
|
import { HandlerAuth } from '@atproto/xrpc-server'
|
|
10
|
-
import * as ComAtprotoAdminDefs from './defs'
|
|
11
11
|
|
|
12
12
|
export interface QueryParams {
|
|
13
|
-
|
|
13
|
+
/** The DID of the repo. */
|
|
14
|
+
did: string
|
|
14
15
|
}
|
|
15
16
|
|
|
16
|
-
export type InputSchema =
|
|
17
|
-
|
|
18
|
-
export
|
|
17
|
+
export type InputSchema = string | Uint8Array
|
|
18
|
+
|
|
19
|
+
export interface HandlerInput {
|
|
20
|
+
encoding: 'application/vnd.ipld.car'
|
|
21
|
+
body: stream.Readable
|
|
22
|
+
}
|
|
19
23
|
|
|
20
24
|
export interface HandlerSuccess {
|
|
21
|
-
encoding: '
|
|
22
|
-
body:
|
|
25
|
+
encoding: 'text/plain'
|
|
26
|
+
body: Uint8Array | stream.Readable
|
|
23
27
|
headers?: { [key: string]: string }
|
|
24
28
|
}
|
|
25
29
|
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GENERATED CODE - DO NOT MODIFY
|
|
3
|
+
*/
|
|
4
|
+
import express from 'express'
|
|
5
|
+
import stream from 'stream'
|
|
6
|
+
import { ValidationResult, BlobRef } from '@atproto/lexicon'
|
|
7
|
+
import { lexicons } from '../../../../lexicons'
|
|
8
|
+
import { isObj, hasProp } from '../../../../util'
|
|
9
|
+
import { CID } from 'multiformats/cid'
|
|
10
|
+
import { HandlerAuth } from '@atproto/xrpc-server'
|
|
11
|
+
|
|
12
|
+
export interface QueryParams {
|
|
13
|
+
/** The DID of the repo. */
|
|
14
|
+
did: string
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export type InputSchema = string | Uint8Array
|
|
18
|
+
|
|
19
|
+
export interface HandlerInput {
|
|
20
|
+
encoding: '*/*'
|
|
21
|
+
body: stream.Readable
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface HandlerError {
|
|
25
|
+
status: number
|
|
26
|
+
message?: string
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export type HandlerOutput = HandlerError | void
|
|
30
|
+
export type HandlerReqCtx<HA extends HandlerAuth = never> = {
|
|
31
|
+
auth: HA
|
|
32
|
+
params: QueryParams
|
|
33
|
+
input: HandlerInput
|
|
34
|
+
req: express.Request
|
|
35
|
+
res: express.Response
|
|
36
|
+
}
|
|
37
|
+
export type Handler<HA extends HandlerAuth = never> = (
|
|
38
|
+
ctx: HandlerReqCtx<HA>,
|
|
39
|
+
) => Promise<HandlerOutput> | HandlerOutput
|
package/src/lexicon/types/com/atproto/{admin/reverseModerationAction.ts → temp/transferAccount.ts}
RENAMED
|
@@ -7,18 +7,23 @@ import { lexicons } from '../../../../lexicons'
|
|
|
7
7
|
import { isObj, hasProp } from '../../../../util'
|
|
8
8
|
import { CID } from 'multiformats/cid'
|
|
9
9
|
import { HandlerAuth } from '@atproto/xrpc-server'
|
|
10
|
-
import * as ComAtprotoAdminDefs from './defs'
|
|
11
10
|
|
|
12
11
|
export interface QueryParams {}
|
|
13
12
|
|
|
14
13
|
export interface InputSchema {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
14
|
+
handle: string
|
|
15
|
+
did: string
|
|
16
|
+
plcOp: {}
|
|
18
17
|
[k: string]: unknown
|
|
19
18
|
}
|
|
20
19
|
|
|
21
|
-
export
|
|
20
|
+
export interface OutputSchema {
|
|
21
|
+
accessJwt: string
|
|
22
|
+
refreshJwt: string
|
|
23
|
+
handle: string
|
|
24
|
+
did: string
|
|
25
|
+
[k: string]: unknown
|
|
26
|
+
}
|
|
22
27
|
|
|
23
28
|
export interface HandlerInput {
|
|
24
29
|
encoding: 'application/json'
|
|
@@ -34,6 +39,14 @@ export interface HandlerSuccess {
|
|
|
34
39
|
export interface HandlerError {
|
|
35
40
|
status: number
|
|
36
41
|
message?: string
|
|
42
|
+
error?:
|
|
43
|
+
| 'InvalidHandle'
|
|
44
|
+
| 'InvalidPassword'
|
|
45
|
+
| 'InvalidInviteCode'
|
|
46
|
+
| 'HandleNotAvailable'
|
|
47
|
+
| 'UnsupportedDomain'
|
|
48
|
+
| 'UnresolvableDid'
|
|
49
|
+
| 'IncompatibleDidDoc'
|
|
37
50
|
}
|
|
38
51
|
|
|
39
52
|
export type HandlerOutput = HandlerError | HandlerSuccess
|
package/src/logger.ts
CHANGED
|
@@ -3,6 +3,8 @@ import { subsystemLogger } from '@atproto/common'
|
|
|
3
3
|
|
|
4
4
|
export const dbLogger: ReturnType<typeof subsystemLogger> =
|
|
5
5
|
subsystemLogger('bsky:db')
|
|
6
|
+
export const cacheLogger: ReturnType<typeof subsystemLogger> =
|
|
7
|
+
subsystemLogger('bsky:cache')
|
|
6
8
|
export const subLogger: ReturnType<typeof subsystemLogger> =
|
|
7
9
|
subsystemLogger('bsky:sub')
|
|
8
10
|
export const labelerLogger: ReturnType<typeof subsystemLogger> =
|
|
@@ -0,0 +1,414 @@
|
|
|
1
|
+
import { sql } from 'kysely'
|
|
2
|
+
import { DatabaseCoordinator, PrimaryDatabase } from './index'
|
|
3
|
+
import { adjustModerationSubjectStatus } from './services/moderation/status'
|
|
4
|
+
import { ModerationEventRow } from './services/moderation/types'
|
|
5
|
+
|
|
6
|
+
type ModerationActionRow = Omit<ModerationEventRow, 'comment' | 'meta'> & {
|
|
7
|
+
reason: string | null
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const getEnv = () => ({
|
|
11
|
+
DB_URL:
|
|
12
|
+
process.env.MODERATION_MIGRATION_DB_URL ||
|
|
13
|
+
'postgresql://pg:password@127.0.0.1:5433/postgres',
|
|
14
|
+
DB_POOL_SIZE: Number(process.env.MODERATION_MIGRATION_DB_POOL_SIZE) || 10,
|
|
15
|
+
DB_SCHEMA: process.env.MODERATION_MIGRATION_DB_SCHEMA || 'bsky',
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
const countEntries = async (db: PrimaryDatabase) => {
|
|
19
|
+
const [allActions, allReports] = await Promise.all([
|
|
20
|
+
db.db
|
|
21
|
+
// @ts-ignore
|
|
22
|
+
.selectFrom('moderation_action')
|
|
23
|
+
// @ts-ignore
|
|
24
|
+
.select((eb) => eb.fn.count<number>('id').as('count'))
|
|
25
|
+
.executeTakeFirstOrThrow(),
|
|
26
|
+
db.db
|
|
27
|
+
// @ts-ignore
|
|
28
|
+
.selectFrom('moderation_report')
|
|
29
|
+
// @ts-ignore
|
|
30
|
+
.select((eb) => eb.fn.count<number>('id').as('count'))
|
|
31
|
+
.executeTakeFirstOrThrow(),
|
|
32
|
+
])
|
|
33
|
+
|
|
34
|
+
return { reportsCount: allReports.count, actionsCount: allActions.count }
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const countEvents = async (db: PrimaryDatabase) => {
|
|
38
|
+
const events = await db.db
|
|
39
|
+
.selectFrom('moderation_event')
|
|
40
|
+
.select((eb) => eb.fn.count<number>('id').as('count'))
|
|
41
|
+
.executeTakeFirstOrThrow()
|
|
42
|
+
|
|
43
|
+
return events.count
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const getLatestReportLegacyRefId = async (db: PrimaryDatabase) => {
|
|
47
|
+
const events = await db.db
|
|
48
|
+
.selectFrom('moderation_event')
|
|
49
|
+
.select((eb) => eb.fn.max('legacyRefId').as('latestLegacyRefId'))
|
|
50
|
+
.where('action', '=', 'com.atproto.admin.defs#modEventReport')
|
|
51
|
+
.executeTakeFirstOrThrow()
|
|
52
|
+
|
|
53
|
+
return events.latestLegacyRefId
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const countStatuses = async (db: PrimaryDatabase) => {
|
|
57
|
+
const events = await db.db
|
|
58
|
+
.selectFrom('moderation_subject_status')
|
|
59
|
+
.select((eb) => eb.fn.count<number>('id').as('count'))
|
|
60
|
+
.executeTakeFirstOrThrow()
|
|
61
|
+
|
|
62
|
+
return events.count
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const processLegacyReports = async (
|
|
66
|
+
db: PrimaryDatabase,
|
|
67
|
+
legacyIds: number[],
|
|
68
|
+
) => {
|
|
69
|
+
if (!legacyIds.length) {
|
|
70
|
+
console.log('No legacy reports to process')
|
|
71
|
+
return
|
|
72
|
+
}
|
|
73
|
+
const reports = await db.db
|
|
74
|
+
.selectFrom('moderation_event')
|
|
75
|
+
.where('action', '=', 'com.atproto.admin.defs#modEventReport')
|
|
76
|
+
.where('legacyRefId', 'in', legacyIds)
|
|
77
|
+
.orderBy('legacyRefId', 'asc')
|
|
78
|
+
.selectAll()
|
|
79
|
+
.execute()
|
|
80
|
+
|
|
81
|
+
console.log(`Processing ${reports.length} reports from ${legacyIds.length}`)
|
|
82
|
+
await db.transaction(async (tx) => {
|
|
83
|
+
// This will be slow but we need to run this in sequence
|
|
84
|
+
for (const report of reports) {
|
|
85
|
+
await adjustModerationSubjectStatus(tx, report)
|
|
86
|
+
}
|
|
87
|
+
})
|
|
88
|
+
console.log(`Completed processing ${reports.length} reports`)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const getReportEventsAboveLegacyId = async (
|
|
92
|
+
db: PrimaryDatabase,
|
|
93
|
+
aboveLegacyId: number,
|
|
94
|
+
) => {
|
|
95
|
+
return await db.db
|
|
96
|
+
.selectFrom('moderation_event')
|
|
97
|
+
.where('action', '=', 'com.atproto.admin.defs#modEventReport')
|
|
98
|
+
.where('legacyRefId', '>', aboveLegacyId)
|
|
99
|
+
.select(sql<number>`"legacyRefId"`.as('legacyRefId'))
|
|
100
|
+
.execute()
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const createEvents = async (
|
|
104
|
+
db: PrimaryDatabase,
|
|
105
|
+
opts?: { onlyReportsAboveId: number },
|
|
106
|
+
) => {
|
|
107
|
+
const commonColumnsToSelect = [
|
|
108
|
+
'subjectDid',
|
|
109
|
+
'subjectUri',
|
|
110
|
+
'subjectType',
|
|
111
|
+
'subjectCid',
|
|
112
|
+
sql`reason`.as('comment'),
|
|
113
|
+
'createdAt',
|
|
114
|
+
]
|
|
115
|
+
const commonColumnsToInsert = [
|
|
116
|
+
'subjectDid',
|
|
117
|
+
'subjectUri',
|
|
118
|
+
'subjectType',
|
|
119
|
+
'subjectCid',
|
|
120
|
+
'comment',
|
|
121
|
+
'createdAt',
|
|
122
|
+
'action',
|
|
123
|
+
'createdBy',
|
|
124
|
+
] as const
|
|
125
|
+
|
|
126
|
+
let totalActions: number
|
|
127
|
+
if (!opts?.onlyReportsAboveId) {
|
|
128
|
+
await db.db
|
|
129
|
+
.insertInto('moderation_event')
|
|
130
|
+
.columns([
|
|
131
|
+
'id',
|
|
132
|
+
...commonColumnsToInsert,
|
|
133
|
+
'createLabelVals',
|
|
134
|
+
'negateLabelVals',
|
|
135
|
+
'durationInHours',
|
|
136
|
+
'expiresAt',
|
|
137
|
+
])
|
|
138
|
+
.expression((eb) =>
|
|
139
|
+
eb
|
|
140
|
+
// @ts-ignore
|
|
141
|
+
.selectFrom('moderation_action')
|
|
142
|
+
// @ts-ignore
|
|
143
|
+
.select([
|
|
144
|
+
'id',
|
|
145
|
+
...commonColumnsToSelect,
|
|
146
|
+
sql`CONCAT('com.atproto.admin.defs#modEvent', UPPER(SUBSTRING(SPLIT_PART(action, '#', 2) FROM 1 FOR 1)), SUBSTRING(SPLIT_PART(action, '#', 2) FROM 2))`.as(
|
|
147
|
+
'action',
|
|
148
|
+
),
|
|
149
|
+
'createdBy',
|
|
150
|
+
'createLabelVals',
|
|
151
|
+
'negateLabelVals',
|
|
152
|
+
'durationInHours',
|
|
153
|
+
'expiresAt',
|
|
154
|
+
])
|
|
155
|
+
.orderBy('id', 'asc'),
|
|
156
|
+
)
|
|
157
|
+
.execute()
|
|
158
|
+
|
|
159
|
+
totalActions = await countEvents(db)
|
|
160
|
+
console.log(`Created ${totalActions} events from actions`)
|
|
161
|
+
|
|
162
|
+
await sql`SELECT setval(pg_get_serial_sequence('moderation_event', 'id'), (select max(id) from moderation_event))`.execute(
|
|
163
|
+
db.db,
|
|
164
|
+
)
|
|
165
|
+
console.log('Reset the id sequence for moderation_event')
|
|
166
|
+
} else {
|
|
167
|
+
totalActions = await countEvents(db)
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
await db.db
|
|
171
|
+
.insertInto('moderation_event')
|
|
172
|
+
.columns([...commonColumnsToInsert, 'meta', 'legacyRefId'])
|
|
173
|
+
.expression((eb) => {
|
|
174
|
+
const builder = eb
|
|
175
|
+
// @ts-ignore
|
|
176
|
+
.selectFrom('moderation_report')
|
|
177
|
+
// @ts-ignore
|
|
178
|
+
.select([
|
|
179
|
+
...commonColumnsToSelect,
|
|
180
|
+
sql`'com.atproto.admin.defs#modEventReport'`.as('action'),
|
|
181
|
+
sql`"reportedByDid"`.as('createdBy'),
|
|
182
|
+
sql`json_build_object('reportType', "reasonType")`.as('meta'),
|
|
183
|
+
sql`id`.as('legacyRefId'),
|
|
184
|
+
])
|
|
185
|
+
|
|
186
|
+
if (opts?.onlyReportsAboveId) {
|
|
187
|
+
// @ts-ignore
|
|
188
|
+
return builder.where('id', '>', opts.onlyReportsAboveId)
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
return builder
|
|
192
|
+
})
|
|
193
|
+
.execute()
|
|
194
|
+
|
|
195
|
+
const totalEvents = await countEvents(db)
|
|
196
|
+
console.log(`Created ${totalEvents - totalActions} events from reports`)
|
|
197
|
+
|
|
198
|
+
return
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const setReportedAtTimestamp = async (db: PrimaryDatabase) => {
|
|
202
|
+
console.log('Initiating lastReportedAt timestamp sync')
|
|
203
|
+
const didUpdate = await sql`
|
|
204
|
+
UPDATE moderation_subject_status
|
|
205
|
+
SET "lastReportedAt" = reports."createdAt"
|
|
206
|
+
FROM (
|
|
207
|
+
select "subjectDid", "subjectUri", MAX("createdAt") as "createdAt"
|
|
208
|
+
from moderation_report
|
|
209
|
+
where "subjectUri" is null
|
|
210
|
+
group by "subjectDid", "subjectUri"
|
|
211
|
+
) as reports
|
|
212
|
+
WHERE reports."subjectDid" = moderation_subject_status."did"
|
|
213
|
+
AND "recordPath" = ''
|
|
214
|
+
AND ("lastReportedAt" is null OR "lastReportedAt" < reports."createdAt")
|
|
215
|
+
`.execute(db.db)
|
|
216
|
+
|
|
217
|
+
console.log(
|
|
218
|
+
`Updated lastReportedAt for ${didUpdate.numUpdatedOrDeletedRows} did subject`,
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
const contentUpdate = await sql`
|
|
222
|
+
UPDATE moderation_subject_status
|
|
223
|
+
SET "lastReportedAt" = reports."createdAt"
|
|
224
|
+
FROM (
|
|
225
|
+
select "subjectDid", "subjectUri", MAX("createdAt") as "createdAt"
|
|
226
|
+
from moderation_report
|
|
227
|
+
where "subjectUri" is not null
|
|
228
|
+
group by "subjectDid", "subjectUri"
|
|
229
|
+
) as reports
|
|
230
|
+
WHERE reports."subjectDid" = moderation_subject_status."did"
|
|
231
|
+
AND "recordPath" is not null
|
|
232
|
+
AND POSITION(moderation_subject_status."recordPath" IN reports."subjectUri") > 0
|
|
233
|
+
AND ("lastReportedAt" is null OR "lastReportedAt" < reports."createdAt")
|
|
234
|
+
`.execute(db.db)
|
|
235
|
+
|
|
236
|
+
console.log(
|
|
237
|
+
`Updated lastReportedAt for ${contentUpdate.numUpdatedOrDeletedRows} subject with uri`,
|
|
238
|
+
)
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
const createStatusFromActions = async (db: PrimaryDatabase) => {
|
|
242
|
+
const allEvents = await db.db
|
|
243
|
+
// @ts-ignore
|
|
244
|
+
.selectFrom('moderation_action')
|
|
245
|
+
// @ts-ignore
|
|
246
|
+
.where('reversedAt', 'is', null)
|
|
247
|
+
// @ts-ignore
|
|
248
|
+
.select((eb) => eb.fn.count<number>('id').as('count'))
|
|
249
|
+
.executeTakeFirstOrThrow()
|
|
250
|
+
|
|
251
|
+
const chunkSize = 2500
|
|
252
|
+
const totalChunks = Math.ceil(allEvents.count / chunkSize)
|
|
253
|
+
|
|
254
|
+
console.log(`Processing ${allEvents.count} actions in ${totalChunks} chunks`)
|
|
255
|
+
|
|
256
|
+
await db.transaction(async (tx) => {
|
|
257
|
+
// This is not used for pagination but only for logging purposes
|
|
258
|
+
let currentChunk = 1
|
|
259
|
+
let lastProcessedId: undefined | number = 0
|
|
260
|
+
do {
|
|
261
|
+
const eventsQuery = tx.db
|
|
262
|
+
// @ts-ignore
|
|
263
|
+
.selectFrom('moderation_action')
|
|
264
|
+
// @ts-ignore
|
|
265
|
+
.where('reversedAt', 'is', null)
|
|
266
|
+
// @ts-ignore
|
|
267
|
+
.where('id', '>', lastProcessedId)
|
|
268
|
+
.limit(chunkSize)
|
|
269
|
+
.selectAll()
|
|
270
|
+
const events = (await eventsQuery.execute()) as ModerationActionRow[]
|
|
271
|
+
|
|
272
|
+
for (const event of events) {
|
|
273
|
+
// Remap action to event data type
|
|
274
|
+
const actionParts = event.action.split('#')
|
|
275
|
+
await adjustModerationSubjectStatus(tx, {
|
|
276
|
+
...event,
|
|
277
|
+
action: `com.atproto.admin.defs#modEvent${actionParts[1]
|
|
278
|
+
.charAt(0)
|
|
279
|
+
.toUpperCase()}${actionParts[1].slice(
|
|
280
|
+
1,
|
|
281
|
+
)}` as ModerationEventRow['action'],
|
|
282
|
+
comment: event.reason,
|
|
283
|
+
meta: null,
|
|
284
|
+
})
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
console.log(`Processed events chunk ${currentChunk} of ${totalChunks}`)
|
|
288
|
+
lastProcessedId = events.at(-1)?.id
|
|
289
|
+
currentChunk++
|
|
290
|
+
} while (lastProcessedId !== undefined)
|
|
291
|
+
})
|
|
292
|
+
|
|
293
|
+
console.log(`Events migration complete!`)
|
|
294
|
+
|
|
295
|
+
const totalStatuses = await countStatuses(db)
|
|
296
|
+
console.log(`Created ${totalStatuses} statuses`)
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
const remapFlagToAcknlowedge = async (db: PrimaryDatabase) => {
|
|
300
|
+
console.log('Initiating flag to ack remap')
|
|
301
|
+
const results = await sql`
|
|
302
|
+
UPDATE moderation_event
|
|
303
|
+
SET "action" = 'com.atproto.admin.defs#modEventAcknowledge'
|
|
304
|
+
WHERE action = 'com.atproto.admin.defs#modEventFlag'
|
|
305
|
+
`.execute(db.db)
|
|
306
|
+
console.log(`Remapped ${results.numUpdatedOrDeletedRows} flag actions to ack`)
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
const syncBlobCids = async (db: PrimaryDatabase) => {
|
|
310
|
+
console.log('Initiating blob cid sync')
|
|
311
|
+
const results = await sql`
|
|
312
|
+
UPDATE moderation_subject_status
|
|
313
|
+
SET "blobCids" = blob_action."cids"
|
|
314
|
+
FROM (
|
|
315
|
+
SELECT moderation_action."subjectUri", moderation_action."subjectDid", jsonb_agg(moderation_action_subject_blob."cid") as cids
|
|
316
|
+
FROM moderation_action_subject_blob
|
|
317
|
+
JOIN moderation_action
|
|
318
|
+
ON moderation_action.id = moderation_action_subject_blob."actionId"
|
|
319
|
+
WHERE moderation_action."reversedAt" is NULL
|
|
320
|
+
GROUP by moderation_action."subjectUri", moderation_action."subjectDid"
|
|
321
|
+
) as blob_action
|
|
322
|
+
WHERE did = "subjectDid" AND position("recordPath" IN "subjectUri") > 0
|
|
323
|
+
`.execute(db.db)
|
|
324
|
+
console.log(`Updated blob cids on ${results.numUpdatedOrDeletedRows} rows`)
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
async function updateStatusFromUnresolvedReports(db: PrimaryDatabase) {
|
|
328
|
+
const { ref } = db.db.dynamic
|
|
329
|
+
const reports = await db.db
|
|
330
|
+
// @ts-ignore
|
|
331
|
+
.selectFrom('moderation_report')
|
|
332
|
+
.whereNotExists((qb) =>
|
|
333
|
+
qb
|
|
334
|
+
.selectFrom('moderation_report_resolution')
|
|
335
|
+
.selectAll()
|
|
336
|
+
// @ts-ignore
|
|
337
|
+
.whereRef('reportId', '=', ref('moderation_report.id')),
|
|
338
|
+
)
|
|
339
|
+
.select(sql<number>`moderation_report.id`.as('legacyId'))
|
|
340
|
+
.execute()
|
|
341
|
+
|
|
342
|
+
console.log('Updating statuses based on unresolved reports')
|
|
343
|
+
await processLegacyReports(
|
|
344
|
+
db,
|
|
345
|
+
reports.map((report) => report.legacyId),
|
|
346
|
+
)
|
|
347
|
+
console.log('Completed updating statuses based on unresolved reports')
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
export async function MigrateModerationData() {
|
|
351
|
+
const env = getEnv()
|
|
352
|
+
const db = new DatabaseCoordinator({
|
|
353
|
+
schema: env.DB_SCHEMA,
|
|
354
|
+
primary: {
|
|
355
|
+
url: env.DB_URL,
|
|
356
|
+
poolSize: env.DB_POOL_SIZE,
|
|
357
|
+
},
|
|
358
|
+
replicas: [],
|
|
359
|
+
})
|
|
360
|
+
|
|
361
|
+
const primaryDb = db.getPrimary()
|
|
362
|
+
|
|
363
|
+
const [counts, existingEventsCount] = await Promise.all([
|
|
364
|
+
countEntries(primaryDb),
|
|
365
|
+
countEvents(primaryDb),
|
|
366
|
+
])
|
|
367
|
+
|
|
368
|
+
// If there are existing events in the moderation_event table, we assume that the migration has already been run
|
|
369
|
+
// so we just bring over any new reports since last run
|
|
370
|
+
if (existingEventsCount) {
|
|
371
|
+
console.log(
|
|
372
|
+
`Found ${existingEventsCount} existing events. Migrating ${counts.reportsCount} reports only, ignoring actions`,
|
|
373
|
+
)
|
|
374
|
+
const reportMigrationStartedAt = Date.now()
|
|
375
|
+
const latestReportLegacyRefId = await getLatestReportLegacyRefId(primaryDb)
|
|
376
|
+
|
|
377
|
+
if (latestReportLegacyRefId) {
|
|
378
|
+
await createEvents(primaryDb, {
|
|
379
|
+
onlyReportsAboveId: latestReportLegacyRefId,
|
|
380
|
+
})
|
|
381
|
+
const newReportEvents = await getReportEventsAboveLegacyId(
|
|
382
|
+
primaryDb,
|
|
383
|
+
latestReportLegacyRefId,
|
|
384
|
+
)
|
|
385
|
+
await processLegacyReports(
|
|
386
|
+
primaryDb,
|
|
387
|
+
newReportEvents.map((evt) => evt.legacyRefId),
|
|
388
|
+
)
|
|
389
|
+
await setReportedAtTimestamp(primaryDb)
|
|
390
|
+
} else {
|
|
391
|
+
console.log('No reports have been migrated into events yet, bailing.')
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
console.log(
|
|
395
|
+
`Time spent: ${(Date.now() - reportMigrationStartedAt) / 1000} seconds`,
|
|
396
|
+
)
|
|
397
|
+
console.log('Migration complete!')
|
|
398
|
+
return
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
const totalEntries = counts.actionsCount + counts.reportsCount
|
|
402
|
+
console.log(`Migrating ${totalEntries} rows of actions and reports`)
|
|
403
|
+
const startedAt = Date.now()
|
|
404
|
+
await createEvents(primaryDb)
|
|
405
|
+
// Important to run this before creation statuses from actions to ensure that we are not attempting to map flag actions
|
|
406
|
+
await remapFlagToAcknlowedge(primaryDb)
|
|
407
|
+
await createStatusFromActions(primaryDb)
|
|
408
|
+
await updateStatusFromUnresolvedReports(primaryDb)
|
|
409
|
+
await setReportedAtTimestamp(primaryDb)
|
|
410
|
+
await syncBlobCids(primaryDb)
|
|
411
|
+
|
|
412
|
+
console.log(`Time spent: ${(Date.now() - startedAt) / 1000 / 60} minutes`)
|
|
413
|
+
console.log('Migration complete!')
|
|
414
|
+
}
|
package/src/redis.ts
CHANGED
|
@@ -11,12 +11,16 @@ export class Redis {
|
|
|
11
11
|
name: opts.sentinel,
|
|
12
12
|
sentinels: opts.hosts.map((h) => addressParts(h, 26379)),
|
|
13
13
|
password: opts.password,
|
|
14
|
+
db: opts.db,
|
|
15
|
+
commandTimeout: opts.commandTimeout,
|
|
14
16
|
})
|
|
15
17
|
} else if ('host' in opts) {
|
|
16
18
|
assert(opts.host)
|
|
17
19
|
this.driver = new RedisDriver({
|
|
18
20
|
...addressParts(opts.host),
|
|
19
21
|
password: opts.password,
|
|
22
|
+
db: opts.db,
|
|
23
|
+
commandTimeout: opts.commandTimeout,
|
|
20
24
|
})
|
|
21
25
|
} else {
|
|
22
26
|
assert(opts.driver)
|
|
@@ -25,6 +29,10 @@ export class Redis {
|
|
|
25
29
|
this.namespace = opts.namespace
|
|
26
30
|
}
|
|
27
31
|
|
|
32
|
+
withNamespace(namespace: string): Redis {
|
|
33
|
+
return new Redis({ driver: this.driver, namespace })
|
|
34
|
+
}
|
|
35
|
+
|
|
28
36
|
async readStreams(
|
|
29
37
|
streams: StreamRef[],
|
|
30
38
|
opts: { count: number; blockMs?: number },
|
|
@@ -97,8 +105,38 @@ export class Redis {
|
|
|
97
105
|
return await this.driver.get(this.ns(key))
|
|
98
106
|
}
|
|
99
107
|
|
|
100
|
-
async set(key: string, val: string | number) {
|
|
101
|
-
|
|
108
|
+
async set(key: string, val: string | number, ttlMs?: number) {
|
|
109
|
+
if (ttlMs !== undefined) {
|
|
110
|
+
await this.driver.set(this.ns(key), val, 'PX', ttlMs)
|
|
111
|
+
} else {
|
|
112
|
+
await this.driver.set(this.ns(key), val)
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
async getMulti(keys: string[]) {
|
|
117
|
+
const namespaced = keys.map((k) => this.ns(k))
|
|
118
|
+
const got = await this.driver.mget(...namespaced)
|
|
119
|
+
const results = {}
|
|
120
|
+
for (let i = 0; i < keys.length; i++) {
|
|
121
|
+
const key = keys[i]
|
|
122
|
+
results[key] = got[i]
|
|
123
|
+
}
|
|
124
|
+
return results
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
async setMulti(vals: Record<string, string | number>, ttlMs?: number) {
|
|
128
|
+
if (Object.keys(vals).length === 0) {
|
|
129
|
+
return
|
|
130
|
+
}
|
|
131
|
+
let builder = this.driver.multi({ pipeline: true })
|
|
132
|
+
for (const key of Object.keys(vals)) {
|
|
133
|
+
if (ttlMs !== undefined) {
|
|
134
|
+
builder = builder.set(this.ns(key), vals[key], 'PX', ttlMs)
|
|
135
|
+
} else {
|
|
136
|
+
builder = builder.set(this.ns(key), vals[key])
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
await builder.exec()
|
|
102
140
|
}
|
|
103
141
|
|
|
104
142
|
async del(key: string) {
|
|
@@ -152,9 +190,11 @@ export type RedisOptions = (
|
|
|
152
190
|
) & {
|
|
153
191
|
password?: string
|
|
154
192
|
namespace?: string
|
|
193
|
+
db?: number
|
|
194
|
+
commandTimeout?: number
|
|
155
195
|
}
|
|
156
196
|
|
|
157
|
-
function addressParts(
|
|
197
|
+
export function addressParts(
|
|
158
198
|
addr: string,
|
|
159
199
|
defaultPort = 6379,
|
|
160
200
|
): { host: string; port: number } {
|