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://pgroonga.github.io/) に変更
- PGroonga のインストールが必要になります!詳しくは[この投稿](https://post.naskya.net/notes/9ldi29amfanomef5)をご覧ください - 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 { Notes } from "@/models/index.js";
import { Note } from "@/models/entities/note.js"; import { Note } from "@/models/entities/note.js";
import config from "@/config/index.js"; // import config from "@/config/index.js";
import es from "@/db/elasticsearch.js"; // import es from "@/db/elasticsearch.js";
import sonic from "@/db/sonic.js"; // import sonic from "@/db/sonic.js";
import meilisearch, { MeilisearchNote } from "@/db/meilisearch.js"; // import meilisearch, { MeilisearchNote } from "@/db/meilisearch.js";
import define from "@/server/api/define.js"; import define from "@/server/api/define.js";
import { makePaginationQuery } from "@/server/api/common/make-pagination-query.js"; import { makePaginationQuery } from "@/server/api/common/make-pagination-query.js";
import { generateVisibilityQuery } from "@/server/api/common/generate-visibility-query.js"; import { generateVisibilityQuery } from "@/server/api/common/generate-visibility-query.js";
@ -73,17 +73,13 @@ export default define(meta, paramDef, async (ps, me) => {
// disable the post search if no credentials are provided // disable the post search if no credentials are provided
if (me == null) return []; if (me == null) return [];
if (es == null && sonic == null && meilisearch == null) { /* if (es == null && sonic == null && meilisearch == null) { */
const query = makePaginationQuery( const query = makePaginationQuery(
Notes.createQueryBuilder("note"), Notes.createQueryBuilder("note"),
ps.sinceId, ps.sinceId,
ps.untilId, ps.untilId,
); );
if (ps.userId != null) {
query.andWhere("note.userId = :userId", { userId: ps.userId });
}
if (ps.channelId != null) { if (ps.channelId != null) {
query.andWhere("note.channelId = :channelId", { query.andWhere("note.channelId = :channelId", {
channelId: ps.channelId, channelId: ps.channelId,
@ -92,9 +88,21 @@ export default define(meta, paramDef, async (ps, me) => {
query query
.andWhere("note.text &@~ :q", { q: `${sqlLikeEscape(ps.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.visibility = 'public'") .andWhere("note.visibility = 'public'")
.innerJoinAndSelect("note.user", "user") .andWhere("user.isIndexable = TRUE");
.andWhere("user.isIndexable = TRUE") }
if (ps.userId != null) {
query.andWhere("note.userId = :userId", { userId: ps.userId });
}
query
.leftJoinAndSelect("user.avatar", "avatar") .leftJoinAndSelect("user.avatar", "avatar")
.leftJoinAndSelect("user.banner", "banner") .leftJoinAndSelect("user.banner", "banner")
.leftJoinAndSelect("note.reply", "reply") .leftJoinAndSelect("note.reply", "reply")
@ -113,7 +121,7 @@ export default define(meta, paramDef, async (ps, me) => {
const notes: Note[] = await query.take(ps.limit).getMany(); const notes: Note[] = await query.take(ps.limit).getMany();
return await Notes.packMany(notes, me); return await Notes.packMany(notes, me);
} else if (sonic) { /* } else if (sonic) {
let start = 0; let start = 0;
const chunkSize = 100; const chunkSize = 100;
@ -270,7 +278,7 @@ export default define(meta, paramDef, async (ps, me) => {
} }
return found; return found;
} else { } else { // Elasticsearch
const userQuery = const userQuery =
ps.userId != null ps.userId != null
? [ ? [
@ -350,5 +358,5 @@ export default define(meta, paramDef, async (ps, me) => {
}); });
return await Notes.packMany(notes, me); return await Notes.packMany(notes, me);
} } */
}); });

View file

@ -50,6 +50,7 @@ import { definePageMetadata } from "@/scripts/page-metadata";
import { defaultStore } from "@/store"; import { defaultStore } from "@/store";
import { deviceKind } from "@/scripts/device-kind"; import { deviceKind } from "@/scripts/device-kind";
import icon from "@/scripts/icon"; import icon from "@/scripts/icon";
import { $i } from "@/reactiveAccount";
import "swiper/scss"; import "swiper/scss";
import "swiper/scss/virtual"; import "swiper/scss/virtual";
@ -62,7 +63,10 @@ const notesPagination = {
endpoint: "notes/search" as const, endpoint: "notes/search" as const,
limit: 10, limit: 10,
params: computed(() => ({ 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, channelId: props.channel,
})), })),
}; };