@atproto/ozone 0.1.53 → 0.1.55
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 +27 -0
- package/dist/api/index.d.ts.map +1 -1
- package/dist/api/index.js +6 -0
- package/dist/api/index.js.map +1 -1
- package/dist/api/moderation/queryStatuses.d.ts.map +1 -1
- package/dist/api/moderation/queryStatuses.js +6 -1
- package/dist/api/moderation/queryStatuses.js.map +1 -1
- package/dist/api/setting/listOptions.d.ts +4 -0
- package/dist/api/setting/listOptions.d.ts.map +1 -0
- package/dist/api/setting/listOptions.js +38 -0
- package/dist/api/setting/listOptions.js.map +1 -0
- package/dist/api/setting/removeOptions.d.ts +4 -0
- package/dist/api/setting/removeOptions.d.ts.map +1 -0
- package/dist/api/setting/removeOptions.js +55 -0
- package/dist/api/setting/removeOptions.js.map +1 -0
- package/dist/api/setting/upsertOption.d.ts +4 -0
- package/dist/api/setting/upsertOption.d.ts.map +1 -0
- package/dist/api/setting/upsertOption.js +109 -0
- package/dist/api/setting/upsertOption.js.map +1 -0
- package/dist/context.d.ts +3 -0
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +6 -0
- package/dist/context.js.map +1 -1
- package/dist/db/migrations/20241018T205730722Z-setting.d.ts +4 -0
- package/dist/db/migrations/20241018T205730722Z-setting.d.ts.map +1 -0
- package/dist/db/migrations/20241018T205730722Z-setting.js +26 -0
- package/dist/db/migrations/20241018T205730722Z-setting.js.map +1 -0
- package/dist/db/migrations/20241026T205730722Z-add-hosting-status-to-subject-status.d.ts +4 -0
- package/dist/db/migrations/20241026T205730722Z-add-hosting-status-to-subject-status.d.ts.map +1 -0
- package/dist/db/migrations/20241026T205730722Z-add-hosting-status-to-subject-status.js +57 -0
- package/dist/db/migrations/20241026T205730722Z-add-hosting-status-to-subject-status.js.map +1 -0
- package/dist/db/migrations/index.d.ts +2 -0
- package/dist/db/migrations/index.d.ts.map +1 -1
- package/dist/db/migrations/index.js +3 -1
- package/dist/db/migrations/index.js.map +1 -1
- package/dist/db/schema/index.d.ts +2 -1
- package/dist/db/schema/index.d.ts.map +1 -1
- package/dist/db/schema/moderation_event.d.ts +1 -1
- package/dist/db/schema/moderation_event.d.ts.map +1 -1
- package/dist/db/schema/moderation_subject_status.d.ts +6 -0
- package/dist/db/schema/moderation_subject_status.d.ts.map +1 -1
- package/dist/db/schema/setting.d.ts +21 -0
- package/dist/db/schema/setting.d.ts.map +1 -0
- package/dist/db/schema/setting.js +5 -0
- package/dist/db/schema/setting.js.map +1 -0
- package/dist/lexicon/index.d.ts +11 -0
- package/dist/lexicon/index.d.ts.map +1 -1
- package/dist/lexicon/index.js +32 -1
- package/dist/lexicon/index.js.map +1 -1
- package/dist/lexicon/lexicons.d.ts +362 -1
- package/dist/lexicon/lexicons.d.ts.map +1 -1
- package/dist/lexicon/lexicons.js +406 -5
- package/dist/lexicon/lexicons.js.map +1 -1
- package/dist/lexicon/types/chat/bsky/convo/defs.d.ts +1 -0
- package/dist/lexicon/types/chat/bsky/convo/defs.d.ts.map +1 -1
- package/dist/lexicon/types/chat/bsky/convo/defs.js.map +1 -1
- package/dist/lexicon/types/tools/ozone/moderation/defs.d.ts +60 -4
- package/dist/lexicon/types/tools/ozone/moderation/defs.d.ts.map +1 -1
- package/dist/lexicon/types/tools/ozone/moderation/defs.js +50 -0
- package/dist/lexicon/types/tools/ozone/moderation/defs.js.map +1 -1
- package/dist/lexicon/types/tools/ozone/moderation/emitEvent.d.ts +1 -1
- package/dist/lexicon/types/tools/ozone/moderation/emitEvent.d.ts.map +1 -1
- package/dist/lexicon/types/tools/ozone/moderation/queryStatuses.d.ts +10 -0
- package/dist/lexicon/types/tools/ozone/moderation/queryStatuses.d.ts.map +1 -1
- package/dist/lexicon/types/tools/ozone/setting/defs.d.ts +20 -0
- package/dist/lexicon/types/tools/ozone/setting/defs.d.ts.map +1 -0
- package/dist/lexicon/types/tools/ozone/setting/defs.js +15 -0
- package/dist/lexicon/types/tools/ozone/setting/defs.js.map +1 -0
- package/dist/lexicon/types/tools/ozone/setting/listOptions.d.ts +43 -0
- package/dist/lexicon/types/tools/ozone/setting/listOptions.d.ts.map +1 -0
- package/dist/lexicon/types/tools/ozone/setting/listOptions.js +3 -0
- package/dist/lexicon/types/tools/ozone/setting/listOptions.js.map +1 -0
- package/dist/lexicon/types/tools/ozone/setting/removeOptions.d.ts +40 -0
- package/dist/lexicon/types/tools/ozone/setting/removeOptions.d.ts.map +1 -0
- package/dist/lexicon/types/tools/ozone/setting/removeOptions.js +3 -0
- package/dist/lexicon/types/tools/ozone/setting/removeOptions.js.map +1 -0
- package/dist/lexicon/types/tools/ozone/setting/upsertOption.d.ts +45 -0
- package/dist/lexicon/types/tools/ozone/setting/upsertOption.d.ts.map +1 -0
- package/dist/lexicon/types/tools/ozone/setting/upsertOption.js +3 -0
- package/dist/lexicon/types/tools/ozone/setting/upsertOption.js.map +1 -0
- package/dist/mod-service/index.d.ts +19 -2
- package/dist/mod-service/index.d.ts.map +1 -1
- package/dist/mod-service/index.js +37 -1
- package/dist/mod-service/index.js.map +1 -1
- package/dist/mod-service/status.d.ts +2 -22
- package/dist/mod-service/status.d.ts.map +1 -1
- package/dist/mod-service/status.js +91 -1
- package/dist/mod-service/status.js.map +1 -1
- package/dist/mod-service/types.d.ts +19 -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 +36 -1
- package/dist/mod-service/views.js.map +1 -1
- package/dist/setting/service.d.ts +33 -0
- package/dist/setting/service.d.ts.map +1 -0
- package/dist/setting/service.js +101 -0
- package/dist/setting/service.js.map +1 -0
- package/package.json +10 -10
- package/src/api/index.ts +6 -0
- package/src/api/moderation/queryStatuses.ts +10 -0
- package/src/api/setting/listOptions.ts +44 -0
- package/src/api/setting/removeOptions.ts +63 -0
- package/src/api/setting/upsertOption.ts +142 -0
- package/src/context.ts +8 -0
- package/src/db/migrations/20241018T205730722Z-setting.ts +27 -0
- package/src/db/migrations/20241026T205730722Z-add-hosting-status-to-subject-status.ts +57 -0
- package/src/db/migrations/index.ts +2 -0
- package/src/db/schema/index.ts +3 -1
- package/src/db/schema/moderation_event.ts +3 -0
- package/src/db/schema/moderation_subject_status.ts +6 -0
- package/src/db/schema/setting.ts +24 -0
- package/src/lexicon/index.ts +46 -0
- package/src/lexicon/lexicons.ts +417 -5
- package/src/lexicon/types/chat/bsky/convo/defs.ts +1 -0
- package/src/lexicon/types/tools/ozone/moderation/defs.ts +132 -2
- package/src/lexicon/types/tools/ozone/moderation/emitEvent.ts +3 -0
- package/src/lexicon/types/tools/ozone/moderation/queryStatuses.ts +10 -0
- package/src/lexicon/types/tools/ozone/setting/defs.ts +37 -0
- package/src/lexicon/types/tools/ozone/setting/listOptions.ts +53 -0
- package/src/lexicon/types/tools/ozone/setting/removeOptions.ts +49 -0
- package/src/lexicon/types/tools/ozone/setting/upsertOption.ts +58 -0
- package/src/mod-service/index.ts +52 -0
- package/src/mod-service/status.ts +114 -2
- package/src/mod-service/types.ts +25 -0
- package/src/mod-service/views.ts +45 -2
- package/src/setting/service.ts +148 -0
- package/tests/__snapshots__/get-record.test.ts.snap +8 -0
- package/tests/__snapshots__/get-records.test.ts.snap +4 -0
- package/tests/__snapshots__/get-repo.test.ts.snap +4 -0
- package/tests/__snapshots__/get-repos.test.ts.snap +4 -0
- package/tests/__snapshots__/moderation-events.test.ts.snap +4 -0
- package/tests/__snapshots__/moderation-statuses.test.ts.snap +24 -0
- package/tests/__snapshots__/settings.test.ts.snap +52 -0
- package/tests/record-and-account-events.test.ts +185 -0
- package/tests/settings.test.ts +310 -0
- package/tsconfig.build.tsbuildinfo +1 -1
- package/tsconfig.tests.tsbuildinfo +1 -1
|
@@ -22,6 +22,16 @@ export interface QueryParams {
|
|
|
22
22
|
reportedBefore?: string
|
|
23
23
|
/** Search subjects reviewed after a given timestamp */
|
|
24
24
|
reviewedAfter?: string
|
|
25
|
+
/** Search subjects where the associated record/account was deleted after a given timestamp */
|
|
26
|
+
hostingDeletedAfter?: string
|
|
27
|
+
/** Search subjects where the associated record/account was deleted before a given timestamp */
|
|
28
|
+
hostingDeletedBefore?: string
|
|
29
|
+
/** Search subjects where the associated record/account was updated after a given timestamp */
|
|
30
|
+
hostingUpdatedAfter?: string
|
|
31
|
+
/** Search subjects where the associated record/account was updated before a given timestamp */
|
|
32
|
+
hostingUpdatedBefore?: string
|
|
33
|
+
/** Search subjects by the status of the associated record/account */
|
|
34
|
+
hostingStatuses?: string[]
|
|
25
35
|
/** Search subjects reviewed before a given timestamp */
|
|
26
36
|
reviewedBefore?: string
|
|
27
37
|
/** By default, we don't include muted subjects in the results. Set this to true to include them. */
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GENERATED CODE - DO NOT MODIFY
|
|
3
|
+
*/
|
|
4
|
+
import { ValidationResult, BlobRef } from '@atproto/lexicon'
|
|
5
|
+
import { lexicons } from '../../../../lexicons'
|
|
6
|
+
import { isObj, hasProp } from '../../../../util'
|
|
7
|
+
import { CID } from 'multiformats/cid'
|
|
8
|
+
|
|
9
|
+
export interface Option {
|
|
10
|
+
key: string
|
|
11
|
+
did: string
|
|
12
|
+
value: {}
|
|
13
|
+
description?: string
|
|
14
|
+
createdAt?: string
|
|
15
|
+
updatedAt?: string
|
|
16
|
+
managerRole?:
|
|
17
|
+
| 'tools.ozone.team.defs#roleModerator'
|
|
18
|
+
| 'tools.ozone.team.defs#roleTriage'
|
|
19
|
+
| 'tools.ozone.team.defs#roleAdmin'
|
|
20
|
+
| (string & {})
|
|
21
|
+
scope: 'instance' | 'personal' | (string & {})
|
|
22
|
+
createdBy: string
|
|
23
|
+
lastUpdatedBy: string
|
|
24
|
+
[k: string]: unknown
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function isOption(v: unknown): v is Option {
|
|
28
|
+
return (
|
|
29
|
+
isObj(v) &&
|
|
30
|
+
hasProp(v, '$type') &&
|
|
31
|
+
v.$type === 'tools.ozone.setting.defs#option'
|
|
32
|
+
)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function validateOption(v: unknown): ValidationResult {
|
|
36
|
+
return lexicons.validate('tools.ozone.setting.defs#option', v)
|
|
37
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GENERATED CODE - DO NOT MODIFY
|
|
3
|
+
*/
|
|
4
|
+
import express from 'express'
|
|
5
|
+
import { ValidationResult, BlobRef } from '@atproto/lexicon'
|
|
6
|
+
import { lexicons } from '../../../../lexicons'
|
|
7
|
+
import { isObj, hasProp } from '../../../../util'
|
|
8
|
+
import { CID } from 'multiformats/cid'
|
|
9
|
+
import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server'
|
|
10
|
+
import * as ToolsOzoneSettingDefs from './defs'
|
|
11
|
+
|
|
12
|
+
export interface QueryParams {
|
|
13
|
+
limit: number
|
|
14
|
+
cursor?: string
|
|
15
|
+
scope: 'instance' | 'personal' | (string & {})
|
|
16
|
+
/** Filter keys by prefix */
|
|
17
|
+
prefix?: string
|
|
18
|
+
/** Filter for only the specified keys. Ignored if prefix is provided */
|
|
19
|
+
keys?: string[]
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export type InputSchema = undefined
|
|
23
|
+
|
|
24
|
+
export interface OutputSchema {
|
|
25
|
+
cursor?: string
|
|
26
|
+
options: ToolsOzoneSettingDefs.Option[]
|
|
27
|
+
[k: string]: unknown
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export type HandlerInput = undefined
|
|
31
|
+
|
|
32
|
+
export interface HandlerSuccess {
|
|
33
|
+
encoding: 'application/json'
|
|
34
|
+
body: OutputSchema
|
|
35
|
+
headers?: { [key: string]: string }
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface HandlerError {
|
|
39
|
+
status: number
|
|
40
|
+
message?: string
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough
|
|
44
|
+
export type HandlerReqCtx<HA extends HandlerAuth = never> = {
|
|
45
|
+
auth: HA
|
|
46
|
+
params: QueryParams
|
|
47
|
+
input: HandlerInput
|
|
48
|
+
req: express.Request
|
|
49
|
+
res: express.Response
|
|
50
|
+
}
|
|
51
|
+
export type Handler<HA extends HandlerAuth = never> = (
|
|
52
|
+
ctx: HandlerReqCtx<HA>,
|
|
53
|
+
) => Promise<HandlerOutput> | HandlerOutput
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GENERATED CODE - DO NOT MODIFY
|
|
3
|
+
*/
|
|
4
|
+
import express from 'express'
|
|
5
|
+
import { ValidationResult, BlobRef } from '@atproto/lexicon'
|
|
6
|
+
import { lexicons } from '../../../../lexicons'
|
|
7
|
+
import { isObj, hasProp } from '../../../../util'
|
|
8
|
+
import { CID } from 'multiformats/cid'
|
|
9
|
+
import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server'
|
|
10
|
+
|
|
11
|
+
export interface QueryParams {}
|
|
12
|
+
|
|
13
|
+
export interface InputSchema {
|
|
14
|
+
keys: string[]
|
|
15
|
+
scope: 'instance' | 'personal' | (string & {})
|
|
16
|
+
[k: string]: unknown
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface OutputSchema {
|
|
20
|
+
[k: string]: unknown
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface HandlerInput {
|
|
24
|
+
encoding: 'application/json'
|
|
25
|
+
body: InputSchema
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface HandlerSuccess {
|
|
29
|
+
encoding: 'application/json'
|
|
30
|
+
body: OutputSchema
|
|
31
|
+
headers?: { [key: string]: string }
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface HandlerError {
|
|
35
|
+
status: number
|
|
36
|
+
message?: string
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough
|
|
40
|
+
export type HandlerReqCtx<HA extends HandlerAuth = never> = {
|
|
41
|
+
auth: HA
|
|
42
|
+
params: QueryParams
|
|
43
|
+
input: HandlerInput
|
|
44
|
+
req: express.Request
|
|
45
|
+
res: express.Response
|
|
46
|
+
}
|
|
47
|
+
export type Handler<HA extends HandlerAuth = never> = (
|
|
48
|
+
ctx: HandlerReqCtx<HA>,
|
|
49
|
+
) => Promise<HandlerOutput> | HandlerOutput
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GENERATED CODE - DO NOT MODIFY
|
|
3
|
+
*/
|
|
4
|
+
import express from 'express'
|
|
5
|
+
import { ValidationResult, BlobRef } from '@atproto/lexicon'
|
|
6
|
+
import { lexicons } from '../../../../lexicons'
|
|
7
|
+
import { isObj, hasProp } from '../../../../util'
|
|
8
|
+
import { CID } from 'multiformats/cid'
|
|
9
|
+
import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server'
|
|
10
|
+
import * as ToolsOzoneSettingDefs from './defs'
|
|
11
|
+
|
|
12
|
+
export interface QueryParams {}
|
|
13
|
+
|
|
14
|
+
export interface InputSchema {
|
|
15
|
+
key: string
|
|
16
|
+
scope: 'instance' | 'personal' | (string & {})
|
|
17
|
+
value: {}
|
|
18
|
+
description?: string
|
|
19
|
+
managerRole?:
|
|
20
|
+
| 'tools.ozone.team.defs#roleModerator'
|
|
21
|
+
| 'tools.ozone.team.defs#roleTriage'
|
|
22
|
+
| 'tools.ozone.team.defs#roleAdmin'
|
|
23
|
+
| (string & {})
|
|
24
|
+
[k: string]: unknown
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface OutputSchema {
|
|
28
|
+
option: ToolsOzoneSettingDefs.Option
|
|
29
|
+
[k: string]: unknown
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface HandlerInput {
|
|
33
|
+
encoding: 'application/json'
|
|
34
|
+
body: InputSchema
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface HandlerSuccess {
|
|
38
|
+
encoding: 'application/json'
|
|
39
|
+
body: OutputSchema
|
|
40
|
+
headers?: { [key: string]: string }
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface HandlerError {
|
|
44
|
+
status: number
|
|
45
|
+
message?: string
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough
|
|
49
|
+
export type HandlerReqCtx<HA extends HandlerAuth = never> = {
|
|
50
|
+
auth: HA
|
|
51
|
+
params: QueryParams
|
|
52
|
+
input: HandlerInput
|
|
53
|
+
req: express.Request
|
|
54
|
+
res: express.Response
|
|
55
|
+
}
|
|
56
|
+
export type Handler<HA extends HandlerAuth = never> = (
|
|
57
|
+
ctx: HandlerReqCtx<HA>,
|
|
58
|
+
) => Promise<HandlerOutput> | HandlerOutput
|
package/src/mod-service/index.ts
CHANGED
|
@@ -18,6 +18,9 @@ import {
|
|
|
18
18
|
isModEventTakedown,
|
|
19
19
|
isModEventEmail,
|
|
20
20
|
isModEventTag,
|
|
21
|
+
isAccountEvent,
|
|
22
|
+
isIdentityEvent,
|
|
23
|
+
isRecordEvent,
|
|
21
24
|
REVIEWESCALATED,
|
|
22
25
|
REVIEWOPEN,
|
|
23
26
|
} from '../lexicon/types/tools/ozone/moderation/defs'
|
|
@@ -386,6 +389,25 @@ export class ModerationService {
|
|
|
386
389
|
}
|
|
387
390
|
}
|
|
388
391
|
|
|
392
|
+
if (isAccountEvent(event)) {
|
|
393
|
+
meta.active = event.active
|
|
394
|
+
meta.timestamp = event.timestamp
|
|
395
|
+
if (event.status) meta.status = event.status
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
if (isIdentityEvent(event)) {
|
|
399
|
+
meta.timestamp = event.timestamp
|
|
400
|
+
if (event.handle) meta.handle = event.handle
|
|
401
|
+
if (event.pdsHost) meta.pdsHost = event.pdsHost
|
|
402
|
+
if (event.tombstone) meta.tombstone = event.tombstone
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
if (isRecordEvent(event)) {
|
|
406
|
+
meta.timestamp = event.timestamp
|
|
407
|
+
meta.op = event.op
|
|
408
|
+
if (event.cid) meta.cid = event.cid
|
|
409
|
+
}
|
|
410
|
+
|
|
389
411
|
if (isModEventTakedown(event) && event.acknowledgeAccountSubjects) {
|
|
390
412
|
meta.acknowledgeAccountSubjects = true
|
|
391
413
|
}
|
|
@@ -758,6 +780,11 @@ export class ModerationService {
|
|
|
758
780
|
reportedAfter,
|
|
759
781
|
reportedBefore,
|
|
760
782
|
includeMuted,
|
|
783
|
+
hostingDeletedBefore,
|
|
784
|
+
hostingDeletedAfter,
|
|
785
|
+
hostingUpdatedBefore,
|
|
786
|
+
hostingUpdatedAfter,
|
|
787
|
+
hostingStatuses,
|
|
761
788
|
onlyMuted,
|
|
762
789
|
ignoreSubjects,
|
|
763
790
|
sortDirection,
|
|
@@ -780,6 +807,11 @@ export class ModerationService {
|
|
|
780
807
|
reportedAfter?: string
|
|
781
808
|
reportedBefore?: string
|
|
782
809
|
includeMuted?: boolean
|
|
810
|
+
hostingDeletedBefore?: string
|
|
811
|
+
hostingDeletedAfter?: string
|
|
812
|
+
hostingUpdatedBefore?: string
|
|
813
|
+
hostingUpdatedAfter?: string
|
|
814
|
+
hostingStatuses?: string[]
|
|
783
815
|
onlyMuted?: boolean
|
|
784
816
|
subject?: string
|
|
785
817
|
ignoreSubjects?: string[]
|
|
@@ -847,6 +879,26 @@ export class ModerationService {
|
|
|
847
879
|
builder = builder.where('lastReviewedAt', '<', reviewedBefore)
|
|
848
880
|
}
|
|
849
881
|
|
|
882
|
+
if (hostingUpdatedAfter) {
|
|
883
|
+
builder = builder.where('hostingUpdatedAt', '>', hostingUpdatedAfter)
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
if (hostingUpdatedBefore) {
|
|
887
|
+
builder = builder.where('hostingUpdatedAt', '<', hostingUpdatedBefore)
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
if (hostingDeletedAfter) {
|
|
891
|
+
builder = builder.where('hostingDeletedAt', '>', hostingDeletedAfter)
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
if (hostingDeletedBefore) {
|
|
895
|
+
builder = builder.where('hostingDeletedAt', '<', hostingDeletedBefore)
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
if (hostingStatuses?.length) {
|
|
899
|
+
builder = builder.where('hostingStatus', 'in', hostingStatuses)
|
|
900
|
+
}
|
|
901
|
+
|
|
850
902
|
if (reportedAfter) {
|
|
851
903
|
builder = builder.where('lastReviewedAt', '>', reportedAfter)
|
|
852
904
|
}
|
|
@@ -126,6 +126,83 @@ const getSubjectStatusForModerationEvent = ({
|
|
|
126
126
|
}
|
|
127
127
|
}
|
|
128
128
|
|
|
129
|
+
const hostingEvents = [
|
|
130
|
+
'tools.ozone.moderation.defs#accountEvent',
|
|
131
|
+
'tools.ozone.moderation.defs#identityEvent',
|
|
132
|
+
'tools.ozone.moderation.defs#recordEvent',
|
|
133
|
+
]
|
|
134
|
+
|
|
135
|
+
const getSubjectStatusForRecordEvent = ({
|
|
136
|
+
event,
|
|
137
|
+
currentStatus,
|
|
138
|
+
}: {
|
|
139
|
+
event: ModerationEventRow
|
|
140
|
+
currentStatus?: ModerationSubjectStatusRow
|
|
141
|
+
}): Partial<ModerationSubjectStatusRow> => {
|
|
142
|
+
const timestamp =
|
|
143
|
+
typeof event.meta?.timestamp === 'string'
|
|
144
|
+
? event.meta?.timestamp
|
|
145
|
+
: event.createdAt
|
|
146
|
+
|
|
147
|
+
if (event.action === 'tools.ozone.moderation.defs#recordEvent') {
|
|
148
|
+
if (event.meta?.op === 'delete') {
|
|
149
|
+
return {
|
|
150
|
+
hostingStatus: 'deleted',
|
|
151
|
+
hostingDeletedAt: timestamp,
|
|
152
|
+
}
|
|
153
|
+
} else if (event.meta?.op === 'update') {
|
|
154
|
+
return {
|
|
155
|
+
hostingStatus: 'active',
|
|
156
|
+
hostingUpdatedAt: timestamp,
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
return {}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (event.action === 'tools.ozone.moderation.defs#accountEvent') {
|
|
163
|
+
const status: Partial<ModerationSubjectStatusRow> = {
|
|
164
|
+
hostingUpdatedAt: timestamp,
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if (event.meta?.status) {
|
|
168
|
+
status.hostingStatus = `${event.meta?.status}`
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if (event.meta?.status === 'deleted') {
|
|
172
|
+
status.hostingDeletedAt = timestamp
|
|
173
|
+
} else if (event.meta?.status === 'deactivated') {
|
|
174
|
+
status.hostingDeactivatedAt = timestamp
|
|
175
|
+
} else {
|
|
176
|
+
// When deactivated accounts are re-activated, we receive the event with just the active flag set to true
|
|
177
|
+
// so we want to make sure that the hostingStatus is not set to an outdated value
|
|
178
|
+
if (
|
|
179
|
+
currentStatus?.hostingStatus === 'deactivated' &&
|
|
180
|
+
event.meta?.active
|
|
181
|
+
) {
|
|
182
|
+
status.hostingStatus = 'active'
|
|
183
|
+
status.hostingReactivatedAt = timestamp
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
return status
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (event.action === 'tools.ozone.moderation.defs#identityEvent') {
|
|
191
|
+
const status: Partial<ModerationSubjectStatusRow> = {
|
|
192
|
+
hostingUpdatedAt: timestamp,
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
if (event.meta?.tombstone) {
|
|
196
|
+
status.hostingStatus = 'tombstoned'
|
|
197
|
+
status.hostingDeletedAt = timestamp
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
return status
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
return {}
|
|
204
|
+
}
|
|
205
|
+
|
|
129
206
|
// Based on a given moderation action event, this function will update the moderation status of the subject
|
|
130
207
|
// If there's no existing status, it will create one
|
|
131
208
|
// If the action event does not affect the status, it will do nothing
|
|
@@ -133,7 +210,7 @@ export const adjustModerationSubjectStatus = async (
|
|
|
133
210
|
db: Database,
|
|
134
211
|
moderationEvent: ModerationEventRow,
|
|
135
212
|
blobCids?: string[],
|
|
136
|
-
) => {
|
|
213
|
+
): Promise<ModerationSubjectStatusRow | null> => {
|
|
137
214
|
const {
|
|
138
215
|
action,
|
|
139
216
|
subjectDid,
|
|
@@ -152,6 +229,7 @@ export const adjustModerationSubjectStatus = async (
|
|
|
152
229
|
|
|
153
230
|
db.assertTransaction()
|
|
154
231
|
|
|
232
|
+
const now = new Date().toISOString()
|
|
155
233
|
const currentStatus = await db.db
|
|
156
234
|
.selectFrom('moderation_subject_status')
|
|
157
235
|
.where('did', '=', identifier.did)
|
|
@@ -161,6 +239,41 @@ export const adjustModerationSubjectStatus = async (
|
|
|
161
239
|
.selectAll()
|
|
162
240
|
.executeTakeFirst()
|
|
163
241
|
|
|
242
|
+
if (hostingEvents.includes(action)) {
|
|
243
|
+
const newStatus = getSubjectStatusForRecordEvent({
|
|
244
|
+
event: moderationEvent,
|
|
245
|
+
currentStatus,
|
|
246
|
+
})
|
|
247
|
+
if (!Object.keys(newStatus).length) {
|
|
248
|
+
return currentStatus || null
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
const status = await db.db
|
|
252
|
+
.insertInto('moderation_subject_status')
|
|
253
|
+
.values({
|
|
254
|
+
...identifier,
|
|
255
|
+
...newStatus,
|
|
256
|
+
// newStatus doesn't contain a reviewState or takendown so in case this is a new entry
|
|
257
|
+
// we need to set a default values so that the insert doesn't fail
|
|
258
|
+
reviewState: currentStatus ? currentStatus.reviewState : REVIEWNONE,
|
|
259
|
+
// @TODO: should we try to update this based on status property of account event?
|
|
260
|
+
// For now we're the only one emitting takedowns so i don't think it makes too much of a difference
|
|
261
|
+
takendown: currentStatus ? currentStatus.takendown : false,
|
|
262
|
+
createdAt: now,
|
|
263
|
+
updatedAt: now,
|
|
264
|
+
})
|
|
265
|
+
.onConflict((oc) =>
|
|
266
|
+
oc.constraint('moderation_status_unique_idx').doUpdateSet({
|
|
267
|
+
...newStatus,
|
|
268
|
+
updatedAt: now,
|
|
269
|
+
}),
|
|
270
|
+
)
|
|
271
|
+
.returningAll()
|
|
272
|
+
.executeTakeFirst()
|
|
273
|
+
|
|
274
|
+
return status || null
|
|
275
|
+
}
|
|
276
|
+
|
|
164
277
|
// If reporting is muted for this reporter, we don't want to update the subject status
|
|
165
278
|
if (meta?.isReporterMuted) {
|
|
166
279
|
return currentStatus || null
|
|
@@ -178,7 +291,6 @@ export const adjustModerationSubjectStatus = async (
|
|
|
178
291
|
durationInHours: moderationEvent.durationInHours,
|
|
179
292
|
})
|
|
180
293
|
|
|
181
|
-
const now = new Date().toISOString()
|
|
182
294
|
if (
|
|
183
295
|
currentStatus?.reviewState === REVIEWESCALATED &&
|
|
184
296
|
subjectStatus.reviewState !== REVIEWCLOSED
|
package/src/mod-service/types.ts
CHANGED
|
@@ -31,3 +31,28 @@ export type ModEventType =
|
|
|
31
31
|
| ToolsOzoneModerationDefs.ModEventMute
|
|
32
32
|
| ToolsOzoneModerationDefs.ModEventReverseTakedown
|
|
33
33
|
| ToolsOzoneModerationDefs.ModEventTag
|
|
34
|
+
| ToolsOzoneModerationDefs.AccountEvent
|
|
35
|
+
| ToolsOzoneModerationDefs.IdentityEvent
|
|
36
|
+
| ToolsOzoneModerationDefs.RecordEvent
|
|
37
|
+
|
|
38
|
+
type AccountHostingView = {
|
|
39
|
+
$type: 'tools.ozone.moderation.defs#accountHosting'
|
|
40
|
+
status: 'active' | 'takendown' | 'suspended' | 'deleted' | 'deactivated'
|
|
41
|
+
createdAt?: Date
|
|
42
|
+
updatedAt?: Date
|
|
43
|
+
deletedAt?: Date
|
|
44
|
+
deactivatedAt?: Date
|
|
45
|
+
reactivatedAt?: Date
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
type RecordHostingView = {
|
|
49
|
+
$type: 'tools.ozone.moderation.defs#recordHosting'
|
|
50
|
+
status: 'active' | 'deleted'
|
|
51
|
+
createdAt?: Date
|
|
52
|
+
updatedAt?: Date
|
|
53
|
+
deletedAt?: Date
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export type ModerationSubjectHostingView =
|
|
57
|
+
| AccountHostingView
|
|
58
|
+
| RecordHostingView
|
package/src/mod-service/views.ts
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import { sql } from 'kysely'
|
|
2
2
|
import { AtUri, INVALID_HANDLE, normalizeDatetimeAlways } from '@atproto/syntax'
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
AtpAgent,
|
|
5
|
+
AppBskyFeedDefs,
|
|
6
|
+
ToolsOzoneModerationDefs,
|
|
7
|
+
} from '@atproto/api'
|
|
4
8
|
import { dedupeStrs } from '@atproto/common'
|
|
5
9
|
import { BlobRef } from '@atproto/lexicon'
|
|
6
10
|
import { Keypair } from '@atproto/crypto'
|
|
@@ -195,6 +199,25 @@ export class ModerationViews {
|
|
|
195
199
|
eventView.event.remove = event.removedTags || []
|
|
196
200
|
}
|
|
197
201
|
|
|
202
|
+
if (event.action === 'tools.ozone.moderation.defs#accountEvent') {
|
|
203
|
+
eventView.event.active = !!event.meta?.active
|
|
204
|
+
eventView.event.timestamp = event.meta?.timestamp
|
|
205
|
+
eventView.event.status = event.meta?.status
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
if (event.action === 'tools.ozone.moderation.defs#identityEvent') {
|
|
209
|
+
eventView.event.timestamp = event.meta?.timestamp
|
|
210
|
+
eventView.event.handle = event.meta?.handle
|
|
211
|
+
eventView.event.pdsHost = event.meta?.pdsHost
|
|
212
|
+
eventView.event.tombstone = !!event.meta?.tombstone
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
if (event.action === 'tools.ozone.moderation.defs#recordEvent') {
|
|
216
|
+
eventView.event.op = event.meta?.op
|
|
217
|
+
eventView.event.cid = event.meta?.cid
|
|
218
|
+
eventView.event.timestamp = event.meta?.timestamp
|
|
219
|
+
}
|
|
220
|
+
|
|
198
221
|
return eventView
|
|
199
222
|
}
|
|
200
223
|
|
|
@@ -574,7 +597,7 @@ export class ModerationViews {
|
|
|
574
597
|
formatSubjectStatus(
|
|
575
598
|
status: ModerationSubjectStatusRowWithHandle,
|
|
576
599
|
): SubjectStatusView {
|
|
577
|
-
|
|
600
|
+
const statusView: SubjectStatusView = {
|
|
578
601
|
id: status.id,
|
|
579
602
|
reviewState: status.reviewState,
|
|
580
603
|
createdAt: status.createdAt,
|
|
@@ -594,6 +617,26 @@ export class ModerationViews {
|
|
|
594
617
|
tags: status.tags || [],
|
|
595
618
|
subject: subjectFromStatusRow(status).lex(),
|
|
596
619
|
}
|
|
620
|
+
|
|
621
|
+
if (status.recordPath !== '') {
|
|
622
|
+
statusView.hosting = {
|
|
623
|
+
$type: 'tools.ozone.moderation.defs#recordHosting',
|
|
624
|
+
updatedAt: status.hostingUpdatedAt ?? undefined,
|
|
625
|
+
deletedAt: status.hostingDeletedAt ?? undefined,
|
|
626
|
+
status: status.hostingStatus ?? 'unknown',
|
|
627
|
+
}
|
|
628
|
+
} else {
|
|
629
|
+
statusView.hosting = {
|
|
630
|
+
$type: 'tools.ozone.moderation.defs#accountHosting',
|
|
631
|
+
updatedAt: status.hostingUpdatedAt ?? undefined,
|
|
632
|
+
deletedAt: status.hostingDeletedAt ?? undefined,
|
|
633
|
+
status: status.hostingStatus ?? 'unknown',
|
|
634
|
+
deactivatedAt: status.hostingDeactivatedAt ?? undefined,
|
|
635
|
+
reactivatedAt: status.hostingReactivatedAt ?? undefined,
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
return statusView
|
|
597
640
|
}
|
|
598
641
|
|
|
599
642
|
async fetchAuthorFeed(
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import Database from '../db'
|
|
2
|
+
import { Selectable } from 'kysely'
|
|
3
|
+
import { Option } from '../lexicon/types/tools/ozone/setting/defs'
|
|
4
|
+
import { Setting, SettingScope } from '../db/schema/setting'
|
|
5
|
+
import { Member } from '../db/schema/member'
|
|
6
|
+
import assert from 'node:assert'
|
|
7
|
+
import { InvalidRequestError } from '@atproto/xrpc-server'
|
|
8
|
+
|
|
9
|
+
export type SettingServiceCreator = (db: Database) => SettingService
|
|
10
|
+
|
|
11
|
+
export class SettingService {
|
|
12
|
+
constructor(public db: Database) {}
|
|
13
|
+
|
|
14
|
+
static creator() {
|
|
15
|
+
return (db: Database) => new SettingService(db)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
async query({
|
|
19
|
+
limit = 100,
|
|
20
|
+
scope,
|
|
21
|
+
did,
|
|
22
|
+
cursor,
|
|
23
|
+
prefix,
|
|
24
|
+
keys,
|
|
25
|
+
}: {
|
|
26
|
+
limit: number
|
|
27
|
+
scope?: 'personal' | 'instance'
|
|
28
|
+
did?: string
|
|
29
|
+
cursor?: string
|
|
30
|
+
prefix?: string
|
|
31
|
+
keys?: string[]
|
|
32
|
+
}): Promise<{
|
|
33
|
+
options: Selectable<Setting>[]
|
|
34
|
+
cursor?: string
|
|
35
|
+
}> {
|
|
36
|
+
let builder = this.db.db.selectFrom('setting').selectAll()
|
|
37
|
+
|
|
38
|
+
if (prefix) {
|
|
39
|
+
builder = builder.where('key', 'like', `${prefix}%`)
|
|
40
|
+
} else if (keys?.length) {
|
|
41
|
+
builder = builder.where('key', 'in', keys)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (scope) {
|
|
45
|
+
builder = builder.where('scope', '=', scope)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (did) {
|
|
49
|
+
builder = builder.where('did', '=', did)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (cursor) {
|
|
53
|
+
const cursorId = parseInt(cursor, 10)
|
|
54
|
+
if (isNaN(cursorId)) {
|
|
55
|
+
throw new InvalidRequestError('invalid cursor')
|
|
56
|
+
}
|
|
57
|
+
builder = builder.where('id', '<', cursorId)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const options = await builder.orderBy('id', 'desc').limit(limit).execute()
|
|
61
|
+
|
|
62
|
+
return {
|
|
63
|
+
options,
|
|
64
|
+
cursor: options[options.length - 1]?.id.toString(),
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
async upsert(
|
|
69
|
+
option: Omit<Setting, 'id' | 'createdAt' | 'updatedAt'> & {
|
|
70
|
+
createdAt: Date
|
|
71
|
+
updatedAt: Date
|
|
72
|
+
},
|
|
73
|
+
): Promise<void> {
|
|
74
|
+
await this.db.db
|
|
75
|
+
.insertInto('setting')
|
|
76
|
+
.values(option)
|
|
77
|
+
.onConflict((oc) => {
|
|
78
|
+
return oc.columns(['key', 'scope', 'did']).doUpdateSet({
|
|
79
|
+
value: option.value,
|
|
80
|
+
updatedAt: option.updatedAt,
|
|
81
|
+
description: option.description,
|
|
82
|
+
managerRole: option.managerRole,
|
|
83
|
+
lastUpdatedBy: option.lastUpdatedBy,
|
|
84
|
+
})
|
|
85
|
+
})
|
|
86
|
+
.execute()
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
async removeOptions(
|
|
90
|
+
keys: string[],
|
|
91
|
+
filters: {
|
|
92
|
+
did?: string
|
|
93
|
+
scope: SettingScope
|
|
94
|
+
managerRole: Member['role'][]
|
|
95
|
+
},
|
|
96
|
+
): Promise<void> {
|
|
97
|
+
if (!keys.length) return
|
|
98
|
+
|
|
99
|
+
if (filters.scope === 'personal') {
|
|
100
|
+
assert(filters.did, 'did is required for personal scope')
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
let qb = this.db.db
|
|
104
|
+
.deleteFrom('setting')
|
|
105
|
+
.where('key', 'in', keys)
|
|
106
|
+
.where('scope', '=', filters.scope)
|
|
107
|
+
|
|
108
|
+
if (filters.managerRole.length) {
|
|
109
|
+
qb = qb.where('managerRole', 'in', filters.managerRole)
|
|
110
|
+
} else {
|
|
111
|
+
qb = qb.where('managerRole', 'is', null)
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (filters.did) {
|
|
115
|
+
qb = qb.where('did', '=', filters.did)
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
await qb.execute()
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
view(setting: Selectable<Setting>): Option {
|
|
122
|
+
const {
|
|
123
|
+
key,
|
|
124
|
+
value,
|
|
125
|
+
did,
|
|
126
|
+
description,
|
|
127
|
+
createdAt,
|
|
128
|
+
createdBy,
|
|
129
|
+
updatedAt,
|
|
130
|
+
lastUpdatedBy,
|
|
131
|
+
managerRole,
|
|
132
|
+
scope,
|
|
133
|
+
} = setting
|
|
134
|
+
|
|
135
|
+
return {
|
|
136
|
+
key,
|
|
137
|
+
value,
|
|
138
|
+
did,
|
|
139
|
+
scope,
|
|
140
|
+
createdBy,
|
|
141
|
+
lastUpdatedBy,
|
|
142
|
+
managerRole: managerRole || undefined,
|
|
143
|
+
description: description || undefined,
|
|
144
|
+
createdAt: createdAt.toISOString(),
|
|
145
|
+
updatedAt: updatedAt.toISOString(),
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|