Files
mostlymatter/store/sql_team_store.go
Joram Wilander 365b8b465e 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
2016-10-19 14:49:25 -04:00

604 строки
17 KiB
Go

// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package store
import (
"database/sql"
"strconv"
"github.com/mattermost/platform/model"
"github.com/mattermost/platform/utils"
)
const (
TEAM_MEMBER_EXISTS_ERROR = "store.sql_team.save_member.exists.app_error"
)
type SqlTeamStore struct {
*SqlStore
}
func NewSqlTeamStore(sqlStore *SqlStore) TeamStore {
s := &SqlTeamStore{sqlStore}
for _, db := range sqlStore.GetAllConns() {
table := db.AddTableWithName(model.Team{}, "Teams").SetKeys(false, "Id")
table.ColMap("Id").SetMaxSize(26)
table.ColMap("DisplayName").SetMaxSize(64)
table.ColMap("Name").SetMaxSize(64).SetUnique(true)
table.ColMap("Email").SetMaxSize(128)
table.ColMap("CompanyName").SetMaxSize(64)
table.ColMap("AllowedDomains").SetMaxSize(500)
table.ColMap("InviteId").SetMaxSize(32)
tablem := db.AddTableWithName(model.TeamMember{}, "TeamMembers").SetKeys(false, "TeamId", "UserId")
tablem.ColMap("TeamId").SetMaxSize(26)
tablem.ColMap("UserId").SetMaxSize(26)
tablem.ColMap("Roles").SetMaxSize(64)
}
return s
}
func (s SqlTeamStore) CreateIndexesIfNotExists() {
s.CreateIndexIfNotExists("idx_teams_name", "Teams", "Name")
s.CreateIndexIfNotExists("idx_teams_invite_id", "Teams", "InviteId")
s.CreateIndexIfNotExists("idx_teammembers_team_id", "TeamMembers", "TeamId")
s.CreateIndexIfNotExists("idx_teammembers_user_id", "TeamMembers", "UserId")
}
func (s SqlTeamStore) Save(team *model.Team) StoreChannel {
storeChannel := make(StoreChannel, 1)
go func() {
result := StoreResult{}
if len(team.Id) > 0 {
result.Err = model.NewLocAppError("SqlTeamStore.Save",
"store.sql_team.save.existing.app_error", nil, "id="+team.Id)
storeChannel <- result
close(storeChannel)
return
}
team.PreSave()
if result.Err = team.IsValid(*utils.Cfg.TeamSettings.RestrictTeamNames); result.Err != nil {
storeChannel <- result
close(storeChannel)
return
}
if err := s.GetMaster().Insert(team); err != nil {
if IsUniqueConstraintError(err.Error(), []string{"Name", "teams_name_key"}) {
result.Err = model.NewLocAppError("SqlTeamStore.Save", "store.sql_team.save.domain_exists.app_error", nil, "id="+team.Id+", "+err.Error())
} else {
result.Err = model.NewLocAppError("SqlTeamStore.Save", "store.sql_team.save.app_error", nil, "id="+team.Id+", "+err.Error())
}
} else {
result.Data = team
}
storeChannel <- result
close(storeChannel)
}()
return storeChannel
}
func (s SqlTeamStore) Update(team *model.Team) StoreChannel {
storeChannel := make(StoreChannel, 1)
go func() {
result := StoreResult{}
team.PreUpdate()
if result.Err = team.IsValid(*utils.Cfg.TeamSettings.RestrictTeamNames); result.Err != nil {
storeChannel <- result
close(storeChannel)
return
}
if oldResult, err := s.GetMaster().Get(model.Team{}, team.Id); err != nil {
result.Err = model.NewLocAppError("SqlTeamStore.Update", "store.sql_team.update.finding.app_error", nil, "id="+team.Id+", "+err.Error())
} else if oldResult == nil {
result.Err = model.NewLocAppError("SqlTeamStore.Update", "store.sql_team.update.find.app_error", nil, "id="+team.Id)
} else {
oldTeam := oldResult.(*model.Team)
team.CreateAt = oldTeam.CreateAt
team.UpdateAt = model.GetMillis()
team.Name = oldTeam.Name
if count, err := s.GetMaster().Update(team); err != nil {
result.Err = model.NewLocAppError("SqlTeamStore.Update", "store.sql_team.update.updating.app_error", nil, "id="+team.Id+", "+err.Error())
} else if count != 1 {
result.Err = model.NewLocAppError("SqlTeamStore.Update", "store.sql_team.update.app_error", nil, "id="+team.Id)
} else {
result.Data = team
}
}
storeChannel <- result
close(storeChannel)
}()
return storeChannel
}
func (s SqlTeamStore) UpdateDisplayName(name string, teamId string) StoreChannel {
storeChannel := make(StoreChannel, 1)
go func() {
result := StoreResult{}
if _, err := s.GetMaster().Exec("UPDATE Teams SET DisplayName = :Name WHERE Id = :Id", map[string]interface{}{"Name": name, "Id": teamId}); err != nil {
result.Err = model.NewLocAppError("SqlTeamStore.UpdateName", "store.sql_team.update_display_name.app_error", nil, "team_id="+teamId)
} else {
result.Data = teamId
}
storeChannel <- result
close(storeChannel)
}()
return storeChannel
}
func (s SqlTeamStore) Get(id string) StoreChannel {
storeChannel := make(StoreChannel, 1)
go func() {
result := StoreResult{}
if obj, err := s.GetReplica().Get(model.Team{}, id); err != nil {
result.Err = model.NewLocAppError("SqlTeamStore.Get", "store.sql_team.get.finding.app_error", nil, "id="+id+", "+err.Error())
} else if obj == nil {
result.Err = model.NewLocAppError("SqlTeamStore.Get", "store.sql_team.get.find.app_error", nil, "id="+id)
} else {
team := obj.(*model.Team)
if len(team.InviteId) == 0 {
team.InviteId = team.Id
}
result.Data = team
}
storeChannel <- result
close(storeChannel)
}()
return storeChannel
}
func (s SqlTeamStore) GetByInviteId(inviteId string) StoreChannel {
storeChannel := make(StoreChannel, 1)
go func() {
result := StoreResult{}
team := model.Team{}
if err := s.GetReplica().SelectOne(&team, "SELECT * FROM Teams WHERE Id = :InviteId OR InviteId = :InviteId", map[string]interface{}{"InviteId": inviteId}); err != nil {
result.Err = model.NewLocAppError("SqlTeamStore.GetByInviteId", "store.sql_team.get_by_invite_id.finding.app_error", nil, "inviteId="+inviteId+", "+err.Error())
}
if len(team.InviteId) == 0 {
team.InviteId = team.Id
}
if len(inviteId) == 0 || team.InviteId != inviteId {
result.Err = model.NewLocAppError("SqlTeamStore.GetByInviteId", "store.sql_team.get_by_invite_id.find.app_error", nil, "inviteId="+inviteId)
}
result.Data = &team
storeChannel <- result
close(storeChannel)
}()
return storeChannel
}
func (s SqlTeamStore) GetByName(name string) StoreChannel {
storeChannel := make(StoreChannel, 1)
go func() {
result := StoreResult{}
team := model.Team{}
if err := s.GetReplica().SelectOne(&team, "SELECT * FROM Teams WHERE Name = :Name", map[string]interface{}{"Name": name}); err != nil {
result.Err = model.NewLocAppError("SqlTeamStore.GetByName", "store.sql_team.get_by_name.app_error", nil, "name="+name+", "+err.Error())
}
if len(team.InviteId) == 0 {
team.InviteId = team.Id
}
result.Data = &team
storeChannel <- result
close(storeChannel)
}()
return storeChannel
}
func (s SqlTeamStore) GetAll() StoreChannel {
storeChannel := make(StoreChannel, 1)
go func() {
result := StoreResult{}
var data []*model.Team
if _, err := s.GetReplica().Select(&data, "SELECT * FROM Teams"); err != nil {
result.Err = model.NewLocAppError("SqlTeamStore.GetAllTeams", "store.sql_team.get_all.app_error", nil, err.Error())
}
for _, team := range data {
if len(team.InviteId) == 0 {
team.InviteId = team.Id
}
}
result.Data = data
storeChannel <- result
close(storeChannel)
}()
return storeChannel
}
func (s SqlTeamStore) GetTeamsByUserId(userId string) StoreChannel {
storeChannel := make(StoreChannel, 1)
go func() {
result := StoreResult{}
var data []*model.Team
if _, err := s.GetReplica().Select(&data, "SELECT Teams.* FROM Teams, TeamMembers WHERE TeamMembers.TeamId = Teams.Id AND TeamMembers.UserId = :UserId AND TeamMembers.DeleteAt = 0 AND Teams.DeleteAt = 0", map[string]interface{}{"UserId": userId}); err != nil {
result.Err = model.NewLocAppError("SqlTeamStore.GetTeamsByUserId", "store.sql_team.get_all.app_error", nil, err.Error())
}
for _, team := range data {
if len(team.InviteId) == 0 {
team.InviteId = team.Id
}
}
result.Data = data
storeChannel <- result
close(storeChannel)
}()
return storeChannel
}
func (s SqlTeamStore) GetAllTeamListing() StoreChannel {
storeChannel := make(StoreChannel, 1)
go func() {
result := StoreResult{}
query := "SELECT * FROM Teams WHERE AllowOpenInvite = 1"
if utils.Cfg.SqlSettings.DriverName == model.DATABASE_DRIVER_POSTGRES {
query = "SELECT * FROM Teams WHERE AllowOpenInvite = true"
}
var data []*model.Team
if _, err := s.GetReplica().Select(&data, query); err != nil {
result.Err = model.NewLocAppError("SqlTeamStore.GetAllTeams", "store.sql_team.get_all_team_listing.app_error", nil, err.Error())
}
for _, team := range data {
if len(team.InviteId) == 0 {
team.InviteId = team.Id
}
}
result.Data = data
storeChannel <- result
close(storeChannel)
}()
return storeChannel
}
func (s SqlTeamStore) PermanentDelete(teamId string) StoreChannel {
storeChannel := make(StoreChannel, 1)
go func() {
result := StoreResult{}
if _, err := s.GetMaster().Exec("DELETE FROM Teams WHERE Id = :TeamId", map[string]interface{}{"TeamId": teamId}); err != nil {
result.Err = model.NewLocAppError("SqlTeamStore.Delete", "store.sql_team.permanent_delete.app_error", nil, "teamId="+teamId+", "+err.Error())
}
storeChannel <- result
close(storeChannel)
}()
return storeChannel
}
func (s SqlTeamStore) AnalyticsTeamCount() StoreChannel {
storeChannel := make(StoreChannel, 1)
go func() {
result := StoreResult{}
if c, err := s.GetReplica().SelectInt("SELECT COUNT(*) FROM Teams WHERE DeleteAt = 0", map[string]interface{}{}); err != nil {
result.Err = model.NewLocAppError("SqlTeamStore.AnalyticsTeamCount", "store.sql_team.analytics_team_count.app_error", nil, err.Error())
} else {
result.Data = c
}
storeChannel <- result
close(storeChannel)
}()
return storeChannel
}
func (s SqlTeamStore) SaveMember(member *model.TeamMember) StoreChannel {
storeChannel := make(StoreChannel, 1)
go func() {
result := StoreResult{}
if result.Err = member.IsValid(); result.Err != nil {
storeChannel <- result
close(storeChannel)
return
}
if count, err := s.GetMaster().SelectInt("SELECT COUNT(0) FROM TeamMembers WHERE TeamId = :TeamId", map[string]interface{}{"TeamId": member.TeamId}); err != nil {
result.Err = model.NewLocAppError("SqlUserStore.Save", "store.sql_user.save.member_count.app_error", nil, "teamId="+member.TeamId+", "+err.Error())
storeChannel <- result
close(storeChannel)
return
} else if int(count) > utils.Cfg.TeamSettings.MaxUsersPerTeam {
result.Err = model.NewLocAppError("SqlUserStore.Save", "store.sql_user.save.max_accounts.app_error", nil, "teamId="+member.TeamId)
storeChannel <- result
close(storeChannel)
return
}
if err := s.GetMaster().Insert(member); err != nil {
if IsUniqueConstraintError(err.Error(), []string{"TeamId", "teammembers_pkey", "PRIMARY"}) {
result.Err = model.NewLocAppError("SqlTeamStore.SaveMember", TEAM_MEMBER_EXISTS_ERROR, nil, "team_id="+member.TeamId+", user_id="+member.UserId+", "+err.Error())
} else {
result.Err = model.NewLocAppError("SqlTeamStore.SaveMember", "store.sql_team.save_member.save.app_error", nil, "team_id="+member.TeamId+", user_id="+member.UserId+", "+err.Error())
}
} else {
result.Data = member
}
storeChannel <- result
close(storeChannel)
}()
return storeChannel
}
func (s SqlTeamStore) UpdateMember(member *model.TeamMember) StoreChannel {
storeChannel := make(StoreChannel, 1)
go func() {
result := StoreResult{}
member.PreUpdate()
if result.Err = member.IsValid(); result.Err != nil {
storeChannel <- result
close(storeChannel)
return
}
if _, err := s.GetMaster().Update(member); err != nil {
result.Err = model.NewLocAppError("SqlTeamStore.UpdateMember", "store.sql_team.save_member.save.app_error", nil, err.Error())
} else {
result.Data = member
}
storeChannel <- result
close(storeChannel)
}()
return storeChannel
}
func (s SqlTeamStore) GetMember(teamId string, userId string) StoreChannel {
storeChannel := make(StoreChannel, 1)
go func() {
result := StoreResult{}
var member model.TeamMember
err := s.GetReplica().SelectOne(&member, "SELECT * FROM TeamMembers WHERE TeamId = :TeamId AND UserId = :UserId", map[string]interface{}{"TeamId": teamId, "UserId": userId})
if err != nil {
if err == sql.ErrNoRows {
result.Err = model.NewLocAppError("SqlTeamStore.GetMember", "store.sql_team.get_member.missing.app_error", nil, "teamId="+teamId+" userId="+userId+" "+err.Error())
} else {
result.Err = model.NewLocAppError("SqlTeamStore.GetMember", "store.sql_team.get_member.app_error", nil, "teamId="+teamId+" userId="+userId+" "+err.Error())
}
} else {
result.Data = member
}
storeChannel <- result
close(storeChannel)
}()
return 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 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 {
result.Data = members
}
storeChannel <- result
close(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)
go func() {
result := StoreResult{}
var members []*model.TeamMember
_, err := s.GetReplica().Select(&members, "SELECT * FROM TeamMembers WHERE UserId = :UserId", map[string]interface{}{"UserId": userId})
if err != nil {
result.Err = model.NewLocAppError("SqlTeamStore.GetMembers", "store.sql_team.get_members.app_error", nil, "userId="+userId+" "+err.Error())
} else {
result.Data = members
}
storeChannel <- result
close(storeChannel)
}()
return storeChannel
}
func (s SqlTeamStore) RemoveMember(teamId string, userId string) StoreChannel {
storeChannel := make(StoreChannel, 1)
go func() {
result := StoreResult{}
_, err := s.GetMaster().Exec("DELETE FROM TeamMembers WHERE TeamId = :TeamId AND UserId = :UserId", map[string]interface{}{"TeamId": teamId, "UserId": userId})
if err != nil {
result.Err = model.NewLocAppError("SqlChannelStore.RemoveMember", "store.sql_team.remove_member.app_error", nil, "team_id="+teamId+", user_id="+userId+", "+err.Error())
}
storeChannel <- result
close(storeChannel)
}()
return storeChannel
}
func (s SqlTeamStore) RemoveAllMembersByTeam(teamId string) StoreChannel {
storeChannel := make(StoreChannel, 1)
go func() {
result := StoreResult{}
_, err := s.GetMaster().Exec("DELETE FROM TeamMembers WHERE TeamId = :TeamId", map[string]interface{}{"TeamId": teamId})
if err != nil {
result.Err = model.NewLocAppError("SqlChannelStore.RemoveMember", "store.sql_team.remove_member.app_error", nil, "team_id="+teamId+", "+err.Error())
}
storeChannel <- result
close(storeChannel)
}()
return storeChannel
}
func (s SqlTeamStore) RemoveAllMembersByUser(userId string) StoreChannel {
storeChannel := make(StoreChannel, 1)
go func() {
result := StoreResult{}
_, err := s.GetMaster().Exec("DELETE FROM TeamMembers WHERE UserId = :UserId", map[string]interface{}{"UserId": userId})
if err != nil {
result.Err = model.NewLocAppError("SqlChannelStore.RemoveMember", "store.sql_team.remove_member.app_error", nil, "user_id="+userId+", "+err.Error())
}
storeChannel <- result
close(storeChannel)
}()
return storeChannel
}