@atproto/bsky 0.0.15 → 0.0.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (236) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/dist/api/com/atproto/moderation/util.d.ts +4 -3
  3. package/dist/cache/read-through.d.ts +30 -0
  4. package/dist/config.d.ts +18 -0
  5. package/dist/context.d.ts +21 -6
  6. package/dist/daemon/config.d.ts +15 -0
  7. package/dist/daemon/context.d.ts +15 -0
  8. package/dist/daemon/index.d.ts +23 -0
  9. package/dist/daemon/logger.d.ts +3 -0
  10. package/dist/daemon/notifications.d.ts +18 -0
  11. package/dist/daemon/services.d.ts +11 -0
  12. package/dist/db/database-schema.d.ts +1 -2
  13. package/dist/db/index.js +41 -1
  14. package/dist/db/index.js.map +3 -3
  15. package/dist/db/migrations/20231003T202833377Z-create-moderation-subject-status.d.ts +3 -0
  16. package/dist/db/migrations/20231205T000257238Z-remove-did-cache.d.ts +3 -0
  17. package/dist/db/migrations/index.d.ts +2 -0
  18. package/dist/db/pagination.d.ts +2 -1
  19. package/dist/db/{periodic-moderation-action-reversal.d.ts → periodic-moderation-event-reversal.d.ts} +3 -5
  20. package/dist/db/tables/moderation.d.ts +24 -34
  21. package/dist/did-cache.d.ts +10 -7
  22. package/dist/feed-gen/types.d.ts +1 -1
  23. package/dist/index.d.ts +6 -1
  24. package/dist/index.js +4370 -2758
  25. package/dist/index.js.map +3 -3
  26. package/dist/indexer/context.d.ts +2 -0
  27. package/dist/indexer/index.d.ts +1 -0
  28. package/dist/lexicon/index.d.ts +23 -18
  29. package/dist/lexicon/lexicons.d.ts +561 -412
  30. package/dist/lexicon/types/app/bsky/feed/defs.d.ts +1 -7
  31. package/dist/lexicon/types/app/bsky/graph/defs.d.ts +1 -0
  32. package/dist/lexicon/types/com/atproto/admin/defs.d.ts +114 -48
  33. package/dist/lexicon/types/com/atproto/admin/{resolveModerationReports.d.ts → deleteAccount.d.ts} +2 -13
  34. package/dist/lexicon/types/com/atproto/admin/{takeModerationAction.d.ts → emitModerationEvent.d.ts} +5 -6
  35. package/dist/lexicon/types/com/atproto/admin/{getModerationAction.d.ts → getModerationEvent.d.ts} +1 -1
  36. package/dist/lexicon/types/com/atproto/admin/{getModerationActions.d.ts → queryModerationEvents.d.ts} +5 -1
  37. package/dist/lexicon/types/com/atproto/admin/{getModerationReports.d.ts → queryModerationStatuses.d.ts} +12 -6
  38. package/dist/lexicon/types/com/atproto/admin/sendEmail.d.ts +1 -0
  39. package/dist/lexicon/types/com/atproto/{admin/getModerationReport.d.ts → temp/importRepo.d.ts} +10 -7
  40. package/dist/lexicon/types/com/atproto/temp/pushBlob.d.ts +25 -0
  41. package/dist/lexicon/types/com/atproto/{admin/reverseModerationAction.d.ts → temp/transferAccount.d.ts} +11 -5
  42. package/dist/logger.d.ts +1 -0
  43. package/dist/migrate-moderation-data.d.ts +1 -0
  44. package/dist/redis.d.ts +10 -1
  45. package/dist/services/actor/index.d.ts +18 -4
  46. package/dist/services/actor/views.d.ts +6 -8
  47. package/dist/services/feed/index.d.ts +7 -4
  48. package/dist/services/feed/util.d.ts +9 -1
  49. package/dist/services/feed/views.d.ts +11 -21
  50. package/dist/services/graph/index.d.ts +5 -29
  51. package/dist/services/graph/types.d.ts +1 -0
  52. package/dist/services/index.d.ts +3 -7
  53. package/dist/services/label/index.d.ts +10 -4
  54. package/dist/services/moderation/index.d.ts +134 -72
  55. package/dist/services/moderation/pagination.d.ts +36 -0
  56. package/dist/services/moderation/status.d.ts +13 -0
  57. package/dist/services/moderation/types.d.ts +35 -0
  58. package/dist/services/moderation/views.d.ts +18 -14
  59. package/dist/services/types.d.ts +3 -0
  60. package/dist/services/util/notification.d.ts +5 -0
  61. package/dist/services/util/post.d.ts +6 -6
  62. package/dist/util/debug.d.ts +1 -1
  63. package/dist/util/retry.d.ts +1 -6
  64. package/package.json +11 -11
  65. package/src/api/app/bsky/feed/getActorFeeds.ts +2 -1
  66. package/src/api/app/bsky/feed/getActorLikes.ts +1 -3
  67. package/src/api/app/bsky/feed/getAuthorFeed.ts +1 -3
  68. package/src/api/app/bsky/feed/getFeed.ts +9 -9
  69. package/src/api/app/bsky/feed/getFeedGenerator.ts +3 -0
  70. package/src/api/app/bsky/feed/getFeedGenerators.ts +2 -1
  71. package/src/api/app/bsky/feed/getListFeed.ts +1 -3
  72. package/src/api/app/bsky/feed/getPostThread.ts +15 -54
  73. package/src/api/app/bsky/feed/getPosts.ts +21 -18
  74. package/src/api/app/bsky/feed/getSuggestedFeeds.ts +2 -1
  75. package/src/api/app/bsky/feed/getTimeline.ts +1 -3
  76. package/src/api/app/bsky/feed/searchPosts.ts +20 -17
  77. package/src/api/app/bsky/graph/getList.ts +6 -3
  78. package/src/api/app/bsky/graph/getListBlocks.ts +3 -2
  79. package/src/api/app/bsky/graph/getListMutes.ts +2 -1
  80. package/src/api/app/bsky/graph/getLists.ts +2 -1
  81. package/src/api/app/bsky/unspecced/getPopularFeedGenerators.ts +3 -1
  82. package/src/api/blob-resolver.ts +6 -11
  83. package/src/api/com/atproto/admin/emitModerationEvent.ts +220 -0
  84. package/src/api/com/atproto/admin/{getModerationActions.ts → getModerationEvent.ts} +5 -11
  85. package/src/api/com/atproto/admin/getRecord.ts +1 -0
  86. package/src/api/com/atproto/admin/{getModerationReports.ts → queryModerationEvents.ts} +13 -16
  87. package/src/api/com/atproto/admin/queryModerationStatuses.ts +55 -0
  88. package/src/api/com/atproto/moderation/createReport.ts +9 -7
  89. package/src/api/com/atproto/moderation/util.ts +38 -20
  90. package/src/api/index.ts +8 -14
  91. package/src/auth.ts +29 -21
  92. package/src/auto-moderator/index.ts +26 -19
  93. package/src/cache/read-through.ts +151 -0
  94. package/src/config.ts +90 -1
  95. package/src/context.ts +11 -7
  96. package/src/daemon/config.ts +60 -0
  97. package/src/daemon/context.ts +27 -0
  98. package/src/daemon/index.ts +78 -0
  99. package/src/daemon/logger.ts +6 -0
  100. package/src/daemon/notifications.ts +54 -0
  101. package/src/daemon/services.ts +22 -0
  102. package/src/db/database-schema.ts +0 -2
  103. package/src/db/migrations/20231003T202833377Z-create-moderation-subject-status.ts +123 -0
  104. package/src/db/migrations/20231205T000257238Z-remove-did-cache.ts +14 -0
  105. package/src/db/migrations/index.ts +2 -0
  106. package/src/db/pagination.ts +26 -3
  107. package/src/db/{periodic-moderation-action-reversal.ts → periodic-moderation-event-reversal.ts} +50 -46
  108. package/src/db/tables/moderation.ts +35 -52
  109. package/src/did-cache.ts +33 -56
  110. package/src/feed-gen/bsky-team.ts +1 -1
  111. package/src/feed-gen/hot-classic.ts +1 -1
  112. package/src/feed-gen/index.ts +0 -4
  113. package/src/feed-gen/mutuals.ts +6 -2
  114. package/src/feed-gen/types.ts +1 -1
  115. package/src/index.ts +57 -17
  116. package/src/indexer/context.ts +5 -0
  117. package/src/indexer/index.ts +10 -7
  118. package/src/lexicon/index.ts +80 -67
  119. package/src/lexicon/lexicons.ts +698 -507
  120. package/src/lexicon/types/app/bsky/feed/defs.ts +1 -18
  121. package/src/lexicon/types/app/bsky/graph/defs.ts +1 -0
  122. package/src/lexicon/types/com/atproto/admin/defs.ts +276 -84
  123. package/src/lexicon/types/com/atproto/admin/{resolveModerationReports.ts → deleteAccount.ts} +2 -13
  124. package/src/lexicon/types/com/atproto/admin/{takeModerationAction.ts → emitModerationEvent.ts} +13 -11
  125. package/src/lexicon/types/com/atproto/admin/{getModerationReport.ts → getModerationEvent.ts} +1 -1
  126. package/src/lexicon/types/com/atproto/admin/{getModerationActions.ts → queryModerationEvents.ts} +8 -1
  127. package/src/lexicon/types/com/atproto/admin/{getModerationReports.ts → queryModerationStatuses.ts} +21 -14
  128. package/src/lexicon/types/com/atproto/admin/sendEmail.ts +1 -0
  129. package/src/lexicon/types/com/atproto/{admin/getModerationAction.ts → temp/importRepo.ts} +11 -7
  130. package/src/lexicon/types/com/atproto/temp/pushBlob.ts +39 -0
  131. package/src/lexicon/types/com/atproto/{admin/reverseModerationAction.ts → temp/transferAccount.ts} +18 -5
  132. package/src/logger.ts +2 -0
  133. package/src/migrate-moderation-data.ts +414 -0
  134. package/src/redis.ts +43 -3
  135. package/src/services/actor/index.ts +55 -7
  136. package/src/services/actor/views.ts +18 -21
  137. package/src/services/feed/index.ts +52 -19
  138. package/src/services/feed/util.ts +47 -19
  139. package/src/services/feed/views.ts +87 -13
  140. package/src/services/graph/index.ts +21 -3
  141. package/src/services/graph/types.ts +1 -0
  142. package/src/services/index.ts +14 -14
  143. package/src/services/indexing/index.ts +7 -10
  144. package/src/services/indexing/plugins/block.ts +2 -3
  145. package/src/services/indexing/plugins/feed-generator.ts +2 -3
  146. package/src/services/indexing/plugins/follow.ts +2 -3
  147. package/src/services/indexing/plugins/like.ts +2 -3
  148. package/src/services/indexing/plugins/list-block.ts +2 -3
  149. package/src/services/indexing/plugins/list-item.ts +2 -3
  150. package/src/services/indexing/plugins/list.ts +2 -3
  151. package/src/services/indexing/plugins/post.ts +16 -4
  152. package/src/services/indexing/plugins/repost.ts +2 -3
  153. package/src/services/indexing/plugins/thread-gate.ts +2 -3
  154. package/src/services/label/index.ts +68 -25
  155. package/src/services/moderation/index.ts +380 -395
  156. package/src/services/moderation/pagination.ts +96 -0
  157. package/src/services/moderation/status.ts +241 -0
  158. package/src/services/moderation/types.ts +49 -0
  159. package/src/services/moderation/views.ts +278 -329
  160. package/src/services/types.ts +4 -0
  161. package/src/services/util/notification.ts +70 -0
  162. package/src/util/debug.ts +2 -2
  163. package/src/util/retry.ts +1 -44
  164. package/tests/__snapshots__/feed-generation.test.ts.snap +322 -6
  165. package/tests/__snapshots__/indexing.test.ts.snap +0 -6
  166. package/tests/admin/__snapshots__/get-record.test.ts.snap +30 -132
  167. package/tests/admin/__snapshots__/get-repo.test.ts.snap +14 -60
  168. package/tests/admin/__snapshots__/moderation-events.test.ts.snap +146 -0
  169. package/tests/admin/__snapshots__/moderation-statuses.test.ts.snap +64 -0
  170. package/tests/admin/__snapshots__/moderation.test.ts.snap +0 -125
  171. package/tests/admin/get-record.test.ts +5 -9
  172. package/tests/admin/get-repo.test.ts +10 -12
  173. package/tests/admin/moderation-events.test.ts +221 -0
  174. package/tests/admin/moderation-statuses.test.ts +145 -0
  175. package/tests/admin/moderation.test.ts +512 -860
  176. package/tests/admin/repo-search.test.ts +3 -3
  177. package/tests/algos/hot-classic.test.ts +1 -2
  178. package/tests/auth.test.ts +1 -1
  179. package/tests/auto-moderator/fuzzy-matcher.test.ts +2 -1
  180. package/tests/auto-moderator/labeler.test.ts +19 -20
  181. package/tests/auto-moderator/takedowns.test.ts +61 -28
  182. package/tests/blob-resolver.test.ts +4 -2
  183. package/tests/daemon.test.ts +191 -0
  184. package/tests/did-cache.test.ts +20 -5
  185. package/tests/feed-generation.test.ts +57 -9
  186. package/tests/handle-invalidation.test.ts +1 -5
  187. package/tests/indexing.test.ts +20 -13
  188. package/tests/redis-cache.test.ts +231 -0
  189. package/tests/seeds/basic.ts +3 -0
  190. package/tests/subscription/repo.test.ts +4 -7
  191. package/tests/views/__snapshots__/block-lists.test.ts.snap +3 -9
  192. package/tests/views/__snapshots__/blocks.test.ts.snap +0 -9
  193. package/tests/views/__snapshots__/mute-lists.test.ts.snap +5 -5
  194. package/tests/views/__snapshots__/mutes.test.ts.snap +0 -3
  195. package/tests/views/__snapshots__/thread.test.ts.snap +0 -30
  196. package/tests/views/actor-search.test.ts +2 -3
  197. package/tests/views/author-feed.test.ts +42 -36
  198. package/tests/views/follows.test.ts +40 -35
  199. package/tests/views/list-feed.test.ts +17 -9
  200. package/tests/views/notifications.test.ts +13 -9
  201. package/tests/views/profile.test.ts +20 -19
  202. package/tests/views/thread.test.ts +117 -94
  203. package/tests/views/threadgating.test.ts +89 -19
  204. package/tests/views/timeline.test.ts +21 -13
  205. package/dist/api/com/atproto/admin/resolveModerationReports.d.ts +0 -3
  206. package/dist/api/com/atproto/admin/reverseModerationAction.d.ts +0 -3
  207. package/dist/api/com/atproto/admin/takeModerationAction.d.ts +0 -3
  208. package/dist/db/tables/did-cache.d.ts +0 -10
  209. package/dist/feed-gen/best-of-follows.d.ts +0 -29
  210. package/dist/feed-gen/whats-hot.d.ts +0 -29
  211. package/dist/feed-gen/with-friends.d.ts +0 -3
  212. package/dist/label-cache.d.ts +0 -19
  213. package/src/api/com/atproto/admin/getModerationAction.ts +0 -44
  214. package/src/api/com/atproto/admin/getModerationReport.ts +0 -43
  215. package/src/api/com/atproto/admin/resolveModerationReports.ts +0 -24
  216. package/src/api/com/atproto/admin/reverseModerationAction.ts +0 -115
  217. package/src/api/com/atproto/admin/takeModerationAction.ts +0 -156
  218. package/src/db/tables/did-cache.ts +0 -13
  219. package/src/feed-gen/best-of-follows.ts +0 -74
  220. package/src/feed-gen/whats-hot.ts +0 -101
  221. package/src/feed-gen/with-friends.ts +0 -39
  222. package/src/label-cache.ts +0 -90
  223. package/tests/admin/__snapshots__/get-moderation-action.test.ts.snap +0 -172
  224. package/tests/admin/__snapshots__/get-moderation-actions.test.ts.snap +0 -178
  225. package/tests/admin/__snapshots__/get-moderation-report.test.ts.snap +0 -177
  226. package/tests/admin/__snapshots__/get-moderation-reports.test.ts.snap +0 -307
  227. package/tests/admin/get-moderation-action.test.ts +0 -100
  228. package/tests/admin/get-moderation-actions.test.ts +0 -164
  229. package/tests/admin/get-moderation-report.test.ts +0 -100
  230. package/tests/admin/get-moderation-reports.test.ts +0 -332
  231. package/tests/algos/whats-hot.test.ts +0 -118
  232. package/tests/algos/with-friends.test.ts +0 -145
  233. /package/dist/api/com/atproto/admin/{getModerationAction.d.ts → emitModerationEvent.d.ts} +0 -0
  234. /package/dist/api/com/atproto/admin/{getModerationActions.d.ts → getModerationEvent.d.ts} +0 -0
  235. /package/dist/api/com/atproto/admin/{getModerationReport.d.ts → queryModerationEvents.d.ts} +0 -0
  236. /package/dist/api/com/atproto/admin/{getModerationReports.d.ts → queryModerationStatuses.d.ts} +0 -0
