@atproto/ozone 0.2.3 → 0.2.5
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 +23 -0
- package/dist/api/health.d.ts.map +1 -1
- package/dist/api/label/queryLabels.d.ts.map +1 -1
- package/dist/api/label/queryLabels.js +13 -21
- package/dist/api/label/queryLabels.js.map +1 -1
- package/dist/api/moderation/util.d.ts.map +1 -1
- package/dist/api/util.d.ts +2 -2
- package/dist/api/util.d.ts.map +1 -1
- package/dist/api/well-known.d.ts.map +1 -1
- package/dist/assignment/index.d.ts.map +1 -1
- package/dist/assignment/index.js +9 -12
- package/dist/assignment/index.js.map +1 -1
- package/dist/auth-verifier.d.ts.map +1 -1
- package/dist/background.d.ts.map +1 -1
- package/dist/background.js.map +1 -1
- package/dist/communication-service/template.d.ts.map +1 -1
- package/dist/communication-service/template.js.map +1 -1
- package/dist/communication-service/util.d.ts.map +1 -1
- package/dist/config/config.d.ts.map +1 -1
- package/dist/config/secrets.d.ts.map +1 -1
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js.map +1 -1
- package/dist/daemon/blob-diverter.d.ts.map +1 -1
- package/dist/daemon/blob-diverter.js.map +1 -1
- package/dist/daemon/context.d.ts.map +1 -1
- package/dist/daemon/context.js.map +1 -1
- package/dist/daemon/event-pusher.d.ts +7 -1
- package/dist/daemon/event-pusher.d.ts.map +1 -1
- package/dist/daemon/event-reverser.d.ts.map +1 -1
- package/dist/daemon/index.d.ts.map +1 -1
- package/dist/daemon/index.js.map +1 -1
- package/dist/daemon/materialized-view-refresher.d.ts.map +1 -1
- package/dist/daemon/queue-router.d.ts.map +1 -1
- package/dist/daemon/scheduled-action-processor.d.ts.map +1 -1
- package/dist/daemon/stats-computer.d.ts.map +1 -1
- package/dist/daemon/strike-expiry-processor.d.ts.map +1 -1
- package/dist/daemon/team-profile-synchronizer.d.ts.map +1 -1
- package/dist/daemon/verification-listener.d.ts.map +1 -1
- package/dist/db/index.d.ts +4 -5
- package/dist/db/index.d.ts.map +1 -1
- package/dist/db/index.js +2 -1
- package/dist/db/index.js.map +1 -1
- package/dist/db/migrations/20241220T144630860Z-stats-materialized-views.d.ts +1 -2
- package/dist/db/migrations/20241220T144630860Z-stats-materialized-views.d.ts.map +1 -1
- package/dist/db/migrations/20241220T144630860Z-stats-materialized-views.js.map +1 -1
- package/dist/db/migrations/20250718T150931000Z-update-appeal-reason-stats.d.ts +1 -2
- package/dist/db/migrations/20250718T150931000Z-update-appeal-reason-stats.d.ts.map +1 -1
- package/dist/db/migrations/20250718T150931000Z-update-appeal-reason-stats.js.map +1 -1
- package/dist/db/migrations/provider.d.ts +2 -1
- package/dist/db/migrations/provider.d.ts.map +1 -1
- package/dist/db/migrations/provider.js.map +1 -1
- package/dist/db/pagination.d.ts +5 -4
- package/dist/db/pagination.d.ts.map +1 -1
- package/dist/db/pagination.js +4 -4
- package/dist/db/pagination.js.map +1 -1
- package/dist/db/schema/index.d.ts.map +1 -1
- package/dist/db/types.d.ts +1 -1
- package/dist/db/types.d.ts.map +1 -1
- package/dist/db/types.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/jetstream/service.d.ts.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 +4 -0
- package/dist/lexicon/index.js.map +1 -1
- package/dist/lexicon/lexicons.d.ts +17160 -17038
- package/dist/lexicon/lexicons.d.ts.map +1 -1
- package/dist/lexicon/lexicons.js +68 -1
- package/dist/lexicon/lexicons.js.map +1 -1
- package/dist/lexicon/types/chat/bsky/convo/defs.d.ts +11 -0
- package/dist/lexicon/types/chat/bsky/convo/defs.d.ts.map +1 -1
- package/dist/lexicon/types/chat/bsky/convo/defs.js +7 -0
- package/dist/lexicon/types/chat/bsky/convo/defs.js.map +1 -1
- package/dist/lexicon/types/chat/bsky/convo/getUnreadCounts.d.ts +25 -0
- package/dist/lexicon/types/chat/bsky/convo/getUnreadCounts.d.ts.map +1 -0
- package/dist/lexicon/types/chat/bsky/convo/getUnreadCounts.js +5 -0
- package/dist/lexicon/types/chat/bsky/convo/getUnreadCounts.js.map +1 -0
- package/dist/lexicon/types/chat/bsky/convo/sendMessage.d.ts +1 -1
- package/dist/lexicon/types/chat/bsky/convo/sendMessage.d.ts.map +1 -1
- package/dist/lexicon/types/chat/bsky/convo/sendMessage.js.map +1 -1
- package/dist/lexicon/types/chat/bsky/convo/sendMessageBatch.d.ts +1 -1
- package/dist/lexicon/types/chat/bsky/convo/sendMessageBatch.d.ts.map +1 -1
- package/dist/lexicon/types/chat/bsky/convo/sendMessageBatch.js.map +1 -1
- package/dist/lexicon/util.d.ts.map +1 -1
- package/dist/mod-service/index.d.ts +38 -38
- package/dist/mod-service/index.d.ts.map +1 -1
- package/dist/mod-service/index.js +28 -52
- package/dist/mod-service/index.js.map +1 -1
- package/dist/mod-service/profile.d.ts.map +1 -1
- package/dist/mod-service/report.d.ts.map +1 -1
- package/dist/mod-service/report.js.map +1 -1
- package/dist/mod-service/status.d.ts +60 -165
- package/dist/mod-service/status.d.ts.map +1 -1
- package/dist/mod-service/strike.d.ts.map +1 -1
- package/dist/mod-service/strike.js.map +1 -1
- package/dist/mod-service/subject.d.ts +4 -4
- package/dist/mod-service/subject.d.ts.map +1 -1
- package/dist/mod-service/util.d.ts +1 -1
- package/dist/mod-service/util.d.ts.map +1 -1
- package/dist/mod-service/views.d.ts.map +1 -1
- package/dist/mod-service/views.js +7 -11
- package/dist/mod-service/views.js.map +1 -1
- package/dist/queue/service.d.ts.map +1 -1
- package/dist/queue/service.js +1 -3
- package/dist/queue/service.js.map +1 -1
- package/dist/report/activity.d.ts +17 -6
- package/dist/report/activity.d.ts.map +1 -1
- package/dist/report/handle-report-update.d.ts.map +1 -1
- package/dist/report/handle-report-update.js.map +1 -1
- package/dist/report/stats.d.ts.map +1 -1
- package/dist/report/stats.js.map +1 -1
- package/dist/report/views.d.ts +2 -2
- package/dist/report/views.d.ts.map +1 -1
- package/dist/safelink/service.d.ts +3 -3
- package/dist/safelink/service.d.ts.map +1 -1
- package/dist/safelink/service.js.map +1 -1
- package/dist/scheduled-action/service.d.ts.map +1 -1
- package/dist/scheduled-action/service.js +16 -20
- package/dist/scheduled-action/service.js.map +1 -1
- package/dist/sequencer/outbox.d.ts.map +1 -1
- package/dist/sequencer/outbox.js.map +1 -1
- package/dist/sequencer/sequencer.d.ts.map +1 -1
- package/dist/sequencer/sequencer.js.map +1 -1
- package/dist/set/service.d.ts +10 -1
- package/dist/set/service.d.ts.map +1 -1
- package/dist/set/service.js +5 -2
- package/dist/set/service.js.map +1 -1
- package/dist/setting/service.d.ts.map +1 -1
- package/dist/setting/service.js.map +1 -1
- package/dist/tag-service/content-tagger.d.ts.map +1 -1
- package/dist/tag-service/content-tagger.js.map +1 -1
- package/dist/tag-service/embed-tagger.d.ts.map +1 -1
- package/dist/tag-service/index.d.ts.map +1 -1
- package/dist/tag-service/index.js.map +1 -1
- package/dist/tag-service/language-tagger.d.ts.map +1 -1
- package/dist/tag-service/util.d.ts.map +1 -1
- package/dist/team/index.d.ts.map +1 -1
- package/dist/team/index.js +5 -4
- package/dist/team/index.js.map +1 -1
- package/dist/util.d.ts.map +1 -1
- package/dist/verification/issuer.d.ts +14 -4
- package/dist/verification/issuer.d.ts.map +1 -1
- package/dist/verification/service.d.ts +18 -6
- package/dist/verification/service.d.ts.map +1 -1
- package/dist/verification/service.js +1 -1
- package/dist/verification/service.js.map +1 -1
- package/dist/verification/util.d.ts.map +1 -1
- package/package.json +15 -16
- package/src/api/label/queryLabels.ts +11 -14
- package/src/assignment/index.ts +15 -18
- package/src/db/index.ts +1 -1
- package/src/db/migrations/20241220T144630860Z-stats-materialized-views.ts +1 -2
- package/src/db/migrations/20250718T150931000Z-update-appeal-reason-stats.ts +1 -2
- package/src/db/migrations/provider.ts +2 -1
- package/src/db/pagination.ts +18 -18
- package/src/db/types.ts +3 -1
- package/src/mod-service/index.ts +78 -71
- package/src/mod-service/report.ts +5 -3
- package/src/mod-service/views.ts +16 -16
- package/src/queue/service.ts +5 -5
- package/src/report/stats.ts +5 -3
- package/src/scheduled-action/service.ts +22 -20
- package/src/set/service.ts +17 -14
- package/src/team/index.ts +6 -5
- package/src/verification/service.ts +2 -2
- package/tsconfig.build.tsbuildinfo +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@atproto/ozone",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.5",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "Backend service for moderating the Bluesky network.",
|
|
6
6
|
"keywords": [
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
"cors": "^2.8.5",
|
|
23
23
|
"express": "^4.17.2",
|
|
24
24
|
"http-terminator": "^3.2.0",
|
|
25
|
-
"kysely": "^0.
|
|
25
|
+
"kysely": "^0.29.2",
|
|
26
26
|
"lande": "^1.0.10",
|
|
27
27
|
"multiformats": "^13.0.0",
|
|
28
28
|
"p-queue": "^8.0.0",
|
|
@@ -33,15 +33,15 @@
|
|
|
33
33
|
"uint8arrays": "^5.0.0",
|
|
34
34
|
"undici": "^6.14.1",
|
|
35
35
|
"ws": "^8.12.0",
|
|
36
|
-
"@atproto/api": "^0.20.
|
|
37
|
-
"@atproto/common": "^0.6.
|
|
38
|
-
"@atproto/
|
|
39
|
-
"@atproto/
|
|
40
|
-
"@atproto/lexicon": "^0.7.
|
|
41
|
-
"@atproto/syntax": "^0.6.
|
|
42
|
-
"@atproto/ws-client": "^0.1.
|
|
43
|
-
"@atproto/xrpc": "^0.8.
|
|
44
|
-
"@atproto/xrpc-server": "^0.11.
|
|
36
|
+
"@atproto/api": "^0.20.16",
|
|
37
|
+
"@atproto/common": "^0.6.3",
|
|
38
|
+
"@atproto/identity": "^0.5.1",
|
|
39
|
+
"@atproto/crypto": "^0.5.1",
|
|
40
|
+
"@atproto/lexicon": "^0.7.2",
|
|
41
|
+
"@atproto/syntax": "^0.6.2",
|
|
42
|
+
"@atproto/ws-client": "^0.1.1",
|
|
43
|
+
"@atproto/xrpc": "^0.8.1",
|
|
44
|
+
"@atproto/xrpc-server": "^0.11.2"
|
|
45
45
|
},
|
|
46
46
|
"devDependencies": {
|
|
47
47
|
"@did-plc/server": "^0.0.1",
|
|
@@ -49,13 +49,12 @@
|
|
|
49
49
|
"@types/cors": "^2.8.12",
|
|
50
50
|
"@types/express": "^4.17.13",
|
|
51
51
|
"@types/express-serve-static-core": "^4.17.36",
|
|
52
|
-
"@types/pg": "^8.
|
|
52
|
+
"@types/pg": "^8.15.5",
|
|
53
53
|
"@types/qs": "^6.9.7",
|
|
54
54
|
"jest": "^30.0.0",
|
|
55
55
|
"ts-node": "^10.8.2",
|
|
56
|
-
"
|
|
57
|
-
"@atproto/
|
|
58
|
-
"@atproto/pds": "^0.5.2"
|
|
56
|
+
"@atproto/lex-cli": "^0.10.1",
|
|
57
|
+
"@atproto/pds": "^0.5.6"
|
|
59
58
|
},
|
|
60
59
|
"type": "module",
|
|
61
60
|
"exports": {
|
|
@@ -67,7 +66,7 @@
|
|
|
67
66
|
"scripts": {
|
|
68
67
|
"codegen:lex": "lex gen-server --yes ./src/lexicon ../../lexicons/com/atproto/*/* ../../lexicons/app/bsky/*/* ../../lexicons/chat/bsky/*/* ../../lexicons/tools/ozone/*/*",
|
|
69
68
|
"prebuild": "[ -f ./src/lexicon/index.ts ] && [ -z \"$(find ../../lexicons -newer ./src/lexicon/index.ts -type f -print -quit)\" ] || pnpm run codegen:lex",
|
|
70
|
-
"build": "
|
|
69
|
+
"build": "tsgo --build tsconfig.build.json",
|
|
71
70
|
"start": "node --enable-source-maps dist/bin.js",
|
|
72
71
|
"test": "NODE_OPTIONS=--experimental-vm-modules ../dev-infra/with-test-redis-and-db.sh jest",
|
|
73
72
|
"test:log": "tail -50 test.log | pino-pretty",
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { sql } from 'kysely'
|
|
2
1
|
import { InvalidRequestError } from '@atproto/xrpc-server'
|
|
3
2
|
import { AppContext } from '../../context.js'
|
|
4
3
|
import { Server } from '../../lexicon/index.js'
|
|
@@ -9,14 +8,13 @@ export default function (server: Server, ctx: AppContext) {
|
|
|
9
8
|
let builder = ctx.db.db.selectFrom('label').selectAll().limit(limit)
|
|
10
9
|
// if includes '*', then we don't need a where clause
|
|
11
10
|
if (!uriPatterns.includes('*')) {
|
|
12
|
-
builder = builder.where((
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
} else {
|
|
11
|
+
builder = builder.where((eb) =>
|
|
12
|
+
eb.or(
|
|
13
|
+
uriPatterns.map((pattern) => {
|
|
14
|
+
// if no '*', then we're looking for an exact match
|
|
15
|
+
if (!pattern.includes('*')) {
|
|
16
|
+
return eb('uri', '=', pattern)
|
|
17
|
+
}
|
|
20
18
|
if (pattern.indexOf('*') < pattern.length - 1) {
|
|
21
19
|
throw new InvalidRequestError(`invalid pattern: ${pattern}`)
|
|
22
20
|
}
|
|
@@ -24,11 +22,10 @@ export default function (server: Server, ctx: AppContext) {
|
|
|
24
22
|
.slice(0, -1)
|
|
25
23
|
.replaceAll('%', '') // sanitize search pattern
|
|
26
24
|
.replaceAll('_', '\\_') // escape any underscores
|
|
27
|
-
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
})
|
|
25
|
+
return eb('uri', 'like', `${searchPattern}%`)
|
|
26
|
+
}),
|
|
27
|
+
),
|
|
28
|
+
)
|
|
32
29
|
}
|
|
33
30
|
if (sources && sources.length > 0) {
|
|
34
31
|
builder = builder.where('src', 'in', sources)
|
package/src/assignment/index.ts
CHANGED
|
@@ -129,8 +129,8 @@ export class AssignmentService {
|
|
|
129
129
|
|
|
130
130
|
if (onlyActive) {
|
|
131
131
|
const now = new Date().toISOString()
|
|
132
|
-
query = query.where((
|
|
133
|
-
|
|
132
|
+
query = query.where((eb) =>
|
|
133
|
+
eb.or([eb('endAt', 'is', null), eb('endAt', '>', now)]),
|
|
134
134
|
)
|
|
135
135
|
}
|
|
136
136
|
|
|
@@ -198,8 +198,8 @@ export class AssignmentService {
|
|
|
198
198
|
|
|
199
199
|
if (onlyActive) {
|
|
200
200
|
const now = new Date().toISOString()
|
|
201
|
-
query = query.where((
|
|
202
|
-
|
|
201
|
+
query = query.where((eb) =>
|
|
202
|
+
eb.or([eb('endAt', '>', now), eb('endAt', 'is', null)]),
|
|
203
203
|
)
|
|
204
204
|
}
|
|
205
205
|
|
|
@@ -266,10 +266,8 @@ export class AssignmentService {
|
|
|
266
266
|
.where('did', '=', did)
|
|
267
267
|
.where('queueId', '=', queueId)
|
|
268
268
|
.where('reportId', 'is', null)
|
|
269
|
-
.where((
|
|
270
|
-
|
|
271
|
-
.where('endAt', 'is', null)
|
|
272
|
-
.orWhere('endAt', '>', now.toISOString()),
|
|
269
|
+
.where((eb) =>
|
|
270
|
+
eb.or([eb('endAt', 'is', null), eb('endAt', '>', now.toISOString())]),
|
|
273
271
|
)
|
|
274
272
|
.executeTakeFirst()
|
|
275
273
|
if (existing) {
|
|
@@ -338,8 +336,8 @@ export class AssignmentService {
|
|
|
338
336
|
.where('did', '=', did)
|
|
339
337
|
.where('queueId', '=', queueId)
|
|
340
338
|
.where('reportId', 'is', null)
|
|
341
|
-
.where((
|
|
342
|
-
|
|
339
|
+
.where((eb) =>
|
|
340
|
+
eb.or([eb('endAt', 'is', null), eb('endAt', '>', now.toISOString())]),
|
|
343
341
|
)
|
|
344
342
|
.executeTakeFirst()
|
|
345
343
|
|
|
@@ -447,10 +445,8 @@ export class AssignmentService {
|
|
|
447
445
|
.selectFrom('moderator_assignment')
|
|
448
446
|
.selectAll()
|
|
449
447
|
.where('reportId', '=', reportId)
|
|
450
|
-
.where((
|
|
451
|
-
|
|
452
|
-
.where('endAt', '>', now.toISOString())
|
|
453
|
-
.orWhere('endAt', 'is', null),
|
|
448
|
+
.where((eb) =>
|
|
449
|
+
eb.or([eb('endAt', '>', now.toISOString()), eb('endAt', 'is', null)]),
|
|
454
450
|
)
|
|
455
451
|
.executeTakeFirst()
|
|
456
452
|
|
|
@@ -533,10 +529,11 @@ export class AssignmentService {
|
|
|
533
529
|
.selectFrom('moderator_assignment')
|
|
534
530
|
.selectAll()
|
|
535
531
|
.where('reportId', '=', reportId)
|
|
536
|
-
.where((
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
532
|
+
.where((eb) =>
|
|
533
|
+
eb.or([
|
|
534
|
+
eb('endAt', '>', now.toISOString()),
|
|
535
|
+
eb('endAt', 'is', null),
|
|
536
|
+
]),
|
|
540
537
|
)
|
|
541
538
|
.executeTakeFirst()
|
|
542
539
|
|
package/src/db/index.ts
CHANGED
|
@@ -3,7 +3,6 @@ import { EventEmitter } from 'node:events'
|
|
|
3
3
|
import {
|
|
4
4
|
Kysely,
|
|
5
5
|
KyselyPlugin,
|
|
6
|
-
Migrator,
|
|
7
6
|
PluginTransformQueryArgs,
|
|
8
7
|
PluginTransformResultArgs,
|
|
9
8
|
PostgresDialect,
|
|
@@ -11,6 +10,7 @@ import {
|
|
|
11
10
|
RootOperationNode,
|
|
12
11
|
UnknownRow,
|
|
13
12
|
} from 'kysely'
|
|
13
|
+
import { Migrator } from 'kysely/migration'
|
|
14
14
|
// eslint-disable-next-line import/default
|
|
15
15
|
import pg from 'pg'
|
|
16
16
|
// eslint-disable-next-line import/no-named-as-default-member
|
|
@@ -4,7 +4,6 @@ import {
|
|
|
4
4
|
REVIEWESCALATED,
|
|
5
5
|
REVIEWOPEN,
|
|
6
6
|
} from '../../lexicon/types/tools/ozone/moderation/defs.js'
|
|
7
|
-
import { DatabaseSchemaType } from '../schema/index.js'
|
|
8
7
|
import * as modEvent from '../schema/moderation_event.js'
|
|
9
8
|
import * as modStatus from '../schema/moderation_subject_status.js'
|
|
10
9
|
import * as recordEventsStats from '../schema/record_events_stats.js'
|
|
@@ -208,7 +207,7 @@ export async function up(db: Kysely<any>): Promise<void> {
|
|
|
208
207
|
.execute()
|
|
209
208
|
}
|
|
210
209
|
|
|
211
|
-
export async function down(db: Kysely<
|
|
210
|
+
export async function down(db: Kysely<any>): Promise<void> {
|
|
212
211
|
db.schema.dropView('account_record_status_stats').materialized().execute()
|
|
213
212
|
db.schema.dropView('account_record_events_stats').materialized().execute()
|
|
214
213
|
db.schema.dropView('record_events_stats').materialized().execute()
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { Kysely, sql } from 'kysely'
|
|
2
2
|
import { OZONE_APPEAL_REASON_TYPE } from '../../api/util.js'
|
|
3
3
|
import { REASONAPPEAL } from '../../lexicon/types/com/atproto/moderation/defs.js'
|
|
4
|
-
import { DatabaseSchemaType } from '../schema/index.js'
|
|
5
4
|
import * as modEvent from '../schema/moderation_event.js'
|
|
6
5
|
import * as recordEventsStats from '../schema/record_events_stats.js'
|
|
7
6
|
|
|
@@ -161,7 +160,7 @@ export async function up(db: Kysely<any>): Promise<void> {
|
|
|
161
160
|
.execute()
|
|
162
161
|
}
|
|
163
162
|
|
|
164
|
-
export async function down(db: Kysely<
|
|
163
|
+
export async function down(db: Kysely<any>): Promise<void> {
|
|
165
164
|
// Drop the updated materialized views
|
|
166
165
|
await db.schema
|
|
167
166
|
.dropView('account_record_events_stats')
|
package/src/db/pagination.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { DynamicModule, sql } from 'kysely'
|
|
1
|
+
import { DynamicModule, SqlBool, sql } from 'kysely'
|
|
2
2
|
import { InvalidRequestError } from '@atproto/xrpc-server'
|
|
3
3
|
import { AnyQb, DbRef } from './types.js'
|
|
4
4
|
|
|
@@ -66,16 +66,16 @@ export abstract class GenericKeyset<R, LR extends LabeledResult> {
|
|
|
66
66
|
if (tryIndex) {
|
|
67
67
|
// The tryIndex param will likely disappear and become the default implementation: here for now for gradual rollout query-by-query.
|
|
68
68
|
if (direction === 'asc') {
|
|
69
|
-
return sql
|
|
69
|
+
return sql<SqlBool>`((${this.primary}, ${this.secondary}) > (${labeled.primary}, ${labeled.secondary}))`
|
|
70
70
|
} else {
|
|
71
|
-
return sql
|
|
71
|
+
return sql<SqlBool>`((${this.primary}, ${this.secondary}) < (${labeled.primary}, ${labeled.secondary}))`
|
|
72
72
|
}
|
|
73
73
|
} else {
|
|
74
74
|
// @NOTE this implementation can struggle to use an index on (primary, secondary) for pagination due to the "or" usage.
|
|
75
75
|
if (direction === 'asc') {
|
|
76
|
-
return sql
|
|
76
|
+
return sql<SqlBool>`((${this.primary} > ${labeled.primary}) or (${this.primary} = ${labeled.primary} and ${this.secondary} > ${labeled.secondary}))`
|
|
77
77
|
} else {
|
|
78
|
-
return sql
|
|
78
|
+
return sql<SqlBool>`((${this.primary} < ${labeled.primary}) or (${this.primary} = ${labeled.primary} and ${this.secondary} < ${labeled.secondary}))`
|
|
79
79
|
}
|
|
80
80
|
}
|
|
81
81
|
}
|
|
@@ -91,7 +91,7 @@ export class StatusKeyset extends GenericKeyset<StatusKeysetParam, Cursor> {
|
|
|
91
91
|
labelResult(result: StatusKeysetParam): Cursor
|
|
92
92
|
labelResult(result: StatusKeysetParam) {
|
|
93
93
|
const primaryField = (
|
|
94
|
-
this.primary as ReturnType<DynamicModule['ref']>
|
|
94
|
+
this.primary as ReturnType<DynamicModule<unknown>['ref']>
|
|
95
95
|
).dynamicReference.includes('lastReviewedAt')
|
|
96
96
|
? 'lastReviewedAt'
|
|
97
97
|
: 'lastReportedAt'
|
|
@@ -134,12 +134,12 @@ export class StatusKeyset extends GenericKeyset<StatusKeysetParam, Cursor> {
|
|
|
134
134
|
if (labeled === undefined) return
|
|
135
135
|
if (direction === 'asc') {
|
|
136
136
|
return !labeled.primary
|
|
137
|
-
? sql
|
|
138
|
-
: sql
|
|
137
|
+
? sql<SqlBool>`(${this.primary} IS NULL AND ${this.secondary} > ${labeled.secondary})`
|
|
138
|
+
: sql<SqlBool>`((${this.primary}, ${this.secondary}) > (${labeled.primary}, ${labeled.secondary}) OR (${this.primary} is null))`
|
|
139
139
|
} else {
|
|
140
140
|
return !labeled.primary
|
|
141
|
-
? sql
|
|
142
|
-
: sql
|
|
141
|
+
? sql<SqlBool>`(${this.primary} IS NULL AND ${this.secondary} < ${labeled.secondary})`
|
|
142
|
+
: sql<SqlBool>`((${this.primary}, ${this.secondary}) < (${labeled.primary}, ${labeled.secondary}) OR (${this.primary} is null))`
|
|
143
143
|
}
|
|
144
144
|
}
|
|
145
145
|
}
|
|
@@ -244,15 +244,15 @@ export class EndAtIdKeyset extends GenericKeyset<EndAtIdKeysetParam, Cursor> {
|
|
|
244
244
|
const primaryRef = sql`COALESCE(${this.primary}, ${PERMANENT_ENDSAT})`
|
|
245
245
|
if (tryIndex) {
|
|
246
246
|
if (direction === 'asc') {
|
|
247
|
-
return sql
|
|
247
|
+
return sql<SqlBool>`((${primaryRef}, ${this.secondary}) > (${labeled.primary}, ${labeled.secondary}))`
|
|
248
248
|
} else {
|
|
249
|
-
return sql
|
|
249
|
+
return sql<SqlBool>`((${primaryRef}, ${this.secondary}) < (${labeled.primary}, ${labeled.secondary}))`
|
|
250
250
|
}
|
|
251
251
|
} else {
|
|
252
252
|
if (direction === 'asc') {
|
|
253
|
-
return sql
|
|
253
|
+
return sql<SqlBool>`((${primaryRef} > ${labeled.primary}) or (${primaryRef} = ${labeled.primary} and ${this.secondary} > ${labeled.secondary}))`
|
|
254
254
|
} else {
|
|
255
|
-
return sql
|
|
255
|
+
return sql<SqlBool>`((${primaryRef} < ${labeled.primary}) or (${primaryRef} = ${labeled.primary} and ${this.secondary} < ${labeled.secondary}))`
|
|
256
256
|
}
|
|
257
257
|
}
|
|
258
258
|
}
|
|
@@ -314,11 +314,11 @@ export const paginate = <
|
|
|
314
314
|
} = opts
|
|
315
315
|
const keysetSql = keyset.getSql(keyset.unpack(cursor), direction, tryIndex)
|
|
316
316
|
return qb
|
|
317
|
-
|
|
318
|
-
|
|
317
|
+
.$if(!!limit, (q) => q.limit(limit as number))
|
|
318
|
+
.$if(!nullsLast, (q) =>
|
|
319
319
|
q.orderBy(keyset.primary, direction).orderBy(keyset.secondary, direction),
|
|
320
320
|
)
|
|
321
|
-
|
|
321
|
+
.$if(!!nullsLast, (q) =>
|
|
322
322
|
q
|
|
323
323
|
.orderBy(
|
|
324
324
|
direction === 'asc'
|
|
@@ -331,5 +331,5 @@ export const paginate = <
|
|
|
331
331
|
: sql`${keyset.secondary} desc nulls last`,
|
|
332
332
|
),
|
|
333
333
|
)
|
|
334
|
-
|
|
334
|
+
.$if(!!keysetSql, (qb) => (keysetSql ? qb.where(keysetSql) : qb)) as QB
|
|
335
335
|
}
|
package/src/db/types.ts
CHANGED
|
@@ -3,7 +3,9 @@ import { DynamicModule, RawBuilder, SelectQueryBuilder, sql } from 'kysely'
|
|
|
3
3
|
import pg from 'pg'
|
|
4
4
|
type PgPool = pg.Pool
|
|
5
5
|
|
|
6
|
-
export type DbRef =
|
|
6
|
+
export type DbRef =
|
|
7
|
+
| RawBuilder<unknown>
|
|
8
|
+
| ReturnType<DynamicModule<unknown>['ref']>
|
|
7
9
|
|
|
8
10
|
export type AnyQb = SelectQueryBuilder<any, any, any>
|
|
9
11
|
|
package/src/mod-service/index.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Expression, Insertable, sql } from 'kysely'
|
|
2
2
|
import { CID } from 'multiformats/cid'
|
|
3
3
|
import { AtpAgent, ToolsOzoneModerationDefs } from '@atproto/api'
|
|
4
4
|
import { addHoursToDate, chunkArray } from '@atproto/common'
|
|
@@ -267,21 +267,24 @@ export class ModerationService {
|
|
|
267
267
|
|
|
268
268
|
// If subjectType is set to 'account' let that take priority and ignore collections filter
|
|
269
269
|
if (collections.length && subjectType !== 'account') {
|
|
270
|
-
builder = builder
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
270
|
+
builder = builder
|
|
271
|
+
.where('subjectUri', 'is not', null)
|
|
272
|
+
.where((eb) =>
|
|
273
|
+
eb.or(
|
|
274
|
+
collections.map((collection) =>
|
|
275
|
+
eb('subjectUri', 'like', `%/${collection}/%`),
|
|
276
|
+
),
|
|
277
|
+
),
|
|
278
|
+
)
|
|
276
279
|
}
|
|
277
280
|
|
|
278
281
|
if (types.length) {
|
|
279
|
-
builder = builder.where((
|
|
282
|
+
builder = builder.where((eb) => {
|
|
280
283
|
if (types.length === 1) {
|
|
281
|
-
return
|
|
284
|
+
return eb('action', '=', types[0])
|
|
282
285
|
}
|
|
283
286
|
|
|
284
|
-
return
|
|
287
|
+
return eb('action', 'in', types)
|
|
285
288
|
})
|
|
286
289
|
}
|
|
287
290
|
if (createdBy) {
|
|
@@ -298,12 +301,11 @@ export class ModerationService {
|
|
|
298
301
|
// the input may end in || in which case, there may be item in the array which is just '' and we want to ignore those
|
|
299
302
|
const keywords = comment.split('||').filter((keyword) => !!keyword.trim())
|
|
300
303
|
if (keywords.length > 1) {
|
|
301
|
-
builder = builder.where((
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
})
|
|
304
|
+
builder = builder.where((eb) =>
|
|
305
|
+
eb.or(
|
|
306
|
+
keywords.map((keyword) => eb('comment', 'ilike', `%${keyword}%`)),
|
|
307
|
+
),
|
|
308
|
+
)
|
|
307
309
|
} else if (keywords.length === 1) {
|
|
308
310
|
builder = builder.where('comment', 'ilike', `%${keywords[0]}%`)
|
|
309
311
|
}
|
|
@@ -324,23 +326,26 @@ export class ModerationService {
|
|
|
324
326
|
})
|
|
325
327
|
}
|
|
326
328
|
if (addedTags.length) {
|
|
327
|
-
builder = builder.where(
|
|
329
|
+
builder = builder.where(
|
|
330
|
+
sql<boolean>`${ref('addedTags')} @> ${jsonb(addedTags)}`,
|
|
331
|
+
)
|
|
328
332
|
}
|
|
329
333
|
if (removedTags.length) {
|
|
330
334
|
builder = builder.where(
|
|
331
|
-
sql
|
|
335
|
+
sql<boolean>`${ref('removedTags')} @> ${jsonb(removedTags)}`,
|
|
332
336
|
)
|
|
333
337
|
}
|
|
334
338
|
if (reportTypes?.length) {
|
|
335
339
|
builder = builder.where(sql`meta->>'reportType'`, 'in', reportTypes)
|
|
336
340
|
}
|
|
337
341
|
if (policies?.length) {
|
|
338
|
-
builder = builder.where((
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
342
|
+
builder = builder.where((eb) =>
|
|
343
|
+
eb.or(
|
|
344
|
+
policies.map((policy) =>
|
|
345
|
+
eb(sql`meta->>'policies'`, 'ilike', `%${policy}%`),
|
|
346
|
+
),
|
|
347
|
+
),
|
|
348
|
+
)
|
|
344
349
|
}
|
|
345
350
|
if (modTool?.length) {
|
|
346
351
|
builder = builder
|
|
@@ -446,8 +451,8 @@ export class ModerationService {
|
|
|
446
451
|
const subjectsToBeResolved = await this.db.db
|
|
447
452
|
.selectFrom('moderation_subject_status')
|
|
448
453
|
.where('did', '=', did)
|
|
449
|
-
.where((
|
|
450
|
-
|
|
454
|
+
.where((eb) =>
|
|
455
|
+
eb.or([eb('recordPath', '!=', ''), eb('convoId', '!=', '')]),
|
|
451
456
|
)
|
|
452
457
|
.where('reviewState', 'in', [REVIEWESCALATED, REVIEWOPEN])
|
|
453
458
|
.selectAll()
|
|
@@ -816,8 +821,9 @@ export class ModerationService {
|
|
|
816
821
|
const now = new Date().toISOString()
|
|
817
822
|
const subjects = await this.db.db
|
|
818
823
|
.selectFrom('moderation_subject_status')
|
|
819
|
-
.where(
|
|
820
|
-
|
|
824
|
+
.where((eb) =>
|
|
825
|
+
eb.or([eb('suspendUntil', '<', now), eb('muteUntil', '<', now)]),
|
|
826
|
+
)
|
|
821
827
|
.selectAll()
|
|
822
828
|
.execute()
|
|
823
829
|
|
|
@@ -1206,16 +1212,17 @@ export class ModerationService {
|
|
|
1206
1212
|
if (subjectType !== 'account' && collections?.length) {
|
|
1207
1213
|
builder = builder
|
|
1208
1214
|
.where('moderation_subject_status.recordPath', '!=', '')
|
|
1209
|
-
.where((
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1215
|
+
.where((eb) =>
|
|
1216
|
+
eb.or(
|
|
1217
|
+
collections.map((collection) =>
|
|
1218
|
+
eb(
|
|
1219
|
+
'moderation_subject_status.recordPath',
|
|
1220
|
+
'like',
|
|
1221
|
+
`${collection}/%`,
|
|
1222
|
+
),
|
|
1223
|
+
),
|
|
1224
|
+
),
|
|
1225
|
+
)
|
|
1219
1226
|
}
|
|
1220
1227
|
|
|
1221
1228
|
if (ignoreSubjects?.length) {
|
|
@@ -1325,30 +1332,32 @@ export class ModerationService {
|
|
|
1325
1332
|
}
|
|
1326
1333
|
|
|
1327
1334
|
if (!includeMuted) {
|
|
1328
|
-
builder = builder.where((
|
|
1329
|
-
|
|
1330
|
-
|
|
1335
|
+
builder = builder.where((eb) =>
|
|
1336
|
+
eb.or([
|
|
1337
|
+
eb(
|
|
1331
1338
|
'moderation_subject_status.muteUntil',
|
|
1332
1339
|
'<',
|
|
1333
1340
|
new Date().toISOString(),
|
|
1334
|
-
)
|
|
1335
|
-
|
|
1341
|
+
),
|
|
1342
|
+
eb('moderation_subject_status.muteUntil', 'is', null),
|
|
1343
|
+
]),
|
|
1336
1344
|
)
|
|
1337
1345
|
}
|
|
1338
1346
|
|
|
1339
1347
|
if (onlyMuted) {
|
|
1340
|
-
builder = builder.where((
|
|
1341
|
-
|
|
1342
|
-
|
|
1348
|
+
builder = builder.where((eb) =>
|
|
1349
|
+
eb.or([
|
|
1350
|
+
eb(
|
|
1343
1351
|
'moderation_subject_status.muteUntil',
|
|
1344
1352
|
'>',
|
|
1345
1353
|
new Date().toISOString(),
|
|
1346
|
-
)
|
|
1347
|
-
|
|
1354
|
+
),
|
|
1355
|
+
eb(
|
|
1348
1356
|
'moderation_subject_status.muteReportingUntil',
|
|
1349
1357
|
'>',
|
|
1350
1358
|
new Date().toISOString(),
|
|
1351
1359
|
),
|
|
1360
|
+
]),
|
|
1352
1361
|
)
|
|
1353
1362
|
}
|
|
1354
1363
|
|
|
@@ -1356,30 +1365,28 @@ export class ModerationService {
|
|
|
1356
1365
|
const conditions = parseTags(tags)
|
|
1357
1366
|
if (conditions?.length) {
|
|
1358
1367
|
// [["tag1"], ["tag2", "tag3"], ["tag4"]] => (tags ? 'tag1') OR (tags ? 'tag2' AND tags ? 'tag3') OR (tags ? 'tag4')
|
|
1359
|
-
builder = builder.where((
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1368
|
+
builder = builder.where((eb) =>
|
|
1369
|
+
// OR between every conditions items (subTags)
|
|
1370
|
+
eb.or(
|
|
1371
|
+
conditions.map((subTags) =>
|
|
1363
1372
|
// AND between every subTags items (subTag)
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
})
|
|
1373
|
+
eb.and(
|
|
1374
|
+
subTags.map(
|
|
1375
|
+
(subTag) =>
|
|
1376
|
+
sql<boolean>`${ref('moderation_subject_status.tags')} ? ${subTag}`,
|
|
1377
|
+
),
|
|
1378
|
+
),
|
|
1379
|
+
),
|
|
1380
|
+
),
|
|
1381
|
+
)
|
|
1374
1382
|
}
|
|
1375
1383
|
|
|
1376
1384
|
if (excludeTags?.length) {
|
|
1377
|
-
builder = builder.where((
|
|
1378
|
-
|
|
1379
|
-
.
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
.orWhere('tags', 'is', null),
|
|
1385
|
+
builder = builder.where((eb) =>
|
|
1386
|
+
eb.or([
|
|
1387
|
+
sql<boolean>`NOT(${ref('moderation_subject_status.tags')} ?| array[${sql.join(excludeTags)}]::TEXT[])`,
|
|
1388
|
+
eb('tags', 'is', null),
|
|
1389
|
+
]),
|
|
1383
1390
|
)
|
|
1384
1391
|
}
|
|
1385
1392
|
|
|
@@ -1620,15 +1627,15 @@ export class ModerationService {
|
|
|
1620
1627
|
const countAll = () => {
|
|
1621
1628
|
return sql<number>`COUNT(*)`
|
|
1622
1629
|
}
|
|
1623
|
-
const countAllDistinctBy = (ref:
|
|
1630
|
+
const countAllDistinctBy = (ref: Expression<unknown>) => {
|
|
1624
1631
|
return sql<number>`COUNT(DISTINCT ${ref})`
|
|
1625
1632
|
}
|
|
1626
|
-
const countTakedownsDistinctBy = (ref:
|
|
1633
|
+
const countTakedownsDistinctBy = (ref: Expression<unknown>) => {
|
|
1627
1634
|
return sql<number>`COUNT(DISTINCT ${ref}) FILTER (
|
|
1628
1635
|
WHERE actions."action" = 'tools.ozone.moderation.defs#modEventTakedown'
|
|
1629
1636
|
)`
|
|
1630
1637
|
}
|
|
1631
|
-
const countLabelsDistinctBy = (ref:
|
|
1638
|
+
const countLabelsDistinctBy = (ref: Expression<unknown>) => {
|
|
1632
1639
|
return sql<number>`COUNT(DISTINCT ${ref}) FILTER (
|
|
1633
1640
|
WHERE actions."action" = 'tools.ozone.moderation.defs#modEventLabel'
|
|
1634
1641
|
)`
|
|
@@ -74,7 +74,9 @@ export async function queryReports(
|
|
|
74
74
|
const collectionConditions = params.collections.map(
|
|
75
75
|
(collection) => sql`r."recordPath" LIKE ${`${collection}/%`}`,
|
|
76
76
|
)
|
|
77
|
-
builder = builder.where(
|
|
77
|
+
builder = builder.where(
|
|
78
|
+
sql<boolean>`(${sql.join(collectionConditions, sql` OR `)})`,
|
|
79
|
+
)
|
|
78
80
|
}
|
|
79
81
|
|
|
80
82
|
if (params.reportTypes?.length) {
|
|
@@ -112,12 +114,12 @@ export async function queryReports(
|
|
|
112
114
|
const [sortValue, id] = params.cursor.split('::')
|
|
113
115
|
const sortCol = sortField === 'updatedAt' ? 'r.updatedAt' : 'r.createdAt'
|
|
114
116
|
if (sortDirection === 'desc') {
|
|
115
|
-
builder = builder.where(sql
|
|
117
|
+
builder = builder.where(sql<boolean>`(
|
|
116
118
|
${sql.ref(sortCol)} < ${sortValue}
|
|
117
119
|
OR (${sql.ref(sortCol)} = ${sortValue} AND r.id < ${Number(id)})
|
|
118
120
|
)`)
|
|
119
121
|
} else {
|
|
120
|
-
builder = builder.where(sql
|
|
122
|
+
builder = builder.where(sql<boolean>`(
|
|
121
123
|
${sql.ref(sortCol)} > ${sortValue}
|
|
122
124
|
OR (${sql.ref(sortCol)} = ${sortValue} AND r.id > ${Number(id)})
|
|
123
125
|
)`)
|