diff --git a/go.mod b/go.mod index df2cd1d98d..e14d5bd000 100644 --- a/go.mod +++ b/go.mod @@ -214,6 +214,7 @@ require ( github.com/kevinburke/ssh_config v1.2.0 // indirect github.com/klauspost/compress v1.15.11 // indirect github.com/klauspost/cpuid/v2 v2.1.0 // indirect + github.com/leonelquinteros/gotext v1.5.2 // indirect github.com/libregraph/oidc-go v1.0.0 // indirect github.com/longsleep/go-metrics v1.0.0 // indirect github.com/longsleep/rndm v1.2.0 // indirect @@ -293,5 +294,3 @@ require ( ) replace github.com/cs3org/go-cs3apis => github.com/c0rby/go-cs3apis v0.0.0-20230110100311-5b424f1baa35 - -replace github.com/cs3org/reva/v2 => github.com/kobergj/reva/v2 v2.0.0-20230306133911-885670aab8df diff --git a/go.sum b/go.sum index f2823cbffd..f3ec4dab07 100644 --- a/go.sum +++ b/go.sum @@ -886,6 +886,8 @@ github.com/labbsr0x/goh v1.0.1/go.mod h1:8K2UhVoaWXcCU7Lxoa2omWnC8gyW8px7/lmO61c github.com/labstack/echo/v4 v4.1.11/go.mod h1:i541M3Fj6f76NZtHSj7TXnyM8n2gaodfvfxNnFqi74g= github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k= github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= +github.com/leonelquinteros/gotext v1.5.2 h1:T2y6ebHli+rMBCjcJlHTXyUrgXqsKBhl/ormgvt7lPo= +github.com/leonelquinteros/gotext v1.5.2/go.mod h1:AT4NpQrOmyj1L/+hLja6aR0lk81yYYL4ePnj2kp7d6M= github.com/libregraph/idm v0.4.1-0.20230221143410-3503963047a5 h1:brLMXSjWoWhGXs8LpK+Lx+FQCtGLUa51Mq/ggHv9AV0= github.com/libregraph/idm v0.4.1-0.20230221143410-3503963047a5/go.mod h1:Tnm4pyVJTEbHm3GUNmceWT1DHzdrYqrJmZyt/xh7L+A= github.com/libregraph/lico v0.59.4 h1:aQNwWAtC3hyQeZpP+5U1M7PIrPtvHanRWRfMp33wbUQ= diff --git a/services/userlog/pkg/service/conversion.go b/services/userlog/pkg/service/conversion.go index a346350894..5f821068ef 100644 --- a/services/userlog/pkg/service/conversion.go +++ b/services/userlog/pkg/service/conversion.go @@ -12,12 +12,16 @@ import ( "github.com/cs3org/reva/v2/pkg/events" "github.com/cs3org/reva/v2/pkg/storagespace" "github.com/cs3org/reva/v2/pkg/utils" + "github.com/leonelquinteros/gotext" ehmsg "github.com/owncloud/ocis/v2/protogen/gen/ocis/messages/eventhistory/v0" ) var ( _resourceTypeSpace = "storagespace" _resourceTypeShare = "share" + + // TODO: from config + _pathToLocales = "/home/jkoberg/ocis/services/userlog/pkg/service/locales" ) // OC10Notification is the oc10 style representation of an event @@ -37,7 +41,7 @@ type OC10Notification struct { } // ConvertEvent converts an eventhistory event to an OC10Notification -func (ul *UserlogService) ConvertEvent(event *ehmsg.Event) (OC10Notification, error) { +func (ul *UserlogService) ConvertEvent(event *ehmsg.Event, locale string) (OC10Notification, error) { etype, ok := ul.registeredEvents[event.Type] if !ok { // this should not happen @@ -55,33 +59,33 @@ func (ul *UserlogService) ConvertEvent(event *ehmsg.Event) (OC10Notification, er return OC10Notification{}, errors.New("unknown event type") // space related case events.SpaceDisabled: - return ul.spaceMessage(event.Id, SpaceDisabled, ev.Executant, ev.ID.GetOpaqueId(), ev.Timestamp) + return ul.spaceMessage(event.Id, SpaceDisabled, ev.Executant, ev.ID.GetOpaqueId(), ev.Timestamp, locale) case events.SpaceDeleted: - return ul.spaceDeletedMessage(event.Id, ev.Executant, ev.ID.GetOpaqueId(), ev.SpaceName, ev.Timestamp) + return ul.spaceDeletedMessage(event.Id, ev.Executant, ev.ID.GetOpaqueId(), ev.SpaceName, ev.Timestamp, locale) case events.SpaceShared: - return ul.spaceMessage(event.Id, SpaceShared, ev.Executant, ev.ID.GetOpaqueId(), ev.Timestamp) + return ul.spaceMessage(event.Id, SpaceShared, ev.Executant, ev.ID.GetOpaqueId(), ev.Timestamp, locale) case events.SpaceUnshared: - return ul.spaceMessage(event.Id, SpaceUnshared, ev.Executant, ev.ID.GetOpaqueId(), ev.Timestamp) + return ul.spaceMessage(event.Id, SpaceUnshared, ev.Executant, ev.ID.GetOpaqueId(), ev.Timestamp, locale) case events.SpaceMembershipExpired: - return ul.spaceMessage(event.Id, SpaceMembershipExpired, ev.SpaceOwner, ev.SpaceID.GetOpaqueId(), ev.ExpiredAt) + return ul.spaceMessage(event.Id, SpaceMembershipExpired, ev.SpaceOwner, ev.SpaceID.GetOpaqueId(), ev.ExpiredAt, locale) // share related case events.ShareCreated: - return ul.shareMessage(event.Id, ShareCreated, ev.Executant, ev.ItemID, ev.ShareID, utils.TSToTime(ev.CTime)) + return ul.shareMessage(event.Id, ShareCreated, ev.Executant, ev.ItemID, ev.ShareID, utils.TSToTime(ev.CTime), locale) case events.ShareExpired: - return ul.shareMessage(event.Id, ShareExpired, ev.ShareOwner, ev.ItemID, ev.ShareID, ev.ExpiredAt) + return ul.shareMessage(event.Id, ShareExpired, ev.ShareOwner, ev.ItemID, ev.ShareID, ev.ExpiredAt, locale) case events.ShareRemoved: - return ul.shareMessage(event.Id, ShareRemoved, ev.Executant, ev.ItemID, ev.ShareID, ev.Timestamp) + return ul.shareMessage(event.Id, ShareRemoved, ev.Executant, ev.ItemID, ev.ShareID, ev.Timestamp, locale) } } -func (ul *UserlogService) spaceDeletedMessage(eventid string, executant *user.UserId, spaceid string, spacename string, ts time.Time) (OC10Notification, error) { +func (ul *UserlogService) spaceDeletedMessage(eventid string, executant *user.UserId, spaceid string, spacename string, ts time.Time, locale string) (OC10Notification, error) { _, user, err := utils.Impersonate(executant, ul.gwClient, ul.cfg.MachineAuthAPIKey) if err != nil { return OC10Notification{}, err } - subj, subjraw, msg, msgraw, err := ul.composeMessage(SpaceDeleted, map[string]string{ + subj, subjraw, msg, msgraw, err := ul.composeMessage(SpaceDeleted, locale, map[string]interface{}{ "username": user.GetDisplayName(), "spacename": spacename, }) @@ -110,7 +114,7 @@ func (ul *UserlogService) spaceDeletedMessage(eventid string, executant *user.Us }, nil } -func (ul *UserlogService) spaceMessage(eventid string, eventname string, executant *user.UserId, spaceid string, ts time.Time) (OC10Notification, error) { +func (ul *UserlogService) spaceMessage(eventid string, nt NotificationTemplate, executant *user.UserId, spaceid string, ts time.Time, locale string) (OC10Notification, error) { ctx, user, err := utils.Impersonate(executant, ul.gwClient, ul.cfg.MachineAuthAPIKey) if err != nil { return OC10Notification{}, err @@ -121,7 +125,7 @@ func (ul *UserlogService) spaceMessage(eventid string, eventname string, executa return OC10Notification{}, err } - subj, subjraw, msg, msgraw, err := ul.composeMessage(eventname, map[string]string{ + subj, subjraw, msg, msgraw, err := ul.composeMessage(nt, locale, map[string]interface{}{ "username": user.GetDisplayName(), "spacename": space.GetName(), }) @@ -144,7 +148,7 @@ func (ul *UserlogService) spaceMessage(eventid string, eventname string, executa }, nil } -func (ul *UserlogService) shareMessage(eventid string, eventname string, executant *user.UserId, resourceid *storageprovider.ResourceId, shareid *collaboration.ShareId, ts time.Time) (OC10Notification, error) { +func (ul *UserlogService) shareMessage(eventid string, nt NotificationTemplate, executant *user.UserId, resourceid *storageprovider.ResourceId, shareid *collaboration.ShareId, ts time.Time, locale string) (OC10Notification, error) { ctx, user, err := utils.Impersonate(executant, ul.gwClient, ul.cfg.MachineAuthAPIKey) if err != nil { return OC10Notification{}, err @@ -155,7 +159,7 @@ func (ul *UserlogService) shareMessage(eventid string, eventname string, executa return OC10Notification{}, err } - subj, subjraw, msg, msgraw, err := ul.composeMessage(eventname, map[string]string{ + subj, subjraw, msg, msgraw, err := ul.composeMessage(nt, locale, map[string]interface{}{ "username": user.GetDisplayName(), "resourcename": info.GetName(), }) @@ -178,23 +182,23 @@ func (ul *UserlogService) shareMessage(eventid string, eventname string, executa }, nil } -func (ul *UserlogService) composeMessage(eventname string, vars map[string]string) (string, string, string, string, error) { - tpl, ok := _templates[eventname] - if !ok { - return "", "", "", "", errors.New("unknown template name") +func (ul *UserlogService) composeMessage(nt NotificationTemplate, locale string, vars map[string]interface{}) (string, string, string, string, error) { + subj, msg, err := ul.parseTemplate(nt, locale) + if err != nil { + return "", "", "", "", err } - subject := ul.executeTemplate(tpl.Subject, vars) + subject := ul.executeTemplate(subj, vars) - subjectraw := ul.executeTemplate(tpl.Subject, map[string]string{ + subjectraw := ul.executeTemplate(subj, map[string]interface{}{ "username": "{user}", "spacename": "{space}", "resourcename": "{resource}", }) - message := ul.executeTemplate(tpl.Message, vars) + message := ul.executeTemplate(msg, vars) - messageraw := ul.executeTemplate(tpl.Message, map[string]string{ + messageraw := ul.executeTemplate(msg, map[string]interface{}{ "username": "{user}", "spacename": "{space}", "resourcename": "{resource}", @@ -238,7 +242,22 @@ func (ul *UserlogService) getDetails(user *user.User, space *storageprovider.Sto return details } -func (ul *UserlogService) executeTemplate(tpl *template.Template, vars map[string]string) string { +func (ul *UserlogService) parseTemplate(nt NotificationTemplate, locale string) (*template.Template, *template.Template, error) { + // Create Locale with library path and language code and load domain '.../default.po' + l := gotext.NewLocale(_pathToLocales, locale) + l.AddDomain("default") + + subject, err := template.New("").Parse(l.Get(nt.Subject)) + if err != nil { + return nil, nil, err + } + + message, err := template.New("").Parse(l.Get(nt.Message)) + return subject, message, err + +} + +func (ul *UserlogService) executeTemplate(tpl *template.Template, vars map[string]interface{}) string { var writer bytes.Buffer if err := tpl.Execute(&writer, vars); err != nil { ul.log.Error().Err(err).Str("templateName", tpl.Name()).Msg("cannot execute template") diff --git a/services/userlog/pkg/service/http.go b/services/userlog/pkg/service/http.go index 725275e04d..96c2fbe4b7 100644 --- a/services/userlog/pkg/service/http.go +++ b/services/userlog/pkg/service/http.go @@ -28,9 +28,11 @@ func (ul *UserlogService) HandleGetEvents(w http.ResponseWriter, r *http.Request return } + locale := r.Header.Get("Prefered-Language") + resp := GetEventResponseOC10{} for _, e := range evs { - noti, err := ul.ConvertEvent(e) + noti, err := ul.ConvertEvent(e, locale) if err != nil { ul.log.Error().Err(err).Str("eventid", e.Id).Str("eventtype", e.Type).Msg("failed to convert event") continue diff --git a/services/userlog/pkg/service/templates.go b/services/userlog/pkg/service/templates.go index bc9bf33d2b..45258fb0f3 100644 --- a/services/userlog/pkg/service/templates.go +++ b/services/userlog/pkg/service/templates.go @@ -1,65 +1,50 @@ package service -import "text/template" - // the available templates var ( - SpaceShared = "space-shared" - SpaceSharedSubject = "Space shared" - SpaceSharedMessage = "{{ .username }} added you to Space {{ .spacename }}" + SpaceShared = NotificationTemplate{ + Subject: "Space shared", + Message: "{{ .username }} added you to Space {{ .spacename }}", + } - SpaceUnshared = "space-unshared" - SpaceUnsharedSubject = "Removed from Space" - SpaceUnsharedMessage = "{{ .username }} removed you from Space {{ .spacename }}" + SpaceUnshared = NotificationTemplate{ + Subject: "Removed from Space", + Message: "{{ .username }} removed you from Space {{ .spacename }}", + } - SpaceDisabled = "space-disabled" - SpaceDisabledSubject = "Space disabled" - SpaceDisabledMessage = "{{ .username }} disabled Space {{ .spacename }}" + SpaceDisabled = NotificationTemplate{ + Subject: "Space disabled", + Message: "{{ .username }} disabled Space {{ .spacename }}", + } - SpaceDeleted = "space-deleted" - SpaceDeletedSubject = "Space deleted" - SpaceDeletedMessage = "{{ .username }} deleted Space {{ .spacename }}" + SpaceDeleted = NotificationTemplate{ + Subject: "Space deleted", + Message: "{{ .username }} deleted Space {{ .spacename }}", + } - SpaceMembershipExpired = "space-membership-expired" - SpaceMembershipExpiredSubject = "Membership expired" - SpaceMembershipExpiredMessage = "Access to Space {{ .spacename }} lost" + SpaceMembershipExpired = NotificationTemplate{ + Subject: "Membership expired", + Message: "Access to Space {{ .spacename }} lost", + } - ShareCreated = "item-shared" - ShareCreatedSubject = "Resource shared" - ShareCreatedMessage = "{{ .username }} shared {{ .resourcename }} with you" + ShareCreated = NotificationTemplate{ + Subject: "Resource shared", + Message: "{{ .username }} shared {{ .resourcename }} with you", + } - ShareRemoved = "item-unshared" - ShareRemovedSubject = "Resource unshared" - ShareRemovedMessage = "{{ .username }} unshared {{ .resourcename }} with you" + ShareRemoved = NotificationTemplate{ + Subject: "Resource unshared", + Message: "{{ .username }} unshared {{ .resourcename }} with you", + } - ShareExpired = "share-expired" - ShareExpiredSubject = "Share expired" - ShareExpiredMessage = "Access to {{ .resourcename }} expired" -) - -// rendered templates -var ( - _templates = map[string]NotificationTemplate{ - SpaceShared: notiTmpl(SpaceSharedSubject, SpaceSharedMessage), - SpaceUnshared: notiTmpl(SpaceUnsharedSubject, SpaceUnsharedMessage), - SpaceDisabled: notiTmpl(SpaceDisabledSubject, SpaceDisabledMessage), - SpaceDeleted: notiTmpl(SpaceDeletedSubject, SpaceDeletedMessage), - SpaceMembershipExpired: notiTmpl(SpaceMembershipExpiredSubject, SpaceMembershipExpiredMessage), - ShareCreated: notiTmpl(ShareCreatedSubject, ShareCreatedMessage), - ShareRemoved: notiTmpl(ShareRemovedSubject, ShareRemovedMessage), - ShareExpired: notiTmpl(ShareExpiredSubject, ShareExpiredMessage), + ShareExpired = NotificationTemplate{ + Subject: "Share expired", + Message: "Access to {{ .resourcename }} expired", } ) // NotificationTemplate is the data structure for the notifications type NotificationTemplate struct { - Subject *template.Template - Message *template.Template -} - -func notiTmpl(subjectname string, messagename string) NotificationTemplate { - return NotificationTemplate{ - Subject: template.Must(template.New("").Parse(subjectname)), - Message: template.Must(template.New("").Parse(messagename)), - } + Subject string + Message string }