From 552d44d2089b7d3b89b55bd1b9211eead530ffea Mon Sep 17 00:00:00 2001 From: Nityananda Gohain Date: Thu, 10 Jul 2025 20:47:04 +0530 Subject: [PATCH] chore: send email on role update (#8489) * chore: send email on role update * fix: minor changes * fix: update template * fix: minor changes * fix: return updated user --- pkg/modules/user/impluser/handler.go | 37 ------------------ pkg/modules/user/impluser/module.go | 57 ++++++++++++++++++++++++++-- pkg/types/emailtypes/template.go | 5 ++- templates/email/update_role.gotmpl | 23 +++++++++++ 4 files changed, 81 insertions(+), 41 deletions(-) create mode 100644 templates/email/update_role.gotmpl diff --git a/pkg/modules/user/impluser/handler.go b/pkg/modules/user/impluser/handler.go index cbd8bfa9b534..c3d7751c81da 100644 --- a/pkg/modules/user/impluser/handler.go +++ b/pkg/modules/user/impluser/handler.go @@ -289,43 +289,6 @@ func (h *handler) UpdateUser(w http.ResponseWriter, r *http.Request) { return } - existingUser, err := h.module.GetUserByID(ctx, claims.OrgID, id) - if err != nil { - render.Error(w, err) - return - } - - // only displayName, role can be updated - if user.DisplayName == "" { - user.DisplayName = existingUser.DisplayName - } - - if user.Role == "" { - user.Role = existingUser.Role - } - - if user.Role != existingUser.Role && claims.Role != types.RoleAdmin { - render.Error(w, errors.New(errors.TypeForbidden, errors.CodeForbidden, "only admins can change roles")) - return - } - - // Make sure that the request is not demoting the last admin user. - // also an admin user can only change role of their own or other user - if user.Role != existingUser.Role && existingUser.Role == types.RoleAdmin.String() { - adminUsers, err := h.module.GetUsersByRoleInOrg(ctx, claims.OrgID, types.RoleAdmin) - if err != nil { - render.Error(w, err) - return - } - - if len(adminUsers) == 1 { - render.Error(w, errors.New(errors.TypeForbidden, errors.CodeForbidden, "cannot demote the last admin")) - return - } - } - - user.UpdatedAt = time.Now() - updatedUser, err := h.module.UpdateUser(ctx, claims.OrgID, id, &user, claims.UserID) if err != nil { render.Error(w, err) diff --git a/pkg/modules/user/impluser/module.go b/pkg/modules/user/impluser/module.go index e22cb1c0ca1b..2a0c80e32556 100644 --- a/pkg/modules/user/impluser/module.go +++ b/pkg/modules/user/impluser/module.go @@ -176,18 +176,69 @@ func (m *Module) ListUsers(ctx context.Context, orgID string) ([]*types.Gettable } func (m *Module) UpdateUser(ctx context.Context, orgID string, id string, user *types.User, updatedBy string) (*types.User, error) { - user, err := m.store.UpdateUser(ctx, orgID, id, user) + + existingUser, err := m.GetUserByID(ctx, orgID, id) if err != nil { return nil, err } - traits := types.NewTraitsFromUser(user) + requestor, err := m.GetUserByID(ctx, orgID, updatedBy) + if err != nil { + return nil, err + } + + // only displayName, role can be updated + if user.DisplayName == "" { + user.DisplayName = existingUser.DisplayName + } + + if user.Role == "" { + user.Role = existingUser.Role + } + + if user.Role != existingUser.Role && requestor.Role != types.RoleAdmin.String() { + return nil, errors.New(errors.TypeForbidden, errors.CodeForbidden, "only admins can change roles") + } + + // Make sure that the request is not demoting the last admin user. + // also an admin user can only change role of their own or other user + if user.Role != existingUser.Role && existingUser.Role == types.RoleAdmin.String() { + adminUsers, err := m.GetUsersByRoleInOrg(ctx, orgID, types.RoleAdmin) + if err != nil { + return nil, err + } + + if len(adminUsers) == 1 { + return nil, errors.New(errors.TypeForbidden, errors.CodeForbidden, "cannot demote the last admin") + } + } + + user.UpdatedAt = time.Now() + + updatedUser, err := m.store.UpdateUser(ctx, orgID, id, user) + if err != nil { + return nil, err + } + + traits := types.NewTraitsFromUser(updatedUser) m.analytics.IdentifyUser(ctx, user.OrgID, user.ID.String(), traits) traits["updated_by"] = updatedBy m.analytics.TrackUser(ctx, user.OrgID, user.ID.String(), "User Updated", traits) - return user, nil + // if the role is updated then send an email + if existingUser.Role != updatedUser.Role { + if err := m.emailing.SendHTML(ctx, existingUser.Email, "Your Role is updated in SigNoz", emailtypes.TemplateNameUpdateRole, map[string]any{ + "CustomerName": existingUser.DisplayName, + "UpdatedByEmail": requestor.Email, + "OldRole": existingUser.Role, + "NewRole": updatedUser.Role, + }); err != nil { + m.settings.Logger().ErrorContext(ctx, "failed to send email", "error", err) + } + } + + return updatedUser, nil } func (m *Module) DeleteUser(ctx context.Context, orgID string, id string, deletedBy string) error { diff --git a/pkg/types/emailtypes/template.go b/pkg/types/emailtypes/template.go index 68b76bd4ee1b..73f542a89ced 100644 --- a/pkg/types/emailtypes/template.go +++ b/pkg/types/emailtypes/template.go @@ -12,11 +12,12 @@ import ( var ( // Templates is a list of all the templates that are supported by the emailing service. // This list should be updated whenever a new template is added. - Templates = []TemplateName{TemplateNameInvitationEmail} + Templates = []TemplateName{TemplateNameInvitationEmail, TemplateNameUpdateRole} ) var ( TemplateNameInvitationEmail = TemplateName{valuer.NewString("invitation_email")} + TemplateNameUpdateRole = TemplateName{valuer.NewString("update_role")} ) type TemplateName struct{ valuer.String } @@ -25,6 +26,8 @@ func NewTemplateName(name string) (TemplateName, error) { switch name { case TemplateNameInvitationEmail.StringValue(): return TemplateNameInvitationEmail, nil + case TemplateNameUpdateRole.StringValue(): + return TemplateNameUpdateRole, nil default: return TemplateName{}, errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, "invalid template name: %s", name) } diff --git a/templates/email/update_role.gotmpl b/templates/email/update_role.gotmpl new file mode 100644 index 000000000000..8815c6a2b181 --- /dev/null +++ b/templates/email/update_role.gotmpl @@ -0,0 +1,23 @@ + + + +

Hi {{.CustomerName}},

+ +

We wanted to inform you that your role in the SigNoz project has been updated by {{.UpdatedByEmail}}.

+ +

+ Previous Role: {{.OldRole}}
+ New Role: {{.NewRole}} +

+ +

+ Please note that you will need to log out and log back in for the changes to take effect. +

+ +

+ If you were not expecting this change or have any questions, please reach out to your project administrator or contact us at support@signoz.io. +

+ +

Thanks,
The SigNoz Team

+ + \ No newline at end of file