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
101 changes: 101 additions & 0 deletions __tests__/nsfw.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import { setSpaceEntityNsfw, fetchManySpaces } from "../src/modules/spaces";
import { createEntity, updateEntity, fetchManyEntities } from "../src/modules/entities";
import { createSpace, updateSpace } from "../src/modules/spaces";
import { makeClient } from "./helpers/mockClient";
import type { Entity } from "../src/interfaces/Entity";
import type { Space } from "../src/interfaces/Space";

/**
* NSFW flagging surface (Phase 5, Task 5.2). Asserts the new mod function hits
* the exact server route `.../spaces/:spaceId/entities/:entityId/nsfw` and that
* the `nsfw` create/update flags + `nsfwFilter` fetch param are shaped through
* to the request. Type-level assertions below guard the interface fields.
*/
describe("node-sdk NSFW — request shaping", () => {
it("setSpaceEntityNsfw patches /spaces/:spaceId/entities/:entityId/nsfw with { nsfw }", async () => {
const { client, projectInstance } = makeClient();
await setSpaceEntityNsfw(client, {
spaceId: "s1",
entityId: "e1",
nsfw: true,
});
expect(projectInstance.patch).toHaveBeenCalledWith(
"/spaces/s1/entities/e1/nsfw",
{ nsfw: true },
);
});

it("setSpaceEntityNsfw returns response.data", async () => {
const { client, projectInstance } = makeClient();
const result = { message: "Entity NSFW flag updated.", nsfw: true };
projectInstance.patch.mockResolvedValueOnce({ data: result });
await expect(
setSpaceEntityNsfw(client, { spaceId: "s1", entityId: "e1", nsfw: true }),
).resolves.toEqual(result);
});

it("createEntity forwards the own nsfw flag in the body", async () => {
const { client, projectInstance } = makeClient();
await createEntity(client, { nsfw: true });
expect(projectInstance.post).toHaveBeenCalledWith(
"/entities",
expect.objectContaining({ nsfw: true }),
);
});

it("updateEntity forwards the own nsfw flag in the body", async () => {
const { client, projectInstance } = makeClient();
await updateEntity(client, { entityId: "e1", nsfw: false });
const [, body] = projectInstance.patch.mock.calls[0];
expect(body).toEqual(expect.objectContaining({ nsfw: false }));
});

it("fetchManyEntities forwards nsfwFilter into the query params", async () => {
const { client, projectInstance } = makeClient();
await fetchManyEntities(client, { nsfwFilter: "only" });
const [, config] = projectInstance.get.mock.calls[0];
expect(config.params).toEqual(expect.objectContaining({ nsfwFilter: "only" }));
});

it("createSpace forwards the own nsfw flag in the body", async () => {
const { client, projectInstance } = makeClient();
await createSpace(client, { name: "NSFW space", userId: "u1", nsfw: true });
expect(projectInstance.post).toHaveBeenCalledWith(
"/spaces",
expect.objectContaining({ nsfw: true }),
);
});

it("updateSpace forwards the own nsfw flag in the body", async () => {
const { client, projectInstance } = makeClient();
await updateSpace(client, { spaceId: "s1", nsfw: true });
const [, body] = projectInstance.patch.mock.calls[0];
expect(body).toEqual(expect.objectContaining({ nsfw: true }));
});

it("fetchManySpaces forwards nsfwFilter into the query params", async () => {
const { client, projectInstance } = makeClient();
await fetchManySpaces(client, { nsfwFilter: "exclude" });
const [, config] = projectInstance.get.mock.calls[0];
expect(config.params).toEqual(expect.objectContaining({ nsfwFilter: "exclude" }));
});
});