@@ -0,0 +1,96 @@
1
+ import { InvalidRequestError } from '@atproto/xrpc-server'
2
+ import { DynamicModule, sql } from 'kysely'
3
+
4
+ import { Cursor, GenericKeyset } from '../../db/pagination'
5
+
6
+ type StatusKeysetParam = {
7
+ lastReviewedAt: string | null
8
+ lastReportedAt: string | null
9
+ id: number
10
+ }
11
+
12
+ export class StatusKeyset extends GenericKeyset<StatusKeysetParam, Cursor> {
13
+ labelResult(result: StatusKeysetParam): Cursor
14
+ labelResult(result: StatusKeysetParam) {
15
+ const primaryField = (
16
+ this.primary as ReturnType<DynamicModule['ref']>
17
+ ).dynamicReference.includes('lastReviewedAt')
18
+ ? 'lastReviewedAt'
19
+ : 'lastReportedAt'
20
+
21
+ return {
22
+ primary: result[primaryField]
23
+ ? new Date(`${result[primaryField]}`).getTime().toString()
24
+ : '',
25
+ secondary: result.id.toString(),
26
+ }
27
+ }
28
+ labeledResultToCursor(labeled: Cursor) {
29
+ return {
30
+ primary: labeled.primary,
31
+ secondary: labeled.secondary,
32
+ }
33
+ }
34
+ cursorToLabeledResult(cursor: Cursor) {
35
+ return {
36
+ primary: cursor.primary
37
+ ? new Date(parseInt(cursor.primary, 10)).toISOString()
38
+ : '',
39
+ secondary: cursor.secondary,
40
+ }
41
+ }
42
+ unpackCursor(cursorStr?: string): Cursor | undefined {
43
+ if (!cursorStr) return
44
+ const result = cursorStr.split('::')
45
+ const [primary, secondary, ...others] = result
46
+ if (!secondary || others.length > 0) {
47
+ throw new InvalidRequestError('Malformed cursor')
48
+ }
49
+ return {
50
+ primary,
51
+ secondary,
52
+ }
53
+ }
54
+ // This is specifically built to handle nullable columns as primary sorting column
55
+ getSql(labeled?: Cursor, direction?: 'asc' | 'desc') {
56
+ if (labeled === undefined) return
57
+ if (direction === 'asc') {
58
+ return !labeled.primary
59
+ ? sql`(${this.primary} IS NULL AND ${this.secondary} > ${labeled.secondary})`
60
+ : sql`((${this.primary}, ${this.secondary}) > (${labeled.primary}, ${labeled.secondary}) OR (${this.primary} is null))`
61
+ } else {
62
+ return !labeled.primary
63
+ ? sql`(${this.primary} IS NULL AND ${this.secondary} < ${labeled.secondary})`
64
+ : sql`((${this.primary}, ${this.secondary}) < (${labeled.primary}, ${labeled.secondary}) OR (${this.primary} is null))`
65
+ }
66
+ }
67
+ }
68
+
69
+ type TimeIdKeysetParam = {
70
+ id: number
71
+ createdAt: string
72
+ }
73
+ type TimeIdResult = TimeIdKeysetParam
74
+
75
+ export class TimeIdKeyset extends GenericKeyset<TimeIdKeysetParam, Cursor> {
76
+ labelResult(result: TimeIdResult): Cursor
77
+ labelResult(result: TimeIdResult) {
78
+ return { primary: result.createdAt, secondary: result.id.toString() }
79
+ }
80
+ labeledResultToCursor(labeled: Cursor) {
81
+ return {
82
+ primary: new Date(labeled.primary).getTime().toString(),
83
+ secondary: labeled.secondary,
84
+ }
85
+ }
86
+ cursorToLabeledResult(cursor: Cursor) {
87
+ const primaryDate = new Date(parseInt(cursor.primary, 10))
88
+ if (isNaN(primaryDate.getTime())) {
89
+ throw new InvalidRequestError('Malformed cursor')
90
+ }
91
+ return {
92
+ primary: primaryDate.toISOString(),
93
+ secondary: cursor.secondary,
94
+ }
95
+ }
96
+ }
@@ -0,0 +1,241 @@
1
+ // This may require better organization but for now, just dumping functions here containing DB queries for moderation status
2
+
3
+ import { AtUri } from '@atproto/syntax'
4
+ import { PrimaryDatabase } from '../../db'
5
+ import { ModerationSubjectStatus } from '../../db/tables/moderation'
6
+ import {
7
+ REVIEWOPEN,
8
+ REVIEWCLOSED,
9
+ REVIEWESCALATED,
10
+ } from '../../lexicon/types/com/atproto/admin/defs'
11
+ import { ModerationEventRow, ModerationSubjectStatusRow } from './types'
12
+ import { HOUR } from '@atproto/common'
13
+ import { CID } from 'multiformats/cid'
14
+ import { sql } from 'kysely'
15
+
16
+ const getSubjectStatusForModerationEvent = ({
17
+ action,
18
+ createdBy,
19
+ createdAt,
20
+ durationInHours,
21
+ }: {
22
+ action: string
23
+ createdBy: string
24
+ createdAt: string
25
+ durationInHours: number | null
26
+ }): Partial<ModerationSubjectStatusRow> | null => {
27
+ switch (action) {
28
+ case 'com.atproto.admin.defs#modEventAcknowledge':
29
+ return {
30
+ lastReviewedBy: createdBy,
31
+ reviewState: REVIEWCLOSED,
32
+ lastReviewedAt: createdAt,
33
+ }
34
+ case 'com.atproto.admin.defs#modEventReport':
35
+ return {
36
+ reviewState: REVIEWOPEN,
37
+ lastReportedAt: createdAt,
38
+ }
39
+ case 'com.atproto.admin.defs#modEventEscalate':
40
+ return {
41
+ lastReviewedBy: createdBy,
42
+ reviewState: REVIEWESCALATED,
43
+ lastReviewedAt: createdAt,
44
+ }
45
+ case 'com.atproto.admin.defs#modEventReverseTakedown':
46
+ return {
47
+ lastReviewedBy: createdBy,
48
+ reviewState: REVIEWCLOSED,
49
+ takendown: false,
50
+ suspendUntil: null,
51
+ lastReviewedAt: createdAt,
52
+ }
53
+ case 'com.atproto.admin.defs#modEventUnmute':
54
+ return {
55
+ lastReviewedBy: createdBy,
56
+ muteUntil: null,
57
+ reviewState: REVIEWOPEN,
58
+ lastReviewedAt: createdAt,
59
+ }
60
+ case 'com.atproto.admin.defs#modEventTakedown':
61
+ return {
62
+ takendown: true,
63
+ lastReviewedBy: createdBy,
64
+ reviewState: REVIEWCLOSED,
65
+ lastReviewedAt: createdAt,
66
+ suspendUntil: durationInHours
67
+ ? new Date(Date.now() + durationInHours * HOUR).toISOString()
68
+ : null,
69
+ }
70
+ case 'com.atproto.admin.defs#modEventMute':
71
+ return {
72
+ lastReviewedBy: createdBy,
73
+ reviewState: REVIEWOPEN,
74
+ lastReviewedAt: createdAt,
75
+ // By default, mute for 24hrs
76
+ muteUntil: new Date(
77
+ Date.now() + (durationInHours || 24) * HOUR,
78
+ ).toISOString(),
79
+ }
80
+ case 'com.atproto.admin.defs#modEventComment':
81
+ return {
82
+ lastReviewedBy: createdBy,
83
+ lastReviewedAt: createdAt,
84
+ }
85
+ default:
86
+ return null
87
+ }
88
+ }
89
+
90
+ // Based on a given moderation action event, this function will update the moderation status of the subject
91
+ // If there's no existing status, it will create one
92
+ // If the action event does not affect the status, it will do nothing
93
+ export const adjustModerationSubjectStatus = async (
94
+ db: PrimaryDatabase,
95
+ moderationEvent: ModerationEventRow,
96
+ blobCids?: CID[],
97
+ ) => {
98
+ const {
99
+ action,
100
+ subjectDid,
101
+ subjectUri,
102
+ subjectCid,
103
+ createdBy,
104
+ meta,
105
+ comment,
106
+ createdAt,
107
+ } = moderationEvent
108
+
109
+ const subjectStatus = getSubjectStatusForModerationEvent({
110
+ action,
111
+ createdBy,
112
+ createdAt,
113
+ durationInHours: moderationEvent.durationInHours,
114
+ })
115
+
116
+ // If there are no subjectStatus that means there are no side-effect of the incoming event
117
+ if (!subjectStatus) {
118
+ return null
119
+ }
120
+
121
+ const now = new Date().toISOString()
122
+ // If subjectUri exists, it's not a repoRef so pass along the uri to get identifier back
123
+ const identifier = getStatusIdentifierFromSubject(subjectUri || subjectDid)
124
+
125
+ db.assertTransaction()
126
+
127
+ const currentStatus = await db.db
128
+ .selectFrom('moderation_subject_status')
129
+ .where('did', '=', identifier.did)
130
+ .where('recordPath', '=', identifier.recordPath)
131
+ .selectAll()
132
+ .executeTakeFirst()
133
+
134
+ if (
135
+ currentStatus?.reviewState === REVIEWESCALATED &&
136
+ subjectStatus.reviewState === REVIEWOPEN
137
+ ) {
138
+ // If the current status is escalated and the incoming event is to open the review
139
+ // We want to keep the status as escalated
140
+ subjectStatus.reviewState = REVIEWESCALATED
141
+ }
142
+
143
+ // Set these because we don't want to override them if they're already set
144
+ const defaultData = {
145
+ comment: null,
146
+ // Defaulting reviewState to open for any event may not be the desired behavior.
147
+ // For instance, if a subject never had any event and we just want to leave a comment to keep an eye on it
148
+ // that shouldn't mean we want to review the subject
149
+ reviewState: REVIEWOPEN,
150
+ recordCid: subjectCid || null,
151
+ }
152
+ const newStatus = {
153
+ ...defaultData,
154
+ ...subjectStatus,
155
+ }
156
+
157
+ if (
158
+ action === 'com.atproto.admin.defs#modEventReverseTakedown' &&
159
+ !subjectStatus.takendown
160
+ ) {
161
+ newStatus.takendown = false
162
+ subjectStatus.takendown = false
163
+ }
164
+
165
+ if (action === 'com.atproto.admin.defs#modEventComment' && meta?.sticky) {
166
+ newStatus.comment = comment
167
+ subjectStatus.comment = comment
168
+ }
169
+
170
+ if (blobCids?.length) {
171
+ const newBlobCids = sql<string[]>`${JSON.stringify(
172
+ blobCids.map((c) => c.toString()),
173
+ )}` as unknown as ModerationSubjectStatusRow['blobCids']
174
+ newStatus.blobCids = newBlobCids
175
+ subjectStatus.blobCids = newBlobCids
176
+ }
177
+
178
+ const insertQuery = db.db
179
+ .insertInto('moderation_subject_status')
180
+ .values({
181
+ ...identifier,
182
+ ...newStatus,
183
+ createdAt: now,
184
+ updatedAt: now,
185
+ // TODO: Need to get the types right here.
186
+ } as ModerationSubjectStatusRow)
187
+ .onConflict((oc) =>
188
+ oc.constraint('moderation_status_unique_idx').doUpdateSet({
189
+ ...subjectStatus,
190
+ updatedAt: now,
191
+ }),
192
+ )
193
+
194
+ const status = await insertQuery.executeTakeFirst()
195
+ return status
196
+ }
197
+
198
+ type ModerationSubjectStatusFilter =
199
+ | Pick<ModerationSubjectStatus, 'did'>
200
+ | Pick<ModerationSubjectStatus, 'did' | 'recordPath'>
201
+ | Pick<ModerationSubjectStatus, 'did' | 'recordPath' | 'recordCid'>
202
+ export const getModerationSubjectStatus = async (
203
+ db: PrimaryDatabase,
204
+ filters: ModerationSubjectStatusFilter,
205
+ ) => {
206
+ let builder = db.db
207
+ .selectFrom('moderation_subject_status')
208
+ // DID will always be passed at the very least
209
+ .where('did', '=', filters.did)
210
+ .where('recordPath', '=', 'recordPath' in filters ? filters.recordPath : '')
211
+
212
+ if ('recordCid' in filters) {
213
+ builder = builder.where('recordCid', '=', filters.recordCid)
214
+ } else {
215
+ builder = builder.where('recordCid', 'is', null)
216
+ }
217
+
218
+ return builder.executeTakeFirst()
219
+ }
220
+
221
+ export const getStatusIdentifierFromSubject = (
222
+ subject: string | AtUri,
223
+ ): { did: string; recordPath: string } => {
224
+ const isSubjectString = typeof subject === 'string'
225
+ if (isSubjectString && subject.startsWith('did:')) {
226
+ return {
227
+ did: subject,
228
+ recordPath: '',
229
+ }
230
+ }
231
+
232
+ if (isSubjectString && !subject.startsWith('at://')) {
233
+ throw new Error('Subject is neither a did nor an at-uri')
234
+ }
235
+
236
+ const uri = isSubjectString ? new AtUri(subject) : subject
237
+ return {
238
+ did: uri.host,
239
+ recordPath: `${uri.collection}/${uri.rkey}`,
240
+ }
241
+ }
@@ -0,0 +1,49 @@
1
+ import { Selectable } from 'kysely'
2
+ import {
3
+ ModerationEvent,
4
+ ModerationSubjectStatus,
5
+ } from '../../db/tables/moderation'
6
+ import { AtUri } from '@atproto/syntax'
7
+ import { CID } from 'multiformats/cid'
8
+ import { ComAtprotoAdminDefs } from '@atproto/api'
9
+
10
+ export type SubjectInfo =
11
+ | {
12
+ subjectType: 'com.atproto.admin.defs#repoRef'
13
+ subjectDid: string
14
+ subjectUri: null
15
+ subjectCid: null
16
+ }
17
+ | {
18
+ subjectType: 'com.atproto.repo.strongRef'
19
+ subjectDid: string
20
+ subjectUri: string
21
+ subjectCid: string
22
+ }
23
+
24
+ export type ModerationEventRow = Selectable<ModerationEvent>
25
+ export type ReversibleModerationEvent = Pick<
26
+ ModerationEventRow,
27
+ 'createdBy' | 'comment' | 'action'
28
+ > & {
29
+ createdAt?: Date
30
+ subject: { did: string } | { uri: AtUri; cid: CID }
31
+ }
32
+
33
+ export type ModerationEventRowWithHandle = ModerationEventRow & {
34
+ subjectHandle?: string | null
35
+ creatorHandle?: string | null
36
+ }
37
+ export type ModerationSubjectStatusRow = Selectable<ModerationSubjectStatus>
38
+ export type ModerationSubjectStatusRowWithHandle =
39
+ ModerationSubjectStatusRow & { handle: string | null }
40
+
41
+ export type ModEventType =
42
+ | ComAtprotoAdminDefs.ModEventTakedown
43
+ | ComAtprotoAdminDefs.ModEventAcknowledge
44
+ | ComAtprotoAdminDefs.ModEventEscalate
45
+ | ComAtprotoAdminDefs.ModEventComment
46
+ | ComAtprotoAdminDefs.ModEventLabel
47
+ | ComAtprotoAdminDefs.ModEventReport
48
+ | ComAtprotoAdminDefs.ModEventMute
49
+ | ComAtprotoAdminDefs.ModEventReverseTakedown