✨ Add reporter_stats materialized view and endpoint to fetch reporter stats (#3509)
* ✨ Add reporter_stats materialized view and endpoint to fetch reporter stats * 🚨 Fix linter issues * ✨ Change reporter stats query from materialized view to on demand select * Add "createdAt" as part of the index --------- Co-authored-by: Matthieu Sieben <matthieu.sieben@gmail.com> Co-authored-by: Matthieu Sieben <matthieusieben@users.noreply.github.com>
This commit is contained in:
parent
f1d323a6ef
commit
b41ff4b4e3
@ -812,6 +812,58 @@
|
||||
"format": "datetime"
|
||||
}
|
||||
}
|
||||
},
|
||||
"reporterStats": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"did",
|
||||
"accountReportCount",
|
||||
"recordReportCount",
|
||||
"reportedAccountCount",
|
||||
"reportedRecordCount",
|
||||
"takendownAccountCount",
|
||||
"takendownRecordCount",
|
||||
"labeledAccountCount",
|
||||
"labeledRecordCount"
|
||||
],
|
||||
"properties": {
|
||||
"did": {
|
||||
"type": "string",
|
||||
"format": "did"
|
||||
},
|
||||
"accountReportCount": {
|
||||
"type": "integer",
|
||||
"description": "The total number of reports made by the user on accounts."
|
||||
},
|
||||
"recordReportCount": {
|
||||
"type": "integer",
|
||||
"description": "The total number of reports made by the user on records."
|
||||
},
|
||||
"reportedAccountCount": {
|
||||
"type": "integer",
|
||||
"description": "The total number of accounts reported by the user."
|
||||
},
|
||||
"reportedRecordCount": {
|
||||
"type": "integer",
|
||||
"description": "The total number of records reported by the user."
|
||||
},
|
||||
"takendownAccountCount": {
|
||||
"type": "integer",
|
||||
"description": "The total number of accounts taken down as a result of the user's reports."
|
||||
},
|
||||
"takendownRecordCount": {
|
||||
"type": "integer",
|
||||
"description": "The total number of records taken down as a result of the user's reports."
|
||||
},
|
||||
"labeledAccountCount": {
|
||||
"type": "integer",
|
||||
"description": "The total number of accounts labeled as a result of the user's reports."
|
||||
},
|
||||
"labeledRecordCount": {
|
||||
"type": "integer",
|
||||
"description": "The total number of records labeled as a result of the user's reports."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
40
lexicons/tools/ozone/moderation/getReporterStats.json
Normal file
40
lexicons/tools/ozone/moderation/getReporterStats.json
Normal file
@ -0,0 +1,40 @@
|
||||
{
|
||||
"lexicon": 1,
|
||||
"id": "tools.ozone.moderation.getReporterStats",
|
||||
"defs": {
|
||||
"main": {
|
||||
"type": "query",
|
||||
"description": "Get reporter stats for a list of users.",
|
||||
"parameters": {
|
||||
"type": "params",
|
||||
"required": ["dids"],
|
||||
"properties": {
|
||||
"dids": {
|
||||
"type": "array",
|
||||
"maxLength": 100,
|
||||
"items": {
|
||||
"type": "string",
|
||||
"format": "did"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"output": {
|
||||
"encoding": "application/json",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"required": ["stats"],
|
||||
"properties": {
|
||||
"stats": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "ref",
|
||||
"ref": "tools.ozone.moderation.defs#reporterStats"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -208,6 +208,7 @@ import * as ToolsOzoneModerationGetEvent from './types/tools/ozone/moderation/ge
|
||||
import * as ToolsOzoneModerationGetRecord from './types/tools/ozone/moderation/getRecord.js'
|
||||
import * as ToolsOzoneModerationGetRecords from './types/tools/ozone/moderation/getRecords.js'
|
||||
import * as ToolsOzoneModerationGetRepo from './types/tools/ozone/moderation/getRepo.js'
|
||||
import * as ToolsOzoneModerationGetReporterStats from './types/tools/ozone/moderation/getReporterStats.js'
|
||||
import * as ToolsOzoneModerationGetRepos from './types/tools/ozone/moderation/getRepos.js'
|
||||
import * as ToolsOzoneModerationQueryEvents from './types/tools/ozone/moderation/queryEvents.js'
|
||||
import * as ToolsOzoneModerationQueryStatuses from './types/tools/ozone/moderation/queryStatuses.js'
|
||||
@ -437,6 +438,7 @@ export * as ToolsOzoneModerationGetEvent from './types/tools/ozone/moderation/ge
|
||||
export * as ToolsOzoneModerationGetRecord from './types/tools/ozone/moderation/getRecord.js'
|
||||
export * as ToolsOzoneModerationGetRecords from './types/tools/ozone/moderation/getRecords.js'
|
||||
export * as ToolsOzoneModerationGetRepo from './types/tools/ozone/moderation/getRepo.js'
|
||||
export * as ToolsOzoneModerationGetReporterStats from './types/tools/ozone/moderation/getReporterStats.js'
|
||||
export * as ToolsOzoneModerationGetRepos from './types/tools/ozone/moderation/getRepos.js'
|
||||
export * as ToolsOzoneModerationQueryEvents from './types/tools/ozone/moderation/queryEvents.js'
|
||||
export * as ToolsOzoneModerationQueryStatuses from './types/tools/ozone/moderation/queryStatuses.js'
|
||||
@ -3722,6 +3724,18 @@ export class ToolsOzoneModerationNS {
|
||||
})
|
||||
}
|
||||
|
||||
getReporterStats(
|
||||
params?: ToolsOzoneModerationGetReporterStats.QueryParams,
|
||||
opts?: ToolsOzoneModerationGetReporterStats.CallOptions,
|
||||
): Promise<ToolsOzoneModerationGetReporterStats.Response> {
|
||||
return this._client.call(
|
||||
'tools.ozone.moderation.getReporterStats',
|
||||
params,
|
||||
undefined,
|
||||
opts,
|
||||
)
|
||||
}
|
||||
|
||||
getRepos(
|
||||
params?: ToolsOzoneModerationGetRepos.QueryParams,
|
||||
opts?: ToolsOzoneModerationGetRepos.CallOptions,
|
||||
|
@ -12219,6 +12219,64 @@ export const schemaDict = {
|
||||
},
|
||||
},
|
||||
},
|
||||
reporterStats: {
|
||||
type: 'object',
|
||||
required: [
|
||||
'did',
|
||||
'accountReportCount',
|
||||
'recordReportCount',
|
||||
'reportedAccountCount',
|
||||
'reportedRecordCount',
|
||||
'takendownAccountCount',
|
||||
'takendownRecordCount',
|
||||
'labeledAccountCount',
|
||||
'labeledRecordCount',
|
||||
],
|
||||
properties: {
|
||||
did: {
|
||||
type: 'string',
|
||||
format: 'did',
|
||||
},
|
||||
accountReportCount: {
|
||||
type: 'integer',
|
||||
description:
|
||||
'The total number of reports made by the user on accounts.',
|
||||
},
|
||||
recordReportCount: {
|
||||
type: 'integer',
|
||||
description:
|
||||
'The total number of reports made by the user on records.',
|
||||
},
|
||||
reportedAccountCount: {
|
||||
type: 'integer',
|
||||
description: 'The total number of accounts reported by the user.',
|
||||
},
|
||||
reportedRecordCount: {
|
||||
type: 'integer',
|
||||
description: 'The total number of records reported by the user.',
|
||||
},
|
||||
takendownAccountCount: {
|
||||
type: 'integer',
|
||||
description:
|
||||
"The total number of accounts taken down as a result of the user's reports.",
|
||||
},
|
||||
takendownRecordCount: {
|
||||
type: 'integer',
|
||||
description:
|
||||
"The total number of records taken down as a result of the user's reports.",
|
||||
},
|
||||
labeledAccountCount: {
|
||||
type: 'integer',
|
||||
description:
|
||||
"The total number of accounts labeled as a result of the user's reports.",
|
||||
},
|
||||
labeledRecordCount: {
|
||||
type: 'integer',
|
||||
description:
|
||||
"The total number of records labeled as a result of the user's reports.",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
ToolsOzoneModerationEmitEvent: {
|
||||
@ -12430,6 +12488,46 @@ export const schemaDict = {
|
||||
},
|
||||
},
|
||||
},
|
||||
ToolsOzoneModerationGetReporterStats: {
|
||||
lexicon: 1,
|
||||
id: 'tools.ozone.moderation.getReporterStats',
|
||||
defs: {
|
||||
main: {
|
||||
type: 'query',
|
||||
description: 'Get reporter stats for a list of users.',
|
||||
parameters: {
|
||||
type: 'params',
|
||||
required: ['dids'],
|
||||
properties: {
|
||||
dids: {
|
||||
type: 'array',
|
||||
maxLength: 100,
|
||||
items: {
|
||||
type: 'string',
|
||||
format: 'did',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
output: {
|
||||
encoding: 'application/json',
|
||||
schema: {
|
||||
type: 'object',
|
||||
required: ['stats'],
|
||||
properties: {
|
||||
stats: {
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'ref',
|
||||
ref: 'lex:tools.ozone.moderation.defs#reporterStats',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
ToolsOzoneModerationGetRepos: {
|
||||
lexicon: 1,
|
||||
id: 'tools.ozone.moderation.getRepos',
|
||||
@ -14133,6 +14231,8 @@ export const ids = {
|
||||
ToolsOzoneModerationGetRecord: 'tools.ozone.moderation.getRecord',
|
||||
ToolsOzoneModerationGetRecords: 'tools.ozone.moderation.getRecords',
|
||||
ToolsOzoneModerationGetRepo: 'tools.ozone.moderation.getRepo',
|
||||
ToolsOzoneModerationGetReporterStats:
|
||||
'tools.ozone.moderation.getReporterStats',
|
||||
ToolsOzoneModerationGetRepos: 'tools.ozone.moderation.getRepos',
|
||||
ToolsOzoneModerationQueryEvents: 'tools.ozone.moderation.queryEvents',
|
||||
ToolsOzoneModerationQueryStatuses: 'tools.ozone.moderation.queryStatuses',
|
||||
|
@ -836,3 +836,34 @@ export function isRecordHosting<V>(v: V) {
|
||||
export function validateRecordHosting<V>(v: V) {
|
||||
return validate<RecordHosting & V>(v, id, hashRecordHosting)
|
||||
}
|
||||
|
||||
export interface ReporterStats {
|
||||
$type?: 'tools.ozone.moderation.defs#reporterStats'
|
||||
did: string
|
||||
/** The total number of reports made by the user on accounts. */
|
||||
accountReportCount: number
|
||||
/** The total number of reports made by the user on records. */
|
||||
recordReportCount: number
|
||||
/** The total number of accounts reported by the user. */
|
||||
reportedAccountCount: number
|
||||
/** The total number of records reported by the user. */
|
||||
reportedRecordCount: number
|
||||
/** The total number of accounts taken down as a result of the user's reports. */
|
||||
takendownAccountCount: number
|
||||
/** The total number of records taken down as a result of the user's reports. */
|
||||
takendownRecordCount: number
|
||||
/** The total number of accounts labeled as a result of the user's reports. */
|
||||
labeledAccountCount: number
|
||||
/** The total number of records labeled as a result of the user's reports. */
|
||||
labeledRecordCount: number
|
||||
}
|
||||
|
||||
const hashReporterStats = 'reporterStats'
|
||||
|
||||
export function isReporterStats<V>(v: V) {
|
||||
return is$typed(v, id, hashReporterStats)
|
||||
}
|
||||
|
||||
export function validateReporterStats<V>(v: V) {
|
||||
return validate<ReporterStats & V>(v, id, hashReporterStats)
|
||||
}
|
||||
|
@ -0,0 +1,38 @@
|
||||
/**
|
||||
* GENERATED CODE - DO NOT MODIFY
|
||||
*/
|
||||
import { HeadersMap, XRPCError } from '@atproto/xrpc'
|
||||
import { ValidationResult, BlobRef } from '@atproto/lexicon'
|
||||
import { CID } from 'multiformats/cid'
|
||||
import { validate as _validate } from '../../../../lexicons'
|
||||
import { $Typed, is$typed as _is$typed, OmitKey } from '../../../../util'
|
||||
import type * as ToolsOzoneModerationDefs from './defs.js'
|
||||
|
||||
const is$typed = _is$typed,
|
||||
validate = _validate
|
||||
const id = 'tools.ozone.moderation.getReporterStats'
|
||||
|
||||
export interface QueryParams {
|
||||
dids: string[]
|
||||
}
|
||||
|
||||
export type InputSchema = undefined
|
||||
|
||||
export interface OutputSchema {
|
||||
stats: ToolsOzoneModerationDefs.ReporterStats[]
|
||||
}
|
||||
|
||||
export interface CallOptions {
|
||||
signal?: AbortSignal
|
||||
headers?: HeadersMap
|
||||
}
|
||||
|
||||
export interface Response {
|
||||
success: boolean
|
||||
headers: HeadersMap
|
||||
data: OutputSchema
|
||||
}
|
||||
|
||||
export function toKnownErr(e: any) {
|
||||
return e
|
||||
}
|
@ -45,6 +45,19 @@ export class ModeratorClient {
|
||||
return result.data
|
||||
}
|
||||
|
||||
async getReporterStats(dids: string[]) {
|
||||
const result = await this.agent.tools.ozone.moderation.getReporterStats(
|
||||
{ dids },
|
||||
{
|
||||
headers: await this.ozone.modHeaders(
|
||||
'tools.ozone.moderation.getReporterStats',
|
||||
'admin',
|
||||
),
|
||||
},
|
||||
)
|
||||
return result.data
|
||||
}
|
||||
|
||||
async queryEvents(input: QueryEventsParams, role?: ModLevel) {
|
||||
const result = await this.agent.tools.ozone.moderation.queryEvents(input, {
|
||||
headers: await this.ozone.modHeaders(
|
||||
|
@ -13,6 +13,7 @@ import getEvent from './moderation/getEvent'
|
||||
import adminGetRecord from './moderation/getRecord'
|
||||
import adminGetRecords from './moderation/getRecords'
|
||||
import getRepo from './moderation/getRepo'
|
||||
import getReporterStats from './moderation/getReporterStats'
|
||||
import getRepos from './moderation/getRepos'
|
||||
import queryEvents from './moderation/queryEvents'
|
||||
import queryStatuses from './moderation/queryStatuses'
|
||||
@ -72,5 +73,6 @@ export default function (server: Server, ctx: AppContext) {
|
||||
upsertOption(server, ctx)
|
||||
listOptions(server, ctx)
|
||||
removeOptions(server, ctx)
|
||||
getReporterStats(server, ctx)
|
||||
return server
|
||||
}
|
||||
|
18
packages/ozone/src/api/moderation/getReporterStats.ts
Normal file
18
packages/ozone/src/api/moderation/getReporterStats.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import { AppContext } from '../../context'
|
||||
import { Server } from '../../lexicon'
|
||||
|
||||
export default function (server: Server, ctx: AppContext) {
|
||||
server.tools.ozone.moderation.getReporterStats({
|
||||
auth: ctx.authVerifier.modOrAdminToken,
|
||||
handler: async ({ params }) => {
|
||||
const db = ctx.db
|
||||
|
||||
const stats = await ctx.modService(db).getReporterStats(params.dids)
|
||||
|
||||
return {
|
||||
encoding: 'application/json',
|
||||
body: { stats },
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
@ -10,6 +10,7 @@ export class MaterializedViewRefresher extends PeriodicBackgroundTask {
|
||||
'record_events_stats',
|
||||
'account_record_events_stats',
|
||||
'account_record_status_stats',
|
||||
'reporter_stats',
|
||||
]) {
|
||||
if (signal.aborted) break
|
||||
|
||||
|
@ -0,0 +1,38 @@
|
||||
import { Kysely, sql } from 'kysely'
|
||||
|
||||
export async function up(db: Kysely<unknown>): Promise<void> {
|
||||
await sql`
|
||||
CREATE INDEX "moderation_event_account_reports_idx"
|
||||
ON moderation_event("createdBy","subjectDid", "createdAt")
|
||||
WHERE "subjectUri" IS NULL
|
||||
AND "action" = 'tools.ozone.moderation.defs#modEventReport'
|
||||
`.execute(db)
|
||||
|
||||
await sql`
|
||||
CREATE INDEX "moderation_event_record_reports_idx"
|
||||
ON moderation_event("createdBy","subjectDid","subjectUri", "createdAt")
|
||||
WHERE "subjectUri" IS NOT NULL
|
||||
AND "action" = 'tools.ozone.moderation.defs#modEventReport'
|
||||
`.execute(db)
|
||||
|
||||
await sql`
|
||||
CREATE INDEX "moderation_event_account_actions_ids"
|
||||
ON moderation_event("subjectDid","action", "createdAt")
|
||||
WHERE "subjectUri" IS NULL
|
||||
AND "action" IN ( 'tools.ozone.moderation.defs#modEventTakedown', 'tools.ozone.moderation.defs#modEventLabel')
|
||||
`.execute(db)
|
||||
|
||||
await sql`
|
||||
CREATE INDEX "moderation_event_record_actions_ids"
|
||||
ON moderation_event("subjectDid","subjectUri", "action", "createdAt")
|
||||
WHERE "subjectUri" IS NOT NULL
|
||||
AND "action" IN ( 'tools.ozone.moderation.defs#modEventTakedown', 'tools.ozone.moderation.defs#modEventLabel')
|
||||
`.execute(db)
|
||||
}
|
||||
|
||||
export async function down(db: Kysely<unknown>): Promise<void> {
|
||||
await db.schema.dropIndex('moderation_event_account_reports_idx').execute()
|
||||
await db.schema.dropIndex('moderation_event_record_reports_idx').execute()
|
||||
await db.schema.dropIndex('moderation_event_account_actions_ids').execute()
|
||||
await db.schema.dropIndex('moderation_event_record_actions_ids').execute()
|
||||
}
|
@ -19,4 +19,5 @@ export * as _20241018T205730722Z from './20241018T205730722Z-setting'
|
||||
export * as _20241026T205730722Z from './20241026T205730722Z-add-hosting-status-to-subject-status'
|
||||
export * as _20241220T144630860Z from './20241220T144630860Z-stats-materialized-views'
|
||||
export * as _20250204T003647759Z from './20250204T003647759Z-add-subject-priority-score'
|
||||
export * as _20250211T003647759Z from './20250211T003647759Z-add-reporter-stats-index'
|
||||
export * as _20250211T132135150Z from './20250211T132135150Z-moderation-event-message-partial-idx'
|
||||
|
@ -173,6 +173,7 @@ import * as ToolsOzoneModerationGetEvent from './types/tools/ozone/moderation/ge
|
||||
import * as ToolsOzoneModerationGetRecord from './types/tools/ozone/moderation/getRecord.js'
|
||||
import * as ToolsOzoneModerationGetRecords from './types/tools/ozone/moderation/getRecords.js'
|
||||
import * as ToolsOzoneModerationGetRepo from './types/tools/ozone/moderation/getRepo.js'
|
||||
import * as ToolsOzoneModerationGetReporterStats from './types/tools/ozone/moderation/getReporterStats.js'
|
||||
import * as ToolsOzoneModerationGetRepos from './types/tools/ozone/moderation/getRepos.js'
|
||||
import * as ToolsOzoneModerationQueryEvents from './types/tools/ozone/moderation/queryEvents.js'
|
||||
import * as ToolsOzoneModerationQueryStatuses from './types/tools/ozone/moderation/queryStatuses.js'
|
||||
@ -2376,6 +2377,17 @@ export class ToolsOzoneModerationNS {
|
||||
return this._server.xrpc.method(nsid, cfg)
|
||||
}
|
||||
|
||||
getReporterStats<AV extends AuthVerifier>(
|
||||
cfg: ConfigOf<
|
||||
AV,
|
||||
ToolsOzoneModerationGetReporterStats.Handler<ExtractAuth<AV>>,
|
||||
ToolsOzoneModerationGetReporterStats.HandlerReqCtx<ExtractAuth<AV>>
|
||||
>,
|
||||
) {
|
||||
const nsid = 'tools.ozone.moderation.getReporterStats' // @ts-ignore
|
||||
return this._server.xrpc.method(nsid, cfg)
|
||||
}
|
||||
|
||||
getRepos<AV extends AuthVerifier>(
|
||||
cfg: ConfigOf<
|
||||
AV,
|
||||
|
@ -12219,6 +12219,64 @@ export const schemaDict = {
|
||||
},
|
||||
},
|
||||
},
|
||||
reporterStats: {
|
||||
type: 'object',
|
||||
required: [
|
||||
'did',
|
||||
'accountReportCount',
|
||||
'recordReportCount',
|
||||
'reportedAccountCount',
|
||||
'reportedRecordCount',
|
||||
'takendownAccountCount',
|
||||
'takendownRecordCount',
|
||||
'labeledAccountCount',
|
||||
'labeledRecordCount',
|
||||
],
|
||||
properties: {
|
||||
did: {
|
||||
type: 'string',
|
||||
format: 'did',
|
||||
},
|
||||
accountReportCount: {
|
||||
type: 'integer',
|
||||
description:
|
||||
'The total number of reports made by the user on accounts.',
|
||||
},
|
||||
recordReportCount: {
|
||||
type: 'integer',
|
||||
description:
|
||||
'The total number of reports made by the user on records.',
|
||||
},
|
||||
reportedAccountCount: {
|
||||
type: 'integer',
|
||||
description: 'The total number of accounts reported by the user.',
|
||||
},
|
||||
reportedRecordCount: {
|
||||
type: 'integer',
|
||||
description: 'The total number of records reported by the user.',
|
||||
},
|
||||
takendownAccountCount: {
|
||||
type: 'integer',
|
||||
description:
|
||||
"The total number of accounts taken down as a result of the user's reports.",
|
||||
},
|
||||
takendownRecordCount: {
|
||||
type: 'integer',
|
||||
description:
|
||||
"The total number of records taken down as a result of the user's reports.",
|
||||
},
|
||||
labeledAccountCount: {
|
||||
type: 'integer',
|
||||
description:
|
||||
"The total number of accounts labeled as a result of the user's reports.",
|
||||
},
|
||||
labeledRecordCount: {
|
||||
type: 'integer',
|
||||
description:
|
||||
"The total number of records labeled as a result of the user's reports.",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
ToolsOzoneModerationEmitEvent: {
|
||||
@ -12430,6 +12488,46 @@ export const schemaDict = {
|
||||
},
|
||||
},
|
||||
},
|
||||
ToolsOzoneModerationGetReporterStats: {
|
||||
lexicon: 1,
|
||||
id: 'tools.ozone.moderation.getReporterStats',
|
||||
defs: {
|
||||
main: {
|
||||
type: 'query',
|
||||
description: 'Get reporter stats for a list of users.',
|
||||
parameters: {
|
||||
type: 'params',
|
||||
required: ['dids'],
|
||||
properties: {
|
||||
dids: {
|
||||
type: 'array',
|
||||
maxLength: 100,
|
||||
items: {
|
||||
type: 'string',
|
||||
format: 'did',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
output: {
|
||||
encoding: 'application/json',
|
||||
schema: {
|
||||
type: 'object',
|
||||
required: ['stats'],
|
||||
properties: {
|
||||
stats: {
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'ref',
|
||||
ref: 'lex:tools.ozone.moderation.defs#reporterStats',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
ToolsOzoneModerationGetRepos: {
|
||||
lexicon: 1,
|
||||
id: 'tools.ozone.moderation.getRepos',
|
||||
@ -14133,6 +14231,8 @@ export const ids = {
|
||||
ToolsOzoneModerationGetRecord: 'tools.ozone.moderation.getRecord',
|
||||
ToolsOzoneModerationGetRecords: 'tools.ozone.moderation.getRecords',
|
||||
ToolsOzoneModerationGetRepo: 'tools.ozone.moderation.getRepo',
|
||||
ToolsOzoneModerationGetReporterStats:
|
||||
'tools.ozone.moderation.getReporterStats',
|
||||
ToolsOzoneModerationGetRepos: 'tools.ozone.moderation.getRepos',
|
||||
ToolsOzoneModerationQueryEvents: 'tools.ozone.moderation.queryEvents',
|
||||
ToolsOzoneModerationQueryStatuses: 'tools.ozone.moderation.queryStatuses',
|
||||
|
@ -836,3 +836,34 @@ export function isRecordHosting<V>(v: V) {
|
||||
export function validateRecordHosting<V>(v: V) {
|
||||
return validate<RecordHosting & V>(v, id, hashRecordHosting)
|
||||
}
|
||||
|
||||
export interface ReporterStats {
|
||||
$type?: 'tools.ozone.moderation.defs#reporterStats'
|
||||
did: string
|
||||
/** The total number of reports made by the user on accounts. */
|
||||
accountReportCount: number
|
||||
/** The total number of reports made by the user on records. */
|
||||
recordReportCount: number
|
||||
/** The total number of accounts reported by the user. */
|
||||
reportedAccountCount: number
|
||||
/** The total number of records reported by the user. */
|
||||
reportedRecordCount: number
|
||||
/** The total number of accounts taken down as a result of the user's reports. */
|
||||
takendownAccountCount: number
|
||||
/** The total number of records taken down as a result of the user's reports. */
|
||||
takendownRecordCount: number
|
||||
/** The total number of accounts labeled as a result of the user's reports. */
|
||||
labeledAccountCount: number
|
||||
/** The total number of records labeled as a result of the user's reports. */
|
||||
labeledRecordCount: number
|
||||
}
|
||||
|
||||
const hashReporterStats = 'reporterStats'
|
||||
|
||||
export function isReporterStats<V>(v: V) {
|
||||
return is$typed(v, id, hashReporterStats)
|
||||
}
|
||||
|
||||
export function validateReporterStats<V>(v: V) {
|
||||
return validate<ReporterStats & V>(v, id, hashReporterStats)
|
||||
}
|
||||
|
@ -0,0 +1,50 @@
|
||||
/**
|
||||
* GENERATED CODE - DO NOT MODIFY
|
||||
*/
|
||||
import express from 'express'
|
||||
import { ValidationResult, BlobRef } from '@atproto/lexicon'
|
||||
import { CID } from 'multiformats/cid'
|
||||
import { validate as _validate } from '../../../../lexicons'
|
||||
import { $Typed, is$typed as _is$typed, OmitKey } from '../../../../util'
|
||||
import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server'
|
||||
import type * as ToolsOzoneModerationDefs from './defs.js'
|
||||
|
||||
const is$typed = _is$typed,
|
||||
validate = _validate
|
||||
const id = 'tools.ozone.moderation.getReporterStats'
|
||||
|
||||
export interface QueryParams {
|
||||
dids: string[]
|
||||
}
|
||||
|
||||
export type InputSchema = undefined
|
||||
|
||||
export interface OutputSchema {
|
||||
stats: ToolsOzoneModerationDefs.ReporterStats[]
|
||||
}
|
||||
|
||||
export type HandlerInput = undefined
|
||||
|
||||
export interface HandlerSuccess {
|
||||
encoding: 'application/json'
|
||||
body: OutputSchema
|
||||
headers?: { [key: string]: string }
|
||||
}
|
||||
|
||||
export interface HandlerError {
|
||||
status: number
|
||||
message?: string
|
||||
}
|
||||
|
||||
export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough
|
||||
export type HandlerReqCtx<HA extends HandlerAuth = never> = {
|
||||
auth: HA
|
||||
params: QueryParams
|
||||
input: HandlerInput
|
||||
req: express.Request
|
||||
res: express.Response
|
||||
resetRouteRateLimits: () => Promise<void>
|
||||
}
|
||||
export type Handler<HA extends HandlerAuth = never> = (
|
||||
ctx: HandlerReqCtx<HA>,
|
||||
) => Promise<HandlerOutput> | HandlerOutput
|
@ -58,6 +58,8 @@ import {
|
||||
ModerationEventRow,
|
||||
ModerationSubjectStatusRow,
|
||||
ModerationSubjectStatusRowWithHandle,
|
||||
ReporterStats,
|
||||
ReporterStatsResult,
|
||||
ReversibleModerationEvent,
|
||||
} from './types'
|
||||
import { formatLabel, formatLabelRow, signLabel } from './util'
|
||||
@ -1272,6 +1274,130 @@ export class ModerationService {
|
||||
throw new InvalidRequestError('Email was accepted but not sent')
|
||||
}
|
||||
}
|
||||
|
||||
buildModerationQuery(
|
||||
subjectType: 'account' | 'record',
|
||||
createdByDids: string[],
|
||||
isActionQuery: boolean,
|
||||
): Promise<(Partial<ReporterStatsResult> & { did: string })[]> {
|
||||
const isAccount = subjectType === 'account'
|
||||
const actionTypes = [
|
||||
'tools.ozone.moderation.defs#modEventTakedown',
|
||||
'tools.ozone.moderation.defs#modEventLabel',
|
||||
] as const
|
||||
|
||||
const query = this.db.db
|
||||
.selectFrom('moderation_event as reports')
|
||||
.where(
|
||||
'reports.action',
|
||||
'=',
|
||||
'tools.ozone.moderation.defs#modEventReport',
|
||||
)
|
||||
.where('reports.subjectUri', isAccount ? 'is' : 'is not', null)
|
||||
.where('reports.createdBy', 'in', createdByDids)
|
||||
.select(['reports.createdBy as did'])
|
||||
|
||||
if (isActionQuery) {
|
||||
return query
|
||||
.leftJoin('moderation_event as actions', (join) =>
|
||||
join
|
||||
.onRef('actions.subjectDid', '=', 'reports.subjectDid')
|
||||
.on('actions.subjectUri', isAccount ? 'is' : 'is not', null)
|
||||
.onRef('actions.createdAt', '>', 'reports.createdAt')
|
||||
.on('actions.action', 'in', actionTypes),
|
||||
)
|
||||
.select([
|
||||
() =>
|
||||
sql<number>`COUNT(DISTINCT actions."subjectDid") FILTER (
|
||||
WHERE actions."action" = 'tools.ozone.moderation.defs#modEventTakedown'
|
||||
)`.as(`takendown${isAccount ? 'Account' : 'Record'}Count`),
|
||||
|
||||
() =>
|
||||
sql<number>`COUNT(DISTINCT actions."subjectDid") FILTER (
|
||||
WHERE actions."action" = 'tools.ozone.moderation.defs#modEventLabel'
|
||||
)`.as(`labeled${isAccount ? 'Account' : 'Record'}Count`),
|
||||
])
|
||||
.groupBy('reports.createdBy')
|
||||
.execute()
|
||||
}
|
||||
|
||||
return query
|
||||
.select([
|
||||
(eb) =>
|
||||
eb.fn.count<number>('reports.id').as(`${subjectType}ReportCount`),
|
||||
(eb) =>
|
||||
eb.fn
|
||||
.count<number>(
|
||||
isAccount ? 'reports.subjectDid' : 'reports.subjectUri',
|
||||
)
|
||||
.distinct()
|
||||
.as(`reported${isAccount ? 'Account' : 'Record'}Count`),
|
||||
])
|
||||
.groupBy('reports.createdBy')
|
||||
.execute()
|
||||
}
|
||||
|
||||
async getReporterStats(dids: string[]) {
|
||||
const [accountReports, recordReports, accountActions, recordActions] =
|
||||
await Promise.all([
|
||||
this.buildModerationQuery('account', dids, false),
|
||||
this.buildModerationQuery('record', dids, false),
|
||||
this.buildModerationQuery('account', dids, true),
|
||||
this.buildModerationQuery('record', dids, true),
|
||||
])
|
||||
|
||||
// Create a map to hold the aggregated stats for each `did`
|
||||
const statsMap = new Map<string, ReporterStats>()
|
||||
|
||||
// Helper function to ensure a `did` entry exists in the map
|
||||
const ensureDidEntry = (did: string) => {
|
||||
if (!statsMap.has(did)) {
|
||||
statsMap.set(did, {
|
||||
did,
|
||||
accountReportCount: 0,
|
||||
recordReportCount: 0,
|
||||
reportedAccountCount: 0,
|
||||
reportedRecordCount: 0,
|
||||
takendownAccountCount: 0,
|
||||
takendownRecordCount: 0,
|
||||
labeledAccountCount: 0,
|
||||
labeledRecordCount: 0,
|
||||
})
|
||||
}
|
||||
return statsMap.get(did)!
|
||||
}
|
||||
|
||||
// Merge accountReports
|
||||
for (const report of accountReports) {
|
||||
const entry = ensureDidEntry(report.did)
|
||||
entry.accountReportCount = report.accountReportCount ?? 0
|
||||
entry.reportedAccountCount = report.reportedAccountCount ?? 0
|
||||
}
|
||||
|
||||
// Merge recordReports
|
||||
for (const report of recordReports) {
|
||||
const entry = ensureDidEntry(report.did)
|
||||
entry.recordReportCount = report.recordReportCount ?? 0
|
||||
entry.reportedRecordCount = report.reportedRecordCount ?? 0
|
||||
}
|
||||
|
||||
// Merge accountActions
|
||||
for (const action of accountActions) {
|
||||
const entry = ensureDidEntry(action.did)
|
||||
entry.takendownAccountCount = action.takendownAccountCount ?? 0
|
||||
entry.labeledAccountCount = action.labeledAccountCount ?? 0
|
||||
}
|
||||
|
||||
// Merge recordActions
|
||||
for (const action of recordActions) {
|
||||
const entry = ensureDidEntry(action.did)
|
||||
entry.takendownRecordCount = action.takendownRecordCount ?? 0
|
||||
entry.labeledRecordCount = action.labeledRecordCount ?? 0
|
||||
}
|
||||
|
||||
// Convert map values to an array and return
|
||||
return Array.from(statsMap.values())
|
||||
}
|
||||
}
|
||||
|
||||
const parseTags = (tags?: string[]) =>
|
||||
|
@ -65,3 +65,26 @@ type RecordHostingView = {
|
||||
export type ModerationSubjectHostingView =
|
||||
| AccountHostingView
|
||||
| RecordHostingView
|
||||
|
||||
export type ReporterStats = {
|
||||
did: string
|
||||
accountReportCount: number
|
||||
recordReportCount: number
|
||||
reportedAccountCount: number
|
||||
reportedRecordCount: number
|
||||
takendownAccountCount: number
|
||||
takendownRecordCount: number
|
||||
labeledAccountCount: number
|
||||
labeledRecordCount: number
|
||||
}
|
||||
|
||||
export type ReporterStatsResult = {
|
||||
accountReportCount?: number
|
||||
recordReportCount?: number
|
||||
reportedAccountCount?: number
|
||||
reportedRecordCount?: number
|
||||
takendownAccountCount?: number
|
||||
takendownRecordCount?: number
|
||||
labeledAccountCount?: number
|
||||
labeledRecordCount?: number
|
||||
}
|
||||
|
117
packages/ozone/tests/get-reporter-stats.test.ts
Normal file
117
packages/ozone/tests/get-reporter-stats.test.ts
Normal file
@ -0,0 +1,117 @@
|
||||
import {
|
||||
ComAtprotoModerationDefs,
|
||||
ToolsOzoneModerationDefs,
|
||||
} from '@atproto/api'
|
||||
import {
|
||||
ModeratorClient,
|
||||
SeedClient,
|
||||
TestNetwork,
|
||||
basicSeed,
|
||||
} from '@atproto/dev-env'
|
||||
|
||||
describe('reporter-stats', () => {
|
||||
let network: TestNetwork
|
||||
let sc: SeedClient
|
||||
let modClient: ModeratorClient
|
||||
|
||||
beforeAll(async () => {
|
||||
network = await TestNetwork.create({
|
||||
dbPostgresSchema: 'ozone_reporter_stats',
|
||||
ozone: {
|
||||
dbMaterializedViewRefreshIntervalMs: 1000,
|
||||
},
|
||||
})
|
||||
sc = network.getSeedClient()
|
||||
modClient = network.ozone.getModClient()
|
||||
await basicSeed(sc)
|
||||
await network.processAll()
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
await network.close()
|
||||
})
|
||||
|
||||
const getReporterStats = async (
|
||||
did: string,
|
||||
): Promise<ToolsOzoneModerationDefs.ReporterStats | undefined> => {
|
||||
const { stats } = await modClient.getReporterStats([did])
|
||||
return stats[0]
|
||||
}
|
||||
|
||||
it('updates reporter stats based on actions', async () => {
|
||||
const bobsPostSubject = {
|
||||
$type: 'com.atproto.repo.strongRef',
|
||||
uri: sc.posts[sc.dids.bob][1].ref.uriStr,
|
||||
cid: sc.posts[sc.dids.bob][1].ref.cidStr,
|
||||
}
|
||||
const carolsAccountSubject = {
|
||||
$type: 'com.atproto.admin.defs#repoRef',
|
||||
did: sc.dids.carol,
|
||||
}
|
||||
|
||||
await Promise.all([
|
||||
sc.createReport({
|
||||
reportedBy: sc.dids.alice,
|
||||
reasonType: ComAtprotoModerationDefs.REASONMISLEADING,
|
||||
reason: 'misleading',
|
||||
subject: bobsPostSubject,
|
||||
}),
|
||||
sc.createReport({
|
||||
reportedBy: sc.dids.alice,
|
||||
reasonType: ComAtprotoModerationDefs.REASONOTHER,
|
||||
reason: 'test',
|
||||
subject: bobsPostSubject,
|
||||
}),
|
||||
sc.createReport({
|
||||
reportedBy: sc.dids.alice,
|
||||
reasonType: ComAtprotoModerationDefs.REASONMISLEADING,
|
||||
reason: 'misleading',
|
||||
subject: carolsAccountSubject,
|
||||
}),
|
||||
])
|
||||
|
||||
await network.processAll()
|
||||
const statsAfterReport = await getReporterStats(sc.dids.alice)
|
||||
expect(statsAfterReport).toMatchObject({
|
||||
did: sc.dids.alice,
|
||||
accountReportCount: 1,
|
||||
recordReportCount: 2,
|
||||
reportedAccountCount: 1,
|
||||
reportedRecordCount: 1,
|
||||
takendownAccountCount: 0,
|
||||
takendownRecordCount: 0,
|
||||
labeledAccountCount: 0,
|
||||
labeledRecordCount: 0,
|
||||
})
|
||||
|
||||
await Promise.all([
|
||||
modClient.performTakedown({
|
||||
subject: bobsPostSubject,
|
||||
policies: ['trolling'],
|
||||
}),
|
||||
modClient.emitEvent({
|
||||
subject: carolsAccountSubject,
|
||||
event: {
|
||||
$type: 'tools.ozone.moderation.defs#modEventLabel',
|
||||
createLabelVals: ['spam'],
|
||||
negateLabelVals: [],
|
||||
},
|
||||
}),
|
||||
])
|
||||
|
||||
await network.processAll()
|
||||
await new Promise((resolve) => setTimeout(resolve, 1000))
|
||||
const statsAfterAction = await getReporterStats(sc.dids.alice)
|
||||
expect(statsAfterAction).toMatchObject({
|
||||
did: sc.dids.alice,
|
||||
accountReportCount: 1,
|
||||
recordReportCount: 2,
|
||||
reportedAccountCount: 1,
|
||||
reportedRecordCount: 1,
|
||||
takendownAccountCount: 0,
|
||||
takendownRecordCount: 1,
|
||||
labeledAccountCount: 1,
|
||||
labeledRecordCount: 0,
|
||||
})
|
||||
})
|
||||
})
|
@ -173,6 +173,7 @@ import * as ToolsOzoneModerationGetEvent from './types/tools/ozone/moderation/ge
|
||||
import * as ToolsOzoneModerationGetRecord from './types/tools/ozone/moderation/getRecord.js'
|
||||
import * as ToolsOzoneModerationGetRecords from './types/tools/ozone/moderation/getRecords.js'
|
||||
import * as ToolsOzoneModerationGetRepo from './types/tools/ozone/moderation/getRepo.js'
|
||||
import * as ToolsOzoneModerationGetReporterStats from './types/tools/ozone/moderation/getReporterStats.js'
|
||||
import * as ToolsOzoneModerationGetRepos from './types/tools/ozone/moderation/getRepos.js'
|
||||
import * as ToolsOzoneModerationQueryEvents from './types/tools/ozone/moderation/queryEvents.js'
|
||||
import * as ToolsOzoneModerationQueryStatuses from './types/tools/ozone/moderation/queryStatuses.js'
|
||||
@ -2376,6 +2377,17 @@ export class ToolsOzoneModerationNS {
|
||||
return this._server.xrpc.method(nsid, cfg)
|
||||
}
|
||||
|
||||
getReporterStats<AV extends AuthVerifier>(
|
||||
cfg: ConfigOf<
|
||||
AV,
|
||||
ToolsOzoneModerationGetReporterStats.Handler<ExtractAuth<AV>>,
|
||||
ToolsOzoneModerationGetReporterStats.HandlerReqCtx<ExtractAuth<AV>>
|
||||
>,
|
||||
) {
|
||||
const nsid = 'tools.ozone.moderation.getReporterStats' // @ts-ignore
|
||||
return this._server.xrpc.method(nsid, cfg)
|
||||
}
|
||||
|
||||
getRepos<AV extends AuthVerifier>(
|
||||
cfg: ConfigOf<
|
||||
AV,
|
||||
|
@ -12219,6 +12219,64 @@ export const schemaDict = {
|
||||
},
|
||||
},
|
||||
},
|
||||
reporterStats: {
|
||||
type: 'object',
|
||||
required: [
|
||||
'did',
|
||||
'accountReportCount',
|
||||
'recordReportCount',
|
||||
'reportedAccountCount',
|
||||
'reportedRecordCount',
|
||||
'takendownAccountCount',
|
||||
'takendownRecordCount',
|
||||
'labeledAccountCount',
|
||||
'labeledRecordCount',
|
||||
],
|
||||
properties: {
|
||||
did: {
|
||||
type: 'string',
|
||||
format: 'did',
|
||||
},
|
||||
accountReportCount: {
|
||||
type: 'integer',
|
||||
description:
|
||||
'The total number of reports made by the user on accounts.',
|
||||
},
|
||||
recordReportCount: {
|
||||
type: 'integer',
|
||||
description:
|
||||
'The total number of reports made by the user on records.',
|
||||
},
|
||||
reportedAccountCount: {
|
||||
type: 'integer',
|
||||
description: 'The total number of accounts reported by the user.',
|
||||
},
|
||||
reportedRecordCount: {
|
||||
type: 'integer',
|
||||
description: 'The total number of records reported by the user.',
|
||||
},
|
||||
takendownAccountCount: {
|
||||
type: 'integer',
|
||||
description:
|
||||
"The total number of accounts taken down as a result of the user's reports.",
|
||||
},
|
||||
takendownRecordCount: {
|
||||
type: 'integer',
|
||||
description:
|
||||
"The total number of records taken down as a result of the user's reports.",
|
||||
},
|
||||
labeledAccountCount: {
|
||||
type: 'integer',
|
||||
description:
|
||||
"The total number of accounts labeled as a result of the user's reports.",
|
||||
},
|
||||
labeledRecordCount: {
|
||||
type: 'integer',
|
||||
description:
|
||||
"The total number of records labeled as a result of the user's reports.",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
ToolsOzoneModerationEmitEvent: {
|
||||
@ -12430,6 +12488,46 @@ export const schemaDict = {
|
||||
},
|
||||
},
|
||||
},
|
||||
ToolsOzoneModerationGetReporterStats: {
|
||||
lexicon: 1,
|
||||
id: 'tools.ozone.moderation.getReporterStats',
|
||||
defs: {
|
||||
main: {
|
||||
type: 'query',
|
||||
description: 'Get reporter stats for a list of users.',
|
||||
parameters: {
|
||||
type: 'params',
|
||||
required: ['dids'],
|
||||
properties: {
|
||||
dids: {
|
||||
type: 'array',
|
||||
maxLength: 100,
|
||||
items: {
|
||||
type: 'string',
|
||||
format: 'did',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
output: {
|
||||
encoding: 'application/json',
|
||||
schema: {
|
||||
type: 'object',
|
||||
required: ['stats'],
|
||||
properties: {
|
||||
stats: {
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'ref',
|
||||
ref: 'lex:tools.ozone.moderation.defs#reporterStats',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
ToolsOzoneModerationGetRepos: {
|
||||
lexicon: 1,
|
||||
id: 'tools.ozone.moderation.getRepos',
|
||||
@ -14133,6 +14231,8 @@ export const ids = {
|
||||
ToolsOzoneModerationGetRecord: 'tools.ozone.moderation.getRecord',
|
||||
ToolsOzoneModerationGetRecords: 'tools.ozone.moderation.getRecords',
|
||||
ToolsOzoneModerationGetRepo: 'tools.ozone.moderation.getRepo',
|
||||
ToolsOzoneModerationGetReporterStats:
|
||||
'tools.ozone.moderation.getReporterStats',
|
||||
ToolsOzoneModerationGetRepos: 'tools.ozone.moderation.getRepos',
|
||||
ToolsOzoneModerationQueryEvents: 'tools.ozone.moderation.queryEvents',
|
||||
ToolsOzoneModerationQueryStatuses: 'tools.ozone.moderation.queryStatuses',
|
||||
|
@ -836,3 +836,34 @@ export function isRecordHosting<V>(v: V) {
|
||||
export function validateRecordHosting<V>(v: V) {
|
||||
return validate<RecordHosting & V>(v, id, hashRecordHosting)
|
||||
}
|
||||
|
||||
export interface ReporterStats {
|
||||
$type?: 'tools.ozone.moderation.defs#reporterStats'
|
||||
did: string
|
||||
/** The total number of reports made by the user on accounts. */
|
||||
accountReportCount: number
|
||||
/** The total number of reports made by the user on records. */
|
||||
recordReportCount: number
|
||||
/** The total number of accounts reported by the user. */
|
||||
reportedAccountCount: number
|
||||
/** The total number of records reported by the user. */
|
||||
reportedRecordCount: number
|
||||
/** The total number of accounts taken down as a result of the user's reports. */
|
||||
takendownAccountCount: number
|
||||
/** The total number of records taken down as a result of the user's reports. */
|
||||
takendownRecordCount: number
|
||||
/** The total number of accounts labeled as a result of the user's reports. */
|
||||
labeledAccountCount: number
|
||||
/** The total number of records labeled as a result of the user's reports. */
|
||||
labeledRecordCount: number
|
||||
}
|
||||
|
||||
const hashReporterStats = 'reporterStats'
|
||||
|
||||
export function isReporterStats<V>(v: V) {
|
||||
return is$typed(v, id, hashReporterStats)
|
||||
}
|
||||
|
||||
export function validateReporterStats<V>(v: V) {
|
||||
return validate<ReporterStats & V>(v, id, hashReporterStats)
|
||||
}
|
||||
|
@ -0,0 +1,50 @@
|
||||
/**
|
||||
* GENERATED CODE - DO NOT MODIFY
|
||||
*/
|
||||
import express from 'express'
|
||||
import { ValidationResult, BlobRef } from '@atproto/lexicon'
|
||||
import { CID } from 'multiformats/cid'
|
||||
import { validate as _validate } from '../../../../lexicons'
|
||||
import { $Typed, is$typed as _is$typed, OmitKey } from '../../../../util'
|
||||
import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server'
|
||||
import type * as ToolsOzoneModerationDefs from './defs.js'
|
||||
|
||||
const is$typed = _is$typed,
|
||||
validate = _validate
|
||||
const id = 'tools.ozone.moderation.getReporterStats'
|
||||
|
||||
export interface QueryParams {
|
||||
dids: string[]
|
||||
}
|
||||
|
||||
export type InputSchema = undefined
|
||||
|
||||
export interface OutputSchema {
|
||||
stats: ToolsOzoneModerationDefs.ReporterStats[]
|
||||
}
|
||||
|
||||
export type HandlerInput = undefined
|
||||
|
||||
export interface HandlerSuccess {
|
||||
encoding: 'application/json'
|
||||
body: OutputSchema
|
||||
headers?: { [key: string]: string }
|
||||
}
|
||||
|
||||
export interface HandlerError {
|
||||
status: number
|
||||
message?: string
|
||||
}
|
||||
|
||||
export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough
|
||||
export type HandlerReqCtx<HA extends HandlerAuth = never> = {
|
||||
auth: HA
|
||||
params: QueryParams
|
||||
input: HandlerInput
|
||||
req: express.Request
|
||||
res: express.Response
|
||||
resetRouteRateLimits: () => Promise<void>
|
||||
}
|
||||
export type Handler<HA extends HandlerAuth = never> = (
|
||||
ctx: HandlerReqCtx<HA>,
|
||||
) => Promise<HandlerOutput> | HandlerOutput
|
Loading…
x
Reference in New Issue
Block a user