@atproto/ozone 0.2.4 → 0.2.6

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 (217) hide show
  1. package/CHANGELOG.md +24 -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/label/queryLabels.d.ts.map +1 -1
  6. package/dist/api/label/queryLabels.js +13 -21
  7. package/dist/api/label/queryLabels.js.map +1 -1
  8. package/dist/api/report/queryActivities.d.ts +4 -0
  9. package/dist/api/report/queryActivities.d.ts.map +1 -0
  10. package/dist/api/report/queryActivities.js +36 -0
  11. package/dist/api/report/queryActivities.js.map +1 -0
  12. package/dist/assignment/index.d.ts.map +1 -1
  13. package/dist/assignment/index.js +9 -12
  14. package/dist/assignment/index.js.map +1 -1
  15. package/dist/background.d.ts +5 -3
  16. package/dist/background.d.ts.map +1 -1
  17. package/dist/background.js +13 -4
  18. package/dist/background.js.map +1 -1
  19. package/dist/context.js +1 -1
  20. package/dist/context.js.map +1 -1
  21. package/dist/daemon/context.js +1 -1
  22. package/dist/daemon/context.js.map +1 -1
  23. package/dist/daemon/event-pusher.d.ts +7 -1
  24. package/dist/daemon/event-pusher.d.ts.map +1 -1
  25. package/dist/daemon/verification-listener.d.ts +1 -1
  26. package/dist/daemon/verification-listener.d.ts.map +1 -1
  27. package/dist/daemon/verification-listener.js +10 -4
  28. package/dist/daemon/verification-listener.js.map +1 -1
  29. package/dist/db/index.d.ts +4 -5
  30. package/dist/db/index.d.ts.map +1 -1
  31. package/dist/db/index.js +2 -1
  32. package/dist/db/index.js.map +1 -1
  33. package/dist/db/migrations/20241220T144630860Z-stats-materialized-views.d.ts +1 -2
  34. package/dist/db/migrations/20241220T144630860Z-stats-materialized-views.d.ts.map +1 -1
  35. package/dist/db/migrations/20241220T144630860Z-stats-materialized-views.js.map +1 -1
  36. package/dist/db/migrations/20250718T150931000Z-update-appeal-reason-stats.d.ts +1 -2
  37. package/dist/db/migrations/20250718T150931000Z-update-appeal-reason-stats.d.ts.map +1 -1
  38. package/dist/db/migrations/20250718T150931000Z-update-appeal-reason-stats.js.map +1 -1
  39. package/dist/db/migrations/20260602T120000000Z-add-report-activity-created-index.d.ts +4 -0
  40. package/dist/db/migrations/20260602T120000000Z-add-report-activity-created-index.d.ts.map +1 -0
  41. package/dist/db/migrations/20260602T120000000Z-add-report-activity-created-index.js +15 -0
  42. package/dist/db/migrations/20260602T120000000Z-add-report-activity-created-index.js.map +1 -0
  43. package/dist/db/migrations/index.d.ts +1 -0
  44. package/dist/db/migrations/index.d.ts.map +1 -1
  45. package/dist/db/migrations/index.js +1 -0
  46. package/dist/db/migrations/index.js.map +1 -1
  47. package/dist/db/migrations/provider.d.ts +2 -1
  48. package/dist/db/migrations/provider.d.ts.map +1 -1
  49. package/dist/db/migrations/provider.js.map +1 -1
  50. package/dist/db/pagination.d.ts +4 -3
  51. package/dist/db/pagination.d.ts.map +1 -1
  52. package/dist/db/pagination.js +4 -4
  53. package/dist/db/pagination.js.map +1 -1
  54. package/dist/db/types.d.ts +1 -1
  55. package/dist/db/types.d.ts.map +1 -1
  56. package/dist/db/types.js.map +1 -1
  57. package/dist/index.d.ts.map +1 -1
  58. package/dist/index.js +23 -13
  59. package/dist/index.js.map +1 -1
  60. package/dist/jetstream/service.d.ts +1 -1
  61. package/dist/jetstream/service.d.ts.map +1 -1
  62. package/dist/jetstream/service.js +3 -1
  63. package/dist/jetstream/service.js.map +1 -1
  64. package/dist/lexicon/index.d.ts +11 -0
  65. package/dist/lexicon/index.d.ts.map +1 -1
  66. package/dist/lexicon/index.js +18 -0
  67. package/dist/lexicon/index.js.map +1 -1
  68. package/dist/lexicon/lexicons.d.ts +338 -0
  69. package/dist/lexicon/lexicons.d.ts.map +1 -1
  70. package/dist/lexicon/lexicons.js +173 -0
  71. package/dist/lexicon/lexicons.js.map +1 -1
  72. package/dist/lexicon/types/app/bsky/notification/defs.d.ts +1 -0
  73. package/dist/lexicon/types/app/bsky/notification/defs.d.ts.map +1 -1
  74. package/dist/lexicon/types/app/bsky/notification/defs.js.map +1 -1
  75. package/dist/lexicon/types/chat/bsky/notification/defs.d.ts +19 -0
  76. package/dist/lexicon/types/chat/bsky/notification/defs.d.ts.map +1 -0
  77. package/dist/lexicon/types/chat/bsky/notification/defs.js +19 -0
  78. package/dist/lexicon/types/chat/bsky/notification/defs.js.map +1 -0
  79. package/dist/lexicon/types/chat/bsky/notification/getPreferences.d.ts +20 -0
  80. package/dist/lexicon/types/chat/bsky/notification/getPreferences.d.ts.map +1 -0
  81. package/dist/lexicon/types/chat/bsky/notification/getPreferences.js +5 -0
  82. package/dist/lexicon/types/chat/bsky/notification/getPreferences.js.map +1 -0
  83. package/dist/lexicon/types/chat/bsky/notification/putPreferences.d.ts +26 -0
  84. package/dist/lexicon/types/chat/bsky/notification/putPreferences.d.ts.map +1 -0
  85. package/dist/lexicon/types/chat/bsky/notification/putPreferences.js +5 -0
  86. package/dist/lexicon/types/chat/bsky/notification/putPreferences.js.map +1 -0
  87. package/dist/lexicon/types/tools/ozone/report/defs.d.ts +1 -0
  88. package/dist/lexicon/types/tools/ozone/report/defs.d.ts.map +1 -1
  89. package/dist/lexicon/types/tools/ozone/report/defs.js.map +1 -1
  90. package/dist/lexicon/types/tools/ozone/report/queryActivities.d.ts +32 -0
  91. package/dist/lexicon/types/tools/ozone/report/queryActivities.d.ts.map +1 -0
  92. package/dist/lexicon/types/tools/ozone/report/queryActivities.js +5 -0
  93. package/dist/lexicon/types/tools/ozone/report/queryActivities.js.map +1 -0
  94. package/dist/mod-service/index.d.ts.map +1 -1
  95. package/dist/mod-service/index.js +28 -52
  96. package/dist/mod-service/index.js.map +1 -1
  97. package/dist/mod-service/report.d.ts +1 -0
  98. package/dist/mod-service/report.d.ts.map +1 -1
  99. package/dist/mod-service/report.js +16 -0
  100. package/dist/mod-service/report.js.map +1 -1
  101. package/dist/mod-service/status.d.ts +23 -128
  102. package/dist/mod-service/status.d.ts.map +1 -1
  103. package/dist/mod-service/views.js +7 -11
  104. package/dist/mod-service/views.js.map +1 -1
  105. package/dist/queue/service.js +1 -3
  106. package/dist/queue/service.js.map +1 -1
  107. package/dist/report/activity.d.ts +31 -2
  108. package/dist/report/activity.d.ts.map +1 -1
  109. package/dist/report/activity.js +27 -1
  110. package/dist/report/activity.js.map +1 -1
  111. package/dist/report/stats.d.ts.map +1 -1
  112. package/dist/report/stats.js.map +1 -1
  113. package/dist/scheduled-action/service.d.ts.map +1 -1
  114. package/dist/scheduled-action/service.js +16 -20
  115. package/dist/scheduled-action/service.js.map +1 -1
  116. package/dist/set/service.d.ts +10 -1
  117. package/dist/set/service.d.ts.map +1 -1
  118. package/dist/set/service.js +5 -2
  119. package/dist/set/service.js.map +1 -1
  120. package/dist/team/index.d.ts.map +1 -1
  121. package/dist/team/index.js +5 -4
  122. package/dist/team/index.js.map +1 -1
  123. package/dist/verification/issuer.d.ts +13 -3
  124. package/dist/verification/issuer.d.ts.map +1 -1
  125. package/dist/verification/service.d.ts +13 -1
  126. package/dist/verification/service.d.ts.map +1 -1
  127. package/dist/verification/service.js +1 -1
  128. package/dist/verification/service.js.map +1 -1
  129. package/package.json +12 -11
  130. package/src/api/index.ts +2 -0
  131. package/src/api/label/queryLabels.ts +11 -14
  132. package/src/api/report/queryActivities.ts +64 -0
  133. package/src/assignment/index.ts +15 -18
  134. package/src/background.ts +19 -4
  135. package/src/context.ts +1 -1
  136. package/src/daemon/context.ts +1 -1
  137. package/src/daemon/verification-listener.ts +9 -4
  138. package/src/db/index.ts +1 -1
  139. package/src/db/migrations/20241220T144630860Z-stats-materialized-views.ts +1 -2
  140. package/src/db/migrations/20250718T150931000Z-update-appeal-reason-stats.ts +1 -2
  141. package/src/db/migrations/20260602T120000000Z-add-report-activity-created-index.ts +17 -0
  142. package/src/db/migrations/index.ts +1 -0
  143. package/src/db/migrations/provider.ts +2 -1
  144. package/src/db/pagination.ts +18 -18
  145. package/src/db/types.ts +3 -1
  146. package/src/index.ts +25 -15
  147. package/src/jetstream/service.ts +3 -1
  148. package/src/mod-service/index.ts +78 -71
  149. package/src/mod-service/report.ts +24 -3
  150. package/src/mod-service/views.ts +16 -16
  151. package/src/queue/service.ts +5 -5
  152. package/src/report/activity.ts +47 -0
  153. package/src/report/stats.ts +5 -3
  154. package/src/scheduled-action/service.ts +22 -20
  155. package/src/set/service.ts +17 -14
  156. package/src/team/index.ts +6 -5
  157. package/src/verification/service.ts +2 -2
  158. package/tests/3p-labeler.test.ts +2 -2
  159. package/tests/_util.ts +8 -25
  160. package/tests/account-strikes.test.ts +1 -1
  161. package/tests/ack-all-subjects-of-account.test.ts +1 -1
  162. package/tests/age-assurance.test.ts +1 -1
  163. package/tests/blob-divert.test.ts +1 -1
  164. package/tests/communication-templates.test.ts +1 -1
  165. package/tests/content-tagger.test.ts +1 -1
  166. package/tests/db.test.ts +1 -1
  167. package/tests/expiring-label.test.ts +1 -1
  168. package/tests/expiring-tags.test.ts +1 -1
  169. package/tests/get-account-timeline.test.ts +1 -1
  170. package/tests/get-config.test.ts +1 -1
  171. package/tests/get-lists.test.ts +2 -1
  172. package/tests/get-profiles.test.ts +1 -1
  173. package/tests/get-record.test.ts +1 -1
  174. package/tests/get-records.test.ts +1 -1
  175. package/tests/get-repo.test.ts +1 -1
  176. package/tests/get-report.test.ts +1 -1
  177. package/tests/get-reporter-stats.test.ts +1 -1
  178. package/tests/get-repos.test.ts +1 -1
  179. package/tests/get-starter-pack.test.ts +1 -1
  180. package/tests/get-subjects.test.ts +1 -1
  181. package/tests/mod-tool.test.ts +1 -1
  182. package/tests/moderation-appeals.test.ts +1 -1
  183. package/tests/moderation-events.test.ts +1 -1
  184. package/tests/moderation-status-tags.test.ts +1 -1
  185. package/tests/moderation-statuses.test.ts +1 -1
  186. package/tests/moderation.test.ts +1 -1
  187. package/tests/protected-tags.test.ts +1 -1
  188. package/tests/query-labels.test.ts +1 -1
  189. package/tests/query-reports.test.ts +1 -1
  190. package/tests/queue-assignment.test.ts +1 -1
  191. package/tests/queue-router.test.ts +1 -1
  192. package/tests/queues.test.ts +1 -1
  193. package/tests/record-and-account-events.test.ts +1 -1
  194. package/tests/repo-search.test.ts +2 -2
  195. package/tests/report-action.test.ts +1 -1
  196. package/tests/report-activity.test.ts +145 -1
  197. package/tests/report-assignment.test.ts +1 -1
  198. package/tests/report-muting.test.ts +1 -1
  199. package/tests/report-reason.test.ts +1 -1
  200. package/tests/report-reassign-queue.test.ts +1 -1
  201. package/tests/report-routing.test.ts +1 -1
  202. package/tests/report-stats.test.ts +1 -1
  203. package/tests/revoke-account-credentials.test.ts +1 -1
  204. package/tests/safelink.test.ts +1 -1
  205. package/tests/scheduled-action-processor.test.ts +1 -1
  206. package/tests/scheduled-action.test.ts +1 -1
  207. package/tests/sequencer.test.ts +1 -1
  208. package/tests/server.test.ts +9 -12
  209. package/tests/sets.test.ts +1 -1
  210. package/tests/settings.test.ts +1 -1
  211. package/tests/strike-expiry-processor.test.ts +1 -1
  212. package/tests/subject-priority-score.test.ts +1 -1
  213. package/tests/takedown.test.ts +1 -1
  214. package/tests/team.test.ts +1 -1
  215. package/tests/verification-listener.test.ts +40 -13
  216. package/tests/verification.test.ts +1 -1
  217. package/tsconfig.build.tsbuildinfo +1 -1
