Merge pull request #3635 from aduffeck/search

Add initial version of the search extension
This commit is contained in:
Jörn Friedrich Dreyer
2022-05-02 19:25:07 +02:00
committed by GitHub
79 changed files with 6258 additions and 44 deletions

View File

@@ -0,0 +1,5 @@
Enhancement: Add initial version of the search extensions
It is now possible to search for files and directories by their name using the web UI. Therefor new search extension indexes files in a persistent local index.
https://github.com/owncloud/ocis/pull/3635

View File

@@ -42,7 +42,7 @@ We also suggest to use the last port in your extensions' range as a debug/metric
| 9205-9209 | [markdown-editor](https://github.com/owncloud/ocis-markdown-editor) |
| 9210-9214 | [reva](https://github.com/owncloud/ocis-reva) unused? |
| 9215-9219 | reva metadata storage |
| 9220-9224 | FREE |
| 9220-9224 | search |
| 9225-9229 | photoprism (state: PoC) |
| 9230-9234 | [nats](https://github.com/owncloud/ocis/tree/master/nats) |
| 9235-9239 | idm TBD |

View File

@@ -34,10 +34,13 @@ For now, the storage service uses these ports to preconfigure those services:
| 9159 | storage users debug |
| 9160 | groups |
| 9161 | groups debug |
| 9164 | storage appprovider |
| 9165 | storage appprovider debug |
| 9164 | storage appprovider |
| 9165 | storage appprovider debug |
| 9178 | storage public link |
| 9179 | storage public link data |
| 9180 | accounts grpc |
| 9181 | accounts http |
| 9182 | accounts debug |
| 9215 | storage meta grpc |
| 9216 | storage meta http |
| 9217 | storage meta debug |

View File

@@ -16,6 +16,7 @@ func RegisteredEvents() []events.Unmarshaller {
events.ReceivedShareUpdated{},
events.LinkAccessed{},
events.LinkAccessFailed{},
events.ContainerCreated{},
events.FileUploaded{},
events.FileDownloaded{},
events.ItemTrashed{},

View File

@@ -236,7 +236,9 @@ func frontendConfigFromStruct(c *cli.Context, cfg *config.Config, filesCfg map[s
"preferred_upload_type": cfg.Checksums.PreferredUploadType,
},
"files": filesCfg,
"dav": map[string]interface{}{},
"dav": map[string]interface{}{
"reports": []string{"search-files"},
},
"files_sharing": map[string]interface{}{
"api_enabled": true,
"resharing": false,

View File

@@ -45,8 +45,10 @@ type Policy struct {
// Route defines forwarding routes
type Route struct {
Type RouteType `yaml:"type"`
Endpoint string `yaml:"endpoint"`
Type RouteType `yaml:"type"`
// Method optionally limits the route to this HTTP method
Method string `yaml:"method"`
Endpoint string `yaml:"endpoint"`
// Backend is a static URL to forward the request to
Backend string `yaml:"backend"`
// Service name to look up in the registry

View File

@@ -97,6 +97,15 @@ func DefaultPolicies() []config.Policy {
Endpoint: "/remote.php/?preview=1",
Backend: "http://localhost:9115",
},
{
// TODO the actual REPORT goes to /dav/files/{username}, which is user specific ... how would this work in a spaces world?
// TODO what paths are returned? the href contains the full path so it should be possible to return urls from other spaces?
// TODO or we allow a REPORT on /dav/spaces to search all spaces and /dav/space/{spaceid} to search a specific space
// send webdav REPORT requests to search service
Method: "REPORT",
Endpoint: "/remote.php/dav/",
Backend: "http://localhost:9115", // TODO use registry?
},
{
Endpoint: "/remote.php/",
Service: "ocdav",

View File

@@ -29,7 +29,8 @@ import (
// MultiHostReverseProxy extends "httputil" to support multiple hosts with different policies
type MultiHostReverseProxy struct {
httputil.ReverseProxy
Directors map[string]map[config.RouteType]map[string]func(req *http.Request)
// Directors holds policy route type method endpoint Director
Directors map[string]map[config.RouteType]map[string]map[string]func(req *http.Request)
PolicySelector policy.Selector
logger log.Logger
config *config.Config
@@ -40,7 +41,7 @@ func NewMultiHostReverseProxy(opts ...Option) *MultiHostReverseProxy {
options := newOptions(opts...)
rp := &MultiHostReverseProxy{
Directors: make(map[string]map[config.RouteType]map[string]func(req *http.Request)),
Directors: make(map[string]map[config.RouteType]map[string]map[string]func(req *http.Request)),
logger: options.Logger,
config: options.Config,
}
@@ -124,6 +125,7 @@ func (p *MultiHostReverseProxy) directorSelectionDirector(r *http.Request) {
return
}
method := ""
// find matching director
for _, rt := range config.RouteTypes {
var handler func(string, url.URL) bool
@@ -137,25 +139,36 @@ func (p *MultiHostReverseProxy) directorSelectionDirector(r *http.Request) {
default:
handler = p.prefixRouteMatcher
}
for endpoint := range p.Directors[pol][rt] {
if p.Directors[pol][rt][r.Method] != nil {
// use specific method
method = r.Method
}
for endpoint := range p.Directors[pol][rt][method] {
if handler(endpoint, *r.URL) {
p.logger.Debug().
Str("policy", pol).
Str("method", r.Method).
Str("prefix", endpoint).
Str("path", r.URL.Path).
Str("routeType", string(rt)).
Msg("director found")
p.Directors[pol][rt][endpoint](r)
p.Directors[pol][rt][method][endpoint](r)
return
}
}
}
// override default director with root. If any
if p.Directors[pol][config.PrefixRoute]["/"] != nil {
p.Directors[pol][config.PrefixRoute]["/"](r)
switch {
case p.Directors[pol][config.PrefixRoute][method]["/"] != nil:
// try specific method
p.Directors[pol][config.PrefixRoute][method]["/"](r)
return
case p.Directors[pol][config.PrefixRoute][""]["/"] != nil:
// fallback to unspecific method
p.Directors[pol][config.PrefixRoute][""]["/"](r)
return
}
@@ -182,20 +195,23 @@ func singleJoiningSlash(a, b string) string {
func (p *MultiHostReverseProxy) AddHost(policy string, target *url.URL, rt config.Route) {
targetQuery := target.RawQuery
if p.Directors[policy] == nil {
p.Directors[policy] = make(map[config.RouteType]map[string]func(req *http.Request))
p.Directors[policy] = make(map[config.RouteType]map[string]map[string]func(req *http.Request))
}
routeType := config.DefaultRouteType
if rt.Type != "" {
routeType = rt.Type
}
if p.Directors[policy][routeType] == nil {
p.Directors[policy][routeType] = make(map[string]func(req *http.Request))
p.Directors[policy][routeType] = make(map[string]map[string]func(req *http.Request))
}
if p.Directors[policy][routeType][rt.Method] == nil {
p.Directors[policy][routeType][rt.Method] = make(map[string]func(req *http.Request))
}
reg := registry.GetRegistry()
sel := selector.NewSelector(selector.Registry(reg))
p.Directors[policy][routeType][rt.Endpoint] = func(req *http.Request) {
p.Directors[policy][routeType][rt.Method][rt.Endpoint] = func(req *http.Request) {
if rt.Service != "" {
// select next node
next, err := sel.Select(rt.Service)

View File

@@ -1,15 +1,19 @@
package proxy
import (
"fmt"
"net/http"
"net/http/httptest"
"net/url"
"testing"
"github.com/owncloud/ocis/extensions/proxy/pkg/config"
"github.com/owncloud/ocis/extensions/proxy/pkg/config/defaults"
)
type matchertest struct {
endpoint, target string
matches bool
method, endpoint, target string
matches bool
}
func TestPrefixRouteMatcher(t *testing.T) {
@@ -99,3 +103,35 @@ func TestSingleJoiningSlash(t *testing.T) {
}
}
}
func TestDirectorSelectionDirector(t *testing.T) {
svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "ok")
}))
defer svr.Close()
p := NewMultiHostReverseProxy(Config(&config.Config{
PolicySelector: &config.PolicySelector{
Static: &config.StaticSelectorConf{
Policy: "default",
},
},
}))
p.AddHost("default", &url.URL{Host: "ocdav"}, config.Route{Type: config.PrefixRoute, Method: "", Endpoint: "/dav", Backend: "ocdav"})
p.AddHost("default", &url.URL{Host: "ocis-webdav"}, config.Route{Type: config.PrefixRoute, Method: "REPORT", Endpoint: "/dav", Backend: "ocis-webdav"})
table := []matchertest{
{method: "PROPFIND", endpoint: "/dav/files/demo/", target: "ocdav"},
{method: "REPORT", endpoint: "/dav/files/demo/", target: "ocis-webdav"},
}
for _, test := range table {
r := httptest.NewRequest(test.method, "/dav/files/demo/", nil)
p.directorSelectionDirector(r)
if r.URL.Host != test.target {
t.Errorf("TestDirectorSelectionDirector got host %s expected %s", r.Host, test.target)
}
}
}

View File

@@ -0,0 +1,14 @@
package main
import (
"os"
"github.com/owncloud/ocis/extensions/search/pkg/command"
"github.com/owncloud/ocis/extensions/search/pkg/config/defaults"
)
func main() {
if err := command.Execute(defaults.DefaultConfig()); err != nil {
os.Exit(1)
}
}

View File

@@ -0,0 +1,53 @@
package command
import (
"fmt"
"net/http"
"github.com/owncloud/ocis/extensions/search/pkg/config"
"github.com/owncloud/ocis/extensions/search/pkg/config/parser"
"github.com/owncloud/ocis/extensions/search/pkg/logging"
"github.com/urfave/cli/v2"
)
// Health is the entrypoint for the health command.
func Health(cfg *config.Config) *cli.Command {
return &cli.Command{
Name: "health",
Usage: "check health status",
Category: "info",
Before: func(c *cli.Context) error {
return parser.ParseConfig(cfg)
},
Action: func(c *cli.Context) error {
logger := logging.Configure(cfg.Service.Name, cfg.Log)
resp, err := http.Get(
fmt.Sprintf(
"http://%s/healthz",
cfg.Debug.Addr,
),
)
if err != nil {
logger.Fatal().
Err(err).
Msg("Failed to request health check")
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
logger.Fatal().
Int("code", resp.StatusCode).
Msg("Health seems to be in bad state")
}
logger.Debug().
Int("code", resp.StatusCode).
Msg("Health got a good state")
return nil
},
}
}

View File

@@ -0,0 +1,52 @@
package command
import (
"context"
"fmt"
"github.com/urfave/cli/v2"
"github.com/owncloud/ocis/extensions/search/pkg/config"
"github.com/owncloud/ocis/extensions/search/pkg/config/parser"
"github.com/owncloud/ocis/ocis-pkg/service/grpc"
searchsvc "github.com/owncloud/ocis/protogen/gen/ocis/services/search/v0"
)
// Index is the entrypoint for the server command.
func Index(cfg *config.Config) *cli.Command {
return &cli.Command{
Name: "index",
Usage: "index the files for one one more users",
Category: "index management",
Aliases: []string{"i"},
Flags: []cli.Flag{
&cli.StringFlag{
Name: "space",
Aliases: []string{"s"},
Required: true,
Usage: "the id of the space to travers and index the files of",
},
&cli.StringFlag{
Name: "user",
Aliases: []string{"u"},
Required: true,
Usage: "the username of the user tha shall be used to access the files",
},
},
Before: func(c *cli.Context) error {
return parser.ParseConfig(cfg)
},
Action: func(c *cli.Context) error {
client := searchsvc.NewSearchProviderService("com.owncloud.api.search", grpc.DefaultClient)
_, err := client.IndexSpace(context.Background(), &searchsvc.IndexSpaceRequest{
SpaceId: c.String("space"),
UserId: c.String("user"),
})
if err != nil {
fmt.Println("failed to index space: " + err.Error())
return err
}
return nil
},
}
}

View File

@@ -0,0 +1,65 @@
package command
import (
"context"
"os"
"github.com/owncloud/ocis/ocis-pkg/clihelper"
"github.com/thejerf/suture/v4"
"github.com/owncloud/ocis/extensions/search/pkg/config"
ociscfg "github.com/owncloud/ocis/ocis-pkg/config"
"github.com/urfave/cli/v2"
)
// GetCommands provides all commands for this service
func GetCommands(cfg *config.Config) cli.Commands {
return []*cli.Command{
// start this service
Server(cfg),
// interaction with this service
Index(cfg),
// infos about this service
Health(cfg),
Version(cfg),
}
}
// Execute is the entry point for the ocis-search command.
func Execute(cfg *config.Config) error {
app := clihelper.DefaultApp(&cli.App{
Name: "ocis-search",
Usage: "Serve search API for oCIS",
Commands: GetCommands(cfg),
})
cli.HelpFlag = &cli.BoolFlag{
Name: "help,h",
Usage: "Show the help",
}
return app.Run(os.Args)
}
// SutureService allows for the search command to be embedded and supervised by a suture supervisor tree.
type SutureService struct {
cfg *config.Config
}
// NewSutureService creates a new search.SutureService
func NewSutureService(cfg *ociscfg.Config) suture.Service {
cfg.Search.Commons = cfg.Commons
return SutureService{
cfg: cfg.Search,
}
}
func (s SutureService) Serve(ctx context.Context) error {
s.cfg.Context = ctx
if err := Execute(s.cfg); err != nil {
return err
}
return nil
}

View File

@@ -0,0 +1,83 @@
package command
import (
"context"
"fmt"
"github.com/oklog/run"
"github.com/owncloud/ocis/extensions/search/pkg/config"
"github.com/owncloud/ocis/extensions/search/pkg/config/parser"
"github.com/owncloud/ocis/extensions/search/pkg/logging"
"github.com/owncloud/ocis/extensions/search/pkg/metrics"
"github.com/owncloud/ocis/extensions/search/pkg/server/debug"
"github.com/owncloud/ocis/extensions/search/pkg/server/grpc"
"github.com/owncloud/ocis/extensions/search/pkg/tracing"
"github.com/owncloud/ocis/ocis-pkg/version"
"github.com/urfave/cli/v2"
)
// Server is the entrypoint for the server command.
func Server(cfg *config.Config) *cli.Command {
return &cli.Command{
Name: "server",
Usage: fmt.Sprintf("start %s extension without runtime (unsupervised mode)", cfg.Service.Name),
Category: "server",
Before: func(c *cli.Context) error {
err := parser.ParseConfig(cfg)
if err != nil {
fmt.Printf("%v", err)
}
return err
},
Action: func(c *cli.Context) error {
logger := logging.Configure(cfg.Service.Name, cfg.Log)
err := tracing.Configure(cfg)
if err != nil {
return err
}
gr := run.Group{}
ctx, cancel := func() (context.Context, context.CancelFunc) {
if cfg.Context == nil {
return context.WithCancel(context.Background())
}
return context.WithCancel(cfg.Context)
}()
defer cancel()
mtrcs := metrics.New()
mtrcs.BuildInfo.WithLabelValues(version.String).Set(1)
grpcServer := grpc.Server(
grpc.Config(cfg),
grpc.Logger(logger),
grpc.Name(cfg.Service.Name),
grpc.Context(ctx),
grpc.Metrics(mtrcs),
)
gr.Add(grpcServer.Run, func(_ error) {
logger.Info().Str("server", "grpc").Msg("shutting down server")
cancel()
})
server, err := debug.Server(
debug.Logger(logger),
debug.Context(ctx),
debug.Config(cfg),
)
if err != nil {
logger.Info().Err(err).Str("transport", "debug").Msg("Failed to initialize server")
return err
}
gr.Add(server.ListenAndServe, func(_ error) {
_ = server.Shutdown(ctx)
cancel()
})
return gr.Run()
},
}
}

View File

@@ -0,0 +1,50 @@
package command
import (
"fmt"
"os"
"github.com/owncloud/ocis/ocis-pkg/registry"
"github.com/owncloud/ocis/ocis-pkg/version"
tw "github.com/olekukonko/tablewriter"
"github.com/owncloud/ocis/extensions/search/pkg/config"
"github.com/urfave/cli/v2"
)
// Version prints the service versions of all running instances.
func Version(cfg *config.Config) *cli.Command {
return &cli.Command{
Name: "version",
Usage: "print the version of this binary and the running extension instances",
Category: "info",
Action: func(c *cli.Context) error {
fmt.Println("Version: " + version.String)
fmt.Printf("Compiled: %s\n", version.Compiled())
fmt.Println("")
reg := registry.GetRegistry()
services, err := reg.GetService(cfg.GRPC.Namespace + "." + cfg.Service.Name)
if err != nil {
fmt.Println(fmt.Errorf("could not get %s services from the registry: %v", cfg.Service.Name, err))
return err
}
if len(services) == 0 {
fmt.Println("No running " + cfg.Service.Name + " service found.")
return nil
}
table := tw.NewWriter(os.Stdout)
table.SetHeader([]string{"Version", "Address", "Id"})
table.SetAutoFormatHeaders(false)
for _, s := range services {
for _, n := range s.Nodes {
table.Append([]string{s.Version, n.Address, n.Id})
}
}
table.Render()
return nil
},
}
}

View File

@@ -0,0 +1,35 @@
package config
import (
"context"
"github.com/owncloud/ocis/ocis-pkg/shared"
)
// Config combines all available configuration parts.
type Config struct {
*shared.Commons `ocisConfig:"-" yaml:"-"`
Service Service `ocisConfig:"-" yaml:"-"`
Tracing *Tracing `ocisConfig:"tracing"`
Log *Log `ocisConfig:"log"`
Debug Debug `ocisConfig:"debug"`
GRPC GRPC `ocisConfig:"grpc"`
Datapath string `yaml:"data_path" env:"SEARCH_DATA_PATH"`
Reva Reva `ocisConfig:"reva"`
Events Events `yaml:"events"`
MachineAuthAPIKey string `yaml:"machine_auth_api_key" env:"OCIS_MACHINE_AUTH_API_KEY;SEARCH_MACHINE_AUTH_API_KEY"`
Context context.Context `ocisConfig:"-" yaml:"-"`
}
// Events combines the configuration options for the event bus.
type Events struct {
Endpoint string `yaml:"events_endpoint" env:"SEARCH_EVENTS_ENDPOINT" desc:"the address of the streaming service"`
Cluster string `yaml:"events_cluster" env:"SEARCH_EVENTS_CLUSTER" desc:"the clusterID of the streaming service. Mandatory when using nats"`
ConsumerGroup string `yaml:"events_group" env:"SEARCH_EVENTS_GROUP" desc:"the customergroup of the service. One group will only get one copy of an event"`
}

View File

@@ -0,0 +1,9 @@
package config
// Debug defines the available debug configuration.
type Debug struct {
Addr string `ocisConfig:"addr" env:"SEARCH_DEBUG_ADDR"`
Token string `ocisConfig:"token" env:"SEARCH_DEBUG_TOKEN"`
Pprof bool `ocisConfig:"pprof" env:"SEARCH_DEBUG_PPROF"`
Zpages bool `ocisConfig:"zpages" env:"SEARCH_DEBUG_ZPAGES"`
}

View File

@@ -0,0 +1,75 @@
package defaults
import (
"path"
"github.com/owncloud/ocis/extensions/search/pkg/config"
"github.com/owncloud/ocis/ocis-pkg/config/defaults"
)
func FullDefaultConfig() *config.Config {
cfg := DefaultConfig()
EnsureDefaults(cfg)
return cfg
}
func DefaultConfig() *config.Config {
return &config.Config{
Debug: config.Debug{
Addr: "127.0.0.1:9224",
Token: "",
},
GRPC: config.GRPC{
Addr: "127.0.0.1:9220",
Namespace: "com.owncloud.api",
},
Service: config.Service{
Name: "search",
},
Datapath: path.Join(defaults.BaseDataPath(), "search"),
Reva: config.Reva{
Address: "127.0.0.1:9142",
},
Events: config.Events{
Endpoint: "127.0.0.1:9233",
Cluster: "ocis-cluster",
ConsumerGroup: "search",
},
MachineAuthAPIKey: "change-me-please",
}
}
func EnsureDefaults(cfg *config.Config) {
// provide with defaults for shared logging, since we need a valid destination address for BindEnv.
if cfg.Log == nil && cfg.Commons != nil && cfg.Commons.Log != nil {
cfg.Log = &config.Log{
Level: cfg.Commons.Log.Level,
Pretty: cfg.Commons.Log.Pretty,
Color: cfg.Commons.Log.Color,
File: cfg.Commons.Log.File,
}
} else if cfg.Log == nil {
cfg.Log = &config.Log{}
}
// provide with defaults for shared tracing, since we need a valid destination address for BindEnv.
if cfg.Tracing == nil && cfg.Commons != nil && cfg.Commons.Tracing != nil {
cfg.Tracing = &config.Tracing{
Enabled: cfg.Commons.Tracing.Enabled,
Type: cfg.Commons.Tracing.Type,
Endpoint: cfg.Commons.Tracing.Endpoint,
Collector: cfg.Commons.Tracing.Collector,
}
} else if cfg.Tracing == nil {
cfg.Tracing = &config.Tracing{}
}
if cfg.MachineAuthAPIKey == "" && cfg.Commons != nil && cfg.Commons.MachineAuthAPIKey != "" {
cfg.MachineAuthAPIKey = cfg.Commons.MachineAuthAPIKey
}
}
func Sanitize(cfg *config.Config) {
// no http endpoint to be sanitized
}

View File

@@ -0,0 +1,7 @@
package config
// GRPC defines the available grpc configuration.
type GRPC struct {
Addr string `ocisConfig:"addr" env:"ACCOUNTS_GRPC_ADDR" desc:"The address of the grpc service."`
Namespace string `ocisConfig:"-" yaml:"-"`
}

View File

@@ -0,0 +1,8 @@
package config
// HTTP defines the available http configuration.
type HTTP struct {
Addr string `ocisConfig:"addr" env:"SEARCH_HTTP_ADDR"`
Namespace string `ocisConfig:"-" yaml:"-"`
Root string `ocisConfig:"root" env:"SEARCH_HTTP_ROOT"`
}

View File

@@ -0,0 +1,9 @@
package config
// Log defines the available log configuration.
type Log struct {
Level string `mapstructure:"level" env:"OCIS_LOG_LEVEL;SEARCH_LOG_LEVEL"`
Pretty bool `mapstructure:"pretty" env:"OCIS_LOG_PRETTY;SEARCH_LOG_PRETTY"`
Color bool `mapstructure:"color" env:"OCIS_LOG_COLOR;SEARCH_LOG_COLOR"`
File string `mapstructure:"file" env:"OCIS_LOG_FILE;SEARCH_LOG_FILE"`
}

View File

@@ -0,0 +1,41 @@
package parser
import (
"errors"
"github.com/owncloud/ocis/extensions/search/pkg/config"
"github.com/owncloud/ocis/extensions/search/pkg/config/defaults"
ociscfg "github.com/owncloud/ocis/ocis-pkg/config"
"github.com/owncloud/ocis/ocis-pkg/shared"
"github.com/owncloud/ocis/ocis-pkg/config/envdecode"
)
// ParseConfig loads configuration from known paths.
func ParseConfig(cfg *config.Config) error {
_, err := ociscfg.BindSourcesToStructs(cfg.Service.Name, cfg)
if err != nil {
return err
}
defaults.EnsureDefaults(cfg)
// load all env variables relevant to the config in the current context.
if err := envdecode.Decode(cfg); err != nil {
// no environment variable set for this config is an expected "error"
if !errors.Is(err, envdecode.ErrNoTargetFieldsAreSet) {
return err
}
}
defaults.Sanitize(cfg)
return Validate(cfg)
}
func Validate(cfg *config.Config) error {
if cfg.MachineAuthAPIKey == "" {
return shared.MissingMachineAuthApiKeyError(cfg.Service.Name)
}
return nil
}

View File

@@ -0,0 +1,6 @@
package config
// Reva defines all available REVA configuration.
type Reva struct {
Address string `ocisConfig:"address" env:"REVA_GATEWAY"`
}

View File

@@ -0,0 +1,6 @@
package config
// Service defines the available service configuration.
type Service struct {
Name string `ocisConfig:"-" yaml:"-"`
}

View File

@@ -0,0 +1,9 @@
package config
// Tracing defines the available tracing configuration.
type Tracing struct {
Enabled bool `ocisConfig:"enabled" env:"OCIS_TRACING_ENABLED;SEARCH_TRACING_ENABLED"`
Type string `ocisConfig:"type" env:"OCIS_TRACING_TYPE;SEARCH_TRACING_TYPE"`
Endpoint string `ocisConfig:"endpoint" env:"OCIS_TRACING_ENDPOINT;SEARCH_TRACING_ENDPOINT"`
Collector string `ocisConfig:"collector" env:"OCIS_TRACING_COLLECTOR;SEARCH_TRACING_COLLECTOR"`
}

View File

@@ -0,0 +1,17 @@
package logging
import (
"github.com/owncloud/ocis/extensions/search/pkg/config"
"github.com/owncloud/ocis/ocis-pkg/log"
)
// LoggerFromConfig initializes a service-specific logger instance.
func Configure(name string, cfg *config.Log) log.Logger {
return log.NewLogger(
log.Name(name),
log.Level(cfg.Level),
log.Pretty(cfg.Pretty),
log.Color(cfg.Color),
log.File(cfg.File),
)
}

View File

@@ -0,0 +1,33 @@
package metrics
import "github.com/prometheus/client_golang/prometheus"
var (
// Namespace defines the namespace for the defines metrics.
Namespace = "ocis"
// Subsystem defines the subsystem for the defines metrics.
Subsystem = "search"
)
// Metrics defines the available metrics of this service.
type Metrics struct {
// Counter *prometheus.CounterVec
BuildInfo *prometheus.GaugeVec
}
// New initializes the available metrics.
func New() *Metrics {
m := &Metrics{
BuildInfo: prometheus.NewGaugeVec(prometheus.GaugeOpts{
Namespace: Namespace,
Subsystem: Subsystem,
Name: "build_info",
Help: "Build information",
}, []string{"version"}),
}
_ = prometheus.Register(m.BuildInfo)
// TODO: implement metrics
return m
}

View File

@@ -0,0 +1,311 @@
// Copyright 2018-2022 CERN
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// In applying this license, CERN does not waive the privileges and immunities
// granted to it by virtue of its status as an Intergovernmental Organization
// or submit itself to any jurisdiction.
package index
import (
"context"
"errors"
"path"
"strings"
"time"
"github.com/blevesearch/bleve/v2"
"github.com/blevesearch/bleve/v2/analysis/analyzer/keyword"
"github.com/blevesearch/bleve/v2/mapping"
"google.golang.org/protobuf/types/known/timestamppb"
sprovider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
"github.com/cs3org/reva/v2/pkg/utils"
searchmsg "github.com/owncloud/ocis/protogen/gen/ocis/messages/search/v0"
searchsvc "github.com/owncloud/ocis/protogen/gen/ocis/services/search/v0"
)
type indexDocument struct {
RootID string
Path string
ID string
Name string
Size uint64
Mtime string
MimeType string
Type uint64
Deleted bool
}
// Index represents a bleve based search index
type Index struct {
bleveIndex bleve.Index
}
// NewPersisted returns a new instance of Index with the data being persisted in the given directory
func NewPersisted(path string) (*Index, error) {
bi, err := bleve.New(path, BuildMapping())
if err != nil {
return nil, err
}
return New(bi)
}
// New returns a new instance of Index using the given bleve Index as the backend
func New(bleveIndex bleve.Index) (*Index, error) {
return &Index{
bleveIndex: bleveIndex,
}, nil
}
// DocCount returns the number of elemenst in the index
func (i *Index) DocCount() (uint64, error) {
return i.bleveIndex.DocCount()
}
// Add adds a new entity to the Index
func (i *Index) Add(ref *sprovider.Reference, ri *sprovider.ResourceInfo) error {
entity := toEntity(ref, ri)
return i.bleveIndex.Index(idToBleveId(ri.Id), entity)
}
// Delete marks an entity from the index as deleten (still keeping it around)
func (i *Index) Delete(id *sprovider.ResourceId) error {
return i.markAsDeleted(idToBleveId(id), true)
}
// Restore marks an entity from the index as not being deleted
func (i *Index) Restore(id *sprovider.ResourceId) error {
return i.markAsDeleted(idToBleveId(id), false)
}
func (i *Index) markAsDeleted(id string, deleted bool) error {
doc, err := i.updateEntity(id, func(doc *indexDocument) {
doc.Deleted = deleted
})
if err != nil {
return err
}
if doc.Type == uint64(sprovider.ResourceType_RESOURCE_TYPE_CONTAINER) {
query := bleve.NewConjunctionQuery(
bleve.NewQueryStringQuery("RootID:"+doc.RootID),
bleve.NewQueryStringQuery("Path:"+doc.Path+"/*"),
)
bleveReq := bleve.NewSearchRequest(query)
bleveReq.Fields = []string{"*"}
res, err := i.bleveIndex.Search(bleveReq)
if err != nil {
return err
}
for _, h := range res.Hits {
_, err := i.updateEntity(h.ID, func(doc *indexDocument) {
doc.Deleted = deleted
})
if err != nil {
return err
}
}
}
return nil
}
func (i *Index) updateEntity(id string, mutateFunc func(doc *indexDocument)) (*indexDocument, error) {
doc, err := i.getEntity(id)
if err != nil {
return nil, err
}
mutateFunc(doc)
err = i.bleveIndex.Index(doc.ID, doc)
if err != nil {
return nil, err
}
return doc, nil
}
func (i *Index) getEntity(id string) (*indexDocument, error) {
req := bleve.NewSearchRequest(bleve.NewDocIDQuery([]string{id}))
req.Fields = []string{"*"}
res, err := i.bleveIndex.Search(req)
if err != nil {
return nil, err
}
if res.Hits.Len() == 0 {
return nil, errors.New("entity not found")
}
return fieldsToEntity(res.Hits[0].Fields), nil
}
// Purge removes an entity from the index
func (i *Index) Purge(id *sprovider.ResourceId) error {
return i.bleveIndex.Delete(idToBleveId(id))
}
// Purge removes an entity from the index
func (i *Index) Move(ri *sprovider.ResourceInfo) error {
doc, err := i.getEntity(idToBleveId(ri.Id))
if err != nil {
return err
}
oldName := doc.Path
newName := utils.MakeRelativePath(ri.Path)
doc, err = i.updateEntity(idToBleveId(ri.Id), func(doc *indexDocument) {
doc.Path = newName
doc.Name = path.Base(newName)
})
if err != nil {
return err
}
if doc.Type == uint64(sprovider.ResourceType_RESOURCE_TYPE_CONTAINER) {
query := bleve.NewConjunctionQuery(
bleve.NewQueryStringQuery("RootID:"+doc.RootID),
bleve.NewQueryStringQuery("Path:"+oldName+"/*"),
)
bleveReq := bleve.NewSearchRequest(query)
bleveReq.Fields = []string{"*"}
res, err := i.bleveIndex.Search(bleveReq)
if err != nil {
return err
}
for _, h := range res.Hits {
_, err := i.updateEntity(h.ID, func(doc *indexDocument) {
doc.Path = strings.Replace(doc.Path, oldName, newName, 1)
})
if err != nil {
return err
}
}
}
return nil
}
// Search searches the index according to the criteria specified in the given SearchIndexRequest
func (i *Index) Search(ctx context.Context, req *searchsvc.SearchIndexRequest) (*searchsvc.SearchIndexResponse, error) {
deletedQuery := bleve.NewBoolFieldQuery(false)
deletedQuery.SetField("Deleted")
query := bleve.NewConjunctionQuery(
bleve.NewQueryStringQuery("Name:"+req.Query),
deletedQuery, // Skip documents that have been marked as deleted
bleve.NewQueryStringQuery("RootID:"+req.Ref.ResourceId.StorageId+"!"+req.Ref.ResourceId.OpaqueId), // Limit search to the space
bleve.NewQueryStringQuery("Path:"+req.Ref.Path+"*"), // Limit search to this directory in the space
)
bleveReq := bleve.NewSearchRequest(query)
bleveReq.Size = 200
bleveReq.Fields = []string{"*"}
res, err := i.bleveIndex.Search(bleveReq)
if err != nil {
return nil, err
}
matches := []*searchmsg.Match{}
for _, h := range res.Hits {
match, err := fromFields(h.Fields)
if err != nil {
return nil, err
}
matches = append(matches, match)
}
return &searchsvc.SearchIndexResponse{
Matches: matches,
}, nil
}
// BuildMapping builds a bleve index mapping which can be used for indexing
func BuildMapping() mapping.IndexMapping {
indexMapping := bleve.NewIndexMapping()
indexMapping.DefaultAnalyzer = keyword.Name
return indexMapping
}
func toEntity(ref *sprovider.Reference, ri *sprovider.ResourceInfo) *indexDocument {
doc := &indexDocument{
RootID: idToBleveId(ref.ResourceId),
Path: ref.Path,
ID: idToBleveId(ri.Id),
Name: ri.Path,
Size: ri.Size,
MimeType: ri.MimeType,
Type: uint64(ri.Type),
Deleted: false,
}
if ri.Mtime != nil {
doc.Mtime = time.Unix(int64(ri.Mtime.Seconds), int64(ri.Mtime.Nanos)).UTC().Format(time.RFC3339)
}
return doc
}
func fieldsToEntity(fields map[string]interface{}) *indexDocument {
doc := &indexDocument{
RootID: fields["RootID"].(string),
Path: fields["Path"].(string),
ID: fields["ID"].(string),
Name: fields["Name"].(string),
Size: uint64(fields["Size"].(float64)),
Mtime: fields["Mtime"].(string),
MimeType: fields["MimeType"].(string),
Type: uint64(fields["Type"].(float64)),
}
return doc
}
func fromFields(fields map[string]interface{}) (*searchmsg.Match, error) {
rootIDParts := strings.SplitN(fields["RootID"].(string), "!", 2)
IDParts := strings.SplitN(fields["ID"].(string), "!", 2)
match := &searchmsg.Match{
Entity: &searchmsg.Entity{
Ref: &searchmsg.Reference{
ResourceId: &searchmsg.ResourceID{
StorageId: rootIDParts[0],
OpaqueId: rootIDParts[1],
},
Path: fields["Path"].(string),
},
Id: &searchmsg.ResourceID{
StorageId: IDParts[0],
OpaqueId: IDParts[1],
},
Name: fields["Name"].(string),
Size: uint64(fields["Size"].(float64)),
Type: uint64(fields["Type"].(float64)),
MimeType: fields["MimeType"].(string),
Deleted: fields["Deleted"].(bool),
},
}
if mtime, err := time.Parse(time.RFC3339, fields["Mtime"].(string)); err == nil {
match.Entity.LastModifiedTime = &timestamppb.Timestamp{Seconds: mtime.Unix(), Nanos: int32(mtime.Nanosecond())}
}
return match, nil
}
func idToBleveId(id *sprovider.ResourceId) string {
if id == nil {
return ""
}
return id.StorageId + "!" + id.OpaqueId
}

View File

@@ -0,0 +1,13 @@
package index_test
import (
"testing"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)
func TestIndex(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Index Suite")
}

View File

@@ -0,0 +1,371 @@
package index_test
import (
"context"
"github.com/blevesearch/bleve/v2"
sprovider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
typesv1beta1 "github.com/cs3org/go-cs3apis/cs3/types/v1beta1"
"github.com/owncloud/ocis/extensions/search/pkg/search/index"
searchmsg "github.com/owncloud/ocis/protogen/gen/ocis/messages/search/v0"
searchsvc "github.com/owncloud/ocis/protogen/gen/ocis/services/search/v0"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)
var _ = Describe("Index", func() {
var (
i *index.Index
bleveIndex bleve.Index
ctx context.Context
rootId = &sprovider.ResourceId{
StorageId: "storageid",
OpaqueId: "rootopaqueid",
}
ref = &sprovider.Reference{
ResourceId: rootId,
Path: "./foo.pdf",
}
ri = &sprovider.ResourceInfo{
Id: &sprovider.ResourceId{
StorageId: "storageid",
OpaqueId: "opaqueid",
},
ParentId: &sprovider.ResourceId{
StorageId: "storageid",
OpaqueId: "someopaqueid",
},
Path: "foo.pdf",
Size: 12345,
Type: sprovider.ResourceType_RESOURCE_TYPE_FILE,
MimeType: "application/pdf",
Mtime: &typesv1beta1.Timestamp{Seconds: 4000},
}
parentRef = &sprovider.Reference{
ResourceId: rootId,
Path: "./sudbir",
}
parentRi = &sprovider.ResourceInfo{
Id: &sprovider.ResourceId{
StorageId: "storageid",
OpaqueId: "parentopaqueid",
},
Path: "subdir",
Size: 12345,
Type: sprovider.ResourceType_RESOURCE_TYPE_CONTAINER,
Mtime: &typesv1beta1.Timestamp{Seconds: 4000},
}
childRef = &sprovider.Reference{
ResourceId: rootId,
Path: "./sudbir/child.pdf",
}
childRi = &sprovider.ResourceInfo{
Id: &sprovider.ResourceId{
StorageId: "storageid",
OpaqueId: "childopaqueid",
},
ParentId: &sprovider.ResourceId{
StorageId: "storageid",
OpaqueId: "parentopaqueid",
},
Path: "child.pdf",
Size: 12345,
Type: sprovider.ResourceType_RESOURCE_TYPE_FILE,
Mtime: &typesv1beta1.Timestamp{Seconds: 4000},
}
assertDocCount = func(rootId *sprovider.ResourceId, query string, expectedCount int) {
res, err := i.Search(ctx, &searchsvc.SearchIndexRequest{
Query: query,
Ref: &searchmsg.Reference{
ResourceId: &searchmsg.ResourceID{
StorageId: rootId.StorageId,
OpaqueId: rootId.OpaqueId,
},
},
})
ExpectWithOffset(1, err).ToNot(HaveOccurred())
ExpectWithOffset(1, len(res.Matches)).To(Equal(expectedCount))
}
)
BeforeEach(func() {
var err error
bleveIndex, err = bleve.NewMemOnly(index.BuildMapping())
Expect(err).ToNot(HaveOccurred())
i, err = index.New(bleveIndex)
Expect(err).ToNot(HaveOccurred())
})
Describe("New", func() {
It("returns a new index instance", func() {
i, err := index.New(bleveIndex)
Expect(err).ToNot(HaveOccurred())
Expect(i).ToNot(BeNil())
})
})
Describe("NewPersisted", func() {
It("returns a new index instance", func() {
i, err := index.NewPersisted("")
Expect(err).ToNot(HaveOccurred())
Expect(i).ToNot(BeNil())
})
})
Describe("Search", func() {
Context("with a file in the root of the space", func() {
BeforeEach(func() {
err := i.Add(ref, ri)
Expect(err).ToNot(HaveOccurred())
})
It("scopes the search to the specified space", func() {
res, err := i.Search(ctx, &searchsvc.SearchIndexRequest{
Ref: &searchmsg.Reference{
ResourceId: &searchmsg.ResourceID{
StorageId: "differentstorageid",
OpaqueId: "differentopaqueid",
},
},
Query: "foo.pdf",
})
Expect(err).ToNot(HaveOccurred())
Expect(res).ToNot(BeNil())
Expect(len(res.Matches)).To(Equal(0))
})
It("limits the search to the relevant fields", func() {
res, err := i.Search(ctx, &searchsvc.SearchIndexRequest{
Ref: &searchmsg.Reference{
ResourceId: &searchmsg.ResourceID{
StorageId: ref.ResourceId.StorageId,
OpaqueId: ref.ResourceId.OpaqueId,
},
},
Query: "*" + ref.ResourceId.OpaqueId + "*",
})
Expect(err).ToNot(HaveOccurred())
Expect(res).ToNot(BeNil())
Expect(len(res.Matches)).To(Equal(0))
})
It("returns all desired fields", func() {
res, err := i.Search(ctx, &searchsvc.SearchIndexRequest{
Ref: &searchmsg.Reference{
ResourceId: &searchmsg.ResourceID{
StorageId: ref.ResourceId.StorageId,
OpaqueId: ref.ResourceId.OpaqueId,
},
},
Query: "foo.pdf",
})
Expect(err).ToNot(HaveOccurred())
Expect(res).ToNot(BeNil())
Expect(len(res.Matches)).To(Equal(1))
match := res.Matches[0]
Expect(match.Entity.Ref.ResourceId.OpaqueId).To(Equal(ref.ResourceId.OpaqueId))
Expect(match.Entity.Ref.Path).To(Equal(ref.Path))
Expect(match.Entity.Id.OpaqueId).To(Equal(ri.Id.OpaqueId))
Expect(match.Entity.Name).To(Equal(ri.Path))
Expect(match.Entity.Size).To(Equal(ri.Size))
Expect(match.Entity.Type).To(Equal(uint64(ri.Type)))
Expect(match.Entity.MimeType).To(Equal(ri.MimeType))
Expect(match.Entity.Deleted).To(BeFalse())
Expect(uint64(match.Entity.LastModifiedTime.AsTime().Unix())).To(Equal(ri.Mtime.Seconds))
})
It("finds files by name, prefix or substring match", func() {
queries := []string{"foo.pdf", "foo*", "*oo.p*"}
for _, query := range queries {
res, err := i.Search(ctx, &searchsvc.SearchIndexRequest{
Ref: &searchmsg.Reference{
ResourceId: &searchmsg.ResourceID{
StorageId: ref.ResourceId.StorageId,
OpaqueId: ref.ResourceId.OpaqueId,
},
},
Query: query,
})
Expect(err).ToNot(HaveOccurred())
Expect(res).ToNot(BeNil())
Expect(len(res.Matches)).To(Equal(1), "query returned no result: "+query)
Expect(res.Matches[0].Entity.Ref.ResourceId.OpaqueId).To(Equal(ref.ResourceId.OpaqueId))
Expect(res.Matches[0].Entity.Ref.Path).To(Equal(ref.Path))
Expect(res.Matches[0].Entity.Id.OpaqueId).To(Equal(ri.Id.OpaqueId))
Expect(res.Matches[0].Entity.Name).To(Equal(ri.Path))
Expect(res.Matches[0].Entity.Size).To(Equal(ri.Size))
}
})
Context("and an additional file in a subdirectory", func() {
var (
nestedRef *sprovider.Reference
nestedRI *sprovider.ResourceInfo
)
BeforeEach(func() {
nestedRef = &sprovider.Reference{
ResourceId: &sprovider.ResourceId{
StorageId: "storageid",
OpaqueId: "rootopaqueid",
},
Path: "./nested/nestedpdf.pdf",
}
nestedRI = &sprovider.ResourceInfo{
Id: &sprovider.ResourceId{
StorageId: "storageid",
OpaqueId: "nestedopaqueid",
},
Path: "nestedpdf.pdf",
Size: 12345,
}
err := i.Add(nestedRef, nestedRI)
Expect(err).ToNot(HaveOccurred())
})
It("finds files living deeper in the tree by filename, prefix or substring match", func() {
queries := []string{"nestedpdf.pdf", "nested*", "*tedpdf.*"}
for _, query := range queries {
res, err := i.Search(ctx, &searchsvc.SearchIndexRequest{
Ref: &searchmsg.Reference{
ResourceId: &searchmsg.ResourceID{
StorageId: ref.ResourceId.StorageId,
OpaqueId: ref.ResourceId.OpaqueId,
},
},
Query: query,
})
Expect(err).ToNot(HaveOccurred())
Expect(res).ToNot(BeNil())
Expect(len(res.Matches)).To(Equal(1), "query returned no result: "+query)
}
})
It("does not find the higher levels when limiting the searched directory", func() {
res, err := i.Search(ctx, &searchsvc.SearchIndexRequest{
Ref: &searchmsg.Reference{
ResourceId: &searchmsg.ResourceID{
StorageId: ref.ResourceId.StorageId,
OpaqueId: ref.ResourceId.OpaqueId,
},
Path: "./nested/",
},
Query: "foo.pdf",
})
Expect(err).ToNot(HaveOccurred())
Expect(res).ToNot(BeNil())
Expect(len(res.Matches)).To(Equal(0))
})
})
})
})
Describe("Add", func() {
It("adds a resourceInfo to the index", func() {
err := i.Add(ref, ri)
Expect(err).ToNot(HaveOccurred())
count, err := bleveIndex.DocCount()
Expect(err).ToNot(HaveOccurred())
Expect(count).To(Equal(uint64(1)))
query := bleve.NewMatchQuery("foo.pdf")
res, err := bleveIndex.Search(bleve.NewSearchRequest(query))
Expect(err).ToNot(HaveOccurred())
Expect(res.Hits.Len()).To(Equal(1))
})
It("updates an existing resource in the index", func() {
err := i.Add(ref, ri)
Expect(err).ToNot(HaveOccurred())
count, _ := bleveIndex.DocCount()
Expect(count).To(Equal(uint64(1)))
err = i.Add(ref, ri)
Expect(err).ToNot(HaveOccurred())
count, _ = bleveIndex.DocCount()
Expect(count).To(Equal(uint64(1)))
})
})
Describe("Delete", func() {
It("marks a resource as deleted", func() {
err := i.Add(parentRef, parentRi)
Expect(err).ToNot(HaveOccurred())
assertDocCount(rootId, "subdir", 1)
err = i.Delete(parentRi.Id)
Expect(err).ToNot(HaveOccurred())
assertDocCount(rootId, "subdir", 0)
})
It("also marks child resources as deleted", func() {
err := i.Add(parentRef, parentRi)
Expect(err).ToNot(HaveOccurred())
err = i.Add(childRef, childRi)
Expect(err).ToNot(HaveOccurred())
assertDocCount(rootId, "subdir", 1)
assertDocCount(rootId, "child.pdf", 1)
err = i.Delete(parentRi.Id)
Expect(err).ToNot(HaveOccurred())
assertDocCount(rootId, "subdir", 0)
assertDocCount(rootId, "child.pdf", 0)
})
})
Describe("Restore", func() {
It("also marks child resources as restored", func() {
err := i.Add(parentRef, parentRi)
Expect(err).ToNot(HaveOccurred())
err = i.Add(childRef, childRi)
Expect(err).ToNot(HaveOccurred())
err = i.Delete(parentRi.Id)
Expect(err).ToNot(HaveOccurred())
assertDocCount(rootId, "subdir", 0)
assertDocCount(rootId, "child.pdf", 0)
err = i.Restore(parentRi.Id)
Expect(err).ToNot(HaveOccurred())
assertDocCount(rootId, "subdir", 1)
assertDocCount(rootId, "child.pdf", 1)
})
})
Describe("Move", func() {
It("moves the parent and its child resources", func() {
err := i.Add(parentRef, parentRi)
Expect(err).ToNot(HaveOccurred())
err = i.Add(childRef, childRi)
Expect(err).ToNot(HaveOccurred())
parentRi.Path = "newname"
err = i.Move(parentRi)
Expect(err).ToNot(HaveOccurred())
assertDocCount(rootId, "subdir", 0)
res, err := i.Search(ctx, &searchsvc.SearchIndexRequest{
Query: "child.pdf",
Ref: &searchmsg.Reference{
ResourceId: &searchmsg.ResourceID{
StorageId: rootId.StorageId,
OpaqueId: rootId.OpaqueId,
},
},
})
Expect(err).ToNot(HaveOccurred())
Expect(len(res.Matches)).To(Equal(1))
Expect(res.Matches[0].Entity.Ref.Path).To(Equal("./newname/child.pdf"))
})
})
})

View File

@@ -0,0 +1,415 @@
// Code generated by mockery v2.10.0. DO NOT EDIT.
package mocks
import (
context "context"
bleve "github.com/blevesearch/bleve/v2"
index "github.com/blevesearch/bleve_index_api"
mapping "github.com/blevesearch/bleve/v2/mapping"
mock "github.com/stretchr/testify/mock"
)
// BleveIndex is an autogenerated mock type for the BleveIndex type
type BleveIndex struct {
mock.Mock
}
// Advanced provides a mock function with given fields:
func (_m *BleveIndex) Advanced() (index.Index, error) {
ret := _m.Called()
var r0 index.Index
if rf, ok := ret.Get(0).(func() index.Index); ok {
r0 = rf()
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(index.Index)
}
}
var r1 error
if rf, ok := ret.Get(1).(func() error); ok {
r1 = rf()
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// Batch provides a mock function with given fields: b
func (_m *BleveIndex) Batch(b *bleve.Batch) error {
ret := _m.Called(b)
var r0 error
if rf, ok := ret.Get(0).(func(*bleve.Batch) error); ok {
r0 = rf(b)
} else {
r0 = ret.Error(0)
}
return r0
}
// Close provides a mock function with given fields:
func (_m *BleveIndex) Close() error {
ret := _m.Called()
var r0 error
if rf, ok := ret.Get(0).(func() error); ok {
r0 = rf()
} else {
r0 = ret.Error(0)
}
return r0
}
// Delete provides a mock function with given fields: id
func (_m *BleveIndex) Delete(id string) error {
ret := _m.Called(id)
var r0 error
if rf, ok := ret.Get(0).(func(string) error); ok {
r0 = rf(id)
} else {
r0 = ret.Error(0)
}
return r0
}
// DeleteInternal provides a mock function with given fields: key
func (_m *BleveIndex) DeleteInternal(key []byte) error {
ret := _m.Called(key)
var r0 error
if rf, ok := ret.Get(0).(func([]byte) error); ok {
r0 = rf(key)
} else {
r0 = ret.Error(0)
}
return r0
}
// DocCount provides a mock function with given fields:
func (_m *BleveIndex) DocCount() (uint64, error) {
ret := _m.Called()
var r0 uint64
if rf, ok := ret.Get(0).(func() uint64); ok {
r0 = rf()
} else {
r0 = ret.Get(0).(uint64)
}
var r1 error
if rf, ok := ret.Get(1).(func() error); ok {
r1 = rf()
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// Document provides a mock function with given fields: id
func (_m *BleveIndex) Document(id string) (index.Document, error) {
ret := _m.Called(id)
var r0 index.Document
if rf, ok := ret.Get(0).(func(string) index.Document); ok {
r0 = rf(id)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(index.Document)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(string) error); ok {
r1 = rf(id)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// FieldDict provides a mock function with given fields: field
func (_m *BleveIndex) FieldDict(field string) (index.FieldDict, error) {
ret := _m.Called(field)
var r0 index.FieldDict
if rf, ok := ret.Get(0).(func(string) index.FieldDict); ok {
r0 = rf(field)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(index.FieldDict)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(string) error); ok {
r1 = rf(field)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// FieldDictPrefix provides a mock function with given fields: field, termPrefix
func (_m *BleveIndex) FieldDictPrefix(field string, termPrefix []byte) (index.FieldDict, error) {
ret := _m.Called(field, termPrefix)
var r0 index.FieldDict
if rf, ok := ret.Get(0).(func(string, []byte) index.FieldDict); ok {
r0 = rf(field, termPrefix)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(index.FieldDict)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(string, []byte) error); ok {
r1 = rf(field, termPrefix)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// FieldDictRange provides a mock function with given fields: field, startTerm, endTerm
func (_m *BleveIndex) FieldDictRange(field string, startTerm []byte, endTerm []byte) (index.FieldDict, error) {
ret := _m.Called(field, startTerm, endTerm)
var r0 index.FieldDict
if rf, ok := ret.Get(0).(func(string, []byte, []byte) index.FieldDict); ok {
r0 = rf(field, startTerm, endTerm)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(index.FieldDict)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(string, []byte, []byte) error); ok {
r1 = rf(field, startTerm, endTerm)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// Fields provides a mock function with given fields:
func (_m *BleveIndex) Fields() ([]string, error) {
ret := _m.Called()
var r0 []string
if rf, ok := ret.Get(0).(func() []string); ok {
r0 = rf()
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]string)
}
}
var r1 error
if rf, ok := ret.Get(1).(func() error); ok {
r1 = rf()
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// GetInternal provides a mock function with given fields: key
func (_m *BleveIndex) GetInternal(key []byte) ([]byte, error) {
ret := _m.Called(key)
var r0 []byte
if rf, ok := ret.Get(0).(func([]byte) []byte); ok {
r0 = rf(key)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]byte)
}
}
var r1 error
if rf, ok := ret.Get(1).(func([]byte) error); ok {
r1 = rf(key)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// Index provides a mock function with given fields: id, data
func (_m *BleveIndex) Index(id string, data interface{}) error {
ret := _m.Called(id, data)
var r0 error
if rf, ok := ret.Get(0).(func(string, interface{}) error); ok {
r0 = rf(id, data)
} else {
r0 = ret.Error(0)
}
return r0
}
// Mapping provides a mock function with given fields:
func (_m *BleveIndex) Mapping() mapping.IndexMapping {
ret := _m.Called()
var r0 mapping.IndexMapping
if rf, ok := ret.Get(0).(func() mapping.IndexMapping); ok {
r0 = rf()
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(mapping.IndexMapping)
}
}
return r0
}
// Name provides a mock function with given fields:
func (_m *BleveIndex) Name() string {
ret := _m.Called()
var r0 string
if rf, ok := ret.Get(0).(func() string); ok {
r0 = rf()
} else {
r0 = ret.Get(0).(string)
}
return r0
}
// NewBatch provides a mock function with given fields:
func (_m *BleveIndex) NewBatch() *bleve.Batch {
ret := _m.Called()
var r0 *bleve.Batch
if rf, ok := ret.Get(0).(func() *bleve.Batch); ok {
r0 = rf()
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*bleve.Batch)
}
}
return r0
}
// Search provides a mock function with given fields: req
func (_m *BleveIndex) Search(req *bleve.SearchRequest) (*bleve.SearchResult, error) {
ret := _m.Called(req)
var r0 *bleve.SearchResult
if rf, ok := ret.Get(0).(func(*bleve.SearchRequest) *bleve.SearchResult); ok {
r0 = rf(req)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*bleve.SearchResult)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(*bleve.SearchRequest) error); ok {
r1 = rf(req)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// SearchInContext provides a mock function with given fields: ctx, req
func (_m *BleveIndex) SearchInContext(ctx context.Context, req *bleve.SearchRequest) (*bleve.SearchResult, error) {
ret := _m.Called(ctx, req)
var r0 *bleve.SearchResult
if rf, ok := ret.Get(0).(func(context.Context, *bleve.SearchRequest) *bleve.SearchResult); ok {
r0 = rf(ctx, req)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*bleve.SearchResult)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, *bleve.SearchRequest) error); ok {
r1 = rf(ctx, req)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// SetInternal provides a mock function with given fields: key, val
func (_m *BleveIndex) SetInternal(key []byte, val []byte) error {
ret := _m.Called(key, val)
var r0 error
if rf, ok := ret.Get(0).(func([]byte, []byte) error); ok {
r0 = rf(key, val)
} else {
r0 = ret.Error(0)
}
return r0
}
// SetName provides a mock function with given fields: _a0
func (_m *BleveIndex) SetName(_a0 string) {
_m.Called(_a0)
}
// Stats provides a mock function with given fields:
func (_m *BleveIndex) Stats() *bleve.IndexStat {
ret := _m.Called()
var r0 *bleve.IndexStat
if rf, ok := ret.Get(0).(func() *bleve.IndexStat); ok {
r0 = rf()
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*bleve.IndexStat)
}
}
return r0
}
// StatsMap provides a mock function with given fields:
func (_m *BleveIndex) StatsMap() map[string]interface{} {
ret := _m.Called()
var r0 map[string]interface{}
if rf, ok := ret.Get(0).(func() map[string]interface{}); ok {
r0 = rf()
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(map[string]interface{})
}
}
return r0
}

View File

@@ -0,0 +1,131 @@
// Code generated by mockery v2.10.0. DO NOT EDIT.
package mocks
import (
context "context"
providerv1beta1 "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
mock "github.com/stretchr/testify/mock"
v0 "github.com/owncloud/ocis/protogen/gen/ocis/services/search/v0"
)
// IndexClient is an autogenerated mock type for the IndexClient type
type IndexClient struct {
mock.Mock
}
// Add provides a mock function with given fields: ref, ri
func (_m *IndexClient) Add(ref *providerv1beta1.Reference, ri *providerv1beta1.ResourceInfo) error {
ret := _m.Called(ref, ri)
var r0 error
if rf, ok := ret.Get(0).(func(*providerv1beta1.Reference, *providerv1beta1.ResourceInfo) error); ok {
r0 = rf(ref, ri)
} else {
r0 = ret.Error(0)
}
return r0
}
// Delete provides a mock function with given fields: ri
func (_m *IndexClient) Delete(ri *providerv1beta1.ResourceId) error {
ret := _m.Called(ri)
var r0 error
if rf, ok := ret.Get(0).(func(*providerv1beta1.ResourceId) error); ok {
r0 = rf(ri)
} else {
r0 = ret.Error(0)
}
return r0
}
// DocCount provides a mock function with given fields:
func (_m *IndexClient) DocCount() (uint64, error) {
ret := _m.Called()
var r0 uint64
if rf, ok := ret.Get(0).(func() uint64); ok {
r0 = rf()
} else {
r0 = ret.Get(0).(uint64)
}
var r1 error
if rf, ok := ret.Get(1).(func() error); ok {
r1 = rf()
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// Move provides a mock function with given fields: ri
func (_m *IndexClient) Move(ri *providerv1beta1.ResourceInfo) error {
ret := _m.Called(ri)
var r0 error
if rf, ok := ret.Get(0).(func(*providerv1beta1.ResourceInfo) error); ok {
r0 = rf(ri)
} else {
r0 = ret.Error(0)
}
return r0
}
// Purge provides a mock function with given fields: ri
func (_m *IndexClient) Purge(ri *providerv1beta1.ResourceId) error {
ret := _m.Called(ri)
var r0 error
if rf, ok := ret.Get(0).(func(*providerv1beta1.ResourceId) error); ok {
r0 = rf(ri)
} else {
r0 = ret.Error(0)
}
return r0
}
// Restore provides a mock function with given fields: ri
func (_m *IndexClient) Restore(ri *providerv1beta1.ResourceId) error {
ret := _m.Called(ri)
var r0 error
if rf, ok := ret.Get(0).(func(*providerv1beta1.ResourceId) error); ok {
r0 = rf(ri)
} else {
r0 = ret.Error(0)
}
return r0
}
// Search provides a mock function with given fields: ctx, req
func (_m *IndexClient) Search(ctx context.Context, req *v0.SearchIndexRequest) (*v0.SearchIndexResponse, error) {
ret := _m.Called(ctx, req)
var r0 *v0.SearchIndexResponse
if rf, ok := ret.Get(0).(func(context.Context, *v0.SearchIndexRequest) *v0.SearchIndexResponse); ok {
r0 = rf(ctx, req)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*v0.SearchIndexResponse)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, *v0.SearchIndexRequest) error); ok {
r1 = rf(ctx, req)
} else {
r1 = ret.Error(1)
}
return r0, r1
}

View File

@@ -0,0 +1,62 @@
// Code generated by mockery v2.10.0. DO NOT EDIT.
package mocks
import (
context "context"
mock "github.com/stretchr/testify/mock"
v0 "github.com/owncloud/ocis/protogen/gen/ocis/services/search/v0"
)
// ProviderClient is an autogenerated mock type for the ProviderClient type
type ProviderClient struct {
mock.Mock
}
// IndexSpace provides a mock function with given fields: ctx, req
func (_m *ProviderClient) IndexSpace(ctx context.Context, req *v0.IndexSpaceRequest) (*v0.IndexSpaceResponse, error) {
ret := _m.Called(ctx, req)
var r0 *v0.IndexSpaceResponse
if rf, ok := ret.Get(0).(func(context.Context, *v0.IndexSpaceRequest) *v0.IndexSpaceResponse); ok {
r0 = rf(ctx, req)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*v0.IndexSpaceResponse)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, *v0.IndexSpaceRequest) error); ok {
r1 = rf(ctx, req)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// Search provides a mock function with given fields: ctx, req
func (_m *ProviderClient) Search(ctx context.Context, req *v0.SearchRequest) (*v0.SearchResponse, error) {
ret := _m.Called(ctx, req)
var r0 *v0.SearchResponse
if rf, ok := ret.Get(0).(func(context.Context, *v0.SearchRequest) *v0.SearchResponse); ok {
r0 = rf(ctx, req)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*v0.SearchResponse)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, *v0.SearchRequest) error); ok {
r1 = rf(ctx, req)
} else {
r1 = ret.Error(1)
}
return r0, r1
}

View File

@@ -0,0 +1,13 @@
package provider_test
import (
"testing"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)
func TestProvider(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Provider Suite")
}

View File

@@ -0,0 +1,278 @@
package provider
import (
"context"
"fmt"
"path/filepath"
"strings"
gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1"
user "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1"
rpcv1beta1 "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1"
provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
typesv1beta1 "github.com/cs3org/go-cs3apis/cs3/types/v1beta1"
ctxpkg "github.com/cs3org/reva/v2/pkg/ctx"
"github.com/cs3org/reva/v2/pkg/errtypes"
"github.com/cs3org/reva/v2/pkg/events"
"github.com/cs3org/reva/v2/pkg/storage/utils/walker"
"github.com/cs3org/reva/v2/pkg/utils"
"github.com/owncloud/ocis/extensions/search/pkg/search"
"github.com/owncloud/ocis/ocis-pkg/log"
"google.golang.org/grpc/metadata"
searchmsg "github.com/owncloud/ocis/protogen/gen/ocis/messages/search/v0"
searchsvc "github.com/owncloud/ocis/protogen/gen/ocis/services/search/v0"
)
type Provider struct {
logger log.Logger
gwClient gateway.GatewayAPIClient
indexClient search.IndexClient
machineAuthAPIKey string
}
func New(gwClient gateway.GatewayAPIClient, indexClient search.IndexClient, machineAuthAPIKey string, eventsChan <-chan interface{}, logger log.Logger) *Provider {
p := &Provider{
gwClient: gwClient,
indexClient: indexClient,
machineAuthAPIKey: machineAuthAPIKey,
logger: logger,
}
go func() {
for {
ev := <-eventsChan
var ref *provider.Reference
var owner *user.User
switch e := ev.(type) {
case events.ItemTrashed:
err := p.indexClient.Delete(e.ID)
if err != nil {
p.logger.Error().Err(err).Interface("Id", e.ID).Msg("failed to remove item from index")
}
continue
case events.ItemRestored:
ref = e.Ref
owner = &user.User{
Id: e.Executant,
}
statRes, err := p.statResource(ref, owner)
if err != nil {
p.logger.Error().Err(err).Msg("failed to stat the changed resource")
}
switch statRes.Status.Code {
case rpc.Code_CODE_OK:
err = p.indexClient.Restore(statRes.Info.Id)
if err != nil {
p.logger.Error().Err(err).Msg("failed to restore the changed resource in the index")
}
default:
p.logger.Error().Interface("statRes", statRes).Msg("failed to stat the changed resource")
}
continue
case events.ItemMoved:
ref = e.Ref
owner = &user.User{
Id: e.Executant,
}
statRes, err := p.statResource(ref, owner)
if err != nil {
p.logger.Error().Err(err).Msg("failed to stat the changed resource")
}
switch statRes.Status.Code {
case rpc.Code_CODE_OK:
err = p.indexClient.Move(statRes.Info)
if err != nil {
p.logger.Error().Err(err).Msg("failed to restore the changed resource in the index")
}
default:
p.logger.Error().Interface("statRes", statRes).Msg("failed to stat the changed resource")
}
continue
case events.ContainerCreated:
ref = e.Ref
owner = &user.User{
Id: e.Executant,
}
case events.FileUploaded:
ref = e.Ref
owner = &user.User{
Id: e.Executant,
}
case events.FileVersionRestored:
ref = e.Ref
owner = &user.User{
Id: e.Executant,
}
default:
// Not sure what to do here. Skip.
continue
}
statRes, err := p.statResource(ref, owner)
if err != nil {
p.logger.Error().Err(err).Msg("failed to stat the changed resource")
}
switch statRes.Status.Code {
case rpc.Code_CODE_OK:
err = p.indexClient.Add(ref, statRes.Info)
if err != nil {
p.logger.Error().Err(err).Msg("error adding updating the resource in the index")
} else {
p.logDocCount()
}
default:
p.logger.Error().Interface("statRes", statRes).Msg("failed to stat the changed resource")
}
}
}()
return p
}
func (p *Provider) statResource(ref *provider.Reference, owner *user.User) (*provider.StatResponse, error) {
// Get auth
ownerCtx := ctxpkg.ContextSetUser(context.Background(), owner)
authRes, err := p.gwClient.Authenticate(ownerCtx, &gateway.AuthenticateRequest{
Type: "machine",
ClientId: "userid:" + owner.Id.OpaqueId,
ClientSecret: p.machineAuthAPIKey,
})
if err != nil || authRes.GetStatus().GetCode() != rpc.Code_CODE_OK {
p.logger.Error().Err(err).Interface("authRes", authRes).Msg("error using machine auth")
}
ownerCtx = metadata.AppendToOutgoingContext(ownerCtx, ctxpkg.TokenHeader, authRes.Token)
// Stat changed resource resource
return p.gwClient.Stat(ownerCtx, &provider.StatRequest{Ref: ref})
}
func (p *Provider) logDocCount() {
c, err := p.indexClient.DocCount()
if err != nil {
p.logger.Error().Err(err).Msg("error getting document count from the index")
}
p.logger.Debug().Interface("count", c).Msg("new document count")
}
func (p *Provider) Search(ctx context.Context, req *searchsvc.SearchRequest) (*searchsvc.SearchResponse, error) {
if req.Query == "" {
return nil, errtypes.PreconditionFailed("empty query provided")
}
listSpacesRes, err := p.gwClient.ListStorageSpaces(ctx, &provider.ListStorageSpacesRequest{
Opaque: &typesv1beta1.Opaque{Map: map[string]*typesv1beta1.OpaqueEntry{
"path": {
Decoder: "plain",
Value: []byte("/"),
},
}},
})
if err != nil {
return nil, err
}
matches := []*searchmsg.Match{}
for _, space := range listSpacesRes.StorageSpaces {
pathPrefix := ""
if space.SpaceType == "grant" {
gpRes, err := p.gwClient.GetPath(ctx, &provider.GetPathRequest{
ResourceId: space.Root,
})
if err != nil {
return nil, err
}
if gpRes.Status.Code != rpcv1beta1.Code_CODE_OK {
return nil, errtypes.NewErrtypeFromStatus(gpRes.Status)
}
pathPrefix = utils.MakeRelativePath(gpRes.Path)
}
res, err := p.indexClient.Search(ctx, &searchsvc.SearchIndexRequest{
Query: req.Query,
Ref: &searchmsg.Reference{
ResourceId: &searchmsg.ResourceID{
StorageId: space.Root.StorageId,
OpaqueId: space.Root.OpaqueId,
},
Path: pathPrefix,
},
})
if err != nil {
return nil, err
}
for _, match := range res.Matches {
if pathPrefix != "" {
match.Entity.Ref.Path = utils.MakeRelativePath(strings.TrimPrefix(match.Entity.Ref.Path, pathPrefix))
}
matches = append(matches, match)
}
}
return &searchsvc.SearchResponse{
Matches: matches,
}, nil
}
func (p *Provider) IndexSpace(ctx context.Context, req *searchsvc.IndexSpaceRequest) (*searchsvc.IndexSpaceResponse, error) {
// get user
res, err := p.gwClient.GetUserByClaim(context.Background(), &user.GetUserByClaimRequest{
Claim: "username",
Value: req.UserId,
})
if err != nil || res.Status.Code != rpc.Code_CODE_OK {
fmt.Println("error: Could not get user by userid")
return nil, err
}
// Get auth context
ownerCtx := ctxpkg.ContextSetUser(context.Background(), res.User)
authRes, err := p.gwClient.Authenticate(ownerCtx, &gateway.AuthenticateRequest{
Type: "machine",
ClientId: "userid:" + res.User.Id.OpaqueId,
ClientSecret: p.machineAuthAPIKey,
})
if err != nil || authRes.GetStatus().GetCode() != rpc.Code_CODE_OK {
return nil, err
}
if authRes.GetStatus().GetCode() != rpc.Code_CODE_OK {
return nil, fmt.Errorf("could not get authenticated context for user")
}
ownerCtx = metadata.AppendToOutgoingContext(ownerCtx, ctxpkg.TokenHeader, authRes.Token)
// Walk the space and index all files
walker := walker.NewWalker(p.gwClient)
rootId := &provider.ResourceId{StorageId: req.SpaceId, OpaqueId: req.SpaceId}
err = walker.Walk(ownerCtx, rootId, func(wd string, info *provider.ResourceInfo, err error) error {
if err != nil {
p.logger.Error().Err(err).Msg("error walking the tree")
}
ref := &provider.Reference{
Path: utils.MakeRelativePath(filepath.Join(wd, info.Path)),
ResourceId: rootId,
}
err = p.indexClient.Add(ref, info)
if err != nil {
p.logger.Error().Err(err).Msg("error adding resource to the index")
} else {
p.logger.Debug().Interface("ref", ref).Msg("added resource to index")
}
return nil
})
if err != nil {
return nil, err
}
p.logDocCount()
return &searchsvc.IndexSpaceResponse{}, nil
}

View File

@@ -0,0 +1,400 @@
package provider_test
import (
"context"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"github.com/stretchr/testify/mock"
gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1"
userv1beta1 "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
sprovider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
typesv1beta1 "github.com/cs3org/go-cs3apis/cs3/types/v1beta1"
"github.com/cs3org/reva/v2/pkg/events"
"github.com/cs3org/reva/v2/pkg/rgrpc/status"
cs3mocks "github.com/cs3org/reva/v2/tests/cs3mocks/mocks"
"github.com/owncloud/ocis/extensions/search/pkg/search/mocks"
provider "github.com/owncloud/ocis/extensions/search/pkg/search/provider"
"github.com/owncloud/ocis/ocis-pkg/log"
searchmsg "github.com/owncloud/ocis/protogen/gen/ocis/messages/search/v0"
searchsvc "github.com/owncloud/ocis/protogen/gen/ocis/services/search/v0"
)
var _ = Describe("Searchprovider", func() {
var (
p *provider.Provider
gwClient *cs3mocks.GatewayAPIClient
indexClient *mocks.IndexClient
ctx context.Context
eventsChan chan interface{}
logger = log.NewLogger()
user = &userv1beta1.User{
Id: &userv1beta1.UserId{
OpaqueId: "user",
},
}
otherUser = &userv1beta1.User{
Id: &userv1beta1.UserId{
OpaqueId: "otheruser",
},
}
personalSpace = &sprovider.StorageSpace{
Opaque: &typesv1beta1.Opaque{
Map: map[string]*typesv1beta1.OpaqueEntry{
"path": {
Decoder: "plain",
Value: []byte("/foo"),
},
},
},
Id: &sprovider.StorageSpaceId{OpaqueId: "personalspace"},
Root: &sprovider.ResourceId{OpaqueId: "personalspaceroot"},
Name: "personalspace",
}
ref = &sprovider.Reference{
ResourceId: &sprovider.ResourceId{
StorageId: "storageid",
OpaqueId: "rootopaqueid",
},
Path: "./foo.pdf",
}
ri = &sprovider.ResourceInfo{
Id: &sprovider.ResourceId{
StorageId: "storageid",
OpaqueId: "opaqueid",
},
Path: "foo.pdf",
Size: 12345,
}
)
BeforeEach(func() {
ctx = context.Background()
eventsChan = make(chan interface{})
gwClient = &cs3mocks.GatewayAPIClient{}
indexClient = &mocks.IndexClient{}
p = provider.New(gwClient, indexClient, "", eventsChan, logger)
gwClient.On("Authenticate", mock.Anything, mock.Anything).Return(&gateway.AuthenticateResponse{
Status: status.NewOK(ctx),
Token: "authtoken",
}, nil)
gwClient.On("Stat", mock.Anything, mock.Anything).Return(&sprovider.StatResponse{
Status: status.NewOK(context.Background()),
Info: ri,
}, nil)
indexClient.On("DocCount").Return(uint64(1), nil)
})
Describe("New", func() {
It("returns a new instance", func() {
p := provider.New(gwClient, indexClient, "", eventsChan, logger)
Expect(p).ToNot(BeNil())
})
})
Describe("events", func() {
It("trigger an index update when a file has been uploaded", func() {
called := false
indexClient.On("Add", mock.Anything, mock.MatchedBy(func(riToIndex *sprovider.ResourceInfo) bool {
return riToIndex.Id.OpaqueId == ri.Id.OpaqueId
})).Return(nil).Run(func(args mock.Arguments) {
called = true
})
eventsChan <- events.FileUploaded{
Ref: ref,
Executant: user.Id,
}
Eventually(func() bool {
return called
}).Should(BeTrue())
})
It("removes an entry from the index when the file has been deleted", func() {
called := false
gwClient.On("Stat", mock.Anything, mock.Anything).Return(&sprovider.StatResponse{
Status: status.NewNotFound(context.Background(), ""),
}, nil)
indexClient.On("Delete", mock.MatchedBy(func(id *sprovider.ResourceId) bool {
return id.OpaqueId == ri.Id.OpaqueId
})).Return(nil).Run(func(args mock.Arguments) {
called = true
})
eventsChan <- events.ItemTrashed{
Ref: ref,
ID: ri.Id,
Executant: user.Id,
}
Eventually(func() bool {
return called
}).Should(BeTrue())
})
It("indexes items when they are being restored", func() {
called := false
indexClient.On("Restore", mock.MatchedBy(func(id *sprovider.ResourceId) bool {
return id.OpaqueId == ri.Id.OpaqueId
})).Return(nil).Run(func(args mock.Arguments) {
called = true
})
eventsChan <- events.ItemRestored{
Ref: ref,
Executant: user.Id,
}
Eventually(func() bool {
return called
}).Should(BeTrue())
})
It("indexes items when a version has been restored", func() {
called := false
indexClient.On("Add", mock.Anything, mock.MatchedBy(func(riToIndex *sprovider.ResourceInfo) bool {
return riToIndex.Id.OpaqueId == ri.Id.OpaqueId
})).Return(nil).Run(func(args mock.Arguments) {
called = true
})
eventsChan <- events.FileVersionRestored{
Ref: ref,
Executant: user.Id,
}
Eventually(func() bool {
return called
}).Should(BeTrue())
})
It("indexes items when they are being moved", func() {
called := false
indexClient.On("Move", mock.Anything, mock.MatchedBy(func(riToIndex *sprovider.ResourceInfo) bool {
return riToIndex.Id.OpaqueId == ri.Id.OpaqueId
})).Return(nil).Run(func(args mock.Arguments) {
called = true
})
eventsChan <- events.ItemMoved{
Ref: ref,
Executant: user.Id,
}
Eventually(func() bool {
return called
}).Should(BeTrue())
})
})
Describe("IndexSpace", func() {
It("walks the space and indexes all files", func() {
gwClient.On("GetUserByClaim", mock.Anything, mock.Anything).Return(&userv1beta1.GetUserByClaimResponse{
Status: status.NewOK(context.Background()),
User: user,
}, nil)
indexClient.On("Add", mock.Anything, mock.MatchedBy(func(riToIndex *sprovider.ResourceInfo) bool {
return riToIndex.Id.OpaqueId == ri.Id.OpaqueId
})).Return(nil)
res, err := p.IndexSpace(ctx, &searchsvc.IndexSpaceRequest{
SpaceId: "storageid",
UserId: "user",
})
Expect(err).ToNot(HaveOccurred())
Expect(res).ToNot(BeNil())
})
})
Describe("Search", func() {
It("fails when an empty query is given", func() {
res, err := p.Search(ctx, &searchsvc.SearchRequest{
Query: "",
})
Expect(err).To(HaveOccurred())
Expect(res).To(BeNil())
})
Context("with a personal space", func() {
BeforeEach(func() {
gwClient.On("ListStorageSpaces", mock.Anything, mock.MatchedBy(func(req *sprovider.ListStorageSpacesRequest) bool {
p := string(req.Opaque.Map["path"].Value)
return p == "/"
})).Return(&sprovider.ListStorageSpacesResponse{
Status: status.NewOK(ctx),
StorageSpaces: []*sprovider.StorageSpace{personalSpace},
}, nil)
indexClient.On("Search", mock.Anything, mock.Anything).Return(&searchsvc.SearchIndexResponse{
Matches: []*searchmsg.Match{
{
Entity: &searchmsg.Entity{
Ref: &searchmsg.Reference{
ResourceId: &searchmsg.ResourceID{
StorageId: personalSpace.Root.StorageId,
OpaqueId: personalSpace.Root.OpaqueId,
},
Path: "./path/to/Foo.pdf",
},
Id: &searchmsg.ResourceID{
StorageId: personalSpace.Root.StorageId,
OpaqueId: "foo-id",
},
Name: "Foo.pdf",
},
},
},
}, nil)
})
It("searches the personal user space", func() {
res, err := p.Search(ctx, &searchsvc.SearchRequest{
Query: "foo",
})
Expect(err).ToNot(HaveOccurred())
Expect(res).ToNot(BeNil())
Expect(len(res.Matches)).To(Equal(1))
match := res.Matches[0]
Expect(match.Entity.Id.OpaqueId).To(Equal("foo-id"))
Expect(match.Entity.Name).To(Equal("Foo.pdf"))
Expect(match.Entity.Ref.ResourceId.OpaqueId).To(Equal(personalSpace.Root.OpaqueId))
Expect(match.Entity.Ref.Path).To(Equal("./path/to/Foo.pdf"))
indexClient.AssertCalled(GinkgoT(), "Search", mock.Anything, mock.MatchedBy(func(req *searchsvc.SearchIndexRequest) bool {
return req.Query == "foo" && req.Ref.ResourceId.OpaqueId == personalSpace.Root.OpaqueId && req.Ref.Path == ""
}))
})
})
Context("with received shares", func() {
var (
grantSpace *sprovider.StorageSpace
)
BeforeEach(func() {
grantSpace = &sprovider.StorageSpace{
SpaceType: "grant",
Owner: otherUser,
Id: &sprovider.StorageSpaceId{OpaqueId: "otherspaceroot!otherspacegrant"},
Root: &sprovider.ResourceId{StorageId: "otherspaceroot", OpaqueId: "otherspacegrant"},
Name: "grantspace",
}
gwClient.On("GetPath", mock.Anything, mock.Anything).Return(&sprovider.GetPathResponse{
Status: status.NewOK(ctx),
Path: "/grant/path",
}, nil)
})
It("searches the received spaces (grants)", func() {
gwClient.On("ListStorageSpaces", mock.Anything, mock.MatchedBy(func(req *sprovider.ListStorageSpacesRequest) bool {
p := string(req.Opaque.Map["path"].Value)
return p == "/"
})).Return(&sprovider.ListStorageSpacesResponse{
Status: status.NewOK(ctx),
StorageSpaces: []*sprovider.StorageSpace{grantSpace},
}, nil)
indexClient.On("Search", mock.Anything, mock.Anything).Return(&searchsvc.SearchIndexResponse{
Matches: []*searchmsg.Match{
{
Entity: &searchmsg.Entity{
Ref: &searchmsg.Reference{
ResourceId: &searchmsg.ResourceID{
StorageId: grantSpace.Root.StorageId,
OpaqueId: grantSpace.Root.OpaqueId,
},
Path: "./grant/path/to/Shared.pdf",
},
Id: &searchmsg.ResourceID{
StorageId: grantSpace.Root.StorageId,
OpaqueId: "grant-shared-id",
},
Name: "Shared.pdf",
},
},
},
}, nil)
res, err := p.Search(ctx, &searchsvc.SearchRequest{
Query: "foo",
})
Expect(err).ToNot(HaveOccurred())
Expect(res).ToNot(BeNil())
Expect(len(res.Matches)).To(Equal(1))
match := res.Matches[0]
Expect(match.Entity.Id.OpaqueId).To(Equal("grant-shared-id"))
Expect(match.Entity.Name).To(Equal("Shared.pdf"))
Expect(match.Entity.Ref.ResourceId.OpaqueId).To(Equal(grantSpace.Root.OpaqueId))
Expect(match.Entity.Ref.Path).To(Equal("./to/Shared.pdf"))
indexClient.AssertCalled(GinkgoT(), "Search", mock.Anything, mock.MatchedBy(func(req *searchsvc.SearchIndexRequest) bool {
return req.Query == "foo" && req.Ref.ResourceId.OpaqueId == grantSpace.Root.OpaqueId && req.Ref.Path == "./grant/path"
}))
})
It("finds matches in both the personal space AND the grant", func() {
gwClient.On("ListStorageSpaces", mock.Anything, mock.MatchedBy(func(req *sprovider.ListStorageSpacesRequest) bool {
p := string(req.Opaque.Map["path"].Value)
return p == "/"
})).Return(&sprovider.ListStorageSpacesResponse{
Status: status.NewOK(ctx),
StorageSpaces: []*sprovider.StorageSpace{personalSpace, grantSpace},
}, nil)
indexClient.On("Search", mock.Anything, mock.MatchedBy(func(req *searchsvc.SearchIndexRequest) bool {
return req.Ref.ResourceId.OpaqueId == grantSpace.Root.OpaqueId
})).Return(&searchsvc.SearchIndexResponse{
Matches: []*searchmsg.Match{
{
Entity: &searchmsg.Entity{
Ref: &searchmsg.Reference{
ResourceId: &searchmsg.ResourceID{
StorageId: grantSpace.Root.StorageId,
OpaqueId: grantSpace.Root.OpaqueId,
},
Path: "./grant/path/to/Shared.pdf",
},
Id: &searchmsg.ResourceID{
StorageId: grantSpace.Root.StorageId,
OpaqueId: "grant-shared-id",
},
Name: "Shared.pdf",
},
},
},
}, nil)
indexClient.On("Search", mock.Anything, mock.MatchedBy(func(req *searchsvc.SearchIndexRequest) bool {
return req.Ref.ResourceId.OpaqueId == personalSpace.Root.OpaqueId
})).Return(&searchsvc.SearchIndexResponse{
Matches: []*searchmsg.Match{
{
Entity: &searchmsg.Entity{
Ref: &searchmsg.Reference{
ResourceId: &searchmsg.ResourceID{
StorageId: personalSpace.Root.StorageId,
OpaqueId: personalSpace.Root.OpaqueId,
},
Path: "./path/to/Foo.pdf",
},
Id: &searchmsg.ResourceID{
StorageId: personalSpace.Root.StorageId,
OpaqueId: "foo-id",
},
Name: "Foo.pdf",
},
},
},
}, nil)
res, err := p.Search(ctx, &searchsvc.SearchRequest{
Query: "foo",
})
Expect(err).ToNot(HaveOccurred())
Expect(res).ToNot(BeNil())
Expect(len(res.Matches)).To(Equal(2))
ids := []string{res.Matches[0].Entity.Id.OpaqueId, res.Matches[1].Entity.Id.OpaqueId}
Expect(ids).To(ConsistOf("foo-id", "grant-shared-id"))
})
})
})
})

View File

@@ -0,0 +1,46 @@
// Copyright 2018-2022 CERN
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// In applying this license, CERN does not waive the privileges and immunities
// granted to it by virtue of its status as an Intergovernmental Organization
// or submit itself to any jurisdiction.
package search
import (
"context"
providerv1beta1 "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
searchsvc "github.com/owncloud/ocis/protogen/gen/ocis/services/search/v0"
)
//go:generate mockery --name=ProviderClient
//go:generate mockery --name=IndexClient
// ProviderClient is the interface to the search provider service
type ProviderClient interface {
Search(ctx context.Context, req *searchsvc.SearchRequest) (*searchsvc.SearchResponse, error)
IndexSpace(ctx context.Context, req *searchsvc.IndexSpaceRequest) (*searchsvc.IndexSpaceResponse, error)
}
// IndexClient is the interface to the search index
type IndexClient interface {
Search(ctx context.Context, req *searchsvc.SearchIndexRequest) (*searchsvc.SearchIndexResponse, error)
Add(ref *providerv1beta1.Reference, ri *providerv1beta1.ResourceInfo) error
Move(ri *providerv1beta1.ResourceInfo) error
Delete(ri *providerv1beta1.ResourceId) error
Restore(ri *providerv1beta1.ResourceId) error
Purge(ri *providerv1beta1.ResourceId) error
DocCount() (uint64, error)
}

View File

@@ -0,0 +1,13 @@
package search_test
import (
"testing"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)
func TestSearch(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Search Suite")
}

View File

@@ -0,0 +1,50 @@
package debug
import (
"context"
"github.com/owncloud/ocis/extensions/search/pkg/config"
"github.com/owncloud/ocis/ocis-pkg/log"
)
// Option defines a single option function.
type Option func(o *Options)
// Options defines the available options for this package.
type Options struct {
Logger log.Logger
Context context.Context
Config *config.Config
}
// newOptions initializes the available default options.
func newOptions(opts ...Option) Options {
opt := Options{}
for _, o := range opts {
o(&opt)
}
return opt
}
// Logger provides a function to set the logger option.
func Logger(val log.Logger) Option {
return func(o *Options) {
o.Logger = val
}
}
// Context provides a function to set the context option.
func Context(val context.Context) Option {
return func(o *Options) {
o.Context = val
}
}
// Config provides a function to set the config option.
func Config(val *config.Config) Option {
return func(o *Options) {
o.Config = val
}
}

View File

@@ -0,0 +1,59 @@
package debug
import (
"io"
"net/http"
"github.com/owncloud/ocis/extensions/search/pkg/config"
"github.com/owncloud/ocis/ocis-pkg/service/debug"
"github.com/owncloud/ocis/ocis-pkg/version"
)
// Server initializes the debug service and server.
func Server(opts ...Option) (*http.Server, error) {
options := newOptions(opts...)
return debug.NewService(
debug.Logger(options.Logger),
debug.Name(options.Config.Service.Name),
debug.Version(version.String),
debug.Address(options.Config.Debug.Addr),
debug.Token(options.Config.Debug.Token),
debug.Pprof(options.Config.Debug.Pprof),
debug.Zpages(options.Config.Debug.Zpages),
debug.Health(health(options.Config)),
debug.Ready(ready(options.Config)),
), nil
}
// health implements the health check.
func health(cfg *config.Config) func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/plain")
w.WriteHeader(http.StatusOK)
// TODO: check if services are up and running
_, err := io.WriteString(w, http.StatusText(http.StatusOK))
// io.WriteString should not fail but if it does we want to know.
if err != nil {
panic(err)
}
}
}
// ready implements the ready check.
func ready(cfg *config.Config) func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/plain")
w.WriteHeader(http.StatusOK)
// TODO: check if services are up and running
_, err := io.WriteString(w, http.StatusText(http.StatusOK))
// io.WriteString should not fail but if it does we want to know.
if err != nil {
panic(err)
}
}
}

View File

@@ -0,0 +1,85 @@
package grpc
import (
"context"
"github.com/owncloud/ocis/extensions/search/pkg/config"
"github.com/owncloud/ocis/extensions/search/pkg/metrics"
svc "github.com/owncloud/ocis/extensions/search/pkg/service/v0"
"github.com/owncloud/ocis/ocis-pkg/log"
"github.com/urfave/cli/v2"
)
// Option defines a single option function.
type Option func(o *Options)
// Options defines the available options for this package.
type Options struct {
Name string
Logger log.Logger
Context context.Context
Config *config.Config
Metrics *metrics.Metrics
Flags []cli.Flag
Handler *svc.Service
}
// newOptions initializes the available default options.
func newOptions(opts ...Option) Options {
opt := Options{}
for _, o := range opts {
o(&opt)
}
return opt
}
// Name provides a name for the service.
func Name(val string) Option {
return func(o *Options) {
o.Name = val
}
}
// Logger provides a function to set the logger option.
func Logger(val log.Logger) Option {
return func(o *Options) {
o.Logger = val
}
}
// Context provides a function to set the context option.
func Context(val context.Context) Option {
return func(o *Options) {
o.Context = val
}
}
// Config provides a function to set the config option.
func Config(val *config.Config) Option {
return func(o *Options) {
o.Config = val
}
}
// Metrics provides a function to set the metrics option.
func Metrics(val *metrics.Metrics) Option {
return func(o *Options) {
o.Metrics = val
}
}
// Flags provides a function to set the flags option.
func Flags(val []cli.Flag) Option {
return func(o *Options) {
o.Flags = append(o.Flags, val...)
}
}
// Handler provides a function to set the handler option.
func Handler(val *svc.Service) Option {
return func(o *Options) {
o.Handler = val
}
}

View File

@@ -0,0 +1,39 @@
package grpc
import (
svc "github.com/owncloud/ocis/extensions/search/pkg/service/v0"
"github.com/owncloud/ocis/ocis-pkg/service/grpc"
"github.com/owncloud/ocis/ocis-pkg/version"
searchsvc "github.com/owncloud/ocis/protogen/gen/ocis/services/search/v0"
)
// Server initializes a new go-micro service ready to run
func Server(opts ...Option) grpc.Service {
options := newOptions(opts...)
service := grpc.NewService(
grpc.Name(options.Config.Service.Name),
grpc.Context(options.Context),
grpc.Address(options.Config.GRPC.Addr),
grpc.Namespace(options.Config.GRPC.Namespace),
grpc.Logger(options.Logger),
grpc.Flags(options.Flags...),
grpc.Version(version.String),
)
handle, err := svc.NewHandler(
svc.Config(options.Config),
svc.Logger(options.Logger),
)
if err != nil {
options.Logger.Error().
Err(err).
Msg("Error initializing search service")
return grpc.Service{}
}
_ = searchsvc.RegisterSearchProviderHandler(
service.Server(),
handle,
)
return service
}

View File

@@ -0,0 +1,39 @@
package service
import (
"github.com/owncloud/ocis/extensions/search/pkg/config"
"github.com/owncloud/ocis/ocis-pkg/log"
)
// Option defines a single option function.
type Option func(o *Options)
// Options defines the available options for this package.
type Options struct {
Logger log.Logger
Config *config.Config
}
func newOptions(opts ...Option) Options {
opt := Options{}
for _, o := range opts {
o(&opt)
}
return opt
}
// Logger provides a function to set the Logger option.
func Logger(val log.Logger) Option {
return func(o *Options) {
o.Logger = val
}
}
// Config provides a function to set the Config option.
func Config(val *config.Config) Option {
return func(o *Options) {
o.Config = val
}
}

View File

@@ -0,0 +1,106 @@
package service
import (
"context"
"errors"
"path/filepath"
"github.com/blevesearch/bleve/v2"
revactx "github.com/cs3org/reva/v2/pkg/ctx"
"github.com/cs3org/reva/v2/pkg/events"
"github.com/cs3org/reva/v2/pkg/events/server"
"github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool"
"github.com/go-micro/plugins/v4/events/natsjs"
"go-micro.dev/v4/metadata"
grpcmetadata "google.golang.org/grpc/metadata"
"github.com/owncloud/ocis/extensions/audit/pkg/types"
"github.com/owncloud/ocis/extensions/search/pkg/config"
"github.com/owncloud/ocis/extensions/search/pkg/search"
"github.com/owncloud/ocis/extensions/search/pkg/search/index"
searchprovider "github.com/owncloud/ocis/extensions/search/pkg/search/provider"
"github.com/owncloud/ocis/ocis-pkg/log"
searchsvc "github.com/owncloud/ocis/protogen/gen/ocis/services/search/v0"
)
// NewHandler returns a service implementation for Service.
func NewHandler(opts ...Option) (searchsvc.SearchProviderHandler, error) {
options := newOptions(opts...)
logger := options.Logger
cfg := options.Config
// Connect to nats to listen for changes that need to trigger an index update
evtsCfg := cfg.Events
client, err := server.NewNatsStream(
natsjs.Address(evtsCfg.Endpoint),
natsjs.ClusterID(evtsCfg.Cluster),
)
if err != nil {
return nil, err
}
evts, err := events.Consume(client, evtsCfg.ConsumerGroup, types.RegisteredEvents()...)
if err != nil {
return nil, err
}
indexDir := filepath.Join(cfg.Datapath, "index.bleve")
bleveIndex, err := bleve.Open(indexDir)
if err != nil {
bleveIndex, err = bleve.New(indexDir, index.BuildMapping())
if err != nil {
return nil, err
}
}
index, err := index.New(bleveIndex)
if err != nil {
return nil, err
}
gwclient, err := pool.GetGatewayServiceClient(cfg.Reva.Address)
if err != nil {
logger.Fatal().Err(err).Str("addr", cfg.Reva.Address).Msg("could not get reva client")
}
provider := searchprovider.New(gwclient, index, cfg.MachineAuthAPIKey, evts, logger)
return &Service{
id: cfg.GRPC.Namespace + "." + cfg.Service.Name,
log: logger,
Config: cfg,
provider: provider,
}, nil
}
// Service implements the searchServiceHandler interface
type Service struct {
id string
log log.Logger
Config *config.Config
provider search.ProviderClient
}
func (s Service) Search(ctx context.Context, in *searchsvc.SearchRequest, out *searchsvc.SearchResponse) error {
// Get token from the context (go-micro) and make it known to the reva client too (grpc)
t, ok := metadata.Get(ctx, revactx.TokenHeader)
if !ok {
s.log.Error().Msg("Could not get token from context")
return errors.New("could not get token from context")
}
ctx = grpcmetadata.AppendToOutgoingContext(ctx, revactx.TokenHeader, t)
res, err := s.provider.Search(ctx, &searchsvc.SearchRequest{
Query: in.Query,
})
if err != nil {
return err
}
out.Matches = res.Matches
out.NextPageToken = res.NextPageToken
return nil
}
func (s Service) IndexSpace(ctx context.Context, in *searchsvc.IndexSpaceRequest, out *searchsvc.IndexSpaceResponse) error {
_, err := s.provider.IndexSpace(ctx, in)
return err
}

View File

@@ -0,0 +1,23 @@
package tracing
import (
"github.com/owncloud/ocis/extensions/search/pkg/config"
pkgtrace "github.com/owncloud/ocis/ocis-pkg/tracing"
"go.opentelemetry.io/otel/trace"
)
var (
// TraceProvider is the global trace provider for the proxy service.
TraceProvider = trace.NewNoopTracerProvider()
)
func Configure(cfg *config.Config) error {
var err error
if cfg.Tracing.Enabled {
if TraceProvider, err = pkgtrace.GetTraceProvider(cfg.Tracing.Endpoint, cfg.Tracing.Collector, cfg.Service.Name, cfg.Tracing.Type); err != nil {
return err
}
}
return nil
}

View File

@@ -49,6 +49,9 @@ func DefaultConfig() *config.Config {
Scope: "openid profile email",
},
Apps: []string{"files", "search", "preview", "text-editor", "pdf-viewer", "external", "user-management"},
Options: map[string]interface{}{
"hideSearchBar": false,
},
},
},
}

View File

@@ -66,12 +66,6 @@ func (p Web) getPayload() (payload []byte, err error) {
if p.config.Web.Path == "" {
// render dynamically using config
// provide default ocis-web options
if p.config.Web.Config.Options == nil {
p.config.Web.Config.Options = make(map[string]interface{})
p.config.Web.Config.Options["hideSearchBar"] = true
}
// build theme url
if themeServer, err := url.Parse(p.config.Web.ThemeServer); err == nil {
p.config.Web.Config.Theme = themeServer.String() + p.config.Web.ThemePath

View File

@@ -0,0 +1,170 @@
package errors
import (
"bytes"
"encoding/xml"
"net/http"
rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1"
"github.com/cs3org/reva/v2/pkg/rgrpc/status"
"github.com/pkg/errors"
"github.com/rs/zerolog"
)
var sabreException = map[int]string{
// the commented states have no corresponding exception in sabre/dav,
// see https://github.com/sabre-io/dav/tree/master/lib/DAV/Exception
// http.StatusMultipleChoices: "Multiple Choices",
// http.StatusMovedPermanently: "Moved Permanently",
// http.StatusFound: "Found",
// http.StatusSeeOther: "See Other",
// http.StatusNotModified: "Not Modified",
// http.StatusUseProxy: "Use Proxy",
// http.StatusTemporaryRedirect: "Temporary Redirect",
// http.StatusPermanentRedirect: "Permanent Redirect",
http.StatusBadRequest: "Sabre\\DAV\\Exception\\BadRequest",
http.StatusUnauthorized: "Sabre\\DAV\\Exception\\NotAuthenticated",
http.StatusPaymentRequired: "Sabre\\DAV\\Exception\\PaymentRequired",
http.StatusForbidden: "Sabre\\DAV\\Exception\\Forbidden", // InvalidResourceType, InvalidSyncToken, TooManyMatches
http.StatusNotFound: "Sabre\\DAV\\Exception\\NotFound",
http.StatusMethodNotAllowed: "Sabre\\DAV\\Exception\\MethodNotAllowed",
// http.StatusNotAcceptable: "Not Acceptable",
// http.StatusProxyAuthRequired: "Proxy Authentication Required",
// http.StatusRequestTimeout: "Request Timeout",
http.StatusConflict: "Sabre\\DAV\\Exception\\Conflict", // LockTokenMatchesRequestUri
// http.StatusGone: "Gone",
http.StatusLengthRequired: "Sabre\\DAV\\Exception\\LengthRequired",
http.StatusPreconditionFailed: "Sabre\\DAV\\Exception\\PreconditionFailed",
// http.StatusRequestEntityTooLarge: "Request Entity Too Large",
// http.StatusRequestURITooLong: "Request URI Too Long",
http.StatusUnsupportedMediaType: "Sabre\\DAV\\Exception\\UnsupportedMediaType", // ReportNotSupported
http.StatusRequestedRangeNotSatisfiable: "Sabre\\DAV\\Exception\\RequestedRangeNotSatisfiable",
// http.StatusExpectationFailed: "Expectation Failed",
// http.StatusTeapot: "I'm a teapot",
// http.StatusMisdirectedRequest: "Misdirected Request",
// http.StatusUnprocessableEntity: "Unprocessable Entity",
http.StatusLocked: "Sabre\\DAV\\Exception\\Locked", // ConflictingLock
// http.StatusFailedDependency: "Failed Dependency",
// http.StatusTooEarly: "Too Early",
// http.StatusUpgradeRequired: "Upgrade Required",
// http.StatusPreconditionRequired: "Precondition Required",
// http.StatusTooManyRequests: "Too Many Requests",
// http.StatusRequestHeaderFieldsTooLarge: "Request Header Fields Too Large",
// http.StatusUnavailableForLegalReasons: "Unavailable For Legal Reasons",
// http.StatusInternalServerError: "Internal Server Error",
http.StatusNotImplemented: "Sabre\\DAV\\Exception\\NotImplemented",
// http.StatusBadGateway: "Bad Gateway",
http.StatusServiceUnavailable: "Sabre\\DAV\\Exception\\ServiceUnavailable",
// http.StatusGatewayTimeout: "Gateway Timeout",
// http.StatusHTTPVersionNotSupported: "HTTP Version Not Supported",
// http.StatusVariantAlsoNegotiates: "Variant Also Negotiates",
http.StatusInsufficientStorage: "Sabre\\DAV\\Exception\\InsufficientStorage",
// http.StatusLoopDetected: "Loop Detected",
// http.StatusNotExtended: "Not Extended",
// http.StatusNetworkAuthenticationRequired: "Network Authentication Required",
}
// SabreException returns a sabre exception text for the HTTP status code. It returns the empty
// string if the code is unknown.
func SabreException(code int) string {
return sabreException[code]
}
// Exception represents a ocdav exception
type Exception struct {
Code int
Message string
Header string
}
// Marshal just calls the xml marshaller for a given exception.
func Marshal(code int, message string, header string) ([]byte, error) {
xmlstring, err := xml.Marshal(&ErrorXML{
Xmlnsd: "DAV",
Xmlnss: "http://sabredav.org/ns",
Exception: sabreException[code],
Message: message,
Header: header,
})
if err != nil {
return nil, err
}
var buf bytes.Buffer
buf.WriteString(xml.Header)
buf.Write(xmlstring)
return buf.Bytes(), err
}
// ErrorXML holds the xml representation of an error
// http://www.webdav.org/specs/rfc4918.html#ELEMENT_error
type ErrorXML struct {
XMLName xml.Name `xml:"d:error"`
Xmlnsd string `xml:"xmlns:d,attr"`
Xmlnss string `xml:"xmlns:s,attr"`
Exception string `xml:"s:exception"`
Message string `xml:"s:message"`
InnerXML []byte `xml:",innerxml"`
// Header is used to indicate the conflicting request header
Header string `xml:"s:header,omitempty"`
}
var (
// ErrInvalidDepth is an invalid depth header error
ErrInvalidDepth = errors.New("webdav: invalid depth")
// ErrInvalidPropfind is an invalid propfind error
ErrInvalidPropfind = errors.New("webdav: invalid propfind")
// ErrInvalidProppatch is an invalid proppatch error
ErrInvalidProppatch = errors.New("webdav: invalid proppatch")
// ErrInvalidLockInfo is an invalid lock error
ErrInvalidLockInfo = errors.New("webdav: invalid lock info")
// ErrUnsupportedLockInfo is an unsupported lock error
ErrUnsupportedLockInfo = errors.New("webdav: unsupported lock info")
// ErrInvalidTimeout is an invalid timeout error
ErrInvalidTimeout = errors.New("webdav: invalid timeout")
// ErrInvalidIfHeader is an invalid if header error
ErrInvalidIfHeader = errors.New("webdav: invalid If header")
// ErrUnsupportedMethod is an unsupported method error
ErrUnsupportedMethod = errors.New("webdav: unsupported method")
// ErrInvalidLockToken is an invalid lock token error
ErrInvalidLockToken = errors.New("webdav: invalid lock token")
// ErrConfirmationFailed is returned by a LockSystem's Confirm method.
ErrConfirmationFailed = errors.New("webdav: confirmation failed")
// ErrForbidden is returned by a LockSystem's Unlock method.
ErrForbidden = errors.New("webdav: forbidden")
// ErrLocked is returned by a LockSystem's Create, Refresh and Unlock methods.
ErrLocked = errors.New("webdav: locked")
// ErrNoSuchLock is returned by a LockSystem's Refresh and Unlock methods.
ErrNoSuchLock = errors.New("webdav: no such lock")
// ErrNotImplemented is returned when hitting not implemented code paths
ErrNotImplemented = errors.New("webdav: not implemented")
)
// HandleErrorStatus checks the status code, logs a Debug or Error level message
// and writes an appropriate http status
func HandleErrorStatus(log *zerolog.Logger, w http.ResponseWriter, s *rpc.Status) {
hsc := status.HTTPStatusFromCode(s.Code)
if hsc == http.StatusInternalServerError {
log.Error().Interface("status", s).Int("code", hsc).Msg(http.StatusText(hsc))
} else {
log.Debug().Interface("status", s).Int("code", hsc).Msg(http.StatusText(hsc))
}
w.WriteHeader(hsc)
}
// HandleWebdavError checks the status code, logs an error and creates a webdav response body
// if needed
func HandleWebdavError(log *zerolog.Logger, w http.ResponseWriter, b []byte, err error) {
if err != nil {
log.Error().Msgf("error marshaling xml response: %s", b)
w.WriteHeader(http.StatusInternalServerError)
return
}
_, err = w.Write(b)
if err != nil {
log.Err(err).Msg("error writing response")
}
}

View File

@@ -0,0 +1,48 @@
package net
// Common HTTP headers.
const (
HeaderAcceptRanges = "Accept-Ranges"
HeaderAccessControlAllowHeaders = "Access-Control-Allow-Headers"
HeaderAccessControlExposeHeaders = "Access-Control-Expose-Headers"
HeaderContentDisposistion = "Content-Disposition"
HeaderContentLength = "Content-Length"
HeaderContentRange = "Content-Range"
HeaderContentType = "Content-Type"
HeaderETag = "ETag"
HeaderLastModified = "Last-Modified"
HeaderLocation = "Location"
HeaderRange = "Range"
HeaderIfMatch = "If-Match"
)
// webdav headers
const (
HeaderDav = "DAV" // https://datatracker.ietf.org/doc/html/rfc4918#section-10.1
HeaderDepth = "Depth" // https://datatracker.ietf.org/doc/html/rfc4918#section-10.2
HeaderDestination = "Destination" // https://datatracker.ietf.org/doc/html/rfc4918#section-10.3
HeaderIf = "If" // https://datatracker.ietf.org/doc/html/rfc4918#section-10.4
HeaderLockToken = "Lock-Token" // https://datatracker.ietf.org/doc/html/rfc4918#section-10.5
HeaderOverwrite = "Overwrite" // https://datatracker.ietf.org/doc/html/rfc4918#section-10.6
HeaderTimeout = "Timeout" // https://datatracker.ietf.org/doc/html/rfc4918#section-10.7
)
// Non standard HTTP headers.
const (
HeaderOCFileID = "OC-FileId"
HeaderOCETag = "OC-ETag"
HeaderOCChecksum = "OC-Checksum"
HeaderOCPermissions = "OC-Perm"
HeaderTusResumable = "Tus-Resumable"
HeaderTusVersion = "Tus-Version"
HeaderTusExtension = "Tus-Extension"
HeaderTusChecksumAlgorithm = "Tus-Checksum-Algorithm"
HeaderTusUploadExpires = "Upload-Expires"
HeaderUploadChecksum = "Upload-Checksum"
HeaderUploadLength = "Upload-Length"
HeaderUploadMetadata = "Upload-Metadata"
HeaderUploadOffset = "Upload-Offset"
HeaderOCMtime = "X-OC-Mtime"
HeaderExpectedEntityLength = "X-Expected-Entity-Length"
HeaderLitmus = "X-Litmus"
)

View File

@@ -0,0 +1,12 @@
package net
import (
"net/url"
)
// EncodePath encodes the path of a url.
//
// slashes (/) are treated as path-separators.
func EncodePath(path string) string {
return (&url.URL{Path: path}).EscapedPath()
}

View File

@@ -0,0 +1,125 @@
package prop
import (
"bytes"
"encoding/xml"
)
// PropertyXML represents a single DAV resource property as defined in RFC 4918.
// http://www.webdav.org/specs/rfc4918.html#data.model.for.resource.properties
type PropertyXML struct {
// XMLName is the fully qualified name that identifies this property.
XMLName xml.Name
// Lang is an optional xml:lang attribute.
Lang string `xml:"xml:lang,attr,omitempty"`
// InnerXML contains the XML representation of the property value.
// See http://www.webdav.org/specs/rfc4918.html#property_values
//
// Property values of complex type or mixed-content must have fully
// expanded XML namespaces or be self-contained with according
// XML namespace declarations. They must not rely on any XML
// namespace declarations within the scope of the XML document,
// even including the DAV: namespace.
InnerXML []byte `xml:",innerxml"`
}
func xmlEscaped(val string) []byte {
buf := new(bytes.Buffer)
xml.Escape(buf, []byte(val))
return buf.Bytes()
}
// EscapedNS returns a new PropertyXML instance while xml-escaping the value
func EscapedNS(namespace string, local string, val string) PropertyXML {
return PropertyXML{
XMLName: xml.Name{Space: namespace, Local: local},
Lang: "",
InnerXML: xmlEscaped(val),
}
}
// Escaped returns a new PropertyXML instance while xml-escaping the value
// TODO properly use the space
func Escaped(key, val string) PropertyXML {
return PropertyXML{
XMLName: xml.Name{Space: "", Local: key},
Lang: "",
InnerXML: xmlEscaped(val),
}
}
// NotFound returns a new PropertyXML instance with an empty value
func NotFound(key string) PropertyXML {
return PropertyXML{
XMLName: xml.Name{Space: "", Local: key},
Lang: "",
}
}
// NotFoundNS returns a new PropertyXML instance with the given namespace and an empty value
func NotFoundNS(namespace, key string) PropertyXML {
return PropertyXML{
XMLName: xml.Name{Space: namespace, Local: key},
Lang: "",
}
}
// Raw returns a new PropertyXML instance for the given key/value pair
// TODO properly use the space
func Raw(key, val string) PropertyXML {
return PropertyXML{
XMLName: xml.Name{Space: "", Local: key},
Lang: "",
InnerXML: []byte(val),
}
}
// Next returns the next token, if any, in the XML stream of d.
// RFC 4918 requires to ignore comments, processing instructions
// and directives.
// http://www.webdav.org/specs/rfc4918.html#property_values
// http://www.webdav.org/specs/rfc4918.html#xml-extensibility
func Next(d *xml.Decoder) (xml.Token, error) {
for {
t, err := d.Token()
if err != nil {
return t, err
}
switch t.(type) {
case xml.Comment, xml.Directive, xml.ProcInst:
continue
default:
return t, nil
}
}
}
// ActiveLock holds active lock xml data
// http://www.webdav.org/specs/rfc4918.html#ELEMENT_activelock
// <!ELEMENT activelock (lockscope, locktype, depth, owner?, timeout?,
// locktoken?, lockroot)>
type ActiveLock struct {
XMLName xml.Name `xml:"activelock"`
Exclusive *struct{} `xml:"lockscope>exclusive,omitempty"`
Shared *struct{} `xml:"lockscope>shared,omitempty"`
Write *struct{} `xml:"locktype>write,omitempty"`
Depth string `xml:"depth"`
Owner Owner `xml:"owner,omitempty"`
Timeout string `xml:"timeout,omitempty"`
Locktoken string `xml:"locktoken>href"`
Lockroot string `xml:"lockroot>href,omitempty"`
}
// Owner captures the inner UML of a lock owner element http://www.webdav.org/specs/rfc4918.html#ELEMENT_owner
type Owner struct {
InnerXML string `xml:",innerxml"`
}
// Escape repaces ", &, ', < and > with their xml representation
func Escape(s string) string {
b := bytes.NewBuffer(nil)
_ = xml.EscapeText(b, []byte(s))
return b.String()
}

View File

@@ -0,0 +1,180 @@
package propfind
import (
"encoding/xml"
"fmt"
"io"
"net/http"
"github.com/owncloud/ocis/extensions/webdav/pkg/errors"
"github.com/owncloud/ocis/extensions/webdav/pkg/prop"
)
const (
_spaceTypeProject = "project"
)
type countingReader struct {
n int
r io.Reader
}
func (c *countingReader) Read(p []byte) (int, error) {
n, err := c.r.Read(p)
c.n += n
return n, err
}
// Props represents properties related to a resource
// http://www.webdav.org/specs/rfc4918.html#ELEMENT_prop (for propfind)
type Props []xml.Name
// XML holds the xml representation of a propfind
// http://www.webdav.org/specs/rfc4918.html#ELEMENT_propfind
type XML struct {
XMLName xml.Name `xml:"DAV: propfind"`
Allprop *struct{} `xml:"DAV: allprop"`
Propname *struct{} `xml:"DAV: propname"`
Prop Props `xml:"DAV: prop"`
Include Props `xml:"DAV: include"`
}
// PropstatXML holds the xml representation of a propfind response
// http://www.webdav.org/specs/rfc4918.html#ELEMENT_propstat
type PropstatXML struct {
// Prop requires DAV: to be the default namespace in the enclosing
// XML. This is due to the standard encoding/xml package currently
// not honoring namespace declarations inside a xmltag with a
// parent element for anonymous slice elements.
// Use of multistatusWriter takes care of this.
Prop []prop.PropertyXML `xml:"d:prop>_ignored_"`
Status string `xml:"d:status"`
Error *errors.ErrorXML `xml:"d:error"`
ResponseDescription string `xml:"d:responsedescription,omitempty"`
}
// ResponseXML holds the xml representation of a propfind response
type ResponseXML struct {
XMLName xml.Name `xml:"d:response"`
Href string `xml:"d:href"`
Propstat []PropstatXML `xml:"d:propstat"`
Status string `xml:"d:status,omitempty"`
Error *errors.ErrorXML `xml:"d:error"`
ResponseDescription string `xml:"d:responsedescription,omitempty"`
}
// MultiStatusResponseXML holds the xml representation of a multistatus propfind response
type MultiStatusResponseXML struct {
XMLName xml.Name `xml:"d:multistatus"`
XmlnsS string `xml:"xmlns:s,attr,omitempty"`
XmlnsD string `xml:"xmlns:d,attr,omitempty"`
XmlnsOC string `xml:"xmlns:oc,attr,omitempty"`
Responses []*ResponseXML `xml:"d:response"`
}
// ResponseUnmarshalXML is a workaround for https://github.com/golang/go/issues/13400
type ResponseUnmarshalXML struct {
XMLName xml.Name `xml:"response"`
Href string `xml:"href"`
Propstat []PropstatUnmarshalXML `xml:"propstat"`
Status string `xml:"status,omitempty"`
Error *errors.ErrorXML `xml:"d:error"`
ResponseDescription string `xml:"responsedescription,omitempty"`
}
// MultiStatusResponseUnmarshalXML is a workaround for https://github.com/golang/go/issues/13400
type MultiStatusResponseUnmarshalXML struct {
XMLName xml.Name `xml:"multistatus"`
XmlnsS string `xml:"xmlns:s,attr,omitempty"`
XmlnsD string `xml:"xmlns:d,attr,omitempty"`
XmlnsOC string `xml:"xmlns:oc,attr,omitempty"`
Responses []*ResponseUnmarshalXML `xml:"response"`
}
// PropstatUnmarshalXML is a workaround for https://github.com/golang/go/issues/13400
type PropstatUnmarshalXML struct {
// Prop requires DAV: to be the default namespace in the enclosing
// XML. This is due to the standard encoding/xml package currently
// not honoring namespace declarations inside a xmltag with a
// parent element for anonymous slice elements.
// Use of multistatusWriter takes care of this.
Prop []*prop.PropertyXML `xml:"prop"`
Status string `xml:"status"`
Error *errors.ErrorXML `xml:"d:error"`
ResponseDescription string `xml:"responsedescription,omitempty"`
}
// NewMultiStatusResponseXML returns a preconfigured instance of MultiStatusResponseXML
func NewMultiStatusResponseXML() *MultiStatusResponseXML {
return &MultiStatusResponseXML{
XmlnsD: "DAV:",
XmlnsS: "http://sabredav.org/ns",
XmlnsOC: "http://owncloud.org/ns",
}
}
// ReadPropfind extracts and parses the propfind XML information from a Reader
// from https://github.com/golang/net/blob/e514e69ffb8bc3c76a71ae40de0118d794855992/webdav/xml.go#L178-L205
func ReadPropfind(r io.Reader) (pf XML, status int, err error) {
c := countingReader{r: r}
if err = xml.NewDecoder(&c).Decode(&pf); err != nil {
if err == io.EOF {
if c.n == 0 {
// An empty body means to propfind allprop.
// http://www.webdav.org/specs/rfc4918.html#METHOD_PROPFIND
return XML{Allprop: new(struct{})}, 0, nil
}
err = errors.ErrInvalidPropfind
}
return XML{}, http.StatusBadRequest, err
}
if pf.Allprop == nil && pf.Include != nil {
return XML{}, http.StatusBadRequest, errors.ErrInvalidPropfind
}
if pf.Allprop != nil && (pf.Prop != nil || pf.Propname != nil) {
return XML{}, http.StatusBadRequest, errors.ErrInvalidPropfind
}
if pf.Prop != nil && pf.Propname != nil {
return XML{}, http.StatusBadRequest, errors.ErrInvalidPropfind
}
if pf.Propname == nil && pf.Allprop == nil && pf.Prop == nil {
// jfd: I think <d:prop></d:prop> is perfectly valid ... treat it as allprop
return XML{Allprop: new(struct{})}, 0, nil
}
return pf, 0, nil
}
// UnmarshalXML appends the property names enclosed within start to pn.
//
// It returns an error if start does not contain any properties or if
// properties contain values. Character data between properties is ignored.
func (pn *Props) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
for {
t, err := prop.Next(d)
if err != nil {
return err
}
switch e := t.(type) {
case xml.EndElement:
// jfd: I think <d:prop></d:prop> is perfectly valid ... treat it as allprop
/*
if len(*pn) == 0 {
return fmt.Errorf("%s must not be empty", start.Name.Local)
}
*/
return nil
case xml.StartElement:
t, err = prop.Next(d)
if err != nil {
return err
}
if _, ok := t.(xml.EndElement); !ok {
return fmt.Errorf("unexpected token %T", t)
}
*pn = append(*pn, e.Name)
}
}
}

View File

@@ -0,0 +1,221 @@
package svc
import (
"context"
"encoding/xml"
"io"
"net/http"
"path"
"strconv"
"time"
revactx "github.com/cs3org/reva/v2/pkg/ctx"
"github.com/owncloud/ocis/extensions/webdav/pkg/net"
"github.com/owncloud/ocis/extensions/webdav/pkg/prop"
"github.com/owncloud/ocis/extensions/webdav/pkg/propfind"
searchmsg "github.com/owncloud/ocis/protogen/gen/ocis/messages/search/v0"
searchsvc "github.com/owncloud/ocis/protogen/gen/ocis/services/search/v0"
merrors "go-micro.dev/v4/errors"
"go-micro.dev/v4/metadata"
)
const (
elementNameSearchFiles = "search-files"
// TODO elementNameFilterFiles = "filter-files"
)
// Search is the endpoint for retrieving search results for REPORT requests
func (g Webdav) Search(w http.ResponseWriter, r *http.Request) {
rep, err := readReport(r.Body)
if err != nil {
renderError(w, r, errBadRequest(err.Error()))
g.log.Error().Err(err).Msg("error reading report")
return
}
if rep.SearchFiles == nil {
renderError(w, r, errBadRequest("missing search-files tag"))
g.log.Error().Err(err).Msg("error reading report")
return
}
t := r.Header.Get(TokenHeader)
ctx := revactx.ContextSetToken(r.Context(), t)
ctx = metadata.Set(ctx, revactx.TokenHeader, t)
rsp, err := g.searchClient.Search(ctx, &searchsvc.SearchRequest{
Query: "*" + rep.SearchFiles.Search.Pattern + "*",
})
if err != nil {
e := merrors.Parse(err.Error())
switch e.Code {
case http.StatusBadRequest:
renderError(w, r, errBadRequest(err.Error()))
default:
renderError(w, r, errInternalError(err.Error()))
}
g.log.Error().Err(err).Msg("could not get search results")
return
}
g.sendSearchResponse(rsp, w, r)
}
func (g Webdav) sendSearchResponse(rsp *searchsvc.SearchResponse, w http.ResponseWriter, r *http.Request) {
responsesXML, err := multistatusResponse(r.Context(), rsp.Matches)
if err != nil {
g.log.Error().Err(err).Msg("error formatting propfind")
w.WriteHeader(http.StatusInternalServerError)
return
}
w.Header().Set(net.HeaderDav, "1, 3, extended-mkcol")
w.Header().Set(net.HeaderContentType, "application/xml; charset=utf-8")
w.WriteHeader(http.StatusMultiStatus)
if _, err := w.Write(responsesXML); err != nil {
g.log.Err(err).Msg("error writing response")
}
}
// multistatusResponse converts a list of matches into a multistatus response string
func multistatusResponse(ctx context.Context, matches []*searchmsg.Match) ([]byte, error) {
responses := make([]*propfind.ResponseXML, 0, len(matches))
for i := range matches {
res, err := matchToPropResponse(ctx, matches[i])
if err != nil {
return nil, err
}
responses = append(responses, res)
}
msr := propfind.NewMultiStatusResponseXML()
msr.Responses = responses
msg, err := xml.Marshal(msr)
if err != nil {
return nil, err
}
return msg, nil
}
func matchToPropResponse(ctx context.Context, match *searchmsg.Match) (*propfind.ResponseXML, error) {
response := propfind.ResponseXML{
Href: net.EncodePath(path.Join("/dav/spaces/", match.Entity.Ref.ResourceId.StorageId+"!"+match.Entity.Ref.ResourceId.OpaqueId, match.Entity.Ref.Path)),
Propstat: []propfind.PropstatXML{},
}
propstatOK := propfind.PropstatXML{
Status: "HTTP/1.1 200 OK",
Prop: []prop.PropertyXML{},
}
// <oc:permissions>RDNVW</oc:permissions>
// <oc:favorite>0</oc:favorite>
// <oc:owner-id>demo</oc:owner-id>
// <oc:owner-display-name>demo</oc:owner-display-name>
// <oc:share-types/>
// <oc:privatelink>https://demo.owncloud.com/f/7</oc:privatelink>
// <d:getcontenttype>application/pdf</d:getcontenttype>
// <d:resourcetype/>
// <oc:downloadURL/>
// done:
// <oc:fileid>7</oc:fileid>
// <oc:size>6668668</oc:size>
// <d:getcontentlength>6668668</d:getcontentlength>
// <d:getetag>"0cdcdd1bb13a8fed3e54d3b2325dc97c"</d:getetag>
// <d:getlastmodified>Mon, 25 Apr 2022 06:48:26 GMT</d:getlastmodified>
propstatOK.Prop = append(propstatOK.Prop, prop.Escaped("oc:fileid", match.Entity.Id.StorageId+"!"+match.Entity.Id.OpaqueId))
propstatOK.Prop = append(propstatOK.Prop, prop.Escaped("d:getetag", match.Entity.Etag))
propstatOK.Prop = append(propstatOK.Prop, prop.Escaped("d:getlastmodified", match.Entity.LastModifiedTime.AsTime().Format(time.RFC3339)))
propstatOK.Prop = append(propstatOK.Prop, prop.Escaped("d:getcontenttype", match.Entity.MimeType))
size := strconv.FormatUint(match.Entity.Size, 10)
propstatOK.Prop = append(propstatOK.Prop, prop.Escaped("oc:size", size))
propstatOK.Prop = append(propstatOK.Prop, prop.Escaped("d:getcontentlength", size))
// TODO find name for score property
score := strconv.FormatFloat(float64(match.Score), 'f', -1, 64)
propstatOK.Prop = append(propstatOK.Prop, prop.Escaped("oc:score", score))
if len(propstatOK.Prop) > 0 {
response.Propstat = append(response.Propstat, propstatOK)
}
return &response, nil
}
type report struct {
SearchFiles *reportSearchFiles
// FilterFiles TODO add this for tag based search
FilterFiles *reportFilterFiles `xml:"filter-files"`
}
type reportSearchFiles struct {
XMLName xml.Name `xml:"search-files"`
Lang string `xml:"xml:lang,attr,omitempty"`
Prop Props `xml:"DAV: prop"`
Search reportSearchFilesSearch `xml:"search"`
}
type reportSearchFilesSearch struct {
Pattern string `xml:"pattern"`
Limit int `xml:"limit"`
Offset int `xml:"offset"`
}
type reportFilterFiles struct {
XMLName xml.Name `xml:"filter-files"`
Lang string `xml:"xml:lang,attr,omitempty"`
Prop Props `xml:"DAV: prop"`
Rules reportFilterFilesRules `xml:"filter-rules"`
}
type reportFilterFilesRules struct {
Favorite bool `xml:"favorite"`
SystemTag int `xml:"systemtag"`
}
// Props represents properties related to a resource
// http://www.webdav.org/specs/rfc4918.html#ELEMENT_prop (for propfind)
type Props []xml.Name
// XML holds the xml representation of a propfind
// http://www.webdav.org/specs/rfc4918.html#ELEMENT_propfind
type XML struct {
XMLName xml.Name `xml:"DAV: propfind"`
Allprop *struct{} `xml:"DAV: allprop"`
Propname *struct{} `xml:"DAV: propname"`
Prop Props `xml:"DAV: prop"`
Include Props `xml:"DAV: include"`
}
func readReport(r io.Reader) (rep *report, err error) {
decoder := xml.NewDecoder(r)
rep = &report{}
for {
t, err := decoder.Token()
if err == io.EOF {
// io.EOF is a successful end
return rep, nil
}
if err != nil {
return nil, err
}
if v, ok := t.(xml.StartElement); ok {
if v.Name.Local == elementNameSearchFiles {
var repSF reportSearchFiles
err = decoder.DecodeElement(&repSF, &v)
if err != nil {
return nil, err
}
rep.SearchFiles = &repSF
/*
} else if v.Name.Local == elementNameFilterFiles {
var repFF reportFilterFiles
err = decoder.DecodeElement(&repFF, &v)
if err != nil {
return nil, http.StatusBadRequest, err
}
rep.FilterFiles = &repFF
*/
}
}
}
}

View File

@@ -23,6 +23,7 @@ import (
"github.com/owncloud/ocis/extensions/webdav/pkg/config"
"github.com/owncloud/ocis/extensions/webdav/pkg/dav/requests"
thumbnailsmsg "github.com/owncloud/ocis/protogen/gen/ocis/messages/thumbnails/v0"
searchsvc "github.com/owncloud/ocis/protogen/gen/ocis/services/search/v0"
thumbnailssvc "github.com/owncloud/ocis/protogen/gen/ocis/services/thumbnails/v0"
)
@@ -51,6 +52,7 @@ func NewService(opts ...Option) (Service, error) {
conf := options.Config
m := chi.NewMux()
chi.RegisterMethod("REPORT")
m.Use(options.Middleware...)
gwc, err := pool.GetGatewayServiceClient(conf.RevaGateway)
@@ -62,6 +64,7 @@ func NewService(opts ...Option) (Service, error) {
config: conf,
log: options.Logger,
mux: m,
searchClient: searchsvc.NewSearchProviderService("com.owncloud.api.search", grpc.DefaultClient),
thumbnailsClient: thumbnailssvc.NewThumbnailService("com.owncloud.api.thumbnails", grpc.DefaultClient),
revaClient: gwc,
}
@@ -71,16 +74,19 @@ func NewService(opts ...Option) (Service, error) {
r.Get("/remote.php/dav/files/{id}/*", svc.Thumbnail)
r.Get("/remote.php/dav/public-files/{token}/*", svc.PublicThumbnail)
r.Head("/remote.php/dav/public-files/{token}/*", svc.PublicThumbnailHead)
r.MethodFunc("REPORT", "/remote.php/dav/files/{id}/*", svc.Search)
})
return svc, nil
}
// Webdav defines implements the business logic for Service.
// Webdav implements the business logic for Service.
type Webdav struct {
config *config.Config
log log.Logger
mux *chi.Mux
searchClient searchsvc.SearchProviderService
thumbnailsClient thumbnailssvc.ThumbnailService
revaClient gatewayv1beta1.GatewayAPIClient
}

4
go.mod
View File

@@ -7,9 +7,10 @@ require (
github.com/GeertJohan/yubigo v0.0.0-20190917122436-175bc097e60e
github.com/ReneKroon/ttlcache/v2 v2.11.0
github.com/blevesearch/bleve/v2 v2.3.2
github.com/blevesearch/bleve_index_api v1.0.1
github.com/coreos/go-oidc/v3 v3.1.0
github.com/cs3org/go-cs3apis v0.0.0-20220412090512-93c5918b4bde
github.com/cs3org/reva/v2 v2.0.0-20220502075009-8bcec2e4663e
github.com/cs3org/reva/v2 v2.0.0-20220502122639-bfbf8690a043
github.com/disintegration/imaging v1.6.2
github.com/glauth/glauth/v2 v2.0.0-20211021011345-ef3151c28733
github.com/go-chi/chi/v5 v5.0.7
@@ -104,7 +105,6 @@ require (
github.com/beorn7/perks v1.0.1 // indirect
github.com/bitly/go-simplejson v0.5.0 // indirect
github.com/bits-and-blooms/bitset v1.2.1 // indirect
github.com/blevesearch/bleve_index_api v1.0.1 // indirect
github.com/blevesearch/go-porterstemmer v1.0.3 // indirect
github.com/blevesearch/gtreap v0.1.1 // indirect
github.com/blevesearch/mmap-go v1.0.3 // indirect

4
go.sum
View File

@@ -318,8 +318,8 @@ github.com/cs3org/go-cs3apis v0.0.0-20220412090512-93c5918b4bde h1:WrD9O8ZaWvsm0
github.com/cs3org/go-cs3apis v0.0.0-20220412090512-93c5918b4bde/go.mod h1:UXha4TguuB52H14EMoSsCqDj7k8a/t7g4gVP+bgY5LY=
github.com/cs3org/reva v1.18.0 h1:MbPS5ZAa8RzKcTxAVeSDdISB3XXqLIxqB03BTN5ReBY=
github.com/cs3org/reva v1.18.0/go.mod h1:e5VDUDu4vVWIeVkZcW//n6UZzhGGMa+Tz/whCiX3N6o=
github.com/cs3org/reva/v2 v2.0.0-20220502075009-8bcec2e4663e h1:ym80MMvfFLHMxt6aiU67kTe/pzRBaSOUNdPkmeKYejk=
github.com/cs3org/reva/v2 v2.0.0-20220502075009-8bcec2e4663e/go.mod h1:2e/4HcIy54Mic3V7Ow0bz4n5dkZU0dHIZSWomFe5vng=
github.com/cs3org/reva/v2 v2.0.0-20220502122639-bfbf8690a043 h1:wAvf45pBDnWIN4kpyWpD9uRl9y147ioAXJkfztrMSCM=
github.com/cs3org/reva/v2 v2.0.0-20220502122639-bfbf8690a043/go.mod h1:2e/4HcIy54Mic3V7Ow0bz4n5dkZU0dHIZSWomFe5vng=
github.com/cubewise-code/go-mime v0.0.0-20200519001935-8c5762b177d8 h1:Z9lwXumT5ACSmJ7WGnFl+OMLLjpz5uR2fyz7dC255FI=
github.com/cubewise-code/go-mime v0.0.0-20200519001935-8c5762b177d8/go.mod h1:4abs/jPXcmJzYoYGF91JF9Uq9s/KL5n1jvFDix8KcqY=
github.com/cyberdelia/templates v0.0.0-20141128023046-ca7fffd4298c/go.mod h1:GyV+0YP4qX0UQ7r2MoYZ+AvYDp12OF5yg4q8rGnyNh4=

View File

@@ -22,6 +22,7 @@ import (
ocdav "github.com/owncloud/ocis/extensions/ocdav/pkg/config"
ocs "github.com/owncloud/ocis/extensions/ocs/pkg/config"
proxy "github.com/owncloud/ocis/extensions/proxy/pkg/config"
search "github.com/owncloud/ocis/extensions/search/pkg/config"
settings "github.com/owncloud/ocis/extensions/settings/pkg/config"
sharing "github.com/owncloud/ocis/extensions/sharing/pkg/config"
storagemetadata "github.com/owncloud/ocis/extensions/storage-metadata/pkg/config"
@@ -100,4 +101,5 @@ type Config struct {
Store *store.Config `yaml:"store"`
Thumbnails *thumbnails.Config `yaml:"thumbnails"`
WebDAV *webdav.Config `yaml:"webdav"`
Search *search.Config `yaml:"search"`
}

View File

@@ -20,6 +20,7 @@ import (
ocdav "github.com/owncloud/ocis/extensions/ocdav/pkg/config/defaults"
ocs "github.com/owncloud/ocis/extensions/ocs/pkg/config/defaults"
proxy "github.com/owncloud/ocis/extensions/proxy/pkg/config/defaults"
search "github.com/owncloud/ocis/extensions/search/pkg/config/defaults"
settings "github.com/owncloud/ocis/extensions/settings/pkg/config/defaults"
sharing "github.com/owncloud/ocis/extensions/sharing/pkg/config/defaults"
storagemetadata "github.com/owncloud/ocis/extensions/storage-metadata/pkg/config/defaults"
@@ -58,6 +59,7 @@ func DefaultConfig() *Config {
OCDav: ocdav.DefaultConfig(),
OCS: ocs.DefaultConfig(),
Proxy: proxy.DefaultConfig(),
Search: search.FullDefaultConfig(),
Settings: settings.DefaultConfig(),
Sharing: sharing.DefaultConfig(),
StorageMetadata: storagemetadata.DefaultConfig(),

View File

@@ -0,0 +1,26 @@
package command
import (
"github.com/owncloud/ocis/extensions/search/pkg/command"
"github.com/owncloud/ocis/ocis-pkg/config"
"github.com/owncloud/ocis/ocis-pkg/config/parser"
"github.com/owncloud/ocis/ocis/pkg/register"
"github.com/urfave/cli/v2"
)
// SearchCommand is the entry point for the search command.
func SearchCommand(cfg *config.Config) *cli.Command {
return &cli.Command{
Name: cfg.Search.Service.Name,
Usage: subcommandDescription(cfg.Search.Service.Name),
Category: "extensions",
Before: func(ctx *cli.Context) error {
return parser.ParseConfig(cfg)
},
Subcommands: command.GetCommands(cfg.Search),
}
}
func init() {
register.AddCommand(SearchCommand)
}

View File

@@ -37,6 +37,7 @@ import (
ocdav "github.com/owncloud/ocis/extensions/ocdav/pkg/command"
ocs "github.com/owncloud/ocis/extensions/ocs/pkg/command"
proxy "github.com/owncloud/ocis/extensions/proxy/pkg/command"
search "github.com/owncloud/ocis/extensions/search/pkg/command"
settings "github.com/owncloud/ocis/extensions/settings/pkg/command"
sharing "github.com/owncloud/ocis/extensions/sharing/pkg/command"
storagemetadata "github.com/owncloud/ocis/extensions/storage-metadata/pkg/command"
@@ -131,6 +132,7 @@ func NewService(options ...Option) (*Service, error) {
s.ServicesRegistry[opts.Config.StoragePublicLink.Service.Name] = storagepublic.NewStoragePublicLink
s.ServicesRegistry[opts.Config.AppProvider.Service.Name] = appprovider.NewAppProvider
s.ServicesRegistry[opts.Config.Notifications.Service.Name] = notifications.NewSutureService
s.ServicesRegistry[opts.Config.Search.Service.Name] = search.NewSutureService
// populate delayed services
s.Delayed[opts.Config.Sharing.Service.Name] = sharing.NewSharing

View File

@@ -1,7 +1,7 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.27.1
// protoc v3.17.3
// protoc-gen-go v1.28.0
// protoc (unknown)
// source: ocis/messages/accounts/v0/accounts.proto
package v0

View File

@@ -0,0 +1,471 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.28.0
// protoc (unknown)
// source: ocis/messages/search/v0/search.proto
package v0
import (
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
timestamppb "google.golang.org/protobuf/types/known/timestamppb"
reflect "reflect"
sync "sync"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
type ResourceID struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
StorageId string `protobuf:"bytes,1,opt,name=storage_id,json=storageId,proto3" json:"storage_id,omitempty"`
OpaqueId string `protobuf:"bytes,2,opt,name=opaque_id,json=opaqueId,proto3" json:"opaque_id,omitempty"`
}
func (x *ResourceID) Reset() {
*x = ResourceID{}
if protoimpl.UnsafeEnabled {
mi := &file_ocis_messages_search_v0_search_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *ResourceID) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ResourceID) ProtoMessage() {}
func (x *ResourceID) ProtoReflect() protoreflect.Message {
mi := &file_ocis_messages_search_v0_search_proto_msgTypes[0]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ResourceID.ProtoReflect.Descriptor instead.
func (*ResourceID) Descriptor() ([]byte, []int) {
return file_ocis_messages_search_v0_search_proto_rawDescGZIP(), []int{0}
}
func (x *ResourceID) GetStorageId() string {
if x != nil {
return x.StorageId
}
return ""
}
func (x *ResourceID) GetOpaqueId() string {
if x != nil {
return x.OpaqueId
}
return ""
}
type Reference struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
ResourceId *ResourceID `protobuf:"bytes,1,opt,name=resource_id,json=resourceId,proto3" json:"resource_id,omitempty"`
Path string `protobuf:"bytes,2,opt,name=path,proto3" json:"path,omitempty"`
}
func (x *Reference) Reset() {
*x = Reference{}
if protoimpl.UnsafeEnabled {
mi := &file_ocis_messages_search_v0_search_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *Reference) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*Reference) ProtoMessage() {}
func (x *Reference) ProtoReflect() protoreflect.Message {
mi := &file_ocis_messages_search_v0_search_proto_msgTypes[1]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use Reference.ProtoReflect.Descriptor instead.
func (*Reference) Descriptor() ([]byte, []int) {
return file_ocis_messages_search_v0_search_proto_rawDescGZIP(), []int{1}
}
func (x *Reference) GetResourceId() *ResourceID {
if x != nil {
return x.ResourceId
}
return nil
}
func (x *Reference) GetPath() string {
if x != nil {
return x.Path
}
return ""
}
type Entity struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Ref *Reference `protobuf:"bytes,1,opt,name=ref,proto3" json:"ref,omitempty"`
Id *ResourceID `protobuf:"bytes,2,opt,name=id,proto3" json:"id,omitempty"`
Name string `protobuf:"bytes,3,opt,name=name,proto3" json:"name,omitempty"`
Etag string `protobuf:"bytes,4,opt,name=etag,proto3" json:"etag,omitempty"`
Size uint64 `protobuf:"varint,5,opt,name=size,proto3" json:"size,omitempty"`
LastModifiedTime *timestamppb.Timestamp `protobuf:"bytes,6,opt,name=last_modified_time,json=lastModifiedTime,proto3" json:"last_modified_time,omitempty"`
MimeType string `protobuf:"bytes,7,opt,name=mime_type,json=mimeType,proto3" json:"mime_type,omitempty"`
Permissions string `protobuf:"bytes,8,opt,name=permissions,proto3" json:"permissions,omitempty"`
Type uint64 `protobuf:"varint,9,opt,name=type,proto3" json:"type,omitempty"`
Deleted bool `protobuf:"varint,10,opt,name=deleted,proto3" json:"deleted,omitempty"`
}
func (x *Entity) Reset() {
*x = Entity{}
if protoimpl.UnsafeEnabled {
mi := &file_ocis_messages_search_v0_search_proto_msgTypes[2]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *Entity) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*Entity) ProtoMessage() {}
func (x *Entity) ProtoReflect() protoreflect.Message {
mi := &file_ocis_messages_search_v0_search_proto_msgTypes[2]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use Entity.ProtoReflect.Descriptor instead.
func (*Entity) Descriptor() ([]byte, []int) {
return file_ocis_messages_search_v0_search_proto_rawDescGZIP(), []int{2}
}
func (x *Entity) GetRef() *Reference {
if x != nil {
return x.Ref
}
return nil
}
func (x *Entity) GetId() *ResourceID {
if x != nil {
return x.Id
}
return nil
}
func (x *Entity) GetName() string {
if x != nil {
return x.Name
}
return ""
}
func (x *Entity) GetEtag() string {
if x != nil {
return x.Etag
}
return ""
}
func (x *Entity) GetSize() uint64 {
if x != nil {
return x.Size
}
return 0
}
func (x *Entity) GetLastModifiedTime() *timestamppb.Timestamp {
if x != nil {
return x.LastModifiedTime
}
return nil
}
func (x *Entity) GetMimeType() string {
if x != nil {
return x.MimeType
}
return ""
}
func (x *Entity) GetPermissions() string {
if x != nil {
return x.Permissions
}
return ""
}
func (x *Entity) GetType() uint64 {
if x != nil {
return x.Type
}
return 0
}
func (x *Entity) GetDeleted() bool {
if x != nil {
return x.Deleted
}
return false
}
type Match struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// the matched entity
Entity *Entity `protobuf:"bytes,1,opt,name=entity,proto3" json:"entity,omitempty"`
// the match score
Score float32 `protobuf:"fixed32,2,opt,name=score,proto3" json:"score,omitempty"`
}
func (x *Match) Reset() {
*x = Match{}
if protoimpl.UnsafeEnabled {
mi := &file_ocis_messages_search_v0_search_proto_msgTypes[3]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *Match) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*Match) ProtoMessage() {}
func (x *Match) ProtoReflect() protoreflect.Message {
mi := &file_ocis_messages_search_v0_search_proto_msgTypes[3]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use Match.ProtoReflect.Descriptor instead.
func (*Match) Descriptor() ([]byte, []int) {
return file_ocis_messages_search_v0_search_proto_rawDescGZIP(), []int{3}
}
func (x *Match) GetEntity() *Entity {
if x != nil {
return x.Entity
}
return nil
}
func (x *Match) GetScore() float32 {
if x != nil {
return x.Score
}
return 0
}
var File_ocis_messages_search_v0_search_proto protoreflect.FileDescriptor
var file_ocis_messages_search_v0_search_proto_rawDesc = []byte{
0x0a, 0x24, 0x6f, 0x63, 0x69, 0x73, 0x2f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x2f,
0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x2f, 0x76, 0x30, 0x2f, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68,
0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x17, 0x6f, 0x63, 0x69, 0x73, 0x2e, 0x6d, 0x65, 0x73,
0x73, 0x61, 0x67, 0x65, 0x73, 0x2e, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x2e, 0x76, 0x30, 0x1a,
0x1f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66,
0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
0x22, 0x48, 0x0a, 0x0a, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x49, 0x44, 0x12, 0x1d,
0x0a, 0x0a, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01,
0x28, 0x09, 0x52, 0x09, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x49, 0x64, 0x12, 0x1b, 0x0a,
0x09, 0x6f, 0x70, 0x61, 0x71, 0x75, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09,
0x52, 0x08, 0x6f, 0x70, 0x61, 0x71, 0x75, 0x65, 0x49, 0x64, 0x22, 0x65, 0x0a, 0x09, 0x52, 0x65,
0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x12, 0x44, 0x0a, 0x0b, 0x72, 0x65, 0x73, 0x6f, 0x75,
0x72, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x23, 0x2e, 0x6f,
0x63, 0x69, 0x73, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x2e, 0x73, 0x65, 0x61,
0x72, 0x63, 0x68, 0x2e, 0x76, 0x30, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x49,
0x44, 0x52, 0x0a, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x49, 0x64, 0x12, 0x12, 0x0a,
0x04, 0x70, 0x61, 0x74, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x70, 0x61, 0x74,
0x68, 0x22, 0xe6, 0x02, 0x0a, 0x06, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x12, 0x34, 0x0a, 0x03,
0x72, 0x65, 0x66, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x6f, 0x63, 0x69, 0x73,
0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x2e, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68,
0x2e, 0x76, 0x30, 0x2e, 0x52, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x52, 0x03, 0x72,
0x65, 0x66, 0x12, 0x33, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x23,
0x2e, 0x6f, 0x63, 0x69, 0x73, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x2e, 0x73,
0x65, 0x61, 0x72, 0x63, 0x68, 0x2e, 0x76, 0x30, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63,
0x65, 0x49, 0x44, 0x52, 0x02, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18,
0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x65,
0x74, 0x61, 0x67, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x65, 0x74, 0x61, 0x67, 0x12,
0x12, 0x0a, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x04, 0x52, 0x04, 0x73,
0x69, 0x7a, 0x65, 0x12, 0x48, 0x0a, 0x12, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x6d, 0x6f, 0x64, 0x69,
0x66, 0x69, 0x65, 0x64, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32,
0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75,
0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x10, 0x6c, 0x61, 0x73,
0x74, 0x4d, 0x6f, 0x64, 0x69, 0x66, 0x69, 0x65, 0x64, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x1b, 0x0a,
0x09, 0x6d, 0x69, 0x6d, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09,
0x52, 0x08, 0x6d, 0x69, 0x6d, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x70, 0x65,
0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52,
0x0b, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x12, 0x0a, 0x04,
0x74, 0x79, 0x70, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x04, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65,
0x12, 0x18, 0x0a, 0x07, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28,
0x08, 0x52, 0x07, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x22, 0x56, 0x0a, 0x05, 0x4d, 0x61,
0x74, 0x63, 0x68, 0x12, 0x37, 0x0a, 0x06, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x18, 0x01, 0x20,
0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x6f, 0x63, 0x69, 0x73, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61,
0x67, 0x65, 0x73, 0x2e, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x2e, 0x76, 0x30, 0x2e, 0x45, 0x6e,
0x74, 0x69, 0x74, 0x79, 0x52, 0x06, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x12, 0x14, 0x0a, 0x05,
0x73, 0x63, 0x6f, 0x72, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x02, 0x52, 0x05, 0x73, 0x63, 0x6f,
0x72, 0x65, 0x42, 0x3f, 0x5a, 0x3d, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d,
0x2f, 0x6f, 0x77, 0x6e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2f, 0x6f, 0x63, 0x69, 0x73, 0x2f, 0x70,
0x72, 0x6f, 0x74, 0x6f, 0x67, 0x65, 0x6e, 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x6f, 0x63, 0x69, 0x73,
0x2f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x2f, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68,
0x2f, 0x76, 0x30, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (
file_ocis_messages_search_v0_search_proto_rawDescOnce sync.Once
file_ocis_messages_search_v0_search_proto_rawDescData = file_ocis_messages_search_v0_search_proto_rawDesc
)
func file_ocis_messages_search_v0_search_proto_rawDescGZIP() []byte {
file_ocis_messages_search_v0_search_proto_rawDescOnce.Do(func() {
file_ocis_messages_search_v0_search_proto_rawDescData = protoimpl.X.CompressGZIP(file_ocis_messages_search_v0_search_proto_rawDescData)
})
return file_ocis_messages_search_v0_search_proto_rawDescData
}
var file_ocis_messages_search_v0_search_proto_msgTypes = make([]protoimpl.MessageInfo, 4)
var file_ocis_messages_search_v0_search_proto_goTypes = []interface{}{
(*ResourceID)(nil), // 0: ocis.messages.search.v0.ResourceID
(*Reference)(nil), // 1: ocis.messages.search.v0.Reference
(*Entity)(nil), // 2: ocis.messages.search.v0.Entity
(*Match)(nil), // 3: ocis.messages.search.v0.Match
(*timestamppb.Timestamp)(nil), // 4: google.protobuf.Timestamp
}
var file_ocis_messages_search_v0_search_proto_depIdxs = []int32{
0, // 0: ocis.messages.search.v0.Reference.resource_id:type_name -> ocis.messages.search.v0.ResourceID
1, // 1: ocis.messages.search.v0.Entity.ref:type_name -> ocis.messages.search.v0.Reference
0, // 2: ocis.messages.search.v0.Entity.id:type_name -> ocis.messages.search.v0.ResourceID
4, // 3: ocis.messages.search.v0.Entity.last_modified_time:type_name -> google.protobuf.Timestamp
2, // 4: ocis.messages.search.v0.Match.entity:type_name -> ocis.messages.search.v0.Entity
5, // [5:5] is the sub-list for method output_type
5, // [5:5] is the sub-list for method input_type
5, // [5:5] is the sub-list for extension type_name
5, // [5:5] is the sub-list for extension extendee
0, // [0:5] is the sub-list for field type_name
}
func init() { file_ocis_messages_search_v0_search_proto_init() }
func file_ocis_messages_search_v0_search_proto_init() {
if File_ocis_messages_search_v0_search_proto != nil {
return
}
if !protoimpl.UnsafeEnabled {
file_ocis_messages_search_v0_search_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*ResourceID); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_ocis_messages_search_v0_search_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*Reference); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_ocis_messages_search_v0_search_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*Entity); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_ocis_messages_search_v0_search_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*Match); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_ocis_messages_search_v0_search_proto_rawDesc,
NumEnums: 0,
NumMessages: 4,
NumExtensions: 0,
NumServices: 0,
},
GoTypes: file_ocis_messages_search_v0_search_proto_goTypes,
DependencyIndexes: file_ocis_messages_search_v0_search_proto_depIdxs,
MessageInfos: file_ocis_messages_search_v0_search_proto_msgTypes,
}.Build()
File_ocis_messages_search_v0_search_proto = out.File
file_ocis_messages_search_v0_search_proto_rawDesc = nil
file_ocis_messages_search_v0_search_proto_goTypes = nil
file_ocis_messages_search_v0_search_proto_depIdxs = nil
}

View File

@@ -0,0 +1,16 @@
// Code generated by protoc-gen-micro. DO NOT EDIT.
// source: ocis/messages/search/v0/search.proto
package v0
import (
fmt "fmt"
proto "google.golang.org/protobuf/proto"
_ "google.golang.org/protobuf/types/known/timestamppb"
math "math"
)
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
var _ = fmt.Errorf
var _ = math.Inf

View File

@@ -0,0 +1,155 @@
// Code generated by protoc-gen-microweb. DO NOT EDIT.
// source: v0.proto
package v0
import (
"bytes"
"encoding/json"
"github.com/golang/protobuf/jsonpb"
)
// ResourceIDJSONMarshaler describes the default jsonpb.Marshaler used by all
// instances of ResourceID. This struct is safe to replace or modify but
// should not be done so concurrently.
var ResourceIDJSONMarshaler = new(jsonpb.Marshaler)
// MarshalJSON satisfies the encoding/json Marshaler interface. This method
// uses the more correct jsonpb package to correctly marshal the message.
func (m *ResourceID) MarshalJSON() ([]byte, error) {
if m == nil {
return json.Marshal(nil)
}
buf := &bytes.Buffer{}
if err := ResourceIDJSONMarshaler.Marshal(buf, m); err != nil {
return nil, err
}
return buf.Bytes(), nil
}
var _ json.Marshaler = (*ResourceID)(nil)
// ResourceIDJSONUnmarshaler describes the default jsonpb.Unmarshaler used by all
// instances of ResourceID. This struct is safe to replace or modify but
// should not be done so concurrently.
var ResourceIDJSONUnmarshaler = new(jsonpb.Unmarshaler)
// UnmarshalJSON satisfies the encoding/json Unmarshaler interface. This method
// uses the more correct jsonpb package to correctly unmarshal the message.
func (m *ResourceID) UnmarshalJSON(b []byte) error {
return ResourceIDJSONUnmarshaler.Unmarshal(bytes.NewReader(b), m)
}
var _ json.Unmarshaler = (*ResourceID)(nil)
// ReferenceJSONMarshaler describes the default jsonpb.Marshaler used by all
// instances of Reference. This struct is safe to replace or modify but
// should not be done so concurrently.
var ReferenceJSONMarshaler = new(jsonpb.Marshaler)
// MarshalJSON satisfies the encoding/json Marshaler interface. This method
// uses the more correct jsonpb package to correctly marshal the message.
func (m *Reference) MarshalJSON() ([]byte, error) {
if m == nil {
return json.Marshal(nil)
}
buf := &bytes.Buffer{}
if err := ReferenceJSONMarshaler.Marshal(buf, m); err != nil {
return nil, err
}
return buf.Bytes(), nil
}
var _ json.Marshaler = (*Reference)(nil)
// ReferenceJSONUnmarshaler describes the default jsonpb.Unmarshaler used by all
// instances of Reference. This struct is safe to replace or modify but
// should not be done so concurrently.
var ReferenceJSONUnmarshaler = new(jsonpb.Unmarshaler)
// UnmarshalJSON satisfies the encoding/json Unmarshaler interface. This method
// uses the more correct jsonpb package to correctly unmarshal the message.
func (m *Reference) UnmarshalJSON(b []byte) error {
return ReferenceJSONUnmarshaler.Unmarshal(bytes.NewReader(b), m)
}
var _ json.Unmarshaler = (*Reference)(nil)
// EntityJSONMarshaler describes the default jsonpb.Marshaler used by all
// instances of Entity. This struct is safe to replace or modify but
// should not be done so concurrently.
var EntityJSONMarshaler = new(jsonpb.Marshaler)
// MarshalJSON satisfies the encoding/json Marshaler interface. This method
// uses the more correct jsonpb package to correctly marshal the message.
func (m *Entity) MarshalJSON() ([]byte, error) {
if m == nil {
return json.Marshal(nil)
}
buf := &bytes.Buffer{}
if err := EntityJSONMarshaler.Marshal(buf, m); err != nil {
return nil, err
}
return buf.Bytes(), nil
}
var _ json.Marshaler = (*Entity)(nil)
// EntityJSONUnmarshaler describes the default jsonpb.Unmarshaler used by all
// instances of Entity. This struct is safe to replace or modify but
// should not be done so concurrently.
var EntityJSONUnmarshaler = new(jsonpb.Unmarshaler)
// UnmarshalJSON satisfies the encoding/json Unmarshaler interface. This method
// uses the more correct jsonpb package to correctly unmarshal the message.
func (m *Entity) UnmarshalJSON(b []byte) error {
return EntityJSONUnmarshaler.Unmarshal(bytes.NewReader(b), m)
}
var _ json.Unmarshaler = (*Entity)(nil)
// MatchJSONMarshaler describes the default jsonpb.Marshaler used by all
// instances of Match. This struct is safe to replace or modify but
// should not be done so concurrently.
var MatchJSONMarshaler = new(jsonpb.Marshaler)
// MarshalJSON satisfies the encoding/json Marshaler interface. This method
// uses the more correct jsonpb package to correctly marshal the message.
func (m *Match) MarshalJSON() ([]byte, error) {
if m == nil {
return json.Marshal(nil)
}
buf := &bytes.Buffer{}
if err := MatchJSONMarshaler.Marshal(buf, m); err != nil {
return nil, err
}
return buf.Bytes(), nil
}
var _ json.Marshaler = (*Match)(nil)
// MatchJSONUnmarshaler describes the default jsonpb.Unmarshaler used by all
// instances of Match. This struct is safe to replace or modify but
// should not be done so concurrently.
var MatchJSONUnmarshaler = new(jsonpb.Unmarshaler)
// UnmarshalJSON satisfies the encoding/json Unmarshaler interface. This method
// uses the more correct jsonpb package to correctly unmarshal the message.
func (m *Match) UnmarshalJSON(b []byte) error {
return MatchJSONUnmarshaler.Unmarshal(bytes.NewReader(b), m)
}
var _ json.Unmarshaler = (*Match)(nil)

View File

@@ -0,0 +1,43 @@
{
"swagger": "2.0",
"info": {
"title": "ocis/messages/search/v0/search.proto",
"version": "version not set"
},
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"paths": {},
"definitions": {
"protobufAny": {
"type": "object",
"properties": {
"@type": {
"type": "string"
}
},
"additionalProperties": {}
},
"rpcStatus": {
"type": "object",
"properties": {
"code": {
"type": "integer",
"format": "int32"
},
"message": {
"type": "string"
},
"details": {
"type": "array",
"items": {
"$ref": "#/definitions/protobufAny"
}
}
}
}
}
}

View File

@@ -1,7 +1,7 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.27.1
// protoc v3.17.3
// protoc-gen-go v1.28.0
// protoc (unknown)
// source: ocis/messages/settings/v0/settings.proto
package v0

View File

@@ -1,7 +1,7 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.27.1
// protoc v3.17.3
// protoc-gen-go v1.28.0
// protoc (unknown)
// source: ocis/messages/store/v0/store.proto
package v0

View File

@@ -1,7 +1,7 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.27.1
// protoc v3.17.3
// protoc-gen-go v1.28.0
// protoc (unknown)
// source: ocis/messages/thumbnails/v0/thumbnails.proto
package v0

View File

@@ -1,7 +1,7 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.27.1
// protoc v3.17.3
// protoc-gen-go v1.28.0
// protoc (unknown)
// source: ocis/services/accounts/v0/accounts.proto
package v0

View File

@@ -0,0 +1,621 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.28.0
// protoc (unknown)
// source: ocis/services/search/v0/search.proto
package v0
import (
_ "github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2/options"
v0 "github.com/owncloud/ocis/protogen/gen/ocis/messages/search/v0"
_ "google.golang.org/genproto/googleapis/api/annotations"
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
_ "google.golang.org/protobuf/types/known/fieldmaskpb"
reflect "reflect"
sync "sync"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
type SearchRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// Optional. The maximum number of entries to return in the response
PageSize int32 `protobuf:"varint,1,opt,name=page_size,json=pageSize,proto3" json:"page_size,omitempty"`
// Optional. A pagination token returned from a previous call to `Get`
// that indicates from where search should continue
PageToken string `protobuf:"bytes,2,opt,name=page_token,json=pageToken,proto3" json:"page_token,omitempty"`
Query string `protobuf:"bytes,3,opt,name=query,proto3" json:"query,omitempty"`
}
func (x *SearchRequest) Reset() {
*x = SearchRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_ocis_services_search_v0_search_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *SearchRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*SearchRequest) ProtoMessage() {}
func (x *SearchRequest) ProtoReflect() protoreflect.Message {
mi := &file_ocis_services_search_v0_search_proto_msgTypes[0]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use SearchRequest.ProtoReflect.Descriptor instead.
func (*SearchRequest) Descriptor() ([]byte, []int) {
return file_ocis_services_search_v0_search_proto_rawDescGZIP(), []int{0}
}
func (x *SearchRequest) GetPageSize() int32 {
if x != nil {
return x.PageSize
}
return 0
}
func (x *SearchRequest) GetPageToken() string {
if x != nil {
return x.PageToken
}
return ""
}
func (x *SearchRequest) GetQuery() string {
if x != nil {
return x.Query
}
return ""
}
type SearchResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Matches []*v0.Match `protobuf:"bytes,1,rep,name=matches,proto3" json:"matches,omitempty"`
// Token to retrieve the next page of results, or empty if there are no
// more results in the list
NextPageToken string `protobuf:"bytes,2,opt,name=next_page_token,json=nextPageToken,proto3" json:"next_page_token,omitempty"`
}
func (x *SearchResponse) Reset() {
*x = SearchResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_ocis_services_search_v0_search_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *SearchResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*SearchResponse) ProtoMessage() {}
func (x *SearchResponse) ProtoReflect() protoreflect.Message {
mi := &file_ocis_services_search_v0_search_proto_msgTypes[1]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use SearchResponse.ProtoReflect.Descriptor instead.
func (*SearchResponse) Descriptor() ([]byte, []int) {
return file_ocis_services_search_v0_search_proto_rawDescGZIP(), []int{1}
}
func (x *SearchResponse) GetMatches() []*v0.Match {
if x != nil {
return x.Matches
}
return nil
}
func (x *SearchResponse) GetNextPageToken() string {
if x != nil {
return x.NextPageToken
}
return ""
}
type SearchIndexRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// Optional. The maximum number of entries to return in the response
PageSize int32 `protobuf:"varint,1,opt,name=page_size,json=pageSize,proto3" json:"page_size,omitempty"`
// Optional. A pagination token returned from a previous call to `Get`
// that indicates from where search should continue
PageToken string `protobuf:"bytes,2,opt,name=page_token,json=pageToken,proto3" json:"page_token,omitempty"`
Query string `protobuf:"bytes,3,opt,name=query,proto3" json:"query,omitempty"`
Ref *v0.Reference `protobuf:"bytes,4,opt,name=ref,proto3" json:"ref,omitempty"`
}
func (x *SearchIndexRequest) Reset() {
*x = SearchIndexRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_ocis_services_search_v0_search_proto_msgTypes[2]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *SearchIndexRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*SearchIndexRequest) ProtoMessage() {}
func (x *SearchIndexRequest) ProtoReflect() protoreflect.Message {
mi := &file_ocis_services_search_v0_search_proto_msgTypes[2]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use SearchIndexRequest.ProtoReflect.Descriptor instead.
func (*SearchIndexRequest) Descriptor() ([]byte, []int) {
return file_ocis_services_search_v0_search_proto_rawDescGZIP(), []int{2}
}
func (x *SearchIndexRequest) GetPageSize() int32 {
if x != nil {
return x.PageSize
}
return 0
}
func (x *SearchIndexRequest) GetPageToken() string {
if x != nil {
return x.PageToken
}
return ""
}
func (x *SearchIndexRequest) GetQuery() string {
if x != nil {
return x.Query
}
return ""
}
func (x *SearchIndexRequest) GetRef() *v0.Reference {
if x != nil {
return x.Ref
}
return nil
}
type SearchIndexResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Matches []*v0.Match `protobuf:"bytes,1,rep,name=matches,proto3" json:"matches,omitempty"`
// Token to retrieve the next page of results, or empty if there are no
// more results in the list
NextPageToken string `protobuf:"bytes,2,opt,name=next_page_token,json=nextPageToken,proto3" json:"next_page_token,omitempty"`
}
func (x *SearchIndexResponse) Reset() {
*x = SearchIndexResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_ocis_services_search_v0_search_proto_msgTypes[3]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *SearchIndexResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*SearchIndexResponse) ProtoMessage() {}
func (x *SearchIndexResponse) ProtoReflect() protoreflect.Message {
mi := &file_ocis_services_search_v0_search_proto_msgTypes[3]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use SearchIndexResponse.ProtoReflect.Descriptor instead.
func (*SearchIndexResponse) Descriptor() ([]byte, []int) {
return file_ocis_services_search_v0_search_proto_rawDescGZIP(), []int{3}
}
func (x *SearchIndexResponse) GetMatches() []*v0.Match {
if x != nil {
return x.Matches
}
return nil
}
func (x *SearchIndexResponse) GetNextPageToken() string {
if x != nil {
return x.NextPageToken
}
return ""
}
type IndexSpaceRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
SpaceId string `protobuf:"bytes,1,opt,name=space_id,json=spaceId,proto3" json:"space_id,omitempty"`
UserId string `protobuf:"bytes,2,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"`
}
func (x *IndexSpaceRequest) Reset() {
*x = IndexSpaceRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_ocis_services_search_v0_search_proto_msgTypes[4]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *IndexSpaceRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*IndexSpaceRequest) ProtoMessage() {}
func (x *IndexSpaceRequest) ProtoReflect() protoreflect.Message {
mi := &file_ocis_services_search_v0_search_proto_msgTypes[4]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use IndexSpaceRequest.ProtoReflect.Descriptor instead.
func (*IndexSpaceRequest) Descriptor() ([]byte, []int) {
return file_ocis_services_search_v0_search_proto_rawDescGZIP(), []int{4}
}
func (x *IndexSpaceRequest) GetSpaceId() string {
if x != nil {
return x.SpaceId
}
return ""
}
func (x *IndexSpaceRequest) GetUserId() string {
if x != nil {
return x.UserId
}
return ""
}
type IndexSpaceResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
}
func (x *IndexSpaceResponse) Reset() {
*x = IndexSpaceResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_ocis_services_search_v0_search_proto_msgTypes[5]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *IndexSpaceResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*IndexSpaceResponse) ProtoMessage() {}
func (x *IndexSpaceResponse) ProtoReflect() protoreflect.Message {
mi := &file_ocis_services_search_v0_search_proto_msgTypes[5]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use IndexSpaceResponse.ProtoReflect.Descriptor instead.
func (*IndexSpaceResponse) Descriptor() ([]byte, []int) {
return file_ocis_services_search_v0_search_proto_rawDescGZIP(), []int{5}
}
var File_ocis_services_search_v0_search_proto protoreflect.FileDescriptor
var file_ocis_services_search_v0_search_proto_rawDesc = []byte{
0x0a, 0x24, 0x6f, 0x63, 0x69, 0x73, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2f,
0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x2f, 0x76, 0x30, 0x2f, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68,
0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x17, 0x6f, 0x63, 0x69, 0x73, 0x2e, 0x73, 0x65, 0x72,
0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x2e, 0x76, 0x30, 0x1a,
0x24, 0x6f, 0x63, 0x69, 0x73, 0x2f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x2f, 0x73,
0x65, 0x61, 0x72, 0x63, 0x68, 0x2f, 0x76, 0x30, 0x2f, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x2e,
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x2d, 0x67, 0x65,
0x6e, 0x2d, 0x6f, 0x70, 0x65, 0x6e, 0x61, 0x70, 0x69, 0x76, 0x32, 0x2f, 0x6f, 0x70, 0x74, 0x69,
0x6f, 0x6e, 0x73, 0x2f, 0x61, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e,
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x61, 0x70,
0x69, 0x2f, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x5f, 0x62, 0x65, 0x68, 0x61, 0x76, 0x69, 0x6f, 0x72,
0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1c, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x61,
0x70, 0x69, 0x2f, 0x61, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x70,
0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x20, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f,
0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x5f, 0x6d, 0x61, 0x73, 0x6b,
0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x6d, 0x0a, 0x0d, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68,
0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x21, 0x0a, 0x09, 0x70, 0x61, 0x67, 0x65, 0x5f,
0x73, 0x69, 0x7a, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x42, 0x04, 0xe2, 0x41, 0x01, 0x01,
0x52, 0x08, 0x70, 0x61, 0x67, 0x65, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x23, 0x0a, 0x0a, 0x70, 0x61,
0x67, 0x65, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x42, 0x04,
0xe2, 0x41, 0x01, 0x01, 0x52, 0x09, 0x70, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12,
0x14, 0x0a, 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05,
0x71, 0x75, 0x65, 0x72, 0x79, 0x22, 0x72, 0x0a, 0x0e, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x52,
0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x38, 0x0a, 0x07, 0x6d, 0x61, 0x74, 0x63, 0x68,
0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x6f, 0x63, 0x69, 0x73, 0x2e,
0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x2e, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x2e,
0x76, 0x30, 0x2e, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x52, 0x07, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x65,
0x73, 0x12, 0x26, 0x0a, 0x0f, 0x6e, 0x65, 0x78, 0x74, 0x5f, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x74,
0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x6e, 0x65, 0x78, 0x74,
0x50, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0xae, 0x01, 0x0a, 0x12, 0x53, 0x65,
0x61, 0x72, 0x63, 0x68, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
0x12, 0x21, 0x0a, 0x09, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x01, 0x20,
0x01, 0x28, 0x05, 0x42, 0x04, 0xe2, 0x41, 0x01, 0x01, 0x52, 0x08, 0x70, 0x61, 0x67, 0x65, 0x53,
0x69, 0x7a, 0x65, 0x12, 0x23, 0x0a, 0x0a, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x74, 0x6f, 0x6b, 0x65,
0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x42, 0x04, 0xe2, 0x41, 0x01, 0x01, 0x52, 0x09, 0x70,
0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x14, 0x0a, 0x05, 0x71, 0x75, 0x65, 0x72,
0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, 0x12, 0x3a,
0x0a, 0x03, 0x72, 0x65, 0x66, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x6f, 0x63,
0x69, 0x73, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x2e, 0x73, 0x65, 0x61, 0x72,
0x63, 0x68, 0x2e, 0x76, 0x30, 0x2e, 0x52, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x42,
0x04, 0xe2, 0x41, 0x01, 0x01, 0x52, 0x03, 0x72, 0x65, 0x66, 0x22, 0x77, 0x0a, 0x13, 0x53, 0x65,
0x61, 0x72, 0x63, 0x68, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
0x65, 0x12, 0x38, 0x0a, 0x07, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03,
0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x6f, 0x63, 0x69, 0x73, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67,
0x65, 0x73, 0x2e, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x2e, 0x76, 0x30, 0x2e, 0x4d, 0x61, 0x74,
0x63, 0x68, 0x52, 0x07, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x65, 0x73, 0x12, 0x26, 0x0a, 0x0f, 0x6e,
0x65, 0x78, 0x74, 0x5f, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x02,
0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x6e, 0x65, 0x78, 0x74, 0x50, 0x61, 0x67, 0x65, 0x54, 0x6f,
0x6b, 0x65, 0x6e, 0x22, 0x47, 0x0a, 0x11, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x53, 0x70, 0x61, 0x63,
0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x73, 0x70, 0x61, 0x63,
0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, 0x70, 0x61, 0x63,
0x65, 0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02,
0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x22, 0x14, 0x0a, 0x12,
0x49, 0x6e, 0x64, 0x65, 0x78, 0x53, 0x70, 0x61, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
0x73, 0x65, 0x32, 0x9c, 0x02, 0x0a, 0x0e, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x50, 0x72, 0x6f,
0x76, 0x69, 0x64, 0x65, 0x72, 0x12, 0x7b, 0x0a, 0x06, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x12,
0x26, 0x2e, 0x6f, 0x63, 0x69, 0x73, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e,
0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x2e, 0x76, 0x30, 0x2e, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68,
0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x27, 0x2e, 0x6f, 0x63, 0x69, 0x73, 0x2e, 0x73,
0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x2e, 0x76,
0x30, 0x2e, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
0x22, 0x20, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1a, 0x22, 0x15, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76,
0x30, 0x2f, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x2f, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x3a,
0x01, 0x2a, 0x12, 0x8c, 0x01, 0x0a, 0x0a, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x53, 0x70, 0x61, 0x63,
0x65, 0x12, 0x2a, 0x2e, 0x6f, 0x63, 0x69, 0x73, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65,
0x73, 0x2e, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x2e, 0x76, 0x30, 0x2e, 0x49, 0x6e, 0x64, 0x65,
0x78, 0x53, 0x70, 0x61, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2b, 0x2e,
0x6f, 0x63, 0x69, 0x73, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x73, 0x65,
0x61, 0x72, 0x63, 0x68, 0x2e, 0x76, 0x30, 0x2e, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x53, 0x70, 0x61,
0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x25, 0x82, 0xd3, 0xe4, 0x93,
0x02, 0x1f, 0x22, 0x1a, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x30, 0x2f, 0x73, 0x65, 0x61, 0x72,
0x63, 0x68, 0x2f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x2d, 0x73, 0x70, 0x61, 0x63, 0x65, 0x3a, 0x01,
0x2a, 0x32, 0x9d, 0x01, 0x0a, 0x0d, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x50, 0x72, 0x6f, 0x76, 0x69,
0x64, 0x65, 0x72, 0x12, 0x8b, 0x01, 0x0a, 0x06, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x12, 0x2b,
0x2e, 0x6f, 0x63, 0x69, 0x73, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x73,
0x65, 0x61, 0x72, 0x63, 0x68, 0x2e, 0x76, 0x30, 0x2e, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x49,
0x6e, 0x64, 0x65, 0x78, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2c, 0x2e, 0x6f, 0x63,
0x69, 0x73, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x73, 0x65, 0x61, 0x72,
0x63, 0x68, 0x2e, 0x76, 0x30, 0x2e, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x49, 0x6e, 0x64, 0x65,
0x78, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x26, 0x82, 0xd3, 0xe4, 0x93, 0x02,
0x20, 0x22, 0x1b, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x30, 0x2f, 0x73, 0x65, 0x61, 0x72, 0x63,
0x68, 0x2f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x2f, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x3a, 0x01,
0x2a, 0x42, 0xde, 0x02, 0x5a, 0x3c, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d,
0x2f, 0x6f, 0x77, 0x6e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2f, 0x6f, 0x63, 0x69, 0x73, 0x2f, 0x70,
0x72, 0x6f, 0x74, 0x6f, 0x67, 0x65, 0x6e, 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x6f, 0x63, 0x69, 0x73,
0x2f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2f, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x2f,
0x76, 0x30, 0x92, 0x41, 0x9c, 0x02, 0x12, 0xb4, 0x01, 0x0a, 0x1e, 0x6f, 0x77, 0x6e, 0x43, 0x6c,
0x6f, 0x75, 0x64, 0x20, 0x49, 0x6e, 0x66, 0x69, 0x6e, 0x69, 0x74, 0x65, 0x20, 0x53, 0x63, 0x61,
0x6c, 0x65, 0x20, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x22, 0x47, 0x0a, 0x0d, 0x6f, 0x77, 0x6e,
0x43, 0x6c, 0x6f, 0x75, 0x64, 0x20, 0x47, 0x6d, 0x62, 0x48, 0x12, 0x20, 0x68, 0x74, 0x74, 0x70,
0x73, 0x3a, 0x2f, 0x2f, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6f,
0x77, 0x6e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2f, 0x6f, 0x63, 0x69, 0x73, 0x1a, 0x14, 0x73, 0x75,
0x70, 0x70, 0x6f, 0x72, 0x74, 0x40, 0x6f, 0x77, 0x6e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x63,
0x6f, 0x6d, 0x2a, 0x42, 0x0a, 0x0a, 0x41, 0x70, 0x61, 0x63, 0x68, 0x65, 0x2d, 0x32, 0x2e, 0x30,
0x12, 0x34, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62,
0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6f, 0x77, 0x6e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2f, 0x6f, 0x63,
0x69, 0x73, 0x2f, 0x62, 0x6c, 0x6f, 0x62, 0x2f, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x2f, 0x4c,
0x49, 0x43, 0x45, 0x4e, 0x53, 0x45, 0x32, 0x05, 0x31, 0x2e, 0x30, 0x2e, 0x30, 0x2a, 0x02, 0x01,
0x02, 0x32, 0x10, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x6a,
0x73, 0x6f, 0x6e, 0x3a, 0x10, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e,
0x2f, 0x6a, 0x73, 0x6f, 0x6e, 0x72, 0x3b, 0x0a, 0x10, 0x44, 0x65, 0x76, 0x65, 0x6c, 0x6f, 0x70,
0x65, 0x72, 0x20, 0x4d, 0x61, 0x6e, 0x75, 0x61, 0x6c, 0x12, 0x27, 0x68, 0x74, 0x74, 0x70, 0x73,
0x3a, 0x2f, 0x2f, 0x6f, 0x77, 0x6e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x64, 0x65, 0x76, 0x2f,
0x65, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x2f, 0x73, 0x65, 0x61, 0x72, 0x63,
0x68, 0x2f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (
file_ocis_services_search_v0_search_proto_rawDescOnce sync.Once
file_ocis_services_search_v0_search_proto_rawDescData = file_ocis_services_search_v0_search_proto_rawDesc
)
func file_ocis_services_search_v0_search_proto_rawDescGZIP() []byte {
file_ocis_services_search_v0_search_proto_rawDescOnce.Do(func() {
file_ocis_services_search_v0_search_proto_rawDescData = protoimpl.X.CompressGZIP(file_ocis_services_search_v0_search_proto_rawDescData)
})
return file_ocis_services_search_v0_search_proto_rawDescData
}
var file_ocis_services_search_v0_search_proto_msgTypes = make([]protoimpl.MessageInfo, 6)
var file_ocis_services_search_v0_search_proto_goTypes = []interface{}{
(*SearchRequest)(nil), // 0: ocis.services.search.v0.SearchRequest
(*SearchResponse)(nil), // 1: ocis.services.search.v0.SearchResponse
(*SearchIndexRequest)(nil), // 2: ocis.services.search.v0.SearchIndexRequest
(*SearchIndexResponse)(nil), // 3: ocis.services.search.v0.SearchIndexResponse
(*IndexSpaceRequest)(nil), // 4: ocis.services.search.v0.IndexSpaceRequest
(*IndexSpaceResponse)(nil), // 5: ocis.services.search.v0.IndexSpaceResponse
(*v0.Match)(nil), // 6: ocis.messages.search.v0.Match
(*v0.Reference)(nil), // 7: ocis.messages.search.v0.Reference
}
var file_ocis_services_search_v0_search_proto_depIdxs = []int32{
6, // 0: ocis.services.search.v0.SearchResponse.matches:type_name -> ocis.messages.search.v0.Match
7, // 1: ocis.services.search.v0.SearchIndexRequest.ref:type_name -> ocis.messages.search.v0.Reference
6, // 2: ocis.services.search.v0.SearchIndexResponse.matches:type_name -> ocis.messages.search.v0.Match
0, // 3: ocis.services.search.v0.SearchProvider.Search:input_type -> ocis.services.search.v0.SearchRequest
4, // 4: ocis.services.search.v0.SearchProvider.IndexSpace:input_type -> ocis.services.search.v0.IndexSpaceRequest
2, // 5: ocis.services.search.v0.IndexProvider.Search:input_type -> ocis.services.search.v0.SearchIndexRequest
1, // 6: ocis.services.search.v0.SearchProvider.Search:output_type -> ocis.services.search.v0.SearchResponse
5, // 7: ocis.services.search.v0.SearchProvider.IndexSpace:output_type -> ocis.services.search.v0.IndexSpaceResponse
3, // 8: ocis.services.search.v0.IndexProvider.Search:output_type -> ocis.services.search.v0.SearchIndexResponse
6, // [6:9] is the sub-list for method output_type
3, // [3:6] is the sub-list for method input_type
3, // [3:3] is the sub-list for extension type_name
3, // [3:3] is the sub-list for extension extendee
0, // [0:3] is the sub-list for field type_name
}
func init() { file_ocis_services_search_v0_search_proto_init() }
func file_ocis_services_search_v0_search_proto_init() {
if File_ocis_services_search_v0_search_proto != nil {
return
}
if !protoimpl.UnsafeEnabled {
file_ocis_services_search_v0_search_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*SearchRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_ocis_services_search_v0_search_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*SearchResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_ocis_services_search_v0_search_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*SearchIndexRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_ocis_services_search_v0_search_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*SearchIndexResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_ocis_services_search_v0_search_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*IndexSpaceRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_ocis_services_search_v0_search_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*IndexSpaceResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_ocis_services_search_v0_search_proto_rawDesc,
NumEnums: 0,
NumMessages: 6,
NumExtensions: 0,
NumServices: 2,
},
GoTypes: file_ocis_services_search_v0_search_proto_goTypes,
DependencyIndexes: file_ocis_services_search_v0_search_proto_depIdxs,
MessageInfos: file_ocis_services_search_v0_search_proto_msgTypes,
}.Build()
File_ocis_services_search_v0_search_proto = out.File
file_ocis_services_search_v0_search_proto_rawDesc = nil
file_ocis_services_search_v0_search_proto_goTypes = nil
file_ocis_services_search_v0_search_proto_depIdxs = nil
}

View File

@@ -0,0 +1,211 @@
// Code generated by protoc-gen-micro. DO NOT EDIT.
// source: ocis/services/search/v0/search.proto
package v0
import (
fmt "fmt"
_ "github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2/options"
_ "github.com/owncloud/ocis/protogen/gen/ocis/messages/search/v0"
_ "google.golang.org/genproto/googleapis/api/annotations"
proto "google.golang.org/protobuf/proto"
_ "google.golang.org/protobuf/types/known/fieldmaskpb"
math "math"
)
import (
context "context"
api "go-micro.dev/v4/api"
client "go-micro.dev/v4/client"
server "go-micro.dev/v4/server"
)
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
var _ = fmt.Errorf
var _ = math.Inf
// Reference imports to suppress errors if they are not otherwise used.
var _ api.Endpoint
var _ context.Context
var _ client.Option
var _ server.Option
// Api Endpoints for SearchProvider service
func NewSearchProviderEndpoints() []*api.Endpoint {
return []*api.Endpoint{
{
Name: "SearchProvider.Search",
Path: []string{"/api/v0/search/search"},
Method: []string{"POST"},
Body: "*",
Handler: "rpc",
},
{
Name: "SearchProvider.IndexSpace",
Path: []string{"/api/v0/search/index-space"},
Method: []string{"POST"},
Body: "*",
Handler: "rpc",
},
}
}
// Client API for SearchProvider service
type SearchProviderService interface {
Search(ctx context.Context, in *SearchRequest, opts ...client.CallOption) (*SearchResponse, error)
IndexSpace(ctx context.Context, in *IndexSpaceRequest, opts ...client.CallOption) (*IndexSpaceResponse, error)
}
type searchProviderService struct {
c client.Client
name string
}
func NewSearchProviderService(name string, c client.Client) SearchProviderService {
return &searchProviderService{
c: c,
name: name,
}
}
func (c *searchProviderService) Search(ctx context.Context, in *SearchRequest, opts ...client.CallOption) (*SearchResponse, error) {
req := c.c.NewRequest(c.name, "SearchProvider.Search", in)
out := new(SearchResponse)
err := c.c.Call(ctx, req, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *searchProviderService) IndexSpace(ctx context.Context, in *IndexSpaceRequest, opts ...client.CallOption) (*IndexSpaceResponse, error) {
req := c.c.NewRequest(c.name, "SearchProvider.IndexSpace", in)
out := new(IndexSpaceResponse)
err := c.c.Call(ctx, req, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
// Server API for SearchProvider service
type SearchProviderHandler interface {
Search(context.Context, *SearchRequest, *SearchResponse) error
IndexSpace(context.Context, *IndexSpaceRequest, *IndexSpaceResponse) error
}
func RegisterSearchProviderHandler(s server.Server, hdlr SearchProviderHandler, opts ...server.HandlerOption) error {
type searchProvider interface {
Search(ctx context.Context, in *SearchRequest, out *SearchResponse) error
IndexSpace(ctx context.Context, in *IndexSpaceRequest, out *IndexSpaceResponse) error
}
type SearchProvider struct {
searchProvider
}
h := &searchProviderHandler{hdlr}
opts = append(opts, api.WithEndpoint(&api.Endpoint{
Name: "SearchProvider.Search",
Path: []string{"/api/v0/search/search"},
Method: []string{"POST"},
Body: "*",
Handler: "rpc",
}))
opts = append(opts, api.WithEndpoint(&api.Endpoint{
Name: "SearchProvider.IndexSpace",
Path: []string{"/api/v0/search/index-space"},
Method: []string{"POST"},
Body: "*",
Handler: "rpc",
}))
return s.Handle(s.NewHandler(&SearchProvider{h}, opts...))
}
type searchProviderHandler struct {
SearchProviderHandler
}
func (h *searchProviderHandler) Search(ctx context.Context, in *SearchRequest, out *SearchResponse) error {
return h.SearchProviderHandler.Search(ctx, in, out)
}
func (h *searchProviderHandler) IndexSpace(ctx context.Context, in *IndexSpaceRequest, out *IndexSpaceResponse) error {
return h.SearchProviderHandler.IndexSpace(ctx, in, out)
}
// Api Endpoints for IndexProvider service
func NewIndexProviderEndpoints() []*api.Endpoint {
return []*api.Endpoint{
{
Name: "IndexProvider.Search",
Path: []string{"/api/v0/search/index/search"},
Method: []string{"POST"},
Body: "*",
Handler: "rpc",
},
}
}
// Client API for IndexProvider service
type IndexProviderService interface {
Search(ctx context.Context, in *SearchIndexRequest, opts ...client.CallOption) (*SearchIndexResponse, error)
}
type indexProviderService struct {
c client.Client
name string
}
func NewIndexProviderService(name string, c client.Client) IndexProviderService {
return &indexProviderService{
c: c,
name: name,
}
}
func (c *indexProviderService) Search(ctx context.Context, in *SearchIndexRequest, opts ...client.CallOption) (*SearchIndexResponse, error) {
req := c.c.NewRequest(c.name, "IndexProvider.Search", in)
out := new(SearchIndexResponse)
err := c.c.Call(ctx, req, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
// Server API for IndexProvider service
type IndexProviderHandler interface {
Search(context.Context, *SearchIndexRequest, *SearchIndexResponse) error
}
func RegisterIndexProviderHandler(s server.Server, hdlr IndexProviderHandler, opts ...server.HandlerOption) error {
type indexProvider interface {
Search(ctx context.Context, in *SearchIndexRequest, out *SearchIndexResponse) error
}
type IndexProvider struct {
indexProvider
}
h := &indexProviderHandler{hdlr}
opts = append(opts, api.WithEndpoint(&api.Endpoint{
Name: "IndexProvider.Search",
Path: []string{"/api/v0/search/index/search"},
Method: []string{"POST"},
Body: "*",
Handler: "rpc",
}))
return s.Handle(s.NewHandler(&IndexProvider{h}, opts...))
}
type indexProviderHandler struct {
IndexProviderHandler
}
func (h *indexProviderHandler) Search(ctx context.Context, in *SearchIndexRequest, out *SearchIndexResponse) error {
return h.IndexProviderHandler.Search(ctx, in, out)
}

View File

@@ -0,0 +1,333 @@
// Code generated by protoc-gen-microweb. DO NOT EDIT.
// source: v0.proto
package v0
import (
"bytes"
"encoding/json"
"net/http"
"github.com/go-chi/chi/v5"
"github.com/go-chi/render"
"github.com/golang/protobuf/jsonpb"
)
type webSearchProviderHandler struct {
r chi.Router
h SearchProviderHandler
}
func (h *webSearchProviderHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
h.r.ServeHTTP(w, r)
}
func (h *webSearchProviderHandler) Search(w http.ResponseWriter, r *http.Request) {
req := &SearchRequest{}
resp := &SearchResponse{}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, err.Error(), http.StatusPreconditionFailed)
return
}
if err := h.h.Search(
r.Context(),
req,
resp,
); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
render.Status(r, http.StatusCreated)
render.JSON(w, r, resp)
}
func (h *webSearchProviderHandler) IndexSpace(w http.ResponseWriter, r *http.Request) {
req := &IndexSpaceRequest{}
resp := &IndexSpaceResponse{}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, err.Error(), http.StatusPreconditionFailed)
return
}
if err := h.h.IndexSpace(
r.Context(),
req,
resp,
); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
render.Status(r, http.StatusCreated)
render.JSON(w, r, resp)
}
func RegisterSearchProviderWeb(r chi.Router, i SearchProviderHandler, middlewares ...func(http.Handler) http.Handler) {
handler := &webSearchProviderHandler{
r: r,
h: i,
}
r.MethodFunc("POST", "/api/v0/search/search", handler.Search)
r.MethodFunc("POST", "/api/v0/search/index-space", handler.IndexSpace)
}
type webIndexProviderHandler struct {
r chi.Router
h IndexProviderHandler
}
func (h *webIndexProviderHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
h.r.ServeHTTP(w, r)
}
func (h *webIndexProviderHandler) Search(w http.ResponseWriter, r *http.Request) {
req := &SearchIndexRequest{}
resp := &SearchIndexResponse{}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, err.Error(), http.StatusPreconditionFailed)
return
}
if err := h.h.Search(
r.Context(),
req,
resp,
); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
render.Status(r, http.StatusCreated)
render.JSON(w, r, resp)
}
func RegisterIndexProviderWeb(r chi.Router, i IndexProviderHandler, middlewares ...func(http.Handler) http.Handler) {
handler := &webIndexProviderHandler{
r: r,
h: i,
}
r.MethodFunc("POST", "/api/v0/search/index/search", handler.Search)
}
// SearchRequestJSONMarshaler describes the default jsonpb.Marshaler used by all
// instances of SearchRequest. This struct is safe to replace or modify but
// should not be done so concurrently.
var SearchRequestJSONMarshaler = new(jsonpb.Marshaler)
// MarshalJSON satisfies the encoding/json Marshaler interface. This method
// uses the more correct jsonpb package to correctly marshal the message.
func (m *SearchRequest) MarshalJSON() ([]byte, error) {
if m == nil {
return json.Marshal(nil)
}
buf := &bytes.Buffer{}
if err := SearchRequestJSONMarshaler.Marshal(buf, m); err != nil {
return nil, err
}
return buf.Bytes(), nil
}
var _ json.Marshaler = (*SearchRequest)(nil)
// SearchRequestJSONUnmarshaler describes the default jsonpb.Unmarshaler used by all
// instances of SearchRequest. This struct is safe to replace or modify but
// should not be done so concurrently.
var SearchRequestJSONUnmarshaler = new(jsonpb.Unmarshaler)
// UnmarshalJSON satisfies the encoding/json Unmarshaler interface. This method
// uses the more correct jsonpb package to correctly unmarshal the message.
func (m *SearchRequest) UnmarshalJSON(b []byte) error {
return SearchRequestJSONUnmarshaler.Unmarshal(bytes.NewReader(b), m)
}
var _ json.Unmarshaler = (*SearchRequest)(nil)
// SearchResponseJSONMarshaler describes the default jsonpb.Marshaler used by all
// instances of SearchResponse. This struct is safe to replace or modify but
// should not be done so concurrently.
var SearchResponseJSONMarshaler = new(jsonpb.Marshaler)
// MarshalJSON satisfies the encoding/json Marshaler interface. This method
// uses the more correct jsonpb package to correctly marshal the message.
func (m *SearchResponse) MarshalJSON() ([]byte, error) {
if m == nil {
return json.Marshal(nil)
}
buf := &bytes.Buffer{}
if err := SearchResponseJSONMarshaler.Marshal(buf, m); err != nil {
return nil, err
}
return buf.Bytes(), nil
}
var _ json.Marshaler = (*SearchResponse)(nil)
// SearchResponseJSONUnmarshaler describes the default jsonpb.Unmarshaler used by all
// instances of SearchResponse. This struct is safe to replace or modify but
// should not be done so concurrently.
var SearchResponseJSONUnmarshaler = new(jsonpb.Unmarshaler)
// UnmarshalJSON satisfies the encoding/json Unmarshaler interface. This method
// uses the more correct jsonpb package to correctly unmarshal the message.
func (m *SearchResponse) UnmarshalJSON(b []byte) error {
return SearchResponseJSONUnmarshaler.Unmarshal(bytes.NewReader(b), m)
}
var _ json.Unmarshaler = (*SearchResponse)(nil)
// SearchIndexRequestJSONMarshaler describes the default jsonpb.Marshaler used by all
// instances of SearchIndexRequest. This struct is safe to replace or modify but
// should not be done so concurrently.
var SearchIndexRequestJSONMarshaler = new(jsonpb.Marshaler)
// MarshalJSON satisfies the encoding/json Marshaler interface. This method
// uses the more correct jsonpb package to correctly marshal the message.
func (m *SearchIndexRequest) MarshalJSON() ([]byte, error) {
if m == nil {
return json.Marshal(nil)
}
buf := &bytes.Buffer{}
if err := SearchIndexRequestJSONMarshaler.Marshal(buf, m); err != nil {
return nil, err
}
return buf.Bytes(), nil
}
var _ json.Marshaler = (*SearchIndexRequest)(nil)
// SearchIndexRequestJSONUnmarshaler describes the default jsonpb.Unmarshaler used by all
// instances of SearchIndexRequest. This struct is safe to replace or modify but
// should not be done so concurrently.
var SearchIndexRequestJSONUnmarshaler = new(jsonpb.Unmarshaler)
// UnmarshalJSON satisfies the encoding/json Unmarshaler interface. This method
// uses the more correct jsonpb package to correctly unmarshal the message.
func (m *SearchIndexRequest) UnmarshalJSON(b []byte) error {
return SearchIndexRequestJSONUnmarshaler.Unmarshal(bytes.NewReader(b), m)
}
var _ json.Unmarshaler = (*SearchIndexRequest)(nil)
// SearchIndexResponseJSONMarshaler describes the default jsonpb.Marshaler used by all
// instances of SearchIndexResponse. This struct is safe to replace or modify but
// should not be done so concurrently.
var SearchIndexResponseJSONMarshaler = new(jsonpb.Marshaler)
// MarshalJSON satisfies the encoding/json Marshaler interface. This method
// uses the more correct jsonpb package to correctly marshal the message.
func (m *SearchIndexResponse) MarshalJSON() ([]byte, error) {
if m == nil {
return json.Marshal(nil)
}
buf := &bytes.Buffer{}
if err := SearchIndexResponseJSONMarshaler.Marshal(buf, m); err != nil {
return nil, err
}
return buf.Bytes(), nil
}
var _ json.Marshaler = (*SearchIndexResponse)(nil)
// SearchIndexResponseJSONUnmarshaler describes the default jsonpb.Unmarshaler used by all
// instances of SearchIndexResponse. This struct is safe to replace or modify but
// should not be done so concurrently.
var SearchIndexResponseJSONUnmarshaler = new(jsonpb.Unmarshaler)
// UnmarshalJSON satisfies the encoding/json Unmarshaler interface. This method
// uses the more correct jsonpb package to correctly unmarshal the message.
func (m *SearchIndexResponse) UnmarshalJSON(b []byte) error {
return SearchIndexResponseJSONUnmarshaler.Unmarshal(bytes.NewReader(b), m)
}
var _ json.Unmarshaler = (*SearchIndexResponse)(nil)
// IndexSpaceRequestJSONMarshaler describes the default jsonpb.Marshaler used by all
// instances of IndexSpaceRequest. This struct is safe to replace or modify but
// should not be done so concurrently.
var IndexSpaceRequestJSONMarshaler = new(jsonpb.Marshaler)
// MarshalJSON satisfies the encoding/json Marshaler interface. This method
// uses the more correct jsonpb package to correctly marshal the message.
func (m *IndexSpaceRequest) MarshalJSON() ([]byte, error) {
if m == nil {
return json.Marshal(nil)
}
buf := &bytes.Buffer{}
if err := IndexSpaceRequestJSONMarshaler.Marshal(buf, m); err != nil {
return nil, err
}
return buf.Bytes(), nil
}
var _ json.Marshaler = (*IndexSpaceRequest)(nil)
// IndexSpaceRequestJSONUnmarshaler describes the default jsonpb.Unmarshaler used by all
// instances of IndexSpaceRequest. This struct is safe to replace or modify but
// should not be done so concurrently.
var IndexSpaceRequestJSONUnmarshaler = new(jsonpb.Unmarshaler)
// UnmarshalJSON satisfies the encoding/json Unmarshaler interface. This method
// uses the more correct jsonpb package to correctly unmarshal the message.
func (m *IndexSpaceRequest) UnmarshalJSON(b []byte) error {
return IndexSpaceRequestJSONUnmarshaler.Unmarshal(bytes.NewReader(b), m)
}
var _ json.Unmarshaler = (*IndexSpaceRequest)(nil)
// IndexSpaceResponseJSONMarshaler describes the default jsonpb.Marshaler used by all
// instances of IndexSpaceResponse. This struct is safe to replace or modify but
// should not be done so concurrently.
var IndexSpaceResponseJSONMarshaler = new(jsonpb.Marshaler)
// MarshalJSON satisfies the encoding/json Marshaler interface. This method
// uses the more correct jsonpb package to correctly marshal the message.
func (m *IndexSpaceResponse) MarshalJSON() ([]byte, error) {
if m == nil {
return json.Marshal(nil)
}
buf := &bytes.Buffer{}
if err := IndexSpaceResponseJSONMarshaler.Marshal(buf, m); err != nil {
return nil, err
}
return buf.Bytes(), nil
}
var _ json.Marshaler = (*IndexSpaceResponse)(nil)
// IndexSpaceResponseJSONUnmarshaler describes the default jsonpb.Unmarshaler used by all
// instances of IndexSpaceResponse. This struct is safe to replace or modify but
// should not be done so concurrently.
var IndexSpaceResponseJSONUnmarshaler = new(jsonpb.Unmarshaler)
// UnmarshalJSON satisfies the encoding/json Unmarshaler interface. This method
// uses the more correct jsonpb package to correctly unmarshal the message.
func (m *IndexSpaceResponse) UnmarshalJSON(b []byte) error {
return IndexSpaceResponseJSONUnmarshaler.Unmarshal(bytes.NewReader(b), m)
}
var _ json.Unmarshaler = (*IndexSpaceResponse)(nil)

View File

@@ -0,0 +1,320 @@
{
"swagger": "2.0",
"info": {
"title": "ownCloud Infinite Scale search",
"version": "1.0.0",
"contact": {
"name": "ownCloud GmbH",
"url": "https://github.com/owncloud/ocis",
"email": "support@owncloud.com"
},
"license": {
"name": "Apache-2.0",
"url": "https://github.com/owncloud/ocis/blob/master/LICENSE"
}
},
"tags": [
{
"name": "SearchProvider"
},
{
"name": "IndexProvider"
}
],
"schemes": [
"http",
"https"
],
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"paths": {
"/api/v0/search/index-space": {
"post": {
"operationId": "SearchProvider_IndexSpace",
"responses": {
"200": {
"description": "A successful response.",
"schema": {
"$ref": "#/definitions/v0IndexSpaceResponse"
}
},
"default": {
"description": "An unexpected error response.",
"schema": {
"$ref": "#/definitions/rpcStatus"
}
}
},
"parameters": [
{
"name": "body",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/v0IndexSpaceRequest"
}
}
],
"tags": [
"SearchProvider"
]
}
},
"/api/v0/search/index/search": {
"post": {
"operationId": "IndexProvider_Search",
"responses": {
"200": {
"description": "A successful response.",
"schema": {
"$ref": "#/definitions/v0SearchIndexResponse"
}
},
"default": {
"description": "An unexpected error response.",
"schema": {
"$ref": "#/definitions/rpcStatus"
}
}
},
"parameters": [
{
"name": "body",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/v0SearchIndexRequest"
}
}
],
"tags": [
"IndexProvider"
]
}
},
"/api/v0/search/search": {
"post": {
"operationId": "SearchProvider_Search",
"responses": {
"200": {
"description": "A successful response.",
"schema": {
"$ref": "#/definitions/v0SearchResponse"
}
},
"default": {
"description": "An unexpected error response.",
"schema": {
"$ref": "#/definitions/rpcStatus"
}
}
},
"parameters": [
{
"name": "body",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/v0SearchRequest"
}
}
],
"tags": [
"SearchProvider"
]
}
}
},
"definitions": {
"protobufAny": {
"type": "object",
"properties": {
"@type": {
"type": "string"
}
},
"additionalProperties": {}
},
"rpcStatus": {
"type": "object",
"properties": {
"code": {
"type": "integer",
"format": "int32"
},
"message": {
"type": "string"
},
"details": {
"type": "array",
"items": {
"$ref": "#/definitions/protobufAny"
}
}
}
},
"v0Entity": {
"type": "object",
"properties": {
"ref": {
"$ref": "#/definitions/v0Reference"
},
"id": {
"$ref": "#/definitions/v0ResourceID"
},
"name": {
"type": "string"
},
"etag": {
"type": "string"
},
"size": {
"type": "string",
"format": "uint64"
},
"lastModifiedTime": {
"type": "string",
"format": "date-time"
},
"mimeType": {
"type": "string"
},
"permissions": {
"type": "string"
},
"type": {
"type": "string",
"format": "uint64"
},
"deleted": {
"type": "boolean"
}
}
},
"v0IndexSpaceRequest": {
"type": "object",
"properties": {
"spaceId": {
"type": "string"
},
"userId": {
"type": "string"
}
}
},
"v0IndexSpaceResponse": {
"type": "object"
},
"v0Match": {
"type": "object",
"properties": {
"entity": {
"$ref": "#/definitions/v0Entity",
"title": "the matched entity"
},
"score": {
"type": "number",
"format": "float",
"title": "the match score"
}
}
},
"v0Reference": {
"type": "object",
"properties": {
"resourceId": {
"$ref": "#/definitions/v0ResourceID"
},
"path": {
"type": "string"
}
}
},
"v0ResourceID": {
"type": "object",
"properties": {
"storageId": {
"type": "string"
},
"opaqueId": {
"type": "string"
}
}
},
"v0SearchIndexRequest": {
"type": "object",
"properties": {
"pageSize": {
"type": "integer",
"format": "int32",
"title": "Optional. The maximum number of entries to return in the response"
},
"pageToken": {
"type": "string",
"title": "Optional. A pagination token returned from a previous call to `Get`\nthat indicates from where search should continue"
},
"query": {
"type": "string"
},
"ref": {
"$ref": "#/definitions/v0Reference"
}
}
},
"v0SearchIndexResponse": {
"type": "object",
"properties": {
"matches": {
"type": "array",
"items": {
"$ref": "#/definitions/v0Match"
}
},
"nextPageToken": {
"type": "string",
"title": "Token to retrieve the next page of results, or empty if there are no\nmore results in the list"
}
}
},
"v0SearchRequest": {
"type": "object",
"properties": {
"pageSize": {
"type": "integer",
"format": "int32",
"title": "Optional. The maximum number of entries to return in the response"
},
"pageToken": {
"type": "string",
"title": "Optional. A pagination token returned from a previous call to `Get`\nthat indicates from where search should continue"
},
"query": {
"type": "string"
}
}
},
"v0SearchResponse": {
"type": "object",
"properties": {
"matches": {
"type": "array",
"items": {
"$ref": "#/definitions/v0Match"
}
},
"nextPageToken": {
"type": "string",
"title": "Token to retrieve the next page of results, or empty if there are no\nmore results in the list"
}
}
}
},
"externalDocs": {
"description": "Developer Manual",
"url": "https://owncloud.dev/extensions/search/"
}
}

View File

@@ -1,7 +1,7 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.27.1
// protoc v3.17.3
// protoc-gen-go v1.28.0
// protoc (unknown)
// source: ocis/services/settings/v0/settings.proto
package v0

View File

@@ -1,7 +1,7 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.27.1
// protoc v3.17.3
// protoc-gen-go v1.28.0
// protoc (unknown)
// source: ocis/services/store/v0/store.proto
package v0

View File

@@ -1,7 +1,7 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.27.1
// protoc v3.17.3
// protoc-gen-go v1.28.0
// protoc (unknown)
// source: ocis/services/thumbnails/v0/thumbnails.proto
package v0

View File

@@ -0,0 +1,37 @@
syntax = "proto3";
package ocis.messages.search.v0;
import "google/protobuf/timestamp.proto";
option go_package = "github.com/owncloud/ocis/protogen/gen/ocis/messages/search/v0";
message ResourceID {
string storage_id = 1;
string opaque_id = 2;
}
message Reference {
ResourceID resource_id = 1;
string path = 2;
}
message Entity {
Reference ref = 1;
ResourceID id = 2;
string name = 3;
string etag = 4;
uint64 size = 5;
google.protobuf.Timestamp last_modified_time = 6;
string mime_type = 7;
string permissions = 8;
uint64 type = 9;
bool deleted = 10;
}
message Match {
// the matched entity
Entity entity = 1;
// the match score
float score = 2;
}

View File

@@ -0,0 +1,107 @@
syntax = "proto3";
package ocis.services.search.v0;
option go_package = "github.com/owncloud/ocis/protogen/gen/ocis/service/search/v0";
import "ocis/messages/search/v0/search.proto";
import "protoc-gen-openapiv2/options/annotations.proto";
import "google/api/field_behavior.proto";
import "google/api/annotations.proto";
import "google/protobuf/field_mask.proto";
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_swagger) = {
info: {
title: "ownCloud Infinite Scale search";
version: "1.0.0";
contact: {
name: "ownCloud GmbH";
url: "https://github.com/owncloud/ocis";
email: "support@owncloud.com";
};
license: {
name: "Apache-2.0";
url: "https://github.com/owncloud/ocis/blob/master/LICENSE";
};
};
schemes: HTTP;
schemes: HTTPS;
consumes: "application/json";
produces: "application/json";
external_docs: {
description: "Developer Manual";
url: "https://owncloud.dev/extensions/search/";
};
};
service SearchProvider {
rpc Search(SearchRequest) returns (SearchResponse) {
option (google.api.http) = {
post: "/api/v0/search/search",
body: "*"
};
};
rpc IndexSpace(IndexSpaceRequest) returns (IndexSpaceResponse) {
option (google.api.http) = {
post: "/api/v0/search/index-space",
body: "*"
};
}
}
service IndexProvider {
rpc Search(SearchIndexRequest) returns (SearchIndexResponse) {
option (google.api.http) = {
post: "/api/v0/search/index/search",
body: "*"
};
};
// rpc Remove(RemoveRequest) returns (RemoveResponse) {};
}
message SearchRequest {
// Optional. The maximum number of entries to return in the response
int32 page_size = 1 [(google.api.field_behavior) = OPTIONAL];
// Optional. A pagination token returned from a previous call to `Get`
// that indicates from where search should continue
string page_token = 2 [(google.api.field_behavior) = OPTIONAL];
string query = 3;
}
message SearchResponse {
repeated ocis.messages.search.v0.Match matches = 1;
// Token to retrieve the next page of results, or empty if there are no
// more results in the list
string next_page_token = 2;
}
message SearchIndexRequest {
// Optional. The maximum number of entries to return in the response
int32 page_size = 1 [(google.api.field_behavior) = OPTIONAL];
// Optional. A pagination token returned from a previous call to `Get`
// that indicates from where search should continue
string page_token = 2 [(google.api.field_behavior) = OPTIONAL];
string query = 3;
ocis.messages.search.v0.Reference ref = 4 [(google.api.field_behavior) = OPTIONAL];
}
message SearchIndexResponse {
repeated ocis.messages.search.v0.Match matches = 1;
// Token to retrieve the next page of results, or empty if there are no
// more results in the list
string next_page_token = 2;
}
message IndexSpaceRequest {
string space_id = 1;
string user_id = 2;
}
message IndexSpaceResponse {
}

View File

@@ -1016,6 +1016,15 @@ _ocdav: api compatibility, return correct status code_
- [apiWebdavOperations/search.feature:265](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L265)
- [apiWebdavOperations/search.feature:270](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L270)
#### [Support for favorites](https://github.com/owncloud/ocis/issues/1228)
- [apiFavorites/favorites.feature:115](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L115)
- [apiFavorites/favorites.feature:116](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L116)
- [apiFavorites/favorites.feature:141](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L141)
- [apiFavorites/favorites.feature:142](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L142)
- [apiFavorites/favorites.feature:267](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L267)
- [apiFavorites/favorites.feature:268](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L268)
And other missing implementation of favorites
- [apiFavorites/favorites.feature:162](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L162)