@atproto/bsky 0.0.11 → 0.0.13

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 (158) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/LICENSE.txt +7 -0
  3. package/README.md +6 -1
  4. package/dist/api/com/atproto/admin/util.d.ts +5 -0
  5. package/dist/context.d.ts +6 -1
  6. package/dist/db/index.js +51 -2
  7. package/dist/db/index.js.map +3 -3
  8. package/dist/db/migrations/20230929T192920807Z-record-cursor-indexes.d.ts +3 -0
  9. package/dist/db/migrations/index.d.ts +1 -0
  10. package/dist/did-cache.d.ts +2 -2
  11. package/dist/index.d.ts +2 -0
  12. package/dist/index.js +1630 -755
  13. package/dist/index.js.map +3 -3
  14. package/dist/lexicon/index.d.ts +8 -0
  15. package/dist/lexicon/lexicons.d.ts +243 -3
  16. package/dist/lexicon/types/app/bsky/actor/defs.d.ts +1 -0
  17. package/dist/lexicon/types/com/atproto/admin/defs.d.ts +28 -0
  18. package/dist/lexicon/types/com/atproto/admin/getAccountInfo.d.ts +29 -0
  19. package/dist/lexicon/types/com/atproto/admin/getSubjectStatus.d.ts +39 -0
  20. package/dist/lexicon/types/com/atproto/admin/searchRepos.d.ts +0 -1
  21. package/dist/lexicon/types/com/atproto/admin/updateSubjectStatus.d.ts +46 -0
  22. package/dist/lexicon/types/com/atproto/server/createAccount.d.ts +4 -2
  23. package/dist/lexicon/types/com/atproto/server/createSession.d.ts +1 -0
  24. package/dist/lexicon/types/com/atproto/server/refreshSession.d.ts +1 -0
  25. package/dist/lexicon/types/com/atproto/server/reserveSigningKey.d.ts +36 -0
  26. package/dist/lexicon/types/com/atproto/sync/listRepos.d.ts +1 -0
  27. package/dist/services/actor/types.d.ts +1 -0
  28. package/dist/services/graph/index.d.ts +2 -0
  29. package/dist/services/moderation/index.d.ts +13 -3
  30. package/package.json +14 -15
  31. package/src/api/app/bsky/feed/getAuthorFeed.ts +2 -2
  32. package/src/api/app/bsky/feed/getPostThread.ts +2 -2
  33. package/src/api/app/bsky/notification/listNotifications.ts +33 -22
  34. package/src/api/com/atproto/admin/getModerationAction.ts +28 -2
  35. package/src/api/com/atproto/admin/getModerationReport.ts +27 -2
  36. package/src/api/com/atproto/admin/getRecord.ts +14 -2
  37. package/src/api/com/atproto/admin/getRepo.ts +13 -2
  38. package/src/api/com/atproto/admin/reverseModerationAction.ts +31 -5
  39. package/src/api/com/atproto/admin/searchRepos.ts +2 -5
  40. package/src/api/com/atproto/admin/takeModerationAction.ts +41 -7
  41. package/src/api/com/atproto/admin/util.ts +50 -0
  42. package/src/api/well-known.ts +8 -0
  43. package/src/auth.ts +12 -5
  44. package/src/auto-moderator/index.ts +1 -0
  45. package/src/context.ts +25 -1
  46. package/src/db/migrations/20230929T192920807Z-record-cursor-indexes.ts +40 -0
  47. package/src/db/migrations/index.ts +1 -0
  48. package/src/did-cache.ts +29 -14
  49. package/src/feed-gen/with-friends.ts +2 -2
  50. package/src/index.ts +4 -1
  51. package/src/indexer/subscription.ts +1 -21
  52. package/src/lexicon/index.ts +48 -0
  53. package/src/lexicon/lexicons.ts +259 -5
  54. package/src/lexicon/types/app/bsky/actor/defs.ts +1 -0
  55. package/src/lexicon/types/com/atproto/admin/defs.ts +61 -0
  56. package/src/lexicon/types/com/atproto/admin/getAccountInfo.ts +41 -0
  57. package/src/lexicon/types/com/atproto/admin/getSubjectStatus.ts +54 -0
  58. package/src/lexicon/types/com/atproto/admin/searchRepos.ts +0 -1
  59. package/src/lexicon/types/com/atproto/admin/updateSubjectStatus.ts +61 -0
  60. package/src/lexicon/types/com/atproto/server/createAccount.ts +4 -2
  61. package/src/lexicon/types/com/atproto/server/createSession.ts +1 -0
  62. package/src/lexicon/types/com/atproto/server/refreshSession.ts +1 -0
  63. package/src/lexicon/types/com/atproto/server/reserveSigningKey.ts +51 -0
  64. package/src/lexicon/types/com/atproto/sync/listRepos.ts +1 -0
  65. package/src/logger.ts +8 -0
  66. package/src/services/actor/index.ts +7 -1
  67. package/src/services/actor/types.ts +1 -0
  68. package/src/services/actor/views.ts +26 -8
  69. package/src/services/graph/index.ts +26 -7
  70. package/src/services/indexing/index.ts +15 -17
  71. package/src/services/moderation/index.ts +94 -14
  72. package/src/services/moderation/views.ts +1 -0
  73. package/tests/__snapshots__/feed-generation.test.ts.snap +12 -12
  74. package/tests/__snapshots__/indexing.test.ts.snap +4 -4
  75. package/tests/admin/__snapshots__/get-moderation-action.test.ts.snap +172 -0
  76. package/tests/admin/__snapshots__/get-moderation-actions.test.ts.snap +178 -0
  77. package/tests/admin/__snapshots__/get-moderation-report.test.ts.snap +177 -0
  78. package/tests/admin/__snapshots__/get-moderation-reports.test.ts.snap +307 -0
  79. package/tests/admin/__snapshots__/get-record.test.ts.snap +275 -0
  80. package/tests/admin/__snapshots__/get-repo.test.ts.snap +103 -0
  81. package/tests/admin/get-moderation-action.test.ts +100 -0
  82. package/tests/admin/get-moderation-actions.test.ts +164 -0
  83. package/tests/admin/get-moderation-report.test.ts +100 -0
  84. package/tests/admin/get-moderation-reports.test.ts +332 -0
  85. package/tests/admin/get-record.test.ts +115 -0
  86. package/tests/admin/get-repo.test.ts +101 -0
  87. package/tests/{moderation.test.ts → admin/moderation.test.ts} +107 -9
  88. package/tests/admin/repo-search.test.ts +124 -0
  89. package/tests/algos/hot-classic.test.ts +3 -5
  90. package/tests/algos/whats-hot.test.ts +3 -5
  91. package/tests/algos/with-friends.test.ts +2 -4
  92. package/tests/auth.test.ts +64 -0
  93. package/tests/auto-moderator/fuzzy-matcher.test.ts +2 -3
  94. package/tests/auto-moderator/labeler.test.ts +5 -7
  95. package/tests/auto-moderator/takedowns.test.ts +11 -12
  96. package/tests/blob-resolver.test.ts +1 -3
  97. package/tests/did-cache.test.ts +2 -5
  98. package/tests/feed-generation.test.ts +8 -6
  99. package/tests/handle-invalidation.test.ts +2 -3
  100. package/tests/image/server.test.ts +1 -4
  101. package/tests/image/sharp.test.ts +1 -1
  102. package/tests/indexing.test.ts +4 -4
  103. package/tests/notification-server.test.ts +2 -3
  104. package/tests/pipeline/backpressure.test.ts +2 -3
  105. package/tests/pipeline/reingest.test.ts +7 -4
  106. package/tests/pipeline/repartition.test.ts +2 -3
  107. package/tests/reprocessing.test.ts +2 -6
  108. package/tests/seeds/basic.ts +4 -4
  109. package/tests/seeds/follows.ts +1 -1
  110. package/tests/seeds/likes.ts +1 -1
  111. package/tests/seeds/reposts.ts +1 -1
  112. package/tests/seeds/users-bulk.ts +1 -1
  113. package/tests/seeds/users.ts +1 -1
  114. package/tests/server.test.ts +1 -3
  115. package/tests/subscription/repo.test.ts +2 -4
  116. package/tests/views/__snapshots__/author-feed.test.ts.snap +24 -24
  117. package/tests/views/__snapshots__/block-lists.test.ts.snap +42 -7
  118. package/tests/views/__snapshots__/blocks.test.ts.snap +2 -2
  119. package/tests/views/__snapshots__/list-feed.test.ts.snap +6 -6
  120. package/tests/views/__snapshots__/mute-lists.test.ts.snap +15 -4
  121. package/tests/views/__snapshots__/mutes.test.ts.snap +2 -2
  122. package/tests/views/__snapshots__/notifications.test.ts.snap +2 -2
  123. package/tests/views/__snapshots__/posts.test.ts.snap +8 -8
  124. package/tests/views/__snapshots__/thread.test.ts.snap +10 -10
  125. package/tests/views/__snapshots__/timeline.test.ts.snap +58 -58
  126. package/tests/views/actor-likes.test.ts +2 -3
  127. package/tests/views/actor-search.test.ts +5 -5
  128. package/tests/views/admin/repo-search.test.ts +2 -4
  129. package/tests/views/author-feed.test.ts +2 -4
  130. package/tests/views/block-lists.test.ts +34 -7
  131. package/tests/views/blocks.test.ts +6 -3
  132. package/tests/views/follows.test.ts +2 -4
  133. package/tests/views/likes.test.ts +2 -5
  134. package/tests/views/list-feed.test.ts +2 -4
  135. package/tests/views/mute-lists.test.ts +23 -5
  136. package/tests/views/mutes.test.ts +2 -5
  137. package/tests/views/notifications.test.ts +2 -4
  138. package/tests/views/posts.test.ts +2 -5
  139. package/tests/views/profile.test.ts +4 -5
  140. package/tests/views/reposts.test.ts +2 -4
  141. package/tests/views/suggested-follows.test.ts +2 -3
  142. package/tests/views/suggestions.test.ts +2 -4
  143. package/tests/views/thread.test.ts +2 -4
  144. package/tests/views/threadgating.test.ts +2 -3
  145. package/tests/views/timeline.test.ts +2 -4
  146. package/LICENSE +0 -21
  147. package/dist/env.d.ts +0 -1
  148. package/example.dev.env +0 -5
  149. package/src/env.ts +0 -9
  150. package/tests/seeds/client.ts +0 -466
  151. /package/tests/{__snapshots__ → admin/__snapshots__}/moderation.test.ts.snap +0 -0
  152. /package/tests/{image/fixtures → sample-img}/at.png +0 -0
  153. /package/tests/{image/fixtures → sample-img}/hd-key.jpg +0 -0
  154. /package/tests/{image/fixtures → sample-img}/key-alt.jpg +0 -0
  155. /package/tests/{image/fixtures → sample-img}/key-landscape-large.jpg +0 -0
  156. /package/tests/{image/fixtures → sample-img}/key-landscape-small.jpg +0 -0
  157. /package/tests/{image/fixtures → sample-img}/key-portrait-large.jpg +0 -0
  158. /package/tests/{image/fixtures → sample-img}/key-portrait-small.jpg +0 -0
