@atproto/bsky 0.0.10 → 0.0.12

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 (173) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/dist/api/com/atproto/admin/util.d.ts +5 -0
  3. package/dist/config.d.ts +2 -0
  4. package/dist/context.d.ts +8 -0
  5. package/dist/db/index.js +51 -2
  6. package/dist/db/index.js.map +3 -3
  7. package/dist/db/migrations/20230929T192920807Z-record-cursor-indexes.d.ts +3 -0
  8. package/dist/db/migrations/index.d.ts +1 -0
  9. package/dist/did-cache.d.ts +2 -2
  10. package/dist/index.d.ts +2 -0
  11. package/dist/index.js +1818 -580
  12. package/dist/index.js.map +3 -3
  13. package/dist/lexicon/index.d.ts +16 -0
  14. package/dist/lexicon/lexicons.d.ts +330 -3
  15. package/dist/lexicon/types/app/bsky/actor/defs.d.ts +1 -0
  16. package/dist/lexicon/types/com/atproto/admin/defs.d.ts +28 -0
  17. package/dist/lexicon/types/com/atproto/admin/getAccountInfo.d.ts +29 -0
  18. package/dist/lexicon/types/com/atproto/admin/getSubjectStatus.d.ts +39 -0
  19. package/dist/lexicon/types/com/atproto/admin/searchRepos.d.ts +0 -1
  20. package/dist/lexicon/types/com/atproto/admin/updateSubjectStatus.d.ts +46 -0
  21. package/dist/lexicon/types/com/atproto/server/confirmEmail.d.ts +27 -0
  22. package/dist/lexicon/types/com/atproto/server/createAccount.d.ts +2 -0
  23. package/dist/lexicon/types/com/atproto/server/createSession.d.ts +2 -0
  24. package/dist/lexicon/types/com/atproto/server/getSession.d.ts +1 -0
  25. package/dist/lexicon/types/com/atproto/server/refreshSession.d.ts +1 -0
  26. package/dist/lexicon/types/com/atproto/server/requestEmailConfirmation.d.ts +19 -0
  27. package/dist/lexicon/types/com/atproto/server/requestEmailUpdate.d.ts +30 -0
  28. package/dist/lexicon/types/com/atproto/server/reserveSigningKey.d.ts +30 -0
  29. package/dist/lexicon/types/com/atproto/server/updateEmail.d.ts +27 -0
  30. package/dist/lexicon/types/com/atproto/sync/listRepos.d.ts +1 -0
  31. package/dist/services/actor/index.d.ts +2 -2
  32. package/dist/services/actor/types.d.ts +1 -0
  33. package/dist/services/graph/index.d.ts +2 -0
  34. package/dist/services/moderation/index.d.ts +13 -3
  35. package/dist/services/util/search.d.ts +3 -3
  36. package/package.json +13 -14
  37. package/src/api/app/bsky/actor/searchActors.ts +36 -22
  38. package/src/api/app/bsky/actor/searchActorsTypeahead.ts +24 -17
  39. package/src/api/app/bsky/feed/getAuthorFeed.ts +2 -2
  40. package/src/api/app/bsky/feed/getPostThread.ts +2 -2
  41. package/src/api/app/bsky/graph/getSuggestedFollowsByActor.ts +1 -0
  42. package/src/api/app/bsky/notification/listNotifications.ts +33 -22
  43. package/src/api/com/atproto/admin/getModerationAction.ts +28 -2
  44. package/src/api/com/atproto/admin/getModerationReport.ts +27 -2
  45. package/src/api/com/atproto/admin/getRecord.ts +14 -2
  46. package/src/api/com/atproto/admin/getRepo.ts +13 -2
  47. package/src/api/com/atproto/admin/reverseModerationAction.ts +31 -5
  48. package/src/api/com/atproto/admin/searchRepos.ts +6 -12
  49. package/src/api/com/atproto/admin/takeModerationAction.ts +41 -7
  50. package/src/api/com/atproto/admin/util.ts +50 -0
  51. package/src/api/well-known.ts +8 -0
  52. package/src/auth.ts +12 -5
  53. package/src/auto-moderator/index.ts +1 -0
  54. package/src/config.ts +7 -0
  55. package/src/context.ts +30 -0
  56. package/src/db/migrations/20230929T192920807Z-record-cursor-indexes.ts +40 -0
  57. package/src/db/migrations/index.ts +1 -0
  58. package/src/did-cache.ts +29 -14
  59. package/src/feed-gen/with-friends.ts +2 -2
  60. package/src/index.ts +9 -1
  61. package/src/indexer/subscription.ts +1 -21
  62. package/src/lexicon/index.ts +96 -0
  63. package/src/lexicon/lexicons.ts +368 -4
  64. package/src/lexicon/types/app/bsky/actor/defs.ts +1 -0
  65. package/src/lexicon/types/com/atproto/admin/defs.ts +61 -0
  66. package/src/lexicon/types/com/atproto/admin/getAccountInfo.ts +41 -0
  67. package/src/lexicon/types/com/atproto/admin/getSubjectStatus.ts +54 -0
  68. package/src/lexicon/types/com/atproto/admin/searchRepos.ts +0 -1
  69. package/src/lexicon/types/com/atproto/admin/updateSubjectStatus.ts +61 -0
  70. package/src/lexicon/types/com/atproto/server/confirmEmail.ts +40 -0
  71. package/src/lexicon/types/com/atproto/server/createAccount.ts +2 -0
  72. package/src/lexicon/types/com/atproto/server/createSession.ts +2 -0
  73. package/src/lexicon/types/com/atproto/server/getSession.ts +1 -0
  74. package/src/lexicon/types/com/atproto/server/refreshSession.ts +1 -0
  75. package/src/lexicon/types/com/atproto/server/requestEmailConfirmation.ts +31 -0
  76. package/src/lexicon/types/com/atproto/server/requestEmailUpdate.ts +43 -0
  77. package/src/lexicon/types/com/atproto/server/reserveSigningKey.ts +44 -0
  78. package/src/lexicon/types/com/atproto/server/updateEmail.ts +41 -0
  79. package/src/lexicon/types/com/atproto/sync/listRepos.ts +1 -0
  80. package/src/logger.ts +8 -0
  81. package/src/services/actor/index.ts +16 -10
  82. package/src/services/actor/types.ts +1 -0
  83. package/src/services/actor/views.ts +26 -8
  84. package/src/services/graph/index.ts +26 -7
  85. package/src/services/indexing/index.ts +15 -17
  86. package/src/services/moderation/index.ts +94 -14
  87. package/src/services/moderation/views.ts +1 -0
  88. package/src/services/util/search.ts +24 -23
  89. package/tests/__snapshots__/feed-generation.test.ts.snap +12 -12
  90. package/tests/__snapshots__/indexing.test.ts.snap +4 -4
  91. package/tests/admin/__snapshots__/get-moderation-action.test.ts.snap +172 -0
  92. package/tests/admin/__snapshots__/get-moderation-actions.test.ts.snap +178 -0
  93. package/tests/admin/__snapshots__/get-moderation-report.test.ts.snap +177 -0
  94. package/tests/admin/__snapshots__/get-moderation-reports.test.ts.snap +307 -0
  95. package/tests/admin/__snapshots__/get-record.test.ts.snap +275 -0
  96. package/tests/admin/__snapshots__/get-repo.test.ts.snap +103 -0
  97. package/tests/admin/get-moderation-action.test.ts +100 -0
  98. package/tests/admin/get-moderation-actions.test.ts +164 -0
  99. package/tests/admin/get-moderation-report.test.ts +100 -0
  100. package/tests/admin/get-moderation-reports.test.ts +332 -0
  101. package/tests/admin/get-record.test.ts +115 -0
  102. package/tests/admin/get-repo.test.ts +101 -0
  103. package/tests/{moderation.test.ts → admin/moderation.test.ts} +107 -9
  104. package/tests/admin/repo-search.test.ts +124 -0
  105. package/tests/algos/hot-classic.test.ts +3 -5
  106. package/tests/algos/whats-hot.test.ts +3 -5
  107. package/tests/algos/with-friends.test.ts +2 -4
  108. package/tests/auth.test.ts +64 -0
  109. package/tests/auto-moderator/fuzzy-matcher.test.ts +2 -3
  110. package/tests/auto-moderator/labeler.test.ts +5 -7
  111. package/tests/auto-moderator/takedowns.test.ts +11 -12
  112. package/tests/blob-resolver.test.ts +1 -3
  113. package/tests/did-cache.test.ts +2 -5
  114. package/tests/feed-generation.test.ts +8 -6
  115. package/tests/handle-invalidation.test.ts +2 -3
  116. package/tests/image/server.test.ts +1 -4
  117. package/tests/image/sharp.test.ts +1 -1
  118. package/tests/indexing.test.ts +4 -4
  119. package/tests/notification-server.test.ts +2 -3
  120. package/tests/pipeline/backpressure.test.ts +2 -3
  121. package/tests/pipeline/reingest.test.ts +7 -4
  122. package/tests/pipeline/repartition.test.ts +2 -3
  123. package/tests/reprocessing.test.ts +2 -6
  124. package/tests/seeds/basic.ts +4 -4
  125. package/tests/seeds/follows.ts +1 -1
  126. package/tests/seeds/likes.ts +1 -1
  127. package/tests/seeds/reposts.ts +1 -1
  128. package/tests/seeds/users-bulk.ts +1 -1
  129. package/tests/seeds/users.ts +1 -1
  130. package/tests/server.test.ts +1 -3
  131. package/tests/subscription/repo.test.ts +2 -4
  132. package/tests/views/__snapshots__/author-feed.test.ts.snap +24 -24
  133. package/tests/views/__snapshots__/block-lists.test.ts.snap +42 -7
  134. package/tests/views/__snapshots__/blocks.test.ts.snap +2 -2
  135. package/tests/views/__snapshots__/list-feed.test.ts.snap +6 -6
  136. package/tests/views/__snapshots__/mute-lists.test.ts.snap +15 -4
  137. package/tests/views/__snapshots__/mutes.test.ts.snap +2 -2
  138. package/tests/views/__snapshots__/notifications.test.ts.snap +2 -2
  139. package/tests/views/__snapshots__/posts.test.ts.snap +8 -8
  140. package/tests/views/__snapshots__/thread.test.ts.snap +10 -10
  141. package/tests/views/__snapshots__/timeline.test.ts.snap +58 -58
  142. package/tests/views/actor-likes.test.ts +2 -3
  143. package/tests/views/actor-search.test.ts +5 -5
  144. package/tests/views/admin/repo-search.test.ts +2 -4
  145. package/tests/views/author-feed.test.ts +2 -4
  146. package/tests/views/block-lists.test.ts +34 -7
  147. package/tests/views/blocks.test.ts +6 -3
  148. package/tests/views/follows.test.ts +2 -4
  149. package/tests/views/likes.test.ts +2 -5
  150. package/tests/views/list-feed.test.ts +2 -4
  151. package/tests/views/mute-lists.test.ts +23 -5
  152. package/tests/views/mutes.test.ts +2 -5
  153. package/tests/views/notifications.test.ts +2 -4
  154. package/tests/views/posts.test.ts +2 -5
  155. package/tests/views/profile.test.ts +4 -5
  156. package/tests/views/reposts.test.ts +2 -4
  157. package/tests/views/suggested-follows.test.ts +2 -3
  158. package/tests/views/suggestions.test.ts +2 -4
  159. package/tests/views/thread.test.ts +2 -4
  160. package/tests/views/threadgating.test.ts +2 -3
  161. package/tests/views/timeline.test.ts +2 -4
  162. package/dist/env.d.ts +0 -1
  163. package/example.dev.env +0 -5
  164. package/src/env.ts +0 -9
  165. package/tests/seeds/client.ts +0 -466
  166. /package/tests/{__snapshots__ → admin/__snapshots__}/moderation.test.ts.snap +0 -0
  167. /package/tests/{image/fixtures → sample-img}/at.png +0 -0
  168. /package/tests/{image/fixtures → sample-img}/hd-key.jpg +0 -0
  169. /package/tests/{image/fixtures → sample-img}/key-alt.jpg +0 -0
  170. /package/tests/{image/fixtures → sample-img}/key-landscape-large.jpg +0 -0
  171. /package/tests/{image/fixtures → sample-img}/key-landscape-small.jpg +0 -0
  172. /package/tests/{image/fixtures → sample-img}/key-portrait-large.jpg +0 -0
  173. /package/tests/{image/fixtures → sample-img}/key-portrait-small.jpg +0 -0
