From 882689549f1a93cdc508fe57369c22504d714c55 Mon Sep 17 00:00:00 2001 From: jkoberg Date: Tue, 11 Jun 2024 16:42:52 +0200 Subject: [PATCH 01/13] feat(activitylog): add api Signed-off-by: jkoberg --- .../6.0.0_2024-06-19/activity-service.md | 1 + services/activitylog/pkg/command/server.go | 32 +++-- services/activitylog/pkg/config/config.go | 25 ++++ .../pkg/config/defaults/defaultconfig.go | 30 +++- .../activitylog/pkg/server/http/option.go | 130 ++++++++++++++++++ .../activitylog/pkg/server/http/server.go | 100 ++++++++++++++ services/activitylog/pkg/service/http.go | 114 +++++++++++++++ services/activitylog/pkg/service/options.go | 18 +++ services/activitylog/pkg/service/response.go | 67 +++++++++ services/activitylog/pkg/service/service.go | 55 +++++--- .../pkg/config/defaults/defaultconfig.go | 5 +- .../pkg/config/defaults/defaultconfig.go | 11 ++ 12 files changed, 557 insertions(+), 31 deletions(-) create mode 100644 services/activitylog/pkg/server/http/option.go create mode 100644 services/activitylog/pkg/server/http/server.go create mode 100644 services/activitylog/pkg/service/http.go create mode 100644 services/activitylog/pkg/service/response.go diff --git a/changelog/6.0.0_2024-06-19/activity-service.md b/changelog/6.0.0_2024-06-19/activity-service.md index 1058280a0..8f82f56fe 100644 --- a/changelog/6.0.0_2024-06-19/activity-service.md +++ b/changelog/6.0.0_2024-06-19/activity-service.md @@ -2,4 +2,5 @@ Enhancement: Activitylog Service Adds a new service `activitylog` which stores events (activities) per resource. This data can be retrieved by clients to show item activities +https://github.com/owncloud/ocis/pull/9360 https://github.com/owncloud/ocis/pull/9327 diff --git a/services/activitylog/pkg/command/server.go b/services/activitylog/pkg/command/server.go index ed07a722a..f80c7b4b8 100644 --- a/services/activitylog/pkg/command/server.go +++ b/services/activitylog/pkg/command/server.go @@ -14,13 +14,15 @@ 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" "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" ) @@ -109,15 +111,27 @@ 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) + { - 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.History(hClient), + http.RegisteredEvents(_registeredEvents), ) if err != nil { diff --git a/services/activitylog/pkg/config/config.go b/services/activitylog/pkg/config/config.go index 2a7f24137..580ca381a 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:"-"` @@ -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/defaults/defaultconfig.go b/services/activitylog/pkg/config/defaults/defaultconfig.go index 84edc80fd..00103af6b 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/server/http/option.go b/services/activitylog/pkg/server/http/option.go new file mode 100644 index 000000000..5d415be3f --- /dev/null +++ b/services/activitylog/pkg/server/http/option.go @@ -0,0 +1,130 @@ +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" + "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 + 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 + } +} + +// History provides a function to configure the event history client +func History(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 + } +} diff --git a/services/activitylog/pkg/server/http/server.go b/services/activitylog/pkg/server/http/server.go new file mode 100644 index 000000000..455f2125c --- /dev/null +++ b/services/activitylog/pkg/server/http/server.go @@ -0,0 +1,100 @@ +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.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 000000000..5636ec243 --- /dev/null +++ b/services/activitylog/pkg/service/http.go @@ -0,0 +1,114 @@ +package service + +import ( + "encoding/json" + "fmt" + "net/http" + "net/url" + + provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" + "github.com/cs3org/reva/v2/pkg/events" + "github.com/cs3org/reva/v2/pkg/storagespace" + "github.com/go-chi/chi/v5" + ehmsg "github.com/owncloud/ocis/v2/protogen/gen/ocis/messages/eventhistory/v0" + ehsvc "github.com/owncloud/ocis/v2/protogen/gen/ocis/services/eventhistory/v0" + "github.com/owncloud/ocis/v2/services/graph/pkg/errorcode" +) + +// 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) { + // TODO: Compare driveid with itemid to avoid bad requests + rid, err := parseIDParam(r, "item-id") + if err != nil { + s.log.Info().Err(err).Msg("invalid resource id") + 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)) + for _, a := range raw { + // TODO: Filter by depth and timestamp + ids = append(ids, a.EventID) + } + + fmt.Println("IDS:", ids) + + 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 + } + + // TODO: compare returned events with initial list and remove missing ones + + fmt.Println("EVENTS:", evRes.GetEvents()) + + var acts []Activity + for _, e := range evRes.GetEvents() { + // FIXME: Should all users get all events? If not we can filter here + + switch ev := s.unwrapEvent(e).(type) { + case nil: + // error already logged in unwrapEvent + continue + case events.UploadReady: + act := UploadReady(e.Id, ev) + acts = append(acts, act) + } + } + + fmt.Println("ACTIVITIES:", acts) + + b, err := json.Marshal(acts) + if err != nil { + s.log.Error().Err(err).Msg("error marshalling activities") + w.WriteHeader(http.StatusInternalServerError) + return + } + + w.Write(b) + 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 +} + +// TODO: I found this on graph service. We should move it to `utils` pkg so both services can use it. +func parseIDParam(r *http.Request, param string) (provider.ResourceId, error) { + driveID, err := url.PathUnescape(chi.URLParam(r, param)) + if err != nil { + return provider.ResourceId{}, errorcode.New(errorcode.InvalidRequest, err.Error()) + } + + id, err := storagespace.ParseID(driveID) + if err != nil { + return provider.ResourceId{}, errorcode.New(errorcode.InvalidRequest, err.Error()) + } + return id, nil +} diff --git a/services/activitylog/pkg/service/options.go b/services/activitylog/pkg/service/options.go index cc96d1603..8c9fef3fe 100644 --- a/services/activitylog/pkg/service/options.go +++ b/services/activitylog/pkg/service/options.go @@ -4,7 +4,9 @@ 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" "github.com/owncloud/ocis/v2/services/activitylog/pkg/config" microstore "go-micro.dev/v4/store" "go.opentelemetry.io/otel/trace" @@ -22,6 +24,8 @@ type Options struct { RegisteredEvents []events.Unmarshaller Store microstore.Store GatewaySelector pool.Selectable[gateway.GatewayAPIClient] + Mux *chi.Mux + HistoryClient ehsvc.EventHistoryService } // Logger configures a logger for the activitylog service @@ -72,3 +76,17 @@ 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 + } +} diff --git a/services/activitylog/pkg/service/response.go b/services/activitylog/pkg/service/response.go new file mode 100644 index 000000000..f09d6cfff --- /dev/null +++ b/services/activitylog/pkg/service/response.go @@ -0,0 +1,67 @@ +package service + +import ( + "time" + + "github.com/cs3org/reva/v2/pkg/events" + "github.com/cs3org/reva/v2/pkg/storagespace" +) + +// GetActivitiesResponse is the response on GET activities requests +type GetActivitiesResponse struct { + Activities []Activity `json:"value"` +} + +// Activity represents an activity as it is returned to the client +type Activity struct { + ID string `json:"id"` + + // TODO: Implement these + Action interface{} `json:"action"` + DriveItem Resource `json:"driveItem"` + Actor Actor `json:"actor"` + Times Times `json:"times"` + + Template Template `json:"template"` +} + +// Resource represents an item such as a file or folder +type Resource struct { + ID string `json:"id"` + Name string `json:"name"` +} + +// Actor represents the user who performed the Action +type Actor struct { + ID string `json:"id"` + DisplayName string `json:"displayName"` +} + +// Times represents the timestamps of the Activity +type Times struct { + RecordedTime time.Time `json:"recordedTime"` +} + +// Template contains activity details +type Template struct { + Message string `json:"message"` + Variables map[string]interface{} `json:"variables"` +} + +// UploadReady converts a UploadReady events to an Activity +func UploadReady(eid string, e events.UploadReady) Activity { + rid, _ := storagespace.FormatReference(e.FileRef) + res := Resource{ + ID: rid, + Name: e.Filename, + } + return Activity{ + ID: eid, + Template: Template{ + Message: "file created", + Variables: map[string]interface{}{ + "resource": res, + }, + }, + } +} diff --git a/services/activitylog/pkg/service/service.go b/services/activitylog/pkg/service/service.go index 4f2c0cf23..00b50c814 100644 --- a/services/activitylog/pkg/service/service.go +++ b/services/activitylog/pkg/service/service.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" "path/filepath" + "reflect" "time" gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1" @@ -13,13 +14,15 @@ 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" "github.com/owncloud/ocis/v2/ocis-pkg/log" + ehsvc "github.com/owncloud/ocis/v2/protogen/gen/ocis/services/eventhistory/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 +30,15 @@ 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 + + registeredEvents map[string]events.Unmarshaller } // New creates a new ActivitylogService @@ -55,13 +62,27 @@ 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, + registeredEvents: make(map[string]events.Unmarshaller), } + s.mux.Route("/graph/v1.0/drives/{drive-id}", func(r chi.Router) { + r.Get("/items/{item-id}/activities", s.HandleGetItemActivities) + }) + + for _, e := range o.RegisteredEvents { + typ := reflect.TypeOf(e) + s.registeredEvents[typ.String()] = e + } + + go s.Run() + return s, nil } @@ -155,7 +176,7 @@ func (a *ActivitylogService) AddActivityTrashed(resourceID *provider.ResourceId, } // 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) { resourceID := storagespace.FormatResourceID(*rid) records, err := a.store.Read(resourceID) @@ -164,10 +185,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) } @@ -214,7 +235,7 @@ func (a *ActivitylogService) storeActivity(rid *provider.ResourceId, eventID str 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 +243,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, diff --git a/services/eventhistory/pkg/config/defaults/defaultconfig.go b/services/eventhistory/pkg/config/defaults/defaultconfig.go index 141817568..fbcb7da44 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/proxy/pkg/config/defaults/defaultconfig.go b/services/proxy/pkg/config/defaults/defaultconfig.go index c638012ac..e73fe14e9 100644 --- a/services/proxy/pkg/config/defaults/defaultconfig.go +++ b/services/proxy/pkg/config/defaults/defaultconfig.go @@ -236,6 +236,17 @@ func DefaultPolicies() []config.Policy { Endpoint: "/app/", // /app or /apps? ocdav only handles /apps Service: "com.owncloud.web.frontend", }, + // reroute activities endpoint to activitylog service + // { + // Type: config.RegexRoute, + // Endpoint: "/graph/v1.0/drives/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/items/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/activities", + // Service: "com.owncloud.web.activitylog", + // }, + { + Type: config.RegexRoute, + Endpoint: "/graph/v1.0/drives/[^/]+/items/[^/]+/activities", + Service: "com.owncloud.web.activitylog", + }, { Endpoint: "/graph/v1.0/invitations", Service: "com.owncloud.web.invitations", From 1fb31178b620a1e4659ed57ea64980d6606af184 Mon Sep 17 00:00:00 2001 From: jkoberg Date: Wed, 12 Jun 2024 15:19:09 +0200 Subject: [PATCH 02/13] feat(activitylog): adjust response format Signed-off-by: jkoberg --- services/activitylog/pkg/service/http.go | 30 +++++--- services/activitylog/pkg/service/response.go | 75 +++++++++++++++----- services/eventhistory/pkg/service/service.go | 4 ++ 3 files changed, 80 insertions(+), 29 deletions(-) diff --git a/services/activitylog/pkg/service/http.go b/services/activitylog/pkg/service/http.go index 5636ec243..81548b3ff 100644 --- a/services/activitylog/pkg/service/http.go +++ b/services/activitylog/pkg/service/http.go @@ -2,13 +2,13 @@ package service import ( "encoding/json" - "fmt" "net/http" "net/url" provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" "github.com/cs3org/reva/v2/pkg/events" "github.com/cs3org/reva/v2/pkg/storagespace" + "github.com/cs3org/reva/v2/pkg/utils" "github.com/go-chi/chi/v5" ehmsg "github.com/owncloud/ocis/v2/protogen/gen/ocis/messages/eventhistory/v0" ehsvc "github.com/owncloud/ocis/v2/protogen/gen/ocis/services/eventhistory/v0" @@ -43,8 +43,6 @@ func (s *ActivitylogService) HandleGetItemActivities(w http.ResponseWriter, r *h ids = append(ids, a.EventID) } - fmt.Println("IDS:", ids) - evRes, err := s.evHistory.GetEvents(r.Context(), &ehsvc.GetEventsRequest{Ids: ids}) if err != nil { s.log.Error().Err(err).Msg("error getting events") @@ -52,25 +50,35 @@ func (s *ActivitylogService) HandleGetItemActivities(w http.ResponseWriter, r *h return } - // TODO: compare returned events with initial list and remove missing ones - - fmt.Println("EVENTS:", evRes.GetEvents()) - var acts []Activity for _, e := range evRes.GetEvents() { + // TODO: compare returned events with initial list and remove missing ones + // FIXME: Should all users get all events? If not we can filter here + var ( + message string + res Resource + act Actor + ts Times + ) + switch ev := s.unwrapEvent(e).(type) { case nil: // error already logged in unwrapEvent continue case events.UploadReady: - act := UploadReady(e.Id, ev) - acts = append(acts, act) + message = "{user} created {resource}" + res, act, ts, err = s.ResponseData(ev.FileRef, ev.ExecutingUser.GetId(), ev.ExecutingUser.GetDisplayName(), utils.TSToTime(ev.Timestamp)) } - } - fmt.Println("ACTIVITIES:", acts) + if err != nil { + s.log.Error().Err(err).Msg("error getting response data") + continue + } + + acts = append(acts, NewActivity(message, res, act, ts.RecordedTime, e.GetId())) + } b, err := json.Marshal(acts) if err != nil { diff --git a/services/activitylog/pkg/service/response.go b/services/activitylog/pkg/service/response.go index f09d6cfff..9b24564ee 100644 --- a/services/activitylog/pkg/service/response.go +++ b/services/activitylog/pkg/service/response.go @@ -3,8 +3,10 @@ package service import ( "time" - "github.com/cs3org/reva/v2/pkg/events" + user "github.com/cs3org/go-cs3apis/cs3/identity/user/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" ) // GetActivitiesResponse is the response on GET activities requests @@ -14,15 +16,14 @@ type GetActivitiesResponse struct { // Activity represents an activity as it is returned to the client type Activity struct { - ID string `json:"id"` + ID string `json:"id"` + DriveItem Resource `json:"driveItem"` + Actor Actor `json:"actor"` + Times Times `json:"times"` + Template Template `json:"template"` - // TODO: Implement these - Action interface{} `json:"action"` - DriveItem Resource `json:"driveItem"` - Actor Actor `json:"actor"` - Times Times `json:"times"` - - Template Template `json:"template"` + // TODO: Implement + Action interface{} `json:"action"` } // Resource represents an item such as a file or folder @@ -48,20 +49,58 @@ type Template struct { Variables map[string]interface{} `json:"variables"` } -// UploadReady converts a UploadReady events to an Activity -func UploadReady(eid string, e events.UploadReady) Activity { - rid, _ := storagespace.FormatReference(e.FileRef) - res := Resource{ - ID: rid, - Name: e.Filename, - } +// NewActivity creates a new activity +func NewActivity(message string, res Resource, user Actor, ts time.Time, eventID string) Activity { return Activity{ - ID: eid, + ID: eventID, + Times: Times{ + RecordedTime: ts, + }, + DriveItem: res, + Actor: user, Template: Template{ - Message: "file created", + Message: message, Variables: map[string]interface{}{ "resource": res, + "user": user, }, }, } } + +// ResponseData returns the relevant response data for the activity +func (s *ActivitylogService) ResponseData(ref *provider.Reference, uid *user.UserId, username string, ts time.Time) (Resource, Actor, Times, error) { + gwc, err := s.gws.Next() + if err != nil { + return Resource{}, Actor{}, Times{}, err + } + + ctx, err := utils.GetServiceUserContext(s.cfg.ServiceAccount.ServiceAccountID, gwc, s.cfg.ServiceAccount.ServiceAccountSecret) + if err != nil { + return Resource{}, Actor{}, Times{}, err + } + + info, err := utils.GetResource(ctx, ref, gwc) + if err != nil { + return Resource{}, Actor{}, Times{}, err + } + + if username == "" { + u, err := utils.GetUser(uid, gwc) + if err != nil { + return Resource{}, Actor{}, Times{}, err + } + username = u.GetUsername() + } + + return Resource{ + ID: storagespace.FormatResourceID(*info.Id), + Name: info.Path, + }, Actor{ + ID: uid.GetOpaqueId(), + DisplayName: username, + }, Times{ + RecordedTime: ts, + }, nil + +} diff --git a/services/eventhistory/pkg/service/service.go b/services/eventhistory/pkg/service/service.go index 848fc4d9b..ba2a49298 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") From 72f4dc90601fecab564fa14f373f811ad52ad887 Mon Sep 17 00:00:00 2001 From: jkoberg Date: Thu, 13 Jun 2024 16:02:05 +0200 Subject: [PATCH 03/13] feat(activitylog): add missing events Signed-off-by: jkoberg --- services/activitylog/pkg/service/http.go | 54 +++++++++++++++++-- services/activitylog/pkg/service/response.go | 13 ++--- .../activitylog/pkg/service/service_test.go | 18 +++---- 3 files changed, 62 insertions(+), 23 deletions(-) diff --git a/services/activitylog/pkg/service/http.go b/services/activitylog/pkg/service/http.go index 81548b3ff..a39b11645 100644 --- a/services/activitylog/pkg/service/http.go +++ b/services/activitylog/pkg/service/http.go @@ -50,7 +50,7 @@ func (s *ActivitylogService) HandleGetItemActivities(w http.ResponseWriter, r *h return } - var acts []Activity + var resp GetActivitiesResponse for _, e := range evRes.GetEvents() { // TODO: compare returned events with initial list and remove missing ones @@ -70,6 +70,48 @@ func (s *ActivitylogService) HandleGetItemActivities(w http.ResponseWriter, r *h case events.UploadReady: message = "{user} created {resource}" res, act, ts, err = s.ResponseData(ev.FileRef, ev.ExecutingUser.GetId(), ev.ExecutingUser.GetDisplayName(), utils.TSToTime(ev.Timestamp)) + case events.FileTouched: + message = "{user} created {resource}" + res, act, ts, err = s.ResponseData(ev.Ref, ev.Executant, "", utils.TSToTime(ev.Timestamp)) + case events.ContainerCreated: + message = "{user} created {resource}" + res, act, ts, err = s.ResponseData(ev.Ref, ev.Executant, "", utils.TSToTime(ev.Timestamp)) + case events.ItemTrashed: + message = "{user} trashed {resource}" + res, act, ts, err = s.ResponseData(ev.Ref, ev.Executant, "", utils.TSToTime(ev.Timestamp)) + case events.ItemPurged: + message = "{user} purged {resource}" + res, act, ts, err = s.ResponseData(ev.Ref, ev.Executant, "", utils.TSToTime(ev.Timestamp)) + case events.ItemMoved: + message = "{user} moved {resource}" + res, act, ts, err = s.ResponseData(ev.Ref, ev.Executant, "", utils.TSToTime(ev.Timestamp)) + case events.ShareCreated: + message = "{user} shared {resource}" + res, act, ts, err = s.ResponseData(toRef(ev.ItemID), ev.Executant, "", utils.TSToTime(ev.CTime)) + case events.ShareUpdated: + message = "{user} updated share of {resource}" + res, act, ts, err = s.ResponseData(toRef(ev.ItemID), ev.Executant, "", utils.TSToTime(ev.MTime)) + case events.ShareRemoved: + message = "{user} removed share of {resource}" + res, act, ts, err = s.ResponseData(toRef(ev.ItemID), ev.Executant, "", ev.Timestamp) + case events.LinkCreated: + message = "{user} created link to {resource}" + res, act, ts, err = s.ResponseData(toRef(ev.ItemID), ev.Executant, "", utils.TSToTime(ev.CTime)) + case events.LinkUpdated: + message = "{user} updated link to {resource}" + res, act, ts, err = s.ResponseData(toRef(ev.ItemID), ev.Executant, "", utils.TSToTime(ev.CTime)) + case events.LinkRemoved: + message = "{user} removed link to {resource}" + res, act, ts, err = s.ResponseData(toRef(ev.ItemID), ev.Executant, "", utils.TSToTime(ev.Timestamp)) + case events.SpaceShared: + message = "{user} shared space {resource}" + res, act, ts, err = s.ResponseData(sToRef(ev.ID), ev.Executant, "", ev.Timestamp) + case events.SpaceShareUpdated: + message = "{user} updated share of space {resource}" + res, act, ts, err = s.ResponseData(sToRef(ev.ID), ev.Executant, "", ev.Timestamp) + case events.SpaceUnshared: + message = "{user} unshared space {resource}" + res, act, ts, err = s.ResponseData(sToRef(ev.ID), ev.Executant, "", ev.Timestamp) } if err != nil { @@ -77,17 +119,21 @@ func (s *ActivitylogService) HandleGetItemActivities(w http.ResponseWriter, r *h continue } - acts = append(acts, NewActivity(message, res, act, ts.RecordedTime, e.GetId())) + resp.Activities = append(resp.Activities, NewActivity(message, res, act, ts.RecordedTime, e.GetId())) } - b, err := json.Marshal(acts) + b, err := json.Marshal(resp) if err != nil { s.log.Error().Err(err).Msg("error marshalling activities") w.WriteHeader(http.StatusInternalServerError) return } - w.Write(b) + 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) } diff --git a/services/activitylog/pkg/service/response.go b/services/activitylog/pkg/service/response.go index 9b24564ee..44273145e 100644 --- a/services/activitylog/pkg/service/response.go +++ b/services/activitylog/pkg/service/response.go @@ -16,14 +16,9 @@ type GetActivitiesResponse struct { // Activity represents an activity as it is returned to the client type Activity struct { - ID string `json:"id"` - DriveItem Resource `json:"driveItem"` - Actor Actor `json:"actor"` - Times Times `json:"times"` - Template Template `json:"template"` - - // TODO: Implement - Action interface{} `json:"action"` + ID string `json:"id"` + Times Times `json:"times"` + Template Template `json:"template"` } // Resource represents an item such as a file or folder @@ -56,8 +51,6 @@ func NewActivity(message string, res Resource, user Actor, ts time.Time, eventID Times: Times{ RecordedTime: ts, }, - DriveItem: res, - Actor: user, Template: Template{ Message: message, Variables: map[string]interface{}{ diff --git a/services/activitylog/pkg/service/service_test.go b/services/activitylog/pkg/service/service_test.go index 55b2f6271..2aa9b92a2 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: From 5249cbc1388fa138e2c42bdfb85da727dfd17747 Mon Sep 17 00:00:00 2001 From: jkoberg Date: Fri, 14 Jun 2024 15:44:42 +0200 Subject: [PATCH 04/13] feat(activitylog): translations Signed-off-by: jkoberg --- ocis-pkg/l10n/l10n.go | 3 + services/activitylog/Makefile | 24 ++++++++ services/activitylog/pkg/command/server.go | 5 +- .../activitylog/pkg/server/http/option.go | 13 ++++- .../activitylog/pkg/server/http/server.go | 1 + services/activitylog/pkg/service/http.go | 56 +++++++++++++------ .../pkg/service/l10n/locale/tmp.txt | 0 .../activitylog/pkg/service/l10n/userlog.pot | 22 ++++++++ services/activitylog/pkg/service/options.go | 9 +++ services/activitylog/pkg/service/response.go | 26 +++++++-- services/activitylog/pkg/service/service.go | 17 +++--- 11 files changed, 145 insertions(+), 31 deletions(-) create mode 100644 services/activitylog/pkg/service/l10n/locale/tmp.txt create mode 100644 services/activitylog/pkg/service/l10n/userlog.pot diff --git a/ocis-pkg/l10n/l10n.go b/ocis-pkg/l10n/l10n.go index f9b89528f..5c99c399a 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 dd41e6af7..8d4cc0e15 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)/userlog.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 f80c7b4b8..50e4a05d6 100644 --- a/services/activitylog/pkg/command/server.go +++ b/services/activitylog/pkg/command/server.go @@ -18,6 +18,7 @@ import ( "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" @@ -119,6 +120,7 @@ func Server(cfg *config.Config) *cli.Command { } hClient := ehsvc.NewEventHistoryService("com.owncloud.api.eventhistory", grpcClient) + vClient := settingssvc.NewValueService("com.owncloud.api.settings", grpcClient) { svc, err := http.Server( @@ -130,7 +132,8 @@ func Server(cfg *config.Config) *cli.Command { http.RegisteredEvents(_registeredEvents), http.Store(evStore), http.GatewaySelector(gatewaySelector), - http.History(hClient), + http.HistoryClient(hClient), + http.ValueClient(vClient), http.RegisteredEvents(_registeredEvents), ) diff --git a/services/activitylog/pkg/server/http/option.go b/services/activitylog/pkg/server/http/option.go index 5d415be3f..760cb131b 100644 --- a/services/activitylog/pkg/server/http/option.go +++ b/services/activitylog/pkg/server/http/option.go @@ -8,6 +8,7 @@ import ( "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" @@ -31,6 +32,7 @@ type Options struct { GatewaySelector pool.Selectable[gateway.GatewayAPIClient] TraceProvider trace.TracerProvider HistoryClient ehsvc.EventHistoryService + ValueClient settingssvc.ValueService RegisteredEvents []events.Unmarshaller } @@ -108,8 +110,8 @@ func GatewaySelector(gatewaySelector pool.Selectable[gateway.GatewayAPIClient]) } } -// History provides a function to configure the event history client -func History(h ehsvc.EventHistoryService) Option { +// HistoryClient provides a function to configure the event history client +func HistoryClient(h ehsvc.EventHistoryService) Option { return func(o *Options) { o.HistoryClient = h } @@ -128,3 +130,10 @@ func TraceProvider(val trace.TracerProvider) Option { 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 index 455f2125c..86a239b28 100644 --- a/services/activitylog/pkg/server/http/server.go +++ b/services/activitylog/pkg/server/http/server.go @@ -86,6 +86,7 @@ func Server(opts ...Option) (http.Service, error) { svc.GatewaySelector(options.GatewaySelector), svc.TraceProvider(options.TraceProvider), svc.HistoryClient(options.HistoryClient), + svc.ValueClient(options.ValueClient), svc.RegisteredEvents(options.RegisteredEvents), ) if err != nil { diff --git a/services/activitylog/pkg/service/http.go b/services/activitylog/pkg/service/http.go index a39b11645..6cf8ba551 100644 --- a/services/activitylog/pkg/service/http.go +++ b/services/activitylog/pkg/service/http.go @@ -1,20 +1,34 @@ package service import ( + "embed" "encoding/json" "net/http" "net/url" 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" "github.com/go-chi/chi/v5" + "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" "github.com/owncloud/ocis/v2/services/graph/pkg/errorcode" ) +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) @@ -22,6 +36,12 @@ func (s *ActivitylogService) ServeHTTP(w http.ResponseWriter, r *http.Request) { // HandleGetItemActivities handles the request to get the activities of an item. func (s *ActivitylogService) HandleGetItemActivities(w http.ResponseWriter, r *http.Request) { + activeUser, ok := revactx.ContextGetUser(r.Context()) + if !ok { + w.WriteHeader(http.StatusUnauthorized) + return + } + // TODO: Compare driveid with itemid to avoid bad requests rid, err := parseIDParam(r, "item-id") if err != nil { @@ -68,49 +88,49 @@ func (s *ActivitylogService) HandleGetItemActivities(w http.ResponseWriter, r *h // error already logged in unwrapEvent continue case events.UploadReady: - message = "{user} created {resource}" + message = MessageResourceCreated res, act, ts, err = s.ResponseData(ev.FileRef, ev.ExecutingUser.GetId(), ev.ExecutingUser.GetDisplayName(), utils.TSToTime(ev.Timestamp)) case events.FileTouched: - message = "{user} created {resource}" + message = MessageResourceCreated res, act, ts, err = s.ResponseData(ev.Ref, ev.Executant, "", utils.TSToTime(ev.Timestamp)) case events.ContainerCreated: - message = "{user} created {resource}" + message = MessageResourceCreated res, act, ts, err = s.ResponseData(ev.Ref, ev.Executant, "", utils.TSToTime(ev.Timestamp)) case events.ItemTrashed: - message = "{user} trashed {resource}" + message = MessageResourceTrashed res, act, ts, err = s.ResponseData(ev.Ref, ev.Executant, "", utils.TSToTime(ev.Timestamp)) case events.ItemPurged: - message = "{user} purged {resource}" + message = MessageResourcePurged res, act, ts, err = s.ResponseData(ev.Ref, ev.Executant, "", utils.TSToTime(ev.Timestamp)) case events.ItemMoved: - message = "{user} moved {resource}" + message = MessageResourceMoved res, act, ts, err = s.ResponseData(ev.Ref, ev.Executant, "", utils.TSToTime(ev.Timestamp)) case events.ShareCreated: - message = "{user} shared {resource}" + message = MessageShareCreated res, act, ts, err = s.ResponseData(toRef(ev.ItemID), ev.Executant, "", utils.TSToTime(ev.CTime)) case events.ShareUpdated: - message = "{user} updated share of {resource}" + message = MessageShareUpdated res, act, ts, err = s.ResponseData(toRef(ev.ItemID), ev.Executant, "", utils.TSToTime(ev.MTime)) case events.ShareRemoved: - message = "{user} removed share of {resource}" + message = MessageShareDeleted res, act, ts, err = s.ResponseData(toRef(ev.ItemID), ev.Executant, "", ev.Timestamp) case events.LinkCreated: - message = "{user} created link to {resource}" + message = MessageLinkCreated res, act, ts, err = s.ResponseData(toRef(ev.ItemID), ev.Executant, "", utils.TSToTime(ev.CTime)) case events.LinkUpdated: - message = "{user} updated link to {resource}" + message = MessageLinkUpdated res, act, ts, err = s.ResponseData(toRef(ev.ItemID), ev.Executant, "", utils.TSToTime(ev.CTime)) case events.LinkRemoved: - message = "{user} removed link to {resource}" + message = MessageLinkDeleted res, act, ts, err = s.ResponseData(toRef(ev.ItemID), ev.Executant, "", utils.TSToTime(ev.Timestamp)) case events.SpaceShared: - message = "{user} shared space {resource}" + message = MessageSpaceShared res, act, ts, err = s.ResponseData(sToRef(ev.ID), ev.Executant, "", ev.Timestamp) case events.SpaceShareUpdated: - message = "{user} updated share of space {resource}" + message = MessageSpaceShareUpdated res, act, ts, err = s.ResponseData(sToRef(ev.ID), ev.Executant, "", ev.Timestamp) case events.SpaceUnshared: - message = "{user} unshared space {resource}" + message = MessageSpaceUnshared res, act, ts, err = s.ResponseData(sToRef(ev.ID), ev.Executant, "", ev.Timestamp) } @@ -119,7 +139,11 @@ func (s *ActivitylogService) HandleGetItemActivities(w http.ResponseWriter, r *h continue } - resp.Activities = append(resp.Activities, NewActivity(message, res, act, ts.RecordedTime, e.GetId())) + // todo: 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), res, act, ts, e.GetId())) } b, err := json.Marshal(resp) 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 000000000..e69de29bb diff --git a/services/activitylog/pkg/service/l10n/userlog.pot b/services/activitylog/pkg/service/l10n/userlog.pot new file mode 100644 index 000000000..606d31510 --- /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 8c9fef3fe..0aa97abfc 100644 --- a/services/activitylog/pkg/service/options.go +++ b/services/activitylog/pkg/service/options.go @@ -7,6 +7,7 @@ import ( "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" @@ -26,6 +27,7 @@ type Options struct { GatewaySelector pool.Selectable[gateway.GatewayAPIClient] Mux *chi.Mux HistoryClient ehsvc.EventHistoryService + ValueClient settingssvc.ValueService } // Logger configures a logger for the activitylog service @@ -90,3 +92,10 @@ func HistoryClient(hc ehsvc.EventHistoryService) Option { 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 index 44273145e..ce185cc20 100644 --- a/services/activitylog/pkg/service/response.go +++ b/services/activitylog/pkg/service/response.go @@ -7,6 +7,24 @@ import ( provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" "github.com/cs3org/reva/v2/pkg/storagespace" "github.com/cs3org/reva/v2/pkg/utils" + "github.com/owncloud/ocis/v2/ocis-pkg/l10n" +) + +// Translations +var ( + MessageResourceCreated = l10n.Template("{user} created {resource}") + MessageResourceTrashed = l10n.Template("{user} trashed {resource}") + MessageResourcePurged = l10n.Template("{user} purged {resource}") + MessageResourceMoved = l10n.Template("{user} moved {resource}") + MessageShareCreated = l10n.Template("{user} shared {resource}") + MessageShareUpdated = l10n.Template("{user} updated share of {resource}") + MessageShareDeleted = l10n.Template("{user} deleted share of {resource}") + MessageLinkCreated = l10n.Template("{user} created link to {resource}") + MessageLinkUpdated = l10n.Template("{user} updated link to {resource}") + MessageLinkDeleted = l10n.Template("{user} deleted link to {resource}") + MessageSpaceShared = l10n.Template("{user} shared space {resource}") + MessageSpaceShareUpdated = l10n.Template("{user} updated share of space {resource}") + MessageSpaceUnshared = l10n.Template("{user} unshared space {resource}") ) // GetActivitiesResponse is the response on GET activities requests @@ -45,12 +63,10 @@ type Template struct { } // NewActivity creates a new activity -func NewActivity(message string, res Resource, user Actor, ts time.Time, eventID string) Activity { +func NewActivity(message string, res Resource, user Actor, ts Times, eventID string) Activity { return Activity{ - ID: eventID, - Times: Times{ - RecordedTime: ts, - }, + ID: eventID, + Times: ts, Template: Template{ Message: message, Variables: map[string]interface{}{ diff --git a/services/activitylog/pkg/service/service.go b/services/activitylog/pkg/service/service.go index 00b50c814..9cfbb7ff2 100644 --- a/services/activitylog/pkg/service/service.go +++ b/services/activitylog/pkg/service/service.go @@ -17,6 +17,7 @@ import ( "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" ) @@ -30,13 +31,14 @@ type RawActivity 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] - mux *chi.Mux - evHistory ehsvc.EventHistoryService + 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 registeredEvents map[string]events.Unmarshaller } @@ -69,6 +71,7 @@ func New(opts ...Option) (*ActivitylogService, error) { gws: o.GatewaySelector, mux: o.Mux, evHistory: o.HistoryClient, + valService: o.ValueClient, registeredEvents: make(map[string]events.Unmarshaller), } From 949c5d08484f61a72a2debf5bf709eb126d3cdaf Mon Sep 17 00:00:00 2001 From: Florian Schade Date: Tue, 18 Jun 2024 12:13:13 +0200 Subject: [PATCH 05/13] enhancement(activitylog): enhance activitylog graph endpoint - make use of libregraph artifacts - add a basic activity kql ast parser --- services/activitylog/pkg/service/http.go | 36 ++++++++++++++-- services/activitylog/pkg/service/response.go | 42 ++++++------------- services/activitylog/pkg/service/service.go | 7 ++-- .../pkg/config/defaults/defaultconfig.go | 9 +--- 4 files changed, 50 insertions(+), 44 deletions(-) diff --git a/services/activitylog/pkg/service/http.go b/services/activitylog/pkg/service/http.go index 6cf8ba551..f574bfea8 100644 --- a/services/activitylog/pkg/service/http.go +++ b/services/activitylog/pkg/service/http.go @@ -5,6 +5,7 @@ import ( "encoding/json" "net/http" "net/url" + "strings" provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" revactx "github.com/cs3org/reva/v2/pkg/ctx" @@ -12,10 +13,14 @@ import ( "github.com/cs3org/reva/v2/pkg/storagespace" "github.com/cs3org/reva/v2/pkg/utils" "github.com/go-chi/chi/v5" + libregraph "github.com/owncloud/libre-graph-api-go" + "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" "github.com/owncloud/ocis/v2/services/graph/pkg/errorcode" + "github.com/owncloud/ocis/v2/services/search/pkg/query/ast" + "github.com/owncloud/ocis/v2/services/search/pkg/query/kql" ) var ( @@ -42,8 +47,33 @@ func (s *ActivitylogService) HandleGetItemActivities(w http.ResponseWriter, r *h return } - // TODO: Compare driveid with itemid to avoid bad requests - rid, err := parseIDParam(r, "item-id") + qraw := r.URL.Query().Get("kql") + if qraw == "" { + w.WriteHeader(http.StatusBadRequest) + } + + qBuilder := kql.Builder{} + qast, err := qBuilder.Build(qraw) + if err != nil { + w.WriteHeader(http.StatusBadRequest) + } + + var itemID string + + for _, n := range qast.Nodes { + v, ok := n.(*ast.StringNode) + if !ok { + continue + } + + if strings.ToLower(v.Key) != "itemid" { + continue + } + + itemID = v.Value + } + + rid, err := storagespace.ParseID(itemID) if err != nil { s.log.Info().Err(err).Msg("invalid resource id") w.WriteHeader(http.StatusBadRequest) @@ -80,7 +110,7 @@ func (s *ActivitylogService) HandleGetItemActivities(w http.ResponseWriter, r *h message string res Resource act Actor - ts Times + ts libregraph.ActivityTimes ) switch ev := s.unwrapEvent(e).(type) { diff --git a/services/activitylog/pkg/service/response.go b/services/activitylog/pkg/service/response.go index ce185cc20..9186d1d3e 100644 --- a/services/activitylog/pkg/service/response.go +++ b/services/activitylog/pkg/service/response.go @@ -7,6 +7,8 @@ import ( 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" ) @@ -29,14 +31,7 @@ var ( // GetActivitiesResponse is the response on GET activities requests type GetActivitiesResponse struct { - Activities []Activity `json:"value"` -} - -// Activity represents an activity as it is returned to the client -type Activity struct { - ID string `json:"id"` - Times Times `json:"times"` - Template Template `json:"template"` + Activities []libregraph.Activity `json:"value"` } // Resource represents an item such as a file or folder @@ -51,23 +46,12 @@ type Actor struct { DisplayName string `json:"displayName"` } -// Times represents the timestamps of the Activity -type Times struct { - RecordedTime time.Time `json:"recordedTime"` -} - -// Template contains activity details -type Template struct { - Message string `json:"message"` - Variables map[string]interface{} `json:"variables"` -} - // NewActivity creates a new activity -func NewActivity(message string, res Resource, user Actor, ts Times, eventID string) Activity { - return Activity{ - ID: eventID, +func NewActivity(message string, res Resource, user Actor, ts libregraph.ActivityTimes, eventID string) libregraph.Activity { + return libregraph.Activity{ + Id: eventID, Times: ts, - Template: Template{ + Template: libregraph.ActivityTemplate{ Message: message, Variables: map[string]interface{}{ "resource": res, @@ -78,26 +62,26 @@ func NewActivity(message string, res Resource, user Actor, ts Times, eventID str } // ResponseData returns the relevant response data for the activity -func (s *ActivitylogService) ResponseData(ref *provider.Reference, uid *user.UserId, username string, ts time.Time) (Resource, Actor, Times, error) { +func (s *ActivitylogService) ResponseData(ref *provider.Reference, uid *user.UserId, username string, ts time.Time) (Resource, Actor, libregraph.ActivityTimes, error) { gwc, err := s.gws.Next() if err != nil { - return Resource{}, Actor{}, Times{}, err + return Resource{}, Actor{}, libregraph.ActivityTimes{}, err } ctx, err := utils.GetServiceUserContext(s.cfg.ServiceAccount.ServiceAccountID, gwc, s.cfg.ServiceAccount.ServiceAccountSecret) if err != nil { - return Resource{}, Actor{}, Times{}, err + return Resource{}, Actor{}, libregraph.ActivityTimes{}, err } info, err := utils.GetResource(ctx, ref, gwc) if err != nil { - return Resource{}, Actor{}, Times{}, err + return Resource{}, Actor{}, libregraph.ActivityTimes{}, err } if username == "" { u, err := utils.GetUser(uid, gwc) if err != nil { - return Resource{}, Actor{}, Times{}, err + return Resource{}, Actor{}, libregraph.ActivityTimes{}, err } username = u.GetUsername() } @@ -108,7 +92,7 @@ func (s *ActivitylogService) ResponseData(ref *provider.Reference, uid *user.Use }, Actor{ ID: uid.GetOpaqueId(), DisplayName: username, - }, Times{ + }, libregraph.ActivityTimes{ RecordedTime: ts, }, nil diff --git a/services/activitylog/pkg/service/service.go b/services/activitylog/pkg/service/service.go index 9cfbb7ff2..036247405 100644 --- a/services/activitylog/pkg/service/service.go +++ b/services/activitylog/pkg/service/service.go @@ -15,11 +15,12 @@ import ( "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" ) // RawActivity represents an activity as it is stored in the activitylog store @@ -75,9 +76,7 @@ func New(opts ...Option) (*ActivitylogService, error) { registeredEvents: make(map[string]events.Unmarshaller), } - s.mux.Route("/graph/v1.0/drives/{drive-id}", func(r chi.Router) { - r.Get("/items/{item-id}/activities", s.HandleGetItemActivities) - }) + s.mux.Get("/graph/v1beta1/extensions/org.libregraph/activities", s.HandleGetItemActivities) for _, e := range o.RegisteredEvents { typ := reflect.TypeOf(e) diff --git a/services/proxy/pkg/config/defaults/defaultconfig.go b/services/proxy/pkg/config/defaults/defaultconfig.go index e73fe14e9..af2093522 100644 --- a/services/proxy/pkg/config/defaults/defaultconfig.go +++ b/services/proxy/pkg/config/defaults/defaultconfig.go @@ -236,15 +236,8 @@ func DefaultPolicies() []config.Policy { Endpoint: "/app/", // /app or /apps? ocdav only handles /apps Service: "com.owncloud.web.frontend", }, - // reroute activities endpoint to activitylog service - // { - // Type: config.RegexRoute, - // Endpoint: "/graph/v1.0/drives/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/items/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/activities", - // Service: "com.owncloud.web.activitylog", - // }, { - Type: config.RegexRoute, - Endpoint: "/graph/v1.0/drives/[^/]+/items/[^/]+/activities", + Endpoint: "/graph/v1beta1/extensions/org.libregraph/activities", Service: "com.owncloud.web.activitylog", }, { From ca9192cf36aad2d5495c1b5eb870b7fb22fde5bc Mon Sep 17 00:00:00 2001 From: jkoberg Date: Tue, 18 Jun 2024 15:41:18 +0200 Subject: [PATCH 06/13] feat(activitylog): allow filtering activities Signed-off-by: jkoberg --- services/activitylog/pkg/config/debug.go | 8 +- services/activitylog/pkg/config/log.go | 8 +- services/activitylog/pkg/config/tracing.go | 8 +- services/activitylog/pkg/service/http.go | 146 ++++++++++++++------ services/activitylog/pkg/service/service.go | 28 +++- 5 files changed, 140 insertions(+), 58 deletions(-) diff --git a/services/activitylog/pkg/config/debug.go b/services/activitylog/pkg/config/debug.go index 4f7fc486c..b1435d9d2 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/log.go b/services/activitylog/pkg/config/log.go index 666107621..fef52d107 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_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;ACTIVITYLOG_USERLOG_LOG_PRETTY" desc:"Activates pretty log output." introductionVersion:"5.0"` + Color bool `mapstructure:"color" env:"OCIS_LOG_COLOR;ACTIVITYLOG_USERLOG_LOG_COLOR" desc:"Activates colorized log output." introductionVersion:"5.0"` + File string `mapstructure:"file" env:"OCIS_LOG_FILE;ACTIVITYLOG_USERLOG_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 f4320a086..f39dabd38 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/service/http.go b/services/activitylog/pkg/service/http.go index f574bfea8..39dbbb268 100644 --- a/services/activitylog/pkg/service/http.go +++ b/services/activitylog/pkg/service/http.go @@ -3,8 +3,9 @@ package service import ( "embed" "encoding/json" + "errors" "net/http" - "net/url" + "strconv" "strings" provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" @@ -12,13 +13,11 @@ import ( "github.com/cs3org/reva/v2/pkg/events" "github.com/cs3org/reva/v2/pkg/storagespace" "github.com/cs3org/reva/v2/pkg/utils" - "github.com/go-chi/chi/v5" libregraph "github.com/owncloud/libre-graph-api-go" "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" - "github.com/owncloud/ocis/v2/services/graph/pkg/errorcode" "github.com/owncloud/ocis/v2/services/search/pkg/query/ast" "github.com/owncloud/ocis/v2/services/search/pkg/query/kql" ) @@ -47,40 +46,15 @@ func (s *ActivitylogService) HandleGetItemActivities(w http.ResponseWriter, r *h return } - qraw := r.URL.Query().Get("kql") - if qraw == "" { - w.WriteHeader(http.StatusBadRequest) - } - - qBuilder := kql.Builder{} - qast, err := qBuilder.Build(qraw) + rid, limit, rawActivityAccepted, activityAccepted, err := s.getFilters(r.URL.Query().Get("kql")) if err != nil { - w.WriteHeader(http.StatusBadRequest) - } - - var itemID string - - for _, n := range qast.Nodes { - v, ok := n.(*ast.StringNode) - if !ok { - continue - } - - if strings.ToLower(v.Key) != "itemid" { - continue - } - - itemID = v.Value - } - - rid, err := storagespace.ParseID(itemID) - if err != nil { - s.log.Info().Err(err).Msg("invalid resource id") + 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) + raw, err := s.Activities(rid) if err != nil { s.log.Error().Err(err).Msg("error getting activities") w.WriteHeader(http.StatusInternalServerError) @@ -88,9 +62,13 @@ func (s *ActivitylogService) HandleGetItemActivities(w http.ResponseWriter, r *h } ids := make([]string, 0, len(raw)) + toDelete := make(map[string]struct{}, len(raw)) for _, a := range raw { - // TODO: Filter by depth and timestamp + if !rawActivityAccepted(a) { + continue + } ids = append(ids, a.EventID) + toDelete[a.EventID] = struct{}{} } evRes, err := s.evHistory.GetEvents(r.Context(), &ehsvc.GetEventsRequest{Ids: ids}) @@ -102,9 +80,15 @@ func (s *ActivitylogService) HandleGetItemActivities(w http.ResponseWriter, r *h var resp GetActivitiesResponse for _, e := range evRes.GetEvents() { - // TODO: compare returned events with initial list and remove missing ones + delete(toDelete, e.GetId()) - // FIXME: Should all users get all events? If not we can filter here + if limit != 0 && len(resp.Activities) >= limit { + continue + } + + if !activityAccepted(e) { + continue + } var ( message string @@ -169,13 +153,23 @@ func (s *ActivitylogService) HandleGetItemActivities(w http.ResponseWriter, r *h continue } - // todo: configurable default locale? + // 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), res, act, ts, e.GetId())) } + // 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") @@ -207,16 +201,80 @@ func (s *ActivitylogService) unwrapEvent(e *ehmsg.Event) interface{} { return einterface } -// TODO: I found this on graph service. We should move it to `utils` pkg so both services can use it. -func parseIDParam(r *http.Request, param string) (provider.ResourceId, error) { - driveID, err := url.PathUnescape(chi.URLParam(r, param)) +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 provider.ResourceId{}, errorcode.New(errorcode.InvalidRequest, err.Error()) + return nil, 0, nil, nil, err } - id, err := storagespace.ParseID(driveID) - if err != nil { - return provider.ResourceId{}, errorcode.New(errorcode.InvalidRequest, err.Error()) + 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") + } + } } - return id, nil + + 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 } diff --git a/services/activitylog/pkg/service/service.go b/services/activitylog/pkg/service/service.go index 036247405..2908b636d 100644 --- a/services/activitylog/pkg/service/service.go +++ b/services/activitylog/pkg/service/service.go @@ -89,7 +89,7 @@ func New(opts ...Option) (*ActivitylogService, error) { } // 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) { @@ -129,7 +129,6 @@ func (a *ActivitylogService) Run() error { 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 @@ -198,6 +197,31 @@ func (a *ActivitylogService) Activities(rid *provider.ResourceId) ([]RawActivity return activities, nil } +// RemoveActivities removes the activities from the given resource +func (a *ActivitylogService) RemoveActivities(rid *provider.ResourceId, toDelete map[string]struct{}) error { + 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, + }) +} + // note: getResource is abstracted to allow unit testing, in general this will just be utils.GetResource func (a *ActivitylogService) addActivity(initRef *provider.Reference, eventID string, timestamp time.Time, getResource func(*provider.Reference) (*provider.ResourceInfo, error)) error { var ( From 0d604dfb9b515146a749c03c21f2d25f5467b675 Mon Sep 17 00:00:00 2001 From: jkoberg Date: Wed, 19 Jun 2024 10:43:25 +0200 Subject: [PATCH 07/13] feat(ocis): bump libre-graph-api-go Signed-off-by: jkoberg --- go.mod | 2 +- go.sum | 4 +- .../owncloud/libre-graph-api-go/README.md | 5 + .../libre-graph-api-go/api_activities.go | 137 +++++++++++ .../owncloud/libre-graph-api-go/client.go | 3 + .../libre-graph-api-go/model_activity.go | 213 ++++++++++++++++++ .../model_activity_template.go | 194 ++++++++++++++++ .../model_activity_times.go | 158 +++++++++++++ .../model_collection_of_activities.go | 124 ++++++++++ vendor/modules.txt | 2 +- 10 files changed, 838 insertions(+), 4 deletions(-) create mode 100644 vendor/github.com/owncloud/libre-graph-api-go/api_activities.go create mode 100644 vendor/github.com/owncloud/libre-graph-api-go/model_activity.go create mode 100644 vendor/github.com/owncloud/libre-graph-api-go/model_activity_template.go create mode 100644 vendor/github.com/owncloud/libre-graph-api-go/model_activity_times.go create mode 100644 vendor/github.com/owncloud/libre-graph-api-go/model_collection_of_activities.go diff --git a/go.mod b/go.mod index b8792fbb0..20a0e946c 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 99a2eec78..a016609a2 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/vendor/github.com/owncloud/libre-graph-api-go/README.md b/vendor/github.com/owncloud/libre-graph-api-go/README.md index d1e630879..c6ceee694 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 000000000..7b859a7b2 --- /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 fcfbb7b78..285f371c1 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 000000000..74d4b8590 --- /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 000000000..75bcadb89 --- /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 000000000..d6970a375 --- /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 000000000..3ebfd31fb --- /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 f94a8a8e0..eee2a2cc1 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 From 7a819412c272060f5342a135c2c7dca2aea3a6c9 Mon Sep 17 00:00:00 2001 From: jkoberg Date: Wed, 19 Jun 2024 12:01:02 +0200 Subject: [PATCH 08/13] feat(ocis): move ast and kql pkg to ocis-pkg Signed-off-by: jkoberg --- {services/search/pkg/query => ocis-pkg}/ast/ast.go | 0 {services/search/pkg/query => ocis-pkg}/ast/test/test.go | 3 +-- {services/search/pkg/query => ocis-pkg}/kql/cast.go | 3 +-- {services/search/pkg/query => ocis-pkg}/kql/connect.go | 2 +- .../search/pkg/query => ocis-pkg}/kql/dictionary.peg | 0 .../search/pkg/query => ocis-pkg}/kql/dictionary_gen.go | 0 .../search/pkg/query => ocis-pkg}/kql/dictionary_test.go | 7 +++---- {services/search/pkg/query => ocis-pkg}/kql/doc.go | 0 .../pkg/query => ocis-pkg}/kql/engine_suite_test.go | 0 {services/search/pkg/query => ocis-pkg}/kql/factory.go | 2 +- {services/search/pkg/query => ocis-pkg}/kql/gen.go | 0 {services/search/pkg/query => ocis-pkg}/kql/kql.go | 2 +- {services/search/pkg/query => ocis-pkg}/kql/kql_test.go | 5 ++--- {services/search/pkg/query => ocis-pkg}/kql/validate.go | 2 +- services/activitylog/Makefile | 2 +- services/activitylog/pkg/config/log.go | 8 ++++---- services/activitylog/pkg/service/http.go | 4 ++-- services/search/Makefile | 2 +- services/search/pkg/query/bleve/bleve.go | 2 +- services/search/pkg/query/bleve/compiler.go | 4 ++-- services/search/pkg/query/bleve/compiler_test.go | 3 +-- services/search/pkg/query/error.go | 2 +- services/search/pkg/query/query.go | 4 +--- 23 files changed, 25 insertions(+), 32 deletions(-) rename {services/search/pkg/query => ocis-pkg}/ast/ast.go (100%) rename {services/search/pkg/query => ocis-pkg}/ast/test/test.go (92%) rename {services/search/pkg/query => ocis-pkg}/kql/cast.go (97%) rename {services/search/pkg/query => ocis-pkg}/kql/connect.go (98%) rename {services/search/pkg/query => ocis-pkg}/kql/dictionary.peg (100%) rename {services/search/pkg/query => ocis-pkg}/kql/dictionary_gen.go (100%) rename {services/search/pkg/query => ocis-pkg}/kql/dictionary_test.go (99%) rename {services/search/pkg/query => ocis-pkg}/kql/doc.go (100%) rename {services/search/pkg/query => ocis-pkg}/kql/engine_suite_test.go (100%) rename {services/search/pkg/query => ocis-pkg}/kql/factory.go (98%) rename {services/search/pkg/query => ocis-pkg}/kql/gen.go (100%) rename {services/search/pkg/query => ocis-pkg}/kql/kql.go (94%) rename {services/search/pkg/query => ocis-pkg}/kql/kql_test.go (88%) rename {services/search/pkg/query => ocis-pkg}/kql/validate.go (91%) 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 01a260de0..946856f8b 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 c6ca30541..55033eb1f 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 3c926d688..674a91561 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 adf4bd2d4..812f831df 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 0eb858dd9..99deef825 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 2cf03a7f0..6e14cc7b6 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 c130f7558..09f1249c5 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 96c6761ab..0712a1a7e 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/services/activitylog/Makefile b/services/activitylog/Makefile index 8d4cc0e15..f57e85e66 100644 --- a/services/activitylog/Makefile +++ b/services/activitylog/Makefile @@ -44,7 +44,7 @@ l10n-push: .PHONY: l10n-read l10n-read: $(GO_XGETTEXT) - go-xgettext -o $(OUTPUT_DIR)/userlog.pot --keyword=l10n.Template -s pkg/service/response.go + go-xgettext -o $(OUTPUT_DIR)/activitylog.pot --keyword=l10n.Template -s pkg/service/response.go .PHONY: l10n-write l10n-write: diff --git a/services/activitylog/pkg/config/log.go b/services/activitylog/pkg/config/log.go index fef52d107..6b19a6204 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;ACTIVITYLOG_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;ACTIVITYLOG_USERLOG_LOG_PRETTY" desc:"Activates pretty log output." introductionVersion:"5.0"` - Color bool `mapstructure:"color" env:"OCIS_LOG_COLOR;ACTIVITYLOG_USERLOG_LOG_COLOR" desc:"Activates colorized log output." introductionVersion:"5.0"` - File string `mapstructure:"file" env:"OCIS_LOG_FILE;ACTIVITYLOG_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/service/http.go b/services/activitylog/pkg/service/http.go index 39dbbb268..73aeda0eb 100644 --- a/services/activitylog/pkg/service/http.go +++ b/services/activitylog/pkg/service/http.go @@ -15,11 +15,11 @@ import ( "github.com/cs3org/reva/v2/pkg/utils" libregraph "github.com/owncloud/libre-graph-api-go" + "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" - "github.com/owncloud/ocis/v2/services/search/pkg/query/ast" - "github.com/owncloud/ocis/v2/services/search/pkg/query/kql" ) var ( diff --git a/services/search/Makefile b/services/search/Makefile index bc58a2eb7..a55dd6fed 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 e720b35f7..b6e825e6e 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 2d0eace10..7046bee9d 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 597ce4c29..c1286cab9 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 89f504c8a..7a3780a38 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 e49dbecd9..2b776595b 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 { From 4b5dca0a13cf62b6e4ffba998a1c30e7e120a3c4 Mon Sep 17 00:00:00 2001 From: jkoberg Date: Thu, 20 Jun 2024 15:24:53 +0200 Subject: [PATCH 09/13] feat(activitylog): finalize translation strings Signed-off-by: jkoberg --- services/activitylog/pkg/command/server.go | 7 - services/activitylog/pkg/service/http.go | 72 +++++--- services/activitylog/pkg/service/response.go | 184 ++++++++++++++----- 3 files changed, 182 insertions(+), 81 deletions(-) diff --git a/services/activitylog/pkg/command/server.go b/services/activitylog/pkg/command/server.go index 50e4a05d6..4ed99d10b 100644 --- a/services/activitylog/pkg/command/server.go +++ b/services/activitylog/pkg/command/server.go @@ -33,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. diff --git a/services/activitylog/pkg/service/http.go b/services/activitylog/pkg/service/http.go index 73aeda0eb..91554b11d 100644 --- a/services/activitylog/pkg/service/http.go +++ b/services/activitylog/pkg/service/http.go @@ -5,15 +5,16 @@ import ( "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" - libregraph "github.com/owncloud/libre-graph-api-go" "github.com/owncloud/ocis/v2/ocis-pkg/ast" "github.com/owncloud/ocis/v2/ocis-pkg/kql" @@ -92,9 +93,8 @@ func (s *ActivitylogService) HandleGetItemActivities(w http.ResponseWriter, r *h var ( message string - res Resource - act Actor - ts libregraph.ActivityTimes + ts time.Time + vars map[string]interface{} ) switch ev := s.unwrapEvent(e).(type) { @@ -103,49 +103,54 @@ func (s *ActivitylogService) HandleGetItemActivities(w http.ResponseWriter, r *h continue case events.UploadReady: message = MessageResourceCreated - res, act, ts, err = s.ResponseData(ev.FileRef, ev.ExecutingUser.GetId(), ev.ExecutingUser.GetDisplayName(), utils.TSToTime(ev.Timestamp)) + ts = utils.TSToTime(ev.Timestamp) + vars, err = s.GetVars(WithResource(ev.FileRef, true), WithUser(ev.ExecutingUser.GetId(), ev.ExecutingUser.GetDisplayName())) case events.FileTouched: message = MessageResourceCreated - res, act, ts, err = s.ResponseData(ev.Ref, ev.Executant, "", utils.TSToTime(ev.Timestamp)) + ts = utils.TSToTime(ev.Timestamp) + vars, err = s.GetVars(WithResource(ev.Ref, true), WithUser(ev.Executant, "")) case events.ContainerCreated: message = MessageResourceCreated - res, act, ts, err = s.ResponseData(ev.Ref, ev.Executant, "", utils.TSToTime(ev.Timestamp)) + ts = utils.TSToTime(ev.Timestamp) + vars, err = s.GetVars(WithResource(ev.Ref, true), WithUser(ev.Executant, "")) case events.ItemTrashed: message = MessageResourceTrashed - res, act, ts, err = s.ResponseData(ev.Ref, ev.Executant, "", utils.TSToTime(ev.Timestamp)) - case events.ItemPurged: - message = MessageResourcePurged - res, act, ts, err = s.ResponseData(ev.Ref, ev.Executant, "", utils.TSToTime(ev.Timestamp)) + ts = utils.TSToTime(ev.Timestamp) + vars, err = s.GetVars(WithResource(ev.Ref, true), WithUser(ev.Executant, "")) case events.ItemMoved: - message = MessageResourceMoved - res, act, ts, err = s.ResponseData(ev.Ref, ev.Executant, "", utils.TSToTime(ev.Timestamp)) + switch isRename(ev.OldReference, ev.Ref) { + case true: + message = MessageResourceRenamed + vars, err = s.GetVars(WithResource(ev.Ref, false), WithOldResource(ev.OldReference), WithUser(ev.Executant, "")) + case false: + message = MessageResourceMoved + vars, err = s.GetVars(WithResource(ev.Ref, true), WithUser(ev.Executant, "")) + } + ts = utils.TSToTime(ev.Timestamp) case events.ShareCreated: message = MessageShareCreated - res, act, ts, err = s.ResponseData(toRef(ev.ItemID), ev.Executant, "", utils.TSToTime(ev.CTime)) - case events.ShareUpdated: - message = MessageShareUpdated - res, act, ts, err = s.ResponseData(toRef(ev.ItemID), ev.Executant, "", utils.TSToTime(ev.MTime)) + ts = utils.TSToTime(ev.CTime) + vars, err = s.GetVars(WithResource(toRef(ev.ItemID), false), WithUser(ev.Executant, ""), WithSharee(ev.GranteeUserID, ev.GranteeGroupID)) case events.ShareRemoved: message = MessageShareDeleted - res, act, ts, err = s.ResponseData(toRef(ev.ItemID), ev.Executant, "", ev.Timestamp) + ts = ev.Timestamp + vars, err = s.GetVars(WithResource(toRef(ev.ItemID), false), WithUser(ev.Executant, ""), WithSharee(ev.GranteeUserID, ev.GranteeGroupID)) case events.LinkCreated: message = MessageLinkCreated - res, act, ts, err = s.ResponseData(toRef(ev.ItemID), ev.Executant, "", utils.TSToTime(ev.CTime)) - case events.LinkUpdated: - message = MessageLinkUpdated - res, act, ts, err = s.ResponseData(toRef(ev.ItemID), ev.Executant, "", utils.TSToTime(ev.CTime)) + ts = utils.TSToTime(ev.CTime) + vars, err = s.GetVars(WithResource(toRef(ev.ItemID), false), WithUser(ev.Executant, "")) case events.LinkRemoved: message = MessageLinkDeleted - res, act, ts, err = s.ResponseData(toRef(ev.ItemID), ev.Executant, "", utils.TSToTime(ev.Timestamp)) + ts = utils.TSToTime(ev.Timestamp) + vars, err = s.GetVars(WithResource(toRef(ev.ItemID), false), WithUser(ev.Executant, "")) case events.SpaceShared: message = MessageSpaceShared - res, act, ts, err = s.ResponseData(sToRef(ev.ID), ev.Executant, "", ev.Timestamp) - case events.SpaceShareUpdated: - message = MessageSpaceShareUpdated - res, act, ts, err = s.ResponseData(sToRef(ev.ID), ev.Executant, "", ev.Timestamp) + ts = ev.Timestamp + vars, err = s.GetVars(WithSpace(ev.ID), WithUser(ev.Executant, ""), WithSharee(ev.GranteeUserID, ev.GranteeGroupID)) case events.SpaceUnshared: message = MessageSpaceUnshared - res, act, ts, err = s.ResponseData(sToRef(ev.ID), ev.Executant, "", ev.Timestamp) + ts = ev.Timestamp + vars, err = s.GetVars(WithSpace(ev.ID), WithUser(ev.Executant, ""), WithSharee(ev.GranteeUserID, ev.GranteeGroupID)) } if err != nil { @@ -157,7 +162,7 @@ func (s *ActivitylogService) HandleGetItemActivities(w http.ResponseWriter, r *h 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), res, act, ts, e.GetId())) + resp.Activities = append(resp.Activities, NewActivity(t.Translate(message, loc), ts, e.GetId(), vars)) } // delete activities in separate go routine @@ -278,3 +283,12 @@ func (s *ActivitylogService) getFilters(query string) (*provider.ResourceId, int } 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/response.go b/services/activitylog/pkg/service/response.go index 9186d1d3e..f5ec0f4ad 100644 --- a/services/activitylog/pkg/service/response.go +++ b/services/activitylog/pkg/service/response.go @@ -1,9 +1,15 @@ 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" @@ -14,19 +20,16 @@ import ( // Translations var ( - MessageResourceCreated = l10n.Template("{user} created {resource}") - MessageResourceTrashed = l10n.Template("{user} trashed {resource}") - MessageResourcePurged = l10n.Template("{user} purged {resource}") - MessageResourceMoved = l10n.Template("{user} moved {resource}") - MessageShareCreated = l10n.Template("{user} shared {resource}") - MessageShareUpdated = l10n.Template("{user} updated share of {resource}") - MessageShareDeleted = l10n.Template("{user} deleted share of {resource}") - MessageLinkCreated = l10n.Template("{user} created link to {resource}") - MessageLinkUpdated = l10n.Template("{user} updated link to {resource}") - MessageLinkDeleted = l10n.Template("{user} deleted link to {resource}") - MessageSpaceShared = l10n.Template("{user} shared space {resource}") - MessageSpaceShareUpdated = l10n.Template("{user} updated share of space {resource}") - MessageSpaceUnshared = l10n.Template("{user} unshared space {resource}") + 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 @@ -40,60 +43,151 @@ type Resource struct { Name string `json:"name"` } -// Actor represents the user who performed the Action +// 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 + } +} + +// 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, res Resource, user Actor, ts libregraph.ActivityTimes, eventID string) libregraph.Activity { +func NewActivity(message string, ts time.Time, eventID string, vars map[string]interface{}) libregraph.Activity { return libregraph.Activity{ Id: eventID, - Times: ts, + Times: libregraph.ActivityTimes{RecordedTime: ts}, Template: libregraph.ActivityTemplate{ - Message: message, - Variables: map[string]interface{}{ - "resource": res, - "user": user, - }, + Message: message, + Variables: vars, }, } } -// ResponseData returns the relevant response data for the activity -func (s *ActivitylogService) ResponseData(ref *provider.Reference, uid *user.UserId, username string, ts time.Time) (Resource, Actor, libregraph.ActivityTimes, error) { +// GetVars calls other service to gather the required data for the activity variables +func (s *ActivitylogService) GetVars(opts ...ActivityOption) (map[string]interface{}, error) { gwc, err := s.gws.Next() if err != nil { - return Resource{}, Actor{}, libregraph.ActivityTimes{}, err + return nil, err } ctx, err := utils.GetServiceUserContext(s.cfg.ServiceAccount.ServiceAccountID, gwc, s.cfg.ServiceAccount.ServiceAccountSecret) if err != nil { - return Resource{}, Actor{}, libregraph.ActivityTimes{}, err + return nil, err } - info, err := utils.GetResource(ctx, ref, gwc) - if err != nil { - return Resource{}, Actor{}, libregraph.ActivityTimes{}, err - } - - if username == "" { - u, err := utils.GetUser(uid, gwc) - if err != nil { - return Resource{}, Actor{}, libregraph.ActivityTimes{}, err + vars := make(map[string]interface{}) + for _, opt := range opts { + if err := opt(ctx, gwc, vars); err != nil { + return nil, err } - username = u.GetUsername() } - return Resource{ - ID: storagespace.FormatResourceID(*info.Id), - Name: info.Path, - }, Actor{ - ID: uid.GetOpaqueId(), - DisplayName: username, - }, libregraph.ActivityTimes{ - RecordedTime: ts, - }, nil - + return vars, nil } From c658c4d8fabbf4834f2b2ef1d6bbb25ff322ed55 Mon Sep 17 00:00:00 2001 From: jkoberg Date: Fri, 21 Jun 2024 10:51:30 +0200 Subject: [PATCH 10/13] feat(activitylog): repair space share events Signed-off-by: jkoberg --- services/activitylog/pkg/service/service.go | 47 +++++++++------------ 1 file changed, 19 insertions(+), 28 deletions(-) diff --git a/services/activitylog/pkg/service/service.go b/services/activitylog/pkg/service/service.go index 2908b636d..32429778e 100644 --- a/services/activitylog/pkg/service/service.go +++ b/services/activitylog/pkg/service/service.go @@ -101,28 +101,20 @@ func (a *ActivitylogService) Run() { 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 { @@ -161,7 +153,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) } @@ -176,6 +168,20 @@ 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) ([]RawActivity, error) { resourceID := storagespace.FormatResourceID(*rid) @@ -236,7 +242,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) } @@ -249,13 +255,7 @@ 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 { records, err := a.store.Read(resourceID) if err != nil && err != microstore.ErrNotFound { return err @@ -291,12 +291,3 @@ func toRef(r *provider.ResourceId) *provider.Reference { ResourceId: r, } } - -func sToRef(s *provider.StorageSpaceId) *provider.Reference { - return &provider.Reference{ - ResourceId: &provider.ResourceId{ - OpaqueId: s.GetOpaqueId(), - SpaceId: s.GetOpaqueId(), - }, - } -} From 2f19daa9ef93e5d007a13c19522a13838df4743a Mon Sep 17 00:00:00 2001 From: jkoberg Date: Fri, 21 Jun 2024 11:19:35 +0200 Subject: [PATCH 11/13] feat(activitylog): repair trash events Signed-off-by: jkoberg --- services/activitylog/pkg/service/http.go | 2 +- services/activitylog/pkg/service/response.go | 29 ++++++++++++++++++++ services/activitylog/pkg/service/service.go | 6 ++++ 3 files changed, 36 insertions(+), 1 deletion(-) diff --git a/services/activitylog/pkg/service/http.go b/services/activitylog/pkg/service/http.go index 91554b11d..9dea4f27a 100644 --- a/services/activitylog/pkg/service/http.go +++ b/services/activitylog/pkg/service/http.go @@ -116,7 +116,7 @@ func (s *ActivitylogService) HandleGetItemActivities(w http.ResponseWriter, r *h case events.ItemTrashed: message = MessageResourceTrashed ts = utils.TSToTime(ev.Timestamp) - vars, err = s.GetVars(WithResource(ev.Ref, true), WithUser(ev.Executant, "")) + vars, err = s.GetVars(WithTrashedResource(ev.Ref, ev.ID), WithUser(ev.Executant, ""), WithSpace(toSpace(ev.Ref))) case events.ItemMoved: switch isRename(ev.OldReference, ev.Ref) { case true: diff --git a/services/activitylog/pkg/service/response.go b/services/activitylog/pkg/service/response.go index f5ec0f4ad..54ad2f4cf 100644 --- a/services/activitylog/pkg/service/response.go +++ b/services/activitylog/pkg/service/response.go @@ -87,6 +87,35 @@ func WithOldResource(ref *provider.Reference) ActivityOption { } } +// 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 { diff --git a/services/activitylog/pkg/service/service.go b/services/activitylog/pkg/service/service.go index 32429778e..7b4cfd3a4 100644 --- a/services/activitylog/pkg/service/service.go +++ b/services/activitylog/pkg/service/service.go @@ -291,3 +291,9 @@ func toRef(r *provider.ResourceId) *provider.Reference { ResourceId: r, } } + +func toSpace(r *provider.Reference) *provider.StorageSpaceId { + return &provider.StorageSpaceId{ + OpaqueId: storagespace.FormatStorageID(r.GetResourceId().GetStorageId(), r.GetResourceId().GetSpaceId()), + } +} From 76ca53bd31e249df7d9348bf046e00c91b7ea48f Mon Sep 17 00:00:00 2001 From: jkoberg Date: Fri, 21 Jun 2024 15:15:36 +0200 Subject: [PATCH 12/13] fix(activitylog): dont use service user context Signed-off-by: jkoberg --- .drone.star | 1 + services/activitylog/pkg/service/http.go | 30 +++++++++++--------- services/activitylog/pkg/service/response.go | 7 +---- 3 files changed, 19 insertions(+), 19 deletions(-) diff --git a/.drone.star b/.drone.star index 292484819..92eace14e 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/services/activitylog/pkg/service/http.go b/services/activitylog/pkg/service/http.go index 9dea4f27a..1fbd2fb53 100644 --- a/services/activitylog/pkg/service/http.go +++ b/services/activitylog/pkg/service/http.go @@ -15,6 +15,7 @@ import ( "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" @@ -41,7 +42,10 @@ func (s *ActivitylogService) ServeHTTP(w http.ResponseWriter, r *http.Request) { // HandleGetItemActivities handles the request to get the activities of an item. func (s *ActivitylogService) HandleGetItemActivities(w http.ResponseWriter, r *http.Request) { - activeUser, ok := revactx.ContextGetUser(r.Context()) + 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 @@ -104,53 +108,53 @@ func (s *ActivitylogService) HandleGetItemActivities(w http.ResponseWriter, r *h case events.UploadReady: message = MessageResourceCreated ts = utils.TSToTime(ev.Timestamp) - vars, err = s.GetVars(WithResource(ev.FileRef, true), WithUser(ev.ExecutingUser.GetId(), ev.ExecutingUser.GetDisplayName())) + 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(WithResource(ev.Ref, true), WithUser(ev.Executant, "")) + 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(WithResource(ev.Ref, true), WithUser(ev.Executant, "")) + 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(WithTrashedResource(ev.Ref, ev.ID), WithUser(ev.Executant, ""), WithSpace(toSpace(ev.Ref))) + 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(WithResource(ev.Ref, false), WithOldResource(ev.OldReference), WithUser(ev.Executant, "")) + vars, err = s.GetVars(ctx, WithResource(ev.Ref, false), WithOldResource(ev.OldReference), WithUser(ev.Executant, "")) case false: message = MessageResourceMoved - vars, err = s.GetVars(WithResource(ev.Ref, true), WithUser(ev.Executant, "")) + 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(WithResource(toRef(ev.ItemID), false), WithUser(ev.Executant, ""), WithSharee(ev.GranteeUserID, ev.GranteeGroupID)) + 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(WithResource(toRef(ev.ItemID), false), WithUser(ev.Executant, ""), WithSharee(ev.GranteeUserID, ev.GranteeGroupID)) + 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(WithResource(toRef(ev.ItemID), false), WithUser(ev.Executant, "")) + 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(WithResource(toRef(ev.ItemID), false), WithUser(ev.Executant, "")) + 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(WithSpace(ev.ID), WithUser(ev.Executant, ""), WithSharee(ev.GranteeUserID, ev.GranteeGroupID)) + 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(WithSpace(ev.ID), WithUser(ev.Executant, ""), WithSharee(ev.GranteeUserID, ev.GranteeGroupID)) + vars, err = s.GetVars(ctx, WithSpace(ev.ID), WithUser(ev.Executant, ""), WithSharee(ev.GranteeUserID, ev.GranteeGroupID)) } if err != nil { diff --git a/services/activitylog/pkg/service/response.go b/services/activitylog/pkg/service/response.go index 54ad2f4cf..9a9d710bc 100644 --- a/services/activitylog/pkg/service/response.go +++ b/services/activitylog/pkg/service/response.go @@ -200,17 +200,12 @@ func NewActivity(message string, ts time.Time, eventID string, vars map[string]i } // GetVars calls other service to gather the required data for the activity variables -func (s *ActivitylogService) GetVars(opts ...ActivityOption) (map[string]interface{}, error) { +func (s *ActivitylogService) GetVars(ctx context.Context, opts ...ActivityOption) (map[string]interface{}, error) { gwc, err := s.gws.Next() if err != nil { return nil, err } - ctx, err := utils.GetServiceUserContext(s.cfg.ServiceAccount.ServiceAccountID, gwc, s.cfg.ServiceAccount.ServiceAccountSecret) - if err != nil { - return nil, err - } - vars := make(map[string]interface{}) for _, opt := range opts { if err := opt(ctx, gwc, vars); err != nil { From 57a30ecd2a728ecefc3117e70dbc7a674b1ff4cb Mon Sep 17 00:00:00 2001 From: jkoberg Date: Mon, 24 Jun 2024 14:55:29 +0200 Subject: [PATCH 13/13] feat(activitylog): add a mutex to the store Signed-off-by: jkoberg --- .../6.0.0_2024-06-19/activity-service.md | 1 - changelog/unreleased/activity-api.md | 5 ++ services/activitylog/pkg/config/config.go | 2 +- services/activitylog/pkg/service/service.go | 50 ++++++++++++------- 4 files changed, 39 insertions(+), 19 deletions(-) create mode 100644 changelog/unreleased/activity-api.md diff --git a/changelog/6.0.0_2024-06-19/activity-service.md b/changelog/6.0.0_2024-06-19/activity-service.md index 8f82f56fe..1058280a0 100644 --- a/changelog/6.0.0_2024-06-19/activity-service.md +++ b/changelog/6.0.0_2024-06-19/activity-service.md @@ -2,5 +2,4 @@ Enhancement: Activitylog Service Adds a new service `activitylog` which stores events (activities) per resource. This data can be retrieved by clients to show item activities -https://github.com/owncloud/ocis/pull/9360 https://github.com/owncloud/ocis/pull/9327 diff --git a/changelog/unreleased/activity-api.md b/changelog/unreleased/activity-api.md new file mode 100644 index 000000000..fd39c2be7 --- /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/services/activitylog/pkg/config/config.go b/services/activitylog/pkg/config/config.go index 580ca381a..7c84b1317 100644 --- a/services/activitylog/pkg/config/config.go +++ b/services/activitylog/pkg/config/config.go @@ -49,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"` } diff --git a/services/activitylog/pkg/service/service.go b/services/activitylog/pkg/service/service.go index 7b4cfd3a4..2653bd434 100644 --- a/services/activitylog/pkg/service/service.go +++ b/services/activitylog/pkg/service/service.go @@ -6,6 +6,7 @@ import ( "fmt" "path/filepath" "reflect" + "sync" "time" gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1" @@ -40,6 +41,7 @@ type ActivitylogService struct { mux *chi.Mux evHistory ehsvc.EventHistoryService valService settingssvc.ValueService + lock sync.RWMutex registeredEvents map[string]events.Unmarshaller } @@ -73,6 +75,7 @@ func New(opts ...Option) (*ActivitylogService, error) { mux: o.Mux, evHistory: o.HistoryClient, valService: o.ValueClient, + lock: sync.RWMutex{}, registeredEvents: make(map[string]events.Unmarshaller), } @@ -184,28 +187,18 @@ func (a *ActivitylogService) AddSpaceActivity(spaceID *provider.StorageSpaceId, // Activities returns the activities for the given resource func (a *ActivitylogService) Activities(rid *provider.ResourceId) ([]RawActivity, error) { - resourceID := storagespace.FormatResourceID(*rid) + a.lock.RLock() + defer a.lock.RUnlock() - records, err := a.store.Read(resourceID) - if err != nil && err != microstore.ErrNotFound { - return nil, fmt.Errorf("could not read activities: %w", err) - } - - if len(records) == 0 { - return []RawActivity{}, nil - } - - var activities []RawActivity - if err := json.Unmarshal(records[0].Value, &activities); err != nil { - return nil, fmt.Errorf("could not unmarshal activities: %w", err) - } - - return activities, nil + return a.activities(rid) } // RemoveActivities removes the activities from the given resource func (a *ActivitylogService) RemoveActivities(rid *provider.ResourceId, toDelete map[string]struct{}) error { - curActivities, err := a.Activities(rid) + a.lock.Lock() + defer a.lock.Unlock() + + curActivities, err := a.activities(rid) if err != nil { return err } @@ -228,6 +221,26 @@ func (a *ActivitylogService) RemoveActivities(rid *provider.ResourceId, toDelete }) } +func (a *ActivitylogService) activities(rid *provider.ResourceId) ([]RawActivity, error) { + resourceID := storagespace.FormatResourceID(*rid) + + records, err := a.store.Read(resourceID) + if err != nil && err != microstore.ErrNotFound { + return nil, fmt.Errorf("could not read activities: %w", err) + } + + if len(records) == 0 { + return []RawActivity{}, nil + } + + var activities []RawActivity + if err := json.Unmarshal(records[0].Value, &activities); err != nil { + return nil, fmt.Errorf("could not unmarshal activities: %w", err) + } + + return activities, nil +} + // note: getResource is abstracted to allow unit testing, in general this will just be utils.GetResource func (a *ActivitylogService) addActivity(initRef *provider.Reference, eventID string, timestamp time.Time, getResource func(*provider.Reference) (*provider.ResourceInfo, error)) error { var ( @@ -256,6 +269,9 @@ func (a *ActivitylogService) addActivity(initRef *provider.Reference, eventID st } 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