MM-24037 Adding getKnowUsers API endpoint (#14332)
* Adding getKnowUsers API endpoint * Adding i18n strings * Fixing golint errors * Adding doc strings * Remove debug line * Updating app_iface * Fixing gofmt
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
d3b36e3455
Коммит
224b72c61e
13
api4/user.go
13
api4/user.go
@@ -26,6 +26,7 @@ func (api *API) InitUser() {
|
||||
api.BaseRoutes.Users.Handle("", api.ApiSessionRequired(getUsers)).Methods("GET")
|
||||
api.BaseRoutes.Users.Handle("/ids", api.ApiSessionRequired(getUsersByIds)).Methods("POST")
|
||||
api.BaseRoutes.Users.Handle("/usernames", api.ApiSessionRequired(getUsersByNames)).Methods("POST")
|
||||
api.BaseRoutes.Users.Handle("/known", api.ApiSessionRequired(getKnownUsers)).Methods("GET")
|
||||
api.BaseRoutes.Users.Handle("/search", api.ApiSessionRequiredDisableWhenBusy(searchUsers)).Methods("POST")
|
||||
api.BaseRoutes.Users.Handle("/autocomplete", api.ApiSessionRequired(autocompleteUsers)).Methods("GET")
|
||||
api.BaseRoutes.Users.Handle("/stats", api.ApiSessionRequired(getTotalUsersStats)).Methods("GET")
|
||||
@@ -717,6 +718,18 @@ func getUsersByNames(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
w.Write([]byte(model.UserListToJson(users)))
|
||||
}
|
||||
|
||||
func getKnownUsers(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
userIds, err := c.App.GetKnownUsers(c.App.Session().UserId)
|
||||
if err != nil {
|
||||
c.Err = err
|
||||
return
|
||||
}
|
||||
|
||||
data, _ := json.Marshal(userIds)
|
||||
|
||||
w.Write(data)
|
||||
}
|
||||
|
||||
func searchUsers(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
props := model.UserSearchFromJson(r.Body)
|
||||
if props == nil {
|
||||
|
||||
@@ -4528,3 +4528,103 @@ func TestPromoteGuestToUser(t *testing.T) {
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestGetKnownUsers(t *testing.T) {
|
||||
th := Setup(t)
|
||||
defer th.TearDown()
|
||||
|
||||
t1, err := th.App.CreateTeam(&model.Team{
|
||||
DisplayName: "dn_" + model.NewId(),
|
||||
Name: GenerateTestTeamName(),
|
||||
Email: th.GenerateTestEmail(),
|
||||
Type: model.TEAM_OPEN,
|
||||
})
|
||||
require.Nil(t, err, "failed to create team")
|
||||
|
||||
t2, err := th.App.CreateTeam(&model.Team{
|
||||
DisplayName: "dn_" + model.NewId(),
|
||||
Name: GenerateTestTeamName(),
|
||||
Email: th.GenerateTestEmail(),
|
||||
Type: model.TEAM_OPEN,
|
||||
})
|
||||
require.Nil(t, err, "failed to create team")
|
||||
|
||||
t3, err := th.App.CreateTeam(&model.Team{
|
||||
DisplayName: "dn_" + model.NewId(),
|
||||
Name: GenerateTestTeamName(),
|
||||
Email: th.GenerateTestEmail(),
|
||||
Type: model.TEAM_OPEN,
|
||||
})
|
||||
require.Nil(t, err, "failed to create team")
|
||||
|
||||
c1, err := th.App.CreateChannel(&model.Channel{
|
||||
DisplayName: "dn_" + model.NewId(),
|
||||
Name: "name_" + model.NewId(),
|
||||
Type: model.CHANNEL_OPEN,
|
||||
TeamId: t1.Id,
|
||||
CreatorId: model.NewId(),
|
||||
}, false)
|
||||
require.Nil(t, err, "failed to create channel")
|
||||
|
||||
c2, err := th.App.CreateChannel(&model.Channel{
|
||||
DisplayName: "dn_" + model.NewId(),
|
||||
Name: "name_" + model.NewId(),
|
||||
Type: model.CHANNEL_OPEN,
|
||||
TeamId: t2.Id,
|
||||
CreatorId: model.NewId(),
|
||||
}, false)
|
||||
require.Nil(t, err, "failed to create channel")
|
||||
|
||||
c3, err := th.App.CreateChannel(&model.Channel{
|
||||
DisplayName: "dn_" + model.NewId(),
|
||||
Name: "name_" + model.NewId(),
|
||||
Type: model.CHANNEL_OPEN,
|
||||
TeamId: t3.Id,
|
||||
CreatorId: model.NewId(),
|
||||
}, false)
|
||||
require.Nil(t, err, "failed to create channel")
|
||||
|
||||
u1 := th.CreateUser()
|
||||
defer th.App.PermanentDeleteUser(u1)
|
||||
u2 := th.CreateUser()
|
||||
defer th.App.PermanentDeleteUser(u2)
|
||||
u3 := th.CreateUser()
|
||||
defer th.App.PermanentDeleteUser(u3)
|
||||
u4 := th.CreateUser()
|
||||
defer th.App.PermanentDeleteUser(u4)
|
||||
|
||||
th.LinkUserToTeam(u1, t1)
|
||||
th.LinkUserToTeam(u1, t2)
|
||||
th.LinkUserToTeam(u2, t1)
|
||||
th.LinkUserToTeam(u3, t2)
|
||||
th.LinkUserToTeam(u4, t3)
|
||||
|
||||
th.App.AddUserToChannel(u1, c1)
|
||||
th.App.AddUserToChannel(u1, c2)
|
||||
th.App.AddUserToChannel(u2, c1)
|
||||
th.App.AddUserToChannel(u3, c2)
|
||||
th.App.AddUserToChannel(u4, c3)
|
||||
|
||||
t.Run("get know users sharing no channels", func(t *testing.T) {
|
||||
_, _ = th.Client.Login(u4.Email, u4.Password)
|
||||
userIds, resp := th.Client.GetKnownUsers()
|
||||
CheckNoError(t, resp)
|
||||
assert.Empty(t, userIds)
|
||||
})
|
||||
|
||||
t.Run("get know users sharing one channel", func(t *testing.T) {
|
||||
_, _ = th.Client.Login(u3.Email, u3.Password)
|
||||
userIds, resp := th.Client.GetKnownUsers()
|
||||
CheckNoError(t, resp)
|
||||
assert.Len(t, userIds, 1)
|
||||
assert.Equal(t, userIds[0], u1.Id)
|
||||
})
|
||||
|
||||
t.Run("get know users sharing multiple channels", func(t *testing.T) {
|
||||
_, _ = th.Client.Login(u1.Email, u1.Password)
|
||||
userIds, resp := th.Client.GetKnownUsers()
|
||||
CheckNoError(t, resp)
|
||||
assert.Len(t, userIds, 2)
|
||||
assert.ElementsMatch(t, userIds, []string{u2.Id, u3.Id})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -156,6 +156,10 @@ type AppIface interface {
|
||||
GetEnvironmentConfig() map[string]interface{}
|
||||
// GetHubForUserId returns the hub for a given user id.
|
||||
GetHubForUserId(userId string) *Hub
|
||||
// GetKnownUsers returns the list of user ids of users with any direct
|
||||
// relationship with a user. That means any user sharing any channel, including
|
||||
// direct and group channels.
|
||||
GetKnownUsers(userID string) ([]string, *model.AppError)
|
||||
// GetLdapGroup retrieves a single LDAP group by the given LDAP group id.
|
||||
GetLdapGroup(ldapGroupID string) (*model.Group, *model.AppError)
|
||||
// GetMarketplacePlugins returns a list of plugins from the marketplace-server,
|
||||
|
||||
@@ -5702,6 +5702,28 @@ func (a *OpenTracingAppLayer) GetJobsPage(page int, perPage int) ([]*model.Job,
|
||||
return resultVar0, resultVar1
|
||||
}
|
||||
|
||||
func (a *OpenTracingAppLayer) GetKnownUsers(userID string) ([]string, *model.AppError) {
|
||||
origCtx := a.ctx
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.GetKnownUsers")
|
||||
|
||||
a.ctx = newCtx
|
||||
a.app.Srv().Store.SetContext(newCtx)
|
||||
defer func() {
|
||||
a.app.Srv().Store.SetContext(origCtx)
|
||||
a.ctx = origCtx
|
||||
}()
|
||||
|
||||
defer span.Finish()
|
||||
resultVar0, resultVar1 := a.app.GetKnownUsers(userID)
|
||||
|
||||
if resultVar1 != nil {
|
||||
span.LogFields(spanlog.Error(resultVar1))
|
||||
ext.Error.Set(span, true)
|
||||
}
|
||||
|
||||
return resultVar0, resultVar1
|
||||
}
|
||||
|
||||
func (a *OpenTracingAppLayer) GetLatestTermsOfService() (*model.TermsOfService, *model.AppError) {
|
||||
origCtx := a.ctx
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.GetLatestTermsOfService")
|
||||
|
||||
@@ -2083,3 +2083,10 @@ func (a *App) invalidateUserCacheAndPublish(userId string) {
|
||||
message.Add("user", user)
|
||||
a.Publish(message)
|
||||
}
|
||||
|
||||
// GetKnownUsers returns the list of user ids of users with any direct
|
||||
// relationship with a user. That means any user sharing any channel, including
|
||||
// direct and group channels.
|
||||
func (a *App) GetKnownUsers(userID string) ([]string, *model.AppError) {
|
||||
return a.Srv().Store.User().GetKnownUsers(userID)
|
||||
}
|
||||
|
||||
@@ -7242,6 +7242,10 @@
|
||||
"id": "store.sql_user.get_for_login.multiple_users",
|
||||
"translation": "We found multiple users matching your credentials and were unable to log you in. Please contact an administrator."
|
||||
},
|
||||
{
|
||||
"id": "store.sql_user.get_known_users.get_users.app_error",
|
||||
"translation": "Unable to get know users from the database."
|
||||
},
|
||||
{
|
||||
"id": "store.sql_user.get_new_users.app_error",
|
||||
"translation": "We encountered an error while finding the new users."
|
||||
|
||||
@@ -5005,6 +5005,17 @@ func (c *Client4) PatchChannelModerations(channelID string, patch []*ChannelMode
|
||||
return ChannelModerationsFromJson(r.Body), BuildResponse(r)
|
||||
}
|
||||
|
||||
func (c *Client4) GetKnownUsers() ([]string, *Response) {
|
||||
r, err := c.DoApiGet(c.GetUsersRoute()+"/known", "")
|
||||
if err != nil {
|
||||
return nil, BuildErrorResponse(r, err)
|
||||
}
|
||||
defer closeBody(r)
|
||||
var userIds []string
|
||||
json.NewDecoder(r.Body).Decode(&userIds)
|
||||
return userIds, BuildResponse(r)
|
||||
}
|
||||
|
||||
func (c *Client4) GetChannelMemberCountsByGroup(channelID string, includeTimezones bool, etag string) ([]*ChannelMemberCountByGroup, *Response) {
|
||||
r, err := c.DoApiGet(c.GetChannelRoute(channelID)+"/member_counts_by_group?include_timezones="+strconv.FormatBool(includeTimezones), etag)
|
||||
if err != nil {
|
||||
|
||||
@@ -7641,6 +7641,24 @@ func (s *OpenTracingLayerUserStore) GetForLogin(loginId string, allowSignInWithU
|
||||
return resultVar0, resultVar1
|
||||
}
|
||||
|
||||
func (s *OpenTracingLayerUserStore) GetKnownUsers(userID string) ([]string, *model.AppError) {
|
||||
origCtx := s.Root.Store.Context()
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "UserStore.GetKnownUsers")
|
||||
s.Root.Store.SetContext(newCtx)
|
||||
defer func() {
|
||||
s.Root.Store.SetContext(origCtx)
|
||||
}()
|
||||
|
||||
defer span.Finish()
|
||||
resultVar0, resultVar1 := s.UserStore.GetKnownUsers(userID)
|
||||
if resultVar1 != nil {
|
||||
span.LogFields(spanlog.Error(resultVar1))
|
||||
ext.Error.Set(span, true)
|
||||
}
|
||||
|
||||
return resultVar0, resultVar1
|
||||
}
|
||||
|
||||
func (s *OpenTracingLayerUserStore) GetNewUsersForTeam(teamId string, offset int, limit int, viewRestrictions *model.ViewUsersRestrictions) ([]*model.User, *model.AppError) {
|
||||
origCtx := s.Root.Store.Context()
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "UserStore.GetNewUsersForTeam")
|
||||
|
||||
@@ -1770,3 +1770,23 @@ func (us SqlUserStore) AutocompleteUsersInChannel(teamId, channelId, term string
|
||||
autocomplete.OutOfChannel = users
|
||||
return autocomplete, nil
|
||||
}
|
||||
|
||||
// GetKnownUsers returns the list of user ids of users with any direct
|
||||
// relationship with a user. That means any user sharing any channel, including
|
||||
// direct and group channels.
|
||||
func (us SqlUserStore) GetKnownUsers(userId string) ([]string, *model.AppError) {
|
||||
var userIds []string
|
||||
usersQuery, args, _ := us.getQueryBuilder().
|
||||
Select("DISTINCT ocm.UserId").
|
||||
From("ChannelMembers AS cm").
|
||||
Join("ChannelMembers AS ocm ON ocm.ChannelId = cm.ChannelId").
|
||||
Where(sq.NotEq{"ocm.UserId": userId}).
|
||||
Where(sq.Eq{"cm.UserId": userId}).
|
||||
ToSql()
|
||||
_, err := us.GetSearchReplica().Select(&userIds, usersQuery, args...)
|
||||
if err != nil {
|
||||
return nil, model.NewAppError("SqlUserStore.GetKnownUsers", "store.sql_user.get_known_users.get_users.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
return userIds, nil
|
||||
}
|
||||
|
||||
@@ -332,6 +332,7 @@ type UserStore interface {
|
||||
DemoteUserToGuest(userID string) *model.AppError
|
||||
DeactivateGuests() ([]string, *model.AppError)
|
||||
AutocompleteUsersInChannel(teamId, channelId, term string, options *model.UserSearchOptions) (*model.UserAutocompleteInChannel, *model.AppError)
|
||||
GetKnownUsers(userID string) ([]string, *model.AppError)
|
||||
}
|
||||
|
||||
type BotStore interface {
|
||||
|
||||
@@ -582,6 +582,31 @@ func (_m *UserStore) GetForLogin(loginId string, allowSignInWithUsername bool, a
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// GetKnownUsers provides a mock function with given fields: userID
|
||||
func (_m *UserStore) GetKnownUsers(userID string) ([]string, *model.AppError) {
|
||||
ret := _m.Called(userID)
|
||||
|
||||
var r0 []string
|
||||
if rf, ok := ret.Get(0).(func(string) []string); ok {
|
||||
r0 = rf(userID)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).([]string)
|
||||
}
|
||||
}
|
||||
|
||||
var r1 *model.AppError
|
||||
if rf, ok := ret.Get(1).(func(string) *model.AppError); ok {
|
||||
r1 = rf(userID)
|
||||
} else {
|
||||
if ret.Get(1) != nil {
|
||||
r1 = ret.Get(1).(*model.AppError)
|
||||
}
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// GetNewUsersForTeam provides a mock function with given fields: teamId, offset, limit, viewRestrictions
|
||||
func (_m *UserStore) GetNewUsersForTeam(teamId string, offset int, limit int, viewRestrictions *model.ViewUsersRestrictions) ([]*model.User, *model.AppError) {
|
||||
ret := _m.Called(teamId, offset, limit, viewRestrictions)
|
||||
|
||||
@@ -82,6 +82,7 @@ func TestUserStore(t *testing.T, ss store.Store, s SqlSupplier) {
|
||||
t.Run("DemoteUserToGuest", func(t *testing.T) { testUserStoreDemoteUserToGuest(t, ss) })
|
||||
t.Run("DeactivateGuests", func(t *testing.T) { testDeactivateGuests(t, ss) })
|
||||
t.Run("ResetLastPictureUpdate", func(t *testing.T) { testUserStoreResetLastPictureUpdate(t, ss) })
|
||||
t.Run("GetKnownUsers", func(t *testing.T) { testGetKnownUsers(t, ss) })
|
||||
}
|
||||
|
||||
func testUserStoreSave(t *testing.T, ss store.Store) {
|
||||
@@ -4624,3 +4625,134 @@ func testUserStoreResetLastPictureUpdate(t *testing.T, ss store.Store) {
|
||||
assert.True(t, user2.UpdateAt > user.UpdateAt)
|
||||
assert.Zero(t, user2.LastPictureUpdate)
|
||||
}
|
||||
|
||||
func testGetKnownUsers(t *testing.T, ss store.Store) {
|
||||
teamId := model.NewId()
|
||||
|
||||
u1, err := ss.User().Save(&model.User{
|
||||
Email: MakeEmail(),
|
||||
Username: "u1" + model.NewId(),
|
||||
})
|
||||
require.Nil(t, err)
|
||||
defer func() { require.Nil(t, ss.User().PermanentDelete(u1.Id)) }()
|
||||
_, err = ss.Team().SaveMember(&model.TeamMember{TeamId: teamId, UserId: u1.Id}, -1)
|
||||
require.Nil(t, err)
|
||||
|
||||
u2, err := ss.User().Save(&model.User{
|
||||
Email: MakeEmail(),
|
||||
Username: "u2" + model.NewId(),
|
||||
})
|
||||
require.Nil(t, err)
|
||||
defer func() { require.Nil(t, ss.User().PermanentDelete(u2.Id)) }()
|
||||
_, err = ss.Team().SaveMember(&model.TeamMember{TeamId: teamId, UserId: u2.Id}, -1)
|
||||
require.Nil(t, err)
|
||||
|
||||
u3, err := ss.User().Save(&model.User{
|
||||
Email: MakeEmail(),
|
||||
Username: "u3" + model.NewId(),
|
||||
})
|
||||
require.Nil(t, err)
|
||||
defer func() { require.Nil(t, ss.User().PermanentDelete(u3.Id)) }()
|
||||
_, err = ss.Team().SaveMember(&model.TeamMember{TeamId: teamId, UserId: u3.Id}, -1)
|
||||
require.Nil(t, err)
|
||||
_, err = ss.Bot().Save(&model.Bot{
|
||||
UserId: u3.Id,
|
||||
Username: u3.Username,
|
||||
OwnerId: u1.Id,
|
||||
})
|
||||
require.Nil(t, err)
|
||||
u3.IsBot = true
|
||||
|
||||
defer func() { require.Nil(t, ss.Bot().PermanentDelete(u3.Id)) }()
|
||||
|
||||
u4, err := ss.User().Save(&model.User{
|
||||
Email: MakeEmail(),
|
||||
Username: "u4" + model.NewId(),
|
||||
})
|
||||
require.Nil(t, err)
|
||||
defer func() { require.Nil(t, ss.User().PermanentDelete(u4.Id)) }()
|
||||
_, err = ss.Team().SaveMember(&model.TeamMember{TeamId: teamId, UserId: u4.Id}, -1)
|
||||
require.Nil(t, err)
|
||||
|
||||
ch1 := &model.Channel{
|
||||
TeamId: teamId,
|
||||
DisplayName: "Profiles in channel",
|
||||
Name: "profiles-" + model.NewId(),
|
||||
Type: model.CHANNEL_OPEN,
|
||||
}
|
||||
c1, err := ss.Channel().Save(ch1, -1)
|
||||
require.Nil(t, err)
|
||||
|
||||
ch2 := &model.Channel{
|
||||
TeamId: teamId,
|
||||
DisplayName: "Profiles in private",
|
||||
Name: "profiles-" + model.NewId(),
|
||||
Type: model.CHANNEL_PRIVATE,
|
||||
}
|
||||
c2, err := ss.Channel().Save(ch2, -1)
|
||||
require.Nil(t, err)
|
||||
|
||||
ch3 := &model.Channel{
|
||||
TeamId: teamId,
|
||||
DisplayName: "Profiles in private",
|
||||
Name: "profiles-" + model.NewId(),
|
||||
Type: model.CHANNEL_PRIVATE,
|
||||
}
|
||||
c3, err := ss.Channel().Save(ch3, -1)
|
||||
require.Nil(t, err)
|
||||
|
||||
_, err = ss.Channel().SaveMember(&model.ChannelMember{
|
||||
ChannelId: c1.Id,
|
||||
UserId: u1.Id,
|
||||
NotifyProps: model.GetDefaultChannelNotifyProps(),
|
||||
})
|
||||
require.Nil(t, err)
|
||||
|
||||
_, err = ss.Channel().SaveMember(&model.ChannelMember{
|
||||
ChannelId: c1.Id,
|
||||
UserId: u2.Id,
|
||||
NotifyProps: model.GetDefaultChannelNotifyProps(),
|
||||
})
|
||||
require.Nil(t, err)
|
||||
|
||||
_, err = ss.Channel().SaveMember(&model.ChannelMember{
|
||||
ChannelId: c2.Id,
|
||||
UserId: u3.Id,
|
||||
NotifyProps: model.GetDefaultChannelNotifyProps(),
|
||||
})
|
||||
require.Nil(t, err)
|
||||
|
||||
_, err = ss.Channel().SaveMember(&model.ChannelMember{
|
||||
ChannelId: c2.Id,
|
||||
UserId: u1.Id,
|
||||
NotifyProps: model.GetDefaultChannelNotifyProps(),
|
||||
})
|
||||
require.Nil(t, err)
|
||||
|
||||
_, err = ss.Channel().SaveMember(&model.ChannelMember{
|
||||
ChannelId: c3.Id,
|
||||
UserId: u4.Id,
|
||||
NotifyProps: model.GetDefaultChannelNotifyProps(),
|
||||
})
|
||||
require.Nil(t, err)
|
||||
|
||||
t.Run("get know users sharing no channels", func(t *testing.T) {
|
||||
userIds, err := ss.User().GetKnownUsers(u4.Id)
|
||||
require.Nil(t, err)
|
||||
assert.Empty(t, userIds)
|
||||
})
|
||||
|
||||
t.Run("get know users sharing one channel", func(t *testing.T) {
|
||||
userIds, err := ss.User().GetKnownUsers(u3.Id)
|
||||
require.Nil(t, err)
|
||||
assert.Len(t, userIds, 1)
|
||||
assert.Equal(t, userIds[0], u1.Id)
|
||||
})
|
||||
|
||||
t.Run("get know users sharing multiple channels", func(t *testing.T) {
|
||||
userIds, err := ss.User().GetKnownUsers(u1.Id)
|
||||
require.Nil(t, err)
|
||||
assert.Len(t, userIds, 2)
|
||||
assert.ElementsMatch(t, userIds, []string{u2.Id, u3.Id})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -6919,6 +6919,22 @@ func (s *TimerLayerUserStore) GetForLogin(loginId string, allowSignInWithUsernam
|
||||
return resultVar0, resultVar1
|
||||
}
|
||||
|
||||
func (s *TimerLayerUserStore) GetKnownUsers(userID string) ([]string, *model.AppError) {
|
||||
start := timemodule.Now()
|
||||
|
||||
resultVar0, resultVar1 := s.UserStore.GetKnownUsers(userID)
|
||||
|
||||
elapsed := float64(timemodule.Since(start)) / float64(timemodule.Second)
|
||||
if s.Root.Metrics != nil {
|
||||
success := "false"
|
||||
if resultVar1 == nil {
|
||||
success = "true"
|
||||
}
|
||||
s.Root.Metrics.ObserveStoreMethodDuration("UserStore.GetKnownUsers", success, elapsed)
|
||||
}
|
||||
return resultVar0, resultVar1
|
||||
}
|
||||
|
||||
func (s *TimerLayerUserStore) GetNewUsersForTeam(teamId string, offset int, limit int, viewRestrictions *model.ViewUsersRestrictions) ([]*model.User, *model.AppError) {
|
||||
start := timemodule.Now()
|
||||
|
||||
|
||||
Ссылка в новой задаче
Block a user