@atproto/ozone 0.1.79 → 0.1.81

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.
Files changed (71) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/dist/api/index.d.ts.map +1 -1
  3. package/dist/api/index.js +2 -0
  4. package/dist/api/index.js.map +1 -1
  5. package/dist/api/moderation/getReporterStats.d.ts +4 -0
  6. package/dist/api/moderation/getReporterStats.d.ts.map +1 -0
  7. package/dist/api/moderation/getReporterStats.js +17 -0
  8. package/dist/api/moderation/getReporterStats.js.map +1 -0
  9. package/dist/daemon/materialized-view-refresher.d.ts.map +1 -1
  10. package/dist/daemon/materialized-view-refresher.js +1 -0
  11. package/dist/daemon/materialized-view-refresher.js.map +1 -1
  12. package/dist/db/migrations/20250211T003647759Z-add-reporter-stats-index.d.ts +4 -0
  13. package/dist/db/migrations/20250211T003647759Z-add-reporter-stats-index.d.ts.map +1 -0
  14. package/dist/db/migrations/20250211T003647759Z-add-reporter-stats-index.js +38 -0
  15. package/dist/db/migrations/20250211T003647759Z-add-reporter-stats-index.js.map +1 -0
  16. package/dist/db/migrations/index.d.ts +1 -0
  17. package/dist/db/migrations/index.d.ts.map +1 -1
  18. package/dist/db/migrations/index.js +2 -1
  19. package/dist/db/migrations/index.js.map +1 -1
  20. package/dist/lexicon/index.d.ts +4 -0
  21. package/dist/lexicon/index.d.ts.map +1 -1
  22. package/dist/lexicon/index.js +8 -0
  23. package/dist/lexicon/index.js.map +1 -1
  24. package/dist/lexicon/lexicons.d.ts +300 -0
  25. package/dist/lexicon/lexicons.d.ts.map +1 -1
  26. package/dist/lexicon/lexicons.js +162 -0
  27. package/dist/lexicon/lexicons.js.map +1 -1
  28. package/dist/lexicon/types/com/atproto/sync/listReposByCollection.d.ts +46 -0
  29. package/dist/lexicon/types/com/atproto/sync/listReposByCollection.d.ts.map +1 -0
  30. package/dist/lexicon/types/com/atproto/sync/listReposByCollection.js +16 -0
  31. package/dist/lexicon/types/com/atproto/sync/listReposByCollection.js.map +1 -0
  32. package/dist/lexicon/types/tools/ozone/moderation/defs.d.ts +22 -0
  33. package/dist/lexicon/types/tools/ozone/moderation/defs.d.ts.map +1 -1
  34. package/dist/lexicon/types/tools/ozone/moderation/defs.js +9 -0
  35. package/dist/lexicon/types/tools/ozone/moderation/defs.js.map +1 -1
  36. package/dist/lexicon/types/tools/ozone/moderation/getReporterStats.d.ts +36 -0
  37. package/dist/lexicon/types/tools/ozone/moderation/getReporterStats.d.ts.map +1 -0
  38. package/dist/lexicon/types/tools/ozone/moderation/getReporterStats.js +7 -0
  39. package/dist/lexicon/types/tools/ozone/moderation/getReporterStats.js.map +1 -0
  40. package/dist/lexicon/types/tools/ozone/team/listMembers.d.ts +2 -0
  41. package/dist/lexicon/types/tools/ozone/team/listMembers.d.ts.map +1 -1
  42. package/dist/mod-service/index.d.ts +5 -1
  43. package/dist/mod-service/index.d.ts.map +1 -1
  44. package/dist/mod-service/index.js +94 -0
  45. package/dist/mod-service/index.js.map +1 -1
  46. package/dist/mod-service/types.d.ts +21 -0
  47. package/dist/mod-service/types.d.ts.map +1 -1
  48. package/dist/team/index.d.ts +3 -1
  49. package/dist/team/index.d.ts.map +1 -1
  50. package/dist/team/index.js +13 -1
  51. package/dist/team/index.js.map +1 -1
  52. package/package.json +3 -3
  53. package/src/api/index.ts +2 -0
  54. package/src/api/moderation/getReporterStats.ts +18 -0
  55. package/src/daemon/materialized-view-refresher.ts +1 -0
  56. package/src/db/migrations/20250211T003647759Z-add-reporter-stats-index.ts +38 -0
  57. package/src/db/migrations/index.ts +1 -0
  58. package/src/lexicon/index.ts +24 -0
  59. package/src/lexicon/lexicons.ts +171 -0
  60. package/src/lexicon/types/com/atproto/sync/listReposByCollection.ts +68 -0
  61. package/src/lexicon/types/tools/ozone/moderation/defs.ts +31 -0
  62. package/src/lexicon/types/tools/ozone/moderation/getReporterStats.ts +50 -0
  63. package/src/lexicon/types/tools/ozone/team/listMembers.ts +2 -0
  64. package/src/mod-service/index.ts +126 -0
  65. package/src/mod-service/types.ts +23 -0
  66. package/src/team/index.ts +20 -0
  67. package/tests/__snapshots__/team.test.ts.snap +0 -338
  68. package/tests/get-reporter-stats.test.ts +117 -0
  69. package/tests/team.test.ts +56 -26
  70. package/tsconfig.build.tsbuildinfo +1 -1
  71. package/tsconfig.tests.tsbuildinfo +1 -1
@@ -3661,6 +3661,67 @@ export const schemaDict = {
3661
3661
  },
3662
3662
  },
3663
3663
  },
3664
+ ComAtprotoSyncListReposByCollection: {
3665
+ lexicon: 1,
3666
+ id: 'com.atproto.sync.listReposByCollection',
3667
+ defs: {
3668
+ main: {
3669
+ type: 'query',
3670
+ description:
3671
+ 'Enumerates all the DIDs which have records with the given collection NSID.',
3672
+ parameters: {
3673
+ type: 'params',
3674
+ required: ['collection'],
3675
+ properties: {
3676
+ collection: {
3677
+ type: 'string',
3678
+ format: 'nsid',
3679
+ },
3680
+ limit: {
3681
+ type: 'integer',
3682
+ description:
3683
+ 'Maximum size of response set. Recommend setting a large maximum (1000+) when enumerating large DID lists.',
3684
+ minimum: 1,
3685
+ maximum: 2000,
3686
+ default: 500,
3687
+ },
3688
+ cursor: {
3689
+ type: 'string',
3690
+ },
3691
+ },
3692
+ },
3693
+ output: {
3694
+ encoding: 'application/json',
3695
+ schema: {
3696
+ type: 'object',
3697
+ required: ['repos'],
3698
+ properties: {
3699
+ cursor: {
3700
+ type: 'string',
3701
+ },
3702
+ repos: {
3703
+ type: 'array',
3704
+ items: {
3705
+ type: 'ref',
3706
+ ref: 'lex:com.atproto.sync.listReposByCollection#repo',
3707
+ },
3708
+ },
3709
+ },
3710
+ },
3711
+ },
3712
+ },
3713
+ repo: {
3714
+ type: 'object',
3715
+ required: ['did'],
3716
+ properties: {
3717
+ did: {
3718
+ type: 'string',
3719
+ format: 'did',
3720
+ },
3721
+ },
3722
+ },
3723
+ },
3724
+ },
3664
3725
  ComAtprotoSyncNotifyOfUpdate: {
3665
3726
  lexicon: 1,
3666
3727
  id: 'com.atproto.sync.notifyOfUpdate',
@@ -12219,6 +12280,64 @@ export const schemaDict = {
12219
12280
  },
12220
12281
  },
12221
12282
  },
