@atproto/ozone 0.0.17-next.0 → 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 (146) hide show
  1. package/dist/api/util.d.ts +10 -0
  2. package/dist/auth-verifier.d.ts +2 -2
  3. package/dist/communication-service/template.d.ts +2 -2
  4. package/dist/config/config.d.ts +5 -0
  5. package/dist/config/env.d.ts +2 -0
  6. package/dist/context.d.ts +6 -0
  7. package/dist/daemon/blob-diverter.d.ts +26 -0
  8. package/dist/daemon/event-pusher.d.ts +4 -0
  9. package/dist/daemon/index.d.ts +1 -0
  10. package/dist/db/index.js +21 -1
  11. package/dist/db/index.js.map +3 -3
  12. package/dist/db/migrations/20240228T003647759Z-add-label-sigs.d.ts +3 -0
  13. package/dist/db/migrations/index.d.ts +1 -0
  14. package/dist/db/schema/index.d.ts +2 -1
  15. package/dist/db/schema/label.d.ts +4 -0
  16. package/dist/db/schema/moderation_event.d.ts +1 -1
  17. package/dist/db/schema/moderation_subject_status.d.ts +1 -1
  18. package/dist/db/schema/signing_key.d.ts +9 -0
  19. package/dist/index.js +10949 -10628
  20. package/dist/index.js.map +3 -3
  21. package/dist/lexicon/index.d.ts +48 -28
  22. package/dist/lexicon/lexicons.d.ts +4641 -4650
  23. package/dist/lexicon/types/app/bsky/actor/defs.d.ts +7 -7
  24. package/dist/lexicon/types/app/bsky/feed/defs.d.ts +1 -0
  25. package/dist/lexicon/types/app/bsky/graph/defs.d.ts +3 -0
  26. package/dist/lexicon/types/com/atproto/admin/defs.d.ts +0 -305
  27. package/dist/lexicon/types/com/atproto/label/defs.d.ts +5 -0
  28. package/dist/lexicon/types/{com/atproto/admin/createCommunicationTemplate.d.ts → tools/ozone/communication/createTemplate.d.ts} +2 -2
  29. package/dist/lexicon/types/tools/ozone/communication/defs.d.ts +14 -0
  30. package/dist/lexicon/types/{com/atproto/admin/listCommunicationTemplates.d.ts → tools/ozone/communication/listTemplates.d.ts} +2 -2
  31. package/dist/lexicon/types/{com/atproto/admin/updateCommunicationTemplate.d.ts → tools/ozone/communication/updateTemplate.d.ts} +2 -2
  32. package/dist/lexicon/types/tools/ozone/moderation/defs.d.ts +269 -0
  33. package/dist/lexicon/types/{com/atproto/admin/emitModerationEvent.d.ts → tools/ozone/moderation/emitEvent.d.ts} +5 -4
  34. package/dist/lexicon/types/{com/atproto/admin/getModerationEvent.d.ts → tools/ozone/moderation/getEvent.d.ts} +2 -2
  35. package/dist/lexicon/types/{com/atproto/admin → tools/ozone/moderation}/getRecord.d.ts +2 -2
  36. package/dist/lexicon/types/{com/atproto/admin → tools/ozone/moderation}/getRepo.d.ts +2 -2
  37. package/dist/lexicon/types/{com/atproto/admin/queryModerationEvents.d.ts → tools/ozone/moderation/queryEvents.d.ts} +2 -2
  38. package/dist/lexicon/types/{com/atproto/admin/queryModerationStatuses.d.ts → tools/ozone/moderation/queryStatuses.d.ts} +2 -2
  39. package/dist/lexicon/types/{com/atproto/admin → tools/ozone/moderation}/searchRepos.d.ts +2 -2
  40. package/dist/mod-service/index.d.ts +16 -15
  41. package/dist/mod-service/subject.d.ts +1 -1
  42. package/dist/mod-service/types.d.ts +2 -2
  43. package/dist/mod-service/util.d.ts +6 -0
  44. package/dist/mod-service/views.d.ts +9 -3
  45. package/dist/sequencer/sequencer.d.ts +6 -4
  46. package/dist/util.d.ts +2 -0
  47. package/package.json +7 -7
  48. package/src/api/{admin/createCommunicationTemplate.ts → communication/createTemplate.ts} +1 -1
  49. package/src/api/{admin/deleteCommunicationTemplate.ts → communication/deleteTemplate.ts} +1 -1
  50. package/src/api/{admin/listCommunicationTemplates.ts → communication/listTemplates.ts} +1 -1
  51. package/src/api/{admin/updateCommunicationTemplate.ts → communication/updateTemplate.ts} +1 -1
  52. package/src/api/index.ts +21 -21
  53. package/src/api/{temp → label}/fetchLabels.ts +4 -2
  54. package/src/api/label/queryLabels.ts +4 -2
  55. package/src/api/moderation/emitEvent.ts +218 -0
  56. package/src/api/{admin/getModerationEvent.ts → moderation/getEvent.ts} +1 -1
  57. package/src/api/{admin → moderation}/getRecord.ts +2 -2
  58. package/src/api/{admin → moderation}/getRepo.ts +2 -2
  59. package/src/api/{admin/queryModerationEvents.ts → moderation/queryEvents.ts} +2 -2
  60. package/src/api/{admin/queryModerationStatuses.ts → moderation/queryStatuses.ts} +2 -2
  61. package/src/api/{admin → moderation}/searchRepos.ts +1 -1
  62. package/src/api/{moderation → report}/createReport.ts +1 -1
  63. package/src/api/util.ts +119 -0
  64. package/src/auth-verifier.ts +2 -2
  65. package/src/communication-service/template.ts +2 -2
  66. package/src/config/config.ts +14 -0
  67. package/src/config/env.ts +4 -0
  68. package/src/context.ts +35 -9
  69. package/src/daemon/blob-diverter.ts +150 -0
  70. package/src/daemon/context.ts +9 -5
  71. package/src/daemon/event-pusher.ts +49 -14
  72. package/src/daemon/index.ts +1 -0
  73. package/src/db/migrations/20240228T003647759Z-add-label-sigs.ts +25 -0
  74. package/src/db/migrations/index.ts +1 -0
  75. package/src/db/schema/index.ts +2 -0
  76. package/src/db/schema/label.ts +3 -0
  77. package/src/db/schema/moderation_event.ts +11 -11
  78. package/src/db/schema/moderation_subject_status.ts +1 -1
  79. package/src/db/schema/signing_key.ts +10 -0
  80. package/src/lexicon/index.ts +178 -138
  81. package/src/lexicon/lexicons.ts +6078 -6106
  82. package/src/lexicon/types/app/bsky/actor/defs.ts +11 -11
  83. package/src/lexicon/types/app/bsky/feed/defs.ts +1 -0
  84. package/src/lexicon/types/app/bsky/graph/defs.ts +3 -0
  85. package/src/lexicon/types/com/atproto/admin/defs.ts +0 -697
  86. package/src/lexicon/types/com/atproto/label/defs.ts +10 -0
  87. package/src/lexicon/types/{com/atproto/admin/createCommunicationTemplate.ts → tools/ozone/communication/createTemplate.ts} +2 -2
  88. package/src/lexicon/types/tools/ozone/communication/defs.ts +35 -0
  89. package/src/lexicon/types/{com/atproto/admin/listCommunicationTemplates.ts → tools/ozone/communication/listTemplates.ts} +2 -2
  90. package/src/lexicon/types/{com/atproto/admin/updateCommunicationTemplate.ts → tools/ozone/communication/updateTemplate.ts} +2 -2
  91. package/src/lexicon/types/tools/ozone/moderation/defs.ts +641 -0
  92. package/src/lexicon/types/{com/atproto/admin/emitModerationEvent.ts → tools/ozone/moderation/emitEvent.ts} +15 -14
  93. package/src/lexicon/types/{com/atproto/admin/getModerationEvent.ts → tools/ozone/moderation/getEvent.ts} +2 -2
  94. package/src/lexicon/types/{com/atproto/admin → tools/ozone/moderation}/getRecord.ts +2 -2
  95. package/src/lexicon/types/{com/atproto/admin → tools/ozone/moderation}/getRepo.ts +2 -2
  96. package/src/lexicon/types/{com/atproto/admin/queryModerationEvents.ts → tools/ozone/moderation/queryEvents.ts} +3 -3
  97. package/src/lexicon/types/{com/atproto/admin/queryModerationStatuses.ts → tools/ozone/moderation/queryStatuses.ts} +2 -2
  98. package/src/lexicon/types/{com/atproto/admin → tools/ozone/moderation}/searchRepos.ts +2 -2
  99. package/src/mod-service/index.ts +42 -47
  100. package/src/mod-service/lang.ts +1 -1
  101. package/src/mod-service/status.ts +19 -16
  102. package/src/mod-service/subject.ts +1 -1
  103. package/src/mod-service/types.ts +10 -10
  104. package/src/mod-service/util.ts +49 -5
  105. package/src/mod-service/views.ts +45 -18
  106. package/src/sequencer/sequencer.ts +12 -11
  107. package/src/util.ts +21 -0
  108. package/tests/__snapshots__/blob-divert.test.ts.snap +22 -0
  109. package/tests/__snapshots__/get-record.test.ts.snap +10 -2
  110. package/tests/__snapshots__/get-repo.test.ts.snap +5 -1
  111. package/tests/__snapshots__/moderation-events.test.ts.snap +8 -8
  112. package/tests/__snapshots__/moderation-statuses.test.ts.snap +6 -6
  113. package/tests/_util.ts +5 -0
  114. package/tests/blob-divert.test.ts +87 -0
  115. package/tests/communication-templates.test.ts +30 -34
  116. package/tests/db.test.ts +6 -6
  117. package/tests/get-record.test.ts +6 -6
  118. package/tests/get-repo.test.ts +11 -11
  119. package/tests/moderation-appeals.test.ts +28 -28
  120. package/tests/moderation-events.test.ts +44 -44
  121. package/tests/moderation-status-tags.test.ts +8 -10
  122. package/tests/moderation-statuses.test.ts +27 -27
  123. package/tests/moderation.test.ts +50 -57
  124. package/tests/query-labels.test.ts +86 -10
  125. package/tests/repo-search.test.ts +8 -8
  126. package/tests/sequencer.test.ts +6 -3
  127. package/dist/api/admin/util.d.ts +0 -5
  128. package/dist/api/moderation/util.d.ts +0 -4
  129. package/src/api/admin/emitModerationEvent.ts +0 -174
  130. package/src/api/admin/util.ts +0 -54
  131. package/src/api/moderation/util.ts +0 -67
  132. /package/dist/api/{admin/createCommunicationTemplate.d.ts → communication/createTemplate.d.ts} +0 -0
  133. /package/dist/api/{admin/deleteCommunicationTemplate.d.ts → communication/deleteTemplate.d.ts} +0 -0
  134. /package/dist/api/{admin/emitModerationEvent.d.ts → communication/listTemplates.d.ts} +0 -0
  135. /package/dist/api/{admin/getModerationEvent.d.ts → communication/updateTemplate.d.ts} +0 -0
  136. /package/dist/api/{temp → label}/fetchLabels.d.ts +0 -0
  137. /package/dist/api/{admin/getRecord.d.ts → moderation/emitEvent.d.ts} +0 -0
  138. /package/dist/api/{admin/getRepo.d.ts → moderation/getEvent.d.ts} +0 -0
  139. /package/dist/api/{admin/listCommunicationTemplates.d.ts → moderation/getRecord.d.ts} +0 -0
  140. /package/dist/api/{admin/queryModerationEvents.d.ts → moderation/getRepo.d.ts} +0 -0
  141. /package/dist/api/{admin/queryModerationStatuses.d.ts → moderation/queryEvents.d.ts} +0 -0
  142. /package/dist/api/{admin/searchRepos.d.ts → moderation/queryStatuses.d.ts} +0 -0
  143. /package/dist/api/{admin/updateCommunicationTemplate.d.ts → moderation/searchRepos.d.ts} +0 -0
  144. /package/dist/api/{moderation → report}/createReport.d.ts +0 -0
  145. /package/dist/lexicon/types/{com/atproto/admin/deleteCommunicationTemplate.d.ts → tools/ozone/communication/deleteTemplate.d.ts} +0 -0
  146. /package/src/lexicon/types/{com/atproto/admin/deleteCommunicationTemplate.ts → tools/ozone/communication/deleteTemplate.ts} +0 -0
