@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.
Files changed (236) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/dist/api/com/atproto/moderation/util.d.ts +4 -3
  3. package/dist/cache/read-through.d.ts +30 -0
  4. package/dist/config.d.ts +18 -0
  5. package/dist/context.d.ts +21 -6
  6. package/dist/daemon/config.d.ts +15 -0
  7. package/dist/daemon/context.d.ts +15 -0
  8. package/dist/daemon/index.d.ts +23 -0
  9. package/dist/daemon/logger.d.ts +3 -0
  10. package/dist/daemon/notifications.d.ts +18 -0
  11. package/dist/daemon/services.d.ts +11 -0
  12. package/dist/db/database-schema.d.ts +1 -2
  13. package/dist/db/index.js +41 -1
  14. package/dist/db/index.js.map +3 -3
  15. package/dist/db/migrations/20231003T202833377Z-create-moderation-subject-status.d.ts +3 -0
  16. package/dist/db/migrations/20231205T000257238Z-remove-did-cache.d.ts +3 -0
  17. package/dist/db/migrations/index.d.ts +2 -0
  18. package/dist/db/pagination.d.ts +2 -1
  19. package/dist/db/{periodic-moderation-action-reversal.d.ts → periodic-moderation-event-reversal.d.ts} +3 -5
  20. package/dist/db/tables/moderation.d.ts +24 -34
  21. package/dist/did-cache.d.ts +10 -7
  22. package/dist/feed-gen/types.d.ts +1 -1
  23. package/dist/index.d.ts +6 -1
  24. package/dist/index.js +4370 -2758
  25. package/dist/index.js.map +3 -3
  26. package/dist/indexer/context.d.ts +2 -0
  27. package/dist/indexer/index.d.ts +1 -0
  28. package/dist/lexicon/index.d.ts +23 -18
  29. package/dist/lexicon/lexicons.d.ts +561 -412
  30. package/dist/lexicon/types/app/bsky/feed/defs.d.ts +1 -7
  31. package/dist/lexicon/types/app/bsky/graph/defs.d.ts +1 -0
  32. package/dist/lexicon/types/com/atproto/admin/defs.d.ts +114 -48
  33. package/dist/lexicon/types/com/atproto/admin/{resolveModerationReports.d.ts → deleteAccount.d.ts} +2 -13
  34. package/dist/lexicon/types/com/atproto/admin/{takeModerationAction.d.ts → emitModerationEvent.d.ts} +5 -6
  35. package/dist/lexicon/types/com/atproto/admin/{getModerationAction.d.ts → getModerationEvent.d.ts} +1 -1
  36. package/dist/lexicon/types/com/atproto/admin/{getModerationActions.d.ts → queryModerationEvents.d.ts} +5 -1
  37. package/dist/lexicon/types/com/atproto/admin/{getModerationReports.d.ts → queryModerationStatuses.d.ts} +12 -6
  38. package/dist/lexicon/types/com/atproto/admin/sendEmail.d.ts +1 -0
  39. package/dist/lexicon/types/com/atproto/{admin/getModerationReport.d.ts → temp/importRepo.d.ts} +10 -7
  40. package/dist/lexicon/types/com/atproto/temp/pushBlob.d.ts +25 -0
  41. package/dist/lexicon/types/com/atproto/{admin/reverseModerationAction.d.ts → temp/transferAccount.d.ts} +11 -5
  42. package/dist/logger.d.ts +1 -0
  43. package/dist/migrate-moderation-data.d.ts +1 -0
  44. package/dist/redis.d.ts +10 -1
  45. package/dist/services/actor/index.d.ts +18 -4
  46. package/dist/services/actor/views.d.ts +6 -8
  47. package/dist/services/feed/index.d.ts +7 -4
  48. package/dist/services/feed/util.d.ts +9 -1
  49. package/dist/services/feed/views.d.ts +11 -21
  50. package/dist/services/graph/index.d.ts +5 -29
  51. package/dist/services/graph/types.d.ts +1 -0
  52. package/dist/services/index.d.ts +3 -7
  53. package/dist/services/label/index.d.ts +10 -4
  54. package/dist/services/moderation/index.d.ts +134 -72
  55. package/dist/services/moderation/pagination.d.ts +36 -0
  56. package/dist/services/moderation/status.d.ts +13 -0
  57. package/dist/services/moderation/types.d.ts +35 -0
  58. package/dist/services/moderation/views.d.ts +18 -14
  59. package/dist/services/types.d.ts +3 -0
  60. package/dist/services/util/notification.d.ts +5 -0
  61. package/dist/services/util/post.d.ts +6 -6
  62. package/dist/util/debug.d.ts +1 -1
  63. package/dist/util/retry.d.ts +1 -6
  64. package/package.json +11 -11
  65. package/src/api/app/bsky/feed/getActorFeeds.ts +2 -1
  66. package/src/api/app/bsky/feed/getActorLikes.ts +1 -3
  67. package/src/api/app/bsky/feed/getAuthorFeed.ts +1 -3
  68. package/src/api/app/bsky/feed/getFeed.ts +9 -9
  69. package/src/api/app/bsky/feed/getFeedGenerator.ts +3 -0
  70. package/src/api/app/bsky/feed/getFeedGenerators.ts +2 -1
  71. package/src/api/app/bsky/feed/getListFeed.ts +1 -3
  72. package/src/api/app/bsky/feed/getPostThread.ts +15 -54
  73. package/src/api/app/bsky/feed/getPosts.ts +21 -18
  74. package/src/api/app/bsky/feed/getSuggestedFeeds.ts +2 -1
  75. package/src/api/app/bsky/feed/getTimeline.ts +1 -3
  76. package/src/api/app/bsky/feed/searchPosts.ts +20 -17
  77. package/src/api/app/bsky/graph/getList.ts +6 -3
  78. package/src/api/app/bsky/graph/getListBlocks.ts +3 -2
  79. package/src/api/app/bsky/graph/getListMutes.ts +2 -1
  80. package/src/api/app/bsky/graph/getLists.ts +2 -1
  81. package/src/api/app/bsky/unspecced/getPopularFeedGenerators.ts +3 -1
  82. package/src/api/blob-resolver.ts +6 -11
  83. package/src/api/com/atproto/admin/emitModerationEvent.ts +220 -0
  84. package/src/api/com/atproto/admin/{getModerationActions.ts → getModerationEvent.ts} +5 -11
  85. package/src/api/com/atproto/admin/getRecord.ts +1 -0
  86. package/src/api/com/atproto/admin/{getModerationReports.ts → queryModerationEvents.ts} +13 -16
  87. package/src/api/com/atproto/admin/queryModerationStatuses.ts +55 -0
  88. package/src/api/com/atproto/moderation/createReport.ts +9 -7
  89. package/src/api/com/atproto/moderation/util.ts +38 -20
  90. package/src/api/index.ts +8 -14
  91. package/src/auth.ts +29 -21
  92. package/src/auto-moderator/index.ts +26 -19
  93. package/src/cache/read-through.ts +151 -0
  94. package/src/config.ts +90 -1
  95. package/src/context.ts +11 -7
  96. package/src/daemon/config.ts +60 -0
  97. package/src/daemon/context.ts +27 -0
  98. package/src/daemon/index.ts +78 -0
  99. package/src/daemon/logger.ts +6 -0
  100. package/src/daemon/notifications.ts +54 -0
  101. package/src/daemon/services.ts +22 -0
  102. package/src/db/database-schema.ts +0 -2
  103. package/src/db/migrations/20231003T202833377Z-create-moderation-subject-status.ts +123 -0
  104. package/src/db/migrations/20231205T000257238Z-remove-did-cache.ts +14 -0
  105. package/src/db/migrations/index.ts +2 -0
  106. package/src/db/pagination.ts +26 -3
  107. package/src/db/{periodic-moderation-action-reversal.ts → periodic-moderation-event-reversal.ts} +50 -46
  108. package/src/db/tables/moderation.ts +35 -52
  109. package/src/did-cache.ts +33 -56
  110. package/src/feed-gen/bsky-team.ts +1 -1
  111. package/src/feed-gen/hot-classic.ts +1 -1
  112. package/src/feed-gen/index.ts +0 -4
  113. package/src/feed-gen/mutuals.ts +6 -2
  114. package/src/feed-gen/types.ts +1 -1
  115. package/src/index.ts +57 -17
  116. package/src/indexer/context.ts +5 -0
  117. package/src/indexer/index.ts +10 -7
  118. package/src/lexicon/index.ts +80 -67
  119. package/src/lexicon/lexicons.ts +698 -507
  120. package/src/lexicon/types/app/bsky/feed/defs.ts +1 -18
  121. package/src/lexicon/types/app/bsky/graph/defs.ts +1 -0
  122. package/src/lexicon/types/com/atproto/admin/defs.ts +276 -84
  123. package/src/lexicon/types/com/atproto/admin/{resolveModerationReports.ts → deleteAccount.ts} +2 -13
  124. package/src/lexicon/types/com/atproto/admin/{takeModerationAction.ts → emitModerationEvent.ts} +13 -11
  125. package/src/lexicon/types/com/atproto/admin/{getModerationReport.ts → getModerationEvent.ts} +1 -1
  126. package/src/lexicon/types/com/atproto/admin/{getModerationActions.ts → queryModerationEvents.ts} +8 -1
  127. package/src/lexicon/types/com/atproto/admin/{getModerationReports.ts → queryModerationStatuses.ts} +21 -14
  128. package/src/lexicon/types/com/atproto/admin/sendEmail.ts +1 -0
  129. package/src/lexicon/types/com/atproto/{admin/getModerationAction.ts → temp/importRepo.ts} +11 -7
  130. package/src/lexicon/types/com/atproto/temp/pushBlob.ts +39 -0
  131. package/src/lexicon/types/com/atproto/{admin/reverseModerationAction.ts → temp/transferAccount.ts} +18 -5
  132. package/src/logger.ts +2 -0
  133. package/src/migrate-moderation-data.ts +414 -0
  134. package/src/redis.ts +43 -3
  135. package/src/services/actor/index.ts +55 -7
  136. package/src/services/actor/views.ts +18 -21
  137. package/src/services/feed/index.ts +52 -19
  138. package/src/services/feed/util.ts +47 -19
  139. package/src/services/feed/views.ts +87 -13
  140. package/src/services/graph/index.ts +21 -3
  141. package/src/services/graph/types.ts +1 -0
  142. package/src/services/index.ts +14 -14
  143. package/src/services/indexing/index.ts +7 -10
  144. package/src/services/indexing/plugins/block.ts +2 -3
  145. package/src/services/indexing/plugins/feed-generator.ts +2 -3
  146. package/src/services/indexing/plugins/follow.ts +2 -3
  147. package/src/services/indexing/plugins/like.ts +2 -3
  148. package/src/services/indexing/plugins/list-block.ts +2 -3
  149. package/src/services/indexing/plugins/list-item.ts +2 -3
  150. package/src/services/indexing/plugins/list.ts +2 -3
  151. package/src/services/indexing/plugins/post.ts +16 -4
  152. package/src/services/indexing/plugins/repost.ts +2 -3
  153. package/src/services/indexing/plugins/thread-gate.ts +2 -3
  154. package/src/services/label/index.ts +68 -25
  155. package/src/services/moderation/index.ts +380 -395
  156. package/src/services/moderation/pagination.ts +96 -0
  157. package/src/services/moderation/status.ts +241 -0
  158. package/src/services/moderation/types.ts +49 -0
  159. package/src/services/moderation/views.ts +278 -329
  160. package/src/services/types.ts +4 -0
  161. package/src/services/util/notification.ts +70 -0
  162. package/src/util/debug.ts +2 -2
  163. package/src/util/retry.ts +1 -44
  164. package/tests/__snapshots__/feed-generation.test.ts.snap +322 -6
  165. package/tests/__snapshots__/indexing.test.ts.snap +0 -6
  166. package/tests/admin/__snapshots__/get-record.test.ts.snap +30 -132
  167. package/tests/admin/__snapshots__/get-repo.test.ts.snap +14 -60
  168. package/tests/admin/__snapshots__/moderation-events.test.ts.snap +146 -0
  169. package/tests/admin/__snapshots__/moderation-statuses.test.ts.snap +64 -0
  170. package/tests/admin/__snapshots__/moderation.test.ts.snap +0 -125
  171. package/tests/admin/get-record.test.ts +5 -9
  172. package/tests/admin/get-repo.test.ts +10 -12
  173. package/tests/admin/moderation-events.test.ts +221 -0
  174. package/tests/admin/moderation-statuses.test.ts +145 -0
  175. package/tests/admin/moderation.test.ts +512 -860
  176. package/tests/admin/repo-search.test.ts +3 -3
  177. package/tests/algos/hot-classic.test.ts +1 -2
  178. package/tests/auth.test.ts +1 -1
  179. package/tests/auto-moderator/fuzzy-matcher.test.ts +2 -1
  180. package/tests/auto-moderator/labeler.test.ts +19 -20
  181. package/tests/auto-moderator/takedowns.test.ts +61 -28
  182. package/tests/blob-resolver.test.ts +4 -2
  183. package/tests/daemon.test.ts +191 -0
  184. package/tests/did-cache.test.ts +20 -5
  185. package/tests/feed-generation.test.ts +57 -9
  186. package/tests/handle-invalidation.test.ts +1 -5
  187. package/tests/indexing.test.ts +20 -13
  188. package/tests/redis-cache.test.ts +231 -0
  189. package/tests/seeds/basic.ts +3 -0
  190. package/tests/subscription/repo.test.ts +4 -7
  191. package/tests/views/__snapshots__/block-lists.test.ts.snap +3 -9
  192. package/tests/views/__snapshots__/blocks.test.ts.snap +0 -9
  193. package/tests/views/__snapshots__/mute-lists.test.ts.snap +5 -5
  194. package/tests/views/__snapshots__/mutes.test.ts.snap +0 -3
  195. package/tests/views/__snapshots__/thread.test.ts.snap +0 -30
  196. package/tests/views/actor-search.test.ts +2 -3
  197. package/tests/views/author-feed.test.ts +42 -36
  198. package/tests/views/follows.test.ts +40 -35
  199. package/tests/views/list-feed.test.ts +17 -9
  200. package/tests/views/notifications.test.ts +13 -9
  201. package/tests/views/profile.test.ts +20 -19
  202. package/tests/views/thread.test.ts +117 -94
  203. package/tests/views/threadgating.test.ts +89 -19
  204. package/tests/views/timeline.test.ts +21 -13
  205. package/dist/api/com/atproto/admin/resolveModerationReports.d.ts +0 -3
  206. package/dist/api/com/atproto/admin/reverseModerationAction.d.ts +0 -3
  207. package/dist/api/com/atproto/admin/takeModerationAction.d.ts +0 -3
  208. package/dist/db/tables/did-cache.d.ts +0 -10
  209. package/dist/feed-gen/best-of-follows.d.ts +0 -29
  210. package/dist/feed-gen/whats-hot.d.ts +0 -29
  211. package/dist/feed-gen/with-friends.d.ts +0 -3
  212. package/dist/label-cache.d.ts +0 -19
  213. package/src/api/com/atproto/admin/getModerationAction.ts +0 -44
  214. package/src/api/com/atproto/admin/getModerationReport.ts +0 -43
  215. package/src/api/com/atproto/admin/resolveModerationReports.ts +0 -24
  216. package/src/api/com/atproto/admin/reverseModerationAction.ts +0 -115
  217. package/src/api/com/atproto/admin/takeModerationAction.ts +0 -156
  218. package/src/db/tables/did-cache.ts +0 -13
  219. package/src/feed-gen/best-of-follows.ts +0 -74
  220. package/src/feed-gen/whats-hot.ts +0 -101
  221. package/src/feed-gen/with-friends.ts +0 -39
  222. package/src/label-cache.ts +0 -90
  223. package/tests/admin/__snapshots__/get-moderation-action.test.ts.snap +0 -172
  224. package/tests/admin/__snapshots__/get-moderation-actions.test.ts.snap +0 -178
  225. package/tests/admin/__snapshots__/get-moderation-report.test.ts.snap +0 -177
  226. package/tests/admin/__snapshots__/get-moderation-reports.test.ts.snap +0 -307
  227. package/tests/admin/get-moderation-action.test.ts +0 -100
  228. package/tests/admin/get-moderation-actions.test.ts +0 -164
  229. package/tests/admin/get-moderation-report.test.ts +0 -100
  230. package/tests/admin/get-moderation-reports.test.ts +0 -332
  231. package/tests/algos/whats-hot.test.ts +0 -118
  232. package/tests/algos/with-friends.test.ts +0 -145
  233. /package/dist/api/com/atproto/admin/{getModerationAction.d.ts → emitModerationEvent.d.ts} +0 -0
  234. /package/dist/api/com/atproto/admin/{getModerationActions.d.ts → getModerationEvent.d.ts} +0 -0
  235. /package/dist/api/com/atproto/admin/{getModerationReport.d.ts → queryModerationEvents.d.ts} +0 -0
  236. /package/dist/api/com/atproto/admin/{getModerationReports.d.ts → queryModerationStatuses.d.ts} +0 -0
