@atproto/ozone 0.0.16 → 0.0.17-next.1

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 (157) hide show
  1. package/dist/api/util.d.ts +10 -0
  2. package/dist/auth-verifier.d.ts +8 -12
  3. package/dist/communication-service/template.d.ts +2 -2
  4. package/dist/config/config.d.ts +6 -0
  5. package/dist/config/env.d.ts +3 -2
  6. package/dist/config/secrets.d.ts +0 -2
  7. package/dist/context.d.ts +6 -0
  8. package/dist/daemon/blob-diverter.d.ts +26 -0
  9. package/dist/daemon/event-pusher.d.ts +6 -0
  10. package/dist/daemon/index.d.ts +1 -0
  11. package/dist/db/index.js +21 -1
  12. package/dist/db/index.js.map +3 -3
  13. package/dist/db/migrations/20240228T003647759Z-add-label-sigs.d.ts +3 -0
  14. package/dist/db/migrations/index.d.ts +1 -0
  15. package/dist/db/schema/index.d.ts +2 -1
  16. package/dist/db/schema/label.d.ts +4 -0
  17. package/dist/db/schema/moderation_event.d.ts +1 -1
  18. package/dist/db/schema/moderation_subject_status.d.ts +2 -2
  19. package/dist/db/schema/signing_key.d.ts +9 -0
  20. package/dist/index.js +10400 -10313
  21. package/dist/index.js.map +3 -3
  22. package/dist/lexicon/index.d.ts +55 -27
  23. package/dist/lexicon/lexicons.d.ts +5046 -4757
  24. package/dist/lexicon/types/app/bsky/actor/defs.d.ts +23 -1
  25. package/dist/lexicon/types/app/bsky/embed/record.d.ts +2 -1
  26. package/dist/lexicon/types/app/bsky/feed/defs.d.ts +1 -0
  27. package/dist/lexicon/types/app/bsky/graph/defs.d.ts +3 -0
  28. package/dist/lexicon/types/app/bsky/labeler/defs.d.ts +41 -0
  29. package/dist/lexicon/types/app/bsky/labeler/getServices.d.ts +36 -0
  30. package/dist/lexicon/types/app/bsky/labeler/service.d.ts +14 -0
  31. package/dist/lexicon/types/com/atproto/admin/defs.d.ts +0 -304
  32. package/dist/lexicon/types/com/atproto/label/defs.d.ts +23 -0
  33. package/dist/lexicon/types/{com/atproto/admin/createCommunicationTemplate.d.ts → tools/ozone/communication/createTemplate.d.ts} +2 -2
  34. package/dist/lexicon/types/tools/ozone/communication/defs.d.ts +14 -0
  35. package/dist/lexicon/types/{com/atproto/admin/listCommunicationTemplates.d.ts → tools/ozone/communication/listTemplates.d.ts} +2 -2
  36. package/dist/lexicon/types/{com/atproto/admin/updateCommunicationTemplate.d.ts → tools/ozone/communication/updateTemplate.d.ts} +2 -2
  37. package/dist/lexicon/types/tools/ozone/moderation/defs.d.ts +269 -0
  38. package/dist/lexicon/types/{com/atproto/admin/emitModerationEvent.d.ts → tools/ozone/moderation/emitEvent.d.ts} +5 -4
  39. package/dist/lexicon/types/{com/atproto/admin/getModerationEvent.d.ts → tools/ozone/moderation/getEvent.d.ts} +2 -2
  40. package/dist/lexicon/types/{com/atproto/admin → tools/ozone/moderation}/getRecord.d.ts +2 -2
  41. package/dist/lexicon/types/{com/atproto/admin → tools/ozone/moderation}/getRepo.d.ts +2 -2
  42. package/dist/lexicon/types/{com/atproto/admin/queryModerationEvents.d.ts → tools/ozone/moderation/queryEvents.d.ts} +2 -2
  43. package/dist/lexicon/types/{com/atproto/admin/queryModerationStatuses.d.ts → tools/ozone/moderation/queryStatuses.d.ts} +2 -2
  44. package/dist/lexicon/types/{com/atproto/admin → tools/ozone/moderation}/searchRepos.d.ts +2 -2
  45. package/dist/mod-service/index.d.ts +16 -15
  46. package/dist/mod-service/subject.d.ts +1 -1
  47. package/dist/mod-service/types.d.ts +2 -2
  48. package/dist/mod-service/util.d.ts +6 -0
  49. package/dist/mod-service/views.d.ts +9 -3
  50. package/dist/sequencer/sequencer.d.ts +6 -4
  51. package/dist/util.d.ts +2 -0
  52. package/package.json +9 -8
  53. package/src/api/{admin/createCommunicationTemplate.ts → communication/createTemplate.ts} +2 -2
  54. package/src/api/{admin/deleteCommunicationTemplate.ts → communication/deleteTemplate.ts} +2 -2
  55. package/src/api/{admin/listCommunicationTemplates.ts → communication/listTemplates.ts} +2 -2
  56. package/src/api/{admin/updateCommunicationTemplate.ts → communication/updateTemplate.ts} +2 -2
  57. package/src/api/index.ts +21 -21
  58. package/src/api/{temp → label}/fetchLabels.ts +5 -3
  59. package/src/api/label/queryLabels.ts +4 -2
  60. package/src/api/moderation/emitEvent.ts +218 -0
  61. package/src/api/{admin/getModerationEvent.ts → moderation/getEvent.ts} +2 -2
  62. package/src/api/{admin → moderation}/getRecord.ts +3 -3
  63. package/src/api/{admin → moderation}/getRepo.ts +3 -3
  64. package/src/api/{admin/queryModerationEvents.ts → moderation/queryEvents.ts} +3 -3
  65. package/src/api/{admin/queryModerationStatuses.ts → moderation/queryStatuses.ts} +3 -3
  66. package/src/api/{admin → moderation}/searchRepos.ts +2 -2
  67. package/src/api/proxied.ts +8 -8
  68. package/src/api/{moderation → report}/createReport.ts +2 -3
  69. package/src/api/util.ts +119 -0
  70. package/src/auth-verifier.ts +20 -30
  71. package/src/communication-service/template.ts +2 -2
  72. package/src/config/config.ts +24 -7
  73. package/src/config/env.ts +6 -4
  74. package/src/config/secrets.ts +0 -6
  75. package/src/context.ts +36 -12
  76. package/src/daemon/blob-diverter.ts +150 -0
  77. package/src/daemon/context.ts +11 -7
  78. package/src/daemon/event-pusher.ts +58 -15
  79. package/src/daemon/index.ts +1 -0
  80. package/src/db/migrations/20240228T003647759Z-add-label-sigs.ts +25 -0
  81. package/src/db/migrations/index.ts +1 -0
  82. package/src/db/schema/index.ts +2 -0
  83. package/src/db/schema/label.ts +3 -0
  84. package/src/db/schema/moderation_event.ts +11 -11
  85. package/src/db/schema/moderation_subject_status.ts +7 -2
  86. package/src/db/schema/signing_key.ts +10 -0
  87. package/src/lexicon/index.ts +200 -137
  88. package/src/lexicon/lexicons.ts +6310 -6012
  89. package/src/lexicon/types/app/bsky/actor/defs.ts +57 -1
  90. package/src/lexicon/types/app/bsky/embed/record.ts +2 -0
  91. package/src/lexicon/types/app/bsky/feed/defs.ts +1 -0
  92. package/src/lexicon/types/app/bsky/graph/defs.ts +3 -0
  93. package/src/lexicon/types/app/bsky/labeler/defs.ts +93 -0
  94. package/src/lexicon/types/app/bsky/labeler/getServices.ts +51 -0
  95. package/src/lexicon/types/app/bsky/labeler/service.ts +31 -0
  96. package/src/lexicon/types/com/atproto/admin/defs.ts +0 -694
  97. package/src/lexicon/types/com/atproto/label/defs.ts +78 -0
  98. package/src/lexicon/types/{com/atproto/admin/createCommunicationTemplate.ts → tools/ozone/communication/createTemplate.ts} +2 -2
  99. package/src/lexicon/types/tools/ozone/communication/defs.ts +35 -0
  100. package/src/lexicon/types/{com/atproto/admin/listCommunicationTemplates.ts → tools/ozone/communication/listTemplates.ts} +2 -2
  101. package/src/lexicon/types/{com/atproto/admin/updateCommunicationTemplate.ts → tools/ozone/communication/updateTemplate.ts} +2 -2
  102. package/src/lexicon/types/tools/ozone/moderation/defs.ts +641 -0
  103. package/src/lexicon/types/{com/atproto/admin/emitModerationEvent.ts → tools/ozone/moderation/emitEvent.ts} +15 -14
  104. package/src/lexicon/types/{com/atproto/admin/getModerationEvent.ts → tools/ozone/moderation/getEvent.ts} +2 -2
  105. package/src/lexicon/types/{com/atproto/admin → tools/ozone/moderation}/getRecord.ts +2 -2
  106. package/src/lexicon/types/{com/atproto/admin → tools/ozone/moderation}/getRepo.ts +2 -2
  107. package/src/lexicon/types/{com/atproto/admin/queryModerationEvents.ts → tools/ozone/moderation/queryEvents.ts} +3 -3
  108. package/src/lexicon/types/{com/atproto/admin/queryModerationStatuses.ts → tools/ozone/moderation/queryStatuses.ts} +2 -2
  109. package/src/lexicon/types/{com/atproto/admin → tools/ozone/moderation}/searchRepos.ts +2 -2
  110. package/src/mod-service/index.ts +46 -50
  111. package/src/mod-service/lang.ts +1 -1
  112. package/src/mod-service/status.ts +60 -41
  113. package/src/mod-service/subject.ts +1 -1
  114. package/src/mod-service/types.ts +10 -10
  115. package/src/mod-service/util.ts +49 -5
  116. package/src/mod-service/views.ts +45 -18
  117. package/src/sequencer/sequencer.ts +12 -11
  118. package/src/util.ts +21 -0
  119. package/tests/__snapshots__/blob-divert.test.ts.snap +22 -0
  120. package/tests/__snapshots__/get-record.test.ts.snap +14 -6
  121. package/tests/__snapshots__/get-repo.test.ts.snap +7 -3
  122. package/tests/__snapshots__/moderation-events.test.ts.snap +8 -8
  123. package/tests/__snapshots__/moderation-statuses.test.ts.snap +6 -6
  124. package/tests/_util.ts +5 -0
  125. package/tests/blob-divert.test.ts +87 -0
  126. package/tests/communication-templates.test.ts +33 -37
  127. package/tests/db.test.ts +6 -6
  128. package/tests/get-record.test.ts +22 -12
  129. package/tests/get-repo.test.ts +33 -21
  130. package/tests/moderation-appeals.test.ts +39 -67
  131. package/tests/moderation-events.test.ts +99 -142
  132. package/tests/moderation-status-tags.test.ts +20 -37
  133. package/tests/moderation-statuses.test.ts +132 -65
  134. package/tests/moderation.test.ts +147 -301
  135. package/tests/query-labels.test.ts +86 -10
  136. package/tests/repo-search.test.ts +18 -11
  137. package/tests/sequencer.test.ts +6 -3
  138. package/dist/api/admin/util.d.ts +0 -5
  139. package/dist/api/moderation/util.d.ts +0 -4
  140. package/src/api/admin/emitModerationEvent.ts +0 -170
  141. package/src/api/admin/util.ts +0 -54
  142. package/src/api/moderation/util.ts +0 -67
  143. /package/dist/api/{admin/createCommunicationTemplate.d.ts → communication/createTemplate.d.ts} +0 -0
  144. /package/dist/api/{admin/deleteCommunicationTemplate.d.ts → communication/deleteTemplate.d.ts} +0 -0
  145. /package/dist/api/{admin/emitModerationEvent.d.ts → communication/listTemplates.d.ts} +0 -0
  146. /package/dist/api/{admin/getModerationEvent.d.ts → communication/updateTemplate.d.ts} +0 -0
  147. /package/dist/api/{temp → label}/fetchLabels.d.ts +0 -0
  148. /package/dist/api/{admin/getRecord.d.ts → moderation/emitEvent.d.ts} +0 -0
  149. /package/dist/api/{admin/getRepo.d.ts → moderation/getEvent.d.ts} +0 -0
  150. /package/dist/api/{admin/listCommunicationTemplates.d.ts → moderation/getRecord.d.ts} +0 -0
  151. /package/dist/api/{admin/queryModerationEvents.d.ts → moderation/getRepo.d.ts} +0 -0
  152. /package/dist/api/{admin/queryModerationStatuses.d.ts → moderation/queryEvents.d.ts} +0 -0
  153. /package/dist/api/{admin/searchRepos.d.ts → moderation/queryStatuses.d.ts} +0 -0
  154. /package/dist/api/{admin/updateCommunicationTemplate.d.ts → moderation/searchRepos.d.ts} +0 -0
  155. /package/dist/api/{moderation → report}/createReport.d.ts +0 -0
  156. /package/dist/lexicon/types/{com/atproto/admin/deleteCommunicationTemplate.d.ts → tools/ozone/communication/deleteTemplate.d.ts} +0 -0
  157. /package/src/lexicon/types/{com/atproto/admin/deleteCommunicationTemplate.ts → tools/ozone/communication/deleteTemplate.ts} +0 -0