@@ -0,0 +1,51 @@
1
+ /**
2
+ * GENERATED CODE - DO NOT MODIFY
3
+ */
4
+ import express from 'express'
5
+ import { ValidationResult, BlobRef } from '@atproto/lexicon'
6
+ import { lexicons } from '../../../../lexicons'
7
+ import { isObj, hasProp } from '../../../../util'
8
+ import { CID } from 'multiformats/cid'
9
+ import { HandlerAuth } from '@atproto/xrpc-server'
10
+
11
+ export interface QueryParams {}
12
+
13
+ export interface InputSchema {
14
+ /** The did to reserve a new did:key for */
15
+ did?: string
16
+ [k: string]: unknown
17
+ }
18
+
19
+ export interface OutputSchema {
20
+ /** Public signing key in the form of a did:key. */
21
+ signingKey: string
22
+ [k: string]: unknown
23
+ }
24
+
25
+ export interface HandlerInput {
26
+ encoding: 'application/json'
27
+ body: InputSchema
28
+ }
29
+
30
+ export interface HandlerSuccess {
31
+ encoding: 'application/json'
32
+ body: OutputSchema
33
+ headers?: { [key: string]: string }
34
+ }
35
+
36
+ export interface HandlerError {
37
+ status: number
38
+ message?: string
39
+ }
40
+
41
+ export type HandlerOutput = HandlerError | HandlerSuccess
42
+ export type HandlerReqCtx<HA extends HandlerAuth = never> = {
43
+ auth: HA
44
+ params: QueryParams
45
+ input: HandlerInput
46
+ req: express.Request
47
+ res: express.Response
48
+ }
49
+ export type Handler<HA extends HandlerAuth = never> = (
50
+ ctx: HandlerReqCtx<HA>,
51
+ ) => Promise<HandlerOutput> | HandlerOutput
@@ -49,6 +49,7 @@ export type Handler<HA extends HandlerAuth = never> = (
49
49
  export interface Repo {
50
50
  did: string
51
51
  head: string
52
+ rev: string
52
53
  [k: string]: unknown
53
54
  }
54
55
 
package/src/logger.ts CHANGED
@@ -12,4 +12,12 @@ export const httpLogger: ReturnType<typeof subsystemLogger> =
12
12
 
13
13
  export const loggerMiddleware = pinoHttp({
14
14
  logger: httpLogger,
15
+ serializers: {
16
+ err: (err) => {
17
+ return {
18
+ code: err?.code,
19
+ message: err?.message,
20
+ }
21
+ },
22
+ },
15
23
  })
@@ -66,7 +66,13 @@ export class ActorService {
66
66
  qb = qb.orWhere('actor.did', 'in', dids)
67
67
  }
68
68
  if (handles.length) {
69
- qb = qb.orWhere('actor.handle', 'in', handles)
69
+ qb = qb.orWhere(
70
+ 'actor.handle',
71
+ 'in',
72
+ handles.length === 1
73
+ ? [handles[0], handles[0]] // a silly (but worthwhile) optimization to avoid usage of actor_handle_tgrm_idx
74
+ : handles,
75
+ )
70
76
  }
71
77
  return qb
72
78
  })
