@atproto/ozone 0.1.173 → 0.1.174

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 (411) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/dist/api/index.d.ts.map +1 -1
  3. package/dist/api/index.js +40 -0
  4. package/dist/api/index.js.map +1 -1
  5. package/dist/api/moderation/emitEvent.d.ts.map +1 -1
  6. package/dist/api/moderation/emitEvent.js +31 -0
  7. package/dist/api/moderation/emitEvent.js.map +1 -1
  8. package/dist/api/queue/assignModerator.d.ts +4 -0
  9. package/dist/api/queue/assignModerator.d.ts.map +1 -0
  10. package/dist/api/queue/assignModerator.js +28 -0
  11. package/dist/api/queue/assignModerator.js.map +1 -0
  12. package/dist/api/queue/createQueue.d.ts +4 -0
  13. package/dist/api/queue/createQueue.d.ts.map +1 -0
  14. package/dist/api/queue/createQueue.js +44 -0
  15. package/dist/api/queue/createQueue.js.map +1 -0
  16. package/dist/api/queue/deleteQueue.d.ts +4 -0
  17. package/dist/api/queue/deleteQueue.d.ts.map +1 -0
  18. package/dist/api/queue/deleteQueue.js +40 -0
  19. package/dist/api/queue/deleteQueue.js.map +1 -0
  20. package/dist/api/queue/getAssignments.d.ts +4 -0
  21. package/dist/api/queue/getAssignments.d.ts.map +1 -0
  22. package/dist/api/queue/getAssignments.js +19 -0
  23. package/dist/api/queue/getAssignments.js.map +1 -0
  24. package/dist/api/queue/listQueues.d.ts +4 -0
  25. package/dist/api/queue/listQueues.d.ts.map +1 -0
  26. package/dist/api/queue/listQueues.js +29 -0
  27. package/dist/api/queue/listQueues.js.map +1 -0
  28. package/dist/api/queue/routeReports.d.ts +4 -0
  29. package/dist/api/queue/routeReports.d.ts.map +1 -0
  30. package/dist/api/queue/routeReports.js +33 -0
  31. package/dist/api/queue/routeReports.js.map +1 -0
  32. package/dist/api/queue/unassignModerator.d.ts +4 -0
  33. package/dist/api/queue/unassignModerator.d.ts.map +1 -0
  34. package/dist/api/queue/unassignModerator.js +24 -0
  35. package/dist/api/queue/unassignModerator.js.map +1 -0
  36. package/dist/api/queue/updateQueue.d.ts +4 -0
  37. package/dist/api/queue/updateQueue.d.ts.map +1 -0
  38. package/dist/api/queue/updateQueue.js +39 -0
  39. package/dist/api/queue/updateQueue.js.map +1 -0
  40. package/dist/api/report/assignModerator.d.ts +4 -0
  41. package/dist/api/report/assignModerator.d.ts.map +1 -0
  42. package/dist/api/report/assignModerator.js +33 -0
  43. package/dist/api/report/assignModerator.js.map +1 -0
  44. package/dist/api/report/createActivity.d.ts +4 -0
  45. package/dist/api/report/createActivity.d.ts.map +1 -0
  46. package/dist/api/report/createActivity.js +44 -0
  47. package/dist/api/report/createActivity.js.map +1 -0
  48. package/dist/api/report/getAssignments.d.ts +4 -0
  49. package/dist/api/report/getAssignments.d.ts.map +1 -0
  50. package/dist/api/report/getAssignments.js +19 -0
  51. package/dist/api/report/getAssignments.js.map +1 -0
  52. package/dist/api/report/getHistoricalStats.d.ts +4 -0
  53. package/dist/api/report/getHistoricalStats.d.ts.map +1 -0
  54. package/dist/api/report/getHistoricalStats.js +32 -0
  55. package/dist/api/report/getHistoricalStats.js.map +1 -0
  56. package/dist/api/report/getLatestReport.d.ts +4 -0
  57. package/dist/api/report/getLatestReport.d.ts.map +1 -0
  58. package/dist/api/report/getLatestReport.js +31 -0
  59. package/dist/api/report/getLatestReport.js.map +1 -0
  60. package/dist/api/report/getLiveStats.d.ts +4 -0
  61. package/dist/api/report/getLiveStats.d.ts.map +1 -0
  62. package/dist/api/report/getLiveStats.js +25 -0
  63. package/dist/api/report/getLiveStats.js.map +1 -0
  64. package/dist/api/report/getReport.d.ts +4 -0
  65. package/dist/api/report/getReport.d.ts.map +1 -0
  66. package/dist/api/report/getReport.js +35 -0
  67. package/dist/api/report/getReport.js.map +1 -0
  68. package/dist/api/report/listActivities.d.ts +4 -0
  69. package/dist/api/report/listActivities.d.ts.map +1 -0
  70. package/dist/api/report/listActivities.js +25 -0
  71. package/dist/api/report/listActivities.js.map +1 -0
  72. package/dist/api/report/queryReports.d.ts +4 -0
  73. package/dist/api/report/queryReports.d.ts.map +1 -0
  74. package/dist/api/report/queryReports.js +29 -0
  75. package/dist/api/report/queryReports.js.map +1 -0
  76. package/dist/api/report/reassignQueue.d.ts +4 -0
  77. package/dist/api/report/reassignQueue.d.ts.map +1 -0
  78. package/dist/api/report/reassignQueue.js +45 -0
  79. package/dist/api/report/reassignQueue.js.map +1 -0
  80. package/dist/api/report/refreshStats.d.ts +4 -0
  81. package/dist/api/report/refreshStats.d.ts.map +1 -0
  82. package/dist/api/report/refreshStats.js +26 -0
  83. package/dist/api/report/refreshStats.js.map +1 -0
  84. package/dist/api/report/unassignModerator.d.ts +4 -0
  85. package/dist/api/report/unassignModerator.d.ts.map +1 -0
  86. package/dist/api/report/unassignModerator.js +21 -0
  87. package/dist/api/report/unassignModerator.js.map +1 -0
  88. package/dist/api/util.d.ts +2 -0
  89. package/dist/api/util.d.ts.map +1 -1
  90. package/dist/api/util.js +9 -1
  91. package/dist/api/util.js.map +1 -1
  92. package/dist/assignment/index.d.ts +89 -0
  93. package/dist/assignment/index.d.ts.map +1 -0
  94. package/dist/assignment/index.js +537 -0
  95. package/dist/assignment/index.js.map +1 -0
  96. package/dist/config/config.d.ts +14 -0
  97. package/dist/config/config.d.ts.map +1 -1
  98. package/dist/config/config.js +9 -0
  99. package/dist/config/config.js.map +1 -1
  100. package/dist/config/env.d.ts +3 -0
  101. package/dist/config/env.d.ts.map +1 -1
  102. package/dist/config/env.js +3 -0
  103. package/dist/config/env.js.map +1 -1
  104. package/dist/context.d.ts +9 -0
  105. package/dist/context.d.ts.map +1 -1
  106. package/dist/context.js +31 -10
  107. package/dist/context.js.map +1 -1
  108. package/dist/daemon/context.d.ts +6 -0
  109. package/dist/daemon/context.d.ts.map +1 -1
  110. package/dist/daemon/context.js +28 -4
  111. package/dist/daemon/context.js.map +1 -1
  112. package/dist/daemon/job-cursor.d.ts +5 -0
  113. package/dist/daemon/job-cursor.d.ts.map +1 -0
  114. package/dist/daemon/job-cursor.js +28 -0
  115. package/dist/daemon/job-cursor.js.map +1 -0
  116. package/dist/daemon/queue-router.d.ts +17 -0
  117. package/dist/daemon/queue-router.d.ts.map +1 -0
  118. package/dist/daemon/queue-router.js +114 -0
  119. package/dist/daemon/queue-router.js.map +1 -0
  120. package/dist/daemon/stats-computer.d.ts +51 -0
  121. package/dist/daemon/stats-computer.d.ts.map +1 -0
  122. package/dist/daemon/stats-computer.js +117 -0
  123. package/dist/daemon/stats-computer.js.map +1 -0
  124. package/dist/daemon/strike-expiry-processor.d.ts.map +1 -1
  125. package/dist/daemon/strike-expiry-processor.js +4 -19
  126. package/dist/daemon/strike-expiry-processor.js.map +1 -1
  127. package/dist/db/migrations/20260219T164523000Z-create-report-table.d.ts +4 -0
  128. package/dist/db/migrations/20260219T164523000Z-create-report-table.d.ts.map +1 -0
  129. package/dist/db/migrations/20260219T164523000Z-create-report-table.js +126 -0
  130. package/dist/db/migrations/20260219T164523000Z-create-report-table.js.map +1 -0
  131. package/dist/db/migrations/20260219T165302248Z-moderator-assignment.d.ts +4 -0
  132. package/dist/db/migrations/20260219T165302248Z-moderator-assignment.d.ts.map +1 -0
  133. package/dist/db/migrations/20260219T165302248Z-moderator-assignment.js +35 -0
  134. package/dist/db/migrations/20260219T165302248Z-moderator-assignment.js.map +1 -0
  135. package/dist/db/migrations/20260225T000000000Z-add-report-queue-table.d.ts +4 -0
  136. package/dist/db/migrations/20260225T000000000Z-add-report-queue-table.d.ts.map +1 -0
  137. package/dist/db/migrations/20260225T000000000Z-add-report-queue-table.js +36 -0
  138. package/dist/db/migrations/20260225T000000000Z-add-report-queue-table.js.map +1 -0
  139. package/dist/db/migrations/20260313T000000000Z-add-report-activity-table.d.ts +4 -0
  140. package/dist/db/migrations/20260313T000000000Z-add-report-activity-table.d.ts.map +1 -0
  141. package/dist/db/migrations/20260313T000000000Z-add-report-activity-table.js +39 -0
  142. package/dist/db/migrations/20260313T000000000Z-add-report-activity-table.js.map +1 -0
  143. package/dist/db/migrations/20260318T152058935Z-add-report-stat.d.ts +4 -0
  144. package/dist/db/migrations/20260318T152058935Z-add-report-stat.d.ts.map +1 -0
  145. package/dist/db/migrations/20260318T152058935Z-add-report-stat.js +34 -0
  146. package/dist/db/migrations/20260318T152058935Z-add-report-stat.js.map +1 -0
  147. package/dist/db/migrations/index.d.ts +5 -0
  148. package/dist/db/migrations/index.d.ts.map +1 -1
  149. package/dist/db/migrations/index.js +6 -1
  150. package/dist/db/migrations/index.js.map +1 -1
  151. package/dist/db/pagination.d.ts +31 -0
  152. package/dist/db/pagination.d.ts.map +1 -1
  153. package/dist/db/pagination.js +74 -1
  154. package/dist/db/pagination.js.map +1 -1
  155. package/dist/db/schema/index.d.ts +6 -1
  156. package/dist/db/schema/index.d.ts.map +1 -1
  157. package/dist/db/schema/index.js.map +1 -1
  158. package/dist/db/schema/moderator_assignment.d.ts +14 -0
  159. package/dist/db/schema/moderator_assignment.d.ts.map +1 -0
  160. package/dist/db/schema/moderator_assignment.js +5 -0
  161. package/dist/db/schema/moderator_assignment.js.map +1 -0
  162. package/dist/db/schema/report.d.ts +25 -0
  163. package/dist/db/schema/report.d.ts.map +1 -0
  164. package/dist/db/schema/report.js +5 -0
  165. package/dist/db/schema/report.js.map +1 -0
  166. package/dist/db/schema/report_activity.d.ts +18 -0
  167. package/dist/db/schema/report_activity.d.ts.map +1 -0
  168. package/dist/db/schema/report_activity.js +5 -0
  169. package/dist/db/schema/report_activity.js.map +1 -0
  170. package/dist/db/schema/report_queue.d.ts +19 -0
  171. package/dist/db/schema/report_queue.d.ts.map +1 -0
  172. package/dist/db/schema/report_queue.js +5 -0
  173. package/dist/db/schema/report_queue.js.map +1 -0
  174. package/dist/db/schema/report_stat.d.ts +20 -0
  175. package/dist/db/schema/report_stat.d.ts.map +1 -0
  176. package/dist/db/schema/report_stat.js +5 -0
  177. package/dist/db/schema/report_stat.js.map +1 -0
  178. package/dist/lexicon/index.d.ts +50 -0
  179. package/dist/lexicon/index.d.ts.map +1 -1
  180. package/dist/lexicon/index.js +120 -2
  181. package/dist/lexicon/index.js.map +1 -1
  182. package/dist/lexicon/lexicons.d.ts +10535 -7389
  183. package/dist/lexicon/lexicons.d.ts.map +1 -1
  184. package/dist/lexicon/lexicons.js +1789 -122
  185. package/dist/lexicon/lexicons.js.map +1 -1
  186. package/dist/lexicon/types/app/bsky/embed/external.d.ts +2 -0
  187. package/dist/lexicon/types/app/bsky/embed/external.d.ts.map +1 -1
  188. package/dist/lexicon/types/app/bsky/embed/external.js.map +1 -1
  189. package/dist/lexicon/types/tools/ozone/moderation/emitEvent.d.ts +19 -0
  190. package/dist/lexicon/types/tools/ozone/moderation/emitEvent.d.ts.map +1 -1
  191. package/dist/lexicon/types/tools/ozone/moderation/emitEvent.js +9 -0
  192. package/dist/lexicon/types/tools/ozone/moderation/emitEvent.js.map +1 -1
  193. package/dist/lexicon/types/tools/ozone/queue/assignModerator.d.ts +27 -0
  194. package/dist/lexicon/types/tools/ozone/queue/assignModerator.d.ts.map +1 -0
  195. package/dist/lexicon/types/tools/ozone/queue/assignModerator.js +7 -0
  196. package/dist/lexicon/types/tools/ozone/queue/assignModerator.js.map +1 -0
  197. package/dist/lexicon/types/tools/ozone/queue/createQueue.d.ts +35 -0
  198. package/dist/lexicon/types/tools/ozone/queue/createQueue.d.ts.map +1 -0
  199. package/dist/lexicon/types/tools/ozone/queue/createQueue.js +7 -0
  200. package/dist/lexicon/types/tools/ozone/queue/createQueue.js.map +1 -0
  201. package/dist/lexicon/types/tools/ozone/queue/defs.d.ts +62 -0
  202. package/dist/lexicon/types/tools/ozone/queue/defs.d.ts.map +1 -0
  203. package/dist/lexicon/types/tools/ozone/queue/defs.js +34 -0
  204. package/dist/lexicon/types/tools/ozone/queue/defs.js.map +1 -0
  205. package/dist/lexicon/types/tools/ozone/queue/deleteQueue.d.ts +29 -0
  206. package/dist/lexicon/types/tools/ozone/queue/deleteQueue.d.ts.map +1 -0
  207. package/dist/lexicon/types/tools/ozone/queue/deleteQueue.js +7 -0
  208. package/dist/lexicon/types/tools/ozone/queue/deleteQueue.js.map +1 -0
  209. package/dist/lexicon/types/tools/ozone/queue/getAssignments.d.ts +30 -0
  210. package/dist/lexicon/types/tools/ozone/queue/getAssignments.d.ts.map +1 -0
  211. package/dist/lexicon/types/tools/ozone/queue/getAssignments.js +7 -0
  212. package/dist/lexicon/types/tools/ozone/queue/getAssignments.js.map +1 -0
  213. package/dist/lexicon/types/tools/ozone/queue/listQueues.d.ts +32 -0
  214. package/dist/lexicon/types/tools/ozone/queue/listQueues.d.ts.map +1 -0
  215. package/dist/lexicon/types/tools/ozone/queue/listQueues.js +7 -0
  216. package/dist/lexicon/types/tools/ozone/queue/listQueues.js.map +1 -0
  217. package/dist/lexicon/types/tools/ozone/queue/routeReports.d.ts +31 -0
  218. package/dist/lexicon/types/tools/ozone/queue/routeReports.d.ts.map +1 -0
  219. package/dist/lexicon/types/tools/ozone/queue/routeReports.js +7 -0
  220. package/dist/lexicon/types/tools/ozone/queue/routeReports.js.map +1 -0
  221. package/dist/lexicon/types/tools/ozone/queue/unassignModerator.d.ts +18 -0
  222. package/dist/lexicon/types/tools/ozone/queue/unassignModerator.d.ts.map +1 -0
  223. package/dist/lexicon/types/tools/ozone/queue/unassignModerator.js +7 -0
  224. package/dist/lexicon/types/tools/ozone/queue/unassignModerator.js.map +1 -0
  225. package/dist/lexicon/types/tools/ozone/queue/updateQueue.d.ts +32 -0
  226. package/dist/lexicon/types/tools/ozone/queue/updateQueue.d.ts.map +1 -0
  227. package/dist/lexicon/types/tools/ozone/queue/updateQueue.js +7 -0
  228. package/dist/lexicon/types/tools/ozone/queue/updateQueue.js.map +1 -0
  229. package/dist/lexicon/types/tools/ozone/report/assignModerator.d.ts +31 -0
  230. package/dist/lexicon/types/tools/ozone/report/assignModerator.d.ts.map +1 -0
  231. package/dist/lexicon/types/tools/ozone/report/assignModerator.js +7 -0
  232. package/dist/lexicon/types/tools/ozone/report/assignModerator.js.map +1 -0
  233. package/dist/lexicon/types/tools/ozone/report/createActivity.d.ts +37 -0
  234. package/dist/lexicon/types/tools/ozone/report/createActivity.d.ts.map +1 -0
  235. package/dist/lexicon/types/tools/ozone/report/createActivity.js +7 -0
  236. package/dist/lexicon/types/tools/ozone/report/createActivity.js.map +1 -0
  237. package/dist/lexicon/types/tools/ozone/report/defs.d.ts +185 -0
  238. package/dist/lexicon/types/tools/ozone/report/defs.d.ts.map +1 -1
  239. package/dist/lexicon/types/tools/ozone/report/defs.js +108 -0
  240. package/dist/lexicon/types/tools/ozone/report/defs.js.map +1 -1
  241. package/dist/lexicon/types/tools/ozone/report/getAssignments.d.ts +30 -0
  242. package/dist/lexicon/types/tools/ozone/report/getAssignments.d.ts.map +1 -0
  243. package/dist/lexicon/types/tools/ozone/report/getAssignments.js +7 -0
  244. package/dist/lexicon/types/tools/ozone/report/getAssignments.js.map +1 -0
  245. package/dist/lexicon/types/tools/ozone/report/getHistoricalStats.d.ts +36 -0
  246. package/dist/lexicon/types/tools/ozone/report/getHistoricalStats.d.ts.map +1 -0
  247. package/dist/lexicon/types/tools/ozone/report/getHistoricalStats.js +7 -0
  248. package/dist/lexicon/types/tools/ozone/report/getHistoricalStats.js.map +1 -0
  249. package/dist/lexicon/types/tools/ozone/report/getLatestReport.d.ts +21 -0
  250. package/dist/lexicon/types/tools/ozone/report/getLatestReport.d.ts.map +1 -0
  251. package/dist/lexicon/types/tools/ozone/report/getLatestReport.js +7 -0
  252. package/dist/lexicon/types/tools/ozone/report/getLatestReport.js.map +1 -0
  253. package/dist/lexicon/types/tools/ozone/report/getLiveStats.d.ts +27 -0
  254. package/dist/lexicon/types/tools/ozone/report/getLiveStats.d.ts.map +1 -0
  255. package/dist/lexicon/types/tools/ozone/report/getLiveStats.js +7 -0
  256. package/dist/lexicon/types/tools/ozone/report/getLiveStats.js.map +1 -0
  257. package/dist/lexicon/types/tools/ozone/report/getReport.d.ts +22 -0
  258. package/dist/lexicon/types/tools/ozone/report/getReport.d.ts.map +1 -0
  259. package/dist/lexicon/types/tools/ozone/report/getReport.js +7 -0
  260. package/dist/lexicon/types/tools/ozone/report/getReport.js.map +1 -0
  261. package/dist/lexicon/types/tools/ozone/report/listActivities.d.ts +26 -0
  262. package/dist/lexicon/types/tools/ozone/report/listActivities.d.ts.map +1 -0
  263. package/dist/lexicon/types/tools/ozone/report/listActivities.js +7 -0
  264. package/dist/lexicon/types/tools/ozone/report/listActivities.js.map +1 -0
  265. package/dist/lexicon/types/tools/ozone/report/queryReports.d.ts +48 -0
  266. package/dist/lexicon/types/tools/ozone/report/queryReports.d.ts.map +1 -0
  267. package/dist/lexicon/types/tools/ozone/report/queryReports.js +7 -0
  268. package/dist/lexicon/types/tools/ozone/report/queryReports.js.map +1 -0
  269. package/dist/lexicon/types/tools/ozone/report/reassignQueue.d.ts +31 -0
  270. package/dist/lexicon/types/tools/ozone/report/reassignQueue.d.ts.map +1 -0
  271. package/dist/lexicon/types/tools/ozone/report/reassignQueue.js +7 -0
  272. package/dist/lexicon/types/tools/ozone/report/reassignQueue.js.map +1 -0
  273. package/dist/lexicon/types/tools/ozone/report/refreshStats.d.ts +28 -0
  274. package/dist/lexicon/types/tools/ozone/report/refreshStats.d.ts.map +1 -0
  275. package/dist/lexicon/types/tools/ozone/report/refreshStats.js +7 -0
  276. package/dist/lexicon/types/tools/ozone/report/refreshStats.js.map +1 -0
  277. package/dist/lexicon/types/tools/ozone/report/unassignModerator.d.ts +25 -0
  278. package/dist/lexicon/types/tools/ozone/report/unassignModerator.d.ts.map +1 -0
  279. package/dist/lexicon/types/tools/ozone/report/unassignModerator.js +7 -0
  280. package/dist/lexicon/types/tools/ozone/report/unassignModerator.js.map +1 -0
  281. package/dist/mod-service/index.d.ts +3 -1
  282. package/dist/mod-service/index.d.ts.map +1 -1
  283. package/dist/mod-service/index.js +39 -2
  284. package/dist/mod-service/index.js.map +1 -1
  285. package/dist/mod-service/report.d.ts +64 -0
  286. package/dist/mod-service/report.d.ts.map +1 -0
  287. package/dist/mod-service/report.js +282 -0
  288. package/dist/mod-service/report.js.map +1 -0
  289. package/dist/mod-service/status.d.ts +20 -0
  290. package/dist/mod-service/status.d.ts.map +1 -1
  291. package/dist/queue/service.d.ts +86 -0
  292. package/dist/queue/service.d.ts.map +1 -0
  293. package/dist/queue/service.js +430 -0
  294. package/dist/queue/service.js.map +1 -0
  295. package/dist/report/activity.d.ts +77 -0
  296. package/dist/report/activity.d.ts.map +1 -0
  297. package/dist/report/activity.js +141 -0
  298. package/dist/report/activity.js.map +1 -0
  299. package/dist/report/handle-report-update.d.ts +47 -0
  300. package/dist/report/handle-report-update.d.ts.map +1 -0
  301. package/dist/report/handle-report-update.js +178 -0
  302. package/dist/report/handle-report-update.js.map +1 -0
  303. package/dist/report/reassign.d.ts +10 -0
  304. package/dist/report/reassign.d.ts.map +1 -0
  305. package/dist/report/reassign.js +75 -0
  306. package/dist/report/reassign.js.map +1 -0
  307. package/dist/report/stats.d.ts +105 -0
  308. package/dist/report/stats.d.ts.map +1 -0
  309. package/dist/report/stats.js +619 -0
  310. package/dist/report/stats.js.map +1 -0
  311. package/dist/report/views.d.ts +111 -0
  312. package/dist/report/views.d.ts.map +1 -0
  313. package/dist/report/views.js +156 -0
  314. package/dist/report/views.js.map +1 -0
  315. package/dist/team/index.d.ts +1 -0
  316. package/dist/team/index.d.ts.map +1 -1
  317. package/dist/team/index.js +11 -0
  318. package/dist/team/index.js.map +1 -1
  319. package/package.json +3 -3
  320. package/src/api/index.ts +40 -0
  321. package/src/api/moderation/emitEvent.ts +38 -0
  322. package/src/api/queue/assignModerator.ts +31 -0
  323. package/src/api/queue/createQueue.ts +62 -0
  324. package/src/api/queue/deleteQueue.ts +56 -0
  325. package/src/api/queue/getAssignments.ts +19 -0
  326. package/src/api/queue/listQueues.ts +39 -0
  327. package/src/api/queue/routeReports.ts +44 -0
  328. package/src/api/queue/unassignModerator.ts +26 -0
  329. package/src/api/queue/updateQueue.ts +54 -0
  330. package/src/api/report/assignModerator.ts +36 -0
  331. package/src/api/report/createActivity.ts +57 -0
  332. package/src/api/report/getAssignments.ts +20 -0
  333. package/src/api/report/getHistoricalStats.ts +41 -0
  334. package/src/api/report/getLatestReport.ts +44 -0
  335. package/src/api/report/getLiveStats.ts +26 -0
  336. package/src/api/report/getReport.ts +55 -0
  337. package/src/api/report/listActivities.ts +34 -0
  338. package/src/api/report/queryReports.ts +44 -0
  339. package/src/api/report/reassignQueue.ts +68 -0
  340. package/src/api/report/refreshStats.ts +27 -0
  341. package/src/api/report/unassignModerator.ts +21 -0
  342. package/src/api/util.ts +12 -0
  343. package/src/assignment/index.ts +731 -0
  344. package/src/config/config.ts +27 -0
  345. package/src/config/env.ts +8 -0
  346. package/src/context.ts +31 -0
  347. package/src/daemon/context.ts +34 -0
  348. package/src/daemon/job-cursor.ts +33 -0
  349. package/src/daemon/queue-router.ts +101 -0
  350. package/src/daemon/stats-computer.ts +101 -0
  351. package/src/daemon/strike-expiry-processor.ts +4 -20
  352. package/src/db/migrations/20260219T164523000Z-create-report-table.ts +155 -0
  353. package/src/db/migrations/20260219T165302248Z-moderator-assignment.ts +42 -0
  354. package/src/db/migrations/20260225T000000000Z-add-report-queue-table.ts +41 -0
  355. package/src/db/migrations/20260313T000000000Z-add-report-activity-table.ts +48 -0
  356. package/src/db/migrations/20260318T152058935Z-add-report-stat.ts +35 -0
  357. package/src/db/migrations/index.ts +5 -0
  358. package/src/db/pagination.ts +85 -0
  359. package/src/db/schema/index.ts +10 -0
  360. package/src/db/schema/moderator_assignment.ts +16 -0
  361. package/src/db/schema/report.ts +27 -0
  362. package/src/db/schema/report_activity.ts +22 -0
  363. package/src/db/schema/report_queue.ts +21 -0
  364. package/src/db/schema/report_stat.ts +27 -0
  365. package/src/lexicon/index.ts +280 -0
  366. package/src/lexicon/lexicons.ts +1910 -160
  367. package/src/lexicon/types/app/bsky/embed/external.ts +2 -0
  368. package/src/lexicon/types/tools/ozone/moderation/emitEvent.ts +24 -0
  369. package/src/lexicon/types/tools/ozone/queue/assignModerator.ts +46 -0
  370. package/src/lexicon/types/tools/ozone/queue/createQueue.ts +54 -0
  371. package/src/lexicon/types/tools/ozone/queue/defs.ts +99 -0
  372. package/src/lexicon/types/tools/ozone/queue/deleteQueue.ts +48 -0
  373. package/src/lexicon/types/tools/ozone/queue/getAssignments.ts +48 -0
  374. package/src/lexicon/types/tools/ozone/queue/listQueues.ts +50 -0
  375. package/src/lexicon/types/tools/ozone/queue/routeReports.ts +50 -0
  376. package/src/lexicon/types/tools/ozone/queue/unassignModerator.ts +37 -0
  377. package/src/lexicon/types/tools/ozone/queue/updateQueue.ts +51 -0
  378. package/src/lexicon/types/tools/ozone/report/assignModerator.ts +50 -0
  379. package/src/lexicon/types/tools/ozone/report/createActivity.ts +60 -0
  380. package/src/lexicon/types/tools/ozone/report/defs.ts +327 -0
  381. package/src/lexicon/types/tools/ozone/report/getAssignments.ts +48 -0
  382. package/src/lexicon/types/tools/ozone/report/getHistoricalStats.ts +54 -0
  383. package/src/lexicon/types/tools/ozone/report/getLatestReport.ts +39 -0
  384. package/src/lexicon/types/tools/ozone/report/getLiveStats.ts +45 -0
  385. package/src/lexicon/types/tools/ozone/report/getReport.ts +38 -0
  386. package/src/lexicon/types/tools/ozone/report/listActivities.ts +44 -0
  387. package/src/lexicon/types/tools/ozone/report/queryReports.ts +72 -0
  388. package/src/lexicon/types/tools/ozone/report/reassignQueue.ts +55 -0
  389. package/src/lexicon/types/tools/ozone/report/refreshStats.ts +46 -0
  390. package/src/lexicon/types/tools/ozone/report/unassignModerator.ts +44 -0
  391. package/src/mod-service/index.ts +45 -3
  392. package/src/mod-service/report.ts +408 -0
  393. package/src/queue/service.ts +599 -0
  394. package/src/report/activity.ts +234 -0
  395. package/src/report/handle-report-update.ts +209 -0
  396. package/src/report/reassign.ts +109 -0
  397. package/src/report/stats.ts +850 -0
  398. package/src/report/views.ts +241 -0
  399. package/src/team/index.ts +11 -0
  400. package/tests/get-report.test.ts +136 -0
  401. package/tests/query-reports.test.ts +608 -0
  402. package/tests/queue-assignment.test.ts +428 -0
  403. package/tests/queue-router.test.ts +306 -0
  404. package/tests/queues.test.ts +690 -0
  405. package/tests/report-action.test.ts +308 -0
  406. package/tests/report-activity.test.ts +567 -0
  407. package/tests/report-assignment.test.ts +517 -0
  408. package/tests/report-reassign-queue.test.ts +340 -0
  409. package/tests/report-routing.test.ts +245 -0
  410. package/tests/report-stats.test.ts +545 -0
  411. package/tsconfig.build.tsbuildinfo +1 -1
