diff --git a/changelog/unreleased/users-preferred-language-email.md b/changelog/unreleased/users-preferred-language-email.md new file mode 100644 index 00000000000..9f2672de63b --- /dev/null +++ b/changelog/unreleased/users-preferred-language-email.md @@ -0,0 +1,5 @@ +Enhancement: Determine the users language to translate via Transifex + +Enhance userlog service with proper api and messages +https://github.com/owncloud/ocis/pull/6089 +https://github.com/owncloud/ocis/issues/6087 diff --git a/services/notifications/pkg/command/server.go b/services/notifications/pkg/command/server.go index d5eff832243..2bacb06619d 100644 --- a/services/notifications/pkg/command/server.go +++ b/services/notifications/pkg/command/server.go @@ -12,6 +12,8 @@ import ( "github.com/go-micro/plugins/v4/events/natsjs" "github.com/owncloud/ocis/v2/ocis-pkg/config/configlog" "github.com/owncloud/ocis/v2/ocis-pkg/crypto" + "github.com/owncloud/ocis/v2/ocis-pkg/service/grpc" + settingssvc "github.com/owncloud/ocis/v2/protogen/gen/ocis/services/settings/v0" "github.com/owncloud/ocis/v2/services/notifications/pkg/channels" "github.com/owncloud/ocis/v2/services/notifications/pkg/config" "github.com/owncloud/ocis/v2/services/notifications/pkg/config/parser" @@ -93,8 +95,8 @@ func Server(cfg *config.Config) *cli.Command { if err != nil { logger.Fatal().Err(err).Str("addr", cfg.Notifications.RevaGateway).Msg("could not get reva client") } - - svc := service.NewEventsNotifier(evts, channel, logger, gwclient, cfg.Notifications.MachineAuthAPIKey, cfg.Notifications.EmailTemplatePath, cfg.WebUIURL) + valueService := settingssvc.NewValueService("com.owncloud.api.settings", grpc.DefaultClient()) + svc := service.NewEventsNotifier(evts, channel, logger, gwclient, valueService, cfg.Notifications.MachineAuthAPIKey, cfg.Notifications.EmailTemplatePath, cfg.WebUIURL) return svc.Run() }, } diff --git a/services/notifications/pkg/service/service.go b/services/notifications/pkg/service/service.go index 714696e720f..e20b917c283 100644 --- a/services/notifications/pkg/service/service.go +++ b/services/notifications/pkg/service/service.go @@ -17,11 +17,17 @@ import ( provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" "github.com/cs3org/reva/v2/pkg/events" "github.com/owncloud/ocis/v2/ocis-pkg/log" + "github.com/owncloud/ocis/v2/ocis-pkg/middleware" + settingssvc "github.com/owncloud/ocis/v2/protogen/gen/ocis/services/settings/v0" "github.com/owncloud/ocis/v2/services/notifications/pkg/channels" "github.com/owncloud/ocis/v2/services/notifications/pkg/email" + "github.com/owncloud/ocis/v2/services/settings/pkg/store/defaults" + "go-micro.dev/v4/metadata" "google.golang.org/protobuf/types/known/fieldmaskpb" ) +var _defaultLocale = "en" + // Service should be named `Runner` type Service interface { Run() error @@ -33,13 +39,16 @@ func NewEventsNotifier( channel channels.Channel, logger log.Logger, gwClient gateway.GatewayAPIClient, + valueService settingssvc.ValueService, machineAuthAPIKey, emailTemplatePath, ocisURL string) Service { + return eventsNotifier{ logger: logger, channel: channel, events: events, signals: make(chan os.Signal, 1), gwClient: gwClient, + valueService: valueService, machineAuthAPIKey: machineAuthAPIKey, emailTemplatePath: emailTemplatePath, ocisURL: ocisURL, @@ -52,6 +61,7 @@ type eventsNotifier struct { events <-chan events.Event signals chan os.Signal gwClient gateway.GatewayAPIClient + valueService settingssvc.ValueService machineAuthAPIKey string emailTemplatePath string translationPath string @@ -87,52 +97,101 @@ func (s eventsNotifier) Run() error { } } -func (s eventsNotifier) render(template email.MessageTemplate, values map[string]interface{}) (string, string, error) { - // The locate have to come from the user setting - return email.RenderEmailTemplate(template, "en", s.emailTemplatePath, s.translationPath, values) +// recipient represent the already rendered message including the user id opaqueID +type recipient struct { + opaqueID string + subject string + msg string } -func (s eventsNotifier) send(ctx context.Context, u *user.UserId, g *group.GroupId, msg, subj, sender string) error { - if u != nil { - return s.channel.SendMessage(ctx, []string{u.GetOpaqueId()}, msg, subj, sender) +func (s eventsNotifier) render(ctx context.Context, template email.MessageTemplate, + granteeFieldName string, fields map[string]interface{}, granteeList []*user.UserId) ([]recipient, error) { + // Render the Email Template for each user + recipientList := make([]recipient, len(granteeList)) + for i, userID := range granteeList { + locale, err := s.getUserLang(ctx, userID) + if err != nil { + return nil, err + } + grantee, err := s.getUserName(ctx, userID) + if err != nil { + return nil, err + } + fields[granteeFieldName] = grantee + subj, msg, err := email.RenderEmailTemplate(template, locale, s.emailTemplatePath, s.translationPath, fields) + if err != nil { + return nil, err + } + recipientList[i] = recipient{opaqueID: userID.GetOpaqueId(), subject: subj, msg: msg} } + return recipientList, nil +} - if g != nil { - return s.channel.SendMessageToGroup(ctx, g, msg, subj, sender) +func (s eventsNotifier) send(ctx context.Context, recipientList []recipient, sender string) { + for _, r := range recipientList { + err := s.channel.SendMessage(ctx, []string{r.opaqueID}, r.msg, r.subject, sender) + if err != nil { + s.logger.Error().Err(err).Str("event", "SendEmail").Msg("failed to send a message") + } } - - return nil } -func (s eventsNotifier) getGranteeName(ctx context.Context, u *user.UserId, g *group.GroupId) (string, error) { +func (s eventsNotifier) getGranteeList(ctx context.Context, owner, u *user.UserId, g *group.GroupId) ([]*user.UserId, error) { switch { case u != nil: - r, err := s.gwClient.GetUser(ctx, &user.GetUserRequest{UserId: u}) - if err != nil { - return "", err - } - - if r.Status.Code != rpc.Code_CODE_OK { - return "", fmt.Errorf("unexpected status code from gateway client: %d", r.GetStatus().GetCode()) - } - - return r.GetUser().GetDisplayName(), nil + return []*user.UserId{u}, nil case g != nil: - r, err := s.gwClient.GetGroup(ctx, &group.GetGroupRequest{GroupId: g}) + res, err := s.gwClient.GetGroup(ctx, &group.GetGroupRequest{GroupId: g}) if err != nil { - return "", err + return nil, err } - - if r.GetStatus().GetCode() != rpc.Code_CODE_OK { - return "", fmt.Errorf("unexpected status code from gateway client: %d", r.GetStatus().GetCode()) + if res.Status.Code != rpc.Code_CODE_OK { + return nil, errors.New("could not get group") } - - return r.GetGroup().GetDisplayName(), nil + for i, userID := range res.GetGroup().GetMembers() { + // remove an executant from a list + if userID.GetOpaqueId() == owner.GetOpaqueId() { + res.Group.Members[i] = res.Group.Members[len(res.Group.Members)-1] + return res.Group.Members[:len(res.Group.Members)-1], nil + } + } + return res.Group.Members, nil default: - return "", errors.New("Need at least one non-nil grantee") + return nil, errors.New("need at least one non-nil grantee") } +} +func (s eventsNotifier) getUserName(ctx context.Context, u *user.UserId) (string, error) { + if u == nil { + return "", errors.New("need at least one non-nil grantee") + } + r, err := s.gwClient.GetUser(ctx, &user.GetUserRequest{UserId: u}) + if err != nil { + return "", err + } + if r.Status.Code != rpc.Code_CODE_OK { + return "", fmt.Errorf("unexpected status code from gateway client: %d", r.GetStatus().GetCode()) + } + return r.GetUser().GetDisplayName(), nil +} + +func (s eventsNotifier) getUserLang(ctx context.Context, u *user.UserId) (string, error) { + granteeCtx := metadata.Set(ctx, middleware.AccountID, u.OpaqueId) + if resp, err := s.valueService.GetValueByUniqueIdentifiers(granteeCtx, + &settingssvc.GetValueByUniqueIdentifiersRequest{ + AccountUuid: u.OpaqueId, + SettingId: defaults.SettingUUIDProfileLanguage, + }); err == nil { + if resp == nil { + return _defaultLocale, nil + } + val := resp.Value.GetValue().GetListValue().GetValues() + if len(val) > 0 && val[0] != nil { + return val[0].GetStringValue(), nil + } + } + return _defaultLocale, nil } func (s eventsNotifier) getResourceInfo(ctx context.Context, resourceID *provider.ResourceId, fieldmask *fieldmaskpb.FieldMask) (*provider.ResourceInfo, error) { diff --git a/services/notifications/pkg/service/service_test.go b/services/notifications/pkg/service/service_test.go index aad56181995..4f6017eded5 100644 --- a/services/notifications/pkg/service/service_test.go +++ b/services/notifications/pkg/service/service_test.go @@ -15,13 +15,19 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/owncloud/ocis/v2/ocis-pkg/log" + ogrpc "github.com/owncloud/ocis/v2/ocis-pkg/service/grpc" + "github.com/owncloud/ocis/v2/ocis-pkg/shared" + settingssvc "github.com/owncloud/ocis/v2/protogen/gen/ocis/services/settings/v0" + "github.com/owncloud/ocis/v2/services/graph/pkg/config/defaults" "github.com/owncloud/ocis/v2/services/notifications/pkg/service" "github.com/test-go/testify/mock" + "go-micro.dev/v4/client" ) var _ = Describe("Notifications", func() { var ( gwc *cs3mocks.GatewayAPIClient + vs *settingssvc.MockValueService sharer = &user.User{ Id: &user.UserId{ OpaqueId: "sharer", @@ -43,15 +49,23 @@ var _ = Describe("Notifications", func() { BeforeEach(func() { gwc = &cs3mocks.GatewayAPIClient{} - gwc.On("GetUser", mock.Anything, mock.Anything).Return(&user.GetUserResponse{Status: &rpc.Status{Code: rpc.Code_CODE_OK}, User: sharer}, nil) + gwc.On("GetUser", mock.Anything, mock.Anything).Return(&user.GetUserResponse{Status: &rpc.Status{Code: rpc.Code_CODE_OK}, User: sharer}, nil).Once() + gwc.On("GetUser", mock.Anything, mock.Anything).Return(&user.GetUserResponse{Status: &rpc.Status{Code: rpc.Code_CODE_OK}, User: sharee}, nil).Once() gwc.On("Authenticate", mock.Anything, mock.Anything).Return(&gateway.AuthenticateResponse{Status: &rpc.Status{Code: rpc.Code_CODE_OK}, User: sharer}, nil) gwc.On("Stat", mock.Anything, mock.Anything).Return(&provider.StatResponse{Status: &rpc.Status{Code: rpc.Code_CODE_OK}, Info: &provider.ResourceInfo{Name: "secrets of the board", Space: &provider.StorageSpace{Name: "secret space"}}}, nil) + vs = &settingssvc.MockValueService{} + vs.GetValueByUniqueIdentifiersFunc = func(ctx context.Context, req *settingssvc.GetValueByUniqueIdentifiersRequest, opts ...client.CallOption) (*settingssvc.GetValueResponse, error) { + return nil, nil + } }) DescribeTable("Sending notifications", func(tc testChannel, ev events.Event) { + cfg := defaults.FullDefaultConfig() + cfg.GRPCClientTLS = &shared.GRPCClientTLS{} + _ = ogrpc.Configure(ogrpc.GetClientOptions(cfg.GRPCClientTLS)...) ch := make(chan events.Event) - evts := service.NewEventsNotifier(ch, tc, log.NewLogger(), gwc, "", "", "") + evts := service.NewEventsNotifier(ch, tc, log.NewLogger(), gwc, vs, "", "", "") go evts.Run() ch <- ev @@ -66,7 +80,7 @@ var _ = Describe("Notifications", func() { Entry("Share Created", testChannel{ expectedReceipients: map[string]bool{sharee.GetId().GetOpaqueId(): true}, expectedSubject: "Dr. S. Harer shared 'secrets of the board' with you", - expectedMessage: `Hello Dr. S. Harer + expectedMessage: `Hello Eric Expireling Dr. S. Harer has shared "secrets of the board" with you. @@ -91,7 +105,7 @@ https://owncloud.com Entry("Share Expired", testChannel{ expectedReceipients: map[string]bool{sharee.GetId().GetOpaqueId(): true}, expectedSubject: "Share to 'secrets of the board' expired at 2023-04-17 16:42:00", - expectedMessage: `Hello Dr. S. Harer, + expectedMessage: `Hello Eric Expireling, Your share to secrets of the board has expired at 2023-04-17 16:42:00 @@ -116,7 +130,7 @@ https://owncloud.com Entry("Added to Space", testChannel{ expectedReceipients: map[string]bool{sharee.GetId().GetOpaqueId(): true}, expectedSubject: "Dr. S. Harer invited you to join secret space", - expectedMessage: `Hello Dr. S. Harer, + expectedMessage: `Hello Eric Expireling, Dr. S. Harer has invited you to join "secret space". @@ -141,7 +155,7 @@ https://owncloud.com Entry("Removed from Space", testChannel{ expectedReceipients: map[string]bool{sharee.GetId().GetOpaqueId(): true}, expectedSubject: "Dr. S. Harer removed you from secret space", - expectedMessage: `Hello Dr. S. Harer, + expectedMessage: `Hello Eric Expireling, Dr. S. Harer has removed you from "secret space". @@ -167,7 +181,7 @@ https://owncloud.com Entry("Space Expired", testChannel{ expectedReceipients: map[string]bool{sharee.GetId().GetOpaqueId(): true}, expectedSubject: "Membership of 'secret space' expired at 2023-04-17 16:42:00", - expectedMessage: `Hello Dr. S. Harer, + expectedMessage: `Hello Eric Expireling, Your membership of space secret space has expired at 2023-04-17 16:42:00 diff --git a/services/notifications/pkg/service/shares.go b/services/notifications/pkg/service/shares.go index 6451783bc2f..4c8d8444cf5 100644 --- a/services/notifications/pkg/service/shares.go +++ b/services/notifications/pkg/service/shares.go @@ -35,28 +35,25 @@ func (s eventsNotifier) handleShareCreated(e events.ShareCreated) { return } - shareGrantee, err := s.getGranteeName(ownerCtx, e.GranteeUserID, e.GranteeGroupID) + granteeList, err := s.getGranteeList(ownerCtx, owner.GetId(), e.GranteeUserID, e.GranteeGroupID) if err != nil { - s.logger.Error().Err(err).Str("event", "ShareCreated").Msg("Could not get grantee name") + s.logger.Error().Err(err).Str("event", "ShareCreated").Msg("Could not get grantee list") return } sharerDisplayName := owner.GetDisplayName() - subj, msg, err := s.render(email.ShareCreated, map[string]interface{}{ - "ShareGrantee": shareGrantee, - "ShareSharer": sharerDisplayName, - "ShareFolder": resourceInfo.Name, - "ShareLink": shareLink, - }) - + recipientList, err := s.render(ownerCtx, email.ShareCreated, + "ShareGrantee", + map[string]interface{}{ + "ShareSharer": sharerDisplayName, + "ShareFolder": resourceInfo.Name, + "ShareLink": shareLink, + }, granteeList) if err != nil { - s.logger.Error().Err(err).Str("event", "ShareCreated").Msg("Could not render E-Mail body template for shares") - } - - if err := s.send(ownerCtx, e.GranteeUserID, e.GranteeGroupID, msg, subj, sharerDisplayName); err != nil { - s.logger.Error().Err(err).Str("event", "ShareCreated").Msg("failed to send a message") + s.logger.Error().Err(err).Str("event", "ShareCreated").Msg("could not get render the email") + return } - + s.send(ownerCtx, recipientList, sharerDisplayName) } func (s eventsNotifier) handleShareExpired(e events.ShareExpired) { @@ -65,13 +62,13 @@ func (s eventsNotifier) handleShareExpired(e events.ShareExpired) { Str("itemid", e.ItemID.GetOpaqueId()). Logger() - ctx, owner, err := utils.Impersonate(e.ShareOwner, s.gwClient, s.machineAuthAPIKey) + ownerCtx, owner, err := utils.Impersonate(e.ShareOwner, s.gwClient, s.machineAuthAPIKey) if err != nil { logger.Error().Err(err).Msg("Could not impersonate sharer") return } - resourceInfo, err := s.getResourceInfo(ctx, e.ItemID, &fieldmaskpb.FieldMask{Paths: []string{"name"}}) + resourceInfo, err := s.getResourceInfo(ownerCtx, e.ItemID, &fieldmaskpb.FieldMask{Paths: []string{"name"}}) if err != nil { logger.Error(). Err(err). @@ -79,24 +76,21 @@ func (s eventsNotifier) handleShareExpired(e events.ShareExpired) { return } - shareGrantee, err := s.getGranteeName(ctx, e.GranteeUserID, e.GranteeGroupID) + granteeList, err := s.getGranteeList(ownerCtx, owner.GetId(), e.GranteeUserID, e.GranteeGroupID) if err != nil { - s.logger.Error().Err(err).Str("event", "ShareCreated").Msg("Could not get grantee name") + s.logger.Error().Err(err).Str("event", "ShareExpired").Msg("Could not get grantee name") return } - subj, msg, err := s.render(email.ShareExpired, map[string]interface{}{ - "ShareGrantee": shareGrantee, - "ShareFolder": resourceInfo.GetName(), - "ExpiredAt": e.ExpiredAt.Format("2006-01-02 15:04:05"), - }) - + recipientList, err := s.render(ownerCtx, email.ShareExpired, + "ShareGrantee", + map[string]interface{}{ + "ShareFolder": resourceInfo.GetName(), + "ExpiredAt": e.ExpiredAt.Format("2006-01-02 15:04:05"), + }, granteeList) if err != nil { - s.logger.Error().Err(err).Str("event", "ShareCreated").Msg("Could not render E-Mail body template for shares") - } - - if err := s.send(ctx, e.GranteeUserID, e.GranteeGroupID, msg, subj, owner.GetDisplayName()); err != nil { - s.logger.Error().Err(err).Str("event", "ShareCreated").Msg("failed to send a message") + s.logger.Error().Err(err).Str("event", "ShareExpired").Msg("could not get render the email") + return } - + s.send(ownerCtx, recipientList, owner.GetDisplayName()) } diff --git a/services/notifications/pkg/service/spaces.go b/services/notifications/pkg/service/spaces.go index bf7d7473428..78f6e59359d 100644 --- a/services/notifications/pkg/service/spaces.go +++ b/services/notifications/pkg/service/spaces.go @@ -13,7 +13,7 @@ func (s eventsNotifier) handleSpaceShared(e events.SpaceShared) { Str("itemid", e.ID.OpaqueId). Logger() - ownerCtx, owner, err := utils.Impersonate(e.Executant, s.gwClient, s.machineAuthAPIKey) + executantCtx, executant, err := utils.Impersonate(e.Executant, s.gwClient, s.machineAuthAPIKey) if err != nil { logger.Error(). Err(err). @@ -29,7 +29,7 @@ func (s eventsNotifier) handleSpaceShared(e events.SpaceShared) { return } - resourceInfo, err := s.getResourceInfo(ownerCtx, &resourceID, nil) + resourceInfo, err := s.getResourceInfo(executantCtx, &resourceID, nil) if err != nil { logger.Error(). Err(err). @@ -45,30 +45,28 @@ func (s eventsNotifier) handleSpaceShared(e events.SpaceShared) { return } - // Note: We're using the 'ownerCtx' (authenticated as the share owner) here for requesting + // Note: We're using the 'executantCtx' (authenticated as the share executant) here for requesting // the Grantees of the shares. Ideally the notfication service would use some kind of service // user for this. - spaceGrantee, err := s.getGranteeName(ownerCtx, e.GranteeUserID, e.GranteeGroupID) + spaceGrantee, err := s.getGranteeList(executantCtx, executant.GetId(), e.GranteeUserID, e.GranteeGroupID) if err != nil { - logger.Error().Err(err).Msg("Could not get grantee name") + logger.Error().Err(err).Str("event", "SpaceGrantee").Msg("Could not get grantee list") return } - sharerDisplayName := owner.GetDisplayName() - subj, msg, err := s.render(email.SharedSpace, map[string]interface{}{ - "SpaceGrantee": spaceGrantee, - "SpaceSharer": sharerDisplayName, - "SpaceName": resourceInfo.GetSpace().GetName(), - "ShareLink": shareLink, - }) + sharerDisplayName := executant.GetDisplayName() + recipientList, err := s.render(executantCtx, email.SharedSpace, + "SpaceGrantee", + map[string]interface{}{ + "SpaceSharer": sharerDisplayName, + "SpaceName": resourceInfo.GetSpace().GetName(), + "ShareLink": shareLink, + }, spaceGrantee) if err != nil { - logger.Error().Err(err).Msg("Could not render E-Mail template for spaces") + s.logger.Error().Err(err).Str("event", "SharedSpace").Msg("could not get render the email") return } - - if err := s.send(ownerCtx, e.GranteeUserID, e.GranteeGroupID, msg, subj, sharerDisplayName); err != nil { - logger.Error().Err(err).Msg("failed to send a message") - } + s.send(executantCtx, recipientList, sharerDisplayName) } func (s eventsNotifier) handleSpaceUnshared(e events.SpaceUnshared) { @@ -77,7 +75,7 @@ func (s eventsNotifier) handleSpaceUnshared(e events.SpaceUnshared) { Str("itemid", e.ID.OpaqueId). Logger() - ownerCtx, owner, err := utils.Impersonate(e.Executant, s.gwClient, s.machineAuthAPIKey) + executantCtx, executant, err := utils.Impersonate(e.Executant, s.gwClient, s.machineAuthAPIKey) if err != nil { logger.Error().Err(err).Msg("could not handle space unshared event") return @@ -91,7 +89,7 @@ func (s eventsNotifier) handleSpaceUnshared(e events.SpaceUnshared) { return } - resourceInfo, err := s.getResourceInfo(ownerCtx, &resourceID, nil) + resourceInfo, err := s.getResourceInfo(executantCtx, &resourceID, nil) if err != nil { logger.Error(). Err(err). @@ -107,31 +105,28 @@ func (s eventsNotifier) handleSpaceUnshared(e events.SpaceUnshared) { return } - // Note: We're using the 'ownerCtx' (authenticated as the share owner) here for requesting + // Note: We're using the 'executantCtx' (authenticated as the share executant) here for requesting // the Grantees of the shares. Ideally the notfication service would use some kind of service // user for this. - spaceGrantee, err := s.getGranteeName(ownerCtx, e.GranteeUserID, e.GranteeGroupID) + spaceGrantee, err := s.getGranteeList(executantCtx, executant.GetId(), e.GranteeUserID, e.GranteeGroupID) if err != nil { - logger.Error().Err(err).Msg("Could not get grantee name") + logger.Error().Err(err).Str("event", "SpaceGrantee").Msg("Could not get grantee list") return } - sharerDisplayName := owner.GetDisplayName() - subj, msg, err := s.render(email.UnsharedSpace, map[string]interface{}{ - "SpaceGrantee": spaceGrantee, - "SpaceSharer": sharerDisplayName, - "SpaceName": resourceInfo.GetSpace().Name, - "ShareLink": shareLink, - }) - + sharerDisplayName := executant.GetDisplayName() + recipientList, err := s.render(executantCtx, email.UnsharedSpace, + "SpaceGrantee", + map[string]interface{}{ + "SpaceSharer": sharerDisplayName, + "SpaceName": resourceInfo.GetSpace().Name, + "ShareLink": shareLink, + }, spaceGrantee) if err != nil { - logger.Error().Err(err).Msg("Could not render E-Mail template for spaces") + s.logger.Error().Err(err).Str("event", "UnsharedSpace").Msg("Could not get render the email") return } - - if err := s.send(ownerCtx, e.GranteeUserID, e.GranteeGroupID, msg, subj, sharerDisplayName); err != nil { - logger.Error().Err(err).Msg("failed to send a message") - } + s.send(executantCtx, recipientList, sharerDisplayName) } func (s eventsNotifier) handleSpaceMembershipExpired(e events.SpaceMembershipExpired) { @@ -140,30 +135,27 @@ func (s eventsNotifier) handleSpaceMembershipExpired(e events.SpaceMembershipExp Str("itemid", e.SpaceID.GetOpaqueId()). Logger() - ctx, owner, err := utils.Impersonate(e.SpaceOwner, s.gwClient, s.machineAuthAPIKey) + ownerCtx, owner, err := utils.Impersonate(e.SpaceOwner, s.gwClient, s.machineAuthAPIKey) if err != nil { logger.Error().Err(err).Msg("Could not impersonate sharer") return } - shareGrantee, err := s.getGranteeName(ctx, e.GranteeUserID, e.GranteeGroupID) + granteeList, err := s.getGranteeList(ownerCtx, owner.GetId(), e.GranteeUserID, e.GranteeGroupID) if err != nil { - s.logger.Error().Err(err).Str("event", "ShareCreated").Msg("Could not get grantee name") + s.logger.Error().Err(err).Str("event", "SpaceUnshared").Msg("Could not get grantee list") return } - subj, msg, err := s.render(email.MembershipExpired, map[string]interface{}{ - "SpaceGrantee": shareGrantee, - "SpaceName": e.SpaceName, - "ExpiredAt": e.ExpiredAt.Format("2006-01-02 15:04:05"), - }) - + recipientList, err := s.render(ownerCtx, email.MembershipExpired, + "SpaceGrantee", + map[string]interface{}{ + "SpaceName": e.SpaceName, + "ExpiredAt": e.ExpiredAt.Format("2006-01-02 15:04:05"), + }, granteeList) if err != nil { - s.logger.Error().Err(err).Str("event", "ShareCreated").Msg("Could not render E-Mail body template for shares") - } - - if err := s.send(ctx, e.GranteeUserID, e.GranteeGroupID, msg, subj, owner.GetDisplayName()); err != nil { - s.logger.Error().Err(err).Str("event", "ShareCreated").Msg("failed to send a message") + s.logger.Error().Err(err).Str("event", "SpaceUnshared").Msg("could not get render the email") + return } - + s.send(ownerCtx, recipientList, owner.GetDisplayName()) } diff --git a/services/settings/pkg/service/v0/settings.go b/services/settings/pkg/service/v0/settings.go index 411e6310bfc..ab58b915ff4 100644 --- a/services/settings/pkg/service/v0/settings.go +++ b/services/settings/pkg/service/v0/settings.go @@ -43,7 +43,8 @@ const ( // CreateSpacePermissionName is the hardcoded setting name for the create space permission CreateSpacePermissionName string = "create-space" - settingUUIDProfileLanguage = "aa8cfbe5-95d4-4f7e-a032-c3c01f5f062f" + // SettingUUIDProfileLanguage is the hardcoded setting UUID for the user profile language + SettingUUIDProfileLanguage = "aa8cfbe5-95d4-4f7e-a032-c3c01f5f062f" // AccountManagementPermissionID is the hardcoded setting UUID for the account management permission AccountManagementPermissionID string = "8e587774-d929-4215-910b-a317b1e80f73" @@ -207,7 +208,7 @@ func generateBundleProfileRequest() *settingsmsg.Bundle { DisplayName: "Profile", Settings: []*settingsmsg.Setting{ { - Id: settingUUIDProfileLanguage, + Id: SettingUUIDProfileLanguage, Name: "language", DisplayName: "Language", Description: "User language", @@ -268,7 +269,7 @@ func generatePermissionRequests() []*settingssvc.AddSettingToBundleRequest { DisplayName: "Permission to read and set the language (anyone)", Resource: &settingsmsg.Resource{ Type: settingsmsg.Resource_TYPE_SETTING, - Id: settingUUIDProfileLanguage, + Id: SettingUUIDProfileLanguage, }, Value: &settingsmsg.Setting_PermissionValue{ PermissionValue: &settingsmsg.Permission{ @@ -286,7 +287,7 @@ func generatePermissionRequests() []*settingssvc.AddSettingToBundleRequest { DisplayName: "Permission to read and set the language (self)", Resource: &settingsmsg.Resource{ Type: settingsmsg.Resource_TYPE_SETTING, - Id: settingUUIDProfileLanguage, + Id: SettingUUIDProfileLanguage, }, Value: &settingsmsg.Setting_PermissionValue{ PermissionValue: &settingsmsg.Permission{ @@ -304,7 +305,7 @@ func generatePermissionRequests() []*settingssvc.AddSettingToBundleRequest { DisplayName: "Permission to read and set the language (self)", Resource: &settingsmsg.Resource{ Type: settingsmsg.Resource_TYPE_SETTING, - Id: settingUUIDProfileLanguage, + Id: SettingUUIDProfileLanguage, }, Value: &settingsmsg.Setting_PermissionValue{ PermissionValue: &settingsmsg.Permission{ @@ -523,7 +524,7 @@ func generatePermissionRequests() []*settingssvc.AddSettingToBundleRequest { DisplayName: "Permission to read and set the language (self)", Resource: &settingsmsg.Resource{ Type: settingsmsg.Resource_TYPE_SETTING, - Id: settingUUIDProfileLanguage, + Id: SettingUUIDProfileLanguage, }, Value: &settingsmsg.Setting_PermissionValue{ PermissionValue: &settingsmsg.Permission{ diff --git a/services/settings/pkg/store/defaults/defaults.go b/services/settings/pkg/store/defaults/defaults.go index 534da706f1c..6e7b463f6b5 100644 --- a/services/settings/pkg/store/defaults/defaults.go +++ b/services/settings/pkg/store/defaults/defaults.go @@ -73,7 +73,8 @@ const ( // SpaceAbilityPermissionName is the hardcoded setting name for the space ability permission SpaceAbilityPermissionName string = "Drive.ReadWriteEnabled" - settingUUIDProfileLanguage = "aa8cfbe5-95d4-4f7e-a032-c3c01f5f062f" + // SettingUUIDProfileLanguage is the hardcoded setting UUID for the user profile language + SettingUUIDProfileLanguage = "aa8cfbe5-95d4-4f7e-a032-c3c01f5f062f" // AccountManagementPermissionID is the hardcoded setting UUID for the account management permission AccountManagementPermissionID string = "8e587774-d929-4215-910b-a317b1e80f73" @@ -159,7 +160,7 @@ func generateBundleAdminRole() *settingsmsg.Bundle { DisplayName: "Permission to read and set the language (anyone)", Resource: &settingsmsg.Resource{ Type: settingsmsg.Resource_TYPE_SETTING, - Id: settingUUIDProfileLanguage, + Id: SettingUUIDProfileLanguage, }, Value: &settingsmsg.Setting_PermissionValue{ PermissionValue: &settingsmsg.Permission{ @@ -416,7 +417,7 @@ func generateBundleSpaceAdminRole() *settingsmsg.Bundle { DisplayName: "Permission to read and set the language (self)", Resource: &settingsmsg.Resource{ Type: settingsmsg.Resource_TYPE_SETTING, - Id: settingUUIDProfileLanguage, + Id: SettingUUIDProfileLanguage, }, Value: &settingsmsg.Setting_PermissionValue{ PermissionValue: &settingsmsg.Permission{ @@ -492,7 +493,7 @@ func generateBundleUserRole() *settingsmsg.Bundle { DisplayName: "Permission to read and set the language (self)", Resource: &settingsmsg.Resource{ Type: settingsmsg.Resource_TYPE_SETTING, - Id: settingUUIDProfileLanguage, + Id: SettingUUIDProfileLanguage, }, Value: &settingsmsg.Setting_PermissionValue{ PermissionValue: &settingsmsg.Permission{ @@ -568,7 +569,7 @@ func generateBundleGuestRole() *settingsmsg.Bundle { DisplayName: "Permission to read and set the language (self)", Resource: &settingsmsg.Resource{ Type: settingsmsg.Resource_TYPE_SETTING, - Id: settingUUIDProfileLanguage, + Id: SettingUUIDProfileLanguage, }, Value: &settingsmsg.Setting_PermissionValue{ PermissionValue: &settingsmsg.Permission{ @@ -593,7 +594,7 @@ func generateBundleProfileRequest() *settingsmsg.Bundle { DisplayName: "Profile", Settings: []*settingsmsg.Setting{ { - Id: settingUUIDProfileLanguage, + Id: SettingUUIDProfileLanguage, Name: "language", DisplayName: "Language", Description: "User language", diff --git a/services/settings/pkg/store/metadata/values.go b/services/settings/pkg/store/metadata/values.go index a9df99dacea..c40f859fa73 100644 --- a/services/settings/pkg/store/metadata/values.go +++ b/services/settings/pkg/store/metadata/values.go @@ -4,7 +4,6 @@ package store import ( "context" "encoding/json" - "errors" "fmt" "github.com/cs3org/reva/v2/pkg/errtypes" @@ -87,8 +86,39 @@ func (s *Store) ReadValue(valueID string) (*settingsmsg.Value, error) { // ReadValueByUniqueIdentifiers tries to find a value given a set of unique identifiers func (s *Store) ReadValueByUniqueIdentifiers(accountUUID, settingID string) (*settingsmsg.Value, error) { - fmt.Println("ReadValueByUniqueIdentifiers not implemented") - return nil, errors.New("not implemented") + if settingID == "" { + return nil, fmt.Errorf("settingID can not be empty %w", settings.ErrNotFound) + } + s.Init() + ctx := context.TODO() + + vIDs, err := s.mdc.ReadDir(ctx, valuesFolderLocation) + if err != nil { + return nil, err + } + + for _, vid := range vIDs { + b, err := s.mdc.SimpleDownload(ctx, valuePath(vid)) + switch err.(type) { + case nil: + // continue + case errtypes.NotFound: + continue + default: + return nil, err + } + + v := &settingsmsg.Value{} + err = json.Unmarshal(b, v) + if err != nil { + return nil, err + } + + if v.AccountUuid == accountUUID && v.SettingId == settingID { + return v, nil + } + } + return nil, settings.ErrNotFound } // WriteValue writes the given value into a file within the dataPath