Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 73 additions & 0 deletions __tests__/users.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import {
checkUsernameAvailability,
createBlock,
createFollow,
deleteBlock,
deleteFollow,
fetchBlockStatus,
fetchBlockedUsers,
deleteUser,
fetchConnectionStatus,
fetchConnectionsByUserId,
Expand Down Expand Up @@ -196,6 +200,38 @@ describe("node-sdk users — graph actions — request shaping (actingUserId vs
params: { actingUserId: "requester1" },
});
});

it("createBlock posts actingUserId in the body, userId (target) in the path", async () => {
const { client, projectInstance } = makeClient();
await createBlock(client, { userId: "target1", actingUserId: "blocker1" });
expect(projectInstance.post).toHaveBeenCalledWith("/users/target1/block", {
actingUserId: "blocker1",
});
});

it("deleteBlock sends actingUserId as a param, userId (target) in the path", async () => {
const { client, projectInstance } = makeClient();
await deleteBlock(client, { userId: "target1", actingUserId: "blocker1" });
expect(projectInstance.delete).toHaveBeenCalledWith("/users/target1/block", {
params: { actingUserId: "blocker1" },
});
});

it("fetchBlockStatus sends actingUserId as a param, userId (target) in the path", async () => {
const { client, projectInstance } = makeClient();
await fetchBlockStatus(client, { userId: "target1", actingUserId: "blocker1" });
expect(projectInstance.get).toHaveBeenCalledWith("/users/target1/block", {
params: { actingUserId: "blocker1" },
});
});

it("fetchBlockedUsers hits /blocks with the actor as the userId query param", async () => {
const { client, projectInstance } = makeClient();
await fetchBlockedUsers(client, { actingUserId: "blocker1", page: 2 });
expect(projectInstance.get).toHaveBeenCalledWith("/blocks", {
params: { userId: "blocker1", page: 2 },
});
});
});

describe("node-sdk users — response mapping", () => {
Expand Down Expand Up @@ -339,4 +375,41 @@ describe("node-sdk users — response mapping", () => {
}),
).resolves.toEqual(result);
});

it("createBlock returns response.data", async () => {
const { client, projectInstance } = makeClient();
const block = { id: "blk1", blockerId: "blocker1", blockedId: "target1" };
projectInstance.post.mockResolvedValueOnce({ data: block });
await expect(
createBlock(client, { userId: "target1", actingUserId: "blocker1" }),
).resolves.toEqual(block);
});

it("deleteBlock resolves to undefined", async () => {
const { client } = makeClient();
await expect(
deleteBlock(client, { userId: "target1", actingUserId: "blocker1" }),
).resolves.toBeUndefined();
});

it("fetchBlockStatus returns response.data", async () => {
const { client, projectInstance } = makeClient();
const result = { blocked: true, blockId: "blk1" };
projectInstance.get.mockResolvedValueOnce({ data: result });
await expect(
fetchBlockStatus(client, { userId: "target1", actingUserId: "blocker1" }),
).resolves.toEqual(result);
});

