Use dynamic tokens & bootstrap meili config on startup (#839)

* initial commit

* persist meili token in cookie

* handle login/logout cookie invalidation

* fix review suggestions

---------

Co-authored-by: Christian Beutel <>
This commit is contained in:
Flomp
2026-02-27 08:52:53 +01:00
committed by GitHub
parent bb8b4fbc2e
commit 6cac905368
4 changed files with 190 additions and 39 deletions

View File

@@ -1009,20 +1009,37 @@ func registerRoutes(se *core.ServeEvent, client meilisearch.ServiceManager) {
return e.JSON(http.StatusOK, map[string]string{"status": "ok"})
})
se.Router.GET("/public/search/token", func(e *core.RequestEvent) error {
se.Router.GET("/search/token", func(e *core.RequestEvent) error {
searchRules := map[string]interface{}{
"lists": map[string]string{
"filter": "public = true",
},
"trails": map[string]string{
"filter": "public = true",
},
"lists": map[string]string{"filter": "public = true"},
"trails": map[string]string{"filter": "public = true"},
}
if e.Auth != nil {
userId := e.Auth.Id
userActor, err := e.App.FindFirstRecordByData("activitypub_actors", "user", e.Auth.Id)
if err != nil {
return err
}
searchRules = map[string]any{
"lists": map[string]string{
"filter": "public = true OR author = " + userActor.Id + " OR shares = " + userId,
},
"trails": map[string]string{
"filter": "public = true OR author = " + userActor.Id + " OR shares = " + userId,
},
}
}
token, err := util.GenerateMeilisearchToken(searchRules, client)
if err != nil {
return err
return e.InternalServerError("Failed to generate search token", err)
}
return e.JSON(http.StatusOK, map[string]string{"token": token})
return e.JSON(http.StatusOK, map[string]string{
"token": token,
})
})
se.Router.POST("/integration/strava/token", func(e *core.RequestEvent) error {
@@ -1277,6 +1294,7 @@ func registerCronJobs(app core.App) {
func bootstrapData(app core.App, client meilisearch.ServiceManager) error {
bootstrapCategories(app)
bootstrapMeilisearchConfig(client)
go bootstrapMeilisearchDocuments(app, client)
return nil
}
@@ -1366,3 +1384,55 @@ func bootstrapMeilisearchDocuments(app core.App, client meilisearch.ServiceManag
return nil
}
func bootstrapMeilisearchConfig(client meilisearch.ServiceManager) {
configs := map[string]meilisearch.Settings{
"trails": {
SearchableAttributes: []string{"author_name", "name", "description", "location", "tags"},
FilterableAttributes: []string{
"_geo", "author", "category", "completed", "date", "difficulty",
"distance", "elevation_gain", "elevation_loss", "likes", "public",
"shares", "tags",
},
SortableAttributes: []string{
"author", "created", "date", "difficulty", "distance",
"duration", "elevation_gain", "elevation_loss", "like_count", "name",
},
RankingRules: []string{"words", "typo", "proximity", "attribute", "sort", "exactness"},
},
"lists": {
SearchableAttributes: []string{"*"},
FilterableAttributes: []string{"author", "public", "shares"},
SortableAttributes: []string{"created", "name"},
RankingRules: []string{"words", "typo", "proximity", "attribute", "sort", "exactness"},
},
}
for indexName, settings := range configs {
_, err := client.GetIndex(indexName)
if err != nil {
log.Printf("Index [%s] not found, creating it...", indexName)
task, err := client.CreateIndex(&meilisearch.IndexConfig{
Uid: indexName,
PrimaryKey: "id",
})
if err != nil {
log.Printf("Failed to create index [%s]: %v", indexName, err)
continue
}
_, err = client.WaitForTask(task.TaskUID, 0)
if err != nil {
log.Printf("Error waiting for index creation [%s]: %v", indexName, err)
continue
}
}
_, err = client.Index(indexName).UpdateSettings(&settings)
if err != nil {
log.Printf("Failed to sync settings for index [%s]: %v", indexName, err)
} else {
log.Printf("Settings synced for index [%s]", indexName)
}
}
}

View File

@@ -0,0 +1,45 @@
package migrations
import (
"github.com/pocketbase/pocketbase/core"
m "github.com/pocketbase/pocketbase/migrations"
)
func init() {
m.Register(func(app core.App) error {
collection, err := app.FindCollectionByNameOrId("_pb_users_auth_")
if err != nil {
return err
}
// remove field
collection.Fields.RemoveById("dlzhxcn2")
return app.Save(collection)
}, func(app core.App) error {
collection, err := app.FindCollectionByNameOrId("_pb_users_auth_")
if err != nil {
return err
}
// add field
if err := collection.Fields.AddMarshaledJSONAt(7, []byte(`{
"autogeneratePattern": "",
"hidden": false,
"id": "dlzhxcn2",
"max": 0,
"min": 0,
"name": "token",
"pattern": "",
"presentable": false,
"primaryKey": false,
"required": false,
"system": false,
"type": "text"
}`)); err != nil {
return err
}
return app.Save(collection)
})
}

View File

@@ -6,10 +6,10 @@ import (
"errors"
"fmt"
"io"
"log"
"net/http"
"net/url"
"path"
"time"
"github.com/meilisearch/meilisearch-go"
"github.com/pocketbase/dbx"
@@ -456,27 +456,37 @@ func UpdateListShares(listId string, shares []string, client meilisearch.Service
return nil
}
func GenerateMeilisearchToken(rules map[string]interface{}, client meilisearch.ServiceManager) (resp string, err error) {
apiKeyUid := ""
apiKey := ""
func GenerateMeilisearchToken(rules map[string]interface{}, client meilisearch.ServiceManager) (string, error) {
var apiKeyUid string
var apiKey string
if keys, err := client.GetKeys(nil); err != nil {
log.Fatal(err)
} else {
for _, k := range keys.Results {
if k.Name == "Default Search API Key" {
keys, err := client.GetKeys(&meilisearch.KeysQuery{Limit: 20})
if err != nil {
return "", fmt.Errorf("meilisearch connection error: %w", err)
}
for _, k := range keys.Results {
for _, action := range k.Actions {
if action == "search" || k.Name == "Default Search API Key" {
apiKeyUid = k.UID
apiKey = k.Key
break
}
}
if apiKey != "" {
break
}
}
if len(apiKey) == 0 || len(apiKeyUid) == 0 {
return "", errors.New("unable to locate meilisearch API key")
if apiKey == "" || apiKeyUid == "" {
return "", errors.New("unable to locate a valid search API key")
}
expiresAt := time.Now().Add(24 * time.Hour)
options := &meilisearch.TenantTokenOptions{
APIKey: apiKey,
APIKey: apiKey,
ExpiresAt: expiresAt,
}
return client.GenerateTenantToken(apiKeyUid, rules, options)