diff --git a/.drone.star b/.drone.star index 2924848194b..92eace14e65 100644 --- a/.drone.star +++ b/.drone.star @@ -2039,6 +2039,7 @@ def ocisServer(storage, accounts_hash_difficulty = 4, volumes = [], depends_on = "NATS_NATS_HOST": "0.0.0.0", "NATS_NATS_PORT": 9233, "OCIS_JWT_SECRET": "some-ocis-jwt-secret", + "EVENTHISTORY_STORE": "memory", } if deploy_type == "": diff --git a/changelog/unreleased/activity-api.md b/changelog/unreleased/activity-api.md new file mode 100644 index 00000000000..fd39c2be72b --- /dev/null +++ b/changelog/unreleased/activity-api.md @@ -0,0 +1,5 @@ +Enhancement: Activitylog API + +Adds an api to the `activitylog` service which allows retrieving data by clients to show item activities + +https://github.com/owncloud/ocis/pull/9361 diff --git a/go.mod b/go.mod index b8792fbb062..20a0e946ca8 100644 --- a/go.mod +++ b/go.mod @@ -72,7 +72,7 @@ require ( github.com/onsi/gomega v1.33.1 github.com/open-policy-agent/opa v0.65.0 github.com/orcaman/concurrent-map v1.0.0 - github.com/owncloud/libre-graph-api-go v1.0.5-0.20240529101512-a631b9eeddb3 + github.com/owncloud/libre-graph-api-go v1.0.5-0.20240618162722-2298241331d1 github.com/pkg/errors v0.9.1 github.com/pkg/xattr v0.4.9 github.com/prometheus/client_golang v1.19.1 diff --git a/go.sum b/go.sum index 99a2eec780f..a016609a2ed 100644 --- a/go.sum +++ b/go.sum @@ -1805,8 +1805,8 @@ github.com/oracle/oci-go-sdk v24.3.0+incompatible/go.mod h1:VQb79nF8Z2cwLkLS35uk github.com/orcaman/concurrent-map v1.0.0 h1:I/2A2XPCb4IuQWcQhBhSwGfiuybl/J0ev9HDbW65HOY= github.com/orcaman/concurrent-map v1.0.0/go.mod h1:Lu3tH6HLW3feq74c2GC+jIMS/K2CFcDWnWD9XkenwhI= github.com/ovh/go-ovh v1.1.0/go.mod h1:AxitLZ5HBRPyUd+Zl60Ajaag+rNTdVXWIkzfrVuTXWA= -github.com/owncloud/libre-graph-api-go v1.0.5-0.20240529101512-a631b9eeddb3 h1:2pMI03tU/IWc+Y4p1doTaTE1zrAR55RYOzYrt57/aPA= -github.com/owncloud/libre-graph-api-go v1.0.5-0.20240529101512-a631b9eeddb3/go.mod h1:yXI+rmE8yYx+ZsGVrnCpprw/gZMcxjwntnX2y2+VKxY= +github.com/owncloud/libre-graph-api-go v1.0.5-0.20240618162722-2298241331d1 h1:w1OhLyFevK8NCYn50TEsDpInk/T6qVk/v1LDca6Zx8Y= +github.com/owncloud/libre-graph-api-go v1.0.5-0.20240618162722-2298241331d1/go.mod h1:yXI+rmE8yYx+ZsGVrnCpprw/gZMcxjwntnX2y2+VKxY= github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c h1:rp5dCmg/yLR3mgFuSOe4oEnDDmGLROTvMragMUXpTQw= github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c/go.mod h1:X07ZCGwUbLaax7L0S3Tw4hpejzu63ZrrQiUe6W0hcy0= github.com/pablodz/inotifywaitgo v0.0.6 h1:BTjQfnixXwG7oYmlIiyhWA6iyO9BtxatB3YgiibOTFc= diff --git a/services/search/pkg/query/ast/ast.go b/ocis-pkg/ast/ast.go similarity index 100% rename from services/search/pkg/query/ast/ast.go rename to ocis-pkg/ast/ast.go diff --git a/services/search/pkg/query/ast/test/test.go b/ocis-pkg/ast/test/test.go similarity index 92% rename from services/search/pkg/query/ast/test/test.go rename to ocis-pkg/ast/test/test.go index 01a260de0ea..946856f8b30 100644 --- a/services/search/pkg/query/ast/test/test.go +++ b/ocis-pkg/ast/test/test.go @@ -4,8 +4,7 @@ package test import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" - - "github.com/owncloud/ocis/v2/services/search/pkg/query/ast" + "github.com/owncloud/ocis/v2/ocis-pkg/ast" ) // DiffAst returns a human-readable report of the differences between two values diff --git a/services/search/pkg/query/kql/cast.go b/ocis-pkg/kql/cast.go similarity index 97% rename from services/search/pkg/query/kql/cast.go rename to ocis-pkg/kql/cast.go index c6ca30541ac..55033eb1ff0 100644 --- a/services/search/pkg/query/kql/cast.go +++ b/ocis-pkg/kql/cast.go @@ -5,9 +5,8 @@ import ( "time" "github.com/jinzhu/now" + "github.com/owncloud/ocis/v2/ocis-pkg/ast" "github.com/owncloud/ocis/v2/services/search/pkg/query" - - "github.com/owncloud/ocis/v2/services/search/pkg/query/ast" ) func toNode[T ast.Node](in interface{}) (T, error) { diff --git a/services/search/pkg/query/kql/connect.go b/ocis-pkg/kql/connect.go similarity index 98% rename from services/search/pkg/query/kql/connect.go rename to ocis-pkg/kql/connect.go index 3c926d688ce..674a9156183 100644 --- a/services/search/pkg/query/kql/connect.go +++ b/ocis-pkg/kql/connect.go @@ -3,7 +3,7 @@ package kql import ( "strings" - "github.com/owncloud/ocis/v2/services/search/pkg/query/ast" + "github.com/owncloud/ocis/v2/ocis-pkg/ast" ) // connectNodes connects given nodes diff --git a/services/search/pkg/query/kql/dictionary.peg b/ocis-pkg/kql/dictionary.peg similarity index 100% rename from services/search/pkg/query/kql/dictionary.peg rename to ocis-pkg/kql/dictionary.peg diff --git a/services/search/pkg/query/kql/dictionary_gen.go b/ocis-pkg/kql/dictionary_gen.go similarity index 100% rename from services/search/pkg/query/kql/dictionary_gen.go rename to ocis-pkg/kql/dictionary_gen.go diff --git a/services/search/pkg/query/kql/dictionary_test.go b/ocis-pkg/kql/dictionary_test.go similarity index 99% rename from services/search/pkg/query/kql/dictionary_test.go rename to ocis-pkg/kql/dictionary_test.go index adf4bd2d45e..812f831df79 100644 --- a/services/search/pkg/query/kql/dictionary_test.go +++ b/ocis-pkg/kql/dictionary_test.go @@ -6,12 +6,11 @@ import ( "time" "github.com/jinzhu/now" + "github.com/owncloud/ocis/v2/ocis-pkg/ast" + "github.com/owncloud/ocis/v2/ocis-pkg/ast/test" + "github.com/owncloud/ocis/v2/ocis-pkg/kql" "github.com/owncloud/ocis/v2/services/search/pkg/query" tAssert "github.com/stretchr/testify/assert" - - "github.com/owncloud/ocis/v2/services/search/pkg/query/ast" - "github.com/owncloud/ocis/v2/services/search/pkg/query/ast/test" - "github.com/owncloud/ocis/v2/services/search/pkg/query/kql" ) func TestParse_Spec(t *testing.T) { diff --git a/services/search/pkg/query/kql/doc.go b/ocis-pkg/kql/doc.go similarity index 100% rename from services/search/pkg/query/kql/doc.go rename to ocis-pkg/kql/doc.go diff --git a/services/search/pkg/query/kql/engine_suite_test.go b/ocis-pkg/kql/engine_suite_test.go similarity index 100% rename from services/search/pkg/query/kql/engine_suite_test.go rename to ocis-pkg/kql/engine_suite_test.go diff --git a/services/search/pkg/query/kql/factory.go b/ocis-pkg/kql/factory.go similarity index 98% rename from services/search/pkg/query/kql/factory.go rename to ocis-pkg/kql/factory.go index 0eb858dd97a..99deef825d8 100644 --- a/services/search/pkg/query/kql/factory.go +++ b/ocis-pkg/kql/factory.go @@ -3,7 +3,7 @@ package kql import ( "strings" - "github.com/owncloud/ocis/v2/services/search/pkg/query/ast" + "github.com/owncloud/ocis/v2/ocis-pkg/ast" ) func base(text []byte, pos position) (*ast.Base, error) { diff --git a/services/search/pkg/query/kql/gen.go b/ocis-pkg/kql/gen.go similarity index 100% rename from services/search/pkg/query/kql/gen.go rename to ocis-pkg/kql/gen.go diff --git a/services/search/pkg/query/kql/kql.go b/ocis-pkg/kql/kql.go similarity index 94% rename from services/search/pkg/query/kql/kql.go rename to ocis-pkg/kql/kql.go index 2cf03a7f066..6e14cc7b6f5 100644 --- a/services/search/pkg/query/kql/kql.go +++ b/ocis-pkg/kql/kql.go @@ -5,7 +5,7 @@ import ( "errors" "time" - "github.com/owncloud/ocis/v2/services/search/pkg/query/ast" + "github.com/owncloud/ocis/v2/ocis-pkg/ast" ) // The operator node value definition diff --git a/services/search/pkg/query/kql/kql_test.go b/ocis-pkg/kql/kql_test.go similarity index 88% rename from services/search/pkg/query/kql/kql_test.go rename to ocis-pkg/kql/kql_test.go index c130f755820..09f1249c5bb 100644 --- a/services/search/pkg/query/kql/kql_test.go +++ b/ocis-pkg/kql/kql_test.go @@ -3,11 +3,10 @@ package kql_test import ( "testing" + "github.com/owncloud/ocis/v2/ocis-pkg/ast" + "github.com/owncloud/ocis/v2/ocis-pkg/kql" "github.com/owncloud/ocis/v2/services/search/pkg/query" tAssert "github.com/stretchr/testify/assert" - - "github.com/owncloud/ocis/v2/services/search/pkg/query/ast" - "github.com/owncloud/ocis/v2/services/search/pkg/query/kql" ) func TestNewAST(t *testing.T) { diff --git a/services/search/pkg/query/kql/validate.go b/ocis-pkg/kql/validate.go similarity index 91% rename from services/search/pkg/query/kql/validate.go rename to ocis-pkg/kql/validate.go index 96c6761ab17..0712a1a7ecc 100644 --- a/services/search/pkg/query/kql/validate.go +++ b/ocis-pkg/kql/validate.go @@ -1,8 +1,8 @@ package kql import ( + "github.com/owncloud/ocis/v2/ocis-pkg/ast" "github.com/owncloud/ocis/v2/services/search/pkg/query" - "github.com/owncloud/ocis/v2/services/search/pkg/query/ast" ) func validateAst(a *ast.Ast) error { diff --git a/ocis-pkg/l10n/l10n.go b/ocis-pkg/l10n/l10n.go index f9b89528f1a..5c99c399a7d 100644 --- a/ocis-pkg/l10n/l10n.go +++ b/ocis-pkg/l10n/l10n.go @@ -14,6 +14,9 @@ import ( micrometadata "go-micro.dev/v4/metadata" ) +// HeaderAcceptLanguage is the header key for the accept-language header +var HeaderAcceptLanguage = "Accept-Language" + // Template marks a string as translatable func Template(s string) string { return s } diff --git a/services/activitylog/Makefile b/services/activitylog/Makefile index dd41e6af723..f57e85e66fa 100644 --- a/services/activitylog/Makefile +++ b/services/activitylog/Makefile @@ -1,6 +1,10 @@ SHELL := bash NAME := activitylog +# Where to write the files generated by this makefile. +OUTPUT_DIR = ./pkg/service/l10n +TEMPLATE_FILE = ./pkg/service/l10n/activitylog.pot + include ../../.make/recursion.mk ############ tooling ############ @@ -29,6 +33,26 @@ ci-go-generate: # CI runs ci-node-generate automatically before this target .PHONY: ci-node-generate ci-node-generate: +############ translations ######## +.PHONY: l10n-pull +l10n-pull: + cd $(OUTPUT_DIR) && tx pull --all --force --skip --minimum-perc=75 + +.PHONY: l10n-push +l10n-push: + cd $(OUTPUT_DIR) && tx push -s --skip + +.PHONY: l10n-read +l10n-read: $(GO_XGETTEXT) + go-xgettext -o $(OUTPUT_DIR)/activitylog.pot --keyword=l10n.Template -s pkg/service/response.go + +.PHONY: l10n-write +l10n-write: + +.PHONY: l10n-clean +l10n-clean: + rm -f $(TEMPLATE_FILE); + ############ licenses ############ .PHONY: ci-node-check-licenses ci-node-check-licenses: diff --git a/services/activitylog/pkg/command/server.go b/services/activitylog/pkg/command/server.go index ed07a722a07..4ed99d10bc1 100644 --- a/services/activitylog/pkg/command/server.go +++ b/services/activitylog/pkg/command/server.go @@ -14,13 +14,16 @@ import ( "github.com/owncloud/ocis/v2/ocis-pkg/handlers" "github.com/owncloud/ocis/v2/ocis-pkg/registry" "github.com/owncloud/ocis/v2/ocis-pkg/service/debug" + ogrpc "github.com/owncloud/ocis/v2/ocis-pkg/service/grpc" "github.com/owncloud/ocis/v2/ocis-pkg/tracing" "github.com/owncloud/ocis/v2/ocis-pkg/version" + ehsvc "github.com/owncloud/ocis/v2/protogen/gen/ocis/services/eventhistory/v0" + settingssvc "github.com/owncloud/ocis/v2/protogen/gen/ocis/services/settings/v0" "github.com/owncloud/ocis/v2/services/activitylog/pkg/config" "github.com/owncloud/ocis/v2/services/activitylog/pkg/config/parser" "github.com/owncloud/ocis/v2/services/activitylog/pkg/logging" "github.com/owncloud/ocis/v2/services/activitylog/pkg/metrics" - "github.com/owncloud/ocis/v2/services/activitylog/pkg/service" + "github.com/owncloud/ocis/v2/services/activitylog/pkg/server/http" "github.com/urfave/cli/v2" microstore "go-micro.dev/v4/store" ) @@ -30,20 +33,13 @@ var _registeredEvents = []events.Unmarshaller{ events.FileTouched{}, events.ContainerCreated{}, events.ItemTrashed{}, - events.ItemPurged{}, events.ItemMoved{}, events.ShareCreated{}, - events.ShareUpdated{}, events.ShareRemoved{}, events.LinkCreated{}, - events.LinkUpdated{}, events.LinkRemoved{}, events.SpaceShared{}, - events.SpaceShareUpdated{}, events.SpaceUnshared{}, - - // TODO: file downloaded only for public links. How to do this? - events.FileDownloaded{}, } // Server is the entrypoint for the server command. @@ -109,15 +105,29 @@ func Server(cfg *config.Config) *cli.Command { return fmt.Errorf("could not get reva client selector: %s", err) } + grpcClient, err := ogrpc.NewClient( + append(ogrpc.GetClientOptions(cfg.GRPCClientTLS), ogrpc.WithTraceProvider(tracerProvider))..., + ) + if err != nil { + return err + } + + hClient := ehsvc.NewEventHistoryService("com.owncloud.api.eventhistory", grpcClient) + vClient := settingssvc.NewValueService("com.owncloud.api.settings", grpcClient) + { - svc, err := service.New( - service.Logger(logger), - service.Config(cfg), - service.TraceProvider(tracerProvider), - service.Stream(evStream), - service.RegisteredEvents(_registeredEvents), - service.Store(evStore), - service.GatewaySelector(gatewaySelector), + svc, err := http.Server( + http.Logger(logger), + http.Config(cfg), + http.Context(ctx), // NOTE: not passing this "option" leads to a panic in go-micro + http.TraceProvider(tracerProvider), + http.Stream(evStream), + http.RegisteredEvents(_registeredEvents), + http.Store(evStore), + http.GatewaySelector(gatewaySelector), + http.HistoryClient(hClient), + http.ValueClient(vClient), + http.RegisteredEvents(_registeredEvents), ) if err != nil { diff --git a/services/activitylog/pkg/config/config.go b/services/activitylog/pkg/config/config.go index 2a7f24137a9..7c84b13178d 100644 --- a/services/activitylog/pkg/config/config.go +++ b/services/activitylog/pkg/config/config.go @@ -23,6 +23,9 @@ type Config struct { RevaGateway string `yaml:"reva_gateway" env:"OCIS_REVA_GATEWAY" desc:"CS3 gateway used to look up user metadata" introductionVersion:"5.0"` GRPCClientTLS *shared.GRPCClientTLS `yaml:"grpc_client_tls"` + HTTP HTTP `yaml:"http"` + TokenManager *TokenManager `yaml:"token_manager"` + ServiceAccount ServiceAccount `yaml:"service_account"` Context context.Context `yaml:"-"` @@ -46,7 +49,7 @@ type Store struct { Database string `yaml:"database" env:"ACTIVITYLOG_STORE_DATABASE" desc:"The database name the configured store should use." introductionVersion:"pre5.0"` Table string `yaml:"table" env:"ACTIVITYLOG_STORE_TABLE" desc:"The database table the store should use." introductionVersion:"pre5.0"` TTL time.Duration `yaml:"ttl" env:"OCIS_PERSISTENT_STORE_TTL;ACTIVITYLOG_STORE_TTL" desc:"Time to live for events in the store. See the Environment Variable Types description for more details." introductionVersion:"pre5.0"` - Size int `yaml:"size" env:"OCIS_PERSISTENT_STORE_SIZE;ACTIVITYLOG_STORE_SIZE" desc:"The maximum quantity of items in the store. Only applies when store type 'ocmem' is configured. Defaults to 512 which is derived from the ocmem package though not exclicitly set as default." introductionVersion:"pre5.0"` + Size int `yaml:"size" env:"OCIS_PERSISTENT_STORE_SIZE;ACTIVITYLOG_STORE_SIZE" desc:"The maximum quantity of items in the store. Only applies when store type 'ocmem' is configured. Defaults to 512 which is derived from the ocmem package though not explicitly set as default." introductionVersion:"pre5.0"` AuthUsername string `yaml:"username" env:"OCIS_PERSISTENT_STORE_AUTH_USERNAME;ACTIVITYLOG_STORE_AUTH_USERNAME" desc:"The username to authenticate with the store. Only applies when store type 'nats-js-kv' is configured." introductionVersion:"5.0"` AuthPassword string `yaml:"password" env:"OCIS_PERSISTENT_STORE_AUTH_PASSWORD;ACTIVITYLOG_STORE_AUTH_PASSWORD" desc:"The password to authenticate with the store. Only applies when store type 'nats-js-kv' is configured." introductionVersion:"5.0"` } @@ -56,3 +59,25 @@ type ServiceAccount struct { ServiceAccountID string `yaml:"service_account_id" env:"OCIS_SERVICE_ACCOUNT_ID;ACTIVITYLOG_SERVICE_ACCOUNT_ID" desc:"The ID of the service account the service should use. See the 'auth-service' service description for more details." introductionVersion:"5.0"` ServiceAccountSecret string `yaml:"service_account_secret" env:"OCIS_SERVICE_ACCOUNT_SECRET;ACTIVITYOG_SERVICE_ACCOUNT_SECRET" desc:"The service account secret." introductionVersion:"5.0"` } + +// CORS defines the available cors configuration. +type CORS struct { + AllowedOrigins []string `yaml:"allow_origins" env:"OCIS_CORS_ALLOW_ORIGINS;ACTIVITYLOG_CORS_ALLOW_ORIGINS" desc:"A list of allowed CORS origins. See following chapter for more details: *Access-Control-Allow-Origin* at https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin. See the Environment Variable Types description for more details." introductionVersion:"pre5.0"` + AllowedMethods []string `yaml:"allow_methods" env:"OCIS_CORS_ALLOW_METHODS;ACTIVITYLOG_CORS_ALLOW_METHODS" desc:"A list of allowed CORS methods. See following chapter for more details: *Access-Control-Request-Method* at https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Request-Method. See the Environment Variable Types description for more details." introductionVersion:"pre5.0"` + AllowedHeaders []string `yaml:"allow_headers" env:"OCIS_CORS_ALLOW_HEADERS;ACTIVITYLOG_CORS_ALLOW_HEADERS" desc:"A list of allowed CORS headers. See following chapter for more details: *Access-Control-Request-Headers* at https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Request-Headers. See the Environment Variable Types description for more details." introductionVersion:"pre5.0"` + AllowCredentials bool `yaml:"allow_credentials" env:"OCIS_CORS_ALLOW_CREDENTIALS;ACTIVITYLOG_CORS_ALLOW_CREDENTIALS" desc:"Allow credentials for CORS.See following chapter for more details: *Access-Control-Allow-Credentials* at https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Credentials." introductionVersion:"pre5.0"` +} + +// HTTP defines the available http configuration. +type HTTP struct { + Addr string `yaml:"addr" env:"ACTIVITYLOG_HTTP_ADDR" desc:"The bind address of the HTTP service." introductionVersion:"pre5.0"` + Namespace string `yaml:"-"` + Root string `yaml:"root" env:"ACTIVITYLOG_HTTP_ROOT" desc:"Subdirectory that serves as the root for this HTTP service." introductionVersion:"pre5.0"` + CORS CORS `yaml:"cors"` + TLS shared.HTTPServiceTLS `yaml:"tls"` +} + +// TokenManager is the config for using the reva token manager +type TokenManager struct { + JWTSecret string `yaml:"jwt_secret" env:"OCIS_JWT_SECRET;ACTIVITYLOG_JWT_SECRET" desc:"The secret to mint and validate jwt tokens." introductionVersion:"pre5.0"` +} diff --git a/services/activitylog/pkg/config/debug.go b/services/activitylog/pkg/config/debug.go index 4f7fc486c6f..b1435d9d261 100644 --- a/services/activitylog/pkg/config/debug.go +++ b/services/activitylog/pkg/config/debug.go @@ -2,8 +2,8 @@ package config // Debug defines the available debug configuration. type Debug struct { - Addr string `yaml:"addr" env:"CLIENTLOG_DEBUG_ADDR" desc:"Bind address of the debug server, where metrics, health, config and debug endpoints will be exposed." introductionVersion:"5.0"` - Token string `yaml:"token" env:"CLIENTLOG_DEBUG_TOKEN" desc:"Token to secure the metrics endpoint." introductionVersion:"5.0"` - Pprof bool `yaml:"pprof" env:"CLIENTLOG_DEBUG_PPROF" desc:"Enables pprof, which can be used for profiling." introductionVersion:"5.0"` - Zpages bool `yaml:"zpages" env:"CLIENTLOG_DEBUG_ZPAGES" desc:"Enables zpages, which can be used for collecting and viewing in-memory traces." introductionVersion:"5.0"` + Addr string `yaml:"addr" env:"ACTIVITYLOG_DEBUG_ADDR" desc:"Bind address of the debug server, where metrics, health, config and debug endpoints will be exposed." introductionVersion:"5.0"` + Token string `yaml:"token" env:"ACTIVITYLOG_DEBUG_TOKEN" desc:"Token to secure the metrics endpoint." introductionVersion:"5.0"` + Pprof bool `yaml:"pprof" env:"ACTIVITYLOG_DEBUG_PPROF" desc:"Enables pprof, which can be used for profiling." introductionVersion:"5.0"` + Zpages bool `yaml:"zpages" env:"ACTIVITYLOG_DEBUG_ZPAGES" desc:"Enables zpages, which can be used for collecting and viewing in-memory traces." introductionVersion:"5.0"` } diff --git a/services/activitylog/pkg/config/defaults/defaultconfig.go b/services/activitylog/pkg/config/defaults/defaultconfig.go index 84edc80fd3c..00103af6be1 100644 --- a/services/activitylog/pkg/config/defaults/defaultconfig.go +++ b/services/activitylog/pkg/config/defaults/defaultconfig.go @@ -38,6 +38,17 @@ func DefaultConfig() *config.Config { Table: "", }, RevaGateway: shared.DefaultRevaConfig().Address, + HTTP: config.HTTP{ + Addr: "127.0.0.1:0", + Root: "/", + Namespace: "com.owncloud.web", + CORS: config.CORS{ + AllowedOrigins: []string{"*"}, + AllowedMethods: []string{"GET"}, + AllowedHeaders: []string{"Authorization", "Origin", "Content-Type", "Accept", "X-Requested-With", "X-Request-Id", "Ocs-Apirequest"}, + AllowCredentials: true, + }, + }, } } @@ -55,6 +66,22 @@ func EnsureDefaults(cfg *config.Config) { cfg.Log = &config.Log{} } + if cfg.GRPCClientTLS == nil && cfg.Commons != nil { + cfg.GRPCClientTLS = structs.CopyOrZeroValue(cfg.Commons.GRPCClientTLS) + } + + if cfg.TokenManager == nil && cfg.Commons != nil && cfg.Commons.TokenManager != nil { + cfg.TokenManager = &config.TokenManager{ + JWTSecret: cfg.Commons.TokenManager.JWTSecret, + } + } else if cfg.TokenManager == nil { + cfg.TokenManager = &config.TokenManager{} + } + + if cfg.Commons != nil { + cfg.HTTP.TLS = cfg.Commons.HTTPServiceTLS + } + // provide with defaults for shared tracing, since we need a valid destination address for "envdecode". if cfg.Tracing == nil && cfg.Commons != nil && cfg.Commons.Tracing != nil { cfg.Tracing = &config.Tracing{ @@ -67,9 +94,6 @@ func EnsureDefaults(cfg *config.Config) { cfg.Tracing = &config.Tracing{} } - if cfg.GRPCClientTLS == nil && cfg.Commons != nil { - cfg.GRPCClientTLS = structs.CopyOrZeroValue(cfg.Commons.GRPCClientTLS) - } } // Sanitize sanitizes the config diff --git a/services/activitylog/pkg/config/log.go b/services/activitylog/pkg/config/log.go index 6661076218f..6b19a620411 100644 --- a/services/activitylog/pkg/config/log.go +++ b/services/activitylog/pkg/config/log.go @@ -2,8 +2,8 @@ package config // Log defines the available log configuration. type Log struct { - Level string `mapstructure:"level" env:"OCIS_LOG_LEVEL;CLIENTLOG_USERLOG_LOG_LEVEL" desc:"The log level. Valid values are: 'panic', 'fatal', 'error', 'warn', 'info', 'debug', 'trace'." introductionVersion:"5.0"` - Pretty bool `mapstructure:"pretty" env:"OCIS_LOG_PRETTY;CLIENTLOG_USERLOG_LOG_PRETTY" desc:"Activates pretty log output." introductionVersion:"5.0"` - Color bool `mapstructure:"color" env:"OCIS_LOG_COLOR;CLIENTLOG_USERLOG_LOG_COLOR" desc:"Activates colorized log output." introductionVersion:"5.0"` - File string `mapstructure:"file" env:"OCIS_LOG_FILE;CLIENTLOG_USERLOG_LOG_FILE" desc:"The path to the log file. Activates logging to this file if set." introductionVersion:"5.0"` + Level string `mapstructure:"level" env:"OCIS_LOG_LEVEL;ACTIVITYLOG_LOG_LEVEL" desc:"The log level. Valid values are: 'panic', 'fatal', 'error', 'warn', 'info', 'debug', 'trace'." introductionVersion:"5.0"` + Pretty bool `mapstructure:"pretty" env:"OCIS_LOG_PRETTY;ACTIVITYLOG_LOG_PRETTY" desc:"Activates pretty log output." introductionVersion:"5.0"` + Color bool `mapstructure:"color" env:"OCIS_LOG_COLOR;ACTIVITYLOG_LOG_COLOR" desc:"Activates colorized log output." introductionVersion:"5.0"` + File string `mapstructure:"file" env:"OCIS_LOG_FILE;ACTIVITYLOG_LOG_FILE" desc:"The path to the log file. Activates logging to this file if set." introductionVersion:"5.0"` } diff --git a/services/activitylog/pkg/config/tracing.go b/services/activitylog/pkg/config/tracing.go index f4320a0860a..f39dabd38ac 100644 --- a/services/activitylog/pkg/config/tracing.go +++ b/services/activitylog/pkg/config/tracing.go @@ -4,10 +4,10 @@ import "github.com/owncloud/ocis/v2/ocis-pkg/tracing" // Tracing defines the available tracing configuration. type Tracing struct { - Enabled bool `yaml:"enabled" env:"OCIS_TRACING_ENABLED;CLIENTLOG_TRACING_ENABLED" desc:"Activates tracing." introductionVersion:"5.0"` - Type string `yaml:"type" env:"OCIS_TRACING_TYPE;CLIENTLOG_TRACING_TYPE" desc:"The type of tracing. Defaults to '', which is the same as 'jaeger'. Allowed tracing types are 'jaeger' and '' as of now." introductionVersion:"5.0"` - Endpoint string `yaml:"endpoint" env:"OCIS_TRACING_ENDPOINT;CLIENTLOG_TRACING_ENDPOINT" desc:"The endpoint of the tracing agent." introductionVersion:"5.0"` - Collector string `yaml:"collector" env:"OCIS_TRACING_COLLECTOR;CLIENTLOG_TRACING_COLLECTOR" desc:"The HTTP endpoint for sending spans directly to a collector, i.e. http://jaeger-collector:14268/api/traces. Only used if the tracing endpoint is unset." introductionVersion:"5.0"` + Enabled bool `yaml:"enabled" env:"OCIS_TRACING_ENABLED;ACTIVITYLOG_TRACING_ENABLED" desc:"Activates tracing." introductionVersion:"5.0"` + Type string `yaml:"type" env:"OCIS_TRACING_TYPE;ACTIVITYLOG_TRACING_TYPE" desc:"The type of tracing. Defaults to '', which is the same as 'jaeger'. Allowed tracing types are 'jaeger' and '' as of now." introductionVersion:"5.0"` + Endpoint string `yaml:"endpoint" env:"OCIS_TRACING_ENDPOINT;ACTIVITYLOG_TRACING_ENDPOINT" desc:"The endpoint of the tracing agent." introductionVersion:"5.0"` + Collector string `yaml:"collector" env:"OCIS_TRACING_COLLECTOR;ACTIVITYLOG_TRACING_COLLECTOR" desc:"The HTTP endpoint for sending spans directly to a collector, i.e. http://jaeger-collector:14268/api/traces. Only used if the tracing endpoint is unset." introductionVersion:"5.0"` } // Convert Tracing to the tracing package's Config struct. diff --git a/services/activitylog/pkg/server/http/option.go b/services/activitylog/pkg/server/http/option.go new file mode 100644 index 00000000000..760cb131b3a --- /dev/null +++ b/services/activitylog/pkg/server/http/option.go @@ -0,0 +1,139 @@ +package http + +import ( + "context" + + gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1" + "github.com/cs3org/reva/v2/pkg/events" + "github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool" + "github.com/owncloud/ocis/v2/ocis-pkg/log" + ehsvc "github.com/owncloud/ocis/v2/protogen/gen/ocis/services/eventhistory/v0" + settingssvc "github.com/owncloud/ocis/v2/protogen/gen/ocis/services/settings/v0" + "github.com/owncloud/ocis/v2/services/activitylog/pkg/config" + "github.com/owncloud/ocis/v2/services/activitylog/pkg/metrics" + "github.com/urfave/cli/v2" + "go-micro.dev/v4/store" + "go.opentelemetry.io/otel/trace" +) + +// Option defines a single option function. +type Option func(o *Options) + +// Options defines the available options for this package. +type Options struct { + Logger log.Logger + Context context.Context + Config *config.Config + Metrics *metrics.Metrics + Flags []cli.Flag + Namespace string + Store store.Store + Stream events.Stream + GatewaySelector pool.Selectable[gateway.GatewayAPIClient] + TraceProvider trace.TracerProvider + HistoryClient ehsvc.EventHistoryService + ValueClient settingssvc.ValueService + RegisteredEvents []events.Unmarshaller +} + +// newOptions initializes the available default options. +func newOptions(opts ...Option) Options { + opt := Options{} + + for _, o := range opts { + o(&opt) + } + + return opt +} + +// Logger provides a function to set the logger option. +func Logger(val log.Logger) Option { + return func(o *Options) { + o.Logger = val + } +} + +// Context provides a function to set the context option. +func Context(val context.Context) Option { + return func(o *Options) { + o.Context = val + } +} + +// Config provides a function to set the config option. +func Config(val *config.Config) Option { + return func(o *Options) { + o.Config = val + } +} + +// Metrics provides a function to set the metrics option. +func Metrics(val *metrics.Metrics) Option { + return func(o *Options) { + o.Metrics = val + } +} + +// Flags provides a function to set the flags option. +func Flags(val []cli.Flag) Option { + return func(o *Options) { + o.Flags = append(o.Flags, val...) + } +} + +// Namespace provides a function to set the Namespace option. +func Namespace(val string) Option { + return func(o *Options) { + o.Namespace = val + } +} + +// Store provides a function to configure the store +func Store(store store.Store) Option { + return func(o *Options) { + o.Store = store + } +} + +// Stream provides a function to configure the stream +func Stream(stream events.Stream) Option { + return func(o *Options) { + o.Stream = stream + } +} + +// GatewaySelector provides a function to configure the gateway client selector +func GatewaySelector(gatewaySelector pool.Selectable[gateway.GatewayAPIClient]) Option { + return func(o *Options) { + o.GatewaySelector = gatewaySelector + } +} + +// HistoryClient provides a function to configure the event history client +func HistoryClient(h ehsvc.EventHistoryService) Option { + return func(o *Options) { + o.HistoryClient = h + } +} + +// RegisteredEvents provides a function to register events +func RegisteredEvents(evs []events.Unmarshaller) Option { + return func(o *Options) { + o.RegisteredEvents = evs + } +} + +// TraceProvider provides a function to set the TracerProvider option +func TraceProvider(val trace.TracerProvider) Option { + return func(o *Options) { + o.TraceProvider = val + } +} + +// ValueClient provides a function to set the ValueClient options +func ValueClient(val settingssvc.ValueService) Option { + return func(o *Options) { + o.ValueClient = val + } +} diff --git a/services/activitylog/pkg/server/http/server.go b/services/activitylog/pkg/server/http/server.go new file mode 100644 index 00000000000..86a239b2873 --- /dev/null +++ b/services/activitylog/pkg/server/http/server.go @@ -0,0 +1,101 @@ +package http + +import ( + "fmt" + + stdhttp "net/http" + + "github.com/go-chi/chi/v5" + chimiddleware "github.com/go-chi/chi/v5/middleware" + "github.com/owncloud/ocis/v2/ocis-pkg/account" + "github.com/owncloud/ocis/v2/ocis-pkg/cors" + "github.com/owncloud/ocis/v2/ocis-pkg/middleware" + "github.com/owncloud/ocis/v2/ocis-pkg/service/http" + "github.com/owncloud/ocis/v2/ocis-pkg/tracing" + "github.com/owncloud/ocis/v2/ocis-pkg/version" + svc "github.com/owncloud/ocis/v2/services/activitylog/pkg/service" + "github.com/riandyrn/otelchi" + "go-micro.dev/v4" +) + +// Service is the service interface +type Service interface{} + +// Server initializes the http service and server. +func Server(opts ...Option) (http.Service, error) { + options := newOptions(opts...) + + service, err := http.NewService( + http.TLSConfig(options.Config.HTTP.TLS), + http.Logger(options.Logger), + http.Namespace(options.Config.HTTP.Namespace), + http.Name(options.Config.Service.Name), + http.Version(version.GetString()), + http.Address(options.Config.HTTP.Addr), + http.Context(options.Context), + http.Flags(options.Flags...), + http.TraceProvider(options.TraceProvider), + ) + if err != nil { + options.Logger.Error(). + Err(err). + Msg("Error initializing http service") + return http.Service{}, fmt.Errorf("could not initialize http service: %w", err) + } + + middlewares := []func(stdhttp.Handler) stdhttp.Handler{ + chimiddleware.RequestID, + middleware.Version( + options.Config.Service.Name, + version.GetString(), + ), + middleware.Logger( + options.Logger, + ), + middleware.ExtractAccountUUID( + account.Logger(options.Logger), + account.JWTSecret(options.Config.TokenManager.JWTSecret), + ), + middleware.Cors( + cors.Logger(options.Logger), + cors.AllowedOrigins(options.Config.HTTP.CORS.AllowedOrigins), + cors.AllowedMethods(options.Config.HTTP.CORS.AllowedMethods), + cors.AllowedHeaders(options.Config.HTTP.CORS.AllowedHeaders), + cors.AllowCredentials(options.Config.HTTP.CORS.AllowCredentials), + ), + } + + mux := chi.NewMux() + mux.Use(middlewares...) + + mux.Use( + otelchi.Middleware( + "actitivylog", + otelchi.WithChiRoutes(mux), + otelchi.WithTracerProvider(options.TraceProvider), + otelchi.WithPropagators(tracing.GetPropagator()), + ), + ) + + handle, err := svc.New( + svc.Logger(options.Logger), + svc.Stream(options.Stream), + svc.Mux(mux), + svc.Store(options.Store), + svc.Config(options.Config), + svc.GatewaySelector(options.GatewaySelector), + svc.TraceProvider(options.TraceProvider), + svc.HistoryClient(options.HistoryClient), + svc.ValueClient(options.ValueClient), + svc.RegisteredEvents(options.RegisteredEvents), + ) + if err != nil { + return http.Service{}, err + } + + if err := micro.RegisterHandler(service.Server(), handle); err != nil { + return http.Service{}, err + } + + return service, nil +} diff --git a/services/activitylog/pkg/service/http.go b/services/activitylog/pkg/service/http.go new file mode 100644 index 00000000000..1fbd2fb53d6 --- /dev/null +++ b/services/activitylog/pkg/service/http.go @@ -0,0 +1,298 @@ +package service + +import ( + "embed" + "encoding/json" + "errors" + "net/http" + "path/filepath" + "strconv" + "strings" + "time" + + provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" + revactx "github.com/cs3org/reva/v2/pkg/ctx" + "github.com/cs3org/reva/v2/pkg/events" + "github.com/cs3org/reva/v2/pkg/storagespace" + "github.com/cs3org/reva/v2/pkg/utils" + "google.golang.org/grpc/metadata" + + "github.com/owncloud/ocis/v2/ocis-pkg/ast" + "github.com/owncloud/ocis/v2/ocis-pkg/kql" + "github.com/owncloud/ocis/v2/ocis-pkg/l10n" + ehmsg "github.com/owncloud/ocis/v2/protogen/gen/ocis/messages/eventhistory/v0" + ehsvc "github.com/owncloud/ocis/v2/protogen/gen/ocis/services/eventhistory/v0" +) + +var ( + //go:embed l10n/locale + _localeFS embed.FS + + // subfolder where the translation files are stored + _localeSubPath = "l10n/locale" + + // domain of the activitylog service (transifex) + _domain = "activitylog" +) + +// ServeHTTP implements the http.Handler interface. +func (s *ActivitylogService) ServeHTTP(w http.ResponseWriter, r *http.Request) { + s.mux.ServeHTTP(w, r) +} + +// HandleGetItemActivities handles the request to get the activities of an item. +func (s *ActivitylogService) HandleGetItemActivities(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + ctx = metadata.AppendToOutgoingContext(ctx, revactx.TokenHeader, r.Header.Get("X-Access-Token")) + + activeUser, ok := revactx.ContextGetUser(ctx) + if !ok { + w.WriteHeader(http.StatusUnauthorized) + return + } + + rid, limit, rawActivityAccepted, activityAccepted, err := s.getFilters(r.URL.Query().Get("kql")) + if err != nil { + s.log.Info().Str("query", r.URL.Query().Get("kql")).Err(err).Msg("error getting filters") + _, _ = w.Write([]byte(err.Error())) + w.WriteHeader(http.StatusBadRequest) + return + } + + raw, err := s.Activities(rid) + if err != nil { + s.log.Error().Err(err).Msg("error getting activities") + w.WriteHeader(http.StatusInternalServerError) + return + } + + ids := make([]string, 0, len(raw)) + toDelete := make(map[string]struct{}, len(raw)) + for _, a := range raw { + if !rawActivityAccepted(a) { + continue + } + ids = append(ids, a.EventID) + toDelete[a.EventID] = struct{}{} + } + + evRes, err := s.evHistory.GetEvents(r.Context(), &ehsvc.GetEventsRequest{Ids: ids}) + if err != nil { + s.log.Error().Err(err).Msg("error getting events") + w.WriteHeader(http.StatusInternalServerError) + return + } + + var resp GetActivitiesResponse + for _, e := range evRes.GetEvents() { + delete(toDelete, e.GetId()) + + if limit != 0 && len(resp.Activities) >= limit { + continue + } + + if !activityAccepted(e) { + continue + } + + var ( + message string + ts time.Time + vars map[string]interface{} + ) + + switch ev := s.unwrapEvent(e).(type) { + case nil: + // error already logged in unwrapEvent + continue + case events.UploadReady: + message = MessageResourceCreated + ts = utils.TSToTime(ev.Timestamp) + vars, err = s.GetVars(ctx, WithResource(ev.FileRef, true), WithUser(ev.ExecutingUser.GetId(), ev.ExecutingUser.GetDisplayName())) + case events.FileTouched: + message = MessageResourceCreated + ts = utils.TSToTime(ev.Timestamp) + vars, err = s.GetVars(ctx, WithResource(ev.Ref, true), WithUser(ev.Executant, "")) + case events.ContainerCreated: + message = MessageResourceCreated + ts = utils.TSToTime(ev.Timestamp) + vars, err = s.GetVars(ctx, WithResource(ev.Ref, true), WithUser(ev.Executant, "")) + case events.ItemTrashed: + message = MessageResourceTrashed + ts = utils.TSToTime(ev.Timestamp) + vars, err = s.GetVars(ctx, WithTrashedResource(ev.Ref, ev.ID), WithUser(ev.Executant, ""), WithSpace(toSpace(ev.Ref))) + case events.ItemMoved: + switch isRename(ev.OldReference, ev.Ref) { + case true: + message = MessageResourceRenamed + vars, err = s.GetVars(ctx, WithResource(ev.Ref, false), WithOldResource(ev.OldReference), WithUser(ev.Executant, "")) + case false: + message = MessageResourceMoved + vars, err = s.GetVars(ctx, WithResource(ev.Ref, true), WithUser(ev.Executant, "")) + } + ts = utils.TSToTime(ev.Timestamp) + case events.ShareCreated: + message = MessageShareCreated + ts = utils.TSToTime(ev.CTime) + vars, err = s.GetVars(ctx, WithResource(toRef(ev.ItemID), false), WithUser(ev.Executant, ""), WithSharee(ev.GranteeUserID, ev.GranteeGroupID)) + case events.ShareRemoved: + message = MessageShareDeleted + ts = ev.Timestamp + vars, err = s.GetVars(ctx, WithResource(toRef(ev.ItemID), false), WithUser(ev.Executant, ""), WithSharee(ev.GranteeUserID, ev.GranteeGroupID)) + case events.LinkCreated: + message = MessageLinkCreated + ts = utils.TSToTime(ev.CTime) + vars, err = s.GetVars(ctx, WithResource(toRef(ev.ItemID), false), WithUser(ev.Executant, "")) + case events.LinkRemoved: + message = MessageLinkDeleted + ts = utils.TSToTime(ev.Timestamp) + vars, err = s.GetVars(ctx, WithResource(toRef(ev.ItemID), false), WithUser(ev.Executant, "")) + case events.SpaceShared: + message = MessageSpaceShared + ts = ev.Timestamp + vars, err = s.GetVars(ctx, WithSpace(ev.ID), WithUser(ev.Executant, ""), WithSharee(ev.GranteeUserID, ev.GranteeGroupID)) + case events.SpaceUnshared: + message = MessageSpaceUnshared + ts = ev.Timestamp + vars, err = s.GetVars(ctx, WithSpace(ev.ID), WithUser(ev.Executant, ""), WithSharee(ev.GranteeUserID, ev.GranteeGroupID)) + } + + if err != nil { + s.log.Error().Err(err).Msg("error getting response data") + continue + } + + // FIXME: configurable default locale? + loc := l10n.MustGetUserLocale(r.Context(), activeUser.GetId().GetOpaqueId(), r.Header.Get(l10n.HeaderAcceptLanguage), s.valService) + t := l10n.NewTranslatorFromCommonConfig("en", _domain, "", _localeFS, _localeSubPath) + + resp.Activities = append(resp.Activities, NewActivity(t.Translate(message, loc), ts, e.GetId(), vars)) + } + + // delete activities in separate go routine + if len(toDelete) > 0 { + go func() { + err := s.RemoveActivities(rid, toDelete) + if err != nil { + s.log.Error().Err(err).Msg("error removing activities") + } + }() + } + + b, err := json.Marshal(resp) + if err != nil { + s.log.Error().Err(err).Msg("error marshalling activities") + w.WriteHeader(http.StatusInternalServerError) + return + } + + if _, err := w.Write(b); err != nil { + s.log.Error().Err(err).Msg("error writing response") + w.WriteHeader(http.StatusInternalServerError) + return + } + w.WriteHeader(http.StatusOK) +} + +func (s *ActivitylogService) unwrapEvent(e *ehmsg.Event) interface{} { + etype, ok := s.registeredEvents[e.GetType()] + if !ok { + s.log.Error().Str("eventid", e.GetId()).Str("eventtype", e.GetType()).Msg("event not registered") + return nil + } + + einterface, err := etype.Unmarshal(e.GetEvent()) + if err != nil { + s.log.Error().Str("eventid", e.GetId()).Str("eventtype", e.GetType()).Msg("failed to umarshal event") + return nil + } + + return einterface +} + +func (s *ActivitylogService) getFilters(query string) (*provider.ResourceId, int, func(RawActivity) bool, func(*ehmsg.Event) bool, error) { + qast, err := kql.Builder{}.Build(query) + if err != nil { + return nil, 0, nil, nil, err + } + + prefilters := make([]func(RawActivity) bool, 0) + postfilters := make([]func(*ehmsg.Event) bool, 0) + + var ( + itemID string + limit int + ) + + for _, n := range qast.Nodes { + switch v := n.(type) { + case *ast.StringNode: + switch strings.ToLower(v.Key) { + case "itemid": + itemID = v.Value + case "depth": + depth, err := strconv.Atoi(v.Value) + if err != nil { + return nil, limit, nil, nil, err + } + + prefilters = append(prefilters, func(a RawActivity) bool { + return a.Depth <= depth + }) + case "limit": + l, err := strconv.Atoi(v.Value) + if err != nil { + return nil, limit, nil, nil, err + } + + limit = l + } + case *ast.DateTimeNode: + switch v.Operator.Value { + case "<", "<=": + prefilters = append(prefilters, func(a RawActivity) bool { + return a.Timestamp.Before(v.Value) + }) + case ">", ">=": + prefilters = append(prefilters, func(a RawActivity) bool { + return a.Timestamp.After(v.Value) + }) + } + case *ast.OperatorNode: + if v.Value != "AND" { + return nil, limit, nil, nil, errors.New("only AND operator is supported") + } + } + } + + rid, err := storagespace.ParseID(itemID) + if err != nil { + return nil, limit, nil, nil, err + } + pref := func(a RawActivity) bool { + for _, f := range prefilters { + if !f(a) { + return false + } + } + return true + } + postf := func(e *ehmsg.Event) bool { + for _, f := range postfilters { + if !f(e) { + return false + } + } + return true + } + return &rid, limit, pref, postf, nil +} + +// returns true if this is just a rename +func isRename(o, n *provider.Reference) bool { + // if resourceids are different we assume it is a move + if !utils.ResourceIDEqual(o.GetResourceId(), n.GetResourceId()) { + return false + } + return filepath.Base(o.GetPath()) != filepath.Base(n.GetPath()) +} diff --git a/services/activitylog/pkg/service/l10n/locale/tmp.txt b/services/activitylog/pkg/service/l10n/locale/tmp.txt new file mode 100644 index 00000000000..e69de29bb2d diff --git a/services/activitylog/pkg/service/l10n/userlog.pot b/services/activitylog/pkg/service/l10n/userlog.pot new file mode 100644 index 00000000000..606d31510c8 --- /dev/null +++ b/services/activitylog/pkg/service/l10n/userlog.pot @@ -0,0 +1,22 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +#, fuzzy +msgid "" +msgstr "Project-Id-Version: \n" + "Report-Msgid-Bugs-To: EMAIL\n" + "POT-Creation-Date: 2024-06-14 15:29+0200\n" + "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" + "Last-Translator: FULL NAME \n" + "Language-Team: LANGUAGE \n" + "Language: \n" + "MIME-Version: 1.0\n" + "Content-Type: text/plain; charset=CHARSET\n" + "Content-Transfer-Encoding: 8bit\n" + +#: pkg/service/response.go:15 +msgid "{user} created {resource}" +msgstr "" + diff --git a/services/activitylog/pkg/service/options.go b/services/activitylog/pkg/service/options.go index cc96d160348..0aa97abfc56 100644 --- a/services/activitylog/pkg/service/options.go +++ b/services/activitylog/pkg/service/options.go @@ -4,7 +4,10 @@ import ( gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1" "github.com/cs3org/reva/v2/pkg/events" "github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool" + "github.com/go-chi/chi/v5" "github.com/owncloud/ocis/v2/ocis-pkg/log" + ehsvc "github.com/owncloud/ocis/v2/protogen/gen/ocis/services/eventhistory/v0" + settingssvc "github.com/owncloud/ocis/v2/protogen/gen/ocis/services/settings/v0" "github.com/owncloud/ocis/v2/services/activitylog/pkg/config" microstore "go-micro.dev/v4/store" "go.opentelemetry.io/otel/trace" @@ -22,6 +25,9 @@ type Options struct { RegisteredEvents []events.Unmarshaller Store microstore.Store GatewaySelector pool.Selectable[gateway.GatewayAPIClient] + Mux *chi.Mux + HistoryClient ehsvc.EventHistoryService + ValueClient settingssvc.ValueService } // Logger configures a logger for the activitylog service @@ -72,3 +78,24 @@ func GatewaySelector(gatewaySelector pool.Selectable[gateway.GatewayAPIClient]) o.GatewaySelector = gatewaySelector } } + +// Mux defines the muxer for the service +func Mux(m *chi.Mux) Option { + return func(o *Options) { + o.Mux = m + } +} + +// HistoryClient adds a grpc client for the eventhistory service +func HistoryClient(hc ehsvc.EventHistoryService) Option { + return func(o *Options) { + o.HistoryClient = hc + } +} + +// ValueClient adds a grpc client for the value service +func ValueClient(vs settingssvc.ValueService) Option { + return func(o *Options) { + o.ValueClient = vs + } +} diff --git a/services/activitylog/pkg/service/response.go b/services/activitylog/pkg/service/response.go new file mode 100644 index 00000000000..9a9d710bc2c --- /dev/null +++ b/services/activitylog/pkg/service/response.go @@ -0,0 +1,217 @@ +package service + +import ( + "context" + "fmt" + "path/filepath" + "time" + + gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1" + group "github.com/cs3org/go-cs3apis/cs3/identity/group/v1beta1" + user "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" + rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" + provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" + "github.com/cs3org/reva/v2/pkg/storagespace" + "github.com/cs3org/reva/v2/pkg/utils" + libregraph "github.com/owncloud/libre-graph-api-go" + + "github.com/owncloud/ocis/v2/ocis-pkg/l10n" +) + +// Translations +var ( + MessageResourceCreated = l10n.Template("{user} added {resource} to {space}") + MessageResourceTrashed = l10n.Template("{user} deleted {resource} from {space}") + MessageResourceMoved = l10n.Template("{user} moved {resource} to {space}") + MessageResourceRenamed = l10n.Template("{user} renamed {oldResource} to {resource}") + MessageShareCreated = l10n.Template("{user} shared {resource} with {sharee}") + MessageShareDeleted = l10n.Template("{user} removed {sharee} from {resource}") + MessageLinkCreated = l10n.Template("{user} shared {resource} via link") + MessageLinkDeleted = l10n.Template("{user} removed link to {resource}") + MessageSpaceShared = l10n.Template("{user} added {sharee} as member of {space}") + MessageSpaceUnshared = l10n.Template("{user} removed {sharee} from {space}") +) + +// GetActivitiesResponse is the response on GET activities requests +type GetActivitiesResponse struct { + Activities []libregraph.Activity `json:"value"` +} + +// Resource represents an item such as a file or folder +type Resource struct { + ID string `json:"id"` + Name string `json:"name"` +} + +// Actor represents a user +type Actor struct { + ID string `json:"id"` + DisplayName string `json:"displayName"` +} + +// ActivityOption allows setting variables for an activity +type ActivityOption func(context.Context, gateway.GatewayAPIClient, map[string]interface{}) error + +// WithResource sets the resource variable for an activity +func WithResource(ref *provider.Reference, addSpace bool) ActivityOption { + return func(ctx context.Context, gwc gateway.GatewayAPIClient, vars map[string]interface{}) error { + info, err := utils.GetResource(ctx, ref, gwc) + if err != nil { + return err + } + + vars["resource"] = Resource{ + ID: storagespace.FormatResourceID(*info.GetId()), + Name: info.GetName(), + } + + if addSpace { + vars["space"] = Resource{ + ID: info.GetSpace().GetId().GetOpaqueId(), + Name: info.GetSpace().GetName(), + } + } + + return nil + } +} + +// WithOldResource sets the oldResource variable for an activity +func WithOldResource(ref *provider.Reference) ActivityOption { + return func(_ context.Context, _ gateway.GatewayAPIClient, vars map[string]interface{}) error { + name := filepath.Base(ref.GetPath()) + vars["oldResource"] = Resource{ + Name: name, + } + return nil + } +} + +// WithTrashedResource sets the resource variable if the resource is trashed +func WithTrashedResource(ref *provider.Reference, rid *provider.ResourceId) ActivityOption { + return func(ctx context.Context, gwc gateway.GatewayAPIClient, vars map[string]interface{}) error { + resp, err := gwc.ListRecycle(ctx, &provider.ListRecycleRequest{ + Ref: ref, + }) + if err != nil { + return err + } + if resp.GetStatus().GetCode() != rpc.Code_CODE_OK { + return fmt.Errorf("error listing recycle: %s", resp.GetStatus().GetMessage()) + } + + for _, item := range resp.GetRecycleItems() { + if item.GetKey() == rid.GetOpaqueId() { + + vars["resource"] = Resource{ + ID: storagespace.FormatResourceID(*rid), + Name: filepath.Base(item.GetRef().GetPath()), + } + + return nil + } + } + + return nil + } +} + +// WithUser sets the user variable for an Activity +func WithUser(uid *user.UserId, username string) ActivityOption { + return func(_ context.Context, gwc gateway.GatewayAPIClient, vars map[string]interface{}) error { + if username == "" { + u, err := utils.GetUser(uid, gwc) + if err != nil { + return err + } + username = u.GetUsername() + } + + vars["user"] = Actor{ + ID: uid.GetOpaqueId(), + DisplayName: username, + } + + return nil + } +} + +// WithSharee sets the sharee variable for an activity +func WithSharee(uid *user.UserId, gid *group.GroupId) ActivityOption { + return func(ctx context.Context, gwc gateway.GatewayAPIClient, vars map[string]interface{}) error { + switch { + case uid != nil: + u, err := utils.GetUser(uid, gwc) + if err != nil { + return err + } + + vars["sharee"] = Actor{ + ID: uid.GetOpaqueId(), + DisplayName: u.GetUsername(), + } + case gid != nil: + r, err := gwc.GetGroup(ctx, &group.GetGroupRequest{GroupId: gid}) + if err != nil { + return fmt.Errorf("error getting group: %w", err) + } + + if r.GetStatus().GetCode() != rpc.Code_CODE_OK { + return fmt.Errorf("error getting group: %s", r.GetStatus().GetMessage()) + } + + vars["sharee"] = Actor{ + ID: gid.GetOpaqueId(), + DisplayName: r.GetGroup().GetDisplayName(), + } + + } + + return nil + } +} + +// WithSpace sets the space variable for an activity +func WithSpace(spaceid *provider.StorageSpaceId) ActivityOption { + return func(ctx context.Context, gwc gateway.GatewayAPIClient, vars map[string]interface{}) error { + s, err := utils.GetSpace(ctx, spaceid.GetOpaqueId(), gwc) + if err != nil { + return err + } + vars["space"] = Resource{ + ID: s.GetId().GetOpaqueId(), + Name: s.GetName(), + } + + return nil + } +} + +// NewActivity creates a new activity +func NewActivity(message string, ts time.Time, eventID string, vars map[string]interface{}) libregraph.Activity { + return libregraph.Activity{ + Id: eventID, + Times: libregraph.ActivityTimes{RecordedTime: ts}, + Template: libregraph.ActivityTemplate{ + Message: message, + Variables: vars, + }, + } +} + +// GetVars calls other service to gather the required data for the activity variables +func (s *ActivitylogService) GetVars(ctx context.Context, opts ...ActivityOption) (map[string]interface{}, error) { + gwc, err := s.gws.Next() + if err != nil { + return nil, err + } + + vars := make(map[string]interface{}) + for _, opt := range opts { + if err := opt(ctx, gwc, vars); err != nil { + return nil, err + } + } + + return vars, nil +} diff --git a/services/activitylog/pkg/service/service.go b/services/activitylog/pkg/service/service.go index 4f2c0cf23db..2653bd434ba 100644 --- a/services/activitylog/pkg/service/service.go +++ b/services/activitylog/pkg/service/service.go @@ -5,6 +5,8 @@ import ( "errors" "fmt" "path/filepath" + "reflect" + "sync" "time" gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1" @@ -13,13 +15,17 @@ import ( "github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool" "github.com/cs3org/reva/v2/pkg/storagespace" "github.com/cs3org/reva/v2/pkg/utils" + "github.com/go-chi/chi/v5" + microstore "go-micro.dev/v4/store" + "github.com/owncloud/ocis/v2/ocis-pkg/log" + ehsvc "github.com/owncloud/ocis/v2/protogen/gen/ocis/services/eventhistory/v0" + settingssvc "github.com/owncloud/ocis/v2/protogen/gen/ocis/services/settings/v0" "github.com/owncloud/ocis/v2/services/activitylog/pkg/config" - microstore "go-micro.dev/v4/store" ) -// Activity represents an activity -type Activity struct { +// RawActivity represents an activity as it is stored in the activitylog store +type RawActivity struct { EventID string `json:"event_id"` Depth int `json:"depth"` Timestamp time.Time `json:"timestamp"` @@ -27,11 +33,17 @@ type Activity struct { // ActivitylogService logs events per resource type ActivitylogService struct { - cfg *config.Config - log log.Logger - events <-chan events.Event - store microstore.Store - gws pool.Selectable[gateway.GatewayAPIClient] + cfg *config.Config + log log.Logger + events <-chan events.Event + store microstore.Store + gws pool.Selectable[gateway.GatewayAPIClient] + mux *chi.Mux + evHistory ehsvc.EventHistoryService + valService settingssvc.ValueService + lock sync.RWMutex + + registeredEvents map[string]events.Unmarshaller } // New creates a new ActivitylogService @@ -55,18 +67,32 @@ func New(opts ...Option) (*ActivitylogService, error) { } s := &ActivitylogService{ - log: o.Logger, - cfg: o.Config, - events: ch, - store: o.Store, - gws: o.GatewaySelector, + log: o.Logger, + cfg: o.Config, + events: ch, + store: o.Store, + gws: o.GatewaySelector, + mux: o.Mux, + evHistory: o.HistoryClient, + valService: o.ValueClient, + lock: sync.RWMutex{}, + registeredEvents: make(map[string]events.Unmarshaller), + } + + s.mux.Get("/graph/v1beta1/extensions/org.libregraph/activities", s.HandleGetItemActivities) + + for _, e := range o.RegisteredEvents { + typ := reflect.TypeOf(e) + s.registeredEvents[typ.String()] = e } + go s.Run() + return s, nil } // Run runs the service -func (a *ActivitylogService) Run() error { +func (a *ActivitylogService) Run() { for e := range a.events { var err error switch ev := e.Event.(type) { @@ -78,35 +104,26 @@ func (a *ActivitylogService) Run() error { err = a.AddActivity(ev.Ref, e.ID, utils.TSToTime(ev.Timestamp)) case events.ItemTrashed: err = a.AddActivityTrashed(ev.ID, ev.Ref, e.ID, utils.TSToTime(ev.Timestamp)) - case events.ItemPurged: - err = a.AddActivity(ev.Ref, e.ID, utils.TSToTime(ev.Timestamp)) case events.ItemMoved: err = a.AddActivity(ev.Ref, e.ID, utils.TSToTime(ev.Timestamp)) case events.ShareCreated: err = a.AddActivity(toRef(ev.ItemID), e.ID, utils.TSToTime(ev.CTime)) - case events.ShareUpdated: - err = a.AddActivity(toRef(ev.ItemID), e.ID, utils.TSToTime(ev.MTime)) case events.ShareRemoved: err = a.AddActivity(toRef(ev.ItemID), e.ID, ev.Timestamp) case events.LinkCreated: err = a.AddActivity(toRef(ev.ItemID), e.ID, utils.TSToTime(ev.CTime)) - case events.LinkUpdated: - err = a.AddActivity(toRef(ev.ItemID), e.ID, utils.TSToTime(ev.CTime)) case events.LinkRemoved: err = a.AddActivity(toRef(ev.ItemID), e.ID, utils.TSToTime(ev.Timestamp)) case events.SpaceShared: - err = a.AddActivity(sToRef(ev.ID), e.ID, ev.Timestamp) - case events.SpaceShareUpdated: - err = a.AddActivity(sToRef(ev.ID), e.ID, ev.Timestamp) + err = a.AddSpaceActivity(ev.ID, e.ID, ev.Timestamp) case events.SpaceUnshared: - err = a.AddActivity(sToRef(ev.ID), e.ID, ev.Timestamp) + err = a.AddSpaceActivity(ev.ID, e.ID, ev.Timestamp) } if err != nil { a.log.Error().Err(err).Interface("event", e).Msg("could not process event") } } - return nil } // AddActivity adds the activity to the given resource and all its parents @@ -139,7 +156,7 @@ func (a *ActivitylogService) AddActivityTrashed(resourceID *provider.ResourceId, } // store activity on trashed item - if err := a.storeActivity(resourceID, eventID, 0, timestamp); err != nil { + if err := a.storeActivity(storagespace.FormatResourceID(*resourceID), eventID, 0, timestamp); err != nil { return fmt.Errorf("could not store activity: %w", err) } @@ -154,8 +171,57 @@ func (a *ActivitylogService) AddActivityTrashed(resourceID *provider.ResourceId, }) } +// AddSpaceActivity adds the activity to the given spaceroot +func (a *ActivitylogService) AddSpaceActivity(spaceID *provider.StorageSpaceId, eventID string, timestamp time.Time) error { + // spaceID is in format $ + // activitylog service uses format $! + // lets do some converting, shall we? + rid, err := storagespace.ParseID(spaceID.GetOpaqueId()) + if err != nil { + return fmt.Errorf("could not parse space id: %w", err) + } + rid.OpaqueId = rid.GetSpaceId() + return a.storeActivity(storagespace.FormatResourceID(rid), eventID, 0, timestamp) + +} + // Activities returns the activities for the given resource -func (a *ActivitylogService) Activities(rid *provider.ResourceId) ([]Activity, error) { +func (a *ActivitylogService) Activities(rid *provider.ResourceId) ([]RawActivity, error) { + a.lock.RLock() + defer a.lock.RUnlock() + + return a.activities(rid) +} + +// RemoveActivities removes the activities from the given resource +func (a *ActivitylogService) RemoveActivities(rid *provider.ResourceId, toDelete map[string]struct{}) error { + a.lock.Lock() + defer a.lock.Unlock() + + curActivities, err := a.activities(rid) + if err != nil { + return err + } + + var acts []RawActivity + for _, a := range curActivities { + if _, ok := toDelete[a.EventID]; !ok { + acts = append(acts, a) + } + } + + b, err := json.Marshal(acts) + if err != nil { + return err + } + + return a.store.Write(µstore.Record{ + Key: storagespace.FormatResourceID(*rid), + Value: b, + }) +} + +func (a *ActivitylogService) activities(rid *provider.ResourceId) ([]RawActivity, error) { resourceID := storagespace.FormatResourceID(*rid) records, err := a.store.Read(resourceID) @@ -164,10 +230,10 @@ func (a *ActivitylogService) Activities(rid *provider.ResourceId) ([]Activity, e } if len(records) == 0 { - return []Activity{}, nil + return []RawActivity{}, nil } - var activities []Activity + var activities []RawActivity if err := json.Unmarshal(records[0].Value, &activities); err != nil { return nil, fmt.Errorf("could not unmarshal activities: %w", err) } @@ -189,7 +255,7 @@ func (a *ActivitylogService) addActivity(initRef *provider.Reference, eventID st return fmt.Errorf("could not get resource info: %w", err) } - if err := a.storeActivity(info.GetId(), eventID, depth, timestamp); err != nil { + if err := a.storeActivity(storagespace.FormatResourceID(*info.GetId()), eventID, depth, timestamp); err != nil { return fmt.Errorf("could not store activity: %w", err) } @@ -202,19 +268,16 @@ func (a *ActivitylogService) addActivity(initRef *provider.Reference, eventID st } } -func (a *ActivitylogService) storeActivity(rid *provider.ResourceId, eventID string, depth int, timestamp time.Time) error { - if rid == nil { - return errors.New("resource id is required") - } - - resourceID := storagespace.FormatResourceID(*rid) +func (a *ActivitylogService) storeActivity(resourceID string, eventID string, depth int, timestamp time.Time) error { + a.lock.Lock() + defer a.lock.Unlock() records, err := a.store.Read(resourceID) if err != nil && err != microstore.ErrNotFound { return err } - var activities []Activity + var activities []RawActivity if len(records) > 0 { if err := json.Unmarshal(records[0].Value, &activities); err != nil { return err @@ -222,7 +285,7 @@ func (a *ActivitylogService) storeActivity(rid *provider.ResourceId, eventID str } // TODO: max len check? - activities = append(activities, Activity{ + activities = append(activities, RawActivity{ EventID: eventID, Depth: depth, Timestamp: timestamp, @@ -245,11 +308,8 @@ func toRef(r *provider.ResourceId) *provider.Reference { } } -func sToRef(s *provider.StorageSpaceId) *provider.Reference { - return &provider.Reference{ - ResourceId: &provider.ResourceId{ - OpaqueId: s.GetOpaqueId(), - SpaceId: s.GetOpaqueId(), - }, +func toSpace(r *provider.Reference) *provider.StorageSpaceId { + return &provider.StorageSpaceId{ + OpaqueId: storagespace.FormatStorageID(r.GetResourceId().GetStorageId(), r.GetResourceId().GetSpaceId()), } } diff --git a/services/activitylog/pkg/service/service_test.go b/services/activitylog/pkg/service/service_test.go index 55b2f62716f..2aa9b92a211 100644 --- a/services/activitylog/pkg/service/service_test.go +++ b/services/activitylog/pkg/service/service_test.go @@ -14,7 +14,7 @@ func TestAddActivity(t *testing.T) { Name string Tree map[string]*provider.ResourceInfo Activities map[string]string - Expected map[string][]Activity + Expected map[string][]RawActivity }{ { Name: "simple", @@ -26,7 +26,7 @@ func TestAddActivity(t *testing.T) { Activities: map[string]string{ "activity": "base", }, - Expected: map[string][]Activity{ + Expected: map[string][]RawActivity{ "base": activitites("activity", 0), "parent": activitites("activity", 1), "spaceid": activitites("activity", 2), @@ -43,7 +43,7 @@ func TestAddActivity(t *testing.T) { "activity1": "base", "activity2": "base", }, - Expected: map[string][]Activity{ + Expected: map[string][]RawActivity{ "base": activitites("activity1", 0, "activity2", 0), "parent": activitites("activity1", 1, "activity2", 1), "spaceid": activitites("activity1", 2, "activity2", 2), @@ -61,7 +61,7 @@ func TestAddActivity(t *testing.T) { "activity1": "base1", "activity2": "base2", }, - Expected: map[string][]Activity{ + Expected: map[string][]RawActivity{ "base1": activitites("activity1", 0), "base2": activitites("activity2", 0), "parent": activitites("activity1", 1, "activity2", 1), @@ -83,7 +83,7 @@ func TestAddActivity(t *testing.T) { "activity2": "base2", "activity3": "base3", }, - Expected: map[string][]Activity{ + Expected: map[string][]RawActivity{ "base1": activitites("activity1", 0), "base2": activitites("activity2", 0), "base3": activitites("activity3", 0), @@ -109,7 +109,7 @@ func TestAddActivity(t *testing.T) { "activity3": "base3", "activity4": "parent2", }, - Expected: map[string][]Activity{ + Expected: map[string][]RawActivity{ "base1": activitites("activity1", 0), "base2": activitites("activity2", 0), "base3": activitites("activity3", 0), @@ -143,9 +143,9 @@ func TestAddActivity(t *testing.T) { } } -func activitites(acts ...interface{}) []Activity { - var activities []Activity - act := Activity{} +func activitites(acts ...interface{}) []RawActivity { + var activities []RawActivity + act := RawActivity{} for _, a := range acts { switch v := a.(type) { case string: diff --git a/services/eventhistory/pkg/config/defaults/defaultconfig.go b/services/eventhistory/pkg/config/defaults/defaultconfig.go index 141817568d3..fbcb7da441a 100644 --- a/services/eventhistory/pkg/config/defaults/defaultconfig.go +++ b/services/eventhistory/pkg/config/defaults/defaultconfig.go @@ -33,9 +33,10 @@ func DefaultConfig() *config.Config { EnableTLS: false, }, Store: config.Store{ - Store: "memory", + Store: "nats-js-kv", + Nodes: []string{"127.0.0.1:9233"}, Database: "eventhistory", - Table: "events", + Table: "", TTL: 336 * time.Hour, }, GRPC: config.GRPCConfig{ diff --git a/services/eventhistory/pkg/service/service.go b/services/eventhistory/pkg/service/service.go index 848fc4d9b48..ba2a492986e 100644 --- a/services/eventhistory/pkg/service/service.go +++ b/services/eventhistory/pkg/service/service.go @@ -130,6 +130,10 @@ func (eh *EventHistoryService) getEvent(id string) (*ehmsg.Event, error) { return nil, err } + if len(evs) == 0 { + return nil, store.ErrNotFound + } + var ev StoreEvent if err := json.Unmarshal(evs[0].Value, &ev); err != nil { eh.log.Error().Err(err).Str("eventid", id).Msg("could not unmarshal event") diff --git a/services/proxy/pkg/config/defaults/defaultconfig.go b/services/proxy/pkg/config/defaults/defaultconfig.go index c638012ac47..af2093522b3 100644 --- a/services/proxy/pkg/config/defaults/defaultconfig.go +++ b/services/proxy/pkg/config/defaults/defaultconfig.go @@ -236,6 +236,10 @@ func DefaultPolicies() []config.Policy { Endpoint: "/app/", // /app or /apps? ocdav only handles /apps Service: "com.owncloud.web.frontend", }, + { + Endpoint: "/graph/v1beta1/extensions/org.libregraph/activities", + Service: "com.owncloud.web.activitylog", + }, { Endpoint: "/graph/v1.0/invitations", Service: "com.owncloud.web.invitations", diff --git a/services/search/Makefile b/services/search/Makefile index bc58a2eb79f..a55dd6fed7f 100644 --- a/services/search/Makefile +++ b/services/search/Makefile @@ -26,7 +26,7 @@ include ../../.make/generate.mk .PHONY: ci-go-generate ci-go-generate: $(PIGEON) $(MOCKERY) # CI runs ci-node-generate automatically before this target $(MOCKERY) - $(PIGEON) -optimize-grammar -optimize-parser -o pkg/query/kql/dictionary_gen.go pkg/query/kql/dictionary.peg + $(PIGEON) -optimize-grammar -optimize-parser -o ../../ocis-pkg/kql/dictionary_gen.go ../../ocis-pkg/kql/dictionary.peg .PHONY: ci-node-generate ci-node-generate: diff --git a/services/search/pkg/query/bleve/bleve.go b/services/search/pkg/query/bleve/bleve.go index e720b35f70e..b6e825e6eee 100644 --- a/services/search/pkg/query/bleve/bleve.go +++ b/services/search/pkg/query/bleve/bleve.go @@ -4,8 +4,8 @@ package bleve import ( bQuery "github.com/blevesearch/bleve/v2/search/query" + "github.com/owncloud/ocis/v2/ocis-pkg/kql" "github.com/owncloud/ocis/v2/services/search/pkg/query" - "github.com/owncloud/ocis/v2/services/search/pkg/query/kql" ) // Creator is combines a Builder and a Compiler which is used to Create the query. diff --git a/services/search/pkg/query/bleve/compiler.go b/services/search/pkg/query/bleve/compiler.go index 2d0eace100b..7046bee9de3 100644 --- a/services/search/pkg/query/bleve/compiler.go +++ b/services/search/pkg/query/bleve/compiler.go @@ -6,8 +6,8 @@ import ( "github.com/blevesearch/bleve/v2" bleveQuery "github.com/blevesearch/bleve/v2/search/query" - "github.com/owncloud/ocis/v2/services/search/pkg/query/ast" - "github.com/owncloud/ocis/v2/services/search/pkg/query/kql" + "github.com/owncloud/ocis/v2/ocis-pkg/ast" + "github.com/owncloud/ocis/v2/ocis-pkg/kql" ) var _fields = map[string]string{ diff --git a/services/search/pkg/query/bleve/compiler_test.go b/services/search/pkg/query/bleve/compiler_test.go index 597ce4c29e5..c1286cab953 100644 --- a/services/search/pkg/query/bleve/compiler_test.go +++ b/services/search/pkg/query/bleve/compiler_test.go @@ -5,9 +5,8 @@ import ( "time" "github.com/blevesearch/bleve/v2/search/query" + "github.com/owncloud/ocis/v2/ocis-pkg/ast" tAssert "github.com/stretchr/testify/assert" - - "github.com/owncloud/ocis/v2/services/search/pkg/query/ast" ) var timeMustParse = func(t *testing.T, ts string) time.Time { diff --git a/services/search/pkg/query/error.go b/services/search/pkg/query/error.go index 89f504c8a45..7a3780a38d5 100644 --- a/services/search/pkg/query/error.go +++ b/services/search/pkg/query/error.go @@ -3,7 +3,7 @@ package query import ( "fmt" - "github.com/owncloud/ocis/v2/services/search/pkg/query/ast" + "github.com/owncloud/ocis/v2/ocis-pkg/ast" ) // StartsWithBinaryOperatorError records an error and the operation that caused it. diff --git a/services/search/pkg/query/query.go b/services/search/pkg/query/query.go index e49dbecd91f..2b776595b50 100644 --- a/services/search/pkg/query/query.go +++ b/services/search/pkg/query/query.go @@ -1,9 +1,7 @@ // Package query provides functions to work with the different search query flavours. package query -import ( - "github.com/owncloud/ocis/v2/services/search/pkg/query/ast" -) +import "github.com/owncloud/ocis/v2/ocis-pkg/ast" // Builder is the interface that wraps the basic Build method. type Builder interface { diff --git a/vendor/github.com/owncloud/libre-graph-api-go/README.md b/vendor/github.com/owncloud/libre-graph-api-go/README.md index d1e630879d9..c6ceee69464 100644 --- a/vendor/github.com/owncloud/libre-graph-api-go/README.md +++ b/vendor/github.com/owncloud/libre-graph-api-go/README.md @@ -78,6 +78,7 @@ All URIs are relative to *https://ocis.ocis-traefik.latest.owncloud.works/graph* Class | Method | HTTP request | Description ------------ | ------------- | ------------- | ------------- +*ActivitiesApi* | [**GetActivities**](docs/ActivitiesApi.md#getactivities) | **Get** /v1beta1/extensions/org.libregraph/activities | Get activities *ApplicationsApi* | [**GetApplication**](docs/ApplicationsApi.md#getapplication) | **Get** /v1.0/applications/{application-id} | Get application by id *ApplicationsApi* | [**ListApplications**](docs/ApplicationsApi.md#listapplications) | **Get** /v1.0/applications | Get all applications *DriveItemApi* | [**DeleteDriveItem**](docs/DriveItemApi.md#deletedriveitem) | **Delete** /v1beta1/drives/{drive-id}/items/{item-id} | Delete a DriveItem. @@ -168,6 +169,9 @@ Class | Method | HTTP request | Description ## Documentation For Models + - [Activity](docs/Activity.md) + - [ActivityTemplate](docs/ActivityTemplate.md) + - [ActivityTimes](docs/ActivityTimes.md) - [AppRole](docs/AppRole.md) - [AppRoleAssignment](docs/AppRoleAssignment.md) - [Application](docs/Application.md) @@ -175,6 +179,7 @@ Class | Method | HTTP request | Description - [ClassMemberReference](docs/ClassMemberReference.md) - [ClassReference](docs/ClassReference.md) - [ClassTeacherReference](docs/ClassTeacherReference.md) + - [CollectionOfActivities](docs/CollectionOfActivities.md) - [CollectionOfAppRoleAssignments](docs/CollectionOfAppRoleAssignments.md) - [CollectionOfApplications](docs/CollectionOfApplications.md) - [CollectionOfClass](docs/CollectionOfClass.md) diff --git a/vendor/github.com/owncloud/libre-graph-api-go/api_activities.go b/vendor/github.com/owncloud/libre-graph-api-go/api_activities.go new file mode 100644 index 00000000000..7b859a7b25e --- /dev/null +++ b/vendor/github.com/owncloud/libre-graph-api-go/api_activities.go @@ -0,0 +1,137 @@ +/* +Libre Graph API + +Libre Graph is a free API for cloud collaboration inspired by the MS Graph API. + +API version: v1.0.4 +*/ + +// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT. + +package libregraph + +import ( + "bytes" + "context" + "io" + "net/http" + "net/url" +) + +// ActivitiesApiService ActivitiesApi service +type ActivitiesApiService service + +type ApiGetActivitiesRequest struct { + ctx context.Context + ApiService *ActivitiesApiService + kql *string +} + +func (r ApiGetActivitiesRequest) Kql(kql string) ApiGetActivitiesRequest { + r.kql = &kql + return r +} + +func (r ApiGetActivitiesRequest) Execute() (*CollectionOfActivities, *http.Response, error) { + return r.ApiService.GetActivitiesExecute(r) +} + +/* +GetActivities Get activities + + @param ctx context.Context - for authentication, logging, cancellation, deadlines, tracing, etc. Passed from http.Request or context.Background(). + @return ApiGetActivitiesRequest +*/ +func (a *ActivitiesApiService) GetActivities(ctx context.Context) ApiGetActivitiesRequest { + return ApiGetActivitiesRequest{ + ApiService: a, + ctx: ctx, + } +} + +// Execute executes the request +// +// @return CollectionOfActivities +func (a *ActivitiesApiService) GetActivitiesExecute(r ApiGetActivitiesRequest) (*CollectionOfActivities, *http.Response, error) { + var ( + localVarHTTPMethod = http.MethodGet + localVarPostBody interface{} + formFiles []formFile + localVarReturnValue *CollectionOfActivities + ) + + localBasePath, err := a.client.cfg.ServerURLWithContext(r.ctx, "ActivitiesApiService.GetActivities") + if err != nil { + return localVarReturnValue, nil, &GenericOpenAPIError{error: err.Error()} + } + + localVarPath := localBasePath + "/v1beta1/extensions/org.libregraph/activities" + + localVarHeaderParams := make(map[string]string) + localVarQueryParams := url.Values{} + localVarFormParams := url.Values{} + + if r.kql != nil { + parameterAddToHeaderOrQuery(localVarQueryParams, "kql", r.kql, "") + } + // to determine the Content-Type header + localVarHTTPContentTypes := []string{} + + // set Content-Type header + localVarHTTPContentType := selectHeaderContentType(localVarHTTPContentTypes) + if localVarHTTPContentType != "" { + localVarHeaderParams["Content-Type"] = localVarHTTPContentType + } + + // to determine the Accept header + localVarHTTPHeaderAccepts := []string{"application/json"} + + // set Accept header + localVarHTTPHeaderAccept := selectHeaderAccept(localVarHTTPHeaderAccepts) + if localVarHTTPHeaderAccept != "" { + localVarHeaderParams["Accept"] = localVarHTTPHeaderAccept + } + req, err := a.client.prepareRequest(r.ctx, localVarPath, localVarHTTPMethod, localVarPostBody, localVarHeaderParams, localVarQueryParams, localVarFormParams, formFiles) + if err != nil { + return localVarReturnValue, nil, err + } + + localVarHTTPResponse, err := a.client.callAPI(req) + if err != nil || localVarHTTPResponse == nil { + return localVarReturnValue, localVarHTTPResponse, err + } + + localVarBody, err := io.ReadAll(localVarHTTPResponse.Body) + localVarHTTPResponse.Body.Close() + localVarHTTPResponse.Body = io.NopCloser(bytes.NewBuffer(localVarBody)) + if err != nil { + return localVarReturnValue, localVarHTTPResponse, err + } + + if localVarHTTPResponse.StatusCode >= 300 { + newErr := &GenericOpenAPIError{ + body: localVarBody, + error: localVarHTTPResponse.Status, + } + var v OdataError + err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) + if err != nil { + newErr.error = err.Error() + return localVarReturnValue, localVarHTTPResponse, newErr + } + newErr.error = formatErrorMessage(localVarHTTPResponse.Status, &v) + newErr.model = v + return localVarReturnValue, localVarHTTPResponse, newErr + } + + err = a.client.decode(&localVarReturnValue, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) + if err != nil { + newErr := &GenericOpenAPIError{ + body: localVarBody, + error: err.Error(), + } + return localVarReturnValue, localVarHTTPResponse, newErr + } + + return localVarReturnValue, localVarHTTPResponse, nil +} diff --git a/vendor/github.com/owncloud/libre-graph-api-go/client.go b/vendor/github.com/owncloud/libre-graph-api-go/client.go index fcfbb7b7842..285f371c16b 100644 --- a/vendor/github.com/owncloud/libre-graph-api-go/client.go +++ b/vendor/github.com/owncloud/libre-graph-api-go/client.go @@ -48,6 +48,8 @@ type APIClient struct { // API Services + ActivitiesApi *ActivitiesApiService + ApplicationsApi *ApplicationsApiService DriveItemApi *DriveItemApiService @@ -111,6 +113,7 @@ func NewAPIClient(cfg *Configuration) *APIClient { c.common.client = c // API Services + c.ActivitiesApi = (*ActivitiesApiService)(&c.common) c.ApplicationsApi = (*ApplicationsApiService)(&c.common) c.DriveItemApi = (*DriveItemApiService)(&c.common) c.DrivesApi = (*DrivesApiService)(&c.common) diff --git a/vendor/github.com/owncloud/libre-graph-api-go/model_activity.go b/vendor/github.com/owncloud/libre-graph-api-go/model_activity.go new file mode 100644 index 00000000000..74d4b8590a0 --- /dev/null +++ b/vendor/github.com/owncloud/libre-graph-api-go/model_activity.go @@ -0,0 +1,213 @@ +/* +Libre Graph API + +Libre Graph is a free API for cloud collaboration inspired by the MS Graph API. + +API version: v1.0.4 +*/ + +// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT. + +package libregraph + +import ( + "bytes" + "encoding/json" + "fmt" +) + +// checks if the Activity type satisfies the MappedNullable interface at compile time +var _ MappedNullable = &Activity{} + +// Activity Represents activity. +type Activity struct { + // Activity ID. + Id string `json:"id"` + Times ActivityTimes `json:"times"` + Template ActivityTemplate `json:"template"` +} + +type _Activity Activity + +// NewActivity instantiates a new Activity object +// This constructor will assign default values to properties that have it defined, +// and makes sure properties required by API are set, but the set of arguments +// will change when the set of required properties is changed +func NewActivity(id string, times ActivityTimes, template ActivityTemplate) *Activity { + this := Activity{} + this.Id = id + this.Times = times + this.Template = template + return &this +} + +// NewActivityWithDefaults instantiates a new Activity object +// This constructor will only assign default values to properties that have it defined, +// but it doesn't guarantee that properties required by API are set +func NewActivityWithDefaults() *Activity { + this := Activity{} + return &this +} + +// GetId returns the Id field value +func (o *Activity) GetId() string { + if o == nil { + var ret string + return ret + } + + return o.Id +} + +// GetIdOk returns a tuple with the Id field value +// and a boolean to check if the value has been set. +func (o *Activity) GetIdOk() (*string, bool) { + if o == nil { + return nil, false + } + return &o.Id, true +} + +// SetId sets field value +func (o *Activity) SetId(v string) { + o.Id = v +} + +// GetTimes returns the Times field value +func (o *Activity) GetTimes() ActivityTimes { + if o == nil { + var ret ActivityTimes + return ret + } + + return o.Times +} + +// GetTimesOk returns a tuple with the Times field value +// and a boolean to check if the value has been set. +func (o *Activity) GetTimesOk() (*ActivityTimes, bool) { + if o == nil { + return nil, false + } + return &o.Times, true +} + +// SetTimes sets field value +func (o *Activity) SetTimes(v ActivityTimes) { + o.Times = v +} + +// GetTemplate returns the Template field value +func (o *Activity) GetTemplate() ActivityTemplate { + if o == nil { + var ret ActivityTemplate + return ret + } + + return o.Template +} + +// GetTemplateOk returns a tuple with the Template field value +// and a boolean to check if the value has been set. +func (o *Activity) GetTemplateOk() (*ActivityTemplate, bool) { + if o == nil { + return nil, false + } + return &o.Template, true +} + +// SetTemplate sets field value +func (o *Activity) SetTemplate(v ActivityTemplate) { + o.Template = v +} + +func (o Activity) MarshalJSON() ([]byte, error) { + toSerialize, err := o.ToMap() + if err != nil { + return []byte{}, err + } + return json.Marshal(toSerialize) +} + +func (o Activity) ToMap() (map[string]interface{}, error) { + toSerialize := map[string]interface{}{} + toSerialize["id"] = o.Id + toSerialize["times"] = o.Times + toSerialize["template"] = o.Template + return toSerialize, nil +} + +func (o *Activity) UnmarshalJSON(data []byte) (err error) { + // This validates that all required properties are included in the JSON object + // by unmarshalling the object into a generic map with string keys and checking + // that every required field exists as a key in the generic map. + requiredProperties := []string{ + "id", + "times", + "template", + } + + allProperties := make(map[string]interface{}) + + err = json.Unmarshal(data, &allProperties) + + if err != nil { + return err + } + + for _, requiredProperty := range requiredProperties { + if _, exists := allProperties[requiredProperty]; !exists { + return fmt.Errorf("no value given for required property %v", requiredProperty) + } + } + + varActivity := _Activity{} + + decoder := json.NewDecoder(bytes.NewReader(data)) + decoder.DisallowUnknownFields() + err = decoder.Decode(&varActivity) + + if err != nil { + return err + } + + *o = Activity(varActivity) + + return err +} + +type NullableActivity struct { + value *Activity + isSet bool +} + +func (v NullableActivity) Get() *Activity { + return v.value +} + +func (v *NullableActivity) Set(val *Activity) { + v.value = val + v.isSet = true +} + +func (v NullableActivity) IsSet() bool { + return v.isSet +} + +func (v *NullableActivity) Unset() { + v.value = nil + v.isSet = false +} + +func NewNullableActivity(val *Activity) *NullableActivity { + return &NullableActivity{value: val, isSet: true} +} + +func (v NullableActivity) MarshalJSON() ([]byte, error) { + return json.Marshal(v.value) +} + +func (v *NullableActivity) UnmarshalJSON(src []byte) error { + v.isSet = true + return json.Unmarshal(src, &v.value) +} diff --git a/vendor/github.com/owncloud/libre-graph-api-go/model_activity_template.go b/vendor/github.com/owncloud/libre-graph-api-go/model_activity_template.go new file mode 100644 index 00000000000..75bcadb899b --- /dev/null +++ b/vendor/github.com/owncloud/libre-graph-api-go/model_activity_template.go @@ -0,0 +1,194 @@ +/* +Libre Graph API + +Libre Graph is a free API for cloud collaboration inspired by the MS Graph API. + +API version: v1.0.4 +*/ + +// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT. + +package libregraph + +import ( + "bytes" + "encoding/json" + "fmt" +) + +// checks if the ActivityTemplate type satisfies the MappedNullable interface at compile time +var _ MappedNullable = &ActivityTemplate{} + +// ActivityTemplate struct for ActivityTemplate +type ActivityTemplate struct { + // Activity description. + Message string `json:"message"` + // Activity description variables. + Variables map[string]interface{} `json:"variables,omitempty"` +} + +type _ActivityTemplate ActivityTemplate + +// NewActivityTemplate instantiates a new ActivityTemplate object +// This constructor will assign default values to properties that have it defined, +// and makes sure properties required by API are set, but the set of arguments +// will change when the set of required properties is changed +func NewActivityTemplate(message string) *ActivityTemplate { + this := ActivityTemplate{} + this.Message = message + return &this +} + +// NewActivityTemplateWithDefaults instantiates a new ActivityTemplate object +// This constructor will only assign default values to properties that have it defined, +// but it doesn't guarantee that properties required by API are set +func NewActivityTemplateWithDefaults() *ActivityTemplate { + this := ActivityTemplate{} + return &this +} + +// GetMessage returns the Message field value +func (o *ActivityTemplate) GetMessage() string { + if o == nil { + var ret string + return ret + } + + return o.Message +} + +// GetMessageOk returns a tuple with the Message field value +// and a boolean to check if the value has been set. +func (o *ActivityTemplate) GetMessageOk() (*string, bool) { + if o == nil { + return nil, false + } + return &o.Message, true +} + +// SetMessage sets field value +func (o *ActivityTemplate) SetMessage(v string) { + o.Message = v +} + +// GetVariables returns the Variables field value if set, zero value otherwise. +func (o *ActivityTemplate) GetVariables() map[string]interface{} { + if o == nil || IsNil(o.Variables) { + var ret map[string]interface{} + return ret + } + return o.Variables +} + +// GetVariablesOk returns a tuple with the Variables field value if set, nil otherwise +// and a boolean to check if the value has been set. +func (o *ActivityTemplate) GetVariablesOk() (map[string]interface{}, bool) { + if o == nil || IsNil(o.Variables) { + return map[string]interface{}{}, false + } + return o.Variables, true +} + +// HasVariables returns a boolean if a field has been set. +func (o *ActivityTemplate) HasVariables() bool { + if o != nil && !IsNil(o.Variables) { + return true + } + + return false +} + +// SetVariables gets a reference to the given map[string]interface{} and assigns it to the Variables field. +func (o *ActivityTemplate) SetVariables(v map[string]interface{}) { + o.Variables = v +} + +func (o ActivityTemplate) MarshalJSON() ([]byte, error) { + toSerialize, err := o.ToMap() + if err != nil { + return []byte{}, err + } + return json.Marshal(toSerialize) +} + +func (o ActivityTemplate) ToMap() (map[string]interface{}, error) { + toSerialize := map[string]interface{}{} + toSerialize["message"] = o.Message + if !IsNil(o.Variables) { + toSerialize["variables"] = o.Variables + } + return toSerialize, nil +} + +func (o *ActivityTemplate) UnmarshalJSON(data []byte) (err error) { + // This validates that all required properties are included in the JSON object + // by unmarshalling the object into a generic map with string keys and checking + // that every required field exists as a key in the generic map. + requiredProperties := []string{ + "message", + } + + allProperties := make(map[string]interface{}) + + err = json.Unmarshal(data, &allProperties) + + if err != nil { + return err + } + + for _, requiredProperty := range requiredProperties { + if _, exists := allProperties[requiredProperty]; !exists { + return fmt.Errorf("no value given for required property %v", requiredProperty) + } + } + + varActivityTemplate := _ActivityTemplate{} + + decoder := json.NewDecoder(bytes.NewReader(data)) + decoder.DisallowUnknownFields() + err = decoder.Decode(&varActivityTemplate) + + if err != nil { + return err + } + + *o = ActivityTemplate(varActivityTemplate) + + return err +} + +type NullableActivityTemplate struct { + value *ActivityTemplate + isSet bool +} + +func (v NullableActivityTemplate) Get() *ActivityTemplate { + return v.value +} + +func (v *NullableActivityTemplate) Set(val *ActivityTemplate) { + v.value = val + v.isSet = true +} + +func (v NullableActivityTemplate) IsSet() bool { + return v.isSet +} + +func (v *NullableActivityTemplate) Unset() { + v.value = nil + v.isSet = false +} + +func NewNullableActivityTemplate(val *ActivityTemplate) *NullableActivityTemplate { + return &NullableActivityTemplate{value: val, isSet: true} +} + +func (v NullableActivityTemplate) MarshalJSON() ([]byte, error) { + return json.Marshal(v.value) +} + +func (v *NullableActivityTemplate) UnmarshalJSON(src []byte) error { + v.isSet = true + return json.Unmarshal(src, &v.value) +} diff --git a/vendor/github.com/owncloud/libre-graph-api-go/model_activity_times.go b/vendor/github.com/owncloud/libre-graph-api-go/model_activity_times.go new file mode 100644 index 00000000000..d6970a37559 --- /dev/null +++ b/vendor/github.com/owncloud/libre-graph-api-go/model_activity_times.go @@ -0,0 +1,158 @@ +/* +Libre Graph API + +Libre Graph is a free API for cloud collaboration inspired by the MS Graph API. + +API version: v1.0.4 +*/ + +// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT. + +package libregraph + +import ( + "bytes" + "encoding/json" + "fmt" + "time" +) + +// checks if the ActivityTimes type satisfies the MappedNullable interface at compile time +var _ MappedNullable = &ActivityTimes{} + +// ActivityTimes struct for ActivityTimes +type ActivityTimes struct { + // Timestamp of the activity. + RecordedTime time.Time `json:"recordedTime"` +} + +type _ActivityTimes ActivityTimes + +// NewActivityTimes instantiates a new ActivityTimes object +// This constructor will assign default values to properties that have it defined, +// and makes sure properties required by API are set, but the set of arguments +// will change when the set of required properties is changed +func NewActivityTimes(recordedTime time.Time) *ActivityTimes { + this := ActivityTimes{} + this.RecordedTime = recordedTime + return &this +} + +// NewActivityTimesWithDefaults instantiates a new ActivityTimes object +// This constructor will only assign default values to properties that have it defined, +// but it doesn't guarantee that properties required by API are set +func NewActivityTimesWithDefaults() *ActivityTimes { + this := ActivityTimes{} + return &this +} + +// GetRecordedTime returns the RecordedTime field value +func (o *ActivityTimes) GetRecordedTime() time.Time { + if o == nil { + var ret time.Time + return ret + } + + return o.RecordedTime +} + +// GetRecordedTimeOk returns a tuple with the RecordedTime field value +// and a boolean to check if the value has been set. +func (o *ActivityTimes) GetRecordedTimeOk() (*time.Time, bool) { + if o == nil { + return nil, false + } + return &o.RecordedTime, true +} + +// SetRecordedTime sets field value +func (o *ActivityTimes) SetRecordedTime(v time.Time) { + o.RecordedTime = v +} + +func (o ActivityTimes) MarshalJSON() ([]byte, error) { + toSerialize, err := o.ToMap() + if err != nil { + return []byte{}, err + } + return json.Marshal(toSerialize) +} + +func (o ActivityTimes) ToMap() (map[string]interface{}, error) { + toSerialize := map[string]interface{}{} + toSerialize["recordedTime"] = o.RecordedTime + return toSerialize, nil +} + +func (o *ActivityTimes) UnmarshalJSON(data []byte) (err error) { + // This validates that all required properties are included in the JSON object + // by unmarshalling the object into a generic map with string keys and checking + // that every required field exists as a key in the generic map. + requiredProperties := []string{ + "recordedTime", + } + + allProperties := make(map[string]interface{}) + + err = json.Unmarshal(data, &allProperties) + + if err != nil { + return err + } + + for _, requiredProperty := range requiredProperties { + if _, exists := allProperties[requiredProperty]; !exists { + return fmt.Errorf("no value given for required property %v", requiredProperty) + } + } + + varActivityTimes := _ActivityTimes{} + + decoder := json.NewDecoder(bytes.NewReader(data)) + decoder.DisallowUnknownFields() + err = decoder.Decode(&varActivityTimes) + + if err != nil { + return err + } + + *o = ActivityTimes(varActivityTimes) + + return err +} + +type NullableActivityTimes struct { + value *ActivityTimes + isSet bool +} + +func (v NullableActivityTimes) Get() *ActivityTimes { + return v.value +} + +func (v *NullableActivityTimes) Set(val *ActivityTimes) { + v.value = val + v.isSet = true +} + +func (v NullableActivityTimes) IsSet() bool { + return v.isSet +} + +func (v *NullableActivityTimes) Unset() { + v.value = nil + v.isSet = false +} + +func NewNullableActivityTimes(val *ActivityTimes) *NullableActivityTimes { + return &NullableActivityTimes{value: val, isSet: true} +} + +func (v NullableActivityTimes) MarshalJSON() ([]byte, error) { + return json.Marshal(v.value) +} + +func (v *NullableActivityTimes) UnmarshalJSON(src []byte) error { + v.isSet = true + return json.Unmarshal(src, &v.value) +} diff --git a/vendor/github.com/owncloud/libre-graph-api-go/model_collection_of_activities.go b/vendor/github.com/owncloud/libre-graph-api-go/model_collection_of_activities.go new file mode 100644 index 00000000000..3ebfd31fbb7 --- /dev/null +++ b/vendor/github.com/owncloud/libre-graph-api-go/model_collection_of_activities.go @@ -0,0 +1,124 @@ +/* +Libre Graph API + +Libre Graph is a free API for cloud collaboration inspired by the MS Graph API. + +API version: v1.0.4 +*/ + +// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT. + +package libregraph + +import ( + "encoding/json" +) + +// checks if the CollectionOfActivities type satisfies the MappedNullable interface at compile time +var _ MappedNullable = &CollectionOfActivities{} + +// CollectionOfActivities struct for CollectionOfActivities +type CollectionOfActivities struct { + Value []Activity `json:"value,omitempty"` +} + +// NewCollectionOfActivities instantiates a new CollectionOfActivities object +// This constructor will assign default values to properties that have it defined, +// and makes sure properties required by API are set, but the set of arguments +// will change when the set of required properties is changed +func NewCollectionOfActivities() *CollectionOfActivities { + this := CollectionOfActivities{} + return &this +} + +// NewCollectionOfActivitiesWithDefaults instantiates a new CollectionOfActivities object +// This constructor will only assign default values to properties that have it defined, +// but it doesn't guarantee that properties required by API are set +func NewCollectionOfActivitiesWithDefaults() *CollectionOfActivities { + this := CollectionOfActivities{} + return &this +} + +// GetValue returns the Value field value if set, zero value otherwise. +func (o *CollectionOfActivities) GetValue() []Activity { + if o == nil || IsNil(o.Value) { + var ret []Activity + return ret + } + return o.Value +} + +// GetValueOk returns a tuple with the Value field value if set, nil otherwise +// and a boolean to check if the value has been set. +func (o *CollectionOfActivities) GetValueOk() ([]Activity, bool) { + if o == nil || IsNil(o.Value) { + return nil, false + } + return o.Value, true +} + +// HasValue returns a boolean if a field has been set. +func (o *CollectionOfActivities) HasValue() bool { + if o != nil && !IsNil(o.Value) { + return true + } + + return false +} + +// SetValue gets a reference to the given []Activity and assigns it to the Value field. +func (o *CollectionOfActivities) SetValue(v []Activity) { + o.Value = v +} + +func (o CollectionOfActivities) MarshalJSON() ([]byte, error) { + toSerialize, err := o.ToMap() + if err != nil { + return []byte{}, err + } + return json.Marshal(toSerialize) +} + +func (o CollectionOfActivities) ToMap() (map[string]interface{}, error) { + toSerialize := map[string]interface{}{} + if !IsNil(o.Value) { + toSerialize["value"] = o.Value + } + return toSerialize, nil +} + +type NullableCollectionOfActivities struct { + value *CollectionOfActivities + isSet bool +} + +func (v NullableCollectionOfActivities) Get() *CollectionOfActivities { + return v.value +} + +func (v *NullableCollectionOfActivities) Set(val *CollectionOfActivities) { + v.value = val + v.isSet = true +} + +func (v NullableCollectionOfActivities) IsSet() bool { + return v.isSet +} + +func (v *NullableCollectionOfActivities) Unset() { + v.value = nil + v.isSet = false +} + +func NewNullableCollectionOfActivities(val *CollectionOfActivities) *NullableCollectionOfActivities { + return &NullableCollectionOfActivities{value: val, isSet: true} +} + +func (v NullableCollectionOfActivities) MarshalJSON() ([]byte, error) { + return json.Marshal(v.value) +} + +func (v *NullableCollectionOfActivities) UnmarshalJSON(src []byte) error { + v.isSet = true + return json.Unmarshal(src, &v.value) +} diff --git a/vendor/modules.txt b/vendor/modules.txt index f94a8a8e08b..eee2a2cc1cb 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -1623,7 +1623,7 @@ github.com/opentracing/opentracing-go/log # github.com/orcaman/concurrent-map v1.0.0 ## explicit github.com/orcaman/concurrent-map -# github.com/owncloud/libre-graph-api-go v1.0.5-0.20240529101512-a631b9eeddb3 +# github.com/owncloud/libre-graph-api-go v1.0.5-0.20240618162722-2298241331d1 ## explicit; go 1.18 github.com/owncloud/libre-graph-api-go # github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c