refactor: use PGroonga for full-text search and remove support of other engines

Co-authored-by: tamaina <tamaina@hotmail.co.jp>
Co-authored-by: sup39 <dev@sup39.dev>
This commit is contained in:
naskya 2024-03-01 21:39:13 +09:00
parent eab42ceae5
commit 48e5d9de71
No known key found for this signature in database
GPG Key ID: 712D413B3A9FED5C
58 changed files with 182 additions and 1504 deletions

View File

@ -85,28 +85,6 @@ redis:
#prefix: example-prefix
#db: 1
# Please configure either MeiliSearch *or* Sonic.
# If both MeiliSearch and Sonic configurations are present, MeiliSearch will take precedence.
# ┌───────────────────────────┐
#───┘ MeiliSearch configuration └─────────────────────────────────────
#meilisearch:
# host: meilisearch
# port: 7700
# ssl: false
# apiKey:
# ┌─────────────────────┐
#───┘ Sonic configuration └─────────────────────────────────────
#sonic:
# host: localhost
# port: 1491
# auth: SecretPassword
# collection: notes
# bucket: default
# ┌───────────────┐
#───┘ ID generation └───────────────────────────────────────────

View File

@ -59,10 +59,6 @@ If you have access to a server that supports one of the sources below, I recomme
### Optional dependencies
- [FFmpeg](https://ffmpeg.org/) for video transcoding
- Full text search (one of the following)
- [Sonic](https://crates.io/crates/sonic-server)
- [MeiliSearch](https://www.meilisearch.com/)
- [ElasticSearch](https://www.elastic.co/elasticsearch/)
- Caching server (one of the following)
- [DragonflyDB](https://www.dragonflydb.io/) (recommended)
- [KeyDB](https://keydb.dev/)

View File

@ -6,10 +6,12 @@ services:
ports:
- "26379:6379"
db:
image: docker.io/postgres:16-alpine
image: docker.io/groonga/pgroonga:latest-alpine-16-slim
environment:
- "POSTGRES_PASSWORD=password"
- "POSTGRES_USER=firefish"
- "POSTGRES_DB=firefish_db"
ports:
- "25432:5432"
volumes:
- "./install.sql:/docker-entrypoint-initdb.d/install.sql:ro"

1
dev/install.sql Normal file
View File

@ -0,0 +1 @@
CREATE EXTENSION pgroonga;

View File

@ -8,9 +8,6 @@ services:
depends_on:
- db
- redis
### Uncomment one of the following to use a search engine
# - meilisearch
# - sonic
ports:
- "3000:3000"
networks:
@ -34,7 +31,7 @@ services:
db:
restart: unless-stopped
image: docker.io/postgres:16-alpine
image: docker.io/groonga/pgroonga:latest-alpine-16-slim
container_name: firefish_db
networks:
- calcnet
@ -43,33 +40,6 @@ services:
volumes:
- ./db:/var/lib/postgresql/data
### Only one of the below should be used.
### Meilisearch is better overall, but resource-intensive. Sonic is a very light full text search engine.
# meilisearch:
# container_name: meilisearch
# image: getmeili/meilisearch:v1.1.1
# environment:
# - MEILI_ENV=${MEILI_ENV:-development}
# ports:
# - "7700:7700"
# networks:
# - calcnet
# volumes:
# - ./meili_data:/meili_data
# restart: unless-stopped
# sonic:
# restart: unless-stopped
# image: docker.io/valeriansaliou/sonic:v1.4.0
# logging:
# driver: none
# networks:
# - calcnet
# volumes:
# - ./sonic:/var/lib/sonic/store
# - ./sonic/config.cfg:/etc/sonic.cfg
networks:
calcnet:
# web:

View File

@ -2,6 +2,10 @@
Breaking changes are indicated by the :warning: icon.
## Unreleased
- `admin/search/index-all` is removed since posts are now indexed automatically.
## v20240301
- With the addition of new features, the following endpoints are added:

View File

@ -2,6 +2,10 @@
Critical security updates are indicated by the :warning: icon.
## Unreleased
- Introduce new full-text search engine
## v20240301
- Add a page (`/my/follow-requests/sent`) to check your follow requests that haven't been approved

View File

@ -1,6 +1,7 @@
BEGIN;
DELETE FROM "migrations" WHERE name IN (
'Pgroonga1698420787202',
'ChangeDefaultConfigs1709251460718',
'AddReplyMuting1704851359889',
'FixNoteUrlIndex1709129810501',
@ -12,6 +13,12 @@ DELETE FROM "migrations" WHERE name IN (
'RemoveNativeUtilsMigration1705877093218'
);
-- pgroonga
DROP INDEX "IDX_f27f5d88941e57442be75ba9c8";
DROP INDEX "IDX_065d4d8f3b5adb4a08841eae3c";
DROP INDEX "IDX_fcb770976ff8240af5799e3ffc";
DROP EXTENSION pgroonga CASCADE;
-- change-default-configs
ALTER TABLE "user_profile" ALTER COLUMN "noCrawle" SET DEFAULT false;
ALTER TABLE "user_profile" ALTER COLUMN "publicReactions" SET DEFAULT false;

View File

@ -1,3 +1,97 @@
# Unreleased
The full-text search engine in Firefish has been changed to [PGroonga](https://pgroonga.github.io/). This is no longer an optional feature, so please enable PGroonga on your system. If you are using Sonic, Meilisearch, or Elasticsearch, you can also uninstall it from your system and remove the settings from `.config/default.yml`.
## For systemd/pm2 users
### 1. Install PGroonga
Please execute `psql --version` to check your PostgreSQL major version. This will print a message like this:
```text
psql (PostgreSQL) 16.1
```
In this case, your PostgreSQL major version is `16`.
There are official installation instructions for many operating systems on <https://pgroonga.github.io/install>, so please follow the instructions on this page. However, since many users are using Ubuntu, and there are no instructions for Arch Linux, we explicitly list the instructions for Ubuntu and Arch Linux here. Please keep in mind that this is not official information and the procedures may change.
#### Ubuntu
1. Add apt repository
```sh
sudo apt install -y software-properties-common
sudo add-apt-repository -y universe
sudo add-apt-repository -y ppa:groonga/ppa
sudo apt install -y wget lsb-release
wget https://packages.groonga.org/ubuntu/groonga-apt-source-latest-$(lsb_release --codename --short).deb
sudo apt install -y -V ./groonga-apt-source-latest-$(lsb_release --codename --short).deb
echo "deb http://apt.postgresql.org/pub/repos/apt/ $(lsb_release --codename --short)-pgdg main" | sudo tee /etc/apt/sources.list.d/pgdg.list
wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | sudo apt-key add -
sudo apt update
```
2. Install PGroonga
```sh
# Please replace "16" with your PostgreSQL major version
sudo apt install postgresql-16-pgdg-pgroonga
```
#### Arch Linux
You can install PGroonga from the Arch User Repository.
```sh
git clone https://aur.archlinux.org/pgroonga.git && cd pgroonga && makepkg -si
# or paru -S pgroonga
# or yay -S pgroonga
```
### 2. Enable PGroonga
After the instllation, please execute this command to enable PGroonga:
```sh
sudo --user=postgres psql --dbname=your_database_name --command='CREATE EXTENSION pgroonga;'
```
The database name can be found in `.config/default.yml`.
```yaml
db:
port: 5432
db: database_name # substitute your_database_name with this
user: firefish
pass: password
```
## For Docker/Podman users
Please edit your `docker-compose.yml` to replace the database container image from `docker.io/postgres` to `docker.io/groonga/pgroonga`.
Please make sure to use the same PostgreSQL version. If you are using `docker.io/postgres:16-alpine` (PostgreSQL v16), the corresponding image tag is `docker.io/groonga/pgroonga:latest-alpine-16` (or `docker.io/groonga/pgroonga:latest-alpine-16-slim`).
The list of tags can be found on <https://hub.docker.com/r/groonga/pgroonga/tags>.
```yaml
db:
restart: unless-stopped
image: docker.io/groonga/pgroonga:latest-alpine-16-slim # change here
container_name: firefish_db
```
After that, execute this command to enable PGroonga:
```sh
docker-compose up db --detach && docker-compose exec db sh -c 'psql --user="${POSTGRES_USER}" --dbname="${POSTGRES_DB}" --command="CREATE EXTENSION pgroonga;"'
# or podman-compose up db --detach && podman-compose exec db sh -c 'psql --user="${POSTGRES_USER}" --dbname="${POSTGRES_DB}" --command="CREATE EXTENSION pgroonga;"'
```
Once this is done, you can start Firefish as usual.
```sh
docker pull registry.firefish.dev/firefish/firefish && docker-compose up --detach
# or podman pull registry.firefish.dev/firefish/firefish && podman-compose up --detach
```
# v20240301
## For all users

View File

@ -1831,11 +1831,6 @@ pushNotificationAlreadySubscribed: Les notificacions push ja estan activades
pushNotificationNotSupported: El vostre navegador o servidor no admet notificacions
push
license: Llicència
indexPosts: Índex de publicacions
indexFrom: Índex a partir de l'ID de Publicacions
indexFromDescription: Deixeu en blanc per indexar cada publicació
indexNotice: Ara indexant. Això probablement trigarà una estona, si us plau, no reinicieu
el servidor durant almenys una hora.
_instanceTicker:
none: No mostrar mai
remote: Mostra per a usuaris remots

View File

@ -1500,9 +1500,6 @@ _widgets:
chooseList: Wählen Sie eine Liste aus
userList: Benutzerliste
serverInfo: Server-Infos
meiliStatus: Server-Status
meiliSize: Indexgröße
meiliIndexCount: Indexierte Beiträge
_cw:
hide: "Verbergen"
show: "Inhalt anzeigen"
@ -2083,7 +2080,6 @@ preventAiLearning: KI gestütztes bot-scraping unterdrücken
preventAiLearningDescription: Fordern Sie KI-Sprachmodelle von Drittanbietern auf,
die von Ihnen hochgeladenen Inhalte, wie z. B. Beiträge und Bilder, nicht zu untersuchen.
license: Lizenz
indexPosts: Gelistete Beiträge
migrationConfirm: "Sind Sie absolut sicher, dass Sie Ihr Nutzerkonto zu diesem {account}
umziehen möchten? Sobald Sie dies bestätigt haben, kann dies nicht mehr rückgängig
gemacht werden und Ihr Nutzerkonto kann nicht mehr von ihnen genutzt werden.\nStellen
@ -2111,9 +2107,6 @@ _experiments:
kann es zu Verlangsamungen beim Laden während des Imports kommen.
noGraze: Bitte deaktivieren Sie die Browsererweiterung "Graze for Mastodon", da sie
die Funktion von Firefish stört.
indexFrom: Indexieren ab Beitragskennung aufwärts
indexNotice: Wird jetzt indexiert. Dies wird wahrscheinlich eine Weile dauern, bitte
starten Sie Ihren Server für mindestens eine Stunde nicht neu.
customKaTeXMacroDescription: "Richten Sie Makros ein, um mathematische Ausdrücke einfach
zu schreiben! Die Notation entspricht den LaTeX-Befehlsdefinitionen und wird als
\\newcommand{\\name}{content} oder \\newcommand{\\name}[number of arguments]{content}
@ -2132,7 +2125,6 @@ expandOnNoteClick: Beitrag bei Klick öffnen
image: Bild
video: Video
audio: Audio
indexFromDescription: Leer lassen, um jeden Beitrag zu indexieren
_filters:
fromUser: Von Benutzer
notesAfter: Beiträge nach

View File

@ -1092,11 +1092,6 @@ migrationConfirm: "Are you absolutely sure you want to migrate your account to {
as the account you're moving from."
defaultReaction: "Default emoji reaction for outgoing and incoming posts"
license: "License"
indexPosts: "Index Posts"
indexFrom: "Index from Post ID onwards"
indexFromDescription: "Leave blank to index every post"
indexNotice: "Now indexing. This will probably take a while, please don't restart
your server for at least an hour."
customKaTeXMacro: "Custom KaTeX macros"
customKaTeXMacroDescription: "Set up macros to write mathematical expressions easily!
The notation conforms to the LaTeX command definitions and is written as \\newcommand{\\
@ -1690,9 +1685,6 @@ _widgets:
serverInfo: "Server Info"
_userList:
chooseList: "Select a list"
meiliStatus: "Server Status"
meiliSize: "Index size"
meiliIndexCount: "Indexed posts"
_cw:
hide: "Hide"

View File

@ -1474,9 +1474,6 @@ _widgets:
_userList:
chooseList: Seleccione una lista
serverInfo: Información del servidor
meiliStatus: Estado del servidor
meiliSize: Tamaño del índice
meiliIndexCount: Publicaciones indizadas
_cw:
hide: "Ocultar"
show: "Ver más"
@ -2123,13 +2120,10 @@ moveFromDescription: 'Esto pondrá un alias en tu cuenta antigua para así poder
ingresa la etiqueta de la cuenta con el formato siguiente: @persona@servidor.tld'
defaultReaction: Emoji por defecto para reaccionar a las publicaciones entrantes y
salientes
indexFromDescription: Deja en blanco para indizar todas las publicaciones
deletePasskeys: Borrar claves de paso
deletePasskeysConfirm: Esto borrará irreversiblemente todas las claves de paso y de
seguridad en esta cuenta, ¿Proceder?
inputNotMatch: Las entradas no coinciden
indexFrom: Indizar desde la ID de la publicación en adelante
indexPosts: Indizar publicaciones
isModerator: Moderador
isAdmin: Administrador
isPatron: Mecenas de Firefish
@ -2139,8 +2133,6 @@ migrationConfirm: "¿Estás absolutamente seguro de que quieres migrar a tu cuen
{account}? Una vez hecho esto, no podrás revertir el cambio, ni tampoco usar tu
cuenta normalmente.\nTambién, asegúrate de que has configurado ésta cuenta como
la cuenta desde la cual estás migrando."
indexNotice: Indizando ahora. Esto puede llevar bastante tiempo, por favor, no reinicies
el servidor por lo menos hasta dentro de una hora.
customKaTeXMacro: Macros KaTeX personalizadas
customKaTeXMacroDescription: '¡Configura macros para escribir expresiones matemáticas
fácilmente! La notación es conforme la las definiciones de comandos LaTeX y puede

View File

@ -1400,10 +1400,7 @@ _widgets:
_userList:
chooseList: Sélectionner une liste
unixClock: Horloge UNIX
meiliIndexCount: Publications indexées
serverInfo: Info serveur
meiliStatus: État du serveur
meiliSize: Taille de l'index
instanceCloud: Nuage de serveurs
rssTicker: Bandeau RSS
_cw:
@ -2061,9 +2058,6 @@ moveToLabel: 'Compte vers lequel vous migrez:'
moveFrom: Migrer vers ce compte depuis un ancien compte
defaultReaction: Émoji de réaction par défaut pour les publications entrantes et sortantes
license: Licence
indexPosts: Indexer les publications
indexNotice: Indexation en cours. Cela prendra certainement du temps, veuillez ne
pas redémarrer votre serveur pour au moins une heure.
customKaTeXMacro: Macros KaTeX personnalisées
enableCustomKaTeXMacro: Activer les macros KaTeX personnalisées
noteId: ID des publications
@ -2110,7 +2104,6 @@ expandOnNoteClick: Ouvrir la publications en cliquant
preventAiLearning: Empêcher le récupération de données par des IA
listsDesc: Les listes vous laissent créer des fils personnalisés avec des utilisateur·rice·s
spécifié·e·s. Elles sont accessibles depuis la page des fils.
indexFromDescription: Laisser vide pour indexer toutes les publications
_feeds:
jsonFeed: flux JSON
atom: Atom
@ -2120,7 +2113,6 @@ alt: ALT
swipeOnMobile: Permettre le balayage entre les pages
expandOnNoteClickDesc: Si désactivé, vous pourrez toujours ouvrir les publications
dans le menu du clic droit et en cliquant sur l'horodatage.
indexFrom: Indexer à partir de l'ID des publications
older: ancien
newer: récent
accessibility: Accessibilité

View File

@ -1390,14 +1390,11 @@ _widgets:
aiscript: "Konsol AiScript"
aichan: "Ai"
rssTicker: Telegraf RSS
meiliIndexCount: Postingan yang terindeks
userList: Daftar Pengguna
instanceCloud: Server Awan
unixClock: Jam UNIX
meiliSize: Ukuran indeks
_userList:
chooseList: Pilih daftar
meiliStatus: Status Server
serverInfo: Info Server
_cw:
hide: "Sembunyikan"
@ -1984,8 +1981,6 @@ speed: Kecepatan
slow: Pelan
remoteOnly: Jarak jauh saja
moveFrom: Dari akun lama pindahkan ke akun ini
indexNotice: Sedang mengindeks. Ini memerlukan beberapa waktu, mohon jangan mulai
ulang server setidaknya satu jam.
sendPushNotificationReadMessage: Hapus pemberitahuan dorong saat pemberitahuan atau
pesan relevan sudah dibaca
moveAccountDescription: Proses ini permanen. Pastikan kamu sudah mengatur alias dari
@ -2010,7 +2005,6 @@ showAds: Tampilkan spanduk komunitas
enterSendsMessage: Tekan Enter pada Pesan untuk mengirim pesan (matikan dengan Ctrl
+ Enter)
showAdminUpdates: Indikasi versi Firefish baru tersedia (hanya admin)
indexFrom: Indeks dari Post ID berikutnya
noteId: ID Postingan
findOtherInstance: Cari server lain
caption: Deskripsi itomatis
@ -2022,7 +2016,6 @@ moveFromDescription: Ini akan mengatur alias akun lamamu jadi kamu dapat pindah
akun tersebut ke akun sekarang. Lakukan ini SEBELUM memindahkan akun lama. Silakan
masukkan tag akun dengan format seperti @orang@server.com
defaultReaction: Reaksi emoji bawaan untuk postingan keluar dan masuk
indexPosts: Indeks Postingan
preventAiLearning: Cegah scraping bot AI
customKaTeXMacro: Makro KaTeX khusus
sendPushNotificationReadMessageCaption: Pemberitahuan yang berisi teks "{emptyPushNotificationMessage}"
@ -2052,7 +2045,6 @@ migrationConfirm: "Kamu sangat yakin ingin memindahkan akunmu ke {account}? Seka
lagi secara normal. \nDan juga, harap pastikan kamu sudah mengatur akun sekarang
sebagai akun yang dipindahkan."
license: Lisensi
indexFromDescription: Kosongkan untuk mengindeks setiap postingan
noGraze: Harap nonaktifkan ekstensi peramban "Graze for Mastodon", karena akan menganggu
Firefish.
silencedWarning: Halaman ini tampil karena pengguna ini datang dari server yang dibisukan

View File

@ -1324,9 +1324,6 @@ _widgets:
instanceCloud: Cloud del server
unixClock: Orologio UNIX
serverInfo: Informazioni sul server
meiliIndexCount: Post indicizzati
meiliStatus: Stato del server
meiliSize: Dimensione indice
userList: Elenco utenti
_cw:
hide: "Nascondi"
@ -1838,9 +1835,7 @@ customSplashIconsDescription: Elenco degli URL di icone personalizzate da mostra
le immagini siano su un URL statico, preferibilmente di dimensioni 192x192.
swipeOnDesktop: Permetti lo swipe su desktop simile alla versione mobile
logoImageUrl: URL del logo
indexFrom: Indicizza dal post ID
customKaTeXMacro: Macro KaTeX personalizzate
indexPosts: Crea indice dei post
signupsDisabled: Le iscrizioni su questo server al momento non sono possibili, ma
puoi sempre iscriverti su un altro server! Se invece hai un codice di invito per
questo server, inseriscilo qua sotto.
@ -1919,9 +1914,6 @@ lastActiveDate: Ultimo utilizzo
enterSendsMessage: Premi "Invio" nei messaggi per inviare (altrimenti è "Ctrl + Invio")
customMOTD: Messaggi di caricamento personalizzati (splash screen)
replayTutorial: Ripeti il tutorial
indexFromDescription: Lascia vuoto per indicizzare tutti i post
indexNotice: Creazione indice in corso. Sarà necessario del tempo, fai attenzione
a non riavviare il server per almeno un'ora.
enableCustomKaTeXMacro: Abilita le macro KaTeX personalizzate
preventAiLearningDescription: Richiedi ai bot di intelligenza artificiale di terze
parti di non studiare e acquisire il contenuto che carichi, come post e immagini.

View File

@ -974,10 +974,6 @@ migrationConfirm: "本当にこのアカウントを {account} に引っ越し
この操作を行う前に引っ越し先のアカウントでエイリアスを作成する必要があります。エイリアスが作成されているか、必ず確認してください。"
defaultReaction: "リモートとローカルの投稿に対するデフォルトの絵文字リアクション"
license: "ライセンス"
indexPosts: "投稿をインデックス"
indexFrom: "この投稿ID以降をインデックスする"
indexFromDescription: "空白で全ての投稿を指定します"
indexNotice: "インデックスを開始しました。完了まで時間がかかる場合があるため、少なくとも1時間はサーバーを再起動しないでください。"
customKaTeXMacro: "カスタムKaTeXマクロ"
customKaTeXMacroDescription: "数式入力を楽にするためのマクロを設定しましょう記法はLaTeXにおけるコマンドの定義と同様に \\newcommand{\\
name}{content} または \\newcommand{\\add}[2]{#1 + #2} のように記述します。後者の例では \\add{3}{foo}
@ -1453,10 +1449,7 @@ _widgets:
userList: "ユーザーリスト"
_userList:
chooseList: "リストを選択"
meiliStatus: サーバーステータス
serverInfo: サーバー情報
meiliSize: インデックスサイズ
meiliIndexCount: インデックス済みの投稿
_cw:
hide: "隠す"
show: "もっと見る"

View File

@ -1331,10 +1331,7 @@ _widgets:
serverInfo: 서버 정보
_userList:
chooseList: 리스트 선택
meiliStatus: 서버 정보
userList: 유저 목록
meiliSize: 인덱스 크기
meiliIndexCount: 인덱싱 완료된 게시물
rssTicker: RSS Ticker
_cw:
hide: "숨기기"
@ -1840,7 +1837,6 @@ customSplashIconsDescription: 유저가 페이지를 로딩/새로고침할 때
이미지는 되도록 정적 URL으로 구성하고, 192x192 해상도로 조정하여 주십시오.
moveFromDescription: '이전 계정에 대한 별칭을 작성하여, 이 계정으로 옮길 수 있도록 합니다. 반드시 계정을 이전하기 전에 수행해야
합니다. 이전 계정을 다음과 같은 형식으로 입력하여 주십시오: @person@server.com'
indexFromDescription: 빈 칸으로 두면 모든 게시물을 인덱싱합니다
customKaTeXMacroDescription: 'KaTeX 매크로를 지정하여 수식을 더욱 편리하게 입력하세요! LaTeX의 커맨드 정의와 동일하게
\newcommand{\ 이름}{내용} 또는 \newcommand{\이름}[인수 갯수]{내용} 와 같이 입력하십시오. 예를 들어 \newcommand{\add}[2]{#1
+ #2} 와 같이 정의한 경우 \add{3}{foo} 를 입력하면 3 + foo 으로 치환됩니다.매크로의 이름을 감싸는 중괄호를 소괄호() 또는
@ -1895,7 +1891,6 @@ accessibility: 접근성
userSaysSomethingReasonReply: '{name} 님이 {reason} 을 포함하는 게시물에 답글했습니다'
userSaysSomethingReasonRenote: '{name} 님이 {reason} 을 포함하는 게시물을 부스트했습니다'
breakFollowConfirm: 팔로워를 해제하시겠습니까?
indexFrom: 이 게시물 ID부터 인덱싱하기
noThankYou: 괜찮습니다
hiddenTags: 숨길 해시태그
image: 이미지
@ -1927,8 +1922,6 @@ removeMember: 멤버를 삭제
license: 라이선스
migrationConfirm: "정말로 이 계정을 {account}로 이사하시겠습니까? 한 번 이사하면, 현재 이 계정은 두 번 다시 사용할 수
없게 됩니다.\n또한, 이사 갈 계정에 현재 사용 중인 계정의 별칭을 올바르게 작성하였는지 다시 한 번 확인하십시오."
indexPosts: 게시물을 인덱싱
indexNotice: 인덱싱을 시작했습니다. 이 작업은 시간이 많이 소요되므로, 최소 1시간 이내에 서버를 재시작하지 마십시오.
noteId: 게시물 ID
signupsDisabled: 현재 이 서버에서는 신규 등록을 받고 있지 않습니다. 초대 코드를 가지고 계신 경우 아래 칸에 입력해 주십시오. 초대
코드를 가지고 있지 않더라도, 신규 등록이 열려 있는 다른 서버에 등록하실 수 있습니다!

View File

@ -939,7 +939,6 @@ allowedInstancesDescription: Tjenernavn for tjenere som skal hvitelistes. En per
(Vil bare bli brukt i privat modus).
previewNoteText: Forhåndsvisning
recentNDays: Siste {n} dager
indexPosts: Indekser poster
objectStorageUseProxy: Koble til gjennom en mellomtjener
objectStorageUseProxyDesc: Skru av dette dersom du ikke vil bruke mellomtjenere for
API-oppkoblinger
@ -1185,10 +1184,6 @@ moveFromDescription: Dette vil sette opp et alias for din gamle kontoen slik at
kan flytte fra den gamle kontoen til denne. Gjør dette FØR du flytter fra den gamle
kontoen. Skriv inn den gamle kontoen på formen @person@server.com
defaultReaction: Standard emoji-reaksjon for utgående og innkommende poster
indexFrom: Indekser poster fra post-id og fremover
indexNotice: Indekserer. Dette vil sannsynligvis ta litt tid, ikke restart tjeneren
før det har gått minst en time.
indexFromDescription: La stå tom for å indeksere alle poster
customKaTeXMacroDescription: 'Sett opp makroer for å skrive matematiske uttrykk enkelt.
Notasjonen følger LaTeX-kommandoer og er skrevet som \newcommand{\ navn}{uttrykk}
eller \newcommand{\navn}{antall argumenter}{uttrykk}. For eksempel vil \newcommand{\add}{2}{#1
@ -1628,14 +1623,12 @@ _antennaSources:
instances: Poster fra alle brukerne på denne tjeneren
_widgets:
timeline: Tidslinje
meiliSize: Indeks-størrelse
instanceCloud: Tjenersky
onlineUsers: Påloggede brukere
clock: Klokke
userList: Brukerliste
rss: RSS-leser
serverMetric: Tjenermetrikker
meiliIndexCount: Indekserte poster
button: Knapp
unixClock: Unix-klokke
calendar: Kalender
@ -1647,7 +1640,6 @@ _widgets:
photos: Bilder
rssTicker: RSS-rulletekst
aiscript: AiScript-konsoll
meiliStatus: Tjenerstatus
memo: Notatlapp
notifications: Varsler
postForm: Ny post

View File

@ -1892,11 +1892,6 @@ sendPushNotificationReadMessageCaption: Powiadomienie zawierające tekst "{empty
baterii Twojego urządzenia.
defaultReaction: Domyślna reakcja emoji dla wychodzących i przychodzących wpisów
license: Licencja
indexPosts: Indeksuj wpisy
indexFrom: Indeksuj wpisy od ID
indexFromDescription: Zostaw puste dla indeksowania wszystkich wpisów
indexNotice: Indeksuję. Zapewne zajmie to chwilę, nie restartuj serwera przez co najmniej
godzinę.
customKaTeXMacro: Niestandardowe makra KaTeX
enableCustomKaTeXMacro: Włącz niestandardowe makra KaTeX
noteId: ID wpisu

View File

@ -1375,9 +1375,6 @@ _widgets:
userList: Список пользователей
_userList:
chooseList: Выберите список
meiliStatus: Состояние сервера
meiliSize: Размер индекса
meiliIndexCount: Индексированные посты
serverInfo: Информация о сервере
_cw:
hide: "Спрятать"
@ -1951,11 +1948,6 @@ showUpdates: Показывать всплывающее окно при обн
recommendedInstances: Рекомендованные серверы
defaultReaction: Эмодзи реакция по умолчанию для выходящих и исходящих постов
license: Лицензия
indexPosts: Индексировать посты
indexFrom: Индексировать начиная с идентификатора поста и далее
indexFromDescription: оставьте пустым для индексации каждого поста
indexNotice: Теперь индексирование. Вероятно, это займет некоторое время, пожалуйста,
не перезагружайте свой сервер по крайней мере в течение часа.
customKaTeXMacro: Кастомные KaTex макросы
enableCustomKaTeXMacro: Включить кастомные KaTeX макросы
noteId: Идентификатор поста

View File

@ -1316,8 +1316,6 @@ customMOTDDescription: ข้อความหน้าจอเริ่มต
คั่นด้วยการขึ้นบรรทัดใหม่เพื่อแสดงแบบสุ่มทุกครั้งที่ผู้ใช้โหลดเว็บหรือโหลดหน้าเว็บซ้ำ
caption: คำอธิบายโดยอัตโนมัติ
moveToLabel: 'บัญชีที่คุณจะย้ายไปยัง:'
indexFromDescription: เว้นว่างไว้เพื่อสร้างดัชนีทุกโพสต์
indexNotice: ตอนนี้กำลังจัดทำดัชนี การดำเนินการนี้อาจใช้เวลาสักครู่ โปรดอย่ารีสตาร์ทเซิร์ฟเวอร์เป็นเวลาอย่างน้อยหนึ่งชั่วโมง
noteId: โพสต์ ID
apps: แอป
enableRecommendedTimeline: เปิดใช้งาน ไทม์ไลน์ที่แนะนำ
@ -1371,9 +1369,7 @@ moveFromDescription: การดำเนินการนี้จะตั
migrationConfirm: "คุณแน่ใจหรือไม่ว่าคุณต้องการย้ายบัญชีของคุณไปยัง {account} เมื่อคุณทำเช่นนี้
คุณจะไม่สามารถกู้คืนมาได้ และคุณจะไม่สามารถใช้บัญชีของคุณได้ตามปกติอีก\nนอกจากนี้
โปรดตรวจสอบให้แน่ใจว่าคุณได้ตั้งบัญชีปัจจุบันนี้เป็นบัญชีที่คุณจะย้ายออก"
indexFrom: จัดทำดัชนีตั้งแต่ Post ID เป็นต้นไป
license: ใบอนุญาต
indexPosts: ดัชนีโพสต์
signupsDisabled: การลงชื่อสมัครใช้บนเซิร์ฟเวอร์นี้ถูกปิดใช้งานอยู่ในขณะนี้ แต่คุณสามารถสมัครที่เซิร์ฟเวอร์อื่นได้ตลอดเวลา
หากคุณมีรหัสเชิญสำหรับเซิร์ฟเวอร์นี้ โปรดป้อนรหัสด้านล่าง
customKaTeXMacroDescription: 'ตั้งค่ามาโครเพื่อเขียนนิพจน์ทางคณิตศาสตร์ได้อย่างง่ายดาย

View File

@ -158,7 +158,6 @@ _widgets:
activity: Aktivite
digitalClock: Dijital Saat
unixClock: UNIX Saati
meiliIndexCount: Indexlenmiş gönderiler
calendar: Takvim
trends: Popüler
memo: Yapışkan Notlar
@ -166,13 +165,11 @@ _widgets:
federation: Federasyon
instanceCloud: Sunucu Bulutu
postForm: Gönderi Formu
meiliSize: Index boyutu
slideshow: Slayt Gösterisi
button: Düğme
clock: Saat
rss: RSS Okuyucu
serverInfo: Sunucu Bilgisi
meiliStatus: Sunucu Durumu
jobQueue: İş Sırası
serverMetric: Sunucu Bilgileri
_profile:
@ -473,7 +470,6 @@ activeEmailValidationDescription: Tek kullanımlık adreslerin kontrol edilmesi
sağlar. İşaretlenmediğinde, yalnızca e-postanın biçimi doğrulanır.
move: Taşı
defaultReaction: Giden ve gelen gönderiler için varsayılan emoji tepkisi
indexPosts: Dizin Gönderileri
youGotNewFollower: takip etti
receiveFollowRequest: Takip isteği alındı
followRequestAccepted: Takip isteği onaylandı
@ -1084,7 +1080,6 @@ check: Kontrol Et
driveCapOverrideLabel: Bu kullanıcı için drive kapasitesini değiştirin
numberOfPageCache: Önbelleğe alınan sayfa sayısı
license: Lisans
indexFrom: Post ID'den itibaren dizin
xl: XL
notificationSetting: Bildirim ayarları
fillAbuseReportDescription: Lütfen bu raporla ilgili ayrıntıları doldurun. Belirli
@ -1159,9 +1154,6 @@ migrationConfirm: "Hesabınızı {account} hesabına taşımak istediğinizden k
emin misiniz? Bunu yaptığınızda, geri alamazsınız ve hesabınızı bir daha normal
şekilde kullanamazsınız.\nAyrıca, lütfen bu cari hesabı, taşındığınız hesap olarak
ayarladığınızdan emin olun."
indexFromDescription: Her gönderiyi dizine eklemek için boş bırakın
indexNotice: Şimdi indeksleniyor. Bu muhtemelen biraz zaman alacaktır, lütfen sunucunuzu
en az bir saat yeniden başlatmayın.
customKaTeXMacro: Özel KaTeX makroları
directNotes: Özel Mesajlar
import: İçeri Aktar

View File

@ -1199,14 +1199,11 @@ _widgets:
aiscript: "Консоль AiScript"
_userList:
chooseList: Оберіть список
meiliStatus: Стан сервера
meiliSize: Розмір індексу
rssTicker: RSS-тікер
instanceCloud: Хмара серверів
unixClock: Годинник UNIX
userList: Список користувачів
serverInfo: Інформація про сервер
meiliIndexCount: Індексовані записи
_cw:
hide: "Сховати"
show: "Показати більше"
@ -1977,11 +1974,6 @@ caption: Автоматичний опис
showAdminUpdates: Вказати, що доступна нова версія Firefish (тільки для адміністратора)
defaultReaction: Емодзі реакція за замовчуванням для вихідних і вхідних записів
license: Ліцензія
indexPosts: Індексувати пости
indexFrom: Індексувати записи з ID
indexFromDescription: Залиште порожнім, щоб індексувати кожен запис
indexNotice: Зараз відбувається індексація. Це, ймовірно, займе деякий час, будь ласка,
не перезавантажуйте сервер принаймні годину.
signupsDisabled: Реєстрація на цьому сервері наразі відключена, але ви завжди можете
зареєструватися на іншому сервері! Якщо у вас є код запрошення на цей сервер, будь
ласка, введіть його нижче.

View File

@ -1437,9 +1437,6 @@ _widgets:
userList: Danh sách người dùng
_userList:
chooseList: Chọn một danh sách
meiliSize: Kích cỡ chỉ mục
meiliIndexCount: Tút đã lập chỉ mục
meiliStatus: Trạng thái máy chủ
serverInfo: Thông tin máy chủ
_cw:
hide: "Ẩn"
@ -2048,8 +2045,6 @@ hiddenTagsDescription: 'Liệt kê các hashtag (không có #) mà bạn muốn
noInstances: Không có máy chủ nào
manageGroups: Quản lý nhóm
accessibility: Khả năng tiếp cận
indexNotice: Đang lập chỉ mục. Quá trình này có thể mất một lúc, vui lòng không khởi
động lại máy chủ của bạn sau ít nhất một giờ.
breakFollowConfirm: Bạn có chắc muốn xóa người theo dõi?
caption: Caption tự động
objectStorageS3ForcePathStyle: Sử dụng URL điểm cuối dựa trên đường dẫn
@ -2069,7 +2064,6 @@ updateAvailable: Có bản cập nhật mới!
swipeOnDesktop: Cho phép vuốt kiểu điện thoại trên máy tính
moveFromLabel: 'Tài khoản cũ của bạn:'
defaultReaction: Biểu cảm mặc định cho những tút đã đăng và sắp đăng
indexFromDescription: Để trống để lập chỉ mục toàn bộ
donationLink: Liên kết tới trang tài trợ
deletePasskeys: Xóa passkey
delete2faConfirm: Thao tác này sẽ xóa 2FA trên tài khoản này một cách không thể phục
@ -2086,8 +2080,6 @@ audio: Âm thanh
selectInstance: Chọn máy chủ
userSaysSomethingReason: '{name} cho biết {reason}'
pushNotification: Thông báo đẩy
indexPosts: Chỉ mục tút
indexFrom: Chỉ mục từ Post ID
customKaTeXMacro: Tùy chỉnh macro KaTeX
license: Giấy phép
cw: Nội dung ẩn

View File

@ -1350,9 +1350,6 @@ _widgets:
aiscript: "AiScript 控制台"
aichan: "小蓝"
userList: 用户列表
meiliStatus: 服务器状态
meiliIndexCount: 已索引的帖子
meiliSize: 索引大小
serverInfo: 服务器信息
_userList:
chooseList: 选择列表
@ -1917,7 +1914,6 @@ isAdmin: 管理员
findOtherInstance: 寻找其它服务器
moveFromDescription: 这将为您的旧账号设置一个别名,以便您可以从该旧账号迁移到当前账号。在从旧账号迁移之前执行此操作。请输入格式如 @person@server.com
的账号标签
indexPosts: 索引帖子
signupsDisabled: 该服务器目前关闭注册,但您随时可以在另一台服务器上注册!如果您有该服务器的邀请码,请在下面输入。
silencedWarning: 显示这个页面是因为这些用户来自您的管理员设置的禁言服务器,所以他们有可能是垃圾信息。
isBot: 这个账号是一个机器人
@ -1931,12 +1927,9 @@ moveTo: 将当前账号迁移至新账号
moveToLabel: 您要迁移到的目标账号:
moveAccount: 迁移账号!
migrationConfirm: "您确实确定要将账号迁移到 {account} 吗?此操作无法撤消,并且您将无法再次正常使用旧账号。\n另外请确保您已将此当前账号设置为要移出的账号。"
indexFromDescription: 留空以索引每个帖子
noteId: 帖子 ID
moveFrom: 从旧账号迁移至此账号
defaultReaction: 发出和收到帖子的默认表情符号反应
indexNotice: 现在开始索引。这可能需要一段时间,请至少一个小时内不要重新启动服务器。
indexFrom: 从帖子 ID 开始的索引
sendModMail: 发送审核通知
isLocked: 该账号设置了关注请求
_filters:

View File

@ -1349,9 +1349,6 @@ _widgets:
userList: 使用者列表
_userList:
chooseList: 選擇一個清單
meiliIndexCount: 編入索引的帖子
meiliStatus: 伺服器狀態
meiliSize: 索引大小
_cw:
hide: "隱藏"
show: "瀏覽更多"
@ -1879,8 +1876,6 @@ pushNotificationNotSupported: 你的瀏覽器或伺服器不支援推送通知
accessibility: 輔助功能
userSaysSomethingReasonReply: '{name} 回覆了包含 {reason} 的貼文'
hiddenTags: 隱藏主題標籤
indexPosts: 索引貼文
indexNotice: 現在開始索引。 這可能需要一段時間,請不要在一個小時內重啟你的伺服器。
deleted: 已刪除
editNote: 編輯貼文
edited: '於 {date} {time} 編輯'
@ -1925,10 +1920,8 @@ sendModMail: 發送審核通知
enableIdenticonGeneration: 啟用Identicon生成
enableServerMachineStats: 啟用伺服器硬體統計資訊
reactionPickerSkinTone: 首選表情符號膚色
indexFromDescription: 留空以索引每個貼文
preventAiLearning: 防止 AI 機器人抓取
preventAiLearningDescription: 請求第三方 AI 語言模型不要研究您上傳的內容,例如貼文和圖像。
indexFrom: 建立此貼文ID以後的索引
isLocked: 該帳戶已獲得以下批准
isModerator: 板主
isAdmin: 管理員

View File

@ -0,0 +1,21 @@
export class Pgroonga1698420787202 {
name = "Pgroonga1698420787202";
async up(queryRunner) {
await queryRunner.query(
`CREATE INDEX "IDX_f27f5d88941e57442be75ba9c8" ON "note" USING "pgroonga" ("text")`,
);
await queryRunner.query(
`CREATE INDEX "IDX_065d4d8f3b5adb4a08841eae3c" ON "user" USING "pgroonga" ("name" pgroonga_varchar_full_text_search_ops_v2)`,
);
await queryRunner.query(
`CREATE INDEX "IDX_fcb770976ff8240af5799e3ffc" ON "user_profile" USING "pgroonga" ("description" pgroonga_varchar_full_text_search_ops_v2) `,
);
}
async down(queryRunner) {
await queryRunner.query(`DROP INDEX "IDX_fcb770976ff8240af5799e3ffc"`);
await queryRunner.query(`DROP INDEX "IDX_065d4d8f3b5adb4a08841eae3c"`);
await queryRunner.query(`DROP INDEX "IDX_f27f5d88941e57442be75ba9c8"`);
}
}

View File

@ -25,7 +25,6 @@
"@bull-board/koa": "5.14.2",
"@bull-board/ui": "5.14.2",
"@discordapp/twemoji": "^15.0.2",
"@elastic/elasticsearch": "8.12.2",
"@koa/cors": "5.0.0",
"@koa/multer": "3.0.2",
"@koa/router": "12.0.1",
@ -81,7 +80,6 @@
"koa-send": "5.0.1",
"koa-slow": "2.1.0",
"megalodon": "workspace:*",
"meilisearch": "0.37.0",
"mfm-js": "0.24.0",
"mime-types": "2.1.35",
"msgpackr": "^1.10.1",
@ -112,7 +110,6 @@
"sanitize-html": "2.12.1",
"semver": "7.6.0",
"sharp": "0.33.2",
"sonic-channel": "^1.3.1",
"stringz": "2.1.0",
"summaly": "2.7.0",
"syslog-pro": "1.0.0",

View File

@ -37,27 +37,6 @@ export type Source = {
user?: string;
tls?: { [z: string]: string };
};
elasticsearch: {
host: string;
port: number;
ssl?: boolean;
user?: string;
pass?: string;
index?: string;
};
sonic: {
host: string;
port: number;
auth?: string;
collection?: string;
bucket?: string;
};
meilisearch: {
host: string;
port: number;
apiKey?: string;
ssl: boolean;
};
proxy?: string;
proxySmtp?: string;

View File

@ -2,7 +2,6 @@ import si from "systeminformation";
import Xev from "xev";
import * as osUtils from "os-utils";
import { fetchMeta } from "@/misc/fetch-meta.js";
import meilisearch from "@/db/meilisearch.js";
const ev = new Xev();
@ -30,7 +29,6 @@ export default function () {
const memStats = await mem();
const netStats = await net();
const fsStats = await fs();
const meilisearchStats = await meilisearchStatus();
const stats = {
cpu: roundCpu(cpu),
@ -47,7 +45,6 @@ export default function () {
r: round(Math.max(0, fsStats.rIO_sec ?? 0)),
w: round(Math.max(0, fsStats.wIO_sec ?? 0)),
},
meilisearch: meilisearchStats,
};
ev.emit("serverStats", stats);
log.unshift(stats);
@ -86,16 +83,3 @@ async function fs() {
const data = await si.disksIO().catch(() => ({ rIO_sec: 0, wIO_sec: 0 }));
return data || { rIO_sec: 0, wIO_sec: 0 };
}
// MEILI STAT
async function meilisearchStatus() {
if (meilisearch) {
return meilisearch.serverStats();
} else {
return {
health: "unconfigured",
size: 0,
indexed_count: 0,
};
}
}

View File

@ -1,65 +0,0 @@
import * as elasticsearch from "@elastic/elasticsearch";
import config from "@/config/index.js";
const index = {
settings: {
analysis: {
analyzer: {
ngram: {
tokenizer: "ngram",
},
},
},
},
mappings: {
properties: {
text: {
type: "text",
index: true,
analyzer: "ngram",
},
userId: {
type: "keyword",
index: true,
},
userHost: {
type: "keyword",
index: true,
},
},
},
};
// Init ElasticSearch connection
const client = config.elasticsearch
? new elasticsearch.Client({
node: `${config.elasticsearch.ssl ? "https://" : "http://"}${
config.elasticsearch.host
}:${config.elasticsearch.port}`,
auth:
config.elasticsearch.user && config.elasticsearch.pass
? {
username: config.elasticsearch.user,
password: config.elasticsearch.pass,
}
: undefined,
pingTimeout: 30000,
})
: null;
if (client) {
client.indices
.exists({
index: config.elasticsearch.index || "misskey_note",
})
.then((exist) => {
if (!exist.body) {
client.indices.create({
index: config.elasticsearch.index || "misskey_note",
body: index,
});
}
});
}
export default client;

View File

@ -1,451 +0,0 @@
import { Health, Index, MeiliSearch, Stats } from "meilisearch";
import { dbLogger } from "./logger.js";
import config from "@/config/index.js";
import { Note } from "@/models/entities/note.js";
import * as url from "url";
import { ILocalUser } from "@/models/entities/user.js";
import { Followings, Users } from "@/models/index.js";
const logger = dbLogger.createSubLogger("meilisearch", "gray", false);
let posts: Index;
let client: MeiliSearch;
const hasConfig =
config.meilisearch &&
(config.meilisearch.host ||
config.meilisearch.port ||
config.meilisearch.apiKey);
if (hasConfig) {
const host = hasConfig ? config.meilisearch.host ?? "localhost" : "";
const port = hasConfig ? config.meilisearch.port ?? 7700 : 0;
const auth = hasConfig ? config.meilisearch.apiKey ?? "" : "";
const ssl = hasConfig ? config.meilisearch.ssl ?? false : false;
logger.info("Connecting to MeiliSearch");
client = new MeiliSearch({
host: `${ssl ? "https" : "http"}://${host}:${port}`,
apiKey: auth,
});
posts = client.index("posts");
posts
.updateSearchableAttributes(["text"])
.catch((e) =>
logger.error(`Setting searchable attr failed, searches won't work: ${e}`),
);
posts
.updateFilterableAttributes([
"userName",
"userHost",
"mediaAttachment",
"createdAt",
"userId",
])
.catch((e) =>
logger.error(
`Setting filterable attr failed, advanced searches won't work: ${e}`,
),
);
posts
.updateSortableAttributes(["createdAt"])
.catch((e) =>
logger.error(
`Setting sortable attr failed, placeholder searches won't sort properly: ${e}`,
),
);
posts
.updateStopWords([
"the",
"a",
"as",
"be",
"of",
"they",
"these",
"is",
"are",
"これ",
"それ",
"あれ",
"この",
"その",
"あの",
"ここ",
"そこ",
"あそこ",
"こちら",
"どこ",
"私",
"僕",
"俺",
"君",
"あなた",
"我々",
"私達",
"彼女",
"彼",
"です",
"ます",
"は",
"が",
"の",
"に",
"を",
"で",
"へ",
"から",
"まで",
"より",
"も",
"どの",
"と",
"それで",
"しかし",
])
.catch((e) =>
logger.error(
`Failed to set Meilisearch stop words, database size will be larger: ${e}`,
),
);
posts
.updateRankingRules([
"sort",
"words",
"typo",
"proximity",
"attribute",
"exactness",
])
.catch((e) => {
logger.error("Failed to set ranking rules, sorting won't work properly.");
});
logger.info("Connected to MeiliSearch");
}
export type MeilisearchNote = {
id: string;
text: string;
userId: string;
userHost: string;
userName: string;
channelId: string;
mediaAttachment: string;
createdAt: number;
};
function timestampToUnix(timestamp: string) {
let unix = 0;
// Only contains numbers => UNIX timestamp
if (/^\d+$/.test(timestamp)) {
unix = Number.parseInt(timestamp);
}
if (unix === 0) {
// Try to parse the timestamp as JavaScript Date
const date = Date.parse(timestamp);
if (Number.isNaN(date)) return 0;
unix = date / 1000;
}
return unix;
}
export default hasConfig
? {
search: async (
query: string,
limit: number,
offset: number,
userCtx: ILocalUser | null,
overrideSort: string | null,
) => {
/// Advanced search syntax
/// from:user => filter by user + optional domain
/// has:image/video/audio/text/file => filter by attachment types
/// domain:domain.com => filter by domain
/// before:Date => show posts made before Date
/// after: Date => show posts made after Date
/// "text" => get posts with exact text between quotes
/// filter:following => show results only from users you follow
/// filter:followers => show results only from followers
/// order:desc/asc => order results ascending or descending
const constructedFilters: string[] = [];
let sortRules: string[] = [];
const splitSearch = query.split(" ");
// Detect search operators and remove them from the actual query
const filteredSearchTerms = (
await Promise.all(
splitSearch.map(async (term) => {
if (term.startsWith("has:")) {
const fileType = term.slice(4);
constructedFilters.push(`mediaAttachment = "${fileType}"`);
return null;
} else if (term.startsWith("from:")) {
let user = term.slice(5);
if (user.length === 0) return null;
// Cut off leading @, those aren't saved in the DB
if (user.charAt(0) === "@") {
user = user.slice(1);
}
// Determine if we got a webfinger address or a single username
if (user.split("@").length > 1) {
const splitUser = user.split("@");
const domain = splitUser.pop();
user = splitUser.join("@");
constructedFilters.push(
`userName = ${user} AND userHost = ${domain}`,
);
} else {
constructedFilters.push(`userName = ${user}`);
}
return null;
} else if (term.startsWith("domain:")) {
const domain = term.slice(7);
if (
domain.length === 0 ||
domain === "local" ||
domain === config.hostname
) {
constructedFilters.push("userHost NOT EXISTS");
return null;
}
constructedFilters.push(`userHost = ${domain}`);
return null;
} else if (term.startsWith("after:")) {
const timestamp = term.slice(6);
const unix = timestampToUnix(timestamp);
if (unix !== 0) constructedFilters.push(`createdAt > ${unix}`);
return null;
} else if (term.startsWith("before:")) {
const timestamp = term.slice(7);
const unix = timestampToUnix(timestamp);
if (unix !== 0) constructedFilters.push(`createdAt < ${unix}`);
return null;
} else if (term.startsWith("filter:following")) {
// Check if we got a context user
if (userCtx) {
// Fetch user follows from DB
const followedUsers = await Followings.find({
where: {
followerId: userCtx.id,
},
select: {
followeeId: true,
},
});
const followIDs = followedUsers.map(
(user) => user.followeeId,
);
if (followIDs.length === 0) return null;
constructedFilters.push(`userId IN [${followIDs.join(",")}]`);
} else {
logger.warn(
"search filtered to follows called without user context",
);
}
return null;
} else if (term.startsWith("filter:followers")) {
// Check if we got a context user
if (userCtx) {
// Fetch users follows from DB
const followedUsers = await Followings.find({
where: {
followeeId: userCtx.id,
},
select: {
followerId: true,
},
});
const followIDs = followedUsers.map(
(user) => user.followerId,
);
if (followIDs.length === 0) return null;
constructedFilters.push(`userId IN [${followIDs.join(",")}]`);
} else {
logger.warn(
"search filtered to followers called without user context",
);
}
return null;
} else if (term.startsWith("order:desc")) {
sortRules.push("createdAt:desc");
return null;
} else if (term.startsWith("order:asc")) {
sortRules.push("createdAt:asc");
return null;
}
return term;
}),
)
).filter((term) => term !== null);
// An empty search term with defined filters means we have a placeholder search => https://www.meilisearch.com/docs/reference/api/search#placeholder-search
// These have to be ordered manually, otherwise the *oldest* posts are returned first, which we don't want
// If the user has defined a sort rule, don't mess with it
if (
filteredSearchTerms.length === 0 &&
constructedFilters.length > 0 &&
sortRules.length === 0
) {
sortRules.push("createdAt:desc");
}
// More than one sorting rule doesn't make sense. We only keep the first one, otherwise weird stuff may happen.
if (sortRules.length > 1) {
sortRules = [sortRules[0]];
}
// An override sort takes precedence, user sorting is ignored here
if (overrideSort) {
sortRules = [overrideSort];
}
logger.info(`Searching for ${filteredSearchTerms.join(" ")}`);
logger.info(`Limit: ${limit}`);
logger.info(`Offset: ${offset}`);
logger.info(`Filters: ${constructedFilters}`);
logger.info(`Ordering: ${sortRules}`);
return posts.search(filteredSearchTerms.join(" "), {
limit: limit,
offset: offset,
filter: constructedFilters,
sort: sortRules,
});
},
ingestNote: async (ingestNotes: Note | Note[]) => {
if (ingestNotes instanceof Note) {
ingestNotes = [ingestNotes];
}
const indexingBatch: MeilisearchNote[] = [];
for (const note of ingestNotes) {
if (note.user === undefined) {
note.user = await Users.findOne({
where: {
id: note.userId,
},
});
}
let attachmentType = "";
if (note.attachedFileTypes.length > 0) {
attachmentType = note.attachedFileTypes[0].split("/")[0];
switch (attachmentType) {
case "image":
case "video":
case "audio":
case "text":
break;
default:
attachmentType = "file";
break;
}
}
indexingBatch.push(<MeilisearchNote>{
id: note.id.toString(),
text: note.text ? note.text : "",
userId: note.userId,
userHost:
note.userHost !== ""
? note.userHost
: url.parse(config.host).host,
channelId: note.channelId ? note.channelId : "",
mediaAttachment: attachmentType,
userName: note.user?.username ?? "UNKNOWN",
createdAt: note.createdAt.getTime() / 1000, // division by 1000 is necessary because Node returns in ms-accuracy
});
}
return posts
.addDocuments(indexingBatch, {
primaryKey: "id",
})
.then(() =>
logger.info(`sent ${indexingBatch.length} posts for indexing`),
);
},
serverStats: async () => {
const health: Health = await client.health();
const stats: Stats = await client.getStats();
return {
health: health.status,
size: stats.databaseSize,
indexed_count: stats.indexes["posts"].numberOfDocuments,
};
},
deleteNotes: async (note: Note | Note[] | string | string[]) => {
if (note instanceof Note) {
note = [note];
}
if (typeof note === "string") {
note = [note];
}
const deletionBatch = note
.map((n) => {
if (n instanceof Note) {
return n.id;
}
if (n.length > 0) return n;
logger.error(
`Failed to delete note from Meilisearch, invalid post ID: ${JSON.stringify(
n,
)}`,
);
throw new Error(
`Invalid note ID passed to meilisearch deleteNote: ${JSON.stringify(
n,
)}`,
);
})
.filter((el) => el !== null);
await posts.deleteDocuments(deletionBatch as string[]).then(() => {
logger.info(
`submitted ${deletionBatch.length} large batch for deletion`,
);
});
},
}
: null;

View File

@ -1,51 +0,0 @@
import * as SonicChannel from "sonic-channel";
import { dbLogger } from "./logger.js";
import config from "@/config/index.js";
const logger = dbLogger.createSubLogger("sonic", "gray", false);
const handlers = (type: string): SonicChannel.Handlers => ({
connected: () => {
logger.succ(`Connected to Sonic ${type}`);
},
disconnected: (error) => {
logger.warn(`Disconnected from Sonic ${type}, error: ${error}`);
},
error: (error) => {
logger.warn(`Sonic ${type} error: ${error}`);
},
retrying: () => {
logger.info(`Sonic ${type} retrying`);
},
timeout: () => {
logger.warn(`Sonic ${type} timeout`);
},
});
const hasConfig =
config.sonic && (config.sonic.host || config.sonic.port || config.sonic.auth);
if (hasConfig) {
logger.info("Connecting to Sonic");
}
const host = hasConfig ? config.sonic.host ?? "localhost" : "";
const port = hasConfig ? config.sonic.port ?? 1491 : 0;
const auth = hasConfig ? config.sonic.auth ?? "SecretPassword" : "";
const collection = hasConfig ? config.sonic.collection ?? "main" : "";
const bucket = hasConfig ? config.sonic.bucket ?? "default" : "";
export default hasConfig
? {
search: new SonicChannel.Search({ host, port, auth }).connect(
handlers("search"),
),
ingest: new SonicChannel.Ingest({ host, port, auth }).connect(
handlers("ingest"),
),
collection,
bucket,
}
: null;

View File

@ -70,6 +70,7 @@ export class DriveFile {
})
public size: number;
@Index() // USING pgroonga pgroonga_varchar_full_text_search_ops_v2
@Column("varchar", {
length: DB_MAX_IMAGE_COMMENT_LENGTH,
nullable: true,

View File

@ -61,6 +61,7 @@ export class Note {
})
public threadId: string | null;
@Index() // USING pgroonga
@Column("text", {
nullable: true,
})
@ -78,6 +79,7 @@ export class Note {
})
public name: string | null;
@Index() // USING pgroonga pgroonga_varchar_full_text_search_ops_v2
@Column("varchar", {
length: 512,
nullable: true,

View File

@ -38,6 +38,7 @@ export class UserProfile {
})
public birthday: string | null;
@Index() // USING pgroonga pgroonga_varchar_full_text_search_ops_v2
@Column("varchar", {
length: 2048,
nullable: true,

View File

@ -58,6 +58,7 @@ export class User {
})
public usernameLower: string;
@Index() // USING pgroonga pgroonga_varchar_full_text_search_ops_v2
@Column("varchar", {
length: 128,
nullable: true,

View File

@ -13,7 +13,6 @@ import processDb from "./processors/db/index.js";
import processObjectStorage from "./processors/object-storage/index.js";
import processSystemQueue from "./processors/system/index.js";
import processWebhookDeliver from "./processors/webhook-deliver.js";
import processBackground from "./processors/background/index.js";
import { endedPollNotification } from "./processors/ended-poll-notification.js";
import { queueLogger } from "./logger.js";
import { getJobInfo } from "./get-job-info.js";
@ -482,14 +481,6 @@ export function createCleanRemoteFilesJob() {
);
}
export function createIndexAllNotesJob(data = {}) {
return backgroundQueue.add("indexAllNotes", data, {
removeOnComplete: true,
removeOnFail: false,
timeout: 1000 * 60 * 60 * 24,
});
}
export function webhookDeliver(
webhook: Webhook,
type: (typeof webhookEventTypes)[number],
@ -526,7 +517,6 @@ export default function () {
webhookDeliverQueue.process(64, processWebhookDeliver);
processDb(dbQueue);
processObjectStorage(objectStorageQueue);
processBackground(backgroundQueue);
systemQueue.add(
"cleanCharts",

View File

@ -1,89 +0,0 @@
import type Bull from "bull";
import type { DoneCallback } from "bull";
import { queueLogger } from "../../logger.js";
import { Notes } from "@/models/index.js";
import { MoreThan } from "typeorm";
import { index } from "@/services/note/create.js";
import { Note } from "@/models/entities/note.js";
import meilisearch from "@/db/meilisearch.js";
import { inspect } from "node:util";
const logger = queueLogger.createSubLogger("index-all-notes");
export default async function indexAllNotes(
job: Bull.Job<Record<string, unknown>>,
done: DoneCallback,
): Promise<void> {
logger.info("Indexing all notes...");
let cursor: string | null = (job.data.cursor as string) ?? null;
let indexedCount: number = (job.data.indexedCount as number) ?? 0;
let total: number = (job.data.total as number) ?? 0;
let running = true;
const take = 10000;
const batch = 100;
while (running) {
logger.info(
`Querying for ${take} notes ${indexedCount}/${
total ? total : "?"
} at ${cursor}`,
);
let notes: Note[] = [];
try {
notes = await Notes.find({
where: {
...(cursor ? { id: MoreThan(cursor) } : {}),
},
take: take,
order: {
id: 1,
},
relations: ["user"],
});
} catch (e: any) {
logger.error(`Failed to query notes:\n${inspect(e)}`);
done(e);
break;
}
if (notes.length === 0) {
await job.progress(100);
running = false;
break;
}
try {
const count = await Notes.count();
total = count;
await job.update({ indexedCount, cursor, total });
} catch (e) {}
for (let i = 0; i < notes.length; i += batch) {
const chunk = notes.slice(i, i + batch);
if (meilisearch) {
await meilisearch.ingestNote(chunk);
}
await Promise.all(chunk.map((note) => index(note, true)));
indexedCount += chunk.length;
const pct = (indexedCount / total) * 100;
await job.update({ indexedCount, cursor, total });
await job.progress(+pct.toFixed(1));
logger.info(`Indexed notes ${indexedCount}/${total ? total : "?"}`);
}
cursor = notes[notes.length - 1].id;
await job.update({ indexedCount, cursor, total });
if (notes.length < take) {
running = false;
}
}
done();
logger.info("All notes have been indexed.");
}

View File

@ -1,12 +0,0 @@
import type Bull from "bull";
import indexAllNotes from "./index-all-notes.js";
const jobs = {
indexAllNotes,
} as Record<string, Bull.ProcessCallbackFunction<Record<string, unknown>>>;
export default function (q: Bull.Queue) {
for (const [k, v] of Object.entries(jobs)) {
q.process(k, 16, v);
}
}

View File

@ -7,7 +7,6 @@ import type { DriveFile } from "@/models/entities/drive-file.js";
import { MoreThan } from "typeorm";
import { deleteFileSync } from "@/services/drive/delete-file.js";
import { sendEmail } from "@/services/send-email.js";
import meilisearch from "@/db/meilisearch.js";
const logger = queueLogger.createSubLogger("delete-account");
@ -42,9 +41,6 @@ export async function deleteAccount(
cursor = notes[notes.length - 1].id;
await Notes.delete(notes.map((note) => note.id));
if (meilisearch) {
await meilisearch.deleteNotes(notes);
}
}
logger.succ("All of notes deleted");

View File

@ -51,7 +51,6 @@ import * as ep___admin_relays_list from "./endpoints/admin/relays/list.js";
import * as ep___admin_relays_remove from "./endpoints/admin/relays/remove.js";
import * as ep___admin_resetPassword from "./endpoints/admin/reset-password.js";
import * as ep___admin_resolveAbuseUserReport from "./endpoints/admin/resolve-abuse-user-report.js";
import * as ep___admin_search_indexAll from "./endpoints/admin/search/index-all.js";
import * as ep___admin_sendEmail from "./endpoints/admin/send-email.js";
import * as ep___admin_sendModMail from "./endpoints/admin/send-mod-mail.js";
import * as ep___admin_serverInfo from "./endpoints/admin/server-info.js";
@ -400,7 +399,6 @@ const eps = [
["admin/relays/remove", ep___admin_relays_remove],
["admin/reset-password", ep___admin_resetPassword],
["admin/resolve-abuse-user-report", ep___admin_resolveAbuseUserReport],
["admin/search/index-all", ep___admin_search_indexAll],
["admin/send-email", ep___admin_sendEmail],
["admin/send-mod-mail", ep___admin_sendModMail],
["admin/server-info", ep___admin_serverInfo],

View File

@ -1,28 +0,0 @@
import define from "@/server/api/define.js";
import { createIndexAllNotesJob } from "@/queue/index.js";
export const meta = {
tags: ["admin"],
requireCredential: true,
requireModerator: true,
} as const;
export const paramDef = {
type: "object",
properties: {
cursor: {
type: "string",
format: "misskey:id",
nullable: true,
default: null,
},
},
required: [],
} as const;
export default define(meta, paramDef, async (ps, _me) => {
createIndexAllNotesJob({
cursor: ps.cursor ?? undefined,
});
});

View File

@ -1,5 +1,4 @@
import { In } from "typeorm";
import { index } from "@/services/note/create.js";
import type { IRemoteUser, User } from "@/models/entities/user.js";
import {
Users,
@ -626,8 +625,6 @@ export default define(meta, paramDef, async (ps, user) => {
}
if (publishing && user.isIndexable) {
index(note, true);
// Publish update event for the updated note details
publishNoteStream(note.id, "updated", {
updatedAt: update.updatedAt,

View File

@ -1,10 +1,5 @@
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 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";
@ -69,282 +64,43 @@ export const paramDef = {
} as const;
export default define(meta, paramDef, async (ps, me) => {
if (es == null && sonic == null && meilisearch == null) {
const query = makePaginationQuery(
Notes.createQueryBuilder("note"),
ps.sinceId,
ps.untilId,
);
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,
});
}
query
.andWhere("note.text ILIKE :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");
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;
// Use sonic to fetch and step through all search results that could match the requirements
const ids = [];
while (true) {
const results = await sonic.search.query(
sonic.collection,
sonic.bucket,
ps.query,
{
limit: chunkSize,
offset: start,
},
);
start += chunkSize;
if (results.length === 0) {
break;
}
const res = results
.map((k) => JSON.parse(k))
.filter((key) => {
if (ps.userId && key.userId !== ps.userId) {
return false;
}
if (ps.channelId && key.channelId !== ps.channelId) {
return false;
}
if (ps.sinceId && key.id <= ps.sinceId) {
return false;
}
if (ps.untilId && key.id >= ps.untilId) {
return false;
}
return true;
})
.map((key) => key.id);
ids.push(...res);
}
// Sort all the results by note id DESC (newest first)
ids.sort((a, b) => b - a);
// Fetch the notes from the database until we have enough to satisfy the limit
start = 0;
const found = [];
while (found.length < ps.limit && start < ids.length) {
const chunk = ids.slice(start, start + chunkSize);
const notes: Note[] = await Notes.find({
where: {
id: In(chunk),
},
});
// The notes are checked for visibility and muted/blocked users when packed
found.push(...(await Notes.packMany(notes, me)));
start += chunkSize;
}
// If we have more results than the limit, trim them
if (found.length > ps.limit) {
found.length = ps.limit;
}
return found;
} else if (meilisearch) {
let start = 0;
const chunkSize = 100;
const sortByDate = ps.order !== "relevancy";
type NoteResult = {
id: string;
createdAt: number;
};
const extractedNotes: NoteResult[] = [];
while (true) {
const searchRes = await meilisearch.search(
ps.query,
chunkSize,
start,
me,
sortByDate ? "createdAt:desc" : null,
);
const results: MeilisearchNote[] = searchRes.hits as MeilisearchNote[];
start += chunkSize;
if (results.length === 0) {
break;
}
const res = results
.filter((key: MeilisearchNote) => {
if (ps.userId && key.userId !== ps.userId) {
return false;
}
if (ps.channelId && key.channelId !== ps.channelId) {
return false;
}
if (ps.sinceId && key.id <= ps.sinceId) {
return false;
}
if (ps.untilId && key.id >= ps.untilId) {
return false;
}
return true;
})
.map((key) => {
return {
id: key.id,
createdAt: key.createdAt,
};
});
extractedNotes.push(...res);
}
// Fetch the notes from the database until we have enough to satisfy the limit
start = 0;
const found = [];
const noteIDs = extractedNotes.map((note) => note.id);
// Index the ID => index number into a map, so we can restore the array ordering efficiently later
const idIndexMap = new Map(noteIDs.map((id, index) => [id, index]));
while (found.length < ps.limit && start < noteIDs.length) {
const chunk = noteIDs.slice(start, start + chunkSize);
let query: FindManyOptions = {
where: {
id: In(chunk),
},
};
const notes: Note[] = await Notes.find(query);
// Re-order the note result according to the noteIDs array (cannot be undefined, we map this earlier)
// @ts-ignore
notes.sort((a, b) => idIndexMap.get(a.id) - idIndexMap.get(b.id));
// The notes are checked for visibility and muted/blocked users when packed
found.push(...(await Notes.packMany(notes, me)));
start += chunkSize;
}
// If we have more results than the limit, trim the results down
if (found.length > ps.limit) {
found.length = ps.limit;
}
return found;
} else {
const userQuery =
ps.userId != null
? [
{
term: {
userId: ps.userId,
},
},
]
: [];
const hostQuery =
ps.userId == null
? ps.host === null
? [
{
bool: {
must_not: {
exists: {
field: "userHost",
},
},
},
},
]
: ps.host !== undefined
? [
{
term: {
userHost: ps.host,
},
},
]
: []
: [];
const result = await es.search({
index: config.elasticsearch.index || "misskey_note",
body: {
size: ps.limit,
from: ps.offset,
query: {
bool: {
must: [
{
simple_query_string: {
fields: ["text"],
query: ps.query.toLowerCase(),
default_operator: "and",
},
},
...hostQuery,
...userQuery,
],
},
},
sort: [
{
_doc: "desc",
},
],
},
});
const hits = result.body.hits.hits.map((hit: any) => hit._id);
if (hits.length === 0) return [];
// Fetch found notes
const notes = await Notes.find({
where: {
id: In(hits),
},
order: {
id: -1,
},
});
return await Notes.packMany(notes, me);
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
.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");
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);
});

View File

@ -1,7 +1,6 @@
import * as os from "node:os";
import si from "systeminformation";
import define from "@/server/api/define.js";
import meilisearch from "@/db/meilisearch.js";
import { fetchMeta } from "@/misc/fetch-meta.js";
export const meta = {
@ -63,15 +62,3 @@ export default define(meta, paramDef, async () => {
},
};
});
async function meilisearchStatus() {
if (meilisearch) {
return meilisearch.serverStats();
} else {
return {
health: "unconfigured",
size: 0,
indexed_count: 0,
};
}
}

View File

@ -78,8 +78,8 @@ export default define(meta, paramDef, async (ps, me) => {
const nameQuery = Users.createQueryBuilder("user")
.where(
new Brackets((qb) => {
qb.where("user.name ILIKE :query", {
query: `%${sqlLikeEscape(ps.query)}%`,
qb.where("user.name &@~ :query", {
query: `${sqlLikeEscape(ps.query)}`,
});
// Also search username if it qualifies as username
@ -115,8 +115,8 @@ export default define(meta, paramDef, async (ps, me) => {
if (users.length < ps.limit) {
const profQuery = UserProfiles.createQueryBuilder("prof")
.select("prof.userId")
.where("prof.description ILIKE :query", {
query: `%${sqlLikeEscape(ps.query)}%`,
.where("prof.description &@~ :query", {
query: `${sqlLikeEscape(ps.query)}`,
});
if (ps.origin === "local") {

View File

@ -82,7 +82,6 @@ const nodeinfo2 = async () => {
disableRecommendedTimeline: meta.disableRecommendedTimeline,
disableGlobalTimeline: meta.disableGlobalTimeline,
emailRequiredForSignup: meta.emailRequiredForSignup,
searchFilters: config.meilisearch ? true : false,
postEditing: true,
postImports: meta.experimentalFeatures?.postImports || false,
enableHcaptcha: meta.enableHcaptcha,

View File

@ -1,6 +1,4 @@
import * as mfm from "mfm-js";
import es from "@/db/elasticsearch.js";
import sonic from "@/db/sonic.js";
import {
publishMainStream,
publishNotesStream,
@ -59,7 +57,6 @@ import type { UserProfile } from "@/models/entities/user-profile.js";
import { db } from "@/db/postgre.js";
import { getActiveWebhooks } from "@/misc/webhook-cache.js";
import { shouldSilenceInstance } from "@/misc/should-block-instance.js";
import meilisearch from "@/db/meilisearch.js";
import { redisClient } from "@/db/redis.js";
import { Mutex } from "redis-semaphore";
import { langmap } from "@/misc/langmap.js";
@ -166,7 +163,6 @@ export default async (
createdAt: User["createdAt"];
isBot: User["isBot"];
inbox?: User["inbox"];
isIndexable?: User["isIndexable"];
},
data: Option,
silent = false,
@ -654,11 +650,6 @@ export default async (
}
});
}
// Register to search database
if (user.isIndexable) {
await index(note, false);
}
});
async function renderNoteOrRenoteActivity(data: Option, note: Note) {
@ -814,40 +805,6 @@ async function insertNote(
}
}
export async function index(note: Note, reindexing: boolean): Promise<void> {
if (!note.text || note.visibility !== "public") return;
if (config.elasticsearch && es) {
es.index({
index: config.elasticsearch.index || "misskey_note",
id: note.id.toString(),
body: {
text: normalizeForSearch(note.text),
userId: note.userId,
userHost: note.userHost,
},
});
}
if (sonic) {
await sonic.ingest.push(
sonic.collection,
sonic.bucket,
JSON.stringify({
id: note.id,
userId: note.userId,
userHost: note.userHost,
channelId: note.channelId,
}),
note.text,
);
}
if (meilisearch && !reindexing) {
await meilisearch.ingestNote(note);
}
}
async function notifyToWatchersOfRenotee(
renote: Note,
user: { id: User["id"] },

View File

@ -16,7 +16,6 @@ import {
import { countSameRenotes } from "@/misc/count-same-renotes.js";
import { registerOrFetchInstanceDoc } from "@/services/register-or-fetch-instance-doc.js";
import { deliverToRelays } from "@/services/relay.js";
import meilisearch from "@/db/meilisearch.js";
/**
* 稿
@ -117,10 +116,6 @@ export default async function (
userId: user.id,
});
}
if (meilisearch) {
await meilisearch.deleteNotes(note.id);
}
}
async function findCascadingNotes(note: Note) {

View File

@ -7,9 +7,6 @@
:display-back-button="true"
/></template>
<MkSpacer :content-max="800" :margin-min="16" :margin-max="32">
<FormButton primary @click="indexPosts">{{
i18n.ts.indexPosts
}}</FormButton>
<FormSuspense
v-slot="{ result: database }"
:p="databasePromiseFactory"
@ -44,7 +41,6 @@ import bytes from "@/filters/bytes";
import number from "@/filters/number";
import { i18n } from "@/i18n";
import { definePageMetadata } from "@/scripts/page-metadata";
import { indexPosts } from "@/scripts/index-posts";
import icon from "@/scripts/icon";
const databasePromiseFactory = () =>

View File

@ -78,7 +78,6 @@ import * as os from "@/os";
import { lookupUser } from "@/scripts/lookup-user";
import { lookupFile } from "@/scripts/lookup-file";
import { lookupInstance } from "@/scripts/lookup-instance";
import { indexPosts } from "@/scripts/index-posts";
import { defaultStore } from "@/store";
import { useRouter } from "@/router";
import {
@ -156,16 +155,6 @@ const menuDef = computed(() => [
},
]
: []),
...($i.isAdmin
? [
{
type: "button",
icon: `${icon("ph-list-magnifying-glass")}`,
text: i18n.ts.indexPosts,
action: indexPosts,
},
]
: []),
],
},
{

View File

@ -29,24 +29,6 @@
<p>Used: {{ bytes(diskUsed, 1) }}</p>
</div>
</div>
<div class="_panel">
<XPie class="pie" :value="meiliProgress" />
<div>
<p><i :class="icon('ph-file-search')"></i>MeiliSearch</p>
<p>
{{ i18n.ts._widgets.meiliStatus }}: {{ meiliAvailable }}
</p>
<p>
{{ i18n.ts._widgets.meiliSize }}:
{{ bytes(meiliTotalSize, 1) }}
</p>
<p>
{{ i18n.ts._widgets.meiliIndexCount }}:
{{ meiliIndexCount }}
</p>
</div>
</div>
</div>
</div>
</template>
@ -57,7 +39,6 @@ import XPie from "../../widgets/server-metric/pie.vue";
import bytes from "@/filters/bytes";
import { useStream } from "@/stream";
import * as os from "@/os";
import { i18n } from "@/i18n";
import icon from "@/scripts/icon";
const stream = useStream();
@ -72,11 +53,6 @@ const memTotal = ref(0);
const memUsed = ref(0);
const memFree = ref(0);
const meiliProgress = ref(0);
const meiliTotalSize = ref(0);
const meiliIndexCount = ref(0);
const meiliAvailable = ref("unavailable");
const diskUsage = computed(() => meta.fs.used / meta.fs.total);
const diskTotal = computed(() => meta.fs.total);
const diskUsed = computed(() => meta.fs.used);
@ -89,11 +65,6 @@ function onStats(stats) {
memTotal.value = stats.mem.total;
memUsed.value = stats.mem.active;
memFree.value = memTotal.value - memUsed.value;
meiliTotalSize.value = stats.meilisearch.size;
meiliIndexCount.value = stats.meilisearch.indexed_count;
meiliAvailable.value = stats.meilisearch.health;
meiliProgress.value = meiliIndexCount.value / serverStats.notesCount;
}
const connection = stream.useChannel("serverStats");

View File

@ -1,26 +0,0 @@
import { i18n } from "@/i18n";
import * as os from "@/os";
export async function indexPosts() {
const { canceled, result: index } = await os.inputText({
title: i18n.ts.indexFrom,
text: i18n.ts.indexFromDescription,
});
if (canceled) return;
if (index == null || index === "") {
await os.api("admin/search/index-all");
await os.alert({
type: "info",
text: i18n.ts.indexNotice,
});
} else {
await os.api("admin/search/index-all", {
cursor: index,
});
await os.alert({
type: "info",
text: i18n.ts.indexNotice,
});
}
}

View File

@ -46,13 +46,6 @@
:connection="connection"
:meta="meta"
/>
<XMeili
v-else-if="
instance.features.searchFilters && widgetProps.view === 5
"
:connection="connection"
:meta="meta"
/>
</div>
</MkContainer>
</template>
@ -66,7 +59,6 @@ import XNet from "./net.vue";
import XCpu from "./cpu.vue";
import XMemory from "./mem.vue";
import XDisk from "./disk.vue";
import XMeili from "./meilisearch.vue";
import MkContainer from "@/components/MkContainer.vue";
import type { GetFormResultType } from "@/scripts/form";
import * as os from "@/os";

View File

@ -1,86 +0,0 @@
<template>
<div class="verusivbr">
<XPie
v-tooltip="i18n.ts.meiliIndexCount"
class="pie"
:value="progress"
:reverse="true"
/>
<div>
<p><i :class="icon('ph-file-search')"></i>MeiliSearch</p>
<p>{{ i18n.ts._widgets.meiliStatus }}: {{ available }}</p>
<p>{{ i18n.ts._widgets.meiliSize }}: {{ bytes(totalSize, 1) }}</p>
<p>{{ i18n.ts._widgets.meiliIndexCount }}: {{ indexCount }}</p>
</div>
</div>
<br />
</template>
<script lang="ts" setup>
import { onBeforeUnmount, onMounted, ref } from "vue";
import XPie from "./pie.vue";
import bytes from "@/filters/bytes";
import { i18n } from "@/i18n";
import * as os from "@/os";
import icon from "@/scripts/icon";
const props = defineProps<{
connection: any;
meta: any;
}>();
const progress = ref<number>(0);
const serverStats = ref(null);
const totalSize = ref<number>(0);
const indexCount = ref<number>(0);
const available = ref<string>("unavailable");
function onStats(stats) {
totalSize.value = stats.meilisearch.size;
indexCount.value = stats.meilisearch.indexed_count;
available.value = stats.meilisearch.health;
progress.value = indexCount.value / serverStats.value.notesCount;
}
onMounted(() => {
os.api("stats", {}).then((res) => {
serverStats.value = res;
});
props.connection.on("stats", onStats);
});
onBeforeUnmount(() => {
props.connection.off("stats", onStats);
});
</script>
<style lang="scss" scoped>
.verusivbr {
display: flex;
padding: 16px;
> .pie {
height: 82px;
flex-shrink: 0;
margin-right: 16px;
}
> div {
flex: 1;
> p {
margin: 0;
font-size: 0.8em;
&:first-child {
font-weight: bold;
margin-bottom: 4px;
> i {
margin-right: 4px;
}
}
}
}
}
</style>

View File

@ -75,9 +75,6 @@ importers:
'@discordapp/twemoji':
specifier: ^15.0.2
version: 15.0.2
'@elastic/elasticsearch':
specifier: 8.12.2
version: 8.12.2
'@koa/cors':
specifier: 5.0.0
version: 5.0.0
@ -243,9 +240,6 @@ importers:
megalodon:
specifier: workspace:*
version: link:../megalodon
meilisearch:
specifier: 0.37.0
version: 0.37.0
mfm-js:
specifier: 0.24.0
version: 0.24.0
@ -336,9 +330,6 @@ importers:
sharp:
specifier: 0.33.2
version: 0.33.2
sonic-channel:
specifier: ^1.3.1
version: 1.3.1
stringz:
specifier: 2.1.0
version: 2.1.0
@ -1889,30 +1880,6 @@ packages:
universalify: 0.1.2
dev: false
/@elastic/elasticsearch@8.12.2:
resolution: {integrity: sha512-04NvH3LIgcv1Uwguorfw2WwzC9Lhfsqs9f0L6uq6MrCw0lqe/HOQ6E8vJ6EkHAA15iEfbhtxOtenbZVVcE+mAQ==}
engines: {node: '>=18'}
dependencies:
'@elastic/transport': 8.4.1
tslib: 2.6.2
transitivePeerDependencies:
- supports-color
dev: false
/@elastic/transport@8.4.1:
resolution: {integrity: sha512-/SXVuVnuU5b4dq8OFY4izG+dmGla185PcoqgK6+AJMpmOeY1QYVNbWtCwvSvoAANN5D/wV+EBU8+x7Vf9EphbA==}
engines: {node: '>=16'}
dependencies:
debug: 4.3.4(supports-color@8.1.1)
hpagent: 1.2.0
ms: 2.1.3
secure-json-parse: 2.7.0
tslib: 2.6.2
undici: 5.23.0
transitivePeerDependencies:
- supports-color
dev: false
/@emnapi/runtime@0.45.0:
resolution: {integrity: sha512-Txumi3td7J4A/xTTwlssKieHKTGl3j4A1tglBx72auZ49YK7ePY6XZricgIg9mnZT4xPfA+UPCUdnhRuEFDL+w==}
requiresBuild: true
@ -7182,6 +7149,7 @@ packages:
node-fetch: 2.6.12
transitivePeerDependencies:
- encoding
dev: true
/cross-spawn@5.1.0:
resolution: {integrity: sha512-pTgQJ5KC0d2hcY8eyL1IzlBPYjTkyH72XRZPnLyKus2mBfNjQs3klqbJU2VILqZryAZUt9JOb3h/mWMy23/f5A==}
@ -12529,14 +12497,6 @@ packages:
engines: {node: '>= 0.6'}
dev: false
/meilisearch@0.37.0:
resolution: {integrity: sha512-LdbK6JmRghCawrmWKJSEQF0OiE82md+YqJGE/U2JcCD8ROwlhTx0KM6NX4rQt0u0VpV0QZVG9umYiu3CSSIJAQ==}
dependencies:
cross-fetch: 3.1.8
transitivePeerDependencies:
- encoding
dev: false
/memoize@10.0.0:
resolution: {integrity: sha512-H6cBLgsi6vMWOcCpvVCdFFnl3kerEXbrYh9q+lY6VXvQSmM6CkmV08VOwT+WE2tzIEqRPFfAq3fm4v/UIW6mSA==}
engines: {node: '>=18'}
@ -15181,10 +15141,6 @@ packages:
ajv-keywords: 3.5.2(ajv@6.12.6)
dev: true
/secure-json-parse@2.7.0:
resolution: {integrity: sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw==}
dev: false
/seedrandom@2.4.2:
resolution: {integrity: sha512-uQ72txMoObtuJooiBLSVs5Yu2e9d/lHQz0boaqHjW8runXB9vR8nFtaZV54wYii613N0C8ZqTBLsfwDhAdpvqQ==}
dev: false
@ -15462,11 +15418,6 @@ packages:
smart-buffer: 4.2.0
dev: false
/sonic-channel@1.3.1:
resolution: {integrity: sha512-+K4IZVFE7Tf2DB4EFZ23xo7a/+gJaiOHhFzXVZpzkX6Rs/rvf4YbSxnEGdYw8mrTcjtpG+jLVQEhP8sNTtN5VA==}
engines: {node: '>= 6.0.0'}
dev: false
/sort-keys-length@1.0.1:
resolution: {integrity: sha512-GRbEOUqCxemTAk/b32F2xa8wDTs+Z1QHOkbhJDQTvv/6G3ZkbJ+frYWsTcc7cBB3Fu4wy4XlLCuNtJuMn7Gsvw==}
engines: {node: '>=0.10.0'}