it("fetchBlockedUsers returns the full PaginatedResponse envelope", async () => {
const { client, projectInstance } = makeClient();
const envelope = {
data: [{ id: "blk1", blockedUser: { id: "target1" }, createdAt: "now" }],
pagination: { page: 1, limit: 10, total: 1 },
};
projectInstance.get.mockResolvedValueOnce({ data: envelope });
await expect(
fetchBlockedUsers(client, { actingUserId: "blocker1" }),
).resolves.toEqual(envelope);
});
});
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@sublay/node",
"version": "7.7.3",
"version": "7.8.0",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"files": [
Expand Down
23 changes: 23 additions & 0 deletions src/interfaces/Block.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { User } from "./User";

/** A directed block edge: `blockerId` has blocked `blockedId`. */
export interface Block {
id: string;
blockerId: string;
blockedId: string;
createdAt: string;
}

/** Outbound-only block status: does the acting user block the target? */
export interface BlockStatusResponse {
blocked: boolean;
blockId?: string;
createdAt?: string;
}

/** A row in the acting user's own block list, carrying the blocked user's summary. */
export interface BlockedUser {
id: string;
blockedUser: User | null;
createdAt: string;
}
5 changes: 5 additions & 0 deletions src/modules/comments/fetchManyComments.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,11 @@ export interface FetchManyCommentsProps extends SpaceReputationContextParams {
sortDir?: "asc" | "desc";
include?: string;
sourceId?: string;

// Block filtering: comments authored by users the viewer is block-edged with
// are hidden by default ("exclude"). "include-outbound-blocked" re-includes
// ONLY the viewer's own outbound-blocked authors; never inbound.
blockedFilter?: "exclude" | "include-outbound-blocked";
}

export async function fetchManyComments(
Expand Down
6 changes: 6 additions & 0 deletions src/modules/entities/fetchManyEntities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,12 @@ export interface FetchManyEntitiesProps extends SpaceReputationContextParams {

// NSFW filtering (keyed off effective NSFW)
nsfwFilter?: "include-all" | "exclude" | "only";

// Block filtering: entities authored by users the viewer is block-edged with
// are hidden by default ("exclude"). "include-outbound-blocked" re-includes
// ONLY the viewer's own outbound-blocked authors (their "view anyway" choice);
// it never surfaces content from users who blocked the viewer.
blockedFilter?: "exclude" | "include-outbound-blocked";
}

export async function fetchManyEntities(
Expand Down
21 changes: 21 additions & 0 deletions src/modules/users/createBlock.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { SublayHttpClient } from "../../core/client";
import { Block } from "../../interfaces/Block";

export interface CreateBlockProps {
/** The user being blocked (the target). */
userId: string;
/** The user performing the block (the blocker). */
actingUserId: string;
}

export async function createBlock(
client: SublayHttpClient,
data: CreateBlockProps
): Promise<Block> {
const { userId, actingUserId } = data;
const response = await client.projectInstance.post<Block>(
`/users/${userId}/block`,
{ actingUserId }
);
return response.data;
}
18 changes: 18 additions & 0 deletions src/modules/users/deleteBlock.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { SublayHttpClient } from "../../core/client";

export interface DeleteBlockProps {
/** The user being unblocked (the target). */
userId: string;
/** The user performing the unblock (the blocker). */
actingUserId: string;
}

export async function deleteBlock(
client: SublayHttpClient,
data: DeleteBlockProps
): Promise<void> {
const { userId, actingUserId } = data;
await client.projectInstance.delete(`/users/${userId}/block`, {
params: { actingUserId },
});
}
21 changes: 21 additions & 0 deletions src/modules/users/fetchBlockStatus.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { SublayHttpClient } from "../../core/client";
import { BlockStatusResponse } from "../../interfaces/Block";

export interface FetchBlockStatusProps {
/** The user whose block relationship is being checked (the target). */
userId: string;
/** The user whose perspective the status is from (the blocker). */
actingUserId: string;
}

export async function fetchBlockStatus(
client: SublayHttpClient,
data: FetchBlockStatusProps
): Promise<BlockStatusResponse> {
const { userId, actingUserId } = data;
const response = await client.projectInstance.get<BlockStatusResponse>(
`/users/${userId}/block`,
{ params: { actingUserId } }
);
return response.data;
}
28 changes: 28 additions & 0 deletions src/modules/users/fetchBlockedUsers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { SublayHttpClient } from "../../core/client";
import { BlockedUser } from "../../interfaces/Block";
import { PaginatedResponse } from "../../interfaces/IPaginatedResponse";

export interface FetchBlockedUsersProps {
/**
* The user whose own outbound block list is being read (the blocker). The
* server resolves this via `resolveActingUserId` (service/master keys), so it
* is sent as the `userId` query param on the `/blocks` route — mirroring the
* server's act-on-behalf handling for the private block list.
*/
actingUserId: string;
page?: number;
limit?: number;
}

export async function fetchBlockedUsers(
client: SublayHttpClient,
data: FetchBlockedUsersProps
): Promise<PaginatedResponse<BlockedUser>> {
const { actingUserId, ...rest } = data;
const response = await client.projectInstance.get<
PaginatedResponse<BlockedUser>
>(`/blocks`, {
params: { userId: actingUserId, ...rest },
});
return response.data;
}
4 changes: 4 additions & 0 deletions src/modules/users/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,7 @@ export { fetchFollowStatus } from "./fetchFollowStatus";
export { requestConnection } from "./requestConnection";
export { fetchConnectionStatus } from "./fetchConnectionStatus";
export { removeConnectionByUserId } from "./removeConnectionByUserId";
export { createBlock } from "./createBlock";
export { deleteBlock } from "./deleteBlock";
export { fetchBlockStatus } from "./fetchBlockStatus";
export { fetchBlockedUsers } from "./fetchBlockedUsers";
Loading