iceshrimp/packages/backend/src/server/api/endpoints/i/notifications.ts
Laura Hausmann 0cb6f578ad
[backend] Don't join avatar and banner on i/notifications
This was an oversight on the recent database refactor and is no longer required
2023-12-14 20:34:24 +01:00

179 lines
4.6 KiB
TypeScript

import { Brackets } from "typeorm";
import {
Notifications,
Followings,
Mutings,
Users,
UserProfiles,
} from "@/models/index.js";
import { notificationTypes } from "@/types.js";
import read from "@/services/note/read.js";
import { readNotification } from "../../common/read-notification.js";
import define from "../../define.js";
import { makePaginationQuery } from "../../common/make-pagination-query.js";
export const meta = {
tags: ["account", "notifications"],
requireCredential: true,
limit: {
duration: 60000,
max: 15,
},
kind: "read:notifications",
res: {
type: "array",
optional: false,
nullable: false,
items: {
type: "object",
optional: false,
nullable: false,
ref: "Notification",
},
},
} as const;
export const paramDef = {
type: "object",
properties: {
limit: { type: "integer", minimum: 1, maximum: 100, default: 10 },
sinceId: { type: "string", format: "misskey:id" },
untilId: { type: "string", format: "misskey:id" },
following: { type: "boolean", default: false },
unreadOnly: { type: "boolean", default: false },
markAsRead: { type: "boolean", default: true },
includeTypes: {
type: "array",
items: {
type: "string",
enum: notificationTypes,
},
},
excludeTypes: {
type: "array",
items: {
type: "string",
enum: notificationTypes,
},
},
},
required: [],
} as const;
export default define(meta, paramDef, async (ps, user) => {
// includeTypes が空の場合はクエリしない
if (ps.includeTypes && ps.includeTypes.length === 0) {
return [];
}
// excludeTypes に全指定されている場合はクエリしない
if (notificationTypes.every((type) => ps.excludeTypes?.includes(type))) {
return [];
}
const followingQuery = Followings.createQueryBuilder("following")
.select("following.followeeId")
.where("following.followerId = :followerId", { followerId: user.id });
const mutingQuery = Mutings.createQueryBuilder("muting")
.select("muting.muteeId")
.where("muting.muterId = :muterId", { muterId: user.id });
const mutingInstanceQuery = UserProfiles.createQueryBuilder("user_profile")
.select("user_profile.mutedInstances")
.where("user_profile.userId = :muterId", { muterId: user.id });
const suspendedQuery = Users.createQueryBuilder("users")
.select("users.id")
.where("users.isSuspended = TRUE");
const query = makePaginationQuery(
Notifications.createQueryBuilder("notification"),
ps.sinceId,
ps.untilId,
)
.andWhere("notification.notifieeId = :meId", { meId: user.id })
.leftJoinAndSelect("notification.notifier", "notifier")
.leftJoinAndSelect("notification.note", "note")
.leftJoinAndSelect("note.user", "user")
.leftJoinAndSelect("note.reply", "reply")
.leftJoinAndSelect("note.renote", "renote")
.leftJoinAndSelect("reply.user", "replyUser")
.leftJoinAndSelect("renote.user", "renoteUser");
// muted users
query.andWhere(
new Brackets((qb) => {
qb.where(
`notification.notifierId NOT IN (${mutingQuery.getQuery()})`,
).orWhere("notification.notifierId IS NULL");
}),
);
query.setParameters(mutingQuery.getParameters());
// muted instances
query.andWhere(
new Brackets((qb) => {
qb.andWhere("notifier.host IS NULL").orWhere(
`NOT (( ${mutingInstanceQuery.getQuery()} )::jsonb ? notifier.host)`,
);
}),
);
query.setParameters(mutingInstanceQuery.getParameters());
// suspended users
query.andWhere(
new Brackets((qb) => {
qb.where(
`notification.notifierId NOT IN (${suspendedQuery.getQuery()})`,
).orWhere("notification.notifierId IS NULL");
}),
);
if (ps.following) {
query.andWhere(
`((notification.notifierId IN (${followingQuery.getQuery()})) OR (notification.notifierId = :meId))`,
{ meId: user.id },
);
query.setParameters(followingQuery.getParameters());
}
if (ps.includeTypes && ps.includeTypes.length > 0) {
query.andWhere("notification.type IN (:...includeTypes)", {
includeTypes: ps.includeTypes,
});
} else if (ps.excludeTypes && ps.excludeTypes.length > 0) {
query.andWhere("notification.type NOT IN (:...excludeTypes)", {
excludeTypes: ps.excludeTypes,
});
}
if (ps.unreadOnly) {
query.andWhere("notification.isRead = false");
}
const notifications = await query.take(ps.limit).getMany();
// Mark all as read
if (notifications.length > 0 && ps.markAsRead) {
readNotification(
user.id,
notifications.map((x) => x.id),
);
}
const notes = notifications
.filter((notification) =>
["mention", "reply", "quote"].includes(notification.type),
)
.map((notification) => notification.note!);
if (notes.length > 0) {
read(user.id, notes);
}
return await Notifications.packMany(notifications, user.id);
});