12283
+ reporterStats: {
12284
+ type: 'object',
12285
+ required: [
12286
+ 'did',
12287
+ 'accountReportCount',
12288
+ 'recordReportCount',
12289
+ 'reportedAccountCount',
12290
+ 'reportedRecordCount',
12291
+ 'takendownAccountCount',
12292
+ 'takendownRecordCount',
12293
+ 'labeledAccountCount',
12294
+ 'labeledRecordCount',
12295
+ ],
12296
+ properties: {
12297
+ did: {
12298
+ type: 'string',
12299
+ format: 'did',
12300
+ },
12301
+ accountReportCount: {
12302
+ type: 'integer',
12303
+ description:
12304
+ 'The total number of reports made by the user on accounts.',
12305
+ },
12306
+ recordReportCount: {
12307
+ type: 'integer',
12308
+ description:
12309
+ 'The total number of reports made by the user on records.',
12310
+ },
12311
+ reportedAccountCount: {
12312
+ type: 'integer',
12313
+ description: 'The total number of accounts reported by the user.',
12314
+ },
12315
+ reportedRecordCount: {
12316
+ type: 'integer',
12317
+ description: 'The total number of records reported by the user.',
12318
+ },
12319
+ takendownAccountCount: {
12320
+ type: 'integer',
12321
+ description:
12322
+ "The total number of accounts taken down as a result of the user's reports.",
12323
+ },
12324
+ takendownRecordCount: {
12325
+ type: 'integer',
12326
+ description:
12327
+ "The total number of records taken down as a result of the user's reports.",
12328
+ },
12329
+ labeledAccountCount: {
12330
+ type: 'integer',
12331
+ description:
12332
+ "The total number of accounts labeled as a result of the user's reports.",
12333
+ },
12334
+ labeledRecordCount: {
12335
+ type: 'integer',
12336
+ description:
12337
+ "The total number of records labeled as a result of the user's reports.",
12338
+ },
12339
+ },
12340
+ },
12222
12341
  },
12223
12342
  },
12224
12343
  ToolsOzoneModerationEmitEvent: {
@@ -12430,6 +12549,46 @@ export const schemaDict = {
12430
12549
  },
12431
12550
  },
12432
12551
  },
