Merge pull request #9361 from kobergj/ActivityApi

Activity API
This commit is contained in:
kobergj
2024-06-25 09:18:55 +02:00
committed by GitHub
52 changed files with 1909 additions and 117 deletions

View File

@@ -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 == "":

View File

@@ -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

2
go.mod
View File

@@ -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

4
go.sum
View File

@@ -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=

View File

@@ -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

View File

@@ -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) {

View File

@@ -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

View File

@@ -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) {

View File

@@ -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) {

View File

@@ -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

View File

@@ -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) {

View File

@@ -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 {

View File

@@ -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 }

View File

@@ -1,6 +1,10 @@
SHELL := bash
NAME := activitylog
# Where to write the files generated by this makefile.
OUTPUT_DIR = ./pkg/service/l10n
TEMPLATE_FILE = ./pkg/service/l10n/activitylog.pot
include ../../.make/recursion.mk
############ tooling ############
@@ -29,6 +33,26 @@ ci-go-generate: # CI runs ci-node-generate automatically before this target
.PHONY: ci-node-generate
ci-node-generate:
############ translations ########
.PHONY: l10n-pull
l10n-pull:
cd $(OUTPUT_DIR) && tx pull --all --force --skip --minimum-perc=75
.PHONY: l10n-push
l10n-push:
cd $(OUTPUT_DIR) && tx push -s --skip
.PHONY: l10n-read
l10n-read: $(GO_XGETTEXT)
go-xgettext -o $(OUTPUT_DIR)/activitylog.pot --keyword=l10n.Template -s pkg/service/response.go
.PHONY: l10n-write
l10n-write:
.PHONY: l10n-clean
l10n-clean:
rm -f $(TEMPLATE_FILE);
############ licenses ############
.PHONY: ci-node-check-licenses
ci-node-check-licenses:

View File

@@ -14,13 +14,16 @@ import (
"github.com/owncloud/ocis/v2/ocis-pkg/handlers"
"github.com/owncloud/ocis/v2/ocis-pkg/registry"
"github.com/owncloud/ocis/v2/ocis-pkg/service/debug"
ogrpc "github.com/owncloud/ocis/v2/ocis-pkg/service/grpc"
"github.com/owncloud/ocis/v2/ocis-pkg/tracing"
"github.com/owncloud/ocis/v2/ocis-pkg/version"
ehsvc "github.com/owncloud/ocis/v2/protogen/gen/ocis/services/eventhistory/v0"
settingssvc "github.com/owncloud/ocis/v2/protogen/gen/ocis/services/settings/v0"
"github.com/owncloud/ocis/v2/services/activitylog/pkg/config"
"github.com/owncloud/ocis/v2/services/activitylog/pkg/config/parser"
"github.com/owncloud/ocis/v2/services/activitylog/pkg/logging"
"github.com/owncloud/ocis/v2/services/activitylog/pkg/metrics"
"github.com/owncloud/ocis/v2/services/activitylog/pkg/service"
"github.com/owncloud/ocis/v2/services/activitylog/pkg/server/http"
"github.com/urfave/cli/v2"
microstore "go-micro.dev/v4/store"
)
@@ -30,20 +33,13 @@ var _registeredEvents = []events.Unmarshaller{
events.FileTouched{},
events.ContainerCreated{},
events.ItemTrashed{},
events.ItemPurged{},
events.ItemMoved{},
events.ShareCreated{},
events.ShareUpdated{},
events.ShareRemoved{},
events.LinkCreated{},
events.LinkUpdated{},
events.LinkRemoved{},
events.SpaceShared{},
events.SpaceShareUpdated{},
events.SpaceUnshared{},
// TODO: file downloaded only for public links. How to do this?
events.FileDownloaded{},
}
// Server is the entrypoint for the server command.
@@ -109,15 +105,29 @@ func Server(cfg *config.Config) *cli.Command {
return fmt.Errorf("could not get reva client selector: %s", err)
}
grpcClient, err := ogrpc.NewClient(
append(ogrpc.GetClientOptions(cfg.GRPCClientTLS), ogrpc.WithTraceProvider(tracerProvider))...,
)
if err != nil {
return err
}
hClient := ehsvc.NewEventHistoryService("com.owncloud.api.eventhistory", grpcClient)
vClient := settingssvc.NewValueService("com.owncloud.api.settings", grpcClient)
{
svc, err := service.New(
service.Logger(logger),
service.Config(cfg),
service.TraceProvider(tracerProvider),
service.Stream(evStream),
service.RegisteredEvents(_registeredEvents),
service.Store(evStore),
service.GatewaySelector(gatewaySelector),
svc, err := http.Server(
http.Logger(logger),
http.Config(cfg),
http.Context(ctx), // NOTE: not passing this "option" leads to a panic in go-micro
http.TraceProvider(tracerProvider),
http.Stream(evStream),
http.RegisteredEvents(_registeredEvents),
http.Store(evStore),
http.GatewaySelector(gatewaySelector),
http.HistoryClient(hClient),
http.ValueClient(vClient),
http.RegisteredEvents(_registeredEvents),
)
if err != nil {

View File

@@ -23,6 +23,9 @@ type Config struct {
RevaGateway string `yaml:"reva_gateway" env:"OCIS_REVA_GATEWAY" desc:"CS3 gateway used to look up user metadata" introductionVersion:"5.0"`
GRPCClientTLS *shared.GRPCClientTLS `yaml:"grpc_client_tls"`
HTTP HTTP `yaml:"http"`
TokenManager *TokenManager `yaml:"token_manager"`
ServiceAccount ServiceAccount `yaml:"service_account"`
Context context.Context `yaml:"-"`
@@ -46,7 +49,7 @@ type Store struct {
Database string `yaml:"database" env:"ACTIVITYLOG_STORE_DATABASE" desc:"The database name the configured store should use." introductionVersion:"pre5.0"`
Table string `yaml:"table" env:"ACTIVITYLOG_STORE_TABLE" desc:"The database table the store should use." introductionVersion:"pre5.0"`
TTL time.Duration `yaml:"ttl" env:"OCIS_PERSISTENT_STORE_TTL;ACTIVITYLOG_STORE_TTL" desc:"Time to live for events in the store. See the Environment Variable Types description for more details." introductionVersion:"pre5.0"`
Size int `yaml:"size" env:"OCIS_PERSISTENT_STORE_SIZE;ACTIVITYLOG_STORE_SIZE" desc:"The maximum quantity of items in the store. Only applies when store type 'ocmem' is configured. Defaults to 512 which is derived from the ocmem package though not exclicitly set as default." introductionVersion:"pre5.0"`
Size int `yaml:"size" env:"OCIS_PERSISTENT_STORE_SIZE;ACTIVITYLOG_STORE_SIZE" desc:"The maximum quantity of items in the store. Only applies when store type 'ocmem' is configured. Defaults to 512 which is derived from the ocmem package though not explicitly set as default." introductionVersion:"pre5.0"`
AuthUsername string `yaml:"username" env:"OCIS_PERSISTENT_STORE_AUTH_USERNAME;ACTIVITYLOG_STORE_AUTH_USERNAME" desc:"The username to authenticate with the store. Only applies when store type 'nats-js-kv' is configured." introductionVersion:"5.0"`
AuthPassword string `yaml:"password" env:"OCIS_PERSISTENT_STORE_AUTH_PASSWORD;ACTIVITYLOG_STORE_AUTH_PASSWORD" desc:"The password to authenticate with the store. Only applies when store type 'nats-js-kv' is configured." introductionVersion:"5.0"`
}
@@ -56,3 +59,25 @@ type ServiceAccount struct {
ServiceAccountID string `yaml:"service_account_id" env:"OCIS_SERVICE_ACCOUNT_ID;ACTIVITYLOG_SERVICE_ACCOUNT_ID" desc:"The ID of the service account the service should use. See the 'auth-service' service description for more details." introductionVersion:"5.0"`
ServiceAccountSecret string `yaml:"service_account_secret" env:"OCIS_SERVICE_ACCOUNT_SECRET;ACTIVITYOG_SERVICE_ACCOUNT_SECRET" desc:"The service account secret." introductionVersion:"5.0"`
}
// CORS defines the available cors configuration.
type CORS struct {
AllowedOrigins []string `yaml:"allow_origins" env:"OCIS_CORS_ALLOW_ORIGINS;ACTIVITYLOG_CORS_ALLOW_ORIGINS" desc:"A list of allowed CORS origins. See following chapter for more details: *Access-Control-Allow-Origin* at https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin. See the Environment Variable Types description for more details." introductionVersion:"pre5.0"`
AllowedMethods []string `yaml:"allow_methods" env:"OCIS_CORS_ALLOW_METHODS;ACTIVITYLOG_CORS_ALLOW_METHODS" desc:"A list of allowed CORS methods. See following chapter for more details: *Access-Control-Request-Method* at https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Request-Method. See the Environment Variable Types description for more details." introductionVersion:"pre5.0"`
AllowedHeaders []string `yaml:"allow_headers" env:"OCIS_CORS_ALLOW_HEADERS;ACTIVITYLOG_CORS_ALLOW_HEADERS" desc:"A list of allowed CORS headers. See following chapter for more details: *Access-Control-Request-Headers* at https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Request-Headers. See the Environment Variable Types description for more details." introductionVersion:"pre5.0"`
AllowCredentials bool `yaml:"allow_credentials" env:"OCIS_CORS_ALLOW_CREDENTIALS;ACTIVITYLOG_CORS_ALLOW_CREDENTIALS" desc:"Allow credentials for CORS.See following chapter for more details: *Access-Control-Allow-Credentials* at https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Credentials." introductionVersion:"pre5.0"`
}
// HTTP defines the available http configuration.
type HTTP struct {
Addr string `yaml:"addr" env:"ACTIVITYLOG_HTTP_ADDR" desc:"The bind address of the HTTP service." introductionVersion:"pre5.0"`
Namespace string `yaml:"-"`
Root string `yaml:"root" env:"ACTIVITYLOG_HTTP_ROOT" desc:"Subdirectory that serves as the root for this HTTP service." introductionVersion:"pre5.0"`
CORS CORS `yaml:"cors"`
TLS shared.HTTPServiceTLS `yaml:"tls"`
}
// TokenManager is the config for using the reva token manager
type TokenManager struct {
JWTSecret string `yaml:"jwt_secret" env:"OCIS_JWT_SECRET;ACTIVITYLOG_JWT_SECRET" desc:"The secret to mint and validate jwt tokens." introductionVersion:"pre5.0"`
}

View File

@@ -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"`
}

View File

@@ -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

View File

@@ -2,8 +2,8 @@ package config
// Log defines the available log configuration.
type Log struct {
Level string `mapstructure:"level" env:"OCIS_LOG_LEVEL;CLIENTLOG_USERLOG_LOG_LEVEL" desc:"The log level. Valid values are: 'panic', 'fatal', 'error', 'warn', 'info', 'debug', 'trace'." introductionVersion:"5.0"`
Pretty bool `mapstructure:"pretty" env:"OCIS_LOG_PRETTY;CLIENTLOG_USERLOG_LOG_PRETTY" desc:"Activates pretty log output." introductionVersion:"5.0"`
Color bool `mapstructure:"color" env:"OCIS_LOG_COLOR;CLIENTLOG_USERLOG_LOG_COLOR" desc:"Activates colorized log output." introductionVersion:"5.0"`
File string `mapstructure:"file" env:"OCIS_LOG_FILE;CLIENTLOG_USERLOG_LOG_FILE" desc:"The path to the log file. Activates logging to this file if set." introductionVersion:"5.0"`
Level string `mapstructure:"level" env:"OCIS_LOG_LEVEL;ACTIVITYLOG_LOG_LEVEL" desc:"The log level. Valid values are: 'panic', 'fatal', 'error', 'warn', 'info', 'debug', 'trace'." introductionVersion:"5.0"`
Pretty bool `mapstructure:"pretty" env:"OCIS_LOG_PRETTY;ACTIVITYLOG_LOG_PRETTY" desc:"Activates pretty log output." introductionVersion:"5.0"`
Color bool `mapstructure:"color" env:"OCIS_LOG_COLOR;ACTIVITYLOG_LOG_COLOR" desc:"Activates colorized log output." introductionVersion:"5.0"`
File string `mapstructure:"file" env:"OCIS_LOG_FILE;ACTIVITYLOG_LOG_FILE" desc:"The path to the log file. Activates logging to this file if set." introductionVersion:"5.0"`
}

View File

@@ -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.

View File

@@ -0,0 +1,139 @@
package http
import (
"context"
gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1"
"github.com/cs3org/reva/v2/pkg/events"
"github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool"
"github.com/owncloud/ocis/v2/ocis-pkg/log"
ehsvc "github.com/owncloud/ocis/v2/protogen/gen/ocis/services/eventhistory/v0"
settingssvc "github.com/owncloud/ocis/v2/protogen/gen/ocis/services/settings/v0"
"github.com/owncloud/ocis/v2/services/activitylog/pkg/config"
"github.com/owncloud/ocis/v2/services/activitylog/pkg/metrics"
"github.com/urfave/cli/v2"
"go-micro.dev/v4/store"
"go.opentelemetry.io/otel/trace"
)
// Option defines a single option function.
type Option func(o *Options)
// Options defines the available options for this package.
type Options struct {
Logger log.Logger
Context context.Context
Config *config.Config
Metrics *metrics.Metrics
Flags []cli.Flag
Namespace string
Store store.Store
Stream events.Stream
GatewaySelector pool.Selectable[gateway.GatewayAPIClient]
TraceProvider trace.TracerProvider
HistoryClient ehsvc.EventHistoryService
ValueClient settingssvc.ValueService
RegisteredEvents []events.Unmarshaller
}
// newOptions initializes the available default options.
func newOptions(opts ...Option) Options {
opt := Options{}
for _, o := range opts {
o(&opt)
}
return opt
}
// Logger provides a function to set the logger option.
func Logger(val log.Logger) Option {
return func(o *Options) {
o.Logger = val
}
}
// Context provides a function to set the context option.
func Context(val context.Context) Option {
return func(o *Options) {
o.Context = val
}
}
// Config provides a function to set the config option.
func Config(val *config.Config) Option {
return func(o *Options) {
o.Config = val
}
}
// Metrics provides a function to set the metrics option.
func Metrics(val *metrics.Metrics) Option {
return func(o *Options) {
o.Metrics = val
}
}
// Flags provides a function to set the flags option.
func Flags(val []cli.Flag) Option {
return func(o *Options) {
o.Flags = append(o.Flags, val...)
}
}
// Namespace provides a function to set the Namespace option.
func Namespace(val string) Option {
return func(o *Options) {
o.Namespace = val
}
}
// Store provides a function to configure the store
func Store(store store.Store) Option {
return func(o *Options) {
o.Store = store
}
}
// Stream provides a function to configure the stream
func Stream(stream events.Stream) Option {
return func(o *Options) {
o.Stream = stream
}
}
// GatewaySelector provides a function to configure the gateway client selector
func GatewaySelector(gatewaySelector pool.Selectable[gateway.GatewayAPIClient]) Option {
return func(o *Options) {
o.GatewaySelector = gatewaySelector
}
}
// HistoryClient provides a function to configure the event history client
func HistoryClient(h ehsvc.EventHistoryService) Option {
return func(o *Options) {
o.HistoryClient = h
}
}
// RegisteredEvents provides a function to register events
func RegisteredEvents(evs []events.Unmarshaller) Option {
return func(o *Options) {
o.RegisteredEvents = evs
}
}
// TraceProvider provides a function to set the TracerProvider option
func TraceProvider(val trace.TracerProvider) Option {
return func(o *Options) {
o.TraceProvider = val
}
}
// ValueClient provides a function to set the ValueClient options
func ValueClient(val settingssvc.ValueService) Option {
return func(o *Options) {
o.ValueClient = val
}
}

View File

@@ -0,0 +1,101 @@
package http
import (
"fmt"
stdhttp "net/http"
"github.com/go-chi/chi/v5"
chimiddleware "github.com/go-chi/chi/v5/middleware"
"github.com/owncloud/ocis/v2/ocis-pkg/account"
"github.com/owncloud/ocis/v2/ocis-pkg/cors"
"github.com/owncloud/ocis/v2/ocis-pkg/middleware"
"github.com/owncloud/ocis/v2/ocis-pkg/service/http"
"github.com/owncloud/ocis/v2/ocis-pkg/tracing"
"github.com/owncloud/ocis/v2/ocis-pkg/version"
svc "github.com/owncloud/ocis/v2/services/activitylog/pkg/service"
"github.com/riandyrn/otelchi"
"go-micro.dev/v4"
)
// Service is the service interface
type Service interface{}
// Server initializes the http service and server.
func Server(opts ...Option) (http.Service, error) {
options := newOptions(opts...)
service, err := http.NewService(
http.TLSConfig(options.Config.HTTP.TLS),
http.Logger(options.Logger),
http.Namespace(options.Config.HTTP.Namespace),
http.Name(options.Config.Service.Name),
http.Version(version.GetString()),
http.Address(options.Config.HTTP.Addr),
http.Context(options.Context),
http.Flags(options.Flags...),
http.TraceProvider(options.TraceProvider),
)
if err != nil {
options.Logger.Error().
Err(err).
Msg("Error initializing http service")
return http.Service{}, fmt.Errorf("could not initialize http service: %w", err)
}
middlewares := []func(stdhttp.Handler) stdhttp.Handler{
chimiddleware.RequestID,
middleware.Version(
options.Config.Service.Name,
version.GetString(),
),
middleware.Logger(
options.Logger,
),
middleware.ExtractAccountUUID(
account.Logger(options.Logger),
account.JWTSecret(options.Config.TokenManager.JWTSecret),
),
middleware.Cors(
cors.Logger(options.Logger),
cors.AllowedOrigins(options.Config.HTTP.CORS.AllowedOrigins),
cors.AllowedMethods(options.Config.HTTP.CORS.AllowedMethods),
cors.AllowedHeaders(options.Config.HTTP.CORS.AllowedHeaders),
cors.AllowCredentials(options.Config.HTTP.CORS.AllowCredentials),
),
}
mux := chi.NewMux()
mux.Use(middlewares...)
mux.Use(
otelchi.Middleware(
"actitivylog",
otelchi.WithChiRoutes(mux),
otelchi.WithTracerProvider(options.TraceProvider),
otelchi.WithPropagators(tracing.GetPropagator()),
),
)
handle, err := svc.New(
svc.Logger(options.Logger),
svc.Stream(options.Stream),
svc.Mux(mux),
svc.Store(options.Store),
svc.Config(options.Config),
svc.GatewaySelector(options.GatewaySelector),
svc.TraceProvider(options.TraceProvider),
svc.HistoryClient(options.HistoryClient),
svc.ValueClient(options.ValueClient),
svc.RegisteredEvents(options.RegisteredEvents),
)
if err != nil {
return http.Service{}, err
}
if err := micro.RegisterHandler(service.Server(), handle); err != nil {
return http.Service{}, err
}
return service, nil
}

View File

@@ -0,0 +1,298 @@
package service
import (
"embed"
"encoding/json"
"errors"
"net/http"
"path/filepath"
"strconv"
"strings"
"time"
provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
revactx "github.com/cs3org/reva/v2/pkg/ctx"
"github.com/cs3org/reva/v2/pkg/events"
"github.com/cs3org/reva/v2/pkg/storagespace"
"github.com/cs3org/reva/v2/pkg/utils"
"google.golang.org/grpc/metadata"
"github.com/owncloud/ocis/v2/ocis-pkg/ast"
"github.com/owncloud/ocis/v2/ocis-pkg/kql"
"github.com/owncloud/ocis/v2/ocis-pkg/l10n"
ehmsg "github.com/owncloud/ocis/v2/protogen/gen/ocis/messages/eventhistory/v0"
ehsvc "github.com/owncloud/ocis/v2/protogen/gen/ocis/services/eventhistory/v0"
)
var (
//go:embed l10n/locale
_localeFS embed.FS
// subfolder where the translation files are stored
_localeSubPath = "l10n/locale"
// domain of the activitylog service (transifex)
_domain = "activitylog"
)
// ServeHTTP implements the http.Handler interface.
func (s *ActivitylogService) ServeHTTP(w http.ResponseWriter, r *http.Request) {
s.mux.ServeHTTP(w, r)
}
// HandleGetItemActivities handles the request to get the activities of an item.
func (s *ActivitylogService) HandleGetItemActivities(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
ctx = metadata.AppendToOutgoingContext(ctx, revactx.TokenHeader, r.Header.Get("X-Access-Token"))
activeUser, ok := revactx.ContextGetUser(ctx)
if !ok {
w.WriteHeader(http.StatusUnauthorized)
return
}
rid, limit, rawActivityAccepted, activityAccepted, err := s.getFilters(r.URL.Query().Get("kql"))
if err != nil {
s.log.Info().Str("query", r.URL.Query().Get("kql")).Err(err).Msg("error getting filters")
_, _ = w.Write([]byte(err.Error()))
w.WriteHeader(http.StatusBadRequest)
return
}
raw, err := s.Activities(rid)
if err != nil {
s.log.Error().Err(err).Msg("error getting activities")
w.WriteHeader(http.StatusInternalServerError)
return
}
ids := make([]string, 0, len(raw))
toDelete := make(map[string]struct{}, len(raw))
for _, a := range raw {
if !rawActivityAccepted(a) {
continue
}
ids = append(ids, a.EventID)
toDelete[a.EventID] = struct{}{}
}
evRes, err := s.evHistory.GetEvents(r.Context(), &ehsvc.GetEventsRequest{Ids: ids})
if err != nil {
s.log.Error().Err(err).Msg("error getting events")
w.WriteHeader(http.StatusInternalServerError)
return
}
var resp GetActivitiesResponse
for _, e := range evRes.GetEvents() {
delete(toDelete, e.GetId())
if limit != 0 && len(resp.Activities) >= limit {
continue
}
if !activityAccepted(e) {
continue
}
var (
message string
ts time.Time
vars map[string]interface{}
)
switch ev := s.unwrapEvent(e).(type) {
case nil:
// error already logged in unwrapEvent
continue
case events.UploadReady:
message = MessageResourceCreated
ts = utils.TSToTime(ev.Timestamp)
vars, err = s.GetVars(ctx, WithResource(ev.FileRef, true), WithUser(ev.ExecutingUser.GetId(), ev.ExecutingUser.GetDisplayName()))
case events.FileTouched:
message = MessageResourceCreated
ts = utils.TSToTime(ev.Timestamp)
vars, err = s.GetVars(ctx, WithResource(ev.Ref, true), WithUser(ev.Executant, ""))
case events.ContainerCreated:
message = MessageResourceCreated
ts = utils.TSToTime(ev.Timestamp)
vars, err = s.GetVars(ctx, WithResource(ev.Ref, true), WithUser(ev.Executant, ""))
case events.ItemTrashed:
message = MessageResourceTrashed
ts = utils.TSToTime(ev.Timestamp)
vars, err = s.GetVars(ctx, WithTrashedResource(ev.Ref, ev.ID), WithUser(ev.Executant, ""), WithSpace(toSpace(ev.Ref)))
case events.ItemMoved:
switch isRename(ev.OldReference, ev.Ref) {
case true:
message = MessageResourceRenamed
vars, err = s.GetVars(ctx, WithResource(ev.Ref, false), WithOldResource(ev.OldReference), WithUser(ev.Executant, ""))
case false:
message = MessageResourceMoved
vars, err = s.GetVars(ctx, WithResource(ev.Ref, true), WithUser(ev.Executant, ""))
}
ts = utils.TSToTime(ev.Timestamp)
case events.ShareCreated:
message = MessageShareCreated
ts = utils.TSToTime(ev.CTime)
vars, err = s.GetVars(ctx, WithResource(toRef(ev.ItemID), false), WithUser(ev.Executant, ""), WithSharee(ev.GranteeUserID, ev.GranteeGroupID))
case events.ShareRemoved:
message = MessageShareDeleted
ts = ev.Timestamp
vars, err = s.GetVars(ctx, WithResource(toRef(ev.ItemID), false), WithUser(ev.Executant, ""), WithSharee(ev.GranteeUserID, ev.GranteeGroupID))
case events.LinkCreated:
message = MessageLinkCreated
ts = utils.TSToTime(ev.CTime)
vars, err = s.GetVars(ctx, WithResource(toRef(ev.ItemID), false), WithUser(ev.Executant, ""))
case events.LinkRemoved:
message = MessageLinkDeleted
ts = utils.TSToTime(ev.Timestamp)
vars, err = s.GetVars(ctx, WithResource(toRef(ev.ItemID), false), WithUser(ev.Executant, ""))
case events.SpaceShared:
message = MessageSpaceShared
ts = ev.Timestamp
vars, err = s.GetVars(ctx, WithSpace(ev.ID), WithUser(ev.Executant, ""), WithSharee(ev.GranteeUserID, ev.GranteeGroupID))
case events.SpaceUnshared:
message = MessageSpaceUnshared
ts = ev.Timestamp
vars, err = s.GetVars(ctx, WithSpace(ev.ID), WithUser(ev.Executant, ""), WithSharee(ev.GranteeUserID, ev.GranteeGroupID))
}
if err != nil {
s.log.Error().Err(err).Msg("error getting response data")
continue
}
// FIXME: configurable default locale?
loc := l10n.MustGetUserLocale(r.Context(), activeUser.GetId().GetOpaqueId(), r.Header.Get(l10n.HeaderAcceptLanguage), s.valService)
t := l10n.NewTranslatorFromCommonConfig("en", _domain, "", _localeFS, _localeSubPath)
resp.Activities = append(resp.Activities, NewActivity(t.Translate(message, loc), ts, e.GetId(), vars))
}
// delete activities in separate go routine
if len(toDelete) > 0 {
go func() {
err := s.RemoveActivities(rid, toDelete)
if err != nil {
s.log.Error().Err(err).Msg("error removing activities")
}
}()
}
b, err := json.Marshal(resp)
if err != nil {
s.log.Error().Err(err).Msg("error marshalling activities")
w.WriteHeader(http.StatusInternalServerError)
return
}
if _, err := w.Write(b); err != nil {
s.log.Error().Err(err).Msg("error writing response")
w.WriteHeader(http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusOK)
}
func (s *ActivitylogService) unwrapEvent(e *ehmsg.Event) interface{} {
etype, ok := s.registeredEvents[e.GetType()]
if !ok {
s.log.Error().Str("eventid", e.GetId()).Str("eventtype", e.GetType()).Msg("event not registered")
return nil
}
einterface, err := etype.Unmarshal(e.GetEvent())
if err != nil {
s.log.Error().Str("eventid", e.GetId()).Str("eventtype", e.GetType()).Msg("failed to umarshal event")
return nil
}
return einterface
}
func (s *ActivitylogService) getFilters(query string) (*provider.ResourceId, int, func(RawActivity) bool, func(*ehmsg.Event) bool, error) {
qast, err := kql.Builder{}.Build(query)
if err != nil {
return nil, 0, nil, nil, err
}
prefilters := make([]func(RawActivity) bool, 0)
postfilters := make([]func(*ehmsg.Event) bool, 0)
var (
itemID string
limit int
)
for _, n := range qast.Nodes {
switch v := n.(type) {
case *ast.StringNode:
switch strings.ToLower(v.Key) {
case "itemid":
itemID = v.Value
case "depth":
depth, err := strconv.Atoi(v.Value)
if err != nil {
return nil, limit, nil, nil, err
}
prefilters = append(prefilters, func(a RawActivity) bool {
return a.Depth <= depth
})
case "limit":
l, err := strconv.Atoi(v.Value)
if err != nil {
return nil, limit, nil, nil, err
}
limit = l
}
case *ast.DateTimeNode:
switch v.Operator.Value {
case "<", "<=":
prefilters = append(prefilters, func(a RawActivity) bool {
return a.Timestamp.Before(v.Value)
})
case ">", ">=":
prefilters = append(prefilters, func(a RawActivity) bool {
return a.Timestamp.After(v.Value)
})
}
case *ast.OperatorNode:
if v.Value != "AND" {
return nil, limit, nil, nil, errors.New("only AND operator is supported")
}
}
}
rid, err := storagespace.ParseID(itemID)
if err != nil {
return nil, limit, nil, nil, err
}
pref := func(a RawActivity) bool {
for _, f := range prefilters {
if !f(a) {
return false
}
}
return true
}
postf := func(e *ehmsg.Event) bool {
for _, f := range postfilters {
if !f(e) {
return false
}
}
return true
}
return &rid, limit, pref, postf, nil
}
// returns true if this is just a rename
func isRename(o, n *provider.Reference) bool {
// if resourceids are different we assume it is a move
if !utils.ResourceIDEqual(o.GetResourceId(), n.GetResourceId()) {
return false
}
return filepath.Base(o.GetPath()) != filepath.Base(n.GetPath())
}

View File

@@ -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 <EMAIL@ADDRESS>, 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 <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\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 ""

View File

@@ -4,7 +4,10 @@ import (
gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1"
"github.com/cs3org/reva/v2/pkg/events"
"github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool"
"github.com/go-chi/chi/v5"
"github.com/owncloud/ocis/v2/ocis-pkg/log"
ehsvc "github.com/owncloud/ocis/v2/protogen/gen/ocis/services/eventhistory/v0"
settingssvc "github.com/owncloud/ocis/v2/protogen/gen/ocis/services/settings/v0"
"github.com/owncloud/ocis/v2/services/activitylog/pkg/config"
microstore "go-micro.dev/v4/store"
"go.opentelemetry.io/otel/trace"
@@ -22,6 +25,9 @@ type Options struct {
RegisteredEvents []events.Unmarshaller
Store microstore.Store
GatewaySelector pool.Selectable[gateway.GatewayAPIClient]
Mux *chi.Mux
HistoryClient ehsvc.EventHistoryService
ValueClient settingssvc.ValueService
}
// Logger configures a logger for the activitylog service
@@ -72,3 +78,24 @@ func GatewaySelector(gatewaySelector pool.Selectable[gateway.GatewayAPIClient])
o.GatewaySelector = gatewaySelector
}
}
// Mux defines the muxer for the service
func Mux(m *chi.Mux) Option {
return func(o *Options) {
o.Mux = m
}
}
// HistoryClient adds a grpc client for the eventhistory service
func HistoryClient(hc ehsvc.EventHistoryService) Option {
return func(o *Options) {
o.HistoryClient = hc
}
}
// ValueClient adds a grpc client for the value service
func ValueClient(vs settingssvc.ValueService) Option {
return func(o *Options) {
o.ValueClient = vs
}
}

View File

@@ -0,0 +1,217 @@
package service
import (
"context"
"fmt"
"path/filepath"
"time"
gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1"
group "github.com/cs3org/go-cs3apis/cs3/identity/group/v1beta1"
user "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1"
provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
"github.com/cs3org/reva/v2/pkg/storagespace"
"github.com/cs3org/reva/v2/pkg/utils"
libregraph "github.com/owncloud/libre-graph-api-go"
"github.com/owncloud/ocis/v2/ocis-pkg/l10n"
)
// Translations
var (
MessageResourceCreated = l10n.Template("{user} added {resource} to {space}")
MessageResourceTrashed = l10n.Template("{user} deleted {resource} from {space}")
MessageResourceMoved = l10n.Template("{user} moved {resource} to {space}")
MessageResourceRenamed = l10n.Template("{user} renamed {oldResource} to {resource}")
MessageShareCreated = l10n.Template("{user} shared {resource} with {sharee}")
MessageShareDeleted = l10n.Template("{user} removed {sharee} from {resource}")
MessageLinkCreated = l10n.Template("{user} shared {resource} via link")
MessageLinkDeleted = l10n.Template("{user} removed link to {resource}")
MessageSpaceShared = l10n.Template("{user} added {sharee} as member of {space}")
MessageSpaceUnshared = l10n.Template("{user} removed {sharee} from {space}")
)
// GetActivitiesResponse is the response on GET activities requests
type GetActivitiesResponse struct {
Activities []libregraph.Activity `json:"value"`
}
// Resource represents an item such as a file or folder
type Resource struct {
ID string `json:"id"`
Name string `json:"name"`
}
// Actor represents a user
type Actor struct {
ID string `json:"id"`
DisplayName string `json:"displayName"`
}
// ActivityOption allows setting variables for an activity
type ActivityOption func(context.Context, gateway.GatewayAPIClient, map[string]interface{}) error
// WithResource sets the resource variable for an activity
func WithResource(ref *provider.Reference, addSpace bool) ActivityOption {
return func(ctx context.Context, gwc gateway.GatewayAPIClient, vars map[string]interface{}) error {
info, err := utils.GetResource(ctx, ref, gwc)
if err != nil {
return err
}
vars["resource"] = Resource{
ID: storagespace.FormatResourceID(*info.GetId()),
Name: info.GetName(),
}
if addSpace {
vars["space"] = Resource{
ID: info.GetSpace().GetId().GetOpaqueId(),
Name: info.GetSpace().GetName(),
}
}
return nil
}
}
// WithOldResource sets the oldResource variable for an activity
func WithOldResource(ref *provider.Reference) ActivityOption {
return func(_ context.Context, _ gateway.GatewayAPIClient, vars map[string]interface{}) error {
name := filepath.Base(ref.GetPath())
vars["oldResource"] = Resource{
Name: name,
}
return nil
}
}
// WithTrashedResource sets the resource variable if the resource is trashed
func WithTrashedResource(ref *provider.Reference, rid *provider.ResourceId) ActivityOption {
return func(ctx context.Context, gwc gateway.GatewayAPIClient, vars map[string]interface{}) error {
resp, err := gwc.ListRecycle(ctx, &provider.ListRecycleRequest{
Ref: ref,
})
if err != nil {
return err
}
if resp.GetStatus().GetCode() != rpc.Code_CODE_OK {
return fmt.Errorf("error listing recycle: %s", resp.GetStatus().GetMessage())
}
for _, item := range resp.GetRecycleItems() {
if item.GetKey() == rid.GetOpaqueId() {
vars["resource"] = Resource{
ID: storagespace.FormatResourceID(*rid),
Name: filepath.Base(item.GetRef().GetPath()),
}
return nil
}
}
return nil
}
}
// WithUser sets the user variable for an Activity
func WithUser(uid *user.UserId, username string) ActivityOption {
return func(_ context.Context, gwc gateway.GatewayAPIClient, vars map[string]interface{}) error {
if username == "" {
u, err := utils.GetUser(uid, gwc)
if err != nil {
return err
}
username = u.GetUsername()
}
vars["user"] = Actor{
ID: uid.GetOpaqueId(),
DisplayName: username,
}
return nil
}
}
// WithSharee sets the sharee variable for an activity
func WithSharee(uid *user.UserId, gid *group.GroupId) ActivityOption {
return func(ctx context.Context, gwc gateway.GatewayAPIClient, vars map[string]interface{}) error {
switch {
case uid != nil:
u, err := utils.GetUser(uid, gwc)
if err != nil {
return err
}
vars["sharee"] = Actor{
ID: uid.GetOpaqueId(),
DisplayName: u.GetUsername(),
}
case gid != nil:
r, err := gwc.GetGroup(ctx, &group.GetGroupRequest{GroupId: gid})
if err != nil {
return fmt.Errorf("error getting group: %w", err)
}
if r.GetStatus().GetCode() != rpc.Code_CODE_OK {
return fmt.Errorf("error getting group: %s", r.GetStatus().GetMessage())
}
vars["sharee"] = Actor{
ID: gid.GetOpaqueId(),
DisplayName: r.GetGroup().GetDisplayName(),
}
}
return nil
}
}
// WithSpace sets the space variable for an activity
func WithSpace(spaceid *provider.StorageSpaceId) ActivityOption {
return func(ctx context.Context, gwc gateway.GatewayAPIClient, vars map[string]interface{}) error {
s, err := utils.GetSpace(ctx, spaceid.GetOpaqueId(), gwc)
if err != nil {
return err
}
vars["space"] = Resource{
ID: s.GetId().GetOpaqueId(),
Name: s.GetName(),
}
return nil
}
}
// NewActivity creates a new activity
func NewActivity(message string, ts time.Time, eventID string, vars map[string]interface{}) libregraph.Activity {
return libregraph.Activity{
Id: eventID,
Times: libregraph.ActivityTimes{RecordedTime: ts},
Template: libregraph.ActivityTemplate{
Message: message,
Variables: vars,
},
}
}
// GetVars calls other service to gather the required data for the activity variables
func (s *ActivitylogService) GetVars(ctx context.Context, opts ...ActivityOption) (map[string]interface{}, error) {
gwc, err := s.gws.Next()
if err != nil {
return nil, err
}
vars := make(map[string]interface{})
for _, opt := range opts {
if err := opt(ctx, gwc, vars); err != nil {
return nil, err
}
}
return vars, nil
}

View File

@@ -5,6 +5,8 @@ import (
"errors"
"fmt"
"path/filepath"
"reflect"
"sync"
"time"
gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1"
@@ -13,13 +15,17 @@ import (
"github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool"
"github.com/cs3org/reva/v2/pkg/storagespace"
"github.com/cs3org/reva/v2/pkg/utils"
"github.com/owncloud/ocis/v2/ocis-pkg/log"
"github.com/owncloud/ocis/v2/services/activitylog/pkg/config"
"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"
)
// Activity represents an activity
type Activity struct {
// RawActivity represents an activity as it is stored in the activitylog store
type RawActivity struct {
EventID string `json:"event_id"`
Depth int `json:"depth"`
Timestamp time.Time `json:"timestamp"`
@@ -27,11 +33,17 @@ type Activity struct {
// ActivitylogService logs events per resource
type ActivitylogService struct {
cfg *config.Config
log log.Logger
events <-chan events.Event
store microstore.Store
gws pool.Selectable[gateway.GatewayAPIClient]
cfg *config.Config
log log.Logger
events <-chan events.Event
store microstore.Store
gws pool.Selectable[gateway.GatewayAPIClient]
mux *chi.Mux
evHistory ehsvc.EventHistoryService
valService settingssvc.ValueService
lock sync.RWMutex
registeredEvents map[string]events.Unmarshaller
}
// New creates a new ActivitylogService
@@ -55,18 +67,32 @@ func New(opts ...Option) (*ActivitylogService, error) {
}
s := &ActivitylogService{
log: o.Logger,
cfg: o.Config,
events: ch,
store: o.Store,
gws: o.GatewaySelector,
log: o.Logger,
cfg: o.Config,
events: ch,
store: o.Store,
gws: o.GatewaySelector,
mux: o.Mux,
evHistory: o.HistoryClient,
valService: o.ValueClient,
lock: sync.RWMutex{},
registeredEvents: make(map[string]events.Unmarshaller),
}
s.mux.Get("/graph/v1beta1/extensions/org.libregraph/activities", s.HandleGetItemActivities)
for _, e := range o.RegisteredEvents {
typ := reflect.TypeOf(e)
s.registeredEvents[typ.String()] = e
}
go s.Run()
return s, nil
}
// Run runs the service
func (a *ActivitylogService) Run() error {
func (a *ActivitylogService) Run() {
for e := range a.events {
var err error
switch ev := e.Event.(type) {
@@ -78,35 +104,26 @@ func (a *ActivitylogService) Run() error {
err = a.AddActivity(ev.Ref, e.ID, utils.TSToTime(ev.Timestamp))
case events.ItemTrashed:
err = a.AddActivityTrashed(ev.ID, ev.Ref, e.ID, utils.TSToTime(ev.Timestamp))
case events.ItemPurged:
err = a.AddActivity(ev.Ref, e.ID, utils.TSToTime(ev.Timestamp))
case events.ItemMoved:
err = a.AddActivity(ev.Ref, e.ID, utils.TSToTime(ev.Timestamp))
case events.ShareCreated:
err = a.AddActivity(toRef(ev.ItemID), e.ID, utils.TSToTime(ev.CTime))
case events.ShareUpdated:
err = a.AddActivity(toRef(ev.ItemID), e.ID, utils.TSToTime(ev.MTime))
case events.ShareRemoved:
err = a.AddActivity(toRef(ev.ItemID), e.ID, ev.Timestamp)
case events.LinkCreated:
err = a.AddActivity(toRef(ev.ItemID), e.ID, utils.TSToTime(ev.CTime))
case events.LinkUpdated:
err = a.AddActivity(toRef(ev.ItemID), e.ID, utils.TSToTime(ev.CTime))
case events.LinkRemoved:
err = a.AddActivity(toRef(ev.ItemID), e.ID, utils.TSToTime(ev.Timestamp))
case events.SpaceShared:
err = a.AddActivity(sToRef(ev.ID), e.ID, ev.Timestamp)
case events.SpaceShareUpdated:
err = a.AddActivity(sToRef(ev.ID), e.ID, ev.Timestamp)
err = a.AddSpaceActivity(ev.ID, e.ID, ev.Timestamp)
case events.SpaceUnshared:
err = a.AddActivity(sToRef(ev.ID), e.ID, ev.Timestamp)
err = a.AddSpaceActivity(ev.ID, e.ID, ev.Timestamp)
}
if err != nil {
a.log.Error().Err(err).Interface("event", e).Msg("could not process event")
}
}
return nil
}
// AddActivity adds the activity to the given resource and all its parents
@@ -139,7 +156,7 @@ func (a *ActivitylogService) AddActivityTrashed(resourceID *provider.ResourceId,
}
// store activity on trashed item
if err := a.storeActivity(resourceID, eventID, 0, timestamp); err != nil {
if err := a.storeActivity(storagespace.FormatResourceID(*resourceID), eventID, 0, timestamp); err != nil {
return fmt.Errorf("could not store activity: %w", err)
}
@@ -154,8 +171,57 @@ func (a *ActivitylogService) AddActivityTrashed(resourceID *provider.ResourceId,
})
}
// AddSpaceActivity adds the activity to the given spaceroot
func (a *ActivitylogService) AddSpaceActivity(spaceID *provider.StorageSpaceId, eventID string, timestamp time.Time) error {
// spaceID is in format <providerid>$<spaceid>
// activitylog service uses format <providerid>$<spaceid>!<resourceid>
// lets do some converting, shall we?
rid, err := storagespace.ParseID(spaceID.GetOpaqueId())
if err != nil {
return fmt.Errorf("could not parse space id: %w", err)
}
rid.OpaqueId = rid.GetSpaceId()
return a.storeActivity(storagespace.FormatResourceID(rid), eventID, 0, timestamp)
}
// Activities returns the activities for the given resource
func (a *ActivitylogService) Activities(rid *provider.ResourceId) ([]Activity, error) {
func (a *ActivitylogService) Activities(rid *provider.ResourceId) ([]RawActivity, error) {
a.lock.RLock()
defer a.lock.RUnlock()
return a.activities(rid)
}
// RemoveActivities removes the activities from the given resource
func (a *ActivitylogService) RemoveActivities(rid *provider.ResourceId, toDelete map[string]struct{}) error {
a.lock.Lock()
defer a.lock.Unlock()
curActivities, err := a.activities(rid)
if err != nil {
return err
}
var acts []RawActivity
for _, a := range curActivities {
if _, ok := toDelete[a.EventID]; !ok {
acts = append(acts, a)
}
}
b, err := json.Marshal(acts)
if err != nil {
return err
}
return a.store.Write(&microstore.Record{
Key: storagespace.FormatResourceID(*rid),
Value: b,
})
}
func (a *ActivitylogService) activities(rid *provider.ResourceId) ([]RawActivity, error) {
resourceID := storagespace.FormatResourceID(*rid)
records, err := a.store.Read(resourceID)
@@ -164,10 +230,10 @@ func (a *ActivitylogService) Activities(rid *provider.ResourceId) ([]Activity, e
}
if len(records) == 0 {
return []Activity{}, nil
return []RawActivity{}, nil
}
var activities []Activity
var activities []RawActivity
if err := json.Unmarshal(records[0].Value, &activities); err != nil {
return nil, fmt.Errorf("could not unmarshal activities: %w", err)
}
@@ -189,7 +255,7 @@ func (a *ActivitylogService) addActivity(initRef *provider.Reference, eventID st
return fmt.Errorf("could not get resource info: %w", err)
}
if err := a.storeActivity(info.GetId(), eventID, depth, timestamp); err != nil {
if err := a.storeActivity(storagespace.FormatResourceID(*info.GetId()), eventID, depth, timestamp); err != nil {
return fmt.Errorf("could not store activity: %w", err)
}
@@ -202,19 +268,16 @@ func (a *ActivitylogService) addActivity(initRef *provider.Reference, eventID st
}
}
func (a *ActivitylogService) storeActivity(rid *provider.ResourceId, eventID string, depth int, timestamp time.Time) error {
if rid == nil {
return errors.New("resource id is required")
}
resourceID := storagespace.FormatResourceID(*rid)
func (a *ActivitylogService) storeActivity(resourceID string, eventID string, depth int, timestamp time.Time) error {
a.lock.Lock()
defer a.lock.Unlock()
records, err := a.store.Read(resourceID)
if err != nil && err != microstore.ErrNotFound {
return err
}
var activities []Activity
var activities []RawActivity
if len(records) > 0 {
if err := json.Unmarshal(records[0].Value, &activities); err != nil {
return err
@@ -222,7 +285,7 @@ func (a *ActivitylogService) storeActivity(rid *provider.ResourceId, eventID str
}
// TODO: max len check?
activities = append(activities, Activity{
activities = append(activities, RawActivity{
EventID: eventID,
Depth: depth,
Timestamp: timestamp,
@@ -245,11 +308,8 @@ func toRef(r *provider.ResourceId) *provider.Reference {
}
}
func sToRef(s *provider.StorageSpaceId) *provider.Reference {
return &provider.Reference{
ResourceId: &provider.ResourceId{
OpaqueId: s.GetOpaqueId(),
SpaceId: s.GetOpaqueId(),
},
func toSpace(r *provider.Reference) *provider.StorageSpaceId {
return &provider.StorageSpaceId{
OpaqueId: storagespace.FormatStorageID(r.GetResourceId().GetStorageId(), r.GetResourceId().GetSpaceId()),
}
}

View File

@@ -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:

View File

@@ -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{

View File

@@ -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")

View File

@@ -236,6 +236,10 @@ func DefaultPolicies() []config.Policy {
Endpoint: "/app/", // /app or /apps? ocdav only handles /apps
Service: "com.owncloud.web.frontend",
},
{
Endpoint: "/graph/v1beta1/extensions/org.libregraph/activities",
Service: "com.owncloud.web.activitylog",
},
{
Endpoint: "/graph/v1.0/invitations",
Service: "com.owncloud.web.invitations",

View File

@@ -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:

View File

@@ -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.

View File

@@ -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{

View File

@@ -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 {

View File

@@ -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.

View File

@@ -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 {

View File

@@ -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)

View File

@@ -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
}

View File

@@ -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)

View File

@@ -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)
}

View File

@@ -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)
}

View File

@@ -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)
}

View File

@@ -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)
}

2
vendor/modules.txt vendored
View File

@@ -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