introduce userlog service

Signed-off-by: jkoberg <jkoberg@owncloud.com>
This commit is contained in:
jkoberg
2023-02-21 14:25:21 +01:00
parent 8ffca8cae4
commit d56565555b
24 changed files with 845 additions and 0 deletions
+1
View File
@@ -44,6 +44,7 @@ OCIS_MODULES = \
services/storage-users \
services/store \
services/thumbnails \
services/userlog \
services/users \
services/web \
services/webdav\
+2
View File
@@ -31,6 +31,7 @@ import (
storageusers "github.com/owncloud/ocis/v2/services/storage-users/pkg/config"
store "github.com/owncloud/ocis/v2/services/store/pkg/config"
thumbnails "github.com/owncloud/ocis/v2/services/thumbnails/pkg/config"
userlog "github.com/owncloud/ocis/v2/services/userlog/pkg/config"
users "github.com/owncloud/ocis/v2/services/users/pkg/config"
web "github.com/owncloud/ocis/v2/services/web/pkg/config"
webdav "github.com/owncloud/ocis/v2/services/webdav/pkg/config"
@@ -106,6 +107,7 @@ type Config struct {
StorageUsers *storageusers.Config `yaml:"storage_users"`
Store *store.Config `yaml:"store"`
Thumbnails *thumbnails.Config `yaml:"thumbnails"`
Userlog *userlog.Config `yaml:"userlog"`
Users *users.Config `yaml:"users"`
Web *web.Config `yaml:"web"`
WebDAV *webdav.Config `yaml:"webdav"`
+2
View File
@@ -29,6 +29,7 @@ import (
storageusers "github.com/owncloud/ocis/v2/services/storage-users/pkg/config/defaults"
store "github.com/owncloud/ocis/v2/services/store/pkg/config/defaults"
thumbnails "github.com/owncloud/ocis/v2/services/thumbnails/pkg/config/defaults"
userlog "github.com/owncloud/ocis/v2/services/userlog/pkg/config/defaults"
users "github.com/owncloud/ocis/v2/services/users/pkg/config/defaults"
web "github.com/owncloud/ocis/v2/services/web/pkg/config/defaults"
webdav "github.com/owncloud/ocis/v2/services/webdav/pkg/config/defaults"
@@ -71,6 +72,7 @@ func DefaultConfig() *Config {
StorageUsers: storageusers.DefaultConfig(),
Store: store.DefaultConfig(),
Thumbnails: thumbnails.DefaultConfig(),
Userlog: userlog.DefaultConfig(),
Users: users.DefaultConfig(),
Web: web.DefaultConfig(),
WebDAV: webdav.DefaultConfig(),
+2
View File
@@ -46,6 +46,7 @@ import (
storageusers "github.com/owncloud/ocis/v2/services/storage-users/pkg/command"
store "github.com/owncloud/ocis/v2/services/store/pkg/command"
thumbnails "github.com/owncloud/ocis/v2/services/thumbnails/pkg/command"
userlog "github.com/owncloud/ocis/v2/services/userlog/pkg/command"
users "github.com/owncloud/ocis/v2/services/users/pkg/command"
web "github.com/owncloud/ocis/v2/services/web/pkg/command"
webdav "github.com/owncloud/ocis/v2/services/webdav/pkg/command"
@@ -131,6 +132,7 @@ func NewService(options ...Option) (*Service, error) {
s.ServicesRegistry[opts.Config.Search.Service.Name] = search.NewSutureService
s.ServicesRegistry[opts.Config.Postprocessing.Service.Name] = postprocessing.NewSutureService
s.ServicesRegistry[opts.Config.EventHistory.Service.Name] = eventhistory.NewSutureService
s.ServicesRegistry[opts.Config.Userlog.Service.Name] = userlog.NewSutureService
// populate delayed services
s.Delayed[opts.Config.Sharing.Service.Name] = sharing.NewSutureService
@@ -202,6 +202,10 @@ func DefaultPolicies() []config.Policy {
Endpoint: "/api/v0/settings",
Service: "com.owncloud.web.settings",
},
{
Endpoint: "/api/v0/activities",
Service: "com.owncloud.userlog.userlog",
},
},
},
}
+37
View File
@@ -0,0 +1,37 @@
SHELL := bash
NAME := userlog
include ../../.make/recursion.mk
############ tooling ############
ifneq (, $(shell command -v go 2> /dev/null)) # suppress `command not found warnings` for non go targets in CI
include ../../.bingo/Variables.mk
endif
############ go tooling ############
include ../../.make/go.mk
############ release ############
include ../../.make/release.mk
############ docs generate ############
include ../../.make/docs.mk
.PHONY: docs-generate
docs-generate: config-docs-generate
############ generate ############
include ../../.make/generate.mk
.PHONY: ci-go-generate
ci-go-generate: # CI runs ci-node-generate automatically before this target
.PHONY: ci-node-generate
ci-node-generate:
############ licenses ############
.PHONY: ci-node-check-licenses
ci-node-check-licenses:
.PHONY: ci-node-save-licenses
ci-node-save-licenses:
+11
View File
@@ -0,0 +1,11 @@
# Userlog service
The `userlog` service provides a way to configure which events a user wants to be informed about and an API to retrieve them.
## Configuring
The `userlog` service has hardcoded configuration for now.
## Retrieving
The `userlog` service provides an API to retrieve configured events.
+14
View File
@@ -0,0 +1,14 @@
package main
import (
"os"
"github.com/owncloud/ocis/v2/services/userlog/pkg/command"
"github.com/owncloud/ocis/v2/services/userlog/pkg/config/defaults"
)
func main() {
if err := command.Execute(defaults.DefaultConfig()); err != nil {
os.Exit(1)
}
}
+18
View File
@@ -0,0 +1,18 @@
package command
import (
"github.com/owncloud/ocis/v2/services/userlog/pkg/config"
"github.com/urfave/cli/v2"
)
// Health is the entrypoint for the health command.
func Health(cfg *config.Config) *cli.Command {
return &cli.Command{
Name: "health",
Usage: "Check health status",
Action: func(c *cli.Context) error {
// Not implemented
return nil
},
}
}
+59
View File
@@ -0,0 +1,59 @@
package command
import (
"context"
"os"
"github.com/owncloud/ocis/v2/ocis-pkg/clihelper"
ociscfg "github.com/owncloud/ocis/v2/ocis-pkg/config"
"github.com/owncloud/ocis/v2/services/userlog/pkg/config"
"github.com/thejerf/suture/v4"
"github.com/urfave/cli/v2"
)
// GetCommands provides all commands for this service
func GetCommands(cfg *config.Config) cli.Commands {
return []*cli.Command{
// start this service
Server(cfg),
// interaction with this service
// infos about this service
Health(cfg),
Version(cfg),
}
}
// Execute is the entry point for the userlog command.
func Execute(cfg *config.Config) error {
app := clihelper.DefaultApp(&cli.App{
Name: "userlog",
Usage: "starts userlog service",
Commands: GetCommands(cfg),
})
return app.Run(os.Args)
}
// SutureService allows for the userlog command to be embedded and supervised by a suture supervisor tree.
type SutureService struct {
cfg *config.Config
}
// NewSutureService creates a new userlog.SutureService
func NewSutureService(cfg *ociscfg.Config) suture.Service {
cfg.Notifications.Commons = cfg.Commons
return SutureService{
cfg: cfg.Userlog,
}
}
func (s SutureService) Serve(ctx context.Context) error {
s.cfg.Context = ctx
if err := Execute(s.cfg); err != nil {
return err
}
return nil
}
+101
View File
@@ -0,0 +1,101 @@
package command
import (
"context"
"fmt"
"github.com/cs3org/reva/v2/pkg/events"
"github.com/cs3org/reva/v2/pkg/events/stream"
"github.com/oklog/run"
"github.com/owncloud/ocis/v2/ocis-pkg/config/configlog"
ogrpc "github.com/owncloud/ocis/v2/ocis-pkg/service/grpc"
"github.com/owncloud/ocis/v2/ocis-pkg/version"
"github.com/owncloud/ocis/v2/services/userlog/pkg/config"
"github.com/owncloud/ocis/v2/services/userlog/pkg/config/parser"
"github.com/owncloud/ocis/v2/services/userlog/pkg/logging"
"github.com/owncloud/ocis/v2/services/userlog/pkg/metrics"
"github.com/owncloud/ocis/v2/services/userlog/pkg/server/http"
"github.com/urfave/cli/v2"
"go-micro.dev/v4/store"
)
// all events we care about
var _registeredEvents = []events.Unmarshaller{
events.UploadReady{},
}
// Server is the entrypoint for the server command.
func Server(cfg *config.Config) *cli.Command {
return &cli.Command{
Name: "server",
Usage: fmt.Sprintf("start the %s service without runtime (unsupervised mode)", cfg.Service.Name),
Category: "server",
Before: func(c *cli.Context) error {
return configlog.ReturnFatal(parser.ParseConfig(cfg))
},
Action: func(c *cli.Context) error {
logger := logging.Configure(cfg.Service.Name, cfg.Log)
err := ogrpc.Configure(ogrpc.GetClientOptions(cfg.GRPCClientTLS)...)
if err != nil {
return err
}
gr := run.Group{}
ctx, cancel := func() (context.Context, context.CancelFunc) {
if cfg.Context == nil {
return context.WithCancel(context.Background())
}
return context.WithCancel(cfg.Context)
}()
mtrcs := metrics.New()
defer cancel()
consumer, err := stream.NatsFromConfig(stream.NatsConfig(cfg.Events))
if err != nil {
return err
}
var st store.Store
switch cfg.Store.Type {
case "inmemory":
st = store.NewMemoryStore()
default:
return fmt.Errorf("unknown store '%s' configured", cfg.Store.Type)
}
mtrcs.BuildInfo.WithLabelValues(version.GetString()).Set(1)
{
server, err := http.Server(
http.Logger(logger),
http.Context(ctx),
http.Config(cfg),
http.Metrics(mtrcs),
http.Store(st),
http.Consumer(consumer),
http.RegisteredEvents(_registeredEvents),
)
if err != nil {
logger.Info().Err(err).Str("transport", "http").Msg("Failed to initialize server")
return err
}
gr.Add(func() error {
return server.Run()
}, func(err error) {
logger.Error().
Str("transport", "http").
Err(err).
Msg("Shutting down server")
cancel()
})
}
return gr.Run()
},
}
}
+19
View File
@@ -0,0 +1,19 @@
package command
import (
"github.com/owncloud/ocis/v2/services/userlog/pkg/config"
"github.com/urfave/cli/v2"
)
// Version prints the service versions of all running instances.
func Version(cfg *config.Config) *cli.Command {
return &cli.Command{
Name: "version",
Usage: "print the version of this binary and the running service instances",
Category: "info",
Action: func(c *cli.Context) error {
// not implemented
return nil
},
}
}
+58
View File
@@ -0,0 +1,58 @@
package config
import (
"context"
"time"
"github.com/owncloud/ocis/v2/ocis-pkg/shared"
)
// Config combines all available configuration parts.
type Config struct {
Commons *shared.Commons `yaml:"-"` // don't use this directly as configuration for a service
Service Service `yaml:"-"`
Log *Log `yaml:"log"`
Debug Debug `yaml:"debug"`
HTTP HTTP `yaml:"http"`
GRPCClientTLS *shared.GRPCClientTLS `yaml:"grpc_client_tls"`
Events Events `yaml:"events"`
Store Store `yaml:"store"`
Context context.Context `yaml:"-"`
}
// Store configures the store to use
type Store struct {
Type string `yaml:"type" env:"USERLOG_STORE_TYPE" desc:"The type of the store. Supported is inmemory"`
RecordExpiry time.Duration `yaml:"record_expiry" env:"USERLOG_RECORD_EXPIRY" desc:"time to life for events in the store"`
}
// Events combines the configuration options for the event bus.
type Events struct {
Endpoint string `yaml:"endpoint" env:"USERLOG_EVENTS_ENDPOINT" desc:"The address of the event system. The event system is the message queuing service. It is used as message broker for the microservice architecture."`
Cluster string `yaml:"cluster" env:"USERLOG_EVENTS_CLUSTER" desc:"The clusterID of the event system. The event system is the message queuing service. It is used as message broker for the microservice architecture. Mandatory when using NATS as event system."`
TLSInsecure bool `yaml:"tls_insecure" env:"OCIS_INSECURE;USERLOG_EVENTS_TLS_INSECURE" desc:"Whether to verify the server TLS certificates."`
TLSRootCACertificate string `yaml:"tls_root_ca_certificate" env:"USERLOG_EVENTS_TLS_ROOT_CA_CERTIFICATE" desc:"The root CA certificate used to validate the server's TLS certificate. If provided NOTIFICATIONS_EVENTS_TLS_INSECURE will be seen as false."`
EnableTLS bool `yaml:"enable_tls" env:"OCIS_EVENTS_ENABLE_TLS;USERLOG_EVENTS_ENABLE_TLS" desc:"Enable TLS for the connection to the events broker. The events broker is the ocis service which receives and delivers events between the services.."`
}
// CORS defines the available cors configuration.
type CORS struct {
AllowedOrigins []string `yaml:"allow_origins" env:"OCIS_CORS_ALLOW_ORIGINS;USERLOG_CORS_ALLOW_ORIGINS" desc:"A comma-separated 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"`
AllowedMethods []string `yaml:"allow_methods" env:"OCIS_CORS_ALLOW_METHODS;USERLOG_CORS_ALLOW_METHODS" desc:"A comma-separated 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"`
AllowedHeaders []string `yaml:"allow_headers" env:"OCIS_CORS_ALLOW_HEADERS;USERLOG_CORS_ALLOW_HEADERS" desc:"A comma-separated 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."`
AllowCredentials bool `yaml:"allow_credentials" env:"OCIS_CORS_ALLOW_CREDENTIALS;USERLOG_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."`
}
// HTTP defines the available http configuration.
type HTTP struct {
Addr string `yaml:"addr" env:"USERLOG_HTTP_ADDR" desc:"The bind address of the HTTP service."`
Namespace string `yaml:"-"`
Root string `yaml:"root" env:"USERLOG_HTTP_ROOT" desc:"Subdirectory that serves as the root for this HTTP service."`
CORS CORS `yaml:"cors"`
TLS shared.HTTPServiceTLS `yaml:"tls"`
}
+9
View File
@@ -0,0 +1,9 @@
package config
// Debug defines the available debug configuration.
type Debug struct {
Addr string `yaml:"addr" env:"USERLOG_DEBUG_ADDR" desc:"Bind address of the debug server, where metrics, health, config and debug endpoints will be exposed."`
Token string `yaml:"token" env:"USERLOG_DEBUG_TOKEN" desc:"Token to secure the metrics endpoint."`
Pprof bool `yaml:"pprof" env:"USERLOG_DEBUG_PPROF" desc:"Enables pprof, which can be used for profiling."`
Zpages bool `yaml:"zpages" env:"USERLOG_DEBUG_ZPAGES" desc:"Enables zpages, which can be used for collecting and viewing in-memory traces."`
}
@@ -0,0 +1,78 @@
package defaults
import (
"strings"
"github.com/owncloud/ocis/v2/ocis-pkg/shared"
"github.com/owncloud/ocis/v2/services/userlog/pkg/config"
)
// FullDefaultConfig returns the full default config
func FullDefaultConfig() *config.Config {
cfg := DefaultConfig()
EnsureDefaults(cfg)
Sanitize(cfg)
return cfg
}
// DefaultConfig return the default configuration
func DefaultConfig() *config.Config {
return &config.Config{
Service: config.Service{
Name: "userlog",
},
Events: config.Events{
Endpoint: "127.0.0.1:9233",
Cluster: "ocis-cluster",
EnableTLS: false,
},
Store: config.Store{
Type: "inmemory",
},
HTTP: config.HTTP{
Addr: "127.0.0.1:0",
Root: "/",
Namespace: "com.owncloud.userlog",
CORS: config.CORS{
AllowedOrigins: []string{"*"},
AllowedMethods: []string{"GET"},
AllowedHeaders: []string{"Authorization", "Origin", "Content-Type", "Accept", "X-Requested-With"},
AllowCredentials: true,
},
},
}
}
// EnsureDefaults ensures the config contains default values
func EnsureDefaults(cfg *config.Config) {
// provide with defaults for shared logging, since we need a valid destination address for "envdecode".
if cfg.Log == nil && cfg.Commons != nil && cfg.Commons.Log != nil {
cfg.Log = &config.Log{
Level: cfg.Commons.Log.Level,
Pretty: cfg.Commons.Log.Pretty,
Color: cfg.Commons.Log.Color,
File: cfg.Commons.Log.File,
}
} else if cfg.Log == nil {
cfg.Log = &config.Log{}
}
if cfg.GRPCClientTLS == nil {
cfg.GRPCClientTLS = &shared.GRPCClientTLS{}
if cfg.Commons != nil && cfg.Commons.GRPCClientTLS != nil {
cfg.GRPCClientTLS = cfg.Commons.GRPCClientTLS
}
}
if cfg.Commons != nil {
cfg.HTTP.TLS = cfg.Commons.HTTPServiceTLS
}
}
// Sanitize sanitizes the config
func Sanitize(cfg *config.Config) {
// sanitize config
if cfg.HTTP.Root != "/" {
cfg.HTTP.Root = strings.TrimSuffix(cfg.HTTP.Root, "/")
}
}
+9
View File
@@ -0,0 +1,9 @@
package config
// Log defines the available log configuration.
type Log struct {
Level string `mapstructure:"level" env:"OCIS_LOG_LEVEL;USERLOG_LOG_LEVEL" desc:"The log level. Valid values are: \"panic\", \"fatal\", \"error\", \"warn\", \"info\", \"debug\", \"trace\"."`
Pretty bool `mapstructure:"pretty" env:"OCIS_LOG_PRETTY;USERLOG_LOG_PRETTY" desc:"Activates pretty log output."`
Color bool `mapstructure:"color" env:"OCIS_LOG_COLOR;USERLOG_LOG_COLOR" desc:"Activates colorized log output."`
File string `mapstructure:"file" env:"OCIS_LOG_FILE;USERLOG_LOG_FILE" desc:"The path to the log file. Activates logging to this file if set."`
}
@@ -0,0 +1,38 @@
package parser
import (
"errors"
ociscfg "github.com/owncloud/ocis/v2/ocis-pkg/config"
"github.com/owncloud/ocis/v2/services/userlog/pkg/config"
"github.com/owncloud/ocis/v2/services/userlog/pkg/config/defaults"
"github.com/owncloud/ocis/v2/ocis-pkg/config/envdecode"
)
// ParseConfig loads configuration from known paths.
func ParseConfig(cfg *config.Config) error {
_, err := ociscfg.BindSourcesToStructs(cfg.Service.Name, cfg)
if err != nil {
return err
}
defaults.EnsureDefaults(cfg)
// load all env variables relevant to the config in the current context.
if err := envdecode.Decode(cfg); err != nil {
// no environment variable set for this config is an expected "error"
if !errors.Is(err, envdecode.ErrNoTargetFieldsAreSet) {
return err
}
}
defaults.Sanitize(cfg)
return Validate(cfg)
}
// Validate validates the config
func Validate(cfg *config.Config) error {
return nil
}
+6
View File
@@ -0,0 +1,6 @@
package config
// Service defines the available service configuration.
type Service struct {
Name string `yaml:"-"`
}
+17
View File
@@ -0,0 +1,17 @@
package logging
import (
"github.com/owncloud/ocis/v2/ocis-pkg/log"
"github.com/owncloud/ocis/v2/services/userlog/pkg/config"
)
// LoggerFromConfig initializes a service-specific logger instance.
func Configure(name string, cfg *config.Log) log.Logger {
return log.NewLogger(
log.Name(name),
log.Level(cfg.Level),
log.Pretty(cfg.Pretty),
log.Color(cfg.Color),
log.File(cfg.File),
)
}
+35
View File
@@ -0,0 +1,35 @@
package metrics
import "github.com/prometheus/client_golang/prometheus"
var (
// Namespace defines the namespace for the defines metrics.
Namespace = "ocis"
// Subsystem defines the subsystem for the defines metrics.
Subsystem = "userlog"
)
// Metrics defines the available metrics of this service.
type Metrics struct {
BuildInfo *prometheus.GaugeVec
}
// New initializes the available metrics.
func New() *Metrics {
m := &Metrics{
BuildInfo: prometheus.NewGaugeVec(prometheus.GaugeOpts{
Namespace: Namespace,
Subsystem: Subsystem,
Name: "build_info",
Help: "Build information",
}, []string{"version"}),
}
_ = prometheus.Register(
m.BuildInfo,
)
// TODO: implement metrics
return m
}
+102
View File
@@ -0,0 +1,102 @@
package http
import (
"context"
"github.com/cs3org/reva/v2/pkg/events"
"github.com/owncloud/ocis/v2/ocis-pkg/log"
"github.com/owncloud/ocis/v2/services/userlog/pkg/config"
"github.com/owncloud/ocis/v2/services/userlog/pkg/metrics"
"github.com/urfave/cli/v2"
"go-micro.dev/v4/store"
)
// 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
Consumer events.Consumer
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
}
}
// Consumer provides a function to configure the consumer
func Consumer(consumer events.Consumer) Option {
return func(o *Options) {
o.Consumer = consumer
}
}
// RegisteredEvents provides a function to register events
func RegisteredEvents(evs []events.Unmarshaller) Option {
return func(o *Options) {
o.RegisteredEvents = evs
}
}
@@ -0,0 +1,65 @@
package http
import (
"fmt"
"github.com/owncloud/ocis/v2/ocis-pkg/service/http"
"github.com/owncloud/ocis/v2/ocis-pkg/version"
svc "github.com/owncloud/ocis/v2/services/userlog/pkg/service"
"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("userlog"),
http.Version(version.GetString()),
http.Address(options.Config.HTTP.Addr),
http.Context(options.Context),
http.Flags(options.Flags...),
)
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{
//middleware.TraceContext,
//chimiddleware.RequestID,
//middleware.Version(
//"userlog",
//version.GetString(),
//),
//middleware.Logger(
//options.Logger,
//),
//}
handle, err := svc.NewUserlogService(options.Config, options.Consumer, options.Store, options.RegisteredEvents)
if err != nil {
return http.Service{}, err
}
{
//handle = svc.NewInstrument(handle, options.Metrics)
//handle = svc.NewLogging(handle, options.Logger)
//handle = svc.NewTracing(handle)
}
if err := micro.RegisterHandler(service.Server(), handle); err != nil {
return http.Service{}, err
}
return service, nil
}
+155
View File
@@ -0,0 +1,155 @@
package service
import (
"context"
"encoding/json"
"fmt"
"net/http"
"reflect"
"github.com/cs3org/reva/v2/pkg/events"
"github.com/owncloud/ocis/v2/ocis-pkg/service/grpc"
ehsvc "github.com/owncloud/ocis/v2/protogen/gen/ocis/services/eventhistory/v0"
"github.com/owncloud/ocis/v2/services/userlog/pkg/config"
"go-micro.dev/v4/store"
)
// Comment when you read this on review
var _adminid = "2502d8b8-a5e7-4ab3-b858-5aafae4a64a2"
// UserlogService is the service responsible for user activities
type UserlogService struct {
ch <-chan events.Event
store store.Store
cfg *config.Config
historyClient ehsvc.EventHistoryService
registeredEvents map[string]events.Unmarshaller
}
// NewUserlogService returns an EventHistory service
func NewUserlogService(cfg *config.Config, consumer events.Consumer, store store.Store, registeredEvents []events.Unmarshaller) (*UserlogService, error) {
if consumer == nil || store == nil {
return nil, fmt.Errorf("Need non nil consumer (%v) and store (%v) to work properly", consumer, store)
}
ch, err := events.Consume(consumer, "userlog", registeredEvents...)
if err != nil {
return nil, err
}
grpcClient := grpc.DefaultClient()
grpcClient.Options()
c := ehsvc.NewEventHistoryService("com.owncloud.api.eventhistory", grpcClient)
ul := &UserlogService{ch: ch, store: store, cfg: cfg, historyClient: c, registeredEvents: make(map[string]events.Unmarshaller)}
for _, e := range registeredEvents {
typ := reflect.TypeOf(e)
ul.registeredEvents[typ.String()] = e
}
go ul.MemorizeEvents()
return ul, nil
}
// MemorizeEvents stores eventIDs a user wants to receive
func (ul *UserlogService) MemorizeEvents() {
for event := range ul.ch {
switch event.Event.(type) {
default:
// for each event type we need to:
// I) find users eligible to receive the event
// II) filter users who want to receive the event
// III) store the eventID for each user
// TEMP TESTING CODE
if err := ul.addEventToUser(_adminid, event.ID); err != nil {
continue
}
}
}
}
// GetEvents allows to retrieve events from the eventhistory by userid
func (ul *UserlogService) GetEvents(ctx context.Context, userid string) ([]interface{}, error) {
rec, err := ul.store.Read(userid)
if err != nil {
return nil, err
}
if len(rec) == 0 {
// no events available
return []interface{}{}, nil
}
var eventIDs []string
if err := json.Unmarshal(rec[0].Value, &eventIDs); err != nil {
// this should never happen
return nil, err
}
resp, err := ul.historyClient.GetEvents(ctx, &ehsvc.GetEventsRequest{Ids: eventIDs})
if err != nil {
return nil, err
}
var events []interface{}
for _, e := range resp.Events {
ev, ok := ul.registeredEvents[e.Type]
if !ok {
// this should not happen but we handle it anyway
continue
}
event, err := ev.Unmarshal(e.Event)
if err != nil {
// this shouldn't happen either
continue
}
events = append(events, event)
}
return events, nil
}
func (ul *UserlogService) ServeHTTP(w http.ResponseWriter, r *http.Request) {
evs, err := ul.GetEvents(r.Context(), _adminid)
if err != nil {
return
}
// TODO: format response
b, _ := json.Marshal(evs)
w.Write(b)
}
func (ul *UserlogService) addEventToUser(userid string, eventid string) error {
recs, err := ul.store.Read(userid)
if err != nil && err != store.ErrNotFound {
return err
}
var ids []string
if len(recs) > 0 {
if err := json.Unmarshal(recs[0].Value, &ids); err != nil {
return err
}
}
ids = append(ids, eventid)
b, err := json.Marshal(ids)
if err != nil {
return err
}
return ul.store.Write(&store.Record{
Key: userid,
Value: b,
})
}
@@ -0,0 +1,3 @@
package service_test
// tests here