mirror of
https://github.com/versia-pub/server.git
synced 2025-12-07 16:58:20 +01:00
feat(api): ✨ Add notifications for follow requests again and mentions
This commit is contained in:
parent
06bcbbe451
commit
47133ac3fe
|
|
@ -35,6 +35,7 @@ import {
|
||||||
status,
|
status,
|
||||||
statusToMentions,
|
statusToMentions,
|
||||||
user,
|
user,
|
||||||
|
notification,
|
||||||
} from "~drizzle/schema";
|
} from "~drizzle/schema";
|
||||||
import { LogLevel } from "~packages/log-manager";
|
import { LogLevel } from "~packages/log-manager";
|
||||||
import type { Note } from "~types/lysand/Object";
|
import type { Note } from "~types/lysand/Object";
|
||||||
|
|
@ -995,6 +996,18 @@ export const createNewStatus = async (
|
||||||
.where(inArray(attachment.id, media_attachments));
|
.where(inArray(attachment.id, media_attachments));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Send notifications for mentioned local users
|
||||||
|
for (const mention of mentions ?? []) {
|
||||||
|
if (mention.instanceId === null) {
|
||||||
|
await db.insert(notification).values({
|
||||||
|
accountId: author.id,
|
||||||
|
notifiedId: mention.id,
|
||||||
|
type: "mention",
|
||||||
|
statusId: newStatus.id,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
(await findFirstStatuses({
|
(await findFirstStatuses({
|
||||||
where: (status, { eq }) => eq(status.id, newStatus.id),
|
where: (status, { eq }) => eq(status.id, newStatus.id),
|
||||||
|
|
@ -1146,6 +1159,18 @@ export const editStatus = async (
|
||||||
.execute();
|
.execute();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Send notifications for mentioned local users
|
||||||
|
for (const mention of mentions ?? []) {
|
||||||
|
if (mention.instanceId === null) {
|
||||||
|
await db.insert(notification).values({
|
||||||
|
accountId: statusToEdit.authorId,
|
||||||
|
notifiedId: mention.id,
|
||||||
|
type: "mention",
|
||||||
|
statusId: updated.id,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Set attachment parents
|
// Set attachment parents
|
||||||
await db
|
await db
|
||||||
.update(attachment)
|
.update(attachment)
|
||||||
|
|
|
||||||
|
|
@ -172,7 +172,7 @@ export const followRequestUser = async (
|
||||||
notify = false,
|
notify = false,
|
||||||
languages: string[] = [],
|
languages: string[] = [],
|
||||||
): Promise<InferSelectModel<typeof relationship>> => {
|
): Promise<InferSelectModel<typeof relationship>> => {
|
||||||
const isRemote = follower.instanceId !== followee.instanceId;
|
const isRemote = followee.instanceId !== null;
|
||||||
|
|
||||||
const updatedRelationship = (
|
const updatedRelationship = (
|
||||||
await db
|
await db
|
||||||
|
|
@ -226,9 +226,9 @@ export const followRequestUser = async (
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
await db.insert(notification).values({
|
await db.insert(notification).values({
|
||||||
accountId: followee.id,
|
accountId: follower.id,
|
||||||
type: followee.isLocked ? "follow_request" : "follow",
|
type: followee.isLocked ? "follow_request" : "follow",
|
||||||
notifiedId: follower.id,
|
notifiedId: followee.id,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
110
server/api/api/v1/notifications/index.test.ts
Normal file
110
server/api/api/v1/notifications/index.test.ts
Normal file
|
|
@ -0,0 +1,110 @@
|
||||||
|
import { afterAll, beforeAll, describe, expect, test } from "bun:test";
|
||||||
|
import { config } from "config-manager";
|
||||||
|
import {
|
||||||
|
deleteOldTestUsers,
|
||||||
|
getTestStatuses,
|
||||||
|
getTestUsers,
|
||||||
|
sendTestRequest,
|
||||||
|
} from "~tests/utils";
|
||||||
|
import { meta } from "./index";
|
||||||
|
import type { Notification as APINotification } from "~types/mastodon/notification";
|
||||||
|
|
||||||
|
await deleteOldTestUsers();
|
||||||
|
|
||||||
|
const { users, tokens, deleteUsers } = await getTestUsers(2);
|
||||||
|
const timeline = (await getTestStatuses(40, users[0])).toReversed();
|
||||||
|
// Create some test notifications: follow, favourite, reblog, mention
|
||||||
|
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}`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
await fetch(
|
||||||
|
new URL(
|
||||||
|
`/api/v1/statuses/${timeline[0].id}/favourite`,
|
||||||
|
config.http.base_url,
|
||||||
|
),
|
||||||
|
{
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${tokens[1].accessToken}`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
await fetch(
|
||||||
|
new URL(
|
||||||
|
`/api/v1/statuses/${timeline[0].id}/reblog`,
|
||||||
|
config.http.base_url,
|
||||||
|
),
|
||||||
|
{
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${tokens[1].accessToken}`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
await fetch(new URL("/api/v1/statuses", config.http.base_url), {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${tokens[1].accessToken}`,
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
status: `@${users[0].username} test mention`,
|
||||||
|
visibility: "direct",
|
||||||
|
federate: false,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
afterAll(async () => {
|
||||||
|
await deleteUsers();
|
||||||
|
});
|
||||||
|
|
||||||
|
// /api/v1/notifications
|
||||||
|
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)),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(response.status).toBe(401);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should return 200 with notifications", async () => {
|
||||||
|
const response = await sendTestRequest(
|
||||||
|
new Request(new URL(meta.route, config.http.base_url), {
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${tokens[0].accessToken}`,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(response.status).toBe(200);
|
||||||
|
expect(response.headers.get("content-type")).toBe("application/json");
|
||||||
|
|
||||||
|
const objects = (await response.json()) as APINotification[];
|
||||||
|
|
||||||
|
expect(objects.length).toBe(4);
|
||||||
|
for (const [index, notification] of objects.entries()) {
|
||||||
|
expect(notification.account).toBeDefined();
|
||||||
|
expect(notification.account?.id).toBe(users[1].id);
|
||||||
|
expect(notification.created_at).toBeDefined();
|
||||||
|
expect(notification.id).toBeDefined();
|
||||||
|
expect(notification.type).toBeDefined();
|
||||||
|
expect(notification.type).toBe(
|
||||||
|
["follow", "favourite", "reblog", "mention"].toReversed()[
|
||||||
|
index
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -47,7 +47,6 @@ export const schema = z.object({
|
||||||
"group_reblog",
|
"group_reblog",
|
||||||
"group_favourite",
|
"group_favourite",
|
||||||
"user_approved",
|
"user_approved",
|
||||||
"update",
|
|
||||||
])
|
])
|
||||||
.array()
|
.array()
|
||||||
.optional(),
|
.optional(),
|
||||||
|
|
@ -73,7 +72,6 @@ export const schema = z.object({
|
||||||
"group_reblog",
|
"group_reblog",
|
||||||
"group_favourite",
|
"group_favourite",
|
||||||
"user_approved",
|
"user_approved",
|
||||||
"update",
|
|
||||||
])
|
])
|
||||||
.array()
|
.array()
|
||||||
.optional(),
|
.optional(),
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue