@atproto/ozone 0.0.9 → 0.0.10

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 (51) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/dist/api/moderation/util.d.ts +1 -1
  3. package/dist/db/index.js +20 -1
  4. package/dist/db/index.js.map +3 -3
  5. package/dist/db/migrations/20240208T213404429Z-add-tags-column-to-moderation-subject.d.ts +3 -0
  6. package/dist/db/migrations/index.d.ts +1 -0
  7. package/dist/db/schema/moderation_event.d.ts +3 -1
  8. package/dist/db/schema/moderation_subject_status.d.ts +1 -0
  9. package/dist/index.js +318 -51
  10. package/dist/index.js.map +3 -3
  11. package/dist/lexicon/lexicons.d.ts +57 -0
  12. package/dist/lexicon/types/com/atproto/admin/defs.d.ts +9 -0
  13. package/dist/lexicon/types/com/atproto/admin/emitModerationEvent.d.ts +1 -1
  14. package/dist/lexicon/types/com/atproto/admin/queryModerationEvents.d.ts +2 -0
  15. package/dist/lexicon/types/com/atproto/admin/queryModerationStatuses.d.ts +2 -0
  16. package/dist/logger.d.ts +1 -0
  17. package/dist/mod-service/index.d.ts +18 -4
  18. package/dist/mod-service/lang.d.ts +15 -0
  19. package/dist/mod-service/status.d.ts +1 -1
  20. package/dist/mod-service/types.d.ts +1 -1
  21. package/dist/mod-service/views.d.ts +2 -1
  22. package/package.json +6 -6
  23. package/src/api/admin/emitModerationEvent.ts +22 -10
  24. package/src/api/admin/queryModerationEvents.ts +4 -0
  25. package/src/api/admin/queryModerationStatuses.ts +4 -0
  26. package/src/api/moderation/createReport.ts +15 -4
  27. package/src/api/moderation/util.ts +1 -0
  28. package/src/db/migrations/20240208T213404429Z-add-tags-column-to-moderation-subject.ts +31 -0
  29. package/src/db/migrations/index.ts +1 -0
  30. package/src/db/schema/moderation_event.ts +3 -0
  31. package/src/db/schema/moderation_subject_status.ts +1 -0
  32. package/src/lexicon/lexicons.ts +62 -0
  33. package/src/lexicon/types/com/atproto/admin/defs.ts +24 -0
  34. package/src/lexicon/types/com/atproto/admin/emitModerationEvent.ts +1 -0
  35. package/src/lexicon/types/com/atproto/admin/queryModerationEvents.ts +4 -0
  36. package/src/lexicon/types/com/atproto/admin/queryModerationStatuses.ts +2 -0
  37. package/src/logger.ts +2 -0
  38. package/src/mod-service/index.ts +60 -10
  39. package/src/mod-service/lang.ts +82 -0
  40. package/src/mod-service/status.ts +18 -4
  41. package/src/mod-service/types.ts +1 -0
  42. package/src/mod-service/views.ts +21 -2
  43. package/tests/__snapshots__/get-record.test.ts.snap +6 -0
  44. package/tests/__snapshots__/get-repo.test.ts.snap +3 -0
  45. package/tests/__snapshots__/moderation-events.test.ts.snap +45 -4
  46. package/tests/__snapshots__/moderation-statuses.test.ts.snap +59 -3
  47. package/tests/__snapshots__/moderation.test.ts.snap +3 -3
  48. package/tests/get-record.test.ts +0 -8
  49. package/tests/moderation-events.test.ts +57 -5
  50. package/tests/moderation-status-tags.test.ts +92 -0
  51. package/tests/moderation-statuses.test.ts +20 -3
@@ -236,6 +236,12 @@ export declare const schemaDict: {
236
236
  type: string;
237
237
  format: string;
238
238
  };
239
+ tags: {
240
+ type: string;
241
+ items: {
242
+ type: string;
243
+ };
244
+ };
239
245
  };
240
246
  };
241
247
  reportViewDetail: {
@@ -779,6 +785,31 @@ export declare const schemaDict: {
779
785
  };
780
786
  };
781
787
  };
