@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
@@ -53,10 +53,6 @@ const skeleton = async (
53
53
  }
54
54
  let notifBuilder = db.db
55
55
  .selectFrom('notification as notif')
56
- .innerJoin('record', 'record.uri', 'notif.recordUri')
57
- .innerJoin('actor as author', 'author.did', 'notif.author')
58
- .where(notSoftDeletedClause(ref('record')))
59
- .where(notSoftDeletedClause(ref('author')))
60
56
  .where('notif.did', '=', viewer)
61
57
  .where((clause) =>
62
58
  clause
@@ -69,16 +65,12 @@ const skeleton = async (
69
65
  ),
70
66
  )
71
67
  .select([
68
+ 'notif.author as authorDid',
72
69
  'notif.recordUri as uri',
73
70
  'notif.recordCid as cid',
74
- 'author.did as authorDid',
75
- 'author.handle as authorHandle',
76
- 'author.indexedAt as authorIndexedAt',
77
- 'author.takedownId as authorTakedownId',
78
71
  'notif.reason as reason',
79
72
  'notif.reasonSubject as reasonSubject',
80
73
  'notif.sortAt as indexedAt',
81
- 'record.json as recordJson',
82
74
  ])
83
75
 
84
76
  const keyset = new NotifsKeyset(ref('notif.sortAt'), ref('notif.recordCid'))
@@ -86,6 +78,7 @@ const skeleton = async (
86
78
  cursor,
87
79
  limit,
88
80
  keyset,
81
+ tryIndex: true,
89
82
  })
90
83
 
91
84
  const actorStateQuery = db.db
@@ -107,17 +100,18 @@ const skeleton = async (
107
100
  }
108
101
 
