@atproto/ozone 0.2.8 → 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 +33 -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,281 +0,0 @@
|
|
|
1
|
-
import { Selectable } from 'kysely'
|
|
2
|
-
import { InvalidRequestError } from '@atproto/xrpc-server'
|
|
3
|
-
import { ScheduledActionStatus, ScheduledActionType } from '../api/util.js'
|
|
4
|
-
import { Database } from '../db/index.js'
|
|
5
|
-
import { ScheduledAction } from '../db/schema/scheduled-action.js'
|
|
6
|
-
import { ScheduledActionView } from '../lexicon/types/tools/ozone/moderation/defs.js'
|
|
7
|
-
import { dbLogger } from '../logger.js'
|
|
8
|
-
import { SchedulingParams } from './types.js'
|
|
9
|
-
|
|
10
|
-
export type ScheduledActionServiceCreator = (
|
|
11
|
-
db: Database,
|
|
12
|
-
) => ScheduledActionService
|
|
13
|
-
|
|
14
|
-
export class ScheduledActionService {
|
|
15
|
-
constructor(public db: Database) {}
|
|
16
|
-
|
|
17
|
-
static creator() {
|
|
18
|
-
return (db: Database) => new ScheduledActionService(db)
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
formatScheduledAction(
|
|
22
|
-
action: Selectable<ScheduledAction>,
|
|
23
|
-
): ScheduledActionView {
|
|
24
|
-
return {
|
|
25
|
-
id: action.id,
|
|
26
|
-
action: action.action,
|
|
27
|
-
eventData: action.eventData as { [x: string]: unknown } | undefined,
|
|
28
|
-
did: action.did,
|
|
29
|
-
executeAt: action.executeAt
|
|
30
|
-
? new Date(action.executeAt).toISOString()
|
|
31
|
-
: undefined,
|
|
32
|
-
executeAfter: action.executeAfter
|
|
33
|
-
? new Date(action.executeAfter).toISOString()
|
|
34
|
-
: undefined,
|
|
35
|
-
executeUntil: action.executeUntil
|
|
36
|
-
? new Date(action.executeUntil).toISOString()
|
|
37
|
-
: undefined,
|
|
38
|
-
randomizeExecution: action.randomizeExecution,
|
|
39
|
-
createdBy: action.createdBy,
|
|
40
|
-
createdAt: new Date(action.createdAt).toISOString(),
|
|
41
|
-
updatedAt: new Date(action.updatedAt).toISOString(),
|
|
42
|
-
status: action.status,
|
|
43
|
-
lastExecutedAt: action.lastExecutedAt
|
|
44
|
-
? new Date(action.lastExecutedAt).toISOString()
|
|
45
|
-
: undefined,
|
|
46
|
-
lastFailureReason: action.lastFailureReason || undefined,
|
|
47
|
-
executionEventId: action.executionEventId || undefined,
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
async scheduleAction(
|
|
52
|
-
schedulingParams: SchedulingParams,
|
|
53
|
-
): Promise<Selectable<ScheduledAction>> {
|
|
54
|
-
const { action, eventData, did, createdBy } = schedulingParams
|
|
55
|
-
|
|
56
|
-
// Only allow one pending action at a time for a given subject and action type
|
|
57
|
-
const existingAction = await this.getPendingActionForSubject(did, action)
|
|
58
|
-
if (existingAction) {
|
|
59
|
-
throw new InvalidRequestError(
|
|
60
|
-
'A pending scheduled action already exists for this subject',
|
|
61
|
-
'ActionAlreadyExists',
|
|
62
|
-
)
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
// When a time-range for action is specified, ensure that the range is valid
|
|
66
|
-
if (
|
|
67
|
-
'executeAfter' in schedulingParams &&
|
|
68
|
-
schedulingParams.executeAfter &&
|
|
69
|
-
schedulingParams.executeUntil &&
|
|
70
|
-
schedulingParams.executeAfter >= schedulingParams.executeUntil
|
|
71
|
-
) {
|
|
72
|
-
throw new InvalidRequestError(
|
|
73
|
-
'executeAfter must be before executeUntil',
|
|
74
|
-
'InvalidScheduling',
|
|
75
|
-
)
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
const now = new Date().toISOString()
|
|
79
|
-
const randomizeExecution =
|
|
80
|
-
!('executeAt' in schedulingParams) && 'executeAfter' in schedulingParams
|
|
81
|
-
|
|
82
|
-
const scheduledAction = await this.db.db
|
|
83
|
-
.insertInto('scheduled_action')
|
|
84
|
-
.values({
|
|
85
|
-
action,
|
|
86
|
-
eventData: JSON.stringify(eventData),
|
|
87
|
-
did,
|
|
88
|
-
executeAt: randomizeExecution
|
|
89
|
-
? null
|
|
90
|
-
: schedulingParams.executeAt?.toISOString(),
|
|
91
|
-
executeAfter: randomizeExecution
|
|
92
|
-
? schedulingParams.executeAfter?.toISOString()
|
|
93
|
-
: null,
|
|
94
|
-
executeUntil: randomizeExecution
|
|
95
|
-
? schedulingParams.executeUntil?.toISOString()
|
|
96
|
-
: null,
|
|
97
|
-
randomizeExecution,
|
|
98
|
-
createdBy,
|
|
99
|
-
createdAt: now,
|
|
100
|
-
updatedAt: now,
|
|
101
|
-
status: 'pending',
|
|
102
|
-
})
|
|
103
|
-
.returningAll()
|
|
104
|
-
.executeTakeFirstOrThrow()
|
|
105
|
-
|
|
106
|
-
return scheduledAction
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
async getPendingActionForSubject(
|
|
110
|
-
did: string,
|
|
111
|
-
action: ScheduledActionType,
|
|
112
|
-
): Promise<Selectable<ScheduledAction> | null> {
|
|
113
|
-
const scheduledAction = await this.db.db
|
|
114
|
-
.selectFrom('scheduled_action')
|
|
115
|
-
.selectAll()
|
|
116
|
-
.where('did', '=', did)
|
|
117
|
-
.where('action', '=', action)
|
|
118
|
-
.where('status', '=', 'pending')
|
|
119
|
-
.executeTakeFirst()
|
|
120
|
-
|
|
121
|
-
return scheduledAction || null
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
async listScheduledActions({
|
|
125
|
-
cursor,
|
|
126
|
-
limit = 50,
|
|
127
|
-
startTime,
|
|
128
|
-
endTime,
|
|
129
|
-
subjects,
|
|
130
|
-
statuses = [],
|
|
131
|
-
direction = 'desc',
|
|
132
|
-
}: {
|
|
133
|
-
cursor?: string
|
|
134
|
-
limit?: number
|
|
135
|
-
startTime?: Date
|
|
136
|
-
endTime?: Date
|
|
137
|
-
subjects?: string[]
|
|
138
|
-
statuses: ScheduledActionStatus[]
|
|
139
|
-
direction?: 'asc' | 'desc'
|
|
140
|
-
}): Promise<{
|
|
141
|
-
actions: Selectable<ScheduledAction>[]
|
|
142
|
-
cursor?: string
|
|
143
|
-
}> {
|
|
144
|
-
let query = this.db.db
|
|
145
|
-
.selectFrom('scheduled_action')
|
|
146
|
-
.where('status', 'in', statuses)
|
|
147
|
-
.selectAll()
|
|
148
|
-
|
|
149
|
-
if (subjects && subjects.length > 0) {
|
|
150
|
-
query = query.where('did', 'in', subjects)
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
if (startTime) {
|
|
154
|
-
query = query.where((eb) =>
|
|
155
|
-
eb.or([
|
|
156
|
-
eb('executeAt', '>=', startTime.toISOString()),
|
|
157
|
-
eb('executeAfter', '>=', startTime.toISOString()),
|
|
158
|
-
]),
|
|
159
|
-
)
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
if (endTime) {
|
|
163
|
-
query = query.where((eb) =>
|
|
164
|
-
eb.or([
|
|
165
|
-
eb('executeAt', '<=', endTime.toISOString()),
|
|
166
|
-
eb('executeUntil', '<=', endTime.toISOString()),
|
|
167
|
-
eb.and([
|
|
168
|
-
eb('executeUntil', 'is', null),
|
|
169
|
-
eb('executeAfter', '<=', endTime.toISOString()),
|
|
170
|
-
]),
|
|
171
|
-
]),
|
|
172
|
-
)
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
if (cursor) {
|
|
176
|
-
query = query.where(
|
|
177
|
-
'id',
|
|
178
|
-
direction === 'asc' ? '>' : '<',
|
|
179
|
-
parseInt(cursor, 10),
|
|
180
|
-
)
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
const actions = await query.orderBy('id', direction).limit(limit).execute()
|
|
184
|
-
|
|
185
|
-
return {
|
|
186
|
-
actions,
|
|
187
|
-
cursor: actions.at(-1)?.id?.toString(),
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
async cancelScheduledActions(subjects: string[]): Promise<{
|
|
192
|
-
succeeded: string[]
|
|
193
|
-
failed: { did: string; error: string; errorCode?: string }[]
|
|
194
|
-
}> {
|
|
195
|
-
const succeeded: string[] = []
|
|
196
|
-
const failed: { did: string; error: string; errorCode?: string }[] = []
|
|
197
|
-
|
|
198
|
-
for (const did of subjects) {
|
|
199
|
-
try {
|
|
200
|
-
const result = await this.db.db
|
|
201
|
-
.updateTable('scheduled_action')
|
|
202
|
-
.set({
|
|
203
|
-
status: 'cancelled',
|
|
204
|
-
updatedAt: new Date().toISOString(),
|
|
205
|
-
})
|
|
206
|
-
.where('did', '=', did)
|
|
207
|
-
.where('status', '=', 'pending')
|
|
208
|
-
.executeTakeFirst()
|
|
209
|
-
|
|
210
|
-
if (result.numUpdatedRows && result.numUpdatedRows > 0) {
|
|
211
|
-
succeeded.push(did)
|
|
212
|
-
} else {
|
|
213
|
-
failed.push({
|
|
214
|
-
did,
|
|
215
|
-
error: 'No pending scheduled actions found for subject',
|
|
216
|
-
errorCode: 'NoPendingActions',
|
|
217
|
-
})
|
|
218
|
-
}
|
|
219
|
-
} catch (err) {
|
|
220
|
-
dbLogger.error({ err, subjects }, 'Error cancelling scheduled action')
|
|
221
|
-
failed.push({
|
|
222
|
-
did,
|
|
223
|
-
error: 'Unknown error',
|
|
224
|
-
errorCode: 'DatabaseError',
|
|
225
|
-
})
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
return { succeeded, failed }
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
async getPendingActionsToExecute(
|
|
233
|
-
now: Date,
|
|
234
|
-
): Promise<Selectable<ScheduledAction>[]> {
|
|
235
|
-
return await this.db.db
|
|
236
|
-
.selectFrom('scheduled_action')
|
|
237
|
-
.selectAll()
|
|
238
|
-
.where('status', '=', 'pending')
|
|
239
|
-
.where((eb) =>
|
|
240
|
-
eb.or([
|
|
241
|
-
eb('executeAfter', '<=', now.toISOString()),
|
|
242
|
-
eb('executeAt', '<=', now.toISOString()),
|
|
243
|
-
]),
|
|
244
|
-
)
|
|
245
|
-
.execute()
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
async markActionAsExecuted(
|
|
249
|
-
actionId: number,
|
|
250
|
-
executionEventId: number,
|
|
251
|
-
): Promise<void> {
|
|
252
|
-
const now = new Date().toISOString()
|
|
253
|
-
await this.db.db
|
|
254
|
-
.updateTable('scheduled_action')
|
|
255
|
-
.set({
|
|
256
|
-
status: 'executed',
|
|
257
|
-
lastExecutedAt: now,
|
|
258
|
-
executionEventId,
|
|
259
|
-
updatedAt: now,
|
|
260
|
-
})
|
|
261
|
-
.where('id', '=', actionId)
|
|
262
|
-
.execute()
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
async markActionAsFailed(
|
|
266
|
-
actionId: number,
|
|
267
|
-
failureReason: string,
|
|
268
|
-
): Promise<void> {
|
|
269
|
-
const now = new Date().toISOString()
|
|
270
|
-
await this.db.db
|
|
271
|
-
.updateTable('scheduled_action')
|
|
272
|
-
.set({
|
|
273
|
-
status: 'failed',
|
|
274
|
-
lastExecutedAt: now,
|
|
275
|
-
lastFailureReason: failureReason,
|
|
276
|
-
updatedAt: now,
|
|
277
|
-
})
|
|
278
|
-
.where('id', '=', actionId)
|
|
279
|
-
.execute()
|
|
280
|
-
}
|
|
281
|
-
}
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import { ScheduledActionType } from '../api/util.js'
|
|
2
|
-
|
|
3
|
-
export type ExecutionSchedule =
|
|
4
|
-
| {
|
|
5
|
-
executeAt: Date
|
|
6
|
-
}
|
|
7
|
-
| {
|
|
8
|
-
executeAfter: Date
|
|
9
|
-
executeUntil?: Date
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export type SchedulingParams = {
|
|
13
|
-
action: ScheduledActionType
|
|
14
|
-
eventData: unknown
|
|
15
|
-
did: string
|
|
16
|
-
createdBy: string
|
|
17
|
-
} & ExecutionSchedule
|
package/src/sequencer/index.ts
DELETED
package/src/sequencer/outbox.ts
DELETED
|
@@ -1,123 +0,0 @@
|
|
|
1
|
-
import { AsyncBuffer, AsyncBufferFullError } from '@atproto/common'
|
|
2
|
-
import { InvalidRequestError } from '@atproto/xrpc-server'
|
|
3
|
-
import { LabelsEvt, Sequencer } from './sequencer.js'
|
|
4
|
-
|
|
5
|
-
export type OutboxOpts = {
|
|
6
|
-
maxBufferSize: number
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
export class Outbox {
|
|
10
|
-
private caughtUp = false
|
|
11
|
-
lastSeen = -1
|
|
12
|
-
|
|
13
|
-
cutoverBuffer: LabelsEvt[]
|
|
14
|
-
outBuffer: AsyncBuffer<LabelsEvt>
|
|
15
|
-
|
|
16
|
-
constructor(
|
|
17
|
-
public sequencer: Sequencer,
|
|
18
|
-
opts: Partial<OutboxOpts> = {},
|
|
19
|
-
) {
|
|
20
|
-
const { maxBufferSize = 500 } = opts
|
|
21
|
-
this.cutoverBuffer = []
|
|
22
|
-
this.outBuffer = new AsyncBuffer<LabelsEvt>(maxBufferSize)
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
// event stream occurs in 3 phases
|
|
26
|
-
// 1. backfill events: events that have been added to the DB since the last time a connection was open.
|
|
27
|
-
// The outbox is not yet listening for new events from the sequencer
|
|
28
|
-
// 2. cutover: the outbox has caught up with where the sequencer purports to be,
|
|
29
|
-
// but the sequencer might already be halfway through sending out a round of updates.
|
|
30
|
-
// Therefore, we start accepting the sequencer's events in a buffer, while making our own request to the
|
|
31
|
-
// database to ensure we're caught up. We then dedupe the query & the buffer & stream the events in order
|
|
32
|
-
// 3. streaming: we're all caught up on historic state, so the sequencer outputs events and we
|
|
33
|
-
// immediately yield them
|
|
34
|
-
async *events(
|
|
35
|
-
backfillCursor?: number,
|
|
36
|
-
signal?: AbortSignal,
|
|
37
|
-
): AsyncGenerator<LabelsEvt> {
|
|
38
|
-
// catch up as much as we can
|
|
39
|
-
if (backfillCursor !== undefined) {
|
|
40
|
-
for await (const evt of this.getBackfill(backfillCursor)) {
|
|
41
|
-
if (signal?.aborted) return
|
|
42
|
-
this.lastSeen = evt.seq
|
|
43
|
-
yield evt
|
|
44
|
-
}
|
|
45
|
-
} else {
|
|
46
|
-
// if not backfill, we don't need to cutover, just start streaming
|
|
47
|
-
this.caughtUp = true
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
// streams updates from sequencer, but buffers them for cutover as it makes a last request
|
|
51
|
-
|
|
52
|
-
const addToBuffer = (evts) => {
|
|
53
|
-
if (this.caughtUp) {
|
|
54
|
-
this.outBuffer.pushMany(evts)
|
|
55
|
-
} else {
|
|
56
|
-
this.cutoverBuffer = [...this.cutoverBuffer, ...evts]
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
if (!signal?.aborted) {
|
|
61
|
-
this.sequencer.on('events', addToBuffer)
|
|
62
|
-
}
|
|
63
|
-
signal?.addEventListener('abort', () =>
|
|
64
|
-
this.sequencer.off('events', addToBuffer),
|
|
65
|
-
)
|
|
66
|
-
|
|
67
|
-
const cutover = async () => {
|
|
68
|
-
// only need to perform cutover if we've been backfilling
|
|
69
|
-
if (backfillCursor !== undefined) {
|
|
70
|
-
const cutoverEvts = await this.sequencer.requestLabelRange({
|
|
71
|
-
earliestId: this.lastSeen > -1 ? this.lastSeen : backfillCursor,
|
|
72
|
-
})
|
|
73
|
-
this.outBuffer.pushMany(cutoverEvts)
|
|
74
|
-
// dont worry about dupes, we ensure order on yield
|
|
75
|
-
this.outBuffer.pushMany(this.cutoverBuffer)
|
|
76
|
-
this.caughtUp = true
|
|
77
|
-
this.cutoverBuffer = []
|
|
78
|
-
} else {
|
|
79
|
-
this.caughtUp = true
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
cutover()
|
|
83
|
-
|
|
84
|
-
while (true) {
|
|
85
|
-
try {
|
|
86
|
-
for await (const evt of this.outBuffer.events()) {
|
|
87
|
-
if (signal?.aborted) return
|
|
88
|
-
if (evt.seq > this.lastSeen) {
|
|
89
|
-
this.lastSeen = evt.seq
|
|
90
|
-
yield evt
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
} catch (err) {
|
|
94
|
-
if (err instanceof AsyncBufferFullError) {
|
|
95
|
-
throw new InvalidRequestError(
|
|
96
|
-
'Stream consumer too slow',
|
|
97
|
-
'ConsumerTooSlow',
|
|
98
|
-
)
|
|
99
|
-
} else {
|
|
100
|
-
throw err
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
// yields only historical events
|
|
107
|
-
async *getBackfill(backfillCursor: number) {
|
|
108
|
-
const PAGE_SIZE = 500
|
|
109
|
-
while (true) {
|
|
110
|
-
const evts = await this.sequencer.requestLabelRange({
|
|
111
|
-
earliestId: this.lastSeen > -1 ? this.lastSeen : backfillCursor,
|
|
112
|
-
limit: PAGE_SIZE,
|
|
113
|
-
})
|
|
114
|
-
for (const evt of evts) {
|
|
115
|
-
yield evt
|
|
116
|
-
}
|
|
117
|
-
// if we're within half a pagesize of the sequencer, we call it good & switch to cutover
|
|
118
|
-
const seqCursor = this.sequencer.lastSeen ?? -1
|
|
119
|
-
if (seqCursor - this.lastSeen < PAGE_SIZE / 2) break
|
|
120
|
-
if (evts.length < 1) break
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
}
|
|
@@ -1,147 +0,0 @@
|
|
|
1
|
-
import EventEmitter from 'node:events'
|
|
2
|
-
import { Selectable } from 'kysely'
|
|
3
|
-
// eslint-disable-next-line import/default
|
|
4
|
-
import pg from 'pg'
|
|
5
|
-
type PoolClient = pg.PoolClient
|
|
6
|
-
import type TypedEmitter from 'typed-emitter'
|
|
7
|
-
import { Database } from '../db/index.js'
|
|
8
|
-
import { Label as LabelTable, LabelChannel } from '../db/schema/label.js'
|
|
9
|
-
import { Labels as LabelsEvt } from '../lexicon/types/com/atproto/label/subscribeLabels.js'
|
|
10
|
-
import { seqLogger as log } from '../logger.js'
|
|
11
|
-
import { ModerationService } from '../mod-service/index.js'
|
|
12
|
-
|
|
13
|
-
export type { Labels as LabelsEvt } from '../lexicon/types/com/atproto/label/subscribeLabels.js'
|
|
14
|
-
type LabelRow = Selectable<LabelTable>
|
|
15
|
-
|
|
16
|
-
export class Sequencer extends (EventEmitter as new () => SequencerEmitter) {
|
|
17
|
-
db: Database
|
|
18
|
-
destroyed = false
|
|
19
|
-
pollPromise: Promise<void> | undefined
|
|
20
|
-
queued = false
|
|
21
|
-
conn: PoolClient | undefined
|
|
22
|
-
|
|
23
|
-
constructor(
|
|
24
|
-
public modSrvc: ModerationService,
|
|
25
|
-
public lastSeen = 0,
|
|
26
|
-
) {
|
|
27
|
-
super()
|
|
28
|
-
this.db = modSrvc.db
|
|
29
|
-
// note: this does not err when surpassed, just prints a warning to stderr
|
|
30
|
-
this.setMaxListeners(100)
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
async start() {
|
|
34
|
-
const curr = await this.curr()
|
|
35
|
-
this.lastSeen = curr ?? 0
|
|
36
|
-
this.poll()
|
|
37
|
-
this.conn = await this.db.pool.connect()
|
|
38
|
-
this.conn.query(`listen ${LabelChannel}`) // if this errors, unhandled rejection should cause process to exit
|
|
39
|
-
this.conn.on('notification', (notif) => {
|
|
40
|
-
if (notif.channel === LabelChannel) {
|
|
41
|
-
this.poll()
|
|
42
|
-
}
|
|
43
|
-
})
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
async destroy() {
|
|
47
|
-
if (this.destroyed) return
|
|
48
|
-
this.destroyed = true
|
|
49
|
-
if (this.conn) {
|
|
50
|
-
this.conn.release()
|
|
51
|
-
this.conn = undefined
|
|
52
|
-
}
|
|
53
|
-
if (this.pollPromise) {
|
|
54
|
-
await this.pollPromise
|
|
55
|
-
}
|
|
56
|
-
this.emit('close')
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
async curr(): Promise<number | null> {
|
|
60
|
-
const got = await this.db.db
|
|
61
|
-
.selectFrom('label')
|
|
62
|
-
.selectAll()
|
|
63
|
-
.orderBy('id', 'desc')
|
|
64
|
-
.limit(1)
|
|
65
|
-
.executeTakeFirst()
|
|
66
|
-
return got?.id ?? null
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
async next(cursor: number): Promise<LabelRow | null> {
|
|
70
|
-
const got = await this.db.db
|
|
71
|
-
.selectFrom('label')
|
|
72
|
-
.selectAll()
|
|
73
|
-
.where('id', '>', cursor)
|
|
74
|
-
.limit(1)
|
|
75
|
-
.orderBy('id', 'asc')
|
|
76
|
-
.executeTakeFirst()
|
|
77
|
-
return got || null
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
async requestLabelRange(opts: {
|
|
81
|
-
earliestId?: number
|
|
82
|
-
limit?: number
|
|
83
|
-
}): Promise<LabelsEvt[]> {
|
|
84
|
-
const { earliestId, limit } = opts
|
|
85
|
-
|
|
86
|
-
let seqQb = this.db.db.selectFrom('label').selectAll().orderBy('id', 'asc')
|
|
87
|
-
if (earliestId !== undefined) {
|
|
88
|
-
seqQb = seqQb.where('id', '>', earliestId)
|
|
89
|
-
}
|
|
90
|
-
if (limit !== undefined) {
|
|
91
|
-
seqQb = seqQb.limit(limit)
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
const rows = await seqQb.execute()
|
|
95
|
-
if (rows.length < 1) {
|
|
96
|
-
return []
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
const evts: LabelsEvt[] = await Promise.all(
|
|
100
|
-
rows.map(async (row) => {
|
|
101
|
-
const formatted = await this.modSrvc.views.formatLabelAndEnsureSig(row)
|
|
102
|
-
return { seq: row.id, labels: [formatted] }
|
|
103
|
-
}),
|
|
104
|
-
)
|
|
105
|
-
|
|
106
|
-
return evts
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
private poll() {
|
|
110
|
-
if (this.destroyed) return
|
|
111
|
-
if (this.pollPromise) {
|
|
112
|
-
this.queued = true
|
|
113
|
-
return
|
|
114
|
-
}
|
|
115
|
-
this.queued = false
|
|
116
|
-
this.pollPromise = this.requestLabelRange({
|
|
117
|
-
earliestId: this.lastSeen,
|
|
118
|
-
limit: 500,
|
|
119
|
-
})
|
|
120
|
-
.then((evts) => {
|
|
121
|
-
this.emit('events', evts)
|
|
122
|
-
this.lastSeen = evts.at(-1)?.seq ?? this.lastSeen
|
|
123
|
-
if (evts.length > 0) {
|
|
124
|
-
this.queued = true
|
|
125
|
-
}
|
|
126
|
-
})
|
|
127
|
-
.catch((err) => {
|
|
128
|
-
log.error(
|
|
129
|
-
{ err, lastSeen: this.lastSeen },
|
|
130
|
-
'sequencer failed to poll db',
|
|
131
|
-
)
|
|
132
|
-
})
|
|
133
|
-
.finally(() => {
|
|
134
|
-
this.pollPromise = undefined
|
|
135
|
-
if (this.queued) {
|
|
136
|
-
this.poll()
|
|
137
|
-
}
|
|
138
|
-
})
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
type SequencerEvents = {
|
|
143
|
-
events: (evts: LabelsEvt[]) => void
|
|
144
|
-
close: () => void
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
export type SequencerEmitter = TypedEmitter.default<SequencerEvents>
|