@@ -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'
@@ -1,4 +1,5 @@
1
- import { Kysely, Migration, MigrationProvider } from 'kysely'
1
+ import { Kysely } from 'kysely'
2
+ import { Migration, MigrationProvider } from 'kysely/migration'
2
3
 
3
4
  // Passes a context argument to migrations. We use this to thread the dialect into migrations
4
5
 
@@ -1,4 +1,4 @@
1
- import { DynamicModule, sql } from 'kysely'
1
+ import { DynamicModule, SqlBool, sql } from 'kysely'
2
2
  import { InvalidRequestError } from '@atproto/xrpc-server'
3
3
  import { AnyQb, DbRef } from './types.js'
4
4
 
@@ -66,16 +66,16 @@ export abstract class GenericKeyset<R, LR extends LabeledResult> {
66
66
  if (tryIndex) {
67
67
  // The tryIndex param will likely disappear and become the default implementation: here for now for gradual rollout query-by-query.
68
68
  if (direction === 'asc') {
69
- return sql`((${this.primary}, ${this.secondary}) > (${labeled.primary}, ${labeled.secondary}))`
69
+ return sql<SqlBool>`((${this.primary}, ${this.secondary}) > (${labeled.primary}, ${labeled.secondary}))`
70
70
  } else {
71
- return sql`((${this.primary}, ${this.secondary}) < (${labeled.primary}, ${labeled.secondary}))`
71
+ return sql<SqlBool>`((${this.primary}, ${this.secondary}) < (${labeled.primary}, ${labeled.secondary}))`
72
72
  }
73
73
  } else {
74
74
  // @NOTE this implementation can struggle to use an index on (primary, secondary) for pagination due to the "or" usage.
75
75
  if (direction === 'asc') {
76
- return sql`((${this.primary} > ${labeled.primary}) or (${this.primary} = ${labeled.primary} and ${this.secondary} > ${labeled.secondary}))`
76
+ return sql<SqlBool>`((${this.primary} > ${labeled.primary}) or (${this.primary} = ${labeled.primary} and ${this.secondary} > ${labeled.secondary}))`
77
77
  } else {
78
- return sql`((${this.primary} < ${labeled.primary}) or (${this.primary} = ${labeled.primary} and ${this.secondary} < ${labeled.secondary}))`
78
+ return sql<SqlBool>`((${this.primary} < ${labeled.primary}) or (${this.primary} = ${labeled.primary} and ${this.secondary} < ${labeled.secondary}))`
79
79
  }
80
80
  }
81
81
  }