package/src/context.ts CHANGED
@@ -14,8 +14,10 @@ import {
14
14
  CommunicationTemplateService,
15
15
  CommunicationTemplateServiceCreator,
16
16
  } from './communication-service/template'
17
+ import { BlobDiverter } from './daemon/blob-diverter'
17
18
  import { AuthVerifier } from './auth-verifier'
18
19
  import { ImageInvalidator } from './image-invalidator'
20
+ import { getSigningKeyId } from './util'
19
21
 
20
22
  export type AppContextOptions = {
21
23
  db: Database
@@ -24,7 +26,9 @@ export type AppContextOptions = {
24
26
  communicationTemplateService: CommunicationTemplateServiceCreator
25
27
  appviewAgent: AtpAgent
26
28
  pdsAgent: AtpAgent | undefined
29
+ blobDiverter?: BlobDiverter
27
30
  signingKey: Keypair
31
+ signingKeyId: number
28
32
  idResolver: IdResolver
29
33
  imgInvalidator?: ImageInvalidator
30
34
  backgroundQueue: BackgroundQueue
@@ -48,11 +52,16 @@ export class AppContext {
48
52
  poolIdleTimeoutMs: cfg.db.poolIdleTimeoutMs,
49
53
  })
50
54
  const signingKey = await Secp256k1Keypair.import(secrets.signingKeyHex)
55
+ const signingKeyId = await getSigningKeyId(db, signingKey.did())
51
56
  const appviewAgent = new AtpAgent({ service: cfg.appview.url })
52
57
  const pdsAgent = cfg.pds
53
58
  ? new AtpAgent({ service: cfg.pds.url })
54
59
  : undefined
55
60
 
61
+ const idResolver = new IdResolver({
62
+ plcUrl: cfg.identity.plcUrl,
63
+ })
64
+
56
65
  const createAuthHeaders = (aud: string) =>
57
66
  createServiceAuthHeaders({
58
67
  iss: `${cfg.service.did}#atproto_labeler`,
@@ -61,30 +70,31 @@ export class AppContext {
61
70
  })
62
71
 
63
72
  const backgroundQueue = new BackgroundQueue(db)
73
+ const blobDiverter = cfg.blobDivert
74
+ ? new BlobDiverter(db, {
75
+ idResolver,
76
+ serviceConfig: cfg.blobDivert,
77
+ })
78
+ : undefined
64
79
  const eventPusher = new EventPusher(db, createAuthHeaders, {
65
80
  appview: cfg.appview.pushEvents ? cfg.appview : undefined,
66
81
  pds: cfg.pds ?? undefined,
67
82
  })
68
-
69
- const idResolver = new IdResolver({
70
- plcUrl: cfg.identity.plcUrl,
71
- })
72
-
73
83
  const modService = ModerationService.creator(
84
+ signingKey,
85
+ signingKeyId,
74
86
  cfg,
75
87
  backgroundQueue,
76
88
  idResolver,
77
89
  eventPusher,
78
90
  appviewAgent,
79
91
  createAuthHeaders,
80
- cfg.service.did,
81
92
  overrides?.imgInvalidator,
82
- cfg.cdn.paths,
83
93
  )
84
94
 
85
95
  const communicationTemplateService = CommunicationTemplateService.creator()
86
96
 
87
- const sequencer = new Sequencer(db)
97
+ const sequencer = new Sequencer(modService(db))
88
98
 
89
99
  const authVerifier = new AuthVerifier(idResolver, {
90
100
  serviceDid: cfg.service.did,
@@ -103,10 +113,12 @@ export class AppContext {
103
113
  appviewAgent,
104
114
  pdsAgent,
105
115
  signingKey,
116
+ signingKeyId,
106
117
  idResolver,
107
118
  backgroundQueue,
108
119
  sequencer,
109
120
  authVerifier,
121
+ blobDiverter,
110
122
  ...(overrides ?? {}),
111
123
  },
112
124
  secrets,
@@ -133,6 +145,10 @@ export class AppContext {
133
145
  return this.opts.modService
134
146
  }
135
147
 
148
+ get blobDiverter(): BlobDiverter | undefined {
149
+ return this.opts.blobDiverter
150
+ }
151
+
136
152
  get communicationTemplateService(): CommunicationTemplateServiceCreator {
137
153
  return this.opts.communicationTemplateService
138
154
  }
@@ -149,6 +165,10 @@ export class AppContext {
149
165
  return this.opts.signingKey
150
166
  }
151
167
 
168
+ get signingKeyId(): number {
169
+ return this.opts.signingKeyId
170
+ }
171
+
152
172
  get plcClient(): plc.Client {
153
173
  return new plc.Client(this.cfg.identity.plcUrl)
154
174
  }
@@ -188,6 +208,12 @@ export class AppContext {
188
208
  async appviewAuth() {
189
209
  return this.serviceAuthHeaders(this.cfg.appview.did)
190
210
  }
191
- }
192
211
 
212
+ devOverride(overrides: Partial<AppContextOptions>) {
213
+ this.opts = {
214
+ ...this.opts,
215
+ ...overrides,
216
+ }
217
+ }
218
+ }
193
219
  export default AppContext
@@ -0,0 +1,150 @@
1
+ import {
2
+ VerifyCidTransform,
3
+ forwardStreamErrors,
4
+ getPdsEndpoint,
5
+ } from '@atproto/common'
6
+ import { IdResolver } from '@atproto/identity'
7
+ import axios from 'axios'
8
+ import { Readable } from 'stream'
9
+ import { CID } from 'multiformats/cid'
10
+
11
+ import Database from '../db'
12
+ import { retryHttp } from '../util'
13
+ import { BlobDivertConfig } from '../config'
14
+
15
+ export class BlobDiverter {
16
+ serviceConfig: BlobDivertConfig
17
+ idResolver: IdResolver
18
+
19
+ constructor(
20
+ public db: Database,
21
+ services: {
22
+ idResolver: IdResolver
23
+ serviceConfig: BlobDivertConfig
24
+ },
25
+ ) {
26
+ this.serviceConfig = services.serviceConfig
27
+ this.idResolver = services.idResolver
28
+ }
29
+
30
+ private async getBlob({
31
+ pds,
32
+ did,
33
+ cid,
34
+ }: {
35
+ pds: string
36
+ did: string
37
+ cid: string
38
+ }) {
39
+ const blobResponse = await axios.get(
40
+ `${pds}/xrpc/com.atproto.sync.getBlob`,
41
+ {
42
+ params: { did, cid },
43
+ decompress: true,
44
+ responseType: 'stream',
45
+ timeout: 5000, // 5sec of inactivity on the connection
46
+ },
47
+ )
48
+ const imageStream: Readable = blobResponse.data
49
+ const verifyCid = new VerifyCidTransform(CID.parse(cid))
50
+ forwardStreamErrors(imageStream, verifyCid)
51
+
52
+ return {
53
+ contentType:
54
+ blobResponse.headers['content-type'] || 'application/octet-stream',
55
+ imageStream: imageStream.pipe(verifyCid),
56
+ }
57
+ }
58
+
59
+ async sendImage({
60
+ url,
61
+ imageStream,
62
+ contentType,
63
+ }: {
64
+ url: string
65
+ imageStream: Readable
66
+ contentType: string
67
+ }) {
68
+ const result = await axios(url, {
69
+ method: 'POST',
70
+ data: imageStream,
71
+ headers: {
72
+ Authorization: basicAuth('admin', this.serviceConfig.adminPassword),
73
+ 'Content-Type': contentType,
74
+ },
75
+ })
76
+
77
+ return result.status === 200
78
+ }
79
+
80
+ private async uploadBlob(
81
+ {
82
+ imageStream,
83
+ contentType,
84
+ }: { imageStream: Readable; contentType: string },
85
+ {
86
+ subjectDid,
87
+ subjectUri,
88
+ }: { subjectDid: string; subjectUri: string | null },
89
+ ) {
90
+ const url = new URL(this.serviceConfig.url)
91
+ url.searchParams.set('did', subjectDid)
92
+ if (subjectUri) url.searchParams.set('uri', subjectUri)
93
+ const result = await this.sendImage({
94
+ url: url.toString(),
95
+ imageStream,
96
+ contentType,
97
+ })
98
+
99
+ return result
100
+ }
101
+
102
+ async uploadBlobOnService({
103
+ subjectDid,
104
+ subjectUri,
105
+ subjectBlobCids,
106
+ }: {
107
+ subjectDid: string
108
+ subjectUri: string
109
+ subjectBlobCids: string[]
110
+ }): Promise<boolean> {
111
+ const didDoc = await this.idResolver.did.resolve(subjectDid)
112
+
113
+ if (!didDoc) {
114
+ throw new Error('Error resolving DID')
115
+ }
116
+
117
+ const pds = getPdsEndpoint(didDoc)
118
+
119
+ if (!pds) {
120
+ throw new Error('Error resolving PDS')
121
+ }
122
+
123
+ // attempt to download and upload within the same retry block since the imageStream is not reusable
124
+ const uploadResult = await Promise.all(
125
+ subjectBlobCids.map((cid) =>
126
+ retryHttp(async () => {
127
+ const { imageStream, contentType } = await this.getBlob({
128
+ pds,
129
+ cid,
130
+ did: subjectDid,
131
+ })
132
+ return this.uploadBlob(
133
+ { imageStream, contentType },
134
+ { subjectDid, subjectUri },
135
+ )
136
+ }),
137
+ ),
138
+ )
139
+
140
+ if (uploadResult.includes(false)) {
141
+ throw new Error(`Error uploading blob ${subjectUri}`)
142
+ }
143
+
144
+ return true
145
+ }
146
+ }
147
+
148
+ const basicAuth = (username: string, password: string) => {
149
+ return 'Basic ' + Buffer.from(`${username}:${password}`).toString('base64')
150
+ }
@@ -1,5 +1,6 @@
1
1
  import { Keypair, Secp256k1Keypair } from '@atproto/crypto'
2
2
  import { createServiceAuthHeaders } from '@atproto/xrpc-server'
3
+ import { IdResolver } from '@atproto/identity'
3
4
  import AtpAgent from '@atproto/api'
4
5
  import { OzoneConfig, OzoneSecrets } from '../config'
5
6
  import { Database } from '../db'
@@ -7,7 +8,7 @@ import { EventPusher } from './event-pusher'
7
8
  import { EventReverser } from './event-reverser'
8
9
  import { ModerationService, ModerationServiceCreator } from '../mod-service'
9
10
  import { BackgroundQueue } from '../background'
10
- import { IdResolver } from '@atproto/identity'
11
+ import { getSigningKeyId } from '../util'
11
12
 
12
13
  export type DaemonContextOptions = {
13
14
  db: Database
@@ -31,6 +32,11 @@ export class DaemonContext {
31
32
  schema: cfg.db.postgresSchema,
32
33
  })
33
34
  const signingKey = await Secp256k1Keypair.import(secrets.signingKeyHex)
35
+ const signingKeyId = await getSigningKeyId(db, signingKey.did())
36
+
37
+ const idResolver = new IdResolver({
38
+ plcUrl: cfg.identity.plcUrl,
39
+ })
34
40
 
35
41
  const appviewAgent = new AtpAgent({ service: cfg.appview.url })
36
42
  const createAuthHeaders = (aud: string) =>
@@ -46,18 +52,16 @@ export class DaemonContext {
46
52
  })
47
53
 
48
54
  const backgroundQueue = new BackgroundQueue(db)
49
- const idResolver = new IdResolver({
50
- plcUrl: cfg.identity.plcUrl,
51
- })
52
55
 
53
56
  const modService = ModerationService.creator(
57
+ signingKey,
58
+ signingKeyId,
54
59
  cfg,
55
60
  backgroundQueue,
56
61
  idResolver,
57
62
  eventPusher,
58
63
  appviewAgent,
59
64
  createAuthHeaders,
60
- cfg.service.did,
61
65
  )
62
66
 
63
67
  const eventReverser = new EventReverser(db, modService)
@@ -6,6 +6,8 @@ import { RepoPushEventType } from '../db/schema/repo_push_event'
6
6
  import { retryHttp } from '../util'
7
7
  import { dbLogger } from '../logger'
8
8
  import { InputSchema } from '../lexicon/types/com/atproto/admin/updateSubjectStatus'
9
+ import { BlobPushEvent } from '../db/schema/blob_push_event'
10
+ import { Insertable, Selectable } from 'kysely'
9
11
 
10
12
  type EventSubject = InputSchema['subject']
11
13
 
@@ -285,20 +287,53 @@ export class EventPusher {
285
287
  subject,
286
288
  evt.takedownRef,
287
289
  )
288
- await dbTxn.db
289
- .updateTable('blob_push_event')
290
- .set(
291
- succeeded
292
- ? { confirmedAt: new Date() }
293
- : {
294
- lastAttempted: new Date(),
295
- attempts: (evt.attempts ?? 0) + 1,
296
- },
297
- )
298
- .where('subjectDid', '=', evt.subjectDid)
299
- .where('subjectBlobCid', '=', evt.subjectBlobCid)
300
- .where('eventType', '=', evt.eventType)
301
- .execute()
290
+ await this.markBlobEventAttempt(dbTxn, evt, succeeded)
302
291
  })
