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  }