From 5030d03404770e58c8e9a993b1dbb6c75dfe72ba Mon Sep 17 00:00:00 2001 From: Jesse Wierzbinski Date: Mon, 15 Apr 2024 20:48:36 -1000 Subject: [PATCH] feat(api): :sparkles: Implement glitch-soc /v1/notifications/destroy_multiple route --- .../destroy_multiple/index.test.ts | 112 ++++++++++++++++++ .../notifications/destroy_multiple/index.ts | 41 +++++++ 2 files changed, 153 insertions(+) create mode 100644 server/api/api/v1/notifications/destroy_multiple/index.test.ts create mode 100644 server/api/api/v1/notifications/destroy_multiple/index.ts diff --git a/server/api/api/v1/notifications/destroy_multiple/index.test.ts b/server/api/api/v1/notifications/destroy_multiple/index.test.ts new file mode 100644 index 00000000..fd89d4a7 --- /dev/null +++ b/server/api/api/v1/notifications/destroy_multiple/index.test.ts @@ -0,0 +1,112 @@ +import { afterAll, beforeAll, describe, expect, test } from "bun:test"; +import { config } from "config-manager"; +import { + deleteOldTestUsers, + getTestStatuses, + getTestUsers, + sendTestRequest, +} from "~tests/utils"; +import type { Notification as APINotification } from "~types/mastodon/notification"; +import { meta } from "./index"; + +await deleteOldTestUsers(); + +const { users, tokens, deleteUsers } = await getTestUsers(2); +const statuses = await getTestStatuses(40, users[0]); +let notifications: APINotification[] = []; + +// Create some test notifications +beforeAll(async () => { + await fetch( + new URL(`/api/v1/accounts/${users[0].id}/follow`, config.http.base_url), + { + method: "POST", + headers: { + Authorization: `Bearer ${tokens[1].accessToken}`, + }, + }, + ); + + for (const i of [0, 1, 2, 3]) { + await fetch( + new URL( + `/api/v1/statuses/${statuses[i].id}/favourite`, + config.http.base_url, + ), + { + method: "POST", + headers: { + Authorization: `Bearer ${tokens[1].accessToken}`, + }, + }, + ); + } + + notifications = await fetch( + new URL("/api/v1/notifications", config.http.base_url), + { + headers: { + Authorization: `Bearer ${tokens[0].accessToken}`, + }, + }, + ).then((r) => r.json()); + + expect(notifications.length).toBe(5); +}); + +afterAll(async () => { + await deleteUsers(); +}); + +// /api/v1/notifications/destroy_multiple +describe(meta.route, () => { + test("should return 401 if not authenticated", async () => { + const response = await sendTestRequest( + new Request(new URL(meta.route, config.http.base_url), { + method: "DELETE", + }), + ); + + expect(response.status).toBe(401); + }); + + test("should dismiss notifications", async () => { + const response = await sendTestRequest( + new Request( + new URL( + `${meta.route}?${new URLSearchParams( + notifications.slice(1).map((n) => ["ids[]", n.id]), + ).toString()}`, + config.http.base_url, + ), + { + method: "DELETE", + headers: { + Authorization: `Bearer ${tokens[0].accessToken}`, + }, + }, + ), + ); + + expect(response.status).toBe(200); + }); + + test("should not display dismissed notification", async () => { + const response = await sendTestRequest( + new Request( + new URL("/api/v1/notifications", config.http.base_url), + { + headers: { + Authorization: `Bearer ${tokens[0].accessToken}`, + }, + }, + ), + ); + + expect(response.status).toBe(200); + + const output = await response.json(); + + expect(output.length).toBe(1); + }); +}); diff --git a/server/api/api/v1/notifications/destroy_multiple/index.ts b/server/api/api/v1/notifications/destroy_multiple/index.ts new file mode 100644 index 00000000..f34bc782 --- /dev/null +++ b/server/api/api/v1/notifications/destroy_multiple/index.ts @@ -0,0 +1,41 @@ +import { apiRoute, applyConfig, idValidator } from "@api"; +import { errorResponse, jsonResponse } from "@response"; +import { inArray } from "drizzle-orm"; +import { z } from "zod"; +import { db } from "~drizzle/db"; +import { notification } from "~drizzle/schema"; + +export const meta = applyConfig({ + allowedMethods: ["DELETE"], + route: "/api/v1/notifications/destroy_multiple", + ratelimits: { + max: 100, + duration: 60, + }, + auth: { + required: true, + oauthPermissions: ["write:notifications"], + }, +}); + +export const schema = z.object({ + ids: z.array(z.string().regex(idValidator)), +}); + +export default apiRoute( + async (req, matchedRoute, extraData) => { + const { user } = extraData.auth; + if (!user) return errorResponse("Unauthorized", 401); + + const { ids } = extraData.parsedRequest; + + await db + .update(notification) + .set({ + dismissed: true, + }) + .where(inArray(notification.id, ids)); + + return jsonResponse({}); + }, +);