feat: add search filter "from: me"

This commit is contained in:
naskya 2023-11-21 02:18:41 +09:00
parent c7cfd66650
commit 7e23a6d32d
Signed by: naskya
GPG key ID: 164DFF24E2D40139
3 changed files with 58 additions and 42 deletions

View file

@ -10,9 +10,13 @@
## 主要な変更点
- 検索フィルターを強化中
- `from: me` を検索ワードの末尾につけると自分の投稿のみを検索できるように変更
- 検索クエリの例: `予定 from:me`
- ホーム・フォロワー限定・ダイレクト・秘密の投稿を含む自分の全ての投稿から検索します
- 全文検索のエンジンを [PGroonga](https://pgroonga.github.io/) に変更
- PGroonga のインストールが必要になります!詳しくは[この投稿](https://post.naskya.net/notes/9ldi29amfanomef5)をご覧ください
- Meilisearch, Elasticsearch, Sonic は非推奨となります
- Meilisearch, Elasticsearch, Sonic は使えません
- 「秘密」という公開範囲を追加
- 宛先無しのダイレクト投稿を言い換えているだけです
- 既存の投稿を削除せずに後から秘密にすることもできます

View file

@ -1,10 +1,10 @@
import { FindManyOptions, In } from "typeorm";
// import { FindManyOptions, In } from "typeorm";
import { Notes } from "@/models/index.js";
import { Note } from "@/models/entities/note.js";
import config from "@/config/index.js";
import es from "@/db/elasticsearch.js";
import sonic from "@/db/sonic.js";
import meilisearch, { MeilisearchNote } from "@/db/meilisearch.js";
// import config from "@/config/index.js";
// import es from "@/db/elasticsearch.js";
// import sonic from "@/db/sonic.js";
// import meilisearch, { MeilisearchNote } from "@/db/meilisearch.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";
@ -73,47 +73,55 @@ export default define(meta, paramDef, async (ps, me) => {
// disable the post search if no credentials are provided
if (me == null) return [];
if (es == null && sonic == null && meilisearch == null) {
const query = makePaginationQuery(
Notes.createQueryBuilder("note"),
ps.sinceId,
ps.untilId,
);
/* if (es == null && sonic == null && meilisearch == null) { */
const query = makePaginationQuery(
Notes.createQueryBuilder("note"),
ps.sinceId,
ps.untilId,
);
if (ps.userId != null) {
query.andWhere("note.userId = :userId", { userId: ps.userId });
}
if (ps.channelId != null) {
query.andWhere("note.channelId = :channelId", {
channelId: ps.channelId,
});
}
if (ps.channelId != null) {
query.andWhere("note.channelId = :channelId", {
channelId: ps.channelId,
});
}
query
.andWhere("note.text &@~ :q", { q: `${sqlLikeEscape(ps.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.text &@~ :q", { q: `${sqlLikeEscape(ps.query)}` })
.andWhere("note.visibility = 'public'")
.innerJoinAndSelect("note.user", "user")
.andWhere("user.isIndexable = TRUE")
.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");
.andWhere("user.isIndexable = TRUE");
}
generateVisibilityQuery(query, me);
if (me) generateMutedUserQuery(query, me);
if (me) generateBlockedUserQuery(query, me);
if (ps.userId != null) {
query.andWhere("note.userId = :userId", { userId: ps.userId });
}
const notes: Note[] = await query.take(ps.limit).getMany();
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");
return await Notes.packMany(notes, me);
} else if (sonic) {
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);
/* } else if (sonic) {
let start = 0;
const chunkSize = 100;
@ -270,7 +278,7 @@ export default define(meta, paramDef, async (ps, me) => {
}
return found;
} else {
} else { // Elasticsearch
const userQuery =
ps.userId != null
? [
@ -350,5 +358,5 @@ export default define(meta, paramDef, async (ps, me) => {
});
return await Notes.packMany(notes, me);
}
} */
});

View file

@ -50,6 +50,7 @@ import { definePageMetadata } from "@/scripts/page-metadata";
import { defaultStore } from "@/store";
import { deviceKind } from "@/scripts/device-kind";
import icon from "@/scripts/icon";
import { $i } from "@/reactiveAccount";
import "swiper/scss";
import "swiper/scss/virtual";
@ -62,7 +63,10 @@ const notesPagination = {
endpoint: "notes/search" as const,
limit: 10,
params: computed(() => ({
query: props.query,
query: props.query.endsWith("from:me")
? props.query.slice(0, -7).trim()
: props.query,
userId: props.query.endsWith("from:me") ? $i.id : null,
channelId: props.channel,
})),
};