@atproto/ozone 0.2.5 → 0.2.7

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 (166) hide show
  1. package/CHANGELOG.md +37 -0
  2. package/dist/api/index.d.ts.map +1 -1
  3. package/dist/api/index.js +2 -0
  4. package/dist/api/index.js.map +1 -1
  5. package/dist/api/report/queryActivities.d.ts +4 -0
  6. package/dist/api/report/queryActivities.d.ts.map +1 -0
  7. package/dist/api/report/queryActivities.js +36 -0
  8. package/dist/api/report/queryActivities.js.map +1 -0
  9. package/dist/background.d.ts +5 -3
  10. package/dist/background.d.ts.map +1 -1
  11. package/dist/background.js +13 -4
  12. package/dist/background.js.map +1 -1
  13. package/dist/context.js +1 -1
  14. package/dist/context.js.map +1 -1
  15. package/dist/daemon/context.js +1 -1
  16. package/dist/daemon/context.js.map +1 -1
  17. package/dist/daemon/verification-listener.d.ts +1 -1
  18. package/dist/daemon/verification-listener.d.ts.map +1 -1
  19. package/dist/daemon/verification-listener.js +10 -4
  20. package/dist/daemon/verification-listener.js.map +1 -1
  21. package/dist/db/migrations/20260602T120000000Z-add-report-activity-created-index.d.ts +4 -0
  22. package/dist/db/migrations/20260602T120000000Z-add-report-activity-created-index.d.ts.map +1 -0
  23. package/dist/db/migrations/20260602T120000000Z-add-report-activity-created-index.js +15 -0
  24. package/dist/db/migrations/20260602T120000000Z-add-report-activity-created-index.js.map +1 -0
  25. package/dist/db/migrations/index.d.ts +1 -0
  26. package/dist/db/migrations/index.d.ts.map +1 -1
  27. package/dist/db/migrations/index.js +1 -0
  28. package/dist/db/migrations/index.js.map +1 -1
  29. package/dist/index.d.ts.map +1 -1
  30. package/dist/index.js +23 -13
  31. package/dist/index.js.map +1 -1
  32. package/dist/jetstream/service.d.ts +1 -1
  33. package/dist/jetstream/service.d.ts.map +1 -1
  34. package/dist/jetstream/service.js +3 -1
  35. package/dist/jetstream/service.js.map +1 -1
  36. package/dist/lexicon/index.d.ts +13 -0
  37. package/dist/lexicon/index.d.ts.map +1 -1
  38. package/dist/lexicon/index.js +22 -0
  39. package/dist/lexicon/index.js.map +1 -1
  40. package/dist/lexicon/lexicons.d.ts +940 -140
  41. package/dist/lexicon/lexicons.d.ts.map +1 -1
  42. package/dist/lexicon/lexicons.js +477 -69
  43. package/dist/lexicon/lexicons.js.map +1 -1
  44. package/dist/lexicon/types/app/bsky/feed/searchPostsV2.d.ts +87 -0
  45. package/dist/lexicon/types/app/bsky/feed/searchPostsV2.d.ts.map +1 -0
  46. package/dist/lexicon/types/app/bsky/feed/searchPostsV2.js +5 -0
  47. package/dist/lexicon/types/app/bsky/feed/searchPostsV2.js.map +1 -0
  48. package/dist/lexicon/types/app/bsky/notification/defs.d.ts +1 -0
  49. package/dist/lexicon/types/app/bsky/notification/defs.d.ts.map +1 -1
  50. package/dist/lexicon/types/app/bsky/notification/defs.js.map +1 -1
  51. package/dist/lexicon/types/chat/bsky/actor/declaration.d.ts +1 -1
  52. package/dist/lexicon/types/chat/bsky/actor/declaration.d.ts.map +1 -1
  53. package/dist/lexicon/types/chat/bsky/actor/declaration.js.map +1 -1
  54. package/dist/lexicon/types/chat/bsky/actor/defs.d.ts +2 -3
  55. package/dist/lexicon/types/chat/bsky/actor/defs.d.ts.map +1 -1
  56. package/dist/lexicon/types/chat/bsky/actor/defs.js.map +1 -1
  57. package/dist/lexicon/types/chat/bsky/convo/defs.d.ts +39 -36
  58. package/dist/lexicon/types/chat/bsky/convo/defs.d.ts.map +1 -1
  59. package/dist/lexicon/types/chat/bsky/convo/defs.js +7 -0
  60. package/dist/lexicon/types/chat/bsky/convo/defs.js.map +1 -1
  61. package/dist/lexicon/types/chat/bsky/convo/getUnreadCounts.d.ts +2 -2
  62. package/dist/lexicon/types/chat/bsky/convo/getUnreadCounts.d.ts.map +1 -1
  63. package/dist/lexicon/types/chat/bsky/convo/getUnreadCounts.js.map +1 -1
  64. package/dist/lexicon/types/chat/bsky/moderation/defs.d.ts +3 -3
  65. package/dist/lexicon/types/chat/bsky/moderation/defs.d.ts.map +1 -1
  66. package/dist/lexicon/types/chat/bsky/moderation/defs.js.map +1 -1
  67. package/dist/lexicon/types/chat/bsky/notification/defs.d.ts +19 -0
  68. package/dist/lexicon/types/chat/bsky/notification/defs.d.ts.map +1 -0
  69. package/dist/lexicon/types/chat/bsky/notification/defs.js +19 -0
  70. package/dist/lexicon/types/chat/bsky/notification/defs.js.map +1 -0
  71. package/dist/lexicon/types/chat/bsky/notification/getPreferences.d.ts +20 -0
  72. package/dist/lexicon/types/chat/bsky/notification/getPreferences.d.ts.map +1 -0
  73. package/dist/lexicon/types/chat/bsky/notification/getPreferences.js +5 -0
  74. package/dist/lexicon/types/chat/bsky/notification/getPreferences.js.map +1 -0
  75. package/dist/lexicon/types/chat/bsky/notification/putPreferences.d.ts +26 -0
  76. package/dist/lexicon/types/chat/bsky/notification/putPreferences.d.ts.map +1 -0
  77. package/dist/lexicon/types/chat/bsky/notification/putPreferences.js +5 -0
  78. package/dist/lexicon/types/chat/bsky/notification/putPreferences.js.map +1 -0
  79. package/dist/lexicon/types/tools/ozone/report/defs.d.ts +1 -0
  80. package/dist/lexicon/types/tools/ozone/report/defs.d.ts.map +1 -1
  81. package/dist/lexicon/types/tools/ozone/report/defs.js.map +1 -1
  82. package/dist/lexicon/types/tools/ozone/report/queryActivities.d.ts +32 -0
  83. package/dist/lexicon/types/tools/ozone/report/queryActivities.d.ts.map +1 -0
  84. package/dist/lexicon/types/tools/ozone/report/queryActivities.js +5 -0
  85. package/dist/lexicon/types/tools/ozone/report/queryActivities.js.map +1 -0
  86. package/dist/mod-service/report.d.ts +1 -0
  87. package/dist/mod-service/report.d.ts.map +1 -1
  88. package/dist/mod-service/report.js +16 -0
  89. package/dist/mod-service/report.js.map +1 -1
  90. package/dist/report/activity.d.ts +19 -1
  91. package/dist/report/activity.d.ts.map +1 -1
  92. package/dist/report/activity.js +27 -1
  93. package/dist/report/activity.js.map +1 -1
  94. package/package.json +15 -14
  95. package/src/api/index.ts +2 -0
  96. package/src/api/report/queryActivities.ts +64 -0
  97. package/src/background.ts +19 -4
  98. package/src/context.ts +1 -1
  99. package/src/daemon/context.ts +1 -1
  100. package/src/daemon/verification-listener.ts +9 -4
  101. package/src/db/migrations/20260602T120000000Z-add-report-activity-created-index.ts +17 -0
  102. package/src/db/migrations/index.ts +1 -0
  103. package/src/index.ts +25 -15
  104. package/src/jetstream/service.ts +3 -1
  105. package/src/mod-service/report.ts +19 -0
  106. package/src/report/activity.ts +47 -0
  107. package/tests/3p-labeler.test.ts +2 -2
  108. package/tests/_util.ts +8 -25
  109. package/tests/account-strikes.test.ts +1 -1
  110. package/tests/ack-all-subjects-of-account.test.ts +1 -1
  111. package/tests/age-assurance.test.ts +1 -1
  112. package/tests/blob-divert.test.ts +1 -1
  113. package/tests/communication-templates.test.ts +1 -1
  114. package/tests/content-tagger.test.ts +1 -1
  115. package/tests/db.test.ts +1 -1
  116. package/tests/expiring-label.test.ts +1 -1
  117. package/tests/expiring-tags.test.ts +1 -1
  118. package/tests/get-account-timeline.test.ts +1 -1
  119. package/tests/get-config.test.ts +1 -1
  120. package/tests/get-lists.test.ts +2 -1
  121. package/tests/get-profiles.test.ts +1 -1
  122. package/tests/get-record.test.ts +1 -1
  123. package/tests/get-records.test.ts +1 -1
  124. package/tests/get-repo.test.ts +1 -1
  125. package/tests/get-report.test.ts +1 -1
  126. package/tests/get-reporter-stats.test.ts +1 -1
  127. package/tests/get-repos.test.ts +1 -1
  128. package/tests/get-starter-pack.test.ts +1 -1
  129. package/tests/get-subjects.test.ts +1 -1
  130. package/tests/mod-tool.test.ts +1 -1
  131. package/tests/moderation-appeals.test.ts +1 -1
  132. package/tests/moderation-events.test.ts +1 -1
  133. package/tests/moderation-status-tags.test.ts +1 -1
  134. package/tests/moderation-statuses.test.ts +1 -1
  135. package/tests/moderation.test.ts +1 -1
  136. package/tests/protected-tags.test.ts +1 -1
  137. package/tests/query-labels.test.ts +1 -1
  138. package/tests/query-reports.test.ts +1 -1
  139. package/tests/queue-assignment.test.ts +1 -1
  140. package/tests/queue-router.test.ts +1 -1
  141. package/tests/queues.test.ts +1 -1
  142. package/tests/record-and-account-events.test.ts +1 -1
  143. package/tests/repo-search.test.ts +2 -2
  144. package/tests/report-action.test.ts +1 -1
  145. package/tests/report-activity.test.ts +145 -1
  146. package/tests/report-assignment.test.ts +1 -1
  147. package/tests/report-muting.test.ts +1 -1
  148. package/tests/report-reason.test.ts +1 -1
  149. package/tests/report-reassign-queue.test.ts +1 -1
  150. package/tests/report-routing.test.ts +1 -1
  151. package/tests/report-stats.test.ts +1 -1
  152. package/tests/revoke-account-credentials.test.ts +1 -1
  153. package/tests/safelink.test.ts +1 -1
  154. package/tests/scheduled-action-processor.test.ts +1 -1
  155. package/tests/scheduled-action.test.ts +1 -1
  156. package/tests/sequencer.test.ts +1 -1
  157. package/tests/server.test.ts +9 -12
  158. package/tests/sets.test.ts +1 -1
  159. package/tests/settings.test.ts +1 -1
  160. package/tests/strike-expiry-processor.test.ts +1 -1
  161. package/tests/subject-priority-score.test.ts +1 -1
  162. package/tests/takedown.test.ts +1 -1
  163. package/tests/team.test.ts +1 -1
  164. package/tests/verification-listener.test.ts +40 -13
  165. package/tests/verification.test.ts +1 -1
  166. package/tsconfig.build.tsbuildinfo +1 -1