303
292
  }
293
+
294
+ async markBlobEventAttempt(
295
+ dbTxn: Database,
296
+ event: Selectable<BlobPushEvent>,
297
+ succeeded: boolean,
298
+ ) {
299
+ await dbTxn.db
300
+ .updateTable('blob_push_event')
301
+ .set(
302
+ succeeded
303
+ ? { confirmedAt: new Date() }
304
+ : {
305
+ lastAttempted: new Date(),
306
+ attempts: (event.attempts ?? 0) + 1,
307
+ },
308
+ )
309
+ .where('subjectDid', '=', event.subjectDid)
310
+ .where('subjectBlobCid', '=', event.subjectBlobCid)
311
+ .where('eventType', '=', event.eventType)
312
+ .execute()
313
+ }
314
+
315
+ async logBlobPushEvent(
316
+ blobValues: Insertable<BlobPushEvent>[],
317
+ takedownRef?: string | null,
318
+ ) {
319
+ return this.db.db
320
+ .insertInto('blob_push_event')
321
+ .values(blobValues)
322
+ .onConflict((oc) =>
323
+ oc.columns(['subjectDid', 'subjectBlobCid', 'eventType']).doUpdateSet({
324
+ takedownRef,
325
+ confirmedAt: null,
326
+ attempts: 0,
327
+ lastAttempted: null,
328
+ }),
329
+ )
330
+ .returning([
331
+ 'id',
332
+ 'subjectDid',
333
+ 'subjectUri',
334
+ 'subjectBlobCid',
335
+ 'eventType',
336
+ ])
337
+ .execute()
338
+ }
304
339
  }