@@ -90,6 +90,15 @@ export const envToCfg = (env: OzoneEnvironment): OzoneConfig => {
90
90
  }
91
91
  : null
92
92
 
93
+ const assignmentsCfg: OzoneConfig['assignments'] = {
94
+ queueDurationMs: env.assignmentQueueDurationMs ?? 5 * MINUTE,
95
+ reportDurationMs: env.assignmentReportDurationMs ?? 5 * MINUTE,
96
+ }
97
+
98
+ const statsCfg: OzoneConfig['stats'] = {
99
+ computerIntervalMinutes: env.statsComputerIntervalMinutes ?? 15,
100
+ }
101
+
93
102
  return {
94
103
  service: serviceCfg,
95
104
  db: dbCfg,
@@ -101,6 +110,8 @@ export const envToCfg = (env: OzoneEnvironment): OzoneConfig => {
101
110
  blobDivert: blobDivertServiceCfg,
102
111
  access: accessCfg,
103
112
  verifier: verifierCfg,
113
+ assignments: assignmentsCfg,
114
+ stats: statsCfg,
104
115
  jetstreamUrl: env.jetstreamUrl,
105
116
  }
106
117
  }
@@ -115,10 +126,21 @@ export type OzoneConfig = {
115
126
  identity: IdentityConfig
116
127
  blobDivert: BlobDivertConfig | null
117
128
  access: AccessConfig
129
+ assignments: AssignmentsConfig
130
+ stats: StatsConfig
118
131
  jetstreamUrl?: string
119
132
  verifier: VerifierConfig | null
120
133
  }