@@ -91,7 +91,7 @@ export class StatusKeyset extends GenericKeyset<StatusKeysetParam, Cursor> {
91
91
  labelResult(result: StatusKeysetParam): Cursor
92
92
  labelResult(result: StatusKeysetParam) {
93
93
  const primaryField = (
94
- this.primary as ReturnType<DynamicModule['ref']>
94
+ this.primary as ReturnType<DynamicModule<unknown>['ref']>
95
95
  ).dynamicReference.includes('lastReviewedAt')
96
96
  ? 'lastReviewedAt'
97
97
  : 'lastReportedAt'
@@ -134,12 +134,12 @@ export class StatusKeyset extends GenericKeyset<StatusKeysetParam, Cursor> {
134
134
  if (labeled === undefined) return
135
135
  if (direction === 'asc') {
136
136
  return !labeled.primary
137
- ? sql`(${this.primary} IS NULL AND ${this.secondary} > ${labeled.secondary})`
138
- : sql`((${this.primary}, ${this.secondary}) > (${labeled.primary}, ${labeled.secondary}) OR (${this.primary} is null))`
137
+ ? sql<SqlBool>`(${this.primary} IS NULL AND ${this.secondary} > ${labeled.secondary})`
138
+ : sql<SqlBool>`((${this.primary}, ${this.secondary}) > (${labeled.primary}, ${labeled.secondary}) OR (${this.primary} is null))`
139
139
  } else {
140
140
  return !labeled.primary
141
- ? sql`(${this.primary} IS NULL AND ${this.secondary} < ${labeled.secondary})`
142
- : sql`((${this.primary}, ${this.secondary}) < (${labeled.primary}, ${labeled.secondary}) OR (${this.primary} is null))`
141
+ ? sql<SqlBool>`(${this.primary} IS NULL AND ${this.secondary} < ${labeled.secondary})`
142
+ : sql<SqlBool>`((${this.primary}, ${this.secondary}) < (${labeled.primary}, ${labeled.secondary}) OR (${this.primary} is null))`
143
143
  }
144
144
  }
145
145
  }
@@ -244,15 +244,15 @@ export class EndAtIdKeyset extends GenericKeyset<EndAtIdKeysetParam, Cursor> {
244
244
  const primaryRef = sql`COALESCE(${this.primary}, ${PERMANENT_ENDSAT})`
245
245
  if (tryIndex) {
246
246
  if (direction === 'asc') {
247
- return sql`((${primaryRef}, ${this.secondary}) > (${labeled.primary}, ${labeled.secondary}))`
247
+ return sql<SqlBool>`((${primaryRef}, ${this.secondary}) > (${labeled.primary}, ${labeled.secondary}))`
248
248
  } else {
249
- return sql`((${primaryRef}, ${this.secondary}) < (${labeled.primary}, ${labeled.secondary}))`
249
+ return sql<SqlBool>`((${primaryRef}, ${this.secondary}) < (${labeled.primary}, ${labeled.secondary}))`
250
250
  }
251
251
  } else {
252
252
  if (direction === 'asc') {
253
- return sql`((${primaryRef} > ${labeled.primary}) or (${primaryRef} = ${labeled.primary} and ${this.secondary} > ${labeled.secondary}))`
253
+ return sql<SqlBool>`((${primaryRef} > ${labeled.primary}) or (${primaryRef} = ${labeled.primary} and ${this.secondary} > ${labeled.secondary}))`
254
254
  } else {
255
- return sql`((${primaryRef} < ${labeled.primary}) or (${primaryRef} = ${labeled.primary} and ${this.secondary} < ${labeled.secondary}))`
255
+ return sql<SqlBool>`((${primaryRef} < ${labeled.primary}) or (${primaryRef} = ${labeled.primary} and ${this.secondary} < ${labeled.secondary}))`
256
256
  }
257
257
  }
258
258
  }
@@ -314,11 +314,11 @@ export const paginate = <
314
314
  } = opts