@@ -3,6 +3,7 @@ import DaemonContext from './context'
3
3
  import { AppContextOptions } from '../context'
4
4
 
5
5
  export { EventPusher } from './event-pusher'
6
+ export { BlobDiverter } from './blob-diverter'
6
7
  export { EventReverser } from './event-reverser'
7
8
 
8
9
  export class OzoneDaemon {
@@ -0,0 +1,25 @@
1
+ import { Kysely, sql } from 'kysely'
2
+
3
+ export async function up(db: Kysely<unknown>): Promise<void> {
4
+ await db.schema.alterTable('label').addColumn('exp', 'varchar').execute()
5
+ await db.schema
6
+ .alterTable('label')
7
+ .addColumn('sig', sql`bytea`)
8
+ .execute()
9
+ await db.schema
10
+ .alterTable('label')
11
+ .addColumn('signingKeyId', 'integer')
12
+ .execute()
13
+ await db.schema
14
+ .createTable('signing_key')
15
+ .addColumn('id', 'serial', (col) => col.primaryKey())
16
+ .addColumn('key', 'varchar', (col) => col.notNull().unique())
17
+ .execute()
18
+ }
19
+
20
+ export async function down(db: Kysely<unknown>): Promise<void> {
21
+ await db.schema.dropTable('signing_key')
22
+ await db.schema.alterTable('label').dropColumn('exp').execute()
23
+ await db.schema.alterTable('label').dropColumn('sig').execute()
24
+ await db.schema.alterTable('label').dropColumn('signingKey').execute()
25
+ }
@@ -6,3 +6,4 @@ export * as _20231219T205730722Z from './20231219T205730722Z-init'
6
6
  export * as _20240116T085607200Z from './20240116T085607200Z-communication-template'
7
7
  export * as _20240201T051104136Z from './20240201T051104136Z-mod-event-blobs'
8
8
  export * as _20240208T213404429Z from './20240208T213404429Z-add-tags-column-to-moderation-subject'
9
+ export * as _20240228T003647759Z from './20240228T003647759Z-add-label-sigs'
@@ -5,11 +5,13 @@ import * as repoPushEvent from './repo_push_event'
5
5
  import * as recordPushEvent from './record_push_event'
6
6
  import * as blobPushEvent from './blob_push_event'
7
7
  import * as label from './label'
8
+ import * as signingKey from './signing_key'
8
9
  import * as communicationTemplate from './communication_template'
9
10
 
10
11
  export type DatabaseSchemaType = modEvent.PartialDB &
11
12
  modSubjectStatus.PartialDB &
12
13
  label.PartialDB &
14
+ signingKey.PartialDB &
13
15
  repoPushEvent.PartialDB &
14
16
  recordPushEvent.PartialDB &
15
17
  blobPushEvent.PartialDB &
@@ -10,6 +10,9 @@ export interface Label {
10
10
  val: string
11
11
  neg: boolean
12
12
  cts: string
13
+ exp: string | null
14
+ sig: Buffer | null
15
+ signingKeyId: number | null
13
16
  }
14
17
 
15
18
  export type LabelRow = Selectable<Label>
@@ -5,17 +5,17 @@ export const eventTableName = 'moderation_event'
5
5
  export interface ModerationEvent {
6
6
  id: Generated<number>
7
7
  action:
8
- | 'com.atproto.admin.defs#modEventTakedown'
9
- | 'com.atproto.admin.defs#modEventAcknowledge'
10
- | 'com.atproto.admin.defs#modEventEscalate'
11
- | 'com.atproto.admin.defs#modEventComment'
12
- | 'com.atproto.admin.defs#modEventLabel'
13
- | 'com.atproto.admin.defs#modEventReport'
14
- | 'com.atproto.admin.defs#modEventMute'
15
- | 'com.atproto.admin.defs#modEventReverseTakedown'
16
- | 'com.atproto.admin.defs#modEventEmail'
17
- | 'com.atproto.admin.defs#modEventResolveAppeal'
18
- | 'com.atproto.admin.defs#modEventTag'
8
+ | 'tools.ozone.moderation.defs#modEventTakedown'
9
+ | 'tools.ozone.moderation.defs#modEventAcknowledge'
10
+ | 'tools.ozone.moderation.defs#modEventEscalate'
11
+ | 'tools.ozone.moderation.defs#modEventComment'
12
+ | 'tools.ozone.moderation.defs#modEventLabel'
13
+ | 'tools.ozone.moderation.defs#modEventReport'
14
+ | 'tools.ozone.moderation.defs#modEventMute'
15
+ | 'tools.ozone.moderation.defs#modEventReverseTakedown'
16
+ | 'tools.ozone.moderation.defs#modEventEmail'
17
+ | 'tools.ozone.moderation.defs#modEventResolveAppeal'
18
+ | 'tools.ozone.moderation.defs#modEventTag'
19
19
  subjectType: 'com.atproto.admin.defs#repoRef' | 'com.atproto.repo.strongRef'
20
20
  subjectDid: string
21
21
  subjectUri: string | null
@@ -4,7 +4,7 @@ import {
4
4
  REVIEWOPEN,
5
5
  REVIEWESCALATED,
6
6
  REVIEWNONE,
7
- } from '../../lexicon/types/com/atproto/admin/defs'
7
+ } from '../../lexicon/types/tools/ozone/moderation/defs'
8
8
 
9
9
  export const subjectStatusTableName = 'moderation_subject_status'
10
10
 
@@ -0,0 +1,10 @@
1
+ import { Generated } from 'kysely'
2
+
3
+ export const tableName = 'signing_key'
4
+
5
+ export interface SigningKey {
6
+ id: Generated<number>
7
+ key: string
8
+ }
9
+
10
+ export type PartialDB = { [tableName]: SigningKey }