121
134
 
135
+ export type StatsConfig = {
136
+ /**
137
+ * Minutes between stats computer cycles.
138
+ * Defaults to 15. Minimum is 1.
139
+ * Set to -1 to disable the stats computer.
140
+ */
141
+ computerIntervalMinutes: number
142
+ }
143
+
122
144
  export type ServiceConfig = {
123
145
  port: number
124
146
  publicUrl: string
@@ -182,3 +204,8 @@ export type VerifierConfig = {
182
204
  jetstreamUrl?: string
183
205
  issuersToIndex?: string[]
184
206
  }
207
+
208
+ export type AssignmentsConfig = {
209
+ queueDurationMs: number
210
+ reportDurationMs: number
211
+ }
package/src/config/env.ts CHANGED
@@ -43,6 +43,11 @@ export const readEnv = (): OzoneEnvironment => {
43
43
  verifierPassword: envStr('OZONE_VERIFIER_PASSWORD'),
44
44
  verifierIssuersToIndex: envList('OZONE_VERIFIER_ISSUERS_TO_INDEX'),
45
45
  jetstreamUrl: envStr('OZONE_JETSTREAM_URL'),
46
+ assignmentQueueDurationMs: envInt('OZONE_ASSIGNMENT_QUEUE_DURATION_MS'),
47
+ assignmentReportDurationMs: envInt('OZONE_ASSIGNMENT_REPORT_DURATION_MS'),
48
+ statsComputerIntervalMinutes: envInt(
49
+ 'OZONE_STATS_COMPUTER_INTERVAL_MINUTES',
50
+ ),
46
51
  }
47
52
  }
