@atproto/ozone 0.2.9 → 0.2.11
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.
- package/CHANGELOG.md +26 -0
- package/package.json +25 -21
- package/bin/migration-create.ts +0 -38
- package/jest.config.cjs +0 -22
- package/src/api/chat/getActorMetadata.ts +0 -23
- package/src/api/chat/getConvo.ts +0 -23
- package/src/api/chat/getConvoMembers.ts +0 -23
- package/src/api/chat/getConvos.ts +0 -23
- package/src/api/chat/getMessageContext.ts +0 -42
- package/src/api/chat/index.ts +0 -16
- package/src/api/communication/createTemplate.ts +0 -51
- package/src/api/communication/deleteTemplate.ts +0 -23
- package/src/api/communication/listTemplates.ts +0 -31
- package/src/api/communication/updateTemplate.ts +0 -51
- package/src/api/health.ts +0 -27
- package/src/api/index.ts +0 -146
- package/src/api/label/fetchLabels.ts +0 -32
- package/src/api/label/queryLabels.ts +0 -57
- package/src/api/label/subscribeLabels.ts +0 -25
- package/src/api/moderation/cancelScheduledActions.ts +0 -72
- package/src/api/moderation/emitEvent.ts +0 -475
- package/src/api/moderation/getAccountTimeline.ts +0 -160
- package/src/api/moderation/getEvent.ts +0 -19
- package/src/api/moderation/getRecord.ts +0 -40
- package/src/api/moderation/getRecords.ts +0 -50
- package/src/api/moderation/getRepo.ts +0 -34
- package/src/api/moderation/getReporterStats.ts +0 -18
- package/src/api/moderation/getRepos.ts +0 -41
- package/src/api/moderation/getSubjects.ts +0 -101
- package/src/api/moderation/listScheduledActions.ts +0 -45
- package/src/api/moderation/queryEvents.ts +0 -72
- package/src/api/moderation/queryStatuses.ts +0 -23
- package/src/api/moderation/scheduleAction.ts +0 -129
- package/src/api/moderation/searchRepos.ts +0 -46
- package/src/api/moderation/util.ts +0 -96
- package/src/api/proxied.ts +0 -327
- package/src/api/queue/assignModerator.ts +0 -31
- package/src/api/queue/createQueue.ts +0 -62
- package/src/api/queue/deleteQueue.ts +0 -56
- package/src/api/queue/getAssignments.ts +0 -19
- package/src/api/queue/listQueues.ts +0 -39
- package/src/api/queue/routeReports.ts +0 -44
- package/src/api/queue/unassignModerator.ts +0 -26
- package/src/api/queue/updateQueue.ts +0 -54
- package/src/api/report/assignModerator.ts +0 -36
- package/src/api/report/createActivity.ts +0 -57
- package/src/api/report/createReport.ts +0 -93
- package/src/api/report/getAssignments.ts +0 -20
- package/src/api/report/getHistoricalStats.ts +0 -41
- package/src/api/report/getLatestReport.ts +0 -44
- package/src/api/report/getLiveStats.ts +0 -26
- package/src/api/report/getReport.ts +0 -55
- package/src/api/report/listActivities.ts +0 -37
- package/src/api/report/queryActivities.ts +0 -64
- package/src/api/report/queryReports.ts +0 -44
- package/src/api/report/reassignQueue.ts +0 -68
- package/src/api/report/refreshStats.ts +0 -27
- package/src/api/report/unassignModerator.ts +0 -21
- package/src/api/safelink/addRule.ts +0 -48
- package/src/api/safelink/queryEvents.ts +0 -32
- package/src/api/safelink/queryRules.ts +0 -58
- package/src/api/safelink/removeRule.ts +0 -42
- package/src/api/safelink/updateRule.ts +0 -48
- package/src/api/server/getConfig.ts +0 -35
- package/src/api/set/addValues.ts +0 -28
- package/src/api/set/deleteSet.ts +0 -34
- package/src/api/set/deleteValues.ts +0 -31
- package/src/api/set/getValues.ts +0 -42
- package/src/api/set/querySets.ts +0 -36
- package/src/api/set/upsertSet.ts +0 -38
- package/src/api/setting/listOptions.ts +0 -44
- package/src/api/setting/removeOptions.ts +0 -64
- package/src/api/setting/upsertOption.ts +0 -156
- package/src/api/team/addMember.ts +0 -51
- package/src/api/team/deleteMember.ts +0 -29
- package/src/api/team/listMembers.ts +0 -20
- package/src/api/team/updateMember.ts +0 -47
- package/src/api/util.ts +0 -265
- package/src/api/verification/grantVerifications.ts +0 -90
- package/src/api/verification/listVerifications.ts +0 -44
- package/src/api/verification/revokeVerifications.ts +0 -43
- package/src/api/well-known.ts +0 -46
- package/src/assignment/index.ts +0 -728
- package/src/auth-verifier.ts +0 -227
- package/src/background.ts +0 -183
- package/src/communication-service/template.ts +0 -110
- package/src/communication-service/util.ts +0 -8
- package/src/config/config.ts +0 -211
- package/src/config/env.ts +0 -95
- package/src/config/index.ts +0 -3
- package/src/config/secrets.ts +0 -17
- package/src/context.ts +0 -399
- package/src/daemon/blob-diverter.ts +0 -186
- package/src/daemon/context.ts +0 -247
- package/src/daemon/event-pusher.ts +0 -363
- package/src/daemon/event-reverser.ts +0 -128
- package/src/daemon/index.ts +0 -33
- package/src/daemon/job-cursor.ts +0 -33
- package/src/daemon/materialized-view-refresher.ts +0 -33
- package/src/daemon/queue-router.ts +0 -101
- package/src/daemon/scheduled-action-processor.ts +0 -304
- package/src/daemon/stats-computer.ts +0 -101
- package/src/daemon/strike-expiry-processor.ts +0 -95
- package/src/daemon/team-profile-synchronizer.ts +0 -15
- package/src/daemon/verification-listener.ts +0 -169
- package/src/db/index.ts +0 -203
- package/src/db/migrations/20231219T205730722Z-init.ts +0 -170
- package/src/db/migrations/20240116T085607200Z-communication-template.ts +0 -23
- package/src/db/migrations/20240201T051104136Z-mod-event-blobs.ts +0 -15
- package/src/db/migrations/20240208T213404429Z-add-tags-column-to-moderation-subject.ts +0 -31
- package/src/db/migrations/20240228T003647759Z-add-label-sigs.ts +0 -25
- package/src/db/migrations/20240408T192432676Z-mute-reporting.ts +0 -15
- package/src/db/migrations/20240506T225055595Z-message-subject.ts +0 -21
- package/src/db/migrations/20240521T211332580Z-member.ts +0 -17
- package/src/db/migrations/20240814T003647759Z-event-created-at-index.ts +0 -13
- package/src/db/migrations/20240903T205730722Z-add-template-lang.ts +0 -12
- package/src/db/migrations/20240904T205730722Z-add-subject-did-index.ts +0 -13
- package/src/db/migrations/20241001T205730722Z-subject-status-review-state-index.ts +0 -15
- package/src/db/migrations/20241008T205730722Z-sets.ts +0 -53
- package/src/db/migrations/20241018T205730722Z-setting.ts +0 -27
- package/src/db/migrations/20241026T205730722Z-add-hosting-status-to-subject-status.ts +0 -57
- package/src/db/migrations/20241220T144630860Z-stats-materialized-views.ts +0 -215
- package/src/db/migrations/20250204T003647759Z-add-subject-priority-score.ts +0 -22
- package/src/db/migrations/20250211T003647759Z-add-reporter-stats-index.ts +0 -38
- package/src/db/migrations/20250211T132135150Z-moderation-event-message-partial-idx.ts +0 -26
- package/src/db/migrations/20250221T132135150Z-member-details.ts +0 -14
- package/src/db/migrations/20250404T201720309Z-subject-status-sort-idxs.ts +0 -18
- package/src/db/migrations/20250415T201720309Z-verification.ts +0 -34
- package/src/db/migrations/20250417T201720309Z-firehose-cursor.ts +0 -16
- package/src/db/migrations/20250609T110704000Z-safelink.ts +0 -53
- package/src/db/migrations/20250618T180246000Z-add-mod-tool-to-moderation-event.ts +0 -18
- package/src/db/migrations/20250701T000000000Z-add-age-assurance-state.ts +0 -25
- package/src/db/migrations/20250715T000000000Z-add-mod-event-external-id.ts +0 -15
- package/src/db/migrations/20250718T150931000Z-update-appeal-reason-stats.ts +0 -310
- package/src/db/migrations/20250813T000000000Z-mod-tool-batch-id-index.ts +0 -14
- package/src/db/migrations/20250923T000000000Z-scheduled-actions.ts +0 -56
- package/src/db/migrations/20251008T120000000Z-add-strike-system.ts +0 -87
- package/src/db/migrations/20260210T154806448Z-mod-event-created-by-indexes.ts +0 -22
- package/src/db/migrations/20260219T164523000Z-create-report-table.ts +0 -155
- package/src/db/migrations/20260219T165302248Z-moderator-assignment.ts +0 -42
- package/src/db/migrations/20260225T000000000Z-add-report-queue-table.ts +0 -41
- package/src/db/migrations/20260313T000000000Z-add-report-activity-table.ts +0 -48
- package/src/db/migrations/20260318T152058935Z-add-report-stat.ts +0 -35
- package/src/db/migrations/20260428T000000000Z-add-expiring-tag-table.ts +0 -32
- package/src/db/migrations/20260513T202941104Z-add-subject-convo-id.ts +0 -114
- package/src/db/migrations/20260602T120000000Z-add-report-activity-created-index.ts +0 -17
- package/src/db/migrations/index.ts +0 -44
- package/src/db/migrations/provider.ts +0 -26
- package/src/db/pagination.ts +0 -335
- package/src/db/schema/account_events_stats.ts +0 -16
- package/src/db/schema/account_record_events_stats.ts +0 -15
- package/src/db/schema/account_record_status_stats.ts +0 -15
- package/src/db/schema/account_strike.ts +0 -13
- package/src/db/schema/blob_push_event.ts +0 -21
- package/src/db/schema/communication_template.ts +0 -19
- package/src/db/schema/expiring_tag.ts +0 -18
- package/src/db/schema/firehose_cursor.ts +0 -13
- package/src/db/schema/index.ts +0 -60
- package/src/db/schema/job_cursor.ts +0 -13
- package/src/db/schema/label.ts +0 -22
- package/src/db/schema/member.ts +0 -22
- package/src/db/schema/moderation_event.ts +0 -61
- package/src/db/schema/moderation_subject_status.ts +0 -52
- package/src/db/schema/moderator_assignment.ts +0 -16
- package/src/db/schema/ozone_set.ts +0 -24
- package/src/db/schema/record_events_stats.ts +0 -15
- package/src/db/schema/record_push_event.ts +0 -21
- package/src/db/schema/repo_push_event.ts +0 -19
- package/src/db/schema/report.ts +0 -28
- package/src/db/schema/report_activity.ts +0 -22
- package/src/db/schema/report_queue.ts +0 -21
- package/src/db/schema/report_stat.ts +0 -27
- package/src/db/schema/safelink.ts +0 -39
- package/src/db/schema/scheduled-action.ts +0 -25
- package/src/db/schema/setting.ts +0 -24
- package/src/db/schema/signing_key.ts +0 -10
- package/src/db/schema/verification.ts +0 -21
- package/src/db/types.ts +0 -24
- package/src/error.ts +0 -12
- package/src/image-invalidator.ts +0 -7
- package/src/index.ts +0 -154
- package/src/jetstream/service.ts +0 -107
- package/src/logger.ts +0 -29
- package/src/mod-service/expiring-tags.ts +0 -104
- package/src/mod-service/index.ts +0 -1842
- package/src/mod-service/profile.ts +0 -139
- package/src/mod-service/report.ts +0 -429
- package/src/mod-service/status.ts +0 -549
- package/src/mod-service/strike.ts +0 -96
- package/src/mod-service/subject.ts +0 -311
- package/src/mod-service/types.ts +0 -96
- package/src/mod-service/util.ts +0 -99
- package/src/mod-service/views.ts +0 -912
- package/src/queue/service.ts +0 -603
- package/src/report/activity.ts +0 -281
- package/src/report/handle-report-update.ts +0 -209
- package/src/report/reassign.ts +0 -109
- package/src/report/stats.ts +0 -852
- package/src/report/views.ts +0 -239
- package/src/safelink/service.ts +0 -304
- package/src/scheduled-action/service.ts +0 -281
- package/src/scheduled-action/types.ts +0 -17
- package/src/sequencer/index.ts +0 -2
- package/src/sequencer/outbox.ts +0 -123
- package/src/sequencer/sequencer.ts +0 -147
- package/src/set/service.ts +0 -230
- package/src/setting/constants.ts +0 -3
- package/src/setting/service.ts +0 -148
- package/src/setting/types.ts +0 -3
- package/src/setting/validators.ts +0 -333
- package/src/tag-service/content-tagger.ts +0 -30
- package/src/tag-service/embed-tagger.ts +0 -70
- package/src/tag-service/index.ts +0 -70
- package/src/tag-service/language-data.ts +0 -561
- package/src/tag-service/language-tagger.ts +0 -101
- package/src/tag-service/util.ts +0 -13
- package/src/team/index.ts +0 -296
- package/src/util.ts +0 -230
- package/src/verification/issuer.ts +0 -146
- package/src/verification/service.ts +0 -208
- package/src/verification/util.ts +0 -53
- package/test.env +0 -2
- package/tests/3p-labeler.test.ts +0 -288
- package/tests/__snapshots__/account-strikes.test.ts.snap +0 -159
- package/tests/__snapshots__/age-assurance.test.ts.snap +0 -66
- package/tests/__snapshots__/blob-divert.test.ts.snap +0 -219
- package/tests/__snapshots__/get-account-timeline.test.ts.snap +0 -36
- package/tests/__snapshots__/get-record.test.ts.snap +0 -271
- package/tests/__snapshots__/get-records.test.ts.snap +0 -175
- package/tests/__snapshots__/get-repo.test.ts.snap +0 -91
- package/tests/__snapshots__/get-repos.test.ts.snap +0 -127
- package/tests/__snapshots__/get-starter-pack.test.ts.snap +0 -535
- package/tests/__snapshots__/get-subjects.test.ts.snap +0 -529
- package/tests/__snapshots__/moderation-events.test.ts.snap +0 -347
- package/tests/__snapshots__/moderation-statuses.test.ts.snap +0 -276
- package/tests/__snapshots__/moderation.test.ts.snap +0 -85
- package/tests/__snapshots__/report-reason.test.ts.snap +0 -14
- package/tests/__snapshots__/safelink.test.ts.snap +0 -179
- package/tests/__snapshots__/scheduled-action.test.ts.snap +0 -61
- package/tests/__snapshots__/sets.test.ts.snap +0 -46
- package/tests/__snapshots__/settings.test.ts.snap +0 -52
- package/tests/__snapshots__/team.test.ts.snap +0 -374
- package/tests/__snapshots__/verification-listener.test.ts.snap +0 -152
- package/tests/__snapshots__/verification.test.ts.snap +0 -302
- package/tests/_util.ts +0 -242
- package/tests/account-strikes.test.ts +0 -184
- package/tests/ack-all-subjects-of-account.test.ts +0 -177
- package/tests/age-assurance.test.ts +0 -372
- package/tests/blob-divert.test.ts +0 -106
- package/tests/communication-templates.test.ts +0 -149
- package/tests/content-tagger.test.ts +0 -170
- package/tests/db.test.ts +0 -184
- package/tests/expiring-label.test.ts +0 -72
- package/tests/expiring-tags.test.ts +0 -232
- package/tests/get-account-timeline.test.ts +0 -85
- package/tests/get-config.test.ts +0 -55
- package/tests/get-lists.test.ts +0 -111
- package/tests/get-profiles.test.ts +0 -70
- package/tests/get-record.test.ts +0 -130
- package/tests/get-records.test.ts +0 -91
- package/tests/get-repo.test.ts +0 -171
- package/tests/get-report.test.ts +0 -136
- package/tests/get-reporter-stats.test.ts +0 -132
- package/tests/get-repos.test.ts +0 -91
- package/tests/get-starter-pack.test.ts +0 -115
- package/tests/get-subjects.test.ts +0 -81
- package/tests/mod-tool.test.ts +0 -268
- package/tests/moderation-appeals.test.ts +0 -260
- package/tests/moderation-events.test.ts +0 -756
- package/tests/moderation-status-tags.test.ts +0 -140
- package/tests/moderation-statuses.test.ts +0 -495
- package/tests/moderation.test.ts +0 -992
- package/tests/protected-tags.test.ts +0 -218
- package/tests/query-labels.test.ts +0 -238
- package/tests/query-reports.test.ts +0 -608
- package/tests/queue-assignment.test.ts +0 -428
- package/tests/queue-router.test.ts +0 -306
- package/tests/queues.test.ts +0 -690
- package/tests/record-and-account-events.test.ts +0 -197
- package/tests/repo-search.test.ts +0 -136
- package/tests/report-action.test.ts +0 -308
- package/tests/report-activity.test.ts +0 -711
- package/tests/report-assignment.test.ts +0 -517
- package/tests/report-muting.test.ts +0 -100
- package/tests/report-reason.test.ts +0 -154
- package/tests/report-reassign-queue.test.ts +0 -340
- package/tests/report-routing.test.ts +0 -245
- package/tests/report-stats.test.ts +0 -545
- package/tests/revoke-account-credentials.test.ts +0 -54
- package/tests/safelink.test.ts +0 -534
- package/tests/scheduled-action-processor.test.ts +0 -488
- package/tests/scheduled-action.test.ts +0 -334
- package/tests/sequencer.test.ts +0 -227
- package/tests/server.test.ts +0 -62
- package/tests/sets.test.ts +0 -246
- package/tests/settings.test.ts +0 -308
- package/tests/strike-expiry-processor.test.ts +0 -299
- package/tests/subject-priority-score.test.ts +0 -96
- package/tests/takedown.test.ts +0 -105
- package/tests/team.test.ts +0 -216
- package/tests/verification-listener.test.ts +0 -129
- package/tests/verification.test.ts +0 -186
- package/tsconfig.build.json +0 -9
- package/tsconfig.build.tsbuildinfo +0 -1
- package/tsconfig.json +0 -7
- package/tsconfig.tests.json +0 -8
|
@@ -1,101 +0,0 @@
|
|
|
1
|
-
import { sql } from 'kysely'
|
|
2
|
-
import { MINUTE } from '@atproto/common'
|
|
3
|
-
import { Database } from '../db/index.js'
|
|
4
|
-
import { dbLogger } from '../logger.js'
|
|
5
|
-
import { ReportStatsServiceCreator } from '../report/stats.js'
|
|
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
|
-
}
|
|
@@ -1,95 +0,0 @@
|
|
|
1
|
-
import { HOUR } from '@atproto/common'
|
|
2
|
-
import { Database } from '../db/index.js'
|
|
3
|
-
import { dbLogger } from '../logger.js'
|
|
4
|
-
import { StrikeServiceCreator } from '../mod-service/strike.js'
|
|
5
|
-
import { getJobCursor, initJobCursor, updateJobCursor } from './job-cursor.js'
|
|
6
|
-
|
|
7
|
-
const JOB_NAME = 'strike_expiry'
|
|
8
|
-
|
|
9
|
-
export class StrikeExpiryProcessor {
|
|
10
|
-
destroyed = false
|
|
11
|
-
processingPromise: Promise<void> = Promise.resolve()
|
|
12
|
-
timer?: NodeJS.Timeout
|
|
13
|
-
|
|
14
|
-
constructor(
|
|
15
|
-
private db: Database,
|
|
16
|
-
private strikeServiceCreator: StrikeServiceCreator,
|
|
17
|
-
) {}
|
|
18
|
-
|
|
19
|
-
start() {
|
|
20
|
-
this.initializeCursor().then(() => this.poll())
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
poll() {
|
|
24
|
-
if (this.destroyed) return
|
|
25
|
-
this.processingPromise = this.processExpiredStrikes()
|
|
26
|
-
.catch((err) =>
|
|
27
|
-
dbLogger.error({ err }, 'strike expiry processing errored'),
|
|
28
|
-
)
|
|
29
|
-
.finally(() => {
|
|
30
|
-
this.timer = setTimeout(() => this.poll(), getInterval())
|
|
31
|
-
})
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
async destroy() {
|
|
35
|
-
this.destroyed = true
|
|
36
|
-
if (this.timer) {
|
|
37
|
-
clearTimeout(this.timer)
|
|
38
|
-
this.timer = undefined
|
|
39
|
-
}
|
|
40
|
-
await this.processingPromise
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
async initializeCursor() {
|
|
44
|
-
await initJobCursor(this.db, JOB_NAME)
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
async getCursor(): Promise<string | null> {
|
|
48
|
-
return getJobCursor(this.db, JOB_NAME)
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
async updateCursor(cursor: string): Promise<void> {
|
|
52
|
-
await updateJobCursor(this.db, JOB_NAME, cursor)
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
async processExpiredStrikes() {
|
|
56
|
-
const now = new Date()
|
|
57
|
-
const strikeService = this.strikeServiceCreator(this.db)
|
|
58
|
-
const lastProcessedAt = await this.getCursor()
|
|
59
|
-
const affectedSubjects = await strikeService.getExpiredStrikeSubjects(
|
|
60
|
-
lastProcessedAt || undefined,
|
|
61
|
-
)
|
|
62
|
-
|
|
63
|
-
if (!affectedSubjects.length) {
|
|
64
|
-
dbLogger.info('no expired strikes to process')
|
|
65
|
-
await this.updateCursor(now.toISOString())
|
|
66
|
-
return
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
dbLogger.info(
|
|
70
|
-
{ count: affectedSubjects.length },
|
|
71
|
-
'processing subjects with expired strikes',
|
|
72
|
-
)
|
|
73
|
-
|
|
74
|
-
await Promise.all(
|
|
75
|
-
affectedSubjects.map(({ subjectDid }) => {
|
|
76
|
-
return strikeService.updateSubjectStrikeCount(subjectDid)
|
|
77
|
-
}),
|
|
78
|
-
)
|
|
79
|
-
|
|
80
|
-
await this.updateCursor(now.toISOString())
|
|
81
|
-
|
|
82
|
-
dbLogger.info(
|
|
83
|
-
{ processed: affectedSubjects.length },
|
|
84
|
-
'strike expiry processing completed',
|
|
85
|
-
)
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
const getInterval = (): number => {
|
|
90
|
-
// Run every hour, synchronized to the hour boundary
|
|
91
|
-
const now = Date.now()
|
|
92
|
-
const intervalMs = HOUR
|
|
93
|
-
const nextIteration = Math.ceil(now / intervalMs)
|
|
94
|
-
return nextIteration * intervalMs - now
|
|
95
|
-
}
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import { HOUR } from '@atproto/common'
|
|
2
|
-
import { BackgroundQueue, PeriodicBackgroundTask } from '../background.js'
|
|
3
|
-
import { TeamService } from '../team/index.js'
|
|
4
|
-
|
|
5
|
-
export class TeamProfileSynchronizer extends PeriodicBackgroundTask {
|
|
6
|
-
constructor(
|
|
7
|
-
backgroundQueue: BackgroundQueue,
|
|
8
|
-
teamService: TeamService,
|
|
9
|
-
interval = 24 * HOUR,
|
|
10
|
-
) {
|
|
11
|
-
super(backgroundQueue, interval, async () => {
|
|
12
|
-
await teamService.syncMemberProfiles()
|
|
13
|
-
})
|
|
14
|
-
}
|
|
15
|
-
}
|
|
@@ -1,169 +0,0 @@
|
|
|
1
|
-
import { lexicons } from '@atproto/api'
|
|
2
|
-
import { BackgroundQueue } from '../background.js'
|
|
3
|
-
import { Database } from '../db/index.js'
|
|
4
|
-
import { CommitCreateEvent, Jetstream } from '../jetstream/service.js'
|
|
5
|
-
import { verificationLogger } from '../logger.js'
|
|
6
|
-
import { VerificationService } from '../verification/service.js'
|
|
7
|
-
|
|
8
|
-
type VerificationRecord = {
|
|
9
|
-
subject: string
|
|
10
|
-
handle: string
|
|
11
|
-
displayName: string
|
|
12
|
-
createdAt: string
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export class VerificationListener {
|
|
16
|
-
destroyed = false
|
|
17
|
-
private cursor?: number
|
|
18
|
-
private jetstream: Jetstream | null = null
|
|
19
|
-
private collection = 'app.bsky.graph.verification'
|
|
20
|
-
public backgroundQueue = new BackgroundQueue(this.db, { concurrency: 1 })
|
|
21
|
-
private verificationService = VerificationService.creator()(this.db)
|
|
22
|
-
|
|
23
|
-
constructor(
|
|
24
|
-
private db: Database,
|
|
25
|
-
private jetstreamUrl: string,
|
|
26
|
-
private verifierIssuersToIndex?: string[],
|
|
27
|
-
) {}
|
|
28
|
-
|
|
29
|
-
// When the queue has capacity, this method returns true which means we can continue to handle events
|
|
30
|
-
// otherwise, it will close jetstream connection and wait for all previously queued events to be processed first
|
|
31
|
-
// and then start jetstream listener again before returning false. At that point, the previous listeners should
|
|
32
|
-
// have updates the cursor in db to the last processed event and the new listener will start from that cursor
|
|
33
|
-
async ensureCoolDown() {
|
|
34
|
-
const { waitingCount, runningCount } = this.backgroundQueue.getStats()
|
|
35
|
-
if (waitingCount > 50 || runningCount > 50) {
|
|
36
|
-
verificationLogger.warn(`Background queue is full, pausing listener`)
|
|
37
|
-
this.jetstream?.close()
|
|
38
|
-
await this.backgroundQueue.processAll()
|
|
39
|
-
await this.start()
|
|
40
|
-
return false
|
|
41
|
-
}
|
|
42
|
-
return true
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
handleNewVerification(
|
|
46
|
-
issuer: string,
|
|
47
|
-
uri: string,
|
|
48
|
-
cid: string,
|
|
49
|
-
record: VerificationRecord,
|
|
50
|
-
cursor: number,
|
|
51
|
-
) {
|
|
52
|
-
this.backgroundQueue.add(async () => {
|
|
53
|
-
try {
|
|
54
|
-
const { subject, handle, displayName, createdAt } = record
|
|
55
|
-
await this.verificationService.create([
|
|
56
|
-
{ uri, cid, issuer, subject, handle, displayName, createdAt },
|
|
57
|
-
])
|
|
58
|
-
await this.updateCursor(cursor)
|
|
59
|
-
} catch (err) {
|
|
60
|
-
verificationLogger.error(
|
|
61
|
-
err,
|
|
62
|
-
'Error handling verification create event',
|
|
63
|
-
)
|
|
64
|
-
}
|
|
65
|
-
})
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
handleDeletedVerification(uri: string, cursor: number) {
|
|
69
|
-
this.backgroundQueue.add(async () => {
|
|
70
|
-
try {
|
|
71
|
-
await this.verificationService.markRevoked({
|
|
72
|
-
uris: [uri],
|
|
73
|
-
})
|
|
74
|
-
await this.updateCursor(cursor)
|
|
75
|
-
} catch (err) {
|
|
76
|
-
verificationLogger.error(
|
|
77
|
-
err,
|
|
78
|
-
'Error handling verification delete event',
|
|
79
|
-
)
|
|
80
|
-
}
|
|
81
|
-
})
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
async getCursor() {
|
|
85
|
-
await this.verificationService.createFirehoseCursor()
|
|
86
|
-
const cursor = await this.verificationService.getFirehoseCursor()
|
|
87
|
-
if (cursor) {
|
|
88
|
-
this.cursor = cursor
|
|
89
|
-
}
|
|
90
|
-
return this.cursor
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
async updateCursor(cursor: number) {
|
|
94
|
-
// Assuming cursors are always incremental, if we have processed an event with higher value cursor, let's not update to a lower value
|
|
95
|
-
if (this.cursor && this.cursor >= cursor) {
|
|
96
|
-
return
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
// This will only update if the cursor is higher than the current one in db
|
|
100
|
-
const updatedCursor =
|
|
101
|
-
await this.verificationService.updateFirehoseCursor(cursor)
|
|
102
|
-
|
|
103
|
-
if (updatedCursor) {
|
|
104
|
-
this.cursor = updatedCursor
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
async start() {
|
|
109
|
-
await this.getCursor()
|
|
110
|
-
|
|
111
|
-
this.jetstream = new Jetstream({
|
|
112
|
-
endpoint: this.jetstreamUrl,
|
|
113
|
-
cursor: this.cursor || undefined,
|
|
114
|
-
wantedCollections: [this.collection],
|
|
115
|
-
wantedDids: this.verifierIssuersToIndex?.length
|
|
116
|
-
? this.verifierIssuersToIndex
|
|
117
|
-
: undefined,
|
|
118
|
-
})
|
|
119
|
-
|
|
120
|
-
await this.jetstream.start({
|
|
121
|
-
onCreate: {
|
|
122
|
-
[this.collection]: async (e: CommitCreateEvent<VerificationRecord>) => {
|
|
123
|
-
const recordValidity = lexicons.validate(
|
|
124
|
-
this.collection,
|
|
125
|
-
e.commit.record,
|
|
126
|
-
)
|
|
127
|
-
|
|
128
|
-
if (!recordValidity.success) {
|
|
129
|
-
verificationLogger.error(
|
|
130
|
-
recordValidity.error,
|
|
131
|
-
'Invalid verification record in the firehose',
|
|
132
|
-
)
|
|
133
|
-
return
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
const hasCapacity = await this.ensureCoolDown()
|
|
137
|
-
if (hasCapacity) {
|
|
138
|
-
const issuer = e.did
|
|
139
|
-
const { record, rkey, collection, cid } = e.commit
|
|
140
|
-
const uri = `at://${issuer}/${collection}/${rkey}`
|
|
141
|
-
this.handleNewVerification(issuer, uri, cid, record, e.time_us)
|
|
142
|
-
}
|
|
143
|
-
},
|
|
144
|
-
},
|
|
145
|
-
onDelete: {
|
|
146
|
-
[this.collection]: async (e) => {
|
|
147
|
-
const hasCapacity = await this.ensureCoolDown()
|
|
148
|
-
if (hasCapacity) {
|
|
149
|
-
this.handleDeletedVerification(
|
|
150
|
-
`at://${e.did}/${e.commit.collection}/${e.commit.rkey}`,
|
|
151
|
-
e.time_us,
|
|
152
|
-
)
|
|
153
|
-
}
|
|
154
|
-
},
|
|
155
|
-
},
|
|
156
|
-
})
|
|
157
|
-
}
|
|
158
|
-
|
|
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
|
-
}
|
|
168
|
-
}
|
|
169
|
-
}
|
package/src/db/index.ts
DELETED
|
@@ -1,203 +0,0 @@
|
|
|
1
|
-
import assert from 'node:assert'
|
|
2
|
-
import { EventEmitter } from 'node:events'
|
|
3
|
-
import {
|
|
4
|
-
Kysely,
|
|
5
|
-
KyselyPlugin,
|
|
6
|
-
PluginTransformQueryArgs,
|
|
7
|
-
PluginTransformResultArgs,
|
|
8
|
-
PostgresDialect,
|
|
9
|
-
QueryResult,
|
|
10
|
-
RootOperationNode,
|
|
11
|
-
UnknownRow,
|
|
12
|
-
} from 'kysely'
|
|
13
|
-
import { Migrator } from 'kysely/migration'
|
|
14
|
-
// eslint-disable-next-line import/default
|
|
15
|
-
import pg from 'pg'
|
|
16
|
-
// eslint-disable-next-line import/no-named-as-default-member
|
|
17
|
-
const { Pool: PgPool, types: pgTypes } = pg
|
|
18
|
-
type PgPool = InstanceType<typeof PgPool>
|
|
19
|
-
import type TypedEmitter from 'typed-emitter'
|
|
20
|
-
import { dbLogger } from '../logger.js'
|
|
21
|
-
import * as migrations from './migrations/index.js'
|
|
22
|
-
import { CtxMigrationProvider } from './migrations/provider.js'
|
|
23
|
-
import { DatabaseSchema, DatabaseSchemaType } from './schema/index.js'
|
|
24
|
-
import { PgOptions } from './types.js'
|
|
25
|
-
|
|
26
|
-
export class Database {
|
|
27
|
-
pool: PgPool
|
|
28
|
-
db: DatabaseSchema
|
|
29
|
-
migrator: Migrator
|
|
30
|
-
txEvt = new EventEmitter() as TxnEmitter
|
|
31
|
-
destroyed = false
|
|
32
|
-
isPrimary = false
|
|
33
|
-
|
|
34
|
-
constructor(
|
|
35
|
-
public opts: PgOptions,
|
|
36
|
-
instances?: { db: DatabaseSchema; pool: PgPool },
|
|
37
|
-
) {
|
|
38
|
-
// if instances are provided, use those
|
|
39
|
-
if (instances) {
|
|
40
|
-
this.db = instances.db
|
|
41
|
-
this.pool = instances.pool
|
|
42
|
-
} else {
|
|
43
|
-
// else create a pool & connect
|
|
44
|
-
const { schema, url } = opts
|
|
45
|
-
const pool =
|
|
46
|
-
opts.pool ??
|
|
47
|
-
new PgPool({
|
|
48
|
-
connectionString: url,
|
|
49
|
-
max: opts.poolSize,
|
|
50
|
-
maxUses: opts.poolMaxUses,
|
|
51
|
-
idleTimeoutMillis: opts.poolIdleTimeoutMs,
|
|
52
|
-
})
|
|
53
|
-
|
|
54
|
-
// Select count(*) and other pg bigints as js integer
|
|
55
|
-
pgTypes.setTypeParser(pgTypes.builtins.INT8, (n) => parseInt(n, 10))
|
|
56
|
-
|
|
57
|
-
// Setup schema usage, primarily for test parallelism (each test suite runs in its own pg schema)
|
|
58
|
-
if (schema && !/^[a-z_]+$/i.test(schema)) {
|
|
59
|
-
throw new Error(
|
|
60
|
-
`Postgres schema must only contain [A-Za-z_]: ${schema}`,
|
|
61
|
-
)
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
pool.on('error', onPoolError)
|
|
65
|
-
pool.on('connect', (client) => {
|
|
66
|
-
client.on('error', onClientError)
|
|
67
|
-
// Used for trigram indexes, e.g. on actor search
|
|
68
|
-
client.query('SET pg_trgm.word_similarity_threshold TO .4;')
|
|
69
|
-
if (schema) {
|
|
70
|
-
// Shared objects such as extensions will go in the public schema
|
|
71
|
-
client.query(`SET search_path TO "${schema}",public;`)
|
|
72
|
-
}
|
|
73
|
-
})
|
|
74
|
-
|
|
75
|
-
this.pool = pool
|
|
76
|
-
this.db = new Kysely<DatabaseSchemaType>({
|
|
77
|
-
dialect: new PostgresDialect({ pool }),
|
|
78
|
-
})
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
this.migrator = new Migrator({
|
|
82
|
-
db: this.db,
|
|
83
|
-
migrationTableSchema: opts.schema,
|
|
84
|
-
provider: new CtxMigrationProvider(migrations, 'pg'),
|
|
85
|
-
})
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
get schema(): string | undefined {
|
|
89
|
-
return this.opts.schema
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
get isTransaction() {
|
|
93
|
-
return this.db.isTransaction
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
assertTransaction() {
|
|
97
|
-
assert(this.isTransaction, 'Transaction required')
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
assertNotTransaction() {
|
|
101
|
-
assert(!this.isTransaction, 'Cannot be in a transaction')
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
async transaction<T>(fn: (db: Database) => Promise<T>): Promise<T> {
|
|
105
|
-
const leakyTxPlugin = new LeakyTxPlugin()
|
|
106
|
-
const { dbTxn, txRes } = await this.db
|
|
107
|
-
.withPlugin(leakyTxPlugin)
|
|
108
|
-
.transaction()
|
|
109
|
-
.execute(async (txn) => {
|
|
110
|
-
const dbTxn = new Database(this.opts, {
|
|
111
|
-
db: txn,
|
|
112
|
-
pool: this.pool,
|
|
113
|
-
})
|
|
114
|
-
const txRes = await fn(dbTxn)
|
|
115
|
-
.catch(async (err) => {
|
|
116
|
-
leakyTxPlugin.endTx()
|
|
117
|
-
// ensure that all in-flight queries are flushed & the connection is open
|
|
118
|
-
await dbTxn.db.getExecutor().provideConnection(noopAsync)
|
|
119
|
-
throw err
|
|
120
|
-
})
|
|
121
|
-
.finally(() => leakyTxPlugin.endTx())
|
|
122
|
-
return { dbTxn, txRes }
|
|
123
|
-
})
|
|
124
|
-
dbTxn?.txEvt.emit('commit')
|
|
125
|
-
return txRes
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
onCommit(fn: () => void) {
|
|
129
|
-
this.assertTransaction()
|
|
130
|
-
this.txEvt.once('commit', fn)
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
async close(): Promise<void> {
|
|
134
|
-
if (this.destroyed) return
|
|
135
|
-
await this.db.destroy()
|
|
136
|
-
this.destroyed = true
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
async migrateToOrThrow(migration: string) {
|
|
140
|
-
if (this.schema) {
|
|
141
|
-
await this.db.schema.createSchema(this.schema).ifNotExists().execute()
|
|
142
|
-
}
|
|
143
|
-
const { error, results } = await this.migrator.migrateTo(migration)
|
|
144
|
-
if (error) {
|
|
145
|
-
throw error
|
|
146
|
-
}
|
|
147
|
-
if (!results) {
|
|
148
|
-
throw new Error('An unknown failure occurred while migrating')
|
|
149
|
-
}
|
|
150
|
-
return results
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
async migrateToLatestOrThrow() {
|
|
154
|
-
if (this.schema) {
|
|
155
|
-
await this.db.schema.createSchema(this.schema).ifNotExists().execute()
|
|
156
|
-
}
|
|
157
|
-
const { error, results } = await this.migrator.migrateToLatest()
|
|
158
|
-
if (error) {
|
|
159
|
-
throw error
|
|
160
|
-
}
|
|
161
|
-
if (!results) {
|
|
162
|
-
throw new Error('An unknown failure occurred while migrating')
|
|
163
|
-
}
|
|
164
|
-
return results
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
export default Database
|
|
169
|
-
|
|
170
|
-
const onPoolError = (err: Error) => dbLogger.error({ err }, 'db pool error')
|
|
171
|
-
const onClientError = (err: Error) => dbLogger.error({ err }, 'db client error')
|
|
172
|
-
|
|
173
|
-
// utils
|
|
174
|
-
// -------
|
|
175
|
-
|
|
176
|
-
class LeakyTxPlugin implements KyselyPlugin {
|
|
177
|
-
private txOver = false
|
|
178
|
-
|
|
179
|
-
endTx() {
|
|
180
|
-
this.txOver = true
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
transformQuery(args: PluginTransformQueryArgs): RootOperationNode {
|
|
184
|
-
if (this.txOver) {
|
|
185
|
-
throw new Error('tx already failed')
|
|
186
|
-
}
|
|
187
|
-
return args.node
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
async transformResult(
|
|
191
|
-
args: PluginTransformResultArgs,
|
|
192
|
-
): Promise<QueryResult<UnknownRow>> {
|
|
193
|
-
return args.result
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
type TxnEmitter = TypedEmitter.default<TxnEvents>
|
|
198
|
-
|
|
199
|
-
type TxnEvents = {
|
|
200
|
-
commit: () => void
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
const noopAsync = async () => {}
|