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