48
53
 
@@ -84,4 +89,7 @@ export type OzoneEnvironment = {
84
89
  verifierPassword?: string
85
90
  verifierIssuersToIndex?: string[]
86
91
  jetstreamUrl?: string
92
+ assignmentQueueDurationMs?: number
93
+ assignmentReportDurationMs?: number
94
+ statsComputerIntervalMinutes?: number
87
95
  }
package/src/context.ts CHANGED
@@ -5,6 +5,7 @@ import { AtpAgent } from '@atproto/api'
5
5
  import { Keypair, Secp256k1Keypair } from '@atproto/crypto'
6
6
  import { DidCache, IdResolver, MemoryCache } from '@atproto/identity'
7
7
  import { createServiceAuthHeaders } from '@atproto/xrpc-server'
8
+ import { AssignmentService } from './assignment'
8
9
  import { AuthVerifier } from './auth-verifier'
9
10
  import { BackgroundQueue } from './background'
10
11
  import {
@@ -22,6 +23,8 @@ import {
22
23
  ModerationServiceProfileCreator,
23
24
  } from './mod-service/profile'
24
25
  import { StrikeService, StrikeServiceCreator } from './mod-service/strike'
26
+ import { QueueService, QueueServiceCreator } from './queue/service'
27
+ import { ReportStatsService, ReportStatsServiceCreator } from './report/stats'
25
28
  import {
26
29
  SafelinkRuleService,
27
30
  SafelinkRuleServiceCreator,
@@ -58,6 +61,8 @@ export type AppContextOptions = {
58
61
  communicationTemplateService: CommunicationTemplateServiceCreator
59
62
  safelinkRuleService: SafelinkRuleServiceCreator
60
63
  scheduledActionService: ScheduledActionServiceCreator
64
+ queueService: QueueServiceCreator
65
+ reportStatsService: ReportStatsServiceCreator
61
66
  setService: SetServiceCreator
62
67
  settingService: SettingServiceCreator
63
68
  strikeService: StrikeServiceCreator
@@ -73,6 +78,7 @@ export type AppContextOptions = {
73
78
  imgInvalidator?: ImageInvalidator
74
79
  backgroundQueue: BackgroundQueue
75
80
  sequencer: Sequencer
81
+ assignmentService: AssignmentService
76
82
  authVerifier: AuthVerifier
77
83
  verificationService: VerificationServiceCreator
78
84
  verificationIssuer: VerificationIssuerCreator
@@ -143,6 +149,8 @@ export class AppContext {
143
149
  cfg.appview.did,
144
150
  createAuthHeaders,
145
151
  )
152
+ const queueService = QueueService.creator()
153
+ const reportStatsService = ReportStatsService.creator()
146
154
  const setService = SetService.creator()
147
155
  const settingService = SettingService.creator()
148
156
  const strikeService = StrikeService.creator()
@@ -164,6 +172,14 @@ export class AppContext {
164
172
  strikeService,
165
173
  overrides?.imgInvalidator,
166
174
  )
175
+ const assignmentService = AssignmentService.creator(
176
+ {
177
+ queueDurationMs: cfg.assignments.queueDurationMs,
178
+ reportDurationMs: cfg.assignments.reportDurationMs,
179
+ },
180
+ queueService,
181
+ teamService,
182
+ )(db)
167
183
 
168
184
  const sequencer = new Sequencer(modService(db))
169
185
 
@@ -183,6 +199,8 @@ export class AppContext {
183
199
  safelinkRuleService,
184
200
  scheduledActionService,
185
201
  teamService,
202
+ queueService,
203
+ reportStatsService,
186
204
  setService,
187
205
  settingService,
188
206
  strikeService,
@@ -195,6 +213,7 @@ export class AppContext {
195
213
  idResolver,
196
214
  backgroundQueue,
197
215
  sequencer,
216
+ assignmentService,
198
217
  authVerifier,
199
218
  blobDiverter,
200
219
  verificationService,
@@ -245,6 +264,14 @@ export class AppContext {
245
264
  return this.opts.teamService
246
265
  }
247
266
 
267
+ get queueService(): QueueServiceCreator {
268
+ return this.opts.queueService
269
+ }
270
+
271
+ get reportStatsService(): ReportStatsServiceCreator {
272
+ return this.opts.reportStatsService
273
+ }
274
+
248
275
  get setService(): SetServiceCreator {
249
276
  return this.opts.setService
250
277
  }
@@ -309,6 +336,10 @@ export class AppContext {
309
336
  return this.opts.sequencer
310
337
  }
311
338
 
339
+ get assignmentService(): AssignmentService {
340
+ return this.opts.assignmentService
341
+ }
342
+
312
343
  get authVerifier(): AuthVerifier {
313
344
  return this.opts.authVerifier
314
345
  }
@@ -8,6 +8,8 @@ import { OzoneConfig, OzoneSecrets } from '../config'
8
8
  import { Database } from '../db'
9
9
  import { ModerationService } from '../mod-service'
10
10
  import { StrikeService } from '../mod-service/strike'
11
+ import { QueueService } from '../queue/service'
12
+ import { ReportStatsService } from '../report/stats'
11
13
  import { ScheduledActionService } from '../scheduled-action/service'
12
14
  import { SettingService } from '../setting/service'
13
15
  import { TeamService } from '../team'
@@ -15,7 +17,9 @@ import { getSigningKeyId } from '../util'
15
17
  import { EventPusher } from './event-pusher'
16
18
  import { EventReverser } from './event-reverser'
17
19
  import { MaterializedViewRefresher } from './materialized-view-refresher'
20
+ import { QueueRouter } from './queue-router'
18
21
  import { ScheduledActionProcessor } from './scheduled-action-processor'
22
+ import { StatsComputer } from './stats-computer'
19
23
  import { StrikeExpiryProcessor } from './strike-expiry-processor'
20
24
  import { TeamProfileSynchronizer } from './team-profile-synchronizer'
21
25
  import { VerificationListener } from './verification-listener'
@@ -31,7 +35,9 @@ export type DaemonContextOptions = {
31
35
  teamProfileSynchronizer: TeamProfileSynchronizer
32
36
  scheduledActionProcessor: ScheduledActionProcessor
33
37
  strikeExpiryProcessor: StrikeExpiryProcessor
38
+ queueRouter: QueueRouter
34
39
  verificationListener?: VerificationListener
40
+ statsComputer?: StatsComputer
35
41
  }
36
42
 
37
43
  export class DaemonContext {
@@ -111,6 +117,16 @@ export class DaemonContext {
111
117
 
112
118
  const strikeExpiryProcessor = new StrikeExpiryProcessor(db, strikeService)
113
119
 
120
+ const queueService = QueueService.creator()
121
+ const queueRouter = new QueueRouter(db, queueService)
122
+
123
+ const reportStatsService = ReportStatsService.creator()
124
+ const statsComputer = new StatsComputer(
125
+ db,
126
+ reportStatsService,
127
+ cfg.stats.computerIntervalMinutes,
128
+ )
129
+
114
130
  // Only spawn the listener if verifier config exists and a jetstream URL is provided
115
131
  const verificationListener =
116
132
  cfg.verifier && cfg.jetstreamUrl
@@ -132,7 +148,9 @@ export class DaemonContext {
132
148
  teamProfileSynchronizer,
133
149
  scheduledActionProcessor,
134
150
  strikeExpiryProcessor,
151
+ queueRouter,
135
152
  verificationListener,
153
+ statsComputer,
136
154
  ...(overrides ?? {}),
137
155
  })
138
156
  }
@@ -173,10 +191,18 @@ export class DaemonContext {
173
191
  return this.opts.strikeExpiryProcessor
174
192
  }
175
193
 
194
+ get queueRouter(): QueueRouter {
195
+ return this.opts.queueRouter
196
+ }
197
+
176
198
  get verificationListener(): VerificationListener | undefined {
177
199
  return this.opts.verificationListener
178
200
  }
179
201
 
202
+ get statsComputer(): StatsComputer | undefined {
203
+ return this.opts.statsComputer
204
+ }
205
+
180
206
  async start() {
181
207
  this.eventPusher.start()
182
208
  this.eventReverser.start()
@@ -184,12 +210,18 @@ export class DaemonContext {
184
210
  this.teamProfileSynchronizer.start()
185
211
  this.scheduledActionProcessor.start()
186
212
  this.strikeExpiryProcessor.start()
213
+ this.queueRouter.start()
187
214
  this.verificationListener?.start()
215
+ this.statsComputer?.start()
188
216
  }
189
217
 
190
218
  async processAll() {
191
219
  // Sequential because the materialized view values depend on the events.
192
220
  await this.eventPusher.processAll()
221
+ // Drain pending modEventReport rows into the report table so test code
222
+ // that calls processAll() after creating reports sees the report rows
223
+ // immediately (in production this happens via the 1-min poll).
224
+ await this.queueRouter.routeReports()
193
225
  await this.materializedViewRefresher.run()
194
226
  await this.teamProfileSynchronizer.run()
195
227
  }
@@ -203,7 +235,9 @@ export class DaemonContext {
203
235
  this.teamProfileSynchronizer.destroy(),
204
236
  this.scheduledActionProcessor.destroy(),
205
237
  this.strikeExpiryProcessor.destroy(),
238
+ this.queueRouter.destroy(),
206
239
  this.verificationListener?.stop(),
240
+ this.statsComputer?.destroy(),
207
241
  ])
208
242
  } finally {
209
243
  await this.backgroundQueue.destroy()
@@ -0,0 +1,33 @@
1
+ import { Database } from '../db'
2
+
3
+ export async function initJobCursor(db: Database, job: string): Promise<void> {
4
+ await db.db
5
+ .insertInto('job_cursor')
6
+ .values({ job, cursor: null })
7
+ .onConflict((oc) => oc.doNothing())
8
+ .execute()
9
+ }
10
+
11
+ export async function getJobCursor(
12
+ db: Database,
13
+ job: string,
14
+ ): Promise<string | null> {
15
+ const entry = await db.db
16
+ .selectFrom('job_cursor')
17
+ .select('cursor')
18
+ .where('job', '=', job)
19
+ .executeTakeFirst()
20
+ return entry?.cursor ?? null
21
+ }
22
+
23
+ export async function updateJobCursor(
24
+ db: Database,
25
+ job: string,
26
+ cursor: string,
27
+ ): Promise<void> {
28
+ await db.db
29
+ .updateTable('job_cursor')
30
+ .set({ cursor })
31
+ .where('job', '=', job)
32
+ .execute()
33
+ }
@@ -0,0 +1,101 @@
1
+ import { MINUTE } from '@atproto/common'
2
+ import { Database } from '../db'
3
+ import { dbLogger } from '../logger'
4
+ import { QueueServiceCreator } from '../queue/service'
5
+ import { initJobCursor } from './job-cursor'
6
+
7
+ const JOB_NAME = 'queue_router'
8
+ const BATCH_SIZE = 100
9
+
10
+ export class QueueRouter {
11
+ destroyed = false
12
+ processingPromise: Promise<void> = Promise.resolve()
13
+ timer?: NodeJS.Timeout
14
+
15
+ constructor(
16
+ private db: Database,
17
+ private queueServiceCreator: QueueServiceCreator,
18
+ ) {}
19
+
20
+ start() {
21
+ this.initializeCursor().then(() => this.poll())
22
+ }
23
+
24
+ poll() {
25
+ if (this.destroyed) return
26
+ this.processingPromise = this.routeReports()
27
+ .catch((err) => dbLogger.error({ err }, 'queue routing errored'))
28
+ .finally(() => {
29
+ this.timer = setTimeout(() => this.poll(), getInterval())
30
+ })
31
+ }
32
+
33
+ async destroy() {
34
+ this.destroyed = true
35
+ if (this.timer) {
36
+ clearTimeout(this.timer)
37
+ this.timer = undefined
38
+ }
39
+ await this.processingPromise
40
+ }
41
+
42
+ async initializeCursor() {
43
+ await initJobCursor(this.db, JOB_NAME)
44
+ }
45
+
46
+ async getCursor(): Promise<number | null> {
47
+ const row = await this.db.db
48
+ .selectFrom('job_cursor')
49
+ .select('cursor')
50
+ .where('job', '=', JOB_NAME)
51
+ .executeTakeFirst()
52
+ return row?.cursor ? parseInt(row.cursor, 10) : null
53
+ }
54
+
55
+ async routeReports() {
56
+ await this.db.transaction(async (txn) => {
57
+ // Acquire row lock on the job_cursor row. A second daemon instance
58
+ // hitting this same query blocks here until the first transaction
59
+ // commits, then reads the now-advanced cursor and processes the next
60
+ // range. The lock is held for the whole batch (~50–200ms).
61
+ const row = await txn.db
62
+ .selectFrom('job_cursor')
63
+ .selectAll()
64
+ .where('job', '=', JOB_NAME)
65
+ .forUpdate()
66
+ .executeTakeFirst()
67
+ if (!row) return
68
+ const cursor = row.cursor ? parseInt(row.cursor, 10) : null
69
+
70
+ const queueService = this.queueServiceCreator(txn)
71
+ const result = await queueService.insertReportsFromEvents({
72
+ cursor,
73
+ limit: BATCH_SIZE,
74
+ })
75
+
76
+ if (result.processed === 0) {
77
+ dbLogger.info('no new report events to route')
78
+ return
79
+ }
80
+
81
+ await txn.db
82
+ .updateTable('job_cursor')
83
+ .set({ cursor: String(result.maxEventId) })
84
+ .where('job', '=', JOB_NAME)
85
+ .execute()
86
+
87
+ dbLogger.info(
88
+ {
89
+ processed: result.processed,
90
+ assigned: result.assigned,
91
+ unmatched: result.unmatched,
92
+ maxEventId: result.maxEventId,
93
+ },
94
+ 'queue routing completed',
95
+ )
96
+ })
97
+ }
98
+ }
99
+
100
+ // Poll every 1 minute
101
+ const getInterval = (): number => 1 * MINUTE
@@ -0,0 +1,101 @@
1
+ import { sql } from 'kysely'
2
+ import { MINUTE } from '@atproto/common'
3
+ import { Database } from '../db'
4
+ import { dbLogger } from '../logger'
5
+ import { ReportStatsServiceCreator } from '../report/stats'
6
+
7
+ // Stable lock ID for pg_try_advisory_lock across all instances
8
+ const ADVISORY_LOCK_ID = 7_239_401
9
+
10
+ /**
11
+ * Background daemon that materializes report statistics on an interval (default is 15 minutes).
12
+ *
13
+ * Each cycle computes calendar-day snapshots: today's stats are recomputed (in-progress day),
14
+ * and yesterday's snapshot is finalized if it wasn't already. Historical snapshots (completed
15
+ * days) are write-once and never recomputed unless explicitly refreshed via the API.
16
+ *
17
+ * Query profile per cycle (assuming ~10K reports/day, 10 queues, 20 moderators, 9 type groups):
18
+ * - 7 batched GROUP BY queries against the report table for today's date window
19
+ * (+ 7 more for yesterday if finalization is needed).
20
+ * Day-window queries scan ~10K rows. Pending-count queries use partial indexes
21
+ * (WHERE status != 'closed') so only scan open reports, not the full table.
22
+ * Expected: ~10-50ms per query, ~100-350ms total report-table time.
23
+ * - ~40 lightweight reads against report_stat for freshness checks (small indexed table).
24
+ * - ~40 lightweight writes to report_stat for upserts.
25
+ *
26
+ * Locking: Uses pg_try_advisory_lock to ensure only one instance materializes at a time
27
+ * when running multiple containers. Advisory locks are cooperative, session-level locks —
28
+ * they do NOT block any table reads, writes, row locks, or transactions from other sessions.
29
+ * Normal application queries (report creation, moderation actions, API reads) are completely
30
+ * unaffected. If another instance already holds the lock, this instance skips the cycle
31
+ * immediately without blocking.
32
+ */
33
+ export class StatsComputer {
34
+ destroyed = false
35
+ processingPromise: Promise<void> = Promise.resolve()
36
+ timer?: NodeJS.Timeout
37
+
38
+ constructor(
39
+ private db: Database,
40
+ private reportStatsServiceCreator: ReportStatsServiceCreator,
41
+ /**
42
+ * Minutes between stats computer cycles.
43
+ * Defaults to 15. Minimum is 1.
44
+ * Set to -1 to disable the stats computer.
45
+ */
46
+ private intervalMinutes: number,
47
+ ) {}
48
+
49
+ get disabled() {
50
+ return this.intervalMinutes < 1
51
+ }
52
+
53
+ start() {
54
+ this.poll()
55
+ }
56
+
57
+ poll() {
58
+ if (this.destroyed || this.disabled) return
59
+ this.processingPromise = this.materializeStats()
60
+ .catch((err) => dbLogger.error({ err }, 'stats materialization errored'))
61
+ .finally(() => {
62
+ this.timer = setTimeout(
63
+ () => this.poll(),
64
+ this.intervalMinutes * MINUTE,
65
+ )
66
+ })
67
+ }
68
+
69
+ private async materializeStats() {
70
+ const lockResult = await sql<{
71
+ locked: boolean
72
+ }>`SELECT pg_try_advisory_lock(${ADVISORY_LOCK_ID}) as locked`.execute(
73
+ this.db.db,
74
+ )
75
+ const acquired = lockResult.rows[0]?.locked === true
76
+ if (!acquired) {
77
+ dbLogger.info(
78
+ 'stats materialization skipped, another instance holds lock',
79
+ )
80
+ return
81
+ }
82
+
83
+ try {
84
+ const statsService = this.reportStatsServiceCreator(this.db)
85
+ await statsService.materializeAll()
86
+ } finally {
87
+ await sql`SELECT pg_advisory_unlock(${ADVISORY_LOCK_ID})`.execute(
88
+ this.db.db,
89
+ )
90
+ }
91
+ }
92
+
93
+ async destroy() {
94
+ this.destroyed = true
95
+ if (this.timer) {
96
+ clearTimeout(this.timer)
97
+ this.timer = undefined
98
+ }
99
+ await this.processingPromise
100
+ }
101
+ }
@@ -2,6 +2,7 @@ import { HOUR } from '@atproto/common'
2
2
  import { Database } from '../db'
3
3
  import { dbLogger } from '../logger'
4
4
  import { StrikeServiceCreator } from '../mod-service/strike'
5
+ import { getJobCursor, initJobCursor, updateJobCursor } from './job-cursor'
5
6
 
6
7
  const JOB_NAME = 'strike_expiry'
7
8
 
@@ -40,32 +41,15 @@ export class StrikeExpiryProcessor {
40
41
  }
41
42
 
42
43
  async initializeCursor() {
43
- await this.db.db
44
- .insertInto('job_cursor')
45
- .values({
46
- job: JOB_NAME,
47
- cursor: null,
48
- })
49
- .onConflict((oc) => oc.doNothing())
50
- .execute()
44
+ await initJobCursor(this.db, JOB_NAME)
51
45
  }
52
46
 
53
47
  async getCursor(): Promise<string | null> {
54
- const entry = await this.db.db
55
- .selectFrom('job_cursor')
56
- .select('cursor')
57
- .where('job', '=', JOB_NAME)
58
- .executeTakeFirst()
59
-
60
- return entry?.cursor || null
48
+ return getJobCursor(this.db, JOB_NAME)
61
49
  }
62
50
 
63
51
  async updateCursor(cursor: string): Promise<void> {
64
- await this.db.db
65
- .updateTable('job_cursor')
66
- .set({ cursor })
67
- .where('job', '=', JOB_NAME)
68
- .execute()
52
+ await updateJobCursor(this.db, JOB_NAME, cursor)
69
53
  }
70
54
 
71
55
  async processExpiredStrikes() {