github.com/wgh-/mattermost-server@v4.8.0-rc2+incompatible/store/sqlstore/channel_member_history_store.go (about) 1 // Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved. 2 // See License.txt for license information. 3 4 package sqlstore 5 6 import ( 7 "net/http" 8 9 "database/sql" 10 11 l4g "github.com/alecthomas/log4go" 12 "github.com/mattermost/mattermost-server/model" 13 "github.com/mattermost/mattermost-server/store" 14 ) 15 16 type SqlChannelMemberHistoryStore struct { 17 SqlStore 18 } 19 20 func NewSqlChannelMemberHistoryStore(sqlStore SqlStore) store.ChannelMemberHistoryStore { 21 s := &SqlChannelMemberHistoryStore{ 22 SqlStore: sqlStore, 23 } 24 25 for _, db := range sqlStore.GetAllConns() { 26 table := db.AddTableWithName(model.ChannelMemberHistory{}, "ChannelMemberHistory").SetKeys(false, "ChannelId", "UserId", "JoinTime") 27 table.ColMap("ChannelId").SetMaxSize(26) 28 table.ColMap("UserId").SetMaxSize(26) 29 table.ColMap("JoinTime").SetNotNull(true) 30 } 31 32 return s 33 } 34 35 func (s SqlChannelMemberHistoryStore) LogJoinEvent(userId string, channelId string, joinTime int64) store.StoreChannel { 36 return store.Do(func(result *store.StoreResult) { 37 channelMemberHistory := &model.ChannelMemberHistory{ 38 UserId: userId, 39 ChannelId: channelId, 40 JoinTime: joinTime, 41 } 42 43 if err := s.GetMaster().Insert(channelMemberHistory); err != nil { 44 result.Err = model.NewAppError("SqlChannelMemberHistoryStore.LogJoinEvent", "store.sql_channel_member_history.log_join_event.app_error", nil, err.Error(), http.StatusInternalServerError) 45 } 46 }) 47 } 48 49 func (s SqlChannelMemberHistoryStore) LogLeaveEvent(userId string, channelId string, leaveTime int64) store.StoreChannel { 50 return store.Do(func(result *store.StoreResult) { 51 query := ` 52 UPDATE ChannelMemberHistory 53 SET LeaveTime = :LeaveTime 54 WHERE UserId = :UserId 55 AND ChannelId = :ChannelId 56 AND LeaveTime IS NULL` 57 58 params := map[string]interface{}{"UserId": userId, "ChannelId": channelId, "LeaveTime": leaveTime} 59 if sqlResult, err := s.GetMaster().Exec(query, params); err != nil { 60 result.Err = model.NewAppError("SqlChannelMemberHistoryStore.LogLeaveEvent", "store.sql_channel_member_history.log_leave_event.update_error", params, err.Error(), http.StatusInternalServerError) 61 } else if rows, err := sqlResult.RowsAffected(); err == nil && rows != 1 { 62 // there was no join event to update - this is best effort, so no need to raise an error 63 l4g.Warn("Channel join event for user %v and channel %v not found", userId, channelId) 64 } 65 }) 66 } 67 68 func (s SqlChannelMemberHistoryStore) GetUsersInChannelDuring(startTime int64, endTime int64, channelId string) store.StoreChannel { 69 return store.Do(func(result *store.StoreResult) { 70 if useChannelMemberHistory, err := s.hasDataAtOrBefore(startTime); err != nil { 71 result.Err = model.NewAppError("SqlChannelMemberHistoryStore.GetUsersInChannelAt", "store.sql_channel_member_history.get_users_in_channel_during.app_error", nil, err.Error(), http.StatusInternalServerError) 72 } else if useChannelMemberHistory { 73 // the export period starts after the ChannelMemberHistory table was first introduced, so we can use the 74 // data from it for our export 75 if channelMemberHistories, err := s.getFromChannelMemberHistoryTable(startTime, endTime, channelId); err != nil { 76 result.Err = model.NewAppError("SqlChannelMemberHistoryStore.GetUsersInChannelAt", "store.sql_channel_member_history.get_users_in_channel_during.app_error", nil, err.Error(), http.StatusInternalServerError) 77 } else { 78 result.Data = channelMemberHistories 79 } 80 } else { 81 // the export period starts before the ChannelMemberHistory table was introduced, so we need to fake the 82 // data by assuming that anybody who has ever joined the channel in question was present during the export period. 83 // this may not always be true, but it's better than saying that somebody wasn't there when they were 84 if channelMemberHistories, err := s.getFromChannelMembersTable(startTime, endTime, channelId); err != nil { 85 result.Err = model.NewAppError("SqlChannelMemberHistoryStore.GetUsersInChannelAt", "store.sql_channel_member_history.get_users_in_channel_during.app_error", nil, err.Error(), http.StatusInternalServerError) 86 } else { 87 result.Data = channelMemberHistories 88 } 89 } 90 }) 91 } 92 93 func (s SqlChannelMemberHistoryStore) hasDataAtOrBefore(time int64) (bool, error) { 94 type NullableCountResult struct { 95 Min sql.NullInt64 96 } 97 var result NullableCountResult 98 query := "SELECT MIN(JoinTime) AS Min FROM ChannelMemberHistory" 99 if err := s.GetReplica().SelectOne(&result, query); err != nil { 100 return false, err 101 } else if result.Min.Valid { 102 return result.Min.Int64 <= time, nil 103 } else { 104 // if the result was null, there are no rows in the table, so there is no data from before 105 return false, nil 106 } 107 } 108 109 func (s SqlChannelMemberHistoryStore) getFromChannelMemberHistoryTable(startTime int64, endTime int64, channelId string) ([]*model.ChannelMemberHistoryResult, error) { 110 query := ` 111 SELECT 112 cmh.*, 113 u.Email, 114 u.Username 115 FROM ChannelMemberHistory cmh 116 INNER JOIN Users u ON cmh.UserId = u.Id 117 WHERE cmh.ChannelId = :ChannelId 118 AND cmh.JoinTime <= :EndTime 119 AND (cmh.LeaveTime IS NULL OR cmh.LeaveTime >= :StartTime) 120 ORDER BY cmh.JoinTime ASC` 121 122 params := map[string]interface{}{"ChannelId": channelId, "StartTime": startTime, "EndTime": endTime} 123 var histories []*model.ChannelMemberHistoryResult 124 if _, err := s.GetReplica().Select(&histories, query, params); err != nil { 125 return nil, err 126 } else { 127 return histories, nil 128 } 129 } 130 131 func (s SqlChannelMemberHistoryStore) getFromChannelMembersTable(startTime int64, endTime int64, channelId string) ([]*model.ChannelMemberHistoryResult, error) { 132 query := ` 133 SELECT DISTINCT 134 ch.ChannelId, 135 ch.UserId, 136 u.Email, 137 u.Username 138 FROM ChannelMembers AS ch 139 INNER JOIN Users AS u ON ch.UserId = u.id 140 WHERE ch.ChannelId = :ChannelId` 141 142 params := map[string]interface{}{"ChannelId": channelId} 143 var histories []*model.ChannelMemberHistoryResult 144 if _, err := s.GetReplica().Select(&histories, query, params); err != nil { 145 return nil, err 146 } else { 147 // we have to fill in the join/leave times, because that data doesn't exist in the channel members table 148 for _, channelMemberHistory := range histories { 149 channelMemberHistory.JoinTime = startTime 150 channelMemberHistory.LeaveTime = model.NewInt64(endTime) 151 } 152 return histories, nil 153 } 154 } 155 156 func (s SqlChannelMemberHistoryStore) PermanentDeleteBatch(endTime int64, limit int64) store.StoreChannel { 157 return store.Do(func(result *store.StoreResult) { 158 var query string 159 if s.DriverName() == model.DATABASE_DRIVER_POSTGRES { 160 query = 161 `DELETE FROM ChannelMemberHistory 162 WHERE ctid IN ( 163 SELECT ctid FROM ChannelMemberHistory 164 WHERE LeaveTime IS NOT NULL 165 AND LeaveTime <= :EndTime 166 LIMIT :Limit 167 );` 168 } else { 169 query = 170 `DELETE FROM ChannelMemberHistory 171 WHERE LeaveTime IS NOT NULL 172 AND LeaveTime <= :EndTime 173 LIMIT :Limit` 174 } 175 176 params := map[string]interface{}{"EndTime": endTime, "Limit": limit} 177 if sqlResult, err := s.GetMaster().Exec(query, params); err != nil { 178 result.Err = model.NewAppError("SqlChannelMemberHistoryStore.PermanentDeleteBatchForChannel", "store.sql_channel_member_history.permanent_delete_batch.app_error", params, err.Error(), http.StatusInternalServerError) 179 } else { 180 if rowsAffected, err1 := sqlResult.RowsAffected(); err1 != nil { 181 result.Err = model.NewAppError("SqlChannelMemberHistoryStore.PermanentDeleteBatchForChannel", "store.sql_channel_member_history.permanent_delete_batch.app_error", params, err.Error(), http.StatusInternalServerError) 182 } else { 183 result.Data = rowsAffected 184 } 185 } 186 }) 187 }