@atproto/bsky 0.0.11 → 0.0.13
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 +29 -0
- package/LICENSE.txt +7 -0
- package/README.md +6 -1
- package/dist/api/com/atproto/admin/util.d.ts +5 -0
- package/dist/context.d.ts +6 -1
- package/dist/db/index.js +51 -2
- package/dist/db/index.js.map +3 -3
- package/dist/db/migrations/20230929T192920807Z-record-cursor-indexes.d.ts +3 -0
- package/dist/db/migrations/index.d.ts +1 -0
- package/dist/did-cache.d.ts +2 -2
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1630 -755
- package/dist/index.js.map +3 -3
- package/dist/lexicon/index.d.ts +8 -0
- package/dist/lexicon/lexicons.d.ts +243 -3
- package/dist/lexicon/types/app/bsky/actor/defs.d.ts +1 -0
- package/dist/lexicon/types/com/atproto/admin/defs.d.ts +28 -0
- package/dist/lexicon/types/com/atproto/admin/getAccountInfo.d.ts +29 -0
- package/dist/lexicon/types/com/atproto/admin/getSubjectStatus.d.ts +39 -0
- package/dist/lexicon/types/com/atproto/admin/searchRepos.d.ts +0 -1
- package/dist/lexicon/types/com/atproto/admin/updateSubjectStatus.d.ts +46 -0
- package/dist/lexicon/types/com/atproto/server/createAccount.d.ts +4 -2
- package/dist/lexicon/types/com/atproto/server/createSession.d.ts +1 -0
- package/dist/lexicon/types/com/atproto/server/refreshSession.d.ts +1 -0
- package/dist/lexicon/types/com/atproto/server/reserveSigningKey.d.ts +36 -0
- package/dist/lexicon/types/com/atproto/sync/listRepos.d.ts +1 -0
- package/dist/services/actor/types.d.ts +1 -0
- package/dist/services/graph/index.d.ts +2 -0
- package/dist/services/moderation/index.d.ts +13 -3
- package/package.json +14 -15
- package/src/api/app/bsky/feed/getAuthorFeed.ts +2 -2
- package/src/api/app/bsky/feed/getPostThread.ts +2 -2
- package/src/api/app/bsky/notification/listNotifications.ts +33 -22
- package/src/api/com/atproto/admin/getModerationAction.ts +28 -2
- package/src/api/com/atproto/admin/getModerationReport.ts +27 -2
- package/src/api/com/atproto/admin/getRecord.ts +14 -2
- package/src/api/com/atproto/admin/getRepo.ts +13 -2
- package/src/api/com/atproto/admin/reverseModerationAction.ts +31 -5
- package/src/api/com/atproto/admin/searchRepos.ts +2 -5
- package/src/api/com/atproto/admin/takeModerationAction.ts +41 -7
- package/src/api/com/atproto/admin/util.ts +50 -0
- package/src/api/well-known.ts +8 -0
- package/src/auth.ts +12 -5
- package/src/auto-moderator/index.ts +1 -0
- package/src/context.ts +25 -1
- package/src/db/migrations/20230929T192920807Z-record-cursor-indexes.ts +40 -0
- package/src/db/migrations/index.ts +1 -0
- package/src/did-cache.ts +29 -14
- package/src/feed-gen/with-friends.ts +2 -2
- package/src/index.ts +4 -1
- package/src/indexer/subscription.ts +1 -21
- package/src/lexicon/index.ts +48 -0
- package/src/lexicon/lexicons.ts +259 -5
- package/src/lexicon/types/app/bsky/actor/defs.ts +1 -0
- package/src/lexicon/types/com/atproto/admin/defs.ts +61 -0
- package/src/lexicon/types/com/atproto/admin/getAccountInfo.ts +41 -0
- package/src/lexicon/types/com/atproto/admin/getSubjectStatus.ts +54 -0
- package/src/lexicon/types/com/atproto/admin/searchRepos.ts +0 -1
- package/src/lexicon/types/com/atproto/admin/updateSubjectStatus.ts +61 -0
- package/src/lexicon/types/com/atproto/server/createAccount.ts +4 -2
- package/src/lexicon/types/com/atproto/server/createSession.ts +1 -0
- package/src/lexicon/types/com/atproto/server/refreshSession.ts +1 -0
- package/src/lexicon/types/com/atproto/server/reserveSigningKey.ts +51 -0
- package/src/lexicon/types/com/atproto/sync/listRepos.ts +1 -0
- package/src/logger.ts +8 -0
- package/src/services/actor/index.ts +7 -1
- package/src/services/actor/types.ts +1 -0
- package/src/services/actor/views.ts +26 -8
- package/src/services/graph/index.ts +26 -7
- package/src/services/indexing/index.ts +15 -17
- package/src/services/moderation/index.ts +94 -14
- package/src/services/moderation/views.ts +1 -0
- package/tests/__snapshots__/feed-generation.test.ts.snap +12 -12
- package/tests/__snapshots__/indexing.test.ts.snap +4 -4
- package/tests/admin/__snapshots__/get-moderation-action.test.ts.snap +172 -0
- package/tests/admin/__snapshots__/get-moderation-actions.test.ts.snap +178 -0
- package/tests/admin/__snapshots__/get-moderation-report.test.ts.snap +177 -0
- package/tests/admin/__snapshots__/get-moderation-reports.test.ts.snap +307 -0
- package/tests/admin/__snapshots__/get-record.test.ts.snap +275 -0
- package/tests/admin/__snapshots__/get-repo.test.ts.snap +103 -0
- package/tests/admin/get-moderation-action.test.ts +100 -0
- package/tests/admin/get-moderation-actions.test.ts +164 -0
- package/tests/admin/get-moderation-report.test.ts +100 -0
- package/tests/admin/get-moderation-reports.test.ts +332 -0
- package/tests/admin/get-record.test.ts +115 -0
- package/tests/admin/get-repo.test.ts +101 -0
- package/tests/{moderation.test.ts → admin/moderation.test.ts} +107 -9
- package/tests/admin/repo-search.test.ts +124 -0
- package/tests/algos/hot-classic.test.ts +3 -5
- package/tests/algos/whats-hot.test.ts +3 -5
- package/tests/algos/with-friends.test.ts +2 -4
- package/tests/auth.test.ts +64 -0
- package/tests/auto-moderator/fuzzy-matcher.test.ts +2 -3
- package/tests/auto-moderator/labeler.test.ts +5 -7
- package/tests/auto-moderator/takedowns.test.ts +11 -12
- package/tests/blob-resolver.test.ts +1 -3
- package/tests/did-cache.test.ts +2 -5
- package/tests/feed-generation.test.ts +8 -6
- package/tests/handle-invalidation.test.ts +2 -3
- package/tests/image/server.test.ts +1 -4
- package/tests/image/sharp.test.ts +1 -1
- package/tests/indexing.test.ts +4 -4
- package/tests/notification-server.test.ts +2 -3
- package/tests/pipeline/backpressure.test.ts +2 -3
- package/tests/pipeline/reingest.test.ts +7 -4
- package/tests/pipeline/repartition.test.ts +2 -3
- package/tests/reprocessing.test.ts +2 -6
- package/tests/seeds/basic.ts +4 -4
- package/tests/seeds/follows.ts +1 -1
- package/tests/seeds/likes.ts +1 -1
- package/tests/seeds/reposts.ts +1 -1
- package/tests/seeds/users-bulk.ts +1 -1
- package/tests/seeds/users.ts +1 -1
- package/tests/server.test.ts +1 -3
- package/tests/subscription/repo.test.ts +2 -4
- package/tests/views/__snapshots__/author-feed.test.ts.snap +24 -24
- package/tests/views/__snapshots__/block-lists.test.ts.snap +42 -7
- package/tests/views/__snapshots__/blocks.test.ts.snap +2 -2
- package/tests/views/__snapshots__/list-feed.test.ts.snap +6 -6
- package/tests/views/__snapshots__/mute-lists.test.ts.snap +15 -4
- package/tests/views/__snapshots__/mutes.test.ts.snap +2 -2
- package/tests/views/__snapshots__/notifications.test.ts.snap +2 -2
- package/tests/views/__snapshots__/posts.test.ts.snap +8 -8
- package/tests/views/__snapshots__/thread.test.ts.snap +10 -10
- package/tests/views/__snapshots__/timeline.test.ts.snap +58 -58
- package/tests/views/actor-likes.test.ts +2 -3
- package/tests/views/actor-search.test.ts +5 -5
- package/tests/views/admin/repo-search.test.ts +2 -4
- package/tests/views/author-feed.test.ts +2 -4
- package/tests/views/block-lists.test.ts +34 -7
- package/tests/views/blocks.test.ts +6 -3
- package/tests/views/follows.test.ts +2 -4
- package/tests/views/likes.test.ts +2 -5
- package/tests/views/list-feed.test.ts +2 -4
- package/tests/views/mute-lists.test.ts +23 -5
- package/tests/views/mutes.test.ts +2 -5
- package/tests/views/notifications.test.ts +2 -4
- package/tests/views/posts.test.ts +2 -5
- package/tests/views/profile.test.ts +4 -5
- package/tests/views/reposts.test.ts +2 -4
- package/tests/views/suggested-follows.test.ts +2 -3
- package/tests/views/suggestions.test.ts +2 -4
- package/tests/views/thread.test.ts +2 -4
- package/tests/views/threadgating.test.ts +2 -3
- package/tests/views/timeline.test.ts +2 -4
- package/LICENSE +0 -21
- package/dist/env.d.ts +0 -1
- package/example.dev.env +0 -5
- package/src/env.ts +0 -9
- package/tests/seeds/client.ts +0 -466
- /package/tests/{__snapshots__ → admin/__snapshots__}/moderation.test.ts.snap +0 -0
- /package/tests/{image/fixtures → sample-img}/at.png +0 -0
- /package/tests/{image/fixtures → sample-img}/hd-key.jpg +0 -0
- /package/tests/{image/fixtures → sample-img}/key-alt.jpg +0 -0
- /package/tests/{image/fixtures → sample-img}/key-landscape-large.jpg +0 -0
- /package/tests/{image/fixtures → sample-img}/key-landscape-small.jpg +0 -0
- /package/tests/{image/fixtures → sample-img}/key-portrait-large.jpg +0 -0
- /package/tests/{image/fixtures → sample-img}/key-portrait-small.jpg +0 -0
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import express from 'express';
|
|
2
|
+
import { HandlerAuth } from '@atproto/xrpc-server';
|
|
3
|
+
export interface QueryParams {
|
|
4
|
+
}
|
|
5
|
+
export interface InputSchema {
|
|
6
|
+
did?: string;
|
|
7
|
+
[k: string]: unknown;
|
|
8
|
+
}
|
|
9
|
+
export interface OutputSchema {
|
|
10
|
+
signingKey: string;
|
|
11
|
+
[k: string]: unknown;
|
|
12
|
+
}
|
|
13
|
+
export interface HandlerInput {
|
|
14
|
+
encoding: 'application/json';
|
|
15
|
+
body: InputSchema;
|
|
16
|
+
}
|
|
17
|
+
export interface HandlerSuccess {
|
|
18
|
+
encoding: 'application/json';
|
|
19
|
+
body: OutputSchema;
|
|
20
|
+
headers?: {
|
|
21
|
+
[key: string]: string;
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
export interface HandlerError {
|
|
25
|
+
status: number;
|
|
26
|
+
message?: string;
|
|
27
|
+
}
|
|
28
|
+
export declare type HandlerOutput = HandlerError | HandlerSuccess;
|
|
29
|
+
export declare type HandlerReqCtx<HA extends HandlerAuth = never> = {
|
|
30
|
+
auth: HA;
|
|
31
|
+
params: QueryParams;
|
|
32
|
+
input: HandlerInput;
|
|
33
|
+
req: express.Request;
|
|
34
|
+
res: express.Response;
|
|
35
|
+
};
|
|
36
|
+
export declare type Handler<HA extends HandlerAuth = never> = (ctx: HandlerReqCtx<HA>) => Promise<HandlerOutput> | HandlerOutput;
|
|
@@ -85,6 +85,7 @@ export declare type RelationshipPair = [didA: string, didB: string];
|
|
|
85
85
|
export declare class BlockAndMuteState {
|
|
86
86
|
hasIdx: Map<string, Set<string>>;
|
|
87
87
|
blockIdx: Map<string, Map<string, string>>;
|
|
88
|
+
blockListIdx: Map<string, Map<string, string>>;
|
|
88
89
|
muteIdx: Map<string, Set<string>>;
|
|
89
90
|
muteListIdx: Map<string, Map<string, string>>;
|
|
90
91
|
constructor(items?: BlockAndMuteInfo[]);
|
|
@@ -93,6 +94,7 @@ export declare class BlockAndMuteState {
|
|
|
93
94
|
blocking(pair: RelationshipPair): string | null;
|
|
94
95
|
blockedBy(pair: RelationshipPair): string | null;
|
|
95
96
|
mute(pair: RelationshipPair): boolean;
|
|
97
|
+
blockList(pair: RelationshipPair): string | null;
|
|
96
98
|
muteList(pair: RelationshipPair): string | null;
|
|
97
99
|
has(pair: RelationshipPair): boolean;
|
|
98
100
|
}
|
|
@@ -6,6 +6,8 @@ import { ModerationAction, ModerationReport } from '../../db/tables/moderation';
|
|
|
6
6
|
import { ModerationViews } from './views';
|
|
7
7
|
import { ImageUriBuilder } from '../../image/uri';
|
|
8
8
|
import { ImageInvalidator } from '../../image/invalidator';
|
|
9
|
+
import { RepoRef, RepoBlobRef } from '../../lexicon/types/com/atproto/admin/defs';
|
|
10
|
+
import { Main as StrongRef } from '../../lexicon/types/com/atproto/repo/strongRef';
|
|
9
11
|
export declare class ModerationService {
|
|
10
12
|
db: PrimaryDatabase;
|
|
11
13
|
imgUriBuilder: ImageUriBuilder;
|
|
@@ -74,20 +76,24 @@ export declare class ModerationService {
|
|
|
74
76
|
durationInHours?: number;
|
|
75
77
|
}): Promise<ModerationActionRow>;
|
|
76
78
|
getActionsDueForReversal(): Promise<ModerationActionRow[]>;
|
|
77
|
-
revertAction({ id, createdBy, createdAt, reason, }: ReversibleModerationAction): Promise<
|
|
79
|
+
revertAction({ id, createdBy, createdAt, reason, }: ReversibleModerationAction): Promise<{
|
|
80
|
+
result: ModerationActionRow;
|
|
81
|
+
restored?: TakedownSubjects;
|
|
82
|
+
}>;
|
|
78
83
|
logReverseAction(info: ReversibleModerationAction): Promise<ModerationActionRow>;
|
|
79
84
|
takedownRepo(info: {
|
|
80
85
|
takedownId: number;
|
|
81
86
|
did: string;
|
|
82
|
-
}): Promise<
|
|
87
|
+
}): Promise<TakedownSubjects>;
|
|
83
88
|
reverseTakedownRepo(info: {
|
|
84
89
|
did: string;
|
|
85
90
|
}): Promise<void>;
|
|
86
91
|
takedownRecord(info: {
|
|
87
92
|
takedownId: number;
|
|
88
93
|
uri: AtUri;
|
|
94
|
+
cid: CID;
|
|
89
95
|
blobCids?: CID[];
|
|
90
|
-
}): Promise<
|
|
96
|
+
}): Promise<TakedownSubjects>;
|
|
91
97
|
reverseTakedownRecord(info: {
|
|
92
98
|
uri: AtUri;
|
|
93
99
|
}): Promise<void>;
|
|
@@ -110,6 +116,10 @@ export declare class ModerationService {
|
|
|
110
116
|
createdAt?: Date;
|
|
111
117
|
}): Promise<ModerationReportRow>;
|
|
112
118
|
}
|
|
119
|
+
export declare type TakedownSubjects = {
|
|
120
|
+
did: string;
|
|
121
|
+
subjects: (RepoRef | RepoBlobRef | StrongRef)[];
|
|
122
|
+
};
|
|
113
123
|
export declare type ModerationActionRow = Selectable<ModerationAction>;
|
|
114
124
|
export declare type ReversibleModerationAction = Pick<ModerationActionRow, 'id' | 'createdBy' | 'reason'> & {
|
|
115
125
|
createdAt?: Date;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@atproto/bsky",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.13",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "Reference implementation of app.bsky App View (Bluesky API)",
|
|
6
6
|
"keywords": [
|
|
@@ -20,7 +20,6 @@
|
|
|
20
20
|
"@isaacs/ttlcache": "^1.4.1",
|
|
21
21
|
"compression": "^1.7.4",
|
|
22
22
|
"cors": "^2.8.5",
|
|
23
|
-
"dotenv": "^16.0.0",
|
|
24
23
|
"express": "^4.17.2",
|
|
25
24
|
"express-async-errors": "^3.1.1",
|
|
26
25
|
"form-data": "^4.0.0",
|
|
@@ -36,14 +35,14 @@
|
|
|
36
35
|
"sharp": "^0.31.2",
|
|
37
36
|
"typed-emitter": "^2.1.0",
|
|
38
37
|
"uint8arrays": "3.0.0",
|
|
39
|
-
"@atproto/api": "^0.6.
|
|
40
|
-
"@atproto/common": "^0.3.
|
|
41
|
-
"@atproto/crypto": "^0.2.
|
|
42
|
-
"@atproto/syntax": "^0.1.
|
|
43
|
-
"@atproto/identity": "^0.
|
|
44
|
-
"@atproto/lexicon": "^0.
|
|
45
|
-
"@atproto/repo": "^0.3.
|
|
46
|
-
"@atproto/xrpc-server": "^0.
|
|
38
|
+
"@atproto/api": "^0.6.22",
|
|
39
|
+
"@atproto/common": "^0.3.3",
|
|
40
|
+
"@atproto/crypto": "^0.2.3",
|
|
41
|
+
"@atproto/syntax": "^0.1.4",
|
|
42
|
+
"@atproto/identity": "^0.3.1",
|
|
43
|
+
"@atproto/lexicon": "^0.3.0",
|
|
44
|
+
"@atproto/repo": "^0.3.4",
|
|
45
|
+
"@atproto/xrpc-server": "^0.4.0"
|
|
47
46
|
},
|
|
48
47
|
"devDependencies": {
|
|
49
48
|
"@did-plc/server": "^0.0.1",
|
|
@@ -54,11 +53,11 @@
|
|
|
54
53
|
"@types/qs": "^6.9.7",
|
|
55
54
|
"@types/sharp": "^0.31.0",
|
|
56
55
|
"axios": "^0.27.2",
|
|
57
|
-
"@atproto/api": "^0.6.
|
|
58
|
-
"@atproto/dev-env": "^0.2.
|
|
59
|
-
"@atproto/lex-cli": "^0.2.
|
|
60
|
-
"@atproto/pds": "^0.1
|
|
61
|
-
"@atproto/xrpc": "^0.
|
|
56
|
+
"@atproto/api": "^0.6.22",
|
|
57
|
+
"@atproto/dev-env": "^0.2.13",
|
|
58
|
+
"@atproto/lex-cli": "^0.2.4",
|
|
59
|
+
"@atproto/pds": "^0.3.1",
|
|
60
|
+
"@atproto/xrpc": "^0.4.0"
|
|
62
61
|
},
|
|
63
62
|
"scripts": {
|
|
64
63
|
"codegen": "lex gen-server ./src/lexicon ../../lexicons/com/atproto/*/* ../../lexicons/app/bsky/*/*",
|
|
@@ -89,8 +89,8 @@ export const skeleton = async (
|
|
|
89
89
|
|
|
90
90
|
if (filter === 'posts_with_media') {
|
|
91
91
|
feedItemsQb = feedItemsQb
|
|
92
|
-
//
|
|
93
|
-
.where('
|
|
92
|
+
// only your own posts
|
|
93
|
+
.where('type', '=', 'post')
|
|
94
94
|
// only posts with media
|
|
95
95
|
.whereExists((qb) =>
|
|
96
96
|
qb
|
|
@@ -37,9 +37,9 @@ export default function (server: Server, ctx: AppContext) {
|
|
|
37
37
|
presentation,
|
|
38
38
|
)
|
|
39
39
|
server.app.bsky.feed.getPostThread({
|
|
40
|
-
auth: ctx.
|
|
40
|
+
auth: ctx.authOptionalAccessOrRoleVerifier,
|
|
41
41
|
handler: async ({ params, auth, res }) => {
|
|
42
|
-
const viewer = auth.credentials.did
|
|
42
|
+
const viewer = 'did' in auth.credentials ? auth.credentials.did : null
|
|
43
43
|
const db = ctx.db.getReplica('thread')
|
|
44
44
|
const feedService = ctx.services.feed(db)
|
|
45
45
|
const actorService = ctx.services.actor(db)
|
|
@@ -53,10 +53,6 @@ const skeleton = async (
|
|
|
53
53
|
}
|
|
54
54
|
let notifBuilder = db.db
|
|
55
55
|
.selectFrom('notification as notif')
|
|
56
|
-
.innerJoin('record', 'record.uri', 'notif.recordUri')
|
|
57
|
-
.innerJoin('actor as author', 'author.did', 'notif.author')
|
|
58
|
-
.where(notSoftDeletedClause(ref('record')))
|
|
59
|
-
.where(notSoftDeletedClause(ref('author')))
|
|
60
56
|
.where('notif.did', '=', viewer)
|
|
61
57
|
.where((clause) =>
|
|
62
58
|
clause
|
|
@@ -69,16 +65,12 @@ const skeleton = async (
|
|
|
69
65
|
),
|
|
70
66
|
)
|
|
71
67
|
.select([
|
|
68
|
+
'notif.author as authorDid',
|
|
72
69
|
'notif.recordUri as uri',
|
|
73
70
|
'notif.recordCid as cid',
|
|
74
|
-
'author.did as authorDid',
|
|
75
|
-
'author.handle as authorHandle',
|
|
76
|
-
'author.indexedAt as authorIndexedAt',
|
|
77
|
-
'author.takedownId as authorTakedownId',
|
|
78
71
|
'notif.reason as reason',
|
|
79
72
|
'notif.reasonSubject as reasonSubject',
|
|
80
73
|
'notif.sortAt as indexedAt',
|
|
81
|
-
'record.json as recordJson',
|
|
82
74
|
])
|
|
83
75
|
|
|
84
76
|
const keyset = new NotifsKeyset(ref('notif.sortAt'), ref('notif.recordCid'))
|
|
@@ -86,6 +78,7 @@ const skeleton = async (
|
|
|
86
78
|
cursor,
|
|
87
79
|
limit,
|
|
88
80
|
keyset,
|
|
81
|
+
tryIndex: true,
|
|
89
82
|
})
|
|
90
83
|
|
|
91
84
|
const actorStateQuery = db.db
|
|
@@ -107,17 +100,18 @@ const skeleton = async (
|
|
|
107
100
|
}
|
|
108
101
|
|
|
109
102
|
const hydration = async (state: SkeletonState, ctx: Context) => {
|
|
110
|
-
const { graphService, actorService, labelService } = ctx
|
|
103
|
+
const { graphService, actorService, labelService, db } = ctx
|
|
111
104
|
const { params, notifs } = state
|
|
112
105
|
const { viewer } = params
|
|
113
106
|
const dids = notifs.map((notif) => notif.authorDid)
|
|
114
107
|
const uris = notifs.map((notif) => notif.uri)
|
|
115
|
-
const [actors, labels, bam] = await Promise.all([
|
|
108
|
+
const [actors, records, labels, bam] = await Promise.all([
|
|
116
109
|
actorService.views.profiles(dids, viewer),
|
|
110
|
+
getRecordMap(db, uris),
|
|
117
111
|
labelService.getLabelsForUris(uris),
|
|
118
112
|
graphService.getBlockAndMuteState(dids.map((did) => [viewer, did])),
|
|
119
113
|
])
|
|
120
|
-
return { ...state, actors, labels, bam }
|
|
114
|
+
return { ...state, actors, records, labels, bam }
|
|
121
115
|
}
|
|
122
116
|
|
|
123
117
|
const noBlockOrMutes = (state: HydrationState) => {
|
|
@@ -131,11 +125,11 @@ const noBlockOrMutes = (state: HydrationState) => {
|
|
|
131
125
|
}
|
|
132
126
|
|
|
133
127
|
const presentation = (state: HydrationState) => {
|
|
134
|
-
const { notifs, cursor, actors, labels, lastSeenNotifs } = state
|
|
128
|
+
const { notifs, cursor, actors, records, labels, lastSeenNotifs } = state
|
|
135
129
|
const notifications = mapDefined(notifs, (notif) => {
|
|
136
130
|
const author = actors[notif.authorDid]
|
|
137
|
-
|
|
138
|
-
|
|
131
|
+
const record = records[notif.uri]
|
|
132
|
+
if (!author || !record) return undefined
|
|
139
133
|
const recordLabels = labels[notif.uri] ?? []
|
|
140
134
|
const recordSelfLabels = getSelfLabels({
|
|
141
135
|
uri: notif.uri,
|
|
@@ -157,6 +151,24 @@ const presentation = (state: HydrationState) => {
|
|
|
157
151
|
return { notifications, cursor }
|
|
158
152
|
}
|
|
159
153
|
|
|
154
|
+
const getRecordMap = async (
|
|
155
|
+
db: Database,
|
|
156
|
+
uris: string[],
|
|
157
|
+
): Promise<RecordMap> => {
|
|
158
|
+
if (!uris.length) return {}
|
|
159
|
+
const { ref } = db.db.dynamic
|
|
160
|
+
const recordRows = await db.db
|
|
161
|
+
.selectFrom('record')
|
|
162
|
+
.select(['uri', 'json'])
|
|
163
|
+
.where('uri', 'in', uris)
|
|
164
|
+
.where(notSoftDeletedClause(ref('record')))
|
|
165
|
+
.execute()
|
|
166
|
+
return recordRows.reduce((acc, { uri, json }) => {
|
|
167
|
+
acc[uri] = jsonStringToLex(json) as Record<string, unknown>
|
|
168
|
+
return acc
|
|
169
|
+
}, {} as RecordMap)
|
|
170
|
+
}
|
|
171
|
+
|
|
160
172
|
type Context = {
|
|
161
173
|
db: Database
|
|
162
174
|
actorService: ActorService
|
|
@@ -178,20 +190,19 @@ type SkeletonState = {
|
|
|
178
190
|
type HydrationState = SkeletonState & {
|
|
179
191
|
bam: BlockAndMuteState
|
|
180
192
|
actors: ActorInfoMap
|
|
193
|
+
records: RecordMap
|
|
181
194
|
labels: Labels
|
|
182
195
|
}
|
|
183
196
|
|
|
197
|
+
type RecordMap = { [uri: string]: Record<string, unknown> }
|
|
198
|
+
|
|
184
199
|
type NotifRow = {
|
|
185
|
-
indexedAt: string
|
|
186
|
-
cid: string
|
|
187
|
-
uri: string
|
|
188
200
|
authorDid: string
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
authorTakedownId: number | null
|
|
201
|
+
uri: string
|
|
202
|
+
cid: string
|
|
192
203
|
reason: string
|
|
193
204
|
reasonSubject: string | null
|
|
194
|
-
|
|
205
|
+
indexedAt: string
|
|
195
206
|
}
|
|
196
207
|
|
|
197
208
|
class NotifsKeyset extends TimeCidKeyset<NotifRow> {
|
|
@@ -1,17 +1,43 @@
|
|
|
1
1
|
import { Server } from '../../../../lexicon'
|
|
2
2
|
import AppContext from '../../../../context'
|
|
3
|
+
import { addAccountInfoToRepoView, getPdsAccountInfo } from './util'
|
|
4
|
+
import {
|
|
5
|
+
isRecordView,
|
|
6
|
+
isRepoView,
|
|
7
|
+
} from '../../../../lexicon/types/com/atproto/admin/defs'
|
|
3
8
|
|
|
4
9
|
export default function (server: Server, ctx: AppContext) {
|
|
5
10
|
server.com.atproto.admin.getModerationAction({
|
|
6
11
|
auth: ctx.roleVerifier,
|
|
7
|
-
handler: async ({ params }) => {
|
|
12
|
+
handler: async ({ params, auth }) => {
|
|
8
13
|
const { id } = params
|
|
9
14
|
const db = ctx.db.getPrimary()
|
|
10
15
|
const moderationService = ctx.services.moderation(db)
|
|
11
16
|
const result = await moderationService.getActionOrThrow(id)
|
|
17
|
+
|
|
18
|
+
const [action, accountInfo] = await Promise.all([
|
|
19
|
+
moderationService.views.actionDetail(result),
|
|
20
|
+
getPdsAccountInfo(ctx, result.subjectDid),
|
|
21
|
+
])
|
|
22
|
+
|
|
23
|
+
// add in pds account info if available
|
|
24
|
+
if (isRepoView(action.subject)) {
|
|
25
|
+
action.subject = addAccountInfoToRepoView(
|
|
26
|
+
action.subject,
|
|
27
|
+
accountInfo,
|
|
28
|
+
auth.credentials.moderator,
|
|
29
|
+
)
|
|
30
|
+
} else if (isRecordView(action.subject)) {
|
|
31
|
+
action.subject.repo = addAccountInfoToRepoView(
|
|
32
|
+
action.subject.repo,
|
|
33
|
+
accountInfo,
|
|
34
|
+
auth.credentials.moderator,
|
|
35
|
+
)
|
|
36
|
+
}
|
|
37
|
+
|
|
12
38
|
return {
|
|
13
39
|
encoding: 'application/json',
|
|
14
|
-
body:
|
|
40
|
+
body: action,
|
|
15
41
|
}
|
|
16
42
|
},
|
|
17
43
|
})
|
|
@@ -1,17 +1,42 @@
|
|
|
1
1
|
import { Server } from '../../../../lexicon'
|
|
2
2
|
import AppContext from '../../../../context'
|
|
3
|
+
import {
|
|
4
|
+
isRecordView,
|
|
5
|
+
isRepoView,
|
|
6
|
+
} from '../../../../lexicon/types/com/atproto/admin/defs'
|
|
7
|
+
import { addAccountInfoToRepoView, getPdsAccountInfo } from './util'
|
|
3
8
|
|
|
4
9
|
export default function (server: Server, ctx: AppContext) {
|
|
5
10
|
server.com.atproto.admin.getModerationReport({
|
|
6
11
|
auth: ctx.roleVerifier,
|
|
7
|
-
handler: async ({ params }) => {
|
|
12
|
+
handler: async ({ params, auth }) => {
|
|
8
13
|
const { id } = params
|
|
9
14
|
const db = ctx.db.getPrimary()
|
|
10
15
|
const moderationService = ctx.services.moderation(db)
|
|
11
16
|
const result = await moderationService.getReportOrThrow(id)
|
|
17
|
+
const [report, accountInfo] = await Promise.all([
|
|
18
|
+
moderationService.views.reportDetail(result),
|
|
19
|
+
getPdsAccountInfo(ctx, result.subjectDid),
|
|
20
|
+
])
|
|
21
|
+
|
|
22
|
+
// add in pds account info if available
|
|
23
|
+
if (isRepoView(report.subject)) {
|
|
24
|
+
report.subject = addAccountInfoToRepoView(
|
|
25
|
+
report.subject,
|
|
26
|
+
accountInfo,
|
|
27
|
+
auth.credentials.moderator,
|
|
28
|
+
)
|
|
29
|
+
} else if (isRecordView(report.subject)) {
|
|
30
|
+
report.subject.repo = addAccountInfoToRepoView(
|
|
31
|
+
report.subject.repo,
|
|
32
|
+
accountInfo,
|
|
33
|
+
auth.credentials.moderator,
|
|
34
|
+
)
|
|
35
|
+
}
|
|
36
|
+
|
|
12
37
|
return {
|
|
13
38
|
encoding: 'application/json',
|
|
14
|
-
body:
|
|
39
|
+
body: report,
|
|
15
40
|
}
|
|
16
41
|
},
|
|
17
42
|
})
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { InvalidRequestError } from '@atproto/xrpc-server'
|
|
2
2
|
import { Server } from '../../../../lexicon'
|
|
3
3
|
import AppContext from '../../../../context'
|
|
4
|
+
import { addAccountInfoToRepoView, getPdsAccountInfo } from './util'
|
|
4
5
|
|
|
5
6
|
export default function (server: Server, ctx: AppContext) {
|
|
6
7
|
server.com.atproto.admin.getRecord({
|
|
7
8
|
auth: ctx.roleVerifier,
|
|
8
|
-
handler: async ({ params }) => {
|
|
9
|
+
handler: async ({ params, auth }) => {
|
|
9
10
|
const { uri, cid } = params
|
|
10
11
|
const db = ctx.db.getPrimary()
|
|
11
12
|
const result = await db.db
|
|
@@ -17,9 +18,20 @@ export default function (server: Server, ctx: AppContext) {
|
|
|
17
18
|
if (!result) {
|
|
18
19
|
throw new InvalidRequestError('Record not found', 'RecordNotFound')
|
|
19
20
|
}
|
|
21
|
+
const [record, accountInfo] = await Promise.all([
|
|
22
|
+
ctx.services.moderation(db).views.recordDetail(result),
|
|
23
|
+
getPdsAccountInfo(ctx, result.did),
|
|
24
|
+
])
|
|
25
|
+
|
|
26
|
+
record.repo = addAccountInfoToRepoView(
|
|
27
|
+
record.repo,
|
|
28
|
+
accountInfo,
|
|
29
|
+
auth.credentials.moderator,
|
|
30
|
+
)
|
|
31
|
+
|
|
20
32
|
return {
|
|
21
33
|
encoding: 'application/json',
|
|
22
|
-
body:
|
|
34
|
+
body: record,
|
|
23
35
|
}
|
|
24
36
|
},
|
|
25
37
|
})
|
|
@@ -1,20 +1,31 @@
|
|
|
1
1
|
import { InvalidRequestError } from '@atproto/xrpc-server'
|
|
2
2
|
import { Server } from '../../../../lexicon'
|
|
3
3
|
import AppContext from '../../../../context'
|
|
4
|
+
import { addAccountInfoToRepoViewDetail, getPdsAccountInfo } from './util'
|
|
4
5
|
|
|
5
6
|
export default function (server: Server, ctx: AppContext) {
|
|
6
7
|
server.com.atproto.admin.getRepo({
|
|
7
8
|
auth: ctx.roleVerifier,
|
|
8
|
-
handler: async ({ params }) => {
|
|
9
|
+
handler: async ({ params, auth }) => {
|
|
9
10
|
const { did } = params
|
|
10
11
|
const db = ctx.db.getPrimary()
|
|
11
12
|
const result = await ctx.services.actor(db).getActor(did, true)
|
|
12
13
|
if (!result) {
|
|
13
14
|
throw new InvalidRequestError('Repo not found', 'RepoNotFound')
|
|
14
15
|
}
|
|
16
|
+
const [partialRepo, accountInfo] = await Promise.all([
|
|
17
|
+
ctx.services.moderation(db).views.repoDetail(result),
|
|
18
|
+
getPdsAccountInfo(ctx, result.did),
|
|
19
|
+
])
|
|
20
|
+
|
|
21
|
+
const repo = addAccountInfoToRepoViewDetail(
|
|
22
|
+
partialRepo,
|
|
23
|
+
accountInfo,
|
|
24
|
+
auth.credentials.moderator,
|
|
25
|
+
)
|
|
15
26
|
return {
|
|
16
27
|
encoding: 'application/json',
|
|
17
|
-
body:
|
|
28
|
+
body: repo,
|
|
18
29
|
}
|
|
19
30
|
},
|
|
20
31
|
})
|
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
AuthRequiredError,
|
|
3
|
+
InvalidRequestError,
|
|
4
|
+
UpstreamFailureError,
|
|
5
|
+
} from '@atproto/xrpc-server'
|
|
2
6
|
import {
|
|
3
7
|
ACKNOWLEDGE,
|
|
4
8
|
ESCALATE,
|
|
@@ -6,6 +10,7 @@ import {
|
|
|
6
10
|
} from '../../../../lexicon/types/com/atproto/admin/defs'
|
|
7
11
|
import { Server } from '../../../../lexicon'
|
|
8
12
|
import AppContext from '../../../../context'
|
|
13
|
+
import { retryHttp } from '../../../../util/retry'
|
|
9
14
|
|
|
10
15
|
export default function (server: Server, ctx: AppContext) {
|
|
11
16
|
server.com.atproto.admin.reverseModerationAction({
|
|
@@ -16,7 +21,7 @@ export default function (server: Server, ctx: AppContext) {
|
|
|
16
21
|
const moderationService = ctx.services.moderation(db)
|
|
17
22
|
const { id, createdBy, reason } = input.body
|
|
18
23
|
|
|
19
|
-
const
|
|
24
|
+
const { result, restored } = await db.transaction(async (dbTxn) => {
|
|
20
25
|
const moderationTxn = ctx.services.moderation(dbTxn)
|
|
21
26
|
const labelTxn = ctx.services.label(dbTxn)
|
|
22
27
|
const now = new Date()
|
|
@@ -53,7 +58,7 @@ export default function (server: Server, ctx: AppContext) {
|
|
|
53
58
|
)
|
|
54
59
|
}
|
|
55
60
|
|
|
56
|
-
const result = await moderationTxn.revertAction({
|
|
61
|
+
const { result, restored } = await moderationTxn.revertAction({
|
|
57
62
|
id,
|
|
58
63
|
createdAt: now,
|
|
59
64
|
createdBy,
|
|
@@ -77,12 +82,33 @@ export default function (server: Server, ctx: AppContext) {
|
|
|
77
82
|
{ create, negate },
|
|
78
83
|
)
|
|
79
84
|
|
|
80
|
-
return result
|
|
85
|
+
return { result, restored }
|
|
81
86
|
})
|
|
82
87
|
|
|
88
|
+
if (restored) {
|
|
89
|
+
const { did, subjects } = restored
|
|
90
|
+
const agent = await ctx.pdsAdminAgent(did)
|
|
91
|
+
const results = await Promise.allSettled(
|
|
92
|
+
subjects.map((subject) =>
|
|
93
|
+
retryHttp(() =>
|
|
94
|
+
agent.api.com.atproto.admin.updateSubjectStatus({
|
|
95
|
+
subject,
|
|
96
|
+
takedown: {
|
|
97
|
+
applied: false,
|
|
98
|
+
},
|
|
99
|
+
}),
|
|
100
|
+
),
|
|
101
|
+
),
|
|
102
|
+
)
|
|
103
|
+
const hadFailure = results.some((r) => r.status === 'rejected')
|
|
104
|
+
if (hadFailure) {
|
|
105
|
+
throw new UpstreamFailureError('failed to revert action on PDS')
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
83
109
|
return {
|
|
84
110
|
encoding: 'application/json',
|
|
85
|
-
body: await moderationService.views.action(
|
|
111
|
+
body: await moderationService.views.action(result),
|
|
86
112
|
}
|
|
87
113
|
},
|
|
88
114
|
})
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { InvalidRequestError } from '@atproto/xrpc-server'
|
|
2
1
|
import { Server } from '../../../../lexicon'
|
|
3
2
|
import AppContext from '../../../../context'
|
|
4
3
|
|
|
@@ -8,16 +7,14 @@ export default function (server: Server, ctx: AppContext) {
|
|
|
8
7
|
handler: async ({ params }) => {
|
|
9
8
|
const db = ctx.db.getPrimary()
|
|
10
9
|
const moderationService = ctx.services.moderation(db)
|
|
11
|
-
const {
|
|
12
|
-
if (invitedBy) {
|
|
13
|
-
throw new InvalidRequestError('The invitedBy parameter is unsupported')
|
|
14
|
-
}
|
|
10
|
+
const { limit, cursor } = params
|
|
15
11
|
// prefer new 'q' query param over deprecated 'term'
|
|
16
12
|
const query = params.q ?? params.term
|
|
17
13
|
|
|
18
14
|
const { results, cursor: resCursor } = await ctx.services
|
|
19
15
|
.actor(db)
|
|
20
16
|
.getSearchResults({ query, limit, cursor, includeSoftDeleted: true })
|
|
17
|
+
|
|
21
18
|
return {
|
|
22
19
|
encoding: 'application/json',
|
|
23
20
|
body: {
|