// Compile-time guards: the model interfaces carry the new NSFW fields. These
// are type-level assertions — if the fields are dropped or mistyped, `tsc`
// (and the jest ts-jest transform) fails to compile this file.
describe("node-sdk NSFW — interface fields (type-level)", () => {
it("Entity exposes nsfw + nsfwEffective as booleans", () => {
const e = {} as Entity;
const nsfw: boolean | undefined = e.nsfw;
const eff: boolean | undefined = e.nsfwEffective;
expect([nsfw, eff]).toBeDefined();
});

it("Space exposes nsfw + nsfwEffective as booleans", () => {
const s = {} as Space;
const nsfw: boolean | undefined = s.nsfw;
const eff: boolean | undefined = s.nsfwEffective;
expect([nsfw, eff]).toBeDefined();
});
});
2 changes: 2 additions & 0 deletions src/interfaces/Entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ export interface Entity {
coordinates: [number, number]; // [longitude, latitude]
} | null;
metadata: Record<string, any>;
nsfw: boolean; // The entity's own NSFW flag
nsfwEffective: boolean; // Computed live: entity.nsfw OR the entity's space's effective NSFW
topComment: TopComment | null;
isSaved?: boolean; // Populated when include contains "saved"
isDraft: boolean | null;
Expand Down
2 changes: 2 additions & 0 deletions src/interfaces/Space.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ export interface Space {
postingPermission: PostingPermission;
visibility: SpaceVisibility;
requireJoinApproval: boolean;
nsfw: boolean; // The space's own NSFW flag
nsfwEffective: boolean; // Denormalized: own nsfw OR any ancestor space's nsfw
parentSpaceId: string | null;
depth: number;
metadata: Record<string, any>;
Expand Down
1 change: 1 addition & 0 deletions src/modules/entities/createEntity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export interface CreateEntityProps {
longitude: number;
};
metadata?: Record<string, any>;
nsfw?: boolean;
userId?: string;
isDraft?: boolean;
excludeUserId?: boolean;
Expand Down
3 changes: 3 additions & 0 deletions src/modules/entities/fetchManyEntities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,9 @@ export interface FetchManyEntitiesProps extends SpaceReputationContextParams {

// Location filtering
locationFilters?: LocationFilters;

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

export async function fetchManyEntities(
Expand Down
1 change: 1 addition & 0 deletions src/modules/entities/updateEntity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export interface UpdateEntityProps {
longitude: number;
};
metadata?: Record<string, any>;
nsfw?: boolean;
}

export async function updateEntity(
Expand Down
1 change: 1 addition & 0 deletions src/modules/spaces/createSpace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export interface CreateSpaceProps {
requireJoinApproval?: boolean;
parentSpaceId?: string;
metadata?: Record<string, any>;
nsfw?: boolean;
}

export async function createSpace(
Expand Down
1 change: 1 addition & 0 deletions src/modules/spaces/fetchManySpaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export interface FetchManySpacesProps {
searchAny?: string;
memberOf?: "true";
parentSpaceId?: string | "null";
nsfwFilter?: "include-all" | "exclude" | "only";
include?: string;
}

Expand Down
1 change: 1 addition & 0 deletions src/modules/spaces/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export { reorderRules } from "./reorderRules";
export { handleEntityReport } from "./handleEntityReport";
export { handleCommentReport } from "./handleCommentReport";
export { moderateSpaceEntity } from "./moderateSpaceEntity";
export { setSpaceEntityNsfw } from "./setSpaceEntityNsfw";
export { moderateSpaceComment } from "./moderateSpaceComment";
export { fetchDigestConfig } from "./fetchDigestConfig";
export { updateDigestConfig } from "./updateDigestConfig";
Expand Down
29 changes: 29 additions & 0 deletions src/modules/spaces/setSpaceEntityNsfw.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { SublayHttpClient } from "../../core/client";

export interface SetSpaceEntityNsfwProps {
spaceId: string;
entityId: string;
nsfw: boolean;
}

export interface SetSpaceEntityNsfwResponse {
message: string;
nsfw: boolean;
}

/**
* Space-moderator endpoint to set the NSFW flag on an entity within their space
* (including other users' entities). Near-copy of `moderateSpaceEntity`, but it
* writes the entity's own `nsfw` (the NSFW axis), not `moderationStatus`.
*/
export async function setSpaceEntityNsfw(
client: SublayHttpClient,
data: SetSpaceEntityNsfwProps
): Promise<SetSpaceEntityNsfwResponse> {
const { spaceId, entityId, ...body } = data;
const response = await client.projectInstance.patch<SetSpaceEntityNsfwResponse>(
`/spaces/${spaceId}/entities/${entityId}/nsfw`,
body
);
return response.data;
}
1 change: 1 addition & 0 deletions src/modules/spaces/updateSpace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export interface UpdateSpaceProps {
postingPermission?: PostingPermission;
visibility?: SpaceVisibility;
metadata?: Record<string, any>;
nsfw?: boolean;
}

export async function updateSpace(
Expand Down
Loading