perf (backend): improved post search with CW/alt text
Co-authored-by: naskya <m@naskya.net>
This commit is contained in:
parent
2220d5c56e
commit
cca63b7286
1 changed files with 107 additions and 75 deletions
|
@ -1,4 +1,3 @@
|
|||
import { Brackets } from "typeorm";
|
||||
import { Notes } from "@/models/index.js";
|
||||
import { Note } from "@/models/entities/note.js";
|
||||
import define from "@/server/api/define.js";
|
||||
|
@ -7,6 +6,7 @@ import { generateVisibilityQuery } from "@/server/api/common/generate-visibility
|
|||
import { generateMutedUserQuery } from "@/server/api/common/generate-muted-user-query.js";
|
||||
import { generateBlockedUserQuery } from "@/server/api/common/generate-block-query.js";
|
||||
import { sqlLikeEscape } from "@/misc/sql-like-escape.js";
|
||||
import type { SelectQueryBuilder } from "typeorm";
|
||||
|
||||
export const meta = {
|
||||
tags: ["notes"],
|
||||
|
@ -69,91 +69,123 @@ export const paramDef = {
|
|||
} as const;
|
||||
|
||||
export default define(meta, paramDef, async (ps, me) => {
|
||||
const query = makePaginationQuery(
|
||||
Notes.createQueryBuilder("note"),
|
||||
ps.sinceId,
|
||||
ps.untilId,
|
||||
ps.sinceDate ?? undefined,
|
||||
ps.untilDate ?? undefined,
|
||||
);
|
||||
async function search(
|
||||
modifier?: (query: SelectQueryBuilder<Note>) => void,
|
||||
): Promise<Note[]> {
|
||||
const query = makePaginationQuery(
|
||||
Notes.createQueryBuilder("note"),
|
||||
ps.sinceId,
|
||||
ps.untilId,
|
||||
ps.sinceDate ?? undefined,
|
||||
ps.untilDate ?? undefined,
|
||||
);
|
||||
modifier?.(query);
|
||||
|
||||
if (ps.userId != null) {
|
||||
query.andWhere("note.userId = :userId", { userId: ps.userId });
|
||||
if (ps.userId != null) {
|
||||
query.andWhere("note.userId = :userId", { userId: ps.userId });
|
||||
}
|
||||
|
||||
if (ps.channelId != null) {
|
||||
query.andWhere("note.channelId = :channelId", {
|
||||
channelId: ps.channelId,
|
||||
});
|
||||
}
|
||||
|
||||
query.innerJoinAndSelect("note.user", "user");
|
||||
|
||||
// "from: me": search all (public, home, followers, specified) my posts
|
||||
// otherwise: search public indexable posts only
|
||||
if (ps.userId == null || ps.userId !== me?.id) {
|
||||
query
|
||||
.andWhere("note.visibility = 'public'")
|
||||
.andWhere("user.isIndexable = TRUE");
|
||||
}
|
||||
|
||||
if (ps.userId != null) {
|
||||
query.andWhere("note.userId = :userId", { userId: ps.userId });
|
||||
}
|
||||
|
||||
if (ps.host === null) {
|
||||
query.andWhere("note.userHost IS NULL");
|
||||
}
|
||||
if (ps.host != null) {
|
||||
query.andWhere("note.userHost = :userHost", { userHost: ps.host });
|
||||
}
|
||||
|
||||
if (ps.withFiles === true) {
|
||||
query.andWhere("note.fileIds != '{}'");
|
||||
}
|
||||
|
||||
query
|
||||
.leftJoinAndSelect("user.avatar", "avatar")
|
||||
.leftJoinAndSelect("user.banner", "banner")
|
||||
.leftJoinAndSelect("note.reply", "reply")
|
||||
.leftJoinAndSelect("note.renote", "renote")
|
||||
.leftJoinAndSelect("reply.user", "replyUser")
|
||||
.leftJoinAndSelect("replyUser.avatar", "replyUserAvatar")
|
||||
.leftJoinAndSelect("replyUser.banner", "replyUserBanner")
|
||||
.leftJoinAndSelect("renote.user", "renoteUser")
|
||||
.leftJoinAndSelect("renoteUser.avatar", "renoteUserAvatar")
|
||||
.leftJoinAndSelect("renoteUser.banner", "renoteUserBanner");
|
||||
|
||||
generateVisibilityQuery(query, me);
|
||||
if (me) generateMutedUserQuery(query, me);
|
||||
if (me) generateBlockedUserQuery(query, me);
|
||||
|
||||
return await query.take(ps.limit).getMany();
|
||||
}
|
||||
|
||||
if (ps.channelId != null) {
|
||||
query.andWhere("note.channelId = :channelId", {
|
||||
channelId: ps.channelId,
|
||||
});
|
||||
}
|
||||
let notes: Note[];
|
||||
|
||||
if (ps.query != null) {
|
||||
const q = sqlLikeEscape(ps.query);
|
||||
|
||||
if (ps.searchCwAndAlt) {
|
||||
query.andWhere(
|
||||
new Brackets((qb) => {
|
||||
qb.where("note.text &@~ :q", { q })
|
||||
.orWhere("note.cw &@~ :q", { q })
|
||||
.orWhere(
|
||||
`EXISTS (
|
||||
SELECT FROM "drive_file"
|
||||
WHERE
|
||||
comment &@~ :q
|
||||
AND
|
||||
drive_file."id" = ANY(note."fileIds")
|
||||
)`,
|
||||
{ q },
|
||||
);
|
||||
}),
|
||||
);
|
||||
// Whether we should return latest notes first
|
||||
const isDescendingOrder =
|
||||
(ps.sinceId == null || ps.untilId != null) &&
|
||||
(ps.sinceId != null ||
|
||||
ps.untilId != null ||
|
||||
ps.sinceDate == null ||
|
||||
ps.untilDate != null);
|
||||
|
||||
const compare = isDescendingOrder
|
||||
? (lhs: Note, rhs: Note) =>
|
||||
Math.sign(rhs.createdAt.getTime() - lhs.createdAt.getTime())
|
||||
: (lhs: Note, rhs: Note) =>
|
||||
Math.sign(lhs.createdAt.getTime() - rhs.createdAt.getTime());
|
||||
|
||||
notes = [
|
||||
...new Map(
|
||||
(
|
||||
await Promise.all([
|
||||
search((query) => {
|
||||
query.andWhere("note.text &@~ :q", { q });
|
||||
}),
|
||||
search((query) => {
|
||||
query.andWhere("note.cw &@~ :q", { q });
|
||||
}),
|
||||
search((query) => {
|
||||
query
|
||||
.andWhere("drive_file.comment &@~ :q", { q })
|
||||
.innerJoin("note.files", "drive_file");
|
||||
}),
|
||||
])
|
||||
)
|
||||
.flatMap((e) => e)
|
||||
.map((note) => [note.id, note]),
|
||||
).values(),
|
||||
]
|
||||
.sort(compare)
|
||||
.slice(0, ps.limit);
|
||||
} else {
|
||||
query.andWhere("note.text &@~ :q", { q });
|
||||
notes = await search((query) => {
|
||||
query.andWhere("note.text &@~ :q", { q });
|
||||
});
|
||||
}
|
||||
} else {
|
||||
notes = await search();
|
||||
}
|
||||
|
||||
query.innerJoinAndSelect("note.user", "user");
|
||||
|
||||
// "from: me": search all (public, home, followers, specified) my posts
|
||||
// otherwise: search public indexable posts only
|
||||
if (ps.userId == null || ps.userId !== me?.id) {
|
||||
query
|
||||
.andWhere("note.visibility = 'public'")
|
||||
.andWhere("user.isIndexable = TRUE");
|
||||
}
|
||||
|
||||
if (ps.userId != null) {
|
||||
query.andWhere("note.userId = :userId", { userId: ps.userId });
|
||||
}
|
||||
|
||||
if (ps.host === null) {
|
||||
query.andWhere("note.userHost IS NULL");
|
||||
}
|
||||
if (ps.host != null) {
|
||||
query.andWhere("note.userHost = :userHost", { userHost: ps.host });
|
||||
}
|
||||
|
||||
if (ps.withFiles === true) {
|
||||
query.andWhere("note.fileIds != '{}'");
|
||||
}
|
||||
|
||||
query
|
||||
.leftJoinAndSelect("user.avatar", "avatar")
|
||||
.leftJoinAndSelect("user.banner", "banner")
|
||||
.leftJoinAndSelect("note.reply", "reply")
|
||||
.leftJoinAndSelect("note.renote", "renote")
|
||||
.leftJoinAndSelect("reply.user", "replyUser")
|
||||
.leftJoinAndSelect("replyUser.avatar", "replyUserAvatar")
|
||||
.leftJoinAndSelect("replyUser.banner", "replyUserBanner")
|
||||
.leftJoinAndSelect("renote.user", "renoteUser")
|
||||
.leftJoinAndSelect("renoteUser.avatar", "renoteUserAvatar")
|
||||
.leftJoinAndSelect("renoteUser.banner", "renoteUserBanner");
|
||||
|
||||
generateVisibilityQuery(query, me);
|
||||
if (me) generateMutedUserQuery(query, me);
|
||||
if (me) generateBlockedUserQuery(query, me);
|
||||
|
||||
const notes: Note[] = await query.take(ps.limit).getMany();
|
||||
|
||||
return await Notes.packMany(notes, me);
|
||||
});
|
||||
|
|
Loading…
Reference in a new issue