315
315
  const keysetSql = keyset.getSql(keyset.unpack(cursor), direction, tryIndex)
316
316
  return qb
317
- .if(!!limit, (q) => q.limit(limit as number))
318
- .if(!nullsLast, (q) =>
317
+ .$if(!!limit, (q) => q.limit(limit as number))
318
+ .$if(!nullsLast, (q) =>
319
319
  q.orderBy(keyset.primary, direction).orderBy(keyset.secondary, direction),
320
320
  )
321
- .if(!!nullsLast, (q) =>
321
+ .$if(!!nullsLast, (q) =>
322
322
  q
323
323
  .orderBy(
324
324
  direction === 'asc'
@@ -331,5 +331,5 @@ export const paginate = <
331
331
  : sql`${keyset.secondary} desc nulls last`,
332
332
  ),
333
333
  )
334
- .if(!!keysetSql, (qb) => (keysetSql ? qb.where(keysetSql) : qb)) as QB
334
+ .$if(!!keysetSql, (qb) => (keysetSql ? qb.where(keysetSql) : qb)) as QB
335
335
  }
package/src/db/types.ts CHANGED
@@ -3,7 +3,9 @@ import { DynamicModule, RawBuilder, SelectQueryBuilder, sql } from 'kysely'
3
3
  import pg from 'pg'
4
4
  type PgPool = pg.Pool
5
5
 
6
- export type DbRef = RawBuilder | ReturnType<DynamicModule['ref']>
6
+ export type DbRef =
7
+ | RawBuilder<unknown>
8
+ | ReturnType<DynamicModule<unknown>['ref']>
7
9
 
8
10
  export type AnyQb = SelectQueryBuilder<any, any, any>
9
11
 
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
  }