@@ -0,0 +1,100 @@
1
+ import { SeedClient, TestNetwork } from '@atproto/dev-env'
2
+ import AtpAgent from '@atproto/api'
3
+ import {
4
+ FLAG,
5
+ TAKEDOWN,
6
+ } from '@atproto/api/src/client/types/com/atproto/admin/defs'
7
+ import {
8
+ REASONOTHER,
9
+ REASONSPAM,
10
+ } from '../../src/lexicon/types/com/atproto/moderation/defs'
11
+ import { forSnapshot } from '../_util'
12
+ import basicSeed from '../seeds/basic'
13
+
14
+ describe('admin get moderation action view', () => {
15
+ let network: TestNetwork
16
+ let agent: AtpAgent
17
+ let sc: SeedClient
18
+
19
+ beforeAll(async () => {
20
+ network = await TestNetwork.create({
21
+ dbPostgresSchema: 'views_admin_get_moderation_report',
22
+ })
23
+ agent = network.pds.getClient()
24
+ sc = network.getSeedClient()
25
+ await basicSeed(sc)
26
+ })
27
+
28
+ afterAll(async () => {
29
+ await network.close()
30
+ })
31
+
32
+ beforeAll(async () => {
33
+ const reportRepo = await sc.createReport({
34
+ reportedBy: sc.dids.bob,
35
+ reasonType: REASONSPAM,
36
+ subject: {
37
+ $type: 'com.atproto.admin.defs#repoRef',
38
+ did: sc.dids.alice,
39
+ },
40
+ })
41
+ const reportRecord = await sc.createReport({
42
+ reportedBy: sc.dids.carol,
43
+ reasonType: REASONOTHER,
44
+ reason: 'defamation',
45
+ subject: {
46
+ $type: 'com.atproto.repo.strongRef',
47
+ uri: sc.posts[sc.dids.alice][0].ref.uriStr,
48
+ cid: sc.posts[sc.dids.alice][0].ref.cidStr,
49
+ },
50
+ })
51
+ const flagRepo = await sc.takeModerationAction({
52
+ action: FLAG,
53
+ subject: {
54
+ $type: 'com.atproto.admin.defs#repoRef',
55
+ did: sc.dids.alice,
56
+ },
57
+ })
58
+ const takedownRecord = await sc.takeModerationAction({
59
+ action: TAKEDOWN,
60
+ subject: {
61
+ $type: 'com.atproto.repo.strongRef',
62
+ uri: sc.posts[sc.dids.alice][0].ref.uriStr,
63
+ cid: sc.posts[sc.dids.alice][0].ref.cidStr,
64
+ },
65
+ })
66
+ await sc.resolveReports({
67
+ actionId: flagRepo.id,
68
+ reportIds: [reportRepo.id, reportRecord.id],
69
+ })
70
+ await sc.resolveReports({
71
+ actionId: takedownRecord.id,
72
+ reportIds: [reportRecord.id],
73
+ })
74
+ await sc.reverseModerationAction({ id: flagRepo.id })
75
+ })
76
+
77
+ it('gets moderation report for a repo.', async () => {
78
+ const result = await agent.api.com.atproto.admin.getModerationReport(
79
+ { id: 1 },
80
+ { headers: network.pds.adminAuthHeaders() },
81
+ )
82
+ expect(forSnapshot(result.data)).toMatchSnapshot()
83
+ })
84
+
85
+ it('gets moderation report for a record.', async () => {
86
+ const result = await agent.api.com.atproto.admin.getModerationReport(
87
+ { id: 2 },
88
+ { headers: network.pds.adminAuthHeaders() },
89
+ )
90
+ expect(forSnapshot(result.data)).toMatchSnapshot()
91
+ })
92
+
93
+ it('fails when moderation report does not exist.', async () => {
94
+ const promise = agent.api.com.atproto.admin.getModerationReport(
95
+ { id: 100 },
96
+ { headers: network.pds.adminAuthHeaders() },
97
+ )
98
+ await expect(promise).rejects.toThrow('Report not found')
99
+ })
100
+ })
@@ -0,0 +1,332 @@
1
+ import { SeedClient, TestNetwork } from '@atproto/dev-env'
2
+ import AtpAgent from '@atproto/api'
3
+ import {
4
+ ACKNOWLEDGE,
5
+ FLAG,
6
+ TAKEDOWN,
7
+ } from '@atproto/api/src/client/types/com/atproto/admin/defs'
8
+ import {
9
+ REASONOTHER,
10
+ REASONSPAM,
11
+ } from '../../src/lexicon/types/com/atproto/moderation/defs'
12
+ import { forSnapshot, paginateAll } from '../_util'
13
+ import basicSeed from '../seeds/basic'
14
+
15
+ describe('admin get moderation reports view', () => {
16
+ let network: TestNetwork
17
+ let agent: AtpAgent
18
+ let sc: SeedClient
19
+
20
+ beforeAll(async () => {
21
+ network = await TestNetwork.create({
22
+ dbPostgresSchema: 'views_admin_get_moderation_reports',
23
+ })
24
+ agent = network.pds.getClient()
25
+ sc = network.getSeedClient()
26
+ await basicSeed(sc)
27
+ })
28
+
29
+ afterAll(async () => {
30
+ await network.close()
31
+ })
32
+
33
+ beforeAll(async () => {
34
+ const oneIn = (n) => (_, i) => i % n === 0
35
+ const getAction = (i) => [FLAG, ACKNOWLEDGE, TAKEDOWN][i % 3]
36
+ const getReasonType = (i) => [REASONOTHER, REASONSPAM][i % 2]
37
+ const getReportedByDid = (i) => [sc.dids.alice, sc.dids.carol][i % 2]
38
+ const posts = Object.values(sc.posts)
39
+ .flatMap((x) => x)
40
+ .filter(oneIn(2))
41
+ const dids = Object.values(sc.dids).filter(oneIn(2))
42
+ const recordReports: Awaited<ReturnType<typeof sc.createReport>>[] = []
43
+ for (let i = 0; i < posts.length; ++i) {
44
+ const post = posts[i]
45
+ recordReports.push(
46
+ await sc.createReport({
47
+ reasonType: getReasonType(i),
48
+ reportedBy: getReportedByDid(i),
49
+ subject: {
50
+ $type: 'com.atproto.repo.strongRef',
51
+ uri: post.ref.uriStr,
52
+ cid: post.ref.cidStr,
53
+ },
54
+ }),
55
+ )
56
+ }
57
+ const repoReports: Awaited<ReturnType<typeof sc.createReport>>[] = []
58
+ for (let i = 0; i < dids.length; ++i) {
59
+ const did = dids[i]
60
+ repoReports.push(
61
+ await sc.createReport({
62
+ reasonType: getReasonType(i),
63
+ reportedBy: getReportedByDid(i),
64
+ subject: {
65
+ $type: 'com.atproto.admin.defs#repoRef',
66
+ did,
67
+ },
68
+ }),
69
+ )
70
+ }
71
+ for (let i = 0; i < recordReports.length; ++i) {
72
+ const report = recordReports[i]
73
+ const ab = oneIn(2)(report, i)
74
+ const action = await sc.takeModerationAction({
75
+ action: getAction(i),
76
+ subject: {
77
+ $type: 'com.atproto.repo.strongRef',
78
+ uri: report.subject.uri,
79
+ cid: report.subject.cid,
80
+ },
81
+ createdBy: `did:example:admin${i}`,
82
+ })
83
+ if (ab) {
84
+ await sc.resolveReports({
85
+ actionId: action.id,
86
+ reportIds: [report.id],
87
+ })
88
+ } else {
89
+ await sc.reverseModerationAction({
90
+ id: action.id,
91
+ })
92
+ }
93
+ }
94
+ for (let i = 0; i < repoReports.length; ++i) {
95
+ const report = repoReports[i]
96
+ const ab = oneIn(2)(report, i)
97
+ const action = await sc.takeModerationAction({
98
+ action: getAction(i),
99
+ subject: {
100
+ $type: 'com.atproto.admin.defs#repoRef',
101
+ did: report.subject.did,
102
+ },
103
+ })
104
+ if (ab) {
105
+ await sc.resolveReports({
106
+ actionId: action.id,
107
+ reportIds: [report.id],
108
+ })
109
+ } else {
110
+ await sc.reverseModerationAction({
111
+ id: action.id,
112
+ })
113
+ }
114
+ }
115
+ })
116
+
117
+ it('ignores subjects when specified.', async () => {
118
+ // Get all reports and then make another request with a filter to ignore some subject dids
119
+ // and assert that the reports for those subject dids are ignored in the result set
120
+ const getDids = (reportsResponse) =>
121
+ reportsResponse.data.reports
122
+ .map((report) => report.subject.did)
123
+ // Not all reports contain a did so we're discarding the undefined values in the mapped array
124
+ .filter(Boolean)
125
+
126
+ const allReports = await agent.api.com.atproto.admin.getModerationReports(
127
+ {},
128
+ { headers: network.pds.adminAuthHeaders() },
129
+ )
130
+
131
+ const ignoreSubjects = getDids(allReports).slice(0, 2)
132
+
133
+ const filteredReportsByDid =
134
+ await agent.api.com.atproto.admin.getModerationReports(
135
+ { ignoreSubjects },
136
+ { headers: network.pds.adminAuthHeaders() },
137
+ )
138
+
139
+ // Validate that when ignored by DID, all reports for that DID is ignored
140
+ getDids(filteredReportsByDid).forEach((resultDid) =>
141
+ expect(ignoreSubjects).not.toContain(resultDid),
142
+ )
143
+
144
+ const ignoredAtUriSubjects: string[] = [
145
+ `${
146
+ allReports.data.reports.find(({ subject }) => !!subject.uri)?.subject
147
+ ?.uri
148
+ }`,
149
+ ]
150
+ const filteredReportsByAtUri =
151
+ await agent.api.com.atproto.admin.getModerationReports(
152
+ {
153
+ ignoreSubjects: ignoredAtUriSubjects,
154
+ },
155
+ { headers: network.pds.adminAuthHeaders() },
156
+ )
157
+
158
+ // Validate that when ignored by at uri, only the reports for that at uri is ignored
159
+ expect(filteredReportsByAtUri.data.reports.length).toEqual(
160
+ allReports.data.reports.length - 1,
161
+ )
162
+ expect(
163
+ filteredReportsByAtUri.data.reports
164
+ .map(({ subject }) => subject.uri)
165
+ .filter(Boolean),
166
+ ).not.toContain(ignoredAtUriSubjects[0])
167
+ })
168
+
169
+ it('gets all moderation reports.', async () => {
170
+ const result = await agent.api.com.atproto.admin.getModerationReports(
171
+ {},
172
+ { headers: network.pds.adminAuthHeaders() },
173
+ )
174
+ expect(forSnapshot(result.data.reports)).toMatchSnapshot()
175
+ })
176
+
177
+ it('gets all moderation reports for a repo.', async () => {
178
+ const result = await agent.api.com.atproto.admin.getModerationReports(
179
+ { subject: Object.values(sc.dids)[0] },
180
+ { headers: network.pds.adminAuthHeaders() },
181
+ )
182
+ expect(forSnapshot(result.data.reports)).toMatchSnapshot()
183
+ })
184
+
185
+ it('gets all moderation reports for a record.', async () => {
186
+ const result = await agent.api.com.atproto.admin.getModerationReports(
187
+ { subject: Object.values(sc.posts)[0][0].ref.uriStr },
188
+ { headers: network.pds.adminAuthHeaders() },
189
+ )
190
+ expect(forSnapshot(result.data.reports)).toMatchSnapshot()
191
+ })
192
+
193
+ it('gets all resolved/unresolved moderation reports.', async () => {
194
+ const resolved = await agent.api.com.atproto.admin.getModerationReports(
195
+ { resolved: true },
196
+ { headers: network.pds.adminAuthHeaders() },
197
+ )
198
+ expect(forSnapshot(resolved.data.reports)).toMatchSnapshot()
199
+ const unresolved = await agent.api.com.atproto.admin.getModerationReports(
200
+ { resolved: false },
201
+ { headers: network.pds.adminAuthHeaders() },
202
+ )
203
+ expect(forSnapshot(unresolved.data.reports)).toMatchSnapshot()
204
+ })
205
+
206
+ it('allows reverting the order of reports.', async () => {
207
+ const [
208
+ {
209
+ data: { reports: reverseList },
210
+ },
211
+ {
212
+ data: { reports: defaultList },
213
+ },
214
+ ] = await Promise.all([
215
+ agent.api.com.atproto.admin.getModerationReports(
216
+ { reverse: true },
217
+ { headers: network.pds.adminAuthHeaders() },
218
+ ),
219
+ agent.api.com.atproto.admin.getModerationReports(
220
+ {},
221
+ { headers: network.pds.adminAuthHeaders() },
222
+ ),
223
+ ])
224
+
225
+ expect(defaultList[0].id).toEqual(reverseList[reverseList.length - 1].id)
226
+ expect(defaultList[defaultList.length - 1].id).toEqual(reverseList[0].id)
227
+ })
228
+
229
+ it('gets all moderation reports by active resolution action type.', async () => {
230
+ const reportsWithTakedown =
231
+ await agent.api.com.atproto.admin.getModerationReports(
232
+ { actionType: TAKEDOWN },
233
+ { headers: network.pds.adminAuthHeaders() },
234
+ )
235
+ expect(forSnapshot(reportsWithTakedown.data.reports)).toMatchSnapshot()
236
+ })
237
+
238
+ it('gets all moderation reports actioned by a certain moderator.', async () => {
239
+ const adminDidOne = 'did:example:admin0'
240
+ const adminDidTwo = 'did:example:admin2'
241
+ const [actionedByAdminOne, actionedByAdminTwo] = await Promise.all([
242
+ agent.api.com.atproto.admin.getModerationReports(
243
+ { actionedBy: adminDidOne },
244
+ { headers: network.pds.adminAuthHeaders() },
245
+ ),
246
+ agent.api.com.atproto.admin.getModerationReports(
247
+ { actionedBy: adminDidTwo },
248
+ { headers: network.pds.adminAuthHeaders() },
249
+ ),
250
+ ])
251
+ const [fullReportOne, fullReportTwo] = await Promise.all([
252
+ agent.api.com.atproto.admin.getModerationReport(
253
+ { id: actionedByAdminOne.data.reports[0].id },
254
+ { headers: network.pds.adminAuthHeaders() },
255
+ ),
256
+ agent.api.com.atproto.admin.getModerationReport(
257
+ { id: actionedByAdminTwo.data.reports[0].id },
258
+ { headers: network.pds.adminAuthHeaders() },
259
+ ),
260
+ ])
261
+
262
+ expect(forSnapshot(actionedByAdminOne.data.reports)).toMatchSnapshot()
263
+ expect(fullReportOne.data.resolvedByActions[0].createdBy).toEqual(
264
+ adminDidOne,
265
+ )
266
+ expect(forSnapshot(actionedByAdminTwo.data.reports)).toMatchSnapshot()
267
+ expect(fullReportTwo.data.resolvedByActions[0].createdBy).toEqual(
268
+ adminDidTwo,
269
+ )
270
+ })
271
+
272
+ it('paginates.', async () => {
273
+ const results = (results) => results.flatMap((res) => res.reports)
274
+ const paginator = async (cursor?: string) => {
275
+ const res = await agent.api.com.atproto.admin.getModerationReports(
276
+ { cursor, limit: 3 },
277
+ { headers: network.pds.adminAuthHeaders() },
278
+ )
279
+ return res.data
280
+ }
281
+
282
+ const paginatedAll = await paginateAll(paginator)
283
+ paginatedAll.forEach((res) =>
284
+ expect(res.reports.length).toBeLessThanOrEqual(3),
285
+ )
286
+
287
+ const full = await agent.api.com.atproto.admin.getModerationReports(
288
+ {},
289
+ { headers: network.pds.adminAuthHeaders() },
290
+ )
291
+
292
+ expect(full.data.reports.length).toEqual(6)
293
+ expect(results(paginatedAll)).toEqual(results([full.data]))
294
+ })
295
+
296
+ it('paginates reverted list of reports.', async () => {
297
+ const paginator =
298
+ (reverse = false) =>
299
+ async (cursor?: string) => {
300
+ const res = await agent.api.com.atproto.admin.getModerationReports(
301
+ { cursor, limit: 3, reverse },
302
+ { headers: network.pds.adminAuthHeaders() },
303
+ )
304
+ return res.data
305
+ }
306
+
307
+ const [reverseResponse, defaultResponse] = await Promise.all([
308
+ paginateAll(paginator(true)),
309
+ paginateAll(paginator()),
310
+ ])
311
+
312
+ const reverseList = reverseResponse.flatMap((res) => res.reports)
313
+ const defaultList = defaultResponse.flatMap((res) => res.reports)
314
+
315
+ expect(defaultList[0].id).toEqual(reverseList[reverseList.length - 1].id)
316
+ expect(defaultList[defaultList.length - 1].id).toEqual(reverseList[0].id)
317
+ })
318
+
319
+ it('filters reports by reporter DID.', async () => {
320
+ const result = await agent.api.com.atproto.admin.getModerationReports(
321
+ { reporters: [sc.dids.alice] },
322
+ { headers: network.pds.adminAuthHeaders() },
323
+ )
324
+
325
+ const reporterDidsFromReports = [
326
+ ...new Set(result.data.reports.map(({ reportedBy }) => reportedBy)),
327
+ ]
328
+
329
+ expect(reporterDidsFromReports.length).toEqual(1)
330
+ expect(reporterDidsFromReports[0]).toEqual(sc.dids.alice)
331
+ })
332
+ })
@@ -0,0 +1,115 @@
1
+ import { SeedClient, TestNetwork } from '@atproto/dev-env'
2
+ import AtpAgent from '@atproto/api'
3
+ import { AtUri } from '@atproto/syntax'
4
+ import {
5
+ ACKNOWLEDGE,
6
+ TAKEDOWN,
7
+ } from '@atproto/api/src/client/types/com/atproto/admin/defs'
8
+ import {
9
+ REASONOTHER,
10
+ REASONSPAM,
11
+ } from '../../src/lexicon/types/com/atproto/moderation/defs'
12
+ import { forSnapshot } from '../_util'
13
+ import basicSeed from '../seeds/basic'
14
+
15
+ describe('admin get record view', () => {
16
+ let network: TestNetwork
17
+ let agent: AtpAgent
18
+ let sc: SeedClient
19
+
20
+ beforeAll(async () => {
21
+ network = await TestNetwork.create({
22
+ dbPostgresSchema: 'views_admin_get_record',
23
+ })
24
+ agent = network.pds.getClient()
25
+ sc = network.getSeedClient()
26
+ await basicSeed(sc)
27
+ })
28
+
29
+ afterAll(async () => {
30
+ await network.close()
31
+ })
32
+
33
+ beforeAll(async () => {
34
+ const acknowledge = await sc.takeModerationAction({
35
+ action: ACKNOWLEDGE,
36
+ subject: {
37
+ $type: 'com.atproto.repo.strongRef',
38
+ uri: sc.posts[sc.dids.alice][0].ref.uriStr,
39
+ cid: sc.posts[sc.dids.alice][0].ref.cidStr,
40
+ },
41
+ })
42
+ await sc.createReport({
43
+ reportedBy: sc.dids.bob,
44
+ reasonType: REASONSPAM,
45
+ subject: {
46
+ $type: 'com.atproto.repo.strongRef',
47
+ uri: sc.posts[sc.dids.alice][0].ref.uriStr,
48
+ cid: sc.posts[sc.dids.alice][0].ref.cidStr,
49
+ },
50
+ })
51
+ await sc.createReport({
52
+ reportedBy: sc.dids.carol,
53
+ reasonType: REASONOTHER,
54
+ reason: 'defamation',
55
+ subject: {
56
+ $type: 'com.atproto.repo.strongRef',
57
+ uri: sc.posts[sc.dids.alice][0].ref.uriStr,
58
+ cid: sc.posts[sc.dids.alice][0].ref.cidStr,
59
+ },
60
+ })
61
+ await sc.reverseModerationAction({ id: acknowledge.id })
62
+ await sc.takeModerationAction({
63
+ action: TAKEDOWN,
64
+ subject: {
65
+ $type: 'com.atproto.repo.strongRef',
66
+ uri: sc.posts[sc.dids.alice][0].ref.uriStr,
67
+ cid: sc.posts[sc.dids.alice][0].ref.cidStr,
68
+ },
69
+ })
70
+ })
71
+
72
+ it('gets a record by uri, even when taken down.', async () => {
73
+ const result = await agent.api.com.atproto.admin.getRecord(
74
+ { uri: sc.posts[sc.dids.alice][0].ref.uriStr },
75
+ { headers: network.pds.adminAuthHeaders() },
76
+ )
77
+ expect(forSnapshot(result.data)).toMatchSnapshot()
78
+ })
79
+
80
+ it('gets a record by uri and cid.', async () => {
81
+ const result = await agent.api.com.atproto.admin.getRecord(
82
+ {
83
+ uri: sc.posts[sc.dids.alice][0].ref.uriStr,
84
+ cid: sc.posts[sc.dids.alice][0].ref.cidStr,
85
+ },
86
+ { headers: network.pds.adminAuthHeaders() },
87
+ )
88
+ expect(forSnapshot(result.data)).toMatchSnapshot()
89
+ })
90
+
91
+ it('fails when record does not exist.', async () => {
92
+ const promise = agent.api.com.atproto.admin.getRecord(
93
+ {
94
+ uri: AtUri.make(
95
+ sc.dids.alice,
96
+ 'app.bsky.feed.post',
97
+ 'badrkey',
98
+ ).toString(),
99
+ },
100
+ { headers: network.pds.adminAuthHeaders() },
101
+ )
102
+ await expect(promise).rejects.toThrow('Record not found')
103
+ })
104
+
105
+ it('fails when record cid does not exist.', async () => {
106
+ const promise = agent.api.com.atproto.admin.getRecord(
107
+ {
108
+ uri: sc.posts[sc.dids.alice][0].ref.uriStr,
109
+ cid: sc.posts[sc.dids.alice][1].ref.cidStr, // Mismatching cid
110
+ },
111
+ { headers: network.pds.adminAuthHeaders() },
112
+ )
113
+ await expect(promise).rejects.toThrow('Record not found')
114
+ })
115
+ })
@@ -0,0 +1,101 @@
1
+ import { SeedClient, TestNetwork } from '@atproto/dev-env'
2
+ import AtpAgent from '@atproto/api'
3
+ import {
4
+ ACKNOWLEDGE,
5
+ TAKEDOWN,
6
+ } from '@atproto/api/src/client/types/com/atproto/admin/defs'
7
+ import {
8
+ REASONOTHER,
9
+ REASONSPAM,
10
+ } from '../../src/lexicon/types/com/atproto/moderation/defs'
11
+ import { forSnapshot } from '../_util'
12
+ import basicSeed from '../seeds/basic'
13
+
14
+ describe('admin get repo view', () => {
15
+ let network: TestNetwork
16
+ let agent: AtpAgent
17
+ let sc: SeedClient
18
+
19
+ beforeAll(async () => {
20
+ network = await TestNetwork.create({
21
+ dbPostgresSchema: 'views_admin_get_repo',
22
+ })
23
+ agent = network.pds.getClient()
24
+ sc = network.getSeedClient()
25
+ await basicSeed(sc)
26
+ })
27
+
28
+ afterAll(async () => {
29
+ await network.close()
30
+ })
31
+
32
+ beforeAll(async () => {
33
+ const acknowledge = await sc.takeModerationAction({
34
+ action: ACKNOWLEDGE,
35
+ subject: {
36
+ $type: 'com.atproto.admin.defs#repoRef',
37
+ did: sc.dids.alice,
38
+ },
39
+ })
40
+ await sc.createReport({
41
+ reportedBy: sc.dids.bob,
42
+ reasonType: REASONSPAM,
43
+ subject: {
44
+ $type: 'com.atproto.admin.defs#repoRef',
45
+ did: sc.dids.alice,
46
+ },
47
+ })
48
+ await sc.createReport({
49
+ reportedBy: sc.dids.carol,
50
+ reasonType: REASONOTHER,
51
+ reason: 'defamation',
52
+ subject: {
53
+ $type: 'com.atproto.admin.defs#repoRef',
54
+ did: sc.dids.alice,
55
+ },
56
+ })
57
+ await sc.reverseModerationAction({ id: acknowledge.id })
58
+ await sc.takeModerationAction({
59
+ action: TAKEDOWN,
60
+ subject: {
61
+ $type: 'com.atproto.admin.defs#repoRef',
62
+ did: sc.dids.alice,
63
+ },
64
+ })
65
+ })
66
+
67
+ it('gets a repo by did, even when taken down.', async () => {
68
+ const result = await agent.api.com.atproto.admin.getRepo(
69
+ { did: sc.dids.alice },
70
+ { headers: network.pds.adminAuthHeaders() },
71
+ )
72
+ expect(forSnapshot(result.data)).toMatchSnapshot()
73
+ })
74
+
75
+ it('does not include account emails for triage mods.', async () => {
76
+ const { data: admin } = await agent.api.com.atproto.admin.getRepo(
77
+ { did: sc.dids.bob },
78
+ { headers: network.pds.adminAuthHeaders() },
79
+ )
80
+ const { data: moderator } = await agent.api.com.atproto.admin.getRepo(
81
+ { did: sc.dids.bob },
82
+ { headers: network.pds.adminAuthHeaders('moderator') },
83
+ )
84
+ const { data: triage } = await agent.api.com.atproto.admin.getRepo(
85
+ { did: sc.dids.bob },
86
+ { headers: network.pds.adminAuthHeaders('triage') },
87
+ )
88
+ expect(admin.email).toEqual('bob@test.com')
89
+ expect(moderator.email).toEqual('bob@test.com')
90
+ expect(triage.email).toBeUndefined()
91
+ expect(triage).toEqual({ ...admin, email: undefined })
92
+ })
93
+
94
+ it('fails when repo does not exist.', async () => {
95
+ const promise = agent.api.com.atproto.admin.getRepo(
96
+ { did: 'did:plc:doesnotexist' },
97
+ { headers: network.pds.adminAuthHeaders() },
98
+ )
99
+ await expect(promise).rejects.toThrow('Repo not found')
100
+ })
101
+ })