From d2d2e576a94dc1a9c80c25ab9c3fd035c7f7bad6 Mon Sep 17 00:00:00 2001 From: Jesse Wierzbinski Date: Fri, 22 Sep 2023 12:25:10 -1000 Subject: [PATCH] Add pin and unpin endpoints for account --- README.md | 2 + server/api/api/v1/accounts/[id]/mute.ts | 2 +- server/api/api/v1/accounts/[id]/pin.ts | 52 +++++++++++++++++++++++ server/api/api/v1/accounts/[id]/unmute.ts | 2 +- server/api/api/v1/accounts/[id]/unpin.ts | 52 +++++++++++++++++++++++ tests/api.test.ts | 48 +++++++++++++++++++++ 6 files changed, 156 insertions(+), 2 deletions(-) create mode 100644 server/api/api/v1/accounts/[id]/pin.ts create mode 100644 server/api/api/v1/accounts/[id]/unpin.ts diff --git a/README.md b/README.md index d8de86c8..5c80a352 100644 --- a/README.md +++ b/README.md @@ -72,6 +72,8 @@ Working endpoints are: - `/api/v1/accounts/:id/unblock` - `/api/v1/accounts/:id/mute` - `/api/v1/accounts/:id/unmute` +- `/api/v1/accounts/:id/pin` +- `/api/v1/accounts/:id/unpin` - `/api/v1/accounts/update_credentials` - `/api/v1/accounts/verify_credentials` - `/api/v1/statuses` diff --git a/server/api/api/v1/accounts/[id]/mute.ts b/server/api/api/v1/accounts/[id]/mute.ts index 22218dcb..4396db3f 100644 --- a/server/api/api/v1/accounts/[id]/mute.ts +++ b/server/api/api/v1/accounts/[id]/mute.ts @@ -6,7 +6,7 @@ import { Relationship } from "~database/entities/Relationship"; import { User } from "~database/entities/User"; /** - * Follow a user + * Mute a user */ export default async ( req: Request, diff --git a/server/api/api/v1/accounts/[id]/pin.ts b/server/api/api/v1/accounts/[id]/pin.ts new file mode 100644 index 00000000..4f495d5e --- /dev/null +++ b/server/api/api/v1/accounts/[id]/pin.ts @@ -0,0 +1,52 @@ +import { getUserByToken } from "@auth"; +import { errorResponse, jsonResponse } from "@response"; +import { MatchedRoute } from "bun"; +import { Relationship } from "~database/entities/Relationship"; +import { User } from "~database/entities/User"; + +/** + * Pin a user + */ +export default async ( + req: Request, + matchedRoute: MatchedRoute +): Promise => { + const id = matchedRoute.params.id; + + // Check auth token + const token = req.headers.get("Authorization")?.split(" ")[1] || null; + + if (!token) + return errorResponse("This method requires an authenticated user", 422); + + const self = await getUserByToken(token); + + if (!self) return errorResponse("Unauthorized", 401); + + const user = await User.findOneBy({ + id, + }); + + if (!user) return errorResponse("User not found", 404); + + // Check if already following + let relationship = await self.getRelationshipToOtherUser(user); + + if (!relationship) { + // Create new relationship + + const newRelationship = await Relationship.createNew(self, user); + + self.relationships.push(newRelationship); + await self.save(); + + relationship = newRelationship; + } + + if (!relationship.endorsed) { + relationship.endorsed = true; + } + + await relationship.save(); + return jsonResponse(await relationship.toAPI()); +}; diff --git a/server/api/api/v1/accounts/[id]/unmute.ts b/server/api/api/v1/accounts/[id]/unmute.ts index 912ae511..21822776 100644 --- a/server/api/api/v1/accounts/[id]/unmute.ts +++ b/server/api/api/v1/accounts/[id]/unmute.ts @@ -5,7 +5,7 @@ import { Relationship } from "~database/entities/Relationship"; import { User } from "~database/entities/User"; /** - * Follow a user + * Unmute a user */ export default async ( req: Request, diff --git a/server/api/api/v1/accounts/[id]/unpin.ts b/server/api/api/v1/accounts/[id]/unpin.ts new file mode 100644 index 00000000..44a7ecbd --- /dev/null +++ b/server/api/api/v1/accounts/[id]/unpin.ts @@ -0,0 +1,52 @@ +import { getUserByToken } from "@auth"; +import { errorResponse, jsonResponse } from "@response"; +import { MatchedRoute } from "bun"; +import { Relationship } from "~database/entities/Relationship"; +import { User } from "~database/entities/User"; + +/** + * Unpin a user + */ +export default async ( + req: Request, + matchedRoute: MatchedRoute +): Promise => { + const id = matchedRoute.params.id; + + // Check auth token + const token = req.headers.get("Authorization")?.split(" ")[1] || null; + + if (!token) + return errorResponse("This method requires an authenticated user", 422); + + const self = await getUserByToken(token); + + if (!self) return errorResponse("Unauthorized", 401); + + const user = await User.findOneBy({ + id, + }); + + if (!user) return errorResponse("User not found", 404); + + // Check if already following + let relationship = await self.getRelationshipToOtherUser(user); + + if (!relationship) { + // Create new relationship + + const newRelationship = await Relationship.createNew(self, user); + + self.relationships.push(newRelationship); + await self.save(); + + relationship = newRelationship; + } + + if (relationship.endorsed) { + relationship.endorsed = false; + } + + await relationship.save(); + return jsonResponse(await relationship.toAPI()); +}; diff --git a/tests/api.test.ts b/tests/api.test.ts index 2ac8ca3e..de8f2e3a 100644 --- a/tests/api.test.ts +++ b/tests/api.test.ts @@ -411,6 +411,54 @@ describe("POST /api/v1/accounts/:id/unmute", () => { }); }); +describe("POST /api/v1/accounts/:id/pin", () => { + test("should pin the specified user and return an APIRelationship object", async () => { + const response = await fetch( + `${config.http.base_url}/api/v1/accounts/${user2.id}/pin`, + { + method: "POST", + headers: { + Authorization: `Bearer ${token.access_token}`, + "Content-Type": "application/json", + }, + body: JSON.stringify({}), + } + ); + + expect(response.status).toBe(200); + expect(response.headers.get("content-type")).toBe("application/json"); + + const account: APIRelationship = await response.json(); + + expect(account.id).toBe(user2.id); + expect(account.endorsed).toBe(true); + }); +}); + +describe("POST /api/v1/accounts/:id/unpin", () => { + test("should unpin the specified user and return an APIRelationship object", async () => { + const response = await fetch( + `${config.http.base_url}/api/v1/accounts/${user2.id}/unpin`, + { + method: "POST", + headers: { + Authorization: `Bearer ${token.access_token}`, + "Content-Type": "application/json", + }, + body: JSON.stringify({}), + } + ); + + expect(response.status).toBe(200); + expect(response.headers.get("content-type")).toBe("application/json"); + + const account: APIRelationship = await response.json(); + + expect(account.id).toBe(user2.id); + expect(account.endorsed).toBe(false); + }); +}); + afterAll(async () => { const activities = await RawActivity.createQueryBuilder("activity") .where("activity.data->>'actor' = :actor", {