From 4b5dca0a13cf62b6e4ffba998a1c30e7e120a3c4 Mon Sep 17 00:00:00 2001 From: jkoberg Date: Thu, 20 Jun 2024 15:24:53 +0200 Subject: [PATCH] feat(activitylog): finalize translation strings Signed-off-by: jkoberg --- services/activitylog/pkg/command/server.go | 7 - services/activitylog/pkg/service/http.go | 72 +++++--- services/activitylog/pkg/service/response.go | 184 ++++++++++++++----- 3 files changed, 182 insertions(+), 81 deletions(-) diff --git a/services/activitylog/pkg/command/server.go b/services/activitylog/pkg/command/server.go index 50e4a05d6f..4ed99d10bc 100644 --- a/services/activitylog/pkg/command/server.go +++ b/services/activitylog/pkg/command/server.go @@ -33,20 +33,13 @@ var _registeredEvents = []events.Unmarshaller{ events.FileTouched{}, events.ContainerCreated{}, events.ItemTrashed{}, - events.ItemPurged{}, events.ItemMoved{}, events.ShareCreated{}, - events.ShareUpdated{}, events.ShareRemoved{}, events.LinkCreated{}, - events.LinkUpdated{}, events.LinkRemoved{}, events.SpaceShared{}, - events.SpaceShareUpdated{}, events.SpaceUnshared{}, - - // TODO: file downloaded only for public links. How to do this? - events.FileDownloaded{}, } // Server is the entrypoint for the server command. diff --git a/services/activitylog/pkg/service/http.go b/services/activitylog/pkg/service/http.go index 73aeda0ebf..91554b11df 100644 --- a/services/activitylog/pkg/service/http.go +++ b/services/activitylog/pkg/service/http.go @@ -5,15 +5,16 @@ import ( "encoding/json" "errors" "net/http" + "path/filepath" "strconv" "strings" + "time" provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" revactx "github.com/cs3org/reva/v2/pkg/ctx" "github.com/cs3org/reva/v2/pkg/events" "github.com/cs3org/reva/v2/pkg/storagespace" "github.com/cs3org/reva/v2/pkg/utils" - libregraph "github.com/owncloud/libre-graph-api-go" "github.com/owncloud/ocis/v2/ocis-pkg/ast" "github.com/owncloud/ocis/v2/ocis-pkg/kql" @@ -92,9 +93,8 @@ func (s *ActivitylogService) HandleGetItemActivities(w http.ResponseWriter, r *h var ( message string - res Resource - act Actor - ts libregraph.ActivityTimes + ts time.Time + vars map[string]interface{} ) switch ev := s.unwrapEvent(e).(type) { @@ -103,49 +103,54 @@ func (s *ActivitylogService) HandleGetItemActivities(w http.ResponseWriter, r *h continue case events.UploadReady: message = MessageResourceCreated - res, act, ts, err = s.ResponseData(ev.FileRef, ev.ExecutingUser.GetId(), ev.ExecutingUser.GetDisplayName(), utils.TSToTime(ev.Timestamp)) + ts = utils.TSToTime(ev.Timestamp) + vars, err = s.GetVars(WithResource(ev.FileRef, true), WithUser(ev.ExecutingUser.GetId(), ev.ExecutingUser.GetDisplayName())) case events.FileTouched: message = MessageResourceCreated - res, act, ts, err = s.ResponseData(ev.Ref, ev.Executant, "", utils.TSToTime(ev.Timestamp)) + ts = utils.TSToTime(ev.Timestamp) + vars, err = s.GetVars(WithResource(ev.Ref, true), WithUser(ev.Executant, "")) case events.ContainerCreated: message = MessageResourceCreated - res, act, ts, err = s.ResponseData(ev.Ref, ev.Executant, "", utils.TSToTime(ev.Timestamp)) + ts = utils.TSToTime(ev.Timestamp) + vars, err = s.GetVars(WithResource(ev.Ref, true), WithUser(ev.Executant, "")) case events.ItemTrashed: message = MessageResourceTrashed - res, act, ts, err = s.ResponseData(ev.Ref, ev.Executant, "", utils.TSToTime(ev.Timestamp)) - case events.ItemPurged: - message = MessageResourcePurged - res, act, ts, err = s.ResponseData(ev.Ref, ev.Executant, "", utils.TSToTime(ev.Timestamp)) + ts = utils.TSToTime(ev.Timestamp) + vars, err = s.GetVars(WithResource(ev.Ref, true), WithUser(ev.Executant, "")) case events.ItemMoved: - message = MessageResourceMoved - res, act, ts, err = s.ResponseData(ev.Ref, ev.Executant, "", utils.TSToTime(ev.Timestamp)) + switch isRename(ev.OldReference, ev.Ref) { + case true: + message = MessageResourceRenamed + vars, err = s.GetVars(WithResource(ev.Ref, false), WithOldResource(ev.OldReference), WithUser(ev.Executant, "")) + case false: + message = MessageResourceMoved + vars, err = s.GetVars(WithResource(ev.Ref, true), WithUser(ev.Executant, "")) + } + ts = utils.TSToTime(ev.Timestamp) case events.ShareCreated: message = MessageShareCreated - res, act, ts, err = s.ResponseData(toRef(ev.ItemID), ev.Executant, "", utils.TSToTime(ev.CTime)) - case events.ShareUpdated: - message = MessageShareUpdated - res, act, ts, err = s.ResponseData(toRef(ev.ItemID), ev.Executant, "", utils.TSToTime(ev.MTime)) + ts = utils.TSToTime(ev.CTime) + vars, err = s.GetVars(WithResource(toRef(ev.ItemID), false), WithUser(ev.Executant, ""), WithSharee(ev.GranteeUserID, ev.GranteeGroupID)) case events.ShareRemoved: message = MessageShareDeleted - res, act, ts, err = s.ResponseData(toRef(ev.ItemID), ev.Executant, "", ev.Timestamp) + ts = ev.Timestamp + vars, err = s.GetVars(WithResource(toRef(ev.ItemID), false), WithUser(ev.Executant, ""), WithSharee(ev.GranteeUserID, ev.GranteeGroupID)) case events.LinkCreated: message = MessageLinkCreated - res, act, ts, err = s.ResponseData(toRef(ev.ItemID), ev.Executant, "", utils.TSToTime(ev.CTime)) - case events.LinkUpdated: - message = MessageLinkUpdated - res, act, ts, err = s.ResponseData(toRef(ev.ItemID), ev.Executant, "", utils.TSToTime(ev.CTime)) + ts = utils.TSToTime(ev.CTime) + vars, err = s.GetVars(WithResource(toRef(ev.ItemID), false), WithUser(ev.Executant, "")) case events.LinkRemoved: message = MessageLinkDeleted - res, act, ts, err = s.ResponseData(toRef(ev.ItemID), ev.Executant, "", utils.TSToTime(ev.Timestamp)) + ts = utils.TSToTime(ev.Timestamp) + vars, err = s.GetVars(WithResource(toRef(ev.ItemID), false), WithUser(ev.Executant, "")) case events.SpaceShared: message = MessageSpaceShared - res, act, ts, err = s.ResponseData(sToRef(ev.ID), ev.Executant, "", ev.Timestamp) - case events.SpaceShareUpdated: - message = MessageSpaceShareUpdated - res, act, ts, err = s.ResponseData(sToRef(ev.ID), ev.Executant, "", ev.Timestamp) + ts = ev.Timestamp + vars, err = s.GetVars(WithSpace(ev.ID), WithUser(ev.Executant, ""), WithSharee(ev.GranteeUserID, ev.GranteeGroupID)) case events.SpaceUnshared: message = MessageSpaceUnshared - res, act, ts, err = s.ResponseData(sToRef(ev.ID), ev.Executant, "", ev.Timestamp) + ts = ev.Timestamp + vars, err = s.GetVars(WithSpace(ev.ID), WithUser(ev.Executant, ""), WithSharee(ev.GranteeUserID, ev.GranteeGroupID)) } if err != nil { @@ -157,7 +162,7 @@ func (s *ActivitylogService) HandleGetItemActivities(w http.ResponseWriter, r *h loc := l10n.MustGetUserLocale(r.Context(), activeUser.GetId().GetOpaqueId(), r.Header.Get(l10n.HeaderAcceptLanguage), s.valService) t := l10n.NewTranslatorFromCommonConfig("en", _domain, "", _localeFS, _localeSubPath) - resp.Activities = append(resp.Activities, NewActivity(t.Translate(message, loc), res, act, ts, e.GetId())) + resp.Activities = append(resp.Activities, NewActivity(t.Translate(message, loc), ts, e.GetId(), vars)) } // delete activities in separate go routine @@ -278,3 +283,12 @@ func (s *ActivitylogService) getFilters(query string) (*provider.ResourceId, int } return &rid, limit, pref, postf, nil } + +// returns true if this is just a rename +func isRename(o, n *provider.Reference) bool { + // if resourceids are different we assume it is a move + if !utils.ResourceIDEqual(o.GetResourceId(), n.GetResourceId()) { + return false + } + return filepath.Base(o.GetPath()) != filepath.Base(n.GetPath()) +} diff --git a/services/activitylog/pkg/service/response.go b/services/activitylog/pkg/service/response.go index 9186d1d3e8..f5ec0f4ad2 100644 --- a/services/activitylog/pkg/service/response.go +++ b/services/activitylog/pkg/service/response.go @@ -1,9 +1,15 @@ package service import ( + "context" + "fmt" + "path/filepath" "time" + gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1" + group "github.com/cs3org/go-cs3apis/cs3/identity/group/v1beta1" user "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" + rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" "github.com/cs3org/reva/v2/pkg/storagespace" "github.com/cs3org/reva/v2/pkg/utils" @@ -14,19 +20,16 @@ import ( // Translations var ( - MessageResourceCreated = l10n.Template("{user} created {resource}") - MessageResourceTrashed = l10n.Template("{user} trashed {resource}") - MessageResourcePurged = l10n.Template("{user} purged {resource}") - MessageResourceMoved = l10n.Template("{user} moved {resource}") - MessageShareCreated = l10n.Template("{user} shared {resource}") - MessageShareUpdated = l10n.Template("{user} updated share of {resource}") - MessageShareDeleted = l10n.Template("{user} deleted share of {resource}") - MessageLinkCreated = l10n.Template("{user} created link to {resource}") - MessageLinkUpdated = l10n.Template("{user} updated link to {resource}") - MessageLinkDeleted = l10n.Template("{user} deleted link to {resource}") - MessageSpaceShared = l10n.Template("{user} shared space {resource}") - MessageSpaceShareUpdated = l10n.Template("{user} updated share of space {resource}") - MessageSpaceUnshared = l10n.Template("{user} unshared space {resource}") + MessageResourceCreated = l10n.Template("{user} added {resource} to {space}") + MessageResourceTrashed = l10n.Template("{user} deleted {resource} from {space}") + MessageResourceMoved = l10n.Template("{user} moved {resource} to {space}") + MessageResourceRenamed = l10n.Template("{user} renamed {oldResource} to {resource}") + MessageShareCreated = l10n.Template("{user} shared {resource} with {sharee}") + MessageShareDeleted = l10n.Template("{user} removed {sharee} from {resource}") + MessageLinkCreated = l10n.Template("{user} shared {resource} via link") + MessageLinkDeleted = l10n.Template("{user} removed link to {resource}") + MessageSpaceShared = l10n.Template("{user} added {sharee} as member of {space}") + MessageSpaceUnshared = l10n.Template("{user} removed {sharee} from {space}") ) // GetActivitiesResponse is the response on GET activities requests @@ -40,60 +43,151 @@ type Resource struct { Name string `json:"name"` } -// Actor represents the user who performed the Action +// Actor represents a user type Actor struct { ID string `json:"id"` DisplayName string `json:"displayName"` } +// ActivityOption allows setting variables for an activity +type ActivityOption func(context.Context, gateway.GatewayAPIClient, map[string]interface{}) error + +// WithResource sets the resource variable for an activity +func WithResource(ref *provider.Reference, addSpace bool) ActivityOption { + return func(ctx context.Context, gwc gateway.GatewayAPIClient, vars map[string]interface{}) error { + info, err := utils.GetResource(ctx, ref, gwc) + if err != nil { + return err + } + + vars["resource"] = Resource{ + ID: storagespace.FormatResourceID(*info.GetId()), + Name: info.GetName(), + } + + if addSpace { + vars["space"] = Resource{ + ID: info.GetSpace().GetId().GetOpaqueId(), + Name: info.GetSpace().GetName(), + } + } + + return nil + } +} + +// WithOldResource sets the oldResource variable for an activity +func WithOldResource(ref *provider.Reference) ActivityOption { + return func(_ context.Context, _ gateway.GatewayAPIClient, vars map[string]interface{}) error { + name := filepath.Base(ref.GetPath()) + vars["oldResource"] = Resource{ + Name: name, + } + return nil + } +} + +// WithUser sets the user variable for an Activity +func WithUser(uid *user.UserId, username string) ActivityOption { + return func(_ context.Context, gwc gateway.GatewayAPIClient, vars map[string]interface{}) error { + if username == "" { + u, err := utils.GetUser(uid, gwc) + if err != nil { + return err + } + username = u.GetUsername() + } + + vars["user"] = Actor{ + ID: uid.GetOpaqueId(), + DisplayName: username, + } + + return nil + } +} + +// WithSharee sets the sharee variable for an activity +func WithSharee(uid *user.UserId, gid *group.GroupId) ActivityOption { + return func(ctx context.Context, gwc gateway.GatewayAPIClient, vars map[string]interface{}) error { + switch { + case uid != nil: + u, err := utils.GetUser(uid, gwc) + if err != nil { + return err + } + + vars["sharee"] = Actor{ + ID: uid.GetOpaqueId(), + DisplayName: u.GetUsername(), + } + case gid != nil: + r, err := gwc.GetGroup(ctx, &group.GetGroupRequest{GroupId: gid}) + if err != nil { + return fmt.Errorf("error getting group: %w", err) + } + + if r.GetStatus().GetCode() != rpc.Code_CODE_OK { + return fmt.Errorf("error getting group: %s", r.GetStatus().GetMessage()) + } + + vars["sharee"] = Actor{ + ID: gid.GetOpaqueId(), + DisplayName: r.GetGroup().GetDisplayName(), + } + + } + + return nil + } +} + +// WithSpace sets the space variable for an activity +func WithSpace(spaceid *provider.StorageSpaceId) ActivityOption { + return func(ctx context.Context, gwc gateway.GatewayAPIClient, vars map[string]interface{}) error { + s, err := utils.GetSpace(ctx, spaceid.GetOpaqueId(), gwc) + if err != nil { + return err + } + vars["space"] = Resource{ + ID: s.GetId().GetOpaqueId(), + Name: s.GetName(), + } + + return nil + } +} + // NewActivity creates a new activity -func NewActivity(message string, res Resource, user Actor, ts libregraph.ActivityTimes, eventID string) libregraph.Activity { +func NewActivity(message string, ts time.Time, eventID string, vars map[string]interface{}) libregraph.Activity { return libregraph.Activity{ Id: eventID, - Times: ts, + Times: libregraph.ActivityTimes{RecordedTime: ts}, Template: libregraph.ActivityTemplate{ - Message: message, - Variables: map[string]interface{}{ - "resource": res, - "user": user, - }, + Message: message, + Variables: vars, }, } } -// ResponseData returns the relevant response data for the activity -func (s *ActivitylogService) ResponseData(ref *provider.Reference, uid *user.UserId, username string, ts time.Time) (Resource, Actor, libregraph.ActivityTimes, error) { +// GetVars calls other service to gather the required data for the activity variables +func (s *ActivitylogService) GetVars(opts ...ActivityOption) (map[string]interface{}, error) { gwc, err := s.gws.Next() if err != nil { - return Resource{}, Actor{}, libregraph.ActivityTimes{}, err + return nil, err } ctx, err := utils.GetServiceUserContext(s.cfg.ServiceAccount.ServiceAccountID, gwc, s.cfg.ServiceAccount.ServiceAccountSecret) if err != nil { - return Resource{}, Actor{}, libregraph.ActivityTimes{}, err + return nil, err } - info, err := utils.GetResource(ctx, ref, gwc) - if err != nil { - return Resource{}, Actor{}, libregraph.ActivityTimes{}, err - } - - if username == "" { - u, err := utils.GetUser(uid, gwc) - if err != nil { - return Resource{}, Actor{}, libregraph.ActivityTimes{}, err + vars := make(map[string]interface{}) + for _, opt := range opts { + if err := opt(ctx, gwc, vars); err != nil { + return nil, err } - username = u.GetUsername() } - return Resource{ - ID: storagespace.FormatResourceID(*info.Id), - Name: info.Path, - }, Actor{ - ID: uid.GetOpaqueId(), - DisplayName: username, - }, libregraph.ActivityTimes{ - RecordedTime: ts, - }, nil - + return vars, nil }