github.com/psyb0t/mattermost-server@v4.6.1-0.20180125161845-5503a1351abf+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.ChannelMemberHistory, error) { 110 query := ` 111 SELECT 112 cmh.*, 113 u.Email 114 FROM ChannelMemberHistory cmh 115 INNER JOIN Users u ON cmh.UserId = u.Id 116 WHERE cmh.ChannelId = :ChannelId 117 AND cmh.JoinTime <= :EndTime 118 AND (cmh.LeaveTime IS NULL OR cmh.LeaveTime >= :StartTime) 119 ORDER BY cmh.JoinTime ASC` 120 121 params := map[string]interface{}{"ChannelId": channelId, "StartTime": startTime, "EndTime": endTime} 122 var histories []*model.ChannelMemberHistory 123 if _, err := s.GetReplica().Select(&histories, query, params); err != nil { 124 return nil, err 125 } else { 126 return histories, nil 127 } 128 } 129 130 func (s SqlChannelMemberHistoryStore) getFromChannelMembersTable(startTime int64, endTime int64, channelId string) ([]*model.ChannelMemberHistory, error) { 131 query := ` 132 SELECT DISTINCT 133 ch.ChannelId, 134 ch.UserId, 135 u.email 136 FROM ChannelMembers AS ch 137 INNER JOIN Users AS u ON ch.UserId = u.id 138 WHERE ch.ChannelId = :ChannelId` 139 140 params := map[string]interface{}{"ChannelId": channelId} 141 var histories []*model.ChannelMemberHistory 142 if _, err := s.GetReplica().Select(&histories, query, params); err != nil { 143 return nil, err 144 } else { 145 // we have to fill in the join/leave times, because that data doesn't exist in the channel members table 146 for _, channelMemberHistory := range histories { 147 channelMemberHistory.JoinTime = startTime 148 channelMemberHistory.LeaveTime = model.NewInt64(endTime) 149 } 150 return histories, nil 151 } 152 } 153 154 func (s SqlChannelMemberHistoryStore) PermanentDeleteBatch(endTime int64, limit int64) store.StoreChannel { 155 return store.Do(func(result *store.StoreResult) { 156 var query string 157 if s.DriverName() == model.DATABASE_DRIVER_POSTGRES { 158 query = 159 `DELETE FROM ChannelMemberHistory 160 WHERE ctid IN ( 161 SELECT ctid FROM ChannelMemberHistory 162 WHERE LeaveTime IS NOT NULL 163 AND LeaveTime <= :EndTime 164 LIMIT :Limit 165 );` 166 } else { 167 query = 168 `DELETE FROM ChannelMemberHistory 169 WHERE LeaveTime IS NOT NULL 170 AND LeaveTime <= :EndTime 171 LIMIT :Limit` 172 } 173 174 params := map[string]interface{}{"EndTime": endTime, "Limit": limit} 175 if sqlResult, err := s.GetMaster().Exec(query, params); err != nil { 176 result.Err = model.NewAppError("SqlChannelMemberHistoryStore.PermanentDeleteBatchForChannel", "store.sql_channel_member_history.permanent_delete_batch.app_error", params, err.Error(), http.StatusInternalServerError) 177 } else { 178 if rowsAffected, err1 := sqlResult.RowsAffected(); err1 != nil { 179 result.Err = model.NewAppError("SqlChannelMemberHistoryStore.PermanentDeleteBatchForChannel", "store.sql_channel_member_history.permanent_delete_batch.app_error", params, err.Error(), http.StatusInternalServerError) 180 } else { 181 result.Data = rowsAffected 182 } 183 } 184 }) 185 }