@@ -16,6 +16,7 @@ export type ActorInfo = {
16
16
  mutedByList?: ListViewBasic
17
17
  blockedBy?: boolean
18
18
  blocking?: string
19
+ blockingByList?: ListViewBasic
19
20
  following?: string
20
21
  followedBy?: string
21
22
  }
@@ -137,10 +137,13 @@ export class ActorViews {
137
137
  ),
138
138
  ])
139
139
  const listUris = mapDefined(profiles, ({ did }) => {
140
- const list = viewer && bam.muteList([viewer, did])
141
- if (!list) return
142
- return list
143
- })
140
+ const muteList = viewer && bam.muteList([viewer, did])
141
+ const blockList = viewer && bam.blockList([viewer, did])
142
+ const lists: string[] = []
143
+ if (muteList) lists.push(muteList)
144
+ if (blockList) lists.push(blockList)
145
+ return lists
146
+ }).flat()
144
147
  const lists = await this.services.graph.getListViews(listUris, viewer)
145
148
  return { profilesDetailed: toMapByDid(profiles), labels, bam, lists }
146
149
  }
@@ -168,6 +171,11 @@ export class ActorViews {
168
171
  mutedByListUri && lists[mutedByListUri]
169
172
  ? this.services.graph.formatListViewBasic(lists[mutedByListUri])
170
173
  : undefined
174
+ const blockingByListUri = viewer && bam.blockList([viewer, did])
175
+ const blockingByList =
176
+ blockingByListUri && lists[blockingByListUri]
177
+ ? this.services.graph.formatListViewBasic(lists[blockingByListUri])
178
+ : undefined
171
179
  const actorLabels = labels[did] ?? []
172
180
  const selfLabels = getSelfLabels({
173
181
  uri: prof.profileUri,
@@ -194,6 +202,7 @@ export class ActorViews {
194
202
  mutedByList,
195
203
  blockedBy: !!bam.blockedBy([viewer, did]),
196
204
  blocking: bam.blocking([viewer, did]) ?? undefined,
205
+ blockingByList,
197
206
  following:
198
207
  prof?.viewerFollowing && !bam.block([viewer, did])
199
208
  ? prof.viewerFollowing
@@ -265,10 +274,13 @@ export class ActorViews {
265
274
  ),
266
275
  ])
267
276
  const listUris = mapDefined(profiles, ({ did }) => {
268
- const list = viewer && bam.muteList([viewer, did])
269
- if (!list) return
270
- return list
271
- })
277
+ const muteList = viewer && bam.muteList([viewer, did])
278
+ const blockList = viewer && bam.blockList([viewer, did])
279
+ const lists: string[] = []
280
+ if (muteList) lists.push(muteList)
281
+ if (blockList) lists.push(blockList)
282
+ return lists
283
+ }).flat()
272
284
  const lists = await this.services.graph.getListViews(listUris, viewer)