@@ -1,4 +1,4 @@
1
- import { Insertable, RawBuilder, sql } from 'kysely'
1
+ import { Expression, Insertable, sql } from 'kysely'
2
2
  import { CID } from 'multiformats/cid'
3
3
  import { AtpAgent, ToolsOzoneModerationDefs } from '@atproto/api'
4
4
  import { addHoursToDate, chunkArray } from '@atproto/common'
@@ -267,21 +267,24 @@ export class ModerationService {
267
267
 
268
268
  // If subjectType is set to 'account' let that take priority and ignore collections filter
269
269
  if (collections.length && subjectType !== 'account') {
270
- builder = builder.where('subjectUri', 'is not', null).where((qb) => {
271
- collections.forEach((collection) => {
272
- qb = qb.orWhere('subjectUri', 'like', `%/${collection}/%`)
273
- })
274
- return qb
275
- })
270
+ builder = builder
271
+ .where('subjectUri', 'is not', null)
272
+ .where((eb) =>
273
+ eb.or(
274
+ collections.map((collection) =>
275
+ eb('subjectUri', 'like', `%/${collection}/%`),
276
+ ),
277
+ ),
278
+ )
276
279
  }
277
280
 
278
281
  if (types.length) {
279
- builder = builder.where((qb) => {
282
+ builder = builder.where((eb) => {
280
283
  if (types.length === 1) {
281
- return qb.where('action', '=', types[0])
284
+ return eb('action', '=', types[0])
282
285
  }
283
286
 
284
- return qb.where('action', 'in', types)
287
+ return eb('action', 'in', types)
285
288
  })
286
289
  }
287
290
  if (createdBy) {
@@ -298,12 +301,11 @@ export class ModerationService {
298
301
  // the input may end in || in which case, there may be item in the array which is just '' and we want to ignore those
299
302
  const keywords = comment.split('||').filter((keyword) => !!keyword.trim())
300
303
  if (keywords.length > 1) {
301
- builder = builder.where((qb) => {
302
- keywords.forEach((keyword) => {
303
- qb = qb.orWhere('comment', 'ilike', `%${keyword}%`)
304
- })
305
- return qb
306
- })
304
+ builder = builder.where((eb) =>
305
+ eb.or(
306
+ keywords.map((keyword) => eb('comment', 'ilike', `%${keyword}%`)),
307
+ ),
308
+ )
307
309
  } else if (keywords.length === 1) {
308
310
  builder = builder.where('comment', 'ilike', `%${keywords[0]}%`)
309
311
  }
@@ -324,23 +326,26 @@ export class ModerationService {
324
326
  })
325
327
  }
326
328
  if (addedTags.length) {
327
- builder = builder.where(sql`${ref('addedTags')} @> ${jsonb(addedTags)}`)
329
+ builder = builder.where(
330
+ sql<boolean>`${ref('addedTags')} @> ${jsonb(addedTags)}`,
331
+ )
328
332
  }
329
333
  if (removedTags.length) {
330
334
  builder = builder.where(
331
- sql`${ref('removedTags')} @> ${jsonb(removedTags)}`,
335
+ sql<boolean>`${ref('removedTags')} @> ${jsonb(removedTags)}`,
332
336
  )
333
337
  }
334
338
  if (reportTypes?.length) {
335
339
  builder = builder.where(sql`meta->>'reportType'`, 'in', reportTypes)
336
340
  }
337
341
  if (policies?.length) {
338
- builder = builder.where((qb) => {
339
- policies.forEach((policy) => {
340
- qb = qb.orWhere(sql`meta->>'policies'`, 'ilike', `%${policy}%`)
341
- })
342
- return qb
343
- })
342
+ builder = builder.where((eb) =>
343
+ eb.or(
344
+ policies.map((policy) =>
345
+ eb(sql`meta->>'policies'`, 'ilike', `%${policy}%`),
346
+ ),
347
+ ),
348
+ )
344
349
  }
345
350
  if (modTool?.length) {
346
351
  builder = builder
@@ -446,8 +451,8 @@ export class ModerationService {
446
451
  const subjectsToBeResolved = await this.db.db
447
452
  .selectFrom('moderation_subject_status')
448
453
  .where('did', '=', did)
449
- .where((qb) =>
450
- qb.where('recordPath', '!=', '').orWhere('convoId', '!=', ''),
454
+ .where((eb) =>
455
+ eb.or([eb('recordPath', '!=', ''), eb('convoId', '!=', '')]),
451
456
  )
452
457
  .where('reviewState', 'in', [REVIEWESCALATED, REVIEWOPEN])
453
458
  .selectAll()
@@ -816,8 +821,9 @@ export class ModerationService {
816
821
  const now = new Date().toISOString()
817
822
  const subjects = await this.db.db
818
823
  .selectFrom('moderation_subject_status')
819
- .where('suspendUntil', '<', now)
820
- .orWhere('muteUntil', '<', now)
824
+ .where((eb) =>
825
+ eb.or([eb('suspendUntil', '<', now), eb('muteUntil', '<', now)]),
826
+ )
821
827
  .selectAll()
822
828
  .execute()
823
829
 
@@ -1206,16 +1212,17 @@ export class ModerationService {
1206
1212
  if (subjectType !== 'account' && collections?.length) {
1207
1213
  builder = builder
1208
1214
  .where('moderation_subject_status.recordPath', '!=', '')
1209
- .where((qb) => {
1210
- for (const collection of collections) {
1211
- qb = qb.orWhere(
1212
- 'moderation_subject_status.recordPath',
1213
- 'like',
1214
- `${collection}/%`,
1215
- )
1216
- }
1217
- return qb
1218
- })
1215
+ .where((eb) =>
1216
+ eb.or(
1217
+ collections.map((collection) =>
1218
+ eb(
1219
+ 'moderation_subject_status.recordPath',
1220
+ 'like',
1221
+ `${collection}/%`,
1222
+ ),
1223
+ ),
1224
+ ),
1225
+ )
1219
1226
  }
1220
1227
 
1221
1228
  if (ignoreSubjects?.length) {
@@ -1325,30 +1332,32 @@ export class ModerationService {
1325
1332
  }
1326
1333
 
1327
1334
  if (!includeMuted) {
1328
- builder = builder.where((qb) =>
1329
- qb
1330
- .where(
1335
+ builder = builder.where((eb) =>
1336
+ eb.or([
1337
+ eb(
1331
1338
  'moderation_subject_status.muteUntil',
1332
1339
  '<',
1333
1340
  new Date().toISOString(),
1334
- )
1335
- .orWhere('moderation_subject_status.muteUntil', 'is', null),
1341
+ ),
1342
+ eb('moderation_subject_status.muteUntil', 'is', null),
1343
+ ]),
1336
1344
  )
1337
1345
  }
1338
1346
 
1339
1347
  if (onlyMuted) {
1340
- builder = builder.where((qb) =>
1341
- qb
1342
- .where(
1348
+ builder = builder.where((eb) =>
1349
+ eb.or([
1350
+ eb(
1343
1351
  'moderation_subject_status.muteUntil',
1344
1352
  '>',
1345
1353
  new Date().toISOString(),
1346
- )
1347
- .orWhere(
1354
+ ),
1355
+ eb(
1348
1356
  'moderation_subject_status.muteReportingUntil',
1349
1357
  '>',
1350
1358
  new Date().toISOString(),
1351
1359
  ),
1360
+ ]),
1352
1361
  )
1353
1362
  }
1354
1363
 
@@ -1356,30 +1365,28 @@ export class ModerationService {
1356
1365
  const conditions = parseTags(tags)
1357
1366
  if (conditions?.length) {
1358
1367
  // [["tag1"], ["tag2", "tag3"], ["tag4"]] => (tags ? 'tag1') OR (tags ? 'tag2' AND tags ? 'tag3') OR (tags ? 'tag4')
1359
- builder = builder.where((qb) => {
1360
- for (const subTags of conditions) {
1361
- // OR between every conditions items (subTags)
1362
- qb = qb.orWhere((qb) => {
1368
+ builder = builder.where((eb) =>
1369
+ // OR between every conditions items (subTags)
1370
+ eb.or(
1371
+ conditions.map((subTags) =>
1363
1372
  // AND between every subTags items (subTag)
1364
- for (const subTag of subTags) {
1365
- qb = qb.where(
1366
- sql`${ref('moderation_subject_status.tags')} ? ${subTag}`,
1367
- )
1368
- }
1369
- return qb
1370
- })
1371
- }
1372
- return qb
1373
- })
1373
+ eb.and(
1374
+ subTags.map(
1375
+ (subTag) =>
1376
+ sql<boolean>`${ref('moderation_subject_status.tags')} ? ${subTag}`,
1377
+ ),
1378
+ ),
1379
+ ),
1380
+ ),
1381
+ )
1374
1382
  }
1375
1383
 
1376
1384
  if (excludeTags?.length) {
1377
- builder = builder.where((qb) =>
1378
- qb
1379
- .where(
1380
- sql`NOT(${ref('moderation_subject_status.tags')} ?| array[${sql.join(excludeTags)}]::TEXT[])`,
1381
- )
1382
- .orWhere('tags', 'is', null),
1385
+ builder = builder.where((eb) =>
1386
+ eb.or([
1387
+ sql<boolean>`NOT(${ref('moderation_subject_status.tags')} ?| array[${sql.join(excludeTags)}]::TEXT[])`,
1388
+ eb('tags', 'is', null),
1389
+ ]),
1383
1390
  )
1384
1391
  }
1385
1392
 
@@ -1620,15 +1627,15 @@ export class ModerationService {
1620
1627
  const countAll = () => {
1621
1628
  return sql<number>`COUNT(*)`
1622
1629
  }
1623
- const countAllDistinctBy = (ref: RawBuilder) => {
1630
+ const countAllDistinctBy = (ref: Expression<unknown>) => {
1624
1631
  return sql<number>`COUNT(DISTINCT ${ref})`
1625
1632
  }
1626
- const countTakedownsDistinctBy = (ref: RawBuilder) => {
1633
+ const countTakedownsDistinctBy = (ref: Expression<unknown>) => {
1627
1634
  return sql<number>`COUNT(DISTINCT ${ref}) FILTER (
1628
1635
  WHERE actions."action" = 'tools.ozone.moderation.defs#modEventTakedown'
1629
1636
  )`
1630
1637
  }
1631
- const countLabelsDistinctBy = (ref: RawBuilder) => {
1638
+ const countLabelsDistinctBy = (ref: Expression<unknown>) => {
1632
1639
  return sql<number>`COUNT(DISTINCT ${ref}) FILTER (
1633
1640
  WHERE actions."action" = 'tools.ozone.moderation.defs#modEventLabel'
1634
1641
  )`
@@ -74,7 +74,9 @@ export async function queryReports(
74
74
  const collectionConditions = params.collections.map(
75
75
  (collection) => sql`r."recordPath" LIKE ${`${collection}/%`}`,
76
76
  )
77
- builder = builder.where(sql`(${sql.join(collectionConditions, sql` OR `)})`)
77
+ builder = builder.where(
78
+ sql<boolean>`(${sql.join(collectionConditions, sql` OR `)})`,
79
+ )
78
80
  }
79
81
 
80
82
  if (params.reportTypes?.length) {
@@ -112,12 +114,12 @@ export async function queryReports(
112
114
  const [sortValue, id] = params.cursor.split('::')
113
115
  const sortCol = sortField === 'updatedAt' ? 'r.updatedAt' : 'r.createdAt'
114
116
  if (sortDirection === 'desc') {
115
- builder = builder.where(sql`(
117
+ builder = builder.where(sql<boolean>`(
116
118
  ${sql.ref(sortCol)} < ${sortValue}
117
119
  OR (${sql.ref(sortCol)} = ${sortValue} AND r.id < ${Number(id)})
118
120
  )`)
119
121
  } else {
120
- builder = builder.where(sql`(
122
+ builder = builder.where(sql<boolean>`(
121
123
  ${sql.ref(sortCol)} > ${sortValue}
122
124
  OR (${sql.ref(sortCol)} = ${sortValue} AND r.id > ${Number(id)})
123
125
  )`)
@@ -173,6 +175,25 @@ export async function getReportById(
173
175
  .executeTakeFirst()
174
176
  }
175
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
+
176
197
  export async function getLatestReport(
177
198
  db: Database,
178
199
  ): Promise<ReportWithEvent | undefined> {
@@ -602,7 +602,7 @@ export class ModerationViews {
602
602
  this.db.db,
603
603
  )
604
604
  .where(
605
- sql<string>`${ref(
605
+ sql<boolean>`${ref(
606
606
  'moderation_subject_status.blobCids',
607
607
  )} @> ${JSON.stringify(blobs.map((blob) => blob.ref.toString()))}`,
608
608
  )
@@ -642,10 +642,10 @@ export class ModerationViews {
642
642
  const res = await this.db.db
643
643
  .selectFrom('label')
644
644
  .where('label.uri', 'in', subjects)
645
- .where((qb) =>
646
- qb.where('label.exp', 'is', null).orWhere('label.exp', '>', now),
645
+ .where((eb) =>
646
+ eb.or([eb('label.exp', 'is', null), eb('label.exp', '>', now)]),
647
647
  )
648
- .if(!includeNeg, (qb) => qb.where('neg', '=', false))
648
+ .$if(!includeNeg, (qb) => qb.where('neg', '=', false))
649
649
  .selectAll()
650
650
  .execute()
651
651
 
@@ -688,21 +688,21 @@ export class ModerationViews {
688
688
 
689
689
  const builder = moderationSubjectStatusQueryBuilder(this.db.db)
690
690
  //
691
- .where((qb) => {
692
- for (const sub of parsedSubjects) {
693
- qb = qb.orWhere((qb) =>
694
- qb
695
- .where('moderation_subject_status.did', '=', sub.did)
696
- .where(
691
+ .where((eb) =>
692
+ eb.or(
693
+ parsedSubjects.map((sub) =>
694
+ eb.and([
695
+ eb('moderation_subject_status.did', '=', sub.did),
696
+ eb(
697
697
  'moderation_subject_status.recordPath',
698
698
  '=',
699
699
  sub.recordPath ?? '',
700
- )
701
- .where('moderation_subject_status.convoId', '=', ''),
702
- )
703
- }
704
- return qb
705
- })
700
+ ),
701
+ eb('moderation_subject_status.convoId', '=', ''),
702
+ ]),
703
+ ),
704
+ ),
705
+ )
706
706
 
707
707
  const [statusRes, accountsByDid] = await Promise.all([
708
708
  builder.execute(),
@@ -204,7 +204,7 @@ export class QueueService {
204
204
  }
205
205
 
206
206
  if (subjectType !== undefined) {
207
- qb = qb.where(sql`"subjectTypes" @> ${jsonb([subjectType])}`)
207
+ qb = qb.where(sql<boolean>`"subjectTypes" @> ${jsonb([subjectType])}`)
208
208
  }
209
209
 
210
210
  if (collection !== undefined) {
@@ -215,7 +215,7 @@ export class QueueService {
215
215
  const conditions = reportTypes.map(
216
216
  (t) => sql`"reportTypes" @> ${jsonb([t])}`,
217
217
  )
218
- qb = qb.where(sql`(${sql.join(conditions, sql` OR `)})`)
218
+ qb = qb.where(sql<boolean>`(${sql.join(conditions, sql` OR `)})`)
219
219
  }
220
220
 
221
221
  const keyset = new TimeIdKeyset(ref('createdAt'), ref('id'))
@@ -309,9 +309,9 @@ export class QueueService {
309
309
  .limit(params.limit)
310
310
 
311
311
  if (opts?.includeUnmatched) {
312
- query = query.where((qb) => {
313
- return qb.orWhere('r.queueId', 'is', null).orWhere('r.queueId', '=', -1)
314
- })
312
+ query = query.where((eb) =>
313
+ eb.or([eb('r.queueId', 'is', null), eb('r.queueId', '=', -1)]),
314
+ )
315
315
  } else {
316
316
  query = query.where('r.queueId', 'is', null)
317
317
  }