Sök — fulltextsök med SQLite FTS5
På Zkrinet: lägg till blocket under Byggblock i panelen — ett klick, din egen kod rörs inte och en ögonblicksbild tas först. Installationsstegen nedan gäller om du kör PocketBase själv, till exempel efter en export.
Snabb fritextsökning i dina samlingar, med rankning (bm25) och utdrag där träffen är markerad. Sökningen respekterar samlingens egna regler: ingen får se en träff som hen inte hade fått se via PocketBase vanliga list-API.
Testat mot PocketBase 0.40.4 (SQLite 3.53.3, byggd med ENABLE_FTS5).
#Installation
Kopiera pb_hooks/ och pb_migrations/ till din PocketBase och starta om. Samlingen zk_sok_installningar dyker upp i adminpanelen. Själva indexet ligger i två vanliga SQLite-tabeller (zk_sok_poster, zk_sok_index) som inte syns i adminpanelen.
#Välj vad som ska vara sökbart
Skapa en rad i zk_sok_installningar per samling (bara superuser):
| Fält | Exempel | |
|---|---|---|
samling | artiklar | Samlingens namn |
falt | ["titel", "innehall"] | Fält av typen text eller editor (HTML-taggarna tas bort innan indexering) |
aktiv | på | Av = samlingen är inte sökbar |
Indexet byggs direkt när raden sparas, och hålls sedan i synk automatiskt när poster skapas, ändras och raderas. Dolda fält (hidden) går inte att välja — utdragen skulle annars visa dem. Görs ett fält dolt i efterhand slutar det sökas direkt.
Bygg om hela indexet (t.ex. efter import direkt i databasen):
POST /api/zk/sok/bygg-om (superuser) alla samlingar
POST /api/zk/sok/bygg-om?samling=x (superuser) en samling
#Söka från appen
const res = await pb.send("/api/zk/sok", { query: { q: "grafikkort", samling: "artiklar", antal: 20, sida: 1 } })
// res = { traffar: [...], mer: true|false, sida, antal }
for (const t of res.traffar) {
t.samling // "artiklar"
t.id // postens id
t.falt // fältet som matchade bäst
t.utdrag // "…om <mark>grafikkort</mark> här…" — HTML-säkert, se nedan
t.poang // högre = bättre träff
t.post // hela posten, precis som list-API:t hade visat den
}
samlingär valfri (kommaseparerad lista). Utan den söks alla inställda samlingar.antal1–50 (standard 20),sida1–50.mersäger om det finns fler träffar.- Inloggning skickas som vanligt av SDK:t (
pb.authStore).
#Frågespråket
- Ord separeras med mellanslag; alla ord måste finnas (
åsa bilen). bil*= prefixsök (bilen, bilar, bilverkstad). Kräver minst två tecken före*.- Allt annat är vanlig text.
",:,(,),^,{},-,+blir ordgränser, ochOR,NOT,NEARär bara ord. Det går inte att skriva FTS5-syntax via sökrutan — med flit. - Max 10 ord, 300 tecken.
#Svenska
Tokenizern är unicode61 remove_diacritics 0. Standardvalet i många guider (remove_diacritics 1/2) viker å/ä/ö till a/o — då hittar "får" även "far" och "hål" även "hal". Här gör det inte det:
| Sökning | Hittar | Hittar inte |
|---|---|---|
får | får | far |
hål | hål | hal |
kör | kör | kor |
åsa, ÅSA | Åsa |
Skiftläge viks (även Å/å). Priset: den som söker ole hittar inte "Olé" — accenttecken måste skrivas som i texten. För svensk text är det rätt avvägning.
#Säkerhet
- Behörighet per post. FTS5 används bara för att få fram kandidater. Varje kandidat hämtas som en riktig post och prövas mot samlingens
listRulemed anroparens inloggning ($app.canAccessRecord). Samling medlistRule= null (låst) → bara superuser. Testat: användare A ser aldrig B:s träffar, gäst ser bara det gäst får lista, och resultatet är exakt detsamma som PocketBase list-API ger samma anropare. - Inga räknare som läcker. Svaret innehåller inget totalantal, bara de poster du får se och
mer. Testat: A:s svar är identiskt oavsett om B har 0 eller 250 träffar. - Utdragen är HTML-säkra. All text escapas; bara pluginets egna
<mark>-taggar finns kvar. Du kan visat.utdragmedinnerHTML. - Posten går genom PocketBase vanliga berikning (
$apis.enrichRecords): dolda fält tas bort precis som i list-API:t (testat). E-postsynlighet ochexpandhanteras av samma PocketBase-funktion men är inte separat testade här. - Frågan saneras till citerade ord innan den når
MATCH— testat med citattecken, kolon, parenteser, NEAR/OR/NOT, SQL-försök, nollbyte, 5000 tecken och emoji.
Vill du begränsa hur ofta någon får söka, slå på PocketBase inbyggda rate limit (Inställningar → Rate limiting) med regel för /api/zk/sok.
#Begränsningar
- Sökning prövar högst 1000 indexrader per anrop. Har en anropare väldigt många träffar som hen inte får se rankade före sina egna (t.ex. tusentals andra användares poster med samma ord) kan hens egna missas. Gränsen skyddar servern; behörigheten håller ändå.
- Tidsskillnad kan avslöja lite. En sökning där många kandidater prövas och nekas tar längre tid än en där inga finns. Svaret läcker inget, men svarstiden gör det i teorin. Behöver du skydda mot det, använd separata samlingar per kund i stället.
poang(bm25) beror på hela indexet, även poster du inte ser. Siffran kan alltså ändras när andra skriver. Använd den för ordning, inte som ett absolut mått.- Regler på postnivå, inte fältnivå. Den som får lista en post ser utdrag ur alla indexerade fält i den — välj bara fält som är okej att visa för alla som får lista posten.
- Byter en samling namn måste inställningsraden uppdateras till det nya namnet (indexet byggs då om). Tas samlingen bort rensas indexet och inställningen automatiskt.
- Ändringar som går förbi PocketBase (rå SQL mot databasen) syns inte i indexet förrän du kör
bygg-om. - Bara fält av typen text och editor. Ingen stamning (bil ≠ bilar utan
*), inga synonymer, ingen stavningstolerans. - Indexet byggs om i samma anrop som inställningen sparas — för mycket stora samlingar (hundratusentals poster) tar det tid. Inte prestandamätt i större skala än testerna (några hundra poster).
#Testa
./bank.py sok
14 tester: svenska tecken (får/far, hål/hal, kör/kor, Åsa/åsa, prefix), rankning, utdrag (HTML-säkert, editor-fält tvättade), ägarregel (A/B/gäst, jämfört mot PocketBase egen list-API), att svaret inte läcker andras träffar, låsta och publika samlingar, dolda fält, injektion i frågespråket, synk vid ändring/radering, misslyckad batch, validering av inställningar, ombyggnad (även med föräldralösa indexrader), borttagen samling, sidor. Säkerhetsskydden är mutantverifierade: varje skydd har brutits med flit och fångats av minst ett test.