273
285
  return { profiles: toMapByDid(profiles), labels, bam, lists }
274
286
  }
@@ -298,6 +310,11 @@ export class ActorViews {
298
310
  mutedByListUri && lists[mutedByListUri]
299
311
  ? this.services.graph.formatListViewBasic(lists[mutedByListUri])
300
312
  : undefined
313
+ const blockingByListUri = viewer && bam.blockList([viewer, did])
314
+ const blockingByList =
315
+ blockingByListUri && lists[blockingByListUri]
316
+ ? this.services.graph.formatListViewBasic(lists[blockingByListUri])
317
+ : undefined
301
318
  const actorLabels = labels[did] ?? []
302
319
  const selfLabels = getSelfLabels({
303
320
  uri: prof.profileUri,
@@ -320,6 +337,7 @@ export class ActorViews {
320
337
  mutedByList,
321
338
  blockedBy: !!bam.blockedBy([viewer, did]),
322
339
  blocking: bam.blocking([viewer, did]) ?? undefined,
340
+ blockingByList,
323
341
  following:
324
342
  prof?.viewerFollowing && !bam.block([viewer, did])
325
343
  ? prof.viewerFollowing
@@ -267,28 +267,44 @@ export type RelationshipPair = [didA: string, didB: string]
267
267
  export class BlockAndMuteState {
268
268
  hasIdx = new Map<string, Set<string>>() // did -> did
269
269
  blockIdx = new Map<string, Map<string, string>>() // did -> did -> block uri
270
+ blockListIdx = new Map<string, Map<string, string>>() // did -> did -> list uri
270
271
  muteIdx = new Map<string, Set<string>>() // did -> did
271
272
  muteListIdx = new Map<string, Map<string, string>>() // did -> did -> list uri
272
273
  constructor(items: BlockAndMuteInfo[] = []) {
273
274
  items.forEach((item) => this.add(item))
274
275
  }
275
276
  add(item: BlockAndMuteInfo) {
276
- const blocking = item.blocking || item.blockingViaList // block or list uri
277
- if (blocking) {
277
+ if (item.source === item.target) {
278
+ return // we do not respect self-blocks or self-mutes
279
+ }
280
+ if (item.blocking) {
278
281
  const map = this.blockIdx.get(item.source) ?? new Map()
279
- map.set(item.target, blocking)
282
+ map.set(item.target, item.blocking)
280
283
  if (!this.blockIdx.has(item.source)) {
281
284
  this.blockIdx.set(item.source, map)
282
285
  }
283
286
  }
284
- const blockedBy = item.blockedBy || item.blockedByViaList // block or list uri
285
- if (blockedBy) {
287
+ if (item.blockingViaList) {
288
+ const map = this.blockListIdx.get(item.source) ?? new Map()
289
+ map.set(item.target, item.blockingViaList)
290
+ if (!this.blockListIdx.has(item.source)) {
291
+ this.blockListIdx.set(item.source, map)
292
+ }
293
+ }
294
+ if (item.blockedBy) {
286
295
  const map = this.blockIdx.get(item.target) ?? new Map()
287
- map.set(item.source, blockedBy)
296
+ map.set(item.source, item.blockedBy)
288
297
  if (!this.blockIdx.has(item.target)) {
289
298
  this.blockIdx.set(item.target, map)
290
299
  }
291
300
  }
301
+ if (item.blockedByViaList) {
302
+ const map = this.blockListIdx.get(item.target) ?? new Map()
303
+ map.set(item.source, item.blockedByViaList)
304
+ if (!this.blockListIdx.has(item.target)) {
305
+ this.blockListIdx.set(item.target, map)
306
+ }
307
+ }
292
308
  if (item.muting) {
293
309
  const set = this.muteIdx.get(item.source) ?? new Set()
294
310
  set.add(item.target)
@@ -314,7 +330,7 @@ export class BlockAndMuteState {
314
330
  }
315
331
  // block or list uri
316
332
  blocking(pair: RelationshipPair): string | null {
317
- return this.blockIdx.get(pair[0])?.get(pair[1]) ?? null
333
+ return this.blockIdx.get(pair[0])?.get(pair[1]) ?? this.blockList(pair)
318
334
  }
319
335
  // block or list uri
320
336
  blockedBy(pair: RelationshipPair): string | null {
@@ -324,6 +340,9 @@ export class BlockAndMuteState {
324
340
  return !!this.muteIdx.get(pair[0])?.has(pair[1]) || !!this.muteList(pair)
325
341
  }
326
342
  // list uri
343
+ blockList(pair: RelationshipPair): string | null {
344
+ return this.blockListIdx.get(pair[0])?.get(pair[1]) ?? null
345
+ }
327
346
  muteList(pair: RelationshipPair): string | null {
328
347
  return this.muteListIdx.get(pair[0])?.get(pair[1]) ?? null
329
348
  }
@@ -144,24 +144,22 @@ export class IndexingService {
144
144
  const handle: string | null =
145
145
  did === handleToDid ? atpData.handle.toLowerCase() : null
146
146
 
147
- if (actor && actor.handle !== handle) {
148
- const actorWithHandle =
149
- handle !== null
150
- ? await this.db.db
151
- .selectFrom('actor')
152
- .where('handle', '=', handle)
153
- .selectAll()
154
- .executeTakeFirst()
155
- : null
147
+ const actorWithHandle =
148
+ handle !== null
149
+ ? await this.db.db
150
+ .selectFrom('actor')
151
+ .where('handle', '=', handle)
152
+ .selectAll()
153
+ .executeTakeFirst()
154
+ : null
156
155
 
157
- // handle contention
158
- if (handle && actorWithHandle && did !== actorWithHandle.did) {
159
- await this.db.db
160
- .updateTable('actor')
161
- .where('actor.did', '=', actorWithHandle.did)
162
- .set({ handle: null })
163
- .execute()
164
- }
156
+ // handle contention
157
+ if (handle && actorWithHandle && did !== actorWithHandle.did) {
158
+ await this.db.db
159
+ .updateTable('actor')
160
+ .where('actor.did', '=', actorWithHandle.did)
161
+ .set({ handle: null })
162
+ .execute()
165
163
  }
166
164
 
167
165
  const actorInfo = { handle, indexedAt: timestamp }
@@ -7,7 +7,12 @@ import { ModerationAction, ModerationReport } from '../../db/tables/moderation'
7
7
  import { ModerationViews } from './views'
8
8
  import { ImageUriBuilder } from '../../image/uri'
9
9
  import { ImageInvalidator } from '../../image/invalidator'
10
- import { TAKEDOWN } from '../../lexicon/types/com/atproto/admin/defs'
10
+ import {
11
+ RepoRef,
12
+ RepoBlobRef,
13
+ TAKEDOWN,
14
+ } from '../../lexicon/types/com/atproto/admin/defs'
15
+ import { Main as StrongRef } from '../../lexicon/types/com/atproto/repo/strongRef'
11
16
  import { addHoursToDate } from '../../util/date'
12
17
 
13
18
  export class ModerationService {
@@ -355,7 +360,10 @@ export class ModerationService {
355
360
  createdBy,
356
361
  createdAt,
357
362
  reason,
358
- }: ReversibleModerationAction) {
363
+ }: ReversibleModerationAction): Promise<{
364
+ result: ModerationActionRow
365
+ restored?: TakedownSubjects
366
+ }> {
359
367
  this.db.assertTransaction()
360
368
  const result = await this.logReverseAction({
361
369
  id,
@@ -364,6 +372,8 @@ export class ModerationService {
364
372
  reason,
365
373
  })
366
374
 
375
+ let restored: TakedownSubjects | undefined
376
+
367
377
  if (
368
378
  result.action === TAKEDOWN &&
369
379
  result.subjectType === 'com.atproto.admin.defs#repoRef' &&
@@ -372,6 +382,15 @@ export class ModerationService {
372
382
  await this.reverseTakedownRepo({
373
383
  did: result.subjectDid,
374
384
  })
385
+ restored = {
386
+ did: result.subjectDid,
387
+ subjects: [
388
+ {
389
+ $type: 'com.atproto.admin.defs#repoRef',
390
+ did: result.subjectDid,
391
+ },
392
+ ],
393
+ }
375
394
  }
376
395
 
377
396
  if (
@@ -379,12 +398,35 @@ export class ModerationService {
379
398
  result.subjectType === 'com.atproto.repo.strongRef' &&
380
399
  result.subjectUri
381
400
  ) {
401
+ const uri = new AtUri(result.subjectUri)
382
402
  await this.reverseTakedownRecord({
383
- uri: new AtUri(result.subjectUri),
403
+ uri,
384
404
  })
405
+ const did = uri.hostname
406
+ const actionBlobs = await this.db.db
407
+ .selectFrom('moderation_action_subject_blob')
408
+ .where('actionId', '=', id)
409
+ .select('cid')
410
+ .execute()
411
+ restored = {
412
+ did,
413
+ subjects: [
414
+ {
415
+ $type: 'com.atproto.repo.strongRef',
416
+ uri: result.subjectUri,
417
+ cid: result.subjectCid ?? '',
418
+ },
419
+ ...actionBlobs.map((row) => ({
420
+ $type: 'com.atproto.admin.defs#repoBlobRef',
421
+ did,
422
+ cid: row.cid,
423
+ recordUri: result.subjectUri,
424
+ })),
425
+ ],
426
+ }
385
427
  }
386
428
 
387
- return result
429
+ return { result, restored }
388
430
  }
389
431
 
390
432
  async logReverseAction(
@@ -410,13 +452,27 @@ export class ModerationService {
410
452
  return result
411
453
  }
412
454
 
413
- async takedownRepo(info: { takedownId: number; did: string }) {
455
+ async takedownRepo(info: {
456
+ takedownId: number
457
+ did: string
458
+ }): Promise<TakedownSubjects> {
459
+ const { takedownId, did } = info
414
460
  await this.db.db
415
461
  .updateTable('actor')
416
- .set({ takedownId: info.takedownId })
417
- .where('did', '=', info.did)
462
+ .set({ takedownId })
463
+ .where('did', '=', did)
418
464
  .where('takedownId', 'is', null)
419
465
  .executeTakeFirst()
466
+
467
+ return {
468
+ did,
469
+ subjects: [
470
+ {
471
+ $type: 'com.atproto.admin.defs#repoRef',
472
+ did,
473
+ },
474
+ ],
475
+ }
420
476
  }
421
477
 
422
478
  async reverseTakedownRepo(info: { did: string }) {
@@ -430,26 +486,45 @@ export class ModerationService {
430
486
  async takedownRecord(info: {
431
487
  takedownId: number
432
488
  uri: AtUri
489
+ cid: CID
433
490
  blobCids?: CID[]
434
- }) {
491
+ }): Promise<TakedownSubjects> {
492
+ const { takedownId, uri, cid, blobCids } = info
493
+ const did = uri.hostname
435
494
  this.db.assertTransaction()
436
495
  await this.db.db
437
496
  .updateTable('record')
438
- .set({ takedownId: info.takedownId })
439
- .where('uri', '=', info.uri.toString())
497
+ .set({ takedownId })
498
+ .where('uri', '=', uri.toString())
440
499
  .where('takedownId', 'is', null)
441
500
  .executeTakeFirst()
442
- if (info.blobCids) {
501
+ if (blobCids) {
443
502
  await Promise.all(
444
- info.blobCids.map(async (cid) => {
503
+ blobCids.map(async (cid) => {
445
504
  const paths = ImageUriBuilder.presets.map((id) => {
446
- const uri = this.imgUriBuilder.getPresetUri(id, info.uri.host, cid)
447
- return uri.replace(this.imgUriBuilder.endpoint, '')
505
+ const imgUri = this.imgUriBuilder.getPresetUri(id, uri.host, cid)
506
+ return imgUri.replace(this.imgUriBuilder.endpoint, '')
448
507
  })
449
508
  await this.imgInvalidator.invalidate(cid.toString(), paths)
450
509
  }),
451
510
  )
452
511
  }
512
+ return {
513
+ did,
514
+ subjects: [
515
+ {
516
+ $type: 'com.atproto.repo.strongRef',
517
+ uri: uri.toString(),
518
+ cid: cid.toString(),
519
+ },
520
+ ...(blobCids || []).map((cid) => ({
521
+ $type: 'com.atproto.admin.defs#repoBlobRef',
522
+ did,
523
+ cid: cid.toString(),
524
+ recordUri: uri.toString(),
525
+ })),
526
+ ],
527
+ }
453
528
  }
454
529
 
455
530
  async reverseTakedownRecord(info: { uri: AtUri }) {
@@ -563,6 +638,11 @@ export class ModerationService {
563
638
  }
564
639
  }
565
640
 
641
+ export type TakedownSubjects = {
642
+ did: string
643
+ subjects: (RepoRef | RepoBlobRef | StrongRef)[]
644
+ }
645
+
566
646
  export type ModerationActionRow = Selectable<ModerationAction>
567
647
  export type ReversibleModerationAction = Pick<
568
648
  ModerationActionRow,
@@ -211,6 +211,7 @@ export class ModerationViews {
211
211
  .selectFrom('moderation_report')
212
212
  .where('subjectType', '=', 'com.atproto.repo.strongRef')
213
213
  .where('subjectUri', '=', result.uri)
214
+ .leftJoin('actor', 'actor.did', 'moderation_report.subjectDid')
214
215
  .orderBy('id', 'desc')
215
216
  .selectAll()
216
217
  .execute(),
@@ -419,12 +419,12 @@ Array [
419
419
  "$type": "app.bsky.embed.images#view",
420
420
  "images": Array [
421
421
  Object {
422
- "alt": "tests/image/fixtures/key-landscape-small.jpg",
422
+ "alt": "tests/sample-img/key-landscape-small.jpg",
423
423
  "fullsize": "https://bsky.public.url/img/feed_fullsize/plain/user(5)/cids(5)@jpeg",
424
424
  "thumb": "https://bsky.public.url/img/feed_thumbnail/plain/user(5)/cids(5)@jpeg",
425
425
  },
426
426
  Object {
427
- "alt": "tests/image/fixtures/key-alt.jpg",
427
+ "alt": "tests/sample-img/key-alt.jpg",
428
428
  "fullsize": "https://bsky.public.url/img/feed_fullsize/plain/user(5)/cids(6)@jpeg",
429
429
  "thumb": "https://bsky.public.url/img/feed_thumbnail/plain/user(5)/cids(6)@jpeg",
430
430
  },
@@ -475,7 +475,7 @@ Array [
475
475
  "$type": "app.bsky.embed.images",
476
476
  "images": Array [
477
477
  Object {
478
- "alt": "tests/image/fixtures/key-landscape-small.jpg",
478
+ "alt": "tests/sample-img/key-landscape-small.jpg",
479
479
  "image": Object {
480
480
  "$type": "blob",
481
481
  "mimeType": "image/jpeg",
@@ -486,7 +486,7 @@ Array [
486
486
  },
487
487
  },
488
488
  Object {
489
- "alt": "tests/image/fixtures/key-alt.jpg",
489
+ "alt": "tests/sample-img/key-alt.jpg",
490
490
  "image": Object {
491
491
  "$type": "blob",
492
492
  "mimeType": "image/jpeg",
@@ -679,12 +679,12 @@ Array [
679
679
  "$type": "app.bsky.embed.images#view",
680
680
  "images": Array [
681
681
  Object {
682
- "alt": "tests/image/fixtures/key-landscape-small.jpg",
682
+ "alt": "tests/sample-img/key-landscape-small.jpg",
683
683
  "fullsize": "https://bsky.public.url/img/feed_fullsize/plain/user(5)/cids(5)@jpeg",
684
684
  "thumb": "https://bsky.public.url/img/feed_thumbnail/plain/user(5)/cids(5)@jpeg",
685
685
  },
686
686
  Object {
687
- "alt": "tests/image/fixtures/key-alt.jpg",
687
+ "alt": "tests/sample-img/key-alt.jpg",
688
688
  "fullsize": "https://bsky.public.url/img/feed_fullsize/plain/user(5)/cids(6)@jpeg",
689
689
  "thumb": "https://bsky.public.url/img/feed_thumbnail/plain/user(5)/cids(6)@jpeg",
690
690
  },
@@ -735,7 +735,7 @@ Array [
735
735
  "$type": "app.bsky.embed.images",
736
736
  "images": Array [
737
737
  Object {
738
- "alt": "tests/image/fixtures/key-landscape-small.jpg",
738
+ "alt": "tests/sample-img/key-landscape-small.jpg",
739
739
  "image": Object {
740
740
  "$type": "blob",
741
741
  "mimeType": "image/jpeg",
@@ -746,7 +746,7 @@ Array [
746
746
  },
747
747
  },
748
748
  Object {
749
- "alt": "tests/image/fixtures/key-alt.jpg",
749
+ "alt": "tests/sample-img/key-alt.jpg",
750
750
  "image": Object {
751
751
  "$type": "blob",
752
752
  "mimeType": "image/jpeg",
@@ -906,12 +906,12 @@ Array [
906
906
  "$type": "app.bsky.embed.images#view",
907
907
  "images": Array [
908
908
  Object {
909
- "alt": "tests/image/fixtures/key-landscape-small.jpg",
909
+ "alt": "tests/sample-img/key-landscape-small.jpg",
910
910
  "fullsize": "https://bsky.public.url/img/feed_fullsize/plain/user(5)/cids(4)@jpeg",
911
911
  "thumb": "https://bsky.public.url/img/feed_thumbnail/plain/user(5)/cids(4)@jpeg",
912
912
  },
913
913
  Object {
914
- "alt": "tests/image/fixtures/key-alt.jpg",
914
+ "alt": "tests/sample-img/key-alt.jpg",
915
915
  "fullsize": "https://bsky.public.url/img/feed_fullsize/plain/user(5)/cids(5)@jpeg",
916
916
  "thumb": "https://bsky.public.url/img/feed_thumbnail/plain/user(5)/cids(5)@jpeg",
917
917
  },
@@ -962,7 +962,7 @@ Array [
962
962
  "$type": "app.bsky.embed.images",
963
963
  "images": Array [
964
964
  Object {
965
- "alt": "tests/image/fixtures/key-landscape-small.jpg",
965
+ "alt": "tests/sample-img/key-landscape-small.jpg",
966
966
  "image": Object {
967
967
  "$type": "blob",
968
968
  "mimeType": "image/jpeg",
@@ -973,7 +973,7 @@ Array [
973
973
  },
974
974
  },
975
975
  Object {
976
- "alt": "tests/image/fixtures/key-alt.jpg",
976
+ "alt": "tests/sample-img/key-alt.jpg",
977
977
  "image": Object {
978
978
  "$type": "blob",
979
979
  "mimeType": "image/jpeg",
@@ -101,7 +101,7 @@ Array [
101
101
  "$type": "app.bsky.embed.images#view",
102
102
  "images": Array [
103
103
  Object {
104
- "alt": "tests/image/fixtures/key-landscape-small.jpg",
104
+ "alt": "tests/sample-img/key-landscape-small.jpg",
105
105
  "fullsize": "https://bsky.public.url/img/feed_fullsize/plain/user(2)/cids(5)@jpeg",
106
106
  "thumb": "https://bsky.public.url/img/feed_thumbnail/plain/user(2)/cids(5)@jpeg",
107
107
  },
@@ -134,7 +134,7 @@ Array [
134
134
  "$type": "app.bsky.embed.images",
135
135
  "images": Array [
136
136
  Object {
137
- "alt": "tests/image/fixtures/key-landscape-small.jpg",
137
+ "alt": "tests/sample-img/key-landscape-small.jpg",
138
138
  "image": Object {
139
139
  "$type": "blob",
140
140
  "mimeType": "image/jpeg",
@@ -245,7 +245,7 @@ Array [
245
245
  "$type": "app.bsky.embed.images",
246
246
  "images": Array [
247
247
  Object {
248
- "alt": "tests/image/fixtures/key-landscape-small.jpg",
248
+ "alt": "tests/sample-img/key-landscape-small.jpg",
249
249
  "image": Object {
250
250
  "$type": "blob",
251
251
  "mimeType": "image/jpeg",
@@ -256,7 +256,7 @@ Array [
256
256
  },
257
257
  },
258
258
  Object {
259
- "alt": "tests/image/fixtures/key-alt.jpg",
259
+ "alt": "tests/sample-img/key-alt.jpg",
260
260
  "image": Object {
261
261
  "$type": "blob",
262
262
  "mimeType": "image/jpeg",