Skip to content

sync: Release candidate v0.33.0 #6514

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 48 commits into from
Apr 11, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
b36dca8
fix: let user delete the container reg if the ref app was deleted
abhibhaw Mar 22, 2025
cc62da4
chore: made where clause more explicit
abhibhaw Mar 25, 2025
0ee9a01
Merge branch 'develop' into delete-reg
abhibhaw Mar 25, 2025
3356c63
Merge pull request #6464 from devtron-labs/delete-reg
abhibhaw Mar 25, 2025
f5752b8
ci triggeredBy updates
kartik-579 Mar 27, 2025
0774faf
bad request for force abort (#6475)
iamayushm Mar 27, 2025
eb8d076
main sync develop
vikramdevtron Mar 27, 2025
02b655e
vendor update in develop
vikramdevtron Mar 28, 2025
7b9f054
Merge pull request #6483 from devtron-labs/main-sync-develop-27mar
vikramdevtron Mar 28, 2025
1f7d0d3
fix: hpa permission denied error (#6485)
iamayushm Apr 1, 2025
e0b95ed
triggeredBy changes
kartik-579 Apr 1, 2025
3177550
Merge branch 'develop' into triggeredBy-changes
kartik-579 Apr 1, 2025
51049c1
patch api for user attribute
prkhrkat Apr 2, 2025
a645721
refactor to remove duplicate code
prkhrkat Apr 2, 2025
858b35e
main sync
Shivam-nagar23 Apr 2, 2025
999d346
make dep update oss
Shivam-nagar23 Apr 2, 2025
7b9bc48
vendor
Shivam-nagar23 Apr 2, 2025
ac74eb4
sync: main changes synced into develop (#6492)
Shivam-nagar23 Apr 2, 2025
fc61f15
oss syncable code for exception users (approval bypass) only limited …
prakash100198 Apr 2, 2025
982b23a
append token
prakash100198 Apr 2, 2025
826621b
add resource type to performExpressEditActionsOnCmCsForExceptionUser
prakash100198 Apr 2, 2025
aff2554
fix
prakash100198 Apr 2, 2025
7cf8c7d
isExpressEdit param in performExpressEditActionsOnDeplTemplateForExce…
prakash100198 Apr 2, 2025
6e4873d
isCM and isCS on configMapbean.resourceType
prakash100198 Apr 2, 2025
7bafe73
Merge branch 'develop' into triggeredBy-changes
kartik-579 Apr 2, 2025
c22a646
Merge pull request #6489 from devtron-labs/triggeredBy-changes
kartik-579 Apr 2, 2025
a1c62c1
fix
prakash100198 Apr 2, 2025
6184233
add sql script
prakash100198 Apr 2, 2025
0e67f5b
chore: code restructuring (#6476)
Ash-exp Apr 3, 2025
377095a
incorporate code review comment feat. nishant sir
prakash100198 Apr 3, 2025
67e2161
Merge branch 'develop' into approval-bypass-v1-oss
prakash100198 Apr 3, 2025
2c98ca1
change script number
prakash100198 Apr 4, 2025
a7ff550
removed unwanted code
prakash100198 Apr 4, 2025
6856cc0
added nil implementations
Shivam-nagar23 Apr 4, 2025
f0e01b3
Merge branch 'develop' into chore-nil-implementation
Shivam-nagar23 Apr 4, 2025
9186fae
Merge pull request #6497 from devtron-labs/chore-nil-implementation
Shivam-nagar23 Apr 4, 2025
6d71edf
Merge branch 'develop' into approval-bypass-v1-oss
prakash100198 Apr 7, 2025
d8a0b4f
Merge branch 'develop' into user-attribute-api
prkhrkat Apr 7, 2025
55c5ac8
Merge pull request #6490 from devtron-labs/user-attribute-api
prkhrkat Apr 7, 2025
6eec9a7
Merge branch 'develop' into approval-bypass-v1-oss
prakash100198 Apr 7, 2025
44bb910
Merge pull request #6493 from devtron-labs/approval-bypass-v1-oss
prakash100198 Apr 7, 2025
70a82f1
main sync develop
vikramdevtron Apr 8, 2025
58ee549
vendor update
vikramdevtron Apr 8, 2025
679b738
Merge pull request #6504 from devtron-labs/main-sync-develop-8apr
vikramdevtron Apr 8, 2025
d2f5ae5
main sync rc33
vikramdevtron Apr 9, 2025
703eb94
Merge pull request #6507 from devtron-labs/main-sync-rc33-9apr
vikramdevtron Apr 9, 2025
31c1a9b
main sync and vendor update rc33
vikramdevtron Apr 10, 2025
06ae166
Merge pull request #6513 from devtron-labs/main-sync-rc33-10apr
vikramdevtron Apr 10, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
4 changes: 4 additions & 0 deletions Wire.go
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,7 @@ import (
repository7 "github.com/devtron-labs/devtron/pkg/kubernetesResourceAuditLogs/repository"
"github.com/devtron-labs/devtron/pkg/notifier"
"github.com/devtron-labs/devtron/pkg/pipeline"
"github.com/devtron-labs/devtron/pkg/pipeline/draftAwareConfigService"
"github.com/devtron-labs/devtron/pkg/pipeline/executors"
history3 "github.com/devtron-labs/devtron/pkg/pipeline/history"
repository3 "github.com/devtron-labs/devtron/pkg/pipeline/history/repository"
Expand Down Expand Up @@ -531,6 +532,9 @@ func InitializeApp() (*App, error) {
chartConfig.NewConfigMapRepositoryImpl,
wire.Bind(new(chartConfig.ConfigMapRepository), new(*chartConfig.ConfigMapRepositoryImpl)),

draftAwareConfigService.NewDraftAwareResourceServiceImpl,
wire.Bind(new(draftAwareConfigService.DraftAwareConfigService), new(*draftAwareConfigService.DraftAwareConfigServiceImpl)),

config.WireSet,

infraConfig.WireSet,
Expand Down
143 changes: 108 additions & 35 deletions api/restHandler/ConfigMapRestHandler.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ package restHandler
import (
"encoding/json"
"fmt"
"github.com/devtron-labs/devtron/pkg/pipeline/draftAwareConfigService"
"net/http"
"strconv"

Expand Down Expand Up @@ -63,31 +64,35 @@ type ConfigMapRestHandler interface {
}

type ConfigMapRestHandlerImpl struct {
pipelineBuilder pipeline.PipelineBuilder
Logger *zap.SugaredLogger
chartService chart.ChartService
userAuthService user.UserService
teamService team.TeamService
enforcer casbin.Enforcer
pipelineRepository pipelineConfig.PipelineRepository
enforcerUtil rbac.EnforcerUtil
configMapService pipeline.ConfigMapService
pipelineBuilder pipeline.PipelineBuilder
Logger *zap.SugaredLogger
chartService chart.ChartService
userAuthService user.UserService
teamService team.TeamService
enforcer casbin.Enforcer
pipelineRepository pipelineConfig.PipelineRepository
enforcerUtil rbac.EnforcerUtil
configMapService pipeline.ConfigMapService
draftAwareResourceService draftAwareConfigService.DraftAwareConfigService
}

func NewConfigMapRestHandlerImpl(pipelineBuilder pipeline.PipelineBuilder, Logger *zap.SugaredLogger,
chartService chart.ChartService, userAuthService user.UserService, teamService team.TeamService,
enforcer casbin.Enforcer, pipelineRepository pipelineConfig.PipelineRepository,
enforcerUtil rbac.EnforcerUtil, configMapService pipeline.ConfigMapService) *ConfigMapRestHandlerImpl {
enforcerUtil rbac.EnforcerUtil, configMapService pipeline.ConfigMapService,
draftAwareResourceService draftAwareConfigService.DraftAwareConfigService,
) *ConfigMapRestHandlerImpl {
return &ConfigMapRestHandlerImpl{
pipelineBuilder: pipelineBuilder,
Logger: Logger,
chartService: chartService,
userAuthService: userAuthService,
teamService: teamService,
enforcer: enforcer,
pipelineRepository: pipelineRepository,
enforcerUtil: enforcerUtil,
configMapService: configMapService,
pipelineBuilder: pipelineBuilder,
Logger: Logger,
chartService: chartService,
userAuthService: userAuthService,
teamService: teamService,
enforcer: enforcer,
pipelineRepository: pipelineRepository,
enforcerUtil: enforcerUtil,
configMapService: configMapService,
draftAwareResourceService: draftAwareResourceService,
}
}

Expand Down Expand Up @@ -118,8 +123,14 @@ func (handler ConfigMapRestHandlerImpl) CMGlobalAddUpdate(w http.ResponseWriter,
return
}
//RBAC END

res, err := handler.configMapService.CMGlobalAddUpdate(&configMapRequest)
ctx := r.Context()
isSuperAdmin := handler.enforcer.Enforce(token, casbin.ResourceGlobal, casbin.ActionCreate, "*")
userEmail, err := handler.userAuthService.GetActiveEmailById(userId)
if err != nil {
common.WriteJsonResp(w, fmt.Errorf("userEmail not found by userId"), "userEmail not found by userId", http.StatusNotFound)
return
}
res, err := handler.draftAwareResourceService.CMGlobalAddUpdate(ctx, &configMapRequest, isSuperAdmin, userEmail)
if err != nil {
handler.Logger.Errorw("service err, CMGlobalAddUpdate", "err", err, "payload", configMapRequest)
common.WriteJsonResp(w, err, nil, http.StatusInternalServerError)
Expand Down Expand Up @@ -162,8 +173,14 @@ func (handler ConfigMapRestHandlerImpl) CMEnvironmentAddUpdate(w http.ResponseWr
}
}
//RBAC END

res, err := handler.configMapService.CMEnvironmentAddUpdate(&configMapRequest)
ctx := r.Context()
isSuperAdmin := handler.enforcer.Enforce(token, casbin.ResourceGlobal, casbin.ActionCreate, "*")
userEmail, err := handler.userAuthService.GetActiveEmailById(userId)
if err != nil {
common.WriteJsonResp(w, fmt.Errorf("userEmail not found by userId"), "userEmail not found by userId", http.StatusNotFound)
return
}
res, err := handler.draftAwareResourceService.CMEnvironmentAddUpdate(ctx, &configMapRequest, isSuperAdmin, userEmail)
if err != nil {
handler.Logger.Errorw("service err, CMEnvironmentAddUpdate", "err", err, "payload", configMapRequest)
common.WriteJsonResp(w, err, nil, http.StatusInternalServerError)
Expand Down Expand Up @@ -359,8 +376,14 @@ func (handler ConfigMapRestHandlerImpl) CSGlobalAddUpdate(w http.ResponseWriter,
return
}
//RBAC END

res, err := handler.configMapService.CSGlobalAddUpdate(&configMapRequest)
ctx := r.Context()
isSuperAdmin := handler.enforcer.Enforce(token, casbin.ResourceGlobal, casbin.ActionCreate, "*")
userEmail, err := handler.userAuthService.GetActiveEmailById(userId)
if err != nil {
common.WriteJsonResp(w, fmt.Errorf("userEmail not found by userId"), "userEmail not found by userId", http.StatusNotFound)
return
}
res, err := handler.draftAwareResourceService.CSGlobalAddUpdate(ctx, &configMapRequest, isSuperAdmin, userEmail)
if err != nil {
handler.Logger.Errorw("service err, CSGlobalAddUpdate", "err", err, "payload", configMapRequest)
common.WriteJsonResp(w, err, nil, http.StatusInternalServerError)
Expand Down Expand Up @@ -404,8 +427,14 @@ func (handler ConfigMapRestHandlerImpl) CSEnvironmentAddUpdate(w http.ResponseWr
}
}
//RBAC END

res, err := handler.configMapService.CSEnvironmentAddUpdate(&configMapRequest)
ctx := r.Context()
isSuperAdmin := handler.enforcer.Enforce(token, casbin.ResourceGlobal, casbin.ActionCreate, "*")
userEmail, err := handler.userAuthService.GetActiveEmailById(userId)
if err != nil {
common.WriteJsonResp(w, fmt.Errorf("userEmail not found by userId"), "userEmail not found by userId", http.StatusNotFound)
return
}
res, err := handler.draftAwareResourceService.CSEnvironmentAddUpdate(ctx, &configMapRequest, isSuperAdmin, userEmail)
if err != nil {
handler.Logger.Errorw("service err, CSEnvironmentAddUpdate", "err", err, "payload", configMapRequest)
common.WriteJsonResp(w, err, nil, http.StatusInternalServerError)
Expand Down Expand Up @@ -517,8 +546,19 @@ func (handler ConfigMapRestHandlerImpl) CMGlobalDelete(w http.ResponseWriter, r
return
}
//RBAC END

res, err := handler.configMapService.CMGlobalDelete(name, id, userId)
ctx := r.Context()
isSuperAdmin := handler.enforcer.Enforce(token, casbin.ResourceGlobal, casbin.ActionCreate, "*")
userEmail, err := handler.userAuthService.GetActiveEmailById(userId)
if err != nil {
common.WriteJsonResp(w, fmt.Errorf("userEmail not found by userId"), "userEmail not found by userId", http.StatusNotFound)
return
}
deleteReq := &bean.ConfigDataRequest{
Id: id,
AppId: appId,
UserId: userId,
}
res, err := handler.draftAwareResourceService.CMGlobalDelete(ctx, name, deleteReq, isSuperAdmin, userEmail)
if err != nil {
handler.Logger.Errorw("service err, CMGlobalDelete", "err", err, "appId", appId, "id", id, "name", name)
common.WriteJsonResp(w, err, nil, http.StatusInternalServerError)
Expand Down Expand Up @@ -572,8 +612,19 @@ func (handler ConfigMapRestHandlerImpl) CMEnvironmentDelete(w http.ResponseWrite
}
}
//RBAC END

res, err := handler.configMapService.CMEnvironmentDelete(name, id, userId)
ctx := r.Context()
isSuperAdmin := handler.enforcer.Enforce(token, casbin.ResourceGlobal, casbin.ActionCreate, "*")
userEmail, err := handler.userAuthService.GetActiveEmailById(userId)
if err != nil {
common.WriteJsonResp(w, fmt.Errorf("userEmail not found by userId"), "userEmail not found by userId", http.StatusNotFound)
return
}
deleteReq := &bean.ConfigDataRequest{
Id: id,
AppId: appId,
UserId: userId,
}
res, err := handler.draftAwareResourceService.CMEnvironmentDelete(ctx, name, deleteReq, isSuperAdmin, userEmail)
if err != nil {
handler.Logger.Errorw("service err, CMEnvironmentDelete", "err", err, "appId", appId, "envId", envId, "id", id)
common.WriteJsonResp(w, err, nil, http.StatusInternalServerError)
Expand Down Expand Up @@ -613,8 +664,19 @@ func (handler ConfigMapRestHandlerImpl) CSGlobalDelete(w http.ResponseWriter, r
return
}
//RBAC END

res, err := handler.configMapService.CSGlobalDelete(name, id, userId)
ctx := r.Context()
isSuperAdmin := handler.enforcer.Enforce(token, casbin.ResourceGlobal, casbin.ActionCreate, "*")
userEmail, err := handler.userAuthService.GetActiveEmailById(userId)
if err != nil {
common.WriteJsonResp(w, fmt.Errorf("userEmail not found by userId"), "userEmail not found by userId", http.StatusNotFound)
return
}
deleteReq := &bean.ConfigDataRequest{
Id: id,
AppId: appId,
UserId: userId,
}
res, err := handler.draftAwareResourceService.CSGlobalDelete(ctx, name, deleteReq, isSuperAdmin, userEmail)
if err != nil {
handler.Logger.Errorw("service err, CSGlobalDelete", "err", err, "appId", appId, "id", id, "name", name)
common.WriteJsonResp(w, err, nil, http.StatusInternalServerError)
Expand Down Expand Up @@ -668,8 +730,19 @@ func (handler ConfigMapRestHandlerImpl) CSEnvironmentDelete(w http.ResponseWrite
}
}
//RBAC END

res, err := handler.configMapService.CSEnvironmentDelete(name, id, userId)
ctx := r.Context()
isSuperAdmin := handler.enforcer.Enforce(token, casbin.ResourceGlobal, casbin.ActionCreate, "*")
userEmail, err := handler.userAuthService.GetActiveEmailById(userId)
if err != nil {
common.WriteJsonResp(w, fmt.Errorf("userEmail not found by userId"), "userEmail not found by userId", http.StatusNotFound)
return
}
deleteReq := &bean.ConfigDataRequest{
Id: id,
AppId: appId,
UserId: userId,
}
res, err := handler.draftAwareResourceService.CSEnvironmentDelete(ctx, name, deleteReq, isSuperAdmin, userEmail)
if err != nil {
handler.Logger.Errorw("service err, CSEnvironmentDelete", "err", err, "appId", appId, "envId", envId, "id", id)
common.WriteJsonResp(w, err, nil, http.StatusInternalServerError)
Expand Down
4 changes: 2 additions & 2 deletions api/restHandler/CoreAppRestHandler.go
Original file line number Diff line number Diff line change
Expand Up @@ -1847,11 +1847,11 @@ func (handler CoreAppRestHandlerImpl) createEnvDeploymentTemplate(appId int, use
templateRequest := bean3.TemplateRequest{
AppId: appId,
ChartRefId: chartRefId,
ValuesOverride: []byte("{}"),
ValuesOverride: util.GetEmptyJSON(),
UserId: userId,
IsAppMetricsEnabled: deploymentTemplateOverride.ShowAppMetrics,
}
newChartEntry, err := handler.chartService.CreateChartFromEnvOverride(templateRequest, context.Background())
newChartEntry, err := handler.chartService.CreateChartFromEnvOverride(context.Background(), templateRequest)
if err != nil {
handler.logger.Errorw("service err, CreateChartFromEnvOverride", "err", err, "appId", appId, "envId", envId, "chartRefId", chartRefId)
return err
Expand Down
89 changes: 42 additions & 47 deletions api/restHandler/UserAttributesRestHandler.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import (
type UserAttributesRestHandler interface {
AddUserAttributes(w http.ResponseWriter, r *http.Request)
UpdateUserAttributes(w http.ResponseWriter, r *http.Request)
PatchUserAttributes(w http.ResponseWriter, r *http.Request)
GetUserAttribute(w http.ResponseWriter, r *http.Request)
}

Expand All @@ -54,35 +55,13 @@ func NewUserAttributesRestHandlerImpl(logger *zap.SugaredLogger, enforcer casbin
}

func (handler *UserAttributesRestHandlerImpl) AddUserAttributes(w http.ResponseWriter, r *http.Request) {
userId, err := handler.userService.GetLoggedInUser(r)
if userId == 0 || err != nil {
common.WriteJsonResp(w, err, "Unauthorized User", http.StatusUnauthorized)
return
}
decoder := json.NewDecoder(r.Body)
var dto attributes.UserAttributesDto
err = decoder.Decode(&dto)
if err != nil {
handler.logger.Errorw("request err, AddUserAttributes", "err", err, "payload", dto)
common.WriteJsonResp(w, err, nil, http.StatusBadRequest)
dto, success := handler.validateUserAttributesRequest(w, r, "PatchUserAttributes")
if !success {
return
}

dto.UserId = userId
//if ok := handler.enforcer.Enforce(token, casbin.ResourceGlobal, casbin.ActionCreate, "*"); !ok {
// common.WriteJsonResp(w, errors.New("unauthorized"), nil, http.StatusForbidden)
// return
//}
emailId, err := handler.userService.GetActiveEmailById(userId)
if err != nil {
handler.logger.Errorw("request err, UpdateUserAttributes", "err", err, "payload", dto)
common.WriteJsonResp(w, errors.New("unauthorized"), nil, http.StatusForbidden)
return
}
dto.EmailId = emailId

handler.logger.Infow("request payload, AddUserAttributes", "payload", dto)
resp, err := handler.userAttributesService.AddUserAttributes(&dto)
resp, err := handler.userAttributesService.AddUserAttributes(dto)
if err != nil {
handler.logger.Errorw("service err, AddUserAttributes", "err", err, "payload", dto)
common.WriteJsonResp(w, err, nil, http.StatusInternalServerError)
Expand All @@ -98,43 +77,64 @@ func (handler *UserAttributesRestHandlerImpl) AddUserAttributes(w http.ResponseW
// @Success 200 {object} attributes.UserAttributesDto
// @Router /orchestrator/attributes/user/update [POST]
func (handler *UserAttributesRestHandlerImpl) UpdateUserAttributes(w http.ResponseWriter, r *http.Request) {
dto, success := handler.validateUserAttributesRequest(w, r, "PatchUserAttributes")
if !success {
return
}

handler.logger.Infow("request payload, UpdateUserAttributes", "payload", dto)
resp, err := handler.userAttributesService.UpdateUserAttributes(dto)
if err != nil {
handler.logger.Errorw("service err, UpdateUserAttributes", "err", err, "payload", dto)
common.WriteJsonResp(w, err, nil, http.StatusInternalServerError)
return
}
common.WriteJsonResp(w, nil, resp, http.StatusOK)
}

func (handler *UserAttributesRestHandlerImpl) PatchUserAttributes(w http.ResponseWriter, r *http.Request) {
dto, success := handler.validateUserAttributesRequest(w, r, "PatchUserAttributes")
if !success {
return
}

handler.logger.Infow("request payload, PatchUserAttributes", "payload", dto)
resp, err := handler.userAttributesService.PatchUserAttributes(dto)
if err != nil {
handler.logger.Errorw("service err, PatchUserAttributes", "err", err, "payload", dto)
common.WriteJsonResp(w, err, nil, http.StatusInternalServerError)
return
}
common.WriteJsonResp(w, nil, resp, http.StatusOK)
}

func (handler *UserAttributesRestHandlerImpl) validateUserAttributesRequest(w http.ResponseWriter, r *http.Request, operation string) (*attributes.UserAttributesDto, bool) {
userId, err := handler.userService.GetLoggedInUser(r)
if userId == 0 || err != nil {
common.WriteJsonResp(w, err, "Unauthorized User", http.StatusUnauthorized)
return
return nil, false
}

decoder := json.NewDecoder(r.Body)
var dto attributes.UserAttributesDto
err = decoder.Decode(&dto)
if err != nil {
handler.logger.Errorw("request err, UpdateUserAttributes", "err", err, "payload", dto)
handler.logger.Errorw("request err, "+operation, "err", err, "payload", dto)
common.WriteJsonResp(w, err, nil, http.StatusBadRequest)
return
return nil, false
}

dto.UserId = userId
//if ok := handler.enforcer.Enforce(token, casbin.ResourceGlobal, casbin.ActionUpdate, "*"); !ok {
// common.WriteJsonResp(w, errors.New("unauthorized"), nil, http.StatusForbidden)
// return
//}

emailId, err := handler.userService.GetActiveEmailById(userId)
if err != nil {
handler.logger.Errorw("request err, UpdateUserAttributes", "err", err, "payload", dto)
handler.logger.Errorw("request err, "+operation, "err", err, "payload", dto)
common.WriteJsonResp(w, errors.New("unauthorized"), nil, http.StatusForbidden)
return
return nil, false
}
dto.EmailId = emailId

handler.logger.Infow("request payload, UpdateUserAttributes", "payload", dto)
resp, err := handler.userAttributesService.UpdateUserAttributes(&dto)
if err != nil {
handler.logger.Errorw("service err, UpdateUserAttributes", "err", err, "payload", dto)
common.WriteJsonResp(w, err, nil, http.StatusInternalServerError)
return
}
common.WriteJsonResp(w, nil, resp, http.StatusOK)
return &dto, true
}

// @Summary get user attributes
Expand All @@ -158,11 +158,6 @@ func (handler *UserAttributesRestHandlerImpl) GetUserAttribute(w http.ResponseWr
return
}

//if ok := handler.enforcer.Enforce(token, casbin.ResourceGlobal, casbin.ActionGet, "*"); !ok {
// common.WriteJsonResp(w, errors.New("unauthorized"), nil, http.StatusForbidden)
// return
//}

dto := attributes.UserAttributesDto{}

emailId, err := handler.userService.GetActiveEmailById(userId)
Expand Down
Loading