diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 1dad03b..f0f7bef 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -73,7 +73,7 @@ jobs: path: bin/ - name: Set bin as executable run: chmod +x bin/* - - run: go mod download + - run: go mod tidy - name: Run generate run: make generate - name: Porcelain diff --git a/cmd/fetch-api/main.go b/cmd/fetch-api/main.go index ccff4bc..ad70502 100644 --- a/cmd/fetch-api/main.go +++ b/cmd/fetch-api/main.go @@ -26,13 +26,17 @@ import ( func main() { settingsFile := flag.String("env", ".env", "env file") flag.Parse() + ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt) defer cancel() + logger := logging.GetAndSetDefaultLogger("fetch-api") + settings, err := env.LoadSettings[config.Settings](*settingsFile) if err != nil { logger.Fatal().Err(err).Msg("Couldn't load settings.") } + webServer, err := app.CreateWebServer(&settings) if err != nil { logger.Fatal().Err(err).Msg("Failed to create web server.") @@ -41,6 +45,7 @@ func main() { if err != nil { logger.Fatal().Err(err).Msg("Failed to create RPC server.") } + group, gCtx := errgroup.WithContext(ctx) monApp := monserver.NewMonitoringServer(&logger, settings.EnablePprof) diff --git a/docs/docs.go b/docs/docs.go index 6996d4c..724100e 100644 --- a/docs/docs.go +++ b/docs/docs.go @@ -433,6 +433,13 @@ const docTemplate = `{ "description": "Subject is an optional field identifying the subject of the event within\nthe context of the event producer. In practice, we always set this.", "type": "string" }, + "tags": { + "description": "Tags are a list of tags that can be used to filter events.", + "type": "array", + "items": { + "type": "string" + } + }, "time": { "description": "Time is an optional field giving the time at which the event occurred. In\npractice, we always set this.", "type": "string" @@ -443,7 +450,7 @@ const docTemplate = `{ } } }, - "eventrepo.ObjectInfo": { + "github_com_DIMO-Network_fetch-api_pkg_eventrepo.ObjectInfo": { "type": "object", "properties": { "key": { @@ -458,7 +465,7 @@ const docTemplate = `{ "description": "Data contains domain-specific information about the event.", "allOf": [ { - "$ref": "#/definitions/eventrepo.ObjectInfo" + "$ref": "#/definitions/github_com_DIMO-Network_fetch-api_pkg_eventrepo.ObjectInfo" } ] }, @@ -498,6 +505,13 @@ const docTemplate = `{ "description": "Subject is an optional field identifying the subject of the event within\nthe context of the event producer. In practice, we always set this.", "type": "string" }, + "tags": { + "description": "Tags are a list of tags that can be used to filter events.", + "type": "array", + "items": { + "type": "string" + } + }, "time": { "description": "Time is an optional field giving the time at which the event occurred. In\npractice, we always set this.", "type": "string" diff --git a/docs/swagger.json b/docs/swagger.json index 8bdb9e3..0724563 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -424,6 +424,13 @@ "description": "Subject is an optional field identifying the subject of the event within\nthe context of the event producer. In practice, we always set this.", "type": "string" }, + "tags": { + "description": "Tags are a list of tags that can be used to filter events.", + "type": "array", + "items": { + "type": "string" + } + }, "time": { "description": "Time is an optional field giving the time at which the event occurred. In\npractice, we always set this.", "type": "string" @@ -434,7 +441,7 @@ } } }, - "eventrepo.ObjectInfo": { + "github_com_DIMO-Network_fetch-api_pkg_eventrepo.ObjectInfo": { "type": "object", "properties": { "key": { @@ -449,7 +456,7 @@ "description": "Data contains domain-specific information about the event.", "allOf": [ { - "$ref": "#/definitions/eventrepo.ObjectInfo" + "$ref": "#/definitions/github_com_DIMO-Network_fetch-api_pkg_eventrepo.ObjectInfo" } ] }, @@ -489,6 +496,13 @@ "description": "Subject is an optional field identifying the subject of the event within\nthe context of the event producer. In practice, we always set this.", "type": "string" }, + "tags": { + "description": "Tags are a list of tags that can be used to filter events.", + "type": "array", + "items": { + "type": "string" + } + }, "time": { "description": "Time is an optional field giving the time at which the event occurred. In\npractice, we always set this.", "type": "string" diff --git a/docs/swagger.yaml b/docs/swagger.yaml index 0069978..be529b4 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -45,6 +45,11 @@ definitions: Subject is an optional field identifying the subject of the event within the context of the event producer. In practice, we always set this. type: string + tags: + description: Tags are a list of tags that can be used to filter events. + items: + type: string + type: array time: description: |- Time is an optional field giving the time at which the event occurred. In @@ -56,7 +61,7 @@ definitions: name. type: string type: object - eventrepo.ObjectInfo: + github_com_DIMO-Network_fetch-api_pkg_eventrepo.ObjectInfo: properties: key: type: string @@ -65,7 +70,7 @@ definitions: properties: data: allOf: - - $ref: '#/definitions/eventrepo.ObjectInfo' + - $ref: '#/definitions/github_com_DIMO-Network_fetch-api_pkg_eventrepo.ObjectInfo' description: Data contains domain-specific information about the event. datacontenttype: description: |- @@ -106,6 +111,11 @@ definitions: Subject is an optional field identifying the subject of the event within the context of the event producer. In practice, we always set this. type: string + tags: + description: Tags are a list of tags that can be used to filter events. + items: + type: string + type: array time: description: |- Time is an optional field giving the time at which the event occurred. In diff --git a/go.mod b/go.mod index b343648..2bfe66d 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.25 require ( github.com/ClickHouse/clickhouse-go/v2 v2.40.1 github.com/DIMO-Network/clickhouse-infra v0.0.5 - github.com/DIMO-Network/cloudevent v0.1.3 + github.com/DIMO-Network/cloudevent v0.1.4 github.com/DIMO-Network/server-garage v0.0.4 github.com/DIMO-Network/shared v1.0.7 github.com/aws/aws-sdk-go-v2 v1.38.0 @@ -19,19 +19,23 @@ require ( github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.2 github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 - github.com/prometheus/client_golang v1.23.0 github.com/rs/zerolog v1.34.0 - github.com/stretchr/testify v1.10.0 + github.com/stretchr/testify v1.11.1 github.com/swaggo/swag v1.16.6 + github.com/volatiletech/sqlboiler/v4 v4.19.1 + go.uber.org/mock v0.6.0 golang.org/x/sync v0.16.0 google.golang.org/grpc v1.74.2 - google.golang.org/protobuf v1.36.7 + google.golang.org/protobuf v1.36.8 ) require ( + dario.cat/mergo v1.0.1 // indirect + github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect github.com/ClickHouse/ch-go v0.67.0 // indirect github.com/KyleBanks/depth v1.2.1 // indirect github.com/MicahParks/keyfunc/v2 v2.1.0 // indirect + github.com/Microsoft/go-winio v0.6.2 // indirect github.com/PuerkitoBio/purell v1.1.1 // indirect github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect github.com/andybalholm/brotli v1.2.0 // indirect @@ -46,54 +50,98 @@ require ( github.com/aws/smithy-go v1.22.5 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/caarlos0/env/v11 v11.3.1 // indirect + github.com/cenkalti/backoff/v4 v4.3.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/containerd/errdefs v1.0.0 // indirect + github.com/containerd/errdefs/pkg v0.3.0 // indirect + github.com/containerd/log v0.1.0 // indirect + github.com/containerd/platforms v0.2.1 // indirect + github.com/cpuguy83/dockercfg v0.3.2 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.5 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 // indirect + github.com/distribution/reference v0.6.0 // indirect + github.com/docker/docker v28.3.3+incompatible // indirect + github.com/docker/go-connections v0.6.0 // indirect + github.com/docker/go-units v0.5.0 // indirect + github.com/ebitengine/purego v0.8.4 // indirect + github.com/felixge/httpsnoop v1.0.4 // indirect github.com/friendsofgo/errors v0.9.2 // indirect github.com/go-faster/city v1.0.1 // indirect github.com/go-faster/errors v0.7.1 // indirect + github.com/go-logr/logr v1.4.3 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/go-ole/go-ole v1.3.0 // indirect github.com/go-openapi/jsonpointer v0.19.5 // indirect github.com/go-openapi/jsonreference v0.19.6 // indirect github.com/go-openapi/spec v0.20.4 // indirect github.com/go-openapi/swag v0.19.15 // indirect + github.com/gogo/protobuf v1.3.2 // indirect github.com/google/uuid v1.6.0 // indirect github.com/holiman/uint256 v1.3.2 // indirect github.com/joho/godotenv v1.5.1 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/klauspost/compress v1.18.0 // indirect + github.com/lufia/plan9stats v0.0.0-20250317134145-8bc96cf8fc35 // indirect + github.com/magiconair/properties v1.8.10 // indirect github.com/mailru/easyjson v0.7.6 // indirect github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-runewidth v0.0.16 // indirect + github.com/mdelapenya/tlscert v0.2.0 // indirect + github.com/mfridman/interpolate v0.0.2 // indirect + github.com/moby/docker-image-spec v1.3.1 // indirect + github.com/moby/go-archive v0.1.0 // indirect + github.com/moby/patternmatcher v0.6.0 // indirect + github.com/moby/sys/sequential v0.6.0 // indirect + github.com/moby/sys/user v0.4.0 // indirect + github.com/moby/sys/userns v0.1.0 // indirect + github.com/moby/term v0.5.2 // indirect + github.com/morikuni/aec v1.0.0 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/opencontainers/go-digest v1.0.0 // indirect + github.com/opencontainers/image-spec v1.1.1 // indirect github.com/paulmach/orb v0.11.1 // indirect github.com/pierrec/lz4/v4 v4.1.22 // indirect + github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect + github.com/pressly/goose/v3 v3.24.3 // indirect + github.com/prometheus/client_golang v1.23.0 // indirect github.com/prometheus/client_model v0.6.2 // indirect github.com/prometheus/common v0.65.0 // indirect github.com/prometheus/procfs v0.16.1 // indirect github.com/rivo/uniseg v0.4.7 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/segmentio/asm v1.2.0 // indirect + github.com/sethvargo/go-retry v0.3.0 // indirect + github.com/shirou/gopsutil/v4 v4.25.5 // indirect github.com/shopspring/decimal v1.4.0 // indirect + github.com/sirupsen/logrus v1.9.3 // indirect github.com/spf13/cast v1.7.0 // indirect github.com/swaggo/files/v2 v2.0.2 // indirect + github.com/testcontainers/testcontainers-go v0.38.0 // indirect + github.com/testcontainers/testcontainers-go/modules/clickhouse v0.38.0 // indirect github.com/tidwall/gjson v1.18.0 // indirect github.com/tidwall/match v1.1.1 // indirect github.com/tidwall/pretty v1.2.1 // indirect github.com/tidwall/sjson v1.2.5 // indirect + github.com/tklauser/go-sysconf v0.3.15 // indirect + github.com/tklauser/numcpus v0.10.0 // indirect github.com/urfave/cli/v2 v2.27.5 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/fasthttp v1.55.0 // indirect github.com/valyala/tcplisten v1.0.0 // indirect github.com/volatiletech/inflect v0.0.1 // indirect - github.com/volatiletech/sqlboiler/v4 v4.19.1 // indirect github.com/volatiletech/strmangle v0.0.7-0.20240503230658-86517898275a // indirect github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect + github.com/yusufpapurcu/wmi v1.2.4 // indirect + go.opentelemetry.io/auto/sdk v1.1.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 // indirect go.opentelemetry.io/otel v1.37.0 // indirect + go.opentelemetry.io/otel/metric v1.37.0 // indirect go.opentelemetry.io/otel/trace v1.37.0 // indirect - go.uber.org/mock v0.6.0 // indirect + go.uber.org/multierr v1.11.0 // indirect golang.org/x/crypto v0.41.0 // indirect golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6 // indirect golang.org/x/mod v0.27.0 // indirect @@ -102,7 +150,8 @@ require ( golang.org/x/text v0.28.0 // indirect golang.org/x/tools v0.36.0 // indirect golang.org/x/xerrors v0.0.0-20240716161551-93cc26a95ae9 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20250818200422-3122310a409c // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250818200422-3122310a409c // indirect google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.5.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/go.sum b/go.sum index 5738332..b7f4f46 100644 --- a/go.sum +++ b/go.sum @@ -1,6 +1,8 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s= dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= +github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 h1:He8afgbRMd7mFxO99hRNu+6tazq8nFF9lIwo9JFroBk= +github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8= github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEKWjV8V+WSxDXJ4NFATAsZjh8iIbsQIg= github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= @@ -12,8 +14,8 @@ github.com/DATA-DOG/go-sqlmock v1.4.1 h1:ThlnYciV1iM/V0OSF/dtkqWb6xo5qITT1TJBG1M github.com/DATA-DOG/go-sqlmock v1.4.1/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= github.com/DIMO-Network/clickhouse-infra v0.0.5 h1:zY1STwb9+vovpYXdjaeakE29pgZPIJLogJA+h3+IrbM= github.com/DIMO-Network/clickhouse-infra v0.0.5/go.mod h1:XS80lhSJNWBWGgZ+m4j7++zFj1wAXfmtV2gJfhGlabQ= -github.com/DIMO-Network/cloudevent v0.1.3 h1:jsQCcVdZuARCobgalrsrV99mjtGlN7igh8f24xqzeDA= -github.com/DIMO-Network/cloudevent v0.1.3/go.mod h1:Aaqfcg9FQod0CbsbPIz+8Ykr0udL0jNpO8AABPqEMaQ= +github.com/DIMO-Network/cloudevent v0.1.4 h1:c6Sq4CyHt05V8OtnEXekUCRGfVuR1pFkJevfiKt1sYM= +github.com/DIMO-Network/cloudevent v0.1.4/go.mod h1:Q2QpMEDYJ+VX0lz9SK2EUFxkuddV1XeF4aQ8LfegB68= github.com/DIMO-Network/server-garage v0.0.4 h1:K0wnQ2vJAtyK8lbINxKneVyqELORAu4Ut1ePZ3yb8cI= github.com/DIMO-Network/server-garage v0.0.4/go.mod h1:bdj0wKTjozjjD2w7Htk/rnzOv+jH+iBrhCuXOP/H00s= github.com/DIMO-Network/shared v1.0.7 h1:LfSgsqJ6R7EUyfo2GTfuhrCpoDcweJqe7eVOa4j7Xbo= @@ -80,10 +82,14 @@ github.com/cpuguy83/dockercfg v0.3.2/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHf github.com/cpuguy83/go-md2man/v2 v2.0.5 h1:ZtcqGrnekaHpVLArFSe4HK5DoKx1T0rq2DwVB0alcyc= github.com/cpuguy83/go-md2man/v2 v2.0.5/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= +github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/decred/dcrd/crypto/blake256 v1.0.1 h1:7PltbUIQB7u/FfZ39+DGa/ShuMyJ5ilcvdfma9wOH6Y= +github.com/decred/dcrd/crypto/blake256 v1.0.1/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 h1:rpfIENRNNilwHwZeG5+P150SMrnNEcHYvcCuK6dPZSg= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0= github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= @@ -94,6 +100,8 @@ github.com/docker/go-connections v0.6.0 h1:LlMG9azAe1TqfR7sO+NJttz1gy6KO7VJBh+pM github.com/docker/go-connections v0.6.0/go.mod h1:AahvXYshr6JgfUJGdDCs2b5EZG/vmaMAntpSFH5BFKE= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= +github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/ebitengine/purego v0.8.4 h1:CF7LEKg5FFOsASUj0+QwaXf8Ht6TlFxg09+S9wz0omw= github.com/ebitengine/purego v0.8.4/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= @@ -114,10 +122,12 @@ github.com/go-faster/errors v0.7.1 h1:MkJTnDoEdi9pDabt1dpWf7AA8/BaSYZqibYyhZ20AY github.com/go-faster/errors v0.7.1/go.mod h1:5ySTjWFiphBs07IKuiL69nxdfd5+fzh1u7FPGZP2quo= github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= @@ -166,6 +176,8 @@ github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.2 h1:sGm2vDRFUrQJO/Veii4h4z github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.2/go.mod h1:wd1YpapPLivG6nQgbf7ZkG1hhSOXDhhn4MLTknx2aAc= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92BcuyuQ/YW4NSIpoGtfXNho= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 h1:YBftPWNWd4WwGqtY2yeZL2ef8rHAxPBD8KFhJpmcqms= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0/go.mod h1:YN5jB8ie0yfIUg6VvR9Kz84aCaG7AsGZnLjhHbUqwPg= github.com/holiman/uint256 v1.3.2 h1:a9EgMPSC1AAaj1SZL5zIQD3WbwTuHrMGOerLjGmM/TA= github.com/holiman/uint256 v1.3.2/go.mod h1:EOMSn4q6Nyt9P6efbI3bueV4e1b3dGlUCXeiRV4ng7E= github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= @@ -214,6 +226,8 @@ github.com/moby/go-archive v0.1.0 h1:Kk/5rdW/g+H8NHdJW2gsXyZ7UnzvJNOy6VKJqueWdcQ github.com/moby/go-archive v0.1.0/go.mod h1:G9B+YoujNohJmrIYFBpSd54GTUB4lt9S+xVQvsJyFuo= github.com/moby/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkVGiPk= github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= +github.com/moby/sys/atomicwriter v0.1.0 h1:kw5D/EqkBwsBFi0ss9v1VG3wIkVhzGvLklJ+w3A14Sw= +github.com/moby/sys/atomicwriter v0.1.0/go.mod h1:Ul8oqv2ZMNHOceF643P6FKPXeCmYtlQMvpizfsSoaWs= github.com/moby/sys/sequential v0.6.0 h1:qrx7XFUd/5DxtqcoH1h438hF5TmOvzC/lspjy7zgvCU= github.com/moby/sys/sequential v0.6.0/go.mod h1:uyv8EUTrca5PnDsdMGXhZe6CCe8U/UiTWd+lL+7b/Ko= github.com/moby/sys/user v0.4.0 h1:jhcMKit7SA80hivmFJcbB1vqmw//wU61Zdui2eQXuMs= @@ -227,6 +241,8 @@ github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4= +github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= @@ -256,11 +272,13 @@ github.com/prometheus/common v0.65.0 h1:QDwzd+G1twt//Kwj/Ww6E9FQq1iVMmODnILtW1t2 github.com/prometheus/common v0.65.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8= github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg= github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= -github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= -github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= +github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= +github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0= github.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY= github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6wYQ= @@ -270,7 +288,6 @@ github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys= github.com/segmentio/asm v1.2.0/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs= github.com/sethvargo/go-retry v0.3.0 h1:EEt31A35QhrcRZtrYFDTBg91cqZVnFL2navjDrah2SE= github.com/sethvargo/go-retry v0.3.0/go.mod h1:mNX17F0C/HguQMyMyJxcnU471gOZGxCLyYaFyAZraas= -github.com/shirou/gopsutil v3.21.11+incompatible h1:+1+c1VGhc88SSonWP6foOcLhvnKlUeu/erjjvaPEYiI= github.com/shirou/gopsutil/v4 v4.25.5 h1:rtd9piuSMGeU8g1RMXjZs9y9luK5BwtnG7dZaQUJAsc= github.com/shirou/gopsutil/v4 v4.25.5/go.mod h1:PfybzyydfZcN+JMMjkF6Zb8Mq1A/VcogFFg7hj50W9c= github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k= @@ -282,13 +299,15 @@ github.com/spf13/cast v1.7.0 h1:ntdiHjuueXFgm5nzDRdOS4yfT43P5Fnud6DH50rz/7w= github.com/spf13/cast v1.7.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= -github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/swaggo/files/v2 v2.0.2 h1:Bq4tgS/yxLB/3nwOMcul5oLEUKa877Ykgz3CJMVbQKU= github.com/swaggo/files/v2 v2.0.2/go.mod h1:TVqetIzZsO9OhHX1Am9sRf9LdrFZqoK49N37KON/jr0= github.com/swaggo/swag v1.16.6 h1:qBNcx53ZaX+M5dxVyTrgQ0PJ/ACK+NzhwcbieTt+9yI= @@ -349,6 +368,10 @@ go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 h1:sbiXRND go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0/go.mod h1:69uWxva0WgAA/4bu2Yy70SLDBwZXuQ6PbBpbsa5iZrQ= go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ= go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0 h1:1fTNlAIJZGWLP5FVu0fikVry1IsiUnXjf7QFvoNN3Xw= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0/go.mod h1:zjPK58DtkqQFn+YUMbx0M2XV3QgKU0gS9LeGohREyK4= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0 h1:IeMeyr1aBvBiPVYihXIaeIZba6b8E1bYp7lbdxK8CQg= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0/go.mod h1:oVdCUtjq9MK9BlS7TtucsQwUcXcymNiEDjgDD2jMtZU= go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE= go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E= go.opentelemetry.io/otel/sdk v1.37.0 h1:ItB0QUqnjesGRvNcmAcU0LyvkVyGJ2xftD29bWdDvKI= @@ -357,6 +380,8 @@ go.opentelemetry.io/otel/sdk/metric v1.36.0 h1:r0ntwwGosWGaa0CrSt8cuNuTcccMXERFw go.opentelemetry.io/otel/sdk/metric v1.36.0/go.mod h1:qTNOhFDfKRwX0yXOqJYegL5WRaW376QbB7P4Pb0qva4= go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4= go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0= +go.opentelemetry.io/proto/otlp v1.5.0 h1:xJvq7gMzB31/d406fB8U5CBdyQGw4P399D1aQWU/3i4= +go.opentelemetry.io/proto/otlp v1.5.0/go.mod h1:keN8WnHxOy8PG0rQZjJJ5A2ebUoafqWp0eVQ4yIXvJ4= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= @@ -409,24 +434,33 @@ golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.34.0 h1:O/2T7POpk0ZZ7MAzMeWFSg6S5IpWd/RXDlM9hgM3DR4= +golang.org/x/term v0.34.0/go.mod h1:5jC53AEywhIVebHgPVeg0mj8OD3VO9OzclacVrqpaAw= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng= golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU= +golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY= +golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= @@ -449,8 +483,10 @@ google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7 google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a h1:v2PbRU4K3llS09c7zodFpNePeamkAwG3mPrAery9VeE= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= +google.golang.org/genproto/googleapis/api v0.0.0-20250818200422-3122310a409c h1:AtEkQdl5b6zsybXcbz00j1LwNodDuH6hVifIaNqk7NQ= +google.golang.org/genproto/googleapis/api v0.0.0-20250818200422-3122310a409c/go.mod h1:ea2MjsO70ssTfCjiwHgI0ZFqcw45Ksuk2ckf9G468GA= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250818200422-3122310a409c h1:qXWI/sQtv5UKboZ/zUk7h+mrf/lXORyI+n9DKDAusdg= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250818200422-3122310a409c/go.mod h1:gw1tLEfykwDz2ET4a12jcXt4couGAm7IwsVaTy0Sflo= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= @@ -462,8 +498,8 @@ google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.5.1 h1:F29+wU6Ee6qgu9TddPgooOda google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.5.1/go.mod h1:5KF+wpkbTSbGcR9zteSqZV6fqFOWBl4Yde8En8MryZA= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.36.7 h1:IgrO7UwFQGJdRNXH/sQux4R1Dj1WAKcLElzeeRaXV2A= -google.golang.org/protobuf v1.36.7/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= +google.golang.org/protobuf v1.36.8 h1:xHScyCOEuuwZEc6UtSOvPbAT4zRh0xcNRYekJwfqyMc= +google.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -478,7 +514,17 @@ gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q= +gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +modernc.org/libc v1.65.0 h1:e183gLDnAp9VJh6gWKdTy0CThL9Pt7MfcR/0bgb7Y1Y= +modernc.org/libc v1.65.0/go.mod h1:7m9VzGq7APssBTydds2zBcxGREwvIGpuUBaKTXdm2Qs= +modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU= +modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg= +modernc.org/memory v1.10.0 h1:fzumd51yQ1DxcOxSO+S6X7+QTuVU+n8/Aj7swYjFfC4= +modernc.org/memory v1.10.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw= +modernc.org/sqlite v1.37.0 h1:s1TMe7T3Q3ovQiK2Ouz4Jwh7dw4ZDqbebSDTlSJdfjI= +modernc.org/sqlite v1.37.0/go.mod h1:5YiWv+YviqGMuGw4V+PNplcyaJ5v+vQd7TQOgkACoJM= sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= diff --git a/internal/fetch/fetch.go b/internal/fetch/fetch.go index bd314c1..de3100d 100644 --- a/internal/fetch/fetch.go +++ b/internal/fetch/fetch.go @@ -9,7 +9,7 @@ import ( "sync" "github.com/DIMO-Network/cloudevent" - "github.com/DIMO-Network/cloudevent/pkg/clickhouse/eventrepo" + "github.com/DIMO-Network/fetch-api/pkg/eventrepo" "github.com/aws/aws-sdk-go-v2/service/s3/types" "golang.org/x/sync/errgroup" ) diff --git a/internal/fetch/httphandler/httphandler.go b/internal/fetch/httphandler/httphandler.go index d9f1657..9d6a57b 100644 --- a/internal/fetch/httphandler/httphandler.go +++ b/internal/fetch/httphandler/httphandler.go @@ -1,4 +1,4 @@ -// Packagge httphandler provides the HTTP handler for the fetch service. +// Package httphandler provides the HTTP handler for the fetch service. package httphandler import ( @@ -12,12 +12,15 @@ import ( "github.com/ClickHouse/clickhouse-go/v2" "github.com/DIMO-Network/cloudevent" - "github.com/DIMO-Network/cloudevent/pkg/clickhouse/eventrepo" "github.com/DIMO-Network/fetch-api/internal/fetch" + "github.com/DIMO-Network/fetch-api/pkg/eventrepo" + "github.com/DIMO-Network/fetch-api/pkg/grpc" "github.com/DIMO-Network/server-garage/pkg/richerrors" "github.com/aws/aws-sdk-go-v2/service/s3" "github.com/ethereum/go-ethereum/common" "github.com/gofiber/fiber/v2" + "google.golang.org/protobuf/types/known/timestamppb" + "google.golang.org/protobuf/types/known/wrapperspb" ) const ( @@ -44,17 +47,29 @@ type searchParams struct { Limit int `query:"limit"` } -func (s *searchParams) toSearchOptions(subject cloudevent.ERC721DID) *eventrepo.SearchOptions { - encodedSubject := subject.String() - return &eventrepo.SearchOptions{ - ID: s.ID, - Subject: &encodedSubject, - Type: s.Type, - Source: s.Source, - Producer: s.Producer, - Before: s.Before, - After: s.After, +func (s *searchParams) toSearchOptions(subject cloudevent.ERC721DID) *grpc.SearchOptions { + opts := &grpc.SearchOptions{} + if s.ID != nil { + opts.Id = &wrapperspb.StringValue{Value: *s.ID} } + opts.Subject = &wrapperspb.StringValue{Value: subject.String()} + + if s.Type != nil { + opts.Type = &wrapperspb.StringValue{Value: *s.Type} + } + if s.Source != nil { + opts.Source = &wrapperspb.StringValue{Value: *s.Source} + } + if s.Producer != nil { + opts.Producer = &wrapperspb.StringValue{Value: *s.Producer} + } + if !s.Before.IsZero() { + opts.Before = ×tamppb.Timestamp{Seconds: s.Before.Unix()} + } + if !s.After.IsZero() { + opts.After = ×tamppb.Timestamp{Seconds: s.After.Unix()} + } + return opts } // NewHandler creates a new Handler instance. diff --git a/internal/fetch/rpc/rpc.go b/internal/fetch/rpc/rpc.go index 7ddeed9..bdb1aeb 100644 --- a/internal/fetch/rpc/rpc.go +++ b/internal/fetch/rpc/rpc.go @@ -6,18 +6,16 @@ import ( "database/sql" "encoding/json" "errors" - "time" "github.com/ClickHouse/clickhouse-go/v2" "github.com/DIMO-Network/cloudevent" - "github.com/DIMO-Network/cloudevent/pkg/clickhouse/eventrepo" "github.com/DIMO-Network/fetch-api/internal/fetch" + "github.com/DIMO-Network/fetch-api/pkg/eventrepo" "github.com/DIMO-Network/fetch-api/pkg/grpc" "github.com/aws/aws-sdk-go-v2/service/s3/types" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" "google.golang.org/protobuf/types/known/timestamppb" - "google.golang.org/protobuf/types/known/wrapperspb" ) // Server is used to implement grpc.IndexRepoServiceServer. @@ -37,8 +35,15 @@ func NewServer(chConn clickhouse.Conn, objGetter eventrepo.ObjectGetter, buckets // GetLatestIndex translates the gRPC call to the indexrepo type and returns the latest index for the given options. func (s *Server) GetLatestIndex(ctx context.Context, req *grpc.GetLatestIndexRequest) (*grpc.GetLatestIndexResponse, error) { - options := translateSearchOptions(req.GetOptions()) - index, err := s.eventService.GetLatestIndex(ctx, options) + var index cloudevent.CloudEvent[eventrepo.ObjectInfo] + var err error + + if req.GetAdvancedOptions() != nil { + index, err = s.eventService.GetLatestIndexAdvanced(ctx, req.GetAdvancedOptions()) + } else { + index, err = s.eventService.GetLatestIndex(ctx, req.GetOptions()) + } + if err != nil { if errors.Is(err, sql.ErrNoRows) { return nil, status.Errorf(codes.NotFound, "no index key Found: %v", err) @@ -55,10 +60,17 @@ func (s *Server) GetLatestIndex(ctx context.Context, req *grpc.GetLatestIndexReq }, nil } -// ListIndex translates the gRPC call to the indexrepo type and fetches index keys for the given options. -func (s *Server) ListIndex(ctx context.Context, req *grpc.ListIndexesRequest) (*grpc.ListIndexesResponse, error) { - options := translateSearchOptions(req.GetOptions()) - indexObjs, err := s.eventService.ListIndexes(ctx, int(req.GetLimit()), options) +// ListIndexes translates the gRPC call to the indexrepo type and fetches index keys for the given options. +func (s *Server) ListIndexes(ctx context.Context, req *grpc.ListIndexesRequest) (*grpc.ListIndexesResponse, error) { + var indexObjs []cloudevent.CloudEvent[eventrepo.ObjectInfo] + var err error + + if req.GetAdvancedOptions() != nil { + indexObjs, err = s.eventService.ListIndexesAdvanced(ctx, int(req.GetLimit()), req.GetAdvancedOptions()) + } else { + indexObjs, err = s.eventService.ListIndexes(ctx, int(req.GetLimit()), req.GetOptions()) + } + if err != nil { if errors.Is(err, sql.ErrNoRows) { return nil, status.Errorf(codes.NotFound, "no index key Found: %v", err) @@ -79,8 +91,15 @@ func (s *Server) ListIndex(ctx context.Context, req *grpc.ListIndexesRequest) (* // ListCloudEvents translates the gRPC call to the indexrepo type and fetches data for the given options. func (s *Server) ListCloudEvents(ctx context.Context, req *grpc.ListCloudEventsRequest) (*grpc.ListCloudEventsResponse, error) { - options := translateSearchOptions(req.GetOptions()) - metaList, err := s.eventService.ListIndexes(ctx, int(req.GetLimit()), options) + var metaList []cloudevent.CloudEvent[eventrepo.ObjectInfo] + var err error + + if req.GetAdvancedOptions() != nil { + metaList, err = s.eventService.ListIndexesAdvanced(ctx, int(req.GetLimit()), req.GetAdvancedOptions()) + } else { + metaList, err = s.eventService.ListIndexes(ctx, int(req.GetLimit()), req.GetOptions()) + } + if err != nil { if errors.Is(err, sql.ErrNoRows) { return nil, status.Errorf(codes.NotFound, "no index keys Found: %v", err) @@ -101,8 +120,15 @@ func (s *Server) ListCloudEvents(ctx context.Context, req *grpc.ListCloudEventsR // GetLatestCloudEvent translates the gRPC call to the indexrepo type and fetches the latest data for the given options. func (s *Server) GetLatestCloudEvent(ctx context.Context, req *grpc.GetLatestCloudEventRequest) (*grpc.GetLatestCloudEventResponse, error) { - options := translateSearchOptions(req.GetOptions()) - metdata, err := s.eventService.GetLatestIndex(ctx, options) + var metdata cloudevent.CloudEvent[eventrepo.ObjectInfo] + var err error + + if req.GetAdvancedOptions() != nil { + metdata, err = s.eventService.GetLatestIndexAdvanced(ctx, req.GetAdvancedOptions()) + } else { + metdata, err = s.eventService.GetLatestIndex(ctx, req.GetOptions()) + } + if err != nil { if errors.Is(err, sql.ErrNoRows) { return nil, status.Errorf(codes.NotFound, "no index key Found: %v", err) @@ -143,53 +169,6 @@ func (s *Server) ListCloudEventsFromIndex(ctx context.Context, req *grpc.ListClo return &grpc.ListCloudEventsFromKeysResponse{CloudEvents: dataObjects}, nil } -// translateProtoToSearchOptions translates a SearchOptions proto message to the Go SearchOptions type. -func translateSearchOptions(protoOptions *grpc.SearchOptions) *eventrepo.SearchOptions { - if protoOptions == nil { - return nil - } - - // Converting after timestamp from proto to Go time - var after time.Time - if protoOptions.GetAfter() != nil { - after = protoOptions.GetAfter().AsTime() - } - - // Converting before timestamp from proto to Go time - var before time.Time - if protoOptions.GetBefore() != nil { - before = protoOptions.GetBefore().AsTime() - } - - // Converting boolean value for timestamp ascending - var timestampAsc bool - if protoOptions.GetTimestampAsc() != nil { - timestampAsc = protoOptions.GetTimestampAsc().GetValue() - } - - return &eventrepo.SearchOptions{ - After: after, - Before: before, - TimestampAsc: timestampAsc, - Type: getStringValue(protoOptions.GetType()), - DataVersion: getStringValue(protoOptions.GetDataVersion()), - Subject: getStringValue(protoOptions.GetSubject()), - Source: getStringValue(protoOptions.GetSource()), - Producer: getStringValue(protoOptions.GetProducer()), - Extras: getStringValue(protoOptions.GetExtras()), - ID: getStringValue(protoOptions.GetId()), - } -} - -// getStringValue extracts the value from a wrappersgrpc.StringValue, returning an empty string if nil. -func getStringValue(protoStr *wrapperspb.StringValue) *string { - if protoStr == nil { - return nil - } - val := protoStr.GetValue() - return &val -} - func cloudEventHeaderToProto(event *cloudevent.CloudEventHeader) *grpc.CloudEventHeader { if event == nil { return nil diff --git a/pkg/eventrepo/db_test.go b/pkg/eventrepo/db_test.go new file mode 100644 index 0000000..d0af874 --- /dev/null +++ b/pkg/eventrepo/db_test.go @@ -0,0 +1,70 @@ +package eventrepo_test + +import ( + "context" + "sync" + "sync/atomic" + "testing" + + "github.com/ClickHouse/clickhouse-go/v2" + chconfig "github.com/DIMO-Network/clickhouse-infra/pkg/connect/config" + "github.com/DIMO-Network/clickhouse-infra/pkg/container" + "github.com/DIMO-Network/cloudevent" + chindexer "github.com/DIMO-Network/cloudevent/pkg/clickhouse" + "github.com/DIMO-Network/cloudevent/pkg/clickhouse/migrations" + "github.com/stretchr/testify/require" +) + +type TestContainer struct { + container *container.Container + onceSetup sync.Once + refs atomic.Int64 +} + +var globalTestContainer TestContainer + +func (tc *TestContainer) TeardownIfLastTest(t *testing.T) { + tc.refs.Add(1) + t.Cleanup(func() { + refs := tc.refs.Add(-1) + if refs != 0 { + return + } + tc.container.Terminate(context.Background()) + globalTestContainer.onceSetup = sync.Once{} + globalTestContainer.refs = atomic.Int64{} + globalTestContainer.container = nil + }) +} + +// setupClickHouseContainer starts a ClickHouse container for testing and returns the connection. +func setupClickHouseContainer(t *testing.T) *container.Container { + globalTestContainer.onceSetup.Do(func() { + ctx := context.Background() + settings := chconfig.Settings{ + User: "default", + Database: "dimo", + } + + chContainer, err := container.CreateClickHouseContainer(ctx, settings) + require.NoError(t, err) + + chDB, err := chContainer.GetClickhouseAsDB() + require.NoError(t, err) + + err = migrations.RunGoose(ctx, []string{"up"}, chDB) + require.NoError(t, err) + globalTestContainer.container = chContainer + }) + globalTestContainer.TeardownIfLastTest(t) + return globalTestContainer.container +} + +// insertTestData inserts test data into ClickHouse. +func insertTestData(t *testing.T, ctx context.Context, conn clickhouse.Conn, index *cloudevent.CloudEventHeader) string { + values := chindexer.CloudEventToSlice(index) + + err := conn.Exec(ctx, chindexer.InsertStmt, values...) + require.NoError(t, err) + return values[len(values)-1].(string) +} diff --git a/pkg/eventrepo/event_repo_test.go b/pkg/eventrepo/event_repo_test.go new file mode 100644 index 0000000..003c7a0 --- /dev/null +++ b/pkg/eventrepo/event_repo_test.go @@ -0,0 +1,794 @@ +//go:generate go tool mockgen -source=./eventrepo.go -destination=eventrepo_mock_test.go -package=eventrepo_test +package eventrepo_test + +import ( + "bytes" + "context" + "encoding/json" + "io" + "log" + "math/big" + "testing" + "time" + + "github.com/DIMO-Network/cloudevent" + chindexer "github.com/DIMO-Network/cloudevent/pkg/clickhouse" + "github.com/DIMO-Network/fetch-api/pkg/eventrepo" + "github.com/DIMO-Network/fetch-api/pkg/grpc" + "github.com/aws/aws-sdk-go-v2/service/s3" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.uber.org/mock/gomock" + "google.golang.org/protobuf/types/known/timestamppb" + "google.golang.org/protobuf/types/known/wrapperspb" +) + +var dataType = "small" + +// TestGetLatestIndexKey tests the GetLatestIndexKey function. +func TestGetLatestIndexKey(t *testing.T) { + t.Parallel() + chContainer := setupClickHouseContainer(t) + + // Insert test data + conn, err := chContainer.GetClickHouseAsConn() + require.NoError(t, err) + contractAddr := randAddress() + device1TokenID := big.NewInt(1234567890) + device2TokenID := big.NewInt(976543210) + ctx := context.Background() + now := time.Now() + + // Create test indices + eventIdx1 := &cloudevent.CloudEventHeader{ + Subject: cloudevent.ERC721DID{ + ChainID: 153, + ContractAddress: contractAddr, + TokenID: device1TokenID, + }.String(), + DataVersion: dataType, + Time: now.Add(-1 * time.Hour), + } + + eventIdx2 := &cloudevent.CloudEventHeader{ + Subject: cloudevent.ERC721DID{ + ChainID: 153, + ContractAddress: contractAddr, + TokenID: device1TokenID, + }.String(), + DataVersion: dataType, + Time: now, + } + + // Insert test data + _ = insertTestData(t, ctx, conn, eventIdx1) + indexKey2 := insertTestData(t, ctx, conn, eventIdx2) + + tests := []struct { + name string + subject cloudevent.ERC721DID + expectedKey string + expectedError bool + }{ + { + name: "valid latest object", + subject: cloudevent.ERC721DID{ + ChainID: 153, + ContractAddress: contractAddr, + TokenID: device1TokenID, + }, + expectedKey: indexKey2, + }, + { + name: "no records", + subject: cloudevent.ERC721DID{ + ChainID: 153, + ContractAddress: contractAddr, + TokenID: device2TokenID, + }, + expectedError: true, + }, + } + + indexService := eventrepo.New(conn, nil) + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + opts := &grpc.SearchOptions{ + DataVersion: &wrapperspb.StringValue{Value: dataType}, + Subject: &wrapperspb.StringValue{Value: tt.subject.String()}, + } + metadata, err := indexService.GetLatestIndex(context.Background(), opts) + + if tt.expectedError { + require.Error(t, err) + } else { + require.NoError(t, err) + require.Equal(t, tt.expectedKey, metadata.Data.Key) + } + }) + } +} + +// TestGetDataFromIndex tests the GetDataFromIndex function. +func TestGetDataFromIndex(t *testing.T) { + t.Parallel() + chContainer := setupClickHouseContainer(t) + contractAddr := randAddress() + device1TokenID := big.NewInt(1234567890) + device2TokenID := big.NewInt(976543210) + + conn, err := chContainer.GetClickHouseAsConn() + require.NoError(t, err) + ctx := context.Background() + + eventIdx := &cloudevent.CloudEventHeader{ + Subject: cloudevent.ERC721DID{ + ChainID: 153, + ContractAddress: contractAddr, + TokenID: device1TokenID, + }.String(), + DataVersion: dataType, + Time: time.Now().Add(-1 * time.Hour), + } + + _ = insertTestData(t, ctx, conn, eventIdx) + + tests := []struct { + name string + subject cloudevent.ERC721DID + expectedContent []byte + expectedError bool + }{ + { + name: "valid object content", + subject: cloudevent.ERC721DID{ + ChainID: 153, + ContractAddress: contractAddr, + TokenID: device1TokenID, + }, + expectedContent: []byte(`{"vin": "1HGCM82633A123456"}`), + }, + { + name: "no records", + subject: cloudevent.ERC721DID{ + ChainID: 153, + ContractAddress: contractAddr, + TokenID: device2TokenID, + }, + expectedError: true, + }, + } + + ctrl := gomock.NewController(t) + mockS3Client := NewMockObjectGetter(ctrl) + content := []byte(`{"vin": "1HGCM82633A123456"}`) + mockS3Client.EXPECT().GetObject(gomock.Any(), gomock.Any(), gomock.Any()).Return(&s3.GetObjectOutput{ + Body: io.NopCloser(bytes.NewReader(content)), + ContentLength: ref(int64(len(content))), + }, nil).AnyTimes() + + indexService := eventrepo.New(conn, mockS3Client) + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + opts := &grpc.SearchOptions{ + DataVersion: &wrapperspb.StringValue{Value: dataType}, + Subject: &wrapperspb.StringValue{Value: tt.subject.String()}, + } + content, err := indexService.GetLatestCloudEvent(context.Background(), "test-bucket", opts) + + if tt.expectedError { + require.Error(t, err) + } else { + require.NoError(t, err) + require.EqualValues(t, tt.expectedContent, []byte(content.Data)) + } + }) + } +} + +func TestStoreObject(t *testing.T) { + t.Parallel() + chContainer := setupClickHouseContainer(t) + + conn, err := chContainer.GetClickHouseAsConn() + require.NoError(t, err) + ctx := context.Background() + + ctrl := gomock.NewController(t) + mockS3Client := NewMockObjectGetter(ctrl) + mockS3Client.EXPECT().PutObject(gomock.Any(), gomock.Any(), gomock.Any()).Return(&s3.PutObjectOutput{}, nil).AnyTimes() + + indexService := eventrepo.New(conn, mockS3Client) + + content := []byte(`{"vin": "1HGCM82633A123456"}`) + did := cloudevent.ERC721DID{ + ChainID: 153, + ContractAddress: randAddress(), + TokenID: big.NewInt(123456), + } + + event := cloudevent.CloudEvent[json.RawMessage]{ + CloudEventHeader: cloudevent.CloudEventHeader{ + Subject: did.String(), + Time: time.Now(), + DataVersion: dataType, + }, + Data: content, + } + err = indexService.StoreObject(ctx, "test-bucket", &event.CloudEventHeader, event.Data) + require.NoError(t, err) + + // Verify the data is stored in ClickHouse + opts := &grpc.SearchOptions{ + DataVersion: &wrapperspb.StringValue{Value: dataType}, + Subject: &wrapperspb.StringValue{Value: did.String()}, + } + metadata, err := indexService.GetLatestIndex(ctx, opts) + require.NoError(t, err) + expectedIndexKey := chindexer.CloudEventToObjectKey(&event.CloudEventHeader) + require.NoError(t, err) + require.Equal(t, expectedIndexKey, metadata.Data.Key) +} + +// TestGetData tests the GetData function with different SearchOptions combinations. +func TestGetData(t *testing.T) { + t.Parallel() + chContainer := setupClickHouseContainer(t) + + // Insert test data + conn, err := chContainer.GetClickHouseAsConn() + require.NoError(t, err) + source1 := randAddress() + contractAddr := randAddress() + device1TokenID := big.NewInt(123456) + device2TokenID := big.NewInt(654321) + ctx := context.Background() + now := time.Now() + eventDID := cloudevent.ERC721DID{ + ChainID: 153, + ContractAddress: contractAddr, + TokenID: device1TokenID, + } + eventIdx := cloudevent.CloudEventHeader{ + Subject: eventDID.String(), + Time: now.Add(-4 * time.Hour), + Producer: cloudevent.ERC721DID{ + ChainID: 153, + ContractAddress: contractAddr, + TokenID: device1TokenID, + }.String(), + Type: cloudevent.TypeStatus, + Source: source1.Hex(), + DataVersion: dataType, + } + indexKey1 := insertTestData(t, ctx, conn, &eventIdx) + eventIdx2 := eventIdx + eventIdx2.Time = now.Add(-3 * time.Hour) + indexKey2 := insertTestData(t, ctx, conn, &eventIdx2) + eventIdx3 := eventIdx + eventIdx3.Time = now.Add(-2 * time.Hour) + eventIdx3.Type = cloudevent.TypeFingerprint + indexKey3 := insertTestData(t, ctx, conn, &eventIdx3) + eventIdx4 := eventIdx + eventIdx4.Time = now.Add(-1 * time.Hour) + eventIdx4.DataContentType = "utf-8" + indexKey4 := insertTestData(t, ctx, conn, &eventIdx4) + + tests := []struct { + name string + opts *grpc.SearchOptions + expectedIndexKeys []string + expectedError bool + }{ + { + name: "valid data with address", + opts: &grpc.SearchOptions{ + DataVersion: &wrapperspb.StringValue{Value: dataType}, + Subject: &wrapperspb.StringValue{Value: eventDID.String()}, + }, + expectedIndexKeys: []string{indexKey4, indexKey3, indexKey2, indexKey1}, + }, + { + name: "no records with address", + opts: &grpc.SearchOptions{ + DataVersion: &wrapperspb.StringValue{Value: dataType}, + Subject: &wrapperspb.StringValue{Value: cloudevent.ERC721DID{ + ChainID: 153, + ContractAddress: contractAddr, + TokenID: device2TokenID, + }.String()}, + }, + expectedIndexKeys: nil, + expectedError: true, + }, + { + name: "data within time range", + opts: &grpc.SearchOptions{ + Subject: &wrapperspb.StringValue{Value: eventDID.String()}, + DataVersion: &wrapperspb.StringValue{Value: dataType}, + After: ×tamppb.Timestamp{Seconds: now.Add(-3 * time.Hour).Unix()}, + Before: ×tamppb.Timestamp{Seconds: now.Add(-1 * time.Minute).Unix()}, + }, + expectedIndexKeys: []string{indexKey4, indexKey3}, + }, + { + name: "data with type filter", + opts: &grpc.SearchOptions{ + Subject: &wrapperspb.StringValue{Value: eventDID.String()}, + DataVersion: &wrapperspb.StringValue{Value: dataType}, + Type: &wrapperspb.StringValue{Value: cloudevent.TypeStatus}, + }, + expectedIndexKeys: []string{indexKey4, indexKey2, indexKey1}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ctrl := gomock.NewController(t) + mockS3Client := NewMockObjectGetter(ctrl) + + indexService := eventrepo.New(conn, mockS3Client) + var expectedContent [][]byte + for _, indexKey := range tt.expectedIndexKeys { + mockS3Client.EXPECT().GetObject(gomock.Any(), gomock.Any(), gomock.Any()).DoAndReturn(func(ctx context.Context, params *s3.GetObjectInput, optFns ...func(*s3.Options)) (*s3.GetObjectOutput, error) { + require.Equal(t, *params.Key, indexKey) + quotedKey := `"` + indexKey + `"` + // content := []byte(`{"data":` + quotedKey + `}`) + expectedContent = append(expectedContent, []byte(quotedKey)) + return &s3.GetObjectOutput{ + Body: io.NopCloser(bytes.NewReader([]byte(quotedKey))), + ContentLength: ref(int64(len(quotedKey))), + }, nil + }) + } + events, err := indexService.ListCloudEvents(context.Background(), "test-bucket", 10, tt.opts) + + if tt.expectedError { + require.Error(t, err) + } else { + require.NoError(t, err) + require.Len(t, events, len(expectedContent)) + for i, content := range expectedContent { + require.Equal(t, string(content), string(events[i].Data)) + } + } + }) + } +} + +// TestGetEventWithAllHeaderFields tests retrieving events with all header fields properly populated +func TestGetEventWithAllHeaderFields(t *testing.T) { + t.Parallel() + chContainer := setupClickHouseContainer(t) + + conn, err := chContainer.GetClickHouseAsConn() + require.NoError(t, err) + ctx := context.Background() + now := time.Now().UTC().Truncate(time.Millisecond) + + // Create a DID for the test + contractAddr := randAddress() + deviceTokenID := big.NewInt(1234567890) + eventDID1 := cloudevent.ERC721DID{ + ChainID: 153, + ContractAddress: contractAddr, + TokenID: deviceTokenID, + } + eventDID2 := cloudevent.ERC721DID{ + ChainID: 151, + ContractAddress: contractAddr, + TokenID: deviceTokenID, + } + + // Create event with all header fields populated + fullHeaderEvent := cloudevent.CloudEventHeader{ + ID: "test-id-123456", + Source: "test-source", + Producer: "test-producer", + Subject: eventDID1.String(), + Time: now, + Type: cloudevent.TypeStatus, + DataContentType: "application/json", + DataSchema: "https://example.com/schemas/status.json", + DataVersion: dataType, + SpecVersion: cloudevent.SpecVersion, + Signature: "0x1234567890", + Tags: []string{"tests.tag1", "tests.tag2"}, + Extras: map[string]any{ + "extraField": "extra-value", + }, + } + + fullHeaderEvent2 := fullHeaderEvent + fullHeaderEvent2.Subject = eventDID2.String() + fullHeaderEvent2.Signature = "" + fullHeaderEvent2.Extras = map[string]any{ + "signature": "0x09876543210", + } + + // Insert the event + indexKey := insertTestData(t, ctx, conn, &fullHeaderEvent) + indexKey2 := insertTestData(t, ctx, conn, &fullHeaderEvent2) + + // Setup mock S3 client for retrieving the event data + ctrl := gomock.NewController(t) + mockS3Client := NewMockObjectGetter(ctrl) + eventData := []byte(`{"status": "online", "lastSeen": "2023-01-01T12:00:00Z"}`) + + // Create service + indexService := eventrepo.New(conn, mockS3Client) + + // Test retrieving the event + t.Run("retrieve event with full headers", func(t *testing.T) { + mockS3Client.EXPECT().GetObject(gomock.Any(), gomock.Any(), gomock.Any()).DoAndReturn( + func(ctx context.Context, params *s3.GetObjectInput, optFns ...func(*s3.Options)) (*s3.GetObjectOutput, error) { + // Verify the correct key was requested + require.Equal(t, indexKey, *params.Key) + return &s3.GetObjectOutput{ + Body: io.NopCloser(bytes.NewReader(eventData)), + ContentLength: ref(int64(len(eventData))), + }, nil + }, + ) + opts := &grpc.SearchOptions{ + DataVersion: &wrapperspb.StringValue{Value: dataType}, + Subject: &wrapperspb.StringValue{Value: eventDID1.String()}, + } + + retrievedEvent, err := indexService.GetLatestCloudEvent(ctx, "test-bucket", opts) + require.NoError(t, err) + + // Verify all header fields + assert.Equal(t, fullHeaderEvent.ID, retrievedEvent.ID, "ID mismatch") + assert.Equal(t, fullHeaderEvent.Source, retrievedEvent.Source, "Source mismatch") + assert.Equal(t, fullHeaderEvent.Producer, retrievedEvent.Producer, "Producer mismatch") + assert.Equal(t, fullHeaderEvent.Subject, retrievedEvent.Subject, "Subject mismatch") + assert.Equal(t, fullHeaderEvent.Time.UTC().Truncate(time.Second), retrievedEvent.Time.UTC().Truncate(time.Second), "Time mismatch") + assert.Equal(t, fullHeaderEvent.Type, retrievedEvent.Type, "Type mismatch") + assert.Equal(t, fullHeaderEvent.DataContentType, retrievedEvent.DataContentType, "DataContentType mismatch") + assert.Equal(t, fullHeaderEvent.DataSchema, retrievedEvent.DataSchema, "DataSchema mismatch") + assert.Equal(t, fullHeaderEvent.DataVersion, retrievedEvent.DataVersion, "DataVersion mismatch") + assert.Equal(t, cloudevent.SpecVersion, retrievedEvent.SpecVersion, "SpecVersion mismatch") + assert.Equal(t, fullHeaderEvent.Signature, retrievedEvent.Signature, "Signature mismatch") + assert.Equal(t, fullHeaderEvent.Tags, retrievedEvent.Tags, "Tags mismatch") + + // Verify extras + require.NotNil(t, retrievedEvent.Extras) + require.Equal(t, 2, len(retrievedEvent.Extras)) + require.Equal(t, "extra-value", retrievedEvent.Extras["extraField"]) + require.Equal(t, fullHeaderEvent.Signature, retrievedEvent.Extras["signature"].(string)) + + // Verify data content + require.Equal(t, string(eventData), string(retrievedEvent.Data)) + }) + + t.Run("retrieve event with signature that is originally in extras", func(t *testing.T) { + mockS3Client.EXPECT().GetObject(gomock.Any(), gomock.Any(), gomock.Any()).DoAndReturn( + func(ctx context.Context, params *s3.GetObjectInput, optFns ...func(*s3.Options)) (*s3.GetObjectOutput, error) { + // Verify the correct key was requested + require.Equal(t, indexKey2, *params.Key) + return &s3.GetObjectOutput{ + Body: io.NopCloser(bytes.NewReader(eventData)), + ContentLength: ref(int64(len(eventData))), + }, nil + }, + ) + opts := &grpc.SearchOptions{ + DataVersion: &wrapperspb.StringValue{Value: dataType}, + Subject: &wrapperspb.StringValue{Value: eventDID2.String()}, + } + retrievedEvent, err := indexService.GetLatestCloudEvent(ctx, "test-bucket", opts) + require.NoError(t, err) + + // Verify all header fields + assert.Equal(t, fullHeaderEvent2.ID, retrievedEvent.ID, "ID mismatch") + assert.Equal(t, fullHeaderEvent2.Extras["signature"], retrievedEvent.Signature, "Signature field not set correctly") + assert.Equal(t, fullHeaderEvent2.Extras["signature"], retrievedEvent.Extras["signature"], "Signature field not set correctly in extras") + }) +} + +func ref[T any](x T) *T { + return &x +} + +// TestListIndexesAdvanced tests the ListIndexesAdvanced function with various advanced filter options. +func TestListIndexesAdvanced(t *testing.T) { + t.Parallel() + chContainer := setupClickHouseContainer(t) + + // Insert test data + conn, err := chContainer.GetClickHouseAsConn() + require.NoError(t, err) + contractAddr := randAddress() + device1TokenID := big.NewInt(1234567890) + ctx := context.Background() + now := time.Now() + subject1 := cloudevent.ERC721DID{ + ChainID: 153, + ContractAddress: contractAddr, + TokenID: device1TokenID, + }.String() + + // Create test events with different characteristics for filtering + eventIdx1 := &cloudevent.CloudEventHeader{ + ID: "event-source1-producer1", + Subject: subject1, + Type: cloudevent.TypeStatus, + Source: "source-1", + Producer: "producer-1", + DataVersion: "v1.0", + Time: now.Add(-3 * time.Hour), + Tags: []string{"vehicle", "status"}, + } + + eventIdx2 := &cloudevent.CloudEventHeader{ + ID: "event-source2-producer2", + Subject: subject1, + Type: cloudevent.TypeFingerprint, + Source: "source-2", + Producer: "producer-2", + DataVersion: "v2.0", + Time: now.Add(-2 * time.Hour), + Tags: []string{"security", "fingerprint"}, + } + + eventIdx3 := &cloudevent.CloudEventHeader{ + ID: "event-source1-producer3", + Subject: subject1, + Type: cloudevent.TypeStatus, + Source: "source-1", + Producer: "producer-3", + DataVersion: "v1.0", + Time: now.Add(-1 * time.Hour), + Tags: []string{"vehicle", "telemetry", "status"}, + } + + // Add an event with different tags for testing ArrayFilterOption + eventIdx4 := &cloudevent.CloudEventHeader{ + ID: "event-source3-producer4", + Subject: subject1, + Type: cloudevent.TypeStatus, + Source: "source-3", + Producer: "producer-4", + DataVersion: "v1.0", + Time: now.Add(-30 * time.Minute), + Tags: []string{"telemetry", "realtime"}, + } + + // Insert test data + keyTypeStatusSource1Producer1 := insertTestData(t, ctx, conn, eventIdx1) + keyTypeFingerprintSource2Producer2 := insertTestData(t, ctx, conn, eventIdx2) + keyTypeStatusSource1Producer3 := insertTestData(t, ctx, conn, eventIdx3) + keyTypeStatusSource3Producer4 := insertTestData(t, ctx, conn, eventIdx4) + + indexService := eventrepo.New(conn, nil) + + tests := []struct { + name string + advancedOpts *grpc.AdvancedSearchOptions + expectedIndexKeys []string + expectedError bool + }{ + { + name: "filter by single type status events", + advancedOpts: &grpc.AdvancedSearchOptions{ + Subject: &grpc.StringFilterOption{ + HasAny: []string{subject1}, + }, + Type: &grpc.StringFilterOption{ + HasAny: []string{cloudevent.TypeStatus}, + }, + }, + expectedIndexKeys: []string{keyTypeStatusSource3Producer4, keyTypeStatusSource1Producer3, keyTypeStatusSource1Producer1}, + }, + { + name: "filter by multiple types with OR logic", + advancedOpts: &grpc.AdvancedSearchOptions{ + Subject: &grpc.StringFilterOption{ + HasAny: []string{subject1}, + }, + Type: &grpc.StringFilterOption{ + HasAny: []string{cloudevent.TypeStatus, cloudevent.TypeFingerprint}, + }, + }, + expectedIndexKeys: []string{keyTypeStatusSource3Producer4, keyTypeStatusSource1Producer3, keyTypeFingerprintSource2Producer2, keyTypeStatusSource1Producer1}, + }, + { + name: "filter by type with negation", + advancedOpts: &grpc.AdvancedSearchOptions{ + Subject: &grpc.StringFilterOption{ + HasAny: []string{subject1}, + }, + Type: &grpc.StringFilterOption{ + HasAny: []string{cloudevent.TypeFingerprint}, + Negate: true, + }, + }, + expectedIndexKeys: []string{keyTypeStatusSource3Producer4, keyTypeStatusSource1Producer3, keyTypeStatusSource1Producer1}, + }, + { + name: "filter by source: multiple results", + advancedOpts: &grpc.AdvancedSearchOptions{ + Subject: &grpc.StringFilterOption{ + HasAny: []string{subject1}, + }, + Source: &grpc.StringFilterOption{ + HasAny: []string{"source-1"}, + }, + }, + expectedIndexKeys: []string{keyTypeStatusSource1Producer3, keyTypeStatusSource1Producer1}, + }, + { + name: "combine multiple filters (AND logic)", + advancedOpts: &grpc.AdvancedSearchOptions{ + Subject: &grpc.StringFilterOption{ + HasAny: []string{subject1}, + }, + Type: &grpc.StringFilterOption{ + HasAny: []string{cloudevent.TypeStatus}, + }, + Source: &grpc.StringFilterOption{ + HasAny: []string{"source-1"}, + }, + }, + expectedIndexKeys: []string{keyTypeStatusSource1Producer3, keyTypeStatusSource1Producer1}, + }, + { + name: "OR logic within StringFilterOption", + advancedOpts: &grpc.AdvancedSearchOptions{ + Subject: &grpc.StringFilterOption{ + HasAny: []string{subject1}, + }, + Type: &grpc.StringFilterOption{ + HasAny: []string{cloudevent.TypeAttestation}, + Or: &grpc.StringFilterOption{ + HasAny: []string{cloudevent.TypeStatus}, + Negate: true, + }, + }, + }, + expectedIndexKeys: []string{keyTypeFingerprintSource2Producer2}, + }, + { + name: "no matching records", + advancedOpts: &grpc.AdvancedSearchOptions{ + Subject: &grpc.StringFilterOption{ + HasAny: []string{subject1}, + }, + Type: &grpc.StringFilterOption{ + HasAny: []string{"non-existent-type"}, + }, + }, + expectedIndexKeys: []string{}, + expectedError: true, + }, + { + name: "filter by tags: has any (telemetry)", + advancedOpts: &grpc.AdvancedSearchOptions{ + Subject: &grpc.StringFilterOption{ + HasAny: []string{subject1}, + }, + Tags: &grpc.ArrayFilterOption{ + HasAny: []string{"telemetry"}, + }, + }, + expectedIndexKeys: []string{keyTypeStatusSource3Producer4, keyTypeStatusSource1Producer3}, + }, + { + name: "filter by tags: has all (vehicle AND status)", + advancedOpts: &grpc.AdvancedSearchOptions{ + Subject: &grpc.StringFilterOption{ + HasAny: []string{subject1}, + }, + Tags: &grpc.ArrayFilterOption{ + HasAll: []string{"vehicle", "status"}, + }, + }, + expectedIndexKeys: []string{keyTypeStatusSource1Producer3, keyTypeStatusSource1Producer1}, + }, + { + name: "filter by tags: has any with multiple values", + advancedOpts: &grpc.AdvancedSearchOptions{ + Subject: &grpc.StringFilterOption{ + HasAny: []string{subject1}, + }, + Tags: &grpc.ArrayFilterOption{ + HasAny: []string{"security", "realtime"}, + }, + }, + expectedIndexKeys: []string{keyTypeStatusSource3Producer4, keyTypeFingerprintSource2Producer2}, + }, + { + name: "filter by tags: negated has_any", + advancedOpts: &grpc.AdvancedSearchOptions{ + Subject: &grpc.StringFilterOption{ + HasAny: []string{subject1}, + }, + Tags: &grpc.ArrayFilterOption{ + HasAny: []string{"vehicle"}, + Negate: true, + }, + }, + expectedIndexKeys: []string{keyTypeStatusSource3Producer4, keyTypeFingerprintSource2Producer2}, + }, + { + name: "complex tags filter: has_any OR has_all combination", + advancedOpts: &grpc.AdvancedSearchOptions{ + Subject: &grpc.StringFilterOption{ + HasAny: []string{subject1}, + }, + Tags: &grpc.ArrayFilterOption{ + HasAny: []string{"fingerprint"}, + Or: &grpc.ArrayFilterOption{ + HasAll: []string{"telemetry", "realtime"}, + }, + }, + }, + expectedIndexKeys: []string{keyTypeStatusSource3Producer4, keyTypeFingerprintSource2Producer2}, + }, + { + name: "complex tags filter: has_all with OR chain", + advancedOpts: &grpc.AdvancedSearchOptions{ + Subject: &grpc.StringFilterOption{ + HasAny: []string{subject1}, + }, + Tags: &grpc.ArrayFilterOption{ + HasAll: []string{"vehicle", "telemetry"}, + Or: &grpc.ArrayFilterOption{ + HasAny: []string{"security"}, + }, + }, + }, + expectedIndexKeys: []string{keyTypeStatusSource1Producer3, keyTypeFingerprintSource2Producer2}, + }, + { + name: "complex negated tags with multiple conditions", + advancedOpts: &grpc.AdvancedSearchOptions{ + Subject: &grpc.StringFilterOption{ + HasAny: []string{subject1}, + }, + Tags: &grpc.ArrayFilterOption{ + HasAny: []string{"fingerprint", "telemetry"}, + Negate: true, // Does NOT have fingerprint or telemetry + Or: &grpc.ArrayFilterOption{ + HasAll: []string{"telemetry", "status"}, + }, + }, + }, + expectedIndexKeys: []string{keyTypeStatusSource1Producer3, keyTypeStatusSource1Producer1}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + results, err := indexService.ListIndexesAdvanced(t.Context(), 10, tt.advancedOpts) + if tt.expectedError { + require.Error(t, err, "Expected error but got none") + } else { + require.NoError(t, err, "Unexpected error: %v", err) + require.Len(t, results, len(tt.expectedIndexKeys), "Number of results mismatch") + + // Verify each result matches expected index keys in order + actualKeys := make([]string, len(results)) + for i, result := range results { + actualKeys[i] = result.Data.Key + } + + require.Equal(t, tt.expectedIndexKeys, actualKeys, "Index keys mismatch") + } + }) + } +} + +func randAddress() common.Address { + privateKey, err := crypto.GenerateKey() + if err != nil { + log.Fatalf("Failed to generate private key: %v", err) + } + return crypto.PubkeyToAddress(privateKey.PublicKey) +} diff --git a/pkg/eventrepo/eventrepo.go b/pkg/eventrepo/eventrepo.go new file mode 100644 index 0000000..879d10a --- /dev/null +++ b/pkg/eventrepo/eventrepo.go @@ -0,0 +1,481 @@ +// Package eventrepo contains service code for gettting and managing cloudevent objects. +package eventrepo + +import ( + "bytes" + "context" + "database/sql" + "encoding/json" + "fmt" + "io" + + "github.com/ClickHouse/clickhouse-go/v2" + "github.com/DIMO-Network/cloudevent" + chindexer "github.com/DIMO-Network/cloudevent/pkg/clickhouse" + "github.com/DIMO-Network/fetch-api/pkg/grpc" + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/s3" + "github.com/volatiletech/sqlboiler/v4/drivers" + "github.com/volatiletech/sqlboiler/v4/queries" + "github.com/volatiletech/sqlboiler/v4/queries/qm" + "google.golang.org/protobuf/types/known/wrapperspb" +) + +const tagsColumn = "JSONExtract(extras, 'tags', 'Array(String)')" + +// Service manages and retrieves data messages from indexed objects in S3. +type Service struct { + objGetter ObjectGetter + chConn clickhouse.Conn +} + +// ObjectInfo is the information about the object in S3. +type ObjectInfo struct { + Key string +} + +// ObjectGetter is an interface for getting an object from S3. +type ObjectGetter interface { + GetObject(ctx context.Context, params *s3.GetObjectInput, optFns ...func(*s3.Options)) (*s3.GetObjectOutput, error) + PutObject(ctx context.Context, params *s3.PutObjectInput, optFns ...func(*s3.Options)) (*s3.PutObjectOutput, error) +} + +// New creates a new instance of serviceService. +func New(chConn clickhouse.Conn, objGetter ObjectGetter) *Service { + return &Service{ + objGetter: objGetter, + chConn: chConn, + } +} + +// GetLatestIndex returns the latest cloud event index that matches the given options. +func (s *Service) GetLatestIndex(ctx context.Context, opts *grpc.SearchOptions) (cloudevent.CloudEvent[ObjectInfo], error) { + advancedOpts := convertSearchOptionsToAdvanced(opts) + return s.GetLatestIndexAdvanced(ctx, advancedOpts) +} + +// GetLatestIndexAdvanced returns the latest cloud event index that matches the given advanced options. +func (s *Service) GetLatestIndexAdvanced(ctx context.Context, advancedOpts *grpc.AdvancedSearchOptions) (cloudevent.CloudEvent[ObjectInfo], error) { + if advancedOpts != nil { + advancedOpts.TimestampAsc = wrapperspb.Bool(false) + } + events, err := s.ListIndexesAdvanced(ctx, 1, advancedOpts) + if err != nil { + return cloudevent.CloudEvent[ObjectInfo]{}, err + } + return events[0], nil +} + +// ListIndexes fetches and returns a list of index for cloud events that match the given options. +func (s *Service) ListIndexes(ctx context.Context, limit int, opts *grpc.SearchOptions) ([]cloudevent.CloudEvent[ObjectInfo], error) { + advancedOpts := convertSearchOptionsToAdvanced(opts) + return s.ListIndexesAdvanced(ctx, limit, advancedOpts) +} + +// ListIndexesAdvanced fetches and returns a list of index for cloud events that match the given advanced options. +func (s *Service) ListIndexesAdvanced(ctx context.Context, limit int, advancedOpts *grpc.AdvancedSearchOptions) ([]cloudevent.CloudEvent[ObjectInfo], error) { + order := " DESC" + if advancedOpts != nil && advancedOpts.GetTimestampAsc().GetValue() { + order = " ASC" + } + mods := []qm.QueryMod{ + qm.Select(chindexer.SubjectColumn, + chindexer.TimestampColumn, + chindexer.TypeColumn, + chindexer.IDColumn, + chindexer.SourceColumn, + chindexer.ProducerColumn, + chindexer.DataContentTypeColumn, + chindexer.DataVersionColumn, + chindexer.ExtrasColumn, + chindexer.IndexKeyColumn, + ), + qm.From(chindexer.TableName), + qm.OrderBy(chindexer.TimestampColumn + order), + qm.Limit(limit), + } + + // Apply advanced search options + if advancedOpts != nil { + advancedMods := AdvancedSearchOptionsToQueryMod(advancedOpts) + mods = append(mods, advancedMods...) + } + query, args := newQuery(mods...) + rows, err := s.chConn.Query(ctx, query, args...) + if err != nil { + return nil, fmt.Errorf("failed to get cloud events: %w", err) + } + + var cloudEvents []cloudevent.CloudEvent[ObjectInfo] + var extras string + for rows.Next() { + var event cloudevent.CloudEvent[ObjectInfo] + err = rows.Scan(&event.Subject, &event.Time, &event.Type, &event.ID, &event.Source, &event.Producer, &event.DataContentType, &event.DataVersion, &extras, &event.Data.Key) + if err != nil { + _ = rows.Close() + return nil, fmt.Errorf("failed to scan cloud event: %w", err) + } + if extras != "" && extras != "null" { + if err = json.Unmarshal([]byte(extras), &event.Extras); err != nil { + _ = rows.Close() + return nil, fmt.Errorf("failed to unmarshal extras: %w", err) + } + // Restore non-column fields from extras + chindexer.RestoreNonColumnFields(&event.CloudEventHeader) + } + cloudEvents = append(cloudEvents, event) + } + _ = rows.Close() + if err = rows.Err(); err != nil { + return nil, fmt.Errorf("failed to iterate over cloud events: %w", err) + } + if len(cloudEvents) == 0 { + return nil, fmt.Errorf("no cloud events found %w", sql.ErrNoRows) + } + return cloudEvents, nil +} + +// ListCloudEvents fetches and returns the cloud events that match the given options. +func (s *Service) ListCloudEvents(ctx context.Context, bucketName string, limit int, opts *grpc.SearchOptions) ([]cloudevent.CloudEvent[json.RawMessage], error) { + advancedOpts := convertSearchOptionsToAdvanced(opts) + return s.ListCloudEventsAdvanced(ctx, bucketName, limit, advancedOpts) +} + +// ListCloudEventsAdvanced fetches and returns the cloud events that match the given advanced options. +func (s *Service) ListCloudEventsAdvanced(ctx context.Context, bucketName string, limit int, advancedOpts *grpc.AdvancedSearchOptions) ([]cloudevent.CloudEvent[json.RawMessage], error) { + events, err := s.ListIndexesAdvanced(ctx, limit, advancedOpts) + if err != nil { + return nil, err + } + data, err := s.ListCloudEventsFromIndexes(ctx, events, bucketName) + if err != nil { + return nil, err + } + + return data, nil +} + +// GetLatestCloudEvent fetches and returns the latest cloud event that matches the given options. +func (s *Service) GetLatestCloudEvent(ctx context.Context, bucketName string, opts *grpc.SearchOptions) (cloudevent.CloudEvent[json.RawMessage], error) { + advancedOpts := convertSearchOptionsToAdvanced(opts) + return s.GetLatestCloudEventAdvanced(ctx, bucketName, advancedOpts) +} + +// GetLatestCloudEventAdvanced fetches and returns the latest cloud event that matches the given advanced options. +func (s *Service) GetLatestCloudEventAdvanced(ctx context.Context, bucketName string, advancedOpts *grpc.AdvancedSearchOptions) (cloudevent.CloudEvent[json.RawMessage], error) { + cloudIdx, err := s.GetLatestIndexAdvanced(ctx, advancedOpts) + if err != nil { + return cloudevent.CloudEvent[json.RawMessage]{}, err + } + + data, err := s.GetCloudEventFromIndex(ctx, cloudIdx, bucketName) + if err != nil { + return cloudevent.CloudEvent[json.RawMessage]{}, err + } + + return data, nil +} + +// ListCloudEventsFromIndexes fetches and returns the cloud events for the given index. +func (s *Service) ListCloudEventsFromIndexes(ctx context.Context, indexes []cloudevent.CloudEvent[ObjectInfo], bucketName string) ([]cloudevent.CloudEvent[json.RawMessage], error) { + events := make([]cloudevent.CloudEvent[json.RawMessage], len(indexes)) + var err error + objectsByKeys := map[string][]byte{} + for i := range indexes { + // Some objects have multiple cloud events so we cache the objects to avoid fetching them multiple times. + if obj, ok := objectsByKeys[indexes[i].Data.Key]; ok { + events[i] = cloudevent.CloudEvent[json.RawMessage]{CloudEventHeader: indexes[i].CloudEventHeader, Data: obj} + continue + } + events[i], err = s.GetCloudEventFromIndex(ctx, indexes[i], bucketName) + if err != nil { + return nil, err + } + objectsByKeys[indexes[i].Data.Key] = events[i].Data + } + return events, nil +} + +// GetCloudEventFromIndex fetches and returns the cloud event for the given index. +func (s *Service) GetCloudEventFromIndex(ctx context.Context, index cloudevent.CloudEvent[ObjectInfo], bucketName string) (cloudevent.CloudEvent[json.RawMessage], error) { + rawData, err := s.GetObjectFromKey(ctx, index.Data.Key, bucketName) + if err != nil { + return cloudevent.CloudEvent[json.RawMessage]{}, err + } + return toCloudEvent(&index.CloudEventHeader, rawData), nil +} + +// ListObjectsFromKeys fetches and returns the objects for the given keys. +func (s *Service) ListObjectsFromKeys(ctx context.Context, keys []string, bucketName string) ([][]byte, error) { + data := make([][]byte, len(keys)) + var err error + for i, key := range keys { + data[i], err = s.GetObjectFromKey(ctx, key, bucketName) + if err != nil { + return nil, fmt.Errorf("failed to get data from key '%s': %w", key, err) + } + } + return data, nil +} + +// GetRawObjectFromKey fetches and returns the raw object for the given key without unmarshalling to a cloud event. +func (s *Service) GetObjectFromKey(ctx context.Context, key, bucketName string) ([]byte, error) { + obj, err := s.objGetter.GetObject(ctx, &s3.GetObjectInput{ + Bucket: aws.String(bucketName), + Key: aws.String(key), + }) + if err != nil { + return nil, fmt.Errorf("failed to get object from S3: %w", err) + } + defer obj.Body.Close() //nolint + + data, err := io.ReadAll(obj.Body) + if err != nil { + return nil, fmt.Errorf("failed to read object body: %w", err) + } + return data, nil +} + +// StoreObject stores the given data in S3 with the given cloudevent header. +func (s *Service) StoreObject(ctx context.Context, bucketName string, cloudHeader *cloudevent.CloudEventHeader, data []byte) error { + key := chindexer.CloudEventToObjectKey(cloudHeader) + _, err := s.objGetter.PutObject(ctx, &s3.PutObjectInput{ + Bucket: &bucketName, + Key: &key, + Body: bytes.NewReader(data), + }) + if err != nil { + return fmt.Errorf("failed to store object in S3: %w", err) + } + + values := chindexer.CloudEventToSlice(cloudHeader) + + err = s.chConn.Exec(ctx, chindexer.InsertStmt, values...) + if err != nil { + return fmt.Errorf("failed to store index in ClickHouse: %w", err) + } + + return nil +} + +// toCloudEvent converts the given data to a cloud event with the given header +// if the provided data is already a cloud event we will replace the header with the given one. +func toCloudEvent(dbHdr *cloudevent.CloudEventHeader, data []byte) cloudevent.CloudEvent[json.RawMessage] { + retData := data + event := cloudevent.CloudEvent[json.RawMessage]{} + err := json.Unmarshal(data, &event) + emptyHdr := cloudevent.CloudEventHeader{} + if err == nil && !event.Equals(emptyHdr) { + // if the data is already a cloud event we use the embedded data field + retData = event.Data + } + return cloudevent.CloudEvent[json.RawMessage]{CloudEventHeader: *dbHdr, Data: retData} +} + +func SearchOptionsToQueryMod(opts *grpc.SearchOptions) ([]qm.QueryMod, error) { + if opts == nil { + return nil, nil + } + var mods []qm.QueryMod + if opts.GetId() != nil { + mods = append(mods, qm.Where(chindexer.IDColumn+" = ?", opts.GetId().GetValue())) + } + if opts.GetAfter() != nil { + mods = append(mods, qm.Where(chindexer.TimestampColumn+" > ?", opts.GetAfter().AsTime())) + } + if opts.GetBefore() != nil { + mods = append(mods, qm.Where(chindexer.TimestampColumn+" < ?", opts.GetBefore().AsTime())) + } + if opts.GetType() != nil { + mods = append(mods, qm.Where(chindexer.TypeColumn+" = ?", opts.GetType().GetValue())) + } + if opts.GetDataVersion() != nil { + mods = append(mods, qm.Where(chindexer.DataVersionColumn+" = ?", opts.GetDataVersion().GetValue())) + } + if opts.GetSubject() != nil { + mods = append(mods, qm.Where(chindexer.SubjectColumn+" = ?", opts.GetSubject().GetValue())) + } + if opts.GetSource() != nil { + mods = append(mods, qm.Where(chindexer.SourceColumn+" = ?", opts.GetSource().GetValue())) + } + if opts.GetProducer() != nil { + mods = append(mods, qm.Where(chindexer.ProducerColumn+" = ?", opts.GetProducer().GetValue())) + } + if opts.GetExtras() != nil { + mods = append(mods, qm.Where(chindexer.ExtrasColumn+" = ?", opts.GetExtras().GetValue())) + } + return mods, nil +} + +// convertSearchOptionsToAdvanced converts basic SearchOptions to AdvancedSearchOptions +func convertSearchOptionsToAdvanced(opts *grpc.SearchOptions) *grpc.AdvancedSearchOptions { + if opts == nil { + return nil + } + + advanced := &grpc.AdvancedSearchOptions{ + After: opts.GetAfter(), + Before: opts.GetBefore(), + TimestampAsc: opts.GetTimestampAsc(), + } + + // Convert each field to StringFilterOption with has_any logic + if opts.GetType() != nil { + advanced.Type = &grpc.StringFilterOption{ + HasAny: []string{opts.GetType().GetValue()}, + } + } + if opts.GetDataVersion() != nil { + advanced.DataVersion = &grpc.StringFilterOption{ + HasAny: []string{opts.GetDataVersion().GetValue()}, + } + } + if opts.GetSubject() != nil { + advanced.Subject = &grpc.StringFilterOption{ + HasAny: []string{opts.GetSubject().GetValue()}, + } + } + if opts.GetSource() != nil { + advanced.Source = &grpc.StringFilterOption{ + HasAny: []string{opts.GetSource().GetValue()}, + } + } + if opts.GetProducer() != nil { + advanced.Producer = &grpc.StringFilterOption{ + HasAny: []string{opts.GetProducer().GetValue()}, + } + } + if opts.GetExtras() != nil { + advanced.Extras = &grpc.StringFilterOption{ + HasAny: []string{opts.GetExtras().GetValue()}, + } + } + if opts.GetId() != nil { + advanced.Id = &grpc.StringFilterOption{ + HasAny: []string{opts.GetId().GetValue()}, + } + } + + return advanced +} + +func AdvancedSearchOptionsToQueryMod(opts *grpc.AdvancedSearchOptions) []qm.QueryMod { + if opts == nil { + return nil + } + var mods []qm.QueryMod + + // Handle timestamp filtering (same as SearchOptions) + if opts.GetAfter() != nil { + mods = append(mods, qm.Where(chindexer.TimestampColumn+" > ?", opts.GetAfter().AsTime())) + } + if opts.GetBefore() != nil { + mods = append(mods, qm.Where(chindexer.TimestampColumn+" < ?", opts.GetBefore().AsTime())) + } + + // Handle advanced filtering for each field + if opts.GetType() != nil { + mods = appendStringFilterMods(mods, opts.GetType(), chindexer.TypeColumn) + } + + if opts.GetDataVersion() != nil { + mods = appendStringFilterMods(mods, opts.GetDataVersion(), chindexer.DataVersionColumn) + } + + if opts.GetSubject() != nil { + mods = appendStringFilterMods(mods, opts.GetSubject(), chindexer.SubjectColumn) + } + + if opts.GetSource() != nil { + mods = appendStringFilterMods(mods, opts.GetSource(), chindexer.SourceColumn) + } + + if opts.GetProducer() != nil { + mods = appendStringFilterMods(mods, opts.GetProducer(), chindexer.ProducerColumn) + } + + if opts.GetExtras() != nil { + mods = appendStringFilterMods(mods, opts.GetExtras(), chindexer.ExtrasColumn) + } + + if opts.GetId() != nil { + mods = appendStringFilterMods(mods, opts.GetId(), chindexer.IDColumn) + } + + if opts.GetTags() != nil { + mods = appendArrayFilterMods(mods, opts.GetTags(), tagsColumn) + } + + return mods +} + +// appendStringFilterMods converts a StringFilterOption to query modifications. +func appendStringFilterMods(mods []qm.QueryMod, filter *grpc.StringFilterOption, columnName string) []qm.QueryMod { + if filter == nil { + return nil + } + + // Process has_any (OR logic) + if len(filter.GetHasAny()) > 0 { + if filter.GetNegate() { + mods = append(mods, qm.Where(columnName+" NOT IN ?", filter.GetHasAny())) + } else { + mods = append(mods, qm.WhereIn(columnName+" IN ?", filter.GetHasAny())) + } + } + if filter.GetOr() != nil { + orMods := appendStringFilterMods(nil, filter.GetOr(), columnName) + if len(orMods) > 0 { + mods = append(mods, qm.Or2(qm.Expr(orMods...))) + } + } + return mods +} + +// appendArrayFilterMods converts an ArrayFilterOption to query modifications. +func appendArrayFilterMods(mods []qm.QueryMod, filter *grpc.ArrayFilterOption, columnName string) []qm.QueryMod { + if filter == nil { + return mods + } + var clause string + if filter.GetNegate() { + clause = "NOT " + } + + if len(filter.GetHasAny()) > 0 { + mods = append(mods, qm.Where(clause+"hasAny("+columnName+", ?)", filter.GetHasAny())) + } + if len(filter.GetHasAll()) > 0 { + mods = append(mods, qm.Where(clause+"hasAll("+columnName+", ?)", filter.GetHasAll())) + } + // Process OR condition recursively + if filter.GetOr() != nil { + orMods := appendArrayFilterMods(nil, filter.GetOr(), columnName) + if len(orMods) > 0 { + mods = append(mods, qm.Or2(qm.Expr(orMods...))) + } + } + + return mods +} + +var dialect = drivers.Dialect{ + LQ: '`', + RQ: '`', + UseIndexPlaceholders: false, + UseLastInsertID: false, + UseSchema: false, + UseDefaultKeyword: false, + UseAutoColumns: false, + UseTopClause: false, + UseOutputClause: false, + UseCaseWhenExistsClause: false, +} + +// newQuery initializes a new Query using the passed in QueryMods. +func newQuery(mods ...qm.QueryMod) (string, []any) { + q := &queries.Query{} + queries.SetDialect(q, &dialect) + qm.Apply(q, mods...) + return queries.BuildQuery(q) +} diff --git a/pkg/eventrepo/eventrepo_mock_test.go b/pkg/eventrepo/eventrepo_mock_test.go new file mode 100644 index 0000000..50daa38 --- /dev/null +++ b/pkg/eventrepo/eventrepo_mock_test.go @@ -0,0 +1,82 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: ./eventrepo.go +// +// Generated by this command: +// +// mockgen -source=./eventrepo.go -destination=eventrepo_mock_test.go -package=eventrepo_test +// + +// Package eventrepo_test is a generated GoMock package. +package eventrepo_test + +import ( + context "context" + reflect "reflect" + + s3 "github.com/aws/aws-sdk-go-v2/service/s3" + gomock "go.uber.org/mock/gomock" +) + +// MockObjectGetter is a mock of ObjectGetter interface. +type MockObjectGetter struct { + ctrl *gomock.Controller + recorder *MockObjectGetterMockRecorder + isgomock struct{} +} + +// MockObjectGetterMockRecorder is the mock recorder for MockObjectGetter. +type MockObjectGetterMockRecorder struct { + mock *MockObjectGetter +} + +// NewMockObjectGetter creates a new mock instance. +func NewMockObjectGetter(ctrl *gomock.Controller) *MockObjectGetter { + mock := &MockObjectGetter{ctrl: ctrl} + mock.recorder = &MockObjectGetterMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockObjectGetter) EXPECT() *MockObjectGetterMockRecorder { + return m.recorder +} + +// GetObject mocks base method. +func (m *MockObjectGetter) GetObject(ctx context.Context, params *s3.GetObjectInput, optFns ...func(*s3.Options)) (*s3.GetObjectOutput, error) { + m.ctrl.T.Helper() + varargs := []any{ctx, params} + for _, a := range optFns { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "GetObject", varargs...) + ret0, _ := ret[0].(*s3.GetObjectOutput) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetObject indicates an expected call of GetObject. +func (mr *MockObjectGetterMockRecorder) GetObject(ctx, params any, optFns ...any) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]any{ctx, params}, optFns...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetObject", reflect.TypeOf((*MockObjectGetter)(nil).GetObject), varargs...) +} + +// PutObject mocks base method. +func (m *MockObjectGetter) PutObject(ctx context.Context, params *s3.PutObjectInput, optFns ...func(*s3.Options)) (*s3.PutObjectOutput, error) { + m.ctrl.T.Helper() + varargs := []any{ctx, params} + for _, a := range optFns { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "PutObject", varargs...) + ret0, _ := ret[0].(*s3.PutObjectOutput) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// PutObject indicates an expected call of PutObject. +func (mr *MockObjectGetterMockRecorder) PutObject(ctx, params any, optFns ...any) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]any{ctx, params}, optFns...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PutObject", reflect.TypeOf((*MockObjectGetter)(nil).PutObject), varargs...) +} diff --git a/pkg/grpc/cloudevent.pb.go b/pkg/grpc/cloudevent.pb.go index 64b8aa7..95a72c7 100644 --- a/pkg/grpc/cloudevent.pb.go +++ b/pkg/grpc/cloudevent.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.7 +// protoc-gen-go v1.36.8 // protoc v5.28.3 // source: pkg/grpc/cloudevent.proto @@ -108,7 +108,9 @@ type CloudEventHeader struct { // DataVersion is the controlled by the source of the event and is used to provide information about the data. DataVersion string `protobuf:"bytes,10,opt,name=data_version,json=dataVersion,proto3" json:"data_version,omitempty"` // Extras contains any additional fields that are not part of the CloudEvent excluding the data field. - Extras map[string][]byte `protobuf:"bytes,11,rep,name=extras,proto3" json:"extras,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` + Extras map[string][]byte `protobuf:"bytes,11,rep,name=extras,proto3" json:"extras,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` + // Tags are a list of tags that can be used to filter the events. + Tags []string `protobuf:"bytes,12,rep,name=tags,proto3" json:"tags,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -220,6 +222,13 @@ func (x *CloudEventHeader) GetExtras() map[string][]byte { return nil } +func (x *CloudEventHeader) GetTags() []string { + if x != nil { + return x.Tags + } + return nil +} + var File_pkg_grpc_cloudevent_proto protoreflect.FileDescriptor const file_pkg_grpc_cloudevent_proto_rawDesc = "" + @@ -229,7 +238,7 @@ const file_pkg_grpc_cloudevent_proto_rawDesc = "" + "\n" + "CloudEvent\x124\n" + "\x06header\x18\x01 \x01(\v2\x1c.cloudevent.CloudEventHeaderR\x06header\x12\x12\n" + - "\x04data\x18\x02 \x01(\fR\x04data\"\xc4\x03\n" + + "\x04data\x18\x02 \x01(\fR\x04data\"\xd8\x03\n" + "\x10CloudEventHeader\x12\x0e\n" + "\x02id\x18\x01 \x01(\tR\x02id\x12\x16\n" + "\x06source\x18\x02 \x01(\tR\x06source\x12\x1a\n" + @@ -243,7 +252,8 @@ const file_pkg_grpc_cloudevent_proto_rawDesc = "" + "dataSchema\x12!\n" + "\fdata_version\x18\n" + " \x01(\tR\vdataVersion\x12@\n" + - "\x06extras\x18\v \x03(\v2(.cloudevent.CloudEventHeader.ExtrasEntryR\x06extras\x1a9\n" + + "\x06extras\x18\v \x03(\v2(.cloudevent.CloudEventHeader.ExtrasEntryR\x06extras\x12\x12\n" + + "\x04tags\x18\f \x03(\tR\x04tags\x1a9\n" + "\vExtrasEntry\x12\x10\n" + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + "\x05value\x18\x02 \x01(\fR\x05value:\x028\x01B,Z*github.com/DIMO-Network/fetch-api/pkg/grpcb\x06proto3" diff --git a/pkg/grpc/cloudevent.proto b/pkg/grpc/cloudevent.proto index 13d08ae..1733d57 100644 --- a/pkg/grpc/cloudevent.proto +++ b/pkg/grpc/cloudevent.proto @@ -55,5 +55,8 @@ message CloudEventHeader { // Extras contains any additional fields that are not part of the CloudEvent excluding the data field. map extras = 11; + + // Tags are a list of tags that can be used to filter the events. + repeated string tags = 12; } diff --git a/pkg/grpc/fetch-api.pb.go b/pkg/grpc/fetch-api.pb.go index bebae53..36d58aa 100644 --- a/pkg/grpc/fetch-api.pb.go +++ b/pkg/grpc/fetch-api.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.7 +// protoc-gen-go v1.36.8 // protoc v5.28.3 // source: pkg/grpc/fetch-api.proto @@ -151,6 +151,280 @@ func (x *SearchOptions) GetId() *wrapperspb.StringValue { return nil } +// AdvancedSearchOptions provides flexible filtering options with boolean logic +// to narrow down search results. Each field can use complex filtering operations. +type AdvancedSearchOptions struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Only include events after this timestamp. + After *timestamppb.Timestamp `protobuf:"bytes,1,opt,name=after,proto3" json:"after,omitempty"` + // Only include events before this timestamp. + Before *timestamppb.Timestamp `protobuf:"bytes,2,opt,name=before,proto3" json:"before,omitempty"` + // Whether to sort results in ascending order by timestamp. + TimestampAsc *wrapperspb.BoolValue `protobuf:"bytes,3,opt,name=timestamp_asc,json=timestampAsc,proto3" json:"timestamp_asc,omitempty"` + // Advanced filtering for event type + Type *StringFilterOption `protobuf:"bytes,4,opt,name=type,proto3" json:"type,omitempty"` + // Advanced filtering for data version + DataVersion *StringFilterOption `protobuf:"bytes,5,opt,name=data_version,json=dataVersion,proto3" json:"data_version,omitempty"` + // Advanced filtering for event subject + Subject *StringFilterOption `protobuf:"bytes,6,opt,name=subject,proto3" json:"subject,omitempty"` + // Advanced filtering for source identifier + Source *StringFilterOption `protobuf:"bytes,8,opt,name=source,proto3" json:"source,omitempty"` + // Advanced filtering for producer + Producer *StringFilterOption `protobuf:"bytes,9,opt,name=producer,proto3" json:"producer,omitempty"` + // Advanced filtering for extras (JSON string from cloud event header) + Extras *StringFilterOption `protobuf:"bytes,10,opt,name=extras,proto3" json:"extras,omitempty"` + // Advanced filtering for event ID + Id *StringFilterOption `protobuf:"bytes,11,opt,name=id,proto3" json:"id,omitempty"` + // Tags are a list of tags that can be used to filter the events. + Tags *ArrayFilterOption `protobuf:"bytes,12,opt,name=tags,proto3" json:"tags,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *AdvancedSearchOptions) Reset() { + *x = AdvancedSearchOptions{} + mi := &file_pkg_grpc_fetch_api_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *AdvancedSearchOptions) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AdvancedSearchOptions) ProtoMessage() {} + +func (x *AdvancedSearchOptions) ProtoReflect() protoreflect.Message { + mi := &file_pkg_grpc_fetch_api_proto_msgTypes[1] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AdvancedSearchOptions.ProtoReflect.Descriptor instead. +func (*AdvancedSearchOptions) Descriptor() ([]byte, []int) { + return file_pkg_grpc_fetch_api_proto_rawDescGZIP(), []int{1} +} + +func (x *AdvancedSearchOptions) GetAfter() *timestamppb.Timestamp { + if x != nil { + return x.After + } + return nil +} + +func (x *AdvancedSearchOptions) GetBefore() *timestamppb.Timestamp { + if x != nil { + return x.Before + } + return nil +} + +func (x *AdvancedSearchOptions) GetTimestampAsc() *wrapperspb.BoolValue { + if x != nil { + return x.TimestampAsc + } + return nil +} + +func (x *AdvancedSearchOptions) GetType() *StringFilterOption { + if x != nil { + return x.Type + } + return nil +} + +func (x *AdvancedSearchOptions) GetDataVersion() *StringFilterOption { + if x != nil { + return x.DataVersion + } + return nil +} + +func (x *AdvancedSearchOptions) GetSubject() *StringFilterOption { + if x != nil { + return x.Subject + } + return nil +} + +func (x *AdvancedSearchOptions) GetSource() *StringFilterOption { + if x != nil { + return x.Source + } + return nil +} + +func (x *AdvancedSearchOptions) GetProducer() *StringFilterOption { + if x != nil { + return x.Producer + } + return nil +} + +func (x *AdvancedSearchOptions) GetExtras() *StringFilterOption { + if x != nil { + return x.Extras + } + return nil +} + +func (x *AdvancedSearchOptions) GetId() *StringFilterOption { + if x != nil { + return x.Id + } + return nil +} + +func (x *AdvancedSearchOptions) GetTags() *ArrayFilterOption { + if x != nil { + return x.Tags + } + return nil +} + +// FilterOption defines a flexible filtering option that can operate on a list of values +// every thing is implicitly ANDed together. +type ArrayFilterOption struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Match if the field has any of these values. + HasAny []string `protobuf:"bytes,1,rep,name=has_any,json=hasAny,proto3" json:"has_any,omitempty"` + // Match if the field has all of these values. + HasAll []string `protobuf:"bytes,2,rep,name=has_all,json=hasAll,proto3" json:"has_all,omitempty"` + // Negate all matches in this filter. + Negate bool `protobuf:"varint,3,opt,name=negate,proto3" json:"negate,omitempty"` + // Additional filter condition to combine with this one using OR logic. + Or *ArrayFilterOption `protobuf:"bytes,4,opt,name=or,proto3" json:"or,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ArrayFilterOption) Reset() { + *x = ArrayFilterOption{} + mi := &file_pkg_grpc_fetch_api_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ArrayFilterOption) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ArrayFilterOption) ProtoMessage() {} + +func (x *ArrayFilterOption) ProtoReflect() protoreflect.Message { + mi := &file_pkg_grpc_fetch_api_proto_msgTypes[2] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ArrayFilterOption.ProtoReflect.Descriptor instead. +func (*ArrayFilterOption) Descriptor() ([]byte, []int) { + return file_pkg_grpc_fetch_api_proto_rawDescGZIP(), []int{2} +} + +func (x *ArrayFilterOption) GetHasAny() []string { + if x != nil { + return x.HasAny + } + return nil +} + +func (x *ArrayFilterOption) GetHasAll() []string { + if x != nil { + return x.HasAll + } + return nil +} + +func (x *ArrayFilterOption) GetNegate() bool { + if x != nil { + return x.Negate + } + return false +} + +func (x *ArrayFilterOption) GetOr() *ArrayFilterOption { + if x != nil { + return x.Or + } + return nil +} + +type StringFilterOption struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Match if the field has any of these values (OR logic) + HasAny []string `protobuf:"bytes,1,rep,name=has_any,json=hasAny,proto3" json:"has_any,omitempty"` + // Negate all matches in this filter. + Negate bool `protobuf:"varint,2,opt,name=negate,proto3" json:"negate,omitempty"` + // Additional filter condition to combine with this one using OR logic. + Or *StringFilterOption `protobuf:"bytes,3,opt,name=or,proto3" json:"or,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *StringFilterOption) Reset() { + *x = StringFilterOption{} + mi := &file_pkg_grpc_fetch_api_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *StringFilterOption) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*StringFilterOption) ProtoMessage() {} + +func (x *StringFilterOption) ProtoReflect() protoreflect.Message { + mi := &file_pkg_grpc_fetch_api_proto_msgTypes[3] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use StringFilterOption.ProtoReflect.Descriptor instead. +func (*StringFilterOption) Descriptor() ([]byte, []int) { + return file_pkg_grpc_fetch_api_proto_rawDescGZIP(), []int{3} +} + +func (x *StringFilterOption) GetHasAny() []string { + if x != nil { + return x.HasAny + } + return nil +} + +func (x *StringFilterOption) GetNegate() bool { + if x != nil { + return x.Negate + } + return false +} + +func (x *StringFilterOption) GetOr() *StringFilterOption { + if x != nil { + return x.Or + } + return nil +} + type CloudEventIndex struct { state protoimpl.MessageState `protogen:"open.v1"` // The cloud event header of the cloud event. @@ -163,7 +437,7 @@ type CloudEventIndex struct { func (x *CloudEventIndex) Reset() { *x = CloudEventIndex{} - mi := &file_pkg_grpc_fetch_api_proto_msgTypes[1] + mi := &file_pkg_grpc_fetch_api_proto_msgTypes[4] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -175,7 +449,7 @@ func (x *CloudEventIndex) String() string { func (*CloudEventIndex) ProtoMessage() {} func (x *CloudEventIndex) ProtoReflect() protoreflect.Message { - mi := &file_pkg_grpc_fetch_api_proto_msgTypes[1] + mi := &file_pkg_grpc_fetch_api_proto_msgTypes[4] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -188,7 +462,7 @@ func (x *CloudEventIndex) ProtoReflect() protoreflect.Message { // Deprecated: Use CloudEventIndex.ProtoReflect.Descriptor instead. func (*CloudEventIndex) Descriptor() ([]byte, []int) { - return file_pkg_grpc_fetch_api_proto_rawDescGZIP(), []int{1} + return file_pkg_grpc_fetch_api_proto_rawDescGZIP(), []int{4} } func (x *CloudEventIndex) GetHeader() *CloudEventHeader { @@ -215,7 +489,7 @@ type ObjectInfo struct { func (x *ObjectInfo) Reset() { *x = ObjectInfo{} - mi := &file_pkg_grpc_fetch_api_proto_msgTypes[2] + mi := &file_pkg_grpc_fetch_api_proto_msgTypes[5] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -227,7 +501,7 @@ func (x *ObjectInfo) String() string { func (*ObjectInfo) ProtoMessage() {} func (x *ObjectInfo) ProtoReflect() protoreflect.Message { - mi := &file_pkg_grpc_fetch_api_proto_msgTypes[2] + mi := &file_pkg_grpc_fetch_api_proto_msgTypes[5] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -240,7 +514,7 @@ func (x *ObjectInfo) ProtoReflect() protoreflect.Message { // Deprecated: Use ObjectInfo.ProtoReflect.Descriptor instead. func (*ObjectInfo) Descriptor() ([]byte, []int) { - return file_pkg_grpc_fetch_api_proto_rawDescGZIP(), []int{2} + return file_pkg_grpc_fetch_api_proto_rawDescGZIP(), []int{5} } func (x *ObjectInfo) GetKey() string { @@ -252,15 +526,16 @@ func (x *ObjectInfo) GetKey() string { // GetLatestIndexRequest includes search options to filter the events. type GetLatestIndexRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - Options *SearchOptions `protobuf:"bytes,1,opt,name=options,proto3" json:"options,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + Options *SearchOptions `protobuf:"bytes,1,opt,name=options,proto3" json:"options,omitempty"` + AdvancedOptions *AdvancedSearchOptions `protobuf:"bytes,2,opt,name=advanced_options,json=advancedOptions,proto3" json:"advanced_options,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *GetLatestIndexRequest) Reset() { *x = GetLatestIndexRequest{} - mi := &file_pkg_grpc_fetch_api_proto_msgTypes[3] + mi := &file_pkg_grpc_fetch_api_proto_msgTypes[6] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -272,7 +547,7 @@ func (x *GetLatestIndexRequest) String() string { func (*GetLatestIndexRequest) ProtoMessage() {} func (x *GetLatestIndexRequest) ProtoReflect() protoreflect.Message { - mi := &file_pkg_grpc_fetch_api_proto_msgTypes[3] + mi := &file_pkg_grpc_fetch_api_proto_msgTypes[6] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -285,7 +560,7 @@ func (x *GetLatestIndexRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetLatestIndexRequest.ProtoReflect.Descriptor instead. func (*GetLatestIndexRequest) Descriptor() ([]byte, []int) { - return file_pkg_grpc_fetch_api_proto_rawDescGZIP(), []int{3} + return file_pkg_grpc_fetch_api_proto_rawDescGZIP(), []int{6} } func (x *GetLatestIndexRequest) GetOptions() *SearchOptions { @@ -295,6 +570,13 @@ func (x *GetLatestIndexRequest) GetOptions() *SearchOptions { return nil } +func (x *GetLatestIndexRequest) GetAdvancedOptions() *AdvancedSearchOptions { + if x != nil { + return x.AdvancedOptions + } + return nil +} + // GetLatestIndexResponse provides the latest index retrieved for the specified search criteria. type GetLatestIndexResponse struct { state protoimpl.MessageState `protogen:"open.v1"` @@ -306,7 +588,7 @@ type GetLatestIndexResponse struct { func (x *GetLatestIndexResponse) Reset() { *x = GetLatestIndexResponse{} - mi := &file_pkg_grpc_fetch_api_proto_msgTypes[4] + mi := &file_pkg_grpc_fetch_api_proto_msgTypes[7] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -318,7 +600,7 @@ func (x *GetLatestIndexResponse) String() string { func (*GetLatestIndexResponse) ProtoMessage() {} func (x *GetLatestIndexResponse) ProtoReflect() protoreflect.Message { - mi := &file_pkg_grpc_fetch_api_proto_msgTypes[4] + mi := &file_pkg_grpc_fetch_api_proto_msgTypes[7] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -331,7 +613,7 @@ func (x *GetLatestIndexResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetLatestIndexResponse.ProtoReflect.Descriptor instead. func (*GetLatestIndexResponse) Descriptor() ([]byte, []int) { - return file_pkg_grpc_fetch_api_proto_rawDescGZIP(), []int{4} + return file_pkg_grpc_fetch_api_proto_rawDescGZIP(), []int{7} } func (x *GetLatestIndexResponse) GetIndex() *CloudEventIndex { @@ -347,14 +629,16 @@ type ListIndexesRequest struct { // Search options to filter the events. Options *SearchOptions `protobuf:"bytes,1,opt,name=options,proto3" json:"options,omitempty"` // The maximum number of index objects to return. - Limit int32 `protobuf:"varint,2,opt,name=limit,proto3" json:"limit,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + Limit int32 `protobuf:"varint,2,opt,name=limit,proto3" json:"limit,omitempty"` + // Advanced search options to filter the events. + AdvancedOptions *AdvancedSearchOptions `protobuf:"bytes,3,opt,name=advanced_options,json=advancedOptions,proto3" json:"advanced_options,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *ListIndexesRequest) Reset() { *x = ListIndexesRequest{} - mi := &file_pkg_grpc_fetch_api_proto_msgTypes[5] + mi := &file_pkg_grpc_fetch_api_proto_msgTypes[8] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -366,7 +650,7 @@ func (x *ListIndexesRequest) String() string { func (*ListIndexesRequest) ProtoMessage() {} func (x *ListIndexesRequest) ProtoReflect() protoreflect.Message { - mi := &file_pkg_grpc_fetch_api_proto_msgTypes[5] + mi := &file_pkg_grpc_fetch_api_proto_msgTypes[8] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -379,7 +663,7 @@ func (x *ListIndexesRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListIndexesRequest.ProtoReflect.Descriptor instead. func (*ListIndexesRequest) Descriptor() ([]byte, []int) { - return file_pkg_grpc_fetch_api_proto_rawDescGZIP(), []int{5} + return file_pkg_grpc_fetch_api_proto_rawDescGZIP(), []int{8} } func (x *ListIndexesRequest) GetOptions() *SearchOptions { @@ -396,6 +680,13 @@ func (x *ListIndexesRequest) GetLimit() int32 { return 0 } +func (x *ListIndexesRequest) GetAdvancedOptions() *AdvancedSearchOptions { + if x != nil { + return x.AdvancedOptions + } + return nil +} + // ListIndexesResponse returns the list of index for cloud events retrieved for the specified search criteria. type ListIndexesResponse struct { state protoimpl.MessageState `protogen:"open.v1"` @@ -407,7 +698,7 @@ type ListIndexesResponse struct { func (x *ListIndexesResponse) Reset() { *x = ListIndexesResponse{} - mi := &file_pkg_grpc_fetch_api_proto_msgTypes[6] + mi := &file_pkg_grpc_fetch_api_proto_msgTypes[9] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -419,7 +710,7 @@ func (x *ListIndexesResponse) String() string { func (*ListIndexesResponse) ProtoMessage() {} func (x *ListIndexesResponse) ProtoReflect() protoreflect.Message { - mi := &file_pkg_grpc_fetch_api_proto_msgTypes[6] + mi := &file_pkg_grpc_fetch_api_proto_msgTypes[9] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -432,7 +723,7 @@ func (x *ListIndexesResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListIndexesResponse.ProtoReflect.Descriptor instead. func (*ListIndexesResponse) Descriptor() ([]byte, []int) { - return file_pkg_grpc_fetch_api_proto_rawDescGZIP(), []int{6} + return file_pkg_grpc_fetch_api_proto_rawDescGZIP(), []int{9} } func (x *ListIndexesResponse) GetIndexes() []*CloudEventIndex { @@ -446,14 +737,16 @@ func (x *ListIndexesResponse) GetIndexes() []*CloudEventIndex { type GetLatestCloudEventRequest struct { state protoimpl.MessageState `protogen:"open.v1"` // Search options to filter the events. - Options *SearchOptions `protobuf:"bytes,1,opt,name=options,proto3" json:"options,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + Options *SearchOptions `protobuf:"bytes,1,opt,name=options,proto3" json:"options,omitempty"` + // Advanced search options to filter the events. + AdvancedOptions *AdvancedSearchOptions `protobuf:"bytes,2,opt,name=advanced_options,json=advancedOptions,proto3" json:"advanced_options,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *GetLatestCloudEventRequest) Reset() { *x = GetLatestCloudEventRequest{} - mi := &file_pkg_grpc_fetch_api_proto_msgTypes[7] + mi := &file_pkg_grpc_fetch_api_proto_msgTypes[10] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -465,7 +758,7 @@ func (x *GetLatestCloudEventRequest) String() string { func (*GetLatestCloudEventRequest) ProtoMessage() {} func (x *GetLatestCloudEventRequest) ProtoReflect() protoreflect.Message { - mi := &file_pkg_grpc_fetch_api_proto_msgTypes[7] + mi := &file_pkg_grpc_fetch_api_proto_msgTypes[10] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -478,7 +771,7 @@ func (x *GetLatestCloudEventRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetLatestCloudEventRequest.ProtoReflect.Descriptor instead. func (*GetLatestCloudEventRequest) Descriptor() ([]byte, []int) { - return file_pkg_grpc_fetch_api_proto_rawDescGZIP(), []int{7} + return file_pkg_grpc_fetch_api_proto_rawDescGZIP(), []int{10} } func (x *GetLatestCloudEventRequest) GetOptions() *SearchOptions { @@ -488,6 +781,13 @@ func (x *GetLatestCloudEventRequest) GetOptions() *SearchOptions { return nil } +func (x *GetLatestCloudEventRequest) GetAdvancedOptions() *AdvancedSearchOptions { + if x != nil { + return x.AdvancedOptions + } + return nil +} + // GetLatestCloudEventResponse provides the data retrieved from the latest cloud event. type GetLatestCloudEventResponse struct { state protoimpl.MessageState `protogen:"open.v1"` @@ -499,7 +799,7 @@ type GetLatestCloudEventResponse struct { func (x *GetLatestCloudEventResponse) Reset() { *x = GetLatestCloudEventResponse{} - mi := &file_pkg_grpc_fetch_api_proto_msgTypes[8] + mi := &file_pkg_grpc_fetch_api_proto_msgTypes[11] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -511,7 +811,7 @@ func (x *GetLatestCloudEventResponse) String() string { func (*GetLatestCloudEventResponse) ProtoMessage() {} func (x *GetLatestCloudEventResponse) ProtoReflect() protoreflect.Message { - mi := &file_pkg_grpc_fetch_api_proto_msgTypes[8] + mi := &file_pkg_grpc_fetch_api_proto_msgTypes[11] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -524,7 +824,7 @@ func (x *GetLatestCloudEventResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetLatestCloudEventResponse.ProtoReflect.Descriptor instead. func (*GetLatestCloudEventResponse) Descriptor() ([]byte, []int) { - return file_pkg_grpc_fetch_api_proto_rawDescGZIP(), []int{8} + return file_pkg_grpc_fetch_api_proto_rawDescGZIP(), []int{11} } func (x *GetLatestCloudEventResponse) GetCloudEvent() *CloudEvent { @@ -540,14 +840,16 @@ type ListCloudEventsRequest struct { // Search options to filter the events. Options *SearchOptions `protobuf:"bytes,1,opt,name=options,proto3" json:"options,omitempty"` // The maximum number of objects to return. - Limit int32 `protobuf:"varint,2,opt,name=limit,proto3" json:"limit,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + Limit int32 `protobuf:"varint,2,opt,name=limit,proto3" json:"limit,omitempty"` + // Advanced search options to filter the events. + AdvancedOptions *AdvancedSearchOptions `protobuf:"bytes,3,opt,name=advanced_options,json=advancedOptions,proto3" json:"advanced_options,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *ListCloudEventsRequest) Reset() { *x = ListCloudEventsRequest{} - mi := &file_pkg_grpc_fetch_api_proto_msgTypes[9] + mi := &file_pkg_grpc_fetch_api_proto_msgTypes[12] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -559,7 +861,7 @@ func (x *ListCloudEventsRequest) String() string { func (*ListCloudEventsRequest) ProtoMessage() {} func (x *ListCloudEventsRequest) ProtoReflect() protoreflect.Message { - mi := &file_pkg_grpc_fetch_api_proto_msgTypes[9] + mi := &file_pkg_grpc_fetch_api_proto_msgTypes[12] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -572,7 +874,7 @@ func (x *ListCloudEventsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListCloudEventsRequest.ProtoReflect.Descriptor instead. func (*ListCloudEventsRequest) Descriptor() ([]byte, []int) { - return file_pkg_grpc_fetch_api_proto_rawDescGZIP(), []int{9} + return file_pkg_grpc_fetch_api_proto_rawDescGZIP(), []int{12} } func (x *ListCloudEventsRequest) GetOptions() *SearchOptions { @@ -589,6 +891,13 @@ func (x *ListCloudEventsRequest) GetLimit() int32 { return 0 } +func (x *ListCloudEventsRequest) GetAdvancedOptions() *AdvancedSearchOptions { + if x != nil { + return x.AdvancedOptions + } + return nil +} + // ListCloudEventsResponse provides the list of data retrieved from the objects. type ListCloudEventsResponse struct { state protoimpl.MessageState `protogen:"open.v1"` @@ -600,7 +909,7 @@ type ListCloudEventsResponse struct { func (x *ListCloudEventsResponse) Reset() { *x = ListCloudEventsResponse{} - mi := &file_pkg_grpc_fetch_api_proto_msgTypes[10] + mi := &file_pkg_grpc_fetch_api_proto_msgTypes[13] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -612,7 +921,7 @@ func (x *ListCloudEventsResponse) String() string { func (*ListCloudEventsResponse) ProtoMessage() {} func (x *ListCloudEventsResponse) ProtoReflect() protoreflect.Message { - mi := &file_pkg_grpc_fetch_api_proto_msgTypes[10] + mi := &file_pkg_grpc_fetch_api_proto_msgTypes[13] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -625,7 +934,7 @@ func (x *ListCloudEventsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListCloudEventsResponse.ProtoReflect.Descriptor instead. func (*ListCloudEventsResponse) Descriptor() ([]byte, []int) { - return file_pkg_grpc_fetch_api_proto_rawDescGZIP(), []int{10} + return file_pkg_grpc_fetch_api_proto_rawDescGZIP(), []int{13} } func (x *ListCloudEventsResponse) GetCloudEvents() []*CloudEvent { @@ -646,7 +955,7 @@ type ListCloudEventsFromKeysRequest struct { func (x *ListCloudEventsFromKeysRequest) Reset() { *x = ListCloudEventsFromKeysRequest{} - mi := &file_pkg_grpc_fetch_api_proto_msgTypes[11] + mi := &file_pkg_grpc_fetch_api_proto_msgTypes[14] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -658,7 +967,7 @@ func (x *ListCloudEventsFromKeysRequest) String() string { func (*ListCloudEventsFromKeysRequest) ProtoMessage() {} func (x *ListCloudEventsFromKeysRequest) ProtoReflect() protoreflect.Message { - mi := &file_pkg_grpc_fetch_api_proto_msgTypes[11] + mi := &file_pkg_grpc_fetch_api_proto_msgTypes[14] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -671,7 +980,7 @@ func (x *ListCloudEventsFromKeysRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListCloudEventsFromKeysRequest.ProtoReflect.Descriptor instead. func (*ListCloudEventsFromKeysRequest) Descriptor() ([]byte, []int) { - return file_pkg_grpc_fetch_api_proto_rawDescGZIP(), []int{11} + return file_pkg_grpc_fetch_api_proto_rawDescGZIP(), []int{14} } func (x *ListCloudEventsFromKeysRequest) GetIndexes() []*CloudEventIndex { @@ -692,7 +1001,7 @@ type ListCloudEventsFromKeysResponse struct { func (x *ListCloudEventsFromKeysResponse) Reset() { *x = ListCloudEventsFromKeysResponse{} - mi := &file_pkg_grpc_fetch_api_proto_msgTypes[12] + mi := &file_pkg_grpc_fetch_api_proto_msgTypes[15] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -704,7 +1013,7 @@ func (x *ListCloudEventsFromKeysResponse) String() string { func (*ListCloudEventsFromKeysResponse) ProtoMessage() {} func (x *ListCloudEventsFromKeysResponse) ProtoReflect() protoreflect.Message { - mi := &file_pkg_grpc_fetch_api_proto_msgTypes[12] + mi := &file_pkg_grpc_fetch_api_proto_msgTypes[15] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -717,7 +1026,7 @@ func (x *ListCloudEventsFromKeysResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListCloudEventsFromKeysResponse.ProtoReflect.Descriptor instead. func (*ListCloudEventsFromKeysResponse) Descriptor() ([]byte, []int) { - return file_pkg_grpc_fetch_api_proto_rawDescGZIP(), []int{12} + return file_pkg_grpc_fetch_api_proto_rawDescGZIP(), []int{15} } func (x *ListCloudEventsFromKeysResponse) GetCloudEvents() []*CloudEvent { @@ -743,30 +1052,56 @@ const file_pkg_grpc_fetch_api_proto_rawDesc = "" + "\bproducer\x18\t \x01(\v2\x1c.google.protobuf.StringValueR\bproducer\x124\n" + "\x06extras\x18\n" + " \x01(\v2\x1c.google.protobuf.StringValueR\x06extras\x12,\n" + - "\x02id\x18\v \x01(\v2\x1c.google.protobuf.StringValueR\x02id\"m\n" + + "\x02id\x18\v \x01(\v2\x1c.google.protobuf.StringValueR\x02id\"\xce\x04\n" + + "\x15AdvancedSearchOptions\x120\n" + + "\x05after\x18\x01 \x01(\v2\x1a.google.protobuf.TimestampR\x05after\x122\n" + + "\x06before\x18\x02 \x01(\v2\x1a.google.protobuf.TimestampR\x06before\x12?\n" + + "\rtimestamp_asc\x18\x03 \x01(\v2\x1a.google.protobuf.BoolValueR\ftimestampAsc\x12,\n" + + "\x04type\x18\x04 \x01(\v2\x18.grpc.StringFilterOptionR\x04type\x12;\n" + + "\fdata_version\x18\x05 \x01(\v2\x18.grpc.StringFilterOptionR\vdataVersion\x122\n" + + "\asubject\x18\x06 \x01(\v2\x18.grpc.StringFilterOptionR\asubject\x120\n" + + "\x06source\x18\b \x01(\v2\x18.grpc.StringFilterOptionR\x06source\x124\n" + + "\bproducer\x18\t \x01(\v2\x18.grpc.StringFilterOptionR\bproducer\x120\n" + + "\x06extras\x18\n" + + " \x01(\v2\x18.grpc.StringFilterOptionR\x06extras\x12(\n" + + "\x02id\x18\v \x01(\v2\x18.grpc.StringFilterOptionR\x02id\x12+\n" + + "\x04tags\x18\f \x01(\v2\x17.grpc.ArrayFilterOptionR\x04tags\"\x86\x01\n" + + "\x11ArrayFilterOption\x12\x17\n" + + "\ahas_any\x18\x01 \x03(\tR\x06hasAny\x12\x17\n" + + "\ahas_all\x18\x02 \x03(\tR\x06hasAll\x12\x16\n" + + "\x06negate\x18\x03 \x01(\bR\x06negate\x12'\n" + + "\x02or\x18\x04 \x01(\v2\x17.grpc.ArrayFilterOptionR\x02or\"o\n" + + "\x12StringFilterOption\x12\x17\n" + + "\ahas_any\x18\x01 \x03(\tR\x06hasAny\x12\x16\n" + + "\x06negate\x18\x02 \x01(\bR\x06negate\x12(\n" + + "\x02or\x18\x03 \x01(\v2\x18.grpc.StringFilterOptionR\x02or\"m\n" + "\x0fCloudEventIndex\x124\n" + "\x06header\x18\x01 \x01(\v2\x1c.cloudevent.CloudEventHeaderR\x06header\x12$\n" + "\x04data\x18\x02 \x01(\v2\x10.grpc.ObjectInfoR\x04data\"\x1e\n" + "\n" + "ObjectInfo\x12\x10\n" + - "\x03key\x18\x01 \x01(\tR\x03key\"F\n" + + "\x03key\x18\x01 \x01(\tR\x03key\"\x8e\x01\n" + "\x15GetLatestIndexRequest\x12-\n" + - "\aoptions\x18\x01 \x01(\v2\x13.grpc.SearchOptionsR\aoptions\"E\n" + + "\aoptions\x18\x01 \x01(\v2\x13.grpc.SearchOptionsR\aoptions\x12F\n" + + "\x10advanced_options\x18\x02 \x01(\v2\x1b.grpc.AdvancedSearchOptionsR\x0fadvancedOptions\"E\n" + "\x16GetLatestIndexResponse\x12+\n" + - "\x05index\x18\x01 \x01(\v2\x15.grpc.CloudEventIndexR\x05index\"Y\n" + + "\x05index\x18\x01 \x01(\v2\x15.grpc.CloudEventIndexR\x05index\"\xa1\x01\n" + "\x12ListIndexesRequest\x12-\n" + "\aoptions\x18\x01 \x01(\v2\x13.grpc.SearchOptionsR\aoptions\x12\x14\n" + - "\x05limit\x18\x02 \x01(\x05R\x05limit\"F\n" + + "\x05limit\x18\x02 \x01(\x05R\x05limit\x12F\n" + + "\x10advanced_options\x18\x03 \x01(\v2\x1b.grpc.AdvancedSearchOptionsR\x0fadvancedOptions\"F\n" + "\x13ListIndexesResponse\x12/\n" + - "\aindexes\x18\x01 \x03(\v2\x15.grpc.CloudEventIndexR\aindexes\"K\n" + + "\aindexes\x18\x01 \x03(\v2\x15.grpc.CloudEventIndexR\aindexes\"\x93\x01\n" + "\x1aGetLatestCloudEventRequest\x12-\n" + - "\aoptions\x18\x01 \x01(\v2\x13.grpc.SearchOptionsR\aoptions\"V\n" + + "\aoptions\x18\x01 \x01(\v2\x13.grpc.SearchOptionsR\aoptions\x12F\n" + + "\x10advanced_options\x18\x02 \x01(\v2\x1b.grpc.AdvancedSearchOptionsR\x0fadvancedOptions\"V\n" + "\x1bGetLatestCloudEventResponse\x127\n" + "\vcloud_event\x18\x01 \x01(\v2\x16.cloudevent.CloudEventR\n" + - "cloudEvent\"]\n" + + "cloudEvent\"\xa5\x01\n" + "\x16ListCloudEventsRequest\x12-\n" + "\aoptions\x18\x01 \x01(\v2\x13.grpc.SearchOptionsR\aoptions\x12\x14\n" + - "\x05limit\x18\x02 \x01(\x05R\x05limit\"T\n" + + "\x05limit\x18\x02 \x01(\x05R\x05limit\x12F\n" + + "\x10advanced_options\x18\x03 \x01(\v2\x1b.grpc.AdvancedSearchOptionsR\x0fadvancedOptions\"T\n" + "\x17ListCloudEventsResponse\x129\n" + "\fcloud_events\x18\x01 \x03(\v2\x16.cloudevent.CloudEventR\vcloudEvents\"Q\n" + "\x1eListCloudEventsFromKeysRequest\x12/\n" + @@ -792,65 +1127,85 @@ func file_pkg_grpc_fetch_api_proto_rawDescGZIP() []byte { return file_pkg_grpc_fetch_api_proto_rawDescData } -var file_pkg_grpc_fetch_api_proto_msgTypes = make([]protoimpl.MessageInfo, 13) +var file_pkg_grpc_fetch_api_proto_msgTypes = make([]protoimpl.MessageInfo, 16) var file_pkg_grpc_fetch_api_proto_goTypes = []any{ (*SearchOptions)(nil), // 0: grpc.SearchOptions - (*CloudEventIndex)(nil), // 1: grpc.CloudEventIndex - (*ObjectInfo)(nil), // 2: grpc.ObjectInfo - (*GetLatestIndexRequest)(nil), // 3: grpc.GetLatestIndexRequest - (*GetLatestIndexResponse)(nil), // 4: grpc.GetLatestIndexResponse - (*ListIndexesRequest)(nil), // 5: grpc.ListIndexesRequest - (*ListIndexesResponse)(nil), // 6: grpc.ListIndexesResponse - (*GetLatestCloudEventRequest)(nil), // 7: grpc.GetLatestCloudEventRequest - (*GetLatestCloudEventResponse)(nil), // 8: grpc.GetLatestCloudEventResponse - (*ListCloudEventsRequest)(nil), // 9: grpc.ListCloudEventsRequest - (*ListCloudEventsResponse)(nil), // 10: grpc.ListCloudEventsResponse - (*ListCloudEventsFromKeysRequest)(nil), // 11: grpc.ListCloudEventsFromKeysRequest - (*ListCloudEventsFromKeysResponse)(nil), // 12: grpc.ListCloudEventsFromKeysResponse - (*timestamppb.Timestamp)(nil), // 13: google.protobuf.Timestamp - (*wrapperspb.BoolValue)(nil), // 14: google.protobuf.BoolValue - (*wrapperspb.StringValue)(nil), // 15: google.protobuf.StringValue - (*CloudEventHeader)(nil), // 16: cloudevent.CloudEventHeader - (*CloudEvent)(nil), // 17: cloudevent.CloudEvent + (*AdvancedSearchOptions)(nil), // 1: grpc.AdvancedSearchOptions + (*ArrayFilterOption)(nil), // 2: grpc.ArrayFilterOption + (*StringFilterOption)(nil), // 3: grpc.StringFilterOption + (*CloudEventIndex)(nil), // 4: grpc.CloudEventIndex + (*ObjectInfo)(nil), // 5: grpc.ObjectInfo + (*GetLatestIndexRequest)(nil), // 6: grpc.GetLatestIndexRequest + (*GetLatestIndexResponse)(nil), // 7: grpc.GetLatestIndexResponse + (*ListIndexesRequest)(nil), // 8: grpc.ListIndexesRequest + (*ListIndexesResponse)(nil), // 9: grpc.ListIndexesResponse + (*GetLatestCloudEventRequest)(nil), // 10: grpc.GetLatestCloudEventRequest + (*GetLatestCloudEventResponse)(nil), // 11: grpc.GetLatestCloudEventResponse + (*ListCloudEventsRequest)(nil), // 12: grpc.ListCloudEventsRequest + (*ListCloudEventsResponse)(nil), // 13: grpc.ListCloudEventsResponse + (*ListCloudEventsFromKeysRequest)(nil), // 14: grpc.ListCloudEventsFromKeysRequest + (*ListCloudEventsFromKeysResponse)(nil), // 15: grpc.ListCloudEventsFromKeysResponse + (*timestamppb.Timestamp)(nil), // 16: google.protobuf.Timestamp + (*wrapperspb.BoolValue)(nil), // 17: google.protobuf.BoolValue + (*wrapperspb.StringValue)(nil), // 18: google.protobuf.StringValue + (*CloudEventHeader)(nil), // 19: cloudevent.CloudEventHeader + (*CloudEvent)(nil), // 20: cloudevent.CloudEvent } var file_pkg_grpc_fetch_api_proto_depIdxs = []int32{ - 13, // 0: grpc.SearchOptions.after:type_name -> google.protobuf.Timestamp - 13, // 1: grpc.SearchOptions.before:type_name -> google.protobuf.Timestamp - 14, // 2: grpc.SearchOptions.timestamp_asc:type_name -> google.protobuf.BoolValue - 15, // 3: grpc.SearchOptions.type:type_name -> google.protobuf.StringValue - 15, // 4: grpc.SearchOptions.data_version:type_name -> google.protobuf.StringValue - 15, // 5: grpc.SearchOptions.subject:type_name -> google.protobuf.StringValue - 15, // 6: grpc.SearchOptions.source:type_name -> google.protobuf.StringValue - 15, // 7: grpc.SearchOptions.producer:type_name -> google.protobuf.StringValue - 15, // 8: grpc.SearchOptions.extras:type_name -> google.protobuf.StringValue - 15, // 9: grpc.SearchOptions.id:type_name -> google.protobuf.StringValue - 16, // 10: grpc.CloudEventIndex.header:type_name -> cloudevent.CloudEventHeader - 2, // 11: grpc.CloudEventIndex.data:type_name -> grpc.ObjectInfo - 0, // 12: grpc.GetLatestIndexRequest.options:type_name -> grpc.SearchOptions - 1, // 13: grpc.GetLatestIndexResponse.index:type_name -> grpc.CloudEventIndex - 0, // 14: grpc.ListIndexesRequest.options:type_name -> grpc.SearchOptions - 1, // 15: grpc.ListIndexesResponse.indexes:type_name -> grpc.CloudEventIndex - 0, // 16: grpc.GetLatestCloudEventRequest.options:type_name -> grpc.SearchOptions - 17, // 17: grpc.GetLatestCloudEventResponse.cloud_event:type_name -> cloudevent.CloudEvent - 0, // 18: grpc.ListCloudEventsRequest.options:type_name -> grpc.SearchOptions - 17, // 19: grpc.ListCloudEventsResponse.cloud_events:type_name -> cloudevent.CloudEvent - 1, // 20: grpc.ListCloudEventsFromKeysRequest.indexes:type_name -> grpc.CloudEventIndex - 17, // 21: grpc.ListCloudEventsFromKeysResponse.cloud_events:type_name -> cloudevent.CloudEvent - 3, // 22: grpc.FetchService.GetLatestIndex:input_type -> grpc.GetLatestIndexRequest - 5, // 23: grpc.FetchService.ListIndexes:input_type -> grpc.ListIndexesRequest - 7, // 24: grpc.FetchService.GetLatestCloudEvent:input_type -> grpc.GetLatestCloudEventRequest - 9, // 25: grpc.FetchService.ListCloudEvents:input_type -> grpc.ListCloudEventsRequest - 11, // 26: grpc.FetchService.ListCloudEventsFromIndex:input_type -> grpc.ListCloudEventsFromKeysRequest - 4, // 27: grpc.FetchService.GetLatestIndex:output_type -> grpc.GetLatestIndexResponse - 6, // 28: grpc.FetchService.ListIndexes:output_type -> grpc.ListIndexesResponse - 8, // 29: grpc.FetchService.GetLatestCloudEvent:output_type -> grpc.GetLatestCloudEventResponse - 10, // 30: grpc.FetchService.ListCloudEvents:output_type -> grpc.ListCloudEventsResponse - 12, // 31: grpc.FetchService.ListCloudEventsFromIndex:output_type -> grpc.ListCloudEventsFromKeysResponse - 27, // [27:32] is the sub-list for method output_type - 22, // [22:27] is the sub-list for method input_type - 22, // [22:22] is the sub-list for extension type_name - 22, // [22:22] is the sub-list for extension extendee - 0, // [0:22] is the sub-list for field type_name + 16, // 0: grpc.SearchOptions.after:type_name -> google.protobuf.Timestamp + 16, // 1: grpc.SearchOptions.before:type_name -> google.protobuf.Timestamp + 17, // 2: grpc.SearchOptions.timestamp_asc:type_name -> google.protobuf.BoolValue + 18, // 3: grpc.SearchOptions.type:type_name -> google.protobuf.StringValue + 18, // 4: grpc.SearchOptions.data_version:type_name -> google.protobuf.StringValue + 18, // 5: grpc.SearchOptions.subject:type_name -> google.protobuf.StringValue + 18, // 6: grpc.SearchOptions.source:type_name -> google.protobuf.StringValue + 18, // 7: grpc.SearchOptions.producer:type_name -> google.protobuf.StringValue + 18, // 8: grpc.SearchOptions.extras:type_name -> google.protobuf.StringValue + 18, // 9: grpc.SearchOptions.id:type_name -> google.protobuf.StringValue + 16, // 10: grpc.AdvancedSearchOptions.after:type_name -> google.protobuf.Timestamp + 16, // 11: grpc.AdvancedSearchOptions.before:type_name -> google.protobuf.Timestamp + 17, // 12: grpc.AdvancedSearchOptions.timestamp_asc:type_name -> google.protobuf.BoolValue + 3, // 13: grpc.AdvancedSearchOptions.type:type_name -> grpc.StringFilterOption + 3, // 14: grpc.AdvancedSearchOptions.data_version:type_name -> grpc.StringFilterOption + 3, // 15: grpc.AdvancedSearchOptions.subject:type_name -> grpc.StringFilterOption + 3, // 16: grpc.AdvancedSearchOptions.source:type_name -> grpc.StringFilterOption + 3, // 17: grpc.AdvancedSearchOptions.producer:type_name -> grpc.StringFilterOption + 3, // 18: grpc.AdvancedSearchOptions.extras:type_name -> grpc.StringFilterOption + 3, // 19: grpc.AdvancedSearchOptions.id:type_name -> grpc.StringFilterOption + 2, // 20: grpc.AdvancedSearchOptions.tags:type_name -> grpc.ArrayFilterOption + 2, // 21: grpc.ArrayFilterOption.or:type_name -> grpc.ArrayFilterOption + 3, // 22: grpc.StringFilterOption.or:type_name -> grpc.StringFilterOption + 19, // 23: grpc.CloudEventIndex.header:type_name -> cloudevent.CloudEventHeader + 5, // 24: grpc.CloudEventIndex.data:type_name -> grpc.ObjectInfo + 0, // 25: grpc.GetLatestIndexRequest.options:type_name -> grpc.SearchOptions + 1, // 26: grpc.GetLatestIndexRequest.advanced_options:type_name -> grpc.AdvancedSearchOptions + 4, // 27: grpc.GetLatestIndexResponse.index:type_name -> grpc.CloudEventIndex + 0, // 28: grpc.ListIndexesRequest.options:type_name -> grpc.SearchOptions + 1, // 29: grpc.ListIndexesRequest.advanced_options:type_name -> grpc.AdvancedSearchOptions + 4, // 30: grpc.ListIndexesResponse.indexes:type_name -> grpc.CloudEventIndex + 0, // 31: grpc.GetLatestCloudEventRequest.options:type_name -> grpc.SearchOptions + 1, // 32: grpc.GetLatestCloudEventRequest.advanced_options:type_name -> grpc.AdvancedSearchOptions + 20, // 33: grpc.GetLatestCloudEventResponse.cloud_event:type_name -> cloudevent.CloudEvent + 0, // 34: grpc.ListCloudEventsRequest.options:type_name -> grpc.SearchOptions + 1, // 35: grpc.ListCloudEventsRequest.advanced_options:type_name -> grpc.AdvancedSearchOptions + 20, // 36: grpc.ListCloudEventsResponse.cloud_events:type_name -> cloudevent.CloudEvent + 4, // 37: grpc.ListCloudEventsFromKeysRequest.indexes:type_name -> grpc.CloudEventIndex + 20, // 38: grpc.ListCloudEventsFromKeysResponse.cloud_events:type_name -> cloudevent.CloudEvent + 6, // 39: grpc.FetchService.GetLatestIndex:input_type -> grpc.GetLatestIndexRequest + 8, // 40: grpc.FetchService.ListIndexes:input_type -> grpc.ListIndexesRequest + 10, // 41: grpc.FetchService.GetLatestCloudEvent:input_type -> grpc.GetLatestCloudEventRequest + 12, // 42: grpc.FetchService.ListCloudEvents:input_type -> grpc.ListCloudEventsRequest + 14, // 43: grpc.FetchService.ListCloudEventsFromIndex:input_type -> grpc.ListCloudEventsFromKeysRequest + 7, // 44: grpc.FetchService.GetLatestIndex:output_type -> grpc.GetLatestIndexResponse + 9, // 45: grpc.FetchService.ListIndexes:output_type -> grpc.ListIndexesResponse + 11, // 46: grpc.FetchService.GetLatestCloudEvent:output_type -> grpc.GetLatestCloudEventResponse + 13, // 47: grpc.FetchService.ListCloudEvents:output_type -> grpc.ListCloudEventsResponse + 15, // 48: grpc.FetchService.ListCloudEventsFromIndex:output_type -> grpc.ListCloudEventsFromKeysResponse + 44, // [44:49] is the sub-list for method output_type + 39, // [39:44] is the sub-list for method input_type + 39, // [39:39] is the sub-list for extension type_name + 39, // [39:39] is the sub-list for extension extendee + 0, // [0:39] is the sub-list for field type_name } func init() { file_pkg_grpc_fetch_api_proto_init() } @@ -865,7 +1220,7 @@ func file_pkg_grpc_fetch_api_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_pkg_grpc_fetch_api_proto_rawDesc), len(file_pkg_grpc_fetch_api_proto_rawDesc)), NumEnums: 0, - NumMessages: 13, + NumMessages: 16, NumExtensions: 0, NumServices: 1, }, diff --git a/pkg/grpc/fetch-api.proto b/pkg/grpc/fetch-api.proto index dbe497a..b127bad 100644 --- a/pkg/grpc/fetch-api.proto +++ b/pkg/grpc/fetch-api.proto @@ -60,6 +60,68 @@ message SearchOptions { google.protobuf.StringValue id = 11; } +// AdvancedSearchOptions provides flexible filtering options with boolean logic +// to narrow down search results. Each field can use complex filtering operations. +message AdvancedSearchOptions { + // Only include events after this timestamp. + google.protobuf.Timestamp after = 1; + + // Only include events before this timestamp. + google.protobuf.Timestamp before = 2; + + // Whether to sort results in ascending order by timestamp. + google.protobuf.BoolValue timestamp_asc = 3; + + // Advanced filtering for event type + StringFilterOption type = 4; + + // Advanced filtering for data version + StringFilterOption data_version = 5; + + // Advanced filtering for event subject + StringFilterOption subject = 6; + + // Advanced filtering for source identifier + StringFilterOption source = 8; + + // Advanced filtering for producer + StringFilterOption producer = 9; + + // Advanced filtering for extras (JSON string from cloud event header) + StringFilterOption extras = 10; + + // Advanced filtering for event ID + StringFilterOption id = 11; + + // Tags are a list of tags that can be used to filter the events. + ArrayFilterOption tags = 12; +} + +// FilterOption defines a flexible filtering option that can operate on a list of values +// every thing is implicitly ANDed together. +message ArrayFilterOption { + // Match if the field has any of these values. + repeated string has_any = 1; + + // Match if the field has all of these values. + repeated string has_all = 2; + + // Negate all matches in this filter. + bool negate = 3; + + // Additional filter condition to combine with this one using OR logic. + ArrayFilterOption or = 4; +} + +message StringFilterOption { + // Match if the field has any of these values (OR logic) + repeated string has_any = 1; + // Negate all matches in this filter. + bool negate = 2; + // Additional filter condition to combine with this one using OR logic. + StringFilterOption or = 3; +} + message CloudEventIndex { // The cloud event header of the cloud event. cloudevent.CloudEventHeader header = 1; @@ -76,8 +138,10 @@ message ObjectInfo { // GetLatestIndexRequest includes search options to filter the events. message GetLatestIndexRequest { SearchOptions options = 1; + AdvancedSearchOptions advanced_options = 2; } + // GetLatestIndexResponse provides the latest index retrieved for the specified search criteria. message GetLatestIndexResponse { // The latest index key retrieved for the specified search criteria. @@ -91,6 +155,9 @@ message ListIndexesRequest{ // The maximum number of index objects to return. int32 limit = 2; + + // Advanced search options to filter the events. + AdvancedSearchOptions advanced_options = 3; } // ListIndexesResponse returns the list of index for cloud events retrieved for the specified search criteria. @@ -103,6 +170,8 @@ message ListIndexesResponse{ message GetLatestCloudEventRequest { // Search options to filter the events. SearchOptions options = 1; + // Advanced search options to filter the events. + AdvancedSearchOptions advanced_options = 2; } // GetLatestCloudEventResponse provides the data retrieved from the latest cloud event. @@ -115,9 +184,10 @@ message GetLatestCloudEventResponse { message ListCloudEventsRequest { // Search options to filter the events. SearchOptions options = 1; - // The maximum number of objects to return. int32 limit = 2; + // Advanced search options to filter the events. + AdvancedSearchOptions advanced_options = 3; } // ListCloudEventsResponse provides the list of data retrieved from the objects. diff --git a/sample.env b/sample.env index ed12ead..c9fd525 100644 --- a/sample.env +++ b/sample.env @@ -1,11 +1,11 @@ -PORT: 3000 -GRPC_PORT: 3003 -CLICKHOUSE_HOST: localhost -CLICKHOUSE_TCP_PORT: 9000 -CLICKHOUSE_USER: admin -CLICKHOUSE_PASSWORD: password -CLICKHOUSE_DATABASE: dimo -TOKEN_EXCHANGE_JWK_KEY_SET_URL: http://127.0.0.1:5556/dex/keys -TOKEN_EXCHANGE_ISSUER_URL: http://127.0.0.1:5556/dex -VEHICLE_NFT_ADDRESS: "0x90c4d6113ec88dd4bdf12f26db2b3998fd13a144" -CHAIN_ID: 137 \ No newline at end of file +PORT=3000 +GRPC_PORT=3003 +CLICKHOUSE_HOST=localhost +CLICKHOUSE_TCP_PORT=9000 +CLICKHOUSE_USER=admin +CLICKHOUSE_PASSWORD=password +CLICKHOUSE_DATABASE=dimo +TOKEN_EXCHANGE_JWK_KEY_SET_URL=http://127.0.0.1:5556/dex/keys +TOKEN_EXCHANGE_ISSUER_URL=http://127.0.0.1:5556/dex +VEHICLE_NFT_ADDRESS="0x90c4d6113ec88dd4bdf12f26db2b3998fd13a144" +CHAIN_ID=137 \ No newline at end of file