788
+ modEventTag: {
789
+ type: string;
790
+ description: string;
791
+ required: string[];
792
+ properties: {
793
+ add: {
794
+ type: string;
795
+ items: {
796
+ type: string;
797
+ };
798
+ description: string;
799
+ };
800
+ remove: {
801
+ type: string;
802
+ items: {
803
+ type: string;
804
+ };
805
+ description: string;
806
+ };
807
+ comment: {
808
+ type: string;
809
+ description: string;
810
+ };
811
+ };
812
+ };
782
813
  communicationTemplateView: {
783
814
  type: string;
784
815
  required: string[];
@@ -1343,6 +1374,20 @@ export declare const schemaDict: {
1343
1374
  };
1344
1375
  description: string;
1345
1376
  };
1377
+ addedTags: {
1378
+ type: string;
1379
+ items: {
1380
+ type: string;
1381
+ };
1382
+ description: string;
1383
+ };
1384
+ removedTags: {
1385
+ type: string;
1386
+ items: {
1387
+ type: string;
1388
+ };
1389
+ description: string;
1390
+ };
1346
1391
  reportTypes: {
1347
1392
  type: string;
1348
1393
  items: {
@@ -1458,6 +1503,18 @@ export declare const schemaDict: {
1458
1503
  maximum: number;
1459
1504
  default: number;
1460
1505
  };
1506
+ tags: {
1507
+ type: string;
1508
+ items: {
1509
+ type: string;
1510
+ };
1511
+ };
1512
+ excludeTags: {
1513
+ type: string;
1514
+ items: {
1515
+ type: string;
1516
+ };
1517
+ };
1461
1518
  cursor: {
1462
1519
  type: string;
1463
1520
  };
@@ -82,6 +82,7 @@ export interface SubjectStatusView {
82
82
  takendown?: boolean;
83
83
  appealed?: boolean;
84
84
  suspendUntil?: string;
85
+ tags?: string[];
85
86
  [k: string]: unknown;
86
87
  }
87
88
  export declare function isSubjectStatusView(v: unknown): v is SubjectStatusView;
@@ -317,6 +318,14 @@ export interface ModEventEmail {
317
318
  }
318
319
  export declare function isModEventEmail(v: unknown): v is ModEventEmail;
319
320
  export declare function validateModEventEmail(v: unknown): ValidationResult;
321
+ export interface ModEventTag {
322
+ add: string[];
323
+ remove: string[];
324
+ comment?: string;
325
+ [k: string]: unknown;
326
+ }
327
+ export declare function isModEventTag(v: unknown): v is ModEventTag;
328
+ export declare function validateModEventTag(v: unknown): ValidationResult;
320
329
  export interface CommunicationTemplateView {
321
330
  id: string;
322
331
  name: string;
@@ -5,7 +5,7 @@ import * as ComAtprotoRepoStrongRef from '../repo/strongRef';
5
5
  export interface QueryParams {
6
6
  }
7
7
  export interface InputSchema {
8
- event: ComAtprotoAdminDefs.ModEventTakedown | ComAtprotoAdminDefs.ModEventAcknowledge | ComAtprotoAdminDefs.ModEventEscalate | ComAtprotoAdminDefs.ModEventComment | ComAtprotoAdminDefs.ModEventLabel | ComAtprotoAdminDefs.ModEventReport | ComAtprotoAdminDefs.ModEventMute | ComAtprotoAdminDefs.ModEventReverseTakedown | ComAtprotoAdminDefs.ModEventUnmute | ComAtprotoAdminDefs.ModEventEmail | {
8
+ event: ComAtprotoAdminDefs.ModEventTakedown | ComAtprotoAdminDefs.ModEventAcknowledge | ComAtprotoAdminDefs.ModEventEscalate | ComAtprotoAdminDefs.ModEventComment | ComAtprotoAdminDefs.ModEventLabel | ComAtprotoAdminDefs.ModEventReport | ComAtprotoAdminDefs.ModEventMute | ComAtprotoAdminDefs.ModEventReverseTakedown | ComAtprotoAdminDefs.ModEventUnmute | ComAtprotoAdminDefs.ModEventEmail | ComAtprotoAdminDefs.ModEventTag | {
9
9
  $type: string;
10
10
  [k: string]: unknown;
11
11
  };
@@ -14,6 +14,8 @@ export interface QueryParams {
14
14
  comment?: string;
15
15
  addedLabels?: string[];
16
16
  removedLabels?: string[];
17
+ addedTags?: string[];
18
+ removedTags?: string[];
17
19
  reportTypes?: string[];
18
20
  cursor?: string;
19
21
  }
@@ -17,6 +17,8 @@ export interface QueryParams {
17
17
  takendown?: boolean;
18
18
  appealed?: boolean;
19
19
  limit: number;
20
+ tags?: string[];
21
+ excludeTags?: string[];
20
22
  cursor?: string;
21
23
  }
22
24
  export type InputSchema = undefined;
package/dist/logger.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { subsystemLogger } from '@atproto/common';
2
2
  export declare const dbLogger: ReturnType<typeof subsystemLogger>;
3
3
  export declare const httpLogger: ReturnType<typeof subsystemLogger>;
4
+ export declare const langLogger: ReturnType<typeof subsystemLogger>;
4
5
  export declare const loggerMiddleware: import("pino-http").HttpLogger;
@@ -38,6 +38,8 @@ export declare class ModerationService {
38
38
  createdBefore?: string;
39
39
  addedLabels: string[];
40
40
  removedLabels: string[];
41
+ addedTags: string[];
42
+ removedTags: string[];
41
43
  reportTypes?: string[];
42
44
  }): Promise<{
43
45
  cursor?: string;
@@ -60,6 +62,7 @@ export declare class ModerationService {
60
62
  comment: string | null;
61
63
  lastReportedAt: string | null;
62
64
  lastReviewedAt: string | null;
65
+ tags: string[] | null;
63
66
  muteUntil: string | null;
64
67
  lastReviewedBy: string | null;
65
68
  lastAppealedAt: string | null;
@@ -74,7 +77,10 @@ export declare class ModerationService {
74
77
  subject: ModSubject;
75
78
  createdBy: string;
76
79
  createdAt?: Date;
77
- }): Promise<ModerationEventRow>;
80
+ }): Promise<{
81
+ event: ModerationEventRow;
82
+ subjectStatus: ModerationSubjectStatusRow | null;
83
+ }>;
78
84
  getLastReversibleEventForSubject(subject: ReversalSubject): Promise<{
79
85
  id: number;
80
86
  subjectBlobCids: string[] | null;
@@ -84,7 +90,7 @@ export declare class ModerationService {
84
90
  createLabelVals: string | null;
85
91
  negateLabelVals: string | null;
86
92
  durationInHours: number | null;
87
- action: "com.atproto.admin.defs#modEventTakedown" | "com.atproto.admin.defs#modEventAcknowledge" | "com.atproto.admin.defs#modEventEscalate" | "com.atproto.admin.defs#modEventComment" | "com.atproto.admin.defs#modEventLabel" | "com.atproto.admin.defs#modEventReport" | "com.atproto.admin.defs#modEventMute" | "com.atproto.admin.defs#modEventReverseTakedown" | "com.atproto.admin.defs#modEventEmail" | "com.atproto.admin.defs#modEventResolveAppeal";
93
+ action: "com.atproto.admin.defs#modEventTakedown" | "com.atproto.admin.defs#modEventAcknowledge" | "com.atproto.admin.defs#modEventEscalate" | "com.atproto.admin.defs#modEventComment" | "com.atproto.admin.defs#modEventLabel" | "com.atproto.admin.defs#modEventReport" | "com.atproto.admin.defs#modEventMute" | "com.atproto.admin.defs#modEventReverseTakedown" | "com.atproto.admin.defs#modEventEmail" | "com.atproto.admin.defs#modEventResolveAppeal" | "com.atproto.admin.defs#modEventTag";
88
94
  subjectType: "com.atproto.admin.defs#repoRef" | "com.atproto.repo.strongRef";
89
95
  subjectDid: string;
90
96
  subjectUri: string | null;
@@ -92,6 +98,8 @@ export declare class ModerationService {
92
98
  meta: Record<string, string | boolean> | null;
93
99
  expiresAt: string | null;
94
100
  legacyRefId: number | null;
101
+ addedTags: string[] | null;
102
+ removedTags: string[] | null;
95
103
  } | null | undefined>;
96
104
  getSubjectsDueForReversal(): Promise<ReversalSubject[]>;
97
105
  isSubjectSuspended(did: string): Promise<boolean>;
@@ -106,8 +114,11 @@ export declare class ModerationService {
106
114
  subject: ModSubject;
107
115
  reportedBy: string;
108
116
  createdAt?: Date;
109
- }): Promise<ModerationEventRow>;
110
- getSubjectStatuses({ cursor, limit, takendown, appealed, reviewState, reviewedAfter, reviewedBefore, reportedAfter, reportedBefore, includeMuted, ignoreSubjects, sortDirection, lastReviewedBy, sortField, subject, }: {
117
+ }): Promise<{
118
+ event: ModerationEventRow;
119
+ subjectStatus: ModerationSubjectStatusRow | null;
120
+ }>;
121
+ getSubjectStatuses({ cursor, limit, takendown, appealed, reviewState, reviewedAfter, reviewedBefore, reportedAfter, reportedBefore, includeMuted, ignoreSubjects, sortDirection, lastReviewedBy, sortField, subject, tags, excludeTags, }: {
111
122
  cursor?: string;
112
123
  limit?: number;
113
124
  takendown?: boolean;
@@ -123,6 +134,8 @@ export declare class ModerationService {
123
134
  sortDirection: 'asc' | 'desc';
124
135
  lastReviewedBy?: string;
125
136
  sortField: 'lastReviewedAt' | 'lastReportedAt';
137
+ tags: string[];
138
+ excludeTags: string[];
126
139
  }): Promise<{
127
140
  statuses: {
128
141
  handle: string;
@@ -135,6 +148,7 @@ export declare class ModerationService {
135
148
  comment: string | null;
136
149
  lastReportedAt: string | null;
137
150
  lastReviewedAt: string | null;
151
+ tags: string[] | null;
138
152
  muteUntil: string | null;
139
153
  lastReviewedBy: string | null;
140
154
  lastAppealedAt: string | null;
@@ -0,0 +1,15 @@
1
+ import { ModerationService } from '.';
2
+ import { ModSubject } from './subject';
3
+ import { ModerationSubjectStatusRow } from './types';
4
+ export declare class ModerationLangService {
5
+ private moderationService;
6
+ constructor(moderationService: ModerationService);
7
+ tagSubjectWithLang({ subject, subjectStatus, createdBy, }: {
8
+ subject: ModSubject;
9
+ createdBy: string;
10
+ subjectStatus: ModerationSubjectStatusRow | null;
11
+ }): Promise<void>;
12
+ getRecordLang({ subject, }: {
13
+ subject: ModSubject;
14
+ }): Promise<string[] | null>;
15
+ }
@@ -2,7 +2,7 @@ import { AtUri } from '@atproto/syntax';
2
2
  import { Database } from '../db';
3
3
  import { ModerationSubjectStatus } from '../db/schema/moderation_subject_status';
4
4
  import { ModerationEventRow } from './types';
5
- export declare const adjustModerationSubjectStatus: (db: Database, moderationEvent: ModerationEventRow, blobCids?: string[]) => Promise<import("kysely").InsertResult | null>;
5
+ export declare const adjustModerationSubjectStatus: (db: Database, moderationEvent: ModerationEventRow, blobCids?: string[]) => Promise<import("kysely").Selectable<ModerationSubjectStatus> | null>;
6
6
  type ModerationSubjectStatusFilter = Pick<ModerationSubjectStatus, 'did'> | Pick<ModerationSubjectStatus, 'did' | 'recordPath'> | Pick<ModerationSubjectStatus, 'did' | 'recordPath' | 'recordCid'>;
7
7
  export declare const getModerationSubjectStatus: (db: Database, filters: ModerationSubjectStatusFilter) => Promise<{} | undefined>;
8
8
  export declare const getStatusIdentifierFromSubject: (subject: string | AtUri) => {
@@ -16,6 +16,6 @@ export type ModerationSubjectStatusRow = Selectable<ModerationSubjectStatus>;
16
16
  export type ModerationSubjectStatusRowWithHandle = ModerationSubjectStatusRow & {
17
17
  handle: string | null;
18
18
  };
19
- export type ModEventType = ComAtprotoAdminDefs.ModEventTakedown | ComAtprotoAdminDefs.ModEventAcknowledge | ComAtprotoAdminDefs.ModEventEscalate | ComAtprotoAdminDefs.ModEventComment | ComAtprotoAdminDefs.ModEventLabel | ComAtprotoAdminDefs.ModEventReport | ComAtprotoAdminDefs.ModEventMute | ComAtprotoAdminDefs.ModEventReverseTakedown;
19
+ export type ModEventType = ComAtprotoAdminDefs.ModEventTakedown | ComAtprotoAdminDefs.ModEventAcknowledge | ComAtprotoAdminDefs.ModEventEscalate | ComAtprotoAdminDefs.ModEventComment | ComAtprotoAdminDefs.ModEventLabel | ComAtprotoAdminDefs.ModEventReport | ComAtprotoAdminDefs.ModEventMute | ComAtprotoAdminDefs.ModEventReverseTakedown | ComAtprotoAdminDefs.ModEventTag;
20
20
  export declare const UNSPECCED_TAKEDOWN_LABEL = "!unspecced-takedown";
21
21
  export declare const UNSPECCED_TAKEDOWN_BLOBS_LABEL = "!unspecced-takedown-blobs";
@@ -1,4 +1,4 @@
1
- import AtpAgent from '@atproto/api';
1
+ import AtpAgent, { AppBskyFeedDefs } from '@atproto/api';
2
2
  import { BlobRef } from '@atproto/lexicon';
3
3
  import { Database } from '../db';
4
4
  import { ModEventView, RepoView, RepoViewDetail, RecordView, RecordViewDetail, ReportViewDetail, BlobView, SubjectStatusView, ModEventViewDetail, AccountView } from '../lexicon/types/com/atproto/admin/defs';
@@ -29,6 +29,7 @@ export declare class ModerationViews {
29
29
  labels(subject: string, includeNeg?: boolean): Promise<Label[]>;
30
30
  getSubjectStatus(subjects: string[]): Promise<Map<string, ModerationSubjectStatusRowWithHandle>>;
31
31
  formatSubjectStatus(status: ModerationSubjectStatusRowWithHandle): SubjectStatusView;
32
+ fetchAuthorFeed(actor: string): Promise<AppBskyFeedDefs.FeedViewPost[]>;
32
33
  }
33
34
  type RecordSubject = {
34
35
  uri: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atproto/ozone",
3
- "version": "0.0.9",
3
+ "version": "0.0.10",
4
4
  "license": "MIT",
5
5
  "description": "Backend service for moderating the Bluesky network.",
6
6
  "keywords": [
@@ -30,12 +30,12 @@
30
30
  "pino-http": "^8.2.1",
31
31
  "typed-emitter": "^2.1.0",
32
32
  "uint8arrays": "3.0.0",
33
- "@atproto/api": "^0.9.7",
33
+ "@atproto/api": "^0.9.8",
34
34
  "@atproto/common": "^0.3.3",
35
35
  "@atproto/crypto": "^0.3.0",
36
- "@atproto/syntax": "^0.1.5",
37
36
  "@atproto/identity": "^0.3.2",
38
37
  "@atproto/lexicon": "^0.3.1",
38
+ "@atproto/syntax": "^0.1.5",
39
39
  "@atproto/xrpc-server": "^0.4.2"
40
40
  },
41
41
  "devDependencies": {
@@ -46,10 +46,10 @@
46
46
  "@types/pg": "^8.6.6",
47
47
  "@types/qs": "^6.9.7",
48
48
  "axios": "^0.27.2",
49
- "@atproto/api": "^0.9.7",
50
- "@atproto/dev-env": "^0.2.30",
49
+ "@atproto/api": "^0.9.8",
50
+ "@atproto/dev-env": "^0.2.31",
51
51
  "@atproto/lex-cli": "^0.3.0",
52
- "@atproto/pds": "^0.3.18",
52
+ "@atproto/pds": "^0.3.19",
53
53
  "@atproto/xrpc": "^0.4.1"
54
54
  },
55
55
  "scripts": {
@@ -7,6 +7,7 @@ import {
7
7
  isModEventTakedown,
8
8
  } from '../../lexicon/types/com/atproto/admin/defs'
9
9
  import { subjectFromInput } from '../../mod-service/subject'
10
+ import { ModerationLangService } from '../../mod-service/lang'
10
11
 
11
12
  export default function (server: Server, ctx: AppContext) {
12
13
  server.com.atproto.admin.emitModerationEvent({
@@ -77,10 +78,21 @@ export default function (server: Server, ctx: AppContext) {
77
78
  createdBy,
78
79
  })
79
80
 
81
+ const moderationLangService = new ModerationLangService(moderationTxn)
82
+ await moderationLangService.tagSubjectWithLang({
83
+ subject,
84
+ createdBy: ctx.cfg.service.did,
85
+ subjectStatus: result.subjectStatus,
86
+ })
87
+
80
88
  if (subject.isRepo()) {
81
89
  if (isTakedownEvent) {
82
- const isSuspend = !!result.durationInHours
83
- await moderationTxn.takedownRepo(subject, result.id, isSuspend)
90
+ const isSuspend = !!result.event.durationInHours
91
+ await moderationTxn.takedownRepo(
92
+ subject,
93
+ result.event.id,
94
+ isSuspend,
95
+ )
84
96
  } else if (isReverseTakedownEvent) {
85
97
  await moderationTxn.reverseTakedownRepo(subject)
86
98
  }
@@ -88,7 +100,7 @@ export default function (server: Server, ctx: AppContext) {
88
100
 
89
101
  if (subject.isRecord()) {
90
102
  if (isTakedownEvent) {
91
- await moderationTxn.takedownRecord(subject, result.id)
103
+ await moderationTxn.takedownRecord(subject, result.event.id)
92
104
  } else if (isReverseTakedownEvent) {
93
105
  await moderationTxn.reverseTakedownRecord(subject)
94
106
  }
@@ -96,20 +108,20 @@ export default function (server: Server, ctx: AppContext) {
96
108
 
97
109
  if (isLabelEvent) {
98
110
  await moderationTxn.formatAndCreateLabels(
99
- result.subjectUri ?? result.subjectDid,
100
- result.subjectCid,
111
+ result.event.subjectUri ?? result.event.subjectDid,
112
+ result.event.subjectCid,
101
113
  {
102
- create: result.createLabelVals?.length
103
- ? result.createLabelVals.split(' ')
114
+ create: result.event.createLabelVals?.length
115
+ ? result.event.createLabelVals.split(' ')
104
116
  : undefined,
105
- negate: result.negateLabelVals?.length
106
- ? result.negateLabelVals.split(' ')
117
+ negate: result.event.negateLabelVals?.length
118
+ ? result.event.negateLabelVals.split(' ')
107
119
  : undefined,
108
120
  },
109
121
  )
110
122
  }
111
123
 
112
- return result
124
+ return result.event
113
125
  })
114
126
 
115
127
  return {
@@ -20,6 +20,8 @@ export default function (server: Server, ctx: AppContext) {
20
20
  createdBefore,
21
21
  addedLabels = [],
22
22
  removedLabels = [],
23
+ addedTags = [],
24
+ removedTags = [],
23
25
  reportTypes,
24
26
  } = params
25
27
  const db = ctx.db
@@ -37,7 +39,9 @@ export default function (server: Server, ctx: AppContext) {
37
39
  createdAfter,
38
40
  createdBefore,
39
41
  addedLabels,
42
+ addedTags,
40
43
  removedLabels,
44
+ removedTags,
41
45
  reportTypes,
42
46
  })
43
47
  return {
@@ -22,6 +22,8 @@ export default function (server: Server, ctx: AppContext) {
22
22
  includeMuted = false,
23
23
  limit = 50,
24
24
  cursor,
25
+ tags = [],
26
+ excludeTags = [],
25
27
  } = params
26
28
  const db = ctx.db
27
29
  const modService = ctx.modService(db)
@@ -41,6 +43,8 @@ export default function (server: Server, ctx: AppContext) {
41
43
  sortField,
42
44
  limit,
43
45
  cursor,
46
+ tags,
47
+ excludeTags,
44
48
  })
45
49
  const subjectStatuses = results.statuses.map((status) =>
46
50
  modService.views.formatSubjectStatus(status),
@@ -4,6 +4,7 @@ import { getReasonType } from './util'
4
4
  import { subjectFromInput } from '../../mod-service/subject'
5
5
  import { REASONAPPEAL } from '../../lexicon/types/com/atproto/moderation/defs'
6
6
  import { ForbiddenError } from '@atproto/xrpc-server'
7
+ import { ModerationLangService } from '../../mod-service/lang'
7
8
 
8
9
  export default function (server: Server, ctx: AppContext) {
9
10
  server.com.atproto.moderation.createReport({
@@ -23,12 +24,22 @@ export default function (server: Server, ctx: AppContext) {
23
24
  const db = ctx.db
24
25
  const report = await db.transaction(async (dbTxn) => {
25
26
  const moderationTxn = ctx.modService(dbTxn)
26
- return moderationTxn.report({
27
- reasonType: getReasonType(reasonType),
28
- reason,
27
+ const { event: reportEvent, subjectStatus } =
28
+ await moderationTxn.report({
29
+ reasonType: getReasonType(reasonType),
30
+ reason,
31
+ subject,
32
+ reportedBy: requester || ctx.cfg.service.did,
33
+ })
34
+
35
+ const moderationLangService = new ModerationLangService(moderationTxn)
36
+ await moderationLangService.tagSubjectWithLang({
29
37
  subject,
30
- reportedBy: requester || ctx.cfg.service.did,
38
+ subjectStatus,
39
+ createdBy: ctx.cfg.service.did,
31
40
  })
41
+
42
+ return reportEvent
32
43
  })
33
44
 
34
45
  const body = ctx.modService(db).views.formatReport(report)
@@ -63,4 +63,5 @@ const eventTypes = new Set([
63
63
  'com.atproto.admin.defs#modEventReverseTakedown',
64
64
  'com.atproto.admin.defs#modEventEmail',
65
65
  'com.atproto.admin.defs#modEventResolveAppeal',
66
+ 'com.atproto.admin.defs#modEventTag',
66
67
  ])
@@ -0,0 +1,31 @@
1
+ import { Kysely } from 'kysely'
2
+
3
+ export async function up(db: Kysely<unknown>): Promise<void> {
4
+ await db.schema
5
+ .alterTable('moderation_event')
6
+ .addColumn('addedTags', 'jsonb')
7
+ .execute()
8
+ await db.schema
9
+ .alterTable('moderation_event')
10
+ .addColumn('removedTags', 'jsonb')
11
+ .execute()
12
+ await db.schema
13
+ .alterTable('moderation_subject_status')
14
+ .addColumn('tags', 'jsonb')
15
+ .execute()
16
+ }
17
+
18
+ export async function down(db: Kysely<unknown>): Promise<void> {
19
+ await db.schema
20
+ .alterTable('moderation_event')
21
+ .dropColumn('addedTags')
22
+ .execute()
23
+ await db.schema
24
+ .alterTable('moderation_event')
25
+ .dropColumn('removedTags')
26
+ .execute()
27
+ await db.schema
28
+ .alterTable('moderation_subject_status')
29
+ .dropColumn('tags')
30
+ .execute()
31
+ }
@@ -5,3 +5,4 @@
5
5
  export * as _20231219T205730722Z from './20231219T205730722Z-init'
6
6
  export * as _20240116T085607200Z from './20240116T085607200Z-communication-template'
7
7
  export * as _20240201T051104136Z from './20240201T051104136Z-mod-event-blobs'
8
+ export * as _20240208T213404429Z from './20240208T213404429Z-add-tags-column-to-moderation-subject'
@@ -15,6 +15,7 @@ export interface ModerationEvent {
15
15
  | 'com.atproto.admin.defs#modEventReverseTakedown'
16
16
  | 'com.atproto.admin.defs#modEventEmail'
17
17
  | 'com.atproto.admin.defs#modEventResolveAppeal'
18
+ | 'com.atproto.admin.defs#modEventTag'
18
19
  subjectType: 'com.atproto.admin.defs#repoRef' | 'com.atproto.repo.strongRef'
19
20
  subjectDid: string
20
21
  subjectUri: string | null
@@ -28,6 +29,8 @@ export interface ModerationEvent {
28
29
  durationInHours: number | null
29
30
  expiresAt: string | null
30
31
  meta: Record<string, string | boolean> | null
32
+ addedTags: string[] | null
33
+ removedTags: string[] | null
31
34
  legacyRefId: number | null
32
35
  }
33
36
 
@@ -25,6 +25,7 @@ export interface ModerationSubjectStatus {
25
25
  takendown: boolean
26
26
  appealed: boolean | null
27
27
  comment: string | null
28
+ tags: string[] | null
28
29
  }
29
30
 
30
31
  export type PartialDB = {
@@ -303,6 +303,12 @@ export const schemaDict = {
303
303
  type: 'string',
304
304
  format: 'datetime',
305
305
  },
306
+ tags: {
307
+ type: 'array',
308
+ items: {
309
+ type: 'string',
310
+ },
311
+ },
306
312
  },
307
313
  },
308
314
  reportViewDetail: {
@@ -897,6 +903,33 @@ export const schemaDict = {
897
903
  },
898
904
  },
899
905
  },
906
+ modEventTag: {
907
+ type: 'object',
908
+ description: 'Add/Remove a tag on a subject',
909
+ required: ['add', 'remove'],
910
+ properties: {
911
+ add: {
912
+ type: 'array',
913
+ items: {
914
+ type: 'string',
915
+ },
916
+ description:
917
+ "Tags to be added to the subject. If already exists, won't be duplicated.",
918
+ },
919
+ remove: {
920
+ type: 'array',
921
+ items: {
922
+ type: 'string',
923
+ },
924
+ description:
925
+ "Tags to be removed to the subject. Ignores a tag If it doesn't exist, won't be duplicated.",
926
+ },
927
+ comment: {
928
+ type: 'string',
929
+ description: 'Additional comment about added/removed tags.',
930
+ },
931
+ },
932
+ },
900
933
  communicationTemplateView: {
901
934
  type: 'object',
902
935
  required: [
@@ -1075,6 +1108,7 @@ export const schemaDict = {
1075
1108
  'lex:com.atproto.admin.defs#modEventReverseTakedown',
1076
1109
  'lex:com.atproto.admin.defs#modEventUnmute',
1077
1110
  'lex:com.atproto.admin.defs#modEventEmail',
1111
+ 'lex:com.atproto.admin.defs#modEventTag',
1078
1112
  ],
1079
1113
  },
1080
1114
  subject: {
@@ -1503,6 +1537,22 @@ export const schemaDict = {
1503
1537
  description:
1504
1538
  'If specified, only events where all of these labels were removed are returned',
1505
1539
  },
1540
+ addedTags: {
1541
+ type: 'array',
1542
+ items: {
1543
+ type: 'string',
1544
+ },
1545
+ description:
1546
+ 'If specified, only events where all of these tags were added are returned',
1547
+ },
1548
+ removedTags: {
1549
+ type: 'array',
1550
+ items: {
1551
+ type: 'string',
1552
+ },
1553
+ description:
1554
+ 'If specified, only events where all of these tags were removed are returned',
1555
+ },
1506
1556
  reportTypes: {
1507
1557
  type: 'array',
1508
1558
  items: {
@@ -1620,6 +1670,18 @@ export const schemaDict = {
1620
1670
  maximum: 100,
1621
1671
  default: 50,
1622
1672
  },
1673
+ tags: {
1674
+ type: 'array',
1675
+ items: {
1676
+ type: 'string',
1677
+ },
1678
+ },
1679
+ excludeTags: {
1680
+ type: 'array',
1681
+ items: {
1682
+ type: 'string',
1683
+ },
1684
+ },
1623
1685
  cursor: {
1624
1686
  type: 'string',
1625
1687
  },
@@ -156,6 +156,7 @@ export interface SubjectStatusView {
156
156
  /** True indicates that the a previously taken moderator action was appealed against, by the author of the content. False indicates last appeal was resolved by moderators. */
157
157
  appealed?: boolean
158
158
  suspendUntil?: string
159
+ tags?: string[]
159
160
  [k: string]: unknown
160
161
  }
161
162
 
@@ -720,6 +721,29 @@ export function validateModEventEmail(v: unknown): ValidationResult {
720
721
  return lexicons.validate('com.atproto.admin.defs#modEventEmail', v)
721
722
  }
722
723
 
724
+ /** Add/Remove a tag on a subject */
725
+ export interface ModEventTag {
726
+ /** Tags to be added to the subject. If already exists, won't be duplicated. */
727
+ add: string[]
728
+ /** Tags to be removed to the subject. Ignores a tag If it doesn't exist, won't be duplicated. */
729
+ remove: string[]
730
+ /** Additional comment about added/removed tags. */
731
+ comment?: string
732
+ [k: string]: unknown
733
+ }
734
+
735
+ export function isModEventTag(v: unknown): v is ModEventTag {
736
+ return (
737
+ isObj(v) &&
738
+ hasProp(v, '$type') &&
739
+ v.$type === 'com.atproto.admin.defs#modEventTag'
740
+ )
741
+ }
742
+
743
+ export function validateModEventTag(v: unknown): ValidationResult {
744
+ return lexicons.validate('com.atproto.admin.defs#modEventTag', v)
745
+ }
746
+
723
747
  export interface CommunicationTemplateView {
724
748
  id: string
725
749
  /** Name of the template. */
@@ -24,6 +24,7 @@ export interface InputSchema {
24
24
  | ComAtprotoAdminDefs.ModEventReverseTakedown
25
25
  | ComAtprotoAdminDefs.ModEventUnmute
26
26
  | ComAtprotoAdminDefs.ModEventEmail
27
+ | ComAtprotoAdminDefs.ModEventTag
27
28
  | { $type: string; [k: string]: unknown }
28
29
  subject:
29
30
  | ComAtprotoAdminDefs.RepoRef