@atproto/ozone 0.1.150 → 0.1.152
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 +16 -0
- package/dist/api/moderation/queryEvents.d.ts.map +1 -1
- package/dist/api/moderation/queryEvents.js +2 -1
- package/dist/api/moderation/queryEvents.js.map +1 -1
- package/dist/context.d.ts +3 -0
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +7 -1
- package/dist/context.js.map +1 -1
- package/dist/daemon/context.d.ts +3 -0
- package/dist/daemon/context.d.ts.map +1 -1
- package/dist/daemon/context.js +11 -1
- package/dist/daemon/context.js.map +1 -1
- package/dist/daemon/index.d.ts +1 -0
- package/dist/daemon/index.d.ts.map +1 -1
- package/dist/daemon/index.js +3 -1
- package/dist/daemon/index.js.map +1 -1
- package/dist/daemon/strike-expiry-processor.d.ts +18 -0
- package/dist/daemon/strike-expiry-processor.d.ts.map +1 -0
- package/dist/daemon/strike-expiry-processor.js +111 -0
- package/dist/daemon/strike-expiry-processor.js.map +1 -0
- package/dist/db/migrations/20251008T120000000Z-add-strike-system.d.ts +4 -0
- package/dist/db/migrations/20251008T120000000Z-add-strike-system.d.ts.map +1 -0
- package/dist/db/migrations/20251008T120000000Z-add-strike-system.js +75 -0
- package/dist/db/migrations/20251008T120000000Z-add-strike-system.js.map +1 -0
- package/dist/db/migrations/index.d.ts +1 -0
- package/dist/db/migrations/index.d.ts.map +1 -1
- package/dist/db/migrations/index.js +2 -1
- package/dist/db/migrations/index.js.map +1 -1
- package/dist/db/schema/account_strike.d.ts +12 -0
- package/dist/db/schema/account_strike.d.ts.map +1 -0
- package/dist/db/schema/account_strike.js +5 -0
- package/dist/db/schema/account_strike.js.map +1 -0
- package/dist/db/schema/index.d.ts +3 -1
- package/dist/db/schema/index.d.ts.map +1 -1
- package/dist/db/schema/index.js.map +1 -1
- package/dist/db/schema/job_cursor.d.ts +11 -0
- package/dist/db/schema/job_cursor.d.ts.map +1 -0
- package/dist/db/schema/job_cursor.js +5 -0
- package/dist/db/schema/job_cursor.js.map +1 -0
- package/dist/db/schema/moderation_event.d.ts +3 -0
- package/dist/db/schema/moderation_event.d.ts.map +1 -1
- package/dist/db/schema/moderation_event.js.map +1 -1
- package/dist/lexicon/lexicons.d.ts +208 -0
- package/dist/lexicon/lexicons.d.ts.map +1 -1
- package/dist/lexicon/lexicons.js +104 -0
- package/dist/lexicon/lexicons.js.map +1 -1
- package/dist/lexicon/types/app/bsky/actor/defs.d.ts +12 -0
- package/dist/lexicon/types/app/bsky/actor/defs.d.ts.map +1 -1
- package/dist/lexicon/types/app/bsky/actor/defs.js.map +1 -1
- package/dist/lexicon/types/app/bsky/feed/defs.d.ts +4 -0
- package/dist/lexicon/types/app/bsky/feed/defs.d.ts.map +1 -1
- package/dist/lexicon/types/app/bsky/feed/defs.js.map +1 -1
- package/dist/lexicon/types/tools/ozone/moderation/defs.d.ts +35 -0
- package/dist/lexicon/types/tools/ozone/moderation/defs.d.ts.map +1 -1
- package/dist/lexicon/types/tools/ozone/moderation/defs.js +9 -0
- package/dist/lexicon/types/tools/ozone/moderation/defs.js.map +1 -1
- package/dist/lexicon/types/tools/ozone/moderation/queryEvents.d.ts +2 -0
- package/dist/lexicon/types/tools/ozone/moderation/queryEvents.d.ts.map +1 -1
- package/dist/lexicon/types/tools/ozone/moderation/queryEvents.js.map +1 -1
- package/dist/lexicon/types/tools/ozone/moderation/queryStatuses.d.ts +2 -0
- package/dist/lexicon/types/tools/ozone/moderation/queryStatuses.d.ts.map +1 -1
- package/dist/lexicon/types/tools/ozone/moderation/queryStatuses.js.map +1 -1
- package/dist/mod-service/index.d.ts +9 -3
- package/dist/mod-service/index.d.ts.map +1 -1
- package/dist/mod-service/index.js +59 -6
- package/dist/mod-service/index.js.map +1 -1
- package/dist/mod-service/status.d.ts +44 -2
- package/dist/mod-service/status.d.ts.map +1 -1
- package/dist/mod-service/status.js +7 -0
- package/dist/mod-service/status.js.map +1 -1
- package/dist/mod-service/strike.d.ts +19 -0
- package/dist/mod-service/strike.d.ts.map +1 -0
- package/dist/mod-service/strike.js +86 -0
- package/dist/mod-service/strike.js.map +1 -0
- package/dist/mod-service/types.d.ts +4 -0
- package/dist/mod-service/types.d.ts.map +1 -1
- package/dist/mod-service/types.js.map +1 -1
- package/dist/mod-service/views.d.ts.map +1 -1
- package/dist/mod-service/views.js +20 -4
- package/dist/mod-service/views.js.map +1 -1
- package/dist/setting/constants.d.ts +1 -0
- package/dist/setting/constants.d.ts.map +1 -1
- package/dist/setting/constants.js +2 -1
- package/dist/setting/constants.js.map +1 -1
- package/dist/setting/validators.d.ts.map +1 -1
- package/dist/setting/validators.js +179 -0
- package/dist/setting/validators.js.map +1 -1
- package/package.json +3 -3
- package/src/api/moderation/queryEvents.ts +2 -0
- package/src/context.ts +20 -11
- package/src/daemon/context.ts +15 -1
- package/src/daemon/index.ts +1 -0
- package/src/daemon/strike-expiry-processor.ts +111 -0
- package/src/db/migrations/20251008T120000000Z-add-strike-system.ts +87 -0
- package/src/db/migrations/index.ts +1 -0
- package/src/db/schema/account_strike.ts +13 -0
- package/src/db/schema/index.ts +4 -0
- package/src/db/schema/job_cursor.ts +13 -0
- package/src/db/schema/moderation_event.ts +3 -0
- package/src/lexicon/lexicons.ts +119 -0
- package/src/lexicon/types/app/bsky/actor/defs.ts +6 -0
- package/src/lexicon/types/app/bsky/feed/defs.ts +2 -0
- package/src/lexicon/types/tools/ozone/moderation/defs.ts +44 -0
- package/src/lexicon/types/tools/ozone/moderation/queryEvents.ts +2 -0
- package/src/lexicon/types/tools/ozone/moderation/queryStatuses.ts +2 -0
- package/src/mod-service/index.ts +70 -3
- package/src/mod-service/status.ts +9 -0
- package/src/mod-service/strike.ts +96 -0
- package/src/mod-service/types.ts +6 -0
- package/src/mod-service/views.ts +25 -4
- package/src/setting/constants.ts +1 -0
- package/src/setting/validators.ts +231 -1
- package/tests/__snapshots__/account-strikes.test.ts.snap +159 -0
- package/tests/account-strikes.test.ts +184 -0
- package/tests/query-labels.test.ts +1 -0
- package/tests/strike-expiry-processor.test.ts +299 -0
- package/tsconfig.build.tsbuildinfo +1 -1
- package/tsconfig.tests.tsbuildinfo +1 -1
package/src/context.ts
CHANGED
|
@@ -21,6 +21,7 @@ import {
|
|
|
21
21
|
ModerationServiceProfile,
|
|
22
22
|
ModerationServiceProfileCreator,
|
|
23
23
|
} from './mod-service/profile'
|
|
24
|
+
import { StrikeService, StrikeServiceCreator } from './mod-service/strike'
|
|
24
25
|
import {
|
|
25
26
|
SafelinkRuleService,
|
|
26
27
|
SafelinkRuleServiceCreator,
|
|
@@ -59,6 +60,7 @@ export type AppContextOptions = {
|
|
|
59
60
|
scheduledActionService: ScheduledActionServiceCreator
|
|
60
61
|
setService: SetServiceCreator
|
|
61
62
|
settingService: SettingServiceCreator
|
|
63
|
+
strikeService: StrikeServiceCreator
|
|
62
64
|
teamService: TeamServiceCreator
|
|
63
65
|
appviewAgent: AtpAgent
|
|
64
66
|
pdsAgent: AtpAgent | undefined
|
|
@@ -132,17 +134,6 @@ export class AppContext {
|
|
|
132
134
|
appview: cfg.appview.pushEvents ? cfg.appview : undefined,
|
|
133
135
|
pds: cfg.pds ?? undefined,
|
|
134
136
|
})
|
|
135
|
-
const modService = ModerationService.creator(
|
|
136
|
-
signingKey,
|
|
137
|
-
signingKeyId,
|
|
138
|
-
cfg,
|
|
139
|
-
backgroundQueue,
|
|
140
|
-
idResolver,
|
|
141
|
-
eventPusher,
|
|
142
|
-
appviewAgent,
|
|
143
|
-
createAuthHeaders,
|
|
144
|
-
overrides?.imgInvalidator,
|
|
145
|
-
)
|
|
146
137
|
|
|
147
138
|
const communicationTemplateService = CommunicationTemplateService.creator()
|
|
148
139
|
const safelinkRuleService = SafelinkRuleService.creator()
|
|
@@ -154,12 +145,25 @@ export class AppContext {
|
|
|
154
145
|
)
|
|
155
146
|
const setService = SetService.creator()
|
|
156
147
|
const settingService = SettingService.creator()
|
|
148
|
+
const strikeService = StrikeService.creator()
|
|
157
149
|
const verificationService = VerificationService.creator()
|
|
158
150
|
const verificationIssuer = VerificationIssuer.creator()
|
|
159
151
|
const moderationServiceProfile = ModerationServiceProfile.creator(
|
|
160
152
|
cfg,
|
|
161
153
|
appviewAgent,
|
|
162
154
|
)
|
|
155
|
+
const modService = ModerationService.creator(
|
|
156
|
+
signingKey,
|
|
157
|
+
signingKeyId,
|
|
158
|
+
cfg,
|
|
159
|
+
backgroundQueue,
|
|
160
|
+
idResolver,
|
|
161
|
+
eventPusher,
|
|
162
|
+
appviewAgent,
|
|
163
|
+
createAuthHeaders,
|
|
164
|
+
strikeService,
|
|
165
|
+
overrides?.imgInvalidator,
|
|
166
|
+
)
|
|
163
167
|
|
|
164
168
|
const sequencer = new Sequencer(modService(db))
|
|
165
169
|
|
|
@@ -181,6 +185,7 @@ export class AppContext {
|
|
|
181
185
|
teamService,
|
|
182
186
|
setService,
|
|
183
187
|
settingService,
|
|
188
|
+
strikeService,
|
|
184
189
|
appviewAgent,
|
|
185
190
|
pdsAgent,
|
|
186
191
|
chatAgent,
|
|
@@ -248,6 +253,10 @@ export class AppContext {
|
|
|
248
253
|
return this.opts.settingService
|
|
249
254
|
}
|
|
250
255
|
|
|
256
|
+
get strikeService(): StrikeServiceCreator {
|
|
257
|
+
return this.opts.strikeService
|
|
258
|
+
}
|
|
259
|
+
|
|
251
260
|
get verificationService(): VerificationServiceCreator {
|
|
252
261
|
return this.opts.verificationService
|
|
253
262
|
}
|
package/src/daemon/context.ts
CHANGED
|
@@ -7,6 +7,7 @@ import { BackgroundQueue } from '../background'
|
|
|
7
7
|
import { OzoneConfig, OzoneSecrets } from '../config'
|
|
8
8
|
import { Database } from '../db'
|
|
9
9
|
import { ModerationService } from '../mod-service'
|
|
10
|
+
import { StrikeService } from '../mod-service/strike'
|
|
10
11
|
import { ScheduledActionService } from '../scheduled-action/service'
|
|
11
12
|
import { SettingService } from '../setting/service'
|
|
12
13
|
import { TeamService } from '../team'
|
|
@@ -15,6 +16,7 @@ import { EventPusher } from './event-pusher'
|
|
|
15
16
|
import { EventReverser } from './event-reverser'
|
|
16
17
|
import { MaterializedViewRefresher } from './materialized-view-refresher'
|
|
17
18
|
import { ScheduledActionProcessor } from './scheduled-action-processor'
|
|
19
|
+
import { StrikeExpiryProcessor } from './strike-expiry-processor'
|
|
18
20
|
import { TeamProfileSynchronizer } from './team-profile-synchronizer'
|
|
19
21
|
import { VerificationListener } from './verification-listener'
|
|
20
22
|
|
|
@@ -28,6 +30,7 @@ export type DaemonContextOptions = {
|
|
|
28
30
|
materializedViewRefresher: MaterializedViewRefresher
|
|
29
31
|
teamProfileSynchronizer: TeamProfileSynchronizer
|
|
30
32
|
scheduledActionProcessor: ScheduledActionProcessor
|
|
33
|
+
strikeExpiryProcessor: StrikeExpiryProcessor
|
|
31
34
|
verificationListener?: VerificationListener
|
|
32
35
|
}
|
|
33
36
|
|
|
@@ -66,6 +69,8 @@ export class DaemonContext {
|
|
|
66
69
|
|
|
67
70
|
const backgroundQueue = new BackgroundQueue(db)
|
|
68
71
|
|
|
72
|
+
const settingService = SettingService.creator()
|
|
73
|
+
const strikeService = StrikeService.creator()
|
|
69
74
|
const modService = ModerationService.creator(
|
|
70
75
|
signingKey,
|
|
71
76
|
signingKeyId,
|
|
@@ -75,8 +80,8 @@ export class DaemonContext {
|
|
|
75
80
|
eventPusher,
|
|
76
81
|
appviewAgent,
|
|
77
82
|
createAuthHeaders,
|
|
83
|
+
strikeService,
|
|
78
84
|
)
|
|
79
|
-
const settingService = SettingService.creator()
|
|
80
85
|
const scheduledActionService = ScheduledActionService.creator()
|
|
81
86
|
const teamService = TeamService.creator(
|
|
82
87
|
appviewAgent,
|
|
@@ -104,6 +109,8 @@ export class DaemonContext {
|
|
|
104
109
|
scheduledActionService,
|
|
105
110
|
)
|
|
106
111
|
|
|
112
|
+
const strikeExpiryProcessor = new StrikeExpiryProcessor(db, strikeService)
|
|
113
|
+
|
|
107
114
|
// Only spawn the listener if verifier config exists and a jetstream URL is provided
|
|
108
115
|
const verificationListener =
|
|
109
116
|
cfg.verifier && cfg.jetstreamUrl
|
|
@@ -124,6 +131,7 @@ export class DaemonContext {
|
|
|
124
131
|
materializedViewRefresher,
|
|
125
132
|
teamProfileSynchronizer,
|
|
126
133
|
scheduledActionProcessor,
|
|
134
|
+
strikeExpiryProcessor,
|
|
127
135
|
verificationListener,
|
|
128
136
|
...(overrides ?? {}),
|
|
129
137
|
})
|
|
@@ -161,6 +169,10 @@ export class DaemonContext {
|
|
|
161
169
|
return this.opts.scheduledActionProcessor
|
|
162
170
|
}
|
|
163
171
|
|
|
172
|
+
get strikeExpiryProcessor(): StrikeExpiryProcessor {
|
|
173
|
+
return this.opts.strikeExpiryProcessor
|
|
174
|
+
}
|
|
175
|
+
|
|
164
176
|
get verificationListener(): VerificationListener | undefined {
|
|
165
177
|
return this.opts.verificationListener
|
|
166
178
|
}
|
|
@@ -171,6 +183,7 @@ export class DaemonContext {
|
|
|
171
183
|
this.materializedViewRefresher.start()
|
|
172
184
|
this.teamProfileSynchronizer.start()
|
|
173
185
|
this.scheduledActionProcessor.start()
|
|
186
|
+
this.strikeExpiryProcessor.start()
|
|
174
187
|
this.verificationListener?.start()
|
|
175
188
|
}
|
|
176
189
|
|
|
@@ -189,6 +202,7 @@ export class DaemonContext {
|
|
|
189
202
|
this.materializedViewRefresher.destroy(),
|
|
190
203
|
this.teamProfileSynchronizer.destroy(),
|
|
191
204
|
this.scheduledActionProcessor.destroy(),
|
|
205
|
+
this.strikeExpiryProcessor.destroy(),
|
|
192
206
|
this.verificationListener?.stop(),
|
|
193
207
|
])
|
|
194
208
|
} finally {
|
package/src/daemon/index.ts
CHANGED
|
@@ -6,6 +6,7 @@ export { EventPusher } from './event-pusher'
|
|
|
6
6
|
export { BlobDiverter } from './blob-diverter'
|
|
7
7
|
export { EventReverser } from './event-reverser'
|
|
8
8
|
export { ScheduledActionProcessor } from './scheduled-action-processor'
|
|
9
|
+
export { StrikeExpiryProcessor } from './strike-expiry-processor'
|
|
9
10
|
|
|
10
11
|
export class OzoneDaemon {
|
|
11
12
|
constructor(public ctx: DaemonContext) {}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { HOUR } from '@atproto/common'
|
|
2
|
+
import { Database } from '../db'
|
|
3
|
+
import { dbLogger } from '../logger'
|
|
4
|
+
import { StrikeServiceCreator } from '../mod-service/strike'
|
|
5
|
+
|
|
6
|
+
const JOB_NAME = 'strike_expiry'
|
|
7
|
+
|
|
8
|
+
export class StrikeExpiryProcessor {
|
|
9
|
+
destroyed = false
|
|
10
|
+
processingPromise: Promise<void> = Promise.resolve()
|
|
11
|
+
timer?: NodeJS.Timeout
|
|
12
|
+
|
|
13
|
+
constructor(
|
|
14
|
+
private db: Database,
|
|
15
|
+
private strikeServiceCreator: StrikeServiceCreator,
|
|
16
|
+
) {}
|
|
17
|
+
|
|
18
|
+
start() {
|
|
19
|
+
this.initializeCursor().then(() => this.poll())
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
poll() {
|
|
23
|
+
if (this.destroyed) return
|
|
24
|
+
this.processingPromise = this.processExpiredStrikes()
|
|
25
|
+
.catch((err) =>
|
|
26
|
+
dbLogger.error({ err }, 'strike expiry processing errored'),
|
|
27
|
+
)
|
|
28
|
+
.finally(() => {
|
|
29
|
+
this.timer = setTimeout(() => this.poll(), getInterval())
|
|
30
|
+
})
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async destroy() {
|
|
34
|
+
this.destroyed = true
|
|
35
|
+
if (this.timer) {
|
|
36
|
+
clearTimeout(this.timer)
|
|
37
|
+
this.timer = undefined
|
|
38
|
+
}
|
|
39
|
+
await this.processingPromise
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async initializeCursor() {
|
|
43
|
+
await this.db.db
|
|
44
|
+
.insertInto('job_cursor')
|
|
45
|
+
.values({
|
|
46
|
+
job: JOB_NAME,
|
|
47
|
+
cursor: null,
|
|
48
|
+
})
|
|
49
|
+
.onConflict((oc) => oc.doNothing())
|
|
50
|
+
.execute()
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async getCursor(): Promise<string | null> {
|
|
54
|
+
const entry = await this.db.db
|
|
55
|
+
.selectFrom('job_cursor')
|
|
56
|
+
.select('cursor')
|
|
57
|
+
.where('job', '=', JOB_NAME)
|
|
58
|
+
.executeTakeFirst()
|
|
59
|
+
|
|
60
|
+
return entry?.cursor || null
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
async updateCursor(cursor: string): Promise<void> {
|
|
64
|
+
await this.db.db
|
|
65
|
+
.updateTable('job_cursor')
|
|
66
|
+
.set({ cursor })
|
|
67
|
+
.where('job', '=', JOB_NAME)
|
|
68
|
+
.execute()
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
async processExpiredStrikes() {
|
|
72
|
+
const now = new Date()
|
|
73
|
+
const strikeService = this.strikeServiceCreator(this.db)
|
|
74
|
+
const lastProcessedAt = await this.getCursor()
|
|
75
|
+
const affectedSubjects = await strikeService.getExpiredStrikeSubjects(
|
|
76
|
+
lastProcessedAt || undefined,
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
if (!affectedSubjects.length) {
|
|
80
|
+
dbLogger.info('no expired strikes to process')
|
|
81
|
+
await this.updateCursor(now.toISOString())
|
|
82
|
+
return
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
dbLogger.info(
|
|
86
|
+
{ count: affectedSubjects.length },
|
|
87
|
+
'processing subjects with expired strikes',
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
await Promise.all(
|
|
91
|
+
affectedSubjects.map(({ subjectDid }) => {
|
|
92
|
+
return strikeService.updateSubjectStrikeCount(subjectDid)
|
|
93
|
+
}),
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
await this.updateCursor(now.toISOString())
|
|
97
|
+
|
|
98
|
+
dbLogger.info(
|
|
99
|
+
{ processed: affectedSubjects.length },
|
|
100
|
+
'strike expiry processing completed',
|
|
101
|
+
)
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const getInterval = (): number => {
|
|
106
|
+
// Run every hour, synchronized to the hour boundary
|
|
107
|
+
const now = Date.now()
|
|
108
|
+
const intervalMs = HOUR
|
|
109
|
+
const nextIteration = Math.ceil(now / intervalMs)
|
|
110
|
+
return nextIteration * intervalMs - now
|
|
111
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { Kysely, sql } from 'kysely'
|
|
2
|
+
|
|
3
|
+
export async function up(db: Kysely<unknown>): Promise<void> {
|
|
4
|
+
await db.schema
|
|
5
|
+
.alterTable('moderation_event')
|
|
6
|
+
.addColumn('severityLevel', 'varchar')
|
|
7
|
+
.execute()
|
|
8
|
+
await db.schema
|
|
9
|
+
.alterTable('moderation_event')
|
|
10
|
+
.addColumn('strikeCount', 'integer')
|
|
11
|
+
.execute()
|
|
12
|
+
await db.schema
|
|
13
|
+
.alterTable('moderation_event')
|
|
14
|
+
.addColumn('strikeExpiresAt', 'varchar')
|
|
15
|
+
.execute()
|
|
16
|
+
|
|
17
|
+
await db.schema
|
|
18
|
+
.createTable('account_strike')
|
|
19
|
+
.addColumn('did', 'text', (col) => col.primaryKey())
|
|
20
|
+
.addColumn('firstStrikeAt', 'varchar')
|
|
21
|
+
.addColumn('lastStrikeAt', 'varchar')
|
|
22
|
+
.addColumn('activeStrikeCount', 'integer', (col) =>
|
|
23
|
+
col.notNull().defaultTo(0),
|
|
24
|
+
)
|
|
25
|
+
.addColumn('totalStrikeCount', 'integer', (col) =>
|
|
26
|
+
col.notNull().defaultTo(0),
|
|
27
|
+
)
|
|
28
|
+
.execute()
|
|
29
|
+
|
|
30
|
+
await db.schema
|
|
31
|
+
.createTable('job_cursor')
|
|
32
|
+
.addColumn('job', 'text', (col) => col.primaryKey())
|
|
33
|
+
.addColumn('cursor', 'text')
|
|
34
|
+
.addColumn('updatedAt', 'text', (col) =>
|
|
35
|
+
col.defaultTo(sql`now()`).notNull(),
|
|
36
|
+
)
|
|
37
|
+
.execute()
|
|
38
|
+
|
|
39
|
+
// This supports fast look up for background job that aggregates strike data per subjectDid
|
|
40
|
+
await db.schema
|
|
41
|
+
.createIndex('moderation_event_subject_did_strike_count_idx')
|
|
42
|
+
.on('moderation_event')
|
|
43
|
+
.columns(['subjectDid', 'strikeCount'])
|
|
44
|
+
.execute()
|
|
45
|
+
|
|
46
|
+
// This supports fast lookup in the background job that needs to find strikes that have expired
|
|
47
|
+
await sql`
|
|
48
|
+
CREATE INDEX moderation_event_strike_expires_at_strike_count_idx
|
|
49
|
+
ON moderation_event ("strikeExpiresAt", "strikeCount")
|
|
50
|
+
WHERE "strikeExpiresAt" IS NOT NULL AND "strikeCount" IS NOT NULL
|
|
51
|
+
`.execute(db)
|
|
52
|
+
|
|
53
|
+
// for sorting and filtering by active strike count
|
|
54
|
+
await db.schema
|
|
55
|
+
.createIndex('account_strike_active_count_idx')
|
|
56
|
+
.on('account_strike')
|
|
57
|
+
.column('activeStrikeCount')
|
|
58
|
+
.execute()
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export async function down(db: Kysely<unknown>): Promise<void> {
|
|
62
|
+
await db.schema
|
|
63
|
+
.dropIndex('moderation_event_subject_did_strike_count_idx')
|
|
64
|
+
.execute()
|
|
65
|
+
await db.schema
|
|
66
|
+
.dropIndex('moderation_event_strike_expires_at_strike_count_idx')
|
|
67
|
+
.execute()
|
|
68
|
+
await db.schema.dropIndex('account_strike_active_count_idx').execute()
|
|
69
|
+
|
|
70
|
+
await db.schema.dropTable('account_strike').execute()
|
|
71
|
+
await db.schema.dropTable('job_cursor').execute()
|
|
72
|
+
|
|
73
|
+
await db.schema
|
|
74
|
+
.alterTable('moderation_event')
|
|
75
|
+
.dropColumn('severityLevel')
|
|
76
|
+
.execute()
|
|
77
|
+
|
|
78
|
+
await db.schema
|
|
79
|
+
.alterTable('moderation_event')
|
|
80
|
+
.dropColumn('strikeCount')
|
|
81
|
+
.execute()
|
|
82
|
+
|
|
83
|
+
await db.schema
|
|
84
|
+
.alterTable('moderation_event')
|
|
85
|
+
.dropColumn('strikeExpiresAt')
|
|
86
|
+
.execute()
|
|
87
|
+
}
|
|
@@ -32,3 +32,4 @@ export * as _20250715T000000000Z from './20250715T000000000Z-add-mod-event-exter
|
|
|
32
32
|
export * as _20250718T150931000Z from './20250718T150931000Z-update-appeal-reason-stats'
|
|
33
33
|
export * as _20250813T000000000Z from './20250813T000000000Z-mod-tool-batch-id-index'
|
|
34
34
|
export * as _20250923T000000000Z from './20250923T000000000Z-scheduled-actions'
|
|
35
|
+
export * as _20251008T120000000Z from './20251008T120000000Z-add-strike-system'
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export const accountStrikeTableName = 'account_strike'
|
|
2
|
+
|
|
3
|
+
export interface AccountStrike {
|
|
4
|
+
did: string // Primary key
|
|
5
|
+
firstStrikeAt: string | null
|
|
6
|
+
lastStrikeAt: string | null
|
|
7
|
+
activeStrikeCount: number
|
|
8
|
+
totalStrikeCount: number
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export type PartialDB = {
|
|
12
|
+
[accountStrikeTableName]: AccountStrike
|
|
13
|
+
}
|
package/src/db/schema/index.ts
CHANGED
|
@@ -2,9 +2,11 @@ import { Kysely } from 'kysely'
|
|
|
2
2
|
import * as accountEventsStats from './account_events_stats'
|
|
3
3
|
import * as accountRecordEventsStats from './account_record_events_stats'
|
|
4
4
|
import * as accountRecordStatusStats from './account_record_status_stats'
|
|
5
|
+
import * as accountStrike from './account_strike'
|
|
5
6
|
import * as blobPushEvent from './blob_push_event'
|
|
6
7
|
import * as communicationTemplate from './communication_template'
|
|
7
8
|
import * as firehoseCursor from './firehose_cursor'
|
|
9
|
+
import * as jobCursor from './job_cursor'
|
|
8
10
|
import * as label from './label'
|
|
9
11
|
import * as member from './member'
|
|
10
12
|
import * as modEvent from './moderation_event'
|
|
@@ -34,8 +36,10 @@ export type DatabaseSchemaType = modEvent.PartialDB &
|
|
|
34
36
|
recordEventsStats.PartialDB &
|
|
35
37
|
accountRecordEventsStats.PartialDB &
|
|
36
38
|
accountRecordStatusStats.PartialDB &
|
|
39
|
+
accountStrike.PartialDB &
|
|
37
40
|
verification.PartialDB &
|
|
38
41
|
firehoseCursor.PartialDB &
|
|
42
|
+
jobCursor.PartialDB &
|
|
39
43
|
safelink.PartialDB &
|
|
40
44
|
scheduledAction.PartialDB
|
|
41
45
|
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Generated } from 'kysely'
|
|
2
|
+
|
|
3
|
+
export const jobCursorTableName = 'job_cursor'
|
|
4
|
+
|
|
5
|
+
export interface JobCursor {
|
|
6
|
+
job: string
|
|
7
|
+
cursor: string | null
|
|
8
|
+
updatedAt: Generated<string>
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export type PartialDB = {
|
|
12
|
+
[jobCursorTableName]: JobCursor
|
|
13
|
+
}
|
|
@@ -48,6 +48,9 @@ export interface ModerationEvent {
|
|
|
48
48
|
legacyRefId: number | null
|
|
49
49
|
modTool: { name: string; meta?: { [_ in string]: unknown } } | null
|
|
50
50
|
externalId: string | null
|
|
51
|
+
severityLevel: string | null
|
|
52
|
+
strikeCount: number | null
|
|
53
|
+
strikeExpiresAt: string | null
|
|
51
54
|
}
|
|
52
55
|
|
|
53
56
|
export type PartialDB = {
|
package/src/lexicon/lexicons.ts
CHANGED
|
@@ -65,6 +65,10 @@ export const schemaDict = {
|
|
|
65
65
|
type: 'ref',
|
|
66
66
|
ref: 'lex:app.bsky.actor.defs#statusView',
|
|
67
67
|
},
|
|
68
|
+
debug: {
|
|
69
|
+
type: 'unknown',
|
|
70
|
+
description: 'Debug information for internal development',
|
|
71
|
+
},
|
|
68
72
|
},
|
|
69
73
|
},
|
|
70
74
|
profileView: {
|
|
@@ -127,6 +131,10 @@ export const schemaDict = {
|
|
|
127
131
|
type: 'ref',
|
|
128
132
|
ref: 'lex:app.bsky.actor.defs#statusView',
|
|
129
133
|
},
|
|
134
|
+
debug: {
|
|
135
|
+
type: 'unknown',
|
|
136
|
+
description: 'Debug information for internal development',
|
|
137
|
+
},
|
|
130
138
|
},
|
|
131
139
|
},
|
|
132
140
|
profileViewDetailed: {
|
|
@@ -214,6 +222,10 @@ export const schemaDict = {
|
|
|
214
222
|
type: 'ref',
|
|
215
223
|
ref: 'lex:app.bsky.actor.defs#statusView',
|
|
216
224
|
},
|
|
225
|
+
debug: {
|
|
226
|
+
type: 'unknown',
|
|
227
|
+
description: 'Debug information for internal development',
|
|
228
|
+
},
|
|
217
229
|
},
|
|
218
230
|
},
|
|
219
231
|
profileAssociated: {
|
|
@@ -1846,6 +1858,10 @@ export const schemaDict = {
|
|
|
1846
1858
|
type: 'ref',
|
|
1847
1859
|
ref: 'lex:app.bsky.feed.defs#threadgateView',
|
|
1848
1860
|
},
|
|
1861
|
+
debug: {
|
|
1862
|
+
type: 'unknown',
|
|
1863
|
+
description: 'Debug information for internal development',
|
|
1864
|
+
},
|
|
1849
1865
|
},
|
|
1850
1866
|
},
|
|
1851
1867
|
viewerState: {
|
|
@@ -14630,6 +14646,12 @@ export const schemaDict = {
|
|
|
14630
14646
|
type: 'ref',
|
|
14631
14647
|
ref: 'lex:tools.ozone.moderation.defs#recordsStats',
|
|
14632
14648
|
},
|
|
14649
|
+
accountStrike: {
|
|
14650
|
+
description:
|
|
14651
|
+
'Strike information for the account (account-level only)',
|
|
14652
|
+
type: 'ref',
|
|
14653
|
+
ref: 'lex:tools.ozone.moderation.defs#accountStrike',
|
|
14654
|
+
},
|
|
14633
14655
|
ageAssuranceState: {
|
|
14634
14656
|
type: 'string',
|
|
14635
14657
|
description: 'Current age assurance state of the subject.',
|
|
@@ -14742,6 +14764,32 @@ export const schemaDict = {
|
|
|
14742
14764
|
},
|
|
14743
14765
|
},
|
|
14744
14766
|
},
|
|
14767
|
+
accountStrike: {
|
|
14768
|
+
description: 'Strike information for an account',
|
|
14769
|
+
type: 'object',
|
|
14770
|
+
properties: {
|
|
14771
|
+
activeStrikeCount: {
|
|
14772
|
+
description:
|
|
14773
|
+
'Current number of active strikes (excluding expired strikes)',
|
|
14774
|
+
type: 'integer',
|
|
14775
|
+
},
|
|
14776
|
+
totalStrikeCount: {
|
|
14777
|
+
description:
|
|
14778
|
+
'Total number of strikes ever received (including expired strikes)',
|
|
14779
|
+
type: 'integer',
|
|
14780
|
+
},
|
|
14781
|
+
firstStrikeAt: {
|
|
14782
|
+
description: 'Timestamp of the first strike received',
|
|
14783
|
+
type: 'string',
|
|
14784
|
+
format: 'datetime',
|
|
14785
|
+
},
|
|
14786
|
+
lastStrikeAt: {
|
|
14787
|
+
description: 'Timestamp of the most recent strike received',
|
|
14788
|
+
type: 'string',
|
|
14789
|
+
format: 'datetime',
|
|
14790
|
+
},
|
|
14791
|
+
},
|
|
14792
|
+
},
|
|
14745
14793
|
subjectReviewState: {
|
|
14746
14794
|
type: 'string',
|
|
14747
14795
|
knownValues: [
|
|
@@ -14797,6 +14845,22 @@ export const schemaDict = {
|
|
|
14797
14845
|
description:
|
|
14798
14846
|
'Names/Keywords of the policies that drove the decision.',
|
|
14799
14847
|
},
|
|
14848
|
+
severityLevel: {
|
|
14849
|
+
type: 'string',
|
|
14850
|
+
description:
|
|
14851
|
+
"Severity level of the violation (e.g., 'sev-0', 'sev-1', 'sev-2', etc.).",
|
|
14852
|
+
},
|
|
14853
|
+
strikeCount: {
|
|
14854
|
+
type: 'integer',
|
|
14855
|
+
description:
|
|
14856
|
+
'Number of strikes to assign to the user for this violation.',
|
|
14857
|
+
},
|
|
14858
|
+
strikeExpiresAt: {
|
|
14859
|
+
type: 'string',
|
|
14860
|
+
format: 'datetime',
|
|
14861
|
+
description:
|
|
14862
|
+
'When the strike should expire. If not provided, the strike never expires.',
|
|
14863
|
+
},
|
|
14800
14864
|
},
|
|
14801
14865
|
},
|
|
14802
14866
|
modEventReverseTakedown: {
|
|
@@ -14807,6 +14871,25 @@ export const schemaDict = {
|
|
|
14807
14871
|
type: 'string',
|
|
14808
14872
|
description: 'Describe reasoning behind the reversal.',
|
|
14809
14873
|
},
|
|
14874
|
+
policies: {
|
|
14875
|
+
type: 'array',
|
|
14876
|
+
maxLength: 5,
|
|
14877
|
+
items: {
|
|
14878
|
+
type: 'string',
|
|
14879
|
+
},
|
|
14880
|
+
description:
|
|
14881
|
+
'Names/Keywords of the policy infraction for which takedown is being reversed.',
|
|
14882
|
+
},
|
|
14883
|
+
severityLevel: {
|
|
14884
|
+
type: 'string',
|
|
14885
|
+
description:
|
|
14886
|
+
"Severity level of the violation. Usually set from the last policy infraction's severity.",
|
|
14887
|
+
},
|
|
14888
|
+
strikeCount: {
|
|
14889
|
+
type: 'integer',
|
|
14890
|
+
description:
|
|
14891
|
+
"Number of strikes to subtract from the user's strike count. Usually set from the last policy infraction's severity.",
|
|
14892
|
+
},
|
|
14810
14893
|
},
|
|
14811
14894
|
},
|
|
14812
14895
|
modEventResolveAppeal: {
|
|
@@ -15050,6 +15133,31 @@ export const schemaDict = {
|
|
|
15050
15133
|
type: 'string',
|
|
15051
15134
|
description: 'Additional comment about the outgoing comm.',
|
|
15052
15135
|
},
|
|
15136
|
+
policies: {
|
|
15137
|
+
type: 'array',
|
|
15138
|
+
maxLength: 5,
|
|
15139
|
+
items: {
|
|
15140
|
+
type: 'string',
|
|
15141
|
+
},
|
|
15142
|
+
description:
|
|
15143
|
+
'Names/Keywords of the policies that necessitated the email.',
|
|
15144
|
+
},
|
|
15145
|
+
severityLevel: {
|
|
15146
|
+
type: 'string',
|
|
15147
|
+
description:
|
|
15148
|
+
"Severity level of the violation. Normally 'sev-1' that adds strike on repeat offense",
|
|
15149
|
+
},
|
|
15150
|
+
strikeCount: {
|
|
15151
|
+
type: 'integer',
|
|
15152
|
+
description:
|
|
15153
|
+
'Number of strikes to assign to the user for this violation. Normally 0 as an indicator of a warning and only added as a strike on a repeat offense.',
|
|
15154
|
+
},
|
|
15155
|
+
strikeExpiresAt: {
|
|
15156
|
+
type: 'string',
|
|
15157
|
+
format: 'datetime',
|
|
15158
|
+
description:
|
|
15159
|
+
'When the strike should expire. If not provided, the strike never expires.',
|
|
15160
|
+
},
|
|
15053
15161
|
},
|
|
15054
15162
|
},
|
|
15055
15163
|
modEventDivert: {
|
|
@@ -16448,6 +16556,11 @@ export const schemaDict = {
|
|
|
16448
16556
|
'blocked',
|
|
16449
16557
|
],
|
|
16450
16558
|
},
|
|
16559
|
+
withStrike: {
|
|
16560
|
+
type: 'boolean',
|
|
16561
|
+
description:
|
|
16562
|
+
'If specified, only events where strikeCount value is set are returned.',
|
|
16563
|
+
},
|
|
16451
16564
|
cursor: {
|
|
16452
16565
|
type: 'string',
|
|
16453
16566
|
},
|
|
@@ -16678,6 +16791,12 @@ export const schemaDict = {
|
|
|
16678
16791
|
description:
|
|
16679
16792
|
'If specified, only subjects that have priority score value above the given value will be returned.',
|
|
16680
16793
|
},
|
|
16794
|
+
minStrikeCount: {
|
|
16795
|
+
type: 'integer',
|
|
16796
|
+
minimum: 1,
|
|
16797
|
+
description:
|
|
16798
|
+
'If specified, only subjects that belong to an account that has at least this many active strikes will be returned.',
|
|
16799
|
+
},
|
|
16681
16800
|
ageAssuranceState: {
|
|
16682
16801
|
type: 'string',
|
|
16683
16802
|
description:
|
|
@@ -34,6 +34,8 @@ export interface ProfileViewBasic {
|
|
|
34
34
|
createdAt?: string
|
|
35
35
|
verification?: VerificationState
|
|
36
36
|
status?: StatusView
|
|
37
|
+
/** Debug information for internal development */
|
|
38
|
+
debug?: { [_ in string]: unknown }
|
|
37
39
|
}
|
|
38
40
|
|
|
39
41
|
const hashProfileViewBasic = 'profileViewBasic'
|
|
@@ -61,6 +63,8 @@ export interface ProfileView {
|
|
|
61
63
|
labels?: ComAtprotoLabelDefs.Label[]
|
|
62
64
|
verification?: VerificationState
|
|
63
65
|
status?: StatusView
|
|
66
|
+
/** Debug information for internal development */
|
|
67
|
+
debug?: { [_ in string]: unknown }
|
|
64
68
|
}
|
|
65
69
|
|
|
66
70
|
const hashProfileView = 'profileView'
|
|
@@ -95,6 +99,8 @@ export interface ProfileViewDetailed {
|
|
|
95
99
|
pinnedPost?: ComAtprotoRepoStrongRef.Main
|
|
96
100
|
verification?: VerificationState
|
|
97
101
|
status?: StatusView
|
|
102
|
+
/** Debug information for internal development */
|
|
103
|
+
debug?: { [_ in string]: unknown }
|
|
98
104
|
}
|
|
99
105
|
|
|
100
106
|
const hashProfileViewDetailed = 'profileViewDetailed'
|