@atproto/ozone 0.1.68 → 0.1.70
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/queryStatuses.d.ts.map +1 -1
- package/dist/api/moderation/queryStatuses.js +1 -33
- package/dist/api/moderation/queryStatuses.js.map +1 -1
- package/dist/background.d.ts +49 -6
- package/dist/background.d.ts.map +1 -1
- package/dist/background.js +149 -14
- package/dist/background.js.map +1 -1
- package/dist/config/config.d.ts +1 -0
- package/dist/config/config.d.ts.map +1 -1
- package/dist/config/config.js +1 -0
- package/dist/config/config.js.map +1 -1
- package/dist/config/env.d.ts +1 -0
- package/dist/config/env.d.ts.map +1 -1
- package/dist/config/env.js +1 -0
- package/dist/config/env.js.map +1 -1
- package/dist/daemon/context.d.ts +9 -3
- package/dist/daemon/context.d.ts.map +1 -1
- package/dist/daemon/context.js +33 -3
- package/dist/daemon/context.js.map +1 -1
- package/dist/daemon/index.d.ts.map +1 -1
- package/dist/daemon/index.js +3 -6
- package/dist/daemon/index.js.map +1 -1
- package/dist/daemon/materialized-view-refresher.d.ts +5 -0
- package/dist/daemon/materialized-view-refresher.d.ts.map +1 -0
- package/dist/daemon/materialized-view-refresher.js +29 -0
- package/dist/daemon/materialized-view-refresher.js.map +1 -0
- package/dist/db/migrations/20241220T144630860Z-stats-materialized-views.d.ts +5 -0
- package/dist/db/migrations/20241220T144630860Z-stats-materialized-views.d.ts.map +1 -0
- package/dist/db/migrations/20241220T144630860Z-stats-materialized-views.js +158 -0
- package/dist/db/migrations/20241220T144630860Z-stats-materialized-views.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_events_stats.d.ts +15 -0
- package/dist/db/schema/account_events_stats.d.ts.map +1 -0
- package/dist/db/schema/account_events_stats.js +5 -0
- package/dist/db/schema/account_events_stats.js.map +1 -0
- package/dist/db/schema/account_record_events_stats.d.ts +15 -0
- package/dist/db/schema/account_record_events_stats.d.ts.map +1 -0
- package/dist/db/schema/account_record_events_stats.js +5 -0
- package/dist/db/schema/account_record_events_stats.js.map +1 -0
- package/dist/db/schema/account_record_status_stats.d.ts +15 -0
- package/dist/db/schema/account_record_status_stats.d.ts.map +1 -0
- package/dist/db/schema/account_record_status_stats.js +5 -0
- package/dist/db/schema/account_record_status_stats.js.map +1 -0
- package/dist/db/schema/index.d.ts +5 -1
- package/dist/db/schema/index.d.ts.map +1 -1
- package/dist/db/schema/record_events_stats.d.ts +14 -0
- package/dist/db/schema/record_events_stats.d.ts.map +1 -0
- package/dist/db/schema/record_events_stats.js +5 -0
- package/dist/db/schema/record_events_stats.js.map +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -4
- package/dist/index.js.map +1 -1
- package/dist/lexicon/index.d.ts +2 -0
- package/dist/lexicon/index.d.ts.map +1 -1
- package/dist/lexicon/index.js +2 -0
- package/dist/lexicon/index.js.map +1 -1
- package/dist/lexicon/lexicons.d.ts +230 -2
- package/dist/lexicon/lexicons.d.ts.map +1 -1
- package/dist/lexicon/lexicons.js +126 -1
- package/dist/lexicon/lexicons.js.map +1 -1
- package/dist/lexicon/types/app/bsky/feed/defs.d.ts +5 -0
- package/dist/lexicon/types/app/bsky/feed/defs.d.ts.map +1 -1
- package/dist/lexicon/types/app/bsky/feed/defs.js +5 -1
- package/dist/lexicon/types/app/bsky/feed/defs.js.map +1 -1
- package/dist/lexicon/types/app/bsky/feed/generator.d.ts +1 -0
- package/dist/lexicon/types/app/bsky/feed/generator.d.ts.map +1 -1
- package/dist/lexicon/types/app/bsky/feed/generator.js.map +1 -1
- package/dist/lexicon/types/tools/ozone/moderation/defs.d.ts +40 -0
- package/dist/lexicon/types/tools/ozone/moderation/defs.d.ts.map +1 -1
- package/dist/lexicon/types/tools/ozone/moderation/defs.js +20 -0
- package/dist/lexicon/types/tools/ozone/moderation/defs.js.map +1 -1
- package/dist/lexicon/types/tools/ozone/moderation/queryStatuses.d.ts +7 -1
- package/dist/lexicon/types/tools/ozone/moderation/queryStatuses.d.ts.map +1 -1
- package/dist/mod-service/index.d.ts +4 -62
- package/dist/mod-service/index.d.ts.map +1 -1
- package/dist/mod-service/index.js +80 -74
- package/dist/mod-service/index.js.map +1 -1
- package/dist/mod-service/status.d.ts +115 -4
- package/dist/mod-service/status.d.ts.map +1 -1
- package/dist/mod-service/status.js +51 -34
- package/dist/mod-service/status.js.map +1 -1
- package/dist/mod-service/types.d.ts +16 -1
- package/dist/mod-service/types.d.ts.map +1 -1
- package/dist/mod-service/views.d.ts.map +1 -1
- package/dist/mod-service/views.js +49 -41
- package/dist/mod-service/views.js.map +1 -1
- package/dist/util.d.ts +34 -0
- package/dist/util.d.ts.map +1 -1
- package/dist/util.js +132 -0
- package/dist/util.js.map +1 -1
- package/package.json +3 -3
- package/src/api/moderation/queryStatuses.ts +1 -63
- package/src/background.ts +140 -14
- package/src/config/config.ts +2 -0
- package/src/config/env.ts +4 -0
- package/src/daemon/context.ts +43 -5
- package/src/daemon/index.ts +3 -6
- package/src/daemon/materialized-view-refresher.ts +27 -0
- package/src/db/migrations/20241220T144630860Z-stats-materialized-views.ts +218 -0
- package/src/db/migrations/index.ts +1 -0
- package/src/db/schema/account_events_stats.ts +16 -0
- package/src/db/schema/account_record_events_stats.ts +15 -0
- package/src/db/schema/account_record_status_stats.ts +15 -0
- package/src/db/schema/index.ts +10 -1
- package/src/db/schema/record_events_stats.ts +15 -0
- package/src/index.ts +1 -7
- package/src/lexicon/index.ts +2 -0
- package/src/lexicon/lexicons.ts +138 -1
- package/src/lexicon/types/app/bsky/feed/defs.ts +9 -0
- package/src/lexicon/types/app/bsky/feed/generator.ts +4 -0
- package/src/lexicon/types/tools/ozone/moderation/defs.ts +62 -0
- package/src/lexicon/types/tools/ozone/moderation/queryStatuses.ts +11 -1
- package/src/mod-service/index.ts +181 -118
- package/src/mod-service/status.ts +55 -28
- package/src/mod-service/types.ts +22 -1
- package/src/mod-service/views.ts +64 -50
- package/src/util.ts +145 -0
- package/tests/__snapshots__/get-record.test.ts.snap +28 -0
- package/tests/__snapshots__/get-records.test.ts.snap +14 -0
- package/tests/__snapshots__/get-repo.test.ts.snap +11 -0
- package/tests/__snapshots__/get-repos.test.ts.snap +11 -0
- package/tests/__snapshots__/moderation-events.test.ts.snap +19 -0
- package/tests/__snapshots__/moderation-statuses.test.ts.snap +114 -0
- package/tests/get-record.test.ts +4 -0
- package/tests/get-records.test.ts +4 -0
- package/tests/get-repo.test.ts +4 -0
- package/tests/get-repos.test.ts +4 -0
- package/tests/moderation-events.test.ts +4 -0
- package/tests/moderation-statuses.test.ts +4 -0
- package/tests/query-labels.test.ts +1 -0
- package/tsconfig.build.tsbuildinfo +1 -1
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
import { Kysely, sql } from 'kysely'
|
|
2
|
+
|
|
3
|
+
import { REASONAPPEAL } from '../../lexicon/types/com/atproto/moderation/defs'
|
|
4
|
+
import { DatabaseSchemaType } from '../schema'
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
REVIEWESCALATED,
|
|
8
|
+
REVIEWOPEN,
|
|
9
|
+
} from '../../lexicon/types/tools/ozone/moderation/defs'
|
|
10
|
+
import * as modEvent from '../schema/moderation_event'
|
|
11
|
+
import * as modStatus from '../schema/moderation_subject_status'
|
|
12
|
+
import * as recordEventsStats from '../schema/record_events_stats'
|
|
13
|
+
|
|
14
|
+
export async function up(db: Kysely<any>): Promise<void> {
|
|
15
|
+
// Used by "tools.ozone.moderation.queryStatuses". Reduces query cost by two
|
|
16
|
+
// order of magnitudes when sorting using "reportedRecordsCount" or
|
|
17
|
+
// "takendownRecordsCount" and filtering by "reviewState".
|
|
18
|
+
await db.schema
|
|
19
|
+
.createIndex('moderation_subject_status_did_id_review_state_idx')
|
|
20
|
+
.on('moderation_subject_status')
|
|
21
|
+
.column('did')
|
|
22
|
+
.expression(sql`"id" ASC NULLS FIRST`)
|
|
23
|
+
.column('reviewState')
|
|
24
|
+
.execute()
|
|
25
|
+
|
|
26
|
+
// ~6sec for 16M events
|
|
27
|
+
await db.schema
|
|
28
|
+
.createView('account_events_stats')
|
|
29
|
+
.materialized()
|
|
30
|
+
.ifNotExists()
|
|
31
|
+
.as(
|
|
32
|
+
(db as Kysely<modEvent.PartialDB>)
|
|
33
|
+
.selectFrom('moderation_event')
|
|
34
|
+
.where('subjectType', '=', 'com.atproto.admin.defs#repoRef')
|
|
35
|
+
.where('subjectUri', 'is', null)
|
|
36
|
+
.select('subjectDid')
|
|
37
|
+
.select([
|
|
38
|
+
(eb) =>
|
|
39
|
+
sql<number>`COUNT(*) FILTER(
|
|
40
|
+
WHERE ${eb.ref('action')} = 'tools.ozone.moderation.defs#modEventTakedown'
|
|
41
|
+
AND ${eb.ref('durationInHours')} IS NULL
|
|
42
|
+
)`.as('takedownCount'),
|
|
43
|
+
(eb) =>
|
|
44
|
+
sql<number>`COUNT(*) FILTER(
|
|
45
|
+
WHERE ${eb.ref('action')} = 'tools.ozone.moderation.defs#modEventTakedown'
|
|
46
|
+
AND ${eb.ref('durationInHours')} IS NOT NULL
|
|
47
|
+
)`.as('suspendCount'),
|
|
48
|
+
(eb) =>
|
|
49
|
+
sql<number>`COUNT(*) FILTER(
|
|
50
|
+
WHERE ${eb.ref('action')} = 'tools.ozone.moderation.defs#modEventEscalate'
|
|
51
|
+
)`.as('escalateCount'),
|
|
52
|
+
(eb) =>
|
|
53
|
+
sql<number>`COUNT(*) FILTER(
|
|
54
|
+
WHERE ${eb.ref('action')} = 'tools.ozone.moderation.defs#modEventReport'
|
|
55
|
+
AND ${eb.ref('meta')} ->> 'reportType' != ${REASONAPPEAL}
|
|
56
|
+
)`.as('reportCount'),
|
|
57
|
+
(eb) =>
|
|
58
|
+
sql<number>`COUNT(*) FILTER(
|
|
59
|
+
WHERE ${eb.ref('action')} = 'tools.ozone.moderation.defs#modEventReport'
|
|
60
|
+
AND ${eb.ref('meta')} ->> 'reportType' = ${REASONAPPEAL}
|
|
61
|
+
)`.as('appealCount'),
|
|
62
|
+
])
|
|
63
|
+
.groupBy('subjectDid'),
|
|
64
|
+
)
|
|
65
|
+
.execute()
|
|
66
|
+
|
|
67
|
+
await db.schema
|
|
68
|
+
.createIndex('account_events_stats_did_idx')
|
|
69
|
+
.unique()
|
|
70
|
+
.on('account_events_stats')
|
|
71
|
+
.column('subjectDid')
|
|
72
|
+
.execute()
|
|
73
|
+
|
|
74
|
+
await db.schema
|
|
75
|
+
.createIndex('account_events_stats_suspend_count_idx')
|
|
76
|
+
.on('account_events_stats')
|
|
77
|
+
.expression(sql`"suspendCount" ASC NULLS FIRST`)
|
|
78
|
+
.column('subjectDid')
|
|
79
|
+
.execute()
|
|
80
|
+
|
|
81
|
+
// ~50sec for 16M events
|
|
82
|
+
await db.schema
|
|
83
|
+
.createView('record_events_stats')
|
|
84
|
+
.materialized()
|
|
85
|
+
.ifNotExists()
|
|
86
|
+
.as(
|
|
87
|
+
(db as Kysely<modEvent.PartialDB>)
|
|
88
|
+
.selectFrom('moderation_event')
|
|
89
|
+
.select([
|
|
90
|
+
'subjectDid',
|
|
91
|
+
'subjectUri',
|
|
92
|
+
(eb) =>
|
|
93
|
+
sql<number>`COUNT(*) FILTER (WHERE ${eb.ref('action')} = 'tools.ozone.moderation.defs#modEventEscalate')`.as(
|
|
94
|
+
'escalateCount',
|
|
95
|
+
),
|
|
96
|
+
(eb) =>
|
|
97
|
+
sql<number>`COUNT(*) FILTER (WHERE ${eb.ref('action')} = 'tools.ozone.moderation.defs#modEventReport' AND ${eb.ref('meta')} ->> 'reportType' != 'com.atproto.moderation.defs#reasonAppeal')`.as(
|
|
98
|
+
'reportCount',
|
|
99
|
+
),
|
|
100
|
+
(eb) =>
|
|
101
|
+
sql<number>`COUNT(*) FILTER (WHERE ${eb.ref('action')} = 'tools.ozone.moderation.defs#modEventReport' AND ${eb.ref('meta')} ->> 'reportType' = 'com.atproto.moderation.defs#reasonAppeal')`.as(
|
|
102
|
+
'appealCount',
|
|
103
|
+
),
|
|
104
|
+
])
|
|
105
|
+
.where('subjectType', '=', 'com.atproto.repo.strongRef')
|
|
106
|
+
.where('subjectUri', 'is not', null)
|
|
107
|
+
.groupBy(['subjectDid', 'subjectUri']),
|
|
108
|
+
)
|
|
109
|
+
.execute()
|
|
110
|
+
|
|
111
|
+
await db.schema
|
|
112
|
+
.createIndex('record_events_stats_uri_idx')
|
|
113
|
+
.unique()
|
|
114
|
+
.on('record_events_stats')
|
|
115
|
+
.column('subjectUri')
|
|
116
|
+
.execute()
|
|
117
|
+
|
|
118
|
+
await db.schema
|
|
119
|
+
.createIndex('record_events_stats_did_idx')
|
|
120
|
+
.on('record_events_stats')
|
|
121
|
+
.column('subjectDid')
|
|
122
|
+
.execute()
|
|
123
|
+
|
|
124
|
+
await db.schema
|
|
125
|
+
.createView('account_record_events_stats')
|
|
126
|
+
.materialized()
|
|
127
|
+
.ifNotExists()
|
|
128
|
+
.as(
|
|
129
|
+
(db as Kysely<recordEventsStats.PartialDB>)
|
|
130
|
+
.selectFrom('record_events_stats')
|
|
131
|
+
.select([
|
|
132
|
+
'subjectDid',
|
|
133
|
+
(eb) =>
|
|
134
|
+
// Casting to "bigint" because "numeric" gets casted to a string
|
|
135
|
+
// by default by postgres-node.
|
|
136
|
+
sql<number>`SUM(${eb.ref('reportCount')})::bigint`.as(
|
|
137
|
+
'totalReports',
|
|
138
|
+
),
|
|
139
|
+
(eb) =>
|
|
140
|
+
sql<number>`COUNT(*) FILTER (WHERE ${eb.ref('reportCount')} > 0)`.as(
|
|
141
|
+
'reportedCount',
|
|
142
|
+
),
|
|
143
|
+
(eb) =>
|
|
144
|
+
sql<number>`COUNT(*) FILTER (WHERE ${eb.ref('escalateCount')} > 0)`.as(
|
|
145
|
+
'escalatedCount',
|
|
146
|
+
),
|
|
147
|
+
(eb) =>
|
|
148
|
+
sql<number>`COUNT(*) FILTER (WHERE ${eb.ref('appealCount')} > 0)`.as(
|
|
149
|
+
'appealedCount',
|
|
150
|
+
),
|
|
151
|
+
])
|
|
152
|
+
.groupBy('subjectDid'),
|
|
153
|
+
)
|
|
154
|
+
.execute()
|
|
155
|
+
|
|
156
|
+
await db.schema
|
|
157
|
+
.createIndex('account_record_events_stats_did_idx')
|
|
158
|
+
.unique()
|
|
159
|
+
.on('account_record_events_stats')
|
|
160
|
+
.column('subjectDid')
|
|
161
|
+
.execute()
|
|
162
|
+
|
|
163
|
+
await db.schema
|
|
164
|
+
.createIndex('account_record_events_stats_reported_count_idx')
|
|
165
|
+
.on('account_record_events_stats')
|
|
166
|
+
.expression(sql`"reportedCount" ASC NULLS FIRST`)
|
|
167
|
+
.column('subjectDid')
|
|
168
|
+
.execute()
|
|
169
|
+
|
|
170
|
+
await db.schema
|
|
171
|
+
.createView('account_record_status_stats')
|
|
172
|
+
.materialized()
|
|
173
|
+
.ifNotExists()
|
|
174
|
+
.as(
|
|
175
|
+
(db as Kysely<modStatus.PartialDB>)
|
|
176
|
+
.selectFrom('moderation_subject_status')
|
|
177
|
+
.select('did')
|
|
178
|
+
.select([
|
|
179
|
+
sql<number>`COUNT(*)`.as('subjectCount'),
|
|
180
|
+
(eb) =>
|
|
181
|
+
sql<number>`COUNT(*) FILTER (WHERE ${eb.ref('reviewState')} IN (${REVIEWOPEN}, ${REVIEWESCALATED}))`.as(
|
|
182
|
+
'pendingCount',
|
|
183
|
+
),
|
|
184
|
+
(eb) =>
|
|
185
|
+
sql<number>`COUNT(*) FILTER (WHERE ${eb.ref('reviewState')} NOT IN (${REVIEWOPEN}, ${REVIEWESCALATED}))`.as(
|
|
186
|
+
'processedCount',
|
|
187
|
+
),
|
|
188
|
+
(eb) =>
|
|
189
|
+
sql<number>`COUNT(*) FILTER (WHERE ${eb.ref('takendown')})`.as(
|
|
190
|
+
'takendownCount',
|
|
191
|
+
),
|
|
192
|
+
])
|
|
193
|
+
.where('recordPath', '!=', '')
|
|
194
|
+
.groupBy('did'),
|
|
195
|
+
)
|
|
196
|
+
.execute()
|
|
197
|
+
|
|
198
|
+
await db.schema
|
|
199
|
+
.createIndex('account_record_status_stats_did_idx')
|
|
200
|
+
.unique()
|
|
201
|
+
.on('account_record_status_stats')
|
|
202
|
+
.column('did')
|
|
203
|
+
.execute()
|
|
204
|
+
|
|
205
|
+
await db.schema
|
|
206
|
+
.createIndex('account_record_status_stats_takendown_count_idx')
|
|
207
|
+
.on('account_record_status_stats')
|
|
208
|
+
.expression(sql`"takendownCount" ASC NULLS FIRST`)
|
|
209
|
+
.column('did')
|
|
210
|
+
.execute()
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
export async function down(db: Kysely<DatabaseSchemaType>): Promise<void> {
|
|
214
|
+
db.schema.dropView('account_record_status_stats').materialized().execute()
|
|
215
|
+
db.schema.dropView('account_record_events_stats').materialized().execute()
|
|
216
|
+
db.schema.dropView('record_events_stats').materialized().execute()
|
|
217
|
+
db.schema.dropView('account_events_stats').materialized().execute()
|
|
218
|
+
}
|
|
@@ -17,3 +17,4 @@ export * as _20241001T205730722Z from './20241001T205730722Z-subject-status-revi
|
|
|
17
17
|
export * as _20241008T205730722Z from './20241008T205730722Z-sets'
|
|
18
18
|
export * as _20241018T205730722Z from './20241018T205730722Z-setting'
|
|
19
19
|
export * as _20241026T205730722Z from './20241026T205730722Z-add-hosting-status-to-subject-status'
|
|
20
|
+
export * as _20241220T144630860Z from './20241220T144630860Z-stats-materialized-views'
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { GeneratedAlways, Selectable } from 'kysely'
|
|
2
|
+
|
|
3
|
+
export const tableName = 'account_events_stats'
|
|
4
|
+
|
|
5
|
+
export type AccountEventsStats = {
|
|
6
|
+
subjectDid: GeneratedAlways<string>
|
|
7
|
+
takedownCount: GeneratedAlways<number>
|
|
8
|
+
suspendCount: GeneratedAlways<number>
|
|
9
|
+
escalateCount: GeneratedAlways<number>
|
|
10
|
+
reportCount: GeneratedAlways<number>
|
|
11
|
+
appealCount: GeneratedAlways<number>
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export type AccountEventsStatsRow = Selectable<AccountEventsStats>
|
|
15
|
+
|
|
16
|
+
export type PartialDB = { [tableName]: AccountEventsStats }
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { GeneratedAlways, Selectable } from 'kysely'
|
|
2
|
+
|
|
3
|
+
export const tableName = 'account_record_events_stats'
|
|
4
|
+
|
|
5
|
+
type AccountRecordEventsStats = {
|
|
6
|
+
subjectDid: GeneratedAlways<string>
|
|
7
|
+
totalReports: GeneratedAlways<number>
|
|
8
|
+
reportedCount: GeneratedAlways<number>
|
|
9
|
+
escalatedCount: GeneratedAlways<number>
|
|
10
|
+
appealedCount: GeneratedAlways<number>
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export type AccountRecordEventsStatsRow = Selectable<AccountRecordEventsStats>
|
|
14
|
+
|
|
15
|
+
export type PartialDB = { [tableName]: AccountRecordEventsStats }
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { GeneratedAlways, Selectable } from 'kysely'
|
|
2
|
+
|
|
3
|
+
export const tableName = 'account_record_status_stats'
|
|
4
|
+
|
|
5
|
+
type AccountRecordStatusStats = {
|
|
6
|
+
did: GeneratedAlways<string>
|
|
7
|
+
subjectCount: GeneratedAlways<number>
|
|
8
|
+
pendingCount: GeneratedAlways<number>
|
|
9
|
+
processedCount: GeneratedAlways<number>
|
|
10
|
+
takendownCount: GeneratedAlways<number>
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export type AccountRecordStatusStatsRow = Selectable<AccountRecordStatusStats>
|
|
14
|
+
|
|
15
|
+
export type PartialDB = { [tableName]: AccountRecordStatusStats }
|
package/src/db/schema/index.ts
CHANGED
|
@@ -11,6 +11,11 @@ import * as set from './ozone_set'
|
|
|
11
11
|
import * as member from './member'
|
|
12
12
|
import * as setting from './setting'
|
|
13
13
|
|
|
14
|
+
import * as recordEventsStats from './record_events_stats'
|
|
15
|
+
import * as accountEventsStats from './account_events_stats'
|
|
16
|
+
import * as accountRecordEventsStats from './account_record_events_stats'
|
|
17
|
+
import * as accountRecordStatusStats from './account_record_status_stats'
|
|
18
|
+
|
|
14
19
|
export type DatabaseSchemaType = modEvent.PartialDB &
|
|
15
20
|
modSubjectStatus.PartialDB &
|
|
16
21
|
label.PartialDB &
|
|
@@ -21,7 +26,11 @@ export type DatabaseSchemaType = modEvent.PartialDB &
|
|
|
21
26
|
communicationTemplate.PartialDB &
|
|
22
27
|
set.PartialDB &
|
|
23
28
|
member.PartialDB &
|
|
24
|
-
setting.PartialDB
|
|
29
|
+
setting.PartialDB &
|
|
30
|
+
accountEventsStats.PartialDB &
|
|
31
|
+
recordEventsStats.PartialDB &
|
|
32
|
+
accountRecordEventsStats.PartialDB &
|
|
33
|
+
accountRecordStatusStats.PartialDB
|
|
25
34
|
|
|
26
35
|
export type DatabaseSchema = Kysely<DatabaseSchemaType>
|
|
27
36
|
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { GeneratedAlways, Selectable } from 'kysely'
|
|
2
|
+
|
|
3
|
+
export const tableName = 'record_events_stats'
|
|
4
|
+
|
|
5
|
+
export type RecordEventsStats = {
|
|
6
|
+
subjectDid: GeneratedAlways<string>
|
|
7
|
+
subjectUri: GeneratedAlways<string>
|
|
8
|
+
escalateCount: GeneratedAlways<number>
|
|
9
|
+
reportCount: GeneratedAlways<number>
|
|
10
|
+
appealCount: GeneratedAlways<number>
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export type RecordEventsStatsRow = Selectable<RecordEventsStats>
|
|
14
|
+
|
|
15
|
+
export type PartialDB = { [tableName]: RecordEventsStats }
|
package/src/index.ts
CHANGED
|
@@ -114,13 +114,7 @@ export class OzoneService {
|
|
|
114
114
|
},
|
|
115
115
|
'db pool stats',
|
|
116
116
|
)
|
|
117
|
-
dbLogger.info(
|
|
118
|
-
{
|
|
119
|
-
runningCount: backgroundQueue.queue.pending,
|
|
120
|
-
waitingCount: backgroundQueue.queue.size,
|
|
121
|
-
},
|
|
122
|
-
'background queue stats',
|
|
123
|
-
)
|
|
117
|
+
dbLogger.info(backgroundQueue.getStats(), 'background queue stats')
|
|
124
118
|
}, 10000)
|
|
125
119
|
await this.ctx.sequencer.start()
|
|
126
120
|
const server = this.app.listen(this.ctx.cfg.service.port)
|
package/src/lexicon/index.ts
CHANGED
|
@@ -211,6 +211,8 @@ export const APP_BSKY_FEED = {
|
|
|
211
211
|
DefsClickthroughAuthor: 'app.bsky.feed.defs#clickthroughAuthor',
|
|
212
212
|
DefsClickthroughReposter: 'app.bsky.feed.defs#clickthroughReposter',
|
|
213
213
|
DefsClickthroughEmbed: 'app.bsky.feed.defs#clickthroughEmbed',
|
|
214
|
+
DefsContentModeUnspecified: 'app.bsky.feed.defs#contentModeUnspecified',
|
|
215
|
+
DefsContentModeVideo: 'app.bsky.feed.defs#contentModeVideo',
|
|
214
216
|
DefsInteractionSeen: 'app.bsky.feed.defs#interactionSeen',
|
|
215
217
|
DefsInteractionLike: 'app.bsky.feed.defs#interactionLike',
|
|
216
218
|
DefsInteractionRepost: 'app.bsky.feed.defs#interactionRepost',
|
package/src/lexicon/lexicons.ts
CHANGED
|
@@ -4851,6 +4851,11 @@ export const schemaDict = {
|
|
|
4851
4851
|
ref: 'lex:app.bsky.actor.defs#profileView',
|
|
4852
4852
|
},
|
|
4853
4853
|
},
|
|
4854
|
+
recId: {
|
|
4855
|
+
type: 'integer',
|
|
4856
|
+
description:
|
|
4857
|
+
'Snowflake for this recommendation, use when submitting recommendation events.',
|
|
4858
|
+
},
|
|
4854
4859
|
},
|
|
4855
4860
|
},
|
|
4856
4861
|
},
|
|
@@ -5773,6 +5778,13 @@ export const schemaDict = {
|
|
|
5773
5778
|
type: 'ref',
|
|
5774
5779
|
ref: 'lex:app.bsky.feed.defs#generatorViewerState',
|
|
5775
5780
|
},
|
|
5781
|
+
contentMode: {
|
|
5782
|
+
type: 'string',
|
|
5783
|
+
knownValues: [
|
|
5784
|
+
'app.bsky.feed.defs#contentModeUnspecified',
|
|
5785
|
+
'app.bsky.feed.defs#contentModeVideo',
|
|
5786
|
+
],
|
|
5787
|
+
},
|
|
5776
5788
|
indexedAt: {
|
|
5777
5789
|
type: 'string',
|
|
5778
5790
|
format: 'datetime',
|
|
@@ -5907,6 +5919,15 @@ export const schemaDict = {
|
|
|
5907
5919
|
description:
|
|
5908
5920
|
'User clicked through to the embedded content of the feed item',
|
|
5909
5921
|
},
|
|
5922
|
+
contentModeUnspecified: {
|
|
5923
|
+
type: 'token',
|
|
5924
|
+
description: 'Declares the feed generator returns any types of posts.',
|
|
5925
|
+
},
|
|
5926
|
+
contentModeVideo: {
|
|
5927
|
+
type: 'token',
|
|
5928
|
+
description:
|
|
5929
|
+
'Declares the feed generator returns posts containing app.bsky.embed.video embeds.',
|
|
5930
|
+
},
|
|
5910
5931
|
interactionSeen: {
|
|
5911
5932
|
type: 'token',
|
|
5912
5933
|
description: 'Feed item was seen by user',
|
|
@@ -6038,6 +6059,13 @@ export const schemaDict = {
|
|
|
6038
6059
|
description: 'Self-label values',
|
|
6039
6060
|
refs: ['lex:com.atproto.label.defs#selfLabels'],
|
|
6040
6061
|
},
|
|
6062
|
+
contentMode: {
|
|
6063
|
+
type: 'string',
|
|
6064
|
+
knownValues: [
|
|
6065
|
+
'app.bsky.feed.defs#contentModeUnspecified',
|
|
6066
|
+
'app.bsky.feed.defs#contentModeVideo',
|
|
6067
|
+
],
|
|
6068
|
+
},
|
|
6041
6069
|
createdAt: {
|
|
6042
6070
|
type: 'string',
|
|
6043
6071
|
format: 'datetime',
|
|
@@ -8358,6 +8386,11 @@ export const schemaDict = {
|
|
|
8358
8386
|
'If true, response has fallen-back to generic results, and is not scoped using relativeToDid',
|
|
8359
8387
|
default: false,
|
|
8360
8388
|
},
|
|
8389
|
+
recId: {
|
|
8390
|
+
type: 'integer',
|
|
8391
|
+
description:
|
|
8392
|
+
'Snowflake for this recommendation, use when submitting recommendation events.',
|
|
8393
|
+
},
|
|
8361
8394
|
},
|
|
8362
8395
|
},
|
|
8363
8396
|
},
|
|
@@ -9429,6 +9462,11 @@ export const schemaDict = {
|
|
|
9429
9462
|
description:
|
|
9430
9463
|
'DID of the account these suggestions are relative to. If this is returned undefined, suggestions are based on the viewer.',
|
|
9431
9464
|
},
|
|
9465
|
+
recId: {
|
|
9466
|
+
type: 'integer',
|
|
9467
|
+
description:
|
|
9468
|
+
'Snowflake for this recommendation, use when submitting recommendation events.',
|
|
9469
|
+
},
|
|
9432
9470
|
},
|
|
9433
9471
|
},
|
|
9434
9472
|
},
|
|
@@ -11284,6 +11322,85 @@ export const schemaDict = {
|
|
|
11284
11322
|
type: 'string',
|
|
11285
11323
|
},
|
|
11286
11324
|
},
|
|
11325
|
+
accountStats: {
|
|
11326
|
+
description: 'Statistics related to the account subject',
|
|
11327
|
+
type: 'ref',
|
|
11328
|
+
ref: 'lex:tools.ozone.moderation.defs#accountStats',
|
|
11329
|
+
},
|
|
11330
|
+
recordsStats: {
|
|
11331
|
+
description:
|
|
11332
|
+
"Statistics related to the record subjects authored by the subject's account",
|
|
11333
|
+
type: 'ref',
|
|
11334
|
+
ref: 'lex:tools.ozone.moderation.defs#recordsStats',
|
|
11335
|
+
},
|
|
11336
|
+
},
|
|
11337
|
+
},
|
|
11338
|
+
accountStats: {
|
|
11339
|
+
description: 'Statistics about a particular account subject',
|
|
11340
|
+
type: 'object',
|
|
11341
|
+
properties: {
|
|
11342
|
+
reportCount: {
|
|
11343
|
+
description: 'Total number of reports on the account',
|
|
11344
|
+
type: 'integer',
|
|
11345
|
+
},
|
|
11346
|
+
appealCount: {
|
|
11347
|
+
description:
|
|
11348
|
+
'Total number of appeals against a moderation action on the account',
|
|
11349
|
+
type: 'integer',
|
|
11350
|
+
},
|
|
11351
|
+
suspendCount: {
|
|
11352
|
+
description: 'Number of times the account was suspended',
|
|
11353
|
+
type: 'integer',
|
|
11354
|
+
},
|
|
11355
|
+
escalateCount: {
|
|
11356
|
+
description: 'Number of times the account was escalated',
|
|
11357
|
+
type: 'integer',
|
|
11358
|
+
},
|
|
11359
|
+
takedownCount: {
|
|
11360
|
+
description: 'Number of times the account was taken down',
|
|
11361
|
+
type: 'integer',
|
|
11362
|
+
},
|
|
11363
|
+
},
|
|
11364
|
+
},
|
|
11365
|
+
recordsStats: {
|
|
11366
|
+
description: 'Statistics about a set of record subject items',
|
|
11367
|
+
type: 'object',
|
|
11368
|
+
properties: {
|
|
11369
|
+
totalReports: {
|
|
11370
|
+
description:
|
|
11371
|
+
'Cumulative sum of the number of reports on the items in the set',
|
|
11372
|
+
type: 'integer',
|
|
11373
|
+
},
|
|
11374
|
+
reportedCount: {
|
|
11375
|
+
description: 'Number of items that were reported at least once',
|
|
11376
|
+
type: 'integer',
|
|
11377
|
+
},
|
|
11378
|
+
escalatedCount: {
|
|
11379
|
+
description: 'Number of items that were escalated at least once',
|
|
11380
|
+
type: 'integer',
|
|
11381
|
+
},
|
|
11382
|
+
appealedCount: {
|
|
11383
|
+
description: 'Number of items that were appealed at least once',
|
|
11384
|
+
type: 'integer',
|
|
11385
|
+
},
|
|
11386
|
+
subjectCount: {
|
|
11387
|
+
description: 'Total number of item in the set',
|
|
11388
|
+
type: 'integer',
|
|
11389
|
+
},
|
|
11390
|
+
pendingCount: {
|
|
11391
|
+
description:
|
|
11392
|
+
'Number of item currently in "reviewOpen" or "reviewEscalated" state',
|
|
11393
|
+
type: 'integer',
|
|
11394
|
+
},
|
|
11395
|
+
processedCount: {
|
|
11396
|
+
description:
|
|
11397
|
+
'Number of item currently in "reviewNone" or "reviewClosed" state',
|
|
11398
|
+
type: 'integer',
|
|
11399
|
+
},
|
|
11400
|
+
takendownCount: {
|
|
11401
|
+
description: 'Number of item currently taken down',
|
|
11402
|
+
type: 'integer',
|
|
11403
|
+
},
|
|
11287
11404
|
},
|
|
11288
11405
|
},
|
|
11289
11406
|
subjectReviewState: {
|
|
@@ -12532,7 +12649,12 @@ export const schemaDict = {
|
|
|
12532
12649
|
sortField: {
|
|
12533
12650
|
type: 'string',
|
|
12534
12651
|
default: 'lastReportedAt',
|
|
12535
|
-
enum: [
|
|
12652
|
+
enum: [
|
|
12653
|
+
'lastReviewedAt',
|
|
12654
|
+
'lastReportedAt',
|
|
12655
|
+
'reportedRecordsCount',
|
|
12656
|
+
'takendownRecordsCount',
|
|
12657
|
+
],
|
|
12536
12658
|
},
|
|
12537
12659
|
sortDirection: {
|
|
12538
12660
|
type: 'string',
|
|
@@ -12587,6 +12709,21 @@ export const schemaDict = {
|
|
|
12587
12709
|
"If specified, subjects of the given type (account or record) will be returned. When this is set to 'account' the 'collections' parameter will be ignored. When includeAllUserRecords or subject is set, this will be ignored.",
|
|
12588
12710
|
knownValues: ['account', 'record'],
|
|
12589
12711
|
},
|
|
12712
|
+
minAccountSuspendCount: {
|
|
12713
|
+
type: 'integer',
|
|
12714
|
+
description:
|
|
12715
|
+
'If specified, only subjects that belong to an account that has at least this many suspensions will be returned.',
|
|
12716
|
+
},
|
|
12717
|
+
minReportedRecordsCount: {
|
|
12718
|
+
type: 'integer',
|
|
12719
|
+
description:
|
|
12720
|
+
'If specified, only subjects that belong to an account that has at least this many reported records will be returned.',
|
|
12721
|
+
},
|
|
12722
|
+
minTakendownRecordsCount: {
|
|
12723
|
+
type: 'integer',
|
|
12724
|
+
description:
|
|
12725
|
+
'If specified, only subjects that belong to an account that has at least this many taken down records will be returned.',
|
|
12726
|
+
},
|
|
12590
12727
|
},
|
|
12591
12728
|
},
|
|
12592
12729
|
output: {
|
|
@@ -247,6 +247,10 @@ export interface GeneratorView {
|
|
|
247
247
|
acceptsInteractions?: boolean
|
|
248
248
|
labels?: ComAtprotoLabelDefs.Label[]
|
|
249
249
|
viewer?: GeneratorViewerState
|
|
250
|
+
contentMode?:
|
|
251
|
+
| 'app.bsky.feed.defs#contentModeUnspecified'
|
|
252
|
+
| 'app.bsky.feed.defs#contentModeVideo'
|
|
253
|
+
| (string & {})
|
|
250
254
|
indexedAt: string
|
|
251
255
|
[k: string]: unknown
|
|
252
256
|
}
|
|
@@ -401,6 +405,11 @@ export const CLICKTHROUGHAUTHOR = 'app.bsky.feed.defs#clickthroughAuthor'
|
|
|
401
405
|
export const CLICKTHROUGHREPOSTER = 'app.bsky.feed.defs#clickthroughReposter'
|
|
402
406
|
/** User clicked through to the embedded content of the feed item */
|
|
403
407
|
export const CLICKTHROUGHEMBED = 'app.bsky.feed.defs#clickthroughEmbed'
|
|
408
|
+
/** Declares the feed generator returns any types of posts. */
|
|
409
|
+
export const CONTENTMODEUNSPECIFIED =
|
|
410
|
+
'app.bsky.feed.defs#contentModeUnspecified'
|
|
411
|
+
/** Declares the feed generator returns posts containing app.bsky.embed.video embeds. */
|
|
412
|
+
export const CONTENTMODEVIDEO = 'app.bsky.feed.defs#contentModeVideo'
|
|
404
413
|
/** Feed item was seen by user */
|
|
405
414
|
export const INTERACTIONSEEN = 'app.bsky.feed.defs#interactionSeen'
|
|
406
415
|
/** User liked the feed item */
|
|
@@ -19,6 +19,10 @@ export interface Record {
|
|
|
19
19
|
labels?:
|
|
20
20
|
| ComAtprotoLabelDefs.SelfLabels
|
|
21
21
|
| { $type: string; [k: string]: unknown }
|
|
22
|
+
contentMode?:
|
|
23
|
+
| 'app.bsky.feed.defs#contentModeUnspecified'
|
|
24
|
+
| 'app.bsky.feed.defs#contentModeVideo'
|
|
25
|
+
| (string & {})
|
|
22
26
|
createdAt: string
|
|
23
27
|
[k: string]: unknown
|
|
24
28
|
}
|
|
@@ -136,6 +136,8 @@ export interface SubjectStatusView {
|
|
|
136
136
|
appealed?: boolean
|
|
137
137
|
suspendUntil?: string
|
|
138
138
|
tags?: string[]
|
|
139
|
+
accountStats?: AccountStats
|
|
140
|
+
recordsStats?: RecordsStats
|
|
139
141
|
[k: string]: unknown
|
|
140
142
|
}
|
|
141
143
|
|
|
@@ -151,6 +153,66 @@ export function validateSubjectStatusView(v: unknown): ValidationResult {
|
|
|
151
153
|
return lexicons.validate('tools.ozone.moderation.defs#subjectStatusView', v)
|
|
152
154
|
}
|
|
153
155
|
|
|
156
|
+
/** Statistics about a particular account subject */
|
|
157
|
+
export interface AccountStats {
|
|
158
|
+
/** Total number of reports on the account */
|
|
159
|
+
reportCount?: number
|
|
160
|
+
/** Total number of appeals against a moderation action on the account */
|
|
161
|
+
appealCount?: number
|
|
162
|
+
/** Number of times the account was suspended */
|
|
163
|
+
suspendCount?: number
|
|
164
|
+
/** Number of times the account was escalated */
|
|
165
|
+
escalateCount?: number
|
|
166
|
+
/** Number of times the account was taken down */
|
|
167
|
+
takedownCount?: number
|
|
168
|
+
[k: string]: unknown
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
export function isAccountStats(v: unknown): v is AccountStats {
|
|
172
|
+
return (
|
|
173
|
+
isObj(v) &&
|
|
174
|
+
hasProp(v, '$type') &&
|
|
175
|
+
v.$type === 'tools.ozone.moderation.defs#accountStats'
|
|
176
|
+
)
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export function validateAccountStats(v: unknown): ValidationResult {
|
|
180
|
+
return lexicons.validate('tools.ozone.moderation.defs#accountStats', v)
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/** Statistics about a set of record subject items */
|
|
184
|
+
export interface RecordsStats {
|
|
185
|
+
/** Cumulative sum of the number of reports on the items in the set */
|
|
186
|
+
totalReports?: number
|
|
187
|
+
/** Number of items that were reported at least once */
|
|
188
|
+
reportedCount?: number
|
|
189
|
+
/** Number of items that were escalated at least once */
|
|
190
|
+
escalatedCount?: number
|
|
191
|
+
/** Number of items that were appealed at least once */
|
|
192
|
+
appealedCount?: number
|
|
193
|
+
/** Total number of item in the set */
|
|
194
|
+
subjectCount?: number
|
|
195
|
+
/** Number of item currently in "reviewOpen" or "reviewEscalated" state */
|
|
196
|
+
pendingCount?: number
|
|
197
|
+
/** Number of item currently in "reviewNone" or "reviewClosed" state */
|
|
198
|
+
processedCount?: number
|
|
199
|
+
/** Number of item currently taken down */
|
|
200
|
+
takendownCount?: number
|
|
201
|
+
[k: string]: unknown
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
export function isRecordsStats(v: unknown): v is RecordsStats {
|
|
205
|
+
return (
|
|
206
|
+
isObj(v) &&
|
|
207
|
+
hasProp(v, '$type') &&
|
|
208
|
+
v.$type === 'tools.ozone.moderation.defs#recordsStats'
|
|
209
|
+
)
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
export function validateRecordsStats(v: unknown): ValidationResult {
|
|
213
|
+
return lexicons.validate('tools.ozone.moderation.defs#recordsStats', v)
|
|
214
|
+
}
|
|
215
|
+
|
|
154
216
|
export type SubjectReviewState =
|
|
155
217
|
| 'lex:tools.ozone.moderation.defs#reviewOpen'
|
|
156
218
|
| 'lex:tools.ozone.moderation.defs#reviewEscalated'
|
|
@@ -49,7 +49,11 @@ export interface QueryParams {
|
|
|
49
49
|
ignoreSubjects?: string[]
|
|
50
50
|
/** Get all subject statuses that were reviewed by a specific moderator */
|
|
51
51
|
lastReviewedBy?: string
|
|
52
|
-
sortField:
|
|
52
|
+
sortField:
|
|
53
|
+
| 'lastReviewedAt'
|
|
54
|
+
| 'lastReportedAt'
|
|
55
|
+
| 'reportedRecordsCount'
|
|
56
|
+
| 'takendownRecordsCount'
|
|
53
57
|
sortDirection: 'asc' | 'desc'
|
|
54
58
|
/** Get subjects that were taken down */
|
|
55
59
|
takendown?: boolean
|
|
@@ -63,6 +67,12 @@ export interface QueryParams {
|
|
|
63
67
|
collections?: string[]
|
|
64
68
|
/** If specified, subjects of the given type (account or record) will be returned. When this is set to 'account' the 'collections' parameter will be ignored. When includeAllUserRecords or subject is set, this will be ignored. */
|
|
65
69
|
subjectType?: 'account' | 'record' | (string & {})
|
|
70
|
+
/** If specified, only subjects that belong to an account that has at least this many suspensions will be returned. */
|
|
71
|
+
minAccountSuspendCount?: number
|
|
72
|
+
/** If specified, only subjects that belong to an account that has at least this many reported records will be returned. */
|
|
73
|
+
minReportedRecordsCount?: number
|
|
74
|
+
/** If specified, only subjects that belong to an account that has at least this many taken down records will be returned. */
|
|
75
|
+
minTakendownRecordsCount?: number
|
|
66
76
|
}
|
|
67
77
|
|
|
68
78
|
export type InputSchema = undefined
|