@atproto/ozone 0.2.5 → 0.2.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +18 -0
- package/dist/api/index.d.ts.map +1 -1
- package/dist/api/index.js +2 -0
- package/dist/api/index.js.map +1 -1
- package/dist/api/report/queryActivities.d.ts +4 -0
- package/dist/api/report/queryActivities.d.ts.map +1 -0
- package/dist/api/report/queryActivities.js +36 -0
- package/dist/api/report/queryActivities.js.map +1 -0
- package/dist/background.d.ts +5 -3
- package/dist/background.d.ts.map +1 -1
- package/dist/background.js +13 -4
- package/dist/background.js.map +1 -1
- package/dist/context.js +1 -1
- package/dist/context.js.map +1 -1
- package/dist/daemon/context.js +1 -1
- package/dist/daemon/context.js.map +1 -1
- package/dist/daemon/verification-listener.d.ts +1 -1
- package/dist/daemon/verification-listener.d.ts.map +1 -1
- package/dist/daemon/verification-listener.js +10 -4
- package/dist/daemon/verification-listener.js.map +1 -1
- package/dist/db/migrations/20260602T120000000Z-add-report-activity-created-index.d.ts +4 -0
- package/dist/db/migrations/20260602T120000000Z-add-report-activity-created-index.d.ts.map +1 -0
- package/dist/db/migrations/20260602T120000000Z-add-report-activity-created-index.js +15 -0
- package/dist/db/migrations/20260602T120000000Z-add-report-activity-created-index.js.map +1 -0
- package/dist/db/migrations/index.d.ts +1 -0
- package/dist/db/migrations/index.d.ts.map +1 -1
- package/dist/db/migrations/index.js +1 -0
- package/dist/db/migrations/index.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +23 -13
- package/dist/index.js.map +1 -1
- package/dist/jetstream/service.d.ts +1 -1
- package/dist/jetstream/service.d.ts.map +1 -1
- package/dist/jetstream/service.js +3 -1
- package/dist/jetstream/service.js.map +1 -1
- package/dist/lexicon/index.d.ts +11 -0
- package/dist/lexicon/index.d.ts.map +1 -1
- package/dist/lexicon/index.js +18 -0
- package/dist/lexicon/index.js.map +1 -1
- package/dist/lexicon/lexicons.d.ts +338 -0
- package/dist/lexicon/lexicons.d.ts.map +1 -1
- package/dist/lexicon/lexicons.js +173 -0
- package/dist/lexicon/lexicons.js.map +1 -1
- package/dist/lexicon/types/app/bsky/notification/defs.d.ts +1 -0
- package/dist/lexicon/types/app/bsky/notification/defs.d.ts.map +1 -1
- package/dist/lexicon/types/app/bsky/notification/defs.js.map +1 -1
- package/dist/lexicon/types/chat/bsky/notification/defs.d.ts +19 -0
- package/dist/lexicon/types/chat/bsky/notification/defs.d.ts.map +1 -0
- package/dist/lexicon/types/chat/bsky/notification/defs.js +19 -0
- package/dist/lexicon/types/chat/bsky/notification/defs.js.map +1 -0
- package/dist/lexicon/types/chat/bsky/notification/getPreferences.d.ts +20 -0
- package/dist/lexicon/types/chat/bsky/notification/getPreferences.d.ts.map +1 -0
- package/dist/lexicon/types/chat/bsky/notification/getPreferences.js +5 -0
- package/dist/lexicon/types/chat/bsky/notification/getPreferences.js.map +1 -0
- package/dist/lexicon/types/chat/bsky/notification/putPreferences.d.ts +26 -0
- package/dist/lexicon/types/chat/bsky/notification/putPreferences.d.ts.map +1 -0
- package/dist/lexicon/types/chat/bsky/notification/putPreferences.js +5 -0
- package/dist/lexicon/types/chat/bsky/notification/putPreferences.js.map +1 -0
- package/dist/lexicon/types/tools/ozone/report/defs.d.ts +1 -0
- package/dist/lexicon/types/tools/ozone/report/defs.d.ts.map +1 -1
- package/dist/lexicon/types/tools/ozone/report/defs.js.map +1 -1
- package/dist/lexicon/types/tools/ozone/report/queryActivities.d.ts +32 -0
- package/dist/lexicon/types/tools/ozone/report/queryActivities.d.ts.map +1 -0
- package/dist/lexicon/types/tools/ozone/report/queryActivities.js +5 -0
- package/dist/lexicon/types/tools/ozone/report/queryActivities.js.map +1 -0
- package/dist/mod-service/report.d.ts +1 -0
- package/dist/mod-service/report.d.ts.map +1 -1
- package/dist/mod-service/report.js +16 -0
- package/dist/mod-service/report.js.map +1 -1
- package/dist/report/activity.d.ts +19 -1
- package/dist/report/activity.d.ts.map +1 -1
- package/dist/report/activity.js +27 -1
- package/dist/report/activity.js.map +1 -1
- package/package.json +9 -8
- package/src/api/index.ts +2 -0
- package/src/api/report/queryActivities.ts +64 -0
- package/src/background.ts +19 -4
- package/src/context.ts +1 -1
- package/src/daemon/context.ts +1 -1
- package/src/daemon/verification-listener.ts +9 -4
- package/src/db/migrations/20260602T120000000Z-add-report-activity-created-index.ts +17 -0
- package/src/db/migrations/index.ts +1 -0
- package/src/index.ts +25 -15
- package/src/jetstream/service.ts +3 -1
- package/src/mod-service/report.ts +19 -0
- package/src/report/activity.ts +47 -0
- package/tests/3p-labeler.test.ts +2 -2
- package/tests/_util.ts +8 -25
- package/tests/account-strikes.test.ts +1 -1
- package/tests/ack-all-subjects-of-account.test.ts +1 -1
- package/tests/age-assurance.test.ts +1 -1
- package/tests/blob-divert.test.ts +1 -1
- package/tests/communication-templates.test.ts +1 -1
- package/tests/content-tagger.test.ts +1 -1
- package/tests/db.test.ts +1 -1
- package/tests/expiring-label.test.ts +1 -1
- package/tests/expiring-tags.test.ts +1 -1
- package/tests/get-account-timeline.test.ts +1 -1
- package/tests/get-config.test.ts +1 -1
- package/tests/get-lists.test.ts +2 -1
- package/tests/get-profiles.test.ts +1 -1
- package/tests/get-record.test.ts +1 -1
- package/tests/get-records.test.ts +1 -1
- package/tests/get-repo.test.ts +1 -1
- package/tests/get-report.test.ts +1 -1
- package/tests/get-reporter-stats.test.ts +1 -1
- package/tests/get-repos.test.ts +1 -1
- package/tests/get-starter-pack.test.ts +1 -1
- package/tests/get-subjects.test.ts +1 -1
- package/tests/mod-tool.test.ts +1 -1
- package/tests/moderation-appeals.test.ts +1 -1
- package/tests/moderation-events.test.ts +1 -1
- package/tests/moderation-status-tags.test.ts +1 -1
- package/tests/moderation-statuses.test.ts +1 -1
- package/tests/moderation.test.ts +1 -1
- package/tests/protected-tags.test.ts +1 -1
- package/tests/query-labels.test.ts +1 -1
- package/tests/query-reports.test.ts +1 -1
- package/tests/queue-assignment.test.ts +1 -1
- package/tests/queue-router.test.ts +1 -1
- package/tests/queues.test.ts +1 -1
- package/tests/record-and-account-events.test.ts +1 -1
- package/tests/repo-search.test.ts +2 -2
- package/tests/report-action.test.ts +1 -1
- package/tests/report-activity.test.ts +145 -1
- package/tests/report-assignment.test.ts +1 -1
- package/tests/report-muting.test.ts +1 -1
- package/tests/report-reason.test.ts +1 -1
- package/tests/report-reassign-queue.test.ts +1 -1
- package/tests/report-routing.test.ts +1 -1
- package/tests/report-stats.test.ts +1 -1
- package/tests/revoke-account-credentials.test.ts +1 -1
- package/tests/safelink.test.ts +1 -1
- package/tests/scheduled-action-processor.test.ts +1 -1
- package/tests/scheduled-action.test.ts +1 -1
- package/tests/sequencer.test.ts +1 -1
- package/tests/server.test.ts +9 -12
- package/tests/sets.test.ts +1 -1
- package/tests/settings.test.ts +1 -1
- package/tests/strike-expiry-processor.test.ts +1 -1
- package/tests/subject-priority-score.test.ts +1 -1
- package/tests/takedown.test.ts +1 -1
- package/tests/team.test.ts +1 -1
- package/tests/verification-listener.test.ts +40 -13
- package/tests/verification.test.ts +1 -1
- package/tsconfig.build.tsbuildinfo +1 -1
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { Database } from '../db/index.js';
|
|
2
|
+
import { ReportView } from '../lexicon/types/tools/ozone/report/defs.js';
|
|
3
|
+
import { QueryParams as QueryActivitiesParams } from '../lexicon/types/tools/ozone/report/queryActivities.js';
|
|
2
4
|
import { Member } from '../lexicon/types/tools/ozone/team/defs.js';
|
|
3
5
|
export type ActivityType = 'queueActivity' | 'assignmentActivity' | 'escalationActivity' | 'closeActivity' | 'reopenActivity' | 'noteActivity';
|
|
4
6
|
export type CreateActivityParams = {
|
|
@@ -59,6 +61,21 @@ export declare function listReportActivities(db: Database, params: ListActivitie
|
|
|
59
61
|
}[];
|
|
60
62
|
cursor: string | undefined;
|
|
61
63
|
}>;
|
|
64
|
+
export declare function queryReportActivities(db: Database, params: QueryActivitiesParams): Promise<{
|
|
65
|
+
activities: {
|
|
66
|
+
activityType: string;
|
|
67
|
+
createdAt: string;
|
|
68
|
+
createdBy: string;
|
|
69
|
+
id: number;
|
|
70
|
+
internalNote: string | null;
|
|
71
|
+
isAutomated: boolean;
|
|
72
|
+
meta: unknown;
|
|
73
|
+
previousStatus: string | null;
|
|
74
|
+
publicNote: string | null;
|
|
75
|
+
reportId: number;
|
|
76
|
+
}[];
|
|
77
|
+
cursor: string | undefined;
|
|
78
|
+
}>;
|
|
62
79
|
export declare function formatActivityView(activity: {
|
|
63
80
|
id: number;
|
|
64
81
|
reportId: number;
|
|
@@ -70,7 +87,7 @@ export declare function formatActivityView(activity: {
|
|
|
70
87
|
isAutomated: boolean;
|
|
71
88
|
createdBy: string;
|
|
72
89
|
createdAt: string;
|
|
73
|
-
}, memberViews?: Map<string, Member>): {
|
|
90
|
+
}, memberViews?: Map<string, Member>, reportViews?: Map<number, ReportView>): {
|
|
74
91
|
id: number;
|
|
75
92
|
reportId: number;
|
|
76
93
|
activity: {
|
|
@@ -83,6 +100,7 @@ export declare function formatActivityView(activity: {
|
|
|
83
100
|
isAutomated: boolean;
|
|
84
101
|
createdBy: string;
|
|
85
102
|
moderator: Member | undefined;
|
|
103
|
+
report: ReportView | undefined;
|
|
86
104
|
createdAt: string;
|
|
87
105
|
};
|
|
88
106
|
//# sourceMappingURL=activity.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"activity.d.ts","sourceRoot":"","sources":["../../src/report/activity.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA;
|
|
1
|
+
{"version":3,"file":"activity.d.ts","sourceRoot":"","sources":["../../src/report/activity.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AAEzC,OAAO,EAAE,UAAU,EAAE,MAAM,6CAA6C,CAAA;AACxE,OAAO,EAAE,WAAW,IAAI,qBAAqB,EAAE,MAAM,wDAAwD,CAAA;AAC7G,OAAO,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAA;AAOlE,MAAM,MAAM,YAAY,GACpB,eAAe,GACf,oBAAoB,GACpB,oBAAoB,GACpB,eAAe,GACf,gBAAgB,GAChB,cAAc,CAAA;AAElB,MAAM,MAAM,oBAAoB,GAAG;IACjC,QAAQ,EAAE,MAAM,CAAA;IAChB,YAAY,EAAE,YAAY,CAAA;IAC1B,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IAC9B,kFAAkF;IAClF,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB,SAAS,EAAE,MAAM,CAAA;CAClB,CAAA;AAED,wBAAsB,oBAAoB,CACxC,EAAE,EAAE,QAAQ,EACZ,MAAM,EAAE,oBAAoB;;;;;;;;;;;GAkF7B;AAED,MAAM,MAAM,kBAAkB,GAAG;IAC/B,QAAQ,EAAE,MAAM,CAAA;IAChB,YAAY,EAAE,MAAM,CAAA;IACpB,cAAc,EAAE,MAAM,GAAG,IAAI,CAAA;IAC7B,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,IAAI,CAAC,EAAE,OAAO,CAAA;IACd,WAAW,EAAE,OAAO,CAAA;IACpB,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;CAClB,CAAA;AAED;;;GAGG;AACH,wBAAsB,0BAA0B,CAC9C,EAAE,EAAE,QAAQ,EACZ,UAAU,EAAE,kBAAkB,EAAE,iBAmBjC;AAED,MAAM,MAAM,oBAAoB,GAAG;IACjC,QAAQ,EAAE,MAAM,CAAA;IAChB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB,CAAA;AAED,wBAAsB,oBAAoB,CACxC,EAAE,EAAE,QAAQ,EACZ,MAAM,EAAE,oBAAoB;;;;;;;;;;;;;;GA6B7B;AAED,wBAAsB,qBAAqB,CACzC,EAAE,EAAE,QAAQ,EACZ,MAAM,EAAE,qBAAqB;;;;;;;;;;;;;;GAsC9B;AAaD,wBAAgB,kBAAkB,CAChC,QAAQ,EAAE;IACR,EAAE,EAAE,MAAM,CAAA;IACV,QAAQ,EAAE,MAAM,CAAA;IAChB,YAAY,EAAE,MAAM,CAAA;IACpB,cAAc,EAAE,MAAM,GAAG,IAAI,CAAA;IAC7B,YAAY,EAAE,MAAM,GAAG,IAAI,CAAA;IAC3B,UAAU,EAAE,MAAM,GAAG,IAAI,CAAA;IACzB,IAAI,EAAE,OAAO,CAAA;IACb,WAAW,EAAE,OAAO,CAAA;IACpB,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;CAClB,EACD,WAAW,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,EACjC,WAAW,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC;IAGnC,EAAE;IACF,QAAQ;IACR,QAAQ;;eA3BA,MAAM;;IA+Bd,YAAY;IACZ,UAAU;IACV,IAAI;IACJ,WAAW;IACX,SAAS;IACT,SAAS;IACT,MAAM;IACN,SAAS;EAEZ"}
|
package/dist/report/activity.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { InvalidRequestError } from '@atproto/xrpc-server';
|
|
2
|
+
import { TimeIdKeyset, paginate } from '../db/pagination.js';
|
|
2
3
|
import { AlreadyInTargetState, InvalidStateTransition, handleReportUpdate, } from './handle-report-update.js';
|
|
3
4
|
export async function createReportActivity(db, params) {
|
|
4
5
|
const { reportId, activityType, internalNote, publicNote, meta, isAutomated = false, createdBy, } = params;
|
|
@@ -111,6 +112,30 @@ export async function listReportActivities(db, params) {
|
|
|
111
112
|
: undefined;
|
|
112
113
|
return { activities, cursor: nextCursor };
|
|
113
114
|
}
|
|
115
|
+
export async function queryReportActivities(db, params) {
|
|
116
|
+
const { activityTypes, createdAfter, createdBefore, sortDirection, limit, cursor, } = params;
|
|
117
|
+
const { ref } = db.db.dynamic;
|
|
118
|
+
let builder = db.db.selectFrom('report_activity').selectAll();
|
|
119
|
+
if (activityTypes && activityTypes.length > 0) {
|
|
120
|
+
builder = builder.where('activityType', 'in', activityTypes);
|
|
121
|
+
}
|
|
122
|
+
if (createdAfter) {
|
|
123
|
+
builder = builder.where('createdAt', '>=', createdAfter);
|
|
124
|
+
}
|
|
125
|
+
if (createdBefore) {
|
|
126
|
+
builder = builder.where('createdAt', '<=', createdBefore);
|
|
127
|
+
}
|
|
128
|
+
const keyset = new TimeIdKeyset(ref('report_activity.createdAt'), ref('report_activity.id'));
|
|
129
|
+
const paginatedBuilder = paginate(builder, {
|
|
130
|
+
limit,
|
|
131
|
+
cursor,
|
|
132
|
+
keyset,
|
|
133
|
+
direction: sortDirection,
|
|
134
|
+
tryIndex: true,
|
|
135
|
+
});
|
|
136
|
+
const activities = await paginatedBuilder.execute();
|
|
137
|
+
return { activities, cursor: keyset.packFromResult(activities) };
|
|
138
|
+
}
|
|
114
139
|
function buildActivityObject(activityType, previousStatus) {
|
|
115
140
|
const $type = `tools.ozone.report.defs#${activityType}`;
|
|
116
141
|
if (previousStatus !== null) {
|
|
@@ -118,7 +143,7 @@ function buildActivityObject(activityType, previousStatus) {
|
|
|
118
143
|
}
|
|
119
144
|
return { $type };
|
|
120
145
|
}
|
|
121
|
-
export function formatActivityView(activity, memberViews) {
|
|
146
|
+
export function formatActivityView(activity, memberViews, reportViews) {
|
|
122
147
|
return {
|
|
123
148
|
id: activity.id,
|
|
124
149
|
reportId: activity.reportId,
|
|
@@ -129,6 +154,7 @@ export function formatActivityView(activity, memberViews) {
|
|
|
129
154
|
isAutomated: activity.isAutomated,
|
|
130
155
|
createdBy: activity.createdBy,
|
|
131
156
|
moderator: memberViews?.get(activity.createdBy),
|
|
157
|
+
report: reportViews?.get(activity.reportId),
|
|
132
158
|
createdAt: activity.createdAt,
|
|
133
159
|
};
|
|
134
160
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"activity.js","sourceRoot":"","sources":["../../src/report/activity.ts"],"names":[],"mappings":"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","sourcesContent":["import { InvalidRequestError } from '@atproto/xrpc-server'\nimport { Database } from '../db/index.js'\nimport { Member } from '../lexicon/types/tools/ozone/team/defs.js'\nimport {\n AlreadyInTargetState,\n InvalidStateTransition,\n handleReportUpdate,\n} from './handle-report-update.js'\n\nexport type ActivityType =\n | 'queueActivity'\n | 'assignmentActivity'\n | 'escalationActivity'\n | 'closeActivity'\n | 'reopenActivity'\n | 'noteActivity'\n\nexport type CreateActivityParams = {\n reportId: number\n activityType: ActivityType\n internalNote?: string\n publicNote?: string\n meta?: Record<string, unknown>\n /** Set true for activities created by automated processes (e.g. queue router). */\n isAutomated?: boolean\n createdBy: string\n}\n\nexport async function createReportActivity(\n db: Database,\n params: CreateActivityParams,\n) {\n const {\n reportId,\n activityType,\n internalNote,\n publicNote,\n meta,\n isAutomated = false,\n createdBy,\n } = params\n\n return db.transaction(async (dbTxn) => {\n // Lock the report row for the duration of the transaction to prevent\n // concurrent writes from racing on status validation + update.\n const report = await dbTxn.db\n .selectFrom('report')\n .select(['id', 'status'])\n .where('id', '=', reportId)\n .forUpdate()\n .executeTakeFirst()\n\n if (!report) {\n throw new InvalidRequestError(\n `Report ${reportId} not found`,\n 'ReportNotFound',\n )\n }\n\n let result\n try {\n result = handleReportUpdate(report.status, {\n type: 'activity',\n activityType,\n })\n } catch (err) {\n if (err instanceof AlreadyInTargetState) {\n throw new InvalidRequestError(err.message, 'AlreadyInTargetState')\n }\n if (err instanceof InvalidStateTransition) {\n throw new InvalidRequestError(err.message, 'InvalidStateTransition')\n }\n throw err\n }\n\n const now = new Date().toISOString()\n\n if (result.nextStatus !== null) {\n const updateSet: Record<string, string | null> = {\n status: result.nextStatus,\n updatedAt: now,\n }\n if (result.nextStatus === 'closed') {\n updateSet.closedAt = now\n } else if (result.nextStatus === 'open') {\n updateSet.closedAt = null\n }\n await dbTxn.db\n .updateTable('report')\n .set(updateSet)\n .where('id', '=', reportId)\n .execute()\n }\n\n const [activity] = await dbTxn.db\n .insertInto('report_activity')\n .values({\n reportId,\n activityType,\n previousStatus: result.activity?.previousStatus ?? null,\n internalNote: internalNote ?? null,\n publicNote: publicNote ?? null,\n meta: meta ?? null,\n isAutomated,\n createdBy,\n createdAt: now,\n })\n .returningAll()\n .execute()\n\n return activity\n })\n}\n\nexport type BulkActivityInsert = {\n reportId: number\n activityType: string\n previousStatus: string | null\n internalNote?: string\n publicNote?: string\n meta?: unknown\n isAutomated: boolean\n createdBy: string\n createdAt: string\n}\n\n/**\n * Insert multiple activity rows in a single query. No validation — caller is\n * responsible for correctness and for being inside an appropriate transaction.\n */\nexport async function bulkInsertReportActivities(\n db: Database,\n activities: BulkActivityInsert[],\n) {\n if (!activities.length) return\n await db.db\n .insertInto('report_activity')\n .values(\n activities.map((a) => ({\n reportId: a.reportId,\n activityType: a.activityType,\n previousStatus: a.previousStatus,\n internalNote: a.internalNote ?? null,\n publicNote: a.publicNote ?? null,\n meta: a.meta ?? null,\n isAutomated: a.isAutomated,\n createdBy: a.createdBy,\n createdAt: a.createdAt,\n })),\n )\n .execute()\n}\n\nexport type ListActivitiesParams = {\n reportId: number\n limit?: number\n cursor?: string\n}\n\nexport async function listReportActivities(\n db: Database,\n params: ListActivitiesParams,\n) {\n const { reportId, limit = 50, cursor } = params\n\n let builder = db.db\n .selectFrom('report_activity')\n .selectAll()\n .where('reportId', '=', reportId)\n .orderBy('createdAt', 'desc')\n .orderBy('id', 'desc')\n .limit(limit + 1)\n\n if (cursor) {\n const cursorId = parseInt(cursor, 10)\n if (!isNaN(cursorId)) {\n builder = builder.where('id', '<', cursorId)\n }\n }\n\n const rows = await builder.execute()\n const hasMore = rows.length > limit\n const activities = hasMore ? rows.slice(0, limit) : rows\n\n const nextCursor =\n hasMore && activities.length > 0\n ? String(activities[activities.length - 1].id)\n : undefined\n\n return { activities, cursor: nextCursor }\n}\n\nfunction buildActivityObject(\n activityType: string,\n previousStatus: string | null,\n): { $type: string; [k: string]: unknown } {\n const $type = `tools.ozone.report.defs#${activityType}`\n if (previousStatus !== null) {\n return { $type, previousStatus }\n }\n return { $type }\n}\n\nexport function formatActivityView(\n activity: {\n id: number\n reportId: number\n activityType: string\n previousStatus: string | null\n internalNote: string | null\n publicNote: string | null\n meta: unknown\n isAutomated: boolean\n createdBy: string\n createdAt: string\n },\n memberViews?: Map<string, Member>,\n) {\n return {\n id: activity.id,\n reportId: activity.reportId,\n activity: buildActivityObject(\n activity.activityType,\n activity.previousStatus,\n ),\n internalNote: activity.internalNote ?? undefined,\n publicNote: activity.publicNote ?? undefined,\n meta: (activity.meta as Record<string, unknown>) ?? undefined,\n isAutomated: activity.isAutomated,\n createdBy: activity.createdBy,\n moderator: memberViews?.get(activity.createdBy),\n createdAt: activity.createdAt,\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"activity.js","sourceRoot":"","sources":["../../src/report/activity.ts"],"names":[],"mappings":"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","sourcesContent":["import { InvalidRequestError } from '@atproto/xrpc-server'\nimport { Database } from '../db/index.js'\nimport { TimeIdKeyset, paginate } from '../db/pagination.js'\nimport { ReportView } from '../lexicon/types/tools/ozone/report/defs.js'\nimport { QueryParams as QueryActivitiesParams } from '../lexicon/types/tools/ozone/report/queryActivities.js'\nimport { Member } from '../lexicon/types/tools/ozone/team/defs.js'\nimport {\n AlreadyInTargetState,\n InvalidStateTransition,\n handleReportUpdate,\n} from './handle-report-update.js'\n\nexport type ActivityType =\n | 'queueActivity'\n | 'assignmentActivity'\n | 'escalationActivity'\n | 'closeActivity'\n | 'reopenActivity'\n | 'noteActivity'\n\nexport type CreateActivityParams = {\n reportId: number\n activityType: ActivityType\n internalNote?: string\n publicNote?: string\n meta?: Record<string, unknown>\n /** Set true for activities created by automated processes (e.g. queue router). */\n isAutomated?: boolean\n createdBy: string\n}\n\nexport async function createReportActivity(\n db: Database,\n params: CreateActivityParams,\n) {\n const {\n reportId,\n activityType,\n internalNote,\n publicNote,\n meta,\n isAutomated = false,\n createdBy,\n } = params\n\n return db.transaction(async (dbTxn) => {\n // Lock the report row for the duration of the transaction to prevent\n // concurrent writes from racing on status validation + update.\n const report = await dbTxn.db\n .selectFrom('report')\n .select(['id', 'status'])\n .where('id', '=', reportId)\n .forUpdate()\n .executeTakeFirst()\n\n if (!report) {\n throw new InvalidRequestError(\n `Report ${reportId} not found`,\n 'ReportNotFound',\n )\n }\n\n let result\n try {\n result = handleReportUpdate(report.status, {\n type: 'activity',\n activityType,\n })\n } catch (err) {\n if (err instanceof AlreadyInTargetState) {\n throw new InvalidRequestError(err.message, 'AlreadyInTargetState')\n }\n if (err instanceof InvalidStateTransition) {\n throw new InvalidRequestError(err.message, 'InvalidStateTransition')\n }\n throw err\n }\n\n const now = new Date().toISOString()\n\n if (result.nextStatus !== null) {\n const updateSet: Record<string, string | null> = {\n status: result.nextStatus,\n updatedAt: now,\n }\n if (result.nextStatus === 'closed') {\n updateSet.closedAt = now\n } else if (result.nextStatus === 'open') {\n updateSet.closedAt = null\n }\n await dbTxn.db\n .updateTable('report')\n .set(updateSet)\n .where('id', '=', reportId)\n .execute()\n }\n\n const [activity] = await dbTxn.db\n .insertInto('report_activity')\n .values({\n reportId,\n activityType,\n previousStatus: result.activity?.previousStatus ?? null,\n internalNote: internalNote ?? null,\n publicNote: publicNote ?? null,\n meta: meta ?? null,\n isAutomated,\n createdBy,\n createdAt: now,\n })\n .returningAll()\n .execute()\n\n return activity\n })\n}\n\nexport type BulkActivityInsert = {\n reportId: number\n activityType: string\n previousStatus: string | null\n internalNote?: string\n publicNote?: string\n meta?: unknown\n isAutomated: boolean\n createdBy: string\n createdAt: string\n}\n\n/**\n * Insert multiple activity rows in a single query. No validation — caller is\n * responsible for correctness and for being inside an appropriate transaction.\n */\nexport async function bulkInsertReportActivities(\n db: Database,\n activities: BulkActivityInsert[],\n) {\n if (!activities.length) return\n await db.db\n .insertInto('report_activity')\n .values(\n activities.map((a) => ({\n reportId: a.reportId,\n activityType: a.activityType,\n previousStatus: a.previousStatus,\n internalNote: a.internalNote ?? null,\n publicNote: a.publicNote ?? null,\n meta: a.meta ?? null,\n isAutomated: a.isAutomated,\n createdBy: a.createdBy,\n createdAt: a.createdAt,\n })),\n )\n .execute()\n}\n\nexport type ListActivitiesParams = {\n reportId: number\n limit?: number\n cursor?: string\n}\n\nexport async function listReportActivities(\n db: Database,\n params: ListActivitiesParams,\n) {\n const { reportId, limit = 50, cursor } = params\n\n let builder = db.db\n .selectFrom('report_activity')\n .selectAll()\n .where('reportId', '=', reportId)\n .orderBy('createdAt', 'desc')\n .orderBy('id', 'desc')\n .limit(limit + 1)\n\n if (cursor) {\n const cursorId = parseInt(cursor, 10)\n if (!isNaN(cursorId)) {\n builder = builder.where('id', '<', cursorId)\n }\n }\n\n const rows = await builder.execute()\n const hasMore = rows.length > limit\n const activities = hasMore ? rows.slice(0, limit) : rows\n\n const nextCursor =\n hasMore && activities.length > 0\n ? String(activities[activities.length - 1].id)\n : undefined\n\n return { activities, cursor: nextCursor }\n}\n\nexport async function queryReportActivities(\n db: Database,\n params: QueryActivitiesParams,\n) {\n const {\n activityTypes,\n createdAfter,\n createdBefore,\n sortDirection,\n limit,\n cursor,\n } = params\n const { ref } = db.db.dynamic\n\n let builder = db.db.selectFrom('report_activity').selectAll()\n\n if (activityTypes && activityTypes.length > 0) {\n builder = builder.where('activityType', 'in', activityTypes)\n }\n if (createdAfter) {\n builder = builder.where('createdAt', '>=', createdAfter)\n }\n if (createdBefore) {\n builder = builder.where('createdAt', '<=', createdBefore)\n }\n\n const keyset = new TimeIdKeyset(\n ref('report_activity.createdAt'),\n ref('report_activity.id'),\n )\n const paginatedBuilder = paginate(builder, {\n limit,\n cursor,\n keyset,\n direction: sortDirection,\n tryIndex: true,\n })\n\n const activities = await paginatedBuilder.execute()\n return { activities, cursor: keyset.packFromResult(activities) }\n}\n\nfunction buildActivityObject(\n activityType: string,\n previousStatus: string | null,\n): { $type: string; [k: string]: unknown } {\n const $type = `tools.ozone.report.defs#${activityType}`\n if (previousStatus !== null) {\n return { $type, previousStatus }\n }\n return { $type }\n}\n\nexport function formatActivityView(\n activity: {\n id: number\n reportId: number\n activityType: string\n previousStatus: string | null\n internalNote: string | null\n publicNote: string | null\n meta: unknown\n isAutomated: boolean\n createdBy: string\n createdAt: string\n },\n memberViews?: Map<string, Member>,\n reportViews?: Map<number, ReportView>,\n) {\n return {\n id: activity.id,\n reportId: activity.reportId,\n activity: buildActivityObject(\n activity.activityType,\n activity.previousStatus,\n ),\n internalNote: activity.internalNote ?? undefined,\n publicNote: activity.publicNote ?? undefined,\n meta: (activity.meta as Record<string, unknown>) ?? undefined,\n isAutomated: activity.isAutomated,\n createdBy: activity.createdBy,\n moderator: memberViews?.get(activity.createdBy),\n report: reportViews?.get(activity.reportId),\n createdAt: activity.createdAt,\n }\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@atproto/ozone",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.6",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "Backend service for moderating the Bluesky network.",
|
|
6
6
|
"keywords": [
|
|
@@ -31,16 +31,16 @@
|
|
|
31
31
|
"structured-headers": "^1.0.1",
|
|
32
32
|
"typed-emitter": "^2.1.0",
|
|
33
33
|
"uint8arrays": "^5.0.0",
|
|
34
|
-
"undici": "^
|
|
34
|
+
"undici": "^8.5.0",
|
|
35
35
|
"ws": "^8.12.0",
|
|
36
|
-
"@atproto/api": "^0.20.
|
|
37
|
-
"@atproto/common": "^0.6.3",
|
|
38
|
-
"@atproto/identity": "^0.5.1",
|
|
36
|
+
"@atproto/api": "^0.20.17",
|
|
39
37
|
"@atproto/crypto": "^0.5.1",
|
|
40
|
-
"@atproto/
|
|
38
|
+
"@atproto/identity": "^0.5.1",
|
|
41
39
|
"@atproto/syntax": "^0.6.2",
|
|
42
|
-
"@atproto/ws-client": "^0.1.
|
|
40
|
+
"@atproto/ws-client": "^0.1.2",
|
|
43
41
|
"@atproto/xrpc": "^0.8.1",
|
|
42
|
+
"@atproto/common": "^0.6.3",
|
|
43
|
+
"@atproto/lexicon": "^0.7.2",
|
|
44
44
|
"@atproto/xrpc-server": "^0.11.2"
|
|
45
45
|
},
|
|
46
46
|
"devDependencies": {
|
|
@@ -51,10 +51,11 @@
|
|
|
51
51
|
"@types/express-serve-static-core": "^4.17.36",
|
|
52
52
|
"@types/pg": "^8.15.5",
|
|
53
53
|
"@types/qs": "^6.9.7",
|
|
54
|
+
"@types/ws": "^8.18.1",
|
|
54
55
|
"jest": "^30.0.0",
|
|
55
56
|
"ts-node": "^10.8.2",
|
|
56
57
|
"@atproto/lex-cli": "^0.10.1",
|
|
57
|
-
"@atproto/pds": "^0.5.
|
|
58
|
+
"@atproto/pds": "^0.5.7"
|
|
58
59
|
},
|
|
59
60
|
"type": "module",
|
|
60
61
|
"exports": {
|
package/src/api/index.ts
CHANGED
|
@@ -41,6 +41,7 @@ import getLatestReport from './report/getLatestReport.js'
|
|
|
41
41
|
import getLiveStats from './report/getLiveStats.js'
|
|
42
42
|
import getReport from './report/getReport.js'
|
|
43
43
|
import listActivities from './report/listActivities.js'
|
|
44
|
+
import queryActivities from './report/queryActivities.js'
|
|
44
45
|
import queryReports from './report/queryReports.js'
|
|
45
46
|
import reassignQueue from './report/reassignQueue.js'
|
|
46
47
|
import refreshStats from './report/refreshStats.js'
|
|
@@ -139,6 +140,7 @@ export default function (server: Server, ctx: AppContext) {
|
|
|
139
140
|
getReportAssignments(server, ctx)
|
|
140
141
|
createActivity(server, ctx)
|
|
141
142
|
listActivities(server, ctx)
|
|
143
|
+
queryActivities(server, ctx)
|
|
142
144
|
reassignQueue(server, ctx)
|
|
143
145
|
return server
|
|
144
146
|
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { AppContext } from '../../context.js'
|
|
2
|
+
import { Server } from '../../lexicon/index.js'
|
|
3
|
+
import { ReportView } from '../../lexicon/types/tools/ozone/report/defs.js'
|
|
4
|
+
import { getReportsByIds } from '../../mod-service/report.js'
|
|
5
|
+
import {
|
|
6
|
+
formatActivityView,
|
|
7
|
+
queryReportActivities,
|
|
8
|
+
} from '../../report/activity.js'
|
|
9
|
+
import { buildReportView, hydrateReportInfo } from '../../report/views.js'
|
|
10
|
+
import { getPdsAccountInfos } from '../util.js'
|
|
11
|
+
|
|
12
|
+
export default function (server: Server, ctx: AppContext) {
|
|
13
|
+
server.tools.ozone.report.queryActivities({
|
|
14
|
+
auth: ctx.authVerifier.modOrAdminToken,
|
|
15
|
+
handler: async ({ params, auth, req }) => {
|
|
16
|
+
const db = ctx.db
|
|
17
|
+
const modService = ctx.modService(db)
|
|
18
|
+
const labelers = ctx.reqLabelers(req)
|
|
19
|
+
|
|
20
|
+
const { activities, cursor: nextCursor } = await queryReportActivities(
|
|
21
|
+
db,
|
|
22
|
+
params,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
// Dedupe report IDs across the page. Many activities can share the
|
|
26
|
+
// same report so we want one bulk fetch + hydrate rather than N.
|
|
27
|
+
const reportIds = Array.from(new Set(activities.map((a) => a.reportId)))
|
|
28
|
+
|
|
29
|
+
const queueService = ctx.queueService(db)
|
|
30
|
+
const teamService = ctx.teamService(db)
|
|
31
|
+
const reports = await getReportsByIds(db, reportIds)
|
|
32
|
+
const hydrated = await hydrateReportInfo(
|
|
33
|
+
reports,
|
|
34
|
+
modService.views,
|
|
35
|
+
(dids) => getPdsAccountInfos(ctx, dids),
|
|
36
|
+
(queueIds) => queueService.getViewsByIds(queueIds),
|
|
37
|
+
(dids) => teamService.viewByDids(dids),
|
|
38
|
+
labelers,
|
|
39
|
+
)
|
|
40
|
+
const reportViews = new Map<number, ReportView>()
|
|
41
|
+
for (const report of reports) {
|
|
42
|
+
reportViews.set(
|
|
43
|
+
report.id,
|
|
44
|
+
buildReportView(report, hydrated, auth.credentials.isModerator),
|
|
45
|
+
)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const createdByDids = Array.from(
|
|
49
|
+
new Set(activities.map((a) => a.createdBy)),
|
|
50
|
+
)
|
|
51
|
+
const memberViews = await teamService.viewByDids(createdByDids)
|
|
52
|
+
|
|
53
|
+
return {
|
|
54
|
+
encoding: 'application/json',
|
|
55
|
+
body: {
|
|
56
|
+
activities: activities.map((activity) =>
|
|
57
|
+
formatActivityView(activity, memberViews, reportViews),
|
|
58
|
+
),
|
|
59
|
+
cursor: nextCursor,
|
|
60
|
+
},
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
})
|
|
64
|
+
}
|
package/src/background.ts
CHANGED
|
@@ -9,9 +9,19 @@ import {
|
|
|
9
9
|
|
|
10
10
|
type Task = (db: Database, signal: AbortSignal) => Promise<void>
|
|
11
11
|
|
|
12
|
+
export type BackgroundQueueOptions = NonNullable<
|
|
13
|
+
ConstructorParameters<typeof PQueue>[0]
|
|
14
|
+
> & {
|
|
15
|
+
concurrency: number
|
|
16
|
+
}
|
|
17
|
+
|
|
12
18
|
/**
|
|
13
19
|
* A simple queue for in-process, out-of-band/backgrounded work
|
|
14
20
|
*/
|
|
21
|
+
// @NOTE Keep this in sync with the BackgroundQueue in
|
|
22
|
+
// - packages/bsky/src/data-plane/server/background.ts
|
|
23
|
+
// - packages/ozone/src/background.ts
|
|
24
|
+
// - packages/pds/src/background.ts
|
|
15
25
|
export class BackgroundQueue {
|
|
16
26
|
private abortController = new AbortController()
|
|
17
27
|
private queue: PQueue
|
|
@@ -26,9 +36,9 @@ export class BackgroundQueue {
|
|
|
26
36
|
|
|
27
37
|
constructor(
|
|
28
38
|
protected db: Database,
|
|
29
|
-
|
|
39
|
+
options: BackgroundQueueOptions,
|
|
30
40
|
) {
|
|
31
|
-
this.queue = new PQueue(
|
|
41
|
+
this.queue = new PQueue(options)
|
|
32
42
|
}
|
|
33
43
|
|
|
34
44
|
getStats() {
|
|
@@ -76,7 +86,8 @@ export class BackgroundQueue {
|
|
|
76
86
|
}
|
|
77
87
|
|
|
78
88
|
async processAll() {
|
|
79
|
-
|
|
89
|
+
const { queue } = this
|
|
90
|
+
while (queue.size || queue.pending) await queue.onIdle()
|
|
80
91
|
}
|
|
81
92
|
|
|
82
93
|
/**
|
|
@@ -86,8 +97,12 @@ export class BackgroundQueue {
|
|
|
86
97
|
* only once http connections have drained (tasks no longer being added).
|
|
87
98
|
*/
|
|
88
99
|
async destroy() {
|
|
100
|
+
if (this.destroyed) {
|
|
101
|
+
dbLogger.warn('BackgroundQueue.destroy() called multiple times')
|
|
102
|
+
}
|
|
103
|
+
|
|
89
104
|
this.abortController.abort()
|
|
90
|
-
|
|
105
|
+
return this.processAll()
|
|
91
106
|
}
|
|
92
107
|
}
|
|
93
108
|
|
package/src/context.ts
CHANGED
|
@@ -135,7 +135,7 @@ export class AppContext {
|
|
|
135
135
|
keypair: signingKey,
|
|
136
136
|
})
|
|
137
137
|
|
|
138
|
-
const backgroundQueue = new BackgroundQueue(db)
|
|
138
|
+
const backgroundQueue = new BackgroundQueue(db, { concurrency: 20 })
|
|
139
139
|
const blobDiverter = cfg.blobDivert
|
|
140
140
|
? new BlobDiverter(db, {
|
|
141
141
|
idResolver,
|
package/src/daemon/context.ts
CHANGED
|
@@ -73,7 +73,7 @@ export class DaemonContext {
|
|
|
73
73
|
pds: cfg.pds ?? undefined,
|
|
74
74
|
})
|
|
75
75
|
|
|
76
|
-
const backgroundQueue = new BackgroundQueue(db)
|
|
76
|
+
const backgroundQueue = new BackgroundQueue(db, { concurrency: 20 })
|
|
77
77
|
|
|
78
78
|
const settingService = SettingService.creator()
|
|
79
79
|
const strikeService = StrikeService.creator()
|
|
@@ -156,9 +156,14 @@ export class VerificationListener {
|
|
|
156
156
|
})
|
|
157
157
|
}
|
|
158
158
|
|
|
159
|
-
stop() {
|
|
160
|
-
this.
|
|
161
|
-
|
|
162
|
-
|
|
159
|
+
async stop() {
|
|
160
|
+
if (!this.destroyed) {
|
|
161
|
+
this.destroyed = true
|
|
162
|
+
try {
|
|
163
|
+
await this.jetstream?.close()
|
|
164
|
+
} finally {
|
|
165
|
+
await this.backgroundQueue.destroy()
|
|
166
|
+
}
|
|
167
|
+
}
|
|
163
168
|
}
|
|
164
169
|
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { Kysely } from 'kysely'
|
|
2
|
+
|
|
3
|
+
export async function up(db: Kysely<unknown>): Promise<void> {
|
|
4
|
+
// Supports time-ordered scans across all reports for downstream pollers
|
|
5
|
+
// (e.g. Nimbus' report-activity watcher). The existing indexes are all
|
|
6
|
+
// leading-`reportId`, which would force a sequential scan for global
|
|
7
|
+
// ordered queries.
|
|
8
|
+
await db.schema
|
|
9
|
+
.createIndex('idx_report_activity_created')
|
|
10
|
+
.on('report_activity')
|
|
11
|
+
.columns(['createdAt', 'id'])
|
|
12
|
+
.execute()
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export async function down(db: Kysely<unknown>): Promise<void> {
|
|
16
|
+
await db.schema.dropIndex('idx_report_activity_created').execute()
|
|
17
|
+
}
|
|
@@ -41,3 +41,4 @@ export * as _20260313T000000000Z from './20260313T000000000Z-add-report-activity
|
|
|
41
41
|
export * as _20260318T152058935Z from './20260318T152058935Z-add-report-stat.js'
|
|
42
42
|
export * as _20260428T000000000Z from './20260428T000000000Z-add-expiring-tag-table.js'
|
|
43
43
|
export * as _20260513T202941104Z from './20260513T202941104Z-add-subject-convo-id.js'
|
|
44
|
+
export * as _20260602T120000000Z from './20260602T120000000Z-add-report-activity-created-index.js'
|
package/src/index.ts
CHANGED
|
@@ -4,11 +4,8 @@ import { AddressInfo } from 'node:net'
|
|
|
4
4
|
import compression from 'compression'
|
|
5
5
|
import cors from 'cors'
|
|
6
6
|
import express from 'express'
|
|
7
|
-
// eslint-disable-next-line import/default
|
|
7
|
+
// eslint-disable-next-line import/default
|
|
8
8
|
import httpTerminator from 'http-terminator'
|
|
9
|
-
// eslint-disable-next-line import/no-named-as-default-member
|
|
10
|
-
const { createHttpTerminator } = httpTerminator
|
|
11
|
-
type HttpTerminator = ReturnType<typeof createHttpTerminator>
|
|
12
9
|
import { DAY, SECOND } from '@atproto/common'
|
|
13
10
|
import API, { health, wellKnown } from './api/index.js'
|
|
14
11
|
import { OzoneConfig, OzoneSecrets } from './config/index.js'
|
|
@@ -29,7 +26,7 @@ export class OzoneService {
|
|
|
29
26
|
public ctx: AppContext
|
|
30
27
|
public app: express.Application
|
|
31
28
|
public server?: http.Server
|
|
32
|
-
private terminator?: HttpTerminator
|
|
29
|
+
private terminator?: httpTerminator.HttpTerminator
|
|
33
30
|
private dbStatsInterval?: NodeJS.Timeout
|
|
34
31
|
|
|
35
32
|
constructor(opts: { ctx: AppContext; app: express.Application }) {
|
|
@@ -108,23 +105,25 @@ export class OzoneService {
|
|
|
108
105
|
// so we need to sync them from env var to the database
|
|
109
106
|
await this.seedInitialMembers()
|
|
110
107
|
|
|
111
|
-
const { db, backgroundQueue } = this.ctx
|
|
112
108
|
this.dbStatsInterval = setInterval(() => {
|
|
113
109
|
dbLogger.info(
|
|
114
110
|
{
|
|
115
|
-
idleCount: db.pool.idleCount,
|
|
116
|
-
totalCount: db.pool.totalCount,
|
|
117
|
-
waitingCount: db.pool.waitingCount,
|
|
111
|
+
idleCount: this.ctx.db.pool.idleCount,
|
|
112
|
+
totalCount: this.ctx.db.pool.totalCount,
|
|
113
|
+
waitingCount: this.ctx.db.pool.waitingCount,
|
|
118
114
|
},
|
|
119
115
|
'db pool stats',
|
|
120
116
|
)
|
|
121
|
-
dbLogger.info(
|
|
117
|
+
dbLogger.info(
|
|
118
|
+
this.ctx.backgroundQueue.getStats(),
|
|
119
|
+
'background queue stats',
|
|
120
|
+
)
|
|
122
121
|
}, 10000)
|
|
123
122
|
await this.ctx.sequencer.start()
|
|
124
123
|
const server = this.app.listen(this.ctx.cfg.service.port)
|
|
125
124
|
this.server = server
|
|
126
125
|
server.keepAliveTimeout = 90000
|
|
127
|
-
this.terminator = createHttpTerminator({ server })
|
|
126
|
+
this.terminator = httpTerminator.createHttpTerminator({ server })
|
|
128
127
|
await events.once(server, 'listening')
|
|
129
128
|
const { port } = server.address() as AddressInfo
|
|
130
129
|
this.ctx.assignPort(port)
|
|
@@ -132,12 +131,23 @@ export class OzoneService {
|
|
|
132
131
|
}
|
|
133
132
|
|
|
134
133
|
async destroy(): Promise<void> {
|
|
135
|
-
await this.terminator?.terminate()
|
|
136
|
-
await this.ctx.backgroundQueue.destroy()
|
|
137
|
-
await this.ctx.sequencer.destroy()
|
|
138
|
-
await this.ctx.db.close()
|
|
139
134
|
clearInterval(this.dbStatsInterval)
|
|
140
135
|
this.dbStatsInterval = undefined
|
|
136
|
+
|
|
137
|
+
// @TODO Use a disposable stack when Node24 becomes the min supported version
|
|
138
|
+
try {
|
|
139
|
+
await this.terminator?.terminate()
|
|
140
|
+
} finally {
|
|
141
|
+
try {
|
|
142
|
+
await this.ctx.backgroundQueue.destroy()
|
|
143
|
+
} finally {
|
|
144
|
+
try {
|
|
145
|
+
await this.ctx.sequencer.destroy()
|
|
146
|
+
} finally {
|
|
147
|
+
await this.ctx.db.close()
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
141
151
|
}
|
|
142
152
|
}
|
|
143
153
|
|
package/src/jetstream/service.ts
CHANGED
|
@@ -175,6 +175,25 @@ export async function getReportById(
|
|
|
175
175
|
.executeTakeFirst()
|
|
176
176
|
}
|
|
177
177
|
|
|
178
|
+
export async function getReportsByIds(
|
|
179
|
+
db: Database,
|
|
180
|
+
ids: number[],
|
|
181
|
+
): Promise<ReportWithEvent[]> {
|
|
182
|
+
if (!ids.length) return []
|
|
183
|
+
return reportQuery(db)
|
|
184
|
+
.where('r.id', 'in', ids)
|
|
185
|
+
.selectAll('r')
|
|
186
|
+
.select([
|
|
187
|
+
'me.subjectDid',
|
|
188
|
+
'me.subjectUri',
|
|
189
|
+
'me.subjectCid',
|
|
190
|
+
'me.createdBy as reportedBy',
|
|
191
|
+
'me.comment',
|
|
192
|
+
'me.meta',
|
|
193
|
+
])
|
|
194
|
+
.execute()
|
|
195
|
+
}
|
|
196
|
+
|
|
178
197
|
export async function getLatestReport(
|
|
179
198
|
db: Database,
|
|
180
199
|
): Promise<ReportWithEvent | undefined> {
|
package/src/report/activity.ts
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { InvalidRequestError } from '@atproto/xrpc-server'
|
|
2
2
|
import { Database } from '../db/index.js'
|
|
3
|
+
import { TimeIdKeyset, paginate } from '../db/pagination.js'
|
|
4
|
+
import { ReportView } from '../lexicon/types/tools/ozone/report/defs.js'
|
|
5
|
+
import { QueryParams as QueryActivitiesParams } from '../lexicon/types/tools/ozone/report/queryActivities.js'
|
|
3
6
|
import { Member } from '../lexicon/types/tools/ozone/team/defs.js'
|
|
4
7
|
import {
|
|
5
8
|
AlreadyInTargetState,
|
|
@@ -190,6 +193,48 @@ export async function listReportActivities(
|
|
|
190
193
|
return { activities, cursor: nextCursor }
|
|
191
194
|
}
|
|
192
195
|
|
|
196
|
+
export async function queryReportActivities(
|
|
197
|
+
db: Database,
|
|
198
|
+
params: QueryActivitiesParams,
|
|
199
|
+
) {
|
|
200
|
+
const {
|
|
201
|
+
activityTypes,
|
|
202
|
+
createdAfter,
|
|
203
|
+
createdBefore,
|
|
204
|
+
sortDirection,
|
|
205
|
+
limit,
|
|
206
|
+
cursor,
|
|
207
|
+
} = params
|
|
208
|
+
const { ref } = db.db.dynamic
|
|
209
|
+
|
|
210
|
+
let builder = db.db.selectFrom('report_activity').selectAll()
|
|
211
|
+
|
|
212
|
+
if (activityTypes && activityTypes.length > 0) {
|
|
213
|
+
builder = builder.where('activityType', 'in', activityTypes)
|
|
214
|
+
}
|
|
215
|
+
if (createdAfter) {
|
|
216
|
+
builder = builder.where('createdAt', '>=', createdAfter)
|
|
217
|
+
}
|
|
218
|
+
if (createdBefore) {
|
|
219
|
+
builder = builder.where('createdAt', '<=', createdBefore)
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
const keyset = new TimeIdKeyset(
|
|
223
|
+
ref('report_activity.createdAt'),
|
|
224
|
+
ref('report_activity.id'),
|
|
225
|
+
)
|
|
226
|
+
const paginatedBuilder = paginate(builder, {
|
|
227
|
+
limit,
|
|
228
|
+
cursor,
|
|
229
|
+
keyset,
|
|
230
|
+
direction: sortDirection,
|
|
231
|
+
tryIndex: true,
|
|
232
|
+
})
|
|
233
|
+
|
|
234
|
+
const activities = await paginatedBuilder.execute()
|
|
235
|
+
return { activities, cursor: keyset.packFromResult(activities) }
|
|
236
|
+
}
|
|
237
|
+
|
|
193
238
|
function buildActivityObject(
|
|
194
239
|
activityType: string,
|
|
195
240
|
previousStatus: string | null,
|
|
@@ -215,6 +260,7 @@ export function formatActivityView(
|
|
|
215
260
|
createdAt: string
|
|
216
261
|
},
|
|
217
262
|
memberViews?: Map<string, Member>,
|
|
263
|
+
reportViews?: Map<number, ReportView>,
|
|
218
264
|
) {
|
|
219
265
|
return {
|
|
220
266
|
id: activity.id,
|
|
@@ -229,6 +275,7 @@ export function formatActivityView(
|
|
|
229
275
|
isAutomated: activity.isAutomated,
|
|
230
276
|
createdBy: activity.createdBy,
|
|
231
277
|
moderator: memberViews?.get(activity.createdBy),
|
|
278
|
+
report: reportViews?.get(activity.reportId),
|
|
232
279
|
createdAt: activity.createdAt,
|
|
233
280
|
}
|
|
234
281
|
}
|
package/tests/3p-labeler.test.ts
CHANGED