Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

✨ Add reporter_stats materialized view and endpoint to fetch reporter stats #3509

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 52 additions & 0 deletions lexicons/tools/ozone/moderation/defs.json
Original file line number Diff line number Diff line change
Expand Up @@ -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."
}
Comment on lines +834 to +865
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it make sense to split the "accounts reports stats" and "record reports stats" in two distinct objects, to be more consistent with the reportee stats ?

Copy link
Contributor

@matthieusieben matthieusieben Feb 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doing this could actually rely on a shared sub-type. Something like:

defs:
  # ...

  reportsCountAggregate:
    description: Aggregated statistics
    type: object
    required:
      - totalCount
      - distinctCount
      - takendownCount
      - labeledCount
    properties:
      totalCount:
        description: The total count of reports in the aggregate (including duplicate reports)
      subjectCount:
        description: The count of distinct subjects
      takendownCount:
        description: The count of reported subjects that were taken down
      labeledCount:
        description: The count of reported subjects that were labeled

  reporterStats:
    type: object
    required: ['did']
    properties:
      did:
        type: string
        description: The DID of the user
      reportedRecordsStats:
        type: ref
        ref: '#reportsCountAggregate'
        description: Aggregated statistics on the user's reported records
      reportedAccountsStats:
        type: ref
        ref: '#reportsCountAggregate'
        description: Aggregated statistics on the user's reported accounts

}
}
}
}
40 changes: 40 additions & 0 deletions lexicons/tools/ozone/moderation/getReporterStats.json
Original file line number Diff line number Diff line change
@@ -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"
}
}
}
}
}
}
}
}
14 changes: 14 additions & 0 deletions packages/api/src/client/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,7 @@ import * as ToolsOzoneModerationGetEvent from './types/tools/ozone/moderation/ge
import * as ToolsOzoneModerationGetRecord from './types/tools/ozone/moderation/getRecord'
import * as ToolsOzoneModerationGetRecords from './types/tools/ozone/moderation/getRecords'
import * as ToolsOzoneModerationGetRepo from './types/tools/ozone/moderation/getRepo'
import * as ToolsOzoneModerationGetReporterStats from './types/tools/ozone/moderation/getReporterStats'
import * as ToolsOzoneModerationGetRepos from './types/tools/ozone/moderation/getRepos'
import * as ToolsOzoneModerationQueryEvents from './types/tools/ozone/moderation/queryEvents'
import * as ToolsOzoneModerationQueryStatuses from './types/tools/ozone/moderation/queryStatuses'
Expand Down Expand Up @@ -436,6 +437,7 @@ export * as ToolsOzoneModerationGetEvent from './types/tools/ozone/moderation/ge
export * as ToolsOzoneModerationGetRecord from './types/tools/ozone/moderation/getRecord'
export * as ToolsOzoneModerationGetRecords from './types/tools/ozone/moderation/getRecords'
export * as ToolsOzoneModerationGetRepo from './types/tools/ozone/moderation/getRepo'
export * as ToolsOzoneModerationGetReporterStats from './types/tools/ozone/moderation/getReporterStats'
export * as ToolsOzoneModerationGetRepos from './types/tools/ozone/moderation/getRepos'
export * as ToolsOzoneModerationQueryEvents from './types/tools/ozone/moderation/queryEvents'
export * as ToolsOzoneModerationQueryStatuses from './types/tools/ozone/moderation/queryStatuses'
Expand Down Expand Up @@ -3716,6 +3718,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,
Expand Down
100 changes: 100 additions & 0 deletions packages/api/src/client/lexicons.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12213,6 +12213,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: {
Expand Down Expand Up @@ -12424,6 +12482,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',
Expand Down Expand Up @@ -14098,6 +14196,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',
Expand Down
33 changes: 33 additions & 0 deletions packages/api/src/client/types/tools/ozone/moderation/defs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -931,3 +931,36 @@ export function isRecordHosting(v: unknown): v is RecordHosting {
export function validateRecordHosting(v: unknown): ValidationResult {
return lexicons.validate('tools.ozone.moderation.defs#recordHosting', v)
}

export interface 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
[k: string]: unknown
}

export function isReporterStats(v: unknown): v is ReporterStats {
return (
isObj(v) &&
hasProp(v, '$type') &&
v.$type === 'tools.ozone.moderation.defs#reporterStats'
)
}

export function validateReporterStats(v: unknown): ValidationResult {
return lexicons.validate('tools.ozone.moderation.defs#reporterStats', v)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/**
* GENERATED CODE - DO NOT MODIFY
*/
import { HeadersMap, XRPCError } from '@atproto/xrpc'
import { ValidationResult, BlobRef } from '@atproto/lexicon'
import { isObj, hasProp } from '../../../../util'
import { lexicons } from '../../../../lexicons'
import { CID } from 'multiformats/cid'
import * as ToolsOzoneModerationDefs from './defs'

export interface QueryParams {
dids: string[]
}

export type InputSchema = undefined

export interface OutputSchema {
stats: ToolsOzoneModerationDefs.ReporterStats[]
[k: string]: unknown
}

export interface CallOptions {
signal?: AbortSignal
headers?: HeadersMap
}

export interface Response {
success: boolean
headers: HeadersMap
data: OutputSchema
}

export function toKnownErr(e: any) {
return e
}
13 changes: 13 additions & 0 deletions packages/dev-env/src/moderator-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
2 changes: 2 additions & 0 deletions packages/ozone/src/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -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 changes: 18 additions & 0 deletions packages/ozone/src/api/moderation/getReporterStats.ts
Original file line number Diff line number Diff line change
@@ -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 },
}
},
})
}
1 change: 1 addition & 0 deletions packages/ozone/src/daemon/materialized-view-refresher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
Loading