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  }