feat(search): Elasticsearch searcher (name+content) and oo search (#37)
This commit is contained in:
@@ -18,6 +18,7 @@
|
||||
// oo docs tools | convert | optimize | ocr | hocr | as-md | put-md | put-txt | put-xlsx
|
||||
// oo catalog match | merge | apply | scan-contacts | scan-projects | scan-thunderbird
|
||||
// oo dav ls | move | copy | mkdir | rename-file | rename-folder | download | fileops
|
||||
// oo search QUERY [--content] [--folder ID] [--limit N] [--json]
|
||||
//
|
||||
// CRM association rules: docs/crm-associations.md
|
||||
//
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
onlyoffice "github.com/eslider/go-onlyoffice"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(searchCmd())
|
||||
}
|
||||
|
||||
// searchCmd queries the OnlyOffice Elasticsearch index directly. The REST
|
||||
// /api/2.0/files/@search endpoint only searches file names in the database;
|
||||
// content search needs ES (see docs/elasticsearch.md).
|
||||
func searchCmd() *cobra.Command {
|
||||
var (
|
||||
content bool
|
||||
folder string
|
||||
limit int
|
||||
asJSON bool
|
||||
)
|
||||
cmd := &cobra.Command{
|
||||
Use: "search QUERY",
|
||||
Short: "Full-text search over documents by name, optionally by content (Elasticsearch)",
|
||||
Long: "Search the OnlyOffice Documents index.\n\n" +
|
||||
"By default only file names are matched. With --content the query also\n" +
|
||||
"matches extracted document text (document.attachment.content); this covers\n" +
|
||||
"Office formats (docx/xlsx/pptx) and is slower.\n\n" +
|
||||
"Requires ONLYOFFICE_ES_URL (and optionally ONLYOFFICE_ES_INDEX,\n" +
|
||||
"ONLYOFFICE_TENANT). See docs/elasticsearch.md for the tunnel setup.",
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
if asJSON {
|
||||
outputFormat = "json"
|
||||
}
|
||||
es, err := onlyoffice.NewESSearcher(onlyoffice.ESConfigFromEnv())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
hits, err := es.Search(cmd.Context(), onlyoffice.SearchQuery{
|
||||
Text: args[0],
|
||||
InContent: content,
|
||||
FolderID: folder,
|
||||
Limit: limit,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
rows := make([]map[string]any, 0, len(hits))
|
||||
for _, h := range hits {
|
||||
rows = append(rows, map[string]any{
|
||||
"id": h.ID,
|
||||
"title": h.Title,
|
||||
"folder": h.ParentID,
|
||||
"score": h.Score,
|
||||
"highlight": h.Highlight,
|
||||
})
|
||||
}
|
||||
if outputFormat == "json" {
|
||||
printJSON(rows)
|
||||
return nil
|
||||
}
|
||||
printTable([]string{"id", "title", "folder", "score", "highlight"}, rows)
|
||||
return nil
|
||||
},
|
||||
}
|
||||
cmd.Flags().BoolVar(&content, "content", false, "also match extracted document content")
|
||||
cmd.Flags().StringVar(&folder, "folder", "", "limit to a Documents folder id")
|
||||
cmd.Flags().IntVar(&limit, "limit", 20, "maximum number of results")
|
||||
cmd.Flags().BoolVar(&asJSON, "json", false, "shorthand for --output json")
|
||||
return cmd
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestSearchCommandRegisteredWithFlags(t *testing.T) {
|
||||
cmd, _, err := rootCmd.Find([]string{"search"})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if cmd.Name() != "search" {
|
||||
t.Fatalf("search resolved to %q", cmd.Name())
|
||||
}
|
||||
for _, name := range []string{"content", "folder", "limit", "json"} {
|
||||
if cmd.Flags().Lookup(name) == nil {
|
||||
t.Errorf("search: missing --%s flag", name)
|
||||
}
|
||||
}
|
||||
if got := cmd.Flags().Lookup("limit").DefValue; got != "20" {
|
||||
t.Errorf("--limit default = %q, want 20", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSearchWithoutESURLIsClearError(t *testing.T) {
|
||||
clearEnv(t, "ONLYOFFICE_ES_URL", "ONLYOFFICE_ES_INDEX", "ONLYOFFICE_TENANT")
|
||||
errBuf := &bytes.Buffer{}
|
||||
rootCmd.SetErr(errBuf)
|
||||
rootCmd.SetOut(&bytes.Buffer{})
|
||||
rootCmd.SetArgs([]string{"search", "Rechnung"})
|
||||
t.Cleanup(func() {
|
||||
rootCmd.SetArgs(nil)
|
||||
rootCmd.SetOut(nil)
|
||||
rootCmd.SetErr(nil)
|
||||
})
|
||||
|
||||
err := rootCmd.Execute()
|
||||
if err == nil {
|
||||
t.Fatal("expected error without ONLYOFFICE_ES_URL")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "ONLYOFFICE_ES_URL") {
|
||||
t.Fatalf("error %q missing ONLYOFFICE_ES_URL", err.Error())
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user