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