@@ -7,7 +7,7 @@ import { lexicons } from '../../../../lexicons'
7
7
  import { isObj, hasProp } from '../../../../util'
8
8
  import { CID } from 'multiformats/cid'
9
9
  import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server'
10
- import * as ComAtprotoAdminDefs from './defs'
10
+ import * as ToolsOzoneModerationDefs from './defs'
11
11
 
12
12
  export interface QueryParams {
13
13
  uri: string
@@ -15,7 +15,7 @@ export interface QueryParams {
15
15
  }
16
16
 
17
17
  export type InputSchema = undefined
18
- export type OutputSchema = ComAtprotoAdminDefs.RecordViewDetail
18
+ export type OutputSchema = ToolsOzoneModerationDefs.RecordViewDetail
19
19
  export type HandlerInput = undefined
20
20
 
21
21
  export interface HandlerSuccess {
@@ -7,14 +7,14 @@ import { lexicons } from '../../../../lexicons'
7
7
  import { isObj, hasProp } from '../../../../util'
8
8
  import { CID } from 'multiformats/cid'
9
9
  import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server'
10
- import * as ComAtprotoAdminDefs from './defs'
10
+ import * as ToolsOzoneModerationDefs from './defs'
11
11
 
12
12
  export interface QueryParams {
13
13
  did: string
14
14
  }
15
15
 
16
16
  export type InputSchema = undefined
17
- export type OutputSchema = ComAtprotoAdminDefs.RepoViewDetail
17
+ export type OutputSchema = ToolsOzoneModerationDefs.RepoViewDetail
18
18
  export type HandlerInput = undefined
19
19
 
20
20
  export interface HandlerSuccess {
@@ -7,10 +7,10 @@ import { lexicons } from '../../../../lexicons'
7
7
  import { isObj, hasProp } from '../../../../util'
8
8
  import { CID } from 'multiformats/cid'
9
9
  import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server'
10
- import * as ComAtprotoAdminDefs from './defs'
10
+ import * as ToolsOzoneModerationDefs from './defs'
11
11
 
12
12
  export interface QueryParams {
13
- /** The types of events (fully qualified string in the format of com.atproto.admin#modEvent<name>) to filter by. If not specified, all events are returned. */
13
+ /** The types of events (fully qualified string in the format of tools.ozone.moderation.defs#modEvent<name>) to filter by. If not specified, all events are returned. */
14
14
  types?: string[]
15
15
  createdBy?: string
16
16
  /** Sort direction for the events. Defaults to descending order of created at timestamp. */
@@ -43,7 +43,7 @@ export type InputSchema = undefined
43
43
 
44
44
  export interface OutputSchema {
45
45
  cursor?: string
46
- events: ComAtprotoAdminDefs.ModEventView[]
46
+ events: ToolsOzoneModerationDefs.ModEventView[]
47
47
  [k: string]: unknown
48
48
  }
49
49
 
@@ -7,7 +7,7 @@ import { lexicons } from '../../../../lexicons'
7
7
  import { isObj, hasProp } from '../../../../util'
8
8
  import { CID } from 'multiformats/cid'
9
9
  import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server'
10
- import * as ComAtprotoAdminDefs from './defs'
10
+ import * as ToolsOzoneModerationDefs from './defs'
11
11
 
12
12
  export interface QueryParams {
13
13
  subject?: string
@@ -44,7 +44,7 @@ export type InputSchema = undefined
44
44
 
45
45
  export interface OutputSchema {
46
46
  cursor?: string
47
- subjectStatuses: ComAtprotoAdminDefs.SubjectStatusView[]
47
+ subjectStatuses: ToolsOzoneModerationDefs.SubjectStatusView[]
48
48
  [k: string]: unknown
49
49
  }
50
50
 
@@ -7,7 +7,7 @@ import { lexicons } from '../../../../lexicons'
7
7
  import { isObj, hasProp } from '../../../../util'
8
8
  import { CID } from 'multiformats/cid'
9
9
  import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server'
10
- import * as ComAtprotoAdminDefs from './defs'
10
+ import * as ToolsOzoneModerationDefs from './defs'
11
11
 
12
12
  export interface QueryParams {
13
13
  /** DEPRECATED: use 'q' instead */
@@ -21,7 +21,7 @@ export type InputSchema = undefined
21
21
 
22
22
  export interface OutputSchema {
23
23
  cursor?: string
24
- repos: ComAtprotoAdminDefs.RepoView[]
24
+ repos: ToolsOzoneModerationDefs.RepoView[]
25
25
  [k: string]: unknown
26
26
  }
27
27
 
@@ -4,6 +4,7 @@ import { CID } from 'multiformats/cid'
4
4
  import { AtUri, INVALID_HANDLE } from '@atproto/syntax'
5
5
  import { InvalidRequestError } from '@atproto/xrpc-server'
6
6
  import { addHoursToDate } from '@atproto/common'
7
+ import { Keypair } from '@atproto/crypto'
7
8
  import { IdResolver } from '@atproto/identity'
8
9
  import AtpAgent from '@atproto/api'
9
10
  import { Database } from '../db'
@@ -17,9 +18,8 @@ import {
17
18
  isModEventTakedown,
18
19
  isModEventEmail,
19
20
  isModEventTag,
20
- RepoRef,
21
- RepoBlobRef,
22
- } from '../lexicon/types/com/atproto/admin/defs'
21
+ } from '../lexicon/types/tools/ozone/moderation/defs'
22
+ import { RepoRef, RepoBlobRef } from '../lexicon/types/com/atproto/admin/defs'
23
23
  import {
24
24
  adjustModerationSubjectStatus,
25
25
  getStatusIdentifierFromSubject,
@@ -46,6 +46,7 @@ import { LabelChannel } from '../db/schema/label'
46
46
  import { BlobPushEvent } from '../db/schema/blob_push_event'
47
47
  import { BackgroundQueue } from '../background'
48
48
  import { EventPusher } from '../daemon'
49
+ import { formatLabel, formatLabelRow, signLabel } from './util'
49
50
  import { ImageInvalidator } from '../image-invalidator'
50
51
  import { httpLogger as log } from '../logger'
51
52
  import { OzoneConfig } from '../config'
@@ -55,45 +56,49 @@ export type ModerationServiceCreator = (db: Database) => ModerationService
55
56
  export class ModerationService {
56
57
  constructor(
57
58
  public db: Database,
59
+ public signingKey: Keypair,
60
+ public signingKeyId: number,
58
61
  public cfg: OzoneConfig,
59
62
  public backgroundQueue: BackgroundQueue,
60
63
  public idResolver: IdResolver,
61
64
  public eventPusher: EventPusher,
62
65
  public appviewAgent: AtpAgent,
63
66
  private createAuthHeaders: (aud: string) => Promise<AuthHeaders>,
64
- public serverDid: string,
65
67
  public imgInvalidator?: ImageInvalidator,
66
- public cdnPaths?: string[],
67
68
  ) {}
68
69
 
69
70
  static creator(
71
+ signingKey: Keypair,
72
+ signingKeyId: number,
70
73
  cfg: OzoneConfig,
71
74
  backgroundQueue: BackgroundQueue,
72
75
  idResolver: IdResolver,
73
76
  eventPusher: EventPusher,
74
77
  appviewAgent: AtpAgent,
75
78
  createAuthHeaders: (aud: string) => Promise<AuthHeaders>,
76
- serverDid: string,
77
79
  imgInvalidator?: ImageInvalidator,
78
- cdnPaths?: string[],
79
80
  ) {
80
81
  return (db: Database) =>
81
82
  new ModerationService(
82
83
  db,
84
+ signingKey,
85
+ signingKeyId,
83
86
  cfg,
84
87
  backgroundQueue,
85
88
  idResolver,
86
89
  eventPusher,
87
90
  appviewAgent,
88
91
  createAuthHeaders,
89
- serverDid,
90
92
  imgInvalidator,
91
- cdnPaths,
92
93
  )
93
94
  }
94
95
 
95
- views = new ModerationViews(this.db, this.appviewAgent, () =>
96
- this.createAuthHeaders(this.cfg.appview.did),
96
+ views = new ModerationViews(
97
+ this.db,
98
+ this.signingKey,
99
+ this.signingKeyId,
100
+ this.appviewAgent,
101
+ () => this.createAuthHeaders(this.cfg.appview.did),
97
102
  )
98
103
 
99
104
  async getEvent(id: number): Promise<ModerationEventRow | undefined> {
@@ -247,7 +252,7 @@ export class ModerationService {
247
252
  async getReport(id: number): Promise<ModerationEventRow | undefined> {
248
253
  return await this.db.db
249
254
  .selectFrom('moderation_event')
250
- .where('action', '=', 'com.atproto.admin.defs#modEventReport')
255
+ .where('action', '=', 'tools.ozone.moderation.defs#modEventReport')
251
256
  .selectAll()
252
257
  .where('id', '=', id)
253
258
  .executeTakeFirst()
@@ -370,12 +375,12 @@ export class ModerationService {
370
375
  // Means the subject was suspended and needs to be unsuspended
371
376
  if (subject.reverseSuspend) {
372
377
  builder = builder
373
- .where('action', '=', 'com.atproto.admin.defs#modEventTakedown')
378
+ .where('action', '=', 'tools.ozone.moderation.defs#modEventTakedown')
374
379
  .where('durationInHours', 'is not', null)
375
380
  }
376
381
  if (subject.reverseMute) {
377
382
  builder = builder
378
- .where('action', '=', 'com.atproto.admin.defs#modEventMute')
383
+ .where('action', '=', 'tools.ozone.moderation.defs#modEventMute')
379
384
  .where('durationInHours', 'is not', null)
380
385
  }
381
386
 
@@ -422,13 +427,13 @@ export class ModerationService {
422
427
  subject,
423
428
  }: ReversibleModerationEvent): Promise<ModerationEventRow> {
424
429
  const isRevertingTakedown =
425
- action === 'com.atproto.admin.defs#modEventTakedown'
430
+ action === 'tools.ozone.moderation.defs#modEventTakedown'
426
431
  this.db.assertTransaction()
427
432
  const { event } = await this.logEvent({
428
433
  event: {
429
434
  $type: isRevertingTakedown
430
- ? 'com.atproto.admin.defs#modEventReverseTakedown'
431
- : 'com.atproto.admin.defs#modEventUnmute',
435
+ ? 'tools.ozone.moderation.defs#modEventReverseTakedown'
436
+ : 'tools.ozone.moderation.defs#modEventUnmute',
432
437
  comment: comment ?? undefined,
433
438
  },
434
439
  createdAt,
@@ -455,7 +460,8 @@ export class ModerationService {
455
460
  const takedownRef = `BSKY-${
456
461
  isSuspend ? 'SUSPEND' : 'TAKEDOWN'
457
462
  }-${takedownId}`
458
- const values = TAKEDOWNS.map((eventType) => ({
463
+
464
+ const values = this.eventPusher.takedowns.map((eventType) => ({
459
465
  eventType,
460
466
  subjectDid: subject.did,
461
467
  takedownRef,
@@ -516,7 +522,7 @@ export class ModerationService {
516
522
  async takedownRecord(subject: RecordSubject, takedownId: number) {
517
523
  this.db.assertTransaction()
518
524
  const takedownRef = `BSKY-TAKEDOWN-${takedownId}`
519
- const values = TAKEDOWNS.map((eventType) => ({
525
+ const values = this.eventPusher.takedowns.map((eventType) => ({
520
526
  eventType,
521
527
  subjectDid: subject.did,
522
528
  subjectUri: subject.uri,
@@ -555,31 +561,21 @@ export class ModerationService {
555
561
 
556
562
  if (blobCids && blobCids.length > 0) {
557
563
  const blobValues: Insertable<BlobPushEvent>[] = []
558
- for (const eventType of TAKEDOWNS) {
564
+ for (const eventType of this.eventPusher.takedowns) {
559
565
  for (const cid of blobCids) {
560
566
  blobValues.push({
561
567
  eventType,
568
+ takedownRef,
562
569
  subjectDid: subject.did,
570
+ subjectUri: subject.uri || null,
563
571
  subjectBlobCid: cid.toString(),
564
- takedownRef,
565
572
  })
566
573
  }
567
574
  }
568
- const blobEvts = await this.db.db
569
- .insertInto('blob_push_event')
570
- .values(blobValues)
571
- .onConflict((oc) =>
572
- oc
573
- .columns(['subjectDid', 'subjectBlobCid', 'eventType'])
574
- .doUpdateSet({
575
- takedownRef,
576
- confirmedAt: null,
577
- attempts: 0,
578
- lastAttempted: null,
579
- }),
580
- )
581
- .returning(['id', 'subjectDid', 'subjectBlobCid', 'eventType'])
582
- .execute()
575
+ const blobEvts = await this.eventPusher.logBlobPushEvent(
576
+ blobValues,
577
+ takedownRef,
578
+ )
583
579
 
584
580
  this.db.onCommit(() => {
585
581
  this.backgroundQueue.add(async () => {
@@ -596,7 +592,7 @@ export class ModerationService {
596
592
  if (this.imgInvalidator) {
597
593
  await Promise.allSettled(
598
594
  (subject.blobCids ?? []).map((cid) => {
599
- const paths = (this.cdnPaths ?? []).map((path) =>
595
+ const paths = (this.cfg.cdn.paths ?? []).map((path) =>
600
596
  path.replace('%s', subject.did).replace('%s', cid),
601
597
  )
602
598
  return this.imgInvalidator
@@ -697,7 +693,7 @@ export class ModerationService {
697
693
 
698
694
  const result = await this.logEvent({
699
695
  event: {
700
- $type: 'com.atproto.admin.defs#modEventReport',
696
+ $type: 'tools.ozone.moderation.defs#modEventReport',
701
697
  reportType: reasonType,
702
698
  comment: reason,
703
699
  },
@@ -875,7 +871,7 @@ export class ModerationService {
875
871
  ): Promise<Label[]> {
876
872
  const { create = [], negate = [] } = labels
877
873
  const toCreate = create.map((val) => ({
878
- src: this.serverDid,
874
+ src: this.cfg.service.did,
879
875
  uri,
880
876
  cid: cid ?? undefined,
881
877
  val,
@@ -883,7 +879,7 @@ export class ModerationService {
883
879
  cts: new Date().toISOString(),
884
880
  }))
885
881
  const toNegate = negate.map((val) => ({
886
- src: this.serverDid,
882
+ src: this.cfg.service.did,
887
883
  uri,
888
884
  cid: cid ?? undefined,
889
885
  val,
@@ -891,21 +887,19 @@ export class ModerationService {
891
887
  cts: new Date().toISOString(),
892
888
  }))
893
889
  const formatted = [...toCreate, ...toNegate]
894
- await this.createLabels(formatted)
895
- return formatted
890
+ return this.createLabels(formatted)
896
891
  }
897
892
 
898
- async createLabels(labels: Label[]) {
899
- if (labels.length < 1) return
900
- const dbVals = labels.map((l) => ({
901
- ...l,
902
- cid: l.cid ?? '',
903
- neg: !!l.neg,
904
- }))
893
+ async createLabels(labels: Label[]): Promise<Label[]> {
894
+ if (labels.length < 1) return []
895
+ const signedLabels = await Promise.all(
896
+ labels.map((l) => signLabel(l, this.signingKey)),
897
+ )
898
+ const dbVals = signedLabels.map((l) => formatLabelRow(l, this.signingKeyId))
905
899
  const { ref } = this.db.db.dynamic
906
900
  await sql`notify ${ref(LabelChannel)}`.execute(this.db.db)
907
901
  const excluded = (col: string) => ref(`excluded.${col}`)
908
- await this.db.db
902
+ const res = await this.db.db
909
903
  .insertInto('label')
910
904
  .values(dbVals)
911
905
  .onConflict((oc) =>
@@ -915,7 +909,9 @@ export class ModerationService {
915
909
  cts: sql`${excluded('cts')}`,
916
910
  }),
917
911
  )
912
+ .returningAll()
918
913
  .execute()
914
+ return res.map((row) => formatLabel(row))
919
915
  }
920
916
 
921
917
  async sendEmail(opts: {
@@ -25,7 +25,7 @@ export class ModerationLangService {
25
25
  })
26
26
  await this.moderationService.logEvent({
27
27
  event: {
28
- $type: 'com.atproto.admin.defs#modEventTag',
28
+ $type: 'tools.ozone.moderation.defs#modEventTag',
29
29
  add: recordLangs
30
30
  ? recordLangs.map((lang) => `lang:${lang}`)
31
31
  : ['lang:und'],
@@ -7,42 +7,49 @@ import {
7
7
  REVIEWOPEN,
8
8
  REVIEWCLOSED,
9
9
  REVIEWESCALATED,
10
- } from '../lexicon/types/com/atproto/admin/defs'
10
+ REVIEWNONE,
11
+ } from '../lexicon/types/tools/ozone/moderation/defs'
11
12
  import { ModerationEventRow, ModerationSubjectStatusRow } from './types'
12
13
  import { HOUR } from '@atproto/common'
13
14
  import { REASONAPPEAL } from '../lexicon/types/com/atproto/moderation/defs'
14
15
  import { jsonb } from '../db/types'
15
16
 
16
17
  const getSubjectStatusForModerationEvent = ({
18
+ currentStatus,
17
19
  action,
18
20
  createdBy,
19
21
  createdAt,
20
22
  durationInHours,
21
23
  }: {
24
+ currentStatus?: ModerationSubjectStatusRow
22
25
  action: string
23
26
  createdBy: string
24
27
  createdAt: string
25
28
  durationInHours: number | null
26
- }): Partial<ModerationSubjectStatusRow> | null => {
29
+ }): Partial<ModerationSubjectStatusRow> => {
30
+ const defaultReviewState = currentStatus
31
+ ? currentStatus.reviewState
32
+ : REVIEWNONE
33
+
27
34
  switch (action) {
28
- case 'com.atproto.admin.defs#modEventAcknowledge':
35
+ case 'tools.ozone.moderation.defs#modEventAcknowledge':
29
36
  return {
30
37
  lastReviewedBy: createdBy,
31
38
  reviewState: REVIEWCLOSED,
32
39
  lastReviewedAt: createdAt,
33
40
  }
34
- case 'com.atproto.admin.defs#modEventReport':
41
+ case 'tools.ozone.moderation.defs#modEventReport':
35
42
  return {
36
43
  reviewState: REVIEWOPEN,
37
44
  lastReportedAt: createdAt,
38
45
  }
39
- case 'com.atproto.admin.defs#modEventEscalate':
46
+ case 'tools.ozone.moderation.defs#modEventEscalate':
40
47
  return {
41
48
  lastReviewedBy: createdBy,
42
49
  reviewState: REVIEWESCALATED,
43
50
  lastReviewedAt: createdAt,
44
51
  }
45
- case 'com.atproto.admin.defs#modEventReverseTakedown':
52
+ case 'tools.ozone.moderation.defs#modEventReverseTakedown':
46
53
  return {
47
54
  lastReviewedBy: createdBy,
48
55
  reviewState: REVIEWCLOSED,
@@ -50,14 +57,16 @@ const getSubjectStatusForModerationEvent = ({
50
57
  suspendUntil: null,
51
58
  lastReviewedAt: createdAt,
52
59
  }
53
- case 'com.atproto.admin.defs#modEventUnmute':
60
+ case 'tools.ozone.moderation.defs#modEventUnmute':
54
61
  return {
55
62
  lastReviewedBy: createdBy,
56
63
  muteUntil: null,
57
- reviewState: REVIEWOPEN,
64
+ // It's not likely to receive an unmute event that does not already have a status row
65
+ // but if it does happen, default to unnecessary
66
+ reviewState: defaultReviewState,
58
67
  lastReviewedAt: createdAt,
59
68
  }
60
- case 'com.atproto.admin.defs#modEventTakedown':
69
+ case 'tools.ozone.moderation.defs#modEventTakedown':
61
70
  return {
62
71
  takendown: true,
63
72
  lastReviewedBy: createdBy,
@@ -67,29 +76,32 @@ const getSubjectStatusForModerationEvent = ({
67
76
  ? new Date(Date.now() + durationInHours * HOUR).toISOString()
68
77
  : null,
69
78
  }
70
- case 'com.atproto.admin.defs#modEventMute':
79
+ case 'tools.ozone.moderation.defs#modEventMute':
71
80
  return {
72
81
  lastReviewedBy: createdBy,
73
- reviewState: REVIEWOPEN,
74
82
  lastReviewedAt: createdAt,
75
83
  // By default, mute for 24hrs
76
84
  muteUntil: new Date(
77
85
  Date.now() + (durationInHours || 24) * HOUR,
78
86
  ).toISOString(),
87
+ // It's not likely to receive a mute event on a subject that does not already have a status row
88
+ // but if it does happen, default to unnecessary
89
+ reviewState: defaultReviewState,
79
90
  }
80
- case 'com.atproto.admin.defs#modEventComment':
91
+ case 'tools.ozone.moderation.defs#modEventComment':
81
92
  return {
82
93
  lastReviewedBy: createdBy,
83
94
  lastReviewedAt: createdAt,
95
+ reviewState: defaultReviewState,
84
96
  }
85
- case 'com.atproto.admin.defs#modEventTag':
86
- return { tags: [] }
87
- case 'com.atproto.admin.defs#modEventResolveAppeal':
97
+ case 'tools.ozone.moderation.defs#modEventTag':
98
+ return { tags: [], reviewState: defaultReviewState }
99
+ case 'tools.ozone.moderation.defs#modEventResolveAppeal':
88
100
  return {
89
101
  appealed: false,
90
102
  }
91
103
  default:
92
- return null
104
+ return {}
93
105
  }
94
106
  }
95
107
 
@@ -114,23 +126,6 @@ export const adjustModerationSubjectStatus = async (
114
126
  createdAt,
115
127
  } = moderationEvent
116
128
 
117
- const isAppealEvent =
118
- action === 'com.atproto.admin.defs#modEventReport' &&
119
- meta?.reportType === REASONAPPEAL
120
-
121
- const subjectStatus = getSubjectStatusForModerationEvent({
122
- action,
123
- createdBy,
124
- createdAt,
125
- durationInHours: moderationEvent.durationInHours,
126
- })
127
-
128
- // If there are no subjectStatus that means there are no side-effect of the incoming event
129
- if (!subjectStatus) {
130
- return null
131
- }
132
-
133
- const now = new Date().toISOString()
134
129
  // If subjectUri exists, it's not a repoRef so pass along the uri to get identifier back
135
130
  const identifier = getStatusIdentifierFromSubject(subjectUri || subjectDid)
136
131
 
@@ -140,25 +135,46 @@ export const adjustModerationSubjectStatus = async (
140
135
  .selectFrom('moderation_subject_status')
141
136
  .where('did', '=', identifier.did)
142
137
  .where('recordPath', '=', identifier.recordPath)
138
+ // Make sure we respect other updates that may be happening at the same time
139
+ .forUpdate()
143
140
  .selectAll()
144
141
  .executeTakeFirst()
145
142
 
143
+ const isAppealEvent =
144
+ action === 'tools.ozone.moderation.defs#modEventReport' &&
145
+ meta?.reportType === REASONAPPEAL
146
+
147
+ const subjectStatus = getSubjectStatusForModerationEvent({
148
+ currentStatus,
149
+ action,
150
+ createdBy,
151
+ createdAt,
152
+ durationInHours: moderationEvent.durationInHours,
153
+ })
154
+
155
+ const now = new Date().toISOString()
146
156
  if (
147
157
  currentStatus?.reviewState === REVIEWESCALATED &&
148
- subjectStatus.reviewState === REVIEWOPEN
158
+ subjectStatus.reviewState !== REVIEWCLOSED
149
159
  ) {
150
- // If the current status is escalated and the incoming event is to open the review
151
- // We want to keep the status as escalated
160
+ // If the current status is escalated only allow incoming events to move the state to
161
+ // reviewClosed because escalated subjects should never move to any other state
152
162
  subjectStatus.reviewState = REVIEWESCALATED
153
163
  }
154
164
 
165
+ if (currentStatus && subjectStatus.reviewState === REVIEWNONE) {
166
+ // reviewNone is ONLY allowed when there is no current status
167
+ // If there is a current status, it should not be allowed to move back to reviewNone
168
+ subjectStatus.reviewState = currentStatus.reviewState
169
+ }
170
+
155
171
  // Set these because we don't want to override them if they're already set
156
172
  const defaultData = {
157
173
  comment: null,
158
174
  // Defaulting reviewState to open for any event may not be the desired behavior.
159
175
  // For instance, if a subject never had any event and we just want to leave a comment to keep an eye on it
160
176
  // that shouldn't mean we want to review the subject
161
- reviewState: REVIEWOPEN,
177
+ reviewState: REVIEWNONE,
162
178
  recordCid: subjectCid || null,
163
179
  }
164
180
  const newStatus = {
@@ -167,7 +183,7 @@ export const adjustModerationSubjectStatus = async (
167
183
  }
168
184
 
169
185
  if (
170
- action === 'com.atproto.admin.defs#modEventReverseTakedown' &&
186
+ action === 'tools.ozone.moderation.defs#modEventReverseTakedown' &&
171
187
  !subjectStatus.takendown
172
188
  ) {
173
189
  newStatus.takendown = false
@@ -185,19 +201,22 @@ export const adjustModerationSubjectStatus = async (
185
201
  }
186
202
 
187
203
  if (
188
- action === 'com.atproto.admin.defs#modEventResolveAppeal' &&
204
+ action === 'tools.ozone.moderation.defs#modEventResolveAppeal' &&
189
205
  subjectStatus.appealed
190
206
  ) {
191
207
  newStatus.appealed = false
192
208
  subjectStatus.appealed = false
193
209
  }
194
210
 
195
- if (action === 'com.atproto.admin.defs#modEventComment' && meta?.sticky) {
211
+ if (
212
+ action === 'tools.ozone.moderation.defs#modEventComment' &&
213
+ meta?.sticky
214
+ ) {
196
215
  newStatus.comment = comment
197
216
  subjectStatus.comment = comment
198
217
  }
199
218
 
200
- if (action === 'com.atproto.admin.defs#modEventTag') {
219
+ if (action === 'tools.ozone.moderation.defs#modEventTag') {
201
220
  let tags = currentStatus?.tags || []
202
221
  if (addedTags?.length) {
203
222
  tags = tags.concat(addedTags)
@@ -1,6 +1,6 @@
1
1
  import { AtUri } from '@atproto/syntax'
2
2
  import { InputSchema as ReportInput } from '../lexicon/types/com/atproto/moderation/createReport'
3
- import { InputSchema as ActionInput } from '../lexicon/types/com/atproto/admin/emitModerationEvent'
3
+ import { InputSchema as ActionInput } from '../lexicon/types/tools/ozone/moderation/emitEvent'
4
4
  import { InvalidRequestError } from '@atproto/xrpc-server'
5
5
  import { ModerationEventRow, ModerationSubjectStatusRow } from './types'
6
6
  import { RepoRef } from '../lexicon/types/com/atproto/admin/defs'
@@ -1,7 +1,7 @@
1
1
  import { Selectable } from 'kysely'
2
2
  import { ModerationEvent } from '../db/schema/moderation_event'
3
3
  import { ModerationSubjectStatus } from '../db/schema/moderation_subject_status'
4
- import { ComAtprotoAdminDefs } from '@atproto/api'
4
+ import { ToolsOzoneModerationDefs } from '@atproto/api'
5
5
  import { ModSubject } from './subject'
6
6
 
7
7
  export type ModerationEventRow = Selectable<ModerationEvent>
@@ -22,15 +22,15 @@ export type ModerationSubjectStatusRowWithHandle =
22
22
  ModerationSubjectStatusRow & { handle: string | null }
23
23
 
24
24
  export type ModEventType =
25
- | ComAtprotoAdminDefs.ModEventTakedown
26
- | ComAtprotoAdminDefs.ModEventAcknowledge
27
- | ComAtprotoAdminDefs.ModEventEscalate
28
- | ComAtprotoAdminDefs.ModEventComment
29
- | ComAtprotoAdminDefs.ModEventLabel
30
- | ComAtprotoAdminDefs.ModEventReport
31
- | ComAtprotoAdminDefs.ModEventMute
32
- | ComAtprotoAdminDefs.ModEventReverseTakedown
33
- | ComAtprotoAdminDefs.ModEventTag
25
+ | ToolsOzoneModerationDefs.ModEventTakedown
26
+ | ToolsOzoneModerationDefs.ModEventAcknowledge
27
+ | ToolsOzoneModerationDefs.ModEventEscalate
28
+ | ToolsOzoneModerationDefs.ModEventComment
29
+ | ToolsOzoneModerationDefs.ModEventLabel
30
+ | ToolsOzoneModerationDefs.ModEventReport
31
+ | ToolsOzoneModerationDefs.ModEventMute
32
+ | ToolsOzoneModerationDefs.ModEventReverseTakedown
33
+ | ToolsOzoneModerationDefs.ModEventTag
34
34
 
35
35
  export const UNSPECCED_TAKEDOWN_LABEL = '!unspecced-takedown'
36
36