@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,549 +0,0 @@
|
|
|
1
|
-
// This may require better organization but for now, just dumping functions here containing DB queries for moderation status
|
|
2
|
-
|
|
3
|
-
import { HOUR } from '@atproto/common'
|
|
4
|
-
import { AtUri } from '@atproto/syntax'
|
|
5
|
-
import { isAppealReport } from '../api/util.js'
|
|
6
|
-
import { Database } from '../db/index.js'
|
|
7
|
-
import { DatabaseSchema } from '../db/schema/index.js'
|
|
8
|
-
import { jsonb } from '../db/types.js'
|
|
9
|
-
import {
|
|
10
|
-
REVIEWCLOSED,
|
|
11
|
-
REVIEWESCALATED,
|
|
12
|
-
REVIEWNONE,
|
|
13
|
-
REVIEWOPEN,
|
|
14
|
-
} from '../lexicon/types/tools/ozone/moderation/defs.js'
|
|
15
|
-
import { ModerationEventRow, ModerationSubjectStatusRow } from './types.js'
|
|
16
|
-
|
|
17
|
-
const getSubjectStatusForModerationEvent = ({
|
|
18
|
-
currentStatus,
|
|
19
|
-
action,
|
|
20
|
-
createdBy,
|
|
21
|
-
createdAt,
|
|
22
|
-
durationInHours,
|
|
23
|
-
}: {
|
|
24
|
-
currentStatus?: ModerationSubjectStatusRow
|
|
25
|
-
action: string
|
|
26
|
-
createdBy: string
|
|
27
|
-
createdAt: string
|
|
28
|
-
durationInHours: number | null
|
|
29
|
-
}): Partial<ModerationSubjectStatusRow> => {
|
|
30
|
-
const defaultReviewState = currentStatus
|
|
31
|
-
? currentStatus.reviewState
|
|
32
|
-
: REVIEWNONE
|
|
33
|
-
|
|
34
|
-
switch (action) {
|
|
35
|
-
case 'tools.ozone.moderation.defs#modEventAcknowledge':
|
|
36
|
-
return {
|
|
37
|
-
lastReviewedBy: createdBy,
|
|
38
|
-
reviewState: REVIEWCLOSED,
|
|
39
|
-
lastReviewedAt: createdAt,
|
|
40
|
-
}
|
|
41
|
-
case 'tools.ozone.moderation.defs#modEventReport':
|
|
42
|
-
return {
|
|
43
|
-
reviewState: REVIEWOPEN,
|
|
44
|
-
lastReportedAt: createdAt,
|
|
45
|
-
}
|
|
46
|
-
case 'tools.ozone.moderation.defs#modEventEscalate':
|
|
47
|
-
return {
|
|
48
|
-
lastReviewedBy: createdBy,
|
|
49
|
-
reviewState: REVIEWESCALATED,
|
|
50
|
-
lastReviewedAt: createdAt,
|
|
51
|
-
}
|
|
52
|
-
case 'tools.ozone.moderation.defs#modEventReverseTakedown':
|
|
53
|
-
return {
|
|
54
|
-
lastReviewedBy: createdBy,
|
|
55
|
-
reviewState: REVIEWCLOSED,
|
|
56
|
-
takendown: false,
|
|
57
|
-
suspendUntil: null,
|
|
58
|
-
lastReviewedAt: createdAt,
|
|
59
|
-
}
|
|
60
|
-
case 'tools.ozone.moderation.defs#modEventUnmuteReporter':
|
|
61
|
-
return {
|
|
62
|
-
lastReviewedBy: createdBy,
|
|
63
|
-
muteReportingUntil: null,
|
|
64
|
-
// It's not likely to receive an unmute event that does not already have a status row
|
|
65
|
-
// but if it does happen, default to unnecessary
|
|
66
|
-
reviewState: defaultReviewState,
|
|
67
|
-
lastReviewedAt: createdAt,
|
|
68
|
-
}
|
|
69
|
-
case 'tools.ozone.moderation.defs#modEventUnmute':
|
|
70
|
-
return {
|
|
71
|
-
lastReviewedBy: createdBy,
|
|
72
|
-
muteUntil: null,
|
|
73
|
-
// It's not likely to receive an unmute event that does not already have a status row
|
|
74
|
-
// but if it does happen, default to unnecessary
|
|
75
|
-
reviewState: defaultReviewState,
|
|
76
|
-
lastReviewedAt: createdAt,
|
|
77
|
-
}
|
|
78
|
-
case 'tools.ozone.moderation.defs#modEventTakedown':
|
|
79
|
-
return {
|
|
80
|
-
// If we are doing a takedown, safe to move the item out of appealed state
|
|
81
|
-
...(currentStatus?.appealed ? { appealed: false } : {}),
|
|
82
|
-
takendown: true,
|
|
83
|
-
lastReviewedBy: createdBy,
|
|
84
|
-
reviewState: REVIEWCLOSED,
|
|
85
|
-
lastReviewedAt: createdAt,
|
|
86
|
-
suspendUntil: durationInHours
|
|
87
|
-
? new Date(Date.now() + durationInHours * HOUR).toISOString()
|
|
88
|
-
: null,
|
|
89
|
-
}
|
|
90
|
-
case 'tools.ozone.moderation.defs#modEventMuteReporter':
|
|
91
|
-
return {
|
|
92
|
-
lastReviewedBy: createdBy,
|
|
93
|
-
lastReviewedAt: createdAt,
|
|
94
|
-
// By default, mute for 24hrs
|
|
95
|
-
muteReportingUntil: new Date(
|
|
96
|
-
Date.now() + (durationInHours || 24) * HOUR,
|
|
97
|
-
).toISOString(),
|
|
98
|
-
// It's not likely to receive a mute event on a subject that does not already have a status row
|
|
99
|
-
// but if it does happen, default to unnecessary
|
|
100
|
-
reviewState: defaultReviewState,
|
|
101
|
-
}
|
|
102
|
-
case 'tools.ozone.moderation.defs#modEventMute':
|
|
103
|
-
return {
|
|
104
|
-
lastReviewedBy: createdBy,
|
|
105
|
-
lastReviewedAt: createdAt,
|
|
106
|
-
// By default, mute for 24hrs
|
|
107
|
-
muteUntil: new Date(
|
|
108
|
-
Date.now() + (durationInHours || 24) * HOUR,
|
|
109
|
-
).toISOString(),
|
|
110
|
-
// It's not likely to receive a mute event on a subject that does not already have a status row
|
|
111
|
-
// but if it does happen, default to unnecessary
|
|
112
|
-
reviewState: defaultReviewState,
|
|
113
|
-
}
|
|
114
|
-
case 'tools.ozone.moderation.defs#modEventComment':
|
|
115
|
-
return {
|
|
116
|
-
lastReviewedBy: createdBy,
|
|
117
|
-
lastReviewedAt: createdAt,
|
|
118
|
-
reviewState: defaultReviewState,
|
|
119
|
-
}
|
|
120
|
-
case 'tools.ozone.moderation.defs#modEventTag':
|
|
121
|
-
return { tags: [], reviewState: defaultReviewState }
|
|
122
|
-
case 'tools.ozone.moderation.defs#modEventResolveAppeal':
|
|
123
|
-
return {
|
|
124
|
-
appealed: false,
|
|
125
|
-
}
|
|
126
|
-
case 'tools.ozone.moderation.defs#ageAssuranceEvent':
|
|
127
|
-
case 'tools.ozone.moderation.defs#ageAssuranceOverrideEvent':
|
|
128
|
-
case 'tools.ozone.moderation.defs#ageAssurancePurgeEvent':
|
|
129
|
-
return {
|
|
130
|
-
reviewState: defaultReviewState,
|
|
131
|
-
}
|
|
132
|
-
default:
|
|
133
|
-
return {}
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
const hostingEvents = [
|
|
138
|
-
'tools.ozone.moderation.defs#accountEvent',
|
|
139
|
-
'tools.ozone.moderation.defs#identityEvent',
|
|
140
|
-
'tools.ozone.moderation.defs#recordEvent',
|
|
141
|
-
]
|
|
142
|
-
|
|
143
|
-
const getSubjectStatusForRecordEvent = ({
|
|
144
|
-
event,
|
|
145
|
-
currentStatus,
|
|
146
|
-
}: {
|
|
147
|
-
event: ModerationEventRow
|
|
148
|
-
currentStatus?: ModerationSubjectStatusRow
|
|
149
|
-
}): Partial<ModerationSubjectStatusRow> => {
|
|
150
|
-
const timestamp =
|
|
151
|
-
typeof event.meta?.timestamp === 'string'
|
|
152
|
-
? event.meta?.timestamp
|
|
153
|
-
: event.createdAt
|
|
154
|
-
|
|
155
|
-
if (event.action === 'tools.ozone.moderation.defs#recordEvent') {
|
|
156
|
-
if (event.meta?.op === 'delete') {
|
|
157
|
-
return {
|
|
158
|
-
hostingStatus: 'deleted',
|
|
159
|
-
hostingDeletedAt: timestamp,
|
|
160
|
-
}
|
|
161
|
-
} else if (event.meta?.op === 'update') {
|
|
162
|
-
return {
|
|
163
|
-
hostingStatus: 'active',
|
|
164
|
-
hostingUpdatedAt: timestamp,
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
return {}
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
if (event.action === 'tools.ozone.moderation.defs#accountEvent') {
|
|
171
|
-
const status: Partial<ModerationSubjectStatusRow> = {
|
|
172
|
-
hostingUpdatedAt: timestamp,
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
if (event.meta?.status) {
|
|
176
|
-
status.hostingStatus = `${event.meta?.status}`
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
if (event.meta?.status === 'deleted') {
|
|
180
|
-
status.hostingDeletedAt = timestamp
|
|
181
|
-
} else if (event.meta?.status === 'deactivated') {
|
|
182
|
-
status.hostingDeactivatedAt = timestamp
|
|
183
|
-
} else {
|
|
184
|
-
// When deactivated accounts are re-activated, we receive the event with just the active flag set to true
|
|
185
|
-
// so we want to make sure that the hostingStatus is not set to an outdated value
|
|
186
|
-
if (
|
|
187
|
-
currentStatus?.hostingStatus === 'deactivated' &&
|
|
188
|
-
event.meta?.active
|
|
189
|
-
) {
|
|
190
|
-
status.hostingStatus = 'active'
|
|
191
|
-
status.hostingReactivatedAt = timestamp
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
return status
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
if (event.action === 'tools.ozone.moderation.defs#identityEvent') {
|
|
199
|
-
const status: Partial<ModerationSubjectStatusRow> = {
|
|
200
|
-
hostingUpdatedAt: timestamp,
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
if (event.meta?.tombstone) {
|
|
204
|
-
status.hostingStatus = 'tombstoned'
|
|
205
|
-
status.hostingDeletedAt = timestamp
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
return status
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
return {}
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
export const moderationSubjectStatusQueryBuilder = (db: DatabaseSchema) => {
|
|
215
|
-
// @NOTE: Using select() instead of selectAll() below because the materialized
|
|
216
|
-
// views might be incomplete, and we don't want the null `did` columns to
|
|
217
|
-
// interfere with the (never null) `did` column from the
|
|
218
|
-
// `moderation_subject_status` table in the results
|
|
219
|
-
return db
|
|
220
|
-
.selectFrom('moderation_subject_status')
|
|
221
|
-
.selectAll('moderation_subject_status')
|
|
222
|
-
.leftJoin('account_events_stats', (join) =>
|
|
223
|
-
join.onRef(
|
|
224
|
-
'moderation_subject_status.did',
|
|
225
|
-
'=',
|
|
226
|
-
'account_events_stats.subjectDid',
|
|
227
|
-
),
|
|
228
|
-
)
|
|
229
|
-
.select([
|
|
230
|
-
'account_events_stats.takedownCount',
|
|
231
|
-
'account_events_stats.suspendCount',
|
|
232
|
-
'account_events_stats.escalateCount',
|
|
233
|
-
'account_events_stats.reportCount',
|
|
234
|
-
'account_events_stats.appealCount',
|
|
235
|
-
])
|
|
236
|
-
.leftJoin('account_record_events_stats', (join) =>
|
|
237
|
-
join.onRef(
|
|
238
|
-
'moderation_subject_status.did',
|
|
239
|
-
'=',
|
|
240
|
-
'account_record_events_stats.subjectDid',
|
|
241
|
-
),
|
|
242
|
-
)
|
|
243
|
-
.select([
|
|
244
|
-
'account_record_events_stats.totalReports',
|
|
245
|
-
'account_record_events_stats.reportedCount',
|
|
246
|
-
'account_record_events_stats.escalatedCount',
|
|
247
|
-
'account_record_events_stats.appealedCount',
|
|
248
|
-
])
|
|
249
|
-
.leftJoin('account_record_status_stats', (join) =>
|
|
250
|
-
join.onRef(
|
|
251
|
-
'moderation_subject_status.did',
|
|
252
|
-
'=',
|
|
253
|
-
'account_record_status_stats.did',
|
|
254
|
-
),
|
|
255
|
-
)
|
|
256
|
-
.select([
|
|
257
|
-
'account_record_status_stats.subjectCount',
|
|
258
|
-
'account_record_status_stats.pendingCount',
|
|
259
|
-
'account_record_status_stats.processedCount',
|
|
260
|
-
'account_record_status_stats.takendownCount',
|
|
261
|
-
])
|
|
262
|
-
.leftJoin('account_strike', (join) =>
|
|
263
|
-
join.onRef('moderation_subject_status.did', '=', 'account_strike.did'),
|
|
264
|
-
)
|
|
265
|
-
.select([
|
|
266
|
-
'account_strike.activeStrikeCount as strikeCount',
|
|
267
|
-
'account_strike.totalStrikeCount',
|
|
268
|
-
'account_strike.firstStrikeAt',
|
|
269
|
-
'account_strike.lastStrikeAt',
|
|
270
|
-
])
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
// Based on a given moderation action event, this function will update the moderation status of the subject
|
|
274
|
-
// If there's no existing status, it will create one
|
|
275
|
-
// If the action event does not affect the status, it will do nothing
|
|
276
|
-
export const adjustModerationSubjectStatus = async (
|
|
277
|
-
db: Database,
|
|
278
|
-
moderationEvent: ModerationEventRow,
|
|
279
|
-
blobCids?: string[],
|
|
280
|
-
): Promise<ModerationSubjectStatusRow | null> => {
|
|
281
|
-
const {
|
|
282
|
-
action,
|
|
283
|
-
subjectDid,
|
|
284
|
-
subjectUri,
|
|
285
|
-
subjectCid,
|
|
286
|
-
subjectConvoId,
|
|
287
|
-
createdBy,
|
|
288
|
-
meta,
|
|
289
|
-
addedTags,
|
|
290
|
-
removedTags,
|
|
291
|
-
comment,
|
|
292
|
-
createdAt,
|
|
293
|
-
} = moderationEvent
|
|
294
|
-
|
|
295
|
-
// If subjectUri exists, it's not a repoRef so pass along the uri to get identifier back
|
|
296
|
-
const identifier = getStatusIdentifierFromSubject(
|
|
297
|
-
subjectUri || subjectDid,
|
|
298
|
-
subjectConvoId,
|
|
299
|
-
)
|
|
300
|
-
|
|
301
|
-
db.assertTransaction()
|
|
302
|
-
|
|
303
|
-
const now = new Date().toISOString()
|
|
304
|
-
const currentStatus = await db.db
|
|
305
|
-
.selectFrom('moderation_subject_status')
|
|
306
|
-
.where('did', '=', identifier.did)
|
|
307
|
-
.where('recordPath', '=', identifier.recordPath)
|
|
308
|
-
.where('convoId', '=', identifier.convoId)
|
|
309
|
-
// Make sure we respect other updates that may be happening at the same time
|
|
310
|
-
.forUpdate()
|
|
311
|
-
.selectAll()
|
|
312
|
-
.executeTakeFirst()
|
|
313
|
-
|
|
314
|
-
if (hostingEvents.includes(action)) {
|
|
315
|
-
const newStatus = getSubjectStatusForRecordEvent({
|
|
316
|
-
event: moderationEvent,
|
|
317
|
-
currentStatus,
|
|
318
|
-
})
|
|
319
|
-
if (!Object.keys(newStatus).length) {
|
|
320
|
-
return currentStatus || null
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
const status = await db.db
|
|
324
|
-
.insertInto('moderation_subject_status')
|
|
325
|
-
.values({
|
|
326
|
-
...identifier,
|
|
327
|
-
...newStatus,
|
|
328
|
-
// newStatus doesn't contain a reviewState or takendown so in case this is a new entry
|
|
329
|
-
// we need to set a default values so that the insert doesn't fail
|
|
330
|
-
reviewState: currentStatus ? currentStatus.reviewState : REVIEWNONE,
|
|
331
|
-
// @TODO: should we try to update this based on status property of account event?
|
|
332
|
-
// For now we're the only one emitting takedowns so i don't think it makes too much of a difference
|
|
333
|
-
takendown: currentStatus ? currentStatus.takendown : false,
|
|
334
|
-
ageAssuranceState: currentStatus
|
|
335
|
-
? currentStatus.ageAssuranceState
|
|
336
|
-
: 'unknown',
|
|
337
|
-
createdAt: now,
|
|
338
|
-
updatedAt: now,
|
|
339
|
-
})
|
|
340
|
-
.onConflict((oc) =>
|
|
341
|
-
oc.constraint('moderation_status_unique_idx').doUpdateSet({
|
|
342
|
-
...newStatus,
|
|
343
|
-
updatedAt: now,
|
|
344
|
-
}),
|
|
345
|
-
)
|
|
346
|
-
.returningAll()
|
|
347
|
-
.executeTakeFirst()
|
|
348
|
-
|
|
349
|
-
return status || null
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
// If reporting is muted for this reporter, we don't want to update the subject status
|
|
353
|
-
if (meta?.isReporterMuted) {
|
|
354
|
-
return currentStatus || null
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
const isAppealEvent =
|
|
358
|
-
action === 'tools.ozone.moderation.defs#modEventReport' &&
|
|
359
|
-
meta?.reportType &&
|
|
360
|
-
isAppealReport(`${meta.reportType}`)
|
|
361
|
-
|
|
362
|
-
const subjectStatus = getSubjectStatusForModerationEvent({
|
|
363
|
-
currentStatus,
|
|
364
|
-
action,
|
|
365
|
-
createdBy,
|
|
366
|
-
createdAt,
|
|
367
|
-
durationInHours: moderationEvent.durationInHours,
|
|
368
|
-
})
|
|
369
|
-
|
|
370
|
-
if (
|
|
371
|
-
currentStatus?.reviewState === REVIEWESCALATED &&
|
|
372
|
-
subjectStatus.reviewState !== REVIEWCLOSED
|
|
373
|
-
) {
|
|
374
|
-
// If the current status is escalated only allow incoming events to move the state to
|
|
375
|
-
// reviewClosed because escalated subjects should never move to any other state
|
|
376
|
-
subjectStatus.reviewState = REVIEWESCALATED
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
if (currentStatus && subjectStatus.reviewState === REVIEWNONE) {
|
|
380
|
-
// reviewNone is ONLY allowed when there is no current status
|
|
381
|
-
// If there is a current status, it should not be allowed to move back to reviewNone
|
|
382
|
-
subjectStatus.reviewState = currentStatus.reviewState
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
// Set these because we don't want to override them if they're already set
|
|
386
|
-
const defaultData = {
|
|
387
|
-
comment: null,
|
|
388
|
-
// Defaulting reviewState to open for any event may not be the desired behavior.
|
|
389
|
-
// For instance, if a subject never had any event and we just want to leave a comment to keep an eye on it
|
|
390
|
-
// that shouldn't mean we want to review the subject
|
|
391
|
-
reviewState: REVIEWNONE,
|
|
392
|
-
recordCid: subjectCid || null,
|
|
393
|
-
ageAssuranceState: currentStatus?.ageAssuranceState || 'unknown',
|
|
394
|
-
}
|
|
395
|
-
const newStatus = {
|
|
396
|
-
...defaultData,
|
|
397
|
-
...subjectStatus,
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
if (
|
|
401
|
-
action === 'tools.ozone.moderation.defs#modEventPriorityScore' &&
|
|
402
|
-
typeof meta?.priorityScore === 'number'
|
|
403
|
-
) {
|
|
404
|
-
newStatus.priorityScore = meta?.priorityScore
|
|
405
|
-
subjectStatus.priorityScore = meta?.priorityScore
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
if (
|
|
409
|
-
action === 'tools.ozone.moderation.defs#modEventReverseTakedown' &&
|
|
410
|
-
!subjectStatus.takendown
|
|
411
|
-
) {
|
|
412
|
-
newStatus.takendown = false
|
|
413
|
-
subjectStatus.takendown = false
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
if (isAppealEvent) {
|
|
417
|
-
newStatus.appealed = true
|
|
418
|
-
subjectStatus.appealed = true
|
|
419
|
-
newStatus.lastAppealedAt = createdAt
|
|
420
|
-
subjectStatus.lastAppealedAt = createdAt
|
|
421
|
-
// Set reviewState to escalated when appeal events are emitted
|
|
422
|
-
subjectStatus.reviewState = REVIEWESCALATED
|
|
423
|
-
newStatus.reviewState = REVIEWESCALATED
|
|
424
|
-
}
|
|
425
|
-
|
|
426
|
-
if (
|
|
427
|
-
action === 'tools.ozone.moderation.defs#modEventResolveAppeal' &&
|
|
428
|
-
subjectStatus.appealed
|
|
429
|
-
) {
|
|
430
|
-
newStatus.appealed = false
|
|
431
|
-
subjectStatus.appealed = false
|
|
432
|
-
}
|
|
433
|
-
|
|
434
|
-
if (
|
|
435
|
-
action === 'tools.ozone.moderation.defs#modEventComment' &&
|
|
436
|
-
meta?.sticky
|
|
437
|
-
) {
|
|
438
|
-
newStatus.comment = comment
|
|
439
|
-
subjectStatus.comment = comment
|
|
440
|
-
}
|
|
441
|
-
|
|
442
|
-
if (action === 'tools.ozone.moderation.defs#modEventTag') {
|
|
443
|
-
let tags = currentStatus?.tags || []
|
|
444
|
-
if (addedTags?.length) {
|
|
445
|
-
tags = tags.concat(addedTags)
|
|
446
|
-
}
|
|
447
|
-
if (removedTags?.length) {
|
|
448
|
-
tags = tags.filter((tag) => !removedTags.includes(tag))
|
|
449
|
-
}
|
|
450
|
-
newStatus.tags = jsonb([...new Set(tags)]) as unknown as string[]
|
|
451
|
-
subjectStatus.tags = newStatus.tags
|
|
452
|
-
}
|
|
453
|
-
|
|
454
|
-
if (action === 'tools.ozone.moderation.defs#ageAssuranceEvent') {
|
|
455
|
-
// Only when the last update was made by an admin AND state was set to reset user event can override final state
|
|
456
|
-
if (
|
|
457
|
-
currentStatus?.ageAssuranceUpdatedBy !== 'admin' ||
|
|
458
|
-
currentStatus?.ageAssuranceState === 'reset'
|
|
459
|
-
) {
|
|
460
|
-
if (typeof meta?.status === 'string') {
|
|
461
|
-
newStatus.ageAssuranceState = meta.status
|
|
462
|
-
subjectStatus.ageAssuranceState = meta.status
|
|
463
|
-
newStatus.ageAssuranceUpdatedBy = 'user'
|
|
464
|
-
subjectStatus.ageAssuranceUpdatedBy = 'user'
|
|
465
|
-
}
|
|
466
|
-
}
|
|
467
|
-
}
|
|
468
|
-
|
|
469
|
-
if (action === 'tools.ozone.moderation.defs#ageAssuranceOverrideEvent') {
|
|
470
|
-
if (typeof meta?.status === 'string') {
|
|
471
|
-
newStatus.ageAssuranceState = meta.status
|
|
472
|
-
subjectStatus.ageAssuranceState = meta.status
|
|
473
|
-
newStatus.ageAssuranceUpdatedBy = 'admin'
|
|
474
|
-
subjectStatus.ageAssuranceUpdatedBy = 'admin'
|
|
475
|
-
}
|
|
476
|
-
}
|
|
477
|
-
|
|
478
|
-
if (action === 'tools.ozone.moderation.defs#ageAssurancePurgeEvent') {
|
|
479
|
-
newStatus.ageAssuranceState = 'unknown'
|
|
480
|
-
subjectStatus.ageAssuranceState = 'unknown'
|
|
481
|
-
newStatus.ageAssuranceUpdatedBy = null
|
|
482
|
-
subjectStatus.ageAssuranceUpdatedBy = null
|
|
483
|
-
}
|
|
484
|
-
|
|
485
|
-
if (blobCids?.length) {
|
|
486
|
-
const newBlobCids = jsonb(
|
|
487
|
-
blobCids,
|
|
488
|
-
) as unknown as ModerationSubjectStatusRow['blobCids']
|
|
489
|
-
newStatus.blobCids = newBlobCids
|
|
490
|
-
subjectStatus.blobCids = newBlobCids
|
|
491
|
-
}
|
|
492
|
-
|
|
493
|
-
const insertQuery = db.db
|
|
494
|
-
.insertInto('moderation_subject_status')
|
|
495
|
-
.values({
|
|
496
|
-
...identifier,
|
|
497
|
-
...newStatus,
|
|
498
|
-
createdAt: now,
|
|
499
|
-
updatedAt: now,
|
|
500
|
-
} as ModerationSubjectStatusRow)
|
|
501
|
-
.onConflict((oc) =>
|
|
502
|
-
oc.constraint('moderation_status_unique_idx').doUpdateSet({
|
|
503
|
-
...subjectStatus,
|
|
504
|
-
updatedAt: now,
|
|
505
|
-
}),
|
|
506
|
-
)
|
|
507
|
-
|
|
508
|
-
const status = await insertQuery.returningAll().executeTakeFirst()
|
|
509
|
-
return status || null
|
|
510
|
-
}
|
|
511
|
-
|
|
512
|
-
/**
|
|
513
|
-
* Get moderation_subject_status identifier (did, recordPath, convoId).
|
|
514
|
-
* @note Supports addressing conversations explicitly (via convoId) and implicitly (via properly formed at-uri)
|
|
515
|
-
*/
|
|
516
|
-
export const getStatusIdentifierFromSubject = (
|
|
517
|
-
subject: string | AtUri,
|
|
518
|
-
convoId?: string | null,
|
|
519
|
-
): { did: string; recordPath: string; convoId: string } => {
|
|
520
|
-
const isSubjectString = typeof subject === 'string'
|
|
521
|
-
if (isSubjectString && subject.startsWith('did:')) {
|
|
522
|
-
return {
|
|
523
|
-
did: subject,
|
|
524
|
-
recordPath: '',
|
|
525
|
-
convoId: convoId || '',
|
|
526
|
-
}
|
|
527
|
-
}
|
|
528
|
-
|
|
529
|
-
if (isSubjectString && !subject.startsWith('at://')) {
|
|
530
|
-
throw new Error('Subject is neither a did nor an at-uri')
|
|
531
|
-
}
|
|
532
|
-
|
|
533
|
-
const uri = isSubjectString ? new AtUri(subject) : subject
|
|
534
|
-
|
|
535
|
-
// Handle conversation URIs
|
|
536
|
-
if (uri.collection === 'chat.bsky.convo') {
|
|
537
|
-
return {
|
|
538
|
-
did: uri.host,
|
|
539
|
-
recordPath: '',
|
|
540
|
-
convoId: uri.rkey,
|
|
541
|
-
}
|
|
542
|
-
}
|
|
543
|
-
|
|
544
|
-
return {
|
|
545
|
-
did: uri.host,
|
|
546
|
-
recordPath: `${uri.collection}/${uri.rkey}`,
|
|
547
|
-
convoId: '',
|
|
548
|
-
}
|
|
549
|
-
}
|
|
@@ -1,96 +0,0 @@
|
|
|
1
|
-
import { Database } from '../db/index.js'
|
|
2
|
-
|
|
3
|
-
export type StrikeServiceCreator = (db: Database) => StrikeService
|
|
4
|
-
|
|
5
|
-
export class StrikeService {
|
|
6
|
-
constructor(private db: Database) {}
|
|
7
|
-
|
|
8
|
-
static creator() {
|
|
9
|
-
return (db: Database) => {
|
|
10
|
-
return new StrikeService(db)
|
|
11
|
-
}
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* Update the strike count in account_strike table
|
|
16
|
-
*/
|
|
17
|
-
async updateSubjectStrikeCount(subjectDid: string): Promise<void> {
|
|
18
|
-
const now = new Date().toISOString()
|
|
19
|
-
|
|
20
|
-
// This should not incur too many rows since we tend to do permanent takedown on relatively low strike count
|
|
21
|
-
// and we have a very specific index to support this query
|
|
22
|
-
const events = await this.db.db
|
|
23
|
-
.selectFrom('moderation_event')
|
|
24
|
-
.where('subjectDid', '=', subjectDid)
|
|
25
|
-
.where('strikeCount', '<>', 0)
|
|
26
|
-
.select(['strikeCount', 'strikeExpiresAt', 'createdAt'])
|
|
27
|
-
.orderBy('createdAt', 'asc')
|
|
28
|
-
.execute()
|
|
29
|
-
|
|
30
|
-
if (!events.length) {
|
|
31
|
-
return
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
let activeStrikeCount = 0
|
|
35
|
-
let totalStrikeCount = 0
|
|
36
|
-
|
|
37
|
-
const firstStrikeAt = events[0].createdAt
|
|
38
|
-
const lastStrikeAt = events[events.length - 1].createdAt
|
|
39
|
-
|
|
40
|
-
for (const event of events) {
|
|
41
|
-
const strikeCount = event.strikeCount || 0
|
|
42
|
-
totalStrikeCount += strikeCount
|
|
43
|
-
|
|
44
|
-
// Count as active if not expired
|
|
45
|
-
const isActive =
|
|
46
|
-
event.strikeExpiresAt === null || event.strikeExpiresAt > now
|
|
47
|
-
if (isActive) {
|
|
48
|
-
activeStrikeCount += strikeCount
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
await this.db.db
|
|
53
|
-
.insertInto('account_strike')
|
|
54
|
-
.values({
|
|
55
|
-
did: subjectDid,
|
|
56
|
-
activeStrikeCount,
|
|
57
|
-
totalStrikeCount,
|
|
58
|
-
firstStrikeAt,
|
|
59
|
-
lastStrikeAt,
|
|
60
|
-
})
|
|
61
|
-
.onConflict((oc) =>
|
|
62
|
-
oc.column('did').doUpdateSet({
|
|
63
|
-
activeStrikeCount,
|
|
64
|
-
totalStrikeCount,
|
|
65
|
-
firstStrikeAt,
|
|
66
|
-
lastStrikeAt,
|
|
67
|
-
}),
|
|
68
|
-
)
|
|
69
|
-
.execute()
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
/**
|
|
73
|
-
* Get distinct subjects with expired strikes since a given timestamp
|
|
74
|
-
* Used by the strike expiry processor to find accounts that need strike count updates
|
|
75
|
-
*/
|
|
76
|
-
async getExpiredStrikeSubjects(
|
|
77
|
-
afterTimestamp?: string,
|
|
78
|
-
): Promise<Array<{ subjectDid: string }>> {
|
|
79
|
-
const now = new Date().toISOString()
|
|
80
|
-
|
|
81
|
-
let query = this.db.db
|
|
82
|
-
.selectFrom('moderation_event')
|
|
83
|
-
.where('strikeExpiresAt', 'is not', null)
|
|
84
|
-
.where('strikeExpiresAt', '<=', now)
|
|
85
|
-
.where('strikeCount', '<>', 0)
|
|
86
|
-
.select('subjectDid')
|
|
87
|
-
.distinct()
|
|
88
|
-
|
|
89
|
-
// Only process strikes that expired since the last run
|
|
90
|
-
if (afterTimestamp) {
|
|
91
|
-
query = query.where('strikeExpiresAt', '>=', afterTimestamp)
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
return await query.execute()
|
|
95
|
-
}
|
|
96
|
-
}
|