109
102
  const hydration = async (state: SkeletonState, ctx: Context) => {
110
- const { graphService, actorService, labelService } = ctx
103
+ const { graphService, actorService, labelService, db } = ctx
111
104
  const { params, notifs } = state
112
105
  const { viewer } = params
113
106
  const dids = notifs.map((notif) => notif.authorDid)
114
107
  const uris = notifs.map((notif) => notif.uri)
115
- const [actors, labels, bam] = await Promise.all([
108
+ const [actors, records, labels, bam] = await Promise.all([
116
109
  actorService.views.profiles(dids, viewer),
110
+ getRecordMap(db, uris),
117
111
  labelService.getLabelsForUris(uris),
118
112
  graphService.getBlockAndMuteState(dids.map((did) => [viewer, did])),
119
113
  ])
120
- return { ...state, actors, labels, bam }
114
+ return { ...state, actors, records, labels, bam }
121
115
  }
122
116
 
123
117
  const noBlockOrMutes = (state: HydrationState) => {
@@ -131,11 +125,11 @@ const noBlockOrMutes = (state: HydrationState) => {
131
125
  }
132
126
 
133
127
  const presentation = (state: HydrationState) => {
134
- const { notifs, cursor, actors, labels, lastSeenNotifs } = state
128
+ const { notifs, cursor, actors, records, labels, lastSeenNotifs } = state
135
129
  const notifications = mapDefined(notifs, (notif) => {
136
130
  const author = actors[notif.authorDid]
137
- if (!author) return undefined
138
- const record = jsonStringToLex(notif.recordJson) as Record<string, unknown>
131
+ const record = records[notif.uri]
132
+ if (!author || !record) return undefined
139
133
  const recordLabels = labels[notif.uri] ?? []
140
134
  const recordSelfLabels = getSelfLabels({
141
135
  uri: notif.uri,
@@ -157,6 +151,24 @@ const presentation = (state: HydrationState) => {
157
151
  return { notifications, cursor }
158
152
  }
159
153
 
154
+ const getRecordMap = async (
155
+ db: Database,
156
+ uris: string[],
157
+ ): Promise<RecordMap> => {
158
+ if (!uris.length) return {}
159
+ const { ref } = db.db.dynamic
160
+ const recordRows = await db.db
161
+ .selectFrom('record')
162
+ .select(['uri', 'json'])
163
+ .where('uri', 'in', uris)
164
+ .where(notSoftDeletedClause(ref('record')))
165
+ .execute()
166
+ return recordRows.reduce((acc, { uri, json }) => {
167
+ acc[uri] = jsonStringToLex(json) as Record<string, unknown>
168
+ return acc
169
+ }, {} as RecordMap)
170
+ }
171
+
160
172
  type Context = {
161
173
  db: Database
162
174
  actorService: ActorService
@@ -178,20 +190,19 @@ type SkeletonState = {
178
190
  type HydrationState = SkeletonState & {
179
191
  bam: BlockAndMuteState
180
192
  actors: ActorInfoMap
193
+ records: RecordMap
181
194
  labels: Labels
182
195
  }
183
196
 
197
+ type RecordMap = { [uri: string]: Record<string, unknown> }
198
+
184
199
  type NotifRow = {
185
- indexedAt: string
186
- cid: string
187
- uri: string
188
200
  authorDid: string
189
- authorHandle: string | null
190
- authorIndexedAt: string
191
- authorTakedownId: number | null
201
+ uri: string
202
+ cid: string
192
203
  reason: string
193
204
  reasonSubject: string | null
194
- recordJson: string
205
+ indexedAt: string
195
206
  }
196
207
 
197
208
  class NotifsKeyset extends TimeCidKeyset<NotifRow> {
@@ -1,17 +1,43 @@
1
1
  import { Server } from '../../../../lexicon'
2
2
  import AppContext from '../../../../context'
3
+ import { addAccountInfoToRepoView, getPdsAccountInfo } from './util'
4
+ import {
5
+ isRecordView,
6
+ isRepoView,
7
+ } from '../../../../lexicon/types/com/atproto/admin/defs'
3
8
 
4
9
  export default function (server: Server, ctx: AppContext) {
5
10
  server.com.atproto.admin.getModerationAction({
6
11
  auth: ctx.roleVerifier,
7
- handler: async ({ params }) => {
12
+ handler: async ({ params, auth }) => {
8
13
  const { id } = params
9
14
  const db = ctx.db.getPrimary()
10
15
  const moderationService = ctx.services.moderation(db)
11
16
  const result = await moderationService.getActionOrThrow(id)
17
+
18
+ const [action, accountInfo] = await Promise.all([
19
+ moderationService.views.actionDetail(result),
20
+ getPdsAccountInfo(ctx, result.subjectDid),
21
+ ])
22
+
23
+ // add in pds account info if available
24
+ if (isRepoView(action.subject)) {
25
+ action.subject = addAccountInfoToRepoView(
26
+ action.subject,
27
+ accountInfo,
28
+ auth.credentials.moderator,
29
+ )
30
+ } else if (isRecordView(action.subject)) {
31
+ action.subject.repo = addAccountInfoToRepoView(
32
+ action.subject.repo,
33
+ accountInfo,
34
+ auth.credentials.moderator,
35
+ )
36
+ }
37
+
12
38
  return {
13
39
  encoding: 'application/json',
14
- body: await moderationService.views.actionDetail(result),
40
+ body: action,
15
41
  }
16
42
  },
17
43
  })
@@ -1,17 +1,42 @@
1
1
  import { Server } from '../../../../lexicon'
2
2
  import AppContext from '../../../../context'
3
+ import {
4
+ isRecordView,
5
+ isRepoView,
6
+ } from '../../../../lexicon/types/com/atproto/admin/defs'
7
+ import { addAccountInfoToRepoView, getPdsAccountInfo } from './util'
3
8
 
4
9
  export default function (server: Server, ctx: AppContext) {
5
10
  server.com.atproto.admin.getModerationReport({
6
11
  auth: ctx.roleVerifier,
7
- handler: async ({ params }) => {
12
+ handler: async ({ params, auth }) => {
8
13
  const { id } = params
9
14
  const db = ctx.db.getPrimary()
10
15
  const moderationService = ctx.services.moderation(db)
11
16
  const result = await moderationService.getReportOrThrow(id)
17
+ const [report, accountInfo] = await Promise.all([
18
+ moderationService.views.reportDetail(result),
19
+ getPdsAccountInfo(ctx, result.subjectDid),
20
+ ])
21
+
22
+ // add in pds account info if available
23
+ if (isRepoView(report.subject)) {
24
+ report.subject = addAccountInfoToRepoView(
25
+ report.subject,
26
+ accountInfo,
27
+ auth.credentials.moderator,
28
+ )
29
+ } else if (isRecordView(report.subject)) {
30
+ report.subject.repo = addAccountInfoToRepoView(
31
+ report.subject.repo,
32
+ accountInfo,
33
+ auth.credentials.moderator,
34
+ )
35
+ }
36
+
12
37
  return {
13
38
  encoding: 'application/json',
14
- body: await moderationService.views.reportDetail(result),
39
+ body: report,
15
40
  }
16
41
  },
17
42
  })
@@ -1,11 +1,12 @@
1
1
  import { InvalidRequestError } from '@atproto/xrpc-server'
2
2
  import { Server } from '../../../../lexicon'
3
3
  import AppContext from '../../../../context'
4
+ import { addAccountInfoToRepoView, getPdsAccountInfo } from './util'
4
5
 
5
6
  export default function (server: Server, ctx: AppContext) {
6
7
  server.com.atproto.admin.getRecord({
7
8
  auth: ctx.roleVerifier,
8
- handler: async ({ params }) => {
9
+ handler: async ({ params, auth }) => {
9
10
  const { uri, cid } = params
10
11
  const db = ctx.db.getPrimary()
11
12
  const result = await db.db
@@ -17,9 +18,20 @@ export default function (server: Server, ctx: AppContext) {
17
18
  if (!result) {
18
19
  throw new InvalidRequestError('Record not found', 'RecordNotFound')
19
20
  }
21
+ const [record, accountInfo] = await Promise.all([
22
+ ctx.services.moderation(db).views.recordDetail(result),
23
+ getPdsAccountInfo(ctx, result.did),
24
+ ])
25
+
26
+ record.repo = addAccountInfoToRepoView(
27
+ record.repo,
28
+ accountInfo,
29
+ auth.credentials.moderator,
30
+ )
31
+
20
32
  return {
21
33
  encoding: 'application/json',
22
- body: await ctx.services.moderation(db).views.recordDetail(result),
34
+ body: record,
23
35
  }
24
36
  },
25
37
  })
@@ -1,20 +1,31 @@
1
1
  import { InvalidRequestError } from '@atproto/xrpc-server'
2
2
  import { Server } from '../../../../lexicon'
3
3
  import AppContext from '../../../../context'
4
+ import { addAccountInfoToRepoViewDetail, getPdsAccountInfo } from './util'
4
5
 
5
6
  export default function (server: Server, ctx: AppContext) {
6
7
  server.com.atproto.admin.getRepo({
7
8
  auth: ctx.roleVerifier,
8
- handler: async ({ params }) => {
9
+ handler: async ({ params, auth }) => {
9
10
  const { did } = params
10
11
  const db = ctx.db.getPrimary()
11
12
  const result = await ctx.services.actor(db).getActor(did, true)
12
13
  if (!result) {
13
14
  throw new InvalidRequestError('Repo not found', 'RepoNotFound')
14
15
  }
16
+ const [partialRepo, accountInfo] = await Promise.all([
17
+ ctx.services.moderation(db).views.repoDetail(result),
18
+ getPdsAccountInfo(ctx, result.did),
19
+ ])
20
+
21
+ const repo = addAccountInfoToRepoViewDetail(
22
+ partialRepo,
23
+ accountInfo,
24
+ auth.credentials.moderator,
25
+ )
15
26
  return {
16
27
  encoding: 'application/json',
17
- body: await ctx.services.moderation(db).views.repoDetail(result),
28
+ body: repo,
18
29
  }
19
30
  },
20
31
  })
@@ -1,4 +1,8 @@
1
- import { AuthRequiredError, InvalidRequestError } from '@atproto/xrpc-server'
1
+ import {
2
+ AuthRequiredError,
3
+ InvalidRequestError,
4
+ UpstreamFailureError,
5
+ } from '@atproto/xrpc-server'
2
6
  import {
3
7
  ACKNOWLEDGE,
4
8
  ESCALATE,
@@ -6,6 +10,7 @@ import {
6
10
  } from '../../../../lexicon/types/com/atproto/admin/defs'
7
11
  import { Server } from '../../../../lexicon'
8
12
  import AppContext from '../../../../context'
13
+ import { retryHttp } from '../../../../util/retry'
9
14
 
10
15
  export default function (server: Server, ctx: AppContext) {
11
16
  server.com.atproto.admin.reverseModerationAction({
@@ -16,7 +21,7 @@ export default function (server: Server, ctx: AppContext) {
16
21
  const moderationService = ctx.services.moderation(db)
17
22
  const { id, createdBy, reason } = input.body
18
23
 
19
- const moderationAction = await db.transaction(async (dbTxn) => {
24
+ const { result, restored } = await db.transaction(async (dbTxn) => {
20
25
  const moderationTxn = ctx.services.moderation(dbTxn)
21
26
  const labelTxn = ctx.services.label(dbTxn)
22
27
  const now = new Date()
@@ -53,7 +58,7 @@ export default function (server: Server, ctx: AppContext) {
53
58
  )
54
59
  }
55
60
 
56
- const result = await moderationTxn.revertAction({
61
+ const { result, restored } = await moderationTxn.revertAction({
57
62
  id,
58
63
  createdAt: now,
59
64
  createdBy,
@@ -77,12 +82,33 @@ export default function (server: Server, ctx: AppContext) {
77
82
  { create, negate },
78
83
  )
79
84
 
80
- return result
85
+ return { result, restored }
81
86
  })
82
87
 
88
+ if (restored) {
89
+ const { did, subjects } = restored
90
+ const agent = await ctx.pdsAdminAgent(did)
91
+ const results = await Promise.allSettled(
92
+ subjects.map((subject) =>
93
+ retryHttp(() =>
94
+ agent.api.com.atproto.admin.updateSubjectStatus({
95
+ subject,
96
+ takedown: {
97
+ applied: false,
98
+ },
99
+ }),
100
+ ),
101
+ ),
102
+ )
103
+ const hadFailure = results.some((r) => r.status === 'rejected')
104
+ if (hadFailure) {
105
+ throw new UpstreamFailureError('failed to revert action on PDS')
106
+ }
107
+ }
108
+
83
109
  return {
84
110
  encoding: 'application/json',
85
- body: await moderationService.views.action(moderationAction),
111
+ body: await moderationService.views.action(result),
86
112
  }
87
113
  },
88
114
  })
@@ -1,4 +1,3 @@
1
- import { InvalidRequestError } from '@atproto/xrpc-server'
2
1
  import { Server } from '../../../../lexicon'
3
2
  import AppContext from '../../../../context'
4
3
 
@@ -8,23 +7,18 @@ export default function (server: Server, ctx: AppContext) {
8
7
  handler: async ({ params }) => {
9
8
  const db = ctx.db.getPrimary()
10
9
  const moderationService = ctx.services.moderation(db)
11
- const { invitedBy } = params
12
- if (invitedBy) {
13
- throw new InvalidRequestError('The invitedBy parameter is unsupported')
14
- }
10
+ const { limit, cursor } = params
15
11
  // prefer new 'q' query param over deprecated 'term'
16
- const { q } = params
17
- if (q) {
18
- params.term = q
19
- }
12
+ const query = params.q ?? params.term
20
13
 
21
- const { results, cursor } = await ctx.services
14
+ const { results, cursor: resCursor } = await ctx.services
22
15
  .actor(db)
23
- .getSearchResults({ ...params, includeSoftDeleted: true })
16
+ .getSearchResults({ query, limit, cursor, includeSoftDeleted: true })
17
+
24
18
  return {
25
19
  encoding: 'application/json',
26
20
  body: {
27
- cursor,
21
+ cursor: resCursor,
28
22
  repos: await moderationService.views.repo(results),
29
23
  },
30
24
  }
@@ -1,6 +1,10 @@
1
1
  import { CID } from 'multiformats/cid'
2
2
  import { AtUri } from '@atproto/syntax'
3
- import { AuthRequiredError, InvalidRequestError } from '@atproto/xrpc-server'
3
+ import {
4
+ AuthRequiredError,
5
+ InvalidRequestError,
6
+ UpstreamFailureError,
7
+ } from '@atproto/xrpc-server'
4
8
  import { Server } from '../../../../lexicon'
5
9
  import AppContext from '../../../../context'
6
10
  import {
@@ -9,6 +13,8 @@ import {
9
13
  TAKEDOWN,
10
14
  } from '../../../../lexicon/types/com/atproto/admin/defs'
11
15
  import { getSubject, getAction } from '../moderation/util'
16
+ import { TakedownSubjects } from '../../../../services/moderation'
17
+ import { retryHttp } from '../../../../util/retry'
12
18
 
13
19
  export default function (server: Server, ctx: AppContext) {
14
20
  server.com.atproto.admin.takeModerationAction({
@@ -52,7 +58,7 @@ export default function (server: Server, ctx: AppContext) {
52
58
 
53
59
  validateLabels([...(createLabelVals ?? []), ...(negateLabelVals ?? [])])
54
60
 
55
- const moderationAction = await db.transaction(async (dbTxn) => {
61
+ const { result, takenDown } = await db.transaction(async (dbTxn) => {
56
62
  const moderationTxn = ctx.services.moderation(dbTxn)
57
63
  const labelTxn = ctx.services.label(dbTxn)
58
64
 
@@ -67,13 +73,15 @@ export default function (server: Server, ctx: AppContext) {
67
73
  durationInHours,
68
74
  })
69
75
 
76
+ let takenDown: TakedownSubjects | undefined
77
+
70
78
  if (
71
79
  result.action === TAKEDOWN &&
72
80
  result.subjectType === 'com.atproto.admin.defs#repoRef' &&
73
81
  result.subjectDid
74
82
  ) {
75
83
  // No credentials to revoke on appview
76
- await moderationTxn.takedownRepo({
84
+ takenDown = await moderationTxn.takedownRepo({
77
85
  takedownId: result.id,
78
86
  did: result.subjectDid,
79
87
  })
@@ -82,11 +90,13 @@ export default function (server: Server, ctx: AppContext) {
82
90
  if (
83
91
  result.action === TAKEDOWN &&
84
92
  result.subjectType === 'com.atproto.repo.strongRef' &&
85
- result.subjectUri
93
+ result.subjectUri &&
94
+ result.subjectCid
86
95
  ) {
87
- await moderationTxn.takedownRecord({
96
+ takenDown = await moderationTxn.takedownRecord({
88
97
  takedownId: result.id,
89
98
  uri: new AtUri(result.subjectUri),
99
+ cid: CID.parse(result.subjectCid),
90
100
  blobCids: subjectBlobCids?.map((cid) => CID.parse(cid)) ?? [],
91
101
  })
92
102
  }
@@ -98,12 +108,36 @@ export default function (server: Server, ctx: AppContext) {
98
108
  { create: createLabelVals, negate: negateLabelVals },
99
109
  )
100
110
 
101
- return result
111
+ return { result, takenDown }
102
112
  })
103
113
 
114
+ if (takenDown) {
115
+ const { did, subjects } = takenDown
116
+ if (did && subjects.length > 0) {
117
+ const agent = await ctx.pdsAdminAgent(did)
118
+ const results = await Promise.allSettled(
119
+ subjects.map((subject) =>
120
+ retryHttp(() =>
121
+ agent.api.com.atproto.admin.updateSubjectStatus({
122
+ subject,
123
+ takedown: {
124
+ applied: true,
125
+ ref: result.id.toString(),
126
+ },
127
+ }),
128
+ ),
129
+ ),
130
+ )
131
+ const hadFailure = results.some((r) => r.status === 'rejected')
132
+ if (hadFailure) {
133
+ throw new UpstreamFailureError('failed to apply action on PDS')
134
+ }
135
+ }
136
+ }
137
+
104
138
  return {
105
139
  encoding: 'application/json',
106
- body: await moderationService.views.action(moderationAction),
140
+ body: await moderationService.views.action(result),
107
141
  }
108
142
  },
109
143
  })
@@ -0,0 +1,50 @@
1
+ import AppContext from '../../../../context'
2
+ import {
3
+ RepoView,
4
+ RepoViewDetail,
5
+ AccountView,
6
+ } from '../../../../lexicon/types/com/atproto/admin/defs'
7
+
8
+ export const getPdsAccountInfo = async (
9
+ ctx: AppContext,
10
+ did: string,
11
+ ): Promise<AccountView | null> => {
12
+ try {
13
+ const agent = await ctx.pdsAdminAgent(did)
14
+ const res = await agent.api.com.atproto.admin.getAccountInfo({ did })
15
+ return res.data
16
+ } catch (err) {
17
+ return null
18
+ }
19
+ }
20
+
21
+ export const addAccountInfoToRepoViewDetail = (
22
+ repoView: RepoViewDetail,
23
+ accountInfo: AccountView | null,
24
+ includeEmail = false,
25
+ ): RepoViewDetail => {
26
+ if (!accountInfo) return repoView
27
+ return {
28
+ ...repoView,
29
+ email: includeEmail ? accountInfo.email : undefined,
30
+ invitedBy: accountInfo.invitedBy,
31
+ invitesDisabled: accountInfo.invitesDisabled,
32
+ inviteNote: accountInfo.inviteNote,
33
+ invites: accountInfo.invites,
34
+ }
35
+ }
36
+
37
+ export const addAccountInfoToRepoView = (
38
+ repoView: RepoView,
39
+ accountInfo: AccountView | null,
40
+ includeEmail = false,
41
+ ): RepoView => {
42
+ if (!accountInfo) return repoView
43
+ return {
44
+ ...repoView,
45
+ email: includeEmail ? accountInfo.email : undefined,
46
+ invitedBy: accountInfo.invitedBy,
47
+ invitesDisabled: accountInfo.invitesDisabled,
48
+ inviteNote: accountInfo.inviteNote,
49
+ }
50
+ }
@@ -12,6 +12,14 @@ export const createRouter = (ctx: AppContext): express.Router => {
12
12
  res.json({
13
13
  '@context': ['https://www.w3.org/ns/did/v1'],
14
14
  id: ctx.cfg.serverDid,
15
+ verificationMethod: [
16
+ {
17
+ id: `${ctx.cfg.serverDid}#atproto`,
18
+ type: 'Multikey',
19
+ controller: ctx.cfg.serverDid,
20
+ publicKeyMultibase: ctx.signingKey.did().replace('did:key:', ''),
21
+ },
22
+ ],
15
23
  service: [
16
24
  {
17
25
  id: '#bsky_notif',
package/src/auth.ts CHANGED
@@ -14,11 +14,18 @@ export const authVerifier =
14
14
  if (!jwtStr) {
15
15
  throw new AuthRequiredError('missing jwt', 'MissingJwt')
16
16
  }
17
- const did = await verifyJwt(jwtStr, opts.aud, async (did: string) => {
18
- const atprotoData = await idResolver.did.resolveAtprotoData(did)
19
- return atprotoData.signingKey
20
- })
21
- return { credentials: { did }, artifacts: { aud: opts.aud } }
17
+ const payload = await verifyJwt(
18
+ jwtStr,
19
+ opts.aud,
20
+ async (did, forceRefresh) => {
21
+ const atprotoData = await idResolver.did.resolveAtprotoData(
22
+ did,
23
+ forceRefresh,
24
+ )
25
+ return atprotoData.signingKey
26
+ },
27
+ )
28
+ return { credentials: { did: payload.iss }, artifacts: { aud: opts.aud } }
22
29
  }
23
30
 
24
31
  export const authOptionalVerifier =
@@ -271,6 +271,7 @@ export class AutoModerator {
271
271
  await modSrvc.takedownRecord({
272
272
  takedownId: action.id,
273
273
  uri: uri,
274
+ cid: recordCid,
274
275
  blobCids: takedownCids,
275
276
  })
276
277
  })
package/src/config.ts CHANGED
@@ -18,6 +18,7 @@ export interface ServerConfigValues {
18
18
  handleResolveNameservers?: string[]
19
19
  imgUriEndpoint?: string
20
20
  blobCacheLocation?: string
21
+ searchEndpoint?: string
21
22
  labelerDid: string
22
23
  adminPassword: string
23
24
  moderatorPassword?: string
@@ -51,6 +52,7 @@ export class ServerConfig {
51
52
  : []
52
53
  const imgUriEndpoint = process.env.IMG_URI_ENDPOINT
53
54
  const blobCacheLocation = process.env.BLOB_CACHE_LOC
55
+ const searchEndpoint = process.env.SEARCH_ENDPOINT
54
56
  const dbPrimaryPostgresUrl =
55
57
  overrides?.dbPrimaryPostgresUrl || process.env.DB_PRIMARY_POSTGRES_URL
56
58
  let dbReplicaPostgresUrls = overrides?.dbReplicaPostgresUrls
@@ -97,6 +99,7 @@ export class ServerConfig {
97
99
  handleResolveNameservers,
98
100
  imgUriEndpoint,
99
101
  blobCacheLocation,
102
+ searchEndpoint,
100
103
  labelerDid,
101
104
  adminPassword,
102
105
  moderatorPassword,
@@ -183,6 +186,10 @@ export class ServerConfig {
183
186
  return this.cfg.blobCacheLocation
184
187
  }
185
188
 
189
+ get searchEndpoint() {
190
+ return this.cfg.searchEndpoint
191
+ }
192
+
186
193
  get labelerDid() {
187
194
  return this.cfg.labelerDid
188
195
  }