12552
+ ToolsOzoneModerationGetReporterStats: {
12553
+ lexicon: 1,
12554
+ id: 'tools.ozone.moderation.getReporterStats',
12555
+ defs: {
12556
+ main: {
12557
+ type: 'query',
12558
+ description: 'Get reporter stats for a list of users.',
12559
+ parameters: {
12560
+ type: 'params',
12561
+ required: ['dids'],
12562
+ properties: {
12563
+ dids: {
12564
+ type: 'array',
12565
+ maxLength: 100,
12566
+ items: {
12567
+ type: 'string',
12568
+ format: 'did',
12569
+ },
12570
+ },
12571
+ },
12572
+ },
12573
+ output: {
12574
+ encoding: 'application/json',
12575
+ schema: {
12576
+ type: 'object',
12577
+ required: ['stats'],
12578
+ properties: {
12579
+ stats: {
12580
+ type: 'array',
12581
+ items: {
12582
+ type: 'ref',
12583
+ ref: 'lex:tools.ozone.moderation.defs#reporterStats',
12584
+ },
12585
+ },
12586
+ },
12587
+ },
12588
+ },
12589
+ },
12590
+ },
12591
+ },
12433
12592
  ToolsOzoneModerationGetRepos: {
12434
12593
  lexicon: 1,
12435
12594
  id: 'tools.ozone.moderation.getRepos',
@@ -13794,6 +13953,15 @@ export const schemaDict = {
13794
13953
  parameters: {
13795
13954
  type: 'params',
13796
13955
  properties: {
13956
+ disabled: {
13957
+ type: 'boolean',
13958
+ },
13959
+ roles: {
13960
+ type: 'array',
13961
+ items: {
13962
+ type: 'string',
13963
+ },
13964
+ },
13797
13965
  limit: {
13798
13966
  type: 'integer',
13799
13967
  minimum: 1,
@@ -13993,6 +14161,7 @@ export const ids = {
13993
14161
  ComAtprotoSyncGetRepoStatus: 'com.atproto.sync.getRepoStatus',
13994
14162
  ComAtprotoSyncListBlobs: 'com.atproto.sync.listBlobs',
13995
14163
  ComAtprotoSyncListRepos: 'com.atproto.sync.listRepos',
14164
+ ComAtprotoSyncListReposByCollection: 'com.atproto.sync.listReposByCollection',
13996
14165
  ComAtprotoSyncNotifyOfUpdate: 'com.atproto.sync.notifyOfUpdate',
13997
14166
  ComAtprotoSyncRequestCrawl: 'com.atproto.sync.requestCrawl',
13998
14167
  ComAtprotoSyncSubscribeRepos: 'com.atproto.sync.subscribeRepos',
@@ -14133,6 +14302,8 @@ export const ids = {
14133
14302
  ToolsOzoneModerationGetRecord: 'tools.ozone.moderation.getRecord',
14134
14303
  ToolsOzoneModerationGetRecords: 'tools.ozone.moderation.getRecords',
14135
14304
  ToolsOzoneModerationGetRepo: 'tools.ozone.moderation.getRepo',
14305
+ ToolsOzoneModerationGetReporterStats:
14306
+ 'tools.ozone.moderation.getReporterStats',
14136
14307
  ToolsOzoneModerationGetRepos: 'tools.ozone.moderation.getRepos',
14137
14308
  ToolsOzoneModerationQueryEvents: 'tools.ozone.moderation.queryEvents',
14138
14309
  ToolsOzoneModerationQueryStatuses: 'tools.ozone.moderation.queryStatuses',
@@ -0,0 +1,68 @@
1
+ /**
2
+ * GENERATED CODE - DO NOT MODIFY
3
+ */
4
+ import express from 'express'
5
+ import { ValidationResult, BlobRef } from '@atproto/lexicon'
6
+ import { CID } from 'multiformats/cid'
7
+ import { validate as _validate } from '../../../../lexicons'
8
+ import { $Typed, is$typed as _is$typed, OmitKey } from '../../../../util'
9
+ import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server'
10
+
11
+ const is$typed = _is$typed,
12
+ validate = _validate
13
+ const id = 'com.atproto.sync.listReposByCollection'
14
+
15
+ export interface QueryParams {
16
+ collection: string
17
+ /** Maximum size of response set. Recommend setting a large maximum (1000+) when enumerating large DID lists. */
18
+ limit: number
19
+ cursor?: string
20
+ }
21
+
22
+ export type InputSchema = undefined
23
+
24
+ export interface OutputSchema {
25
+ cursor?: string
26
+ repos: Repo[]
27
+ }
28
+
29
+ export type HandlerInput = undefined
30
+
31
+ export interface HandlerSuccess {
32
+ encoding: 'application/json'
33
+ body: OutputSchema
34
+ headers?: { [key: string]: string }
35
+ }
36
+
37
+ export interface HandlerError {
38
+ status: number
39
+ message?: string
40
+ }
41
+
42
+ export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough
43
+ export type HandlerReqCtx<HA extends HandlerAuth = never> = {
44
+ auth: HA
45
+ params: QueryParams
46
+ input: HandlerInput
47
+ req: express.Request
48
+ res: express.Response
49
+ resetRouteRateLimits: () => Promise<void>
50
+ }
51
+ export type Handler<HA extends HandlerAuth = never> = (
52
+ ctx: HandlerReqCtx<HA>,
53
+ ) => Promise<HandlerOutput> | HandlerOutput
54
+
55
+ export interface Repo {
56
+ $type?: 'com.atproto.sync.listReposByCollection#repo'
57
+ did: string
58
+ }
59
+
60
+ const hashRepo = 'repo'
61
+
62
+ export function isRepo<V>(v: V) {
63
+ return is$typed(v, id, hashRepo)
64
+ }
65
+
66
+ export function validateRepo<V>(v: V) {
67
+ return validate<Repo & V>(v, id, hashRepo)
68
+ }
@@ -836,3 +836,34 @@ export function isRecordHosting<V>(v: V) {
836
836
  export function validateRecordHosting<V>(v: V) {
837
837
  return validate<RecordHosting & V>(v, id, hashRecordHosting)
838
838
  }
839
+
840
+ export interface ReporterStats {
841
+ $type?: 'tools.ozone.moderation.defs#reporterStats'
842
+ did: string
843
+ /** The total number of reports made by the user on accounts. */
844
+ accountReportCount: number
845
+ /** The total number of reports made by the user on records. */
846
+ recordReportCount: number
847
+ /** The total number of accounts reported by the user. */
848
+ reportedAccountCount: number
849
+ /** The total number of records reported by the user. */
850
+ reportedRecordCount: number
851
+ /** The total number of accounts taken down as a result of the user's reports. */
852
+ takendownAccountCount: number
853
+ /** The total number of records taken down as a result of the user's reports. */
854
+ takendownRecordCount: number
855
+ /** The total number of accounts labeled as a result of the user's reports. */
856
+ labeledAccountCount: number
857
+ /** The total number of records labeled as a result of the user's reports. */
858
+ labeledRecordCount: number
859
+ }
860
+
861
+ const hashReporterStats = 'reporterStats'
862
+
863
+ export function isReporterStats<V>(v: V) {
864
+ return is$typed(v, id, hashReporterStats)
865
+ }
866
+
867
+ export function validateReporterStats<V>(v: V) {
868
+ return validate<ReporterStats & V>(v, id, hashReporterStats)
869
+ }
@@ -0,0 +1,50 @@
1
+ /**
2
+ * GENERATED CODE - DO NOT MODIFY
3
+ */
4
+ import express from 'express'
5
+ import { ValidationResult, BlobRef } from '@atproto/lexicon'
6
+ import { CID } from 'multiformats/cid'
7
+ import { validate as _validate } from '../../../../lexicons'
8
+ import { $Typed, is$typed as _is$typed, OmitKey } from '../../../../util'
9
+ import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server'
10
+ import type * as ToolsOzoneModerationDefs from './defs.js'
11
+
12
+ const is$typed = _is$typed,
13
+ validate = _validate
14
+ const id = 'tools.ozone.moderation.getReporterStats'
15
+
16
+ export interface QueryParams {
17
+ dids: string[]
18
+ }
19
+
20
+ export type InputSchema = undefined
21
+
22
+ export interface OutputSchema {
23
+ stats: ToolsOzoneModerationDefs.ReporterStats[]
24
+ }
25
+
26
+ export type HandlerInput = undefined
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
+ resetRouteRateLimits: () => Promise<void>
47
+ }
48
+ export type Handler<HA extends HandlerAuth = never> = (
49
+ ctx: HandlerReqCtx<HA>,
50
+ ) => Promise<HandlerOutput> | HandlerOutput
@@ -14,6 +14,8 @@ const is$typed = _is$typed,
14
14
  const id = 'tools.ozone.team.listMembers'
15
15
 
16
16
  export interface QueryParams {
17
+ disabled?: boolean
18
+ roles?: string[]
17
19
  limit: number
18
20
  cursor?: string
19
21
  }
@@ -58,6 +58,8 @@ import {
58
58
  ModerationEventRow,
59
59
  ModerationSubjectStatusRow,
60
60
  ModerationSubjectStatusRowWithHandle,
61
+ ReporterStats,
62
+ ReporterStatsResult,
61
63
  ReversibleModerationEvent,
62
64
  } from './types'
63
65
  import { formatLabel, formatLabelRow, signLabel } from './util'
@@ -1272,6 +1274,130 @@ export class ModerationService {
1272
1274
  throw new InvalidRequestError('Email was accepted but not sent')
1273
1275
  }
1274
1276
  }
1277
+
1278
+ buildModerationQuery(
1279
+ subjectType: 'account' | 'record',
1280
+ createdByDids: string[],
1281
+ isActionQuery: boolean,
1282
+ ): Promise<(Partial<ReporterStatsResult> & { did: string })[]> {
1283
+ const isAccount = subjectType === 'account'
1284
+ const actionTypes = [
1285
+ 'tools.ozone.moderation.defs#modEventTakedown',
1286
+ 'tools.ozone.moderation.defs#modEventLabel',
1287
+ ] as const
1288
+
1289
+ const query = this.db.db
1290
+ .selectFrom('moderation_event as reports')
1291
+ .where(
1292
+ 'reports.action',
1293
+ '=',
1294
+ 'tools.ozone.moderation.defs#modEventReport',
1295
+ )
1296
+ .where('reports.subjectUri', isAccount ? 'is' : 'is not', null)
1297
+ .where('reports.createdBy', 'in', createdByDids)
1298
+ .select(['reports.createdBy as did'])
1299
+
1300
+ if (isActionQuery) {
1301
+ return query
1302
+ .leftJoin('moderation_event as actions', (join) =>
1303
+ join
1304
+ .onRef('actions.subjectDid', '=', 'reports.subjectDid')
1305
+ .on('actions.subjectUri', isAccount ? 'is' : 'is not', null)
1306
+ .onRef('actions.createdAt', '>', 'reports.createdAt')
1307
+ .on('actions.action', 'in', actionTypes),
1308
+ )
1309
+ .select([
1310
+ () =>
1311
+ sql<number>`COUNT(DISTINCT actions."subjectDid") FILTER (
1312
+ WHERE actions."action" = 'tools.ozone.moderation.defs#modEventTakedown'
1313
+ )`.as(`takendown${isAccount ? 'Account' : 'Record'}Count`),
1314
+
1315
+ () =>
1316
+ sql<number>`COUNT(DISTINCT actions."subjectDid") FILTER (
1317
+ WHERE actions."action" = 'tools.ozone.moderation.defs#modEventLabel'
1318
+ )`.as(`labeled${isAccount ? 'Account' : 'Record'}Count`),
1319
+ ])
1320
+ .groupBy('reports.createdBy')
1321
+ .execute()
1322
+ }
1323
+
1324
+ return query
1325
+ .select([
1326
+ (eb) =>
1327
+ eb.fn.count<number>('reports.id').as(`${subjectType}ReportCount`),
1328
+ (eb) =>
1329
+ eb.fn
1330
+ .count<number>(
1331
+ isAccount ? 'reports.subjectDid' : 'reports.subjectUri',
1332
+ )
1333
+ .distinct()
1334
+ .as(`reported${isAccount ? 'Account' : 'Record'}Count`),
1335
+ ])
1336
+ .groupBy('reports.createdBy')
1337
+ .execute()
1338
+ }
1339
+
1340
+ async getReporterStats(dids: string[]) {
1341
+ const [accountReports, recordReports, accountActions, recordActions] =
1342
+ await Promise.all([
1343
+ this.buildModerationQuery('account', dids, false),
1344
+ this.buildModerationQuery('record', dids, false),
1345
+ this.buildModerationQuery('account', dids, true),
1346
+ this.buildModerationQuery('record', dids, true),
1347
+ ])
1348
+
1349
+ // Create a map to hold the aggregated stats for each `did`
1350
+ const statsMap = new Map<string, ReporterStats>()
1351
+
1352
+ // Helper function to ensure a `did` entry exists in the map
1353
+ const ensureDidEntry = (did: string) => {
1354
+ if (!statsMap.has(did)) {
1355
+ statsMap.set(did, {
1356
+ did,
1357
+ accountReportCount: 0,
1358
+ recordReportCount: 0,
1359
+ reportedAccountCount: 0,
1360
+ reportedRecordCount: 0,
1361
+ takendownAccountCount: 0,
1362
+ takendownRecordCount: 0,
1363
+ labeledAccountCount: 0,
1364
+ labeledRecordCount: 0,
1365
+ })
1366
+ }
1367
+ return statsMap.get(did)!
1368
+ }
1369
+
1370
+ // Merge accountReports
1371
+ for (const report of accountReports) {
1372
+ const entry = ensureDidEntry(report.did)
1373
+ entry.accountReportCount = report.accountReportCount ?? 0
1374
+ entry.reportedAccountCount = report.reportedAccountCount ?? 0
1375
+ }
1376
+
1377
+ // Merge recordReports
1378
+ for (const report of recordReports) {
1379
+ const entry = ensureDidEntry(report.did)
1380
+ entry.recordReportCount = report.recordReportCount ?? 0
1381
+ entry.reportedRecordCount = report.reportedRecordCount ?? 0
1382
+ }
1383
+
1384
+ // Merge accountActions
1385
+ for (const action of accountActions) {
1386
+ const entry = ensureDidEntry(action.did)
1387
+ entry.takendownAccountCount = action.takendownAccountCount ?? 0
1388
+ entry.labeledAccountCount = action.labeledAccountCount ?? 0
1389
+ }
1390
+
1391
+ // Merge recordActions
1392
+ for (const action of recordActions) {
1393
+ const entry = ensureDidEntry(action.did)
1394
+ entry.takendownRecordCount = action.takendownRecordCount ?? 0
1395
+ entry.labeledRecordCount = action.labeledRecordCount ?? 0
1396
+ }
1397
+
1398
+ // Convert map values to an array and return
1399
+ return Array.from(statsMap.values())
1400
+ }
1275
1401
  }
1276
1402
 
1277
1403
  const parseTags = (tags?: string[]) =>
@@ -65,3 +65,26 @@ type RecordHostingView = {
65
65
  export type ModerationSubjectHostingView =
66
66
  | AccountHostingView
67
67
  | RecordHostingView
68
+
69
+ export type ReporterStats = {
70
+ did: string
71
+ accountReportCount: number
72
+ recordReportCount: number
73
+ reportedAccountCount: number
74
+ reportedRecordCount: number
75
+ takendownAccountCount: number
76
+ takendownRecordCount: number
77
+ labeledAccountCount: number
78
+ labeledRecordCount: number
79
+ }
80
+
81
+ export type ReporterStatsResult = {
82
+ accountReportCount?: number
83
+ recordReportCount?: number
84
+ reportedAccountCount?: number
85
+ reportedRecordCount?: number
86
+ takendownAccountCount?: number
87
+ takendownRecordCount?: number
88
+ labeledAccountCount?: number
89
+ labeledRecordCount?: number
90
+ }
package/src/team/index.ts CHANGED
@@ -21,14 +21,34 @@ export class TeamService {
21
21
  async list({
22
22
  cursor,
23
23
  limit = 25,
24
+ roles,
25
+ disabled,
24
26
  }: {
25
27
  cursor?: string
26
28
  limit?: number
29
+ disabled?: boolean
30
+ roles?: string[]
27
31
  }): Promise<{ members: Selectable<Member>[]; cursor?: string }> {
28
32
  let builder = this.db.db.selectFrom('member').selectAll()
29
33
  if (cursor) {
30
34
  builder = builder.where('createdAt', '>', new Date(cursor))
31
35
  }
36
+ if (roles !== undefined) {
37
+ const knownRoles = roles.filter(
38
+ (r) =>
39
+ r === 'tools.ozone.team.defs#roleAdmin' ||
40
+ r === 'tools.ozone.team.defs#roleModerator' ||
41
+ r === 'tools.ozone.team.defs#roleTriage',
42
+ )
43
+
44
+ // Optimization: no need to query to know that no values will be returned
45
+ if (!knownRoles.length) return { members: [] }
46
+
47
+ builder = builder.where('role', 'in', knownRoles)
48
+ }
49
+ if (disabled !== undefined) {
50
+ builder = builder.where('disabled', disabled ? 'is' : 'is not', true)
51
+ }
32
52
  const members = await builder
33
53
  .limit(limit)
34
54
  .orderBy('createdAt', 'asc')