@atproto/bsky 0.0.15 → 0.0.16

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