From 74cf09c5c805832e1aa4b4868ad637f56cf947a6 Mon Sep 17 00:00:00 2001 From: naskya <m@naskya.net> Date: Fri, 31 May 2024 22:21:54 +0900 Subject: [PATCH] chore: export searchNotes function --- packages/backend/src/misc/search.ts | 90 +++++++++++++++ .../src/server/api/endpoints/notes/search.ts | 105 ++++-------------- 2 files changed, 114 insertions(+), 81 deletions(-) create mode 100644 packages/backend/src/misc/search.ts diff --git a/packages/backend/src/misc/search.ts b/packages/backend/src/misc/search.ts new file mode 100644 index 0000000000..4d03e6ad27 --- /dev/null +++ b/packages/backend/src/misc/search.ts @@ -0,0 +1,90 @@ +import { Notes } from "@/models/index.js"; +import type { Note } from "@/models/entities/note.js"; +import { makePaginationQuery } from "@/server/api/common/make-pagination-query.js"; +import { generateVisibilityQuery } from "@/server/api/common/generate-visibility-query.js"; +import { generateMutedUserQuery } from "@/server/api/common/generate-muted-user-query.js"; +import { generateBlockedUserQuery } from "@/server/api/common/generate-block-query.js"; +import type { SelectQueryBuilder } from "typeorm"; + +export interface SearchParams { + withFilesOnly: boolean; + limit: number; + myId: string | undefined; + sinceId: string | undefined; + untilId: string | undefined; + sinceDate: number | undefined; + untilDate: number | undefined; + userId: string | null | undefined; + channelId: string | null; + host: string | null | undefined; +} + +export async function searchNotes( + params: SearchParams, + modifier?: (query: SelectQueryBuilder<Note>) => void, +): Promise<Note[]> { + const query = makePaginationQuery( + Notes.createQueryBuilder("note"), + params.sinceId ?? undefined, + params.untilId ?? undefined, + params.sinceDate ?? undefined, + params.untilDate ?? undefined, + ); + modifier?.(query); + + if (params.userId != null) { + query.andWhere("note.userId = :userId", { userId: params.userId }); + } + + if (params.channelId != null) { + query.andWhere("note.channelId = :channelId", { + channelId: params.channelId, + }); + } + + query.innerJoinAndSelect("note.user", "user"); + + // "from: me": search all (public, home, followers, specified) my posts + // otherwise: search public indexable posts only + if (params.userId == null || params.userId !== params.myId) { + query + .andWhere("note.visibility = 'public'") + .andWhere("user.isIndexable = TRUE"); + } + + if (params.userId != null) { + query.andWhere("note.userId = :userId", { userId: params.userId }); + } + + if (params.host === null) { + // search local notes only + query.andWhere("note.userHost IS NULL"); + } + if (params.host != null) { + query.andWhere("note.userHost = :userHost", { userHost: params.host }); + } + + if (params.withFilesOnly) { + 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"); + + const me = params.myId != null ? { id: params.myId } : null; + + generateVisibilityQuery(query, me); + if (params.myId != null) generateMutedUserQuery(query, { id: params.myId }); + if (params.myId != null) generateBlockedUserQuery(query, { id: params.myId }); + + return await query.take(params.limit).getMany(); +} diff --git a/packages/backend/src/server/api/endpoints/notes/search.ts b/packages/backend/src/server/api/endpoints/notes/search.ts index f28208cba9..59d0175c39 100644 --- a/packages/backend/src/server/api/endpoints/notes/search.ts +++ b/packages/backend/src/server/api/endpoints/notes/search.ts @@ -1,12 +1,8 @@ import { Notes } from "@/models/index.js"; import type { Note } from "@/models/entities/note.js"; import define from "@/server/api/define.js"; -import { makePaginationQuery } from "@/server/api/common/make-pagination-query.js"; -import { generateVisibilityQuery } from "@/server/api/common/generate-visibility-query.js"; -import { generateMutedUserQuery } from "@/server/api/common/generate-muted-user-query.js"; -import { generateBlockedUserQuery } from "@/server/api/common/generate-block-query.js"; import { sqlLikeEscape } from "backend-rs"; -import type { SelectQueryBuilder } from "typeorm"; +import { searchNotes, type SearchParams } from "@/misc/search.js"; export const meta = { tags: ["notes"], @@ -69,76 +65,23 @@ export const paramDef = { } as const; export default define(meta, paramDef, async (ps, me) => { - 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.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(); - } - let notes: Note[]; + const params: SearchParams = { + withFilesOnly: ps.withFiles ?? false, + limit: ps.limit, + myId: me?.id, + sinceId: ps.sinceId, + untilId: ps.untilId, + sinceDate: ps.sinceDate ?? undefined, + untilDate: ps.untilDate ?? undefined, + userId: ps.userId, + channelId: ps.channelId, + host: ps.host, + }; + if (ps.query != null) { - const q = sqlLikeEscape(ps.query); + const searchWord = sqlLikeEscape(ps.query); if (ps.searchCwAndAlt) { // Whether we should return latest notes first @@ -159,15 +102,15 @@ export default define(meta, paramDef, async (ps, me) => { ...new Map( ( await Promise.all([ - search((query) => { - query.andWhere("note.text &@~ :q", { q }); + searchNotes(params, (query) => { + query.andWhere("note.text &@~ :q", { q: searchWord }); }), - search((query) => { - query.andWhere("note.cw &@~ :q", { q }); + searchNotes(params, (query) => { + query.andWhere("note.cw &@~ :q", { q: searchWord }); }), - search((query) => { + searchNotes(params, (query) => { query - .andWhere("drive_file.comment &@~ :q", { q }) + .andWhere("drive_file.comment &@~ :q", { q: searchWord }) .innerJoin("note.files", "drive_file"); }), ]) @@ -179,12 +122,12 @@ export default define(meta, paramDef, async (ps, me) => { .sort(compare) .slice(0, ps.limit); } else { - notes = await search((query) => { - query.andWhere("note.text &@~ :q", { q }); + notes = await searchNotes(params, (query) => { + query.andWhere("note.text &@~ :q", { q: searchWord }); }); } } else { - notes = await search(); + notes = await searchNotes(params); } return await Notes.packMany(notes, me);