@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.
- package/CHANGELOG.md +26 -0
- package/dist/api/com/atproto/admin/util.d.ts +5 -0
- package/dist/config.d.ts +2 -0
- package/dist/context.d.ts +8 -0
- package/dist/db/index.js +51 -2
- package/dist/db/index.js.map +3 -3
- package/dist/db/migrations/20230929T192920807Z-record-cursor-indexes.d.ts +3 -0
- package/dist/db/migrations/index.d.ts +1 -0
- package/dist/did-cache.d.ts +2 -2
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1818 -580
- package/dist/index.js.map +3 -3
- package/dist/lexicon/index.d.ts +16 -0
- package/dist/lexicon/lexicons.d.ts +330 -3
- package/dist/lexicon/types/app/bsky/actor/defs.d.ts +1 -0
- package/dist/lexicon/types/com/atproto/admin/defs.d.ts +28 -0
- package/dist/lexicon/types/com/atproto/admin/getAccountInfo.d.ts +29 -0
- package/dist/lexicon/types/com/atproto/admin/getSubjectStatus.d.ts +39 -0
- package/dist/lexicon/types/com/atproto/admin/searchRepos.d.ts +0 -1
- package/dist/lexicon/types/com/atproto/admin/updateSubjectStatus.d.ts +46 -0
- package/dist/lexicon/types/com/atproto/server/confirmEmail.d.ts +27 -0
- package/dist/lexicon/types/com/atproto/server/createAccount.d.ts +2 -0
- package/dist/lexicon/types/com/atproto/server/createSession.d.ts +2 -0
- package/dist/lexicon/types/com/atproto/server/getSession.d.ts +1 -0
- package/dist/lexicon/types/com/atproto/server/refreshSession.d.ts +1 -0
- package/dist/lexicon/types/com/atproto/server/requestEmailConfirmation.d.ts +19 -0
- package/dist/lexicon/types/com/atproto/server/requestEmailUpdate.d.ts +30 -0
- package/dist/lexicon/types/com/atproto/server/reserveSigningKey.d.ts +30 -0
- package/dist/lexicon/types/com/atproto/server/updateEmail.d.ts +27 -0
- package/dist/lexicon/types/com/atproto/sync/listRepos.d.ts +1 -0
- package/dist/services/actor/index.d.ts +2 -2
- package/dist/services/actor/types.d.ts +1 -0
- package/dist/services/graph/index.d.ts +2 -0
- package/dist/services/moderation/index.d.ts +13 -3
- package/dist/services/util/search.d.ts +3 -3
- package/package.json +13 -14
- package/src/api/app/bsky/actor/searchActors.ts +36 -22
- package/src/api/app/bsky/actor/searchActorsTypeahead.ts +24 -17
- package/src/api/app/bsky/feed/getAuthorFeed.ts +2 -2
- package/src/api/app/bsky/feed/getPostThread.ts +2 -2
- package/src/api/app/bsky/graph/getSuggestedFollowsByActor.ts +1 -0
- package/src/api/app/bsky/notification/listNotifications.ts +33 -22
- package/src/api/com/atproto/admin/getModerationAction.ts +28 -2
- package/src/api/com/atproto/admin/getModerationReport.ts +27 -2
- package/src/api/com/atproto/admin/getRecord.ts +14 -2
- package/src/api/com/atproto/admin/getRepo.ts +13 -2
- package/src/api/com/atproto/admin/reverseModerationAction.ts +31 -5
- package/src/api/com/atproto/admin/searchRepos.ts +6 -12
- package/src/api/com/atproto/admin/takeModerationAction.ts +41 -7
- package/src/api/com/atproto/admin/util.ts +50 -0
- package/src/api/well-known.ts +8 -0
- package/src/auth.ts +12 -5
- package/src/auto-moderator/index.ts +1 -0
- package/src/config.ts +7 -0
- package/src/context.ts +30 -0
- package/src/db/migrations/20230929T192920807Z-record-cursor-indexes.ts +40 -0
- package/src/db/migrations/index.ts +1 -0
- package/src/did-cache.ts +29 -14
- package/src/feed-gen/with-friends.ts +2 -2
- package/src/index.ts +9 -1
- package/src/indexer/subscription.ts +1 -21
- package/src/lexicon/index.ts +96 -0
- package/src/lexicon/lexicons.ts +368 -4
- package/src/lexicon/types/app/bsky/actor/defs.ts +1 -0
- package/src/lexicon/types/com/atproto/admin/defs.ts +61 -0
- package/src/lexicon/types/com/atproto/admin/getAccountInfo.ts +41 -0
- package/src/lexicon/types/com/atproto/admin/getSubjectStatus.ts +54 -0
- package/src/lexicon/types/com/atproto/admin/searchRepos.ts +0 -1
- package/src/lexicon/types/com/atproto/admin/updateSubjectStatus.ts +61 -0
- package/src/lexicon/types/com/atproto/server/confirmEmail.ts +40 -0
- package/src/lexicon/types/com/atproto/server/createAccount.ts +2 -0
- package/src/lexicon/types/com/atproto/server/createSession.ts +2 -0
- package/src/lexicon/types/com/atproto/server/getSession.ts +1 -0
- package/src/lexicon/types/com/atproto/server/refreshSession.ts +1 -0
- package/src/lexicon/types/com/atproto/server/requestEmailConfirmation.ts +31 -0
- package/src/lexicon/types/com/atproto/server/requestEmailUpdate.ts +43 -0
- package/src/lexicon/types/com/atproto/server/reserveSigningKey.ts +44 -0
- package/src/lexicon/types/com/atproto/server/updateEmail.ts +41 -0
- package/src/lexicon/types/com/atproto/sync/listRepos.ts +1 -0
- package/src/logger.ts +8 -0
- package/src/services/actor/index.ts +16 -10
- package/src/services/actor/types.ts +1 -0
- package/src/services/actor/views.ts +26 -8
- package/src/services/graph/index.ts +26 -7
- package/src/services/indexing/index.ts +15 -17
- package/src/services/moderation/index.ts +94 -14
- package/src/services/moderation/views.ts +1 -0
- package/src/services/util/search.ts +24 -23
- package/tests/__snapshots__/feed-generation.test.ts.snap +12 -12
- package/tests/__snapshots__/indexing.test.ts.snap +4 -4
- package/tests/admin/__snapshots__/get-moderation-action.test.ts.snap +172 -0
- package/tests/admin/__snapshots__/get-moderation-actions.test.ts.snap +178 -0
- package/tests/admin/__snapshots__/get-moderation-report.test.ts.snap +177 -0
- package/tests/admin/__snapshots__/get-moderation-reports.test.ts.snap +307 -0
- package/tests/admin/__snapshots__/get-record.test.ts.snap +275 -0
- package/tests/admin/__snapshots__/get-repo.test.ts.snap +103 -0
- package/tests/admin/get-moderation-action.test.ts +100 -0
- package/tests/admin/get-moderation-actions.test.ts +164 -0
- package/tests/admin/get-moderation-report.test.ts +100 -0
- package/tests/admin/get-moderation-reports.test.ts +332 -0
- package/tests/admin/get-record.test.ts +115 -0
- package/tests/admin/get-repo.test.ts +101 -0
- package/tests/{moderation.test.ts → admin/moderation.test.ts} +107 -9
- package/tests/admin/repo-search.test.ts +124 -0
- package/tests/algos/hot-classic.test.ts +3 -5
- package/tests/algos/whats-hot.test.ts +3 -5
- package/tests/algos/with-friends.test.ts +2 -4
- package/tests/auth.test.ts +64 -0
- package/tests/auto-moderator/fuzzy-matcher.test.ts +2 -3
- package/tests/auto-moderator/labeler.test.ts +5 -7
- package/tests/auto-moderator/takedowns.test.ts +11 -12
- package/tests/blob-resolver.test.ts +1 -3
- package/tests/did-cache.test.ts +2 -5
- package/tests/feed-generation.test.ts +8 -6
- package/tests/handle-invalidation.test.ts +2 -3
- package/tests/image/server.test.ts +1 -4
- package/tests/image/sharp.test.ts +1 -1
- package/tests/indexing.test.ts +4 -4
- package/tests/notification-server.test.ts +2 -3
- package/tests/pipeline/backpressure.test.ts +2 -3
- package/tests/pipeline/reingest.test.ts +7 -4
- package/tests/pipeline/repartition.test.ts +2 -3
- package/tests/reprocessing.test.ts +2 -6
- package/tests/seeds/basic.ts +4 -4
- package/tests/seeds/follows.ts +1 -1
- package/tests/seeds/likes.ts +1 -1
- package/tests/seeds/reposts.ts +1 -1
- package/tests/seeds/users-bulk.ts +1 -1
- package/tests/seeds/users.ts +1 -1
- package/tests/server.test.ts +1 -3
- package/tests/subscription/repo.test.ts +2 -4
- package/tests/views/__snapshots__/author-feed.test.ts.snap +24 -24
- package/tests/views/__snapshots__/block-lists.test.ts.snap +42 -7
- package/tests/views/__snapshots__/blocks.test.ts.snap +2 -2
- package/tests/views/__snapshots__/list-feed.test.ts.snap +6 -6
- package/tests/views/__snapshots__/mute-lists.test.ts.snap +15 -4
- package/tests/views/__snapshots__/mutes.test.ts.snap +2 -2
- package/tests/views/__snapshots__/notifications.test.ts.snap +2 -2
- package/tests/views/__snapshots__/posts.test.ts.snap +8 -8
- package/tests/views/__snapshots__/thread.test.ts.snap +10 -10
- package/tests/views/__snapshots__/timeline.test.ts.snap +58 -58
- package/tests/views/actor-likes.test.ts +2 -3
- package/tests/views/actor-search.test.ts +5 -5
- package/tests/views/admin/repo-search.test.ts +2 -4
- package/tests/views/author-feed.test.ts +2 -4
- package/tests/views/block-lists.test.ts +34 -7
- package/tests/views/blocks.test.ts +6 -3
- package/tests/views/follows.test.ts +2 -4
- package/tests/views/likes.test.ts +2 -5
- package/tests/views/list-feed.test.ts +2 -4
- package/tests/views/mute-lists.test.ts +23 -5
- package/tests/views/mutes.test.ts +2 -5
- package/tests/views/notifications.test.ts +2 -4
- package/tests/views/posts.test.ts +2 -5
- package/tests/views/profile.test.ts +4 -5
- package/tests/views/reposts.test.ts +2 -4
- package/tests/views/suggested-follows.test.ts +2 -3
- package/tests/views/suggestions.test.ts +2 -4
- package/tests/views/thread.test.ts +2 -4
- package/tests/views/threadgating.test.ts +2 -3
- package/tests/views/timeline.test.ts +2 -4
- package/dist/env.d.ts +0 -1
- package/example.dev.env +0 -5
- package/src/env.ts +0 -9
- package/tests/seeds/client.ts +0 -466
- /package/tests/{__snapshots__ → admin/__snapshots__}/moderation.test.ts.snap +0 -0
- /package/tests/{image/fixtures → sample-img}/at.png +0 -0
- /package/tests/{image/fixtures → sample-img}/hd-key.jpg +0 -0
- /package/tests/{image/fixtures → sample-img}/key-alt.jpg +0 -0
- /package/tests/{image/fixtures → sample-img}/key-landscape-large.jpg +0 -0
- /package/tests/{image/fixtures → sample-img}/key-landscape-small.jpg +0 -0
- /package/tests/{image/fixtures → sample-img}/key-portrait-large.jpg +0 -0
- /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
|
+
})
|