summaryrefslogtreecommitdiffstats
path: root/store
diff options
context:
space:
mode:
authorJoram Wilander <jwawilander@gmail.com>2016-10-19 14:49:25 -0400
committerGitHub <noreply@github.com>2016-10-19 14:49:25 -0400
commit365b8b465e8a53ebb2da2bf3aef659ac81a2bc6a (patch)
tree643b2dd52b478c2c0b049ac28798d870b9dfd397 /store
parent0512bd26ee85473aa47206d5f207a9a506019138 (diff)
downloadchat-365b8b465e8a53ebb2da2bf3aef659ac81a2bc6a.tar.gz
chat-365b8b465e8a53ebb2da2bf3aef659ac81a2bc6a.tar.bz2
chat-365b8b465e8a53ebb2da2bf3aef659ac81a2bc6a.zip
Merging performance branch into master (#4268)
* improve performance on sendNotifications * Fix SQL queries * Remove get direct profiles, not needed anymore * Add raw data to error details if AppError fails to decode * men * Fix decode (#4052) * Fixing json decode * Adding unit test * Initial work for client scaling (#4051) * Begin adding paging to profiles API * Added more paging functionality * Finish hooking up admin console user lists * Add API for searching users and add searching to all user lists * Add lazy loading of profiles * Revert config.json * Fix unit tests and some style issues * Add GetProfilesFromList to Go driver and fix web unit test * Update etag for GetProfiles * Updating ui for filters and pagination (#4044) * Updating UI for pagination * Adjusting margins for filter row * Adjusting margin for specific modals * Adding relative padding to system console * Adjusting responsive view * Update client user tests * Minor fixes for direct messages modal (#4056) * Remove some unneeded initial load calls (#4057) * UX updates to user lists, added smart counts and bug fixes (#4059) * Improved getExplicitMentions and unit tests (#4064) * Refactor getting posts to lazy load profiles correctly (#4062) * Comment out SetActiveChannel test (#4066) * Profiler cpu, block, and memory profiler. (#4081) * Fix TestSetActiveChannel unit test (#4071) * Fixing build failure caused by dependancies updating (#4076) * Adding profiler * Fix admin_team_member_dropdown eslint errors * Bumping session cache size (#4077) * Bumping session cache size * Bumping status cache * Refactor how the client handles channel members to be large team friendly (#4106) * Refactor how the client handles channel members to be large team friendly * Change Id to ChannelId in ChannelStats model * Updated getChannelMember and getProfilesByIds routes to match proposal * Performance improvements (#4100) * Performance improvements * Fixing re-connect issue * Fixing error message * Some other minor perf tweaks * Some other minor perf tweaks * Fixing config file * Fixing buffer size * Fixing web socket send message * adding some error logging * fix getMe to be user required * Fix websocket event for new user * Fixing shutting down * Reverting web socket changes * Fixing logging lvl * Adding caching to GetMember * Adding some logging * Fixing caching * Fixing caching invalidate * Fixing direct message caching * Fixing caching * Fixing caching * Remove GetDirectProfiles from initial load * Adding logging and fixing websocket client * Adding back caching from bad merge. * Explicitly close go driver requests (#4162) * Refactored how the client handles team members to be more large team friendly (#4159) * Refactor getProfilesForDirectMessageList API into getAllProfiles API * Refactored how the client handles team members to be more large team friendly * Fix js error when receiving a notification * Fix JS error caused by current user being overwritten with sanitized version (#4165) * Adding error message to status failure (#4167) * Fix a few bugs caused by client scaling refactoring (#4170) * When there is no read replica, don't open a second set of connections to the master database (#4173) * Adding connection tacking to stats (#4174) * Reduce DB writes for statuses and other status related changes (#4175) * Fix bug preventing opening of DM channels from more modal (#4181) * Fixing socket timing error (#4183) * Fixing ping/pong handler * Fixing socket timing error * Commenting out status broadcasting * Removing user status changes * Removing user status changes * Removing user status changes * Removing user status changes * Adding DoPreComputeJson() * Performance improvements (#4194) * * Fix System Console Analytics queries * Add db.SetConnMaxLifetime to 15 minutes * Add "net/http/pprof" for profiling * Add FreeOSMemory() to manually release memory on reload config * Add flag to enable http profiler * Fix memory leak (#4197) * Fix memory leak * removed unneeded nil assignment * Fixing go routine leak (#4208) * Merge fixes * Merge fix * Refactored statuses to be queried by the client rather than broadcast by the server (#4212) * Refactored server code to reduce status broadcasts and to allow getting statuses by IDs * Refactor client code to periodically fetch statuses * Add store unit test for getting statuses by ids * Fix status unit test * Add getStatusesByIds REST API and move the client over to use that instead of the WebSocket * Adding multiple threads to websocket hub (#4230) * Adding multiple threads to websocket hub * Fixing unit tests * Fixing so websocket connections from the same user end up in the sameā€¦ (#4240) * Fixing so websocket connections from the same user end up in the same list * Removing old comment * Refactor user autocomplete to query the server (#4239) * Add API for autocompleting users * Converted at mention autocomplete to query server * Converted user search autocomplete to query server * Switch autocomplete API naming to use term instead of username * Split autocomplete API into two, one for channels and for teams * Fix copy/paste error * Some final client scaling fixes (#4246) * Add lazy loading of profiles to integration pages * Add lazy loading of profiles to emoji page * Fix JS error when receiving post in select team menu and also clean up channel store
Diffstat (limited to 'store')
-rw-r--r--store/sql_channel_store.go126
-rw-r--r--store/sql_channel_store_test.go65
-rw-r--r--store/sql_post_store.go70
-rw-r--r--store/sql_status_store.go41
-rw-r--r--store/sql_status_store_test.go9
-rw-r--r--store/sql_store.go26
-rw-r--r--store/sql_team_store.go69
-rw-r--r--store/sql_team_store_test.go79
-rw-r--r--store/sql_user_store.go359
-rw-r--r--store/sql_user_store_test.go428
-rw-r--r--store/store.go26
11 files changed, 1106 insertions, 192 deletions
diff --git a/store/sql_channel_store.go b/store/sql_channel_store.go
index eb150a63c..a860fea73 100644
--- a/store/sql_channel_store.go
+++ b/store/sql_channel_store.go
@@ -6,21 +6,26 @@ package store
import (
"database/sql"
+ l4g "github.com/alecthomas/log4go"
"github.com/go-gorp/gorp"
"github.com/mattermost/platform/model"
"github.com/mattermost/platform/utils"
)
const (
- MISSING_CHANNEL_ERROR = "store.sql_channel.get_by_name.missing.app_error"
- MISSING_CHANNEL_MEMBER_ERROR = "store.sql_channel.get_member.missing.app_error"
- CHANNEL_EXISTS_ERROR = "store.sql_channel.save_channel.exists.app_error"
+ MISSING_CHANNEL_ERROR = "store.sql_channel.get_by_name.missing.app_error"
+ MISSING_CHANNEL_MEMBER_ERROR = "store.sql_channel.get_member.missing.app_error"
+ CHANNEL_EXISTS_ERROR = "store.sql_channel.save_channel.exists.app_error"
+ ALL_CHANNEL_MEMBERS_FOR_USER_CACHE_SIZE = model.SESSION_CACHE_SIZE
+ ALL_CHANNEL_MEMBERS_FOR_USER_CACHE_SEC = 900 // 15 mins
)
type SqlChannelStore struct {
*SqlStore
}
+var allChannelMembersForUserCache *utils.Cache = utils.NewLru(ALL_CHANNEL_MEMBERS_FOR_USER_CACHE_SIZE)
+
func NewSqlChannelStore(sqlStore *SqlStore) ChannelStore {
s := &SqlChannelStore{sqlStore}
@@ -517,6 +522,8 @@ func (s SqlChannelStore) SaveMember(member *model.ChannelMember) StoreChannel {
}
}
+ s.InvalidateAllChannelMembersForUser(member.UserId)
+
storeChannel <- result
close(storeChannel)
}()
@@ -619,6 +626,33 @@ func (s SqlChannelStore) GetMember(channelId string, userId string) StoreChannel
return storeChannel
}
+func (us SqlChannelStore) InvalidateAllChannelMembersForUser(userId string) {
+ allChannelMembersForUserCache.Remove(userId)
+}
+
+func (us SqlChannelStore) IsUserInChannelUseCache(userId string, channelId string) bool {
+ if cacheItem, ok := allChannelMembersForUserCache.Get(userId); ok {
+ ids := cacheItem.(map[string]string)
+ if _, ok := ids[channelId]; ok {
+ return true
+ } else {
+ return false
+ }
+ }
+
+ if result := <-us.GetAllChannelMembersForUser(userId, true); result.Err != nil {
+ l4g.Error("SqlChannelStore.IsUserInChannelUseCache: " + result.Err.Error())
+ return false
+ } else {
+ ids := result.Data.(map[string]string)
+ if _, ok := ids[channelId]; ok {
+ return true
+ } else {
+ return false
+ }
+ }
+}
+
func (s SqlChannelStore) GetMemberForPost(postId string, userId string) StoreChannel {
storeChannel := make(StoreChannel, 1)
@@ -649,26 +683,43 @@ func (s SqlChannelStore) GetMemberForPost(postId string, userId string) StoreCha
return storeChannel
}
-func (s SqlChannelStore) GetMemberCount(channelId string) StoreChannel {
+type allChannelMember struct {
+ ChannelId string
+ Roles string
+}
+
+func (s SqlChannelStore) GetAllChannelMembersForUser(userId string, allowFromCache bool) StoreChannel {
storeChannel := make(StoreChannel, 1)
go func() {
result := StoreResult{}
- count, err := s.GetReplica().SelectInt(`
- SELECT
- count(*)
- FROM
- ChannelMembers,
- Users
- WHERE
- ChannelMembers.UserId = Users.Id
- AND ChannelMembers.ChannelId = :ChannelId
- AND Users.DeleteAt = 0`, map[string]interface{}{"ChannelId": channelId})
+ if allowFromCache {
+ if cacheItem, ok := allChannelMembersForUserCache.Get(userId); ok {
+ result.Data = cacheItem.(map[string]string)
+ storeChannel <- result
+ close(storeChannel)
+ return
+ }
+ }
+
+ var data []allChannelMember
+ _, err := s.GetReplica().Select(&data, "SELECT ChannelId, Roles FROM Channels, ChannelMembers WHERE Channels.Id = ChannelMembers.ChannelId AND ChannelMembers.UserId = :UserId AND Channels.DeleteAt = 0", map[string]interface{}{"UserId": userId})
+
if err != nil {
- result.Err = model.NewLocAppError("SqlChannelStore.GetMemberCount", "store.sql_channel.get_member_count.app_error", nil, "channel_id="+channelId+", "+err.Error())
+ result.Err = model.NewLocAppError("SqlChannelStore.GetAllChannelMembersForUser", "store.sql_channel.get_channels.get.app_error", nil, "userId="+userId+", err="+err.Error())
} else {
- result.Data = count
+
+ ids := make(map[string]string)
+ for i := range data {
+ ids[data[i].ChannelId] = data[i].Roles
+ }
+
+ result.Data = ids
+
+ if allowFromCache {
+ allChannelMembersForUserCache.AddWithExpiresInSecs(userId, ids, ALL_CHANNEL_MEMBERS_FOR_USER_CACHE_SEC)
+ }
}
storeChannel <- result
@@ -678,55 +729,26 @@ func (s SqlChannelStore) GetMemberCount(channelId string) StoreChannel {
return storeChannel
}
-func (s SqlChannelStore) GetExtraMembers(channelId string, limit int) StoreChannel {
+func (s SqlChannelStore) GetMemberCount(channelId string) StoreChannel {
storeChannel := make(StoreChannel, 1)
go func() {
result := StoreResult{}
- var members []model.ExtraMember
- var err error
-
- if limit != -1 {
- _, err = s.GetReplica().Select(&members, `
- SELECT
- Id,
- Nickname,
- Email,
- ChannelMembers.Roles,
- Username
- FROM
- ChannelMembers,
- Users
- WHERE
- ChannelMembers.UserId = Users.Id
- AND Users.DeleteAt = 0
- AND ChannelId = :ChannelId
- LIMIT :Limit`, map[string]interface{}{"ChannelId": channelId, "Limit": limit})
- } else {
- _, err = s.GetReplica().Select(&members, `
+ count, err := s.GetReplica().SelectInt(`
SELECT
- Id,
- Nickname,
- Email,
- ChannelMembers.Roles,
- Username
+ count(*)
FROM
ChannelMembers,
Users
WHERE
ChannelMembers.UserId = Users.Id
- AND Users.DeleteAt = 0
- AND ChannelId = :ChannelId`, map[string]interface{}{"ChannelId": channelId})
- }
-
+ AND ChannelMembers.ChannelId = :ChannelId
+ AND Users.DeleteAt = 0`, map[string]interface{}{"ChannelId": channelId})
if err != nil {
- result.Err = model.NewLocAppError("SqlChannelStore.GetExtraMembers", "store.sql_channel.get_extra_members.app_error", nil, "channel_id="+channelId+", "+err.Error())
+ result.Err = model.NewLocAppError("SqlChannelStore.GetMemberCount", "store.sql_channel.get_member_count.app_error", nil, "channel_id="+channelId+", "+err.Error())
} else {
- for i := range members {
- members[i].Sanitize(utils.Cfg.GetSanitizeOptions())
- }
- result.Data = members
+ result.Data = count
}
storeChannel <- result
diff --git a/store/sql_channel_store_test.go b/store/sql_channel_store_test.go
index 19db3d003..d80d54d52 100644
--- a/store/sql_channel_store_test.go
+++ b/store/sql_channel_store_test.go
@@ -408,11 +408,6 @@ func TestChannelMemberStore(t *testing.T) {
t.Fatal("should have go member")
}
- extraMembers := (<-store.Channel().GetExtraMembers(o1.ChannelId, 20)).Data.([]model.ExtraMember)
- if len(extraMembers) != 1 {
- t.Fatal("should have 1 extra members")
- }
-
if err := (<-store.Channel().SaveMember(&o1)).Err; err == nil {
t.Fatal("Should have been a duplicate")
}
@@ -422,18 +417,6 @@ func TestChannelMemberStore(t *testing.T) {
if t4 != t3 {
t.Fatal("Should not update time upon failure")
}
-
- // rejoin the channel and make sure that an inactive user isn't returned by GetExtraMambers
- Must(store.Channel().SaveMember(&o2))
-
- u2.DeleteAt = 1000
- Must(store.User().Update(&u2, true))
-
- if result := <-store.Channel().GetExtraMembers(o1.ChannelId, 20); result.Err != nil {
- t.Fatal(result.Err)
- } else if extraMembers := result.Data.([]model.ExtraMember); len(extraMembers) != 1 {
- t.Fatal("should have 1 extra members")
- }
}
func TestChannelDeleteMemberStore(t *testing.T) {
@@ -534,6 +517,42 @@ func TestChannelStoreGetChannels(t *testing.T) {
if list.Channels[0].Id != o1.Id {
t.Fatal("missing channel")
}
+
+ acresult := <-store.Channel().GetAllChannelMembersForUser(m1.UserId, false)
+ ids := acresult.Data.(map[string]string)
+ if _, ok := ids[o1.Id]; !ok {
+ t.Fatal("missing channel")
+ }
+
+ acresult2 := <-store.Channel().GetAllChannelMembersForUser(m1.UserId, true)
+ ids2 := acresult2.Data.(map[string]string)
+ if _, ok := ids2[o1.Id]; !ok {
+ t.Fatal("missing channel")
+ }
+
+ acresult3 := <-store.Channel().GetAllChannelMembersForUser(m1.UserId, true)
+ ids3 := acresult3.Data.(map[string]string)
+ if _, ok := ids3[o1.Id]; !ok {
+ t.Fatal("missing channel")
+ }
+
+ if !store.Channel().IsUserInChannelUseCache(m1.UserId, o1.Id) {
+ t.Fatal("missing channel")
+ }
+
+ if store.Channel().IsUserInChannelUseCache(m1.UserId, o2.Id) {
+ t.Fatal("missing channel")
+ }
+
+ if store.Channel().IsUserInChannelUseCache(m1.UserId, "blahblah") {
+ t.Fatal("missing channel")
+ }
+
+ if store.Channel().IsUserInChannelUseCache("blahblah", "blahblah") {
+ t.Fatal("missing channel")
+ }
+
+ store.Channel().InvalidateAllChannelMembersForUser(m1.UserId)
}
func TestChannelStoreGetMoreChannels(t *testing.T) {
@@ -974,22 +993,10 @@ func TestUpdateExtrasByUser(t *testing.T) {
t.Fatal("failed to update extras by user: %v", result.Err)
}
- if result := <-store.Channel().GetExtraMembers(c1.Id, -1); result.Err != nil {
- t.Fatal("failed to get extras: %v", result.Err)
- } else if len(result.Data.([]model.ExtraMember)) != 0 {
- t.Fatal("got incorrect member count %v", len(result.Data.([]model.ExtraMember)))
- }
-
u1.DeleteAt = 0
Must(store.User().Update(u1, true))
if result := <-store.Channel().ExtraUpdateByUser(u1.Id, u1.DeleteAt); result.Err != nil {
t.Fatal("failed to update extras by user: %v", result.Err)
}
-
- if result := <-store.Channel().GetExtraMembers(c1.Id, -1); result.Err != nil {
- t.Fatal("failed to get extras: %v", result.Err)
- } else if len(result.Data.([]model.ExtraMember)) != 1 {
- t.Fatal("got incorrect member count %v", len(result.Data.([]model.ExtraMember)))
- }
}
diff --git a/store/sql_post_store.go b/store/sql_post_store.go
index ec8679b31..44ffb556e 100644
--- a/store/sql_post_store.go
+++ b/store/sql_post_store.go
@@ -43,6 +43,7 @@ func (s SqlPostStore) CreateIndexesIfNotExists() {
s.CreateIndexIfNotExists("idx_posts_create_at", "Posts", "CreateAt")
s.CreateIndexIfNotExists("idx_posts_channel_id", "Posts", "ChannelId")
s.CreateIndexIfNotExists("idx_posts_root_id", "Posts", "RootId")
+ s.CreateIndexIfNotExists("idx_posts_user_id", "Posts", "UserId")
s.CreateFullTextIndexIfNotExists("idx_posts_message_txt", "Posts", "Message")
s.CreateFullTextIndexIfNotExists("idx_posts_hashtags_txt", "Posts", "Hashtags")
@@ -811,47 +812,36 @@ func (s SqlPostStore) AnalyticsUserCountsWithPostsByDay(teamId string) StoreChan
result := StoreResult{}
query :=
- `SELECT
- t1.Name, COUNT(t1.UserId) AS Value
- FROM
- (SELECT DISTINCT
+ `SELECT DISTINCT
DATE(FROM_UNIXTIME(Posts.CreateAt / 1000)) AS Name,
- Posts.UserId
- FROM
- Posts, Channels
- WHERE
- Posts.ChannelId = Channels.Id`
+ COUNT(DISTINCT Posts.UserId) AS Value
+ FROM Posts
+ INNER JOIN Channels
+ ON Posts.ChannelId = Channels.Id`
if len(teamId) > 0 {
query += " AND Channels.TeamId = :TeamId"
}
query += ` AND Posts.CreateAt >= :StartTime AND Posts.CreateAt <= :EndTime
- ORDER BY Name DESC) AS t1
- GROUP BY Name
+ GROUP BY DATE(FROM_UNIXTIME(Posts.CreateAt / 1000))
ORDER BY Name DESC
LIMIT 30`
if utils.Cfg.SqlSettings.DriverName == model.DATABASE_DRIVER_POSTGRES {
query =
`SELECT
- TO_CHAR(t1.Name, 'YYYY-MM-DD') AS Name, COUNT(t1.UserId) AS Value
- FROM
- (SELECT DISTINCT
- DATE(TO_TIMESTAMP(Posts.CreateAt / 1000)) AS Name,
- Posts.UserId
- FROM
- Posts, Channels
- WHERE
- Posts.ChannelId = Channels.Id`
+ TO_CHAR(DATE(TO_TIMESTAMP(Posts.CreateAt / 1000)), 'YYYY-MM-DD') AS Name, COUNT(DISTINCT Posts.UserId) AS Value
+ FROM Posts
+ INNER JOIN Channels
+ ON Posts.ChannelId = Channels.Id`
if len(teamId) > 0 {
query += " AND Channels.TeamId = :TeamId"
}
query += ` AND Posts.CreateAt >= :StartTime AND Posts.CreateAt <= :EndTime
- ORDER BY Name DESC) AS t1
- GROUP BY Name
+ GROUP BY DATE(TO_TIMESTAMP(Posts.CreateAt / 1000))
ORDER BY Name DESC
LIMIT 30`
}
@@ -884,15 +874,12 @@ func (s SqlPostStore) AnalyticsPostCountsByDay(teamId string) StoreChannel {
result := StoreResult{}
query :=
- `SELECT
- Name, COUNT(Value) AS Value
- FROM
- (SELECT
+ `SELECT
DATE(FROM_UNIXTIME(Posts.CreateAt / 1000)) AS Name,
- '1' AS Value
- FROM
- Posts, Channels
- WHERE
+ COUNT(Posts.Id) AS Value
+ FROM Posts
+ INNER JOIN Channels
+ ON
Posts.ChannelId = Channels.Id`
if len(teamId) > 0 {
@@ -900,31 +887,26 @@ func (s SqlPostStore) AnalyticsPostCountsByDay(teamId string) StoreChannel {
}
query += ` AND Posts.CreateAt <= :EndTime
- AND Posts.CreateAt >= :StartTime) AS t1
- GROUP BY Name
+ AND Posts.CreateAt >= :StartTime
+ GROUP BY DATE(FROM_UNIXTIME(Posts.CreateAt / 1000))
ORDER BY Name DESC
LIMIT 30`
if utils.Cfg.SqlSettings.DriverName == model.DATABASE_DRIVER_POSTGRES {
query =
- `SELECT
- Name, COUNT(Value) AS Value
- FROM
- (SELECT
- TO_CHAR(DATE(TO_TIMESTAMP(Posts.CreateAt / 1000)), 'YYYY-MM-DD') AS Name,
- '1' AS Value
- FROM
- Posts, Channels
- WHERE
- Posts.ChannelId = Channels.Id`
+ `SELECT
+ TO_CHAR(DATE(TO_TIMESTAMP(Posts.CreateAt / 1000)), 'YYYY-MM-DD') AS Name, Count(Posts.Id) AS Value
+ FROM Posts
+ INNER JOIN Channels
+ ON Posts.ChannelId = Channels.Id`
if len(teamId) > 0 {
query += " AND Channels.TeamId = :TeamId"
}
query += ` AND Posts.CreateAt <= :EndTime
- AND Posts.CreateAt >= :StartTime) AS t1
- GROUP BY Name
+ AND Posts.CreateAt >= :StartTime
+ GROUP BY DATE(TO_TIMESTAMP(Posts.CreateAt / 1000))
ORDER BY Name DESC
LIMIT 30`
}
diff --git a/store/sql_status_store.go b/store/sql_status_store.go
index 4d186a30e..7b9fdea5d 100644
--- a/store/sql_status_store.go
+++ b/store/sql_status_store.go
@@ -5,6 +5,7 @@ package store
import (
"database/sql"
+ "strconv"
"github.com/mattermost/platform/model"
)
@@ -43,11 +44,11 @@ func (s SqlStatusStore) SaveOrUpdate(status *model.Status) StoreChannel {
if err := s.GetReplica().SelectOne(&model.Status{}, "SELECT * FROM Status WHERE UserId = :UserId", map[string]interface{}{"UserId": status.UserId}); err == nil {
if _, err := s.GetMaster().Update(status); err != nil {
- result.Err = model.NewLocAppError("SqlStatusStore.SaveOrUpdate", "store.sql_status.update.app_error", nil, "")
+ result.Err = model.NewLocAppError("SqlStatusStore.SaveOrUpdate", "store.sql_status.update.app_error", nil, err.Error())
}
} else {
if err := s.GetMaster().Insert(status); err != nil {
- result.Err = model.NewLocAppError("SqlStatusStore.SaveOrUpdate", "store.sql_status.save.app_error", nil, "")
+ result.Err = model.NewLocAppError("SqlStatusStore.SaveOrUpdate", "store.sql_status.save.app_error", nil, err.Error())
}
}
@@ -89,6 +90,38 @@ func (s SqlStatusStore) Get(userId string) StoreChannel {
return storeChannel
}
+func (s SqlStatusStore) GetByIds(userIds []string) StoreChannel {
+ storeChannel := make(StoreChannel, 1)
+
+ go func() {
+ result := StoreResult{}
+
+ props := make(map[string]interface{})
+ idQuery := ""
+
+ for index, userId := range userIds {
+ if len(idQuery) > 0 {
+ idQuery += ", "
+ }
+
+ props["userId"+strconv.Itoa(index)] = userId
+ idQuery += ":userId" + strconv.Itoa(index)
+ }
+
+ var statuses []*model.Status
+ if _, err := s.GetReplica().Select(&statuses, "SELECT * FROM Status WHERE UserId IN ("+idQuery+")", props); err != nil {
+ result.Err = model.NewLocAppError("SqlStatusStore.GetByIds", "store.sql_status.get.app_error", nil, err.Error())
+ } else {
+ result.Data = statuses
+ }
+
+ storeChannel <- result
+ close(storeChannel)
+ }()
+
+ return storeChannel
+}
+
func (s SqlStatusStore) GetOnlineAway() StoreChannel {
storeChannel := make(StoreChannel, 1)
@@ -96,7 +129,7 @@ func (s SqlStatusStore) GetOnlineAway() StoreChannel {
result := StoreResult{}
var statuses []*model.Status
- if _, err := s.GetReplica().Select(&statuses, "SELECT * FROM Status WHERE Status = :Online OR Status = :Away", map[string]interface{}{"Online": model.STATUS_ONLINE, "Away": model.STATUS_AWAY}); err != nil {
+ if _, err := s.GetReplica().Select(&statuses, "SELECT * FROM Status WHERE Status = :Online OR Status = :Away LIMIT 300", map[string]interface{}{"Online": model.STATUS_ONLINE, "Away": model.STATUS_AWAY}); err != nil {
result.Err = model.NewLocAppError("SqlStatusStore.GetOnlineAway", "store.sql_status.get_online_away.app_error", nil, err.Error())
} else {
result.Data = statuses
@@ -157,7 +190,7 @@ func (s SqlStatusStore) ResetAll() StoreChannel {
go func() {
result := StoreResult{}
- if _, err := s.GetMaster().Exec("UPDATE Status SET Status = :Status", map[string]interface{}{"Status": model.STATUS_OFFLINE}); err != nil {
+ if _, err := s.GetMaster().Exec("UPDATE Status SET Status = :Status WHERE Manual = 0", map[string]interface{}{"Status": model.STATUS_OFFLINE}); err != nil {
result.Err = model.NewLocAppError("SqlStatusStore.ResetAll", "store.sql_status.reset_all.app_error", nil, "")
}
diff --git a/store/sql_status_store_test.go b/store/sql_status_store_test.go
index dff4db55e..dce973850 100644
--- a/store/sql_status_store_test.go
+++ b/store/sql_status_store_test.go
@@ -60,6 +60,15 @@ func TestSqlStatusStore(t *testing.T) {
}
}
+ if result := <-store.Status().GetByIds([]string{status.UserId, "junk"}); result.Err != nil {
+ t.Fatal(result.Err)
+ } else {
+ statuses := result.Data.([]*model.Status)
+ if len(statuses) != 1 {
+ t.Fatal("should only have 1 status")
+ }
+ }
+
if err := (<-store.Status().ResetAll()).Err; err != nil {
t.Fatal(err)
}
diff --git a/store/sql_store.go b/store/sql_store.go
index a2bc8f1b8..1c0de5932 100644
--- a/store/sql_store.go
+++ b/store/sql_store.go
@@ -33,6 +33,7 @@ import (
const (
INDEX_TYPE_FULL_TEXT = "full_text"
INDEX_TYPE_DEFAULT = "default"
+ MAX_DB_CONN_LIFETIME = 15
)
const (
@@ -94,9 +95,7 @@ func initConnection() *SqlStore {
if len(utils.Cfg.SqlSettings.DataSourceReplicas) == 0 {
sqlStore.replicas = make([]*gorp.DbMap, 1)
- sqlStore.replicas[0] = setupConnection(fmt.Sprintf("replica-%v", 0), utils.Cfg.SqlSettings.DriverName, utils.Cfg.SqlSettings.DataSource,
- utils.Cfg.SqlSettings.MaxIdleConns, utils.Cfg.SqlSettings.MaxOpenConns,
- utils.Cfg.SqlSettings.Trace)
+ sqlStore.replicas[0] = sqlStore.master
} else {
sqlStore.replicas = make([]*gorp.DbMap, len(utils.Cfg.SqlSettings.DataSourceReplicas))
for i, replica := range utils.Cfg.SqlSettings.DataSourceReplicas {
@@ -183,6 +182,7 @@ func setupConnection(con_type string, driver string, dataSource string, maxIdle
db.SetMaxIdleConns(maxIdle)
db.SetMaxOpenConns(maxOpen)
+ db.SetConnMaxLifetime(time.Duration(MAX_DB_CONN_LIFETIME) * time.Minute)
var dbmap *gorp.DbMap
@@ -205,6 +205,26 @@ func setupConnection(con_type string, driver string, dataSource string, maxIdle
return dbmap
}
+func (ss SqlStore) TotalMasterDbConnections() int {
+ return ss.GetMaster().Db.Stats().OpenConnections
+}
+
+func (ss SqlStore) TotalReadDbConnections() int {
+
+ if len(utils.Cfg.SqlSettings.DataSourceReplicas) == 0 {
+ return 0
+ } else {
+ count := 0
+ for _, db := range ss.replicas {
+ count = count + db.Db.Stats().OpenConnections
+ }
+
+ return count
+ }
+
+ return 0
+}
+
func (ss SqlStore) GetCurrentSchemaVersion() string {
version, _ := ss.GetMaster().SelectStr("SELECT Value FROM Systems WHERE Name='Version'")
return version
diff --git a/store/sql_team_store.go b/store/sql_team_store.go
index 34a4a097d..a69c84904 100644
--- a/store/sql_team_store.go
+++ b/store/sql_team_store.go
@@ -5,6 +5,7 @@ package store
import (
"database/sql"
+ "strconv"
"github.com/mattermost/platform/model"
"github.com/mattermost/platform/utils"
@@ -441,14 +442,14 @@ func (s SqlTeamStore) GetMember(teamId string, userId string) StoreChannel {
return storeChannel
}
-func (s SqlTeamStore) GetMembers(teamId string) StoreChannel {
+func (s SqlTeamStore) GetMembers(teamId string, offset int, limit int) StoreChannel {
storeChannel := make(StoreChannel, 1)
go func() {
result := StoreResult{}
var members []*model.TeamMember
- _, err := s.GetReplica().Select(&members, "SELECT * FROM TeamMembers WHERE TeamId = :TeamId", map[string]interface{}{"TeamId": teamId})
+ _, err := s.GetReplica().Select(&members, "SELECT * FROM TeamMembers WHERE TeamId = :TeamId AND DeleteAt = 0 LIMIT :Limit OFFSET :Offset", map[string]interface{}{"TeamId": teamId, "Offset": offset, "Limit": limit})
if err != nil {
result.Err = model.NewLocAppError("SqlTeamStore.GetMembers", "store.sql_team.get_members.app_error", nil, "teamId="+teamId+" "+err.Error())
} else {
@@ -462,6 +463,70 @@ func (s SqlTeamStore) GetMembers(teamId string) StoreChannel {
return storeChannel
}
+func (s SqlTeamStore) GetMemberCount(teamId string) StoreChannel {
+ storeChannel := make(StoreChannel, 1)
+
+ go func() {
+ result := StoreResult{}
+
+ count, err := s.GetReplica().SelectInt(`
+ SELECT
+ count(*)
+ FROM
+ TeamMembers,
+ Users
+ WHERE
+ TeamMembers.UserId = Users.Id
+ AND TeamMembers.TeamId = :TeamId
+ AND TeamMembers.DeleteAt = 0
+ AND Users.DeleteAt = 0`, map[string]interface{}{"TeamId": teamId})
+ if err != nil {
+ result.Err = model.NewLocAppError("SqlTeamStore.GetMemberCount", "store.sql_team.get_member_count.app_error", nil, "teamId="+teamId+" "+err.Error())
+ } else {
+ result.Data = count
+ }
+
+ storeChannel <- result
+ close(storeChannel)
+ }()
+
+ return storeChannel
+}
+
+func (s SqlTeamStore) GetMembersByIds(teamId string, userIds []string) StoreChannel {
+ storeChannel := make(StoreChannel, 1)
+
+ go func() {
+ result := StoreResult{}
+
+ var members []*model.TeamMember
+ props := make(map[string]interface{})
+ idQuery := ""
+
+ for index, userId := range userIds {
+ if len(idQuery) > 0 {
+ idQuery += ", "
+ }
+
+ props["userId"+strconv.Itoa(index)] = userId
+ idQuery += ":userId" + strconv.Itoa(index)
+ }
+
+ props["TeamId"] = teamId
+
+ if _, err := s.GetReplica().Select(&members, "SELECT * FROM TeamMembers WHERE TeamId = :TeamId AND UserId IN ("+idQuery+") AND DeleteAt = 0", props); err != nil {
+ result.Err = model.NewLocAppError("SqlTeamStore.GetMembersByIds", "store.sql_team.get_members_by_ids.app_error", nil, "teamId="+teamId+" "+err.Error())
+ } else {
+ result.Data = members
+ }
+
+ storeChannel <- result
+ close(storeChannel)
+ }()
+
+ return storeChannel
+}
+
func (s SqlTeamStore) GetTeamsForUser(userId string) StoreChannel {
storeChannel := make(StoreChannel, 1)
diff --git a/store/sql_team_store_test.go b/store/sql_team_store_test.go
index be72786d3..46215d9be 100644
--- a/store/sql_team_store_test.go
+++ b/store/sql_team_store_test.go
@@ -298,7 +298,7 @@ func TestTeamMembers(t *testing.T) {
Must(store.Team().SaveMember(m2))
Must(store.Team().SaveMember(m3))
- if r1 := <-store.Team().GetMembers(teamId1); r1.Err != nil {
+ if r1 := <-store.Team().GetMembers(teamId1, 0, 100); r1.Err != nil {
t.Fatal(r1.Err)
} else {
ms := r1.Data.([]*model.TeamMember)
@@ -308,7 +308,7 @@ func TestTeamMembers(t *testing.T) {
}
}
- if r1 := <-store.Team().GetMembers(teamId2); r1.Err != nil {
+ if r1 := <-store.Team().GetMembers(teamId2, 0, 100); r1.Err != nil {
t.Fatal(r1.Err)
} else {
ms := r1.Data.([]*model.TeamMember)
@@ -342,7 +342,7 @@ func TestTeamMembers(t *testing.T) {
t.Fatal(r1.Err)
}
- if r1 := <-store.Team().GetMembers(teamId1); r1.Err != nil {
+ if r1 := <-store.Team().GetMembers(teamId1, 0, 100); r1.Err != nil {
t.Fatal(r1.Err)
} else {
ms := r1.Data.([]*model.TeamMember)
@@ -363,7 +363,7 @@ func TestTeamMembers(t *testing.T) {
t.Fatal(r1.Err)
}
- if r1 := <-store.Team().GetMembers(teamId1); r1.Err != nil {
+ if r1 := <-store.Team().GetMembers(teamId1, 0, 100); r1.Err != nil {
t.Fatal(r1.Err)
} else {
ms := r1.Data.([]*model.TeamMember)
@@ -434,3 +434,74 @@ func TestGetTeamMember(t *testing.T) {
t.Fatal("empty team id - should have failed")
}
}
+
+func TestGetTeamMembersByIds(t *testing.T) {
+ Setup()
+
+ teamId1 := model.NewId()
+
+ m1 := &model.TeamMember{TeamId: teamId1, UserId: model.NewId()}
+ Must(store.Team().SaveMember(m1))
+
+ if r := <-store.Team().GetMembersByIds(m1.TeamId, []string{m1.UserId}); r.Err != nil {
+ t.Fatal(r.Err)
+ } else {
+ rm1 := r.Data.([]*model.TeamMember)[0]
+
+ if rm1.TeamId != m1.TeamId {
+ t.Fatal("bad team id")
+ }
+
+ if rm1.UserId != m1.UserId {
+ t.Fatal("bad user id")
+ }
+ }
+
+ m2 := &model.TeamMember{TeamId: teamId1, UserId: model.NewId()}
+ Must(store.Team().SaveMember(m2))
+
+ if r := <-store.Team().GetMembersByIds(m1.TeamId, []string{m1.UserId, m2.UserId, model.NewId()}); r.Err != nil {
+ t.Fatal(r.Err)
+ } else {
+ rm := r.Data.([]*model.TeamMember)
+
+ if len(rm) != 2 {
+ t.Fatal("return wrong number of results")
+ }
+ }
+
+ if r := <-store.Team().GetMembersByIds(m1.TeamId, []string{}); r.Err == nil {
+ t.Fatal("empty user ids - should have failed")
+ }
+}
+
+func TestTeamStoreMemberCount(t *testing.T) {
+ Setup()
+
+ u1 := &model.User{}
+ u1.Email = model.NewId()
+ Must(store.User().Save(u1))
+
+ teamId1 := model.NewId()
+ m1 := &model.TeamMember{TeamId: teamId1, UserId: u1.Id}
+ Must(store.Team().SaveMember(m1))
+
+ if result := <-store.Team().GetMemberCount(teamId1); result.Err != nil {
+ t.Fatal(result.Err)
+ } else {
+ if result.Data.(int64) != 1 {
+ t.Fatal("wrong count")
+ }
+ }
+
+ m2 := &model.TeamMember{TeamId: teamId1, UserId: model.NewId()}
+ Must(store.Team().SaveMember(m2))
+
+ if result := <-store.Team().GetMemberCount(teamId1); result.Err != nil {
+ t.Fatal(result.Err)
+ } else {
+ if result.Data.(int64) != 1 {
+ t.Fatal("wrong count")
+ }
+ }
+}
diff --git a/store/sql_user_store.go b/store/sql_user_store.go
index 8ada9eb2c..ca86ef115 100644
--- a/store/sql_user_store.go
+++ b/store/sql_user_store.go
@@ -15,14 +15,20 @@ import (
)
const (
- MISSING_ACCOUNT_ERROR = "store.sql_user.missing_account.const"
- MISSING_AUTH_ACCOUNT_ERROR = "store.sql_user.get_by_auth.missing_account.app_error"
+ MISSING_ACCOUNT_ERROR = "store.sql_user.missing_account.const"
+ MISSING_AUTH_ACCOUNT_ERROR = "store.sql_user.get_by_auth.missing_account.app_error"
+ PROFILES_IN_CHANNEL_CACHE_SIZE = 5000
+ PROFILES_IN_CHANNEL_CACHE_SEC = 900 // 15 mins
+ USER_SEARCH_TYPE_ALL = "Username, FirstName, LastName, Nickname"
+ USER_SEARCH_TYPE_USERNAME = "Username"
)
type SqlUserStore struct {
*SqlStore
}
+var profilesInChannelCache *utils.Cache = utils.NewLru(PROFILES_IN_CHANNEL_CACHE_SIZE)
+
func NewSqlUserStore(sqlStore *SqlStore) UserStore {
us := &SqlUserStore{sqlStore}
@@ -49,6 +55,9 @@ func NewSqlUserStore(sqlStore *SqlStore) UserStore {
func (us SqlUserStore) CreateIndexesIfNotExists() {
us.CreateIndexIfNotExists("idx_users_email", "Users", "Email")
+
+ us.CreateFullTextIndexIfNotExists("idx_users_username_txt", "Users", USER_SEARCH_TYPE_USERNAME)
+ us.CreateFullTextIndexIfNotExists("idx_users_all_names_txt", "Users", USER_SEARCH_TYPE_ALL)
}
func (us SqlUserStore) Save(user *model.User) StoreChannel {
@@ -457,7 +466,7 @@ func (s SqlUserStore) GetEtagForAllProfiles() StoreChannel {
return storeChannel
}
-func (us SqlUserStore) GetAllProfiles() StoreChannel {
+func (us SqlUserStore) GetAllProfiles(offset int, limit int) StoreChannel {
storeChannel := make(StoreChannel, 1)
@@ -466,8 +475,8 @@ func (us SqlUserStore) GetAllProfiles() StoreChannel {
var users []*model.User
- if _, err := us.GetReplica().Select(&users, "SELECT * FROM Users"); err != nil {
- result.Err = model.NewLocAppError("SqlUserStore.GetProfiles", "store.sql_user.get_profiles.app_error", nil, err.Error())
+ if _, err := us.GetReplica().Select(&users, "SELECT * FROM Users ORDER BY Username ASC LIMIT :Limit OFFSET :Offset", map[string]interface{}{"Offset": offset, "Limit": limit}); err != nil {
+ result.Err = model.NewLocAppError("SqlUserStore.GetAllProfiles", "store.sql_user.get_profiles.app_error", nil, err.Error())
} else {
userMap := make(map[string]*model.User)
@@ -509,7 +518,7 @@ func (s SqlUserStore) GetEtagForProfiles(teamId string) StoreChannel {
return storeChannel
}
-func (us SqlUserStore) GetProfiles(teamId string) StoreChannel {
+func (us SqlUserStore) GetProfiles(teamId string, offset int, limit int) StoreChannel {
storeChannel := make(StoreChannel, 1)
@@ -518,7 +527,7 @@ func (us SqlUserStore) GetProfiles(teamId string) StoreChannel {
var users []*model.User
- if _, err := us.GetReplica().Select(&users, "SELECT Users.* FROM Users, TeamMembers WHERE TeamMembers.TeamId = :TeamId AND Users.Id = TeamMembers.UserId", map[string]interface{}{"TeamId": teamId}); err != nil {
+ if _, err := us.GetReplica().Select(&users, "SELECT Users.* FROM Users, TeamMembers WHERE TeamMembers.TeamId = :TeamId AND Users.Id = TeamMembers.UserId AND TeamMembers.DeleteAt = 0 ORDER BY Users.Username ASC LIMIT :Limit OFFSET :Offset", map[string]interface{}{"TeamId": teamId, "Offset": offset, "Limit": limit}); err != nil {
result.Err = model.NewLocAppError("SqlUserStore.GetProfiles", "store.sql_user.get_profiles.app_error", nil, err.Error())
} else {
@@ -541,9 +550,64 @@ func (us SqlUserStore) GetProfiles(teamId string) StoreChannel {
return storeChannel
}
-func (us SqlUserStore) GetDirectProfiles(userId string) StoreChannel {
+func (us SqlUserStore) InvalidateProfilesInChannelCache(channelId string) {
+ profilesInChannelCache.Remove(channelId)
+}
- storeChannel := make(StoreChannel, 1)
+func (us SqlUserStore) GetProfilesInChannel(channelId string, offset int, limit int, allowFromCache bool) StoreChannel {
+
+ storeChannel := make(StoreChannel)
+
+ go func() {
+ result := StoreResult{}
+
+ if allowFromCache && offset == -1 && limit == -1 {
+ if cacheItem, ok := profilesInChannelCache.Get(channelId); ok {
+ result.Data = cacheItem.(map[string]*model.User)
+ storeChannel <- result
+ close(storeChannel)
+ return
+ }
+ }
+
+ var users []*model.User
+
+ query := "SELECT Users.* FROM Users, ChannelMembers WHERE ChannelMembers.ChannelId = :ChannelId AND Users.Id = ChannelMembers.UserId AND Users.DeleteAt = 0"
+
+ if limit >= 0 && offset >= 0 {
+ query += " ORDER BY Users.Username ASC LIMIT :Limit OFFSET :Offset"
+ }
+
+ if _, err := us.GetReplica().Select(&users, query, map[string]interface{}{"ChannelId": channelId, "Offset": offset, "Limit": limit}); err != nil {
+ result.Err = model.NewLocAppError("SqlUserStore.GetProfilesInChannel", "store.sql_user.get_profiles.app_error", nil, err.Error())
+ } else {
+
+ userMap := make(map[string]*model.User)
+
+ for _, u := range users {
+ u.Password = ""
+ u.AuthData = new(string)
+ *u.AuthData = ""
+ userMap[u.Id] = u
+ }
+
+ result.Data = userMap
+
+ if allowFromCache && offset == -1 && limit == -1 {
+ profilesInChannelCache.AddWithExpiresInSecs(channelId, userMap, PROFILES_IN_CHANNEL_CACHE_SEC)
+ }
+ }
+
+ storeChannel <- result
+ close(storeChannel)
+ }()
+
+ return storeChannel
+}
+
+func (us SqlUserStore) GetProfilesNotInChannel(teamId string, channelId string, offset int, limit int) StoreChannel {
+
+ storeChannel := make(StoreChannel)
go func() {
result := StoreResult{}
@@ -551,35 +615,20 @@ func (us SqlUserStore) GetDirectProfiles(userId string) StoreChannel {
var users []*model.User
if _, err := us.GetReplica().Select(&users, `
- SELECT
- Users.*
- FROM
- Users
- WHERE
- Id IN (SELECT DISTINCT
- UserId
- FROM
- ChannelMembers
- WHERE
- ChannelMembers.UserId != :UserId
- AND ChannelMembers.ChannelId IN (SELECT
- Channels.Id
- FROM
- Channels,
- ChannelMembers
- WHERE
- Channels.Type = 'D'
- AND Channels.Id = ChannelMembers.ChannelId
- AND ChannelMembers.UserId = :UserId))
- OR Id IN (SELECT
- Name
- FROM
- Preferences
- WHERE
- UserId = :UserId
- AND Category = 'direct_channel_show')
- `, map[string]interface{}{"UserId": userId}); err != nil {
- result.Err = model.NewLocAppError("SqlUserStore.GetDirectProfiles", "store.sql_user.get_profiles.app_error", nil, err.Error())
+ SELECT
+ u.*
+ FROM Users u
+ INNER JOIN TeamMembers tm
+ ON tm.UserId = u.Id
+ AND tm.TeamId = :TeamId
+ LEFT JOIN ChannelMembers cm
+ ON cm.UserId = u.Id
+ AND cm.ChannelId = :ChannelId
+ WHERE cm.UserId IS NULL
+ ORDER BY u.Username ASC
+ LIMIT :Limit OFFSET :Offset
+ `, map[string]interface{}{"TeamId": teamId, "ChannelId": channelId, "Offset": offset, "Limit": limit}); err != nil {
+ result.Err = model.NewLocAppError("SqlUserStore.GetProfilesNotInChannel", "store.sql_user.get_profiles.app_error", nil, err.Error())
} else {
userMap := make(map[string]*model.User)
@@ -601,6 +650,99 @@ func (us SqlUserStore) GetDirectProfiles(userId string) StoreChannel {
return storeChannel
}
+func (us SqlUserStore) GetProfilesByUsernames(usernames []string, teamId string) StoreChannel {
+ storeChannel := make(StoreChannel)
+
+ go func() {
+ result := StoreResult{}
+
+ var users []*model.User
+ props := make(map[string]interface{})
+ idQuery := ""
+
+ for index, usernames := range usernames {
+ if len(idQuery) > 0 {
+ idQuery += ", "
+ }
+
+ props["username"+strconv.Itoa(index)] = usernames
+ idQuery += ":username" + strconv.Itoa(index)
+ }
+
+ props["TeamId"] = teamId
+
+ if _, err := us.GetReplica().Select(&users, `SELECT Users.* FROM Users INNER JOIN TeamMembers ON
+ Users.Id = TeamMembers.UserId AND Users.Username IN (`+idQuery+`) AND TeamMembers.TeamId = :TeamId `, props); err != nil {
+ result.Err = model.NewLocAppError("SqlUserStore.GetProfilesByUsernames", "store.sql_user.get_profiles.app_error", nil, err.Error())
+ } else {
+ userMap := make(map[string]*model.User)
+
+ for _, u := range users {
+ u.Password = ""
+ u.AuthData = new(string)
+ *u.AuthData = ""
+ userMap[u.Id] = u
+ }
+
+ result.Data = userMap
+ }
+
+ storeChannel <- result
+ close(storeChannel)
+ }()
+
+ return storeChannel
+}
+
+type UserWithLastActivityAt struct {
+ model.User
+ LastActivityAt int64
+}
+
+func (us SqlUserStore) GetRecentlyActiveUsersForTeam(teamId string) StoreChannel {
+
+ storeChannel := make(StoreChannel)
+
+ go func() {
+ result := StoreResult{}
+
+ var users []*UserWithLastActivityAt
+
+ if _, err := us.GetReplica().Select(&users, `
+ SELECT
+ u.*,
+ s.LastActivityAt
+ FROM Users AS u
+ INNER JOIN TeamMembers AS t ON u.Id = t.UserId
+ INNER JOIN Status AS s ON s.UserId = t.UserId
+ WHERE t.TeamId = :TeamId
+ ORDER BY s.LastActivityAt DESC
+ LIMIT 100
+ `, map[string]interface{}{"TeamId": teamId}); err != nil {
+ result.Err = model.NewLocAppError("SqlUserStore.GetRecentlyActiveUsers", "store.sql_user.get_recently_active_users.app_error", nil, err.Error())
+ } else {
+
+ userMap := make(map[string]*model.User)
+
+ for _, userWithLastActivityAt := range users {
+ u := userWithLastActivityAt.User
+ u.Password = ""
+ u.AuthData = new(string)
+ *u.AuthData = ""
+ u.LastActivityAt = userWithLastActivityAt.LastActivityAt
+ userMap[u.Id] = &u
+ }
+
+ result.Data = userMap
+ }
+
+ storeChannel <- result
+ close(storeChannel)
+ }()
+
+ return storeChannel
+}
+
func (us SqlUserStore) GetProfileByIds(userIds []string) StoreChannel {
storeChannel := make(StoreChannel, 1)
@@ -938,3 +1080,144 @@ func (us SqlUserStore) GetUnreadCountForChannel(userId string, channelId string)
return storeChannel
}
+
+func (us SqlUserStore) Search(teamId string, term string, searchType string) StoreChannel {
+ storeChannel := make(StoreChannel, 1)
+
+ go func() {
+ searchQuery := ""
+ if teamId == "" {
+ searchQuery = `
+ SELECT
+ *
+ FROM
+ Users
+ WHERE
+ DeleteAt = 0
+ SEARCH_CLAUSE
+ ORDER BY Username ASC
+ LIMIT 50`
+ } else {
+ searchQuery = `
+ SELECT
+ Users.*
+ FROM
+ Users, TeamMembers
+ WHERE
+ TeamMembers.TeamId = :TeamId
+ AND Users.Id = TeamMembers.UserId
+ AND Users.DeleteAt = 0
+ AND TeamMembers.DeleteAt = 0
+ SEARCH_CLAUSE
+ ORDER BY Users.Username ASC
+ LIMIT 100`
+ }
+
+ storeChannel <- us.performSearch(searchQuery, term, searchType, map[string]interface{}{"TeamId": teamId})
+ close(storeChannel)
+
+ }()
+
+ return storeChannel
+}
+
+func (us SqlUserStore) SearchNotInChannel(teamId string, channelId string, term string, searchType string) StoreChannel {
+ storeChannel := make(StoreChannel, 1)
+
+ go func() {
+ searchQuery := ""
+ if teamId == "" {
+ searchQuery = `
+ SELECT
+ u.*
+ FROM Users u
+ LEFT JOIN ChannelMembers cm
+ ON cm.UserId = u.Id
+ AND cm.ChannelId = :ChannelId
+ WHERE cm.UserId IS NULL
+ SEARCH_CLAUSE
+ ORDER BY u.Username ASC
+ LIMIT 100`
+ } else {
+ searchQuery = `
+ SELECT
+ u.*
+ FROM Users u
+ INNER JOIN TeamMembers tm
+ ON tm.UserId = u.Id
+ AND tm.TeamId = :TeamId
+ LEFT JOIN ChannelMembers cm
+ ON cm.UserId = u.Id
+ AND cm.ChannelId = :ChannelId
+ WHERE cm.UserId IS NULL
+ SEARCH_CLAUSE
+ ORDER BY u.Username ASC
+ LIMIT 100`
+ }
+
+ storeChannel <- us.performSearch(searchQuery, term, searchType, map[string]interface{}{"TeamId": teamId, "ChannelId": channelId})
+ close(storeChannel)
+
+ }()
+
+ return storeChannel
+}
+
+func (us SqlUserStore) SearchInChannel(channelId string, term string, searchType string) StoreChannel {
+ storeChannel := make(StoreChannel, 1)
+
+ go func() {
+ searchQuery := `
+ SELECT
+ Users.*
+ FROM
+ Users, ChannelMembers
+ WHERE
+ ChannelMembers.ChannelId = :ChannelId
+ AND ChannelMembers.UserId = Users.Id
+ AND Users.DeleteAt = 0
+ SEARCH_CLAUSE
+ ORDER BY Users.Username ASC
+ LIMIT 100`
+
+ storeChannel <- us.performSearch(searchQuery, term, searchType, map[string]interface{}{"ChannelId": channelId})
+ close(storeChannel)
+
+ }()
+
+ return storeChannel
+}
+
+func (us SqlUserStore) performSearch(searchQuery string, term string, searchType string, parameters map[string]interface{}) StoreResult {
+ result := StoreResult{}
+
+ if term == "" {
+ searchQuery = strings.Replace(searchQuery, "SEARCH_CLAUSE", "", 1)
+ } else if utils.Cfg.SqlSettings.DriverName == model.DATABASE_DRIVER_POSTGRES {
+ term = term + ":*"
+ searchClause := fmt.Sprintf("AND (%s) @@ to_tsquery(:Term)", searchType)
+ searchQuery = strings.Replace(searchQuery, "SEARCH_CLAUSE", searchClause, 1)
+ } else if utils.Cfg.SqlSettings.DriverName == model.DATABASE_DRIVER_MYSQL {
+ term = term + "*"
+ searchClause := fmt.Sprintf("AND MATCH(%s) AGAINST (:Term IN BOOLEAN MODE)", searchType)
+ searchQuery = strings.Replace(searchQuery, "SEARCH_CLAUSE", searchClause, 1)
+ }
+
+ var users []*model.User
+
+ parameters["Term"] = term
+
+ if _, err := us.GetReplica().Select(&users, searchQuery, parameters); err != nil {
+ result.Err = model.NewLocAppError("SqlUserStore.Search", "store.sql_user.search.app_error", nil, "term="+term+", "+"search_type="+searchType+", "+err.Error())
+ } else {
+ for _, u := range users {
+ u.Password = ""
+ u.AuthData = new(string)
+ *u.AuthData = ""
+ }
+
+ result.Data = users
+ }
+
+ return result
+}
diff --git a/store/sql_user_store_test.go b/store/sql_user_store_test.go
index 076be1a81..7ffb68a47 100644
--- a/store/sql_user_store_test.go
+++ b/store/sql_user_store_test.go
@@ -205,7 +205,7 @@ func TestUserStoreGetAllProfiles(t *testing.T) {
Must(store.User().Save(u2))
Must(store.Team().SaveMember(&model.TeamMember{TeamId: teamId, UserId: u2.Id}))
- if r1 := <-store.User().GetAllProfiles(); r1.Err != nil {
+ if r1 := <-store.User().GetAllProfiles(0, 100); r1.Err != nil {
t.Fatal(r1.Err)
} else {
users := r1.Data.(map[string]*model.User)
@@ -213,6 +213,15 @@ func TestUserStoreGetAllProfiles(t *testing.T) {
t.Fatal("invalid returned users")
}
}
+
+ if r2 := <-store.User().GetAllProfiles(0, 1); r2.Err != nil {
+ t.Fatal(r2.Err)
+ } else {
+ users := r2.Data.(map[string]*model.User)
+ if len(users) != 1 {
+ t.Fatal("invalid returned users, limit did not work")
+ }
+ }
}
func TestUserStoreGetProfiles(t *testing.T) {
@@ -230,7 +239,7 @@ func TestUserStoreGetProfiles(t *testing.T) {
Must(store.User().Save(u2))
Must(store.Team().SaveMember(&model.TeamMember{TeamId: teamId, UserId: u2.Id}))
- if r1 := <-store.User().GetProfiles(teamId); r1.Err != nil {
+ if r1 := <-store.User().GetProfiles(teamId, 0, 100); r1.Err != nil {
t.Fatal(r1.Err)
} else {
users := r1.Data.(map[string]*model.User)
@@ -243,7 +252,7 @@ func TestUserStoreGetProfiles(t *testing.T) {
}
}
- if r2 := <-store.User().GetProfiles("123"); r2.Err != nil {
+ if r2 := <-store.User().GetProfiles("123", 0, 100); r2.Err != nil {
t.Fatal(r2.Err)
} else {
if len(r2.Data.(map[string]*model.User)) != 0 {
@@ -252,7 +261,7 @@ func TestUserStoreGetProfiles(t *testing.T) {
}
}
-func TestUserStoreGetDirectProfiles(t *testing.T) {
+func TestUserStoreGetProfilesInChannel(t *testing.T) {
Setup()
teamId := model.NewId()
@@ -267,22 +276,166 @@ func TestUserStoreGetDirectProfiles(t *testing.T) {
Must(store.User().Save(u2))
Must(store.Team().SaveMember(&model.TeamMember{TeamId: teamId, UserId: u2.Id}))
- if r1 := <-store.User().GetDirectProfiles(u1.Id); r1.Err != nil {
+ c1 := model.Channel{}
+ c1.TeamId = teamId
+ c1.DisplayName = "Profiles in channel"
+ c1.Name = "profiles-" + model.NewId()
+ c1.Type = model.CHANNEL_OPEN
+
+ c2 := model.Channel{}
+ c2.TeamId = teamId
+ c2.DisplayName = "Profiles in private"
+ c2.Name = "profiles-" + model.NewId()
+ c2.Type = model.CHANNEL_PRIVATE
+
+ Must(store.Channel().Save(&c1))
+ Must(store.Channel().Save(&c2))
+
+ m1 := model.ChannelMember{}
+ m1.ChannelId = c1.Id
+ m1.UserId = u1.Id
+ m1.NotifyProps = model.GetDefaultChannelNotifyProps()
+
+ m2 := model.ChannelMember{}
+ m2.ChannelId = c1.Id
+ m2.UserId = u2.Id
+ m2.NotifyProps = model.GetDefaultChannelNotifyProps()
+
+ m3 := model.ChannelMember{}
+ m3.ChannelId = c2.Id
+ m3.UserId = u1.Id
+ m3.NotifyProps = model.GetDefaultChannelNotifyProps()
+
+ Must(store.Channel().SaveMember(&m1))
+ Must(store.Channel().SaveMember(&m2))
+ Must(store.Channel().SaveMember(&m3))
+
+ if r1 := <-store.User().GetProfilesInChannel(c1.Id, -1, -1, false); r1.Err != nil {
t.Fatal(r1.Err)
} else {
users := r1.Data.(map[string]*model.User)
- if len(users) != 0 {
+ if len(users) != 2 {
t.Fatal("invalid returned users")
}
+
+ if users[u1.Id].Id != u1.Id {
+ t.Fatal("invalid returned user")
+ }
}
- if r2 := <-store.User().GetDirectProfiles("123"); r2.Err != nil {
+ if r2 := <-store.User().GetProfilesInChannel(c2.Id, -1, -1, false); r2.Err != nil {
t.Fatal(r2.Err)
} else {
- if len(r2.Data.(map[string]*model.User)) != 0 {
+ if len(r2.Data.(map[string]*model.User)) != 1 {
+ t.Fatal("should have returned empty map")
+ }
+ }
+
+ if r2 := <-store.User().GetProfilesInChannel(c2.Id, -1, -1, true); r2.Err != nil {
+ t.Fatal(r2.Err)
+ } else {
+ if len(r2.Data.(map[string]*model.User)) != 1 {
+ t.Fatal("should have returned empty map")
+ }
+ }
+
+ if r2 := <-store.User().GetProfilesInChannel(c2.Id, -1, -1, true); r2.Err != nil {
+ t.Fatal(r2.Err)
+ } else {
+ if len(r2.Data.(map[string]*model.User)) != 1 {
t.Fatal("should have returned empty map")
}
}
+
+ store.User().InvalidateProfilesInChannelCache(c2.Id)
+}
+
+func TestUserStoreGetProfilesNotInChannel(t *testing.T) {
+ Setup()
+
+ teamId := model.NewId()
+
+ u1 := &model.User{}
+ u1.Email = model.NewId()
+ Must(store.User().Save(u1))
+ Must(store.Team().SaveMember(&model.TeamMember{TeamId: teamId, UserId: u1.Id}))
+
+ u2 := &model.User{}
+ u2.Email = model.NewId()
+ Must(store.User().Save(u2))
+ Must(store.Team().SaveMember(&model.TeamMember{TeamId: teamId, UserId: u2.Id}))
+
+ c1 := model.Channel{}
+ c1.TeamId = teamId
+ c1.DisplayName = "Profiles in channel"
+ c1.Name = "profiles-" + model.NewId()
+ c1.Type = model.CHANNEL_OPEN
+
+ c2 := model.Channel{}
+ c2.TeamId = teamId
+ c2.DisplayName = "Profiles in private"
+ c2.Name = "profiles-" + model.NewId()
+ c2.Type = model.CHANNEL_PRIVATE
+
+ Must(store.Channel().Save(&c1))
+ Must(store.Channel().Save(&c2))
+
+ if r1 := <-store.User().GetProfilesNotInChannel(teamId, c1.Id, 0, 100); r1.Err != nil {
+ t.Fatal(r1.Err)
+ } else {
+ users := r1.Data.(map[string]*model.User)
+ if len(users) != 2 {
+ t.Fatal("invalid returned users")
+ }
+
+ if users[u1.Id].Id != u1.Id {
+ t.Fatal("invalid returned user")
+ }
+ }
+
+ if r2 := <-store.User().GetProfilesNotInChannel(teamId, c2.Id, 0, 100); r2.Err != nil {
+ t.Fatal(r2.Err)
+ } else {
+ if len(r2.Data.(map[string]*model.User)) != 2 {
+ t.Fatal("invalid returned users")
+ }
+ }
+
+ m1 := model.ChannelMember{}
+ m1.ChannelId = c1.Id
+ m1.UserId = u1.Id
+ m1.NotifyProps = model.GetDefaultChannelNotifyProps()
+
+ m2 := model.ChannelMember{}
+ m2.ChannelId = c1.Id
+ m2.UserId = u2.Id
+ m2.NotifyProps = model.GetDefaultChannelNotifyProps()
+
+ m3 := model.ChannelMember{}
+ m3.ChannelId = c2.Id
+ m3.UserId = u1.Id
+ m3.NotifyProps = model.GetDefaultChannelNotifyProps()
+
+ Must(store.Channel().SaveMember(&m1))
+ Must(store.Channel().SaveMember(&m2))
+ Must(store.Channel().SaveMember(&m3))
+
+ if r1 := <-store.User().GetProfilesNotInChannel(teamId, c1.Id, 0, 100); r1.Err != nil {
+ t.Fatal(r1.Err)
+ } else {
+ users := r1.Data.(map[string]*model.User)
+ if len(users) != 0 {
+ t.Fatal("invalid returned users")
+ }
+ }
+
+ if r2 := <-store.User().GetProfilesNotInChannel(teamId, c2.Id, 0, 100); r2.Err != nil {
+ t.Fatal(r2.Err)
+ } else {
+ if len(r2.Data.(map[string]*model.User)) != 1 {
+ t.Fatal("should have had 1 user not in channel")
+ }
+ }
}
func TestUserStoreGetProfilesByIds(t *testing.T) {
@@ -326,7 +479,7 @@ func TestUserStoreGetProfilesByIds(t *testing.T) {
}
}
- if r2 := <-store.User().GetProfiles("123"); r2.Err != nil {
+ if r2 := <-store.User().GetProfiles("123", 0, 100); r2.Err != nil {
t.Fatal(r2.Err)
} else {
if len(r2.Data.(map[string]*model.User)) != 0 {
@@ -335,6 +488,50 @@ func TestUserStoreGetProfilesByIds(t *testing.T) {
}
}
+func TestUserStoreGetProfilesByUsernames(t *testing.T) {
+ Setup()
+
+ teamId := model.NewId()
+
+ u1 := &model.User{}
+ u1.Email = model.NewId()
+ u1.Username = "username1" + model.NewId()
+ Must(store.User().Save(u1))
+ Must(store.Team().SaveMember(&model.TeamMember{TeamId: teamId, UserId: u1.Id}))
+
+ u2 := &model.User{}
+ u2.Email = model.NewId()
+ u2.Username = "username2" + model.NewId()
+ Must(store.User().Save(u2))
+ Must(store.Team().SaveMember(&model.TeamMember{TeamId: teamId, UserId: u2.Id}))
+
+ if r1 := <-store.User().GetProfilesByUsernames([]string{u1.Username, u2.Username}, teamId); r1.Err != nil {
+ t.Fatal(r1.Err)
+ } else {
+ users := r1.Data.(map[string]*model.User)
+ if len(users) != 2 {
+ t.Fatal("invalid returned users")
+ }
+
+ if users[u1.Id].Id != u1.Id {
+ t.Fatal("invalid returned user")
+ }
+ }
+
+ if r1 := <-store.User().GetProfilesByUsernames([]string{u1.Username}, teamId); r1.Err != nil {
+ t.Fatal(r1.Err)
+ } else {
+ users := r1.Data.(map[string]*model.User)
+ if len(users) != 1 {
+ t.Fatal("invalid returned users")
+ }
+
+ if users[u1.Id].Id != u1.Id {
+ t.Fatal("invalid returned user")
+ }
+ }
+}
+
func TestUserStoreGetSystemAdminProfiles(t *testing.T) {
Setup()
@@ -713,3 +910,216 @@ func TestUserStoreUpdateMfaActive(t *testing.T) {
t.Fatal(err)
}
}
+
+func TestUserStoreGetRecentlyActiveUsersForTeam(t *testing.T) {
+ Setup()
+
+ u1 := &model.User{}
+ u1.Email = model.NewId()
+ Must(store.User().Save(u1))
+ Must(store.Status().SaveOrUpdate(&model.Status{u1.Id, model.STATUS_ONLINE, false, model.GetMillis(), ""}))
+ tid := model.NewId()
+ Must(store.Team().SaveMember(&model.TeamMember{TeamId: tid, UserId: u1.Id}))
+
+ if r1 := <-store.User().GetRecentlyActiveUsersForTeam(tid); r1.Err != nil {
+ t.Fatal(r1.Err)
+ }
+}
+
+func TestUserStoreSearch(t *testing.T) {
+ Setup()
+
+ u1 := &model.User{}
+ u1.Username = "jimbo" + model.NewId()
+ u1.FirstName = "Tim"
+ u1.LastName = "Bill"
+ u1.Nickname = "Rob"
+ u1.Email = model.NewId()
+ Must(store.User().Save(u1))
+
+ tid := model.NewId()
+ Must(store.Team().SaveMember(&model.TeamMember{TeamId: tid, UserId: u1.Id}))
+
+ if r1 := <-store.User().Search(tid, "jimb", USER_SEARCH_TYPE_USERNAME); r1.Err != nil {
+ t.Fatal(r1.Err)
+ } else {
+ profiles := r1.Data.([]*model.User)
+ found := false
+ for _, profile := range profiles {
+ if profile.Id == u1.Id {
+ found = true
+ break
+ }
+ }
+
+ if !found {
+ t.Fatal("should have found user")
+ }
+ }
+
+ if r1 := <-store.User().Search("", "jimb", USER_SEARCH_TYPE_USERNAME); r1.Err != nil {
+ t.Fatal(r1.Err)
+ } else {
+ profiles := r1.Data.([]*model.User)
+ found := false
+ for _, profile := range profiles {
+ if profile.Id == u1.Id {
+ found = true
+ break
+ }
+ }
+
+ if !found {
+ t.Fatal("should have found user")
+ }
+ }
+
+ if r1 := <-store.User().Search(tid, "", USER_SEARCH_TYPE_USERNAME); r1.Err != nil {
+ t.Fatal(r1.Err)
+ }
+
+ c1 := model.Channel{}
+ c1.TeamId = tid
+ c1.DisplayName = "NameName"
+ c1.Name = "a" + model.NewId() + "b"
+ c1.Type = model.CHANNEL_OPEN
+ c1 = *Must(store.Channel().Save(&c1)).(*model.Channel)
+
+ if r1 := <-store.User().SearchNotInChannel(tid, c1.Id, "jimb", USER_SEARCH_TYPE_USERNAME); r1.Err != nil {
+ t.Fatal(r1.Err)
+ } else {
+ profiles := r1.Data.([]*model.User)
+ found := false
+ for _, profile := range profiles {
+ if profile.Id == u1.Id {
+ found = true
+ break
+ }
+ }
+
+ if !found {
+ t.Fatal("should have found user")
+ }
+ }
+
+ if r1 := <-store.User().SearchNotInChannel("", c1.Id, "jimb", USER_SEARCH_TYPE_USERNAME); r1.Err != nil {
+ t.Fatal(r1.Err)
+ } else {
+ profiles := r1.Data.([]*model.User)
+ found := false
+ for _, profile := range profiles {
+ if profile.Id == u1.Id {
+ found = true
+ break
+ }
+ }
+
+ if !found {
+ t.Fatal("should have found user")
+ }
+ }
+
+ if r1 := <-store.User().SearchNotInChannel("junk", c1.Id, "jimb", USER_SEARCH_TYPE_USERNAME); r1.Err != nil {
+ t.Fatal(r1.Err)
+ } else {
+ profiles := r1.Data.([]*model.User)
+ found := false
+ for _, profile := range profiles {
+ if profile.Id == u1.Id {
+ found = true
+ break
+ }
+ }
+
+ if found {
+ t.Fatal("should not have found user")
+ }
+ }
+
+ if r1 := <-store.User().SearchInChannel(c1.Id, "jimb", USER_SEARCH_TYPE_USERNAME); r1.Err != nil {
+ t.Fatal(r1.Err)
+ } else {
+ profiles := r1.Data.([]*model.User)
+ found := false
+ for _, profile := range profiles {
+ if profile.Id == u1.Id {
+ found = true
+ break
+ }
+ }
+
+ if found {
+ t.Fatal("should not have found user")
+ }
+ }
+
+ Must(store.Channel().SaveMember(&model.ChannelMember{ChannelId: c1.Id, UserId: u1.Id, NotifyProps: model.GetDefaultChannelNotifyProps()}))
+
+ if r1 := <-store.User().SearchInChannel(c1.Id, "jimb", USER_SEARCH_TYPE_USERNAME); r1.Err != nil {
+ t.Fatal(r1.Err)
+ } else {
+ profiles := r1.Data.([]*model.User)
+ found := false
+ for _, profile := range profiles {
+ if profile.Id == u1.Id {
+ found = true
+ break
+ }
+ }
+
+ if !found {
+ t.Fatal("should have found user")
+ }
+ }
+
+ if r1 := <-store.User().Search(tid, "Tim", USER_SEARCH_TYPE_ALL); r1.Err != nil {
+ t.Fatal(r1.Err)
+ } else {
+ profiles := r1.Data.([]*model.User)
+ found := false
+ for _, profile := range profiles {
+ if profile.Id == u1.Id {
+ found = true
+ break
+ }
+ }
+
+ if !found {
+ t.Fatal("should have found user")
+ }
+ }
+
+ if r1 := <-store.User().Search(tid, "Bill", USER_SEARCH_TYPE_ALL); r1.Err != nil {
+ t.Fatal(r1.Err)
+ } else {
+ profiles := r1.Data.([]*model.User)
+ found := false
+ for _, profile := range profiles {
+ if profile.Id == u1.Id {
+ found = true
+ break
+ }
+ }
+
+ if !found {
+ t.Fatal("should have found user")
+ }
+ }
+
+ if r1 := <-store.User().Search(tid, "Rob", USER_SEARCH_TYPE_ALL); r1.Err != nil {
+ t.Fatal(r1.Err)
+ } else {
+ profiles := r1.Data.([]*model.User)
+ found := false
+ for _, profile := range profiles {
+ if profile.Id == u1.Id {
+ found = true
+ break
+ }
+ }
+
+ if !found {
+ t.Fatal("should have found user")
+ }
+ }
+}
diff --git a/store/store.go b/store/store.go
index 7474d3afb..900709f16 100644
--- a/store/store.go
+++ b/store/store.go
@@ -49,6 +49,8 @@ type Store interface {
MarkSystemRanUnitTests()
Close()
DropAllTables()
+ TotalMasterDbConnections() int
+ TotalReadDbConnections() int
}
type TeamStore interface {
@@ -66,7 +68,9 @@ type TeamStore interface {
SaveMember(member *model.TeamMember) StoreChannel
UpdateMember(member *model.TeamMember) StoreChannel
GetMember(teamId string, userId string) StoreChannel
- GetMembers(teamId string) StoreChannel
+ GetMembers(teamId string, offset int, limit int) StoreChannel
+ GetMembersByIds(teamId string, userIds []string) StoreChannel
+ GetMemberCount(teamId string) StoreChannel
GetTeamsForUser(userId string) StoreChannel
RemoveMember(teamId string, userId string) StoreChannel
RemoveAllMembersByTeam(teamId string) StoreChannel
@@ -89,16 +93,17 @@ type ChannelStore interface {
GetChannelCounts(teamId string, userId string) StoreChannel
GetAll(teamId string) StoreChannel
GetForPost(postId string) StoreChannel
-
SaveMember(member *model.ChannelMember) StoreChannel
UpdateMember(member *model.ChannelMember) StoreChannel
GetMembers(channelId string) StoreChannel
GetMember(channelId string, userId string) StoreChannel
+ GetAllChannelMembersForUser(userId string, allowFromCache bool) StoreChannel
+ InvalidateAllChannelMembersForUser(userId string)
+ IsUserInChannelUseCache(userId string, channelId string) bool
GetMemberForPost(postId string, userId string) StoreChannel
GetMemberCount(channelId string) StoreChannel
RemoveMember(channelId string, userId string) StoreChannel
PermanentDeleteMembersByUser(userId string) StoreChannel
- GetExtraMembers(channelId string, limit int) StoreChannel
UpdateLastViewedAt(channelId string, userId string) StoreChannel
SetLastViewedAt(channelId string, userId string, newLastViewedAt int64) StoreChannel
IncrementMentionCount(channelId string, userId string) StoreChannel
@@ -135,9 +140,12 @@ type UserStore interface {
UpdateMfaActive(userId string, active bool) StoreChannel
Get(id string) StoreChannel
GetAll() StoreChannel
- GetAllProfiles() StoreChannel
- GetProfiles(teamId string) StoreChannel
- GetDirectProfiles(userId string) StoreChannel
+ InvalidateProfilesInChannelCache(channelId string)
+ GetProfilesInChannel(channelId string, offset int, limit int, allowFromCache bool) StoreChannel
+ GetProfilesNotInChannel(teamId string, channelId string, offset int, limit int) StoreChannel
+ GetProfilesByUsernames(usernames []string, teamId string) StoreChannel
+ GetAllProfiles(offset int, limit int) StoreChannel
+ GetProfiles(teamId string, offset int, limit int) StoreChannel
GetProfileByIds(userId []string) StoreChannel
GetByEmail(email string) StoreChannel
GetByAuth(authData *string, authService string) StoreChannel
@@ -147,7 +155,6 @@ type UserStore interface {
VerifyEmail(userId string) StoreChannel
GetEtagForAllProfiles() StoreChannel
GetEtagForProfiles(teamId string) StoreChannel
- GetEtagForDirectProfiles(userId string) StoreChannel
UpdateFailedPasswordAttempts(userId string, attempts int) StoreChannel
GetTotalUsersCount() StoreChannel
GetSystemAdminProfiles() StoreChannel
@@ -155,6 +162,10 @@ type UserStore interface {
AnalyticsUniqueUserCount(teamId string) StoreChannel
GetUnreadCount(userId string) StoreChannel
GetUnreadCountForChannel(userId string, channelId string) StoreChannel
+ GetRecentlyActiveUsersForTeam(teamId string) StoreChannel
+ Search(teamId string, term string, searchType string) StoreChannel
+ SearchInChannel(channelId string, term string, searchType string) StoreChannel
+ SearchNotInChannel(teamId string, channelId string, term string, searchType string) StoreChannel
}
type SessionStore interface {
@@ -274,6 +285,7 @@ type EmojiStore interface {
type StatusStore interface {
SaveOrUpdate(status *model.Status) StoreChannel
Get(userId string) StoreChannel
+ GetByIds(userIds []string) StoreChannel
GetOnlineAway() StoreChannel
GetOnline() StoreChannel
GetAllFromTeam(teamId string) StoreChannel