@@ -1,9 +1,5 @@
1
1
  import { SeedClient, TestNetwork } from '@atproto/dev-env'
2
2
  import AtpAgent from '@atproto/api'
3
- import {
4
- ACKNOWLEDGE,
5
- TAKEDOWN,
6
- } from '@atproto/api/src/client/types/com/atproto/admin/defs'
7
3
  import {
8
4
  REASONOTHER,
9
5
  REASONSPAM,
@@ -23,6 +19,7 @@ describe('admin get repo view', () => {
23
19
  agent = network.pds.getClient()
24
20
  sc = network.getSeedClient()
25
21
  await basicSeed(sc)
22
+ await network.processAll()
26
23
  })
27
24
 
28
25
  afterAll(async () => {
@@ -30,8 +27,8 @@ describe('admin get repo view', () => {
30
27
  })
31
28
 
32
29
  beforeAll(async () => {
33
- const acknowledge = await sc.takeModerationAction({
34
- action: ACKNOWLEDGE,
30
+ await sc.emitModerationEvent({
31
+ event: { $type: 'com.atproto.admin.defs#modEventAcknowledge' },
35
32
  subject: {
36
33
  $type: 'com.atproto.admin.defs#repoRef',
37
34
  did: sc.dids.alice,
@@ -54,9 +51,8 @@ describe('admin get repo view', () => {
54
51
  did: sc.dids.alice,
55
52
  },
56
53
  })
57
- await sc.reverseModerationAction({ id: acknowledge.id })
58
- await sc.takeModerationAction({
59
- action: TAKEDOWN,
54
+ await sc.emitModerationEvent({
55
+ event: { $type: 'com.atproto.admin.defs#modEventTakedown' },
60
56
  subject: {
61
57
  $type: 'com.atproto.admin.defs#repoRef',
62
58
  did: sc.dids.alice,
@@ -101,9 +97,11 @@ describe('admin get repo view', () => {
101
97
  expect(beforeEmailVerification.emailConfirmedAt).toBeUndefined()
102
98
  const timestampBeforeVerification = Date.now()
103
99
  const bobsAccount = sc.accounts[sc.dids.bob]
104
- const verificationToken = await network.pds.ctx.services
105
- .account(network.pds.ctx.db)
106
- .createEmailToken(sc.dids.bob, 'confirm_email')
100
+ const verificationToken =
101
+ await network.pds.ctx.accountManager.createEmailToken(
102
+ sc.dids.bob,
103
+ 'confirm_email',
104
+ )
107
105
  await agent.api.com.atproto.server.confirmEmail(
108
106
  { email: bobsAccount.email, token: verificationToken },
109
107
  {
@@ -0,0 +1,221 @@
1
+ import { TestNetwork, SeedClient } from '@atproto/dev-env'
2
+ import AtpAgent, { ComAtprotoAdminDefs } from '@atproto/api'
3
+ import { forSnapshot } from '../_util'
4
+ import basicSeed from '../seeds/basic'
5
+ import {
6
+ REASONMISLEADING,
7
+ REASONSPAM,
8
+ } from '../../src/lexicon/types/com/atproto/moderation/defs'
9
+
10
+ describe('moderation-events', () => {
11
+ let network: TestNetwork
12
+ let agent: AtpAgent
13
+ let pdsAgent: AtpAgent
14
+ let sc: SeedClient
15
+
16
+ const emitModerationEvent = async (eventData) => {
17
+ return pdsAgent.api.com.atproto.admin.emitModerationEvent(eventData, {
18
+ encoding: 'application/json',
19
+ headers: network.bsky.adminAuthHeaders('moderator'),
20
+ })
21
+ }
22
+
23
+ const queryModerationEvents = (eventQuery) =>
24
+ agent.api.com.atproto.admin.queryModerationEvents(eventQuery, {
25
+ headers: network.bsky.adminAuthHeaders('moderator'),
26
+ })
27
+
28
+ const seedEvents = async () => {
29
+ const bobsAccount = {
30
+ $type: 'com.atproto.admin.defs#repoRef',
31
+ did: sc.dids.bob,
32
+ }
33
+ const alicesAccount = {
34
+ $type: 'com.atproto.admin.defs#repoRef',
35
+ did: sc.dids.alice,
36
+ }
37
+ const bobsPost = {
38
+ $type: 'com.atproto.repo.strongRef',
39
+ uri: sc.posts[sc.dids.bob][0].ref.uriStr,
40
+ cid: sc.posts[sc.dids.bob][0].ref.cidStr,
41
+ }
42
+ const alicesPost = {
43
+ $type: 'com.atproto.repo.strongRef',
44
+ uri: sc.posts[sc.dids.alice][0].ref.uriStr,
45
+ cid: sc.posts[sc.dids.alice][0].ref.cidStr,
46
+ }
47
+
48
+ for (let i = 0; i < 4; i++) {
49
+ await emitModerationEvent({
50
+ event: {
51
+ $type: 'com.atproto.admin.defs#modEventReport',
52
+ reportType: i % 2 ? REASONSPAM : REASONMISLEADING,
53
+ comment: 'X',
54
+ },
55
+ // Report bob's account by alice and vice versa
56
+ subject: i % 2 ? bobsAccount : alicesAccount,
57
+ createdBy: i % 2 ? sc.dids.alice : sc.dids.bob,
58
+ })
59
+ await emitModerationEvent({
60
+ event: {
61
+ $type: 'com.atproto.admin.defs#modEventReport',
62
+ reportType: REASONSPAM,
63
+ comment: 'X',
64
+ },
65
+ // Report bob's post by alice and vice versa
66
+ subject: i % 2 ? bobsPost : alicesPost,
67
+ createdBy: i % 2 ? sc.dids.alice : sc.dids.bob,
68
+ })
69
+ }
70
+ }
71
+
72
+ beforeAll(async () => {
73
+ network = await TestNetwork.create({
74
+ dbPostgresSchema: 'bsky_moderation_events',
75
+ })
76
+ agent = network.bsky.getClient()
77
+ pdsAgent = network.pds.getClient()
78
+ sc = network.getSeedClient()
79
+ await basicSeed(sc)
80
+ await network.processAll()
81
+ await seedEvents()
82
+ })
83
+
84
+ afterAll(async () => {
85
+ await network.close()
86
+ })
87
+
88
+ describe('query events', () => {
89
+ it('returns all events for record or repo', async () => {
90
+ const [bobsEvents, alicesPostEvents] = await Promise.all([
91
+ queryModerationEvents({
92
+ subject: sc.dids.bob,
93
+ }),
94
+ queryModerationEvents({
95
+ subject: sc.posts[sc.dids.alice][0].ref.uriStr,
96
+ }),
97
+ ])
98
+
99
+ expect(forSnapshot(bobsEvents.data.events)).toMatchSnapshot()
100
+ expect(forSnapshot(alicesPostEvents.data.events)).toMatchSnapshot()
101
+ })
102
+
103
+ it('filters events by types', async () => {
104
+ const alicesAccount = {
105
+ $type: 'com.atproto.admin.defs#repoRef',
106
+ did: sc.dids.alice,
107
+ }
108
+ await Promise.all([
109
+ emitModerationEvent({
110
+ event: {
111
+ $type: 'com.atproto.admin.defs#modEventComment',
112
+ comment: 'X',
113
+ },
114
+ subject: alicesAccount,
115
+ createdBy: 'did:plc:moderator',
116
+ }),
117
+ emitModerationEvent({
118
+ event: {
119
+ $type: 'com.atproto.admin.defs#modEventEscalate',
120
+ comment: 'X',
121
+ },
122
+ subject: alicesAccount,
123
+ createdBy: 'did:plc:moderator',
124
+ }),
125
+ ])
126
+ const [allEvents, reportEvents] = await Promise.all([
127
+ queryModerationEvents({
128
+ subject: sc.dids.alice,
129
+ }),
130
+ queryModerationEvents({
131
+ subject: sc.dids.alice,
132
+ types: ['com.atproto.admin.defs#modEventReport'],
133
+ }),
134
+ ])
135
+
136
+ expect(allEvents.data.events.length).toBeGreaterThan(
137
+ reportEvents.data.events.length,
138
+ )
139
+ expect(
140
+ [...new Set(reportEvents.data.events.map((e) => e.event.$type))].length,
141
+ ).toEqual(1)
142
+
143
+ expect(
144
+ [...new Set(allEvents.data.events.map((e) => e.event.$type))].length,
145
+ ).toEqual(3)
146
+ })
147
+
148
+ it('returns events for all content by user', async () => {
149
+ const [forAccount, forPost] = await Promise.all([
150
+ queryModerationEvents({
151
+ subject: sc.dids.bob,
152
+ includeAllUserRecords: true,
153
+ }),
154
+ queryModerationEvents({
155
+ subject: sc.posts[sc.dids.bob][0].ref.uriStr,
156
+ includeAllUserRecords: true,
157
+ }),
158
+ ])
159
+
160
+ expect(forAccount.data.events.length).toEqual(forPost.data.events.length)
161
+ // Save events are returned from both requests
162
+ expect(forPost.data.events.map(({ id }) => id).sort()).toEqual(
163
+ forAccount.data.events.map(({ id }) => id).sort(),
164
+ )
165
+ })
166
+
167
+ it('returns paginated list of events with cursor', async () => {
168
+ const allEvents = await queryModerationEvents({
169
+ subject: sc.dids.bob,
170
+ includeAllUserRecords: true,
171
+ })
172
+
173
+ const getPaginatedEvents = async (
174
+ sortDirection: 'asc' | 'desc' = 'desc',
175
+ ) => {
176
+ let defaultCursor: undefined | string = undefined
177
+ const events: ComAtprotoAdminDefs.ModEventView[] = []
178
+ let count = 0
179
+ do {
180
+ // get 1 event at a time and check we get all events
181
+ const { data } = await queryModerationEvents({
182
+ limit: 1,
183
+ subject: sc.dids.bob,
184
+ includeAllUserRecords: true,
185
+ cursor: defaultCursor,
186
+ sortDirection,
187
+ })
188
+ events.push(...data.events)
189
+ defaultCursor = data.cursor
190
+ count++
191
+ // The count is a circuit breaker to prevent infinite loop in case of failing test
192
+ } while (defaultCursor && count < 10)
193
+
194
+ return events
195
+ }
196
+
197
+ const defaultEvents = await getPaginatedEvents()
198
+ const reversedEvents = await getPaginatedEvents('asc')
199
+
200
+ expect(allEvents.data.events.length).toEqual(4)
201
+ expect(defaultEvents.length).toEqual(allEvents.data.events.length)
202
+ expect(reversedEvents.length).toEqual(allEvents.data.events.length)
203
+ expect(reversedEvents[0].id).toEqual(defaultEvents[3].id)
204
+ })
205
+ })
206
+
207
+ describe('get event', () => {
208
+ it('gets an event by specific id', async () => {
209
+ const { data } = await pdsAgent.api.com.atproto.admin.getModerationEvent(
210
+ {
211
+ id: 1,
212
+ },
213
+ {
214
+ headers: network.bsky.adminAuthHeaders('moderator'),
215
+ },
216
+ )
217
+
218
+ expect(forSnapshot(data)).toMatchSnapshot()
219
+ })
220
+ })
221
+ })
@@ -0,0 +1,145 @@
1
+ import { TestNetwork, SeedClient } from '@atproto/dev-env'
2
+ import AtpAgent, {
3
+ ComAtprotoAdminDefs,
4
+ ComAtprotoAdminQueryModerationStatuses,
5
+ } from '@atproto/api'
6
+ import { forSnapshot } from '../_util'
7
+ import basicSeed from '../seeds/basic'
8
+ import {
9
+ REASONMISLEADING,
10
+ REASONSPAM,
11
+ } from '../../src/lexicon/types/com/atproto/moderation/defs'
12
+
13
+ describe('moderation-statuses', () => {
14
+ let network: TestNetwork
15
+ let agent: AtpAgent
16
+ let pdsAgent: AtpAgent
17
+ let sc: SeedClient
18
+
19
+ const emitModerationEvent = async (eventData) => {
20
+ return pdsAgent.api.com.atproto.admin.emitModerationEvent(eventData, {
21
+ encoding: 'application/json',
22
+ headers: network.bsky.adminAuthHeaders('moderator'),
23
+ })
24
+ }
25
+
26
+ const queryModerationStatuses = (statusQuery) =>
27
+ agent.api.com.atproto.admin.queryModerationStatuses(statusQuery, {
28
+ headers: network.bsky.adminAuthHeaders('moderator'),
29
+ })
30
+
31
+ const seedEvents = async () => {
32
+ const bobsAccount = {
33
+ $type: 'com.atproto.admin.defs#repoRef',
34
+ did: sc.dids.bob,
35
+ }
36
+ const carlasAccount = {
37
+ $type: 'com.atproto.admin.defs#repoRef',
38
+ did: sc.dids.alice,
39
+ }
40
+ const bobsPost = {
41
+ $type: 'com.atproto.repo.strongRef',
42
+ uri: sc.posts[sc.dids.bob][1].ref.uriStr,
43
+ cid: sc.posts[sc.dids.bob][1].ref.cidStr,
44
+ }
45
+ const alicesPost = {
46
+ $type: 'com.atproto.repo.strongRef',
47
+ uri: sc.posts[sc.dids.alice][1].ref.uriStr,
48
+ cid: sc.posts[sc.dids.alice][1].ref.cidStr,
49
+ }
50
+
51
+ for (let i = 0; i < 4; i++) {
52
+ await emitModerationEvent({
53
+ event: {
54
+ $type: 'com.atproto.admin.defs#modEventReport',
55
+ reportType: i % 2 ? REASONSPAM : REASONMISLEADING,
56
+ comment: 'X',
57
+ },
58
+ // Report bob's account by alice and vice versa
59
+ subject: i % 2 ? bobsAccount : carlasAccount,
60
+ createdBy: i % 2 ? sc.dids.alice : sc.dids.bob,
61
+ })
62
+ await emitModerationEvent({
63
+ event: {
64
+ $type: 'com.atproto.admin.defs#modEventReport',
65
+ reportType: REASONSPAM,
66
+ comment: 'X',
67
+ },
68
+ // Report bob's post by alice and vice versa
69
+ subject: i % 2 ? bobsPost : alicesPost,
70
+ createdBy: i % 2 ? sc.dids.alice : sc.dids.bob,
71
+ })
72
+ }
73
+ }
74
+
75
+ beforeAll(async () => {
76
+ network = await TestNetwork.create({
77
+ dbPostgresSchema: 'bsky_moderation_statuses',
78
+ })
79
+ agent = network.bsky.getClient()
80
+ pdsAgent = network.pds.getClient()
81
+ sc = network.getSeedClient()
82
+ await basicSeed(sc)
83
+ await network.processAll()
84
+ await seedEvents()
85
+ })
86
+
87
+ afterAll(async () => {
88
+ await network.close()
89
+ })
90
+
91
+ describe('query statuses', () => {
92
+ it('returns statuses for subjects that received moderation events', async () => {
93
+ const response = await queryModerationStatuses({})
94
+
95
+ expect(forSnapshot(response.data.subjectStatuses)).toMatchSnapshot()
96
+ })
97
+
98
+ it('returns paginated statuses', async () => {
99
+ // We know there will be exactly 4 statuses in db
100
+ const getPaginatedStatuses = async (
101
+ params: ComAtprotoAdminQueryModerationStatuses.QueryParams,
102
+ ) => {
103
+ let cursor: string | undefined = ''
104
+ const statuses: ComAtprotoAdminDefs.SubjectStatusView[] = []
105
+ let count = 0
106
+ do {
107
+ const results = await queryModerationStatuses({
108
+ limit: 1,
109
+ cursor,
110
+ ...params,
111
+ })
112
+ cursor = results.data.cursor
113
+ statuses.push(...results.data.subjectStatuses)
114
+ count++
115
+ // The count is just a brake-check to prevent infinite loop
116
+ } while (cursor && count < 10)
117
+
118
+ return statuses
119
+ }
120
+
121
+ const list = await getPaginatedStatuses({})
122
+ expect(list[0].id).toEqual(4)
123
+ expect(list[list.length - 1].id).toEqual(1)
124
+
125
+ await emitModerationEvent({
126
+ subject: list[1].subject,
127
+ event: {
128
+ $type: 'com.atproto.admin.defs#modEventAcknowledge',
129
+ comment: 'X',
130
+ },
131
+ createdBy: sc.dids.bob,
132
+ })
133
+
134
+ const listReviewedFirst = await getPaginatedStatuses({
135
+ sortDirection: 'desc',
136
+ sortField: 'lastReviewedAt',
137
+ })
138
+
139
+ // Verify that the item that was recently reviewed comes up first when sorted descendingly
140
+ // while the result set always contains same number of items regardless of sorting
141
+ expect(listReviewedFirst[0].id).toEqual(list[1].id)
142
+ expect(listReviewedFirst.length).toEqual(list.length)
143
+ })
144
+ })
145
+ })