package/src/api/index.ts CHANGED
@@ -41,6 +41,7 @@ import getLatestReport from './report/getLatestReport.js'
41
41
  import getLiveStats from './report/getLiveStats.js'
42
42
  import getReport from './report/getReport.js'
43
43
  import listActivities from './report/listActivities.js'
44
+ import queryActivities from './report/queryActivities.js'
44
45
  import queryReports from './report/queryReports.js'
45
46
  import reassignQueue from './report/reassignQueue.js'
46
47
  import refreshStats from './report/refreshStats.js'
@@ -139,6 +140,7 @@ export default function (server: Server, ctx: AppContext) {
139
140
  getReportAssignments(server, ctx)
140
141
  createActivity(server, ctx)
141
142
  listActivities(server, ctx)
143
+ queryActivities(server, ctx)
142
144
  reassignQueue(server, ctx)
143
145
  return server
144
146
  }
@@ -0,0 +1,64 @@
1
+ import { AppContext } from '../../context.js'
2
+ import { Server } from '../../lexicon/index.js'
3
+ import { ReportView } from '../../lexicon/types/tools/ozone/report/defs.js'
4
+ import { getReportsByIds } from '../../mod-service/report.js'
5
+ import {
6
+ formatActivityView,
7
+ queryReportActivities,
8
+ } from '../../report/activity.js'
9
+ import { buildReportView, hydrateReportInfo } from '../../report/views.js'
10
+ import { getPdsAccountInfos } from '../util.js'
11
+
12
+ export default function (server: Server, ctx: AppContext) {
13
+ server.tools.ozone.report.queryActivities({
14
+ auth: ctx.authVerifier.modOrAdminToken,
15
+ handler: async ({ params, auth, req }) => {
16
+ const db = ctx.db
17
+ const modService = ctx.modService(db)
18
+ const labelers = ctx.reqLabelers(req)
19
+
20
+ const { activities, cursor: nextCursor } = await queryReportActivities(
21
+ db,
22
+ params,
23
+ )
24
+
25
+ // Dedupe report IDs across the page. Many activities can share the
26
+ // same report so we want one bulk fetch + hydrate rather than N.
27
+ const reportIds = Array.from(new Set(activities.map((a) => a.reportId)))
28
+
29
+ const queueService = ctx.queueService(db)
30
+ const teamService = ctx.teamService(db)
31
+ const reports = await getReportsByIds(db, reportIds)
32
+ const hydrated = await hydrateReportInfo(
33
+ reports,
34
+ modService.views,
35
+ (dids) => getPdsAccountInfos(ctx, dids),
36
+ (queueIds) => queueService.getViewsByIds(queueIds),
37
+ (dids) => teamService.viewByDids(dids),
38
+ labelers,
39
+ )
40
+ const reportViews = new Map<number, ReportView>()
41
+ for (const report of reports) {
42
+ reportViews.set(
43
+ report.id,
44
+ buildReportView(report, hydrated, auth.credentials.isModerator),
45
+ )
46
+ }
47
+
48
+ const createdByDids = Array.from(
49
+ new Set(activities.map((a) => a.createdBy)),
50
+ )
51
+ const memberViews = await teamService.viewByDids(createdByDids)
52
+
53
+ return {
54
+ encoding: 'application/json',
55
+ body: {
56
+ activities: activities.map((activity) =>
57
+ formatActivityView(activity, memberViews, reportViews),
58
+ ),
59
+ cursor: nextCursor,
60
+ },
61
+ }
62
+ },
63
+ })
64
+ }
package/src/background.ts CHANGED
@@ -9,9 +9,19 @@ import {
9
9
 
10
10
  type Task = (db: Database, signal: AbortSignal) => Promise<void>
11
11
 
12
+ export type BackgroundQueueOptions = NonNullable<
13
+ ConstructorParameters<typeof PQueue>[0]
14
+ > & {
15
+ concurrency: number
16
+ }
17
+
12
18
  /**
13
19
  * A simple queue for in-process, out-of-band/backgrounded work
14
20
  */
21
+ // @NOTE Keep this in sync with the BackgroundQueue in
22
+ // - packages/bsky/src/data-plane/server/background.ts
23
+ // - packages/ozone/src/background.ts
24
+ // - packages/pds/src/background.ts
15
25
  export class BackgroundQueue {
16
26
  private abortController = new AbortController()
17
27
  private queue: PQueue
@@ -26,9 +36,9 @@ export class BackgroundQueue {
26
36
 
27
37
  constructor(
28
38
  protected db: Database,
29
- queueOpts?: { concurrency?: number },
39
+ options: BackgroundQueueOptions,
30
40
  ) {
31
- this.queue = new PQueue(queueOpts ?? { concurrency: 20 })
41
+ this.queue = new PQueue(options)
32
42
  }
33
43
 
34
44
  getStats() {
@@ -76,7 +86,8 @@ export class BackgroundQueue {
76
86
  }
77
87
 
78
88
  async processAll() {
79
- await this.queue.onIdle()
89
+ const { queue } = this
90
+ while (queue.size || queue.pending) await queue.onIdle()
80
91
  }
81
92
 
82
93
  /**
@@ -86,8 +97,12 @@ export class BackgroundQueue {
86
97
  * only once http connections have drained (tasks no longer being added).
87
98
  */
88
99
  async destroy() {
100
+ if (this.destroyed) {
101
+ dbLogger.warn('BackgroundQueue.destroy() called multiple times')
102
+ }
103
+
89
104
  this.abortController.abort()
90
- await this.queue.onIdle()
105
+ return this.processAll()
91
106
  }
92
107
  }
93
108
 
package/src/context.ts CHANGED
@@ -135,7 +135,7 @@ export class AppContext {
135
135
  keypair: signingKey,
136
136
  })
137
137
 
138
- const backgroundQueue = new BackgroundQueue(db)
138
+ const backgroundQueue = new BackgroundQueue(db, { concurrency: 20 })
139
139
  const blobDiverter = cfg.blobDivert
140
140
  ? new BlobDiverter(db, {
141
141
  idResolver,
@@ -73,7 +73,7 @@ export class DaemonContext {
73
73
  pds: cfg.pds ?? undefined,
74
74
  })
75
75
 
76
- const backgroundQueue = new BackgroundQueue(db)
76
+ const backgroundQueue = new BackgroundQueue(db, { concurrency: 20 })
77
77
 
78
78
  const settingService = SettingService.creator()
79
79
  const strikeService = StrikeService.creator()
@@ -156,9 +156,14 @@ export class VerificationListener {
156
156
  })
157
157
  }
158
158
 
159
- stop() {
160
- this.jetstream?.close()
161
- this.backgroundQueue.destroy()
162
- this.destroyed = true
159
+ async stop() {
160
+ if (!this.destroyed) {
161
+ this.destroyed = true
162
+ try {
163
+ await this.jetstream?.close()
164
+ } finally {
165
+ await this.backgroundQueue.destroy()
166
+ }
167
+ }
163
168
  }
164
169
  }
@@ -0,0 +1,17 @@
1
+ import { Kysely } from 'kysely'
2
+
3
+ export async function up(db: Kysely<unknown>): Promise<void> {
4
+ // Supports time-ordered scans across all reports for downstream pollers
5
+ // (e.g. Nimbus' report-activity watcher). The existing indexes are all
6
+ // leading-`reportId`, which would force a sequential scan for global
7
+ // ordered queries.
8
+ await db.schema
9
+ .createIndex('idx_report_activity_created')
10
+ .on('report_activity')
11
+ .columns(['createdAt', 'id'])
12
+ .execute()
13
+ }
14
+
15
+ export async function down(db: Kysely<unknown>): Promise<void> {
16
+ await db.schema.dropIndex('idx_report_activity_created').execute()
17
+ }
@@ -41,3 +41,4 @@ export * as _20260313T000000000Z from './20260313T000000000Z-add-report-activity
41
41
  export * as _20260318T152058935Z from './20260318T152058935Z-add-report-stat.js'
42
42
  export * as _20260428T000000000Z from './20260428T000000000Z-add-expiring-tag-table.js'
43
43
  export * as _20260513T202941104Z from './20260513T202941104Z-add-subject-convo-id.js'
44
+ export * as _20260602T120000000Z from './20260602T120000000Z-add-report-activity-created-index.js'
package/src/index.ts CHANGED
@@ -4,11 +4,8 @@ import { AddressInfo } from 'node:net'
4
4
  import compression from 'compression'
5
5
  import cors from 'cors'
6
6
  import express from 'express'
7
- // eslint-disable-next-line import/default, import/no-named-as-default-member
7
+ // eslint-disable-next-line import/default
8
8
  import httpTerminator from 'http-terminator'
9
- // eslint-disable-next-line import/no-named-as-default-member
10
- const { createHttpTerminator } = httpTerminator
11
- type HttpTerminator = ReturnType<typeof createHttpTerminator>
12
9
  import { DAY, SECOND } from '@atproto/common'
13
10
  import API, { health, wellKnown } from './api/index.js'
14
11
  import { OzoneConfig, OzoneSecrets } from './config/index.js'
@@ -29,7 +26,7 @@ export class OzoneService {
29
26
  public ctx: AppContext
30
27
  public app: express.Application
31
28
  public server?: http.Server
32
- private terminator?: HttpTerminator
29
+ private terminator?: httpTerminator.HttpTerminator
33
30
  private dbStatsInterval?: NodeJS.Timeout
34
31
 
35
32
  constructor(opts: { ctx: AppContext; app: express.Application }) {
@@ -108,23 +105,25 @@ export class OzoneService {
108
105
  // so we need to sync them from env var to the database
109
106
  await this.seedInitialMembers()
110
107
 
111
- const { db, backgroundQueue } = this.ctx
112
108
  this.dbStatsInterval = setInterval(() => {
113
109
  dbLogger.info(
114
110
  {
115
- idleCount: db.pool.idleCount,
116
- totalCount: db.pool.totalCount,
117
- waitingCount: db.pool.waitingCount,
111
+ idleCount: this.ctx.db.pool.idleCount,
112
+ totalCount: this.ctx.db.pool.totalCount,
113
+ waitingCount: this.ctx.db.pool.waitingCount,
118
114
  },
119
115
  'db pool stats',
120
116
  )
121
- dbLogger.info(backgroundQueue.getStats(), 'background queue stats')
117
+ dbLogger.info(
118
+ this.ctx.backgroundQueue.getStats(),
119
+ 'background queue stats',
120
+ )
122
121
  }, 10000)
123
122
  await this.ctx.sequencer.start()
124
123
  const server = this.app.listen(this.ctx.cfg.service.port)
125
124
  this.server = server
126
125
  server.keepAliveTimeout = 90000
127
- this.terminator = createHttpTerminator({ server })
126
+ this.terminator = httpTerminator.createHttpTerminator({ server })
128
127
  await events.once(server, 'listening')
129
128
  const { port } = server.address() as AddressInfo
130
129
  this.ctx.assignPort(port)
@@ -132,12 +131,23 @@ export class OzoneService {
132
131
  }
133
132
 
134
133
  async destroy(): Promise<void> {
135
- await this.terminator?.terminate()
136
- await this.ctx.backgroundQueue.destroy()
137
- await this.ctx.sequencer.destroy()
138
- await this.ctx.db.close()
139
134
  clearInterval(this.dbStatsInterval)
140
135
  this.dbStatsInterval = undefined
136
+
137
+ // @TODO Use a disposable stack when Node24 becomes the min supported version
138
+ try {
139
+ await this.terminator?.terminate()
140
+ } finally {
141
+ try {
142
+ await this.ctx.backgroundQueue.destroy()
143
+ } finally {
144
+ try {
145
+ await this.ctx.sequencer.destroy()
146
+ } finally {
147
+ await this.ctx.db.close()
148
+ }
149
+ }
150
+ }
141
151
  }
142
152
  }
143
153
 
@@ -99,7 +99,9 @@ export class Jetstream {
99
99
  /**
100
100
  * Closes the WebSocket connection.
101
101
  */
102
- close() {
102
+ async close() {
103
+ // @TODO This should return a promise that fulfills when the connection is
104
+ // fully closed.
103
105
  this.ws?.ws?.close()
104
106
  }
105
107
  }
@@ -175,6 +175,25 @@ export async function getReportById(
175
175
  .executeTakeFirst()
176
176
  }
177
177
 
178
+ export async function getReportsByIds(
179
+ db: Database,
180
+ ids: number[],
181
+ ): Promise<ReportWithEvent[]> {
182
+ if (!ids.length) return []
183
+ return reportQuery(db)
184
+ .where('r.id', 'in', ids)
185
+ .selectAll('r')
186
+ .select([
187
+ 'me.subjectDid',
188
+ 'me.subjectUri',
189
+ 'me.subjectCid',
190
+ 'me.createdBy as reportedBy',
191
+ 'me.comment',
192
+ 'me.meta',
193
+ ])
194
+ .execute()
195
+ }
196
+
178
197
  export async function getLatestReport(
179
198
  db: Database,
180
199
  ): Promise<ReportWithEvent | undefined> {
@@ -1,5 +1,8 @@
1
1
  import { InvalidRequestError } from '@atproto/xrpc-server'
2
2
  import { Database } from '../db/index.js'
3
+ import { TimeIdKeyset, paginate } from '../db/pagination.js'
4
+ import { ReportView } from '../lexicon/types/tools/ozone/report/defs.js'
5
+ import { QueryParams as QueryActivitiesParams } from '../lexicon/types/tools/ozone/report/queryActivities.js'
3
6
  import { Member } from '../lexicon/types/tools/ozone/team/defs.js'
4
7
  import {
5
8
  AlreadyInTargetState,
@@ -190,6 +193,48 @@ export async function listReportActivities(
190
193
  return { activities, cursor: nextCursor }
191
194
  }
192
195
 
196
+ export async function queryReportActivities(
197
+ db: Database,
198
+ params: QueryActivitiesParams,
199
+ ) {
200
+ const {
201
+ activityTypes,
202
+ createdAfter,
203
+ createdBefore,
204
+ sortDirection,
205
+ limit,
206
+ cursor,
207
+ } = params
208
+ const { ref } = db.db.dynamic
209
+
210
+ let builder = db.db.selectFrom('report_activity').selectAll()
211
+
212
+ if (activityTypes && activityTypes.length > 0) {
213
+ builder = builder.where('activityType', 'in', activityTypes)
214
+ }
215
+ if (createdAfter) {
216
+ builder = builder.where('createdAt', '>=', createdAfter)
217
+ }
218
+ if (createdBefore) {
219
+ builder = builder.where('createdAt', '<=', createdBefore)
220
+ }
221
+
222
+ const keyset = new TimeIdKeyset(
223
+ ref('report_activity.createdAt'),
224
+ ref('report_activity.id'),
225
+ )
226
+ const paginatedBuilder = paginate(builder, {
227
+ limit,
228
+ cursor,
229
+ keyset,
230
+ direction: sortDirection,
231
+ tryIndex: true,
232
+ })
233
+
234
+ const activities = await paginatedBuilder.execute()
235
+ return { activities, cursor: keyset.packFromResult(activities) }
236
+ }
237
+
193
238
  function buildActivityObject(
194
239
  activityType: string,
195
240
  previousStatus: string | null,
@@ -215,6 +260,7 @@ export function formatActivityView(
215
260
  createdAt: string
216
261
  },
217
262
  memberViews?: Map<string, Member>,
263
+ reportViews?: Map<number, ReportView>,
218
264
  ) {
219
265
  return {
220
266
  id: activity.id,
@@ -229,6 +275,7 @@ export function formatActivityView(
229
275
  isAutomated: activity.isAutomated,
230
276
  createdBy: activity.createdBy,
231
277
  moderator: memberViews?.get(activity.createdBy),
278
+ report: reportViews?.get(activity.reportId),
232
279
  createdAt: activity.createdAt,
233
280
  }
234
281
  }
@@ -53,8 +53,8 @@ describe('labels from 3p labelers', () => {
53
53
  })
54
54
 
55
55
  afterAll(async () => {
56
- await network.close()
57
- await thirdPartyLabeler.close()
56
+ await network?.close()
57
+ await thirdPartyLabeler?.close()
58
58
  })
59
59
 
60
60
  const getPostSubject = () => ({
package/tests/_util.ts CHANGED
@@ -1,6 +1,7 @@
1
- import { Server } from 'node:http'
1
+ import { RequestListener, createServer } from 'node:http'
2
2
  import { AddressInfo } from 'node:net'
3
- import { type Express } from 'express'
3
+ // eslint-disable-next-line import/default
4
+ import httpTerminator from 'http-terminator'
4
5
  import { CID } from 'multiformats/cid'
5
6
  import { lexToJson } from '@atproto/lexicon'
6
7
  import { AtUri } from '@atproto/syntax'
@@ -215,19 +216,11 @@ export const stripViewerFromThread = <T extends ThreadViewPost>(
215
216
  return thread
216
217
  }
217
218
 
218
- export async function startServer(app: Express) {
219
- return new Promise<{
220
- origin: string
221
- server: Server
222
- stop: () => Promise<void>
223
- }>((resolve, reject) => {
219
+ export async function startServer(listener: RequestListener) {
220
+ return new Promise<AsyncDisposable & { port: number }>((resolve, reject) => {
224
221
  const onListen = () => {
225
222
  const port = (server.address() as AddressInfo).port
226
- resolve({
227
- server,
228
- origin: `http://localhost:${port}`,
229
- stop: () => stopServer(server),
230
- })
223
+ resolve({ port, [Symbol.asyncDispose]: () => terminator.terminate() })
231
224
  cleanup()
232
225
  }
233
226
  const onError = (err: Error) => {
@@ -239,21 +232,11 @@ export async function startServer(app: Express) {
239
232
  server.removeListener('error', onError)
240
233
  }
241
234
 
242
- const server = app
235
+ const server = createServer(listener)
243
236
  .listen(0)
244
237
  .once('listening', onListen)
245
238
  .once('error', onError)
246
- })
247
- }
248
239
 
249
- export async function stopServer(server: Server) {
250
- return new Promise<void>((resolve, reject) => {
251
- server.close((err) => {
252
- if (err) {
253
- reject(err)
254
- } else {
255
- resolve()
256
- }
257
- })
240
+ const terminator = httpTerminator.createHttpTerminator({ server })
258
241
  })
259
242
  }
@@ -65,7 +65,7 @@ describe('account-strikes', () => {
65
65
  })
66
66
 
67
67
  afterAll(async () => {
68
- await network.close()
68
+ await network?.close()
69
69
  })
70
70
 
71
71
  it('tracks strikes and exposes them through queryStatuses and queryEvents', async () => {
@@ -93,7 +93,7 @@ describe('acknowledge all subjects of account', () => {
93
93
  })
94
94
 
95
95
  afterAll(async () => {
96
- await network.close()
96
+ await network?.close()
97
97
  })
98
98
 
99
99
  it('acknowledges all open/escalated review subjects with takedown.', async () => {
@@ -22,7 +22,7 @@ describe('age assurance events', () => {
22
22
  })
23
23
 
24
24
  afterAll(async () => {
25
- await network.close()
25
+ await network?.close()
26
26
  })
27
27
 
28
28
  it('handles age assurance events from user', async () => {
@@ -30,7 +30,7 @@ describe('blob divert', () => {
30
30
  })
31
31
 
32
32
  afterAll(async () => {
33
- await network.close()
33
+ await network?.close()
34
34
  })
35
35
 
36
36
  const mockReportServiceResponse = (succeeds: boolean) => {
@@ -18,7 +18,7 @@ describe('communication-templates', () => {
18
18
  })
19
19
 
20
20
  afterAll(async () => {
21
- await network.close()
21
+ await network?.close()
22
22
  })
23
23
 
24
24
  const templateOne = {
@@ -23,7 +23,7 @@ describe('moderation subject content tagging', () => {
23
23
  })
24
24
 
25
25
  afterAll(async () => {
26
- await network.close()
26
+ await network?.close()
27
27
  })
28
28
 
29
29
  const getStatus = async (subject: string) => {
package/tests/db.test.ts CHANGED
@@ -15,7 +15,7 @@ describe('db', () => {
15
15
  })
16
16
 
17
17
  afterAll(async () => {
18
- await network.close()
18
+ await network?.close()
19
19
  })
20
20
 
21
21
  it('handles client errors without crashing.', async () => {
@@ -25,7 +25,7 @@ describe('expiring label', () => {
25
25
  })
26
26
 
27
27
  afterAll(async () => {
28
- await network.close()
28
+ await network?.close()
29
29
  })
30
30
 
31
31
  const emitExpiringLabel = async (did: string) =>
@@ -23,7 +23,7 @@ describe('expiring tags', () => {
23
23
  })
24
24
 
25
25
  afterAll(async () => {
26
- await network.close()
26
+ await network?.close()
27
27
  })
28
28
 
29
29
  const emitTagEvent = (
@@ -62,7 +62,7 @@ describe('account timeline', () => {
62
62
  })
63
63
 
64
64
  afterAll(async () => {
65
- await network.close()
65
+ await network?.close()
66
66
  })
67
67
 
68
68
  it('Returns entire timeline of events for a given account', async () => {
@@ -19,7 +19,7 @@ describe('get-config', () => {
19
19
  })
20
20
 
21
21
  afterAll(async () => {
22
- await network.close()
22
+ await network?.close()
23
23
  })
24
24
 
25
25
  const getConfig = async (role: 'moderator' | 'admin' | 'triage') => {
@@ -35,8 +35,9 @@ describe('admin get lists', () => {
35
35
  })
36
36
 
37
37
  afterAll(async () => {
38
+ // @TODO figure out why we even need this in afterAll ?
38
39
  AtpAgent.configure({ appLabelers: [BSKY_LABELER_DID] })
39
- await network.close()
40
+ await network?.close()
40
41
  })
41
42
 
42
43
  const getAlicesList = async () => {
@@ -26,7 +26,7 @@ describe('get profiles through ozone', () => {
26
26
  })
27
27
 
28
28
  afterAll(async () => {
29
- await network.close()
29
+ await network?.close()
30
30
  })
31
31
 
32
32
  it('allows getting profiles by dids for takendown accounts.', async () => {
@@ -38,7 +38,7 @@ describe('admin get record view', () => {
38
38
  })
39
39
 
40
40
  afterAll(async () => {
41
- await network.close()
41
+ await network?.close()
42
42
  })
43
43
 
44
44
  beforeAll(async () => {
@@ -37,7 +37,7 @@ describe('admin get records view', () => {
37
37
  })
38
38
 
39
39
  afterAll(async () => {
40
- await network.close()
40
+ await network?.close()
41
41
  })
42
42
 
43
43
  beforeAll(async () => {
@@ -43,7 +43,7 @@ describe('admin get repo view', () => {
43
43
  })
44
44
 
45
45
  afterAll(async () => {
46
- await network.close()
46
+ await network?.close()
47
47
  })
48
48
 
49
49
  beforeAll(async () => {
@@ -29,7 +29,7 @@ describe('ozone-get-report', () => {
29
29
  })
30
30
 
31
31
  afterAll(async () => {
32
- await network.close()
32
+ await network?.close()
33
33
  })
34
34
 
35
35
  it('returns a single report by id', async () => {
@@ -28,7 +28,7 @@ describe('reporter-stats', () => {
28
28
  })
29
29
 
30
30
  afterAll(async () => {
31
- await network.close()
31
+ await network?.close()
32
32
  })
33
33
 
34
34
  const getReporterStats = async (
@@ -43,7 +43,7 @@ describe('admin get multiple repos', () => {
43
43
  })
44
44
 
45
45
  afterAll(async () => {
46
- await network.close()
46
+ await network?.close()
47
47
  })
48
48
 
49
49
  beforeAll(async () => {
@@ -30,7 +30,7 @@ describe('admin get starter pack view', () => {
30
30
  })
31
31
 
32
32
  afterAll(async () => {
33
- await network.close()
33
+ await network?.close()
34
34
  })
35
35
 
36
36
  beforeAll(async () => {